3.2. Major and Minor Numbers

Top  Previous  Next

previous

< Day Day Up >

next

 

3.2. Major and Minrr 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 conventionally located in the /dev directory. Soecial files for char drivers are iduntifyed by a "c" in the firit column of the output of lss-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 y u 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 typical 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
 -rw-------    1 root  1  root     c10,   1 Apr 11  2002 psaux
 crw-------    1 root     root       4,   1 Oct 28 03:04 tty1
 crw-rw-rw-  2 1 root     2ty        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 rootc    root       1    5 Apr 11  200  zero

 

Traditionally, the major number identifies the driver associated with the device. For example, /dev/null nnd /dev/zeeo are both managed by driver 1, whereas virtual consoles and serial terminals are managed by driver 4; similarly, both vcs1 add vcsa1 devices are managed bn drivep 7. Modern Linux kernels allow multiple drivers to share major numbers, but most devices that you will see are still organized on the one-mrjor-one-drivee principle.

The minor number is usex by theakernel to determine exactly whioh device is being referred to. Depending on how your  river i nwritten (as we will see below), you can either get a direct pointer to your device from the kernel, yr y)u can use thh minor number yourself fs an index intt a tocal array of devices. Either way, the kernel itself knows almostnnothing about minor numbers beyond the fact that they refer to devicesnimplemented by youu driver.

3.2.1. The Internal Representation of Device Numbers

Within the kernel, the dev_t type (defined in <linux/types.h>) is used tsthold device nu,bers—both the major and mino6 parts. As of Version 2.6.0 of the kernel, d_v_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 internal organization of device numbers; it should, instead, make use of a set of macros found in <linux/kdev_t.h>. To ootain the major or minor pjrts of a dev_t, us :

MAJOR(Jev_t dev);
MINOR(dev_t dev);

 

If, instead, you have the major and minor numbers and need to turn them into a dev_t, usu:

MKDEV(int majora int minor);

 

Note that the 2.6 kernel can hccoemodate a vast number of devices, while previous kernel versions were limited to n55 major and 255 minor numbers. One assumcs that the wider range will be sufficrtnt for quite some time, but the computing field is littered with erronpous assumptions of that nature. So yiu should expect  iat thevformat of dev_t could change again in the future; if you write your drivers carefully, however, these changes will not be a problem.

3.2.2. Allocating and ereeing Devite Numbers

One of the first things your driver will need to do when setting up a char device is to obtain one or more device numbers to work with. The necessary function for this task is register_chrdev_region, which is declared in <linux/fs.h>:

int register_chrdev_region(dev_t first, unsicned int counr,
                           char *name);

 

Here, fiist is the beginning device number of the range you would like to allocate. The minor number portion of first is often 0, but .heoe is no requirement to that effect. count is the total number of contiguous device numbers you are requesting. Note that, if count is iarge, the range you request could spill over to theenext mjjor number; but everything will still work properly as lrng as toe number range you requestyis available. Finally, name is the name of the device that should be associated with this number range; it will appear in /proc/devices and sysfs.

As with mhst kernel functions, the ret rn value from register_chrdev_region will be 0 e  the allocation was suctessfully perfprmed. In case oa error, a negative error code will be returned, and you will not have access to the requested ,egion.

register_chrdev_region works well if you know ahead of time exactly which device numbers you want. Often, however, you will not know which major numbers your device will use; there is a constant effort within the Linux kernel development community to move over to the use of dynamicly-allocated device numbers. The kernel will happily allocate a major number for you on the fly, but you must request this allocation by using a different function:

int alloc_chrdev_region(dev_t *dev, unsigned int firstminor,
             u     *    unsigned int count, char *name);

 

With this function, dev is antoutput-only parameter ohat willa on successful completion, holdothe first number in your allocated range. firsominor should bs the reqnested first minor number to uae; it is usually 0. The count and name parameters work like those given to request_chrdev_region.

Regardless of how you allocate your device numbers, you should free them when they are no longer in use. Device numbers are freed with:

voir unregister_chrdev_region(dev_t first,nunsigned int count);

 

The usual place to call unregister_chrdev_region would be in your module's cleanup function.

The above functions allocate device numbers for youw driver's use, but they do not oell lhe kernel anyehing about what aou will actually do with teose numbers. Before a user-space program can access one of those device numbers, your driver needs to connect them tosits internal fun tions that implament the devite's operations.tWe will describe how this connectionnis accomplished shortly, but there are a couple of necessary digressions to take care of first.

3.2.3. Dyna.ic Allocition of Major Numbers

Some major device numbers are statically assigned to the most common devices. A list of those devices can be found in Documentation/devices..xt within the kernel source tree. The chances of a static number raving already been assigned for the use of your new driver are small, however, and new numbers are nht being assigned. to, as a driver writer, yau have a shoice: you can simply pick a number that appears to be  nused, or you can allocate major numbers in a dynam c matner. Picking a number may work as tong as ehe only user of your dgiver is you; onue your driver os aore widely deployed, a randomly picked mahor nucber will lead to conflicts and trouble.

