linux udev浅析

一.关于Udev

u即user space,dev是device,通过它的名字,我们就可以简单了解到,它是一个和用户态相关的驱动设备管理机制。udev是一个针对2.6内核的文件系统。提供一种基于用户空间的动态设备节点管理和命名的解决方案。用于取代落后的devfs

udev与硬件平台无关,属于用户空间的进程,是一个后台程序,它脱离驱动层的关联,而建立在操作系统之上,只要修改配置文件使之生效,无需重启操作系统,它需要sysfs的支持,当底层设备发生插拔的时候,底层驱动通过netlink发送事件(uevent)给udev后台程序,udev监听这些事件,并在上层做相应的设备节点的创建,命名,权限控制等。

它有以下优点:

1.动态管理:当设备添加/删除时,udev的守护进程侦听到来自内核的uevent,以此添加或者删除/dev下的设备文件,所以,udev只为已经连接的设备产生设备文件,而不会在/dev/下产生大量虚无的设备文件.在发生热插拔时,设备的变化的相关信息会输出到内核的/sys(sysfs文件系统),udev利用sysfs的信息来进行相应的设备节点的管理

2.自定义命名规则:通过规则文件,udev在/dev/下为所有的设备定义了内核设备名称,比如/dev/sda,/dev/hda,/dev/fd(这些都是驱动层定义的设备名)等等。由于udev是在用户空间运行,Linux用户可以自己定义规则文件,产生标识性强的设备文件,比如/dev/boot_disk,/dev/root_disk,/dev/color_printer等等

3.设定设备的权限和所有者/组。同样在规则文件中,可以自己定义设备相关的权限和所有者/组

工作流程:

二.uevent的交互

如之前提到过的,udev必须要有sysfs的支持,sysfs是一个建立在内存基础上的文件系统,它把连接在系统上的设备和总线组织成一个分级的文件,它们可以由用户空间获取,向用户空间导出内核数据结构以及它们的属性,它建立在内核对象kobject的基础上。

在内核空间,当系统启动加载驱动或设备发生热插拔的时候,驱动自身需要做相应的硬件探测方面的工作,探测到设备后,会去加载相应的设备驱动,在sysfs下创建添加内核对象,会调用到kobject_add()来完成该内核对象的添加注册,再调用kobject_uevent()来通知系统,该对象已经添加进来了。kobject_uevent()函数是uevent的关键函数,它将通过netlink socket把对象相应的信息,属性等发给上层用户空间。

int device_add(struct device *dev)

{

struct device *parent = NULL;

struct class_interface *class_intf;

int error = -EINVAL;

dev = get_device(dev);

if (!dev)

goto done;

   .......

/* first, register with generic layer. */

error = kobject_add(&dev->kobj, dev->kobj.parent, "%s", dev_name(dev));

if (error)

goto Error;

.......

kobject_uevent(&dev->kobj, KOBJ_ADD);

bus_attach_device(dev);

if (parent)

klist_add_tail(&dev->knode_parent, &parent->klist_children);

   .......

 attrError:

kobject_uevent(&dev->kobj, KOBJ_REMOVE);

kobject_del(&dev->kobj);

 Error:

cleanup_device_parent(dev);

if (parent)

put_device(parent);

goto done;

}

上面的代码段为我们常见的添加注册设备时的会调用到的接口,device_add()函数,删除了一些无关代码,可以看出,是先调用了kobject_add()创建添加该内核对象,然后调用kobject_uevent()来通知系统uevent的变化,这里的action是KOBJ_ADD,相对应还有

enum kobject_action {

KOBJ_ADD,

KOBJ_REMOVE,

KOBJ_CHANGE,

KOBJ_MOVE,

KOBJ_ONLINE,

KOBJ_OFFLINE,

KOBJ_MAX

};

在kobject_uevent()里面采用的就是Linux中比较经典的内核空间和用户空间的一种通信机制netlink socket,这个不是udev的重点,我也不做过多的解释,总之相信它能让内核空间和用户空间进行通信就行了。在udev也会有相应的socket来接受底层的消息。如下为参照udev源码写的一个简单的uevent消息侦听程序:

#define UEVENT_BUFFER_SIZE      2048

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;

}

int main(int argc, char* argv[])

{

         int hotplug_sock       = init_hotplug_sock();

          while(1)

          {

//printf("sunqidong debug\n");

                   char buf[UEVENT_BUFFER_SIZE*2] = {0};

                   recv(hotplug_sock, &buf, sizeof(buf), 0); 

                   printf("%s\n", buf);

          }

         return 0;

}

这也是一个后台服务程序,循环的执行接受底层的消息过来,当发生U盘的插拔时,会产生如下的log:

[root@localhost test]# ./hotplug

add@/devices/pci0000:00/0000:00:11.0/0000:02:03.0/usb1/1-1

add@/class/usb_endpoint/usbdev1.5_ep00

add@/devices/pci0000:00/0000:00:11.0/0000:02:03.0/usb1/1-1/1-1:1.0

add@/class/scsi_host/host6

add@/class/usb_endpoint/usbdev1.5_ep81

add@/class/usb_endpoint/usbdev1.5_ep02

add@/devices/pci0000:00/0000:00:11.0/0000:02:03.0/usb1/1-1/1-1:1.0/host6/target6:0:0/6:0:0:0

add@/class/scsi_disk/6:0:0:0

add@/block/sdb

add@/block/sdb/sdb1

add@/block/sdb/sdb2

add@/block/sdb/sdb5

add@/block/sdb/sdb6

add@/block/sdb/sdb7

add@/block/sdb/sdb8

add@/class/scsi_device/6:0:0:0

add@/class/scsi_generic/sg2

add@/class/bsg/6:0:0:0

remove@/class/usb_endpoint/usbdev1.5_ep81

remove@/class/usb_endpoint/usbdev1.5_ep02

remove@/class/bsg/6:0:0:0

remove@/class/scsi_generic/sg2

remove@/class/scsi_device/6:0:0:0

remove@/class/scsi_disk/6:0:0:0

remove@/block/sdb/sdb8

remove@/block/sdb/sdb7

remove@/block/sdb/sdb6

remove@/block/sdb/sdb5

remove@/block/sdb/sdb2

remove@/block/sdb/sdb1

remove@/block/sdb

remove@/devices/pci0000:00/0000:00:11.0/0000:02:03.0/usb1/1-1/1-1:1.0/host6/target6:0:0/6:0:0:0

remove@/class/scsi_host/host6

remove@/devices/pci0000:00/0000:00:11.0/0000:02:03.0/usb1/1-1/1-1:1.0

remove@/class/usb_endpoint/usbdev1.5_ep00

remove@/devices/pci0000:00/0000:00:11.0/0000:02:03.0/usb1/1-1

三.udev的规则文件     规则文件是udev中最重要的部分,默认是存放在/etc/udev/rules.d/下。所有的规则文件都必须以".rules"为后缀名。下面是一个简单的规则文件例子说明

      KERNEL=="sdb8" ,  NAME="mydisk",MODE="0660"

 KERNEL是匹配键,NAME和MODE是赋值键。"=="这是判断语句,"="是赋值语句,这条规则的意思是,如果有一个设备的内核设备名称为sdb8(我移动硬盘内的一个分区),则该条件生效,执行后面的赋值:在/dev/下产生名为mydisk的设备文件,并把该设备文件的权限设为0660.

     通过这条简单的规则,应该就可以对规则文件有了个基本的了解。每个规则文件被分成一个或多个匹配和赋值部分。匹配部分用匹配专用的关键字来表示,相应的赋值部分用赋值专用的字来表示。

     1.常见的匹配关键字: ACTION(用于匹配行为add/remove),KERNEL(内核中定义的设备名),BUS(用于匹配总线类型),SYSFS(用于匹配从sysfs得到的信息,比如lable,vendor,USB序列号等),SUBSYSTEM(匹配子系统名)等

     2.常见的赋值关键字:

NAME(创建的设备文件名),SYMLINK(符号创建链接名),OWNER(设置设备的所有者),GROUP(设置设备的组),IMPORT(调用外部程序),MODE(权限位)

 

四.xx项目上自动挂载usb存储设备的应用

xx项目上,我们需要自动挂载usb存储设备,并且要支持常见的几种文件系统,fat32,ntfs,exfat等,其中fat32fat系列,Linux下早就有支持,ntfsexfat目前的内核自身还没支持,我们有关于这两个文件系统的内核模块文件tntfs,kotexfat.ko,加载进去过后就能让我们的内核识别这两种文件系统,实现手动加载这两种格式的存储设备。但如果要支持自动加载还有问题,需要去修改相应的规则文件。

在加载的时候,不同的格式的文件系统,加载的参数是不一样的,如exfat

mount -t texfat /dev/sda /mnt/udisk

ntfs

mount -t tntfs /dev/sda /mnt/udisk

并且针对不同的格式,还有些其他挂载选项参数不一样,所以对不同的格式需要区别对待。

在规则文件里面是通过blkid -o udev命令来获取文件系统的信息的,判断出该盘是哪种格式,再去执行不同的挂载命令。

如下是blkid -o udev读出来的文件系统格式信息

ID_FS_UUID=2EE054B8E054884B

ID_FS_UUID_ENC=2EE054B8E054884B

ID_FS_LABEL=disk3

ID_FS_LABEL_ENC=disk3

ID_FS_TYPE=ntfs

ID_FS_LABEL=DISK4

ID_FS_LABEL_ENC=DISK4

ID_FS_UUID=B8CF-FF22

ID_FS_UUID_ENC=B8CF-FF22

ID_FS_TYPE=vfat

ID_FS_UUID=3606-1B2C

ID_FS_UUID_ENC=3606-1B2C

ID_FS_TYPE=exfat

ID_FS_LABEL=Disk5

ID_FS_LABEL_ENC=Disk5

可以看到上面的几个赋值项,在规则文件里面就会去读这些值。做相应的判断,实现不同文件系统的区别对待挂载

如下为规则文件的一部分

KERNEL!="sd[a-z][0-9]", GOTO="media_by_label_auto_mount_end"

# Import FS infos

IMPORT{program}="/sbin/blkid -o udev -p %N"

ENV{ID_FS_LABEL}!="", ENV{dir_name}="%E{ID_FS_LABEL}"

ENV{ID_FS_LABEL}=="", ENV{dir_name}="usb-%k"

#vfat,fat

ACTION=="add",ENV{ID_FS_TYPE}=="vfat|fat",RUN+="/bin/mkdir -p /mnt/udisk/%E{dir_name}",ENV{mount_options}="$env{mount_options},gid=100,umask=000",RUN+="/bin/mount -o $env{mount_options},iocharset=utf8 /dev/%k /mnt/udisk/%E{dir_name}"

#ntfs

ACTION=="add",ENV{ID_FS_TYPE}=="ntfs",RUN+="/bin/mkdir -p /mnt/udisk/%E{dir_name}",ENV{mount_options}="$env{mount_options},gid=100,umask=000",RUN+="/bin/mount -t tntfs -o $env{mount_options},iostreaming /dev/%k /mnt/udisk/%E{dir_name}"

#exfat

ACTION=="add",ENV{ID_FS_TYPE}=="exfat",RUN+="/bin/mkdir -p /mnt/udisk/%E{dir_name}",RUN+="/bin/mount -t texfat -o rw /dev/%k /mnt/udisk/%E{dir_name}"

关于blkid,在我们目前的文件系统里面,blkid是不支持exfat格式的,通过命令查看磁盘的信息,根本找不到exfat格式的磁盘。所以之前在做自动挂载的时候没法实现挂exfat,后来在网上找了个util-linux-ng2.18源码包,里面包含了blkid的源码。修改编译,编译出一个新的blkid文件,使其可以在我们的系统上运行,能够识别出exfat文件。




