Chinaunix首页 | 论坛 | 博客
  • 博客访问: 3006356
  • 博文数量: 523
  • 博客积分: 11908
  • 博客等级: 上将
  • 技术积分: 5475
  • 用 户 组: 普通用户
  • 注册时间: 2009-04-03 15:50
文章分类

全部博文(523)

文章存档

2019年(3)

2013年(4)

2012年(71)

2011年(78)

2010年(57)

2009年(310)

分类: LINUX

2009-06-30 21:46:56

 

Udev相关的文章很多,本文的主要目的不是提供一个完整的教学文档,对其使用,只是给出网上现有的主要资源。着重分析其基本工作原理连同在使用中碰到的一些README文档没有明确说明的问题。
1
基本概念
udev
文档系统是针对2.6内核,提供一个基于用户空间的动态设备节点管理和命名的解决方案,网上关于为什么要使用udev文档系统,udev文档系统和devfs文档系统的比较,等等的文章已很多了,假如您想了解这方面的内容,请直接搜索相关的关键字。
udev
的官方网址:http://www.kernel.org/pub/linux/utils/kernel/hotplug/udev.html
src code
的下载地址:
此外,关于udevrules规则的撰写,网上也有很多文章,假如要获得最准确的版本,能够在src code的代码树里找到writing_udev_rules的帮助文档,这个文档其实没有逐条介绍rules的任何关键字,能够结合man udev udev自带的一些rules文档来理解如何撰写您所需要的规则文档。
2
安装和启动
2.1
安装
Udev
的代码树里的版本很多,我下载的最新的版本是udev-117,配合2.6.21版本的内核能够正常使用。网上很多文章介绍的可能都是稍微早期一些的版本,有些步骤包括udevREADME文档似乎描述的不是很准确。 . 
基本上这个版本的udev需要注意的是,安装时只需要udevdudevadm两个文档,其他必需的包括udevtrigger等只是udevadm的一个符号链接。udevstart不是必需的。当然Udev.conf等配置文档还是相同。
2.2
启动
您能够在启动脚本中用udevd d 参数启动udev文档系统的守护进程,然后使用udevtriggerbuildin的设备驱动的节点创建出来,以后模块插入移除时节点的管理会自动处理。
能够正常加载udev的前提,基本包括如下操作:
Ø
配置路径变量
Ø
加载sysfs文档系统
Ø
加载一个基于ram的可写的/dev目录(其实,只要提供一个可写的目录即可,目录路径本身也是能够配置的)
Ø /dev
目录下需要有已创建好的 console节点和null节点
脚本类似:
# Set the path
PATH=/bin:/sbin:/usr/bin:/usr/sbin
export PATH
# mount proc and devpts filesystem
/bin/mount -a
mknod /dev/console c 5 1
mknod /dev/null c 1 3
/sbin/udevd -d
/sbin/udevtrigger
Mount
使用的fstab文档类似:
none /tmp ramfs defaults 0 0
特别注意:本站所有转载文章言论不代表本站观点,本站所提供的摄影照片,插画,设计作品,如需使用,请与原作者联系.
udev /dev ramfs defaults 0 0
none /proc proc defaults 0 0
sysfs /sys sysfs defaults 0 0
当然,您的系统上可能还会需要预先创建一些其他的设备节点,比如串口的ttySx 才能正常启动shell,完成以上脚本的执行,那就要看具体情况了。
3
使用中的一些问题的思考
3.1
关于规则的多次匹配
帮助文档中说一个设备能够被多条规则多次匹配,但是,需要明确的一点是:
多次匹配只能添加多个Symlink,不能创建多个Name
例如:
KERNEL=="mtdblock4", NAME ="mtdbb4"
KERNEL=="mtdblock4", NAME ="%k"
就只会创建 /dev/mtdbb4 而不会创建/dev/mtdblock4
而类似:
KERNEL=="mtdblock4", NAME ="mtdbb4"
KERNEL=="mtdblock4", SYMLINK ="mtdbb4link"
是能够正常工作的。
3.2
关于udev.conf的语法
可能大家会发现,似乎没有什么周详文档描述udev.conf的写法,实际上从udevd的代码里能够看出: 版权申明:本站文章均来自网络.
udev.conf
文档里面只会解析这三个参数:
udev_root
定义udev的目录路径
udev_rules
定义udev的规则文档的目录路径
udev_log
定义log的级别
也许以后会添加一些别的配置参数?
4
基本工作原理方面的问题
这部分主要是分析了一下udevsource code,对一些自己关心的问题的理解
4.1 Udevd
如何获取内核的这些模块动态变化的信息
设备节点的创建,是通过sysfs接口分析dev文档取得设备节点号,这个很显而易见。那么udevd是通过什么机制来得知内核里模块的变化情况,如何得知设备的插入移除情况呢?当然是通过hotplug机制了,那hotplug又是怎么实现的?或说内核是如何通知用户空间一个事件的发生的呢?
答案是通过netlink socket通讯,在内核和用户空间之间传递信息。
内核调用kobject_uevent函数发送netlink message给用户空间,这部分工作通常无需驱动去自己处理,在统一设备模型里面,在子系统这一层面,已将这部分代码处理好了,包括在设备对应的特定的Kobject创建和移除的时候都会发送相应addremove消息,当然前提是您在内核中配置了hotplug的支持。
Netlink socket
作为一种内核和用户空间的通信方式,不但仅用在hotplug机制中,同样还应用在其他很多真正和网络相关的内核子系统中。 ...
Udevd
通过标准的socket机制,创建socket连接来获取内核广播的uevent事件 并解析这些uevent事件。
4.2 Udevd
如何监控规则文档的变更
假如内核版本足够新的话,在规则文档发生变化的时候,udev也能够自动的重新应用这些规则,这得益于内核的inotify机制, inotify是一种文档系统的变化通知机制,如文档增加、删除等事件能够立即让用户态得知。
udevd中,对inotifyudevnetlink socket文档描述符都进行了select的等待操作。有事件发生以后再进一步处理。
4.3 Udevtrigger
的工作机制?
运行udevd以后,使用udevtrigger的时候,会把内核中已存在的设备的节点创建出来,那么他是怎么做到这一点的? 分析udevtrigger的代码能够看出:
udevtrigger
通过向/sysfs 文档系统下现有设备的uevent节点写"add"字符串,从而触发uevent事件,使得udevd能够接收到这些事件,并创建buildin的设备驱动的设备节点连同任何已insmod的模块的设备节点。

 

 

转载时请注明出处和作者联系方式:http://blog.csdn.net/absurd

作者联系方式:李先静

 

 

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

 

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

 

但我的想法并不正确,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的事件。

#include

#include

#include

#include

#include

#include

#include

#include

#include

#include

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 < 0) {

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

 

~~end~~

 

本文来自CSDN博客,转载请标明出处:http://blog.csdn.net/absurd/archive/2007/04/27/1587938.aspx

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