initrd 中init 脚本的分析

initrd  中init 脚本的分析    
 
        由前面 cpio-initrd的处理流程可以看到,内核在将其解开并放入 rootfs 后,将要执 行 /init文件,所以我们分析的重点就是这个文件。其它的文件请结合具体的源码与本文的内容进行理解。   

#!/bin/sh
该行说明该init文件是一个由sh解释并执行的脚本文件,内核通过文件头来确定应该怎样执行(即是直接执行还是调用哪个程序执行该文件)。

[ -d /dev ] || mkdir -m 0755 /dev
[ -d /root ] || mkdir -m 0700 /root      #这是硬盘上的根分区预先挂载到的目录
[ -d /sys ] || mkdir /sys
[ -d /proc ] || mkdir /proc
[ -d /tmp ] || mkdir /tmp
mkdir -p /var/lock
mount -t sysfs -o nodev,noexec,nosuid none/sys      #udev会参考的vfs,udev会根据其中的信息加载modules和创建设备文件
mount -t proc -o nodev,noexec,nosuid none/proc       

这一部分很简单,建立了相关目录和挂载点,并将kernel运行过程中产生的信息挂载到/sys和/proc目录下。注意/sys目录是udev会参考的vfs,udev会根据其中的信息加载modules和创建设备文件,当不使用udev机制(我后面会讲)时/sys目录可以不建立。/proc目录和相应的proc文件系统必须建立和挂载,因为脚本会参考其中的/proc/cmdline文件获得kernel命令行上的参数。

grep -q '\<quiet\>' /proc/cmdline ||echo "Loading, please wait..."

如果在GRUB的kernel行上有quiet关键字,则在kernel启动和initrd中init脚本执行的过程中不会在屏幕上显示相关信息而是一个闪烁的下划线,否则将显示"Loading,please wait..."

# Note that this only becomes /dev on the real filesystem if udev'sscripts
# are used; which they will be, but it's worth pointingout
if ! mount -t devtmpfs -o mode=0755 none /dev;then    #其实then的代码一般不会执行
        mount -t tmpfs -o mode=0755 none/dev
        mknod -m 0600 /dev/console c 51
        mknod /dev/null c 1 3
fi

这一部分在/dev目录下建立devtmpfs文件系统,devtmpfs是一个虚拟的文件系统被挂载后会自动在/dev目录生成很多常见的设备节点文件,当挂载devtmpfs失败时会手动建立/dev/console和/dev/null设备节点文件。/dev/console总是代表当前终端,用于输出kernel启动时的输出内容,在最后通 过 exec 命令用指定程序替换当前 shell时使用。/dev/null 也是很常用的,凡是重定向到它的数据都将消失得无影无踪。

mkdir /dev/pts #主要用于nfs等启动时使用,对于本地/dev/pts不使用,故下面一段代码可忽略
mount -t devpts -o noexec,nosuid,gid=5,mode=0620 none /dev/pts ||true
> /dev/.initramfs-tools
mkdir /dev/.initramfs
usplash 会使用/dev/.initramfs 目录。usplash 会在机器启动的时候提供类似windows的启动画面,ubuntu linux 的启动画面就是通过 usplash 实现的。由于 在 /sbin 目录当中没有任何usplash 相关的文件,所以我们可以忽略这个目录的存 在。

# Export the dpkg architecture
export DPKG_ARCH=
. /conf/arch.conf
DPKG_ARCH    表明了当前运行linux的计算机的类型,对一般的pc是大多i386,也可能是别的比如 powerpc 一类的。用export是为了让这个变量不仅在此 shell环境中有效,而且在它的子shell环境中仍然有效。而且在第27行 export DPKG_ARCH 变量的时候,让 DPKG_ARCH变量等于空。这样,当前运行的计算机 的类型就完全由 /conf/arch.conf 决定了
# Set modprobe env
export MODPROBE_OPTIONS="-qb"
设置modprobe默认的选项,-b表示用use-blacklist(主要是系统的硬件有多个modules支持,选择使用哪一个,其他的加入到黑名单中以防冲突),-q表示quiet

