Introduction One of the purposes of an operating system is to hide the peculiarities of the system's hardware devices from its users. For example the Virtual File System presents a uniform view of the mounted filesystems irrespective of the underlying physical devices. This chapter describes how the Linux kernel manages the physical devices in the system.
Device Drivers The CPU is not the only intelligent device in the system, every physical device has its own hardware controller. The keyboard, mouse and serial ports are controlled by a SuperIO chip. the IDE disks by an IDE controller and so on. Each hardware controller has its own control and status registers (CSRs) and these differ between devices. The CSRs are used to start and stop the device, to initialize it and to diagnose any problems with it. Instead of putting code to manage the hardware controllers in the system into every application, the code is kept in the Linux kernel. The software that handles or manages a hardware controller is known as a device driver. The Linux kernel device drivers are, essentially, a shared library of privileged, memory resident, low level hardware handling routines. It is Linux's device drivers that handle the peculiarities of the devices they are managing.
Architectures Microkernel Examples: Minix Device drivers are isolated from the kernel, and are usually in their own user-mode address spaces. They communicate with the main kernel and with each other by means of messaging which involves a process switch and context switch. Monolithic Kernel Examples: Linux, Windows NT Device drivers run as part of the kernel, either compiled in or as run-time loadable modules. Monolithic kernels have the advantage of speed and efficiency: calls to driver functions are simple local calls instead of whole address space switches. However, because it is running in kernel mode, a buggy device driver has the capability to crash the entire system.
Device Management – goals Goals Isolate devices drivers from the kernel so that driver writers can worry about interfacing to the hardware and not about interfacing to the kernel Isolate user applications from the hardware so that applications can work on the majority of devices the user might connect to their system All hardware devices look like regular files; they can be opened, closed, read and written using the same, standard, system calls that are used to manipulate files.
Device Management - Requirements Asynchronous I/O: that is, applications will be able to start an I/O operation and continue to run until it terminates. This is instead of blocking I/O, whereby applications are stalled while I/O operations execute. Plug and Play: drivers will be able to be loaded and unloaded (dynamically) as devices are added to and removed from the system. Devices will be detected automatically on system startup, if possible.
Interfaces The details of all the devices in the system are kept in a /dev directory so that they act and look like normal files residing on the filesystem. E.g. /dev/hda – filesystem name for the first IDE disk. /dev/rtc – Real Time Clock /dev/zero – virtual device providing an endless stream of zeros. Each device file is referred to by a major device number and a minor device number. E.g. ls -l /dev/hda – major no : 3 ; minor no : 0 brw-rw-rw- 1 root root 3, 0 Oct 2 08:08 /dev/had Use /proc/devices – to determine what driver controls the device.
Types of hardware device. Linux supports three types of hardware device: Character, A character device is accessed as a linear queue of bytes. The device driver places bytes on the queue, one by one, and user space reads the bytes in that order. E.g. keyboard. Block A block device is accessed as an array of bytes in multiples of the block size. Block devices are accessed via the buffer cache and may be randomly accessed, that is to say, any block can be read or written no matter where it is on the device. E.g. Hard disks, floppy-drives, CD-ROMs Network. Network devices are accessed via the BSD socket interface and the networking subsytems.
Polling and Interrupts Each time the device is given a command, for example ``move the read head to sector 42 of the floppy disk'' the device driver has a choice as to how it finds out that the command has completed. The device drivers can either poll the device or they can use interrupts.
Polling Approach Use polling to detect completion of I/O operations. User program performs an I/O request using system call. Device driver starts the device Periodically polls device (using system timers instead of polling the status register) to inspect its status for detecting completion of I/O operation.
Interrupt-Driven Device Driver An interrupt driven device driver is one where the hardware device being controlled will raise a hardware interrupt whenever it needs to be serviced. For example, an ethernet device driver would interrupt whenever it receives an ethernet packet from the network. The Linux kernel needs to be able to deliver the interrupt from the hardware device to the correct device driver. This is achieved by the device driver registering its usage of the interrupt with the kernel at driver initialization time. It registers the address of an interrupt handling routine and the interrupt number that it wishes to own. Details of which are in /proc/interrupts: CPU0 CPU1 0: 34584323 34936135 IO-APIC-edge timer 1: 224407 226473 IO-APIC-edge keyboard 2: 0 0 XT-PIC cascade 5: 5636751 5636666 IO-APIC-level eth0 9: 0 0 IO-APIC-level acpi 10: 565910 565269 IO-APIC-level aic7xxx 12: 889091 884276 IO-APIC-edge PS/2 Mouse LOC: 69513717 69513716 ERR: 0
Interrupt Approach Involves Device driver Interrupt handler or Interrupt service routine(ISR) Device bottom half Steps Process issues I/O request Device driver checks device status, If available, starts I/O operation. Process blocks but state is TASK_INTERRUPTIBLE. Device does I/O until finished and issues IRQ to CPU causing corresponding ISR to execute. How an interrupt is delivered to the CPU itself is architecture dependent but on most architectures the interrupt is delivered in a special mode that stops other interrupts from happening in the system. Once ISR completes, ret_from_sys_call code is executed. Process eventually runs after I/O completion
Interrupt Handling in Linux Linux handles interrupts in much the same way as it handles signals in user space. ) To handle an interrupt, a driver need only register a handler(ISR) for its devices interrupt. To register an interrupt service routine int request_irq( unsigned int irq, // interrupt number being requested. void (*handler)(), // pointer to the handling function being installed unsigned long flags, // control bits e.g. to indicate a fast handler, permit sharing etc. const char *dev_name, // owner of the interrupt as in /proc/interrupts void *dev_id // used for shared input lines ); void free_irq( unsigned int irq, void *dev_id );
Why interrupt method is better? Using polling: When a process issues I/O, the device driver will keep polling, kind of wait until device status says that I/O has been completed, the same process can then continue. No other process can be scheduled. Using interrupt When a process issues I/O, device driver runs, the process blocks and other processes can be picked by scheduler to run.
When is Polling is better to use? When the I/O operation is critical to the complete operation of the system i.e: It is worthwile to have CPU poll the device for completion When the I/O operation is fast, i.e: It is not worthwile to have the interrupt management overheads.
Bottom-Half Processing One of the main problems with interrupt handling is how to perform longish tasks within a handler. Often a substantial amount of work must be done in response to a device interrupt, but interrupt handlers need to finish up quickly and not keep interrupts blocked for long. These two needs (work and speed) conflict with each other, leaving the driver writer in a bit of a bind. Linux resolves this problem by splitting the interrupt handler(ISR) into two halves. The top half is the routine that actually responds to the interrupt. The bottom half is a routine that is scheduled by the top half to be executed later, at a safer time.
But what is a bottom half useful for? The big difference between the top-half handler and the bottom half is that all interrupts are enabled during execution of the bottom half -- that's why it runs at a safer time. In the typical scenario, the top half saves device data to a device-specific buffer, schedules its bottom half, and exits: this is very fast. The bottom half then performs whatever other work is required, such as awakening processes, starting up another I/O operation, and so on. This setup permits the top half to service a new interrupt while the bottom half is still working.
Examples When a network interface reports the arrival of a new packet, the handler just retrieves the data and pushes it up to the protocol layer; actual processing of the packet is performed in a bottom half. When a timer interrupt occurs, the handler just increments the jiffies counter; the updation of the other system timers is performed in a bottom half.
How to Divide Work? If the work is time-sensitive, perform it in the interrupt handler. If the work is related to the hardware itself, perform it in the interrupt handler. If the work needs to ensure that another interrupt (particularly the same interrupt) does not interrupt it, perform it in the interrupt handler. For everything else, consider performing the work in the bottom half. The quicker the interrupt handler executes, the better.
Kernel Contexts Code in the Linux kernel runs in one of three con- texts: Process Process context executes directly on behalf of a user process. All syscalls run in process context, for example. Interrupt. Interrupt handlers run in interrupt context. Bottom-half Softirqs, tasklets and timers all run in bottom-half context
Difference between Process Context and Interrupt Context Kernel code running in process context is preemptible An interrupt context is not preemptible; always runs to completion. Therefore code running in interrupt context cannot do the following: Go to sleep or relinquish the processor Acquire a mutex Perform time-consuming tasks Access user-space virtual memory.
Direct Memory Access (DMA) Using interrupt driven device drivers to transfer data to or from hardware devices works well when the amount of data is reasonably low. For high speed devices, such as hard disk controllers or ethernet devices the data transfer rate is a lot higher, up to 40 Mbytes of information per second.
DMA Direct Memory Access, or DMA, was invented to solve this problem. A DMA controller allows devices to transfer data to or from the system's memory without the intervention of the processor. A PC's DMA controller has 8 DMA channels of which 7 are available for use by the device drivers. Each DMA channel has associated with it a 16 bit address register and a 16 bit count register. To initiate a data transfer the device driver sets up the DMA channel's address and count registers together with the direction of the data transfer, read or write. It then tells the device that it may start the DMA when it wishes. When the transfer is complete the device interrupts the PC. Whilst the transfer is taking place the CPU is free to do other things.