udev机制以及udev机制下的书写规则

582阅读 0评论2012-11-16 aalvcying
分类:

udev的书写规则
悟空为何会在五指山下被压五百年?为何又在五百年之后被释放出来?算了,别计较了,它只是一只猴子……
说明:
本文翻译自http://www.reactivated.net/writing_udev_rules.html,纯粹是蛋疼好玩所致,依照本人不拘一格的性格,不会照本宣科, 欢迎指正。
概要:
udev是针对linux kernel 2.6以后的动态创建/dev下的设备节点而推出的方案,相比2.6以前的devfs的解决方案,udev给出了整体的替代方案并且拥有更明显的优势。
过去我们应用的udev规则在这几年已经有些改变,在更新的系统当中,udev提供了一些永久的设备类型名,用来取代这些设备的自定义类型名,然而有些用户需求仍然需要额外定制。
本篇文档的前提是udev已经安装并且相应配置启用,在linux发行版当中都是默认的,不需要调整。
本篇文档不会注重太多的细节,更多的是概念性的引入,如果你想更多的了解细节,在udev man page上有详细的介绍。
思想和观念也许不尽然相同,但是例子最能说明一切,实际基于理论却高于理论,必须是我们每个孜孜不倦追求者心中最明确的概念。
概念介绍:
术语:devfs, sysfs, nodes, etc.
下面是一些不求甚解的介绍:
传统意义上的基于linux的系统,/dev目录下面都是系统当中相应的设备节点,这些设备节点并不是所有都存在,但是都是构成整个系统的一分子。用户控件通过这些设备节点作为接口
操作相应的硬件,比如“/dev/input/mice”和鼠标的关联。
最初的/dev目录很大是因为其下面云集着所有可能在系统中出现的设备,devfs便是为了管理/dev而出现的(说是管理,也仅仅是把系统运行条件下的设备添加到/dev下),当然还有些
其它的功能,但是一个很严重的问题:通过devfs很难修复系统遇到的一些问题。
udev就是在这种情况下诞生的,其拥有更健壮的管理功能和发展空间,弥补了devfs无法解决的一些问题。为了创建设备节点,udev依靠的是sysfs提供的可靠的匹配信息,而这些匹配
信息的规则则是由user制定的。本文用来描述其书写规则,这是每一个试图利用udev机制的user必须遵循的。
sysfs是出现在linux kernel 2.6以后的file system(sysfs千万别理解成系统文件系统,会被拍砖的,具体奥妙还等研究研究再说),kernel控制sysfs提供相应设备的一些基本信息,
udev利用这些信息创建了与设备对应的设备节点,sysfs mount在/sys/目录下,在系统运行时,/sys目录下的一些文件和参数时刻反映着系统运行的变化,所以,在使用udev之前,你可以先去check
 /sys/目录下的一些东西,本文也会对其做一定的介绍。有兴趣的可以去研究下/dev /sys /proc /etc /var的关系,会为我们以后研究和理解linux系统很有帮助。

为什么我们要知道udev rules?
udev规则是灵活而健壮的,利用这个规则我们可以做的事情有:
1.重命名设备节点
2.通过创建硬链接的方式,提供一个可替代的设备节点。
3.根据输出命名设备节点
4.改变设备节点的权限和和改变ownership
5.当设备创建和删除时启动某个脚本(自动挂载的实现!!!)
6.重命名网络接口
针对一些设备节点规则不存在的特定设备,书写规则也许不能给出有效解决方法,但是即便不存在,udev会利用kernel提供的默认名字创建设备节点。
有固定命名的设备节点有以下优点:设备名称将不再以插入顺序命名为sda和sdb等等,而是有区别的以你最初的命名来决定,比如apple、banana……
固定命名方案:
udev对某些特定的设备类型有创造性的命名方案,这是一个非常有用的特性,其意味着你不用书写任何规则。
#ls -lR /dev/disk
上述命令适用于所有的存储设备,比如你的scsi硬盘和USB等等。

