Handling a character device is relatively easy, since usually sophisticated buffering strategies are not needed and disk caches are not involved. Of course, character devices differ in their requirements: some of them must implement a sophisticated communication protocol to drive the hardware device, while others just have to read a few values from a couple of I/O ports of the hardware devices. For instance, the device driver of a multiport serial card device (a hardware device offering many serial ports) is much more complicated than the device driver of a bus mouse.
A small complication, however, comes from the fact that the same major number might be allocated to several different device drivers. For instance, the major number 10 is used by many different device drivers, such as a real-time clock and a PS/2 mouse.
To keep track of which character device drivers are currently in use,
the kernel uses a hash table indexed by the major and minor
numbers.[97] The hash table
array is stored in cdev_hashtable
variable; it
includes 64 lists of character device descriptors. Each descriptor is
a char_device
data structure, whose fields are
shown in Table 13-10.
Table 13-10. The fields of the character device descriptor
Type |
Field |
Description |
---|---|---|
|
|
Pointers for the hash table list |
|
|
Usage counter for the character device descriptor |
|
|
Major and minor numbers of the character device |
|
|
Not used |
|
|
Semaphore protecting the character device |
As for block device drivers, a hash table is required because the kernel cannot determine whether a character device driver is in use by simply checking whether a character device file has been already opened. In fact, the system directory tree might include several character device files having different pathnames but equal major and minor numbers, and they all refer to the very same device driver.
A character device descriptor is inserted into the hash table
whenever a device file referring to it is opened for the first time.
This job is performed by the init_special_inode( )
function, which is invoked by the low-level filesystem layer when it
determines that a disk inode represents a device file.
init_special_inode( )
looks up the character
device descriptor in the hash table; if the descriptor is not found,
the function allocates a new descriptor and inserts that into the
hash table. The function also stores the descriptor address into the
i_cdev
field of the inode object of the device
file.
We mentioned in Section 13.2.3 that the
dentry_open( )
function triggered by the
open( )
system call service routine customizes the
f_op
field in the file object of the character
device file so that it points to the def_chr_fops
table. This table is almost empty; it only defines the
chrdev_open( )
function as the
open
method of the device file. This method is
immediately invoked by dentry_open( )
.
The chrdev_open( )
function rewrites the
f_op
field of the file object with the address
stored in the chrdevs
table element that
corresponds to the major number of the character device file. Then
the function invokes the open
method again.
If the major number is assigned to a unique device driver, the method
initializes the device driver. Otherwise, if the major number is
shared among several device drivers, the method rewrites once more
the f_op
field of the file object with an address
found in the data structure indexed by the minor number of the device
file. For instance, the file_operations
data
structures for the device file that has the major number 10 are
stored in the simply linked list misc_list
.
Finally, the open
method is invoked for the last
time to initialize the device driver.
Once opened, the character device file usually can be accessed for
reading and/or for writing; to do this, the read
and write
methods of the file object points to
suitable functions of the device driver. Most device drivers also
support the ioctl( )
system call through the
ioctl
file object method; it allows special
commands to be sent to the underlying hardware device.
[97] A character device driver registered with the devfs device file might not have major and minor numbers. In this case, the kernel assumes that its major and minor numbers are equal to zero.