# Export relevant variables
export ROOT=              #从kernel中提取的realfs所在的设备
export ROOTDELAY=   
export ROOTFLAGS=
export ROOTFSTYPE=
export IPOPTS=
export HWADDR=
export break=
export init=/sbin/init        #realfs中的第一个执行的程序位置
export quiet=n
export readonly=y
export rootmnt=/root                #realfs在rootfs中的临时挂载点
export debug=                          #将本脚本的输出定向到一个文件,以便启动系统后分析
export panic=
export blacklist=                    #设置modules的黑名单
export resume_offset=

这一部分主要是输出一些变量到环境中
ROOT              保存GRUB的kernel命令行上的root参数即硬盘上根分区所对应的设备节点文件
ROOTDELAY    指定将要进入的系统的根目录所在的分区必 须在多少秒之内准备好
ROOTFLAGS    指定将要进入的系统的根目录所在的分区挂 载到 ${rootmnt}目录时的参数
ROOTFSTYPE    指定根分区所在的文件系统类型
IPOPTS            当kernel为nfs时指定的服务器IP
HWADDR            当kernel为nfs时指定的服务器MAC
break        由 maybe_break 函数使用。若break    的值同 maybe_break 的第一个参数相同,则 maybe_break函数调 用 panic 函数(注意 panic 函数和 panic 变量是不同 的)。 若 panic 变量为"0"(此处是字符串,其内容是"0",不是整数), 则 panic 函数将重新启动机器。其他情况下(包括 panic变量为空的情况)都将以交互的方式调出 shell,此shell的输入输出使用已经创建好的节点/dev/console。
init    指定硬盘realfs中的第一个执行的程序位置,此变量指定在这个脚本最后要执行的进程。 此处 /sbin/init是系统上所有进程的父进程,负责开启其它进程。当然,你也可以把它换成其他的程序,甚至是 ls,不一定非要是/sbin/init,虽然这样你的系统启动之后什么都不能做。
quiet=n    指定为非"y",会显示一些启动的状态信息;若指定为"y"则不显示这些信息。   
readonly=y    如果 readonly 等于字符串"y",则以只读方式挂载最终要进入的系统的根目录所在分区到 ${rootmnt} 目录,其他情况(包括 readonly 为空)以读写方式挂载。   
rootmnt=/root                指定硬盘上的realfs在rootfs中的临时挂载点
debug                        #将本脚本的输出定向到一个文件,以便启动系统后分析
panic                      描述见break参数的说明。
blacklist                #设置modules的黑名单
resume_offset        一般用不到


