很多人觉得开机是很简单的事情,只要按下电源开关,然后系统就会自然启动,就能登陆。其实不是,如果系统没有什么问题,可以正常登陆的时候,当然开机很简单。但更多的时候,我们需要知道当机器不能正常开机的如何解决。

那么我们就来分析一下Linux的开机流程。

文章参考:

http://www.opsers.org/base/one-day-little-learning-linux-boot-process-analysis-of-rhel6.html

http://blog.csdn.net/gates84/article/details/1389222 

 

一、BIOS, 开机自检与MBR

        在个人计算机架构下,你想要启动整部系统首先就得要让系统去加载BIOS(Basic Input Output System),并通过BIOS程序去加载CMOS的信息,并且藉由CMOS内的设定值取得主机的各项硬件配置信息(如CPU与接口设备的沟通频率、开机设备的搜寻顺序、硬盘的大小与类型、 系统时间、各周边总线的是否启动Plug and Play (PnP, 即插即用设备)、 各接口设备的I/O地址、以及与CPU沟通的IRQ(Interrupt ReQuest)中断等等)。

        在取得这些信息后,BIOS还会进行开机自检(Power-on Self Test, POST)。 然后开始执行硬件检测的初始化,并设定PnP设备,之后再定义出可开机的设备顺序, 接下来就会开始进行开机设备的数据读取了(MBR相关的任务开始)。

        由于不同的操作系统的文件系统格式不相同,因此我们必须要以一个开机管理程序来处理内核文件的加载问题,因此这个开机管理程序就被称为Boot Loader。这个Boot Loader程序安装在开机设备的第一个扇区(sector)内,也就是我们一直谈到的 MBR (Master Boot Record,主要启动记录区)。下图就是MBR的组成图: 

         一块硬盘,有一个主引导记录,就是0面0道1扇区,又称MBR。此后硬盘最多可以分成4个主分区,也可以把其中的一个主分区变成扩展分区,进而分成若干个逻辑分区。其中每一个分区又有自己的引导扇区,虽然名字不叫MBR,但是作用是一样的,不同的是,MBR是由BIOS自动装载到内存中并CPU跳转过去执行的,而普通分区上的引导扇区,需要MBR中的引导程序去装载并提交控制权。

 

 二、Boot Loader 的功能

 

 

 

        Boot Loader最主要功能是认识操作系统的文件格式并加载内核到主存储器中去执行。 由于不同操作系统的文件格式不一致,因此每种操作系统都有自己的boot loader,用自己的loader才有办法载入内核文件。
问题就来了:如果在一部主机上面安装多种不同的操作系统。 既然必须要使用自己的 loader才能够加载属于自己的操作系统内核,而系统的MBR只有一个,那怎么会有办法同时在一部主机上面安装多系统呢?下面就来看看如何实现的。

 

 

        每个文件系统(filesystem, 或者是 partition) 都会保留一块启动扇区 (boot sector) 提供操作系统安装 boot loader,而通常操作系统默认都会安装一份 loader 到他根目录所在的文件系统的boot sector 上。如果我们在一部主机上面安装 Windows 与 Linux 后,该 boot sector, boot loader 与 MBR 的相关性会有点像下图:

 

 

          如上图所示,每个操作系统默认是会安装一套 boot loader 到他自己的文件系统中 (就是每个filesystem左下角的方框),而在Linux 统安装时,你可以选择将 boot loader 安装到MBR中,也可以选择不安装。如果选择安装到MBR的话,那理论上在 MBR与boot sector都会保有一份boot loader程序。至于Windows安装时,他预设会主动的将MBR与boot sector都装上一份 boot loader。所以,你会发现安装多操作系统时,你的MBR常常会被不同的操作系统的 boot loader所覆盖。

        现在还是没有解决我们上面的问题。虽然各个操作系统都可以安装一份 boot loader 到他们的boot sector中,这样操作系统可以通过自己的boot loader 来加载内核了。问题是系统的MBR只有一个,要怎么执行 boot sector里面的 loader呢?

        下面就来看看boot loader主要的功能

1.提供选项:用户可以选择不同的开机项目,这是多重引导的重要功能。

2.载入内核文件:直接指向可开机的程序区段来开始操作系统;

3.转交其他loader:将开机管理功能转交给其他loader负责。