书写规则
文件规则和方案:
udev在决定针对设备如何命名以及其他操作之前,先要在/etc/dev/rules.d目录当中查询相应的规则脚本,这些脚本都以.rules结尾。(读到这里,我们也应该有一个大概的概念,根目录下的dev、etc、sys的关系了吧)
默认的规则都存放在50-udev.rules脚本(一个默认的规则脚本)当中,打开这个文件你会发现有趣的几点:它包含几个例子和一些默认的/dev层的devfs风格的规则。切记不要直接在这个脚本当中加入你的私有规则。(并不一定完全准确,也可能在一个其他的默认的规则脚本当中,在我的ubuntu12.04, kernel-3.2.0-32当中是两个针对一些特定设备比如光驱等等和网卡的规则,但是这句话传递我们的信息很重要,细细体会,操作系统的人是活的,切记!!!)
在/etc/dev/rules.d这个目录下,规则的顺序是按照命名词汇解析顺序开始的,一般是10-*.rules,20-*.rules,......,在某些时候,解析顺序是很重要的,因而当我们希望我们自定义的规则放在最前面来覆盖默认的时候,建议命名为10-*.rules,把自定义的规则都写在这个文件当中就好。
#开始的都是注释,每个规则只能放在一行,不能写在多行(比如针对sd[a-z][0-9]的,那就只能写在一行)。
一台设备可以有两个以上的规则适应,这种情况被证明是非常有用的,比如一台设备两个名字等等,有人会担心是否两个规则都能用,这点放心,udev知道所有的规则并且会应用。

规则语法:
每个规则都是key-value成对的,以逗号隔开,keys是用来匹配相应的设备,当所有的key都被满足的时候,这条规则就会被执行,每个规则至少有一对key-value
例子:
KERNEL=="hdb", NAME="my_spare_disk"
两个key, 匹配key KERNEL,赋值key NAME。这些key的定义会在后面给出介绍,匹配符号是==, 赋值符号是=
记住udev规则是不允许换行的,比如上面的你分开两行就会识别错误,对于设备的规则描述只能在一行去描述。
基本规则:
udev有好几类的key值可以非常精确的匹配到设备,这里只介绍最常用的,其余的可以参考udev手册。
KERNEL  ---- 匹配设备的内核默认名称
SUBSYSTEM  ----  匹配设备subsystem
DRIVER  ---- 匹配设备对应的驱动
一个设备在内核当中会有相对应的驱动,处于相应的子系统当中,拥有独特的内核默认的名称,这三项已经足以让我们精确的匹配到某个特定设备
我们用一系列的匹配key可以精确找到我们的设备,udev还给我们提供的赋值key让我们更好的控制设备,这些最基本的赋值key介绍如下,具体参考udev手册。

NAME   --- 你为设备自定义的名称
SYMLINK  --- 设备节点的硬链接

udev对于一个设备只创建一个真正的设备节点,如果你想为一个设备创建备用名称,用硬链接的方法。当硬链接建立以后,实质上是在维护一个硬链接的列表,而这些硬链接都指向真正的设备节点。如果你要操作这些列表,一个新的规则运算+=,你可以分配多个硬链接,用空格分开。例子:
KERNEL=="hdb", DRIVER=="ide-disk", SYMLINK+="sparedisk"
KERNEL=="hdc", SYMLINK+="cdrom cdrom0"
hdb,sda这些都是由kernel命名的,我们可以自己命名,也会在/dev下出现的。

