I/O software is normally organized in four layers, as illustrated in Figure 1. Each layer has a well-defined function to perform and a well-defined interface to the adjacent layers. The functionality and interfaces differ from system to system, so the discussion that follows, which examines all the layers starting at the bottom, is not specific to one machine.

Layers of the IO software system

Interrupt Handlers

As programmed I/O is occasionally useful, for most I/O, interrupts are an unpleasant fact of life and cannot be avoided. They should be hidden away, deep in the bowels of the operating system, so that as little of the operating system as possible knows about them. The best way to hide them is to have the driver starting an I/O operation block until the I/O has completed and the interrupt occurs. The driver can block itself by doing a down on a semaphore, a wait on a condition variable, a receive on a message, or something similar, for instance.

When the interrupt happens, the interrupt procedure does whatever it has to in order to handle the interrupt. Then it can unblock the driver that started it. In some cases it will just complete up on a semaphore. In others it will do a signal on a condition variable in a monitor. In still others, it will send a message to the blocked driver. In all cases the net effect of the interrupt will be that a driver that was previously blocked will now be able to run. This model works best if drivers are structured as kernel processes, with their own states, stacks, and program counters.

Certainly, reality is not quite so simple. Processing an interrupt is not just a matter of taking the interrupt, doing an up on some semaphore, and then executing an IRET instruction to return from the interrupt to the previous process. There is a great deal more work involved for the operating system. We will now give an outline of this work as a series of steps that must be performed in software after the hardware interrupt has completed. It should be noted that the details are very system dependent, so some of the steps listed below may not be required on a specific machine and steps not listed may be needed. Also, the steps that do occur may be in a different order on some machines.

1 . Save any registers (including the PSW) that have not already been saved by the interrupt hardware.

2. Set up a context for the interrupt service procedure. Doing this may involve setting up the TLB, MMU and a page table.

3. Set up a stack for the interrupt service procedure.

4. Acknowledge the interrupt controller. If there is no centralized interrupt controller, reenable interrupts.

5 . Copy the registers from where they were saved (possibly some stack) to the process table.

6. Run the interrupt service procedure. It will extract information from the interrupting device controller's registers.

7 . Choose which process to run next. If the interrupt has caused some high-priority process that was blocked to become ready, it may be chosen to run now.

8. Set up the MMU context for the process to run next. Some TLB setup may also be required.

9. Load the new process' registers, including its PSW.

10. Start running the new process.

As can be seen, interrupt processing is far from trivial. It also takes a substantial number of CPU instructions, especially on  machines in which virtual memory is present and page tables have to be set up or the state of the MMU stored (e.g., the R and M  bits). On some machines the TLB and CPU cache may also have to be managed when switching between user and kernel modes, which takes additional machine cycles.


operating system, interrupt handlers, interrupt controller, mmu context, interrupt processing