操作系统是如何启动的
花了一下午时间,成功安装了 Arch Linux, 收获颇丰。在此以 Arch Linux 的安装为例来聊一聊"操作系统是如何启动的"。虽然是以 Arch 为例,但其实所有的操作系统(手机和电脑操作系统)都大同小异,我也不想列出所有系统的详细启动流程。先上图为敬
![](/images/操作系统是如何启动的/OS startup process.png)
首先,你的电脑有一堆硬件,CPU、内存、硬盘都与主板相连,主板上有 UEFI 固件。
UEFI
UEFI 全称是 Unified Extensible Firmware Interface, 一个统一的、可扩展的、硬件和操作系统之间的固件接口。大家都喜欢统一, 统一意味着只需要实现一套标准;大家也都喜欢接口,接口抽象程度高;大家更喜欢可扩展,可以自己扩展功能。UEFI 是 BIOS 的继任者,近几年的电脑都是使用 UEFI 的。因为 BIOS 必须从硬盘(BIOS 设置里的第一块)的前 512 Bytes(称为 Master Boot Record) 开始加载。并且只有前 440 Bytes 是提供给 Bootloader 修改定义的。由于空间太小了,能做的事情很有限,做 Bootloader 的表示心里苦。
但 UEFI 就不一样了,UEFI 能识别文件系统,意味着你可以把 Bootloader 的可执行文件放在硬盘中的任何一个地方。存放 Bootloader 可执行文件的地方叫做 EFI System Partition, 一般为 FAT32 分区。
Bootloader
Bootloader, 顾名思义,就是启动加载器。你电脑有多个操作系统,你告诉 Bootloader 去启动哪个,它就去启动那个系统,加载它的 Kernel. Linux 下有名的 Bootloader 是 grub, Windows 下是 winload. Android 刷机的第一步就是要解锁 Bootloader, 解锁之后才能启动第三方系统和 Recovery. 其实,Bootloader 并不是必需的, Linux 支持通过 EFISTUB 直接从 UEFI 启动,但这样就不支持多系统了。
上图就是 grub 的界面,可以让你选择操作系统来启动(目前只有一个 Manjaro)。
安装 Arch
大致了解了 UEFI 和 Bootloader 后,就可以开始安装 Arch 了。安裝操作系統本质上就是把操作系统的文件放在某个位置(可以是硬盘,也可以是网络位置,一般是硬盘)上,在告诉 Bootloader 如何启动这个操作系统。所以多系统也很简单,把不同操作系统的文件放在不同的分区,然后告诉 Bootloader 这些系统应该如何启动。我们只要在 grub 里选择,就能加载不同的操作系统。为了方便,这里在虚拟机里安装 Arch.
装个 VirtualBox, 下载 archlinux 的镜像。配置一下虚拟机,记得勾选 Settings->System->Enable EFI, 不然会启动不了的。
说在前面:值得注意的是,下载的 arch iso 镜像只是给你一个预安装环境,类似于 Live CD 模式。我们需要在这个 Pre-install 的环境下把 Arch 安装到硬盘上。
分区
之前提到, UEFI 会去 EFI System Partition(简称 ESP) 里加载 Bootloader(或者直接加载 Linux Kernel). 那首先,我们要准备这样一个分区。首先看看有没有现成的
可以看出,虚拟机添加的硬盘还没有被挂载。使用 fdisk 列出虚拟机连接的硬盘。
![](/images/操作系统是如何启动的/fdisk list.png)
/dev/sda 就是我们为虚拟机添加的硬盘,但可以发现,它还没有初始化。使用 fdisk 来为 /dev/sda 这块硬盘分区
fdisk /dev/sda
按照下图操作
![](/images/操作系统是如何启动的/fdisk partition.png)
- 命令
g
将分区表类型改为 GPT - 命令
n
创建一个新分区。这里创建了两次,第一个大小 550M, 记为 /dev/sda1,第二个大小为剩下所有的空间, 记为 /dev/sda2. 可以发现后面的数字代表分区号 - 命令
t
更改分区类型。这里将 /dev/sda1 改成了我们要的 ESP 分区,550MB 是 ESP 分区的推荐大小 - 命令
w
保存修改并退出 - 命令
m
查看帮助
格式化分区
mkfs.fat -F32 /dev/sda1
mkfs.ext4 /dev/sda2
ESP 通常为 FAT32 格式,Linux 分区通常为 ext4.
挂载分区
mount /dev/sda2 /mnt
mkdir /mnt/boot
mount /dev/sda1 /mnt/boot
将 Linux 分区(sda2)挂载到 /mnt 目录,把 ESP 分区挂载到 /mnt/boot 目录。
为什么要这么挂载呢?
我们现在所处的环境是预安装环境,需要把操作系统所需的文件安装到 Linux 分区(sda2)上,把启动文件安装到 ESP 分区上,所以先挂载这两个分区。当然,你可以把 sda2 挂载到任何新建的目录,然后再把 ESP 分区挂载到那个目录下的 boot 目录。因为一般 ESP 分区都是挂载到 Root Directory 下的 boot 或者 boot/efi 下的。
安装 base packages
pacstrap /mnt base
base 里含有 Linux Kernel, bash, gcc-libs 等基础包。这条命令就是往 sda2 上安装操作系统所需的文件。可以 ls /mnt 看一看,发现它就是一个 Root directory.
生成 fstab 配置文件
genfstab -U /mnt >> /mnt/etc/fstab
fstab 配置文件定义了如何挂载分区到文件系统中,相当于把 mount 命令固化了。启动安装好的系统,会根据这个 fstab 配置文件来挂载分区。
Change root
目前我们所有的操作都是在 Pre-install 环境下的,目前的 Root directory 也是 Pre-install 环境的。现在,我们已经把操作系统所需的文件安装到了 /mnt 目录下,它也是一个完整的 Root directory, 而我们现在需要在新的操作系统的环境下继续配置,而不是 Pre-install 环境。因此,我们需要更换 Root directory, 用的命令就是 chroot. Arch 为我们准备了 arch-chroot 命令。
arch-chroot /mnt
顺利切换到新的 Root Directory, 之前 /mnt 变成了现在的 Root directory. 比如,之前的 /mnt/boot, change root 之后,就变成了 /boot. 所有的命令也是来自新的 Root directory.
如果你之前为 sda2 挂载的目录不是 /mnt, 这里换成相应的目录即可。
Root password
修改新系统的 root 密码。只有 chroot 之后才能改新系统的 root 密码等各种配置
passwd
安装 Bootloader
以 grub 为例
## 安装 grub 和 efibootmgt 工具, pacman 是 Arch 的包管理工具,类似于 Ubuntu 下的 apt.
pacman -Sy grub efibootmgr
## grub-install 将 grub 安装到 /boot 文件夹中
grub-install --target=x86_64-efi --efi-directory=/boot --bootloader-id=GRUB
看一下生成的文件
其中:
- EFI 文件夹里有 grub 的 EFI 可执行文件,UEFI 会在这个文件夹查找并执行 grubx64.efi 来加载 grub
- grub 文件夹里有 grub 的配置文件 grub.cfg
- vmlinuz-linux 就是 linux Kernel, 只不过它是被 zlib 压缩过的,所以是 vmlinuz, 笑
- initramfs-linux.img 就是启动流程中的 Early User Space(initrd) 里用到的镜像
Bootloader
看一看 grub 的配置文件
![](/images/操作系统是如何启动的/grub entry.png)
可以看到 grub 先加载 Kernel,然后再加载 initramfs-linux.
- Kernel 首先初始化内存(分页),CPU, Interrupt
- initrd 动态加载驱动模块。因为驱动种类繁杂,把这些驱动分离到 initrd 中的 initramfs-linux.img 可以有效减少 Kernel 的大小。这一步会创建一个临时的 Root directory 来执行命令,完成之后,会切换到真正的 Root directory. 详见 Initial ramdisk
- Kernel 初始化设备,开始 Init Process, 进入 User Space
- Init Process 是操作系统执行的第一个进程,PID 为1。它负责启动所有的进程,因此它是所有进程的直接或间接父进程。SysV init 和 Systemd 是常见的两种 Init Process. SysV init 是基于 Shell Script 的(想必大家有往 /etc/rc 中添加脚本来设置应用自启的经历),而且是顺序执行的,可想而知启动速度会很慢,因此现在很多发行版已经不把 SysV init 作为 Init Process 了。Systemd 是为了解决 init 的问题而生的,它使用声明式的配置文件,提高了可读性和可维护性;它采用并行执行,极大地提升了启动速度。现在,主流的 Linux 发行版都采用了 systemd 作为 Init Process. 感兴趣的可以看看这篇文章 Rethinking PID 1 来了解 systemd 的起源。
上图是 Tizen 系统中的 systemd, 可以发现 systemd 不仅只做 Init 初始化,还提供日志、网络、cgroup 资源控制等。
- Systemd 启动 getty, 用户输入 username 和 password, 调用 login, 验证通过之后,根据 /etc/passwd 来为该用户启动相应的 shell(e.g. bash 或者 zsh), shell 加载用户的配置文件(e.g. ~/.bash_profile 或者 ~/.zprofile)为用户初始化环境。
完成安装
## 完成 grub 的安装后,退出当前的 change root 环境
exit
## 重启电脑
reboot
总结
Linux 操作系统启动流程主要分以下几步: 主板上的 UEFI 加载 ESP 分区中的 Bootloader; Bootloader 加载 Linux Kernel 和 Initrd; Kernel 初始化内存管理、CPU、中断、设备等,Initrd 加载设备驱动模块; Kernel 启动 Systemd 来初始化 User Space 的应用。
Windows 和 macOS 也是一样的,无非是 Bootloader 的名字不一样,文件系统不一样,启动的流程略有差别而已。当然,grub 也可以引导 Windows, 只要 Windows 的 EFI 文件在 ESP 分区,同时 grub.cfg 里有配置 entry 就行。其实,若真要安装双系统,也不用手动敲这么多命令的,现在很多发行版(不包括 Arch😂)会帮你搞定一切的。只是,了解一些原理上的东西,出错的时候不容易慌。
One more thing
## 查看启动日志
journalctl --boot
## 学习一下 Systemd 的目录结构
systemd-path
最后, 安装好的 Arch 还要经过一系列配置之后才能使用(网络都不可用呢🤣),在此仅为阐述 Linux 启动流程。