Chinaunix首页 | 论坛 | 博客
  • 博客访问: 723894
  • 博文数量: 104
  • 博客积分: 4320
  • 博客等级: 上校
  • 技术积分: 1948
  • 用 户 组: 普通用户
  • 注册时间: 2010-05-30 14:42
文章分类

全部博文(104)

文章存档

2012年(4)

2011年(65)

2010年(35)

分类: LINUX

2011-10-28 11:36:55

dev是什么?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的事件。

  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. }

编译:

  1. gcc -g hotplug.c -o hotplug_monitor

运行后插/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. change@/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
  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/sdb1
  14. add@/devices/virtual/bdi/8:16


  15. remove@/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
  16. remove@/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
  17. remove@/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
  18. remove@/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
  19. remove@/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/sdb1
  20. remove@/devices/virtual/bdi/8:16
  21. remove@/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
  22. remove@/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
  23. remove@/devices/pci0000:00/0000:00:1a.0/usb1/1-1/1-1.2/1-1.2:1.0/host9/target9:0:0
  24. remove@/devices/pci0000:00/0000:00:1a.0/usb1/1-1/1-1.2/1-1.2:1.0/host9/scsi_host/host9
  25. remove@/devices/pci0000:00/0000:00:1a.0/usb1/1-1/1-1.2/1-1.2:1.0/host9
  26. remove@/devices/pci0000:00/0000:00:1a.0/usb1/1-1/1-1.2/1-1.2:1.0
  27. remove@/devices/pci0000:00/0000:00:1a.0/usb1/1-1/1-1.2

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










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