linux udev学习

自2.6 核心开始,就可以使用udev 协助管理系统中各设备名称。例如,磁盘设备排序、网卡设备排序等。udev能动态地在/dev 目录里产生自定义的、标识性强的设备文件或设备链接。本文即以红旗Asianux 3.0 平台,给新加载的U盘设备自定义一个链接为例进行简要说明。

一、关于udev
2.4 内核使用devfs(设备文件系统)在设备初始化时创建设备文件,设备驱动程序可以指定设备号、所有者、用户空间等信息,devfs 运行在内核环境中,并有不少缺点:可能出现主/辅设备号不够,命名不灵活,不能指定设备名称等问题。而自2.6 内核开始,引入了sysfs 文件系统。sysfs 把连接在系统上的设备和总线组织成一个分级的文件,并提供给用户空间存取使用。udev 运行在用户模式,而非内核中。udev 的初始化脚本在系统启动时创建设备节点,并且当插入新设备——加入驱动模块——在sysfs上注册新的数据后,udev会创新新的设备节点。
udev 是一个工作在用户空间的工具,它能根据系统中硬件设备的状态动态的更新设备文件,包括设备文件的创建,删除,权限等。这些文件通常都定义在/dev 目录下,但也可以在配置文件中指定。udev 必须内核中的sysfs和tmpfs支持,sysfs 为udev 提供设备入口和uevent 通道,tmpfs 为udev 设备文件提供存放空间。
注意,udev 是通过对内核产生的设备文件修改,或增加别名的方式来达到自定义设备文件的目的。但是,udev 是用户模式程序,其不会更改内核行为。也就是说,内核仍然会创建sda,sdb等设备文件,而udev可根据设备的唯一信息来区分不同的设备,并产生新的设备文件(或链接)。而在用户的应用中,只要使用新产生的设备文件即可。
udev 的工作流程图:

 

二、参考文档
正如前面提到的,udev 依赖于2.6 核心上的sysfs 文件系统。因此,只有在红旗DC 5.0以上版本中才能使用。随着udev 的不断发展,不同版本的udev 规则也有不少差别,编写规则时必须注意。
因为不同版本的udev 规则定义方式不同,在编写时需特别留意。我主要是参考以下资料来了解相关规则的:
官网:http://www.kernel.org/pub/linux/utils/kernel/hotplug/udev.html
man udev
Writing udev rules
使用 udev 高效、动态地管理 Linux 设备文件

udev 服务的主要配置文件在/etc/udev/udev.conf,但通常不同修改。
规 则文件是存放在/etc/udev/rules.d 目录下面,所有的规则文件都必须以.rules 作为后缀名。系统安装完毕后,该目录中就会有一些默认的规则文件,主要用于产生一些容易标识的设备符号链接。同时,一些应用程序,为了在/dev 下产生方便使用的标识符,也会放入一些规则,例如:40-multipath.rules、99-fuse.rules 等。
udev 是按照规则文件的字母顺序来解析各规则文件的,并根据匹配上的规则创建对应的设备文件或链接。所以,解析的顺序很重要,为了使自定义的规则生效,可以把规则写入较前的规则文件中,例如20-names.rules。

三、规则说明
以下规则说明来自使用 udev 高效、动态地管理 Linux 设备文件一文,与Asianux 3.0 中man udev 中的说明一致,我就不一一翻译了。
1、udev 规则的所有操作符

引用
“==”:比较键、值,若等于,则该条件满足;
“!=”: 比较键、值,若不等于,则该条件满足;
“=”: 对一个键赋值;
“+=”:为一个表示多个条目的键赋值。
“:=”:对一个键赋值,并拒绝之后所有对该键的改动。目的是防止后面的规则文件对该键赋值。


2、udev 规则的匹配键

