udev——设备文件管理的用户空间实现


Tags: Linux, 驱动程序

Abstract

自2.5版内核起,用户空间的进程可以通过sysfs文件系统访问按层次组织的所有系统的外部设备(包括物理设备和虚拟设备)的元信息。另外,“/sbin/hotplug” 会在系统设备热插拔时向用户空间发出提示。具备了这两项功能后,在用户空间内动态管理设备文件(/dev目录)成为现实,一直需求的更灵活的设备名分配策略成为现实。

本报告文分析udev,一支代替devfs——只是实现动态管理/dev目录条目的方案——的用户空间程序,解决由devfs不能单独解决的一些问题:

  • 第一,无论设备何时何处插入都使用同一个设备名;
  • 第二,发现并用户空间提示系统设备发生变动;
  • 第三,灵活的设备命名方案;
  • 第四,允许内核使用动态的主设备号和次设备号;
  • 第五,将命名策略移出内核空间

本文将解释为什么在用户空间实现的udev会优于在内核实现的devfs,并且详述在实现udev上的一些设计考虑。另外,这里也讲解udve的工作原理和怎样给udev制作插件(如命名方案naming scheme),还有一些udev使用上需要考虑的事情。

1.Introduction

/dev目录挂接了所有系统设备的设备文件,用户程序通过这些文件调用设备的驱动程序,从而使用设备。例如,/dev/hda 是系统第一个IDE磁盘的设备文件,/dev/hda 被绑定了一主设备号和次设备号,内核根据主设备号找到IDE磁盘的驱动程序。目前,/dev下的很多文件名(和相应的设备号)已经被分配,分配任务由 The Linux Assigned Names And Numbers Authority (LANANA their web site at http://www.lanana.org/docs/device-list/devices.txt)负责。

当Linux支持一种新设备类型时,它必须被分配一个主设备号和数个次设备号。在2.4版及以前,可用的主设备号和次设备号都只是8位(1-255)。由于设备号有限,在2.3版的开发期设备号已经告急。2.6版起,主设备号提升为12位,次设备号20位。

2.前sysfs的问题

2.1 设备与设备文件的映射不固定

当内核发现一支已知类型的新硬件时,它一般会按顺序为这新硬件分配硬件所属类型的主设备号和次设备号。例如,在devices.txt分配中,USB设备号是180,USB打印机的设备号是0-15;当系统启动时,第一支被发现的USB打印机会分配(180,0),设备名是/dev/usb/lp0;第二支被发现的USB打印机会分配(180,1),设备名是/dev/usb/lp1。当用户改动USB的拓扑结构,例如添加一个USB hub,那么上述的USB打印机的检测次序有可能改变,也就是说次设备号有可能交换。在sysfs出现以前,这种变动,用户程序很难发现。

同样的情况出现在具有热插拔功能的总线设备上——PCI、IEEE1394、 USB和CardBus。

有了sysfs文件系统,用户程序想知道物理设备被分配了哪个次设备号变得容易。像上面的系统有两支不同的USB打印机的情况,/sys/class/usb的目录树结构像下面的:

在/sys下总是有某一设备的物理信息,例如生产厂商和序列号,而这些信息可以通过一个具有丰富语义的层次目录内找到。例如次设备号为0 的USB打印机在/sys/class/usb/lp0/device内。由上图可知设备文件/dev/usb/lp0所关联的设备是序列号为HXOLL0012202323480的USB打印机,而设备文件/dev/usb/lp1所关联的设备是序列号尾号330的USB打印机。

sysfs现在可以让用户查对哪个设备关联了哪个设备文件了。如果设备关联变动,那么设备重配置可以在用户空间进行,不必麻烦内核[注]。

注:虽然如此,普通用户一般并不关心/dev/usb/lp0 和 /dev/usb/lp1是否变动过,他们只想原封不动使用原有功能,不想做任何的配置工作,哪怕在用户空间。

2.2 没有足够的设备号

前sysfs的设备号分配是静态的。设备名和设备号的静态分配管理方案中,设备号由16位提升到32位暂缓了设备号短缺的问题,但是也保不齐有一天也不够用;而且静态管理方案还需要一个额外的名字授权管理。如果将管理方法改用动态方式,那么设备名与设备号只有单机冲突问题,没有社区冲突问题,这样名字授权管理可以省去,设备号也不可能用完。但是,要实现动态分配的最大问题是,用户空间无法知道设备的设备号(包括主设备和次设备号)。

其实,早在2.2版本内核里,动态分配次设备号已经在USB设备驱动子系统和串口设备驱动子系统实现,并取得成功。但是还是那个问题,用户还是没法知道哪个设备分配了哪个设备号,只能通过查对系统日志来确定设备号。有了sysfs,这个问题被解决。

2.3 /dev is too big

很多发行版系统中, /dev下的设备文件并不是完全与实际设备配对的,有很多是空的。在系统启动的时候,/dev被填入可能使用的节点,并不根据是否存在实际设备。例如,在Red Hat 9,/dev有18,000个设备文件。

为系统创建一大堆暂时甚至一直无用的设备文件,肯定产生性能和管理问题。因此很多操作系统把设备文件管理的任务移入内核,因为内核总是确切知道有多少实际设备的。设备文件管理的内核方案就是基于ram的文件系统——devfs。Linux也使用了这一方案,并且在数个流行的发行版上得到实现。

2.4 devfs

很多UNIX类操作系统都实现了devfs,用以解决前面提到的设备文件管理上的一些问题。Linux也实现了devfs,解决了不少人的燃眉之急。但是,基于Linux的devfs实现只解决部分需求,如devfs只是解决了“/dev太大了”的问题,仍然存在一些没有解决的问题。另外,devfs 还带入新问题。引起新问题包括:

第一,devfs 的命名方案与LANANA授权命名不兼容。由于这种不兼容性,在devfs系统与静态/dev系统间切换需额外的配置,devfs的作者们必须实现某种与静态/dev命名的兼容模式。

第二,命名策略( naming policy )被移进内核。

没解决的问题包括,第一,设备号依然能静态分配;第二,设备与设备文件的映射依然不固定。

3 udev’s goals

在出现前面提到的所有问题的同时,udev项目已启动。其目标如下:

  • 运行在用户空间
  • 创建一个动态的/dev
  • 提供一致的设备命名方法
  • 提供一个用户空间的API来访问关于当前系统设备的信息

基于两个事实,第一个目标——“在用户空间中运行”——已经很容易做到。首先,/sbin/hotplug 实现了当系统添加或删除设备向用户空间发送事件消息;其次,结合在sysfs中能展示所有设备的元信息。其余的目标可把udev的项目拆分成三个独立的子系统:

  • namedev:处理设备的命名
  • libsysfs:一个用于访问系统上的设备信息的标准库
  • udev:动态管理 /dev

3.1 namedev

鉴于不同的设备命名方案的需要,udev的设备命名部分已被独立出来,成为独立的子系统——namedev。命名策略移出udev的二进制后,不同需求的用户可根据需要自定设备命名方案。namedev与udev之间实现了一个标准的接口,udev可以通过标准接口透明地调用命名一个特定的设备。

udev的初始版本,namedev逻辑仍然是有几个源文件链接进入udev的二进制。目前只有一个命名方案实施,就是由LANANA指定的。这个方案很简单,设备名命名基本上与sysfs使用相同的名称,这个方案适用于目前大多数Linux用户。

udev项目目标之一是为用户提供一种基于一套策略来命名设备的方法。 namedev目前版本为用户提供了一个五个步骤序列来确定一个给定的设备的名称。这些步骤按指定顺序征询,如果设备的名称可以在任何一步确定,就使用步确定的名称。现有的步骤如下:

  1. 标签或序号
  2. 总线设备号
  3. 总线拓扑
  4. 名称简单替换
  5. 内核指定的名称

第一步,添加到系统的设备会被检查它是否有一个某种类型的设备的唯一的标识符。例如,USB设备检查USB序列号,SCSI设备的检查UUID,块设备检查文件系统的标签。如果匹配用户提供的标识符(在配置文件中),匹配结果被接受。

第二步,检查设备的总线号。对于很多总线,这个数字一般不随时间变化,并保证所有的总线号要在系统中的任何一个点在时间上是唯一的。一个很好的例子是PCI总线的号很少改变。再次,如果总线号匹配由用户提供的,匹配结果名称用于该设备。

第三步,检查设备在总线上的位置。例如,一个USB设备位于根集线器第一端口连接的次集线口的第三个端口上。这种拓扑结构不会改变,即便机器重新启动总线号改变,除非用户物理上移动了设备。如果总线上的拓扑位置和用户提供的位置相匹配,请求的名称被指定给设备。

第四步是一个简单的字符串替换。如果设备的内核的名字在这里指定的名称相匹配,请求的新名称将用于设备。如果用户总是知道将具有相同的内核名称,但希望名称不同的东西,这是有用的。

第五步是捕捉所有落下的步骤。如果前面的步骤都未能提供此设备的名称,默认的内核的名称将被用于此设备。对于大多数系统中的设备,这是将要使用的规则,因为它匹配在Linux系统上没有devfs的或udev的命名的设备。

下图表显示了一个namedev配置文件例子。这个配置文件说明了如何替换默认的内核命名方案的四种不同的替换方式。

  • LABEL: 前两个条目显示如何根据设备标识(序列号)指定设备名;
  • NUMBER: 第三和第四个条目显示如何避开(override)总线探测顺序的影响,根据总线ID来命名设备;
  • TOPOLOGY: 第五和第六项显示如何根据USB的拓扑结构指定的设备名称;
  • REPLACE: 第七项演示了如何做一个简单的名称替换。

# USB Epson printer to be called lp_epson
LABEL, BUS=”usb”, serial=”HXOLL0012202323480″, NAME=”lp_epson”
# USB HP printer to be called lp_hp,
LABEL, BUS=”usb”, serial=”W09090207101241330″, NAME=”lp_hp”
# sound card with PCI bus id 00:0b.0 to be the first sound card
NUMBER, BUS=”pci”, id=”00:0b.0″, NAME=”dsp”
# sound card with PCI bus id 00:07.1 to be the second sound card
NUMBER, BUS=”pci”, id=”00:07.1″, NAME=”dsp1″
# USB mouse plugged into the third port of the first hub to be
# called mouse0
TOPOLOGY, BUS=”usb”, place=”1.3″, NAME=”mouse0″
# USB tablet plugged into the second port of the second hub to be
# called mouse1
TOPOLOGY, BUS=”usb”, place=”2.2″, NAME=”mouse1″
# ttyUSB1 should always be called visor
REPLACE, KERNEL=”ttyUSB1″, NAME=”visor”
Figure 3: Example namedev configuration file

3.2 libsysfs

为了让更多的用户空间程序访问sysfs,而不仅仅是udev的项目,有必要有一个通用的API来访问sysfs中的设备信息。与将查询逻辑重复写入不同的项目内不同,将查询逻辑在sysfs之上分割出来为一个单独的库,将更有意义。另外,sysfs表征不同类型设备的方式没有标准,PCI设备与USB有不同的属性项,这也是创建一个用于查询设备信息的标准库接口的另一个原因。

目前版本的udev使用的最初版本的libsysfs,libsysfs代码库的开发正处于活跃阶段。

3.3 udev

udev负责沟通namedev和libsysfs库以完成设备的命名。当内核调用/sbin/hotplug时,udev被触发运行,udev是通过在/etc/hotplug.d/default目录中添加一个链接自身的符号链接来实现这种触发关联的。/sbin/hotplug通过搜索/etc/hotplug.d/default目录间接调用udev。

/sbin/hotplug 被内核调用的时候,“热插事件名”会被传给/sbin/hotplug ,另外,内核还通过环境变量传递更多的事件信息,例如事件类型(插入是拔出)、发生事件的设备类型(USB, PCI等),还有具体(在sysfs目录树的)哪个设备发生了事件。udev根据这些信息,调用namedev取得设备名,如果是添加新设备,udev调用libsysfs取得设备号,然后创建相应的设备文件;如果是移除设备,udev移除相应用设备文件。

4 Enhancements

有不少用户要求对udev进一步优化增强,这些优化可以添加到现有的udev实现。

很多用户空间程序希望能检测到新设备被添加或从系统中删除的事件。Gnome和KDE都想在系统添加新磁盘的时候添加一个新的图标,或者在USB Palm设备插入后启动一个同步程序。D-BUS项目的目标就是为应用程序使用消息实现彼此通信提供一种简单的方法。例如,udev在创建或移除设备文件后向系统广播一条D-BUS消息,所有侦听消息应用程序可以作出相应的动作。

目前,namedev使用一个非常简单的配置文件,创建一个简单的基于RAM的数据库,用于存储当前所有的设备信息和设备的命名规则。有人建议,将这个“数据库”转移到一个真实的,备份式存储类型的数据库,以持久化系统的状态,或者提供一个更复杂的命名方案给udev使用。

你可能感兴趣的:(linux,数据库,Scheme,Palm,磁盘,Numbers)