创建硬链接的时候我们并没有给他重命名,因而udev还是会用内核默认分配的名称创建设备节点,为了保证/dev的布局,通常我们不会改变名字(使用NAME)而是采用硬链接的方法。
KERNEL=="hdc", SYMLINK+="cdrom cdrom0"
上面的也许更加典型,cdrom 和 cdrom0 都是硬链接指向hdc,因为没有指定NAME,因而hdc得以保留。
匹配sysfs特性
上述的匹配是非常局限的,而现实当中我们需要更具体的对一个设备的控制:我们需要知道设备的特性比如厂商、设备号、存储容量、分区大小等等。
sysfs当中记录了相关设备的很多信息,这些都是由相关驱动report出来的,udev给我们了利用这些信息的机会。
下面是一个利用了sysfs中的特性的例子:
SUBSYSTEM=="block", ATTR{size}=="234441648", SYMLINK+="my_disk
设备层次结构:
linux内核实际上是将所有设备(其实这里的设备含义很范的,包括虚拟的)都放在一个目录树当中,并通过sysfs机制将所有信息呈现给我们,因而利用这一点编写我们的规则就会非常有用:比如我们的硬盘式SCSI目录树下的一分支,当然也就成为ATA串行总线的一个分支,也是PCI总线的一个分支。因而具体设备的信息是从其相关父目录当中获取的,比如硬盘的ATA序列号就是由其在SCSI范畴中的父目录分配的。(其实这一点我们完全可以从驱动当中的major 和 minor number得到启发)
我们现在知道的四个主要的匹配key是(KERNEL/SUBSYSTEM/DRIVER/ATTR),通过这几个key值我们只能找到设备,但是却不知道目录树当中所处的位置,就是其父目录的相关信息。udev给我们提供了在整个目录树当中寻找设备的匹配key值。
KERNELS ---- 匹配设备在内核当中的名称和相关父设备的名称。
SUBSYSTEMS ---- 匹配设备在内核中的子系统,或其父设备在内核中对应的子系统。
DRIVERS ---- 匹配设备在内核当中的驱动,或其父设备在内核中对应的子系统。
ATTRS ---- 匹配sysfs当中的信息,或其父设备在sysfs当中的对应信息。
这么说太累,所有的设备在内核当中都有分类,这一类(并不完全这样)设备都有一个统一的major number,而每一个后面插入的子设备都会由驱动分配一个minor number。也就是说,我们可以通过上述匹配key值得到m这类设备对应的信息。
现在在我们看来这些规则有些复杂了,后面我们会介绍一些相关的工具。
字符串替换(变量设置)
我们的每一条规则也许针对的是多个设备,udev当中类似print的字符串的替换操作是非常有用的,你可以很方便的把这些规则应用到你自己书写的规则当中,udev会自己判断相关设备是否符合规则。
最常用的就是k%和n%了,k%是用来得到设备的内核名称的,比如最常用的sd[a-z][0-9]就是内核提供的设备名称。udev还提供了一些其它的规则,比如如果你想匹配一个%,你只需要加上%%; 匹配$,你只需要加入$$,下面会给出一些示例:
KERNEL=="mice", NAME="input/%k"
KERNEL=="loop0", NAME="loop/%n", SYMLINK+="%k"
针对第一个例子,是确保最终创建后的设备节点在input/目录下(本来默认应该是/dev/mice),第二条示例是仅仅创建一个新的硬链接。上述示例还不能表示出替代的好处,这些我们在下面会给出一些介绍。
字符串匹配
udev支持shell风格的字符串匹配,有以下三种:
* ---- 匹配任何字符,任何次数(不太理解)
?---- 任何字符只允许匹配一次
[]---- 匹配括号中的所有字符以及字符顺序。
例子:
KERNEL=="fd[0-9]*", NAME="floppy/%n", SYMLINK+="%k"
KERNEL=="hiddev*", NAME="usb/%k"

对于第一条规则,是适用于所有的软盘的,并且将所有的设备节点都放在/dev/floppy目录下,并且用默认的内核分配名称创建相关的硬链接。(也就是说/dev/fd[0-9]*还是存在的,只不过是硬链接罢了)
第二条设备呢,是确保hiddev的所有相关设备节点创建于/dev/usb下。
从sysfs当中找到设备信息!!!