由于具有选项功能,因此我们可以选择不同的内核来开机。而由于具有控制权转交的功能,因此我们可以加载其他 boot sector内的loader。不过 Windows的loader 预设不具有控制权转交的功能,因此你不能使用 Windows 的 loader 来加载 Linux 的 loader。这也是特别强调先装Windows再装Linux的缘故 

 

 如上图所示,MBR使用Linux的grub 这个开机管理程序,并且里面已经有了三个选项,第一个选项可以直接指向Linux 的内核文件并且直接加载内核来开机;第二个选项可以将开机管理程控权交给 Windows 来管理,此时 Windows 的 loader 会接管开机流程,这个时候他就能够启动 windows 了。第三个选项则是使用 Linux 在 boot sector 内的开机管理程序,此时就会跳出另一个grub的选项。

重点就是要知道“boot loader的功能就是加载kernel文件”。

        grub既可以装到MBR中,也可以装到任意分区的引导扇区上,所以一台电脑上可以装多个grub。grub1(grub第二代已经推出,机制改变了)由三段程序组成:stage1、stage1.5、stage2。如果你装的是grub1,可以进入/boot/grub目录看一看。

安装grub时stage1和stage2是必装的,而stage1.5是可选的。 

1. stage1:/boot/grub中的stage1文件大小为512b,它是引导扇区中引导程序(前446字节为引导程序)的备份文件,功能是用来装载 stage1.5或stage2的。

2. stage1.5:因为STAGE2较大,通常都是放在一个文件系统当中的,但是STAGE1并不能识别文件系统格式,所以才需要STAGE1.5来引导位于某个文件系统当中的STAGE2,根据文件系统格式的不同,STAGE1.5也需要相应的文件,如:e2fs_stage1_5,fat_stage1_5,分别用于识别ext和fat的文件系统格式。stage1_5并不大,我的系统上的几个stage1_5大小分别如下:

8.5K grub/e2fs_stage1_5

8.4K grub/fat_stage1_5

9.4K grub/jfs_stage1_5

7.8K grub/minix_stage1_5

11K grub/reiserfs_stage1_5

11K grub/xfs_stage1_5

但是,当stage1加载1.5时,原则上是不认识ext文件系统的,应该也是无法找到1.5程序的,而实际上在安装grub时,程序已经把1.5程序写到硬盘最前面的32K中,因为紧连着MBR的一段硬盘空间通常是空白无用的,grub就把1.5程序写到这个地方,这个地方没有文件系统,stage1程序可以直接去加载1.5。

3. stage2:grub能让用户以选项方式将OS加载、改变参数、修改选项,这些全都是stage2程序的功能。stage2可以去获取grub.conf以及menu.lst等文件的内容。

在grub中硬盘编号从0开始,比如(hd0,0)表示第一块硬盘的第一个分区,主分区从(hd0,0)-(hd0,3),(hd0,4)以后都是逻辑分区。

  MBR其实只有512字节的大小,其中bootloader更是只占了这其中的446字节,另外的分别是64字节的分区表信息(一个分区需要16字节,这也是为什么只能分4个主分区的原因)以及2字节的magic number。而GRUB的大小绝对不止512字节,那么它怎么放下多出来的东西呢?

[root@laptop grub]# ls -lh /boot/grub

  我们特别注意stage1这个文件的大小,发现他正好是512字节,我们来看看这个文件的属性

[root@laptop grub]# file stage1

stage1: x86 boot sector; GRand Unified Bootloader, stage1 version 0×3, GRUB version 0.94, code offset 0×48

这个文件很有意思,是X86的启动扇区,GRand Unified Bootloader(GRUB)

通过上面我们可以得出这样一个结论,那就是(我个人认为)这个stage1就是MBR。所以说,Stage1是执行boot loader的主程序,他是安装在我们的启动扇区。

我们再来看看这个stage2。

[root@laptop grub]# hexdump -C stage2

如下图  

 

 

然我们看不懂这些代码,但是我们在右侧会发现点什么

这个文件会加载/boot/grub/grub.conf这个文件。

我们在/boot/grub这个目录里面还能看到很多*stage1_5的文件,那这些文件是干吗的呢?

我的理解就是个过渡的一些内容,就像stage1到stage2之间的桥梁。所以都是stage1_5(就如同1-1.5-2一样)。这些文件都是一些针对不同的文件系统格式的识别文件。

再来了解一下device.map和splash.xpm.gz这两个文件

[root@laptop grub]# file device.map

device.map: ASCII text

[root@laptop grub]# cat device.map

# this device map was generated by anaconda

(hd0) /dev/sda

看到了吗,他就是记录grub所安装的位置了

 

下面就是GRUB与开机顺序的关系:

1.BIOS将控制权交给硬盘的主引导区,即MBR。

2.MBR中的bootloader(stage1)通过内置的地址加载*stage1_5

