设备文件是Linux/Unix系统中特殊的文件类型,它们作为用户空间程序访问硬件设备或内核功能的抽象接口,这些文件(如/dev/sda, /dev/ttyS0)并非存储数据,而是代表内核中的设备驱动程序对象,程序通过标准的文件操作(如open, read, write)与底层硬件或内核服务进行交互。
在 Linux 系统中,设备文件(通常位于 /dev
目录下)是用户空间程序与硬件设备(如磁盘、键盘、鼠标、声卡)或内核提供的虚拟设备(如 /dev/null
, /dev/random
, /dev/loop*
)进行交互的关键接口,理解其创建机制对于系统管理和开发至关重要。
首先必须明确:设备文件本身并不是“设备”,它们是内核中注册的设备驱动程序所管理的实际设备或虚拟设备的用户空间访问点,创建设备文件本质上是在内核识别并准备好设备驱动后,在文件系统中创建一个代表该内核设备对象的入口点。
创建设备文件的两种主要方式
-
手动创建:
mknod
命令 (历史遗留/特殊场景)- 原理: 这是最原始的方法,直接调用
mknod
系统调用在文件系统中创建一个设备节点(字符设备或块设备)。 - 命令语法:
mknod [OPTION]... NAME TYPE [MAJOR MINOR]
- 关键参数:
NAME
: 要创建的设备文件名(通常包含完整路径,如/dev/mydevice
)。TYPE
:c
或u
: 创建字符设备 (Unbuffered, 按字节流访问,如终端、串口)。b
: 创建块设备 (Buffered, 按数据块访问,如硬盘分区)。
MAJOR
: 主设备号,这是一个整数,用于标识设备类型或管理该类型设备的驱动程序,SCSI 磁盘的主设备号通常是 8。MINOR
: 次设备号,这是一个整数,用于标识同一驱动程序管理的不同具体设备实例或分区,第一块 SCSI 硬盘的第一个分区次设备号是 1。
- 示例:
- 创建一个名为
/dev/my_char_dev
的字符设备,主设备号 250,次设备号 0:sudo mknod /dev/my_char_dev c 250 0
- 创建一个名为
/dev/my_block_dev
的块设备,主设备号 240,次设备号 1:sudo mknod /dev/my_block_dev b 240 1
- 创建一个名为
- 重要限制与注意事项:
- 内核驱动是前提:
mknod
仅仅创建了文件系统节点,它不会加载驱动或使设备可用,对应的内核驱动必须已经加载,并且该主/次设备号组合必须在内核中有效注册(通常由驱动在初始化时调用register_chrdev
或类似函数完成),如果内核中没有对应的驱动注册这个设备号,访问这个手动创建的节点会失败(通常报错ENODEV
或ENXIO
)。 - 权限: 创建后通常需要手动设置权限 (
chmod
,chown
) 以便用户或程序访问。 - 持久性: 手动创建的节点在重启后会消失(因为
/dev
通常是内存文件系统tmpfs
),需要脚本在启动时重新创建。 - 现代系统不推荐: 在现代 Linux 发行版中,强烈不推荐手动使用
mknod
来管理标准硬件设备,原因如下:- 主次设备号分配复杂且易冲突。
- 无法动态响应设备的插拔(热插拔)。
- 管理繁琐,容易出错。
- 内核驱动是前提:
- 原理: 这是最原始的方法,直接调用
-
自动动态创建:
udev
系统 (现代标准方式)- 原理:
udev
是 Linux 内核(2.6 及以后版本)的用户空间设备管理器,它是现代 Linux 系统/dev
目录内容动态创建和管理的核心机制。 - 工作流程:
- 内核事件: 当内核检测到硬件设备发生变化时(开机检测、热插拔 USB、加载内核模块等),内核会通过
netlink
套接字(通常是uevent
)向用户空间发送一个事件,这个事件包含设备标识符(如DEVPATH
)、子系统(如usb
,block
,input
)、属性(如厂商 IDID_VENDOR_ID
、产品 IDID_MODEL_ID
、序列号ID_SERIAL
)等关键信息。 udevd
守护进程:udev
守护进程 (udevd
) 持续监听这些内核事件。- 规则匹配:
udevd
根据/lib/udev/rules.d/
和/etc/udev/rules.d/
目录下的一系列规则文件(按数字顺序读取)进行匹配,这些规则文件定义了如何根据事件中的属性(ATTR{...}
,ENV{...}
,KERNEL
等)来识别设备。 - 节点创建与命名: 当规则匹配成功时,
udev
会执行规则中定义的操作:- 创建设备节点 (
mknod
的封装): 在/dev
下创建对应的字符或块设备文件。udev
知道设备的主次设备号(从内核事件或sysfs
中获取),并自动处理创建。 - 设置权限和所有权: 使用
MODE="0666"
,GROUP="video"
,OWNER="root"
等指令设置文件权限和属主/属组。 - 创建符号链接: 使用
SYMLINK+="descriptive_name"
创建更友好、持久或有意义的符号链接(如/dev/disk/by-uuid/xxxx
,/dev/serial/by-id/usb-FTDI_FT232R_USB_UART_...
)。 - 触发脚本: 使用
RUN+="/path/to/script"
执行自定义脚本(如加载固件、通知其他服务)。 - 设置环境变量: 使用
ENV{KEY}="value"
。
- 创建设备节点 (
- 节点删除: 当设备被移除时,
udev
也会收到事件,并自动删除/dev
下对应的设备节点和它创建的符号链接。
- 内核事件: 当内核检测到硬件设备发生变化时(开机检测、热插拔 USB、加载内核模块等),内核会通过
- 如何“创建”设备文件(开发者/管理员视角):
- 编写内核驱动: 设备文件存在的根本是内核驱动,驱动代码中必须:
- 定义设备类型(字符设备
struct cdev
或块设备)。 - 在初始化时调用
alloc_chrdev_region
或register_chrdev_region
(动态或静态分配主设备号) 和cdev_init
/cdev_add
(字符设备) 向内核注册设备及其主次设备号。 - 实现必要的文件操作函数集 (
struct file_operations
)。
- 定义设备类型(字符设备
- 创建设备类 (推荐): 在
/sys/class/
下创建一个设备类 (class_create
),并在驱动中为每个设备实例在该类下创建设备属性 (device_create
),这会在sysfs
中创建条目,udev
会自动检测到并触发规则匹配。 - 编写
udev
规则 (可选但重要):- 如果你需要自定义设备节点的名称(而不是默认的
sda
,ttyUSB0
等)、权限、符号链接或执行特定动作,就需要编写自定义udev
规则。 - 规则文件通常放在
/etc/udev/rules.d/
目录下,文件名以数字开头(如99-mydevice.rules
),数字越大优先级越高(覆盖低优先级规则)。 - 示例规则: 为特定 USB 转串口适配器(通过厂商和产品 ID 识别)创建一个固定的符号链接
/dev/ttyMyDevice
并设置组权限:SUBSYSTEM=="tty", ATTRS{idVendor}=="0403", ATTRS{idProduct}=="6001", SYMLINK+="ttyMyDevice", GROUP="dialout", MODE="0660"
- 应用规则: 保存规则文件后,通常需要重新加载规则并触发事件:
sudo udevadm control --reload-rules && sudo udevadm trigger
。
- 如果你需要自定义设备节点的名称(而不是默认的
- 编写内核驱动: 设备文件存在的根本是内核驱动,驱动代码中必须:
- 优点:
- 动态管理: 自动响应设备添加/移除。
- 持久命名: 通过唯一标识符(序列号、UUID、路径等)创建稳定符号链接,避免设备名(如
eth0
,sda
)因加载顺序变化而改变。 - 灵活配置: 规则系统允许高度定制节点名称、权限、链接和动作。
- 用户空间处理: 将设备节点管理从内核移出,更安全、灵活。
- 无需手动分配设备号: 驱动通常动态申请主设备号,
udev
自动获取。
- 原理:
总结与最佳实践
- 内核驱动是基石: 任何设备文件的存在都依赖于内核中已加载并正确注册了主次设备号的驱动程序。
udev
是标准方式: 对于绝大多数情况(包括标准硬件、USB设备、虚拟机设备等),设备文件的创建、命名、权限设置和删除都是由udev
系统自动、动态完成的,管理员和开发者主要通过编写内核驱动和(可选的)udev
规则来影响这个过程。mknod
用途有限: 手动使用mknod
命令在现代 Linux 系统上主要用于:- 创建一些特殊的、内核已提供但
udev
默认不创建的虚拟设备节点(虽然这种情况很少见)。 - 在嵌入式开发或非常特殊的环境中进行低级测试或调试。
- 理解设备文件底层原理的教学演示。
- 创建一些特殊的、内核已提供但
- 优先使用
udev
规则: 如果需要自定义设备节点的行为(名称、权限、链接),永远优先考虑编写udev
规则,而不是手动mknod
加启动脚本。udev
提供了强大、标准且可维护的管理方式。 - 检查
/sys
(sysfs
):/sys
文件系统是内核导出设备、驱动、总线信息的窗口。udev
主要依赖/sys
中的信息,查看/sys/class/
,/sys/bus/
,/sys/devices/
下的内容有助于理解设备层次结构和属性,对编写驱动和udev
规则至关重要。
引用说明:
- Linux 内核文档 (
Documentation/admin-guide/devices.txt
– 传统静态设备号列表,仅供参考,动态分配是主流) udev
手册页 (man 7 udev
,man udevadm
,man udev.rules
)- Linux 设备驱动开发相关书籍与资料 (如 Linux Device Drivers, 3rd Edition)
mknod
命令手册页 (man mknod
)- Linux 内核源码 (驱动注册相关函数如
register_chrdev_region
,alloc_chrdev_region
,cdev_add
,class_create
,device_create
)
原创文章,发布者:酷番叔,转转请注明出处:https://cloud.kd.cn/ask/8935.html