sysfs目录树介绍
上面或多或少的提到了sysfs当中的一些信息,但是利用这些信息来编写udev规则的前提,是你需要知道一些额外的设备相关信息。
sysfs的结构是非常简单的,呈现给我们的就是sys这个目录树,每个相关的目录其实是一种或者一类设备信息的集成(就是根据我们了解的major 和 minor number去分配的了)。
在进入/sys,我们看到的是一些top-level的目录,这些目录都对应着一类的设备和相应的设备节点。对于这个目录结构,udev也有相关的规则。
# find /sys -name dev
在/sys目录下,比如我自己的硬盘,/sys/block/sda就是代表我的设备,我的设备的相关信息就放在这个目录下。而/sys/block/sda只是一个链接到其父设备目录的一个硬链接。
而我们需要利用来写自己规则的信息,就放在这个目录下,比如硬盘的大小:
# cat /sys/block/sda/size
234441648
在udev规则当中,我们可以使用 ATTR{size}=="234441648"来定位到自己的设备。由于udev机制维护着一个所有设备相关的设备练,当然也可以选择此目录的其它信息使用ATTR规则,有些地方我们需要注意下,这些特性不是我们随便用的,在下面我们会讲到。
上面我们都是知道的情况下去使用的,那存不存在相关的工具帮助我们呢?

udevinfo
udevinfo就是那个可以帮助我们得到相关设备所有信息的工具。
# udevinfo -a -p /sys/block/sda

  looking at device '/block/sda':
    KERNEL=="sda"
    SUBSYSTEM=="block"
    ATTR{stat}=="  128535     2246  2788977   766188    73998   317300  3132216  5735004        0   516516  6503316"
    ATTR{size}=="234441648"
    ATTR{removable}=="0"
    ATTR{range}=="16"
    ATTR{dev}=="8:0"

  looking at parent device '/devices/pci0000:00/0000:00:07.0/host0/target0:0:0/0:0:0:0':
    KERNELS=="0:0:0:0"
    SUBSYSTEMS=="scsi"
    DRIVERS=="sd"
    ATTRS{ioerr_cnt}=="0x0"
    ATTRS{iodone_cnt}=="0x31737"
    ATTRS{iorequest_cnt}=="0x31737"
    ATTRS{iocounterbits}=="32"
    ATTRS{timeout}=="30"
    ATTRS{state}=="running"
    ATTRS{rev}=="3.42"
    ATTRS{model}=="ST3120827AS     "
    ATTRS{vendor}=="ATA     "
    ATTRS{scsi_level}=="6"
    ATTRS{type}=="0"
    ATTRS{queue_type}=="none"
    ATTRS{queue_depth}=="1"
    ATTRS{device_blocked}=="0"

  looking at parent device '/devices/pci0000:00/0000:00:07.0':
    KERNELS=="0000:00:07.0"
    SUBSYSTEMS=="pci"
    DRIVERS=="sata_nv"
    ATTRS{vendor}=="0x10de"
    ATTRS{device}=="0x037f"

正如我们看到的,udevinfo生成了一个设备属性列表,列表当中的信息则可以被我们利用。
SUBSYSTEM=="block", ATTR{size}=="234441648", NAME="my_hard_disk"
SUBSYSTEM=="block", SUBSYSTEMS=="scsi", ATTRS{model}=="ST3120827AS", NAME="my_hard_disk"

但是我们在使用的时候,一定要用我们设备属性密切相关的东西,那些毫无意义的就不要使用了,其实上面三种列表说的很清楚了,只有第一阵列的是真正适合我们所指的特定设备的,后面的都是其相应父目录的属性哦。

udevinfo工具不是必选项,也有像usbview这样的工具可以用的。
Advanced topics(翻译须耗时,后续补)

Controlling permissions and ownership
Using external programs to name devices
Running external programs upon certain events
Environment interaction
Additional options
上一篇:ARM开发环境下搭建DLNA server
下一篇:开发板上移植vsftp的过程