在本文中,我们将完成把我们的
Linux 系统转换到 devfs,即设备文件系统。对于那些正在加入 devfs 系列的人来说,请阅读 本系列的第
4 部分,在那里面我解释了 devfs 是如何解决内核级设备注册这一令人头疼的问题。然后请阅读 本系列的第 5 部分,在那一部分里我谈到了所有的所需步骤,您需要采用这些步骤来使得 Linux 系统与 devfs 兼容以便将系统最终转 换到 devfs。
如果还没有阅读过第 5 部分,那么在按这里所写的去做之前现在就阅读它是很重要的。 如果跳过了第 5 部分中的步骤,那么几乎可以肯定将要安装的初始化封装器将不能正确地运行,并且将最终得到一个无法引导的、需要紧急恢复的系统。这不是一件好事。然而,如果已经读过第 5 部分,那么就可以往下做了。
初始化封装器
开始
我曾经在第 5 部分结束时,介绍了初始化封装器的概念,并且解释了初始化封装器非常适合于解决若干 devfs 初始化问题的原因。无须多说,让我们逐步来看完整的初始化封装器并且研究每一部分做什么。我们将从头开始:
初始化封装器,开始部分
#!/bin/bash
# Copyright 2001 Dan
iel Robbins <drobbins@gentoo.org>, Gentoo Technologies, Inc.
# Distributed under the GNU General Public License, version 2.0 or later.
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 继续执行封装器时,将跳过该条件语句。就这么提一下,以下是接下来的几行:
初始化封装器中的更多内容
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
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