Linux 设备节点
概述
A device node, device file, or device special file is a type of special file used on many Unix-like operating systems, including Linux.
设备节点、设备文件或设备专用文件是一种在许多类 Unix 操作系统(包括 Linux)上使用的专用文件。
Device nodes facilitate transparent communication between user space applications and computer hardware.
设备节点促进用户空间应用程序和计算机硬件之间的透明通信。
By definition, device nodes correspond to resources that have already been allocated by the operating system Kernel. The resources are identified by a major number and a minor number, which are stored as part of the structure of a node. The assignment of these numbers is specific to different operating systems and computer platforms. Generally, the major number identifies the device driver and the minor number identifies a particular device (possibly out of many) that the driver controls, and is passed to the driver as an argument.
根据定义,设备节点对应于操作系统内核已经分配的资源。 资源由主要编号和次要编号标识,它们作为节点结构的一部分存储。 这些编号的分配特定于不同的操作系统和计算机平台。 通常,主要编号标识设备驱动程序,次要编号标识驱动程序控制的特定设备(可能是许多),并作为参数传递给驱动程序。
Like other special file types, device nodes are accessed using standard system calls and treated like regular files.
与其他特殊文件类型一样,设备节点使用标准系统调用进行访问,并被视为常规文件。
设备节点
Linux 下的设备通常分为三类:字符设备、块设备和网络设备。相对应的,驱动程序也分为三类,即字符设备驱动程序、块设备驱动程序和网络设备驱动程序。
- 常见的字符设备有鼠标、键盘、串口、控制台等,它们的特点是只能顺序访问。
- 常见的块设备有各种磁盘、硬盘、Flash、RAM 等,它们的特点是可以随机访问。
- Linux 中的网络设备是比较特殊的,一个网络设备通常也称为一个网络接口(如 eth0),应用程序并非通过设备节点而是通过 socket 来访问网络设备。
其实在 Linux 系 统中根本就不存在网络设备节点(通过 cat /proc/devices
查看只有 Character devices 和 Block devices)。网络接口没有像字符设备和块设备一样的设备号,只有一个唯一的名字,如 eth0、eth1 等,而这个名字也不需要与设备文件节点对应。
设备节点被创建在 /dev 下,是连接内核与用户层的枢纽,就是设备是接到对应哪种接口的哪个 ID 上。 相当于硬盘的 inode一样的东西,记录了硬件设备的位置和信息。在Linux中,所有设备都以文件的形式存放在 /dev 目录下,都是通过文件的方式进行访问,设备节点是 Linux 内核对设备的抽象,一个设备节点就是一个文件。应用程序通过一组标准化的调用执行访问设备,这些调用独立于任何特定的驱动程序。而驱动程序负责将这些标准调用映射到实际硬件的特有操作。
在 /dev 目录下除了字符设备和块设备节点之外,还通常还会存在 FIFO 管道、Socket、软/硬连接、目录等,不过这些东西没有主/次设备号。
主设备号和次设备号
在 Linux 系统中,对字符设备的访问是通过文件系统内的设备名称进行的,设备文件位于 /dev 目录,例如 /dev/device0。
当执行 ls -l
命令查看设备文件时,可以看到字符设备文件用字符 'c' 标识,块设备文件用字符 'b' 标识。对于设备文件,还会显示出相应设备的主设备号和次设备号。
例如,当前系统有四个 /dev/ttyUSB* 字 符设备,主设备号为 188,次设备号依次为 0 至 3。
$ ls -l /dev/ttyUSB*
crw-rw---- 1 root dialout 188, 0 6月 23 10:00 /dev/ttyUSB0
crw-rw---- 1 root dialout 188, 1 6月 23 09:26 /dev/ttyUSB1
crw-rw---- 1 root dialout 188, 2 6月 23 10:59 /dev/ttyUSB2
crw-rw---- 1 root dialout 188, 3 6月 23 09:26 /dev/ttyUSB3
对于块设备,同样有主设备号和次设备号。
$ ls -l /dev/sda*
brw-rw---- 1 root disk 8, 0 6月 22 08:51 /dev/sda
brw-rw---- 1 root disk 8, 1 6月 22 08:51 /dev/sda1
brw-rw---- 1 root disk 8, 2 6月 22 08:51 /dev/sda2
通常,主设备号标识设备对应的驱动程序。现在的 Linux 内核允许多个驱动程序共享主设备号,但大多数设备仍遵守「一个主设备号对应一个驱动程序」的原则。次设备号则由 Linux 内核使用,用于确定设备文件所指的设备。
设备节点,驱动,硬件设备是如何关联到一起的呢?
这是通过设备号实现的,包括主设备号和次设备号。当我们创建一个设备节点时需要指定主设备号和次设备号。应用程序通过名称访问设备,而设备号指定了对应的驱动程序和对应的设备。主设备号标识设备对应的驱动程序,次设备号由内核使用,用于确定设备节点所指设备。
- 主设备号(major number):驱动程序在初始化时,会注册它的驱动及对应主设备号到系统中,这样当应用程序访问设备节点时,系统就知道它所访问的驱动程序了。你可以通过 /proc/devices 文件来查看系统设备的主设备号。
- 次设备号(minor number):驱动程序遍历设备时,每发现一个它能驱动的设备,就创建一个设备对象,并为其分配一个次设备号以区分不同的设备。这样当应用程序访问设备节点时驱动程序就可以根据次设备号知道它说访问的设备了。
- 设备节点(设备文件):Linux中设备节点是通过“mknod”命令来创建的。一个设备节点其实就是一个文件,Linux 中称为设备文件。有一点必要说明的是,在 Linux 中,所有的设备访问都是通过文件的方式,一般的数据文件称为普通文件,设备节点则称为设备文件。
- 设备驱动(device driver):也称为设备驱动程序,或简称为驱动(driver),是一个允许高级(High level)计算机软件(computer software)与硬件(hardware)交互的程序,这种程序建立了一个硬件与硬件,或硬件与软件沟通的接口,经由主板上的总线(bus)或其它沟通子系统(subsystem)与硬件形成连接的机制,这样的机制使得硬件设备(device)上的数据交换成为可能。想象平时我们说的写驱动,例如点 led 灯的驱动,就是简单的 io 操作。
设备名称、设备节点和主次设备号
The Linux® kernel represents character and block devices as pairs of numbers <major>:<minor>
.
Some major numbers are reserved for particular device drivers. Other device nodes are dynamically assigned to a device driver when Linux boots. For example, major number 94 is always the major number for DASD devices while the device driver for channel-attached tape devices has no fixed major number. A major number can also be shared by multiple device drivers. See /proc/devices to find out how major numbers are assigned on a running Linux instance.
The device driver uses the minor number <minor>
to distinguish individual physical or logical devices. For example, the DASD device driver assigns four minor numbers to each DASD: one to the DASD as a whole and the other three for up to three partitions.
Device drivers assign device names to their devices, according to a device driver-specific naming scheme. Each device name is associated with a minor number.
User space programs access character and block devices through device nodes also referred to as device special files. When a device node is created, it is associated with a major and minor number.
SUSE Linux Enterprise Server 15 SP3 uses udev to create device nodes for you. Standard device nodes match the device name that is used by the kernel, but different or additional nodes might be created by special udev rules. See SUSE Linux Enterprise Server 15 SP3 Administration Guide and the udev man page for more details.
设备编号的内部表达
设备编码
在 Linux 内核中,dev_t
类型用来保存设备编号(包括主设备号和次设备号),其定义包含在头文件 <linux/types.h>
中。dev_t
是一个 32 位的无符号整型数,其中的 12 位用来表示主设备号,其余 20 位用来表示次设备号。
typedef u32 __kernel_dev_t;
typedef __kernel_dev_t dev_t;
从 dev_t 设备编号中获取主设备号和次设备号可以使用 <linux/kdev_t.h>
中定义的宏 MAJOR
和 MINOR
,反过来可以使用宏 MKDEV
将主次设备号转换成 dev_t 类型数值。
#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))
静态申请设备编号
在建立一个字符设备之前,驱动程序需要获得一个或多个设备编号。如果提前确定所需要的设备编号,可以通过 register_chrdev_region() 函数注册设备编号:
#include <linux/fs.h>
int register_chrdev_region(dev_t first, unsigned int count, char *name);