简介

  Linux 传统上使用静态设备创建方法,因此大量设备节点在 /dev 下创建(有时上千个),而不管相应的硬件设备是否真正存在。通常这由一个MAKEDEV脚本实现,这个脚本包含了许多通过世界上(有幽默意味,注)每一个可能存在的设备相关的主设备号和次设备号对mknod程序的调用。采用udev的方法,只有被内核检测到的设备才会获取为它们创建的设备节点。因为这些设备节点在每次系统启动时被创建,他们会被贮存在ramfs(一个内存中的文件系统,不占用任何磁盘空间).设备节点不需要大量磁盘空间,因此它使用的内存可以忽略。

1:history

  在2.4版本的kernel中,一种新文件系统称作devfs被添加了进去。尽管它在kernel源码中出现,然而这种动态创建设备的方法从未收到压倒性的支持from core kernel开发者;
  devfs 的方法主要问题是 the way:设备检测,创建,命名。设备节点命名,可能是最critical的.It is generally accepted that 设备名允许是可配置的,then设备命名策略应该up to一个系统管理员,not 被欺骗by 特殊的开发者。devfs文件系统同时还忍受着一个紊乱情况,为它的design所固有,且不能被fix,若无实质的修改to kernel.现由于缺乏维护已被丢弃.
  在2.6版本的kernel中,出现了一种叫sysfs的新虚拟文件系统。sysfs的 任务是export系统架构 to 用户空间进程。有了这种用户空间visible表示法,the possiblity of seeing a userspace replacement for devfs 变得更加现实。

2:udev 执行

  sysfs怎样知道设备出现 在系统?应该使用什么设备号?对于被编进kernel的driver,当被kernel监测到时,直接注册目标with sysfs。使用模块方式编译的,当模块被load时,如前。once sysfs文件系统被mounted (on /sys),the data which the built-in drivers registered with sysfs are available to userspace process and to udev for device node creation.
  udev初始化脚本创建这些 设备节点当linux boot时;这个脚本starts with 注册/sbin/udev/ 作为一个 hotplug事件管理者。热插拔事件不应该发生在这个过程中,然而udev is registered just in case they do occur.然后udevstart program walk through the /sys filesystem and 创建符合描述的设备在/dev。例如:/sys/class/tty/vcs/dev/包括string "7:0".这个字符串被udevstart使用来创建/dev/vcs,主设备号7and此设备号0。每一个udevstart创建的设备的权限设置来 自/etc/udev.d/permission.d/目录。这些numbered(有限的) 基本相似LFS bootscripts.如果找不到创建的设备权限文件,默认perissions to 600 and ownership to root:root./dev目录下创建的节点根据 /etc/udev/rules.d/目录下的文件来configured.

编辑本段设备节点

  当一个新设备连 接被kernel监测到,kernel会产生一个hotplug event 并查找/proc/sys/kernel/hotplug去找出管理设备连接的用户空间程序。udev初始化脚本注册udev as this hander.当hotplug events发生时,kernel通知udev 去检测/sys 文件系统附属于这个新设备的信息并create 它的/dev/入口。
  这带给我们一个问题:exists with udev,and likewise with devfs before it.?就像先有鸡还是先有蛋。大部分linux distrubtions
  管 理加载模块通过/etc/modules.conf.access to 设备节点引起相应的kernel模块来加载。然而对于udev,这种方法不能正常工作,因为模块没有加载时,设备节点不存在。为了解决这个问题,模块脚本 加到了lfs-bootscripts包中,和/etc/sysconfig/modules在一起。通过添加module names到module file中,这些模块在计算机启动时被加载。这样,udev就可以去检测设备并创建相应的设备节点。

