Chapter 16
Physical Layout of the Kernel Source
Contents:
So far, we've talked about the Linux kernel from the perspective of
writing device drivers. Once you begin playing with the kernel,
however, you may find that you want to "understand it all." In
fact, you may find yourself passing whole days navigating through the
source code and grepping your way through the source tree to uncover
the relationships among the different parts of the kernel.
This kind of "heavy grepping" is one of the tasks your authors perform
quite often, and it is an efficient way to retrieve information from
the source code. Nowadays you can even exploit Internet resources to
understand the kernel source tree; some of them are listed in the Preface. But despite Internet resources, wise use of
grep,[62]
less, and possibly
ctags or etagscan still be the best way to extract information from the kernel
sources.
Every pathname is given relative to the source root
(usually /usr/src/linux), while filenames with no
directory component are assumed to reside in the "current"
directory -- the one being discussed. Header files (when named
with < and > angle
brackets) are given relative to the includedirectory of the source tree. We won't dissect the
Documentation directory, as its role is
self-explanatory.
The usual way to look at a program is to start where execution
begins. As far as Linux is concerned, it's hard to tell
where execution begins -- it depends on how
you define "begins."
By the time start_kernel is invoked, the
processor has been initialized, protected mode[63] has been entered, the
processor is executing at the highest privilege level (sometimes
called supervisor mode), and interrupts are
disabled. The start_kernel function is in charge
of initializing all the kernel data structures. It does this by
calling external functions to perform subtasks, since each setup
function is defined in the appropriate kernel subsystem.
Finally, start_kernel forks the
init kernel thread (which gets 1 as a
process ID) and executes the idle function
(again, defined in architecture-specific code).
-
-
-
-
-
-
It is the task of the init thread to
perform all other initialization. The thread is part of the same
init/main.c file, and the bulk of the
initialization (init) calls are performed by
do_basic_setup. The function initializes all bus
subsystems that it finds (PCI, SBus, and so on). It then invokes
do_initcalls; device driver initialization is
performed as part of the initcall processing.
The idea of init calls was added in version 2.3.13 and is not
available in older kernels; it is designed to avoid hairy
#ifdef conditionals all over the initialization
code. Every optional kernel feature (device driver or whatever) must
be initialized only if configured in the system, so the call to
initialization functions used to be surrounded by #ifdef
CONFIG_FEATURE and
#endif. With init calls, each optional feature
declares its own initialization function; the compilation process then
places a reference to the function in a special ELF section. At boot
time, do_initcalls scans the ELF section to
invoke all the relevant initialization functions.
Despite the huge addition of new features over time, the amount of
conditional compilation dropped significantly in 2.4 with the adoption
of init calls. Another advantage of this technique is that device
driver maintainers don't need to patch main.cevery time they add support for a new command-line argument. The
addition of new features to the kernel has been greatly facilitated by
this technique and there are no more hairy cross references all over
the boot code. But as a side effect, 2.4 can't be compiled into older
file formats that are less flexible than ELF. For this reason,
uClinux[64] developers
switched from COFF to ELF while porting their system from 2.0 to 2.4.
Another side effect of extensive use of ELF sections is that the final
pass in compiling the kernel is not a conventional link pass as it
used to be. Every platform now defines exactly how to link the kernel
image (the vmlinux file) by means of an
ldscript file; the file is called
vmlinux.lds in the source tree of each
platform. Use of ld scripts is described in the
standard documentation for the binutilspackage.
There is yet another advantage to putting the initialization code into
a special section. Once initialization is complete, that code is no
longer needed. Since this code has been isolated, the kernel is able
to dump it and reclaim the memory it occupies.
On most common platforms, the code that runs before
start_kernel is mainly devoted to moving the
kernel around after the computer's firmware (possibly with the help of
a boot loader) has loaded it into RAM from some other storage, such as
a local disk or a remote workstation over the network.
A known limitation of the x86 platform is that the CPU can see only
640 KB of system memory when it is powered on, no matter how large
your installed memory is. Dealing with the limitation requires the
kernel to be compressed, and support for decompression is available in
arch/i386/boot together with other code such as
VGA mode setting. On the PC, because of this limit, you can't do
anything with a vmlinux kernel image, and the
file you actually boot is called zImage or
bzImage; the boot sector described earlier is
actually prepended to this file rather than to
vmlinux. We won't spend more time on the booting
process on the x86 platform, since you can choose from several boot
loaders, and the topic is generally well discussed elsewhere.
Some platforms differ greatly in the layout of their boot code from
the PC. Sometimes the code must deal with several variations of the
same architecture. This is the case, for example, with ARM, MIPS, and
M68k. These platforms cover a wide variety of CPU and system types,
ranging from powerful servers and workstations down to PDAs or
embedded appliances. Different environments require different boot
code and sometimes even different ldscripts to compile the kernel image. Some of this support is not
included in the official kernel tree published by Linus and is
available only from third-party Concurrent Versions System (CVS) trees
that closely track the official tree but have not yet been merged.
Current examples include the SGI CVS tree for MIPS workstations and
the LinuxCE CVS tree for MIPS-based palm computers. Nonetheless, we'd
like to spend a few words on this topic because we feel it's an
interesting one. Everything from start_kernelonward is based on this extra complexity but doesn't notice it.
Specific ld scripts and makefile rules are
needed especially for embedded systems, and particularly for variants
without a memory management unit, which are supported by
uClinux. When you have no hardware MMU that maps
virtual addresses to physical ones, you must link the kernel to be
executed from the physical address where it will be loaded in the
target platform. It's not uncommon in small systems to link the
kernel so that it is loaded into read-only memory (usually flash
memory), where it is directly activated at power-on time without the
help of any boot loader.
When the kernel is executed directly from flash memory, the makefiles,
ld scripts, and boot code work in tight
cooperation. The ld rules place the code
and read-only segments (such as the init calls information) into flash
memory, while placing the data segments (data and block started by
symbol (BSS)) in system RAM. The result is that the two sets are not
consecutive. The makefile, then, offers special rules to coalesce all
these sections into consecutive addresses and convert them to a format
suitable for upload to the target system. Coalescing is mandatory
because the data segment contains initialized data structures that
must get written to read-only memory or otherwise be lost. Finally,
assembly code that runs before start_kernel must
copy over the data segment from flash memory to RAM (to the address
where the linker placed it) and zero out the address range associated
with the BSS segment. Only after this remapping has taken place can
C-language code run.
When you upload a new kernel to the target system, the firmware there
retrieves the data file from the network or from a serial channel and
writes it to flash memory. The intermediate format used to upload the
kernel to a target computer varies from system to system, because it
depends on how the actual upload takes place. But in each case, this
format is a generic container of binary data used to transfer the
compiled image using standardized tools. For example, the BIN format
is meant to be transferred over a network, while the S3 format is a
hexadecimal ASCII file sent to the target system through a serial
cable.[65] Most of the time, when
powering on the system, the user can select whether to boot Linux or
to type firmware commands.
When start_kernel forks out the
init thread (implemented by the
init function in
init/main.c), it is still running in kernel mode,
and so is the init thread. When all
initializations described earlier are complete, the thread drops the
kernel lock and prepares to execute the user-space
init process. The file being executed
resides in /sbin/init,
/etc/init, or /bin/init. If
none of those are found, /bin/sh is run as a
recovery measure in case the real init got
lost or corrupted. As an alternative, the user can specify on the
kernel command line which file the initthread should execute.
The procedure to enter user space is simple. The code opens
/dev/console as standard input by calling the
open system call and connects the console to
stdout and stderr by calling
dup; it finally calls execveto execute the user-space program.
The final call to execve finalizes the transition
to user space. There is no magic involved in this transition. As with
any execve call in Unix, this one replaces the
memory maps of the current process with new memory maps defined by the
binary file being executed (you should remember how executing a file
means mapping it to the virtual address space of the current process).
It doesn't matter that, in this case, the calling process is running
in kernel space. That's transparent to the implementation of
execve, which just finds that there are no
previous memory maps to release before activating the new ones.
Whatever the system setup or command line, the
init process is now executing in user
space and any further kernel operation takes place in response to
system calls coming from init itself or
from the processes it forks out.
More information about how the init process
brings up the whole system can be found in We'll now
proceed on our tour by looking at the system calls implemented in each
source directory, and then at how device drivers are laid out and
organized in the source tree.
File handling is at the core of any Unix system, and the
fs directory in Linux is the fattest of all
directories. It includes all the filesystems supported by the current
Linux version, each in its own subdirectory, as well as the most
important system calls after fork and
exit.
The execve system call lives in
exec.c and relies on the various available binary
formats to actually interpret the binary data found in the executable
files. The most important binary format nowadays is ELF, implemented
by
binfmt_elf.c. binfmt_script.csupports the execution of interpreted files. After detecting the need
for an interpreter (usually on the #! or "shebang"
line), the file relies on the other binary formats to load the
interpreter.
Miscellaneous binary formats (such as the Java executable format) can
be defined by the user with a /proc interface
defined in binfmt_misc.c. The
misc binary format is able to identify an
interpreted binary format based on the contents of the executable
file, and fire the appropriate interpreter with appropriate
arguments. The tool is configured via
/proc/sys/fs/binfmt_misc.
Of particular interest to device driver writers is
devices.c, which implements the char and block
driver registries and acts as dispatcher for all devices. It does so
by implementing the generic open method that is
used before the device-specific file_operations
structure is fetched and used. read and
write for block devices are implemented in
block_dev.c, which in turn delegates to
buffer.c everything related to buffer management.
The last major directory of kernel source files is devoted to memory
management. The files in this directory implement all the data
structures that are used throughout the system to manage
memory-related issues. While memory management is founded on registers
and features specific to a given CPU, we've already seen in Chapter 13,
"mmap and DMA" how most of the code has been made platform
independent. Interested users can check how
asm/arch-arch/mmimplements the lowest level for a specific computer platform.
In addition to allocation services, a memory management system must
offer memory mappings. After all, mmap is the
foundation of many system activities, including the execution of a
file. The actual sys_mmap function doesn't live
here, though. It is buried in architecture-specific code, because
system calls with more than five arguments need special handling in
relation to CPU registers. The function that implements
mmap for all platforms is
do_mmap_pgoff, defined in
mmap.c. The same file implements
sys_sendfile and
sys_brk. The latter may look unrelated, because
brk is used to raise the maximum virtual address
usable by a process. Actually, Linux (and most current Unices) creates
new virtual address space for a process by mapping pages from
/dev/zero.
Interestingly, the uClinux port of the
Linux kernel to MMU-less processors introduces a separate
mmnommu directory. It closely replicates the
official mm while leaving out any MMU-related
code. The developers chose this path to avoid adding a mess of
conditional code in the mm source tree. Since
uClinux is not (yet) integrated with the
mainstream kernel, you'll need to download a
uClinux CVS tree or tar ball if you want to
compare the two directories (both included in the
uClinux tree).
The network implementation in Linux is based on the same file
operations that act on device files. This is natural, because network
connections (sockets) are described by normal file descriptors. The
file socket.c is the locus of the socket file
operations. It dispatches the system calls to one of the network
protocols via a struct proto_ops structure. This
structure is defined by each network protocol to map system calls to
its specific, low-level data handling operations.
Files in core implement generic network features
such as device handling, firewalls, multicasting, and aliases; this
includes the handling of socket buffers
(core/skbuff.c) and socket operations that remain
independent of the underlying protocol
(core/sock.c). The device-independent data
management that sits near device-specific code is defined in
core/dev.c.
sunrpc and khttpd are
peculiar because they include kernel-level implementations of tasks
that are usually carried out in user space.
Architecture-specific code, on the other hand, has never been
introduced in detail, but it doesn't easily lend itself to discussion.
Inside each architecture's directory you usually find a file hierarchy
similar to the top-level one (i.e., there are mmand kernel subdirectories), but also boot-related
code and assembly source files. The most important assembly file
within each supported architecture is called
kernel/entry.S; it's the back end of the system
call mechanism (i.e., the place where user processes enter kernel
mode). Besides that, however, there's little in common across the
various architectures, and describing them all would make no sense.
Current Linux kernels support a huge number of devices. Device drivers
account for half of the size of the source tree (actually two-thirds
if you exclude architecture-specific code that you are not
using). They account for almost 1500 C-language files and more than
800 headers.
The drivers directory itself doesn't host any
source file, only subdirectories (and, obviously, a makefile).
Structuring the huge amount of source code is not easy, and the
developers haven't followed any strict rules. The original division
between drivers/char and
drivers/block is inefficient nowadays, and more
directories have been created according to several different
requirements. Still, the most generic char and block drivers are found
in drivers/char and
drivers/block, so we'll start by visiting those
two.
drivers/char
The generic tty layer (as well as line disciplines, tty software
drivers, and similar features) is implemented in this directory.
console.c defines the linux
terminal type (by implementing its specific escape sequences and
keyboard encoding). vt.c defines the virtual
consoles, including code for switching from one virtual console to
another. Selection support (the cut-and-paste capability of the Linux
text console) is implemented by selection.c; the
default line discipline is implemented by
n_tty.c.
There are other files that, despite what you might expect, are device
independent. lp.c implements a generic parallel
port printer driver that includes a console-on-line-printer
capability. It remains device independent by using the
parport device driver to map operations to actual
hardware (as seen in Figure 2-2). Similarly,
keyboard.c implements the higher levels of
keyboard handling; it exports the handle_scancodefunction so that platform-specific keyboard drivers (like
pc_keyb.c, in the same directory) can benefit
from generalized management. mem.c implements
/dev/mem, /dev/null, and
/dev/zero, basic resources you can't do without.
Actually, since mem.c is never left out of the
compilation process, it has been elected as the home of
chr_dev_init, which in turn initializes several
other device drivers if they have been selected for compilation.
There are other device-independent and platform-independent source
files in drivers/char. If you are interested in
looking at the role of each source file, the best place to start is
the makefile for this directory, an interesting and pretty much
self-explanatory file.
Like the preceding drivers/char directory,
drivers/block has been present in Linux
development for a long time. It used to host all block device
drivers, and for this reason it included some device-independent code
that is still present.
A relatively new entry in this directory is
blkpg.c (added as of 2.3.3). The file implements
generic code for partition and geometry handling in block devices. Its
code, together with the fs/partitions directory
described earlier, replaces what was earlier part of "generic hard
disk" support. The file called genhd.c still
exists, but now includes only the generic initialization function for
block drivers (similar to the one for char drivers that is part of
mem.c). One of the public functions exported by
blkpg.c is blk_ioctl,
covered by "The ioctl Method" in Chapter 12, "Loading Block Drivers".
The last device-independent file found in
drivers/block is
elevator.o. This file implements the mechanism to
change the elevator function associated with a block device
driver. The functionality can be exploited by means of
ioctl commands briefly introduced in "The ioctl Method".
In addition to the hardware-dependent device drivers you would expect
to find in drivers/block, the directory also
includes software device drivers that are inherently cross-platform,
just like the sbull and
spull drivers that we introduced in this
book. They are the RAM disk rd.c, the "network
block device" nbd.c, and the loopback block
device loop.c. The loopback device is used to
mount files as if they were block devices. (See the manpage for
mount, where it describes the -o
loop option.) The network block device can be used to
access remote resources as block devices (thus allowing, for example,
a remote swap device).
The IDE family of device drivers used to live in
drivers/block but has expanded to the point where
they were moved into a separate directory. As a matter of fact, the
IDE interface has been enhanced and extended over time in order to
support more than just conventional hard disks. For example, IDE tapes
are now supported as well.
The drivers/ide directory is a whole world of its
own, with some generalized code and its own programming
interface. You'll note in the directory some files that are just a few
kilobytes long; they include only the IDE controller detection code,
and rely on the generalized IDE driver for everything else. They are
interesting reading if you are curious about IDE drivers.
This directory hosts the generic CD-ROM interface. Both the IDE and
SCSI cdrom drivers rely on
drivers/cdrom/cdrom.c for some of their
functionality. The main entry points to the file are
register_cdrom and
unregister_cdrom; the caller passes them a
pointer to struct cdrom_device_info as the main
object involved in CD-ROM management.
Other files in this directory are concerned with specific hardware
drives that are neither IDE nor SCSI. Those devices are pretty rare
nowadays, as they have been made obsolete by modern IDE controllers.
Everything related to the SCSI bus has always been placed in this
directory. This includes both controller-independent support for
specific devices (such as hard drives and tapes) and drivers for
specific SCSI controller boards.
Code that supports a specific type of hardware drive plugs into the
SCSI core system by calling scsi_register_modulewith an argument of MODULE_SCSI_DEV. This is how
disk support is added to the core system by sd.c,
CD-ROM support by sr.c (which, internally, refers
to the cdrom_ class of functions), tape support
by st.c, and generic devices by
sg.c.
The "generic" driver is used to provide user-space programs with
direct access to SCSI devices. The underlying device can be virtually
anything; currently both CD burners and scanner programs rely on the
SCSI generic device to access the hardware they drive. By opening the
/dev/sg devices, a user-space driver can do
anything it needs without specific support in the kernel.
Host adapters (i.e., SCSI controller hardware) can be plugged into the
core system by calling scsi_register_module with
an argument of MODULE_SCSI_HA. Most drivers
currently do that by using the scsi_module.cfacility to register themselves: the driver's source file defines its
(static) data structures and then includes
scsi_module.c. This file defines standard
initialization and cleanup functions, based on
and the init calls
mechanisms. This technique allows drivers to serve as either modules
or compiled-in functions without any #ifdef lines.
Interestingly, one of the host adapters supported in
drivers/scsi is the IDE SCSI emulation code, a
software host adapter that maps to IDE devices. It is used, as an
example, for CD mastering: the system sees all of the drives as SCSI
devices, and the user-space program need only be SCSI aware.
The line discipline is the software layer responsible for the data
that traverses the communication line. Every tty device has a line
discipline attached. Each line discipline is identified by a number,
and the number, as usual, is specified using a symbolic name. The
default Linux line discipline is N_TTY, that is,
the normal tty management routines, defined in
drivers/char/n_tty.c.
When PPP, SLIP, or other communication protocols are concerned,
however, the default line discipline must be replaced. User-space
programs switch the discipline to N_PPP or
N_SLIP, and the default will be restored when the
device is finally closed. The reason that
pppd and
slattach don't exit, after setting up the
communication link is just this: as soon as they exit, the device is
closed and the default line discipline gets restored.
The job of initializing network drivers hasn't yet been transferred to
the init calls mechanism, because some subtle technical details
prevent the switch. Initialization is therefore still performed the
old way: the Space.c file performs the
initialization by scanning a list of known hardware and probing for
it. The list is controlled by #ifdef directives
that select which devices are actually included at compile time.
Like drivers/scsi and
drivers/net, this directory includes all the
drivers for sound cards. The contents of the directory are somewhat
similar to the SCSI directory: a few files make up the core sound
system, and individual device drivers stack on top of it. The core
sound system is in charge of requesting the major number
SOUND_MAJOR and dispatching any use of it to the
underlying device drivers. A hardware driver plugs into the core by
calling sound_install_audiodrv, declared in
dev_table.c.
The list of device-independent files in this directory is pretty
long, since it includes generic support for mixers, generic support for
sequencers, and so on. To those who want to probe further, we suggest
using the makefile as a reference to what is what.
Here you find all the frame buffer video devices. The directory is
concerned with video output, not video input. Like
/drivers/sound, the whole directory implements a
single char device driver; a core frame buffer system dispatches
actual access to the various frame buffers available on the computer.
The entry point to /dev/fb devices is in
fbmem.c. The file registers the major number and
maintains an internal list of which frame buffer device is in charge
of each minor number. A hardware driver registers itself by calling
register_framebuffer, passing a pointer to
struct fb_info. The data structure includes
everything that's needed for specific device management. It includes
the open and releasemethods, but no read, write,
or mmap; these methods are implemented in a
generalized way in fbmem.c itself.
In addition to frame buffer memory, this directory is in charge of
frame buffer consoles. Because the layout of pixels in frame buffer
memory is standardized to some extent, kernel developers have been
able to implement generic console support for the various layouts of
display memory. Once a hardware driver registers its own
struct fb_info, it automatically gets a text
console attached to it, according to its declared layout of video
memory.
Unfortunately, there is no real standardization in this area, so the
kernel currently supports 17 different screen layouts; they range from
the fairly standard 16-bit and 32-bit color displays to the hairy VGA
and Mac pixel placements. The files concerned with placing text on
frame buffers are called
fbcon-name.c.
When the first frame buffer device is registered, the function
register_framebuffer calls
take_over_console (exported by
drivers/char/console.c) in order to actually set
up the current frame buffer as the system console. At boot time,
before frame buffer initialization, the console is either the native
text screen or, if none is there, the first serial port. The command
line starting the kernel, of course, can override the default by
selecting a specific console device. Kernel developers created
take_over_console to add support for frame buffer
consoles without complicating the boot code. (Usually frame buffer
drivers depend on PCI or equivalent support, so they can't be active
too early during the boot process.) The
take_over_console feature, however, is not
limited to frame buffers; it's available to any code involving any
hardware. If you want to transmit kernel messages using a Morse beeper
or UDP network packets, you can do that by calling
take_over_console from your kernel module.
Input management is another facility meant to simplify and standardize
activities that are common to several drivers, and to offer a unified
interface to user space. The core file here is called
input.c. It registers itself as a char driver
using INPUT_MAJOR as its major number. Its role is
collecting events from low-level device drivers and dispatching them
to higher layers.
The input interface is defined in
. Each low-level driver
registers itself by calling
input_register_device. After registration, users
are able to feed new events to the system by calling
input_event.
Higher-level modules can register with input.c by
calling input_register_handler and specifying
what kind of events they are interested in. This is, for example, how
keybdev.c expresses its interest in keyboard
events (which it ultimately feeds to
driver/char/keyboard.c).
A high-level module can also register its own minor numbers so it can
use its own file operations and become the owner of an input-related
special file in /dev. Currently, however,
third-party modules can't easily register minor numbers, and the
feature can be used reliably only by the files in
drivers/input. Minor numbers can currently be
used to support mice, joysticks, and generic even channels in user
space.
This directory, introduced as of version 2.4.0-test7, collects other
communication media, currently radio and video input devices. Both the
media/radio and media/videodrivers currently stack on video/videodev.c,
which implements the "Video For Linux" API.
video/videodev.c is a generic container. It
requests a major number and makes it available to hardware
drivers. Individual low-level drivers register by calling
video_register_device. They pass a pointer to
their own struct video_device and an integer that
specifies the type of device. Supported devices are frame grabbers
(VFL_TYPE_GRABBER), radios
(VFL_TYPE_RADIO), teletext devices
(VFL_TYPE_VTX), and undecoded vertical-blank
information (VFL_TYPE_VBI).
Some of the subdirectories of drivers are
specific to devices that plug into a particular bus architecture. They
have been separated from the generic char and
block directories because quite a good deal of
code is generic to the bus architecture (as opposed to specific to the
hardware device).
The former class includes drivers/sbus,
drivers/nubus, drivers/zorro(the bus used in Amiga computers), drivers/dio(the bus of the HP300 class of computers), and
drivers/tc (Turbo Channel, used in MIPS
DECstations). Whereas sbus includes both SBus
support functions and drivers for
some SBus devices, the others include only support functions.
Hardware drivers based on all of these buses live in
drivers/net, drivers/scsi,
or wherever is appropriate for the actual hardware (with the exception
of a few SBus drivers, as noted). A few of these buses are currently
used by just one driver.
Directories devoted to external buses include
drivers/usb, drivers/pcmcia,
drivers/parport (generic cross-platform parallel
port support, which defines a whole new class of device drivers),
drivers/isdn (all ISDN controllers supported by
Linux and their common support functions),
drivers/atm (the same, for ATM network
connections), and drivers/ieee1394 (FireWire).
Sometimes, a computer platform has its own directory tree in the
drivers hierarchy. This has tended to happen when
kernel development for that platform has proceeded alongside the main
source tree without being merged for a while. In these cases, keeping
the directory tree separate helped in maintaining the code. Examples
include drivers/acorn (old ARM-based computers),
drivers/macintosh,
drivers/sgi (Silicon Graphics workstations), and
drivers/s390 (IBM mainframes). There is little of
value, usually, in looking at that code, unless you are interested in
the specific platform.
There are other subdirectories in drivers, but
they are, in our opinion, currently of minor interest and very
specific use. drivers/mtd implements a Memory
Technology Device layer, which is used to manage solid-state disks
(flash memories and other kinds of
EEPROM). drivers/i2c offers an implementation of
the i2c protocol, which is the "Inter Integrated Circuit" two-wire
bus used internally by several modern peripherals, especially frame
grabbers. drivers/i2o, similarly, handles I2O
devices (a proprietary high-speed communication standard for certain
PCI devices, which has been unveiled under pressure from the free
software community). drivers/pnp is a collection
of common ISA Plug-and-Play code from various drivers, but fortunately
the PnP hack is not really used nowadays by manufacturers.
Under drivers/ you also find initial support for
new device classes that are currently implemented by a very small
range of devices.
That's the case for fiber channel support
(drivers/fc4) and
drivers/telephony. There's even an empty
directory drivers/misc, which claims to be "for
misc devices that really don't fit anywhere else." The directory is
empty of code, but hosts an (empty) makefile with the comment just
quoted.
Although we consider it unlikely, it may even happen that 2.6 or 3.0
will turn out to be pretty different from 2.4; unfortunately, this
edition of the book won't automatically update itself to cover the new
releases and will become obsolete over time. Despite our best efforts
to cover the current version of the kernel, both in this chapter and
in the whole book, there's no substitute for direct reference to the
source code.