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). Our ultimate aim is to write a modularized char driver, but we won't talk about modularization issues in this chapter. 本章的目标是编写一个完整的字符设备驱动程序。 我们开发了一个字符驱动程序,因为这个类适用于大多数简单的硬件设备。 字符驱动程序也比块驱动程序或网络驱动程序(我们将在后面的章节中介绍)更容易理解。 我们的最终目标是编写一个模块化的字符驱动程序,但是本章我们不会讨论模块化问题。
Throughout the chapter, we present code fragments extracted from a real device driver: scull (Simple Character Utility for Loading Localities). scull is a char driver that acts on a memory area as though it were a device. In this chapter, because of that peculiarity of scull, we use the word device interchangeably with "the memory area used by scull." 在本章中,我们展示了从真实设备驱动程序中提取的代码片段:scull(用于加载位置的简单字符实用程序)。 scull 是一个字符驱动程序,它作用于内存区域,就好像它是一个设备一样。 在本章中,由于 scull 的特殊性,我们将“设备”一词与“scull 使用的内存区域”互换使用。
The advantage of scull is that it isn't hardware dependent. scull just acts on some memory, allocated from the kernel. Anyone can compile and run scull, and scull is portable across the computer architectures on which Linux runs. On the other hand, the device doesn't do anything "useful" other than demonstrate the interface between the kernel and char drivers and allow the user to run some tests. scull 的优点是它不依赖于硬件。 scull 只是作用于一些从内核分配的内存。 任何人都可以编译和运行 scull,并且 scull 可以跨 Linux 运行的计算机体系结构移植。 另一方面,除了演示内核和字符驱动程序之间的接口并允许用户运行一些测试之外,该设备没有做任何“有用”的事情。
The Design of scull
The first step of driver writing is defining the capabilities (the mechanism) the driver will offer to user programs. Since our "device" is part of the computer's memory, we're free to do what we want with it. It can be a sequential or random-access device, one device or many, and so on. 编写驱动程序的第一步是定义驱动程序将提供给用户程序的功能(机制)。 由于我们的“设备”是计算机内存的一部分,我们可以自由地用它做我们想做的事。 它可以是顺序或随机访问设备、一个或多个设备,等等。
To make scull useful as a template for writing real drivers for real devices, we'll show you how to implement several device abstractions on top of the computer memory, each with a different personality. 为了使 scull 成为为真实设备编写真实驱动程序的模板,我们将向您展示如何在计算机内存之上实现几个设备抽象,每个抽象都有不同的个性。
The scull source implements the following devices. Each kind of device implemented by the module is referred to as a type . scull 源代码实现了以下设备。 模块实现的每一种设备都称为一个类型。
scull0 to scull3
Four devices, each consisting of a memory area that is both global and persistent. Global means that if the device is opened multiple times, the data contained within the device is shared by all the file descriptors that opened it. Persistent means that if the device is closed and reopened, data isn't lost. This device can be fun to work with, because it can be accessed and tested using conventional commands, such as cp, cat, and shell I/O redirection. 四个设备,每个设备都包含一个全局和持久的内存区域。 全局意味着如果设备被多次打开,则设备中包含的数据由打开它的所有文件描述符共享。 持久意味着如果设备关闭并重新打开,数据不会丢失。 使用此设备可能很有趣,因为可以使用常规命令访问和测试它,例如 cp、cat 和 shell I/O 重定向。
scullpipe0 to scullpipe3
Four FIFO (first-in-first-out) devices, which act like pipes. One process reads what another process writes. If multiple processes read the same device, they contend for data. The internals of scullpipe will show how blocking and nonblocking read and write can be implemented without having to resort to interrupts. Although real drivers synchronize with their devices using hardware interrupts, the topic of blocking and nonblocking operations is an important one and is separate from interrupt handling (covered in Chapter 10). 四个 FIFO(先进先出)设备,其作用类似于管道。 一个进程读取另一个进程写入的内容。 如果多个进程读取同一个设备,它们就会争用数据。 scullpipe 的内部结构将展示如何在不求助于中断的情况下实现阻塞和非阻塞读写。 尽管真正的驱动程序使用硬件中断与他们的设备同步,但阻塞和非阻塞操作的主题是一个重要的主题,并且与中断处理(在第 10 章中介绍)是分开的。
scullsingle
scullpriv
sculluid
scullwuid
These devices are similar to scull0 but with some limitations on when an open is permitted. The first (scullsingle) allows only one process at a time to use the driver, whereas scullpriv is private to each virtual console (or X terminal session), because processes on each console/terminal get different memory areas. sculluid and scullwuid can be opened multiple times, but only by one user at a time; the former returns an error of "Device Busy" if another user is locking the device, whereas the latter implements blocking open. These variations of scull would appear to be confusing policy and mechanism, but they are worth looking at, because some real-life devices require this sort of management. 这些设备类似于 scull0,但对何时允许打开有一些限制。 第一个(scullsingle)一次只允许一个进程使用驱动程序,而 scullpriv 对每个虚拟控制台(或 X 终端会话)是私有的,因为每个控制台/终端上的进程获得不同的内存区域。 sculluid 和 scullwuid 可以多次打开,但一次只能由一个用户打开; 如果另一个用户正在锁定设备,前者返回“设备忙”错误,而后者实现阻塞打开。 scull 的这些变化似乎会混淆策略和机制,但它们值得一看,因为一些现实生活中的设备需要这种管理。
Each of the scull devices demonstrates different features of a driver and presents different difficulties. This chapter covers the internals of scull0 to scull3; the more advanced devices are covered in Chapter 6. scullpipe is described in the section Section 3.4 and the others are described in Section 6.6每个scull设备都展示了驱动程序的不同特征并提出了不同的困难。 本章介绍了 scull0 到 scull3 的内部结构; 更高级的设备在第 6 章中介绍。 scullpipe 在第 3.4 节中描述,其他设备在第 6.6 节中描述
Major and Minor 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. Special files for char drivers are identified by a "c" in the first column of the output of ls -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. 通过文件系统中的名称访问字符设备。 这些名称称为特殊文件或设备文件,或简称为文件系统树的节点; 它们通常位于 /dev 目录中。 用于 char 驱动程序的特殊文件由 ls -l 输出的第一列中的“c”标识。 块设备也出现在 /dev 中,但它们由“b”标识。 本章的重点是字符设备,但以下大部分信息也适用于块设备。
If you 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. 如果您发出 ls -l 命令,您将在最后一次修改日期之前的设备文件条目中看到两个数字(用逗号分隔),通常显示文件长度。 这些数字是特定设备的主要和次要设备号。 下面的清单显示了一些设备,它们出现在典型系统上。 它们的主要数字是 1、4、7 和 10,而次要数字是 1、3、5、64、65 和 129。
crw-rw-rw- 1 root root 1, 3 Apr 11 2002 null
crw------- 1 root root 10, 1 Apr 11 2002 psaux
crw------- 1 root root 4, 1 Oct 28 03:04 tty1
crw-rw-rw- 1 root tty 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 root root 1, 5 Apr 11 2002 zero
Traditionally, the major number identifies the driver associated with the device. For example, /dev/null and /dev/zero are both managed by driver 1, whereas virtual consoles and serial terminals are managed by driver 4; similarly, both vcs1 and vcsa1 devices are managed by driver 7. Modern Linux kernels allow multiple drivers to share major numbers, but most devices that you will see are still organized on the one-major-one-driver principle. 传统上,主要编号标识与设备关联的驱动程序。 例如,/dev/null 和 /dev/zero 都由驱动程序 1 管理,而虚拟控制台和串行终端由驱动程序 4 管理; 同样,vcs1 和 vcsa1 设备都由驱动程序 7 管理。现代 Linux 内核允许多个驱动程序共享主设备号,但您将看到的大多数设备仍然是按照一个主设备一驱动程序原则组织的。
The minor number is used by the kernel to determine exactly which device is being referred to. Depending on how your driver is written (as we will see below), you can either get a direct pointer to your device from the kernel, or you can use the minor number yourself as an index into a local array of devices. Either way, the kernel itself knows almost nothing about minor numbers beyond the fact that they refer to devices implemented by your driver. 内核使用次要设备号来准确确定所引用的设备。 根据驱动程序的编写方式(如下所示),您可以从内核获取指向设备的直接指针,也可以自己使用次要编号作为本地设备数组的索引。 无论哪种方式,内核本身对次要数字几乎一无所知,除了它们指的是驱动程序实现的设备这一事实。
The Internal Representation of Device Numbers
Within the kernel, the dev_t type (defined in
MAJOR(dev_t dev);
MINOR(dev_t dev);
If, instead, you have the major and minor numbers and need to turn them into a dev_t, use: 相反,如果您有主编号和次编号并需要将它们转换为 dev_t,请使用:
MKDEV(int major, int minor);
Note that the 2.6 kernel can accommodate a vast number of devices, while previous kernel versions were limited to 255 major and 255 minor numbers. One assumes that the wider range will be sufficient for quite some time, but the computing field is littered with erroneous assumptions of that nature. So you should expect that the format of dev_t could change again in the future; if you write your drivers carefully, however, these changes will not be a problem. 请注意,2.6 内核可以容纳大量设备,而以前的内核版本仅限于 255 个主要编号和 255 个次要编号。 人们假设更广泛的范围在相当长的一段时间内就足够了,但计算领域充斥着这种性质的错误假设。 所以你应该预料到 dev_t 的格式将来可能会再次改变; 但是,如果您仔细编写驱动程序,这些更改将不会成为问题。