# Bring in the main config
. /conf/initramfs.conf        #最重要的是BOOT参数,定义了是本地启动还是nfs启动
for conf in conf/conf.d/*; do    #对于不支持kernel命令行选项的bootloader很有用
        [ -f ${conf} ]&& . ${conf}
done
. /scripts/functions        #这个脚本文件很重要,在其中定义了很多以后要用到的工具函数

在当前shell中引入主配置文件 /conf/initramfs.conf。 这个配置文件实际上是mkinitramfs(8)    的配置文件,其中定义了一些变量,并赋予了适当的值,如 BOOT=local则默认从本地磁盘启动(可以是可移动磁盘)。 BOOT 变量的值实际上是 /scripts 目录下的一个文件,可以是 local 或 是nfs。在此 init 脚本挂载将要进入的系统的根目录所在分区的时候,会先读取并运 行 /scripts/${BOOT} 文件。在这个文件中定义了 mountroot 函数,对于 local 启动和 nfs 启动此函数的实现不同。这样通过对不同情况引入不同的文件,来达到同样名称的函数行为不同的目的。这就导致了具体挂载的行为和启动方式相关。   

引入 /conf/conf.d 下的所有文件,注意在引入的时候用了 -f 参数判断,这样只有普通的文件才会被引入,链接(硬链接除外)、目录之类的非普通文件不会被引入,当使用不支持命令行参数的开机引导
程序时,可以在该目录下建立各种参数设置文件。(Uubunt使用的GRUB支持kernel命令行参数,所以这个目录下就上面提到的两个文件initramfs.conf和arch.conf)



按照/scripts/init-top/ORDER文件的配置依次执行其下的脚本文件,这里最重要的是开启了udevdaemon
udev 以 daemon 的方式启动 udevd,接着执行 udevtrigger 触发在机器启动前已 经接入系统的设备的uevent,然后调用 udevsettle 等待,直到当 前 events 都被处理完毕。之后,如果 ROOTDELAY变量不为空,就 sleep ROOTDELAY 秒以等待 usb/firewire disks 准备好。   

maybe_break modules       
log_begin_msg "Loading essential drivers..."
load_modules
log_end_msg

load_modules按照/conf/modules文件的配置加载重要的module,由于使用了udev机制这一步其实是多余的,实际上/conf/modules文件并不存在。
maybe_break premount
[ "$quiet" != "y" ] &&log_begin_msg "Running /scripts/init-premount"
run_scripts /scripts/init-premount
[ "$quiet" != "y" ] &&log_end_msg
这段代码从字面上理解是为接下来挂载将要使用的系统的根目录所在的分区作准备,/scripts/init-premount实际并不存在,所以这一步其实啥都没干。

maybe_break mount                                #这一步主要是执行/scripts/local中的mountroot函数,将realfs挂载到${rootmnt},在mountroot函数中最重要的是检测realfs的fytype
log_begin_msg "Mounting root file system..."
. /scripts/${BOOT}
parse_numeric ${ROOT}                    #这一步主要用于解析lilo启动管理程序传递的参数,对于GRUB用不到。
mountroot
log_end_msg
这一部分的功能主要是把硬盘上的realfs挂载到${rootmnt}特别重要,大部分的系统启动问题都是这一部分存在问题引起的。其中很大一部分的原因是硬盘控制器的module
没有被udev或上面的load_modules加载,导致kernel不能读取硬盘中的数据。

下面我们来看一下 /scripts/local 中定义的 mountroot 函数是如何工作的。   
        首先,它通 过 run_scripts 函数执行/scripts/local-top目录下所有具有可执行权限的文件。在这个目录下有3个文件:lvm,mdrun    和udev_helper。   
        lvm    是逻辑卷管理方面的脚本,我没有过(估计一般pc很少有人会用),而且其中调用的具有可执行权限的文件在此initrd.img    中也不存在。因为这个脚本在运行的时候会先检查需要的文件是否存在,若不存在则退出,所以这个脚本相当于什么也没做。略过。   
        mdrun    是 raid 方面的脚本。它要求 udev_helper先被执行(见第136行代码的说明)其中用到的具有可执行权限的文件在 此 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恢复状态。
          在这之后,mountroot 函数根据变量readonly 确定是以只读还是读写的方式挂载,根 据 FSTYPE 变量加载适当得内核模块。在得到了所有必要的参数之后,通过mount 命令将将要进入的 系统的根目录所在的分区挂载到 ${rootmnt}目录下。   
          最后,mountroot 函数通过run_scripts 函数执行 /scripts/local- bottom下具有可执行权限的文件。由于在此目录下没有文件,所以这一步什么都没有做。   
            parse_numeric 函数 ( /scripts/functions中定义)从它的注释中可以看出,这个是为了和 lilo 兼容而存 在的。由于现在一般用 grub 作为bootloader,我们平常写的 root=/dev /hdxx,root=LABEL=xx...xx 或root=UUID=x...x-...-xxx 的形式都会造 成此函数的直接返回,相当于什么都没有做。由于我没有用过lilo,所以对于下面 lilo 的处理,我也不好说什 么。   

小 结   
        好了,上面我已经说了这么 多。那么,init脚本究竟都作了什么呢?   
          首先,建立一些必要的文件夹作为程序工作的时候需要的目录或者必要的挂载点,以及必需的设备节点。   
          然后,根据提供的参数建立适当的设备节点并加载适当的内核模块,启动适当的进程(udevd)帮助我们完成这一步骤。当没有使用udev机制时应在/conf/modules中指明要加载的驱动。同时要自己建好相关的设备节点。
          最后,在做完了这
  • 1
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值