引用
ACTION: 事件 (uevent) 的行为,例如:add( 添加设备 )、remove( 删除设备 )。
KERNEL: 内核设备名称,例如:sda, cdrom。
DEVPATH:设备的 devpath 路径。
SUBSYSTEM: 设备的子系统名称,例如:sda 的子系统为 block。
BUS: 设备在 devpath 里的总线名称,例如:usb。
DRIVER: 设备在 devpath 里的设备驱动名称,例如:ide-cdrom。
ID: 设备在 devpath 里的识别号。
SYSFS{filename}: 设备的 devpath 路径下,设备的属性文件“filename”里的内容。
例如:SYSFS{model}==“ST936701SS”表示:如果设备的型号为 ST936701SS,则该设备匹配该 匹配键。
在一条规则中,可以设定最多五条 SYSFS 的 匹配键。
ENV{key}: 环境变量。在一条规则中,可以设定最多五条环境变量的 匹配键。
PROGRAM:调用外部命令。
RESULT: 外部命令 PROGRAM 的返回结果。


3、udev 的重要赋值键

引用
NAME:在 /dev下产生的设备文件名。只有第一次对某个设备的 NAME 的赋值行为生效,之后匹配的规则再对该设备的 NAME 赋值行为将被忽略。如果没有任何规则对设备的 NAME 赋值,udev 将使用内核设备名称来产生设备文件。
SYMLINK:为 /dev/下的设备文件产生符号链接。由于 udev 只能为某个设备产生一个设备文件,所以为了不覆盖系统默认的 udev 规则所产生的文件,推荐使用符号链接。
OWNER, GROUP, MODE:为设备设定权限。
ENV{key}:导入一个环境变量。


4、udev 的值和可调用的替换操作符

引用
Linux 用户可以随意地定制 udev 规则文件的值。例如:my_root_disk, my_printer。同时也可以引用下面的替换操作符:
$kernel, %k:设备的内核设备名称,例如:sda、cdrom。
$number, %n:设备的内核号码,例如:sda3 的内核号码是 3。
$devpath, %p:设备的 devpath路径。
$id, %b:设备在 devpath里的 ID 号。
$sysfs{file}, %s{file}:设备的 sysfs里 file 的内容。其实就是设备的属性值。
$env{key}, %E{key}:一个环境变量的值。
$major, %M:设备的 major 号。
$minor %m:设备的 minor 号。
$result, %c:PROGRAM 返回的结果。
$parent, %P:父设备的设备文件名。
$root, %r:udev_root的值,默认是 /dev/。
$tempnode, %N:临时设备名。
%%:符号 % 本身。
$$:符号 $ 本身。


四、实例说明
下面以在Asianux 3.0 平台上为一块U盘创建一个新的设备文件为例说明。
1、查询设备属性
规则中,需要给出匹配的设备属性,例如设备的序列号、厂商ID、磁盘大小等,用于区分不同的设备。这有三种方法:
a、由udevinfo 命令得到

引用
# udevinfo -a -p /block/sda
  looking at device '/block/sda':
    KERNEL=="sda"
    SUBSYSTEM=="block"
    SYSFS{stat}=="      42       25      536     1882        0        0        0        0        0     1882     1882"
    SYSFS{size}=="128000"
......
  looking at parent device '/devices/pci0000:00/0000:00:03.0/usb1':
    ID=="usb1"
    BUS=="usb"
    DRIVER=="usb"
    SYSFS{configuration}==""
    SYSFS{serial}=="0000:00:03.0"
    SYSFS{product}=="OHCI Host Controller"
......


b、由sysfs 文件系统获得

引用
# cat /sys/block/sda/size
128000


c、通过外部命令
通过scsi_id、path_id、usb_id 等命令获取:

引用
# /lib/udev/usb_id -x /block/sda
ID_VENDOR=0c76
ID_MODEL=0005
ID_REVISION=0100
ID_SERIAL=0c76_0005
ID_TYPE=disk
ID_BUS=usb


※ 注意,可供选择的设备属性有不少,但必须选择固定不变的属性,例如厂商ID、大小等。换句话说,不能选择随重启或插入而改变的信息,例如pci_id等,否则,同样的规则下次将不能匹配成功。
2、创建规则

