热度 12 |
# Check init bootarg
if [ -n "${init}" ] && [ ! -x "${rootmnt}${init}" ]; then
echo "Target filesystem doesn't have ${init}."
init=
fi
# Search for valid init
if [ -z "${init}" ] ; then
for init in /sbin/init /etc/init /bin/init /bin/sh; do
if [ ! -x "${rootmnt}${init}" ]; then
continue
fi
break
done
fi
# No init on rootmount
if [ ! -x "${rootmnt}${init}" ]; then
panic "No init found. Try passing init= bootarg."
fi
# Confuses /etc/init.d/rc
if [ -n ${debug} ]; then
unset debug
fi
# Chain to real filesystem
maybe_break init
exec run-init ${rootmnt} ${init} "$@" <${rootmnt}/dev/console >${rootmnt}/dev/console 2>&1
panic "Could not execute run-init."
这段代码检查 ${rootmnt}${init} 是否存在,也就是下面我们把根目录切换到 ${rootmnt} 下时要执行的 ${init},在上面 init 变量已经被赋值 "/sbin/init"。如果不存在,则通过 panic 函数生一个交互的 shell,或重启机器。这取决于 panic 变量。接下来就是找到加载失败的init.
最后来看最后一段代码,之前把系统的启动交给了将要进入的系统的 ${init} (上面初始化为 "/sbin/init"),并用 /dev/console 作为输入与输出的设备。那么这个 run-init (/bin/run-init) 究竟作了些什么。我们得到 klibc-utils 源码包并解开之后,run-init 的源码在 klibc-1.4.34/usr/kinit/run-init 目录下。run_init(realroot, console, init, initargs) (runinitlib.c 中定义)函数的调用。坐在这个函数中首先通过 chdir 调用将目录切换到了 realroot。因为此时还没有改变根目录,所以 / 和 . 应该不是同一个目录。然后确认 / 和 . 不在同一个文件系统上(注意,同样的分区格式,不同的分区,也是不同的文件系统)。接下来确定存在 /init 文件,并且当前的根目录所在的文件系统类型是 ramfs 或 tmpfs。在这一切都确定之后,通过 nuke_dir("/") (runinitlib.c 中定义)调用删除当前根目录下除挂载点以外的内容,以释放它们所占用的内存。紧接着把当前目录,也就是 realroot 通过 mount 调用移动到根目录,并通过 chroot 函数将根目录设为当前目录,再通过一个 chdir("/") 调用改变当前工作目录为根目录。现在,我们剩下的只让 /sbin/init 跑起来。但在开始之前要得到 0, 1, 2 三个文件描述符,用来做我们的 stdin, stdout 和 stderr。在得到这些之后就通过 execv(init, initargs) 调用让我们的 /sbin/init 跑起来了。
小结 init 脚本究竟都作了什么呢?首先,建立一些必要的文件夹作为程序工作的时候需要的目录或者必要的挂载点,以及必需的设备节点。然后,根据提供的参数建立适当的设备节点并加载适当的内核模块,启动适当的进程(udevd)帮助我们完成这一步骤。