Linux 启动过程(二)

vmlinuz

Boot loader 根据 grub.cfg 中的配置选择对应的内核 vmlinuz 引导入内存。内核驻留于操作系统与应用程序的整个活动周期,应用程序在用户空间内运行,处于内核控制之下。

Linux 内核2.6开始,内核的编译需要两层 Makefile。简而言之,顶层 Makefile 生成未压缩的原始内核 vmlinuxvmlinx 体积较大且不能被 boot loader 加载。第二层 Makefile 生成 bzImage 格式的内核 vmlinuzvmlinuz 是可引导的、压缩的内核,虽然它里面包含了压缩的 vmlinux,但不能通过 gzipgunzip 解压,可以通过 boot loader 引导和解压。vmlinuz 文件的开头部分内嵌有 gzip 解压缩代码,可以用比较 tricky 的方法来解压:
od -t x1 -A d vmlinuz-3.16.0-31-generic | grep "1f 8b 08"
输出为:
0018352 ac fe ff ff 1f 8b 08 00 00 00 00 00 02 03 ec fd
将被压缩的内核解压,得到 vmlinuz 文件
dd if=vmlinuz-3.16.0-31-generic bs=1 skip=18356 | zcat > vmlinuz
通过 file vmlinuz 命令查看解压后得到的 vmlinuz 文件类型,输出为:

vmlinuz: ELF 64-bit LSB  executable, x86-64, version 1 (SYSV), statically linked, BuildID[sha1]=ebcab26a6ba64692d07728ce1ebe07aa3dc88a5f, stripped  

ELF 64-bit LSB executable 格式不能直接查看里面内容,可以用 strings 命令查看里面的一些信息,如 strings vmlinuz | grep "Linux version"

Linux version 3.16.0-31-generic (buildd@toyol) (gcc version 4.8.2 (Ubuntu 4.8.2-19ubuntu1) ) #43~14.04.1-Ubuntu SMP Tue Mar 10 20:13:38 UTC 2015 (Ubuntu 3.16.0-31.43~14.04.1-generic 3.16.7-ckt5)  
  • vmlinuz 中的 vmVirtual Memory,得名于 Linux 支持使用硬盘空间作为虚拟内存。
  • 1f 8b 表示 gzip 格式,08 是 gzip 选项,计算 gzip 压缩内容开始处的 offset 为:18352 + 4 = 18356。


Linux 内核本身是一个单内核结构,所有内容都集成在一起,这种结构效率高但是可扩展性和可维护性较差,模块机制就是为了弥补这一缺陷而设计的。模块是具有独立功能的程序,可以被单独编译,但它没有单独的 main 函数,只有初始化函数,所以不能单独运行。模块可以在编译内核时一起编译到内核中,也可以在运行时动态地加载到内核中并作为内核的一部分在内核空间中运行,与运行在用户空间内的进程是不同的。 由于核心模块存在于 /lib/modules 目录内,所以在开机过程中内核必须要挂载根目录才能载入模块(根目录与 /lib 需在同一分区),而挂载根目录又可能需要一些内核模块的支持。

假设我们根目录是在 SCSI 硬盘上,而内核 vmlinuz 中并没有 SCSI 的硬件驱动,那么在载入 SCSI 模块之前,内核不能挂载根目录,而 SCSI 模块存储在根文件系统的 /lib/modules 下。为了解决这种鸡生蛋蛋生鸡的死锁问题,我们就需要 initrd 的帮助。


initrd

initrd (initial RAM disk) 是在系统引导过程中的一个临时根文件系统,其中包含了各种可执行程序和挂载真实根目录所需要的驱动程序,可以用来挂载实际的根文件系统。内核2.6以后的 initrd 其实都是 initramfs (initial RAM file system) 了,只是沿袭传统还是使用 initrd 的名字。

initramfs 是用 gzip 压缩过的 cpio 格式的打包文件,无须挂载就可以展开成一个文件系统,不需要额外的文件系统支持。当内核启动时,会从这个打包文件中导出文件到内核的 rootfs,然后执行 init 脚本。这个 init 进程负责启动系统后续的工作,包括定位、挂载真正的根文件系统。

  • Linux 内核2.6开始都有一个特殊的文件系统 rootfs,是内核启动的初始始根文件系统,initramfs 的文件会复制到 rootfs。rootfs 是 ramfs 的一个特殊实例。ramfs是一种非常简单的基于内存的文件系统。没有容量大小的限制,可以根据需要动态增加容量。它直接利用了内核的磁盘高速缓存机制,所有的文件读写数据都会在内存中做高速缓存。高速缓存中的写入数据会在适当的时候回写到对应的文件系统设备中(比如磁盘),这时缓存数据的状态就标识为 clean,系统在必要时可以释放掉这些内存。ramfs 没有对应的文件系统设备,所以它的数据永远都不会回写,也就不会被标识为 clean,因此系统也永远不会释放 ramfs 所占用的内存。由于 ramfs 直接使用了内核已有的磁盘高速缓存机制,所以 ramfs 特性不能通过内核配置参数来删除,它是内核的天然特性。
  • 如果有个新的硬件操作系统不支持,该怎么办?重新编译内核,并将最新的硬件驱动程序写入内核;将该硬件的驱动程序编译成模块,通过 initrd 加载该模块。
  • 需要 initrd 的主要是为了解决在开机时无法挂载根目录,如果我们将所有需要的核心模块都编译进内核,理论上就可以不需要 initrd 了。但是在一些特殊情况下 initrd 是必不可少的。挂载根目录过程中可能需要执行一些用户空间的可执行程序,比如设备被加密需要用户输入密码来解密,这时候我们就必须要 initrd 来提供用户空间程序。
  • initramfs 是一个 gzip 压缩的 cpio 文件系统打包,如果需要对 initramfs 做一些个性化的改动,将 initramfs 解压编辑后,再用 cpio 和 gzip 打包即可。


