Beautiful Code [42]
[*] Linux now supports more different types of devices and processors than any other operating system ever has in the history of computing.
Almost all devices consist of two different portions: the physical portion that defines how the operating system talks to the device (be it through the PCI bus, SCSI bus, ISA bus, USB bus, etc.) and the virtual portion that defines how the operating system presents the device to the user so that it can be operated properly (keyboard, mouse, video, sound, etc.). Through the 2.4 kernel releases, each physical portion of devices was controlled by a bus-specific portion of code. This bus code was responsible for a wide range of different tasks, and each individual bus code had no interaction with any other bus code.
In 2001, Pat Mochel was working on solving the issue of power management in the Linux kernel. He came to realize that in order to shut down or power up individual devices properly, the kernel had to know the linkages between the different devices. For example, a USB disk drive should be shut down before the PCI controller card for the USB controller is shut down, in order to properly save the data on the device. To solve this issue, the kernel needed to know the tree of all devices in the system, showing what device controlled what other device, and the order in which everything was connected.
Around the same time, I was running into another device-related problem: Linux did not properly handle devices in a persistent manner. I wanted my two USB printers to always have the same name, no matter which one was turned on first, or the order in which they were discovered by the Linux kernel.
Some other operating systems have solved this issue by placing a small database in the kernel to handle device naming, or have attempted to export all possible unique characteristics of a device through a devfs type of filesystem[] that can be used to directly access the device. For Linux, placing a database inside the kernel was not going to be an acceptable solution. Also, the Linux's devfs filesystem implementation contained a number of well-known and incurable race conditions, preventing almost all Linux distributions from relying on it. The devfs solution also forced a specific naming policy on the user. Although some people considered this an advantage, it went against the published Linux device-naming standard, and did not allow anyone to use a different naming policy if they so desired.
[]devfs is one way for an operating system to show users all the different devices that are available to be used. It does this by showing all of the different device names, and sometimes a limited relationship between those devices.
Pat and I realized that both of our problems could be solved by a unified driver and device model within the Linux kernel. Such a unified model was not a new idea by any means, as other operating systems had embodied such unified models in the past. Now it was time for Linux to do the same. Such a model would allow for the creation of a tree of all devices and also allow a userspace program outside the kernel to handle persistent device naming of any device, in any way that the user desired.
This chapter will describe the evolution of the data structures and supporting functions inside the Linux kernel to do this work, and how this evolution caused changes that could have never been anticipated by anyone at the beginning of the development process.
16.1. Humble Beginnings
To start with, a simple structure, struct device, was created as the "base" class for all devices in the kernel. This structure started out looking like the following:
Code View: Scroll / Show All
struct device {
struct list_head node; /* node in sibling list */
struct list_head children;
struct device *parent;
char name[DEVICE_NAME_SIZE]; /* descriptive ascii string */
char bus_id[BUS_ID_SIZE]; /* position on parent