目录
1.1 加载BIOS(Basic Input Output System),硬件自检。
3.1.3 GRUB核心文件的分布以及加载方法(非grub2)
1.启动过程概括
计算机启动过程大致可以分为四个阶段:
1.加载BIOS,硬件自检。(UEFI作为BIOS的后继者和取代者已经广泛使用,但是各种资料都是BIOS)
1.1 加载BIOS(Basic Input Output System),硬件自检。
1. 打开计算机电源时,计算机首先加载主板BIOS信息。BIOS执行程序存储在ROM中,当CS:IP(x86架构)指向该程序的起始位置时,BIOS开始执行。
2. BIOS进行服务器硬件环境自检(POST:Power On Self Test)和硬件初始化;
3. 按照设定的顺序搜索处于处于活动状态并可引导的设备,从第一个设备的第一个扇区加载MBR(主引导记录),执行引导程序。
1.2 启动引导管理器
加载和运行系统启动引导管理器(grub2)。
1.3 加载内核与镜像文件系统
1) 确定启动分区;
2) 在启动分区中,根据启动引导管理器的预设或临时配置加载内核文件、镜像文件系统;
3) 加载镜像文件系统中基本的设备驱动,识别和自动挂载根等文件系统。例如加载了磁盘驱动,从而识别磁盘以及磁盘上的分区和文件系统,可以在文件系统上找到后续启动过程所需要的文件和配置。。
1.4 初始化系统以及加载服务
1) 启动init(RHEL 6.x)或systemd(RHEL 7.x)进程;
2) 执行系统初始化sysinit脚本(RHEL 6.x)或systemd的服务单元(RHEL 7.x);
3) 进入指定的运行级别(RHEL 3-6)或模式(RHEL 7-8);
4) 启动相关服务;
5) 开启服务。
2. BIOS自检阶段
在该阶段中BIOS将执行基本的硬件环境的自检和初始化,并移交启动引导管理权至下一阶段。
BIOS基本概念:
1) 位于系统CMOS ROM(只读存储器)内的微型操作系统;
2) CMOS是一种类型的EPROM(可擦除编程只读存储器);
3) 大多数BIOS都可通过特殊的开机按键进入(F10、F1、F2);
BIOS基本作用:
1) BIOS代码包括两个部分——POST和Run Time;
2) POST(Power On Self Test)代码对服务器硬件环境执行基本检查;
3) Run Time代码用于基本操作系统启动;
4) 为键盘、视频设备、串行口初始化核心设备驱动并分配资源;
5) 根据CMOS的设定顺序搜索处于活动状态的并可引导的设备(软盘、硬盘、CD-ROM、PXE);
6) 将磁盘第一个扇区(512字节)装入内存并传递引导权到该区域。(引导盘的第一个数据块都包含一个可执行文件——引导程序)。
UEFI——可扩展固件接口(Unified Extensible Fireware Interface)
现代计算机主板上都集成了UEFI固件;
更好地支持GPT分区以及2TB以上硬盘;
更快地启动速度以及更多功能扩展。
3. 启动引导管理器阶段
编制本文时刚开始查找到的grub资料,要么只介绍grub,要么只介绍grub2,搞得我一头懵逼(咋查到的资料不一样呢)。所以本文将先后介绍grub和grub2。grub大抵是不怎么用了,也没必要详细地了解,知道有这么回事就行了。要直接了解grub2可以直接跳转到3.2节。
3.1 GRUB
3.1.1 GRUB介绍
- BIOS通过访问磁盘主引导记录(MBR)来加载启动引导管理程序(GRUB)。
1) MBR位于磁盘上0磁道0柱面1扇区的前446字节,后64字节为分区表,2字节签名信息;
2) MBR的头446字节通常用于装载系统引导程序GRUB的部分代码(stage 1)并执行。
- 通过运行启动引导管理程序来获取和加载必须的启动信息(启动分区位置、内核文件位置、镜像文件系统位置以及相关选项)。
- Linux系统中最常用的多重启动引导管理程序为GRUB(GRand Unified Bootloader)。
1) 有GRUB和GRUB2两个版本;GRUB2支持更多的功能。
2) GRUB或GRUB2的部分内容会被安装在磁盘主引导记录MBR中。
3.1.2 GRUB的基本组成部分和作用(非grub2)
stage1
- stage1由GRUB源码的Stage1.S汇编生成,默认将被安装到磁盘MBR的前446字节内;
- BIOS自检完成后会将stage1载入内存执行,标志着该阶段开始;
- stage1唯一作用:将磁盘0磁道0柱面2扇区装载到内存中执行。
start.S
- 位于0磁道0柱面2扇区;
- 由GRUB源码Start.S汇编得到;
- Start.S的作用:
1) 读取0磁道0柱面3扇区至N扇区的位置到内存(取决于文件系统支撑代码大小);
2) 作为进入和访问stage1_5或stage2的入口;
如果加载stage1_5,则可通过文件系统而非硬件跳转来访问stage2;
如果不加载stage1_5,则通过硬件跳转而非文件系统方式来访问stage2.
3) 而系统默认将使用INIT 13中断通过硬件跳转来访问GRUB主要功能(stage2).
stage1_5:
通常安装于磁盘0磁道0柱面3扇区之后到文件系统的位置;
stage1_5提供文件系统识别能力,支持通过文件系统访问stage2,是stage1和stage2之间的桥梁;
尽管stage1_5会被安装,但现代Linux操作系统绝大多数情况下将跳过stage1_5而直接执行和使用stage2;
只有手动安装grub的情况下才会先通过stage1_5获得访问文件系统能力,并通过文件系统访问stage2以及其他信息。
stage2
提供了GRUB的所有功能,包括GRUB启动菜单和交互的GRUB shell以实现GRUB操作管理;
用于管理GRUB配置文件grub.conf,自动和手动加载内核、ramdisk镜像文件系统等工作;
GRUB/GRUB2配置文件
- /boot/grub/grub.conf,/boot/grub2/grub.cfg;
- 用以定义启动分区、内核文件、镜像文件和根文件系统位置;
- 定义多系统启动引导参数。
3.1.3 GRUB核心文件的分布以及加载方法(非grub2)
GRUB会在系统安装的最后阶段写入磁盘,并且可以选择安装在磁盘或者分区头部。
GRUB默认情况下会被安装在磁盘头部(例如/dev/sda):
stage1安装到启动盘的MBR中;
start.S位于0磁道0柱面2扇区;
stage1_5安装于磁盘0磁道0柱面3扇区之后到文件系统的位置。
3.2 GRUB2
3.2.1 grub2介绍
grub2使用img文件,不再使用grub中的stage1、stage1_5和stage2。
grub2将boot.img转换后的内容安装到MBR中的boot_loader部分,将diskboot.img和kernel.img结合成为core.img,同时还会嵌入一些模块或加载模块的代码到core.img中,然后将core.img转换后的内容安装到磁盘的指定位置处。
3.2.2 grub2中的img文件
这部分内容主要来自:grub2详解(翻译和整理官方手册)-CSDN博客
grub2生成了几个img文件,有些分布在/usr/lib/grub/i386-pc目录下,有些分布在/boot/grub2/i386-pc目录下。
boot.img
在BIOS平台下,boot.img是grub启动的第一个img文件,它被写入到MBR,因为boot sector的大小是512字节,所以该img文件的大小也是512字节。
boot.img唯一的作用是读取属于core.img的第一个扇区并跳转到它身上,将控制权交给该扇区的img。由于体积大小的限制,boot.img无法理解文件系统的结构,因此grub2-install会将core.img的位置硬编码到boot.img中,这样就一定能找到core.img的位置。
core.img
core.img根据diskboot.img、kernel.img和一系列的模块被grub2-mkimage程序动态创建。core.img中嵌入了足够多的功能模块以保证grub能访问/boot/grub,并且可以加载相关的模块实现相关的功能,例如加载启动菜单、加载目标操作系统的信息等,由于grub2大量使用了动态功能模块,使得core.img体积变得足够小。
diskboot.img
如果启动设备是硬盘,core.img第一个扇区的内容就是diskboot.img。diskboot.img的作用是读取core.img中剩余的部分到内存中,并将控制权交给kernel.img,由于此时还不识别文件系统,所以将core.img的全部位置以block列表的方式编码,使得diskboot.img能够找到剩余的内容。
该img文件因为占用一个扇区,所以大小为512字节。
cdboot.img
如果启动设备是光驱(cd-rom),core.img第一个扇区的内容就是cdboot.img。它的作用和diskboot.img一样。
pexboot.img
如果是从网络的PXE环境启动,core.img第一个扇区的内容就是pxeboot.img。
kernel.img
kernel.img文件包含了grub的基本运行时环境:设备框架、文件句柄、环境变量、救援模式下的命令行解析器等。很少直接使用它,因为它们已经嵌入到了core.img中。注意,kernel.img是grub的kernel,和操作系统的内核无关。大小约28k。
*.mod
各种功能模块,部分模块已经嵌入到core.img中,或者会被grub自动加载,但有时也需要使用insmod命令加载。
3.2.3 grub2加载流程
本节主要来自《Linux操作系统学习——启动.md》。
boot.img由boot.S编译而成,512字节,安装在启动盘的第一个扇区,即MBR。由于空间有限,其代码十分简单,仅仅是起到一个引导的作用,指向后续的核心镜像文件,即core.img。core.img包括很多重要的部分,如lzma_decompress.img、diskboot.img、kernel.img等,结构如下图。
整个加载流程如下:
1、boot.img加载core.img的第一个扇区,即diskboot.img,对应代码为diskboot.S
2、diskboot.img加载core.img的其他部分模块,先是解压缩程序 lzma_decompress.img,再往下是 kernel.img,最后是各个模块 module 对应的映像。这里需要注意,它不是 Linux 的内核,而是 grub 的内核。注意,lzma_decompress.img 对应的代码是 startup_raw.S,本来 kernel.img 是压缩过的,现在执行的时候,需要解压缩。
3、加载完core之后,启动grub_main函数。
4、grub_main函数初始化控制台,计算模块基地址,设置 root 设备,读取 grub 配置文件,加载模块。最后,将 GRUB 置于 normal 模式,在这个模式中,grub_normal_execute (from grub-core/normal/main.c) 将被调用以完成最后的准备工作,然后显示一个菜单列出所用可用的操作系统。当某个操作系统被选择之后,grub_menu_execute_entry 开始执行,它将调用 GRUB 的 boot 命令,来引导被选中的操作系统。
在这之前,我们所有遇到过的程序都非常非常小,完全可以在实模式下运行,但是随着我们加载的东西越来越大,实模式这 1M 的地址空间实在放不下了,所以在真正的解压缩之前,lzma_decompress.img 做了一个重要的决定,就是调用 real_to_prot,切换到保护模式,这样就能在更大的寻址空间里面,加载更多的东西。
开机时的16位实模式与内核启动的main函数执行需要的32位保护模式之间有很大的差距,这个差距谁来填补?head.S做的就是这项工作。就像 kernel boot protocol 所描述的,引导程序必须填充 kernel setup header (位于 kernel setup code 偏移 0x01f1 处) 的必要字段,这些均在head.S中定义。在这期间,head程序打开A20,打开pe、pg,废弃旧的、16位的中断响应机制,建立新的32位的IDT……这些工作都做完了,计算机已经处在32位的保护模式状态了,调用32位内核的一切条件已经准备完毕,这时顺理成章地调用main函数。后面的操作就可以用32位编译的main函数完成,从而正式启动内核,进入波澜壮阔的Linux内核操作系统之中。
3.4 操作grub shell
当系统正常加载GRUB后,下一步的启动需要依靠GRUB指定必要的配置信息:
- 内核和镜像文件系统所在分区
- 内核文件名称和位置、根文件系统位置、根文件系统挂载方式以及其他启动选项。
- 镜像文件系统名称和位置。
启动系统时当出现grub菜单界面,通过按“c”可进入grub shell指定启动信息。
(补充操作图)
3.5 grub配置文件
grub2配置文件中的部分关键字
default=N 使用第几个启动项
timeout=5 启动项菜单停留时间
3.3. GRUB2使用实践——安装双系统
排查某个性能问题过程中,需要修改内核为可抢占模式和原生系统(CPU不可抢占)进行IO性能对比测试。
1. 修改linux源码下.config,设置可抢占式为yes,在Makefile文件中在版本号中添加一些字符避免和原系统的版本号一致,我这里添加的就是_jr。
2. 编译内核(make -j 88)、打包成rpm包make binrpm-pkg;
3.在/root/rpmbuild/RPMS/x86_64获取打包完成的kernel-xxx_jr.x86_64.rpm
4. 使用命令rpm -ivh kernel-xxx.x86_64.rpm将编译的新系统安装到机器上。可以看到/boot目录下增加了一个系统配置文件config-xxx_jr.x86_64、镜像文件initramfs-xxx_jr.x86_64.img、内核文件vmlinuz-xxx_jr.x86_64。在/lib/modules下多了一个目录xxx_jr.x86_64,其中包括了编译出的各模块的ko。
5.在grub2配置文件/boot/grub2/grub.cfg中新增一个menuentry项,将其中关键字linux设置为vmlinuz-xxx_jr.x86_64,修改menuentry的名称,将关键字initrd设置为initramfs-xxx_jr.x86_64.img,将关键字timeout增大为10便于在启动到grub菜单界面时来得及选择启动项。
6.重启机器,在启动到grub菜单界面时,选择我们新建的启动项,计算机便会加载我们新编译的内核和模块。
4.内核与镜像文件系统的加载
内核加载阶段的主要工作:
- 内核文件被解压;内核被装载到内存中并执行初始化;
- 挂载镜像文件系统并以只读的方式加载其中的静态驱动模块;
- 加载基本的设备驱动和文件系统后,退出镜像文件系统,识别和挂载真正的根分区。
系统内核文件:
通过kernel包安装,位于/boot目录下,命名命名:vmlinuz-<version>。加载并执行内核后,系统将具有进程管理、内存管理等核心能力。
系统镜像文件系统
- 命名initramfs-<version>.img,kernel安装之后通过dracut建立。在linux系统上通过命令"rpm -qa --scripts kernel"可以查看到rpm的postinstall阶段中有以下命令:/sbin/new-kernel-pkg --package kernel --mkinitrd --dracut --depmod --update 5.10-xxx.xx"。这个命令就会在/boot目录下产生initramfs-xxx.img。
- 镜像文件系统中包含了一些基本的设备驱动、文件系统ko文件,通过安装这些模块,内核可以获得基本的访问磁盘、网卡设备的能力,例如包含有:iscsi、ext4、device_mapper等模块。
5. 系统初始化及服务加载
5.1 init进程
历史上,Linux 的启动一直采用init进程(1号进程)。可以通过以下命令启动服务:
/etc/init.d/apache2 start
或
service apache2 start
不过这种方法有两个缺点。
一是系统启动时间长。init进程是串行启动,只有前一个服务启动完,才会启动下一个服务。
二是启动脚本复杂。init进程只是执行启动脚本,不管其他事情。脚本需要自己处理各种情况,这往往使得脚本变得很长。
5.2 systemd
Systemd 就是为了解决init的问题而诞生的。它的设计目标是,为系统的启动和管理提供一套完整的解决方案。
根据 Linux 惯例,字母d是守护进程(daemon)的缩写。 Systemd 这个名字的含义,就是它要守护整个系统。
使用了 Systemd,就不需要再用init了。Systemd 取代了initd,成为系统的第一个进程(PID 等于 1),其他进程都是它的子进程。
Systemd 并不只是一个进程或一个命令,而是一组命令,涉及到系统管理的方方面面。详细可参考文章:全面易懂的 Systemd 服务管理教程
5.3 系统启动流程
- 运行systemd进程(https://linux.cn/article-5457-1.html1号进程);
- systemd执行的第一个目标是default.target。但实际上default.target是指向graphical.target的软链接。文件Graphical.target的实际位置是/usr/lib/systemd/system/graphical.target。在下面的截图里显示了graphical.target文件的内容。Requires=multi-user.target,表示该target依赖multi-user.target,在当前target启动时同时启动multi-user.target,若multi-user.target启动失败,则当前target也跟着终止。下图中,Wants字段也类似Requires表示依赖关系,不过Wants=后的display-manager.service启动失败不影响当前target的启动。
- multi-user.target这个target将自己的子单元放在目录“/etc/systemd/system/multi-user.target.wants”里。这个target为多用户支持设定系统环境。非root用户会在这个阶段的引导过程中启用。防火墙相关的服务也会在这个阶段启动。若某个unit的WantedBy=multi-user.target,则该unit被激活时,其符号链接将被放入到目录 “/etc/systemd/system/multi-user.target.wants/“。启动 multi-user.target时,在这个组里的所有服务,都将开机启动。
- "multi-user.target"会将控制权交给另一层“basic.target”
- "basic.target"单元用于启动普通服务特别是图形管理服务。basic.target之后将控制权交给sysinit.target。
- "sysinit.target"会启动重要的系统服务例如系统挂载,内存交换空间和设备,内核补充选项等等。sysinit.target在启动过程中会传递给local-fs.target。这个target单元的内容如下面截图里所展示。
- local-fs.target,这个target单元不会启动用户相关的服务,它只处理底层核心服务。这个target会根据/etc/fstab和/etc/inittab来执行相关操作。
5.4 通过systemd实现开机自动挂载
目的:实现开机自动挂载指定的NAS存储"22.22.121.121:/NAS-01"到目录/mnt/point上。
操作如下:
在目录/etc/systemd/system下新建文件mnt-point.mount,文件名和文件内容中Where字段所指示的挂载点要对应,其中挂载点路径中的“/”转换为文件名中的“-”。在文件中填写以下内容。
[Unit]
Description=mount nas
Wants=network.target
[Mount]
What=33.33.133.133:/NAS-01
Where=/mnt/point
Type=nfs
[Install]
WantedBy=multi-user.target
其中WantedBy表示Wants当前服务的模块,它的值是一个或多个 Target,当前 Unit 激活时(enable)符号链接会放入 /etc/systemd/system 目录下面以 <Target 名> + .wants 后缀构成的子目录中。
执行以下命令,激活Unit
systemctl enable mnt-point.mount
激活后可以看到在/etc/systemd/system/multi-user.target.wants目录下生成了一个软链接mnt-point.mount指向我们刚创建的文件/etc/systemd/system/mnt-point.mount。重启系统,就能自动挂载指定的nas存储。
如果想立即启动这个mount服务,挂载这个nas,执行以下命令即可:
systemctl start mnt-point.mount