3.bootloader通过*stage1_5的内容,将分区中的stage2加载

4.stage2此时就可以在文件系统中将grub.conf文件加载,让用户看到选项界面。

 

 

 三、加载内核检测硬件与initrd的功能

 

 

1.内核

由boot loader的管理而开始读取内核文件后,接下来,Linux 就会将内核解压缩到主存储器当中, 并且利用内核的功能,开始测试与驱动各个周边设备,包括储存设备、CPU、网卡、声卡等等。 此时 Linux 内核会以自己的功能重新检测一次硬件,而不一定会使用 BIOS 检测到的硬件信息。也就是说,内核此时才开始接管 BIOS 后的工作。 内核档案一般来说,他会被放置到 /boot 里面,并且取名为 /boot/vmlinuz。

[root@laptop ~]# ls –format=single-column /boot

config-2.6.32-71.el6.i686 <===系统kernel的配置文件,内核编译完成后保存的就是这个配置文件

efi <===Extensible Firmware Interface(EFI,可扩展固件接口)是 Intel 为全新类型的 PC 固件的体系结构、接口和服务提出的建议标准。

grub <===开机管理程序grub相关数据目录

initramfs-2.6.32-71.el6.i686.img<===虚拟文件系统文件(RHEL6用initramfs代替了initrd,他们的目的是一样的,只是本身处理的方式有点不同)

initrd-2.6.32-71.el6.i686.img <===此文件是linux系统启动时的模块供应主要来源,initrd的目的就是在kernel加载系统识别cpu和内存等内核信息之后,让系统进一步知道还有那些硬件是启动所必须使用的;

symvers-2.6.32-71.el6.i686.gz <===模块符号信息

System.map-2.6.32-71.el6.i686 <===是系统kernel中的变量对应表;(也可以理解为是索引文件)

vmlinuz-2.6.32-71.el6.i686 <===系统使用kernel,用于启动的压缩内核镜像, 它也就是/arch/<arch>/boot中的压缩镜像.

Linux内核是可以通过动态加载内核模块的 (就请想成驱动程序即可),这些内核模块就放在 /lib/modules/目录内。由于模块放到磁盘根目录内(这就是为什么/lib不可以与 / 分别放在不同的分区原因), 因此在开机的过程中内核必须要挂载根目录,这样才能够读取内核模块提供加载驱动程序的功能。 而且为了担心影响到磁盘内的文件系统,开机过程中根目录是以只读的方式来挂载的。

2.initrd

了解/boot/initrd这个文件。

虚拟文件系统(Initial RAM Disk) 一般使用的文件名为 /boot/initrd ,这个文件的作用是,能够通过 boot loader 来加载到内存中, 然后这个文件会被解压缩并且在内存当中仿真 成一个根目录, 且此仿真在内存当中的文件系统能够提供一支可执行的程序,通过该程序来加载开机过程中所最需要的内核模块,通常这些模块就是 USB, RAID, LVM, SCSI 等文件系统与磁盘接口的驱动程序。等载入完成后,会帮助内核重新呼叫 /sbin/init 来开始后续的正常开机流程。

ram disk是一个基于ram的块设备,因此它占据了一块固定的内存,而且事先要使用特定的工具比如mke2fs格式化,还需要一个文件系统驱动来读写其上的文件。如果这个disk上的空间没有用完,这些未用的内存就浪费掉了,并且这个disk的空间固定导致容量有限,要想装入更多的文件就需要重新格式化。由于Linux的块设备缓冲特性,ram disk上的数据被拷贝到page cache(对于文件数据)和dentry cache(对于目录项),这个也导致内存浪费。

下面让我们来看看initrd文件的具体内容,

[root@laptop ~]# mkdir tmp

[root@laptop ~]# cd tmp/

[root@laptop tmp]# cp /boot/initrd-2.6.32-71.el6.i686.img ./

[root@laptop tmp]# file initrd-2.6.32-71.el6.i686.img

initrd-2.6.32-71.el6.i686.img: gzip compressed data, from Unix, last modified: Thu Dec 16 00:29:07 2010, max compression

#我们可以看到,这个文件是GZIP压缩的文件,我们把后缀改成gz,然后再进行解压

[root@laptop tmp]# mv initrd-2.6.32-71.el6.i686.img initrd-2.6.32-71.el6.i686.gz

[root@laptop tmp]# gunzip initrd-2.6.32-71.el6.i686.gz

解压后,我们再来看看这个文件是什么类型的

[root@laptop tmp]# file initrd-2.6.32-71.el6.i686

