Lesson 16: Let's talk about devices.

Printer-friendly versionPrinter-friendly version

The first step in writing a character driver

Now that we've taken all this time to establish the necessary principles of kernel programming to the point where you can write and load some simple modules and do some basic debugging, this is the first lesson in how to write a simple character driver and let's start by giving credit where credit is due since I'm getting some of this material from the classic book "Linux Device Drivers (3rd ed)" -- I'm simply updating some of that material and will be presenting it in bite-size pieces so that you can write your first character driver a bit at a time, and test it that way as well.

Note also that writing a character driver is part of this first course in kernel programming, but writing more specific drivers like a PCI or USB driver will come as separate courses some time down the road, if that's what you're interested in.

And so, to work.

Why start with a character driver?

As the authors of LDD3 explain it:

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).

Ergo, we're starting there, and also because much of what happens here will also apply to writing other, more complicated, types of drivers.

The basics of device files

If you've worked with Linux much at all, you've already run across the /dev directory of special device files, so let's clarify what's in there:

$ ls -l /dev
crw-rw----+ 1 root audio    14,  12 2010-07-07 16:05 adsp
crw-------  1 root video    10, 175 2010-07-07 16:05 agpgart
crw-rw----+ 1 root audio    14,   4 2010-07-07 16:05 audio
drwxr-xr-x  2 root root         660 2010-07-07 16:05 block
drwxr-xr-x  2 root root          80 2010-07-07 16:05 bsg
drwxr-xr-x  3 root root          60 2010-07-07 16:05 bus
lrwxrwxrwx  1 root root           3 2010-07-07 16:05 cdrom -> sr0
lrwxrwxrwx  1 root root           3 2010-07-07 16:05 cdrw -> sr0
drwxr-xr-x  2 root root        3320 2010-07-07 16:05 char
crw-------  1 root root      5,   1 2010-07-07 16:05 console
lrwxrwxrwx  1 root root          11 2010-07-07 16:05 core -> /proc/kcore
... snip ...

For people new to /dev, those files are not device drivers as some people (erroneously) like to refer to them -- they are simply special kinds of files that, in a way, refer mainly to device driver code running in kernel space.

The most common types of special files you'll find there are block ("b") and character ("c") special files, with a few directories and symlinks thrown in to add some aesthetic organization to the lot, but it's those first two types of special files that we care about since accessing them in particular ways gives us access to the underlying device driver that is allegedly handling the device to which that file corresponds. In short, given a properly written character device, accessing its corresponding device file with read and write operations will, ultimately, translate into I/O operations on the device itself.

And how does that mapping happen? I'm glad you asked.

Major and minor numbers

While I'm sure most readers at this point know this, I'll explain it, anyway. When you attempt (from user space) to access a character device by operating on its special device file, the actual name of the device file is irrelevant -- all that matters are the major and minor numbers attached to that device file, because its those two values that uniquely determine what kernel code you're trying to access, and how.

As an example, consider (on my system):

$ ls -l /dev/sr0
brw-rw----+ 1 root cdrom 11, 0 2010-07-07 16:05 /dev/sr0

For now, ignore that this is a block device. What it clearly represents is my CD-ROM device, so if I wanted to access a CD-ROM, I would have to mount the device by its special file name /dev/sr0. And from the above, you can see that that device's major device number is 11. What that means is that, somewhere in kernel space, there is driver code that knows how to talk to my CD-ROM, and the unique way to refer to that driver code from user space is to specify the major number of 11. Got that? CD-ROM driver is accessible via (block) device file with major number 11.

And what about that minor number of zero? While a device file's major number typically identifies the driver code for that device, the minor number more specifically identifies how you want to talk to that device or, more commonly, it identifies one of a number of equivalent instances of that device. For instance, if my system had two CD-ROM devices, it's almost a guarantee that I would see:

$ ls -l /dev/sr*
brw-rw----+ 1 root cdrom 11, 0 2010-07-07 16:05 /dev/sr0
brw-rw----+ 1 root cdrom 11, 1 2010-07-07 16:05 /dev/sr1

or something like that where (unsurprisingly) exactly the same driver would be used to access both devices, but the minor number is used to identify which CD-ROM drive I'm talking to. But wait -- there's more.

Because the canonical name of a device is sometimes ugly and incomprehensible, it's typical to have one or more symlinks to that device file, simply for convenience. For my CD-ROM, I can check that with:

