热度 10 |
if [ -z "${noresume}" ]; then
export resume=${RESUME}
else
export noresume
fi
[ -n "${netconsole}" ] && modprobe netconsole netconsole=${netconsole}
maybe_break top
# export BOOT variable value for compcache,
# so we know if we run from casper
export BOOT
# Don't do log messages here to avoid confusing usplash
run_scripts /scripts/init-top
maybe_break modules
log_begin_msg "Loading essential drivers..."
load_modules
log_end_msg
maybe_break premount
[ "$quiet" != "y" ] && log_begin_msg "Running /scripts/init-premount"
run_scripts /scripts/init-premount
[ "$quiet" != "y" ] && log_end_msg
maybe_break mount
log_begin_msg "Mounting root file system..."
. /scripts/${BOOT}
parse_numeric ${ROOT}
mountroot
log_end_msg
maybe_break bottom
[ "$quiet" != "y" ] && log_begin_msg "Running /scripts/init-bottom"
run_scripts /scripts/init-bottom
[ "$quiet" != "y" ] && log_end_msg
# Move virtual filesystems over to the real filesystem
mount -n -o move /sys ${rootmnt}/sys
mount -n -o move /proc ${rootmnt}/proc
若 NORESUME 变量不为空,则将 RESUME 变量的值赋给 resume 变量,并把 resume 变量 export 出去,使得在子 shell 环境中也可以使用 resume 变量。resume 变量将在 /scripts/local-premount/resume 脚本中使用。
run_scripts 函数(/scripts/functions 中定义)的唯一一个参数是一个目录,这个目录中所有的具有可执行权限的文件都将被执行。并且,在这些文件被执行之前,它们所要求的必须在它们之前执行的文件会被执行。这就要求这些可执行文件必须按照某一个规则编写,以便我们可以得到它们的先决条件,这就是:这些文件必须能够处理 prereqs 参数。有了这个条件,我们就可以带这个参数调用相应的文件,然后从 stdout 里得到必须在它们之前执行的命令。在 /scripts/init-top 目录当中只存在 framebuffer 这样一个脚本文件,它负责解析 splash*, vga=* 和 video=* 这样的参数,并依照处理结果加载适当的内核模块,设置适当的显示模式。另外,/dev/tty{0..8} 这8个tty也在这里被创建。其中,/dev/tty0 代表当前终端(它代表的终端和X下的终端是不同的)。
下面的是对本脚本上一篇写的break 的说明。log_begin_msg 函数(/scripts/functions 中定义)功能很简单,就是将其所有的参数输出。load_modules 函数(/scripts/functions 中定义)从 /conf/modules 中读取要加载的内核模块,在模块不存在时并不显示错误信息。在 /conf/modules 中只有 unix 模块,它提供对 Unix domain sockets 的支持。许多程序(如 X Window, syslog 等)即使没有联网也需要这些 sockets。但是,在 /lib/modules/2.6.18-4-686 中并不存在 unix.ko 模块,所以我们可以推断,其已经被编译到了内核中。log_end_msg 函数(/scripts/functions 中定义)仅仅输出"Done."字符串,表示这一阶段已完成。
接下来这段代码从字面上理解是为接下来挂载将要使用的系统的根目录所在的分区作准备。在 /scripts/init-premount 目录下存在两个脚本:thermal 和 udev。thermal 根据上面介绍过的 DPKG_ARCH 变量决定需要加载的控制 cpu 温度传感器和风扇的内核模块。udev 以 daemon 的方式启动 udevd,接着执行 udevtrigger 触发在机器启动前已经接入系统的设备的 uevent,然后调用 udevsettle 等待,直到当前 events 都被处理完毕。之后,如果 ROOTDELAY 变量不为空,就sleep ROOTDELAY 秒以等待 usb/firewire disks 准备好。
下面是读取并执行 /scripts/${BOOT} 中的命令。由于我们前面并没有讲 nfs 作为将要使用的系统的根目录,所以我们这里假定本地启动 BOOT=local。在这个假定的前提下,便引入了 /scripts/local 文件,这个文件定义了具有本地启动行为的 mountroot 函数,调用 mountroot 就会把将要使用的系统的根目录所在的分区挂载到 ${rootmnt}。下面我们来看一下 /scripts/local 中定义的 mountroot 函数是如何工作的。 首先,它通过 run_scripts 函数,执行 /scripts/local-top 目录下所有具有可执行权限的文件。在这个目录下有3个文件:lvm,mdrun 和 udev_helper。lvm 是逻辑卷管理方面的脚本,我没有过(估计一般pc很少有人会用),而且其中调用的具有可执行权限的文件在此 initrd.img 中也不存在。因为这个脚本在运行的时候会先检查需要的文件是否存在,若不存在则退出,所以这个脚本相当于什么也没做。略过。mdrun 是 raid 方面的脚本。它要求 udev_helper 先被执行,其中用到的具有可执行权限的文件在此 initrd.img 中不存在。这等效于这个脚本不起作用。udev_helper 脚本 mdrun 的先决条件,根据实际情况 ide-generic 模块可能会被加载。 在这三个脚本执行过之后,mountroot 函数会查看 ROOT 设备节点是否已经存在,如果不存在将等待 ${ROOTDELAY} 秒。若在这段时间内 ROOT 设备节点没有出现则调用 panic 函数,重启机器或是生一个交互 shell。 若 ROOT 设备节点已经存在,则查看 ROOTFSTYPE 变量是否为空。若不空,则 FSTYPE 变量的值就是 ${ROOTFSTYPE};否则通过 eval 调用 fstype 命令得到 ROOT 的分区格式。其中,fstype 命令会输出 FSTYPE=blabla 类型的字符串,它跟在 eval 后面就相当于作了 FSTYPE=blabla 这样的赋值操作。如果经过这一步之后 ROOTFSTYPE 的值是 "unknown"(包括通过在 kernel 后添加 rootfstype=unknown 参数和 fstype 输出的 FSTYPE=unknown),则 mountroot 函数调用 /lib/udev/vol_id 得到分区的格式。此时,FSTYPE 的值仍有可能是 "unknown"。如果是这样的话,在最后的 mount 操作就会失败。或许你会觉得这里要判断分区格式是不是很麻烦。是的,确实如此。但是要知道这里的 mount 不会自己判断分区格式,所以要在参数中指定。在得到了 FSTYPE 之后,mountroot 函数调用 run_scripts 函数运行 /scripts/local-premount 下面具有可执行权限的文件。在 /scripts/local-premount 目录中只有一个具有可执行权限的脚本 resume。此脚本负责在计算机休眠后恢复休眠前的状态。若 resume 变量为空或者这个变量所指的设备不存在,则直接退出;否则,运行 /bin/resume 恢复状态。FIXME: 如果安装了 uswsusp 包,在 /scripts/local-premount 目录下会多一个 uswsusp 脚本,它会调用 /sbin/resume 关于这两个脚本的关系目前不是很清楚。在这之后,mountroot 函数根据变量 readonly 确定是以只读还是读写的方式挂载,根据 FSTYPE 变量加载适当得内核模块。在得到了所有必要的参数之后,通过 mount 命令将将要进入的系统的根目录所在的分区挂载到 ${rootmnt} 目录下。 最后,mountroot 函数通过 run_scripts 函数执行 /scripts/local-bottom 下具有可执行权限的文件。由于在此目录下没有文件,所以这一步什么都没有做。 parse_numeric 函数( /scripts/functions 中定义)从它的注释中可以看出,这个是为了和 lilo 兼容而存在的。
在 /scripts/init-bottom 目录下只有一个具有可执行权限的脚本文件 udev。在这个脚本当中,首先停止 udevd 进程,然后删除 /dev/.udev/queue/ 目录。接下来读取并执行 /etc/udev/udev.conf 文件。在这之后,判断 no_static_dev 变量是否为空。若是,则建立 /dev/.static/ 及 /dev/.static/dev/ 目录,并把 ${rootmnt}/dev 目录通过 mount 命令 bind 到 /dev/.static/dev 目录。从一上行为很容易理解 .static/dev 目录目录的含义,它就是用来放硬盘上的 ${rootmnt}/dev 当中东西的地方。因为不是动态建立的,所以放在 /dev/.static 目录下。之后,把 /dev 目录 move 到 ${rootmnt}/dev 目录。通过以上操作就把磁盘上 /dev 目录中的内容和在此脚本动态运行过程中建立的 /dev 目录中的内容整合了起来,一起放到了 ${rootmnt}/dev 目录下。 因为此时 /dev 目录中已经没有东西了,所以现在删除这个目录,然后做一个叫 /dev 的软链接指向 ${rootmnt}/dev 目录。因为现在的根目录在 tmpfs 文件系统中,而 ${rootmnt}/dev 目录在磁盘上的文件系统中(如 ext2, reiserfs 等),不是同一个文件系统,所以做硬链接是不可能的,我们只能做一个软链接。
最后这段代码把当前的 /sys 和 /proc 移动到 ${rootmnt}/sys 和 ${rootmnt}/proc 下面。不要忘了,${rootmnt} 才是我们最终要使用的系统的根目录所在的地方。
以上内容是参考一些资料写的,如果有错误的地方,请大家指教。