| Article Index |
|---|
| Char Drivers |
| Page 2 |
| Page 3 |
| Page 4 |
| Page 5 |
| Page 6 |
| Page 7 |
| All Pages |
Char Drivers
The goal of this chapter is to write a complete char device driver. We develop a character driver because this class is suitable for most simple hardware devices. Char drivers are also easier to understand than block drivers or network drivers (which we get to in later chapters). Our ultimate aim is to write a modularized char driver, but we won’t talk about modularization issues in this chapter.
Throughout the chapter, we present code fragments extracted from a real device driver: scull (Simple Character Utility for Loading Localities). scull is a char driver that acts on a memory area as though it were a device. In this chapter, because of that peculiarity of scull, we use the word device interchangeably with “the memory area used by scull.”
memory, allocated from the kernel. Anyone can compile and run scull, and scull is
portable across the computer architectures on which Linux runs. On the other hand,
the device doesn’t do anything “useful” other than demonstrate the interface
between the kernel and char drivers and allow the user to run some tests.
The Design of scull
The first step of driver writing is defining the capabilities (the mechanism) the driver
will offer to user programs. Since our “device” is part of the computer’s memory,
we’re free to do what we want with it. It can be a sequential or random-access
device, one device or many, and so on.
To make scull useful as a template for writing real drivers for real devices, we’ll show
you how to implement several device abstractions on top of the computer memory,
each with a different personality.
The scull source implements the following devices. Each kind of device implemented
by the module is referred to as a type.
scull0 to scull3
Four devices, each consisting of a memory area that is both global and persis-tent. Global means that if the device is opened multiple times, the data con-
tained within the device is shared by all the file descriptors that opened it.
Persistent means that if the device is closed and reopened, data isn’t lost. This
device can be fun to work with, because it can be accessed and tested using con-
ventional commands, such as cp, cat, and shell I/O redirection.
scullpipe0 to scullpipe3
Four FIFO (first-in-first-out) devices, which act like pipes. One process readswhat another process writes. If multiple processes read the same device, they
contend for data. The internals of scullpipe will show how blocking and non-
blocking read and write can be implemented without having to resort to inter-
rupts. Although real drivers synchronize with their devices using hardware
interrupts, the topic of blocking and nonblocking operations is an important one
and is separate from interrupt handling (covered in Chapter 10).
scullsingle
scullpriv
sculluid
scullwuid
These devices are similar to scull0 but with some limitations on when an open is
permitted. The first (scullsingle) allows only one process at a time to use the
driver, whereas scullpriv is private to each virtual console (or X terminal ses-
sion), because processes on each console/terminal get different memory areas.
sculluid and scullwuid can be opened multiple times, but only by one user at a
time; the former returns an error of “Device Busy” if another user is locking the
device, whereas the latter implements blocking open. These variations of scull
would appear to be confusing policy and mechanism, but they are worth look-
ing at, because some real-life devices require this sort of management.
Each of the scull devices demonstrates different features of a driver and presents dif-
ferent difficulties. This chapter covers the internals of scull0 to scull3; the more
advanced devices are covered in Chapter 6. scullpipe is described in the section “A
Blocking I/O Example,” and the others are described in “Access Control on a Device
File.”
Major and Minor Numbers
Char devices are accessed through names in the filesystem. Those names are called
special files or device files or simply nodes of the filesystem tree; they are convention-
ally located in the /dev directory. Special files for char drivers are identified by a “c”
in the first column of the output of ls –l. Block devices appear in /dev as well, but
they are identified by a “b.” The focus of this chapter is on char devices, but much of
the following information applies to block devices as well.
If you issue the ls –l command, you’ll see two numbers (separated by a comma) in
the device file entries before the date of the last modification, where the file length
normally appears. These numbers are the major and minor device number for the
particular device. The following listing shows a few devices as they appear on a typi-
cal system. Their major numbers are 1, 4, 7, and 10, while the minors are 1, 3, 5, 64,
65, and 129.
crw-rw-rw- 1 root root 1, 3 Apr 11 2002 null
crw------- 1 root root 10, 1 Apr 11 2002 psaux
crw------- 1 root root 4, 1 Oct 28 03:04 tty1
crw-rw-rw- 1 root tty 4, 64 Apr 11 2002 ttys0
crw-rw---- 1 root uucp 4, 65 Apr 11 2002 ttyS1
crw--w---- 1 vcsa tty 7, 1 Apr 11 2002 vcs1
crw--w---- 1 vcsa tty 7, 129 Apr 11 2002 vcsa1
crw-rw-rw- 1 root root 1, 5 Apr 11 2002 zero
Traditionally, the major number identifies the driver associated with the device. For
example, /dev/null and /dev/zero are both managed by driver 1, whereas virtual con-
soles and serial terminals are managed by driver 4; similarly, both vcs1 and vcsa1
devices are managed by driver 7. Modern Linux kernels allow multiple drivers to
share major numbers, but most devices that you will see are still organized on the
one-major-one-driver principle.
The minor number is used by the kernel to determine exactly which device is being
referred to. Depending on how your driver is written (as we will see below), you can
either get a direct pointer to your device from the kernel, or you can use the minor
number yourself as an index into a local array of devices. Either way, the kernel itself
knows almost nothing about minor numbers beyond the fact that they refer to
devices implemented by your driver.
The Internal Representation of Device Numbers
Within the kernel, the dev_t type (defined in <linux/types.h>) is used to hold device
numbers—both the major and minor parts. As of Version 2.6.0 of the kernel, dev_t is
a 32-bit quantity with 12 bits set aside for the major number and 20 for the minor
number. Your code should, of course, never make any assumptions about the inter-
nal organization of device numbers; it should, instead, make use of a set of macros
found in <linux/kdev_t.h>. To obtain the major or minor parts of a dev_t, use:
MAJOR(dev_t dev);
MINOR(dev_t dev);
If, instead, you have the major and minor numbers and need to turn them into a dev_t,
use:
MKDEV(int major, int minor);
Note that the 2.6 kernel can accommodate a vast number of devices, while previous
kernel versions were limited to 255 major and 255 minor numbers. One assumes
that the wider range will be sufficient for quite some time, but the computing field is
littered with erroneous assumptions of that nature. So you should expect that the
format of dev_t could change again in the future; if you write your drivers carefully,
however, these changes will not be a problem.




