天天看点

devfs文件系统

伴随着 Linux 2.4 版本的发行,出现了大量的文件系统可能性,其中包括 ReiserFS、XFS、GFS 和其它文件系统。这些文件系统听起来的确都很酷,但是它们真正能做些什么呢,擅长在哪些方面,以及在 Linux 产品环境下如何才能安全地使用它们呢?Daniel Robbins 通过向您展示如何在 Linux 2.4 的环境下建立这些新的高级文件系统来回答以上的问题。遵从这个方法,它提供了在实际实现过程中的有价值的建议、性能信息和重要的技术性注意要点,以便于您 在新的文件系统中能有令人愉快的经历。在本文中:Daniel 解释了使用设备管理文件系统 devfs 的意义和好处,让您对其有所认识以便在下一篇文章中向您展示如何最佳地在系统上安装 devfs。

介绍 devfs

设备,到处都是设备

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)访问光盘驱动器,这都 是因为有一个非常便捷的叫 devf sd的 小程序。 devfsd 是一个有功能很多的程序,它负责创建老式的“兼容性”特殊文件,还允许您以很多种方式自定义 /dev。在我的下一篇文章中,我们会详细讨论 devfsd,到时我会一直引导您启动 devfs 并在您自己的系统上运行它。在那之前,请参考下面的参考资料以了解更多关于 devfs 的信息。 在 上一部分(第 4 部分)中,我们具体讨论了什么是 devfs,以及它是如何解决几乎所有的设备管理问题的。现在是在您的系统上启动和运行 devfs 的时候了。 在本文中,我们将使您的系统为启用 d

evfs 作好准备,在下篇文章中,我们将真正开始向 devfs 的转换。即使在 devfs 的下一篇(也是最后一篇)出版以前,您也完全可以遵循本文中的步骤,因为当我们完成了所有这些步骤之后,您的系统将仍旧继续正常运行 ― 它仅仅为即将来临的 devfs 转换作好了准备。因此,没有必要因为后继文章还未出现而不遵循这些步骤。

请注意: 因为我们将对 Linux 系统的几个部分作出相当大的更改,所以确实可能搞糟您的系统。因此,如果您在对 Linux 系统内部更改方面没有经验的话,毫无疑问您应该在一个无关紧要的 Linux 机器上这样做,至少第一次应该这样。

需求

要启动和运行 devfs,需要使用 Linux 2.4 的一些版本(2.4.6 或 2.4.8 就不错)以及 glibc 2.1.3 或更高版本。还建议您使用 Xfree86 4.0 或更高版本,如果您的系统版本较低,建议您首先升级到 Xfree86 4.1。

紧急 bash 救援

在下篇文章中,我们将更改 Linux 系统中对启动至关重要的部分。既然完全可能因某个错误而使您偶尔搞糟引导过程,我将在本文首先讲述:如何用紧急 bash shell 命令启动和运行系统。如果您确实碰巧发现系统因为 init 脚本或 /sbin/init 本身的问题而不能引导,就可以使用这个紧急引导过程。

进行紧急引导最简单的方法是:在引导时刻使用 GRUB 或 LILO 把 init=/bin/bash 选项传递给内核。 如果使用 GRUB,您应该能够通过点击 e 来实时地编辑当前菜单项,从而在需要时交互地传递该选项。如果使用 LILO,则确保在继续下一步之前,您已知道如何向内核传递启动选项,必要时,还要创建一个新的“紧急”LILO 启动选项。

过程

这样,基本的“救援”过程如下。首先,将 init=/bin/bash 作为一个内核引导选项传递给内核。当内核引导时,它将以 /bin/bash 而不是通常的 /sbin/init 作为第一个进程启动。 您不会被提示进行登录就看到一个 root 用户 bash 提示符:

#

然而,尽管您看到一个 root bash 提示符,实际上只安装了根文件系统,而且仅以只读的形式安装。下面介绍在这之后如何启动和运行您的系统。如果文件系统没有卸装干净的话,应该首先对它们进 行 fsck 。首先对根文件系统执行 fsck -a ,然后 fsck -R -A -a 就会负责处理所有其它的文件系统:

# fsck -a /dev/hda1

# fsck -R -A -a

