在 第六章, 我们通过 systemd 的源码包安装好了 udev。在开始了解它是如何工作之前,我们先来简要的回顾下以前处理设备的方法。
传统的 Linux 不管硬件是否真实存在,都以创建静态设备的方法来处理硬件,因此需要在 /dev
目录下创建大量的设备节点文件(有时会有上千个)。这通常由
MAKEDEV
脚本完成,它通过大量调用 mknod
程序为这个世界上可能存在的每一个设备建立对应的主设备号和次设备号。
而使用 udev
方法,只有当内核检测到硬件接入,才会建立对应的节点文件。因为需要在系统启动的时候重新建立设备节点文件,所以将它存储在
devtmpfs
文件系统中(完全存在于内存中的虚拟文件系统)。设备节点文件无需太多的空间,所以占用的内存也很小。
7.3.1. 历史
2000 年 2 月,一种名叫 devfs
的文件系统被合并到 2.3.46 内核版本,2.4 系列的稳定内核中基本可用。尽管它存在于内核的源代码中,但是这种动态创建设备的方法却从来都得不到核心内核开发者的大力支持。
问题存在于它处理设备的检测、创建和命令的方式,其中最大的问题莫过于它对设备节点的命名方式。大部分开发者的观点是,设备的命名应该由系统的所有者决定,而不是开发者。
devfs
存在一个严重的设计缺陷:它存在严重的 race conditions
问题,如不对内核做大量的修改就无法修正这一问题。最终,因为缺乏有效的维护,在 2006 年 6 月终被移出内核源代码。
再后来,随着非稳定的 2.5 版本的内核开发,到稳定的 2.6 内核,又出现了一种全新的虚拟文件系统
sysfs
。sysfs
的工作是将系统的硬件配置导入到用户空间进程。通过对用户空间可视化的改善,代替devfs
变得更加现实。
7.3.2. Udev 实现
7.3.2.1. Sysfs
上文简单的提及了 sysfs
文件系统。有些人可能会问,sysfs
到底是如何知道当前系统有哪些设备,这些设备又该使用什么设备号呢。对于那些已经编译进内核的设备,会在内核检测到时直接注册为
sysfs
对象(devtmpfs
内建)。对于编译为内核模块的设备,将会在模块载入的时候注册。一旦
sysfs
文件系统挂载到 /sys,已经在
sysfs
注册的硬件数据就可以被用户空间的进程使用,
udevd 也就能够处理了(包括对设备节点进行修改)。
7.3.2.2. 设备节点的创建
设备文件是通过内核中的 devtmpfs
文件系统创建的。任何想要注册的设备都需要通过 devtmpfs
(通过驱动程序核心)实现。每当一个 devtmpfs
实例挂载到 /dev
,就会建立一个设备节点文件,它拥有固定
的名称、权限、所有者。
很短的时间之后,内核将给 udevd
一个 uevent。基于 /etc/udev/rules.d
,/lib/udev/rules.d
和 /run/udev/rules.d
目录内文件指定的规则,udevd
将会建立到设备节点文件的额外符号链接,这有可能更改其权限,所有者,所在组,或者是更改 udevd 内建接口(名称)。
这三个目录下的规则都像 LFS-Bootscripts 包那样标有数字,所有三个目录都会统一到一起。如果
udevd 找不到和所创建设备相应的规则,它会保留
devtmpfs
里初始化时使用过的权限和属主。
7.3.2.3. 加载模块
编译成模块的设备驱动可能会包含别名。别名可以通过
modinfo
打印出来查看,一般是模块支持的特定总线的设备描述符。举个例子,驱动
snd-fm801
支持厂商 ID 为 0x1319 以及设备 ID 为 0x0801 的设备,它包含一个
“pci:v00001319d00000801sv*sd*bc04sc01i*”
的别名,总线驱动导出该驱动别名并通过 sysfs
处理相关设备。例如,文件 /sys/bus/pci/devices/0000:00:0d.0/modalias
应该会包含字符串 “pci:v00001319d00000801sv00001319sd00001319bc04sc01i00”。Udev
采用的默认规则会让 udevd 根据 uevent 环境变量
MODALIAS
的内容(它应该和 sysfs 里的 modalias
文件内容一样)调用
/sbin/modprobe,这样就可以加载在通配符扩展后能和这个字符串一致的所有模块。
在这个例子里,意味着,除了 snd-fm801 之外,一个已经废弃的(不是我们所希望的)驱动 forte 如果存在的话也会被加载。下面有几种可以避免加载多余驱动的方式。
内核本身也能够根据需要加载网络协议,文件系统以及 NLS 支持模块。
7.3.2.4. 处理热插拔/动态设备
在你插入一个设备时,例如一个通用串行总线(USB)MP3 播放器,内核检测到设备已连接就会生成一个 uevent。这个 uevent 随后会被上面所说的 udevd 处理。
7.3.3. 加载模块和创建设备时可能碰到的问题
在自动创建设备节点时可能会碰到一些问题。
7.3.3.1. 内核模块没有自动加载
Udev 只会加载包含有特定总线别名而且已经被总线驱动导出到 sysfs
下的模块。在其它情况下,你应该考虑用其它方式加载模块。采用 Linux-3.19,Udev 可以加载编写合适的 INPUT、IDE、PCI、USB、SCSI、SERIO 和 FireWire 设备驱动。
要确定你希望加载的驱动是否支持 Udev,可以用模块名字作为参数运行 modinfo。然后查看 /sys/bus
下的设备目录里是否有个 modalias
文件。
如果在 sysfs
下能找到 modalias
文件,那么就能驱动这个设备并可以直接操作它,但是如果该文件里没有包含设备别名,那意味着这个驱动有问题。我们可以先尝试不依靠 Udev 直接加载驱动,等这个问题以后解决。
如果在 /sys/bus
下的相应目录里没有 modalias
的话,意味着内核开发人员还没有为这个总线类型增加 modalias 支持。使用 Linux-3.19 内核,应该是 ISA 总线的问题。希望这个可以在后面的内核版本里得到解决。
Udev 不会尝试加载类似 snd-pcm-oss 这样的“封装”驱动,也不会加载类似 loop 这样的非硬件驱动。
7.3.3.2. 内核驱动没有自动加载,Udev 也没有尝试加载
如果是 “封装” 模块只是强化其它模块的功能(比如,snd-pcm-oss 模块通过允许 OSS 应用直接访问声卡的方式加强了 snd-pcm 模块的功能),需要配置 modprobe 在 Udev 加载硬件驱动模块后再加载相应的封装模块。可以在任意 /etc/modprobe.d/
文件里增加一行 “softdep”,例如:
<filename>
.conf
softdep snd-pcm post: snd-pcm-oss
请注意 “softdep” 也支持 pre:
的依赖方式,或者混合 pre:
和 post:
。查看 modprobe.d(5)
手册了解更多关于 “softdep” 语法和功能的信息。
如果问题模块不是一个封装而且也是有用的,配置 modules 开机脚本在引导系统的时候加载模块。这样需要把模块名字添加到 /etc/sysconfig/modules
文件里的单独一行。这个也可以用于封装模块,但是只是备用方式。
7.3.3.3. Udev 加载了一些无用模块
要么不要编译该模块,或者把它加入到模块黑名单 /etc/modprobe.d/blacklist.conf
里,像下面的例子里屏蔽了 forte 模块:
blacklist forte
被屏蔽的模块仍然可以用 modprobe 强行加载。
7.3.3.4. Udev 创建了错误的设备节点,或错误的软链接
这个情况通常是因为设备匹配错误。例如,一条写的不好的规则可能同时匹配到 SCSI 磁盘(希望加载的)和对应厂商的 SCSI 通用设备(错误的)。找出这条问题规则,并通过 udevadm info 命令的帮助改得更具体一些。
7.3.3.5. Udev 规则工作不可靠
这可能是上个问题的另一种表现形式。如果不是,而且你的规则使用了 sysfs
特性,那可能是内核时序问题,希望在后面版本内核里能解决。目前的话,你可以暂时建立一条规则等待使用的 sysfs
特性,并附加到 /etc/udev/rules.d/10-wait_for_sysfs.rules
文件里(如果没有这个文件就创建一个)。如果你碰到这种情形请通知 LFS 开发邮件列表,这个对我们有帮助。
7.3.3.6. Udev 没有创建设备
后面的内容会假设驱动已经静态编译进内核或已经作为模块加载,而且你也已经确认 Udev 没有创建相应的设备节点。
如果内核驱动没有将自己的数据导出到 sysfs
里,Udev 就没有相关信息来创建设备节点。这通常发生在内核树之外的第三方驱动里。我们可以使用合适的主/副设备数字 ID(查看内核文档里或第三方驱动厂商提供的文档里的 devices.txt
文件) 在 /lib/udev/devices
目录里创建一个静态设备节点。这个静态设备节点随后会被 udev 引导脚本复制到 /dev
里。
7.3.3.7. 设备名称顺序在重启后随机改变
这是因为 Udev 被设计成并行处理 uevents 并加载模块,所以是不可预期的顺序。这个不会“修复”。你不应该依赖稳定的内核模块名称。而是,在检测到设备的稳定特征,比如序列号或 Udev 安装的一些 *_id 应用的输出,来判断设备的稳定名称,之后创建自己的规则生成相应的软链接。可以参考 7.4, “创建自定义符号链接到设备”和7.2, “通用网络设置”。
7.3.4. 有用的读物
点击以下链接可以获得一些额外的帮助文档: