Chinaunix首页 | 论坛 | 博客
  • 博客访问: 3514888
  • 博文数量: 1805
  • 博客积分: 135
  • 博客等级: 入伍新兵
  • 技术积分: 3345
  • 用 户 组: 普通用户
  • 注册时间: 2010-03-19 20:01
文章分类

全部博文(1805)

文章存档

2017年(19)

2016年(80)

2015年(341)

2014年(438)

2013年(349)

2012年(332)

2011年(248)

分类: LINUX

2015-09-09 08:27:56

原文地址:udev实现原理 作者:zhenchengjin

第一、什么是udev?

这篇文章给我们娓娓道来,花点时间预习一下是值得的。当然,不知道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!

 

 

 

4         基本工作原理方面的问题
这部分主要是分析了一下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

 

//--------------------------------------------------------------------

 

 

linux 传统上使用静态设备创建方法,因此大量设备节点在 /dev 下创建(有时上千个),而不管相应的硬件设备是否真正存在。通常这由一个MAKEDEV脚本,组成它由许多calls of mknod program with the revelant major and minor device number for每一个世界上可能存在的设备。采用udev的方法,只有被kernel检测到的设备才会get devices nodes created for them.因为这些设备节点在每次系统启动时被创建,他们会被stored在a ramfs(a file system in memory,does not take up any disk space).设备节点不需要much 磁盘空间,因此
the memory that is used 可以忽略。

1:history
在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 变得更加现实。

2:udev 执行
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 并查

找/proc/sys/kernel/hotplug去找出管理设备连接的用户空间程序。udev初始化脚本注册udev as this hander.当hotplug events发生时,kernel通知udev 去检测/sys 文件系统附属于这个新设备的信息并create 它的/dev/入口。
?这带给我们一个问题: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就可以去检测设备并创建相应的设备节点。

3:处理可热插拔/动态设备
当 你插入一个设备,比如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()会显得非常有价值。当此包安装后,它会响应前述的kernel总线驱动hotplug事件。此包将加载相应的模块并为设备创建节点。

4:创建设备的问题
自动创建设备节点时常遇到的一些问题
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.


 

//---------------------------------------------------------------

 

udev规则简介

  关于这个规则,有一篇很经典的英文说明 http://www.reactivated.net/writing_udev_rules.html


  udev是一种Linux2.6内核采用的/dev 目录的管理系统,它通过从sysfs获得的信息,可以提供对特定设备的固定的设备名。


  sysfs是 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 或在线文档 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 在线文档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 spacedev是指deviceudev是用户空间的设备驱动程序吗?最初我也这样认为,调试内核空间的程序要比调试用户空间的程序复杂得多,内核空间的程序的BUG所引起的后果也严重得多,device driver是内核空间中所占比较最大的代码,如果把这些device driver中硬件无关的代码,从内核空间移动到用户空间,自然是一个不错的想法。


 


但我的想法并不正确,udev的文档是这样说的,


1.         dynamic replacement for /dev。作为devfs的替代者,传统的devfs不能动态分配majorminor的值,而majorminor非常有限,很快就会用完了。udev能够像DHCP动态分配IP地址一样去动态分配majorminor


 


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 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_listexec_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的实现原理。

阅读(1008) | 评论(0) | 转发(0) |
给主人留下些什么吧!~~