跳到文章

操作系统是如何启动的

花了一下午时间,成功安装了 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 initSystemd 是常见的两种 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 启动流程。

参考