Linux开机启动过程-LMLPHP
一、加载bios

BIOS是英文”Basic Input Output System”的缩略语,直译过来后中文名称就是”基本输入输出系统”。其实,它是一组固化到计算机内主板上一个ROM芯片上的程序,它保存着计算机最重要的基本输入输出的程序、系统设置信息、开机后自检程序和系统自启动程序。 其主要功能是为计算机提供最底层的、最直接的硬件设置和控制。早期的BIOS芯片确实是”只读”的,里面的内容是用一种烧录器写入的,一旦写入就不能更改,除非更换芯片。现在的主机板都使用一种叫Flash EPROM的芯片来存储系统BIOS,里面的内容可通过使用主板厂商提供的擦写程序擦除后重新写入,这样就给用户升级BIOS提供了极大的方便。除了bios,主板上还有一个cmos,cmos是记录各项硬件参数且嵌入主板上面的存储器。

BIOS的功能由两部分组成,分别是POST码和Runtime服务。POST阶段完成后它将从存储器中被清除,而Runtime服务会被一直保留,用于目标操作系统的启动。BIOS两个阶段所做的详细工作如下:

步骤1:上电自检POST(Power-on self test),主要负责检测系统外围关键设备(如:CPU、内存、显卡、I/O、键盘鼠标等)是否正常。例如,最常见的是内存松动的情况,BIOS自检阶段会报错,系统就无法启动起来;
 步骤2:步骤1成功后,便会执行一段小程序用来枚举本地设备并对其初始化。这一步主要是根据我们在BIOS中设置的系统启动顺序来搜索用于启动系统的驱动器,如硬盘、光盘、U盘、软盘和网络等。我们以硬盘启动为例,BIOS此时去读取硬盘驱动器的第一个扇区(MBR,512字节),然后执行里面的代码。实际上这里BIOS并不关心启动设备第一个扇区中是什么内容,它只是负责读取该扇区内容、并执行。

至此,BIOS的任务就完成了,此后将系统启动的控制权移交到MBR部分的代码。

二 读取MBR

MBR(Main Boot Record 主引导记录区)位于整个硬盘的0磁道0柱面1扇区。要理解MBR我们需要首先对硬盘有所了解。硬盘根据台式机和笔记本又分为3.5英寸及2.5英寸的大小,我们以3.5英寸硬盘来说明,硬盘其实是有许多的盘片,机械手臂,磁头和主轴马达组成的,实际的数据都是写在具有磁性物质的盘片片,读写硬盘主要是通过机械手臂上的读取磁头来完成的,一个硬盘根据大小的不同会含有一个或一个以上的盘片。磁盘的最小存储单位是扇区,扇区是以盘片的圆心呈放射状分割出来的磁盘的最小存储单位,在物理组成方面,每个扇区大小为512字节,而所有扇区组成一个圆就是磁道,如果是多盘片的磁盘,所有盘片上的同一个磁道就组成了一个柱面,现在我们知道MBR的大小为512个字节,在512字节的MBR中,主引导程序占用了其中的446个字节,另外的64个字节交给了 DPT(Disk Partition Table硬盘分区表),最后两个字节“55,AA”是分区的结束标志。这个整体构成了硬盘的主引导扇区。

主引导记录中包含了一段系统启动引导程序boot loader(如grub或lilo)和硬盘分区的一系列参数DPT。其中的系统启动引导程序的主要作用是检查DPT是否正确并且在系统硬件完成自检以后引导具有激活标志的分区上的操作系统,并将控制权交给启动程序。那么什么是DPT呢?

DPT起始存储的是每一个分区的起始柱面与结束柱面。其实我们刚拿到一块硬盘时什么都不能做,你必须首先对硬盘进行分区,这样硬盘才能被你使用。分区的过程其实就是将硬盘格式化为某种文件系统(FAT32,ntfs,ext2,ext3,ext4等)的过程,以便能被操作系统识别和读取,一块硬盘可以被分成多个分区,但主分区和扩展分区的总数不能超过四个,而每个分区的起始柱面和结束柱面的信息就存储在DPT中,DPT大小为64B,而每个分区的分区信息需要占用16个字节,DPT最多只能存储四个分区的信息,这也是为什么硬盘主分区和扩展分区总数不能超过四个的原因,但这并不意味着我们只能有四个分区,因为我们还有一个扩展分区,有了扩展分区,我们就可以分出多个逻辑分区,而这些逻辑分区的分区表信息都记录在扩展分区中,所以扩展分区相当于逻辑分区的分区表,对于ide硬盘我们可以分区59个逻辑分区(5-63),对于sata硬盘,我们可以分出11个逻辑分区(5-15)。

