这篇文章 UDEV Primer给我们娓娓道来,花点时间预习一下是值得的。当然,不知道udev是什么也没关系,
把它当个助记符好了,有了下面的上路指南,可以节省很多时间。我们只需要树立一个信念:udev很简单!
嵌入式的udev应用尤其简单。
第二、为什么udev要取代devfs?
这是生产关系适应生产力的需要,udev好,devfs坏,用好的不用坏的。
udev是硬件平台无关的,属于user space的进程,它脱离驱动层的关联而建立在操作系统之上,基于这种设
计实现,我们可以随时修改及删除/dev下的设备文件名称和指向,随心所欲地按照我们的愿望安排和管理设
备文件系统,而完成如此灵活的功能只需要简单地修改udev的配置文件即可,无需重新启动操作系统。udev
已经使得我们对设备的管理如探囊取物般轻松自如。
第三、如何得到udev?
udev的主页在这里: http://www.kernel.org/pub/linux/utils/kernel/hotplug/udev.html
我们按照下面的步骤来生成udev的工具程序,以arm-linux为例:
1、wget http://www.us.kernel.org/pub/linux/utils/kernel/hotplug/udev-100.tar.bz2
2、tar xjf udev-100.tar.bz2
3、cd udev-100 编辑Makefile,查找CROSS_COMPILE,修改CROSS_COMPILE ?= arm-linux-
4、make
没有什么意外的话当前目录下生成udev,udevcontrol, udevd, udevinfo,udevmonitor,udevsettle,udevstart,
udevtest,udevtrigger九个工具程序,在嵌入式系统里,我们只需要 udevd和udevstart就能使udev工作得很好,
其他工具则帮助我们完成udev的信息察看、事件捕捉或者更高级的操作。
另外一个方法是直接使用debian提供的已编译好的二进制包,美中不足的是版本老了一些。
1、wget http://ftp.us.debian.org/debian/pool/main/u/udev/udev_0.056-3_arm.deb
2、ar -xf udev_0.056-3_arm.deb
3、tar xzf data.tar.gz
在sbin目录里就有我们需要的 udevd和udevstart工具程序。
建议大家采用第一种方式生成的 udevd和udevstart。为什么要用最新udev呢?新的强,旧的弱,用强的不用弱的。
第四、如何配置udev?
首先,udev需要内核sysfs和tmpfs的支持,sysfs为udev提供设备入口和uevent通道,tmpfs为udev设备文件提
供存放空间,也就是说,在上电之前系统上是没有足够的设备文件可用的,我们需要一些技巧让kernel先引导
起来。
由于在kernel启动未完成以前我们的设备文件不可用,如果使用mtd设备作为rootfs的挂载点,这个时候/dev/mtdblock
是不存在的,我们无法让kernel找到rootfs,kernel只好停在那里惊慌。
这个问题我们可以通过给kernel传递设备号的方式来解决,在linux系统中,mtdblock的主设备号是31,part号
从0开始,那么以前的/dev/mtdblock/3就等同于31:03,以次类推,所以我们只需要修改bootloader传给kernel
的cmd line参数,使root=31:03,就可以让kernel在 udevd未起来之前成功的找到rootfs。
O.K.下一个问题。
其次,需要做的工作就是重新生成rootfs,把 udevd和udevstart复制到/sbin目录。然后我们需要在/etc/下为udev
建立设备规则,这可以说是udev最为复杂的一步。这篇文章提供了最完整的指导: Writing udev rules
文中描述的复杂规则我们可以暂时不用去理会,上路指南将带领我们轻松穿过这片迷雾。这里提供一个由简入
繁的方法,对于嵌入式系统,这样做可以一劳永逸。
1、在前面用到的udev-100目录里,有一个etc目录,里面放着的udev目录包含了udev设备规则的详细样例文
本。为了简单而又简洁,我们只需要用到etc/udev/udev.conf这个文件,在我们的rootfs/etc下建立一个udev目
录,把它复制过去,这个文件很简单,除了注释只有一行,是用来配置日志信息的,嵌入式系统也许用不上
日志,但是 udevd需要检查这个文件。
2、在rootfs/etc/udev下建立一个rules.d目录,生成一个空的配置文件touch etc/udev/rules.d/udev.conf。然后
我们来编辑这个文件并向它写入以下配置项:
###############################################
# vc devices
KERNEL=="tty[0-9]*", NAME="vc/%n"
# block devices
KERNEL=="loop[0-9]*", NAME="loop/%n"
# mtd devices
KERNEL=="mtd[0-9]*", NAME="mtd/%n"
KERNEL=="mtdblock*", NAME="mtdblock/%n"
# input devices
KERNEL=="mice" NAME="input/%k"
KERNEL=="mouse[0-9]*", NAME="input/%k"
KERNEL=="ts[0-9]*", NAME="input/%k"
KERNEL=="event[0-9]*", NAME="input/%k"
# misc devices
KERNEL=="apm_bios", NAME="misc/%k"
KERNEL=="rtc", NAME="misc/%k"
################################################
保存它,我们的设备文件系统基本上就可以了, udevd和udevstart会自动分析这个文件。
3、为了使 udevd在kernel起来后能够自动运行,我们在rootfs/etc/init.d/rcS中增加以下几行:
##################################
/bin/mount -t tmpfs tmpfs /dev
echo "Starting udevd..."
/sbin/ udevd --daemon
/sbin/udevstart
##################################
4、重新生成rootfs,烧写到flash指定的rootfs part中。
5、如果需要动态改变设备规则,可以把etc/udev放到jffs或yaffs part,以备修改,根据需求而定,可以随时扩
充udev.conf中的配置项。
mission completed!
这部分主要是分析了一下udev的source code,对一些自己关心的问题的理解
4.1 Udevd怎么获取内核的这些模块动态变化的信息
设备节点的创建,是通过sysfs接口分析dev文件取得设备节点号,这个非常显而易见。那么 udevd是通过什么机制来得知内核里模块的变化情况,怎么得知设备的插入移除情况呢?当然是通过hotplug机制了,那hotplug又是怎么实现的?或说内核是怎么通知用户空间一个事件的发生的呢?
答案是通过netlink socket通讯,在内核和用户空间之间传递信息。
内核调用kobject_uevent函数发送netlink message给用户空间,这部分工作通常不必驱动去自己处理,在统一设备模型里面,在子系统这一层面,已将这部分代码处理好了,包括在设备对应的特定的Kobject创建和移除的时候都会发送相应add和remove消息,当然前提是你在内核中设置了hotplug的支持。
Netlink socket作为一种内核和用户空间的通信方式,不仅仅用在hotplug机制中,同样还应用在其他非常多真正和网络相关的内核子系统中。
Udevd通过标准的socket机制,创建socket连接来获取内核广播的uevent事件 并解析这些uevent事件。
4.2 Udevd怎么监视规则文件的变更
如果内核版本足够新的话,在规则文件发生变化的时候,udev也能够自动的重新应用这些规则,这得益于内核的inotify机制, inotify是一种文件系统的变化通知机制,如文件增加、删除等事件能即时让用户态得知。
在 udevd中,对inotify和udev的netlink socket文件描述符都进行了select的等待操作。有事件发生以后再进一步处理。
4.3 Udevtrigger的工作机制?
运行 udevd以后,使用udevtrigger的时候,会把内核中已存在的设备的节点创建出来,那么他是怎么做到这一点的? 分析udevtrigger的代码能看出:
udevtrigger通过向/sysfs 文件系统下现有设备的uevent节点写"add"字符串,从而触发uevent事件,使得 udevd能够接收到这些事件,并创建buildin的设备驱动的设备节点及所有已insmod的模块的设备节点。
所以,我们也能手工用命令行来模拟这一过程:
/ # echo "add" > /sys/block/mtdblock2/uevent
/ #
/ # UEVENT[178.415520] add /block/mtdblock2 (block)
不过,进一步看代码,你会发现,实际上,不管你往uevent里面写什么,都会触发add事件,这个从kernel内部对uevent属性的实现函数能看出来,默认的实现是:
static ssize_t store_uevent(struct device *dev, struct device_attribute *attr,
const char *buf, size_t count)
{
kobject_uevent(&dev->kobj, KOBJ_ADD);
return count;
}
所以不管写的内容是什么,都是触发add操作,真遗憾,我还想通过这个属性实验remove的操作。 不知道这样限制的原因是什么。
而udevstart的实现方式和udevtrigger就不同了,他基本上是重复实现了 udevd里面的机制,通过遍历sysfs,自己完成设备节点的创建,不通过 udevd来完成。
4.4 其他
Ø udevd创建每一个节点的时候,都会fork出一个新的进程来独立完成这个节点的创建工作。
Ø Uevent_seqnum 用来标识当前的uevent事件的序号(已产生了多少uevent事件),你能通过如下操作来查看:
$ cat /sys/kernel/uevent_seqnum
2673
the memory that is used 可以忽略。
在2.4版本的kernel中,一种新文件系统called devfs被添加了进去。尽管它在kernel源码中出现,然而这种动态创建设备的方法从未收到压倒性的支持from core kernel开发者;
devfs 的方法主要问题是 the way:设备检测,创建,命名。设备节点命名,可能是最critical的.It is generally accepted that 设备名允许是可配置的,then设备命名策略应该up to一个系统管理员,not 被欺骗by 特殊的开发者。devfs文件系统同时还忍受着一个紊乱情况,为它的design所固有,且不能被fix,若无实质的修改to kernel.现由于缺乏维护已被deprecated.
在2.6版本的kernel中,出现了一种叫sysfs的新虚拟文件系统。sysfs的 任务是export系统架构 to 用户空间进程。有了这种用户空间visible表示法,the possiblity of seeing a userspace replacement for devfs 变得更加现实。
sysfs怎样知道设备出现 在系统?应该使用什么设备号?对于被编进kernel的driver,当被kernel监测到时,直接注册目标with sysfs。使用模块方式编译的,当模块被load时,如前。once sysfs文件系统被mounted (on /sys),the data which the built-in drivers registered with sysfs are available to userspace process and to udev for device node creation.
udev初始化脚本创建这些 设备节点当linux boot时;这个脚本starts with 注册/sbin/udev/ 作为一个 hotplug事件管理者。热插拔事件不应该发生在这个过程中,然而udev is registered just in case they do occur.然后udevstart program walk through the /sys filesystem and 创建符合描述的设备在/dev。例如:/sys/class/tty/vcs/dev/包括string "7:0".这个字符串被udevstart使用来创建/dev/vcs,主设备号7and此设备号0。每一个udevstart创建的设备的权限设置来 自/etc/udev.d/permission.d/目录。这些numbered(有限的) 基本相似LFS bootscripts.如果找不到创建的设备权限文件,默认perissions to 600 and ownership to root:root./dev目录下创建的节点根据 /etc/udev/rules.d/目录下的文件来configured.
当一个新设备连 接被kernel监测到,kernel会产生一个hotplug event 并查
?这带给我们一个问题:exists with udev,and likewise with devfs before it.?就像先有鸡还是先有蛋。大部分linux distrubtions
管 理加载模块通过/etc/modules.conf.access to 设备节点引起相应的kernel模块来加载。然而对于udev,这种方法不能正常工作,因为模块没有加载时,设备节点不存在。为了解决这个问题,模块脚本 加到了lfs-bootscripts包中,和/etc/sysconfig/modules在一起。通过添加module names到module file中,这些模块在计算机启动时被加载。这样,udev就可以去检测设备并创建相应的设备节点。
当 你插入一个设备,比如usb mp3 player,内核辨认出设备连接同时产生一个热插拔事件。如果驱动已经loaded(不管是编进kernel还是通过s05modules bootscript加载),udev将被调用来创建相关的设备节点,根据sysfs data in /sys.如果刚插入的设备驱动以模块形式然而并未加载,那么刚attach to system 的设备只会引起kernel总线驱动产生一个热插拔事件通知用户空间一个新设备的连接and它不attached to a driver.结果,什么都没有发生,device依然不能使用。
如果建立一个system,that具有大量的以模块形式编译的驱动,使用s05modules并不实际。the hotplug package( http://linux-hotplug.sourceforge.net)会显得非常有价值。当此包安装后,它会响应前述的kernel总线驱动hotplug事件。此包将加载相应的模块并为设备创建节点。
自动创建设备节点时常遇到的一些问题
1)A kernel driver may not exports its data to sysfs
当 使用第三方驱动(在kernel source tree 之外)时常遇到这种问题。
这些驱动end up时没有创建设备节点。使用/etc/sysconfig/creatfiles 配置文件to 人工创建设备。参考devices.txt文件(在kernel文档中)或者驱动文档来找出正确的major/minor设备号。
2)无硬件设备 is required.这种很常见with the advanced linux sound architecture(ALSA) project's open sound system(oss) compatibility 模块.这种形式的驱动可以使用以下下面两种方法来管理:
*将module names 加到 /etc/sysconfig/modules;
* 使用"install"line 在/etc/modprobe.conf中。This tells the modprobe command "when loading this module, also load this other module,at the same time."例如
install snd-pcm modprobe -i snd-pcm;modprobe snd-pcm-oss;true
当系统中有加载snd-pcm驱动的请求时,这会使系统加载both snd-pcm and snd-pcm-oss modules.
http://fedora.redhat.com/docs/udev/
http://www.kernel.org/pub/linux/utils/kernel/hotplug/udev.html
http://www.kroah.com/linux/talks/ols_2003_udev_paper/Reprint-Kroah-Hartman-OLS2003.pdf
http://www.kernel.org/pub/linux/utils/kernel/hotplug/udev-FAQ
关于这个规则,有一篇很经典的英文说明 http://www.reactivated.net/writing_udev_rules.html
udev是一种Linux2.6内核采用的/dev 目录的管理系统,它通过从sysfs获得的信息,可以提供对特定设备的固定的设备名。
sysfs是linux 2.6内核的一种新型文件系统,它提供了当前设备的基本信息。
udev的一个重要目的就是提供固定的设备名,像我们刚才碰到的情况,如果ms插入系统,系统能使用固定的设备名
(例如/dev/ms) CF卡插入系统,使用/dev/cf,就可以很方便的解决我们需要到的困难。
/etc/udev/rules.d/ 下面的文件根据字母的顺序来解析,一般udev当找到满足它条件的说明项后就会终于解析过程,
因为为了使用我们的配置优先于系统的默认值,选择文件名时一定要注意,例如,我们选择 /etc/udev/rule.d/10-local.rules
BUS="usb", SYSFS{serial}="HXOLL0012202323480", NAME="lp_epson", SYMLINK="printers/epson_stylus"
上面是一个USB打印机的印子。当一台USB打印机序列号是HXOLL0012202323480,就会创建一个device名 /dev/lpepson,
同时创建一个symbol link /dev/printers/epson_styles
注意:在任何规则修改后,为了让它生效:需要执行udevstart
规则书写格式
最方便的查找方法是 man udev 或在线文档 http://www.die.net/doc/linux/man/man8/udev.8.html
常用的有
* BUS - 匹配总路类型,比如PCI USB等 * KERNEL - 匹配Kernel设备名,比如hda hdb. * DRIVER - 匹配Kernel的驱动程序名 * SUBSYSTEM - 匹配子系统名。 * ID - 匹配总路系统的ID (e.g. PCI bus ID)。 * PLACE - 匹配物理位置 (对USB很有用)。 * SYSFS{filename} - 匹配从sysfs得到的信息,比如label, vendor, USB serial number, SCSI UUID or file system label. * PROGRAM - 调用外部程序。 * RESULT - 匹配最后一次调用外部程序所得到的返回字符串 * NAME - 需要创建的设备或 * SYMLINK -需要创建的符号链接名 * OWNER, GROUP, MODE 设置设备的所有者,组,及模式
匹配符号:
%n 内核设备号 例如 sda3 的3 %k 内核设备名 %M 设备的major号 %m 设备的minor号 %b bus id %c %s{filename} sysfs属性的内容 %% 引用%时使用 * 可以匹配任意个字符 ? 可以匹配一个字符 [ ] 从中选一个字符sample:
# if /sbin/scsi_id returns "OEM 0815" device will be called disk1BUS="scsi", PROGRAM="/sbin/scsi_id", RESULT="OEM 0815", NAME="disk1"# USB printer to be called lp_colorBUS="usb", SYSFS{serial}="W09090207101241330", NAME="lp_color"# SCSI disk with a specific vendor and model number will be called bootBUS="scsi", SYSFS{vendor}="IBM", SYSFS{model}="ST336", NAME="boot%n"# sound card with PCI bus id 00:0b.0 to be called dspBUS="pci", ID="00:0b.0", NAME="dsp"# USB mouse at third port of the second hub to be called mouse1BUS="usb", PLACE="2.3", NAME="mouse1"# ttyUSB1 should always be called pda with two additional symlinksKERNEL="ttyUSB1", NAME="pda", SYMLINK="palmtop handheld"# multiple USB webcams with symlinks to be called webcam0, webcam1, ……BUS="usb", SYSFS{model}="XV3", NAME="video%n", SYMLINK="webcam%n"
查看sysfs的信息
这里使用udevinfo的指令, man udevinfo 在线文档http://www.die.net/doc/linux/man/man8/udevinfo.8.html 基本用法
-a SYSFS{filename} attributes along the device chain. -p sysfs_path Specify the sysfs path of the device to query. -q query_type Query the database for specified value of a created device node or network interface. valid type: name, symlink, mode ,owner , group , path or all. -n name Specify the name of the node, the symlink or the network interface for the device to queue sample:
udevinfo -a -p /sys/path/to/hardware/info udevinfo -a -p /sys/block/sda udevinfo -q path -n /dev/sda 联起来用: # udevinfo -a -p $(udevinfo -q path -n /dev/sda)
测试方法
# udevtest /sys/class/sound/dsp/version 056looking at '/class/sound/dsp/'opened class_dev->name='dsp'configured rule in '/etc/udev/rules.d/50-udev.rules[132]' applied, added symlink '%k'configured rule in '/etc/udev/rules.d/50-udev.rules[132]' applied, 'dsp' becomes 'sound/%k'creating device node '/dev/sound/dsp', major = '14', minor = '3', mode = '0660', uid = '0', gid = '18'
实际的例子
010_local.rules
#Clie th55#syncBUS="usb", SYSFS{product}="Palm Handheld", KERNEL="ttyUSB*", SYMLINK="pilot%n"#exportBus="usb", SYSFS{product}="Sony PEG Mass Storage", KERNEL="sd*",SYMLINK="cliems"#usb HDDBUS="usb", SYSFS{product}="USB TO IDE", KERNEL="sd*", SYMLINK="usbhdd%n"#cf1 used for 5in1 card readerBUS="ide", ID="2.0", KERNEL="hd*", SYMLINK="ms"]
相对于linux来说,udev还是一个新事物。然而,尽管它03年才出现,尽管它很低调(J),但它无疑已经成为linux下不可或缺的组件了。udev是什么?它是如何实现的?最近研究Linux设备管理时,花了一些时间去研究udev的实现。
udev是什么?u 是指user space,dev是指device,udev是用户空间的设备驱动程序吗?最初我也这样认为,调试内核空间的程序要比调试用户空间的程序复杂得多,内核空间的程序的BUG所引起的后果也严重得多,device driver是内核空间中所占比较最大的代码,如果把这些device driver中硬件无关的代码,从内核空间移动到用户空间,自然是一个不错的想法。
但我的想法并不正确,udev的文档是这样说的,
1. dynamic replacement for /dev。作为devfs的替代者,传统的devfs不能动态分配major和minor的值,而major和minor非常有限,很快就会用完了。udev能够像DHCP动态分配IP地址一样去动态分配major和minor。
2. device naming。提供设备命名持久化的机制。传统设备命名方式不具直观性,像/dev/hda1这样的名字肯定没有boot_disk这样的名字直观。udev能够像DNS解析域名一样去给设备指定一个有意义的名称。
3. API to access info about current system devices 。提供了一组易用的API去操作sysfs,避免重复实现同样的代码,这没有什么好说的。
我们知道,用户空间的程序与设备通信的方法,主要有以下几种方式,
1. 通过ioperm获取操作IO端口的权限,然后用inb/inw/ inl/ outb/outw/outl等函数,避开设备驱动程序,直接去操作IO端口。(没有用过)
2. 用ioctl函数去操作/dev目录下对应的设备,这是设备驱动程序提供的接口。像键盘、鼠标和触摸屏等输入设备一般都是这样做的。
3. 用write/read/mmap去操作/dev目录下对应的设备,这也是设备驱动程序提供的接口。像framebuffer等都是这样做的。
上面的方法在大多数情况下,都可以正常工作,但是对于热插拨(hotplug)的设备,比如像U盘,就有点困难了,因为你不知道:什么时候设备插上了,什么时候设备拔掉了。这就是所谓的hotplug问题了。
处理hotplug传统的方法是,在内核中执行一个称为hotplug的程序,相关参数通过环境变量传递过来,再由hotplug通知其它关注hotplug事件的应用程序。这样做不但效率低下,而且感觉也不那么优雅。新的方法是采用NETLINK实现的,这是一种特殊类型的socket,专门用于内核空间与用户空间的异步通信。下面的这个简单的例子,可以监听来自内核hotplug的事件。
#include <stdio.h> #include #include <string.h> #include <ctype.h> #include #include #include <sys/socket.h> #include #include #include <errno.h>
static int init_hotplug_sock(void) { struct sockaddr_nl snl; const int buffersize = 16 * 1024 * 1024; int retval;
memset(&snl, 0x00, sizeof(struct sockaddr_nl)); snl.nl_family = AF_NETLINK; snl.nl_pid = getpid(); snl.nl_groups = 1;
int hotplug_sock = socket(PF_NETLINK, SOCK_DGRAM, NETLINK_KOBJECT_UEVENT); if (hotplug_sock == -1) { printf("error getting socket: %s", strerror(errno)); return -1; }
/* set receive buffersize */ setsockopt(hotplug_sock, SOL_SOCKET, SO_RCVBUFFORCE, &buffersize, sizeof(buffersize));
retval = bind(hotplug_sock, (struct sockaddr *) &snl, sizeof(struct sockaddr_nl)); if (retval < 0) { printf("bind failed: %s", strerror(errno)); close(hotplug_sock); hotplug_sock = -1; return -1; }
return hotplug_sock; }
#define UEVENT_BUFFER_SIZE 2048
int main(int argc, char* argv[]) { int hotplug_sock = init_hotplug_sock();
while(1) { char buf[UEVENT_BUFFER_SIZE*2] = {0}; recv(hotplug_sock, &buf, sizeof(buf), 0); printf("%s\n", buf); }
return 0; } |
编译:
gcc -g hotplug.c -o hotplug_monitor
运行后插/拔U盘,可以看到:
add@/devices/pci0000:00/0000:00:1d.1/usb2/2-1 add@/devices/pci0000:00/0000:00:1d.1/usb2/2-1/usbdev2.2_ep00 add@/devices/pci0000:00/0000:00:1d.1/usb2/2-1/2-1:1.0 add@/class/scsi_host/host2 add@/devices/pci0000:00/0000:00:1d.1/usb2/2-1/2-1:1.0/usbdev2.2_ep81 add@/devices/pci0000:00/0000:00:1d.1/usb2/2-1/2-1:1.0/usbdev2.2_ep02 add@/devices/pci0000:00/0000:00:1d.1/usb2/2-1/2-1:1.0/usbdev2.2_ep83 add@/class/usb_device/usbdev2.2 add@/devices/pci0000:00/0000:00:1d.1/usb2/2-1/2-1:1.0/host2/target2:0:0/2:0:0:0 add@/class/scsi_disk/2:0:0:0 add@/block/sda add@/block/sda/sda1 add@/class/scsi_device/2:0:0:0 add@/class/scsi_generic/sg0 remove@/devices/pci0000:00/0000:00:1d.1/usb2/2-1/2-1:1.0/usbdev2.2_ep81 remove@/devices/pci0000:00/0000:00:1d.1/usb2/2-1/2-1:1.0/usbdev2.2_ep02 remove@/devices/pci0000:00/0000:00:1d.1/usb2/2-1/2-1:1.0/usbdev2.2_ep83 remove@/class/scsi_generic/sg0 remove@/class/scsi_device/2:0:0:0 remove@/class/scsi_disk/2:0:0:0 remove@/block/sda/sda1 remove@/block/sda remove@/devices/pci0000:00/0000:00:1d.1/usb2/2-1/2-1:1.0/host2/target2:0:0/2:0:0:0 remove@/class/scsi_host/host2 remove@/devices/pci0000:00/0000:00:1d.1/usb2/2-1/2-1:1.0 remove@/class/usb_device/usbdev2.2 remove@/devices/pci0000:00/0000:00:1d.1/usb2/2-1/usbdev2.2_ep00 remove@/devices/pci0000:00/0000:00:1d.1/usb2/2-1 |
udev的主体部分在udevd.c文件中,它主要监控来自4个文件描述符的事件/消息,并做出处理:
1. 来自客户端的控制消息。这通常由udevcontrol命令通过地址为/org/kernel/udev/udevd的本地socket,向udevd发送的控制消息。其中消息类型有:
l UDEVD_CTRL_STOP_EXEC_QUEUE 停止处理消息队列。
l UDEVD_CTRL_START_EXEC_QUEUE 开始处理消息队列。
l UDEVD_CTRL_SET_LOG_LEVEL 设置LOG的级别。
l UDEVD_CTRL_SET_MAX_CHILDS 设置最大子进程数限制。好像没有用。
l UDEVD_CTRL_SET_MAX_CHILDS_RUNNING 设置最大运行子进程数限制(遍历proc目录下所有进程,根据session的值判断)。
l UDEVD_CTRL_RELOAD_RULES 重新加载配置文件。
2. 来自内核的hotplug事件。如果有事件来源于hotplug,它读取该事件,创建一个udevd_uevent_msg对象,记录当前的消息序列号,设置消息的状态为EVENT_QUEUED,然后并放入running_list和exec_list两个队列中,稍后再进行处理。
3. 来自signal handler中的事件。signal handler是异步执行的,即使有signal产生,主进程的select并不会唤醒,为了唤醒主进程的select,它建立了一个管道,在signal handler中,向该管道写入长度为1个子节的数据,这样就可以唤醒主进程的select了。
4. 来自配置文件变化的事件。udev通过文件系统inotify功能,监控其配置文件目录/etc/udev/rules.d,一旦该目录中文件有变化,它就重新加载配置文件。
其中最主要的事件,当然是来自内核的hotplug事件,如何处理这些事件是udev的关键。udev本身并不知道如何处理这些事件,也没有必要知道,因为它只实现机制,而不实现策略。事件的处理是由配置文件决定的,这些配置文件即所谓的rule。
关于rule的编写方法可以参考《writing_udev_rules》,udev_rules.c实现了对规则的解析。
在规则中,可以让外部应用程序处理某个事件,这有两种方式,一种是直接执行命令,通常是让modprobe去加载驱动程序,或者让mount去加载分区。另外一种是通过本地socket发送消息给某个应用程序。
在udevd.c:udev_event_process函数中,我们可以看到,如果RUN参数以”socket:”开头则认为是发到socket,否则认为是执行指定的程序。
下面的规则是执行指定程序:
60-pcmcia.rules: RUN+="/sbin/modprobe pcmcia"
下面的规则是通过socket发送消息:
90-hal.rules:RUN+="socket:/org/freedesktop/hal/udev_event"
hal正是我们下一步要关心的,接下来我会分析HAL的实现原理。