既然文件系统已经一致(或者,如果文件系统在系统重引导时已经卸装干净,并且您跳过了上一步骤),我们可以简单地以可读写方式重新安装根文件系统并且安装 / proc,如下所示:

# mount / -o remount,rw

# mount /proc

然后,安装可能位于其它分区中的所有重要的文件系统树。例如,如果在另一个分区上有 /usr,则还要输入:

# mount /usr

如果您想做的不仅仅只是打开一个编辑器的话,则最好是激活交换分区。如果您使用 e macs,您可能会需要它:)

# sw apon -a

现在,您应该能够使用您喜爱的编辑器来编辑任何需要编辑的文件,以便修复现有的引导问题。一旦完成,只需按安装时的顺序,以只读方式重新安装分区即可。例如,如果有一个单独的 /usr 分区,为使所有的文件系统处于一致的状态(准备重新引导),可以输入:

# mount /usr -o remount,ro

# mount / -o remount,ro

现在,您可以安全地重新引导了。但愿现在已经解决了引导问题,并且可以使用正常的 LILO 或 GRUB 选项启动和运行系统。

# /sbin/reboot -nfi

为 devfs 作好准备

devfs 配置

既然您知道了在紧急情况下该怎么做,我们就可以使系统为 devfs 做好准备了。在下一篇文章里,我们将对 Linux 系统作相对复杂的更改。为什么需要这样呢?因为我们不仅仅在内核中启用 devfs 功能,这的确非常容易。我们还将以一种特别方式设置 devf sd (设备管理守护进程),用它来备份和恢复任何对设备许可权和所有权的更改。我们需要用到很多小窍门来使这个新的系统极佳地工作。但是一旦实现,我想您将对这个结果 非常满意。

devfs 内核支持

在系统上启用 devfs 的第一步比较简单:就是要使内核支持 devfs。为此目的,您需要一个 2.4 系列的内核。使用 make menuconfig 或者 make xconfig ,转至 Code maturity level选项部分并确保启用了 Prompt for development and/or incomplete code/drivers选项。然后转至 File systems内核配置部分,查找 /dev file system support (E XPERIMENTAL) 选项。选中它。您将会看到在您刚启用的选项下方,出现了两个附加选项。第一个选项控制 devfs 在内核引导时是否自动安装到 /dev。 不要启用它,我们将用一个特殊脚本手动安装 /dev。 第二个选项, Debug devfs,也应该被禁用。

禁用 /dev/pts

当您在屏幕上看到 File systems kernel configuration 部分时,如果您碰巧启用了 /dev/pts file system for Unix98 PTYs 的支持,则禁用该支持。devfs 提供了相似的功能,所以您就不再需要 devpts 文件系统了。继续进行然后保存内核配置;我们很快就要编译并安装一个新的内核!最后,在进行下一步以前,检查一下在 /etc/fstab 中是否有 /dev/pts 项;如果有,把它注释掉,使它在启动时不再被安装。

各种配置风格

下一步,将 /etc/securetty 文件装入编辑器。该文件由 login 使用,它允许您指定允许 root 用户使用以进行登录的 tty s。通常,它包含从 tty1 到 tty12 的设备,每行一个。为了使这个文件适用于 devfs,您应当为这些 ttys 加入适当的 devfs 类型名字,并保留原有的 tty? 名字,以备日后您决定禁用 devfs 引导之需。把以下几行添加到 /etc/securetty 的最下面。

vc/1     vc/2      vc/3     vc/4     vc/5    vc/6    vc/7    vc/8    vc/9    vc/10    vc/11    vc/12

安装 devfsd

接下来就是在系统上安装 devfsd ,即 devfs 助手守护进程。Devfsd 将会负责创建“旧类型”兼容性设备节点;在注册/注销设备时执行自动化操作;负责备份对根文件系统上某个目录的设备许可权和所有权的更改,以及其它更多功 能。现在,我们将只安装 devfsd;在下一篇文章中,我们将使它和 devfs 一起启动和运行。为了安装 devfsd,首先需要下载最新版本的 devfsd 压缩文件。(请参阅本文后面的 参考资料),当前版本为 1.3.16。然后执行下列步骤:

# tar xzvf devfsd-1.3.16.tar.gz

# cd devfsd

# make