# cat 20-names.rules
SUBSYSTEM=="block",BUS=="usb",SYSFS{product}=="OHCI Host Controller",NAME="usbhd%n"


※ 注意,最后的%n,若没有这变量符,将不会创建该设备下分区的映射;另外,双等号“==”与等号“=”是不能混淆使用。

3、测试规则

引用
# udevtest /block/sda
main: looking at device '/block/sda' from subsystem 'block'
udev_rules_get_name: rule applied, 'sda' becomes 'usbhd'
run_program: '/lib/udev/usb_id -x'
run_program: '/lib/udev/usb_id' (stdout) 'ID_VENDOR=0c76'
run_program: '/lib/udev/usb_id' (stdout) 'ID_MODEL=0^'
run_program: '/lib/udev/usb_id' (stdout) 'ID_REVISION=0100'
run_program: '/lib/udev/usb_id' (stdout) 'ID_SERIAL=0c76_0^'
run_program: '/lib/udev/usb_id' (stdout) 'ID_TYPE=disk'
run_program: '/lib/udev/usb_id' (stdout) 'ID_BUS=usb'
run_program: '/lib/udev/usb_id' returned with status 0
udev_rules_get_name: 1 untrusted character(s) replaced
udev_rules_get_name: add symlink 'disk/by-id/usb-0c76_0_'
run_program: '/lib/udev/path_id /block/sda'
run_program: '/lib/udev/path_id' (stdout) 'ID_PATH=pci-0000:00:03.2-usb-0:1:1.0-scsi-0:0:0:0'
run_program: '/lib/udev/path_id' returned with status 0
udev_rules_get_name: add symlink 'disk/by-path/pci-0000:00:03.2-usb-0:1:1.0-scsi-0:0:0:0'
udev_device_event: device '/block/sda' already in database, validate currently present symlinks
udev_node_add: creating device node '/dev/usbhd', major = '8', minor = '0', mode = '0640', uid = '0', gid = '6'
udev_node_add: creating symlink '/dev/disk/by-id/usb-0c76_0_' to '../../usbhd'
udev_node_add: creating symlink '/dev/disk/by-path/pci-0000:00:03.2-usb-0:1:1.0-scsi-0:0:0:0' to '../../usbhd'
main: run: 'socket:/org/kernel/udev/monitor'
main: run: '/lib/udev/udev_run_devd'
main: run: 'socket:/org/freedesktop/hal/udev_event'
main: run: '/sbin/pam_console_apply /dev/usbhd /dev/disk/by-id/usb-0c76_0_ /dev/disk/by-path/pci-0000:00:03.2-usb-0:1:1.0-scsi-0:0:0:0'


从红色标记的地方可见,原来的sda设备被改为自定义的usbhd设备符(剩余信息是其他规则匹配的情况)。

4、启动udev

引用
# start_udev
启动 udev:                                                [确定]


5、测试
从/dev 目录下,可查看到新创建的设备符:

引用
# ll /dev/usbhd*
brw-r----- 1 root disk 8, 0 08-10 17:49 /dev/usbhd
brw-r----- 1 root disk 8, 1 08-10 17:50 /dev/usbhd1


挂载测试:

引用
# mount /dev/usbhd1 /mnt/disk
# mount|grep usbhd1
/dev/usbhd1 on /mnt/disk type vfat (rw)
# df |grep usbhd1
/dev/usbhd1              62952     52172     10780  83% /mnt/disk


可见,我们新创建的设备符已经生效,并可以使用。
※ 注意,由于我们使用的是NAME方式,即新的usbhd会直接替换为sda,因此,在fdisk -l 命令下,可能会看不到原来的sda设备。这时,可通过dmesg命令获取内核分配的设备符:

引用
# dmesg
usbcore: registered new driver usb-storage
USB Mass Storage support registered.
  Vendor:           Model:                   Rev:
  Type:   Direct-Access                      ANSI SCSI revision: 02
