Linux 文件系统与设备文件系统 (一)—— udev 设备文件系统

一、什么是Linux设备文件系统

      首先我们不看定义,定义总是太抽象很难理解,我们先看现象。当我们往开发板上移植了一个新的文件系统之后(假如各种设备驱动也移植好了),启动开发板,我们用串口工具进入开发板,查看系统/dev目录,往往里面没有或者就只有null、console等几个系统必须的设备文件在这儿外,没有任何设备文件了。那我们移植好的各种设备驱动的设备文件怎么没有啊?如果要使用这些设备,那不是要一个一个的去手动的创建这些设备的设备文件节点,这给我们使用设备带来了极为的不便(在之前篇幅中讲的各种设备驱动的移植都是这样)。

      设备文件系统就是给我们解决这一问题的关键,他能够在系统设备初始化时动态的在/dev目录下创建好各种设备的设备文件节点(也就是说,系统启动后/dev目录下就有了各种设备的设备文件,直接就可使用了)。除此之外,他还可以在设备卸载后自动的删除/dev下对应的设备文件节点(这对于一些热插拔设备很有用,插上的时候自动创建,拔掉的时候又自动删除)。还有一个好处就是,在我们编写设备驱动的时候,不必再去为设备指定主设备号,在设备注册时用0来动态的获取可用的主设备号,然后在驱动中来实现创建和销毁设备文件(一般在驱动模块加载和卸载函数中来实现)。


二、关于udev

       2.4 内核使用devfs(设备文件系统)在设备初始化时创建设备文件,设备驱动程序可以指定设备号、所有者、用户空间等信息,devfs 运行在内核环境中,并有不少缺点:可能出现主/辅设备号不够,命名不灵活,不能指定设备名称等问题。

      而自2.6 内核开始,引入了sysfs 文件系统。sysfs 把连接在系统上的设备和总线组织成一个分级的文件,并提供给用户空间存取使用。udev 运行在用户模式,而非内核中udev 的初始化脚本在系统启动时创建设备节点,并且当插入新设备——加入驱动模块——在sysfs上注册新的数据后,udev会创新新的设备节点

     udev 是一个工作在用户空间的工具,它能根据系统中硬件设备的状态动态的更新设备文件,包括设备文件的创建,删除,权限等。这些文件通常都定义在/dev 目录下,但也可以在配置文件中指定。udev 必须内核中的sysfs和tmpfs支持,sysfs 为udev 提供设备入口和uevent 通道,tmpfs 为udev 设备文件提供存放空间

    注意,udev 是通过对内核产生的设备文件修改,或增加别名的方式来达到自定义设备文件的目的。但是,udev 是用户模式程序,其不会更改内核行为。也就是说,内核仍然会创建sda,sdb等设备文件,而udev可根据设备的唯一信息来区分不同的设备,并产生新的设备文件(或链接)。而在用户的应用中,只要使用新产生的设备文件即可。

    

三、udev和devfs设备文件的对比

       提到udev,不能不提的就是devfs,下面看一下udev 与 devfs 的区别:

1、udev能够实现所有devfs实现的功能。但udev运行在用户模式中,而devfs运行在内核中

2、当一个并不存在的 /dev 节点被打开的时候, devfs一样自动加载驱动程序而udev确不能。udev设计时,是在设备被发现的时候加载模块,而不是当它被访问的时候。 devfs这个功能对于一个配置正确的计算机是多余的。系统中所有的设备都应该产生hotplug 事件、加载恰当的驱动,而 udev 将会注意到这点并且为它创建对应的设备节点。如果你不想让所有的设备驱动停留在内存之中,应该使用其它东西来 管理你的模块 (如脚本, modules.conf, 等等) 。其中devfs 用的方法导致了大量无用的 modprobe 尝试,以此程序探测设备是否存在。每个试探性探测都新建一个运行 modprobe 的进程,而几乎所有这些都是无用的

3、udev是通过对内核产生的设备名增加别名的方式来达到上述目的的。前面说过,udev是用户模式程序,不会更改内核的行为

       因此,内核依然会我行我素地产生设备名如sda,sdb等。但是,udev可以根据设备的其他信息如总线(bus),生产商(vendor)等不同来区分不同的设备,并产生设备文件。udev只要为这个设备文件取一个固定的文件名就可以解决这个问题。在后续对设备的操作中,只要引用新的设备名就可以了。但为了保证最大限度的兼容,一般来说,