下面,我们以一个实例让大家更直观地来了解DPT:
例:80 01 01 00 0B FE BF FC 3F 00 00 00 7E 86 BB 00
在这里我们可以看到,最前面的“80”是一个分区的激活标志,表示系统可引导;“01 01 00”表示分区开始的磁头号为01,开始的扇区号为01,开始的柱面号为00;“0B”表示分区的文件类型是FAT32,其他比较常用的有 04(FAT16)、07(NTFS);“FE BF FC”表示分区结束的磁头号为254,分区结束的扇区号为63、分区结束的柱面号为764;“3F 00 00 00”表示首扇区的相对扇区号为63;“7E 86 BB 00”表示总扇区数为12289622。
最后的四个字节(”主分区的扇区总数”),决定了这个主分区的长度。也就是说,一个主分区的扇区总数最多不超过2的32次方。
如果每个扇区为512个字节,就意味着单个分区最大不超过2TB。再考虑到扇区的逻辑地址也是32位,所以单个硬盘可利用的空间最大也不超过2TB。如果想使用更大的硬盘,只有2个方法:一是提高每个扇区的字节数,二是增加扇区总数。

三、启动bootloader

Boot Loader 就是在操作系统内核运行之前运行的一段小程序。通过这段小程序,我们可以初始化硬件设备、建立内存空间的映射图,从而将系统的软硬件环境带到一个合适的状态,以便为最终调用操作系统内核做好一切准备。

Boot Loader有若干种,其中Grub、Lilo和spfdisk是常见的Loader。我们以grub为例来分析这个引导过程
grub引导也分为两个阶段stage1阶段和stage2阶段(有些较新的grub又定义了stage1.5阶段):
1)、stage1:stage1是直接被写入到MBR中去的,这样机器一启动检测完硬件后,就将控制权交给了GRUB的代码。也就是上图所看到的前446个字节空间中存放的是stage1的代码。BIOS将stage1载入内存中0x7c00处并跳转执行。stage1(/stage1/start.S)的任务非常单纯,仅仅是将硬盘0头0道2扇区读入内存。而0头0道2扇区内容是源代码中的/stage2/start.S,编译后512字节,它是stage2或者stage1_5的入口。而此时,stage1是没有识别文件系统能力的。
 定位硬盘的0头0道2扇区的过程:
BIOS将stage1载入内存0x7c00处并执行,然后调用BIOS INIT13中断,将硬盘0头0道2扇区内容载入内存0x7000处,然后调用copy_buffer将其转移到内存0x8000处。在定位0头0道2扇区时通常有两种寻址方式:LBA和CHS。
2)、stage2:严格来说这里还应该再区分个stage1.5的,就一并把stage1.5放在这里一起介绍了,我们继续说0头0道2扇区的/stage2/start.S文件,当它的内容被读入到内存之后,它的主要作用就是负责将stage2或stage1.5从硬盘读到内存中。如果是stage2,它将被载入到0x820处;如果是stage1.5,它将被载入到0x2200处。这里的stage2或者stage1_5不是/boot分区/boot/grub目录下的文件,因为这个时候grub还没有能力识别任何文件系统。

情形1:如果start.S加载stage1.5:stage1.5它存放在硬盘0头0道3扇区向后的位置,stage1.5作为stage1和stage2中间的桥梁,stage1.5有识别文件系统的能力,此后grub才有能力去访问/boot分区/boot/grub目录下的 stage2文件,将stage2载入内存并执行。

情形2:如果start.S加载stage2:同样,这个stage2也不是/boot分区/boot/grub目录下的stage2,这个时候start.S读取的是存放在/boot分区Boot Sector的stage2。这种情况下就有一个限制:因为start.S通过BIOS中断方式直接对硬盘寻址(而非通过访问具体的文件系统),其寻址范围有限,限制在8GB以内。因此这种情况需要将/boot分区分在硬盘8GB寻址空间之前。

假如是情形2,我们将/boot/grub目录下的内容清空,依然能成功启动grub;假如是情形1,将/boot/grub目录下stage2删除后,则系统启动过程中grub会启动失败。

四、加载内核

当stage2被载入内存执行时,它首先会去解析grub的配置文件
/boot/grub/grub.conf,产生grub界面, 然后将initrd加载到内存里,将其中的内容释放到内存中,由于此时真正的跟文件系统还不能载,所以内核在访问真正跟文件系统之前首先会去访问initrd中的内容,然后执行initrd中的init脚本,这时内核将控制权交给了init文件处理。我们简单浏览一下init脚本的内容,发现它也主要是加载各种存储介质相关的设备驱动程序。当所需的驱动程序加载完后,会创建一个根设备,然后将根文件系统rootfs以只读的方式挂载。这一步结束后,释放未使用的内存,转换到真正的根文件系统上面去,同时运行/sbin/init程序,执行系统的1号进程。此后将系统的控制权全部交给/sbin/init进程。
 /sbin/init进程是系统其他所有进程的父进程,当它接管了系统的控制权之后,它首先会去读取/etc/inittab文件来执行相应的脚本进行系统初始化,如设置键盘、字体,装载模块,设置网络等。主要包括以下工作:

1 执行系统初始化脚本(/etc/rc.d/rc.sysinit),对系统进行基本的配置,以读写方式挂载根文件系统及其它文件系统,到此系统算是基本运行起来了,后面需要进行运行级别的确定及相应服务的启动。rc.sysinit所做的事情(不同的Linux发行版,该文件可能有些差异)如下:

(1)获取网络环境与主机类型。首先会读取网络环境设置文件”/etc/sysconfig/network”,获取主机名称与默认网关等网络环境。

(2)测试与载入内存设备/proc及usb设备/sys。除了/proc外,系统会主动检测是否有usb设备,并主动加载usb驱动,尝试载入usb文件系统。

(3)决定是否启动SELinux。

(4)接口设备的检测与即插即用(pnp)参数的测试。

(5)用户自定义模块的加载。用户可以再”/etc/sysconfig/modules/*.modules”加入自定义的模块,此时会加载到系统中。

(6)加载核心的相关设置。按”/etc/sysctl.conf”这个文件的设置值配置功能。

(7)设置系统时间(clock)。

(8)设置终端的控制台的字形。

(9)设置raid及LVM等硬盘功能。

(10)以读写方式查看检验磁盘文件系统。

(11)进行磁盘配额quota的转换。

(12)重新以读写模式载入系统磁盘。

(13)启动quota功能。

(14)启动系统随机数设备(产生随机数功能)。

(15)清除启动过程中的临时文件。

(16)将启动信息加载到”/var/log/dmesg”文件中。

当/etc/rc.d/rc.sysinit执行完后,系统就可以顺利工作了,只是还需要启动系统所需要的各种服务,这样主机才可以提供相关的网络和主机功能,因此便会执行下面的脚本。

2)、执行/etc/rc.d/rc脚本。该文件定义了服务启动的顺序是先K后S,而具体的每个运行级别的服务状态是放在/etc/rc.d/rc*.d(=0~6)目录下,所有的文件均是指向/etc/init.d下相应文件的符号链接。rc.sysinit通过分析/etc/inittab文件来确定系统的启动级别,然后才去执行/etc/rc.d/rc.d下的文件。

/etc/init.d-> /etc/rc.d/init.d

/etc/rc ->/etc/rc.d/rc

/etc/rc*.d ->/etc/rc.d/rc*.d

/etc/rc.local-> /etc/rc.d/rc.local

/etc/rc.sysinit-> /etc/rc.d/rc.sysinit

也就是说,/etc目录下的init.d、rc、rc*.d、rc.local和rc.sysinit均是指向/etc/rc.d目录下相应文件和文件夹的符号链接。我们以启动级别3为例来简要说明一下。

/etc/rc.d/rc3.d目录,该目录下的内容全部都是以 S 或 K 开头的链接文件,都链接到”/etc/rc.d/init.d”目录下的各种shell脚本。S表示的是启动时需要start的服务内容,K表示关机时需要关闭的服务内容。/etc/rc.d/rc*.d中的系统服务会在系统后台启动,如果要对某个运行级别中的服务进行更具体的定制,通过chkconfig命令来操作,或者通过setup、ntsys、system-config-services来进行定制。如果我们需要自己增加启动的内容,可以在init.d目录中增加相关的shell脚本,然后在rc*.d目录中建立链接文件指向该shell脚本。这些shell脚本的启动或结束顺序是由S或K字母后面的数字决定,数字越小的脚本越先执行。例如,/etc/rc.d/rc3.d /S01sysstat就比/etc/rc.d/rc3.d /S99local先执行。

3)、执行用户自定义引导程序/etc/rc.d/rc.local。其实当执行/etc/rc.d/rc3.d/S99local时,它就是在执行/etc/rc.d/rc.local。S99local是指向rc.local的符号链接。就是一般来说,自定义的程序不需要执行上面所说的繁琐的建立shell增加链接文件的步骤,只需要将命令放在rc.local里面就可以了,这个shell脚本就是保留给用户自定义启动内容的。

4)、完成了系统所有的启动任务后,linux会启动终端或X-Window来等待用户登录。tty1,tty2,tty3…这表示在运行等级1,2,3,4的时候,都会执行”/usr/sbin/getty”,而且执行了6个,所以linux会有6个纯文本终端,getty就是启动终端的命令。
5)、linux的登录主要是由两个文件在控制,/usr/sbin/getty启动终端,等待用户登陆,获得用户名,并进行检查用户名是否存在,然后启动/usr/bin/login,并将用户名传递给/usr/bin/login来获取用户输入密码和并检查用户密码是否正确.
1.getty实现的主要功能是:
1)打开指定的tty;
2)提示用户登录(login:);
3)获得登录用户名;
4)把用户名传递给login命令

2.login实现的主要功能是:
1)先检查是不是超级用户;
2)提示用户输入密码(通过getpass()实现);
3)检查密码并检查是否登录;
4)设置登录的用户的ID和组ID,并设置相应的环境变量.
 除了这6个终端之外,对于桌面版的linux系统还会执行”/etc/X11/prefdm-nodaemon”启动X-Window system,x-window是linux的图形界面,l其实inux本身是没有图形界面,linux桌面版的图形界面的实现只是通过linux下的应用程序实现的。图形界面并不是linux的一部分,linux只是个基于命令行的操作系统。Linux内核为linux系统中的图形界面提供了显示设备驱动。

10-07 16:14