SCSI device sda: 128000 512-byte hdwr sectors (66 MB)
sda: Write Protect is off
sda: Mode Sense: 0b 00 00 08
sda: assuming drive cache: write through
SCSI device sda: 128000 512-byte hdwr sectors (66 MB)
sda: Write Protect is off
sda: Mode Sense: 0b 00 00 08
sda: assuming drive cache: write through
sda: sda1
sd 0:0:0:0: Attached scsi removable disk sda
usb-storage: device scan complete
sd 0:0:0:0: Attached scsi generic sg0 type 0


另外,为避免与内核产生的设备文件冲突,新创建的设备文件应使用自定义的易标识名称,而不要使用sdb/sdc 等,否则,可能会给其他应用(如ASM等)带来麻烦。

6、使用其他属性创建规则
上面,我们是直接使用udevinfo 得到的信息来编写规则,下面借助外部程序来匹配设备属性。
首先,编写一个自定义规则:

引用
# cat /lib/udev/select_usb_disk.sh
#!/bin/bash
/lib/udev/usb_id -x $1|grep ID_VENDOR|awk -F '=' {'print $2'}


给予可执行权限:

# chmod +x /lib/udev/select_usb_disk.sh


测试其输入的结果:

# /lib/udev/select_usb_disk.sh /block/sda
0c76


编写规则:

引用
# cat 20-names.rules
KERNEL=="sd*",PROGRAM="/lib/udev/select_usb_disk.sh %p",RESULT=="0c76",SYMLINK+="usbhd%n"


测试规则:

引用
# udevtest /block/sda
main: looking at device '/block/sda' from subsystem 'block'
run_program: '/lib/udev/select_usb_disk.sh /block/sda'
run_program: '/lib/udev/select_usb_disk.sh' (stdout) '0c76'
run_program: '/lib/udev/select_usb_disk.sh' returned with status 0
udev_rules_get_name: add symlink 'usbhd'
run_program: '/lib/udev/usb_id -x'
run_program: '/lib/udev/usb_id' (stdout) 'ID_VENDOR=0c76'
run_program: '/lib/udev/usb_id' (stdout) 'ID_MODEL=0^'
run_program: '/lib/udev/usb_id' (stdout) 'ID_REVISION=0100'
run_program: '/lib/udev/usb_id' (stdout) 'ID_SERIAL=0c76_0^'
run_program: '/lib/udev/usb_id' (stdout) 'ID_TYPE=disk'
run_program: '/lib/udev/usb_id' (stdout) 'ID_BUS=usb'
run_program: '/lib/udev/usb_id' returned with status 0
udev_rules_get_name: 1 untrusted character(s) replaced
udev_rules_get_name: add symlink 'disk/by-id/usb-0c76_0_'
run_program: '/lib/udev/path_id /block/sda'
run_program: '/lib/udev/path_id' (stdout) 'ID_PATH=pci-0000:00:03.2-usb-0:1:1.0-scsi-0:0:0:0'
run_program: '/lib/udev/path_id' returned with status 0
udev_rules_get_name: add symlink 'disk/by-path/pci-0000:00:03.2-usb-0:1:1.0-scsi-0:0:0:0'
udev_rules_get_name: no node name set, will use kernel name 'sda'
udev_device_event: device '/block/sda' already in database, validate currently present symlinks
udev_node_add: creating device node '/dev/sda', major = '8', minor = '0', mode = '0640', uid = '0', gid = '6'
udev_node_add: creating symlink '/dev/usbhd' to 'sda'
udev_node_add: creating symlink '/dev/disk/by-id/usb-0c76_0_' to '../../sda'
udev_node_add: creating symlink '/dev/disk/by-path/pci-0000:00:03.2-usb-0:1:1.0-scsi-0:0:0:0' to '../../sda'
main: run: 'socket:/org/kernel/udev/monitor'
main: run: '/lib/udev/udev_run_devd'
main: run: 'socket:/org/freedesktop/hal/udev_event'
main: run:'/sbin/pam_console_apply /dev/sda /dev/usbhd/dev/disk/by-id/usb-0c76_0_ /dev/disk/by-path/pci-0000:00:03.2-usb-0:1:1.0-scsi-0:0:0:0'