新设备名总是作为一个对内核自动产生的设备名的符号链接(link)来使用的。

     例如:内核产生了sda设备名,而根据信息,这个设备对应于是我的内置硬盘,那我就可以制定udev规则,让udev除了产生/dev/sda设备文件 外,另外创建一个符号链接叫/dev/internalHD。这样,我在fstab文件中,就可以用/dev/internalHD来代替原来的 /dev/sda了。下次,由于某些原因,这个硬盘在内核中变成了sdb设备名了,那也不用着急,udev还会自动产生/dev/internalHD这 个链接,并指向正确的/dev/sdb设备。所有其他的文件像fstab等都不用修改。

    而在在2.6内核以前一直使用的是 devfs,devfs挂载于/dev目录下,提供了一种类似于文件的方法来管理位于/dev目录下的所有设备,但是devfs文件系统有一些缺点,例 如:不确定的设备映射,有时一个设备映射的设备文件可能不同,例如我的U盘可能对应sda有可能对应sdb 。


四、udev 的工作流程图

       下面先看一张流程图:


        前面提到设备文件系统udev的工作过程依赖于sysfs文件系统udev文件系统在用户空间工作,它可以根据sysfs文件系统导出的信息(设备号(dev)等),动态建立和删除设备文件

        sysfs文件系统特点:sysfs把连接在系统上的设备和总线组织成为一个分级的目录及文件,它们可以由用户空间存取,向用户空间导出内核数据结构以及它们的属性,这其中就包括设备的主次设备号

       

      那么udev 是如何建立设备文件的呢?

a -- 对于已经编入内核的驱动程序

   当被内核检测到的时候,会直接在 sysfs 中注册其对象;对于编译成模块的驱动程序,当模块载入的时候才会这样做。一旦挂载了 sysfs 文件系统(挂载到 /sys),内建的驱动程序在 sysfs 注册的数据就可以被用户空间的进程使用,并提供给 udev 以创建设备节点

    udev 初始化脚本负责在 Linux 启动的时候创建设备节点,该脚本首先将 /sbin/udevsend 注册为热插拔事件处理程序。热插拔事件本不应该在这个阶段发生,注册 udev 只是为了以防万一。

   然后 udevstart 遍历 /sys 文件系统(其属性文件dev中记录这设备的主设备号,与次设备号),并在 /dev 目录下创建符合描述的设备文件。

  例如,/sys/class/tty/vcs/dev 里含有"7:0"字符串,udevstart 就根据这个字符串创建主设备号为 7 、次设备号为 0 的 /dev/vcs 设备。udevstart 创建的每个设备的名字和权限由 /etc/udev/rules.d/ 目录下的文件指定的规则来设置。如果 udev 找不到所创建设备的权限文件,就将其权限设置为缺省的 660 ,所有者为 root:root 。


b -- 编译成模块的驱动程序

      前面我们提到了"热插拔事件处理程序"的概念,当内核检测到一个新设备连接时,内核会产生一个热插拔事件,并在 /proc/sys/kernel/hotplug 文件里查找处理设备连接的用户空间程序。udev 初始化脚本将 udevsend 注册为该处理程序。当产生热插拔事件的时候,内核让 udev 在 /sys 文件系统里检测与新设备的有关信息,并为新设备在 /dev 里创建项目

     大多数 Linux 发行版通过 /etc/modules.conf 配置文件来处理模块加载,对某个设备节点的访问导致相应的内核模块被加载。对 udev 这个方法就行不通了,因为在模块加载前,设备节点根本不存在。为了解决这个问题,在 LFS-Bootscripts 软件包里加入了 modules 启动脚本,以及 /etc/sysconfig/modules 文件。通过在 modules 文件里添加模块名,就可以在系统启动的时候加载这些模块,这样 udev 就可以检测到设备,并创建相应的设备节点了。如果插入的设备有一个驱动程序模块但是尚未加载,Hotplug 软件包就有用了,它就会响应上述的内核总线驱动热插拔事件并加载相应的模块,为其创建设备节点,这样设备就可以使用了。



五、创建和配置mdev

         mdev是udev的简化版本,是busybox中所带的程序,最适合用在嵌入式系统,而udev一般都用在PC上的Linux中,相对mdev来说要复杂些;

