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文件
posted @ 2017-06-04 15:18  D_R_Y  阅读(1424)  评论(0编辑  收藏  举报