删除旧设备符:

# rm -f /dev/usbhd*


※ 注意,使用start_udev并不会删除原来由NAME在/dev 目录下创建的设备文件,并且可能与新规则产生冲突,导致start_udev服务启动超时或出错,所以,在测试规则时,应预先删除旧规则创建的设备符。拔 去对应的物理设备,udev 会自动删除规则创建的设备文件或链接,但期间若修改了规则,导致规则所对应的设备文件或链接不相符,则这些旧的设备文件或链接可能会被遗留下来。

启动udev,创建新设备符:

引用
# start_udev
# ll /dev/usbhd*
lrwxrwxrwx 1 root root 3 08-10 18:18 /dev/usbhd -> sda
lrwxrwxrwx 1 root root 4 08-10 18:18 /dev/usbhd1 -> sda1


挂载测试:

引用
# mount /dev/usbhd1 /mnt/disk
# mount |grep disk
/dev/sda1on /mnt/disk type vfat (rw)


可见,新设备符可用。
※ 请特别留意,规则中使用的是SYMLINK,所以,创建的仅是一个指向内核产生的设备的链接,而非取代原来的设备符。所以,在挂载成功后,mount命令显示的也是实际的内核设备。这很容易引起误会,请特别小心。

至此,我要讲解的udev 自定义规则方式已完毕。除了用于存储设备外,其他如Network interface等也能用udev 来处理。例如最常见的就是Asianux 3.0 下网络设备名自动变更的问题,可通过创建自定义规则:

引用
SUBSYSTEM=="net", SYSFS{address}=="AA:BB:CC:DD:EE:FF", NAME="public_NIC"


绑定固定的MAC地址,即可轻松解决。这比修改/etc/sysconfig/network-scripts/ifcfg-ethx 要可靠和稳定得多。

但是,udev 也不是万能的。至少,在我的实践中,可能因为udev 版本太低等原因,DC 5.0 平台下(2.6.9-89 核心),udev 为039,配置RDAC后,udev 的规则无法匹配内核产生的sdb等设备,至今没找到解决办法。

不过,udev 毕竟提供了一条很好的处理设备名冲突、变更等问题引起故障的解决办法,应熟悉和充分利用。

五、附录
使用 udev 高效、动态地管理 Linux 设备文件的pdf 版本:

http://www.linuxfly.org/attachment/1281437205_4694a742.pdf

http://www.cnblogs.com/sopost/archive/2013/01/09/2853200.html

 

如果你使用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

error = kobject_add(&dev->kobj, dev->kobj.parent, "%s", dev_name(dev));

if (error)

goto Error;

.......

kobject_uevent(&dev->kobj, KOBJ_ADD);

bus_attach_device(dev);

if (parent)

klist_add_tail(&dev->knode_parent, &parent->klist_children);

   .......

 attrError:

kobject_uevent(&dev->kobj, KOBJ_REMOVE);

kobject_del(&dev->kobj);

 Error:

cleanup_device_parent(dev);

if (parent)

put_device(parent);

goto done;

}

上面的代码段为我们常见的添加注册设备时的会调用到的接口,device_add()函数,删除了一些无关代码,可以看出,是先调用了kobject_add()创建添加该内核对象,然后调用kobject_uevent()来通知系统uevent的变化,这里的action是KOBJ_ADD,相对应还有

enum kobject_action {

KOBJ_ADD,

KOBJ_REMOVE,

KOBJ_CHANGE,

KOBJ_MOVE,

KOBJ_ONLINE,

KOBJ_OFFLINE,

KOBJ_MAX

};

在kobject_uevent()里面采用的就是Linux中比较经典的内核空间和用户空间的一种通信机制netlink socket,这个不是udev的重点,我也不做过多的解释,总之相信它能让内核空间和用户空间进行通信就行了。在udev也会有相应的socket来接受底层的消息。如下为参照udev源码写的一个简单的uevent消息侦听程序:

#define UEVENT_BUFFER_SIZE      2048

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;

    }

    

    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;

}

int main(int argc, char* argv[])

{

         int hotplug_sock       = init_hotplug_sock();

          while(1)

          {

//printf("sunqidong debug\n");

                   char buf[UEVENT_BUFFER_SIZE*2] = {0};

                   recv(hotplug_sock, &buf, sizeof(buf), 0); 

                   printf("%s\n", buf);

          }

         return 0;

}

这也是一个后台服务程序,循环的执行接受底层的消息过来,当发生U盘的插拔时,会产生如下的log:

[root@localhost test]# ./hotplug

add@/devices/pci0000:00/0000:00:11.0/0000:02:03.0/usb1/1-1

add@/class/usb_endpoint/usbdev1.5_ep00

add@/devices/pci0000:00/0000:00:11.0/0000:02:03.0/usb1/1-1/1-1:1.0

add@/class/scsi_host/host6

add@/class/usb_endpoint/usbdev1.5_ep81

add@/class/usb_endpoint/usbdev1.5_ep02

add@/devices/pci0000:00/0000:00:11.0/0000:02:03.0/usb1/1-1/1-1:1.0/host6/target6:0:0/6:0:0:0

add@/class/scsi_disk/6:0:0:0

add@/block/sdb

add@/block/sdb/sdb1

add@/block/sdb/sdb2

add@/block/sdb/sdb5

add@/block/sdb/sdb6

add@/block/sdb/sdb7

add@/block/sdb/sdb8

add@/class/scsi_device/6:0:0:0

add@/class/scsi_generic/sg2

add@/class/bsg/6:0:0:0

remove@/class/usb_endpoint/usbdev1.5_ep81

remove@/class/usb_endpoint/usbdev1.5_ep02

remove@/class/bsg/6:0:0:0

remove@/class/scsi_generic/sg2

remove@/class/scsi_device/6:0:0:0

remove@/class/scsi_disk/6:0:0:0

remove@/block/sdb/sdb8

remove@/block/sdb/sdb7

remove@/block/sdb/sdb6

remove@/block/sdb/sdb5

remove@/block/sdb/sdb2

remove@/block/sdb/sdb1

remove@/block/sdb

remove@/devices/pci0000:00/0000:00:11.0/0000:02:03.0/usb1/1-1/1-1:1.0/host6/target6:0:0/6:0:0:0

remove@/class/scsi_host/host6

remove@/devices/pci0000:00/0000:00:11.0/0000:02:03.0/usb1/1-1/1-1:1.0

remove@/class/usb_endpoint/usbdev1.5_ep00

remove@/devices/pci0000:00/0000:00:11.0/0000:02:03.0/usb1/1-1

三.udev的规则文件     规则文件是udev中最重要的部分,默认是存放在/etc/udev/rules.d/下。所有的规则文件都必须以".rules"为后缀名。下面是一个简单的规则文件例子说明

      KERNEL=="sdb8" ,  NAME="mydisk",MODE="0660"

 KERNEL是匹配键,NAME和MODE是赋值键。"=="这是判断语句,"="是赋值语句,这条规则的意思是,如果有一个设备的内核设备名称为sdb8(我移动硬盘内的一个分区),则该条件生效,执行后面的赋值:在/dev/下产生名为mydisk的设备文件,并把该设备文件的权限设为0660.

     通过这条简单的规则,应该就可以对规则文件有了个基本的了解。每个规则文件被分成一个或多个匹配和赋值部分。匹配部分用匹配专用的关键字来表示,相应的赋值部分用赋值专用的字来表示。

     1.常见的匹配关键字: ACTION(用于匹配行为add/remove),KERNEL(内核中定义的设备名),BUS(用于匹配总线类型),SYSFS(用于匹配从sysfs得到的信息,比如lable,vendor,USB序列号等),SUBSYSTEM(匹配子系统名)等

     2.常见的赋值关键字:

NAME(创建的设备文件名),SYMLINK(符号创建链接名),OWNER(设置设备的所有者),GROUP(设置设备的组),IMPORT(调用外部程序),MODE(权限位)

 