initrd-2.6.32-71.el6.i686: ASCII cpio archive (SVR4 with no CRC)

是cpio压缩成的文件

[root@laptop ~]# cpio -iv < initrd-2.6.32-71.el6.i686

  3.initramfs:

最初的想法是Linus提出的: 把cache当作文件系统装载。 他在一个叫ramfs的cache实现上加了一层很薄的封装,其它内核开发人员编写了一个改进版tmpfs, 这个文件系统上的数据可以写出到交换分区,而且可以设定一个tmpfs装载点的最大尺寸以免耗尽内存。initramfs就是tmpfs的一个应用。Linux 2.6 kernel提出了这种新的实现机制,即initramfs。顾名思义,initramfs只是一种RAM filesystem而不是disk。

initramfs的优点:

(1)tmpfs随着其中数据的增减自动增减容量。

(2)在tmpfs和page cache/dentry cache之间没有重复数据。

(3)tmpfs重复利用了Linux caching的代码,因此几乎没有增加内核尺寸,而caching的代码 已经经过良好测试,所以tmpfs的代码质量也有保证。

(4)不需要额外的文件系统驱动。

另外,initrd机制被设计为旧的"root="机制的前端,而非其替代物,它假设真正的根设备是一个块设备,而且也假设了自己不是真正的根设备,这样不便将NFS等作为根文件系统,最后/linuxrc不是以PID=1执行的,因为1这个进程ID是给/sbin/init保留的。initrd机制找到真正的根设备后将其设备号写入/proc/sys/kernel/real-root-dev,然后控制转移到内核由其装载根文件系统并启动/sbin/init.initramfs则去掉了上述假设,而且/init以PID=1执行,由init装载根文件系统并用exec转到真正的/sbin/init,这样也导致一个更为干净漂亮的设计。

四、init及配置文件 /etc/inittab 与 runlevel

在内核加载完毕、进行完硬件检测与驱动程序加载后,此时主机硬件已经准备就绪了,这时候内核会主动的呼叫第一支程序,那就是 /sbin/init

/sbin/init 最主要的功能就是准备软件执行的环境,包括系统的主机名、网络设定、语言、文件系统格式及其他服务的启动等。 而所有的动作都会通过 init的配置文件/etc/inittab来规划,而inittab 内还有一个很重要的设定内容,那就是默认的 runlevel (开机运行级别)。

长期以来,多数Linux发行版一直在使用Unix System V引入的 init 系统。init 由内核自身产生,任务是启动系统剩余部分,产生并监视所有其它进程,看其是否停止或僵死。System V init 虽然过去一直运行的很好,但它已经有些迟暮了。这就是 Ubuntu 6.10(Edgy Eft) 为什么使用Upstart来代替正在老化的 init 系统。

SysVinit守护进程(sysvinit软件包)是一个基于运行级别的系统,它使用运行级别(单用户、多用户以及其他更多级别)和链接(位于/etc /rc?.d目录中,分别链接到/etc/init.d中的init脚本)来启动和关闭系统服务。SysV启动是线性、顺序的。一个S20的服务必须要等待S19启动完成才能启动,如果一个启动要花很多时间,那么后面的服务就算完全无关,也必须要等。

  UpStart(Upstart init daemon)是基于事件的启动系统,它使用事件来启动和关闭系统服务。Upstart是是并行的,只要事件发生,服务可以并发启动。这种方式无疑要优越得多,因为它可以充分利用现在计算机多核的特点,大大减少启动所需的时间。

 设计 Upstart 的目标就是提供一种代替 System V Init (sysvinit)的机制,并与其它已经开发好的 init 替代者目的有所不同。由于现在的桌面计算机和服务器跟十年前相比已经非常不同了,在 Ubuntu 里试图继续运转系统时碰到了一些问题。

过去,启动时的操作相对简单,比如检查和挂载文件系统。仅支持有限的硬件,上电时连接设备,掉电时断开。内核知道磁盘类型和数目,所以用户空间的启动进程检查和挂载它们毫不费力。

处理新的硬件

目前的系统必需处理新产生的硬件,它们可以几乎无数目限制地被连接到更复杂的总线上。这些新设备可以即插即用,使用前不需供电,这意味着当硬件改变时内核接要收到这些信息。

Linux内核在硬件支持上变得越来越好,随着udev的引入,内核驱动核心、用户空间都能知道硬件连接信息。尽管如此,我们仍然依赖为启动程序设置的一些shell脚本。启动进程里关于哪些硬件可用仍做了假设。

 

1./ etc / sysconfig / init中还有一些额外的配置。在这里,定义了一些参数来决定 启动信息的格式。除了那些不很重要的设置,有三行我们需要注意:

AUTOSWAP=no

ACTIVE_CONSOLES=/dev/tty[1-6]

SINGLE=/sbin/sushell

   #其中,第一行的值你可以设定为Yes,这样可以让你的系统能够自动检测交换分区。使用此选项意味着你再也不必在/ etc / fstab中 挂载交换分区了。

       #在ACTIVE_CONSOLES这一行决定了虚拟控制台的创建。在大多数情况下,tty[1-6]工作得很好,同时 这个选项也允许您分配更多或者更少的虚拟控制台。但是要注意,不要使用tty [1-8],因为tty7是专门为图形界面预留的。

   #最后很重要的一行是single= / sbin/ sushell。这一行可以有两个参数:/ sbin/ sushell(系统默认的参数),它会在启动单用户模式时将你带入一个root的 shell,参数/ sbin / sulogin会在单用户模式启动之前弹出一个登录提示,你必须输入root账户的密码才能继续下去。

2..RHEL6系统上的这个文件和以前的版本有很大的差别,其它的相关配置文件,在此文件中已经做了说明如:

1)./etc/init/rcS.conf

#通过启动大部分的基本服务来对系统进行初始化的设定

2)./etc/init/rc.conf

#对启动各自的运行级别(runlevel)的设定

3)./etc/init/control-alt-delete.conf

#定义当用户按“control-alt-delete”三个键时的系统行为

4)./etc/init/tty.conf and /etc/init/serial.conf

#定义了系统处理终端登录的方式

   下面是RHEL6上面Upstart大致的一个启动过程:

1.内核启动init

2.系统初始化:(/etc/init/rcS.conf exec /etc/rc.d/rc.sysinit)

3.init找到/etc/inittab文件,确定默认的运行级别(X) (/etc/init/rcS.conf exec telinit $runlevel)

4.触发相应的runlevel事件(/etc/init/rc.conf exec /etc/rc.d/rc $RUNLEVEL)

5.开始运行/etc/rc.d/rc,传入参数X

6./etc/rc.d/rc脚本进行一系列设置,最后运行相应的/etc/rcX.d/中的脚本

7./etc/rcX.d/中的脚本按事先设定的优先级依次启动

8.最后执行/etc/rc.d/rc.local

9.加载终端或X-Window接口

想要了解更多的内容,请大家打开/etc/init/这个目录里面的文件看看。

/etc/rc.sysinit 这个文件干了哪些工作?

vim /etc/rc.sysinit

1、获得网络环境

2、挂载设备

3、开机启动画面Plymouth(取替了过往的 RHGB)

4、判断是否启用SELinux

5、显示于开机过程中的欢迎画面

6、初始化硬件

7、用户自定义模块的加载

8、配置内核的参数

9、设置主机名

10、同步存储器

11、设备映射器及相关的初始化

12、初始化软件磁盘阵列(RAID)

13、初始化 LVM 的文件系统功能

14、检验磁盘文件系统(fsck)

15、磁盘配额(quota)

16、重新以可读写模式挂载系统磁盘

17、更新quota(非必要)

18、启动系统虚拟随机数生成器

19、配置机器(非必要)

20、清除开机过程当中的临时文件

21、创建ICE目录

22、启动交换分区(swap)

23、将开机信息写入/var/log/dmesg文件中

这个文件里面的许多预设配置文件在/etc/sysconfig/这个目录当中,要想更多的系统启动信息,大家可以到/var/log/dmesg文件中查看,也可以用dmesg命令来查看。

3.系统服务的启动

经过 /etc/rc.sysinit 的系统模块与相关硬件信息的初始化后,我们的RHEL6系统应该已经能顺利工作了。但我们还需要启动一些为我们提供服务的服务。这个时候,依据在/etc/inittab里面run level的设定值,就可以来决定启动的服务项目了。

4.用户自定义开机启动脚本

上面讲到的都是一些系统服务,大家知道,我们的Linux系统容许安装其它的软件来提供服务,那我想要自己安装的服务也要在开机启动,那怎么办,没有关系,找 /etc/rc.local 来完成。这就是我们要讲的用户自定义开机启动脚本。我们只要把想启动的脚本写到这个文件中,开机就能启动了,注意一点,写在这里面的脚本要使用绝对路径。

5.加载终端或X-Window接口

在完成了系统所有服务的启动后,接下来Linux就会启动终端或者是X Window来等待使用者登陆了!

在/etc/init/start-ttys.conf中我们可以看到有6个纯文本终端(tty[1-6])。