$ ls -l /dev | grep sr0
lrwxrwxrwx  1 root root           3 2010-07-07 16:05 cdrom -> sr0
lrwxrwxrwx  1 root root           3 2010-07-07 16:05 cdrw -> sr0
lrwxrwxrwx  1 root root           3 2010-07-07 16:05 dvd -> sr0
lrwxrwxrwx  1 root root           3 2010-07-07 16:05 dvdrw -> sr0
lrwxrwxrwx  1 root root           3 2010-07-07 16:05 scd0 -> sr0
brw-rw----+ 1 root cdrom    11,   0 2010-07-07 16:05 sr0

In short, to access my CD-ROM, I can use the canonical name for the device file, or I can use any of those symlinks just as easily.

Exercise for the student: Even if you don't know much about device files, scan the contents of /dev and see what other device files have more convenient symlinks.

Do devices for all those device files actually exist?

A good question, with two answers. Historically, the contents of the /dev directory were static -- pre-loaded when you installed your OS -- so you typically had a /dev directory with thousands upon thousands of device files, even for devices you didn't even have; the Linux distro was just playing it safe. And it wasn't that big a deal since device files are actually quite small; in fact, they don't take up any data space at all on the disk, they just cost you an inode (if you know what that is). But even though they didn't cost much in terms of space, it was still wasteful and messy to have a special device file for every imaginable device.

These days, your /dev directory is almost certainly generated dynamically, probably by the udev facility, so that when you examine the contents of /dev, it's much more likely that what you see corresponds to what's actually on your system. But there's a better way to tell what drivers have registered with which major numbers on your Linux system -- the /proc/devices file:

$ cat /proc/devices
Character devices:
  1 mem
  4 /dev/vc/0
  4 tty
  4 ttyS
  5 /dev/tty
  5 /dev/console
  5 /dev/ptmx
  6 lp
  7 vcs
  ... snip ...
Block devices:
  1 ramdisk
259 blkext
  7 loop
  8 sd
  9 md
 11 sr
... snip ...

The contents of the /proc/devices file is the primary source to tell you what drivers are registered, and at what major numbers, and you can see that, sure enough, the block device major number 11 appears to be associated with the "sr" driver -- there's my CD-ROM.

Some quick tips about the above:

  • There is no connection between a block device and a character device having the same major number.
  • Notice that it's possible for more than one driver to register with the same major number, as long as they register for different minor numbers (although that last part isn't obvious since the /proc/devices doesn't list minor numbers).
  • When you finally write and load your first character driver, it should be obvious that an entry for it is going to show up in that file as well -- we'll talk more about that when we get to it.

And now that we've covered everything you need to know about devices in user space, let's hop into the kernel for the rest of this lesson to see how devices and major and minor numbers are handled there.

What "devices" look like in kernel space

At this point, we can look at the internal (kernel space) representation of device numbers, and we can start in the kernel header file include/linux/types.h:

typedef __u32 __kernel_dev_t;
typedef __kernel_dev_t          dev_t;

which tells us that a typedef for a "device" in kernel space is an unsigned 32-bit value, and it's of type dev_t, at which point the header file include/linux/kdev_t.h tells us everything else we need to know, starting with:

#define MINORBITS       20
#define MINORMASK       ((1U << MINORBITS) - 1)

#define MAJOR(dev)      ((unsigned int) ((dev) >> MINORBITS))
#define MINOR(dev)      ((unsigned int) ((dev) & MINORMASK))
#define MKDEV(ma,mi)    (((ma) << MINORBITS) | (mi))

It should be obvious what the above is telling you -- that a device type identifier is represented by a type of dev_t, that it's 32 bits long, and that it's partitioned into a leading 12-bit major number, plus a 20-bit minor number. Also, that you're not supposed to know about the 20 bits part and that all management of that kernel type should be done via those macros so the partitioning can change without notice and you'll never have to worry about it.

And on that note, we've covered the fundamentals of how to refer to device drivers in kernel space; in the next lesson, we'll get into how you use this information to register them with the system when you load them.

Until then.


Post new comment

The content of this field is kept private and will not be shown publicly.
  • Web page addresses and e-mail addresses turn into links automatically.
  • Allowed HTML tags: <a> <em> <strong> <cite> <code> <ul> <ol> <li> <dl> <dt> <dd> <p> <br> <pre> <h1> <h2> <h3> <h4>
  • Lines and paragraphs break automatically.

More information about formatting options

By submitting this form, you accept the Mollom privacy policy.