现在,devfsd 应该编译好并可以安装了。如果您的帮助手册页存储在 /usr/man 中,输入 make install ;如果您正在使用 FHS 兼容系统,并且您的帮助手册页存储在 /usr/share/man 中,输入 make mandir=/usr/share/man install 。现在将安装 Devfsd,但还未运行,这正是我们现在要做的。

安装注释

我们即将配置 devfsd ,以便完全支持兼容性设备,所以tty? 应该足够了。然而,小心驶得万年船,尤其在可能会影响到 login 是否允许超级用户进入系统的时候。用我们的方法,即使存在问题而且 devfsd 无法启动,超级用户在 login: 提示符下登录时也不会有问题,哪怕已经启用了 devfs。

启动新内核

现在,继续前进,编译并安装刚刚配置好的内核。这个内核应该是您现在内核的临时替代品;它应能正常引导;并且尽管它内置了 devfs 支持,您应该觉察不出它与您现在正在运行的内核有什么差别。 一旦新内核安装完毕,重新引导系统以确保到目前为止一切工作正常。

Devfs 配置方法

您的系统现在已经作好了向 devfs 转换的准备,我将在下一篇文章中详细介绍。现在,是熟悉一下我们正在使用的方法的时候了。您将看到,用 devfs 启用系统可能会很棘手,尤其当您使用到 devfs 的所有优点(如持久的许可权和所有权)的时候。

内核自动安装带来的问题

确实有很多方法来使系统 devfs 启用。其中之一就是让内核在引导时自动将 devfs 安装到 /dev;我们将不使用此选项,尽管这样 确实是可以的。乍看起来,这种方法似乎很有意义,因为它可以保证所有 devfs 类型的设备对于所有进程都是可用的,甚至对第一个进程 /sbin/init 也是如此。然而,这种方法有一个问题。尽管 devfs 提供所有“新类型”的设备,但旧类型的设备节点却是由 devfsd 守护进程创建。 devfsd 不是由内核启动的,所以即使让内核在引导时安装 devfs ,当 /sbin/init 启动时我们仍然只会得到部分而非所有的设备节点。这就意味着为了使 devfsd 能在恰当的时间启动和运行,您必须修改系统初始化脚本。这不但棘手(因为这需要对系统启动脚本有专家级的理解),而且这种方法还存在其它问题。

内核安装方法的主要的问题是, devfsd 只有在能够访问原来旧类型磁盘上的 /dev 目录下的内容时,才能最好地工作。我们允许访问原来的旧类型设备的典型办法是,在 devfs 自身被安装到 /dev 之前,绑定安装 /dev 到另一个位置(通常是 /dev-state)。