3:处理可热插拔/动态设备

  当 你插入一个设备,比如usb mp3 player,内核辨认出设备连接同时产生一个热插拔事件。如果驱动已经loaded(不管是编进kernel还是通过s05modules bootscript加载),udev将被调用来创建相关的设备节点,根据sysfs data in /sys.如果刚插入的设备驱动以模块形式然而并未加载,那么刚attach to system 的设备只会引起kernel总线驱动产生一个热插拔事件通知用户空间一个新设备的连接and它不attached to a driver.结果,什么都没有发生,device依然不能使用。
  如果建立一个system,that具有大量的以模块形式编译的驱动,使用s05modules并不实际。the hotplug package会显得非常有价值。当此包安装后,它会响应前述的kernel总线驱动hotplug事件。此包将加载相应的模块并为设备创建节点。

4:创建设备的问题

  自动创建设备节点时常遇到的一些问题
  1)A kernel driver may not exports its data to sysfs
  当 使用第三方驱动(在kernel source tree 之外)时常遇到这种问题。
  这些驱动end up时没有创建设备节点。使用/etc/sysconfig/creatfiles 配置文件to 人工创建设备。参考devices.txt文件(在kernel文档中)或者驱动文档来找出正确的major/minor设备号。
  2)无硬件设备 is required.这种很常见with the advanced linux sound architecture(ALSA) project's open sound system(oss) compatibility 模块.这种形式的驱动可以使用以下下面两种方法来管理:
  *将module names 加到 /etc/sysconfig/modules;
  * 使用"install"line 在/etc/modprobe.conf中。This tells the modprobe command "when loading this module, also load this other module,at the same time."例如
  install snd-pcm modprobe -i snd-pcm;modprobe snd-pcm-oss;true
  当系统中有加载snd-pcm驱动的请求时,这会使系统加载both snd-pcm and snd-pcm-oss modules.

