Linux 的启动流程目前比较流行的方式主要是以下步骤:
1、引导器(例如 GRUB)启动;
2、内核启动;
3、系统进程启动与配置。
本文以 GRUB 为研究对象,对 GRUB 启动与内核启动两个部分进行描述,关于系统进程的进一步启动与配置将用另一篇文章来说明。
常见的目录结构 (以 CentOS 5.3 为例):
/boot
|-- System.map-2.6.18-128.el5
|-- System.map-2.6.18-128.el5xen
|-- config-2.6.18-128.el5
|-- config-2.6.18-128.el5xen
|-- initrd-2.6.18-128.el5.img
|-- initrd-2.6.18-128.el5xen.img
|-- lost+found
|-- memtest86+-1.65
|-- message
|-- symvers-2.6.18-128.el5.gz
|-- symvers-2.6.18-128.el5xen.gz
|-- vmlinuz-2.6.18-128.el5
|-- vmlinuz-2.6.18-128.el5xen
|-- xen-syms-2.6.18-128.el5
|-- xen.gz-2.6.18-128.el5
`-- grub
|-- device.map
|-- e2fs_stage1_5
|-- fat_stage1_5
|-- ffs_stage1_5
|-- grub.conf
|-- iso9660_stage1_5
|-- jfs_stage1_5
|-- menu.lst -> ./grub.conf
|-
- minix_stage1_5
|-- reiserfs_stage1_5
|-- splash.xpm.gz
|-- stage1
|-- stage2
|-- ufs2_stage1_5
|-- vstafs_stage1_5
`-- xfs_stage1_5
图一: CentOS 5.3 的 /boot 目录
目录分作两大部分,一个是 /boot 目录下除 grub 目录以外的所有文件,这些是 Linux 的内核以及内核启动相关的一些文件;另一个就是 grub 下的所有文件, GRUB 引导器启动所需要的所有文件都在 grub 目录下。
核心文件组成:
/boot
|-- System.map-2.6.18-128.el5
|-- initrd-2.6.18-128.el5.img
|-- vmlinuz-2.6.18-128.el5
`-- grub
|-- grub.conf
|-- menu.lst -> ./grub.conf
|-- stage1
|-- stage2
图二: 核心文件说明
图二是一个简化了的目录结构,在大多数Linux实现中,这些是 Linux 启动最核心的几个文件,下面我们对这个结构中的文件加以说明。
内核文件说明:
Linux 内核启动相关文件:
vmlinuz --- Linux 内核
这个就是 Linux 可引导的、压缩的内核文件。
此文件通常后面带有内核版本号,但不是必需的,但有利于系统中存在多个不同的内核时加以区分。
vmlinuz 通常在 2.4 版内核时有两种方法建立,一个是通过编译时使用 make zimage 命令,这个方法是老的 2.4 版内核所支持的方式,用于生成较小的内核(512K 以下), 同时,在 2.4 版内核编译时使用 make bzimage 则可以生成一个大内核,其中 bz 的意思是 big zimage。 二者的主要区别在于小内核(zimage 形势)可以很小(4K),放在磁盘的前八个引导扇区,在装载时,先装载到
0x1000:0000 高端内存,然后再移动到 0x0200:0000 位置的低端内存,然后启动CPU,所以可以不需要单独的引导器; 而 bzimage 形式较大,在 512K 以上
,只能加载到
0x1000:0000 高端内存,然后启动CPU,需要有独立的引导器进行引导。
vmlinuz 在 2.6 版内核中并不直接支持 zimage 小内核方式,2.6版内核的编译过程不需要 make zimage 或 make bzimage 这一步 。
vmlinuz 虽然采用了 gzip 进行压缩 , 但由于其头部中添加了精简过的代码,所以不能直接用 gunzip 直接解压,我们通过观察一般的 gzip缩文件,发现有一个特征码 "1f8b" 。于是分析 vmlinuz 这个文件:
# xxd vmlinuz-2.6.18-128.el5 |egrep "/b1f8b" |head -n5
0002080: 1b00
1f8b
0800 e642 7749 0203 ec3b 6d78 .......BwI...;mx
00315f0: ebe4 177f 065d 6e19 434a 4a5a 1f8b 111e .....]n.CJJZ....
0058390: d1ad 1e11 0683 9f1f a857 1f8b a542 cb27 .........W...B.'
0073cb0: 9b51 e43c 482b 3685 1f8b 05f5 32fd b758 .Q.
0086a50: ed06 0bb5 1f8b 6d67 930c d7d0 2c6c 6c18 ......mg....,ll.
发现该特征码在 0x2082 处,由以下命令可以摘除这个部分:
# dd if=
vmlinuz-2.6.18-128.el5 of=vmlinuztest.gz bs=1 skip=$((0x2082))
然后解压 vmlinuz
test.gz 即可得到二进制的内核。
值得一提的是,2.4 以后版本的内核,不再提供无需引导器引导的机制
。
initrd --- initialized ram disk 初始化 RAM 磁盘
启动器(boot loader 即本文的 GRUB)会在内核启动前把 initrd 装入内存,该文件的作用是生成一个 RAM 磁盘,并在其上形成根文件系统,内核启动时会在访问真实的磁盘根文件系统前访问这个 RAM 磁盘中的根文件系统。2.6 版内核中,当内核执行完 initrd 中的内容后,对于 cpio 类型的 initrd ,会由 initrd 负责执行switchroot 来切换根文析系统到真实的文件系统中去,并开始真正的 init 进程;对于 image-initrd 这个类型来说,内核执行完 initrd 这个阶段以后,会返回到内核,继续内核初始化,然后由内核去调用真实文件系统中的 init 。本文的主要内容是介绍 Linux 2.6 版引导的过程,对于initrd 的更多细节不再赘述,有兴趣的读者,可以查阅
相关的链接
。 关于 initrd 的动手实验,则可以在
本站中找到
。
此文件通常后面带有内核版本号,但不是必需的,但有利于系统中存在多个不同的内核时加以区分。
关于 initrd 的另一个重点,是为什么要使用它,initrd 最重要的作用在于使引导过程更加灵活。为了在各种硬件平台上启动,将所有的硬件驱动都放到内核中显然不现实,initrd 的作用之一就是加载硬件驱动模块,从而可以在内核中只包含最基本的硬件驱动即可,将加载不同硬件驱动的任务交给 initrd ;关于initrd 的另一个作用是支持 usb 启动,由于 usb 从驱动加载到真正可用的过程较慢,可能需要几秒钟的时间比较慢,在内核访问usb时,USB 设备可能还没初始化完成,将该过程放入到 initrd 中可以进行延时,完成正确加载和引导。
在某些情况下,我们可以使用 noinitrd 参数,使启动过程不使用 initrd 文件是可能的。
System.map --- 内核符号映射表
在弄清楚 System.map 的作用以前,首先要先了解两个名词,其中一个叫做 symbol(符号),另一个叫做 Oops 。
Symbol : 符号,学过程序设计的话应该知道,一个符号是一个程序的创建块,它是一个变量名或一个函数名。 这里为什么要提到这个名词呢? 因为 Linux 内核并不使用符号来调用函数,而是直接使用函数的地址(指针), 这似乎造成了一个矛盾,因为编程的人并不喜欢使用地址的方式,于是符号表产生了,它允许在编程的过程中,使用符号,但是在编译时使用地址,”符号映射表 “由此产生了,它就是 System.map .
用 more 命令查看 System.map 文件可以看到类似下面的段:
64位的系统:
ffffffff81000000 A _text
ffffffff81000000 T startup_64
ffffffff810000b7 t ident_complete
ffffffff81000100 T secondary_startup_64
32位的系统:
c0100000 A _text
c0100000 T startup_32
c01000c6 t checkCPUtype
c0100147 t is486
Oops : 当内核引用了一个无效指针时,通常被称为 Oops ,说明内核存在一个Bug。 内核在出现此错误时,会由 klogd 这个服务将此错误记载到日志中,如果该日志指出一个地址错误,显然还需要我们花些时间来找该地址对应的符号,klogd 通过检视 System.map 直接将符号取出,并记载该符号引发了一个 Oops 。
通过以上的描述,我们得到一个结论:System.map 是一个静态的内核符号映射表!
既然说到静态,那么就说明如果是在运行期间动态加载的某些模块,可能不在其中,如何得到它们呢? 系统中的 proc 文件系统中存在一个动态的映射表, 在 2.6 Kernel 中通常是 /proc/kallsyms 。
另外,类似 lsof 和 ps 等命令,是需要这个映射表的存在的,但既使没有这个文件,系统的启动依然可以进行。
GRUB 文件说明:
stage1 --- 磁盘引导第一阶段
当 BIOS 加电自检完成以后,假设系统是从硬盘启动,则 BIOS 的最后一件事情就是读取该硬盘的 0 道 0 面 1 扇区,即我们常用说的 MBR ,它只有 512 个字节大小,它与 stage1 具有什么样的关系呢? 我们先来看一下关于 MBR 的构成:
图三:MBR 的组成(摘自 IBM 官方网站)
MBR 共由三个部分组成:1、Bootloader 就是引导代码,其作用主要是加载第二阶段启动(即stage2),2、Partition table 分区表, 3、Magic Number 魔数,就是那个 55AA 标志,用以检验该 MBR 的有效性。
我们可以用如下命令导出该扇区到文件(假设该硬盘为 hda):
dd if=/dev/hda of=mbr.bin bs=512 count=1
然后我们用
xxd mbr.bin
xxd /boot/grub/stage1
仔细对比发现,该扇区的 Bootloader 部分与 MagicNumber部分与 stage1 文件完全一样,原来,在使用 grub 的安装命令进行引导器安装时,grub 会用 stage1 文件的前 446 字节覆盖 MBR的前446 字节。在这里我们得到另一个启示,那就是不能简单的认为 MBR 等价于 stage1 ,为了保护 MBR ,我们还是应该对 MBR 做备份。
stage1_5 --- 关于文件系统格式的“魔术师”
应该注意到,所有带有 stage1_5 字样的文件,全部都和文件系统名字有关:
|-- e2fs_stage1_5
|-- fat_stage1_5
|-- ffs_stage1_5
|-- iso9660_stage1_5
|-- jfs_stage1_5
|-
- minix_stage1_5
|-- reiserfs_stage1_5
|-- ufs2_stage1_5
|-- vstafs_stage1_5
`-- xfs_stage1_5
顾名思义,其实 stage1_5d 确实是在 stage1 和 stage2 之间运行的,它包括了一些常见的文件系统的识别能力,这些文件的存在,意味着 grub 可以从多种文件系统中读取并加载 Linux 内核。
这个特性使得 grub 更加灵活的处理不同的文件系统格式。
虽然有
e2fs_stage1_5 这个文件的存在,但经过实验,我们发现在默认情况下编译的内核如果装载在 ext3 文件系统上,是不需要 stage1_5这个过程的。
stage2 --- 真正的 grub 就在这里
注意到 stage2 的文件尺寸,超过了 512 字节的大小,所以 grub 本身并不能放到 mbr 中去,mbr 那446字节的作用,主要就是找到这个 stage2,它读取 menu.list 文件显示系统引导菜单,识别不同的引导指令,并完成 vmlinuz 和 initrd 的加载,其实,这个就是真正的 grub 了!
menu.list 文件就是 grub 的引导菜单, grub 会执行菜单里的命令,完成引导,因为红帽这个系列的系统进行了一些特别的设置,所以存在一个叫做 grub.conf 的软链接,指向 menu.list 。
结论:
启动中最重要的东西:mbr/stage1 、grub/stage2 、kernel/vmlinuz ,三者是最最核心的。stage1_5 的作用是使 grub 有处理更多文件系统的灵活能力,而 initrd 使内核启动更加灵活,在不同的平台上,对不同的硬件,使用不同的驱动,从而减小静态内核的尺寸。