Linux启动过程
Step1:BIOS(Basic Input Output System 基本输出输入系统)
BlOS是计算机上电后执行的第一个程序。BIOS首先先载入CMOS的信息,并借由CMOS内的设置值取得主机的各项硬件设置,例如CPU时钟频率、开机设备的搜索顺序、硬盘大小与类型等。取得信息后BIOS进行开机自我检测(Power-on Self Test,POST)。然后开始执行硬件侦测的初始化,并设置PnP设备,之后再定义出可开机的设备顺序,接下来开始进行开机设备的数据读取。
由于系统软件大多存储在硬盘中,所以BIOS指定开机的设备来读取磁盘中的操作系统核心文件。但由于不同操作系统文件的系统格式不同,因此必须以一个开机管理程序来处理核心文件的载入问题,即 BootLoader。
BIOS主要包括:
1. 自诊断程序(通过读取CMOSRAM中的内容,识别硬件配置,并对其进行自检和初始化)
2. CMOS设置程序(引导过程中,用特殊热键启动,进行设置后,存入CMOS RAM中)
3. 系统自检装载程序(在自检成功后,将磁盘0磁道0扇区上的引导程序装入内存,运行加载操作系统)
4. 主要I/O设备驱动程序和中断服务(BIOS和硬件直接打交道,需要加载I/O驱动程序)
Step2:MBR(Master Boot Record 硬盘主引导记录)
BIOS在硬件自检完成后会读取并执行开机设备的一个扇区(Sector)内 的程序,即 Boot Loader。一般情况下,系统从硬盘启动,硬盘中存放boot loader stage1程序的扇区被称为MBR,即前512个字节,从而获取磁盘的分区信息和操作系统的存放位置。MBR是启动硬盘(/dev/sda)的首个扇区,告诉计算机从哪个分区来装载BootLoader,从而引导出操作系统。
有时候一个计算机会有多个硬盘,那么究竟在BIOS结束后读取哪一块硬盘最前面区块的MBR区域以获取BootLoader呢?这个取决于BIOS的设置,在BIOS中把要读取的MBR所在的硬盘设置为第一个开机设备即可。
MBR的组成:
第1-446字节:调用操作系统的机器码,是GRUB的关键引导程序
第447-510字节:硬盘分区表(Disk Partition table)
第511-512字节:主引导记录签名(0x55和0xAA)
分区表是将硬盘分为若干个区,长度为64字节,内部分为4项,每项16字节。一个硬盘最多只能分4个一级分区,又叫“主分区”。主分区的组成:
第1个字节:如果为0x80,就表示该主分区是装有操作系统的分区。
第2-4个字节:主分区第一个扇区的物理位置(柱面、磁头、扇区号等等)。
第5个字节:主分区类型。
第6-8个字节:主分区最后一个扇区的物理位置。
第9-12字节:该主分区第一个扇区的逻辑地址。
第13-16字节:主分区的扇区总数。
最后的四个字节(”主分区的扇区总数”),决定了这个主分区的长度。也就是说,一个主分区的扇区总数最多不超过2的32次方。
如果每个扇区为512个字节,就意味着单个分区最大不超过2TB。再考虑到扇区的逻辑地址也是32位,所以单个硬盘可利用的空间最大也不超过2TB。如果想使用更大的硬盘,只有2个方法:一是提高每个扇区的字节数,二是增加扇区总数。
Step3:BootLoader(系统引导程序)和 GRUB
BootLoader是操作系统内核运行之前先运行的一段程序。它最主要功能是要认识操作系统的文件格式并据以载入核心到内存中去执行。计算机读取MBR前面446字节的机器码之后,运行事先安装的BootLoader,由用户选择启动哪一个操作系统。它主要的功能如下:
- 提供菜单:使用者可以选择不同的开机项目
- 载入核心文件:直接指向可开机的程序区段来开始操作系统
- 转交其他 loader:将开机管理功能转交给其他 loader 负责
(此后机器运行的过程中与BootLoader再无关系)
Linux中,最流行的BootLoader是GRUB(GRandUnified Bootloader),对于GRUB而言,MBR的前446字节的引导程序属于GRUB的开始执行程序(stage1),当BIOS把MBR加载到内存后就把控制权交给GRUB,而后加载出GRUB的剩余代码,随后(stage1.5)将完成其它代码的加载和搬移以及文件系统初始化查找等工作,最终(stage2)把内核(kernel)和initramfs文件(即initrd镜像)加载入内存中,然后把控制权交给内核。
Step4:Kernel 和 initrd
接下来内核kernel被解压到内存中并执行,Kernel此时接管BIOS后的工作,先重新侦测一次硬件,然后完成初始化硬件、进程调度、内存管理等任务。
在这个过程中,Kernel需要从磁盘中读取根文件系统并以只读形式挂载后以从磁盘中读取操作系统的核心模块,但是根文件系统本身也是保存在磁盘中,而Kernel本身并没有读取磁盘的驱动程序,导致Kernel进退两难。为了解决这个问题,BootLoader在加载Kernel的同时也会把虚拟文件系统(initramfs/initrd)一同加载到內存。这个文件会被解压缩并且在内存当中仿真成一个根文件系统, 且此仿真在内存当中的文件系统能够提供一支可执行的程序(init),通过该程序来载入开机过程中所最需要的核心模块,即:USB, RAID, LVM, SCSI 等文件系统与磁盘接口的驱动程序。等载入完成后, Kernel就可以读取磁盘并挂载真正的根文件系统以/sbin/init(systemd )来开始后续的正常开机流程。
这个过程总结如下:
1、 解压缩Kernel并再次检测硬件,安装必要驱动
2、 初始化与文件系统相关的虚拟设备,LVM或RAID
3、 把initramfs文件(initrd,虚拟文件系统,包含一个最小linux系统)装载到根文件系统,挂在根目录下面
4、 执行initramfs(initrd)中的init进程(挂载真正的根文件系统),完成加载模块、检查磁盘fsck等任务
5、挂载真正的根文件系统之后执行/sbin/init程序
Kernel内核文件:
initramfs(initrd)文件:
Step5:/sbin/init
/sbin/init是Linux启动后第一个用户态下的进程(PID为1)。init读取的第一个文件是/etc/inittab,通过它init会确定我们Linux操作系统的运行级别。它会从文件/etc/fstab里查找分区表信息 然后做相应的挂载。然后init会启动/etc/init.d里指定的默认启动级别的所有服务/脚本。所有服务在这里通过init一个一个被初始化。在这 个过程里,init每次只启动一个服务,所有服务/守护进程都在后台执行并由init来管理。
以上提到的启动过程有一些不足的地方。而用一种更好的方式来替代传统init的需求已经存在很长时间了,也产生了许多替代方案。其中比较著名的有Upstart,Epoch,Muda和Systemd,其中Systemd获得最多关注并被认为是目前最佳的方案。
至此,全部启动过程完成。
附:ubuntu20.04启动系统时systemd执行流程(systemd-analyze critical-chain
命令)
The time when unit became active or started is printed after the "@" character.
The time the unit took to start is printed after the "+" character.
graphical.target @6.863s
└─multi-user.target @6.863s
└─snapd.seeded.service @6.843s +19ms
└─snapd.service @5.183s +1.658s
└─basic.target @5.139s
└─sockets.target @5.139s
└─snapd.socket @5.138s +467us
└─sysinit.target @5.134s
└─snapd.apparmor.service @4.040s +1.094s
└─apparmor.service @1.090s +2.948s
└─local-fs.target @1.089s
└─run-vmblock\x2dfuse.mount @5.812s
└─local-fs-pre.target @339ms
└─lvm2-monitor.service @236ms +102ms
└─systemd-journald.socket @225ms
└─-.mount @221ms
└─system.slice @221ms
└─-.slice @221ms