四.xx项目上自动挂载usb存储设备的应用

在xx项目上,我们需要自动挂载usb存储设备,并且要支持常见的几种文件系统,fat32,ntfs,exfat等,其中fat32等fat系列,Linux下早就有支持,ntfs和exfat目前的内核自身还没支持,我们有关于这两个文件系统的内核模块文件tntfs,ko和texfat.ko,加载进去过后就能让我们的内核识别这两种文件系统,实现手动加载这两种格式的存储设备。但如果要支持自动加载还有问题,需要去修改相应的规则文件。

在加载的时候,不同的格式的文件系统,加载的参数是不一样的,如exfat为

mount -t texfat /dev/sda /mnt/udisk

而ntfs为

mount -t tntfs /dev/sda /mnt/udisk

并且针对不同的格式,还有些其他挂载选项参数不一样,所以对不同的格式需要区别对待。

在规则文件里面是通过blkid -o udev命令来获取文件系统的信息的,判断出该盘是哪种格式,再去执行不同的挂载命令。

如下是blkid -o udev读出来的文件系统格式信息

ID_FS_UUID=2EE054B8E054884B

ID_FS_UUID_ENC=2EE054B8E054884B

ID_FS_LABEL=disk3

ID_FS_LABEL_ENC=disk3

ID_FS_TYPE=ntfs

ID_FS_LABEL=DISK4

ID_FS_LABEL_ENC=DISK4

ID_FS_UUID=B8CF-FF22

ID_FS_UUID_ENC=B8CF-FF22

ID_FS_TYPE=vfat

ID_FS_UUID=3606-1B2C

ID_FS_UUID_ENC=3606-1B2C

ID_FS_TYPE=exfat

ID_FS_LABEL=Disk5

ID_FS_LABEL_ENC=Disk5

可以看到上面的几个赋值项,在规则文件里面就会去读这些值。做相应的判断,实现不同文件系统的区别对待挂载

如下为规则文件的一部分

KERNEL!="sd[a-z][0-9]", GOTO="media_by_label_auto_mount_end"

# Import FS infos

IMPORT{program}="/sbin/blkid -o udev -p %N"

ENV{ID_FS_LABEL}!="", ENV{dir_name}="%E{ID_FS_LABEL}"

ENV{ID_FS_LABEL}=="", ENV{dir_name}="usb-%k"

#vfat,fat

ACTION=="add",ENV{ID_FS_TYPE}=="vfat|fat",RUN+="/bin/mkdir -p /mnt/udisk/%E{dir_name}",ENV{mount_options}="$env{mount_options},gid=100,umask=000",RUN+="/bin/mount -o $env{mount_options},iocharset=utf8 /dev/%k /mnt/udisk/%E{dir_name}"

#ntfs

ACTION=="add",ENV{ID_FS_TYPE}=="ntfs",RUN+="/bin/mkdir -p /mnt/udisk/%E{dir_name}",ENV{mount_options}="$env{mount_options},gid=100,umask=000",RUN+="/bin/mount -t tntfs -o $env{mount_options},iostreaming /dev/%k /mnt/udisk/%E{dir_name}"

#exfat

ACTION=="add",ENV{ID_FS_TYPE}=="exfat",RUN+="/bin/mkdir -p /mnt/udisk/%E{dir_name}",RUN+="/bin/mount -t texfat -o rw /dev/%k /mnt/udisk/%E{dir_name}"

关于blkid,在我们目前的文件系统里面,blkid是不支持exfat格式的,通过命令查看磁盘的信息,根本找不到exfat格式的磁盘。所以之前在做自动挂载的时候没法实现挂exfat,后来在网上找了个util-linux-ng2.18源码包,里面包含了blkid的源码。修改编译,编译出一个新的blkid文件,使其可以在我们的系统上运行,能够识别出exfat文件。

posted @ 2016-11-03 16:18  lpfuture  阅读(15225)  评论(0编辑  收藏  举报