The CPU and memory are not the only resources that the operating system must deal with. I/O devices also interact heavily with the operating system. As we saw in "COMPUTER HARDWARE REVIEW" figure, I/O devices are normally made of two parts: a controller and the device itself. The controller is a chip or a set of chips that physically controls the device. It accepts commands from the operating system, for instance, to read data from the device, and carries them out.
In many cases, the actual control of the device is very complex and detailed, so it is the job of the controller to present a simpler interface to the operating system (but still very complicated). For instance, a disk controller might accept a command to read sector 11,206 from disk 2. The controller then has to convert this linear sector number to a cylinder, sector, and head. This conversion may be complex by the fact that outer cylinders have more sectors than inner ones and that some bad sectors have been remapped onto other ones. Then the controller has to decide which cylinder the disk arm is on and give it a sequence of pulses to move in or out the requisite number of cylinders. It has to wait until the proper sector has rotated under the head and then start reading and storing the bits as they come off the drive, removing the preamble and computing the checksum. Lastly, it has to assemble the incoming bits into words and store them in memory. To do all this work, controllers often contain small embedded computers that are programmed to do their work.
The other piece is the actual device itself. Devices have quite simple interfaces, both because they cannot do much and to make them standard. The latter is required so that any IDE disk controller can handle any IDE disk, for instance. IDE stands for Integrated Drive Electronics and is the standard type of disk on many computers. Since the actual device interface is hidden behind the controller, all that the operating system sees is the interface to the controller, which may be fairly different from the interface to the device.
Because each type of controller is different, different software is required to control each one. The software that talks to a controller, giving it commands and accepting responses, is called a device driver. Each controller manufacturer has to supply a driver for each operating system it supports. Thus a scanner may come with drivers for Windows 2000, Windows XP, Vista, and Linux, for example.
To be used, the driver has to be put into the operating system so it can run in kernel mode. Drivers can in fact run outside the kernel, but only a few current systems support this possibility because it requires the ability to allow a user-space driver to be able to access the device in a controlled way, a feature rarely supported. There are three ways the driver can be put into the kernel. The first way is to relink the kernel with the new driver and then reboot the system. Many older UNIX systems work like this. The second way is to make an entry in an operating system file telling it that it needs the driver and then reboot the system. At boot time, the operating system goes and finds the drivers it requires and loads them. Windows works this way. The third way is for the operating system to be able to accept new drivers while running and install them on the fly without the need to reboot. This way used to be rare but is becoming much more common now. Hot pluggable devices, such as USB and IEEE 1394 devices (discussed below) always require dynamically loaded drivers.
Every controller has a small number of registers that are used to communicate with it. For instance, a minimal disk controller might have registers for specifying the disk address, memory address, sector count, and direction (read or write). To activate the controller, the driver gets a command from the operating system, then translates it into the proper values to write into the device registers. The collection of all the device registers forms the I/O port space, a subject we will come back to in "INPUT/OUTPUT".
On a number of computers, the device registers are mapped into the operating system's address space (the addresses it can use), so they can be read and written like ordinary memory words. On such computers, no special I/O instructions are needed and user programs can be kept away from the hardware by not putting these memory addresses within their reach (e.g., by using base and limit registers). On other computers, the device registers are put in a special I/0 port space, with each register having a port address. On these machines, special IN and OUT instructions are available in kernel mode to allow drivers to read and write the registers. The former scheme eliminates the need for special I/O instructions but uses up some of the address space. The latter uses no address space but needs special instructions. Both systems are broadly used.
Input and output can be done in three different ways. In the simplest method, a user program issues a system call, which the kernel then translates into a procedure call to the proper driver. The driver then starts the I/O and sits in a tight loop continuously polling the device to see if it is done (generally there is some bit that indicates that the device is still busy). When the I/0 has completed, the driver puts the data (if any) where they are required and returns. The operating system then returns control to the caller. This method is called busy waiting and has the disadvantage of tying up the CPU polling the device until it is ended.
The second method is for the driver to start the device and ask it to give an interrupt when it is ended. At that point the driver returns. The operating system then blocks the caller if need be and looks for other work to do. When the controller detects the end of the transfer, it creates an interrupt to signal completion.
Interrupts are very important in operating systems, so let us look at the idea more closely. In the following figure (a) we see a three-step process for I/O. In step 1, the driver tells the controller what to do by writing into its device registers. The controller then starts the device. When the controller has finished reading or writing the number of bytes it has been told to transfer, it signals the interrupt controller chip using certain bus lines in step 2. If the interrupt controller is ready to accept the interrupt (which it may not be if it is busy with a higher-priority one), it asserts a pin on the CPU chip informing it, in step 3. In step 4, the interrupt controller puts the number of the device on the bus so the CPU can read it and know which device has just ended (many devices may be running at the same time).
Once the CPU has determined to take the interrupt, the program counter and PSW are typically then pushed onto the current stack and the CPU switched into kernel mode. The device number may be used as an index into part of memory to find the address of the interrupt handler for this device. This part of memory is called the interrupt vector. Once the interrupt handler (part of the driver for the interrupting device) has started, it removes the stacked program counter and PSW and saves them, then queries the device to learn its status. When the handler is all ended, it returns to the previously running user program to the first instruction that was not yet carried out. These steps are shown in the following figure (b).
The third method for doing I/0 makes use of special hardware: a DMA (Direct Memory Access) chip that can control the flow of bits between memory and some controller without constant CPU interference.
The CPU sets up the DMA chip, telling it how many bytes to transfer, the device and memory addresses involved, and the direction, and lets it go. When the DMA chip is done, it causes an interrupt, which is handled as explained above. DMA and I/0 hardware in general will be discussed in more detail in "INPUT/OUTPUT".
Interrupts can frequently happen at highly difficult moments, for instance, while another interrupt handler is running. For this reason, the CPU has a way to disable interrupts and then reenable them later. While interrupts are disabled, any devices that finish continue to assert their interrupt signals, but the CPU is not interrupted until interrupts are enabled again. If multiple devices end while interrupts are disabled, the interrupt controller decides which one to let through first, generally based on static priorities allocated to each device. The highest-priority device wins.