Exception
An exception is when the processor executes code that is not on its normal path. This is an 'exception' to normal operation, which is essentially linear movement through code and control structures. Different languages have support for various types of exceptions, typically used to handle errors during program operation.
Interrupt
An interrupt is an exception at the hardware level (generally). The interrupt is a physical signal in the processor that tells the CPU to store its current state and jump to interrupt (or exception) handler code. Once the handler is done the original state is restored and processing can continue.
An interrupt is always an exception, even when it's intended. Interrupts might indicate:
- errors, such as a memory access violation
- that the OS needs to perform an operation to support a running program, such as a software interrupt, or a memory paging request
- a hardware device requires attention, such as a received network packet, or an empty transmit buffer
These always force the processor to pause its current activity to deal with the raised exception, only resuming once the interrupt handler is complete.
Pitfalls
In terms of interrupts, common pitfalls are race conditions. For instance you might have an interrupt that periodically increments a global realtime clock. The clock might be 64 bits on a 32 bit machine.
If a program is reading the clock, and gets the first 32 bit word, then the interrupt occurs, once the interrupt handler exits the process gets the second 32 bit word, and the data will be incoherent - the two words may be out of sync. If you attempt to use a mutex or semaphore to lock the variable in the process, then the interrupt will hang waiting for the lock and halt the system (deadlock), unless both the handler and the processes that use the data are written very carefully. It's easy to get in trouble when writing for interrupts.
Re-entrant functions are also another problem. If you are executing funcA in program code, take an interrupt which also executes funcA you may end up with unintended consequences due to shared variables (static, or heap variables, classes, etc). You typically want to execute as little code as possible in the interrupt handler, and frequently have it set a flag so the process can do the real work later, without worrying about conflicts.
In some ways this is similar to developing for a multiprocessor, and is one of the reasons why kernel programming is still considered black magic by many.