2.4 从手动 mknod 到 udev 自动出节点


2.4 从手动 mknod 到 udev 自动出节点

本节摘要:设备节点是用户态通向驱动的门牌。本节从手工创建节点的原始做法讲起,剖析它的三类顽疾(易漂移、易出错、难运维),再拆解 udev 依据内核事件自动创建节点的完整链路,最后用一条规则给 LED 设备定制权限与别名。

老一辈系统管理员的抽屉里都压过一本"设备号手册":早期 Unix 系统的设备文件要靠管理员用命令手工创建,主次设备号从厂商文档里查出来,敲错一位,设备文件就指向别人的驱动。那是一本全行业共同维护的户口簿——每家厂商的新设备都得去登记号码。这套流程在设备固定的机房时代勉强够用,到了设备可插拔、号码动态分配的年代,彻底撑不住了。要理解 udev 解决了什么,得先回到手工时代亲手吃一次亏。

手工创建:三步走与三个坑

创建设备节点的命令本身很简单——给定类型与号码,内核文件系统里就多出一个字符设备文件:

$ grep led /proc/devices # 先查驱动动态分到的主设备号 248 led $ sudo mknod /dev/led0 c 248 0 # c 表示字符设备,248 主号,0 次号 $ ls -l /dev/led0 crw-r--r-- 1 root root 248, 0 Sep 3 10:12 /dev/led0 $ echo 1 > /dev/led0 # 灯亮

节点一建,2.3 节的回调立刻可用,灯应声而亮。但这份"能用"经不起推敲。

坑一:号码漂移。主设备号是 alloc_chrdev_region 动态申请的,内核重启、加载顺序变化都可能让 LED 拿到不同的号——今天 248,明天 250,昨晚写进启动脚本的 mknod 命令今天就指向了别人的驱动。

坑二:静态节点失配。为治漂移,有人把节点预先生成进镜像,可动态号驱动装机后才领号,静态节点与实际号码对不上,打开设备报"没有设备"——1.1 节那张责任表里"驱动未注册或节点未创建"说的就是这类惨案。

坑三:运维规模。几十台设备的板子上,每台的节点、权限、属主都要人工维护,漏一条就是用户态程序集体报错。

三个坑指向同一个病根:节点信息(号码)与节点动作(创建)分离,且都依赖人工。udev 的设计正是对症下药:节点跟着内核事件走,事件里带着权威的号码。

udev 链路:事件从内核流向用户态

回看 2.2 节埋的伏笔:device_create 执行时,内核设备模型除了在 sys 文件系统登记,还会向用户态广播一个设备事件——内容涵盖设备名、所在类别、设备号、父设备等。用户态的守护进程接住事件,按规则库处理后创建节点。整条链路:

[驱动] device_create ↓ 内核广播 uevent(含 DEVNAME、MAJOR、MINOR) [用户态] udev 守护进程监听事件 ↓ 匹配规则库:属主、权限、别名、符号链接 [文件系统] /dev/led0 自动出现

驱动侧无需任何额外代码——2.2 节的三步注册已经把事件备好了。验证这条链路只需观察事件与节点的时序:

$ udevadm monitor --env & # 挂上事件监听器 $ sudo insmod led.ko # 装入驱动 UDEV [1024.551023] add /devices/virtual/led/led0 (led) DEVNAME=/dev/led0 MAJOR=248 MINOR=0 SUBSYSTEM=led $ ls -l /dev/led0 # 节点已就位,无需任何手工命令 crw-r--r-- 1 root root 248, 0 Sep 3 10:40 /dev/led0

事件里 MAJOR=248DEVNAME=/dev/led0 一目了然——号码由内核权威发布,漂移问题连根拔掉:无论 LED 这次分到几号,事件里说的是几号,节点就建几号。卸载驱动时对应的"移除"事件到来,节点自动消失,也不留孤儿。

节点诞生的完整时序

节点诞生的完整时序

规则:让节点长成你要的样子

udev 不只是自动 mknod,规则库还允许按事件属性定制节点。假设开发板上的 LED 要交给灯光控制程序访问,规则可以这样写(放在系统的 udev 规则目录,命名以数字开头控制加载顺序):

# /etc/udev/rules.d/70-led.rules # 匹配 LED 子系统下的设备,交给灯光组,起一个好记的别名 SUBSYSTEM=="led", KERNEL=="led0", GROUP="light", MODE="0660", SYMLINK+="led/status"

规则语法是"匹配+赋值":逗号分隔若干键,== 是匹配条件、=+= 是赋值动作。这条规则生效后,节点属主变更为灯光组、权限收紧为组内读写,并多出一个语义化符号链接——用户态程序访问别名,底层设备名变化也不受影响。验证与调试用系统自带的查询工具:

$ udevadm info /dev/led0 | grep -E 'MAJOR|MINOR|SUBSYSTEM' E: MAJOR=248 E: MINOR=0 E: SUBSYSTEM=led $ ls -l /dev/led/status lrwxrwxrwx 1 root root 8 Sep 3 10:47 /dev/led/status -> led0

深挖:事件从内核广播到用户态的通道

udev 收到事件靠的不是轮询,而是一条内核到用户态的专用通道——内核事件套接字。驱动调用的设备登记函数会构造一条事件报文(键值对序列:动作类型、设备路径、子系统、主次设备号、驱动名),通过这条通道组播给所有监听者。用系统的事件监听工具可以原样看到这些报文,第 7 章排错时它还是"确认事件有没有发出来"的利器:

$ udevadm monitor --kernel KERNEL[1055.221344] add /devices/virtual/led/led0 (led) ACTION=add DEVPATH=/devices/virtual/led/led0 SUBSYSTEM=led MAJOR=250 MINOR=0 DEVNAME=led0

三个字段值得记牢:ACTION 说明这是添加还是移除事件;MAJORMINOR 就是创建节点要用的号码;DEVNAME 是内核建议的节点名。整条链路的权威信息源是内核,udev 只是执行者——理解了这一点,"节点没出现"的排查就有了清晰的二分:监听器里能看到事件,问题在 udev 规则或权限;看不到事件,问题在驱动的设备登记步骤(第 7 章案例一的排查路径之一)。

顺带把嵌入式场景的说明补上:小型嵌入式系统有时裁掉完整 udev,改用轻量级的节点管理器,甚至由启动脚本在设备枚举后统一创建节点——机制简化了,但"节点信息以内核事件为准"的原则不变。理解了完整链路,才看得懂这些变体省掉了什么、又补上了什么。

本节要点回顾

  • 手工节点的病根是号码与动作分离:漂移、失配、难运维三条死路。
  • 事件是权威信源:内核广播的 uevent 自带设备号,节点跟着事件走。
  • 驱动侧零成本:注册三步完成后,自动交付是设备模型的赠品。
  • 规则库管定制:权限、属组、别名在规则里声明,节点"生而合身"。

至此 LED 驱动完成了全部落地手续。下一章向硬件侧进发——寄存器怎么映射、中断怎么响应、数据怎么搬运,驱动的"物理课"开始。


作者与出处
原作者: 灏天文库
来源:灏天文库
整理: 灏天文库整理
由灏天文库平台收录,内容或由平台用户上传,仅供学习交流
发布者: 作者: 灏天文库 转发
评论区 (0)
U