这样就确保了即使在安装了 devfs 以后,也可以在 /dev-state 中访问旧的 /dev,这就允许 devfsd 将该目录用于持久设备存储。理解这点很重要,即如果没有绑定安装的话,/dev 里的旧内容将不可访问,因为在 /dev 安装 devfs 时实际上会覆盖它们。这就是用内核安装 devfs 存在的问题。如果 kernel 在任何其它进程能够启动之前就在 /dev 安装 devfs 文件系统的话,那么我们就没有机会执行绑定安装,/dev 的最初内容也就被完全隐藏。这很不友善,是吗?(想知道更多绑定安装的内容,请参阅本系列的 第 3 部分。

最佳解决方案

理想的情况是:我们能在 /sbin/init 一启动时就能拥有完整的设备节点(新类型 和兼容性设备), 并且有机会在安装 devfs 以前将 /dev 绑定安装到另一位置。但如何才能做到这点?

初始封装器

一个方法是添加一个内核补丁来执行从 /dev 到 /dev-state 的绑定安装。然而,尽管这完全可行,而且也确实很容易执行,手工为您安装的每个 Linux 内核打补丁仍是相当麻烦的。因此,解决 devfs 的“先有鸡还是先有蛋”问题的最好办法,可能就是使用 初始封装器。对于我们这个特别的应用而言,初始封装器就是一个 bash 脚本,它代替 /sbin/init ― 而 真正的 init 则已被重命名为 /sbin/init.system 。简而言之,初始封装器将做以下事情:

#!/bin/bash

mkdir -f /dev-state

mount --bind /dev /dev-state

mount -t devfs none /dev

devfsd /devexec /sbin/init.system

正如您所见,初始封装器所做的第一件事就是确保 /dev-state 存在。然后将 /dev 树绑定安装到 /dev-state,以便可以通过 /dev-state 目录使用 /dev 的内容。然后,在 /dev 之上安装我们的 devfs 文件,然后启动 devfsd ,以便自动在 devfs 中注册我们的兼容性设备。 最后,我们 exec 最初的 /sbin/init ,现在它已被重命名为 /sbin/init.system 。 exec 命令使 init.system 取代正在运行的 bash 进程。这意味着我们的 bash 脚本被终止,而 init.system 继承了标识符为 1 的进程,也就是 init 进程以前被占用的进程标识。当 /sbin/init.system 启动后,系统将正常引导,devfs 现在也已经 完全可操作了;通过使用初始封装器,我们不必给内核打补丁,不必修改启动脚本,也不必为只有一半可操作的 devfs 系统而伤脑筋了。

在我的下一篇文章中,我将指导您经历启动和运行完整版本的初始封装器的整个过程,并为您演示如何利用 devfsd 的众多强大特性。请继续阅读!

在本文中,我们将完成把我们的 Linux 系统转换到 devfs,即设备文件系统。对于那些正在加入 devfs 系列的人来说,请阅读 本系列的第

4 部分,在那里面我解释了 devfs 是如何解决内核级设备注册这一令人头疼的问题。然后请阅读 本系列的第 5 部分,在那一部分里我谈到了所有的所需步骤,您需要采用这些步骤来使得 Linux 系统与 devfs 兼容以便将系统最终转 换到 devfs。

如果还没有阅读过第 5 部分,那么在按这里所写的去做之前现在就阅读它是很重要的。 如果跳过了第 5 部分中的步骤,那么几乎可以肯定将要安装的初始化封装器将不能正确地运行,并且将最终得到一个无法引导的、需要紧急恢复的系统。这不是一件好事。然而,如 果已经读过第 5 部分,那么就可以往下做了。

初始化封装器

开始

我曾经在第 5 部分结束时,介绍了初始化封装器的概念,并且解释了初始化封装器非常适合于解决若干 devfs 初始化问题的原因。无须多说,让我们逐步来看完整的初始化封装器并且研究每一部分做什么。我们将从头开始:

初始化封装器,开始部分

#!/bin/bash

# Copyright 2001 Dan iel Robbins <[email protected]>, Gentoo Technologies, Inc.

# Distributed under the GNU General Public License, version 2.0 or later.

tr ap ":" INT QUIT TSTP  

e xport PATH=/sbin:/bin:/usr/sbin:/usr/bin

umask 022

if [ $$ -ne 1 ]

then

     exec /sbin/init.system $*

fi

正如您所看到的一样,由于在脚本的开始有 #!/bin/bash 语句,所以初始化封装器是一个真正的 bash 脚本。这里正好向您指出初始化封装器运行 需要 bash 2.0 或更高版本;输入 /bin/bash --version 命令来看一下 bash shell 版本是否足够新 。如果不是,可能想知道是否安装了 /bin/bash2 可执行文件。如果安装了,请将脚本的第 1 行改为 #!/bin/bash2 。

现在,让我们来阅读脚本。 trap 命令防止用户在脚本执行时中断(例如,在引导时键入 control-C)脚本。然后, export 一个合理的缺省路径并且设置缺省 umask 为 022。由于在 2.4 之前发布的一些内核中有一个会产生 umask 缺省为 0 的错误,这一错误可能会造成安全威胁,因此在引导阶段尽可能早的设置一个缺省的 umask 总不失为一个好主意。

接下来,碰到了第一个条件语句, if [ $$ -ne 1 ] 。 bash 将 $$ 扩展为当前正在运行的进程标识,因此可以发现我们真正想问的问题是“我们的进程标识根本不是 1 吗?”这样做有什么意义呢?如果是在引导期间,则 bash 是由内核启动的,由于 PID 1 是为 init 进程保留的,所以将总会得到 PID 为 1。如果 PID 不是 1,则知道在系统已经引导之后,正在从命令行方式运行。由于 /sbin/init 命令有双重用途,允许超级用户改变已经引导的系统的运行级别,因此这很正常。如果是这样的话,那么仅仅 exec 了原来的 /sbin/init (现在 改名为 /sbin/init.system ) 。通过使用 $* 变量来传递任何命令行参数给 init.system ,初始化封装器终止,并且 init.system 开始执行。

内核引导选项

然而,如果在引导期间 正在由内核启动封装器,则 bash 的 PID 将为 1 ,当 bash 继续执行封装器时,将跳过该条件语句。就这么提一下,以下是接下来的几行:

初始化封装器中的更多内容

mount -n / proc

devfs="yes"

for copt in ` cat /proc/cmdline`

do

     if [ "${copt%=*}" = "wrapper" ]

     then

         parms=${copt##*=}

         #parse wrapper option

         if [ "${parms/nodevfs//}" != "${parms}" ]

         then

             devfs="no"

         fi

     fi

done

如果运行到这一代码块,那就意味着在系统引导期间,正在启动;作为处理的第一条命令,将 /proc 安装到根文件系统,这个 /proc 当前为只读。在那之后,执行一个大而复杂的 bash 代码块,该代码块利用了一个非常便利的 Linux 特性。您可能不了解这一特性,内核允许查看 /proc/cmdline 的内容来弄清楚 LILO 或 GRUB 传给内核什么选项。在我们的开发机器中,/proc/cmdline 的内容如下所示:

/proc/cmdline 的内容

# cat /proc/cmdline

root=/dev/hda6 hda=89355,16,63 mem=524224K

在上面的代码中,利用已有的 /proc/cmdline,通过它来查找一个我们自己创建的、称为 wrapper 的内核引导变量。如果 wrapper=nodevfs 出现在内核引导选项中,那么该脚本知道不去启动 devfs。然而,如果这一变量没有出现在 /proc/cmdline 中,那么封装器将进行 devfs 初始化。这里的含意是说您可以通过使用 wrapper=nodevfs 内核引导选项来禁止 devfs。如果这么做的话, devfs 变量将被设置成 no ;否则它将被设置成 yes 。

封装它

下面是该封装器的剩余部分:

初始化封装器的剩余部分

if [ "$devfs" = "yes" ]

then

if [ -e /dev/.devf sd ]

then

     clear

     echo

     echo "The init wrapper has detected that /dev has been automatically mounted by"

     echo "the kernel. This will prevent devfs from automatically saving and"

     echo "restoring device permissions. While not optimal, your system will still"

     echo "be able to boot, but any perm/ownership changes or creation of new compat."

     echo "device nodes will not be persistent across reboots until you fix this"

     echo "problem."

     echo

     echo "Fortunately, the fix for this problem is quite simple; all you need to"

     echo "do is pass the /"devfs=nomount/" boot option to the kernel (via GRUB"

     echo "or LILO) the next time you boot.   Then /dev will not be auto-mounted."

     echo "The next time you compile your kernel, be sure that you do not"

     echo "enable the /"Automatically mount filesystem at boot/" devfs kernel"

     echo "configuration option.   Then the /"devfs=nomount/" hack will no longer be"

     echo "needed."

     echo

      read -t 15 -p "(hit Enter to continue or wait 15 seconds...)"

else   

     mount -n /dev /dev-state -o bind

     mount -n -t devfs none /dev

     if [ -d /dev-state/compat ]

     then

             echo Copying devices from /dev-state/compat to /dev

             cp -ax /dev-state/compat/* /dev

     fi

fi

/sbin/devfsd /dev >/dev/null 2>&1;

fi

exec /sbin/init.system $*

现在我们碰到了一个大的条件语句,只有在 devfs 被设置成为 yes 时,该条件语句才会执行。如果不是这样,则完全跳过 devfs 初始化,甚至不会安装 devfs。这会导致一个常见的非 devfs 引导。

然而,如果 正在安装 devfs,则进入该条件语句。在该条件语句里,检查内核是否已经安装 devfs;通过检查 /dev/.devfsd 字符设备是否存在来实现这一目的。当安装 devfs 时,内核自动创建该设备,并且随后的 devfsd 进程将使用它来与内核通信。如果已经安装了 devfs(因为用户选择了“Automatically mount devfs at boot”内核选项),将打印一条信息告诉用户:由于只有在内核 还没有 devfs 时,才可以安装 devfs 的持久性特性,现在无法完成这一任务。

设备持久性

然而,如果一切正常,则执行了在上篇文章中所谈到的 devfs 安装:/dev 被绑定安装到 /dev-state 并且 devfs 文件系统被安装在 /dev 上。然后,执行一项在上篇文章中 没有提到的一个步骤;检查目录 /dev-state/compat 是否存在并且递归地把它的内容复制到 /dev 目录。虽然这一过程最初看起来有些多余(我们将利用 devfsd 的设备持久性特性,不是吗?),但是最终会证明这一过程是必要和有用的。需要一个 compat 目录的原因在于 devfsd 的持久性特性 仅仅只能使用支持 devfs 的驱动器。

最后,启动 devfsd ,然后退出该条件语句并且 exec 实际的 init, /sbin/init.system 来开始标准的系统引导过程。除了现在需要一个支持 devfs 的系统之外,所有东西都是标准的 :)

初始化封装器安装

下面是我们如何安装初始化封装器。首先, 获取 wrapper.sh 所需资源,并且把它保存在系统的某个地方。然后,按下面所说的做:

安装初始化封装器

# cd /sbin

# cp init init.system

# cp /path/to/wrapper.sh init

# chmod +x init

现在初始化封装器安装在正确的地方了。

调整 umount

通过使用初始化封装器,避免了编制大量复杂的启动脚本来进行调整。不过,我们可能还是不能避免 一个调整。既然,我们将 devfs 安装在 /dev,则 rc 脚本卸载根文件系统将可能会非常困难。幸运的是,有一个简便方法可以解决这一问题。只需要输入 cd /etc/rc.d; grep -r umount * 或 cd /etc/init.d; grep -r umount * 来 grep rc 脚本目录中所有出现 umount 的地方,具体输入哪条命令取决于 rc 脚本安装在什么地方。然后,在每个引用 umount 的脚本里,请确保调用时带有选项 -r 。虽然在各处使用 umount -r 仍然会起作用,但这一特定的 umount 的命令对于卸载根文件系统是十分重要的:)

-r 选项告诉 umount ,如果没能成功卸载文件系统,就以只读方式重新安装。对于把根文件系统设置到一致状态来说,这已经足够了,而且即便根文件系统由于在 /dev 上的有一个安装而无法卸载(由于无法卸载打开的设备节点),这也为根文件系统重新引导做好了准备。

现在,我们 几乎为重新引导做好了准备;但是在重新引导之前,让我们来看一下 devfsd 并且修改 /etc/devfsd.conf 以便支持兼容性设备和设备持久性。不用担心,我们离完成转换到 devfs 只有一步之遥。

devfsd.conf

用您喜爱的编辑器打开 /etc/devfsd.conf。下面是我推荐的 devfsd.conf 中的头四行:

devfsd.conf,开始部分

REGISTER         .*               MKOLDCOMPAT

UNREGISTER       .*               RMOLDCOMPAT

REGISTER         .*               MKNEWCOMPAT

UNREGISTER       .*               RMNEWCOMPAT

上面四行中的每一行都含有一个 事件( REGISTER 或 UNREGISTER ),一个正则表达式( .* )以及一项操作( *COMPAT 字符串)。那么,它们都表示什么意思呢?第 1 行告诉 devfsd ,当有内核中注册 任何设备( .* 是表示匹配 任何设备的正则表达式)时,执行 MKOLDCOMPAT 操作。 MKOLDCOMPAT 操作是 devfsd 内置的,它的含义是“创建任何与正在通过 devfs 注册的设备相对应的旧兼容设备”。正如您可能想到的一样,在删除设备时,会运行 RM*COMPAT 操作,这使得这些特定的兼容设备魔术般的消失。总的来说,这四行语句告诉 devfsd ,当设备注册时,创建兼容设备(如果有的话),当解除设备注册时,删除兼容设备。多亏了这几行,当 IDE 设备驱动器在系统中注册 /dev/ide/host0/bus0/target0/lun0/disc devfs 样式的设备时, devfs 自动创建一个匹配 /dev/hda 兼容样式的设备。这对于诸如 mount 和 fsck 命令来说,是极其有帮助的,这些命令可能会读一个包含旧样式设备的 /etc/fstab 。一般来说,创建兼容设备使得 devfs 的转换成了一个无缝转换。devfsd.conf 中的下一行是:

自动装载模块

devfsd.conf,续上

LOOKUP           .*               MODLOAD

这一项告诉 devfsd ,无论什么时侯“查看”任何设备( .* ),就执行 MODLOAD 操作,这是指当程序查找特定设备节点是否存在时所发生的操作。 MODLOAD 操作将导致执行 modprobe /dev/mydev ,这里的 /dev/mydev 是特定进程所要查找的设备名。多亏了这一特性(以及正确配置的 /etc/modules.conf),当启动音乐播放器或其它美妙的东西时,可能可以按照需要自动装入声卡驱动器。

设备持久性

以下是 devfsd.conf 中的接下来的几行:

devfsd.conf,续上

REGISTER         ^pt[sy]/.*       IGNORE

CHANGE           ^pt[sy]/.*       IGNORE

REGISTER         .*               COPY     /dev-state/$devname $devpath

CHANGE           .*               COPY     $devpath /dev-state/$devname

CREATE           .*               COPY     $devpath /dev-state/$devname

这几行告诉 devfsd 使用 /dev-state 作为用于设备许可权和所有权变更以及任何用户可以创建的兼容设备的资源库。在头两行中,显式地告诉 devfsd ,当内核中注册了任何伪终端设备或当它们的属性被更改时,不要执行任何特殊的操作。如果没有这几行,则在重引导系统之后,仍然会保留伪终端的许可权和所有 权。那样做不太理想,因为应该总是在系统启动之后给予伪终端一套新的缺省许可权。

接下来的三行为所有其它设备打开 /dev-state 持久性。特别地,注册设备或 devfsd 自己启动时,将 从 /dev-state 恢复设备的任何属性(以及复制给任何现有的兼容设备),并且我们将立即 备份属性的任何更改,以及将任何新兼容设备创建到 /dev-state 中去。

CFUNCTION 和符号链接

给出以下几行,最终完成 devfsd.conf:

devfsd.conf,结束

REGISTER         ^cdrom/cdrom0$           CFUNCTION GLOBAL symlink cdroms/cdrom0 cdrom

UNREGISTER       ^cdrom/cdrom0$           CFUNCTION GLOBAL unlink cdrom

REGISTER         ^misc/psaux$             CFUNCTION GLOBAL symlink misc/psaux mouse

UNREGISTER       ^misc/psaux$             CFUNCTION GLOBAL unlink mouse

这最后四行是可选的,但它们也值得一看。虽然对于设备节点,/dev-state 持久性工作得非常好,但对符号链接却根本不起任何作用,它会忽略符号链接。因此,这就产生一个问题:人们怎么确保 /dev/mouse 或 /dev/cdrom 符号链接不仅存在,而且在重新引导系统之后它还是存在的呢?幸运的是, devfsd 可配置性非常好,这四行(或类似这样的,可以定制你的特定系统)将完成这一任务。头两行告诉 devfsd ,当注册 /dev/cdrom/cdrom() 时,使 /dev/cdrom 符号链接出现。为了做到这一点, devfsd 实际上执行指定的 libc 函数的动态调用,这里是 symlink() 和 unlink() 。该文件的最后两行使用相同的方法,在 /dev/misc/psaux(PS/2 鼠标)设备注册到 devfs 时,创建 /dev/mouse 符号链接。根据 您的系统来定制这几行,然后保存该文件。如果您愿意,可以 下载这个 devfsd.conf 文件,用在您自己的系统上。

重新引导之前的注意事项

在重新引导之前,您可能想看一下 Richard Gooch 的 devfs FAQ;您可能会找到关于 devfs 命名方案的信息,这些信息对于熟悉新风格设备名是非常有帮助的(请参阅下面的 参考资料)。我还建议您打印一份 本系列第 5 部分,以备您在解决与引导相关的问题时,能利用“紧急 bash 抢救”指导。记住,如果初始化封装器因为某种原因而崩溃,请遵循我的紧急抢救指导,重新安装根文件系统为读/写,然后执行下面步骤,这总是可以除去它的:

如果需要,请返回至使用封装器前的状态

# cd /sbin

# mv init wrapper.sh

# mv init.system init

在执行完这些步骤,并将文件系统重新安装成只读,然后,重新引导之后,系统将返回到使用预封装器前的状态。现在继续,重新引导,体验一下 devfs!

继续阅读