设备,到处都是设备
Devfs,也叫设备文件系统(Device Filesystem),设计它的唯一目的就是提供一个新的(更理性的)方式管理通常位于 /dev 的所有块设备和字符设备。您也许知道,典型的 /dev 树包含数百个块特殊文件和字符特殊文件,它们全都在根文件系统上。每个特殊文件都可以让用户空间进程轻松地与内核设备实现交互。举例来说,通过对这些特殊文件执行操作,您的 X 服务器就能够访问视频硬件, fsck 可以执行文件系统检验, lpd 可以通过并行端口向打印机发送数据。
实际上,通常 Linux 和 Unix 更“酷”的方面是,设备不是简单地隐藏在晦涩的 API 之后,而是真正地与普通文件、目录和符号链接一样存在于文件系统上。因为字符和块设备是映射到普通文件系统名称空间的,我们通常可以用有意义的方式来与硬件交互,可以仅使用标准 Unix 命令,如 cat 和 dd。除了有趣之外,这还使我们有更强的能力,并提高生产力。
设备管理问题
然而,虽然设备特殊文件本身是一件好事情,但典型的 Linux 系统以一种不太理想而且麻烦的方式管理这些特殊文件。如今,Linux 支持 很多不同种类的硬件。这意味着严格意义上我们中绝大多数在 /dev 中都有数百个特殊文件来表示所有这些设备。还不止这样,这些特殊文件中大多数甚至不会映射到系统中存在的设备上(但需要它们存在,只是考虑到我们最终会在系统中添加新的硬件/驱动器),这让事情变得更令人困惑。
仅从这个方面来看,我们就知道 /dev 需要彻底检修,而创建 devfs 的明确目的就是让 /dev 变回原形。为了很好地理解 devfs 是怎样解决绝大多数 /dev 管理问题的,我们从设备驱动程序的角度来看看 devfs。
|
设备管理内幕
为了很好地理解 devfs ,最好是先理解从设备驱动程序的角度来看 devfs 是怎样改变事物的。传统地(不使用 devfs),根据是否注册在 块设备或 字符设备,基于内核的设备驱动程序通过调用 register_blkdev()或 register_chrdev() 向系统的其余部分注册设备。
您必须提供一个 主设备号(一个无符号 8 位整数)作为 register_blkdev()或 register_chrdev() 的参数;然后,在设备注册之后,内核就会知道这个特定的主设备号对应于执行 register_--?dev()调用的特定设备驱动程序。
那么,设备驱动程序开发人员为调用 register_--?dev() 提供的主设备号 应该是什么呢?如果开发人员不打算将设备驱动程序与外界共享,那么什么号码都可以,只要它与当前内核使用的其它主设备号都不冲突即可。开发人员还可以选择动态地分配 register_--?dev() 调用的设备的主设备号。然而,这样的解决方案通常只是在驱动程序不会被其它人使用的情况下可行。
获取号码
然而,如果开发人员想让驱动程序与外界共享(大多数 Linux 开发人员常常采用这一方法),那么仅仅从“真空”中抽一个主设备号或者使用动态的主设备号分配就不行了。相反,开发人员必须联系 Linux 内核开发人员,这样他(她)的特定的设备才能分配一个“正式”主设备号。那么,在整个 Linux 世界中,这个特定的设备(也 只有这个设备)才会被关联到那个特定的主设备号。
有一个“正式的”主设备号很重要,因为要与特定的设备交互,管理员必须在 /dev 创建一个特殊文件。当设备节点(特殊文件)创建后,它使用的主设备号必须同内核内部使用的完全相同。这样,进程对设备执行操作时,内核就会知道应该引用什么设备驱动程序。让特殊文件到内核驱动程序的映射成为可能的是主设备号,而不是真实的设备名称(它和非 devfs 系统无关)。
一旦设备驱动程序具备正式主设备号,设备就可以被公开使用了,设备节点也就可以开始并入不同分发版的 /dev 树,还有它们的正式 /dev/MAKEDEV 脚本(用来帮助超级用户用正确的主从设备号、权限和所有权创建设备节点的特殊脚本)中。
传统的问题
不幸的是,这种方法有很多可伸缩性问题。不仅设备驱动程序开发人员联系内核开发人员来获取正式主设备号是一件讨厌的事,内核开发人员弄清他们怎样分配所有这些主设备号甚至更加恼人。这种任务在很多方面很象系统管理员跟踪公司局域网静态 IP 地址分配的工作 ― 这并不十分有趣。正如系统管理员可以利用 DHCP 来缓解这种管理负担,如果设备注册有某种类似的方法就好了。
不只是这样,Linux 还正在耗尽主设备号和副号码。虽然这种问题可以通过简单地扩展主设备号和副号码使用的位数,首先维护这些主设备号映射就很讨厌了,所以我们又在考虑有没有更好的方法来处理这些事情。幸运的是,有这样的方法;进入 devfs。
|
进入 devfs
devfs_register()
这里是对 devfs 如何一下子处理事情和解决这些问题的一个简单明了的快速纲要。一旦 devfs 被正确配置(包括在内核添加 devfs 支持和对启动脚本进行一些稍复杂的更改),超级用户重新启动系统。然后内核开始启动,设备驱动程序开始向系统的剩余部分注册设备。您会记起在非 devfs 系统上, register_blkdev()和 register_chrdev() 调用(连同提供的主设备号)正是用于这一目的。然而,现在启用了 devfs,设备驱动程序是用一种新的、改进了的内核调用来注册设备,称为 devfs_register()。
这里是 devfs_register() 调用有趣的地方。虽然为了兼容性目的指定主设备号和副号码作为参数是可能的,但不再需要这样了。相反, devfs_register()调用接受 设备路径(就是它在 /dev 下可能的出现形式)作为参数。举例来说,假设 foo 设备驱动程序希望使用 devfs 注册设备。它会提供一个 foo0 的参数给 devfs_register(),从而告诉内核应该在 devfs 名称空间的根目录创建一个新的 foo0 设备。相应的, devfs_register() 在 devfs 名称空间的根目录添加 foo0设备节点,并记录这个新的 foo0 节点应该映射到内核中的 foo设备驱动程序。
运行的 Devfs
一旦所有设备驱动程序启动并向内核注册适当的设备,内核就启动 /sbin/init 和系统初始化脚本开始执行。在启动过程初期(在文件系统检查前),rc 脚本将 devfs 文件系统安装在 /dev 中,/dev 包含了 devfs 名称空间的表达。这意味着在安装 /dev 后,所有注册的设备(如上面的 /dev/foo0)都可以访问,就象在非 devfs 上一样。当它们被访问时,内核 通过 devfs 设备名称映射到合适的设备驱动程序,而不是通过主设备号。
这种系统的优点是,所有需要的设备节点(没有别的了)都由内核自动创建。这不仅仅意味着不再需要 MAKEDEV(因为所有注册的设备都只“出现”在 /dev 中),还意味着 /dev 不再被成百个“无用的”设备节点所充斥。实际上,使用 devfs,您可以只要查看 /dev 就知道系统上有什么设备。所以,如果您有一台支持热插拔的膝上型电脑,这意味着您甚至可以在您从系统中插入和拔出 PC 卡时魔术般地让设备从 /dev 中出现和消失。这让 devfs 成为对以前笨拙局面的一个非常彻底和实用的解决方案。
devfs 的优点
Devfs 让很多事变得容易许多。请考虑一下创建一张 Linux 可引导光盘的问题,它包括一个位于 CD 上的引导装载器、一个 initrd、一个内核和一个回送文件系统。当 CD 引导时,引导装载器装载内核和 initrd,然后内核执行 initrd 上的 /linuxrc脚本。 /linuxrc 的主要任务是安装 CD,从而使回送文件系统本身也可以被安装和访问。
没有 devfs, linuxrc 就需要“查看” /dev 中的很多特殊文件,它们可能有也可能没有表示连接到系统的真实硬件。例如, linuxrc 会需要检测 /dev/hdc、/dev/scd0、/dev/hdb 和其它的设备以检测“活动的”光盘驱动器设备。在检测进程中,很可能命中几个“无用的”设备节点。
然而,使用 devfs, linuxrc 只在 /dev/cdroms 中寻找,它包含了系统中所有和 活动的光盘驱动器相关联的特殊文件,不管是 IDE 的还是 SCSI 的。由于这种便捷的新式 devfs 约定,再不需要猜测了;只有活动的设备才会列出,而且设备检测代码甚至不必担心底层的光盘驱动器的细节,比如说它使用什么 IDE 通道或者什么 SCSI ID。实际上,这是 devfs 的另一个主要好处;在我下一篇文章中,我们会看到 devfs 下 /dev 中的设备有全新的缺省位置。
实际上,如果您想访问一个特定的块设备(如磁盘、分区、光盘驱动器等等),事实上有 几个不同的特殊文件可以引用。例如,我的服务器只有一个 SCSI 光盘驱动器;如果启用了 devfs,我就可以通过安装 /dev/cdroms/cdrom0 或 /dev/scsi/host0/bus0/target4/lun0/cd 访问它。两种都引用同一个设备,我可以引用我认为最方便的特殊文件。如果愿意,我还可以使用一种老式的设备名称(/dev/sr0)访问光盘驱动器,这都是因为有一个非常便捷的叫 devfsd的小程序。 devfsd 是一个有功能很多的程序,它负责创建老式的“兼容性”特殊文件,还允许您以很多种方式自定义 /dev。在我的下一篇文章中,我们会详细讨论 devfsd,到时我会一直引导您启动 devfs 并在您自己的系统上运行它。在那之前,请参考下面的参考资料以了解更多关于 devfs 的信息。