udev 初探

7110阅读 1评论2013-12-18 Bean_lee
分类:LINUX

    最近需要支持U盘的热插拔,开始写的代码比较简单,就是第一次需要用到U盘的东西的时候,去遍历所有分区找到分辨出我的U盘,然后将它挂载。这种代码就不够cool了,我看着不顺眼,就琢磨开始学习udev的东西。
    我日常用Ubuntu系统,当U盘插入的时候,Ubuntu总是帮我讲我的U盘挂载到/media目录下:
  1. manu@manu-hacks:/media$ ll
  2. total 12
  3. drwxr-xr-x 3 root root 4096 Dec 17 23:11 ./
  4. drwxr-xr-x 23 root root 4096 Nov 29 22:40 ../
  5. drwx------ 19 manu manu 4096 Jan 1 1970 Ubuntu 12.0/
    Ubuntu是怎么做到的呢,如果你细心,你就会发现ps时,总有如下进程
  1. manu@manu-hacks:/media$ ps -ef|grep udevd
  2. root 378 1 0 19:31 ? 00:00:00 /sbin/udevd --daemon
  3. root 9310 378 0 23:11 ? 00:00:00 /sbin/udevd --daemon
  4. root 9311 378 0 23:11 ? 00:00:00 /sbin/udevd --daemon
  5. manu 9339 8894 0 23:13 pts/1 00:00:00 grep --color=auto udevd
    udev就是我们今天故事的主角。 udev是Linux2.6 管理设备用的。当Kernel检测到硬件插拔之类的事件,内核会通过netlink socket发出相应的事件uenvent。对于用户层,我们完全捕捉到kernel发出的事件,作相应的action。
    在2007年李先静前辈在给出了一个sample程序,因为目前我对netlink的机制理解不够深入,我讲前辈的代码copy于此,学习了一番。还是那句话,没有恶意抄袭前辈的意思,光荣属于前辈:
  1. #include <stdio.h>
  2. #include <stdlib.h>
  3. #include <string.h>
  4. #include <ctype.h>
  5. #include <sys/un.h>
  6. #include <sys/ioctl.h>
  7. #include <sys/socket.h>
  8. #include <linux/types.h>
  9. #include <linux/netlink.h>
  10. #include <errno.h>

  11. static int init_hotplug_sock(void)
  12. {
  13.     struct sockaddr_nl snl;
  14.     const int buffersize = 16 * 1024 * 1024;
  15.     int retval;

  16.     memset(&snl, 0x00, sizeof(struct sockaddr_nl));
  17.     snl.nl_family = AF_NETLINK;
  18.     snl.nl_pid = getpid();
  19.     snl.nl_groups = 1;

  20.     int hotplug_sock = socket(PF_NETLINK, SOCK_DGRAM, NETLINK_KOBJECT_UEVENT);
  21.     if (hotplug_sock == -1) {
  22.         printf("error getting socket: %s", strerror(errno));
  23.         return -1;
  24.     }

  25.     /* set receive buffersize */
  26.     setsockopt(hotplug_sock, SOL_SOCKET, SO_RCVBUFFORCE, &buffersize, sizeof(buffersize));

  27.     retval = bind(hotplug_sock, (struct sockaddr *) &snl, sizeof(struct sockaddr_nl));
  28.     if (retval < 0) {
  29.         printf("bind failed: %s", strerror(errno));
  30.         close(hotplug_sock);
  31.         hotplug_sock = -1;
  32.         return -1;
  33.     }

  34.     return hotplug_sock;
  35. }

  36. #define UEVENT_BUFFER_SIZE 2048

  37. int main(int argc, char* argv[])
  38. {
  39.     int hotplug_sock = init_hotplug_sock();

  40.     while(1)
  41.     {
  42.         char buf[UEVENT_BUFFER_SIZE*2] = {0};
  43.         recv(hotplug_sock, &buf, sizeof(buf), 0);
  44.         printf("%s\n", buf);
  45.     }

  46.     return 0;
  47. }
    我们可以看到尝试一下:
    当我拔出我的U盘的时候输出:
  1. remove@/devices/pci0000:00/0000:00:1a.0/usb1/1-1/1-1.2/1-1.2:1.0/host8/target8:0:0/8:0:0:0/bsg/8:0:0:0
  2. remove@/devices/pci0000:00/0000:00:1a.0/usb1/1-1/1-1.2/1-1.2:1.0/host8/target8:0:0/8:0:0:0/scsi_generic/sg2
  3. remove@/devices/pci0000:00/0000:00:1a.0/usb1/1-1/1-1.2/1-1.2:1.0/host8/target8:0:0/8:0:0:0/scsi_device/8:0:0:0
  4. remove@/devices/pci0000:00/0000:00:1a.0/usb1/1-1/1-1.2/1-1.2:1.0/host8/target8:0:0/8:0:0:0/scsi_disk/8:0:0:0
  5. remove@/devices/pci0000:00/0000:00:1a.0/usb1/1-1/1-1.2/1-1.2:1.0/host8/target8:0:0/8:0:0:0/block/sdb/sdb4
  6. remove@/devices/virtual/bdi/8:16
  7. remove@/devices/pci0000:00/0000:00:1a.0/usb1/1-1/1-1.2/1-1.2:1.0/host8/target8:0:0/8:0:0:0/block/sdb
  8. remove@/devices/pci0000:00/0000:00:1a.0/usb1/1-1/1-1.2/1-1.2:1.0/host8/target8:0:0/8:0:0:0
  9. remove@/devices/pci0000:00/0000:00:1a.0/usb1/1-1/1-1.2/1-1.2:1.0/host8/scsi_host/host8
  10. remove@/devices/pci0000:00/0000:00:1a.0/usb1/1-1/1-1.2/1-1.2:1.0/host8
  11. remove@/devices/pci0000:00/0000:00:1a.0/usb1/1-1/1-1.2/1-1.2:1.0
  12. remove@/devices/pci0000:00/0000:00:1a.0/usb1/1-1/1-1.2
  13. remove@/host8/target8:0:0
    紧接着我又插入了我的U盘:
  1. add@/devices/pci0000:00/0000:00:1a.0/usb1/1-1/1-1.2
  2. add@/devices/pci0000:00/0000:00:1a.0/usb1/1-1/1-1.2/1-1.2:1.0
  3. add@/devices/pci0000:00/0000:00:1a.0/usb1/1-1/1-1.2/1-1.2:1.0/host9
  4. add@/devices/pci0000:00/0000:00:1a.0/usb1/1-1/1-1.2/1-1.2:1.0/host9/scsi_host/host9
  5. add@/devices/pci0000:00/0000:00:1a.0/usb1/1-1/1-1.2/1-1.2:1.0/host9/target9:0:0
  6. add@/devices/pci0000:00/0000:00:1a.0/usb1/1-1/1-1.2/1-1.2:1.0/host9/target9:0:0/9:0:0:0
  7. add@/devices/pci0000:00/0000:00:1a.0/usb1/1-1/1-1.2/1-1.2:1.0/host9/target9:0:0/9:0:0:0/scsi_disk/9:0:0:0
  8. add@/devices/pci0000:00/0000:00:1a.0/usb1/1-1/1-1.2/1-1.2:1.0/host9/target9:0:0/9:0:0:0/scsi_device/9:0:0:0
  9. add@/devices/pci0000:00/0000:00:1a.0/usb1/1-1/1-1.2/1-1.2:1.0/host9/target9:0:0/9:0:0:0/scsi_generic/sg2
  10. add@/devices/pci0000:00/0000:00:1a.0/usb1/1-1/1-1.2/1-1.2:1.0/host9/target9:0:0/9:0:0:0/bsg/9:0:0:0
  11. add@/devices/virtual/bdi/8:16
  12. add@/devices/pci0000:00/0000:00:1a.0/usb1/1-1/1-1.2/1-1.2:1.0/host9/target9:0:0/9:0:0:0/block/sdb
  13. add@/devices/pci0000:00/0000:00:1a.0/usb1/1-1/1-1.2/1-1.2:1.0/host9/target9:0:0/9:0:0:0/block/sdb/sdb4
    拔掉电源线和插入电源线,检测的时间如下:
  1. change@/devices/LNXSYSTM:00/LNXSYBUS:00/ACPI0003:00/power_supply/ADP1
  2. change@/devices/LNXSYSTM:00/LNXSYBUS:00/ACPI0003:00/power_supply/ADP1
  3. change@/devices/LNXSYSTM:00/LNXSYBUS:00/PNP0A08:00/device:0e/PNP0C09:00/PNP0C0A:00/power_supply/BAT1
    总之了,利用netlink 我们可以侦测到一些硬件层面的变化。udev是一个开源的tool,他帮助我们利用kernel的发出的uevent 来管理我们的硬件。udev作为守护进程运行,他会监听当设备初始化或者设备从系统移除时 kernel发出的uevent,同时我们可以制定一些的rule,当事件匹配rule的filter条件,则执行相应的action。最简单的一个例子就是当检测到U盘插入的时候,挂载到制定位置。
    udev同时提供了udevadmin,这个工具也非常实用。我们可以用udevadm来侦测内核事件。 
  1. manu@manu-hacks:~/code/c/self/udev$ udevadm monitor
  2. custom logging function 0xb8670008 registered
  3. selinux=0
  4. runtime dir '/run/udev'
  5. calling: monitor
  6. monitor will print the received events for:
  7. UDEV - the event which udev sends out after rule processing
  8. KERNEL - the kernel uevent

  9. KERNEL[14475.004474] add /devices/pci0000:00/0000:00:1a.0/usb1/1-1/1-1.2 (usb)
  10. KERNEL[14475.007146] add /devices/pci0000:00/0000:00:1a.0/usb1/1-1/1-1.2/1-1.2:1.0 (usb)
  11. KERNEL[14475.007442] add /devices/pci0000:00/0000:00:1a.0/usb1/1-1/1-1.2/1-1.2:1.0/host11 (scsi)
  12. KERNEL[14475.007709] add /devices/pci0000:00/0000:00:1a.0/usb1/1-1/1-1.2/1-1.2:1.0/host11/scsi_host/host11 (scsi_host)
  13. UDEV  [14475.016589] add /devices/pci0000:00/0000:00:1a.0/usb1/1-1/1-1.2 (usb)
  14. UDEV  [14475.017925] add /devices/pci0000:00/0000:00:1a.0/usb1/1-1/1-1.2/1-1.2:1.0 (usb)
  15. UDEV  [14475.019205] add /devices/pci0000:00/0000:00:1a.0/usb1/1-1/1-1.2/1-1.2:1.0/host11 (scsi)
  16. 。。。。。。
    udevadm可以查看硬件的信息,这个对输出内容给了比较详细的解读:
Understand output of `udevadm info -a -n /dev/sdb`

  1. manu@manu-hacks:~/code/c/self/udev$ udevadm info -a -n /dev/sdb
  2. custom logging function 0xb8950008 registered
  3. selinux=0
  4. runtime dir '/run/udev'
  5. calling: info
  6. device 0xb8950318 has devpath '/devices/pci0000:00/0000:00:1a.0/usb1/1-1/1-1.2/1-1.2:1.0/host11/target11:0:0/11:0:0:0/block/sdb'

  7. Udevadm info starts with the device specified by the devpath and then
  8. walks up the chain of parent devices. It prints for every device
  9. found, all possible attributes in the udev rules key format.
  10. A rule to match, can be composed by the attributes of the device
  11. and the attributes from one single parent device.

  12.   looking at device '/devices/pci0000:00/0000:00:1a.0/usb1/1-1/1-1.2/1-1.2:1.0/host11/target11:0:0/11:0:0:0/block/sdb':
  13.     KERNEL=="sdb"
  14.     SUBSYSTEM=="block"
  15.     DRIVER==""
  16.     ATTR{ro}=="0"
  17. device 0xb89507f0 has devpath '/devices/virtual/bdi/8:16'
  18.     ATTR{size}=="15794176"
  19.     ATTR{stat}==" 176 580 1365 204 0 0 0 0 0 204 204"
  20.     ATTR{range}=="16"
  21.     ATTR{discard_alignment}=="0"
  22. device 0xb89507f0 has devpath '/devices/pci0000:00/0000:00:1a.0/usb1/1-1/1-1.2/1-1.2:1.0/host11/target11:0:0/11:0:0:0'
  23.     ATTR{events}=="media_change"
  24.     ATTR{ext_range}=="256"
  25.     ATTR{events_poll_msecs}=="2000"
  26.     ATTR{alignment_offset}=="0"
  27.     ATTR{inflight}==" 0 0"
  28.     ATTR{removable}=="1"
  29.     ATTR{capability}=="51"
  30.     ATTR{events_async}==""

  31. device 0xb8950b60 has devpath '/devices/pci0000:00/0000:00:1a.0/usb1/1-1/1-1.2/1-1.2:1.0/host11/target11:0:0/11:0:0:0'
  32.   looking at parent device '/devices/pci0000:00/0000:00:1a.0/usb1/1-1/1-1.2/1-1.2:1.0/host11/target11:0:0/11:0:0:0':
  33.     KERNELS=="11:0:0:0"
  34.     SUBSYSTEMS=="scsi"
  35.     DRIVERS=="sd"
  36.     ATTRS{rev}=="8.07"
  37.     ATTRS{type}=="0"
  38.     ATTRS{scsi_level}=="5"
  39.     ATTRS{model}=="Flash Disk "
  40.     ATTRS{state}=="running"
  41.     ATTRS{queue_type}=="none"
  42.     ATTRS{iodone_cnt}=="0x1a4"
  43.     ATTRS{iorequest_cnt}=="0x1a4"
  44.     ATTRS{timeout}=="30"
  45.     ATTRS{evt_media_change}=="0"
  46.     ATTRS{max_sectors}=="240"
  47.     ATTRS{ioerr_cnt}=="0x2"
  48. device 0xb8951868 has devpath '/devices/pci0000:00/0000:00:1a.0/usb1/1-1/1-1.2/1-1.2:1.0/host11/target11:0:0/11:0:0:0/scsi_generic/sg2'
  49.     ATTRS{queue_depth}=="1"
  50.     ATTRS{vendor}=="Generic "
  51.     ATTRS{device_blocked}=="0"
  52.     ATTRS{iocounterbits}=="32"

  53. device 0xb89519c8 has devpath '/devices/pci0000:00/0000:00:1a.0/usb1/1-1/1-1.2/1-1.2:1.0/host11/target11:0:0'
  54.   looking at parent device '/devices/pci0000:00/0000:00:1a.0/usb1/1-1/1-1.2/1-1.2:1.0/host11/target11:0:0':
  55.     KERNELS=="target11:0:0"
  56.     SUBSYSTEMS=="scsi"
  57.     DRIVERS==""

  58. device 0xb8951e78 has devpath '/devices/pci0000:00/0000:00:1a.0/usb1/1-1/1-1.2/1-1.2:1.0/host11'
  59.   looking at parent device '/devices/pci0000:00/0000:00:1a.0/usb1/1-1/1-1.2/1-1.2:1.0/host11':
  60.     KERNELS=="host11"
  61.     SUBSYSTEMS=="scsi"
  62.     DRIVERS==""

  63. device 0xb8952220 has devpath '/devices/pci0000:00/0000:00:1a.0/usb1/1-1/1-1.2/1-1.2:1.0'
  64.   looking at parent device '/devices/pci0000:00/0000:00:1a.0/usb1/1-1/1-1.2/1-1.2:1.0':
  65.     KERNELS=="1-1.2:1.0"
  66.     SUBSYSTEMS=="usb"
  67.     DRIVERS=="usb-storage"
  68.     ATTRS{bInterfaceClass}=="08"
  69.     ATTRS{bInterfaceSubClass}=="06"
  70.     ATTRS{bInterfaceProtocol}=="50"
  71.     ATTRS{bNumEndpoints}=="02"
  72.     ATTRS{supports_autosuspend}=="1"
  73.     ATTRS{bAlternateSetting}==" 0"
  74.     ATTRS{bInterfaceNumber}=="00"

  75. .......
    我们继续我们的udevd介绍,udevd会load一些rules,这些rules存储在/etc/udev/下:
  1. manu@manu-hacks:/etc/udev$ ll
  2. total 24
  3. drwxr-xr-x 3 root root 4096 Dec 15 23:56 ./
  4. drwxr-xr-x 147 root root 12288 Dec 17 23:32 ../
  5. drwxr-xr-x 2 root root 4096 Dec 17 23:11 rules.d/
  6. -rw-r--r-- 1 root root 219 Dec 15 23:56 udev.conf
    如果我们想写我们自己的rule,那么可以放到rules.d下,我去检测我的特殊的一块U盘,就定制了自己的rule,放到了/etc/udev/rules.d/下。向了解规则的话,可以去此处http://www.reactivated.net/writing_udev_rules.html,非常详尽。
   如果我检查到有USB插入,我会判断U盘是否是特殊的那块U盘,如果是,我需要将U盘挂载到指定的路径下如/mnt/manu_usb,当有U盘拔出的时候,需要判断是否特殊的U盘,是的话umount掉。

  1. root@manu-hacks:/etc/udev/rules.d# cat 11-add-usb.rules
  2. ACTION!="add",GOTO="farsight"
  3. KERNEL=="sd[a-z][0-9]",SUBSYSTEMS=="usb",RUN+="/home/manu/code/shell/mount-special-usb.sh %k"
  4. label="farsight"

  5. root@manu-hacks:/etc/udev/rules.d# cat 11-remove-usb.rules
  6. ACTION !="remove",GOTO="farsight"
  7. SUBSYSTEM!="block",GOTO="farsight"
  8. KERNEL=="sd[a-z][0-9]", RUN+="/home/manu/code/shell/umount-special-usb.sh %k"
  9. LABEL="farsight"
  10. root@manu-hacks:/etc/udev/rules.d#
    rule非常简单,检测到事件执行相应的shell脚本。写完自己的rule之后,执行
  1. udevadm control --reload-rules
    通知udevd这个daemon重新load一下rule。
    插入我的U盘后:
  1. root@manu-hacks:/etc/udev/rules.d# mount
  2. /dev/sda1 on / type ext4 (rw,errors=remount-ro)
  3. proc on /proc type proc (rw,noexec,nosuid,nodev)
  4. sysfs on /sys type sysfs (rw,noexec,nosuid,nodev)
  5. none on /sys/fs/fuse/connections type fusectl (rw)
  6. none on /sys/kernel/debug type debugfs (rw)
  7. none on /sys/kernel/security type securityfs (rw)
  8. udev on /dev type devtmpfs (rw,mode=0755)
  9. devpts on /dev/pts type devpts (rw,noexec,nosuid,gid=5,mode=0620)
  10. tmpfs on /run type tmpfs (rw,noexec,nosuid,size=10%,mode=0755)
  11. none on /run/lock type tmpfs (rw,noexec,nosuid,nodev,size=5242880)
  12. none on /run/shm type tmpfs (rw,nosuid,nodev)
  13. gvfs-fuse-daemon on /home/manu/.gvfs type fuse.gvfs-fuse-daemon (rw,nosuid,nodev,user=manu)
  14. /dev/sdb4 on /mnt/manu_usb type vfat (rw)
    拔出U盘后:  
  1. root@manu-hacks:/etc/udev/rules.d# mount
  2. /dev/sda1 on / type ext4 (rw,errors=remount-ro)
  3. proc on /proc type proc (rw,noexec,nosuid,nodev)
  4. sysfs on /sys type sysfs (rw,noexec,nosuid,nodev)
  5. none on /sys/fs/fuse/connections type fusectl (rw)
  6. none on /sys/kernel/debug type debugfs (rw)
  7. none on /sys/kernel/security type securityfs (rw)
  8. udev on /dev type devtmpfs (rw,mode=0755)
  9. devpts on /dev/pts type devpts (rw,noexec,nosuid,gid=5,mode=0620)
  10. tmpfs on /run type tmpfs (rw,noexec,nosuid,size=10%,mode=0755)
  11. none on /run/lock type tmpfs (rw,noexec,nosuid,nodev,size=5242880)
  12. none on /run/shm type tmpfs (rw,nosuid,nodev)
  13. gvfs-fuse-daemon on /home/manu/.gvfs type fuse.gvfs-fuse-daemon (rw,nosuid,nodev,user=manu)
    运行不错,我在我的CentOS-5.4下也运行通过。

参考文献:
Writing udev rules
2
 udev的实现原理
How to reload udev rules without reboot?
Understand output of `udevadm info -a -n /dev/sdb`

上一篇:golang编程之时间编程
下一篇:Linux signal那些事儿

文章评论