/boot/initrd.img-3.16.0-31-generic 复制到一个个人文件夹,用 zcat initrd.img-3.16.0-31-generic | cpio -id 命令将 initrd 解压到当前目录下,得到如下文件:

drwxrwxr-x 10 jimmy jimmy     4096 Apr  4 11:43 ./  
drwxr-xr-x 20 jimmy jimmy     4096 Apr  4 11:43 ../  
drwxr-xr-x  2 jimmy jimmy     4096 Mar 24 10:09 bin/  
drwxr-xr-x  3 jimmy jimmy     4096 Mar 24 10:09 conf/  
drwxr-xr-x  7 jimmy jimmy     4096 Mar 24 10:09 etc/  
-rwxr-xr-x  1 jimmy jimmy     7237 Mar 24 10:09 init*
drwxr-xr-x  7 jimmy jimmy     4096 Mar 24 10:09 lib/  
drwxr-xr-x  2 jimmy jimmy     4096 Mar 24 10:09 lib64/  
drwxr-xr-x  2 jimmy jimmy     4096 Mar 24 10:09 run/  
drwxr-xr-x  2 jimmy jimmy     4096 Mar 24 10:09 sbin/  
drwxr-xr-x  7 jimmy jimmy     4096 Mar 24 10:09 scripts/

是不是很像一个微型的操作系统?

查看 init 脚本内比较重要的运行项目

......
mount -t sysfs -o nodev,noexec,nosuid sysfs /sys #挂载 sysfs  
mount -t proc -o nodev,noexec,nosuid proc /proc #挂载 proc  
# Some things don't work properly without /etc/mtab.
ln -sf /proc/mounts /etc/mtab #建立 /proc/mounts 到 /etc/mtab 的软链接  
......
mknod -m 0600 /dev/console c 5 1 #创建系统控制台设备  
mknod /dev/null c 1 3 #创建空设备  
......
#按特定顺序运行 scripts 文件夹中的脚本文件,将真正的根目录挂载在 initramfs 目录下,即 /initramfs/root
......
# Move virtual filesystems over to the real filesystem
mount -n -o move /sys ${rootmnt}/sys #将 /sys 的 mounted tree 移动到真正的根目录下,即 /initrmfs/root/sys  
mount -n -o move /proc ${rootmnt}/proc #同上,-n 表示不写入 /etc/mtab  
......
exec run-init ${rootmnt} ${init} #将当前根目录更改为真正的根目录,即 /initramfs/root,并运行真正根目录下的 /sbin/init  
......
  • proc 是一个虚拟的文件系统,该目录中的所有文件都不会消耗磁盘空间。通过它能够非常方便地了解系统信息,许多程序实际上只是从 /proc 的文件中收集信息,然后按照它们自己的格式组织后显示出来,比如 top 和 ps。
  • sysfs 是 Linux 内核2.6开始所提供的一种虚拟的基于内存的文件系统。它的作用与 proc 有些类似,但除了与 proc 相同的具有查看和设定内核参数功能之外,还可以用来对设备和驱动程序做设置。
  • nodev 表示不解析文件系统上的块特殊设备,noexec 表示不允许执行此文件系统上的二进制文件,nosuid 表示禁止 suid 操作和设定 sgid 位,suid 和 sgid 通常用于一些特殊任务,使一般用户运行程序时临时提升权限。
  • /etc/mtab 文件记录着当前已挂载的文件系统以及它们的初始化选项
  • /dev/console 代表的是系统控制台设备,错误信息和诊断信息通常会被发送到这个设备。
  • /dev/null 是个空设备,也称为 bit bucket。所有写向这个设备的输出都将被丢弃,如果读/dev/null,则会立即得到一个文件尾标志而返回。
  • run-init 是将 chroot 的功能进行了封装,以此更加方便简单的切换根目录。


简而言之就是 kernelinitrd 中执行 init 脚本加载根文件系统所需要的驱动程序,并以读写方式挂载根文件系统。下一步的任务则是执行 /sbin/init 进程完成系统的后续初始化工作。


下一篇:Linux 启动过程(三)