如果你使用Linux比较长时间了,那你就知道,在对待设备文件这块,Linux改变了几次策略。在Linux早期,设备文件仅仅是是一些带有适当的属性集的普通文件,它由mknod命令创建,文件存放在/dev目录下。后来,采用了devfs, 一个基于内核的动态设备文件系统,他首次出现在2.3.46内核中。Mandrake,Gentoo等Linux分发版本采用了这种方式。devfs创建 的设备文件是动态的。但是devfs有一些严重的限制,从2.6.13版本后移走了。目前取代他的便是文本要提到的udev--一个用户空间程序。
目前很多的Linux分发版本采纳了udev的方式,因为它在Linux设备访问,特别是那些对设备有极端需求的站点(比如需要控制上千个硬盘)和热插拔设备(比如USB摄像头和MP3播放器)上解决了几个问题。下面我我们来看看如何管理udev设备。
实际上,对于那些为磁盘,终端设备等准备的标准配置文件而言,你不需要修改什么。但是,你需要了解udev配置来使用新的或者外来设备,如果不修改配置, 这些设备可能无法访问,或者说Linux可能会采用不恰当的名字,属组或权限来创建这些设备文件。你可能也想知道如何修改RS-232串口,音频设备等文件的属组或者权限。这点在实际的Linux实施中是会遇到的。
为什么使用udev
在此之前的设备文件管理方法(静态文件和devfs)有几个缺点:
*不确定的设备映射。特别是那些动态设备,比如USB设备,设备文件到实际设备的映射并不可靠和确定。举一个例子:如果你有两个USB打印机。一个可能称 为/dev/usb/lp0,另外一个便是/dev/usb/lp1。但是到底哪个是哪个并不清楚,lp0,lp1和实际的设备没有一一对应的关系,因为 他可能因为发现设备的顺序,打印机本身关闭等原因而导致这种映射并不确定。理想的方式应该是:两个打印机应该采用基于他们的序列号或者其他标识信息的唯一 设备文件来映射。但是静态文件和devfs都无法做到这点。
*没有足够的主/辅设备号。我们知道,每一个设备文件是有两个8位的数字:一个是主设备号 ,另外一个是辅设备号来分配的。这两个8位的数字加上设备类型(块设备或者字符设备)来唯一标识一个设备。不幸的是,关联这些身边的的数字并不足够。
*/dev目录下文件太多。一个系统采用静态设备文件关联的方式,那么这个目录下的文件必然是足够多。而同时你又不知道在你的系统上到底有那些设备文件是激活的。
*命名不够灵活。尽管devfs解决了以前的一些问题,但是它自身又带来了一些问题。其中一个就是命名不够灵活;你别想非常简单的就能修改设备文件的名字。缺省的devfs命令机制本身也很奇怪,他需要修改大量的配置文件和程序。
*内核内存使用,devfs特有的另外一个问题是,作为内核驱动模块,devfs需要消耗大量的内存,特别当系统上有大量的设备时(比如上面我们提到的系统一个上有好几千磁盘时)
udev的目标是想解决上面提到的这些问题,他通采用用户空间(user-space)工具来管理/dev/目录树,他和文件系统分开。知道如何改变缺省配置能让你之大如何定制自己的系统,比如创建设备字符连接,改变设备文件属组,权限等。
udev配置文件
主要的udev配置文件是/etc/udev/udev.conf。这个文件通常很短,他可能只是包含几行#开头的注释,然后有几行选项:
udev_root=“/dev/”
udev_rules=“/etc/udev/rules.d/”
udev_log=“err“
上面的第二行非常重要,因为他表示udev规则存储的目录,这个目录存储的是以.rules结束的文件。每一个文件处理一系列规则来帮助udev分配名字给设备文件以保证能被内核识别。
你的/etc/udev/rules.d下面可能有好几个udev规则文件,这些文件一部分是udev包安装的,另外一部分则是可能是别的硬件或者软件包 生成的。比如在Fedora Core 5系统上,sane-backends包就会安装60-libsane.rules文件,另外initscripts包会安装60-net.rules文 件。这些规则文件的文件名通常是两个数字开头,它表示系统应用该规则的顺序。
规则文件里的规则有一系列的键/值对组成,键/值对之间用逗号(,)分割。每一个键或者是用户匹配键,或者是一个赋值键。匹配键确定规则是否被应用,而赋 值键表示分配某值给该键。这些值将影响udev创建的设备文件。赋值键可以处理一个多值列表。匹配键和赋值键操作符解释见下表:
udev 键/值对操作符
操作符 匹配或赋值 解释
----------------------------------------
== 匹配 相等比较
!= 匹配 不等比较
= 赋值 分配一个特定的值给该键,他可以覆盖之前的赋值。
+= 赋值 追加特定的值给已经存在的键
:= 赋值 分配一个特定的值给该键,后面的规则不可能覆盖它。
这有点类似我们常见的编程语言,比如C语言。只是这里的键一次可以处理多个值。有一些键在udev规则文件里经常出现,这些键的值可以使用通配符(*,?,甚至范围,比如[0-9]),这些常用键列举如下:
常用udev键
键 含义
ACTION 一个时间活动的名字,比如add,当设备增加的时候
KERNEL 在内核里看到的设备名字,比如sd*表示任意SCSI磁盘设备
DEVPATH 内核设备录进,比如/devices/*
SUBSYSTEM 子系统名字,比如sound,net
BUS 总线的名字,比如IDE,USB
DRIVER 设备驱动的名字,比如ide-cdrom
ID 独立于内核名字的设备名字
SYSFS{ value} sysfs属性值,他可以表示任意
ENV{ key} 环境变量,可以表示任意
PROGRAM 可执行的外部程序,如果程序返回0值,该键则认为为真(true)
RESULT 上一个PROGRAM调用返回的标准输出。
NAME 根据这个规则创建的设备文件的文件名。注意:仅仅第一行的NAME描述是有效的,后面的均忽略。 如果你想使用使用两个以上的名字来访问一个设备的话,可以考虑SYMLINK键。
SYMLINK 根据规则创建的字符连接名
OWNER 设备文件的属组
GROUP 设备文件所在的组。
MODE 设备文件的权限,采用8进制
RUN 为设备而执行的程序列表
LABEL 在配置文件里为内部控制而采用的名字标签(下下面的GOTO服务)
GOTO 跳到匹配的规则(通过LABEL来标识),有点类似程序语言中的GOTO
IMPORT{ type} 导入一个文件或者一个程序执行后而生成的规则集到当前文件
WAIT_FOR_SYSFS 等待一个特定的设备文件的创建。主要是用作时序和依赖问题。
PTIONS 特定的选项: last_rule 对这类设备终端规则执行; ignore_device 忽略当前规则; ignore_remove 忽略接下来的并移走请求。all_partitions 为所有的磁盘分区创建设备文件。
我们给出一个列子来解释如何使用这些键。下面的例子来自Fedora Core 5系统的标准配置文件。
KERNEL=="*", OWNER="root" GROUP="root", MODE="0600"
KERNEL=="tty", NAME="%k", GROUP="tty", MODE="0666", OPTIONS="last_rule"
KERNEL=="scd[0-9]*", SYMLINK+="cdrom cdrom-%k"
KERNEL=="hd[a-z]", BUS=="ide", SYSFS{removable}=="1",
SYSFS{device/media}=="cdrom", SYMLINK+="cdrom cdrom-%k"
ACTION=="add", SUBSYSTEM=="scsi_device", RUN+="/sbin/modprobe sg"上面的例子给出了5个规则,每一个都是KERNEL或者ACTION键开头:
*第一个规则是缺省的,他匹配任意被内核识别到的设备,然后设定这些设备的属组是root,组是root,访问权限模式是0600(-rw——-)。这也是一个安全的缺省设置保证所有的设备在默认情况下只有root可以读写
*第二个规则也是比较典型的规则了。它匹配终端设备(tty),然后设置新的权限为0600,所在的组是tty。它也设置了一个特别的设备文件名:%K。在这里例子里,%k代表设备的内核名字。那也就意味着内核识别出这些设备是什么名字,就创建什么样的设备文件名。
*第三行开始的KERNEL==”scd[0-9]*”,表示 SCSI CD-ROM 驱动. 它创建一对设备符号连接:cdrom和cdrom-%k。
*第四行,开始的 KERNEL==”hd[a-z]“, 表示ATA CDROM驱动器。这个规则创建和上面的规则相同的符号连接。ATA CDROM驱动器需要sysfs值以来区别别的ATA设备,因为SCSI CDROM可以被内核唯一识别。.
*第五行以 ACTION==”add”开始,它告诉udev增加 /sbin/modprobe sg 到命令列表,当任意SCSI设备增加到系统后,这些命令将执行。其效果就是计算机应该会增加sg内核模块来侦测新的SCSI设备。
当然,上面仅仅是一小部分例子,如果你的系统采用了udev方式,那你应该可以看到更多的规则。如果你想修改设备的权限或者创建信的符号连接,那么你需要熟读这些规则,特别是要仔细注意你修改的那些与之相关的设备。
修改你的udev配置
在修改udev配置之前,我们一定要仔细,通常的考虑是:你最好不要修改系统预置的那些规则,特别不要指定影响非常广泛的配置,比如上面例子中的第一行。不正确的配置可能会导致严重的系统问题或者系统根本就无法这个正确的访问设备。
而我们正确的做法应该是在/etc/udev/rules.d/下创建一个信的规则文件。确定你给出的文件的后缀是rules文件名给出的数字序列应该比 标准配置文件高。比如,你可以创建一个名为99-my-udev.rules的规则文件。在你的规则文件中,你可以指定任何你想修改的配置,比如,假设你 修改修改floppy设备的所在组,还准备创建一个信的符号连接/dev/floppy,那你可以这么写:
KERNEL==”fd[0-9]*“, GROUP=“users“, SYMLINK+=“floppy“
有些发行版本,比如Fedora,采用了外部脚本来修改某些特定设备的属组,组关系和权限。因此上面的改动可能并不见得生效。如果你遇到了这个问题,你就需要跟踪和修改这个脚本来达到你的目的。或者你可以修改PROGRAM或RUN键的值来做到这点。
某些规则的修改可能需要更深的挖掘。比如,你可能想在一个设备上使用sysfs信息来唯一标识一个设备。这些信息最好通过udevinfo命令来获取。
$ udevinfo –a –p $(udevinfo –q path –n /dev/hda)上面的命令两次使用udevinfo:一次是返回sysfs设备路径(他通常和我们看到的Linux设备文件名所在路径--/dev/hda--不同);第 二次才是查询这个设备路径,结果将是非常常的syfs信息汇总。你可以找到最够的信息来唯一标志你的设备,你可以采用适当的替换udev配置文件中的 SYSFS选项。下面的结果就是上面的命令输出
[root@localhost rules.d]# udevinfo -a -p $(udevinfo -q path -n /dev/hda1)
Udevinfo starts with the device specified by the devpath and then walks up the chain of
parent devices. It prints for every device found,all possible attributes in the udev rules
key format. A rule to match, can be composed by the attributes of the device and the
attributes from one single parent device.
looking at device '/block/hda/hda1':
KERNEL=="hda1" SUBSYSTEM=="block" DRIVER==""
ATTR{stat}==" 1133 2268 2 4" ATTR{size}=="208782"
ATTR{start}=="63" ATTR{dev}=="3:1" looking at parent device '/block/hda':
KERNELS=="hda" SUBSYSTEMS=="block" DRIVERS==""
ATTRS{stat}=="28905 18814 1234781 302540 34087 133247 849708 981336 0 218340 1283968"
ATTRS{size}=="117210240" ATTRS{removable}=="0"
ATTRS{range}=="64" ATTRS{dev}=="3:0"
looking at parent device '/devices/pci0000:00/0000:00:1f.1/ide0/0.0':
KERNELS=="0.0" SUBSYSTEMS=="ide" DRIVERS=="ide-disk"
ATTRS{modalias}=="ide:m-disk" ATTRS{drivename}=="hda"
ATTRS{media}=="disk"
looking at parent device '/devices/pci0000:00/0000:00:1f.1/ide0':
KERNELS=="ide0" SUBSYSTEMS=="" DRIVERS==""
looking at parent device '/devices/pci0000:00/0000:00:1f.1':
KERNELS=="0000:00:1f.1" SUBSYSTEMS=="pci" DRIVERS=="PIIX_IDE"
ATTRS{broken_parity_status}=="0" ATTRS{enable}=="1"
ATTRS{modalias}=="pci:v00008086d000024CAsv0000144Dsd0000C009bc01sc01i8a"
ATTRS{local_cpus}=="1" ATTRS{irq}=="11" ATTRS{class}=="0x01018a"
ATTRS{subsystem_device}=="0xc009" ATTRS{subsystem_vendor}=="0x144d"
ATTRS{device}=="0x24ca" ATTRS{vendor}=="0x8086"
looking at parent device '/devices/pci0000:00':
KERNELS=="pci0000:00" SUBSYSTEMS=="" DRIVERS==""
举一个例子:假设你想修改USB扫描仪的配置。通过一系列的尝试,你已经为这个扫描仪标识了Linux设备文件(每次打开扫描仪时,名字都会变)。你可以使 用上面的命令替换这个正确的Linux设备文件名,然后定位输出的采用SYSFS{idVendor}行和SYSFS{idProduct}行。最后你可 以使用这些信息来为这个扫描仪创建新的选项。
SYSFS{idVendor}=="0686", SYSFS{idProduct}=="400e",
SYMLINK+="scanner", MODE="0664", group="scanner"上面的例子表示将扫描仪的组设置为scanner,访问权限设置为0664,同时创建一个/dev/scanner的符号连接。
udev-FAQ
问:udev是什么? 它的目的何在?
答:udev是一种工具,它能够根据系统中的硬件设备的状态动态更新设备文件,包括设备文件的创建,删除等。设备文件通常放在/dev目录下。使用udev后,在/dev目录下就只包含系统中真正存在的设备。
问:udev支持什么内核?
答:udev只支持linux-2.6内核,因为udev严重依赖于sysfs文件系统提供的信息,而sysfs文件系统只在linux-2.6内核中才有。
问:udev是一个内核程序还是用户程序?
答:udev是一个用户程序(user-mode daemon)。
问:udev和devfs有什么差别?
答:udev能够实现所有devfs实现的功能。但udev运行在用户模式中,而devfs运行在内核模式中。据称:devfs具有一些不太容易解决的先天缺陷。
问:udev的配置文件放在哪里?
答:udev是一个用户模式程序。它的配置文件是/etc/udev/udev.conf。这个文件一般缺省有这样几项:
udev_root=”/dev” ; udev产生的设备文件的根目录是/dev
udev_db=”/dev/.udevdb” ; 通过udev产生的设备文件形成的数据库
udev_rules=”/etc/udev/rules.d” ;用于指导udev工作的规则所在目录。
udev_log=”err” ;当出现错误时,用syslog记录错误信息。
问:udev的工作过程是怎样的?
答:由于没有研究过udev的源程序,不敢贸然就说udev的工作过程。我只是通过一些网上的资料和udev的说明文档,大致猜测它的工作过程可能是这样的。
当内核检测到在系统中出现了新设备后,内核会在sysfs文件系统中为该新设备生成一项新的记录,一般sysfs文件系统会被mount到/sys目录中。新记录是以一个或多个文件或目录的方式来表示。每个文件都包含有特定的信息。(信息是如何表述的,还要另外研究?)
udev在系统中是以守护进程的方式udevd在运行,它通过某种途径(到底什么途径,目前还没搞懂。)检测到新设备的出现,通过查找设备对应的sysfs中的记录得到设备的一些信息。
udev会根据/etc/udev/udev.conf文件中的udev_rules指定的目录,逐个检查该目录下的文件,这个目录下的文件都是针对某类或某个设备应该施行什么措施的规则文件。udev读取文件是按照文件名的ASCII字母顺序来读取的,如果udev一旦找到了与新加入的设备匹配的规则,udev就会根据规则定义的措施对新设备进行配置。同时不再读后续的规则文件。
问:udev的规则文件的语法是怎样的?
答:udev的规则文件以行为单位,以”#”开头的行代表注释行。其余的每一行代表一个规则。每个规则分成一个或多个“匹配”和“赋值”部分。“匹配”部分用“匹配“专用的关键字来表示,相应的“赋值”部分用“赋值”专用的关键字来表示。“匹配”关键字包括:ACTION,KERNEL,BUS,SYSFS等等,“赋值”关键字包括:NAME,SYMLINK,OWNER等等。具体详细的描述可以阅读udev的man文档。
下面举个例子来说明一下,有这样一条规则:SUBSYSTEM==”net”, ACTION==”add”, SYSFS{address}==”00:0d:87:f6:59:f3″, IMPORT=”/sbin/rename_netiface %k eth0″
这个规则中的“匹配”部分有三项,分别是SUBSYSTEM,ACTION和SYSFS。而”赋值”部分有一项,是IMPORT。这个规则就是说,当系统中出现的新硬件属于net子系统范畴,系统对该硬件采取的动作是加入这个硬件,且这个硬件在SYSFS文件系统中的“address”信息等于“00:0d…”时,对这个硬件在udev层次施行的动作是调用外部程序/sbin/rename_netiface,传递的参数有两个,一个是“%k”,代表内核对该新设备定义的名称。另一个是”eth0“。 从上面这个例子中可以看出,udev的规则的写法比较灵活的,尤其在“匹配”部分中,可以通过诸如”*“, ”?“,[a-c],[1-9]等shell通配符来灵活匹配多个匹配项。具体的语法可以参考udev的man文档。
问:udev怎样做到不管设备连接的顺序而维持一个统一的设备名?
答:实际上,udev是通过对内核产生的设备名增加别名的方式来达到上述目的的。前面说过,udev是用户模式程序,不会更改内核的行为。因此,内核依然会我行我素地产生设备名如sda,sdb等。但是,udev可以根据设备的其他信息如总线(bus),生产商(vendor)等不同来区分不同的设备,并产生设备文件。udev只要为这个设备文件取一个固定的文件名就可以解决这个问题。在后续对设备的操作中,只要引用新的设备名就可以了。但为了保证最大限度的兼容,一般来说,新设备名总是作为一个对内核自动产生的设备名的符号链接(link)来使用的。
例如:内核产生了sda设备名,而根据信息,这个设备对应于是我的内置硬盘,那我就可以制定udev规则,让udev除了产生/dev/sda设备文件外,另外创建一个符号链接叫/dev/internalHD。这样,我在fstab文件中,就可以用/dev/internalHD来代替原来的/dev/sda了。下次,由于某些原因,这个硬盘在内核中变成了sdb设备名了,那也不用着急,udev还会自动产生/dev/internalHD这个链接,并指向正确的/dev/sdb设备。所有其他的文件像fstab等都不用修改。
问:怎样才能找到这些设备信息,并把他们放到udev的规则文件中来匹配呢?
答:这个问题比较难,网上资料不多,我只找到一篇文章来介绍如何写udev的规则。他的基本方法是通过udevinfo这个实用程序来找到那些可以作为规则文件里的匹配项的项目。有这样两种情况可以使用这个工具:
第一种情况是,当你把设备插入系统后,系统为设备产生了设备名(如/dev/sda)。那样的
话,你先用udevinfo -q path -n/dev/sda,命令会产生一个该设备名对应的在sysfs下的路径,如/block/sda。然后,你再用udevinfo -a -p/sys/block/sda,这个命令会显示一堆信息,信息分成很多块。这些信息实际来自于操作系统维护的sysfs链表,不同的块对应不同的路径。你就可以用这些信息来作为udev规则文件中的匹配项。但需要注意的是,同一个规则只能使用同一块中显示的信息,不能跨块书写规则。
第二种情况是,不知道系统产生的设备名,那就只有到/sys目录下去逐个目录查找了,反复用udevinfo -a -p/sys/path…这个命令看信息,如果对应的信息是这个设备的,那就恭喜你。否则就再换个目录。当然,在这种情况下,成功的可能性比较小。
问: udev和devfs是什么关系
答: udev完全在用户态(userspace)工作,利用设备加入或移除时内核所发送的hotplug事件(event)来工作。关于设备的详细信息是由内核输出(export)到位于/sys的sysfs文件系统的。所有的设备命名策略、权限控制和事件处理都是在用户态下完成的。与此相反,devfs是作为内核的一部分工作的。
问: 如果udev不能完成所有devfs的工作的话,为什么把devfs标记为OBSOLETE/removed?
答: 引用 Al Viro (Linux VFS 内核维护者):
-devfs所做的工作被确信可以在用户态来完成。
-devfs被加入内核之时,大家寄望它的质量可以迎头赶上。
-devfs被发现了一些可修复和无法修复的 bug。
-对于可修复的 bug,几个月前就已经被修复了,其维护者认为一切良好。
-对于后者,同样是相当常一段时间以来没有改观了。
-devfs的维护者和作者对它感到失望并且已经停止了对代码的维护工作。
问: 但是当一个并不存在的/dev节点被打开的时候,udev并不能如devfs一样自动加载驱动程序。
答: 的确如此,但Linux的设计是在设备被发现的时候加载模块,而不是当它被访问的时候。
问: 不过等等,我确实希望 udev 可以在不存在的节点被打开的时候自动加载驱动。这是我使用devfs的唯一原因了。给udev 增加这个功能吧。
答: 不,udev 是用来管理/dev 的,不是用来加载内核驱动的。
问: 嗨,求你们了。这不难做到的。
答: 这么个功能对于一个配置正确的计算机是多余的。系统中所有的设备都应该产生hotplug 事件、加载恰当的驱动,而 udev 将会注意到这点并且为它创建对应的设备节点。如果你不想让所有的设备驱动停留在内存之中,应该使用其它东西来管理你的模块(如脚本, modules.conf, 等等) 这不是udev 的工作。
问: 但是我真的喜欢那个功能,还是加上吧
答: devfs用的方法导致了大量无用的modprobe尝试,以此程序探测设备是否存在。每个试探性探测都新建一个运行 modprobe 的进程,而几乎所有这些都是无用的。
问: 我喜欢devfs的设备文件命名方式,udev 可以这样命名么?
答: 可以,udev 可以使用 /dev 的命名策略来创建节点。通过一个配置文件,可以把内核缺省的名字映射到 devfs 的名字。可以看看udev 中带的 udev.rules.devfs 文件。注意: devfs 的命名方式是不被建议并且不被官方支持的,因为它所用的简单枚举设备的方式在设备可能被随时加入或删除的情况下确实是一个比较笨的方法。这些编号代给你的将只有麻烦,而并不能用来确定设备。看看那个永久性磁盘 (persistentdisk) 的规则就知道如何在用户态下正确的做这件事,而不是傻傻地列出设备。
问: udev 可以为哪些设备创建节点?
答: 所有在 sysfs 中显示的设备都可以由 udev 来创建节点。如果内核中增加了其它设备的支持,udev 也就自动地可以为它们工作了。现在所有的块设备都在被支持之列,大部分的主字符设备也是被支持的。内核开发者们正致力于让所有的字符设备都被支持。可以到linux-kernel邮件列表上寻找补丁或是查看补丁的状态。
问: udev 是否会去掉匿名设备数量的限制?
答: udev 完全工作于用户态。如果内核支持了更多的匿名设备,udev就会支持。
问: udev 是否会支持符号链接?
答: udev 现在就支持符号链接,每个设备节点拥有多个符号链接也是被支持的。
问: udev如何处理/dev文件系统?
答: 建议使用一个每次启动系统的时候重新创建的 tmpfs 作为 /dev 的文件系统。不过实际上udev并不关心那种文件系统在被使用。
问: 在 init 运行之前,udev 如何处理设备?
答: udev 可以被放入 initramfs 之中,并在每个设备被发现的时候运行。也可以让udev 工作在一个真的根分区被加载之后根据 /sys 的内容创建的初始/dev目录之中。
问: 我是否可以利用 udev 在一个 USB 设备被加载的时候自动加载上这个设备?
答: 技术上讲是可以的,但是 udev 不是用于这个工作的。所有的主流发布版 (distro)都包含了 HAL (http://freedesktop.org/wiki/Software_2fhal) 用于这个工作,它也是专门用于监视设备变更的,并且集成进入了桌面软件。
换个角度说,这可以简单的通过 fstab 来实现:/dev/disk/by-label/PENDRIVE /media/PENDRIVE vfat user,noauto 0 0
这样,用户可以用如下命令来访问设备:
$mount /media/PENDRIVE
同样不需要管理员权限,但却拥有了设备的全部访问权限。使用永久性磁盘链接(label, uuid) 将可以指定同一设备,无论其实际上的内核名字是什么。
问: 有什么我需要注意的安全问题么?
答: 当使用动态设备编号的时候,一个给定的主/从设备号可能在不同时间对应不同的设备,如果一个用户拥有对这个节点的访问权限,并且可以创建一个到这个节点的硬链接,他就可以如此得到一个这个设备节点的拷贝。当设备被移除之后,udev 删除了设备节点,但硬链接依然存在。如果这个设备节点之后被重新使用不同的访问权限被创建的时候,其硬链接仍然可以使用先前的访问权限来访问。(同样的问题也存在在使用 PAM 改变访问权限的 login 上。)
简单的解决方案就是通过把 /dev 放在 tmpfs 这样的单独的文件系统之上来防止建立硬链接。
跟我一起写udev rules 2013-02-01 21:32:26
分类: LINUX
目录
介绍
关于本文档
更新历史
概念
术语: devfs, sysfs, nodes, etc.
为什么?
内置固定命名设计
编写规则
规则文件和语义
规则语法
基本规则
sysfs匹配属性
设备级联结构
字符串替换
字符串匹配
从sysfs中查找合适信息
sysfs树
udevinfo
其他方法
高级话题
权限和所有权控制
使用外部程序命名设备
发生特定事件时运行外部程序
环境交互
另外选项
例子
USB打印机
USB相机
USB硬盘
USB读卡器
USB Palm导航仪
CD/DVD驱动
网卡
测试和调试
让你的规则跑起来
udevtest
作者及联系方式
介绍
关于本文档
udev面向2.6以上的linux内核在用户空间提供动态的/dev下固定设备命名方案. 之前的/dev实现: devfs现在已被废弃,udev成为继任者. udev vsdevfs是一个敏感的谈话内容,在进行比较之前你应该读一下这个文档(http://kernel.org/pub/linux/utils/kernel/hotplug/udev_vs_devfs).
几年间你为之使用udev规则的设备发生改变了,如同规则自身的弹性一样. 在现代系统中udev为系统外的类型设备提供了固定的命名方法, 避免了为这些设备提供定制规则. 但是一些用户仍然需要额外的定制级别.
本文档假设你已经安装了udev并使用缺省配置运行ok. 这通常通过你的linux发行版做到的.
本文档不会覆盖规则书写的方方面面, 只集中介绍所有主要概念. 更多细节信息可以在udev的man页中找到.
本文档使用各种例子(一些完全是虚构的)来阐述观点和概念. 不是所有语法都会显式的在附带文本中描述,请确信通过查看例子规则来获取完整的理解.
更新历史
(略)
概念
语义: devfs, sysfs, nodes等
仅仅是基本介绍,可能并不完全准确.
在典型的基于linux的系统中,/dev目录用来存储文件一样的设备节点, 它们指向系统中特定的设备. 每一个节点指向系统的一部分(一个设备),可能存在也可能不存在. 用户空间应用程序可以使用这些设备节点跟系统硬件打交道,例如,X服务器"监听"/dev/input/mice来根据用户的鼠标移动来移动可视鼠标指针.
原来的/dev目录仅仅在设备可能在系统中出现时产生,因此/dev目录一般非常大. 随之而来的devfs提供了一种易于管理的途径(注意它仅仅在硬件插入到系统中时产生/dev)以及其他功能,但系统会出现无法容易修复的问题.
udev是一种新的管理/dev目录的方法,它的设计清除了以前的/dev实现的一些问题并提供了鲁棒的路径向后兼容.为了创建并命名系统中相应的/dev设备结点,udev需要依赖于根据用户提供的规则从sysfs中得到的匹配信息.本文着重规则书写的过程,udev相关的任务由用户自己完成.
sysfs是2.6内核中一个新的文件系统, 它由内核管理,并导出当前系统中插入的设备基本信息.udev可使用这些信息创建对应的硬件设备结点. sysfs挂载在/sys下而且是可浏览的.你可能很希望在使用udev之前刺探下存储在那儿的有关文件. 本文中我将交替使用/sys和sysfs术语.
为什么?
udev规则具有弹性非常强大,这里是一些你使用规则可以达到的结果:
1. 重命名设备节点的缺省名字为其他名字
2. 通过创建符号链接到缺省设备节点来提供一个可选的固定的设备节点名字
3. 基于程序的输出命名设备节点
4. 改变设备节点的权限和所有权
5. 但设备节点被创建或删除时(通常是添加设备或拔出设备时)执行一个脚本
6. 重命名网络接口
当存在的特定设备没有设备节点时,这不是书写规则的工作范围. 即使没有匹配的规则,udev也会利用内核提供的缺省名字来创建设备节点.
拥有固定命名设备节点有很多好处. 假设你有两个USB存储设备:一个数码相机,一个是USB闪存盘.这些设备通过被赋予/dev/sda和/dev/sdb设备节点,准确的赋值取决于它们连接到系统的顺序.这可能为一些用户造成麻烦,如果每个设备每次都可以固定命名,比如/dev/camera和/dev/flashdisk,用户就会获益.
内置固定命名方法
udev为系统外的一些设备类型提供了固定命名,这是一个很有用的特征,在某些情况下意味着你不用书写任何规则.
udev为存储设备在/dev/disk目录下提供了系统外命名方法. 要查看它为你的存储硬件创建的固定命名,你可以使用下列命名:
#ls -lR /dev/disk
所有存储类型都可以这么用.例如udev为我的根分区创建了固定命名链接:/dev/disk/by-id/scsi-SATA_ST3120827AS_4MS1NDXZ-part3.但我插入我的USB闪存盘udev就会创建另外一个固定命名节点:/dev/disk/by-id/usb-Prolific_Technology_Inc._USB_Mass_Storage_Device-part1.
规则书写
规则文件和语义
为决定如何命名设备以及执行什么另外动作,udev会读取一系列规则文件. 这些文件保存在/etc/udev/rules.d目录下并且都必须有.rules后缀名.
缺省udev规则存储在/etc/udev/rules.d/50-udev.rules里面. 你可能发现整个文件很有意思, 它包含了少量例子,一些缺省规则提供了devfs风格的/dev布局, 但是你不应该直接在这个文件里面书写规则.
/etc/udev/rules.d/下面的文件通过lexical顺序解析,在某些情况下规则的解析顺序很重要.通常来说你希望你的规则可以在缺省规则之前解析,所以我建议你创建一个文件/etc/udev/rules.d/10-local.rules并把自己的所有规则写到这里面去.
在一个规则文件中, 以"#"开头的行被认为是注释. 每一个非空的行都是一条规则. 规则不能跨越多行.
一个设备可以被多条规则匹配到, 这有着很实用的优点, 例如, 我们可以写两个匹配同一个设备的规则,每一个规则为设备提供了它自己的可选命名. 即使分开在不同的文件种, 两个可选命名也都会被创建,要明白udev在找到一个匹配规则后不会停止处理其他规则, 它仍然会继续查找并尝试应用已知的每条规则, 这很重要.
规则语法
每条规则通过一系列键值对创建,这些键值对通过逗号分隔. 匹配键是用来识别要应用规则的设备的条件, 但规则中对应设备的所有匹配键被处理后,就会应用规则并且赋值键的行为也会触发. 每条规则应该包含至少一个匹配键和至少一个赋值键.
这是用来阐述上面内容的一个例子规则:
KERNEL=="hdb",NAME="my_spare_disk"
上述规则包含一个匹配键(KERNEL)以及一个赋值键(NAME). 这些键和它们的属性的语义将在稍后具体说明. 注意到匹配键通过连等号(==)与它的值联系起来, 赋值键通过等号(=)与它的值关联.
注意udev不支持任何形式的行连接符, 不要在你的规则种插入任何断行符,这将会导致udev把你的一条规则看做是多条规则但不会按预料工作.
基本规则
udev提供一些用来书写精确匹配规则的匹配键, 其中一些常用键将在下面介绍, 其他将在文档的后面说明. 要得到完整列表可以查看udev的手册页.
KERNEL - 为设备匹配的内核名字
SUBSYSTEM - 匹配设备的子系统
DRIVER - 匹配支持设备的驱动名称
在你使用一系列匹配键来准确匹配设备后,udev通过赋值键为接下来发生的事给你提供更好的控制. 你可以查看udev的手册页查看完整的赋值键列表. 最基本的赋值键在下面说明, 其他的将在文档结束时说明.
NAME - 设备节点应该使用的名字
SYMLINK - 一个设备节点可选名字的符号链接列表
正如之前所说,udev会为设备创建一个真正的设备节点. 如果你希望为设备节点提供可选名字,你得通过SYMLINK使用符号链接功能,实际上是维护一个符号链接列表,这些符号链接都会指向真实的设备节点. 为了维护这些链接我们介绍一个新的附加操作符: +=.你可以在一个规则中附加多个符号链接到列表中,每个链接通过空格分开.
KERNEL=="hdb", NAME="my_spare_disk"
上面规则意思是:匹配一个设备命名为hdb的设备,把它重新命名为my_spare_disk. 设备节点出现在/dev/my_spare_disk.
KERNEL=="hdb", DRIVER=="ide-disk", SYMLINK+="sparedisk"
上面规则意思是:匹配一个内核命名为hdb以及驱动为ide-disk的设备,命名设备节点为缺省名字并创建一个指向它的sparedisk符号链接。注意到我们没有指明设备节点名字,于是udev使用缺省名字。为了保留标准/dev布局,你自己的规则通常没有NAME但会创建一些SYMLINK并/或执行其他赋值操作.
KERNEL=="hdc", SYMLINK+="cdrom cdrom0"
上面规则很可能就是你要写的典型规则。 它在/dev/cdrom和/dev/cdrom0创建了两个符号链接,都指向/dev/hdc. 再一次地,没有NAME赋值键,所以使用缺省的内核名字(hdc).
匹配sysfs属性
到目前为止介绍的匹配键仅仅提供了有限的匹配能力. 实际上我们需要更加优良的控制:我们想基于设备的高级属性来识别设备, 如供应商编码, 产品编号, 序列号, 存储能力, 分区数等等.
一些驱动导出这些信息到sysfs, udev允许我们使用ATTR键通过稍捎不同的语法来合并sysfs匹配到自己的规则中.
这里有一个匹配sysfs中单个属性例子. 更多细节将在稍后的帮助你基于sysfs属性书写规则的文档中提供.
SUBSYSTEM=="block", ATTR{size}=="234441648", SYMLINK+="my_disk"
设备级联
linux内核实际上以树状结构展示设备, 这个信息通过sysfs显露出来,在书写规则时这非常有用.例如我的硬盘设备的展示是一个SCSI磁盘设备的孩子, 这个SCSI磁盘设备又是一个ATA控制器设备的孩子, 该控制器又是PCI总线设备的孩子.你很有可能发现你需要从一个讨论中的设备的双亲那里引用信息, 比如我的硬盘设备的序列号在设备级别并不暴露出来,而是在SCSI磁盘级别通过它的直接双亲展现。
目前介绍的四个主要匹配键(KERNEL/SUBSYSTEM/DRIVER/ATTR)仅仅跟对应设备的值匹配, 并不跟双亲设备的值匹配. udev提供了在树中向上查找的匹配键变量:
KERNELS - 为设备匹配的内核名字,或任何双亲设备中的内核名
SUBSYSTEMS - 匹配设备的子系统名,或任何双亲设备中的子系统名
DRIVERS - 匹配支持设备的驱动名,或任何支持双亲设备的驱动名
ATTRS - 匹配设备的sysfs属性,或任何双亲设备的sysfs属性
由于在心里要考虑到级联结构,你可能感觉到规则书写变的有点复杂了. 歇一会吧,有工具可以帮助我们的,稍微献上.
字符串替换
但写的规则潜在的要处理多个相似的设备时, udev的printf-like string substitution operators就非常有用了. 你可以在你的规则里面的任何赋值里面包含这些操作符, udev在它们执行时会计算.
最常用的操作符是%k和%n. %k计算设备的内核名, 例如设备的"sda3"将(缺省)出现在/dev/sda3. %n计算设备(存储设备的分区号)的内核号码, 例如"3"将被换成"/dev/sda3".
udev也提供了一些高级功能替换操作符. 在读完本文剩下内容后可以查询udev的手册页. 以上例子中的操作符也有一个可选的语法 -$kernel和$number. 因此如果你希望在规则中匹配字符%, 你必需写成%%, 如果你希望匹配字符$, 你必须写成$$.
为阐述下字符串替换的概念,我们来展示几个例子:
KERNEL=="mice", NAME="input/%k"
KERNEL=="loop0", NAME="loop/%n", SYMLINK+="%k"
第一条规则确保鼠标设备节点一定出现在/dev/input目录下(缺省是在/dev/mice下面). 第二条规则确保名字为loop0的设备节点在/dev/loop/0创建,也会照常创建一个符号链接/dev/loop0.
上面规则的使用都比较可疑,因为他们都可以通过不使用任何替换操作符来重写. 这些替换的真正威力将会在下一节显现.
字符串匹配
不仅有字符串精确匹配, udev也允许你使用shell风格的模式匹配. 支持的3种模式为:
* - 匹配任何字符, 匹配0次或多次
? - 匹配任何字符,但只匹配一次.
[] - 匹配任何单个字符, 这些字符在方括号里面指定, 范围是受限的.
这里有一些例子, 注意字符串替换符的使用:
KERNEL=="fd[0-9]*", NAME="floppy/%n", SYMLINK+="%k"
KERNEL=="hiddev*", NAME="usb/%k"
第一条规则匹配所有软盘驱动并确保设备节点放置在/dev/floppy目录下, 也创建一个缺省名字的符号链接. 第二条规则确保hiddev设备节点放在/dev/usb目录下面.
从sysfs中查找信息
sysfs树
从sysfs中获取有意思信息的概念在之前的例子中已经触摸到了. 为了基于这些信息书写规则,你首先需要知道属性名和他们的当前值.
sysfs实际上是一个非常简单的结构,逻辑上以目录形式区分. 每一个目录包含一定量的文件(属性),这些文件往往都仅仅包含一个值. 也会有一些符号链接,它们把设备链到双亲那里, 级联结构已在上面说明了.
一些目录被引向顶层设备路径,这些目录展示了拥有对应设备节点的实际设备. 顶层设备路径可以被分类为包含dev文件的sysfs目录, 下列命令可以列举出这些文件:
#find /sys -name dev
例如, 在我的系统中, /sys/block/sda目录就是我的硬盘设备路径, 它通过/sys/block/sda/device符号链接链向它的双亲SCSI磁盘设备.
但你书写基于sysfs信息的规则时, 只是简单的匹配链条上一部分文件的属性内容. 例如, 我可以这样读我的硬盘的大小:
#cat /sys/block/sda/size
234441648
在一个udev规则里面, 我可以使用ATTR{size}=="234441648"来识别这个磁盘.因为udev反复遍历设备链的入口,我可以通过ATTRS匹配另外链中的属性(如:/sys/class/block/sda/device属性),但是在处理链中不同部分时会有一些告诫, 稍后描述.
虽然这对于关于sysfs的结构和udev如何匹配值的介绍很有用, 但对sysfs的全面梳理是个既耗时也没必要的事.
udevinfo
敲入udevinfo大概就是你用来创建规则的最直接的工具了。你需要知道的全部就是设备的sysfs设备路径. 下面是一个精简的例子:
# 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}=="0×0"
ATTRS{iodone_cnt}=="0×31737"
ATTRS{iorequest_cnt}=="0×31737"
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}=="0×10de"
ATTRS{device}=="0×037f"
正如你看到的, udevinfo简单的产生一个你可以在udev规则中作为匹配键的属性列表. 从上面例子中我可以为该设备产生下面两条规则:
SUBSYSTEM=="block", ATTR{size}=="234441648", NAME="my_hard_disk"
SUBSYSTEM=="block", SUBSYSTEMS=="scsi", ATTRS{model}=="ST3120827AS", NAME="my_hard_disk"
你可能发现到例子中使用了颜色, 这是为了阐述把设备属性和单个双亲设备属性并在一起是合法的, 你不能混合匹配多个双亲设备属性,这样你的规则不能工作. 例如,下列规则是不合理的,因为它试图匹配两个双亲设备属性:
SUBSYSTEM=="block", ATTRS{model}=="ST3120827AS", DRIVERS=="sata_nv", NAME="my_hard_disk"
通常有大量的属性提供给你,你必需挑选其中一些来创建你的规则. 一般来讲,你希望挑选那些能够固定标志你的设备并便于理解的属性. 上例中我选取的是我的磁盘大小和它的模型号,而没有使用没有意义的诸如ATTRS{iodone_cnt}=="0×31737"数字.
仔细观察下udevinfo的输出级联结构效果, 设备的绿色部分使用了标准匹配键如KERNEL和ATTR,蓝色部分和栗色部分使用了双亲遍历变量如SUBSYSTEMS和ATTRS,这就是为什么级联结构的复杂性实际上是很容易处理的原因,只要确保使用udevinfo建议的准确值就行了.
另外一点需要指出的是udevinfo输出的文本属性之间用空格填充(例如上面的ST3120827AS)是很常见的. 在你的规则中你可以添加额外的空格,也可以像我那样去掉.
使用udevinfo的唯一复杂之处在于要求你知道顶级设备路径(例如上面例子中的/sys/block/sda), 这通常并不明显. 但是, 因为你通常是为已经存在的设备节点写规则, 你可以自己使用udevinfo查找设备路径:
#udevinfo -a -p $(udevinfo -q path -n /dev/sda)
可选方法
虽然udevinfo差不多是列举你要构建的规则的准确属性的最直接方式, 但一些用户仍然乐于使用其他工具, 诸如usbview的工具可以显示类似的信息集, 这些信息也可以用在规则中.
高级话题
控制权限和所有权
udev允许你在规则中使用另外的赋值来控制每个设备的所有权和权限属性.
GROUP赋值允许你定义哪个Unix组应该拥有设备节点. 这里有一个例子规则, 它定义video组拥有framebuffer设备:
KERNEL=="fb[0-9]*", NAME="fb/%n", SYMLINK+="%k", GROUP="video"
OWNER键可能用处不大, 它允许你定义哪个Unix用户应该具有设备节点的拥有权限. 假设有个临时情况要你让join拥有软盘设备,你可以使用:
KERNEL="fd[0-9]*", OWNER="join"
udev缺省用Unix的0660权限(拥有者和组员拥有读写功能)创建设备节点. 需要的话你可以在特定设备的规则中使用包含MODE赋值键覆盖这些缺省值. 作为例子,下面的规则定义了inotify节点可以被每个人读写:
KERNEL=="inotify", NAME="misc/%k", SYMLINK+="%k", MODE="0666"
使用外部程序来命名设备
某些情况下你可能要求比udev标准规则提供的更多弹性, 这种情况下你可以请求udev运行一个程序并运用程序的标准输出来提供设备命名.
要使用这个功能,你只需简单的在PROGRAM赋值中指定要运行程序(以及任何阐述)的完整路径, 然后在NAME/SYMLINK赋值中使用一些%c替换.
下列例子引用一个位于/bin/device_namer的虚构程序. device_namer带一个表示内核名字的命令行参数,基于内核名device_namer做一些魔幻变换接着产生一些输出到普通stdout管道,这些输出被分割为很多小块,每一小块是一个单词,块之间用一个空格分开.
在我们的第一个例子中, 我们假设device_namer输出块的数目, 每一个形成当前设备的一个符号链接(名字可选).
KERNEL=="hda", PROGRAM="/bin/device_namer %k", SYMLINK+="%c"
下一个例子假设device_namer输出两块,第一块是设备名字, 第二块是另外的符号链接名字. 我们现在介绍%c[N]替换, 它引向输出的第N块:
KERNEL=="hda", PROGRAM="/bin/device_namer %k", NAME="%c{1}", SYMLINK+="%c{2}"
再下个例子假设device_namer输出设备名的一部分, 后面跟着的是快数, 它将形成另外的符号链接. 我们现在介绍%c{N+}替换, 它将被计算为块N, N+1, N+2…直到输出结束.
KERNEL=="hda", PROGRAM="/bin/device_namer %k", NAME="%c{1}", SYMLINK+="%c{2+}"
输出块也可在赋值键中使用,而不仅仅是NAME和SYMLINK中. 下面例子使用一个虚构程序来决定哪个Unix组拥有设备:
KERNEL=="hda", PROGRAM="/bin/who_owns_device %k", GROUP="%c"
特定事件发生时运行外部程序
另外一个书写udev规则的原因是为了在设备连接或者断开时运行一个特定程序. 例如, 你可能想在你的数码相机连到系统时执行一个脚本来自动下载相机里面的所有照片.
不要把这个跟上述的PROGRAM功能弄混淆了, PROGRAM是用来运行产生设备名字(除此之外不应该做其他事情)的程序. 但这些程序执行时, 设备节点设备节点还没有被创建, 所以对设备的任何形式的操作都是不可能的.
这里介绍的功能允许你在设备节点到位后运行一个程序. 该程序可以作用在设备上, 但是它不准在时间周期外运行,因为当程序运行时udev会正常中止. 这个限制的一个权宜之计是确保你的程序立即分离自身.
这里有个展示RUN赋值的例子:
KERNEL=="sdb", RUN+="/usr/bin/my_program"
当/usr/bin/my_program执行时, udev环境的各部分可作为环境变量可用,包括诸如SUBSYSTEM的键值. 你也可以使用ACTION环境变量来检测设备是否连接或断开, 它的值是"add"和"remove"其中的一个.
udev并不在任何激活终端中运行这些程序,也不再shell上下文中执行. 确信你的程序是被标记为可执行的, 如果它是个shell脚本请确保它以适当的shabang开头(比如: #!/bin/sh)也不要期望任何标准输出出现在你的终端上.
环境交互
udev为环境变量提供了一个ENV键, 即可用来匹配也可用来赋值.
在赋值情况下,你可以设置稍后匹配的环境变量. 你也可以设置环境变量,供通过上面提供的技巧触发的任何外部程序使用. 一个虚构的设置环境变量的例子规则如下:
KERNEL=="fd0", SYMLINK+="floppy", ENV{some_var}="value"
在匹配情况下你可以确保规则仅仅依赖一个环境变量的值而运行. 注意udev看到的环境跟你在控制台上得到的用户环境不一样. 一个虚构的涉及环境匹配的规则如下:
KERNEL=="fd0", ENV{an_env_var}=="yes", SYMLINK+="floppy"
上面规则仅仅在udev环境中的$an_env_var的值设为"yes"时才创建/dev/floppy链接.
另外选项
另外一个有用的赋值是OPTIONS列表. 不多的可用的选项有:
all_partitions - 为一个块设备创建所有可能的分区, 而不是初始检测到的那些分区.
ignore_device - 完全忽略事件.
last_rule - 确保后面的所有规则不会有效.
例如, 下面规则设置我的硬盘节点的组所有权,并且后面的规则对它没有任何效果:
KERNEL=="sda", GROUP="disk", OPTIONS+="last_rule"
例子
USB打印机
我启动我的打印机, 它就被赋予了一个设备节点/dev/lp0. 我对这样的单调的名字不满意并打算使用udevinfo帮我写一个规则来提供一个可选名字:
# udevinfo -a -p $(udevinfo -q path -n /dev/lp0)
looking at device '/class/usb/lp0':
KERNEL=="lp0"
SUBSYSTEM=="usb"
DRIVER==""
ATTR{dev}=="180:0"
looking at parent device '/devices/pci0000:00/0000:00:1d.0/usb1/1-1':
SUBSYSTEMS=="usb"
ATTRS{manufacturer}=="EPSON"
ATTRS{product}=="USB Printer"
ATTRS{serial}=="L72010011070626380"
我的规则变成了这样:
SUBSYSTEM=="usb", ATTRS{serial}=="L72010011070626380", SYMLINK+="epson_680"
USB相机
跟大多数相机一样, 我的相机标识自己为一个通过USB总线来连接通过SCSI传输的外部硬盘. 为了访问我的相片我先挂载驱动然后拷贝图片文件到我的硬盘中.
不是所有相机都这样工作, 其中一些使用非存储协议,如gphoto2支持的相机. 在gphoto情况下,你不用为你的设备写任何规则,因为纯粹由用户空间控制(而不是指定的内核驱动).
USB相机设备的一个通常的复杂性在于它们通常标识自己是一个单分区磁盘,即带有/dev/sdb1的/dev/sdb.sdb节点对我毫无用处,但对sdb1有兴趣 - 这才是我要挂载的. 这里有个麻烦因为sysfs是链式的,udevinfo为/dev/sdb1产生的有用属性跟为/dev/sdb产生的一样, 这导致你的规则潜在的匹配到原始磁盘和分区,这不是你想要的, 你的规则应该明确下.
为解决这个问题,你需要简单的考虑下sdb和sdb1之间有什么区别. 令人惊奇的简单: 只是名字上的区别, 所以我们可在NAME域上使用一个简单的模式匹配.
# udevinfo -a -p $(udevinfo -q path -n /dev/sdb1)
looking at device '/block/sdb/sdb1':
KERNEL=="sdb1"
SUBSYSTEM=="block"
looking at parent device '/devices/pci0000:00/0000:00:02.1/usb1/1-1/1-1:1.0/host6/target6:0:0/6:0:0:0':
KERNELS=="6:0:0:0"
SUBSYSTEMS=="scsi"
DRIVERS=="sd"
ATTRS{rev}=="1.00"
ATTRS{model}=="X250,D560Z,C350Z"
ATTRS{vendor}=="OLYMPUS "
ATTRS{scsi_level}=="3"
ATTRS{type}=="0"
我的规则:
KERNEL=="sd?1", SUBSYSTEMS=="scsi", ATTRS{model}=="X250,D560Z,C350Z", SYMLINK+="camera"
USB硬盘
USB硬盘跟USB相机差不多,但典型的使用方式不同. 在相机例子中, 我有讲到我对sdb节点没有兴趣, 它仅仅在分区(比如使用fdisk)时才有用, 但我为什么要为我的相机分区呢?
当然如果你有一个100GB的USB硬盘, 你希望为它分区是很好理解的, 这种情况下我们可以使用udev的字符串替换长处:
KERNEL=="sd*", SUBSYSTEM=="scsi", ATTRS{model}=="USB 2.0 Storage Device", SYMLINK+="usbhd%n"
这个规则创建诸如下面的符号链接:
/dev/usbhd - 可被fdisk使用的node
/dev/usbhd1 - 第一块分区(可挂载)
/dev/usbhd2 - 第二块分区(可挂载)
USB读卡器
USB读卡器(CompactFlash, SmartMedia等)属于USB存储设备的另外一种范围, 它有不同的使用需求.
这些设备特别的在媒介改变时不用通知主机. 所以如果你插入没有媒介的设备,接着插入一张卡, 计算机不会意识到这点, 你也不会有媒介的可挂载的sdb1分区节点.
一个可能的解决办法是使用all_partttions选项优点, 它将为每个规则匹配的块设备创建16个分区节点:
KERNEL="sd*", SUBSYSTEM=="scsi", ATTRS{model}=="USB 2.0 CompactFlash Reader", SYMLINK+="cfrdr%n", OPTIONS+="all_partitions"
你将得到这些命名节点:cfrdr, cfrdr1, cfrdr2, cfrdr3, …, cfrdr15.
USB Palm导航仪
这些设备作为USB串行设备工作, 所以缺省的你仅仅得到ttyUSB1设备节点. palm工具依赖于/dev/pilot, 一些用户希望使用一条规则提供这个.
Carsten Clasohm的博客上有完整的源. Carsten的规则如下:
SUBSYSTEM=="usb", ATTRS{product}=="Palm Handheld", KERNEL=="ttyUSB*", SYMLINK+="pilot"
注意到产品字符串因产品不同而不同, 所以确保你检查(使用udevinfo)了哪一个可以应用到你自己的产品.
CD/VCD驱动
我的电脑有两个光驱: 一个DVD只读驱动(hdc)和一个DVD刻录机(hdd). 我不希望这些设备节点改变除非我物理性的重新接线, 但是一些用户喜欢拥有诸如/dev/dvd这么方便的设备节点.
我们都知道KERNEL是这些设备的名字, 规则书写就很简单了. 这是我的系统上一些规则:
SUBSYSTEM=="block", KERNEL=="hdc", SYMLINK+="dvd", GROUP="cdrom"
SUBSYSTEM=="block", KERNEL=="hdd", SYMLINK+="dvdrw", GROUP="cdrom"
网卡
尽管它们都是通过名字引用,网卡往往没有与之关联的设备节点. 尽管这样,规则书写过程还是相同的.
在规则中简单的匹配网卡MAC地址是有意义的,因为它们是唯一的. 但是, 确信你使用的是udevinfo显示的准确MAC地址, 因为如果你没有精确匹配,你的规则不会工作.
# udevinfo -a -p /sys/class/net/eth0
looking at class device '/sys/class/net/eth0':
KERNEL=="eth0"
ATTR{address}=="00:52:8b:d5:04:48"
这是我的规则:
KERNEL=="eth*", ATTR{address}=="00:52:8b:d5:04:48", NAME="lan"
为了让这个规则生效你得重启网络驱动, 你可以卸载并重新加载模块或简单的重启系统即可.你还得需要重新配置你的系统使用"lan"取代"eth0". 我以前遇到过这样的麻烦(网卡不能重命名)直到我去除了所有对eth0的引用.在此之后你应该可以在任何ifconfig或类似的工具的调用中使用"lan"而不是"eth0"了.
测试和调试
让你的规则跑起来
假定你在一个有inotify支持的最近内核上工作, udev将自动监视你的规则目录并且自动挑取你对规则文件的任何修改.
尽管这样, udev也不会自动重新处理所有设备并试图应用新规则. 例如, 如果你写了个规则来为你的已连接相机添加一个额外符号链接, 你不能指望这个符号链接可以马上显现出来.
为使符号链接显示, 你可以断开并重连你的相机或者在非可移除设备情况下运行udevtrigger.
如果你的内核没有inotify支持, 新规则不会自动被检测到. 这种情况下你必需在做出更改后运行udevcontrol reload_rules使之生效.
udevtest
如果你知道sysfs中的顶级设备路径, 你可以使用udevtest来显示udev将要执行的动作, 这可能会帮你调试你的规则. 例如, 假设你想调试作用在/sys/class/sound/dsp上的规则:
# udevtest /class/sound/dsp
main: looking at device '/class/sound/dsp' from subsystem 'sound'
udev_rules_get_name: add symlink 'dsp'
udev_rules_get_name: rule applied, 'dsp' becomes 'sound/dsp'
udev_device_event: device '/class/sound/dsp' already known, remove possible symlinks
udev_node_add: creating device node '/dev/sound/dsp', major = '14', minor = '3', mode = '0660', uid = '0', gid = '18'
udev_node_add: creating symlink '/dev/dsp' to 'sound/dsp'
注意/sys前缀在udevtest命令行中被删除了, 这是因为udevtest在设备路径上操作. 还要留意的是udevtest是一个纯粹的测试/调试工具, 它不创建任何设备节点无论输出怎么建议.