固件 (firmware) 是指设备最底层的,让设备得以运行的程序代码。简单理解就是:固定在硬件上的软件。计算机中的许多设备都拥有固件(如硬盘、鼠标、光驱、U 盘等),在计算机启动过程中,最先读取的就是位于主板上的固件,这个固件当前有两种类型:传统的 BIOS 和新的通用性更强的 UEFI。
在上一篇中,我们提到另一种磁盘分区格式 GTP 也是 UEFI 标准的一部分。于是,当前计算机启动中,出现了两种不同的方式:BIOS/MBR 和 UEFI/GTP。
在 linux 操作系统的世界中,同样在经历着变革,系统初始化软件 sysvinit 正逐渐被 systemd 取代。
本文将主要讲述传统的 BIOS/MBR–>sysvinit 启动方式,同时,作为补充,也将简述 UEFI/GTP–>systemd 的启动方式。
BIOS/MBR–>sysvinit
1、BIOS 阶段
系统加电后会立即读取 BIOS 中内容并执行,BIOS 中程序的执行包括两个步骤:
- 加电自检 POST(power-on self test),主要负责检测系统外围设备 (如 CPU、内存、显卡、键盘鼠标等) 是否正常。如果硬件出现问题,主板会发出不同含义的蜂鸣声,启动终止。如果没有问题,屏幕就会显示出 CPU、内存、硬盘等信息。
- 自检完成后,BIOS 会执行一段程序来枚举本地设备 (如光盘、U 盘、硬盘、网络等,可以在 BIOS 中设置枚举顺序) 寻找下一阶段的启动程序所在位置。BIOS 会将控制权交给启动顺序 (Boot Sequence) 中排在第一位的设备,此时,计算机读取该设备中的最前面的 512 个字节,如果这 512 个字节的最后两个字节是 0x55 和 0xAA(Magic Number),表明这个设备可以用于启动;如果不是,表明该设备不能用于启动,控制权于是转交给启动顺序中的下一个设备。如上一篇所述,硬盘中的最前面的 512 字节即为主引导记录 MBR。
2、MBR 阶段
前一篇中我们描述过 MBR 的结构,其中包括 446 字节的 Bootloader,64 字节的 DPT 和 2 字节的 Magic Number。
Bootloader(引导加载程序) 中较常用的一种是 grub,grub 引导分为两个阶段 (有些 grub 还定义了 1.5 阶段):
- BIOS 将 stage1 载入内存中的指定位置 (0x7C00) 并跳转执行,stage1 的内容即为 MBR 中起始的 446 字节;此阶段执行作用主要是将硬盘 0 磁头 0 磁道 2 扇区的内容载入到内存 0x8000 处并跳转执行。
- 由于 stage2 的代码 (较大) 存放在文件系统下的 /boot 分区中(或者 /boot 没有单独分区的 /etc/),因此识别 stage2 文件需要文件系统环境(此时还只能直接读取硬盘指定位置的内容,并不能识别文件系统)。stage1.5 的作用就是为 stage2 提供文件系统环境,使系统能够找到位于文件系统中的 stage2 文件。
- stage2 被载入内存并执行,它首先会解析 grub 的配置文件 menu.lst 即 /boot/grub/grub.conf,该文件中指定了系统内核文件所处的位置,如果没有找到该文件,就会执行一个 shell,等待用户手动指定内核文件的位置。此阶段的最终状态就是执行 boot 命令,将内核和 initrd 镜像载入内存,进而将控制权交给内核。
grub.conf 内容 (版本:GNU GRUB 0.97):
|
|
文件中 # 开头的行是注释行,最重要的部分是两个 title 下面指定的内核位置及具体文件 (kernel 和 initrd 项)
3、内核阶段
grub 的 stage2 将 initrd 文件加载到内存中,内核于是开始执行 initrd 中的 init 文件,此文件是一个脚本,主要作用是加载各种存储介质相关的设备驱动程序。当所需的驱动程序加载完成后,会创建一个根设备,然后将根文件系统 (rootfs) 以只读的方式挂载。这一步结束后,释放未使用的内存,转换到真正的根文件系统中运行程序 /sbin/init,启动系统 PID 为 1 的进程。此后系统的控制权就交给 /sbin/init 进程了。
4、init 阶段
当 init 进程接管了系统的控制权之后,它首先会读取 /etc/inittab 文件,此文件描述了在特定的运行级别 (runlevel) 下,init 进程该如何初始化系统。
linux 中定义了 7 种运行级别:
- 0 表示关机
- 1 表示单用户模式
- 2 表示无网络的多用户模式
- 3 表示多用户模式
- 4 未使用
- 5 表示图形界面模式
- 6 表示重启
inittab 文件中指定了系统的默认运行级别,如 id:3:initdefault: 表示默认运行级别为 3(多用户模式)。
init 进程根据 inittab 文件,运行一系列指定的初始化脚本:
- /etc/rc.d/rc.sysinit 系统初始化脚本,它的作用包括设置主机名和默认网关、决定是否启用 SELinux、加载用户自定义模块、根据文件 /etc/sysctl.conf 设置内核参数、设置 raid 及 LVM 等硬盘功能、重新以读写方式挂载根文件系统等等
- 执行 /etc/rc.d/rc 文件,该文件确认由 inittab 指定的运行级别 N,并启动相应级别下的服务 (通过执行 /etc/rc.d/rcN.d 中的文件),例如运行级别为 3 时,则先执行 /etc/rc.d/rc3.d 下以 K 开头的文件,然后执行以 S 开头的文件。这些文件都是指向 /etc/init.d 下的符号链接。以 K 开头的文件表示此运行级别下需要关闭的服务,以 S 开头的文件表示此运行级别下需要开启的服务。
- 在运行级别 2、3、4、5 中最后一个执行的文件均指向文件 /etc/rc.local, 用户可以在此文件中自定义启动内容。
- 之后根据 inittab 中设置,运行 6 个终端,以便用户登录系统,如果是运行级别 5,则还会执行 /etc/X11/prefdm -nodaemon 启动相应的桌面环境。
- 然后执行 /bin/login 程序用于接收和验证来自 mingetty 的用户名和密码。
至此整个系统即启动完毕了
UEFI/GTP–>systemd
UEFI 的出现是为了代替 BIOS,同样,GTP 和 systemd 也是为了弥补 MBR 和 sysvinit 的不足。和 BIOS 只负责 POST 和找到 MBR 不同,UEFI 将贯穿系统加电到关机的整个过程。粗略划分,UEFI 系统启动分为 4 个阶段:
1、UEFI 初始化阶段
- SEC(安全验证):接收并处理系统启动和重启信号,初始化临时存储区域,传递系统参数给下一阶段 (即 PEI)。
- PEI(EFI 前期初始化):为 DXE 准备执行环境,将需要传递到 DXE 的信息组成 HOB(Handoff Block) 列表,最终将控制权转交到 DXE 手中。
- DXE(驱动执行环境):根据 HOB 列表初始化系统服务,然后遍历固件中的所有 Driver,当驱动的依赖资源满足时,调度 Dirver 到执行队列执行,直到所有满足条件的 Dirver 都被加载。
2、操作系统加载器作为 UEFI 应用程序运行阶段
- BDS(启动设备选择):初始化控制台设备,加载必要的设备驱动,根据系统设置加载和执行启动项,用户选中某个启动项(或系统进入默认的启动项)后,OS Loader 启动,系统进入 TSL 阶段。
- UEFI 中程序能够识别存储介质上的分区信息和文件系统 (如:fat32),此时会将 /EFI/boot/grub2.efi(位于 GTP 格式硬盘的一个分区 ESP,安装时自动生成) 作为 UEFI 应用程序运行。
- TSL(临时系统加载):操作系统加载器 (OS Loader 也位于 ESP 分区) 执行的第一阶段,在这一阶段 OS Loader 作为一个 UEFI 应用程序运行,系统资源仍然由 UEFI 内核控制。当启动服务的 ExitBootServices()服务被调用后,系统进入 RT(Run Time)阶段。
3、操作系统运行阶段
RT(运行时):系统的控制权从 UEFI 内核转交到 OS Loader 手中,UEFI 占用的各种资源被回收到 OS Loader,仅有 UEFI 运行时服务保留给 OS Loader 和 OS 使用。随着 OS Loader 的执行,OS 最终取得对系统的控制权。
在 init 作为系统初始化程序时,服务是通过 /etc/rc.d/init.d 中的脚本来管理并且是顺序执行的,当使用 systemd 作为系统初始化程序后,这些脚本被服务单元替换,并尽可能的并行启动进程。
在 systemd 中,一个单元配置文件可以描述如下内容之一:
- 系统服务 (.service)
- 挂载点 (.mount)
- 套接字 (.sockets)
- 系统设备 (.device)
- 交换分区 (.swap)
- 文件路径 (.path)
- 启动目标 (.target)
- 由 systemd 管理的计时器 (.timer)
- ….
systemd 为保持向下兼容性还保留了一些 init 命令和概念,但所对应的文件都是指向 systemd 对应命令或文件的符号链接:
|
|
systemd 启动后执行的第一个目标是 default.target,但实际上 default.target 是指向 graphical.target 的符号链接。
|
|
其中 Requires 行指明了本单元的依赖关系 (其他各项意义可以通过命令 man systemd.unit 查看),顺着此文件,可以找到需要执行的单元:multi-user.target、basic.target、sysinit.target、local-fs.target swap.target、local-fs-pre.target。
4、关机阶段
AL(After-life):当系统硬件或操作系统出现严重错误不能继续正常运行时,固件会尝试修复错误,这时系统进入 AL 期。UEFI 标准并没有定义此阶段的行为和规范。系统供应商可以自行定义。
相关命令
init
1、init
init 除了在系统初始化时起的重要作用外,还可以用来执行关机、重启、切换运行级别的作用:
|
|
2、runlevel 显示运行级别
|
|
输出中 N 表示当前运行级别,如果系统启动后切换过运行级别,则输出类似于 3 5 表示之前运行级别为 3,现在的运行级别为 5。
3、halt reboot poweroff shutdown
这几个命令的作用就是关机或重启系统,通常只使用 shutdown 就可以了:
|
|
4、chkconfig 更新或查询服务的运行级别信息
|
|
5、service 运行服务脚本 (服务脚本位于 /etc/init.d 内,service 本身也是脚本,位于 /sbin 内)
|
|
systemd
systemd 并不是一个命令,而是一组命令,涉及到系统管理的方方面面。
1、systemctl 控制 systemd 系统和管理服务
|
|
如切换运行级别或开关机:
|
|
系统服务单元相关:
|
|
系统和服务管理:
|
|
还有许多其他选项,这里就不一一列举了。
2、systemd-analyze 查看启动用时
|
|
输出显示了系统启动过程中各部分耗时
|
|
3、systemd-cgls 递归显示控制组 (Cgroups) 信息
linux 内核从版本 2.6.24 开始,引入了一个叫做控制组 (control groups) 的特性,是用于限制、记录、隔离进程组(process groups)所使用的物理资源(如:cpu,memory,IO 等等)的机制。关于 Cgroups 的内容本文不再展开。
|
|
4、systemd-cgtop 显示各控制组的使用量 (CPU, 内存, IO)
显示效果类似命令 top
|
|
5、systemd-loginctl 控制 systemd 登录管理
此命令是命令 loginctl 的符号链接
|
|
6、timedatectl 系统时间和日期控制
|
|
7、hostnamectl 系统主机名控制
|
|
以上 systemd 相关所有命令 (除 systemd-cgls 和 systemd-cgtop 外),都可以使用选项 -H 指定远程基于 systemd 的主机 (使用 ssh 协议):
|
|
systemd 功能强大,使用方便,但也比较复杂,体系庞大。本文只介绍一点相关命令,更多内容就不在此展开了。
本文简述了传统的BIOS和新的UEFI启动流程,介绍了init和systemd部分相关命令。