Char devices are accessed through names (or ``nodes'') in the
filesystem, usually located in the /dev
directory. Device
files are special files and are identified by a ``c'' in the first
column of the output of ls -l, indicating that
they are char nodes. Block devices appear in /dev
as well, but
they are identified by a ``b''; even if some of the following information
applies also to block devices, I am now focusing on char drivers.
If you issue the ls command, you’ll see two numbers (separated by a comma) on the device file entries before the date of last modification, where the file length normally appears. These numbers are the “major” and “minor” numbers for the particular device. The following listing shows a few devices as they appear on my system. Their major numbers are 10, 1, and 4, while the minors are 0, 3, 5, 64-65, and 128-129.
crw-rw-rw- 1 root root 10, 3 Nov 30 1993 bmouseatixl crw-rw-rw- 1 root sys 1, 3 Nov 30 1993 null crw-rw-rw- 1 root root 4, 128 Apr 30 13:02 ptyp0 crw-rw-rw- 1 root root 4, 129 Apr 30 13:02 ptyp1 crw-rw-rw- 1 rubini staff 4, 0 Jan 30 1995 tty0 crw-rw-rw- 1 root tty 4, 64 Jan 25 1995 ttyS0 crw-rw-rw- 1 root root 4, 65 May 1 00:04 ttyS1 crw-rw-rw- 1 root sys 1, 5 Nov 30 1993 zero
The major number identifies the driver associated
with the device. For example, /dev/null
and
/dev/zero
are both managed by driver 1, while all the
tty’s and pty’s are managed by driver 4. The kernel uses the major
number to associate the appropriate driver with its device.
The minor number is only used by the device driver; other parts of the kernel don’t use it, and merely pass it it along to the driver. It isn’t unusual for a driver to control several devices (as in the example above)--the minor number provides a way to differentiate among them.
Adding a new driver to the system means assigning a major number to
it. The assignment should be made at driver (module)
initialization by calling the following function, defined in
<linux/fs.h>
:
int register_chrdev(unsigned int major, const char *name, struct file_operations *fops);
The return value is the error code. A negative return code indicates an
error; a zero or positive return code means successful completion. The
major
argument is the
major number being requested, name
is the name of your device,
which will appear in /proc/devices
, and fops
is the
pointer to a jump table used to invoke the driver functions, as
explained in Section 3.3, later in this chapter.
The major number is a small integer that serves as the index into a static array of char drivers. In 1.2.13 and early 2.x kernels, the array holds 64 elements, while 2.0.26 and 2.1.11 raised the number to 128. Minor numbers aren’t passed to register_chrdev, because only the driver cares about them.
Once the driver has been registered in the kernel table,
whenever an operation is performed on a character file whose major
number matches your driver’s major number, the kernel invokes the
correct function in the driver’s code by indexing into the fops
jump table.
The next question is how to give programs a name by which
they can request your driver. A name must be inserted into the /dev
directory and associated with your driver’s major and minor numbers.
The command to create a device node on a filesystem is mknod, and you must be the superuser to create devices. The command takes three arguments in addition to the name of the node being created. For example, the command:
mknod /dev/scull0 c 127 0
creates a char device (c
) whose major number is 127 and
whose minor number is 0. Minor numbers should be in the range 0-255,
because, for historical reasons,
they are sometimes stored in a single byte. There are sound reasons to
extend the range of available minor numbers, but for the time being, the
8-bit limit is still in force.
Some major device numbers are statically assigned to the most
common devices. A list of those devices can be found in
Documentation/devices.txt
within the kernel source tree. Because many numbers are
already assigned, choosing a unique number for a new driver can be
difficult--there are more custom drivers than available
major numbers.
Fortunately (or rather, thanks to someone’s
ingenuity), you can request dynamic assignment of a major number. If
the argument major
is set to zero when you call
register_chrdev, the function selects a free number and
returns it. The major number is always positive and thus can’t be
mistaken for an error code.
I strongly suggest that you use dynamic allocation to obtain your major device number, rather than choosing a number randomly from the ones that are currently free.
The disadvantage of dynamic assignment is that you can’t create the
device nodes in advance, because the major number assigned to your
module can’t be guaranteed to always be the same. This is hardly a problem,
however, because once the number has been assigned, you can read it
from /proc/devices
. To load a driver, the invocation of
insmod can be replaced by a simple script
that reads /proc/devices
to get
the newly assigned major number in order to create the node(s).
A typical /proc/devices
looks like the following:
Character devices: 1 mem 2 pty 4 ttyp 7 vcs 10 misc 63 scull Block devices: 3 ide0 22 ide1
The script to load a module that has been assigned a dynamic
number can thus be written using a tool such as awk to retrieve
information from
/proc/devices
in order to create the files in /dev
.
The following script, scull_load, is part of
the scull distribution. The user of a driver that is
distributed in the form of a module can invoke such a script from
/etc/rc.d/rc.local
or call it manually whenever the module is
needed. There is also a third option: using kerneld. This and
other advanced features of modularization are covered
in Chapter 11.
#!/bin/sh module="scull" device="scull" group="wheel" mode="664" # invoke insmod with all arguments we got /sbin/insmod -f $module $* || exit 1 # remove stale nodes rm -f /dev/${device}[0-3] major=`cat /proc/devices | awk "\$2=="$module" {print \$1}"` mknod /dev/${device}0 c $major 0 mknod /dev/${device}1 c $major 1 mknod /dev/${device}2 c $major 2 mknod /dev/${device}3 c $major 3 # give appropriate group/permissions chgrp $group /dev/${device}[0-3] chmod $mode /dev/${device}[0-3]
The script can be adapted for another driver by redefining the variables and adjusting the mknod lines. The script shown above creates four devices because four is the default in the scull sources.
The last two lines of the script may seem obscure: why
change the group and mode of a device? The reason is that the node is
created by root and is thus owned by root. The permission bits default
so that only root has write access, while anyone can get read access.
Normally, a device node requires a different policy,
and some change is needed. Access to a device is
usually granted to a group of users, but the details depend on the
device and on the system administrator. Security is a huge problem,
beyond the scope of this book. The chmod
and chgrp
lines
in scull_load
are there only as a hint about handling permissions.
Later, in the section Section 5.6 in Chapter 5, the code for
sculluid
will demonstrate how the
driver can enforce its own kind of authorization for device
access.
If repeatedly creating and destroying /dev
nodes
sounds like overkill, there is a useful workaround. If you look
at the kernel source, in fs/devices.c
, you can see
that dynamic numbers are
assigned starting from 127 (or 63)
and moving down, so you can create long-living
nodes with 127 as the major number and avoid calling the script every time
the associated device is loaded. This trick won’t work if you use
several dynamic drivers, or if a new kernel release changes the
behavior of dynamic allocation. (Code that you’ve written based on
having peeked at the internals of the kernel isn’t guaranteed to
keep working if the kernel changes.) Nonetheless, you might find
this technique useful during
development, when the module is constantly being loaded and unloaded.
The best way to assign major numbers, in my opinion, is by defaulting
to dynamic allocation while leaving yourself the option of specifying
the major number at load time, or even at compile time. The
code I suggest using is similar to the code introduced for
autodetection of port numbers. The scull implementation uses a
global variable, scull_major
, to hold the chosen number. The
variable is initialized to SCULL_MAJOR
, defined in
scull.h
. The default value of SCULL_MAJOR
in the
distributed source is zero, which means ``select dynamic assignment.''
The user can accept the default or choose
a particular major number, either by modifying the macro before
compiling, or by specifying a value for scull_major
on the
insmod command line. Finally, by using the scull_load script,
the user can pass arguments to insmod on scull_load
’s
command line.
Here’s the code I use in scull/main.c
to get a major number:
result = register_chrdev(scull_major, "scull", &scull_fops); if (result < 0) { printk(KERN_WARNING "scull: can't get major %d ",scull_major); return result; } if (scull_major == 0) scull_major = result; /* dynamic */
When a module is unloaded from the system, the major number should be released. This is accomplished with the following function, which is called from cleanup_module:
int unregister_chrdev(unsigned int major, const char *name);
The arguments are the major number being released and the name of
the associated device. The kernel compares the name to the registered
name for that number: if they differ, -EINVAL
is returned. The
kernel also returns -EINVAL
if the major number is out of
the allowed range for majors or is not assigned to a driver.
Failing to unregister the resource from
cleanup_module has unpleasant effects. /proc/devices
will generate a fault the next time you try to read it, because one of the
name
strings still points to the module’s memory, which is
no longer mapped. This kind of fault is called an Oops because
that’s the message the kernel prints when it tries to access invalid
addresses.[9]
When you unload the driver without unregistering the major number, the
situation is unrecoverable, even from a ``rescue'' module written for
that purpose, because the strcmp call in unregister_chrdev will
use the unmapped name
string and will oops when trying to
release the number. Needless to say, any attempt to open a
device node associated with the phantom major number will oops as well.
In addition to unloading the module, you’ll often need to
remove the device nodes for the driver being unloaded. If the device nodes
were created at load time, a simple script
can be written to remove them at unload time. The script
scull_unload
does the job for our sample device. If
dynamic nodes are not removed from /dev
, there’s a
possibility of unexpected errors: a spare
/dev/framegrabber
on a developer’s computer might refer to a
fire-alarm device one month later if both used dynamic assignment to
get a major number. ``No such file or directory'' is a friendlier
response to opening /dev/framegrabber
than the new device would
produce.
So far we’ve talked about the major number. Now it’s time to discuss the minor number and how the driver uses the minor number to differentiate among devices.
Every time the kernel calls a device driver, it tells the driver what
device is being acted upon. The major and minor numbers are paired in a
single data type that is then used to identify a particular
device. The combined device number (the major and minor numbers
concatenated together) resides in the field
i_rdev
of the ``inode'' structure, which is introduced
later. Every driver
function receives a pointer to struct inode
as the first argument.
The pointer is usually called inode
as well, and the function can
extract the device number by looking at inode->i_rdev
.
Historically, Unix declared dev_t
(device-type) to hold
the device numbers. It used to be a 16-bit integer value defined in
<sys/types.h>
. Nowadays, more than 256 minor numbers
are needed at times, but changing dev_t
is difficult, because
there are applications (and the C library itself) that know the
internals of dev_t
and would break if the structure were to
change. The dev_t
type, therefore, hasn’t been changed; it
is still a 16-bit
integer, and the minor numbers are restricted to the range 0-255.
Within the Linux kernel, however, a new type, kdev_t
, is used.
This new type is designed to be a black box for every kernel function.
The idea is that user programs won’t even know about kdev_t
.
If kdev_t
remains hidden, it can change from one kernel version
to the next as needed, without requiring changes to everyone’s
device drivers.
The information about kdev_t
is confined in
<linux/kdev_t.h>
, which is mostly comments. The header
makes instructive reading if you’re interested in the philosophy
behind the code. There’s no need to include the header
explicitly in the drivers, however, because <linux/fs.h>
does
it for you.
The kdev_t
type, unfortunately, is a ``modern'' idea and
was missing from kernel version 1.2. In recent kernels, all the
kernel variables and structure fields referring to devices are
kdev_t
items, but in 1.2.13 the same variables are in
dev_t
. This is not a problem if your driver uses only
structure fields it receives, without declaring its own variables.
If you need to declare your own device-type variable, you should add
the following lines to your headers in order to ensure portability:
#if LINUX_VERSION_CODE < VERSION_CODE(1,3,28) # define kdev_t dev_t /* two conversion functions are defined in newer kernels */ # define kdev_t_to_nr(dev) (dev) # define to_kdev_t(dev) (dev) #endif
This code is part of the sysdep.h
header in the
sample source files. I won’t refer to dev_t
any more in the
code, but will assume the previous conditional statement has been executed.
The following macros and functions are the operations you can
perform on kdev_t
:
The headers associated to Linux 1.2 defined the same functions
to act on dev_t
quantities, with the exception of the
two conversion functions. That’s why the conditional code
shown earlier defines them simply to
return their argument.
3.128.30.77