The basic function of the device-independent software is to perform the I/O
functions that are common to all devices and to provide a uniform interface to the
user-level software. We will now look at the above issues in more detail.
Uniform Interfacing for Device Drivers
A major issue in an operating system is how to make all I/O devices and driv-
ers look more or less the same.
If disks, printers, keyboards, and so on, are all in-
terfaced in different ways, every time a new device comes along, the operating sys-
tem must be modified for the new device. Having to hack on the operating system
for each new device is not a good idea.
One aspect of this issue is the interface between the device drivers and the rest
of the operating system.
In Fig. 5-14(a) we illustrate a situation in which each de-
vice driver has a different interface to the operating system. What this means is that
the driver functions available for the system to call differ from driver to driver. It
might also mean that the kernel functions that the driver needs also differ from
driver to driver. Taken together, it means that interfacing each new driver requires a
lot of new programming effort.
SATA disk driver
USB disk driver SCSI disk driver
SATA disk driver
USB disk driver SCSI disk driver
(a) Without a standard driver interface. (b) With a standard driver
In contrast, in Fig. 5-14(b), we show a different design in which all drivers
have the same interface. Now it becomes much easier to plug in a new driver, pro-
viding it conforms to the driver interface. It also means that driver writers know
what is expected of them.
In practice, not all devices are absolutely identical, but
usually there are only a small number of device types and even these are generally
almost the same.
The way this works is as follows. For each class of devices, such as disks or
printers, the operating system defines a set of functions that the driver must supply.
For a disk these would naturally include read and write, but also turning the power
I/O SOFTWARE LAYERS
on and off, formatting, and other disky things. Often the driver holds a table with
pointers into itself for these functions.
When the driver is loaded, the operating
system records the address of this table of function pointers, so when it needs to
call one of the functions, it can make an indirect call via this table. This table of
function pointers defines the interface between the driver and the rest of the operat-
ing system. All devices of a given class (disks, printers, etc.) must obey it.
Another aspect of having a uniform interface is how I/O devices are named.
The device-independent software takes care of mapping symbolic device names
onto the proper driver. For example, in UNIX a device name, such as
uniquely specifies the i-node for a special file, and this i-node contains the
, which is used to locate the appropriate driver. The i-node also
minor device number
, which is passed as a parameter to the driver in
order to specify the unit to be read or written. All devices have major and minor
numbers, and all drivers are accessed by using the major device number to select
Closely related to naming is protection. How does the system prevent users
from accessing devices that they are not entitled to access?
In both UNIX and
Windows, devices appear in the file system as named objects, which means that the
usual protection rules for files also apply to I/O devices. The system administrator
can then set the proper permissions for each device.
Buffering is also an issue, both for block and character devices, for a variety of
reasons. To see one of them, consider a process that wants to read data from an
(ADSL—Asymmetric Digital Subscriber Line) modem, something many people
use at home to connect to the Internet.
One possible strategy for dealing with the
incoming characters is to have the user process do a
system call and block
waiting for one character. Each arriving character causes an interrupt.
rupt-service procedure hands the character to the user process and unblocks it.
After putting the character somewhere, the process reads another character and
blocks again. This model is indicated in Fig. 5-15(a).
The trouble with this way of doing business is that the user process has to be
started up for every incoming character. Allowing a process to run many times for
short runs is inefficient, so this design is not a good one.
An improvement is shown in Fig. 5-15(b). Here the user process provides an
-character buffer in user space and does a read of
characters. The interrupt-ser-
vice procedure puts incoming characters in this buffer until it is completely full.
Only then does it wakes up the user process.
This scheme is far more efficient than
the previous one, but it has a drawback: what happens if the buffer is paged out
when a character arrives? The buffer could be locked in memory, but if many
processes start locking pages in memory willy nilly, the pool of available pages
will shrink and performance will degrade.