1、我们应该明白,不管是udev还是mdev,他们就是一个应用程序,就跟其他应用程序一样(比如:Boa服务),配置了就可以使用了。为了方便起见,我们就使用busybox自带的一个mdev,这样在配置编译busybox时,只要将mdev的支持选项选上,编译后就包含了mdev设备文件系统的应用(当然你也可以不使用busybox自带的,去下载udev的源码进行编译移植。

#cd busybox-1.13.0/
#make menuconfig

Linux System Utilities --->
    [*] mdev 
    [*]   Support /etc/mdev.conf 
    [*]     Support subdirs/symlinks 
    [*]       Support regular expressions substitutions when renaming device 
    [*]     Support command execution at device addition/removal


2、udev或者mdev需要内核sysfs和tmpfs虚拟文件系统的支持,sysfs为udev提供设备入口和uevent通道,tmpfs为udev设备文件提供存放空间。所以在/etc/fstab配置文件中添加如下内容(红色部分):

# device  mount-point     type      options    dump    fsck order
#----------------------------------------------------------------
procfs    /proc           proc      defaults    0      0
sysfs     /sys            sysfs     defaults    0      0
tmpfs     /dev/shm        tmpfs     defaults    0      0

usbfs     /proc/bus/usb   usbfs     defaults    0      0
ramfs     /dev            ramfs     defaults    0      0
none      /dev/pts        devpts    mode=0622   0      0


3.、在系统初始化配置文件/etc/init.d/rcS中挂载mdev要用到的sysfs文件系统和tmpfs文件系统 ,这个在我们介绍根文件系统时已经看到:



     然后启动/sbin目录下的mdev应用对系统的设备进行搜索(红色部分)。

# Mount virtual filesystem
/bin/mount -t     proc     procfs    /proc
/bin/mount -n -t  sysfs    sysfs     /sys
/bin/mount -n -t  usbfs    usbfs     /proc/bus/usb
/bin/mount -t     ramfs    ramfs     /dev

# Make dir
/bin/mkdir -p /dev/pts
/bin/mkdir -p /dev/shm
/bin/mkdir -p /var/log
/bin/mount -n -t devpts none     /dev/pts -o mode=0622
/bin/mount -n -t tmpfs tmpfs     /dev/shm

# Make device node
echo /sbin/mdev > /proc/sys/kernel/hotplug
/sbin/mdev -s


a -- " mdev -s " 的含义是扫描 /sys 中所有的类设备目录,如果在目录中有含有名为“dev”的文件,且文件中包含的是设备号,则mdev就利用这些信息为该设备在/dev下创建设备节点文件;

b -- "echo /sbin/mdev > /proc/sys/kernel/hotplug" 的含义是当有热插拔事件产生时,内核就会调用位于 /sbin 目录的 mdev 。这时 mdev 通过环境变量中的 ACTION 和DEVPATH,来确定此次热插拔事件的动作以及影响了 /sys 中的那个目录。接着,会看这个目录中是否有“dev”的属性文件。如果有就利用这些信息为这个设备在 /dev 下创建设备节点文件。

4、在设备驱动程序中加上对类设备接口的支持,即在驱动程序加载和卸载函数中实现设备文件的创建与销毁,这个在我们前面Linux 字符设备驱动结构(二)—— 自动创建设备节点 可以看到实例,下面在介绍一个

     例如在之前篇幅的按键驱动中添加(红色部分):

[cpp]  view plain  copy
  1. #include <linux/device.h> //设备类用到的头文件  
  2. staticint device_major = DEVICE_MAJOR; //用于保存系统动态生成的主设备号  
  3. staticstruct class *button_class; //定义一个类  
  4.   
  5. static int __init button_init(void)  
  6. {  
  7.     //注册字符设备,这里定义DEVICE_MAJOR=0,让系统去分配,注册成功后将返回动态分配的主设备号  
  8.     device_major = register_chrdev(DEVICE_MAJOR, DEVICE_NAME,&buttons_fops);  
  9.   
  10.     if(device_major< 0)  
  11.     {  
  12.         printk(DEVICE_NAME " register faild!/n");  
  13.         return device_major;  
  14.     }  
  15.   
  16.     //注册一个设备类,使mdev可以在/dev/目录下建立设备节点  
  17.     button_class =class_create(THIS_MODULE, DEVICE_NAME);  
  18.   
  19.     if(IS_ERR(button_class))  
  20.     {  
  21.         printk(DEVICE_NAME" create class faild!/n");  
  22.         return-1;  
  23.     }  
  24.   
  25.     //创建一个设备节点,取名为DEVICE_NAME(即my2440_buttons)  
  26.     //注意2.6内核较早版本的函数名是class_device_create,现该为device_create  
  27.     device_create(button_class,NULL,MKDEV(device_major,0),NULL,DEVICE_NAME);  
  28.   
  29.     return 0;  
  30. }  
  31.   
  32. static void __exit button_exit(void)  
  33. {  
  34.     //注销字符设备  
  35.     unregister_chrdev(device_major, DEVICE_NAME);  
  36.   
  37.     //删除设备节点,注意2.6内核较早版本的函数名是class_device_destroy,现该为device_destroy  
  38.     device_destroy(button_class,MKDEV(device_major,0));  
  39.   
  40.     //注销类  
  41.     class_destroy(button_class);  
  42. }  


5、至于mdev的配置文件/etc/mdev.conf,这个可有可无,只是设定设备文件的一些规则。我这里就不管他了,让他为空好了。
 
6、完成以上步骤后,重新编译文件系统,下载到开发板上,启动开发板后进入开发板的/dev目录查看,就会有很多系统设备节点在这里产生了,我们就可以直接使用这些设备节点了。

猜你喜欢

转载自blog.csdn.net/zjy900507/article/details/79567858
今日推荐