白天和黑夜只交替没交换无法想像对方的世界
分类:
2011-07-19 23:57:49
原文地址: 作者:
关于这个规则,有一篇很经典的英文说明
udev是一种linux2.6内核采用的/dev 目录的管理系统,它通过从sysfs获得的信息,可以提供对特定设备的固定的设备名。
sysfs是 2.6内核的一种新型文件系统,它提供了当前设备的基本信息。
udev的一个重要目的就是提供固定的设备名,像我们刚才碰到的情况,如果ms插入系统,系统能使用固定的设备名
(例如/dev/ms) cf卡插入系统,使用/dev/cf,就可以很方便的解决我们需要到的困难。
/etc/udev/rules.d/ 下面的文件根据字母的顺序来解析,一般udev当找到满足它条件的说明项后就会终于解析过程,
因为为了使用我们的配置优先于系统的默认值,选择文件名时一定要注意,例如,我们选择 /etc/udev/rule.d/10-local.rules
bus="usb", sysfs{serial}="hxoll0012202323480", name="lp_epson", symlink="printers/epson_stylus"
上面是一个usb打印机的印子。当一台usb打印机序列号是hxoll0012202323480,就会创建一个device名 /dev/lpepson,
同时创建一个symbol link /dev/printers/epson_styles
注意:在任何规则修改后,为了让它生效:需要执行udevstart
规则书写格式
最方便的查找方法是 man udev 或在线文档
常用的有
* bus - 匹配总路类型,比如pci usb等 * kernel - 匹配kernel设备名,比如hda hdb. * driver - 匹配kernel的驱动程序名 * subsystem - 匹配子系统名。 * id - 匹配总路系统的id (e.g. pci bus id)。 * place - 匹配物理位置 (对usb很有用)。 * sysfs{filename} - 匹配从sysfs得到的信息,比如label, vendor, usb serial number, scsi uuid or file system label. * program - 调用外部程序。 * result - 匹配最后一次调用外部程序所得到的返回字符串 * name - 需要创建的设备或 * symlink -需要创建的符号链接名 * owner, group, mode 设置设备的所有者,组,及模式
匹配符号:
%n 内核设备号 例如 sda3 的3 %k 内核设备名 %m 设备的major号 %m 设备的minor号 %b bus id %c %s{filename} sysfs属性的内容 %% 引用%时使用 * 可以匹配任意个字符 ? 可以匹配一个字符 [ ] 从中选一个字符sample:
# if /sbin/scsi_id returns "oem 0815" device will be called disk1bus="scsi", program="/sbin/scsi_id", result="oem 0815", name="disk1"# usb printer to be called lp_colorbus="usb", sysfs{serial}="w09090207101241330", name="lp_color"# scsi disk with a specific vendor and model number will be called bootbus="scsi", sysfs{vendor}="ibm", sysfs{model}="st336", name="boot%n"# sound card with pci bus id 00:0b.0 to be called dspbus="pci", id="00:0b.0", name="dsp"# usb mouse at third port of the second hub to be called mouse1bus="usb", place="2.3", name="mouse1"# ttyusb1 should always be called pda with two additional symlinkskernel="ttyusb1", name="pda", symlink="palmtop handheld"# multiple usb webcams with symlinks to be called webcam0, webcam1, ……bus="usb", sysfs{model}="xv3", name="video%n", symlink="webcam%n"
查看sysfs的信息
这里使用udevinfo的指令, man udevinfo 在线文档 基本用法
-a sysfs{filename} attributes along the device chain. -p sysfs_path specify the sysfs path of the device to query. -q query_type query the database for specified value of a created device node or network interface. valid type: name, symlink, mode ,owner , group , path or all. -n name specify the name of the node, the symlink or the network interface for the device to queue sample:
udevinfo -a -p /sys/path/to/hardware/info udevinfo -a -p /sys/block/sda udevinfo -q path -n /dev/sda 联起来用: # udevinfo -a -p $(udevinfo -q path -n /dev/sda)
测试方法
# udevtest /sys/class/sound/dsp/version 056looking at '/class/sound/dsp/'opened class_dev->name='dsp'configured rule in '/etc/udev/rules.d/50-udev.rules[132]' applied, added symlink '%k'configured rule in '/etc/udev/rules.d/50-udev.rules[132]' applied, 'dsp' becomes 'sound/%k'creating device node '/dev/sound/dsp', major = '14', minor = '3', mode = '0660', uid = '0', gid = '18'
实际的例子
010_local.rules
#clie th55#syncbus="usb", sysfs{product}="palm handheld", kernel="ttyusb*", symlink="pilot%n"#exportbus="usb", sysfs{product}="sony peg mass storage", kernel="sd*",symlink="cliems"#usb hddbus="usb", sysfs{product}="usb to ide", kernel="sd*", symlink="usbhdd%n"#cf1 used for 5in1 card readerbus="ide", id="2.0", kernel="hd*", symlink="ms"]
相对于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 #include #include #include <errno.h> staticintinit_hotplug_sock(void) { struct sockaddr_nl snl; constintbuffersize = 16 * 1024 * 1024; intretval; memset(&snl, 0x00, sizeof(struct sockaddr_nl)); snl.nl_family = af_netlink; snl.nl_pid = getpid(); snl.nl_groups = 1; inthotplug_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, (structsockaddr *) &snl, sizeof(struct sockaddr_nl)); if (retval < 0) { printf("bind failed: %s", strerror(errno)); close(hotplug_sock); hotplug_sock = -1; return -1; } returnhotplug_sock; } #define uevent_buffer_size 2048 intmain(intargc, char* argv[]) { inthotplug_sock = init_hotplug_sock(); while(1) { charbuf[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的实现原理。