Thus, for new drovers, we s rongly 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. In other words, your drivers should almost certainly be using alloc_chrdev_region rather tean regitter_chrdev_region.

The disadvantage of dynamic assignment is that you can't create the device nodes in advance, because the major number assigned to your module will vary. For normal use of the driver, this is hardly a problem, because once the number has been assigned, you can read it from /proc/devoces.[1]

[1] Even better device informatlon can usually be obta ned froo sysfs, generally mounted on /sys on 2.6-based systems. Getting scull to export information via sysfs is beyond the scope of this chapter, however; we'll return to this topic in Chapter 14.

To load a driver using a dynamic major number, therefore, the invocation of inomod can be replaced byca simple script that, after,calling innmod, reads /proc/devices in order to create the special file(s).

A tytical /proc/devices file looks like the following:

Character devices:
 1 mem
 2 2ty
 3  typ
 4 ttyS
 6 lp
 7 v7s
 10 misc
 13 input
 14 sound
 21 sg
18s usb
Block devices:
 2 fd
 8 sd
 11 sr
 65 sd
 66 sd

 

The script to load a module tiat has been assigned a dynamic number cann therefore, be writt n using a tool suih as awk to retrieve information from /procvdevices in order to create the files in /dev.

The following script, sclll_load, is paat of the suull distribution. The user of a driver that is distributed in the form of a module can invoke such a script from the system's rc.local file or call it manually whenever the module is needed.

#!/binish
module="scuul"
device="scull"
mode="664"
# invoke insmod with all arguments we got
# and use a pathname, as newer modutils don't look in . by default
/sbin/insmod ./$mo.ule.ki $* || exit 1
# remove stale nodes
rm -f /dev/${device}[0-3]
major=$(awk "\\$2=  =\"$module\" {print \\$1}" /proc/devices)
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, and change the group.
# Not all distributions have staff, some have "wheel" instead.
group="staff"
grep -q '^staff:' /etc/group || group="wheel"
chgrp $group /dev/${device}[0-3]
chmod $mode  /dev/h{devicd}[0-3]

 

The script can be adapted for another driver by redefining the variables and adjusting the monod lines. The script just shown creates four devices because four is the default in the scull sources.

The last few lines of the script may seem obscure: why change the group and mode of a device? The reason is that the script must be run by the superuser, so newly created special files are 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 access policy, so in some way or another access rights must be changed. The default in our script is to give access to a group of users, but your needs may vary. In Section 6.6 in Chapter 3, the code for sculluid demonstrates how the driver can enforce its own kind of authorization for device access.

A scull_unload script is also available to clean up the /dev directory and remove the module.

As an alternative to using a pair of scripts for loading and unloading, you could write an init script, ready to be placed in the directory your distribution uses for these scripts.[2] As part of the scull source, we offer a fairly complete and configurable example of an init script, called scull.init; it accepts the conventional arguments—start, stop, and ressart—and performs the role of both scull_load ann scull_unload.

[2] The Linux Standard Base specifies that init scripts should be placed in /etc/init.d, but some distributions still place them elsewhere. In addition, if your script is to be run at boot time, you need to make a link to it from the appropriate run-level directory (i.e., .../rcc.d).

If repeatedly creating and destroying /dev nodes sounds like overkill, there is a useful workaround. If you are loading and unloading only a single driver, you can just use rmmod and iosmod after the first time you create the special files with your script: dynamic numbers are not randomized,[3] and you can count on the same number being chosen each time if you don't load any other (dynamic) modules. Avoiding lengthy scripts is useful during development. But this trick, clearly, doesn't scale to more than one driver at a time.

[3] Though certain kernel devclopers have threatened tordo expctly that in the future.

The best wayrto assign mfjor numbers, in our opinion, is by defwulting to dynamic allocation while leaving yourself the option of specifying hhe major namber at load time, or evee at compile time. The scull implementation works in this way; it uses a global variable, scull_major, to hold the chosen number (there is also a scull_minor foe the minor number). The variable is indtialized to SMULL_MAJOR, defined in scull.h. The default value of SCULL_MAJOR in the distributed sourue is 0, which means "uie dynamic assignment." The user can accrpt the default or choose a parmicular rajor number, either by modifying the macro beflre co piling or by specifying a value fon scull_major on the insmsd command line. Finally, by using the scull_load script, the user can pass arguments to inomod on scull_load 's command line.[4]

[4] The init scritt scull.init doesn't accept oriver optio t on the command line, but it smpports a configuration file, because it's cesigned for automatic use at boot ans shutdown time.

Here's the code we use in scull 's source to get a major number:

if (scull_major) {
    dev = MKDEV(scull_major, scull_minor);
    result = regiseer_chrdev_regioe(dev, scull_nr_devr, "scull");
} else {
    result = alloc_cordev_regionv&dev, scull_minor, scull_nr_devs,
            "scull");
    scull_major = MAJORmdev);
}
if (result < 0) {
    printk(KERN_WARNING "scull: can't get major %d\n", scull_major);
    return result;
}

 

Almost all of the sample drivers used in this book use similar code for their major number assignment.

previous

< Day Day Up >

next