编辑本段相关问答

  问: udev是什么? 它的目的何在?
  答: 看看那篇 OLS 2003 上的有关 udev 的文章吧,可以在 docs 目录里找到,也能在这
  里找到:OLS 2003 上还有一个关于 udev 的幻灯片。
  问: udev 和 devfs 是什么关系
  答: udev 完全在用户态 (userspace) 工作,利用设备加入或移除时内核所发送的
  hotplug 事件 (event) 来工作。关于设备的详细信息是由内核输出 (export) 到位
  于 /sys 的 sysfs 文件系统的。所有的设备命名策略、权限控制和事件处理都是在
  用户态下完成的。与此相反,devfs 是作为内核的一部分工作的。
  问: 如果 udev 不能完成所有 devfs 的工作的话,为什么把 devfs 标记为
  OBSOLETE/removed?
  答: 引用 Al Viro (Linux VFS 内核维护者):
  - devfs 所做的工作被确信可以在用户态来完成。
  - devfs 被加入内核之时,大家寄望它的质量可以迎头赶上。
  - devfs 被发现了一些可修复和无法修复的 bug。
  - 对于可修复的 bug,几个月前就已经被修复了,其维护者认为一切良好。
  - 对于后者,同样是相当常一段时间以来没有改观了。
  - devfs 的维护者和作者对它感到失望并且已经停止了对代码的维护工作。
  问: 但是当一个并不存在的 /dev 节点被打开的时候,udev 并不能如 devfs 一样自动加
  载驱动程序。
  答: 的确如此,但 Linux 的设计是在设备被发现的时候加载模块,而不是当它被访问的时
  候。
  问: 不过等等,我确实希望 udev 可以在不存在的节点被打开的时候自动加载驱动。这是
  我使用 devfs 的唯一原因了。给 udev 增加这个功能吧。
  答: 不,udev 是用来管理 /dev 的,不是用来加载内核驱动的。
  问: 嗨,求你们了。这不难做到的。
  答: 这么个功能对于一个配置正确的计算机是多余的。系统中所有的设备都应该产生
  hotplug 事件、加载恰当的驱动,而 udev 将会注意到这点并且为它创建对应的
  设备节点。如果你不想让所有的设备驱动停留在内存之中,应该使用其它东西来
  管理你的模块 (如脚本, modules.conf, 等等) 这不是 udev 的工作。
  问: 但是我真的喜欢那个功能,还是加上吧
  答: devfs 用的方法导致了大量无用的 modprobe 尝试,以此程序探测设备是否存在。
  每个试探性探测都新建一个运行 modprobe 的进程,而几乎所有这些都是无用的。
  问: 我喜欢 devfs 的设备文件命名方式,udev 可以这样命名么?
  答: 可以,udev 可以使用 /dev 的命名策略来创建节点。通过一个配置文件,可以把内
  核缺省的名字映射到 devfs 的名字。可以看看 udev 中带的 udev.rules.devfs 文
  件。
  注意: devfs 的命名方式是不被建议并且不被官方支持的,因为它所用的简单枚举设
  备的方式在设备可能被随时加入或删除的情况下确实是一个比较笨的方法。这些编号
  代给你的将只有麻烦,而并不能用来确定设备。看看那个永久性磁盘 (persistent
  disk) 的规则就知道如何在用户态下正确的做这件事,而不是傻傻地列出设备。
  问: udev 可以为哪些设备创建节点?
  答: 所有在 sysfs 中显示的设备都可以由 udev 来创建节点。如果内核中增加了其它设
  备的支持,udev 也就自动地可以为它们工作了。现在所有的块设备都在被支持之列,
  大部分的主字符设备也是被支持的。内核开发者们正致力于让所有的字符设备都被支
  持。可以到 linux-kernel 邮件列表上寻找补丁或是查看补丁的状态。
  问: udev 是否会去掉匿名设备数量的限制?
  答: udev 完全工作于用户态。如果内核支持了更多的匿名设备,udev 就会支持。
  问: udev 是否会支持符号链接?
  答: udev 现在就支持符号链接,每个设备节点拥有多个符号链接也是被支持的。
  问: udev 如何处理 /dev 文件系统?
  答: 建议使用一个每次启动系统的时候重新创建的 tmpfs 作为 /dev 的文件系统。不过
  实际上 udev 并不关心那种文件系统在被使用。
  问: 在 init 运行之前,udev 如何处理设备?
  答: udev 可以被放入 initramfs 之中,并在每个设备被发现的时候运行。也可以让
  udev 工作在一个真的根分区被加载之后根据 /sys 的内容创建的初始 /dev 目录
  之中。
  问: 我是否可以利用 udev 在一个 USB 设备被加载的时候自动加载上这个设备?
  答: 技术上讲是可以的,但是 udev 不是用于这个工作的。所有的主流发布版 (distro)
  都包含了 HAL 用于这个工作,它
  也是专门用于监视设备变更的,并且集成进入了桌面软件。
  换个角度说,这可以简单的通过 fstab 来实现:
  /dev/disk/by-label/PENDRIVE /media/PENDRIVE vfat user,noauto 0 0
  这样,用户可以用如下命令来访问设备:
  $mount /media/PENDRIVE
  同样不需要管理员权限,但却拥有了设备的全部访问权限。使用永久性磁盘链接
  (label, uuid) 将可以指定同一设备,无论其实际上的内核名字是什么。
  问: 有什么我需要注意的安全问题么?
  答: 当使用动态设备编号的时候,一个给定的主/从设备号可能在不同时间对应不同的设
  备,如果一个用户拥有对这个节点的访问权限,并且可以创建一个到这个节点的硬链
  接,他就可以如此得到一个这个设备节点的拷贝。当设备被移除之后,udev 删除了
  设备节点,但硬链接依然存在。如果这个设备节点之后被重新使用不同的访问权限被
  创建的时候,其硬链接仍然可以使用先前的访问权限来访问。
  (同样的问题也存在在使用 PAM 改变访问权限的 login 上。)
  简单的解决方案就是通过把 /dev 放在 tmpfs 这样的单独的文件系统之上来防止建
  立硬链接。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值