免费注册 查看新帖 |

Chinaunix

  平台 论坛 博客 文库
最近访问板块 发新帖
查看: 654 | 回复: 0
打印 上一主题 下一主题

4、udev的实现原理 [复制链接]

论坛徽章:
0
跳转到指定楼层
1 [收藏(0)] [报告]
发表于 2008-08-02 11:09 |只看该作者 |倒序浏览

                                                转载时请注明出处和作者联系方式:
http://blog.csdn.net/absurd
作者联系方式:李先静
更新时间:2007-4-29

相对于linux来说,udev还是一个新事物。然而,尽管它03年才出现,尽管它很低调(J),但它无疑已经成为linux下不可或缺的组件了。udev是什么?它是如何实现的?最近研究Linux设备管理时,花了一些时间去研究udev的实现。

udev是什么?u 是指user space,dev是指device,udev是用户空间的设备驱动程序吗?最初我也这样认为,调试内核空间的程序要比调试用户空间的程序复杂得多,内核空间的程序的BUG所引起的后果也严重得多,device driver是内核空间中所占比较最大的代码,如果把这些device driver中硬件无关的代码,从内核空间移动到用户空间,自然是一个不错的想法。

但我的想法并不正确,udev的文档是这样说的,
1.         dynamic replacement for /dev。作为devfs的替代者,传统的devfs不能动态分配major和minor的值,而major和minor非常有限,很快就会用完了。udev能够像DHCP动态分配IP地址一样去动态分配major和minor。

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的事件。
   
        
            
            #include stdio.h>
            #include
            #include string.h>
            #include ctype.h>
            #include
            #include
            #include socket.h>
            #include
            #include
            #include errno.h>
            
            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;
                }
            
                /* set receive buffersize */
                setsockopt(hotplug_sock, SOL_SOCKET, SO_RCVBUFFORCE, &buffersize, sizeof(buffersize));
            
                retval = bind(hotplug_sock, (struct sockaddr *) &snl, sizeof(struct sockaddr_nl));
                if (retval
                    printf("bind failed: %s", strerror(errno));
                    close(hotplug_sock);
                    hotplug_sock = -1;
                    return -1;
                }
            
                return hotplug_sock;
            }
            
            #define UEVENT_BUFFER_SIZE      2048
            
            int main(int argc, char* argv[])
            {
                     int hotplug_sock       = init_hotplug_sock();
                     
                     while(1)
                     {
                               char buf[UEVENT_BUFFER_SIZE*2] = {0};
                               recv(hotplug_sock, &buf, sizeof(buf), 0);  
                               printf("%s\n", buf);
                     }
            
                     return 0;
            }
            
        
   

编译:
gcc -g hotplug.c -o hotplug_monitor

运行后插/拔U盘,可以看到:
   
        
            
            add@/devices/pci0000:00/0000:00:1d.1/usb2/2-1
            add@/devices/pci0000:00/0000:00:1d.1/usb2/2-1/usbdev2.2_ep00
            add@/devices/pci0000:00/0000:00:1d.1/usb2/2-1/2-1:1.0
            add@/class/scsi_host/host2
            add@/devices/pci0000:00/0000:00:1d.1/usb2/2-1/2-1:1.0/usbdev2.2_ep81
            add@/devices/pci0000:00/0000:00:1d.1/usb2/2-1/2-1:1.0/usbdev2.2_ep02
            add@/devices/pci0000:00/0000:00:1d.1/usb2/2-1/2-1:1.0/usbdev2.2_ep83
            add@/class/usb_device/usbdev2.2
            add@/devices/pci0000:00/0000:00:1d.1/usb2/2-1/2-1:1.0/host2/target2:0:0/2:0:0:0
            add@/class/scsi_disk/2:0:0:0
            add@/block/sda
            add@/block/sda/sda1
            add@/class/scsi_device/2:0:0:0
            add@/class/scsi_generic/sg0
            remove@/devices/pci0000:00/0000:00:1d.1/usb2/2-1/2-1:1.0/usbdev2.2_ep81
            remove@/devices/pci0000:00/0000:00:1d.1/usb2/2-1/2-1:1.0/usbdev2.2_ep02
            remove@/devices/pci0000:00/0000:00:1d.1/usb2/2-1/2-1:1.0/usbdev2.2_ep83
            remove@/class/scsi_generic/sg0
            remove@/class/scsi_device/2:0:0:0
            remove@/class/scsi_disk/2:0:0:0
            remove@/block/sda/sda1
            remove@/block/sda
            remove@/devices/pci0000:00/0000:00:1d.1/usb2/2-1/2-1:1.0/host2/target2:0:0/2:0:0:0
            remove@/class/scsi_host/host2
            remove@/devices/pci0000:00/0000:00:1d.1/usb2/2-1/2-1:1.0
            remove@/class/usb_device/usbdev2.2
            remove@/devices/pci0000:00/0000:00:1d.1/usb2/2-1/usbdev2.2_ep00
            remove@/devices/pci0000:00/0000:00:1d.1/usb2/2-1
            
        
   

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_list和exec_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的实现原理。
另外一篇见:http://hi.baidu.com/littertiger/blog/item/315d12dd8448fedc8c102918.html
kernel和udev间传递消息
kernel和udev间传递消息,比如add,remove等,是通过netlink进行。 netlink是个通用的机制,传递udev event只是其中一个应用。
由于uevent是广播的,所以写个小程序很容易不会这些事件。
写得比较匆忙,也很丑陋,呵呵。
#include
#include
#include
char buf[2048 + 512];
int main()
{
     int bufsize = 16 * 1024 * 1024;
     struct sockaddr_nl anl;
     int res, i, sk;
     anl.nl_family = AF_NETLINK;
     anl.nl_pid = getpid();
     anl.nl_groups = 1;
     sk = socket(PF_NETLINK, SOCK_DGRAM, NETLINK_KOBJECT_UEVENT);
     if (sk == -1){
          printf("socket create failed\n");
          return sk;
     }
     
     setsockopt(sk, SOL_SOCKET, SO_RCVBUFFORCE, &bufsize, sizeof(bufsize));
     res = bind(sk, &anl, sizeof(anl));
     if (res == -1){
          close(sk);
          printf("socket bind failed\n");
          return res;
     }
     while(1){
     res = recv(sk, buf, sizeof(buf), 0);
     printf("res:%d\n",res);
     res = res > sizeof(buf) ? sizeof(buf) : res;
     printf("begin\n");
     for(i=0; i<res; i++)
          putchar(buf);
     printf("end\n");
     }
     return 0;
}
               
               
               
               

本文来自ChinaUnix博客,如果查看原文请点:http://blog.chinaunix.net/u2/66024/showart_1101331.html
您需要登录后才可以回帖 登录 | 注册

本版积分规则 发表回复

  

北京盛拓优讯信息技术有限公司. 版权所有 京ICP备16024965号-6 北京市公安局海淀分局网监中心备案编号:11010802020122 niuxiaotong@pcpop.com 17352615567
未成年举报专区
中国互联网协会会员  联系我们:huangweiwei@itpub.net
感谢所有关心和支持过ChinaUnix的朋友们 转载本站内容请注明原作者名及出处

清除 Cookies - ChinaUnix - Archiver - WAP - TOP