该学习过程是一个Linux从主引导记录到第一个用户空间程序的指导.
启动一个Linux系统的过程包含一系列步骤.但是无论你是启动一个标准的X86桌面或嵌入式PowerPC,大部分流程是相似的.该文章探索Linux从最初的引导程序到开启第一个用户空间程序的过程.同时,我们也将会学到其他一些启动相关的课题,例如引导装载程序,内核解压缩,最初的RAM和其他Linux启动的元素.
早期,引导启动一个计算机意味着提供一个包含启动程序或使用前面板地址/数据/控制开关手动加载一个启动程序的纸带.现在的计算机装备了设备来简化启动过程,但是并没有使它变得简单.
我们先从高层视角开始看Linux启动.然后我们将会看每一步执行了哪些工作.
概览
下面的图片给了你20000英尺的视角:
当一个系统首次被起到启动,或被重置,处理器执行位于指定位置的代码.在个人计算机中,这个位置就位于基本输入/输出系统(BIOS)中,BIOS被存储在母板上的flash内存中.位于嵌入式系统上的中央处理器(CPU)调用重置向量来启动位于广为人知的地址上的程序,该程序位于flash/ROM中.在任何情况下,结果都是一样的.因为PC提供了这么多的灵活性,BIOS必须决定哪一个设备是用于启动的候选设备.后面我们将详细讲解.
当一个启动设备被发现,第一阶段boot loader被加载到RAM中然后执行.这个启动装载器长度上少于512字节(一个扇区的大小),他的工作就是去加载第二阶段的boot loader.
当第二阶段boot loader被加载到RAM中并被执行的时候,启动动画显示在屏幕上,Linux和可选的最初的RAM磁盘(临时的root文件系统)被加载到内存中.当镜像被装在,第二阶段boot loader将控制传递给内核镜像,然后内核镜像被解压并被初始化.在这个阶段,第二阶段boot loader检查系统硬件,列举相关硬件设备,挂载根设备,然后加载必要的内核模块.当完成这些工作之后,第一个用户空间程序(init)开启,高层系统初始化被运行.
这就是简明的描述了Linux的启动过程.现在我们深入探索Linux启动过程的细节.
系统开启
系统启动阶段取决于引导Linux系统上的硬件。在一个嵌入式平台上,一个引导启动环境是在系统被上电或重置之后启用的.例子包含U-Boot,RedBoot,和来自Lucent的MicroMonitor.嵌入的平台被装备一个启动监视器.这些程序位于目标硬件上的flash内存中的特定区域,并且提供下载Linux内核镜像到flash内存并且运行它的方法.除了拥有保存和启动Linux内核镜像之后,这些启动监视器运行一些系统测试和硬件初始化.在一个嵌入式的目标平台中,这些启动见识其通常覆盖第一和第二阶段的boot loader.
提取MBR
为了查看你的MBR的内容,使用下面这个命令:
# dd if=/dev/hda of=mbr.bin bs=512 count=1
# od -xa mbr.bin
dd命令,需要在root模式下运行,从/dev/hda中读取第一个512字节然后将他们写入到mbr.bin文件中.od命令以hex和ASCII格式打印二进制文件.
在PC中,启动Linux从BIOS中的地址0XFFFF0处开始.BIOS的第一步就是启动自我检测(POST).POST的工作就是运行硬件检查.BIOS的第二步就是本地设备列举和初始化.
对于给定的BIOS功能的不同用法,BIOS由两部分组成:POST代码和运行时服务.在POST完成之后,它从内存中被清除,但是BIOS运行时服务被保留下来,并且在目标操作系统中都是可用的.
为了启动一个操作系统,BIOS运行时按照CMOS定义的优先级的顺序寻找那些既活跃又可启动的设备.一个启动设备可以是软盘,一个CD-ROM,位于硬盘上的分区,位于网络上的设备或者甚至是USB闪存棒.
通常情况下,Linux从硬盘中被启动,在硬盘上的MBR包含基本的boot loader.MBR是一个512字节的扇区,位于磁盘的第一扇区(0磁头,0磁道的第一个扇区).
阶段1 boot loader
位于MBR中的基本boot loader是512字节的镜像,包含程序源码和一个小的分区表.前446字节是基本boot loader,其包含可执行嗲吗和错误信息文本.后面64字节是分区表,其包含为每四个分区生成的记录.MBR以两个字节结尾,这两个字节被定义成魔鬼数字(0XAA55).魔鬼数字位MBR的验证检查服务.
下面这张图就是MBR的解剖图:
基本boot loader的工作就是寸照和加载第二boot loader(第二阶段).它通过查询分区表上的可用分区来工作.当它发现一个活跃的分区,他遍历位于表上的剩下的分区来确保他们是不活跃的.当这个被验证之后,活跃的分区上的启动记录从设备中被读取到RAM中运行.
阶段2 boot loader
第二个或者说是第二阶段,boot loader可以更形象的称为内核加载器.在这个阶段的任务就是加载Linux内核和可选的最初的RAM磁盘.
GRUB阶段 boot loaders:
/boot/grub目录包含阶段1,阶段1.5和阶段2的boot loaders,和交替加载器的数量一样.
第一和第二阶段的boot loaders 联合起来被称为Linux Loader(LILO)或是GRUB.因为LILO有一些缺点而在GRUB中被改正了,所以我们研究GRUB.
关于GRUB最棒的事情就是它包含对Linux文件系统的了解.而不是像LILO似的使用位于磁盘上的原生扇区,GRUB可以从ext2或ext3文件系统中加载一个Linux内核.它通过将二阶段boot loader分解成三阶段boot loader来实现的.阶段1(MBR)启动阶段1.5 boot loader,该阶段理解包含Linux内核镜像的特殊文件系统.例子包括reiserfs_stage1_5
(从Reiser日志文件系统中加载)或者是e2fs_stage1_5
(从ext2或ext3文件系统中加载).当阶段1.5 boot loader在加载并且运行的时候,阶段2 boot loader就可以被加载了.
随着阶段2被加载,GRUB可以展示一个可用内核列表(定义在/etc/grub.conf
,是从/etc/grub/menu.lst
和/etc/grub.conf
的软连接).你可以选择一个内核,你甚至可以使用其他内核选项修改它.你也可以使用命令行脚本通过启动进程提高人工控制.
伴随着第二阶段的boot loader运行在内存中,文件系统被查询,并且默认的内核镜像和initrd
镜像被加载到内存中.随着镜像被准备好,阶段2的boot loader调用内核镜像.
内核
在GRUB中手动启动
通过GRUB的命令行,你可以启动一个带有命名的`initrd`的特定内核.就像下面这样:
grub> kernel /bzImage-2.6.14.2
[Linux-bzImage, setup=0x1400, size=0x29672e]
grub> initrd /initrd-2.6.14.2.img
[Linux-initrd @ 0x5f13000, 0xcc199 bytes]
grub> boot
Uncompressing Linux... OK, booting the kernel.
如果你不知道要启动的内核的名称,进需要输入'/',然后按下Tab键即可.GRUB将会显示内核和initrd镜像的列表.
随着内核镜像运行在内存中以及阶段2的boot loader给予的控制,内核阶段开始了.内核镜像不是一个可执行内核,而是一个压缩的内核镜像.这是一个zImage(压缩的镜像,小于512字节)或者是一个bzImage(大压缩镜像,大于512字节),他们是之前使用zlib被压缩的.在这个内核镜像的开头部分是一个例程,进行一些硬件设置然后解压内核镜像,并将他放到高内存中.如果一个最初的RAM磁盘是可用的,则这个例程便会将他移到内存中,并且标记后期使用.然后例程调用内核,内核启动开始了.
当bzImage被调用,程序便会从位于开始例程中的./arch/i386/boot/head.S
开始运行.这个例程做一些基本的硬件设置和调用位于./arch/i386/boot/compressed/head.S
中的startup_32
例程.这个例程设置一个基本的环境(栈等),并且清空符号开始的块(BSS).然后内核通过对函数decompress_kernel
(位于./arch/i386/boot/compressed/misc.c
)的调用而被解压.当内核被解压的内存的时候,他便会被调用.这是另外一个startup_32
函数,但是该函数位于./arch/i386/kernel/head.S
中.
在新的startup_32
函数中(也被称作交换器或0进程),页表被初始化,内存页变为可用.带有任何就可选FPU的CPU的类型被检测,并被存储后面使用.start_kernel
函数接着被调用(init/main.c
),这会将你带到非特异性Linux内核的体系结构.这就是本质上Linux 内核的main
函数.
下面是Linux内核 i386启动的主要函数流:
随着对start_kernel
函数的调用,一系列初始化函数被调用来设置中断,运行内存配置,加载最初的RAM磁盘.最后,一个对kernel_thread
(位于arch/i386/kernel/process.c
)的调用开始了init
函数,这是第一个用户空间进程.最后,idle进程被启动,并且调度器可以进行控制了(在调用cpu_idle
之后).伴随着中断的可用,抢先的调度器控制来提供多任务.
在内核启动期间,被第2阶段 boot loader 加载到内存中的初始化RAM磁盘(initrd)被复制到RAM中,并被挂载.这个initrd
作为一个位于RAM中的临时根文件系统服务,并且内核在没有任何物理磁盘挂在的情况下全启动.自从服务于外部设备的必要内核模块可以是initrd
的一部分之后,内核便可以非常小,但是之中支持大量的硬件配置.在内核引导之后,就可以正式装备根文件系统了(图那个过pivot_root
),此时会将initrd
根文件系统卸载掉,并挂载真正的根文件系统.
initrd
函数允许你创建一个驱动被编译为可加载模块的微型Linux内核.这些可加载模块给了内核访问磁盘和位于磁盘上的文件系统的方法,就像是其他硬件资源的驱动一样.因为根文件系统是位于磁盘上的一个文件系统,initrd
函数提供了启动引导程序访问磁盘和挂载真正的根文件系统的方法.在没有硬盘的嵌入式平台中,initrd
可以是最终的根文件系统,或者是最终的根文件系统可以通过网络文件系统(NFS)被挂载.
Init
在内核被启动和初始化之后,内核开启第一个用户空间应用.这是第一个使用标准C库编译被调用的程序.在这个进程这个点之前,没有标准C程序被执行.
在桌面Linux系统中,第一个启动的应用程序通常是/sbin/init
.但也不必是它.很少有嵌入式系统需要init
提供的广泛的初始化(就像通过/etc/inittab
配置一样).在大多数情况下,你可以调用一个简单的shell脚本来启动必要的嵌入式应用.
总结
很像Linux本身,Linux启动进程是非常灵活的,支持大量的处理器和硬件平台.在开始的时候,加载的boot loader提了一个简单的方式启动Linux.LILO boot loader扩展了启动能力,但是缺少文件系统意识.最新的boot loader实现,例如GRUB,允许Linux从文件系统中启动.