Usually devices are memory-mapped; the OS sees the controller and its registers but not the actual device.
Devices usually interrupt when input arrives, or an error occurs.
The OS must have an interrupt handler to perform the tasks when interrupts occur.
On most machines, interrupts are prioritised.
There are usually two types of devices:
Character-based: keyboards, screens, serial connections, printers, mice. One byte I/O at a time.
Block-based: disks, tapes, networking. One block at a time. Blocks depend on the device, and can range from 128 bytes to over 4,096 bytes.
Some devices, like clocks (which only interrupt), or ROM/RAM, don't fit the above category.
We want to organise I/O handling as a series of layers. The lower layers worry about hiding a device's peculiarities from the upper ones.
Upper layers present a simple, clean interface to the users.
A major goal is Device Independence: users should be able to perform I/O on devices regardless of the actual devices themselves.
Error Handling: errors should be handled as close to the hardware as possible:
If the controller finds an error, it should try to fix it. If not, the device driver may retry the operation. If lower layers cannot handle it, only then report to upper layers, and maybe the user.
Blocking/non-blocking: user processes expect to be blocked on I/O. On the other hand, the OS usually passes the request to the controller, and returns to other work, expecting to be interrupted. The OS must remember to unblock the processes when I/O completes.
Sharing: some devices are shareable (e.g disks); others are not (e.g printers). The OS must provide device sharing, and cope with deadlocks, discussed later.
It's convenient to break I/O software into four layers:
User-level software. Device-independent software. Device drivers. Interrupt handlers.
These get called when an interrupt occurs. They check a controller's registers, and pass on the reason for the interrupt (and any data) to the device driver.
They should be as fast as possible, as while running, the CPU is taken away from the task of running processes. Thus, large handlers degrade the processing performance of a computer.
Also, a large high-priority handler slows response to low-priority interrupts.
These hold the device-dependent software for one device, or sometimes for a class of devices, e.g all terminals.
A driver knows about the registers of the controller, and the characteristics of the device (e.g number of tracks/heads on each disk).
The interrupt handler notifies the driver of success/error, and the driver takes these and converts them to abstract success/error events for the device-independent layer.
Similarly, it takes abstract requests for I/O from the device-independent layer and converts them to requests that the device controller can perform.
For example, a read request on disk block X must be converted to the correct track/head/sector number. The driver loads this information into the controller, and asks it to read the block, and place it in a certain location using DMA. On some drives, the controller must be asked to seek to the track first.
driver: n. 1. The main loop of an event-processing program; the code that gets commands and dispatches them for execution. 2. [techspeak] In `device driver', code designed to handle a particular peripheral device such as a magnetic disk or tape unit. 3. In the TeX general, `driver' also means a program that translates some device-independent or other common format to something a real device can actually understand.
A large fraction of I/O software is device-independent.
The boundary between this later and the drivers is OS-dependent.
Typically, this layer does:
uniform interfacing for the device drivers device naming device protection providing a device-independent block size buffering storage allocation allocating and releasing dedicated devices reporting errors
The basic function is to perform I/O functions that are common to all devices, and to provide a uniform interface to the user-level software.
For example, a terminal-layer performs terminal operations, even when the devices used are keyboards/screens, remote serial terminal and network-linked terminals.
A uniform interface makes writing user software easier.
Similarly, a uniform naming method makes it easier to use devices.
Protection is important, and is best to do it here, so that all devices get protection in the same manner.
Different devices have different block sizes. This layer must provide a standard block size. To do so it may need to buffer, or to read/write multiple real blocks.
Buffering must be done as a user may only want to read/write half a block, or to read one character from a device where 20 are available to be read.
Allocating device blocks is done at a device-independent level. We will cover this in the filesystem lectures.
The OS must ensure that only one user is accessing particular devices, e.g a printer. Thus, new opens on opened devices must fail. User access may lead to starvation.
To avoid this, the OS may use spooling, and OS services to regulate device access, for example:
Output to printer is spooled in a file on a disk. When the printer becomes ready, the file is queued for printing. The queue is FIFO, and the OS opens/closes the printer.
Without spooling, one user could open the device indefinitely.
Error reporting must be done at the device-independent level, and done only if the lower levels cannot rectify the error.
buffer overflow: n. What happens when you try to stuff more data into a buffer (holding area) than it can handle. This may be due to a mismatch in the processing rates of the producing and consuming processes, or because the buffer is simply too small to hold all the data that must accumulate before a piece of it can be processed. The term is used of and by humans in a metaphorical sense. ``What time did I agree to meet you? My buffer must have overflowed.''
spool: [from early IBM `Simultaneous Peripheral Operation Off-Line', but this acronym is widely thought to have been contrived for effect] vt. To send files to some device or program (a `spooler') that queues them up and does something useful with them later. The spooler usually understood is the `print spooler' controlling output of jobs to a printer, but the term has been used in connection with other peripherals (especially plotters and graphics devices).
As mentioned above, clocks don't really `do' I/O. As they are hardware devices, they are included here.
Clocks usually do two things:
Send interrupts to the CPU at regular intervals ( clock ticks). These can be used to prompt process rescheduling. Send an interrupt after a requested time (alarm clock). A few provide time of day values in registers. The time of day is often battery-backed.
Most clocks have settable clock tick periods. The usual speeds are 50Hz, 60Hz or 100Hz.
Alarm clocks are achieved by writing a value into a clock register. This is decremented each clock tick, and an interrupt is sent when the value reaches zero.
Software receives the clock tick interrupt. The driver must:
Maintain the time of day clock when the clock hardware doesn't. Pre-empt processes when their timeslice has been exhausted. Handle any `alarm calls' that have been requested. Provide `alarm calls' for the OS itself. Perform time-based statistics gathering for the OS.
The time of day is hard because, at 60Hz, a 32-bit value overflows in 2 years.
It is most often achieved by keeping two counters, a tick counter, and a seconds counter. Under Unix, the seconds counter has its epoch at 1st January 1970.
Each process has a timeslice. When scheduled, this is copied into an OS variable, and decremented on each tick. At value zero, the process is pre-empted.
Some OS allow processes to set up `alarm calls'. When the alarm goes off, exceptional things happen to the process e.g a user signal under Unix.
The clock driver simulates alarm calls by keeping a linked list of calls and their differences.
The head node's difference is decremented until zero, at which time the alarm `goes off'. The node is removed, and the next node becomes the head.
The OS uses alarm calls to timeout on I/O operations, e.g a disk read, a network transmission.
Most OS gather time-based statistics to aid adaptive scheduling algorithms. The statistics are used by high level process schedulers, user information, system administration.
jiffy: n. 1. The duration of one tick of the system clock on the computer. Often one AC cycle time (1/60 second in the U.S. and Canada, 1/50 most other places), but more recently 1/100 sec has become common. ``The swapper runs every 6 jiffies'' means that the virtual memory management routine is executed once for every 6 ticks of the clock, or about ten times a second. 2. Indeterminate time from a few seconds to forever. ``I'll do it in a jiffy'' means certainly not now and possibly never. This is a bit contrary to the more widespread use of the word.