自己跑了一遍 Linux 启动流程,这回算是理解了大学教材里的启动流程了
UEFI
↓
bootloader
↓
加载 kernel 和 initramfs
↓
kernel 启动
↓
kernel 使用 initramfs 作为临时 root
↓
initramfs 找到真正 rootfsUEFI
理解 UEFI,我们需要先理解 BIOS
BIOS 是在主板的硬件上,预先烧录好的一段程序(这个程序本身是软件)
严谨的说,UEFI 就是 BIOS 的现代形态,是两种不同的固件规范。但是在日常生活中,我们说刷 BIOS,进 BIOS,实际上进入的是 UEFI
UEFI 从 FAT 分区读取引导文件(如 grubx64.efi)
它的职责:找到并加载 bootloader 到内存里
要点:在 FAT 分区里找
bootloader
grubx64.efi 这个引导文件(可执行程序),就是 bootloader
bootloader 有好几种,常见的是:
(1)
如果只装 Windows,是叫做 bootmgfw.efi。
(2)
GRUB。GNU 项目的默认,大多 Linux 发行版走这一个,功能强大而复杂,支持双系统。
(3)
systemd-boot。systemd 项目发起。简洁、轻量。
进入 Windows 系统之后,你是看不到 efi 文件的,因为 Windows 相当于只挂载了硬盘的一部分(NTFS 那部分),而 efi 是在那个 FAT32 里面,大概几百 MB 的样子的分区
Linux 同理,/挂载的是 ext4 那个分区,而你的 efi 是在 FAT32 分区里,除非用 mount 重新挂载 /dev/sda1这种
它的职责:读取磁盘上的内核文件 vmlinuz,把内核加载到内存 RAM
/boot 目录与挂载
实际上,内核在 /boot/ 下面
问题:为什么 bootloader 也在 boot 下面?它不是在另一个分区吗,那个 ESP 分区,FAT32
解答:
bootloader 不是挂载到 /boot/ 下的,那样确实会盖掉内核
实际上我们做了一个操作:
先把内核复制到 tmp,挂载 ESP 分区,然后把内核和 initramfs 复制到 ESP 分区里(因为 rootfs 镜像里内核先落到了 ext4 的 /boot,挂 ESP 会盖住它)
这种是我们通过 systemd-boot 启动的常见操作
还有一种:把 ESP 挂载到 /boot/efi(GRUB 自己能读 ext4)
这种的 /boot/ 是来源于 ext4 分区里的,因为挂 ESP 在 /boot/efi,所以不会覆盖 /boot
/dev 是什么?
dev 是 device 的缩写
/dev 目录是 Linux 中的一个虚拟目录,用来代表“可读写的存储设备”,块设备文件
/dev/sr0 光驱、ISO 镜像
/dev/vda VirtIO 虚拟磁盘
/dev/sda SATA/SCSI/USB 磁盘
/dev/tty 当前终端
/dev/null
/dev/random
关键命令:lsblk -o NAME,MODEL,SIZE,TYPE,FSTYPE,MOUNTPOINTS
/ 的挂载 与 硬盘分区
我们之前也提到了
一个物理硬盘一般分两个区:
systemd-boot(我这次用的):
/dev/sda 整块硬盘
/dev/sda1 EFI 分区(FAT32,挂到 /boot,内核和 initramfs 也在这里面)
/dev/sda2 Linux 根分区systemd-boot 只认 FAT32,所以内核和 .efi 文件必须一起挤在 ESP 上
GRUB 方案(提一嘴):
/dev/sda1 EFI 分区(FAT32,挂到 /boot/efi,只放 grubx64.efi)
/dev/sda2 /boot 分区(ext4,放内核和 initramfs)
/dev/sda3 Linux 根分区GRUB 内置了 ext4 驱动,能从 ext4 分区读内核,所以多一个 /boot 分区
Linux 就是把根分区(对应上面的 sda2 或 sda3)挂载到/
鸡和蛋的问题是:
既然 / 是从 /dev/sda3 挂载来的,那 /dev/sda3 又在 / 里面
这是因为 /dev/sda3 是你进入系统后看到的路径,它启动时,看到的是 UUID,然后将 UUID=某个值的分区,当作 /
即 / 是某个块设备里的文件系统被挂载后的视图
findmnt 查看 / 到底来自哪个设备
Windows 和 Linux 的区别是,多根系统和单根系统
Windows 就是把有 Windows 的那个分区找到,当作 C。其他分区,自动挂载到 D 之类的。
Linux 就是 Linux 内核在的那个分区,当作 / ,然后其他分区,继续挂载到 / 下面(不一定是/mnt 下面,任何位置都行,mnt 只是为了表明,就是外挂的)
mount 时,原目录的内容会被盖住
文件系统
一个分区本质上是一大段连续的存储空间
区域本身只是“裸空间”,你需要看到文件/文件夹,就需要在上面创建文件系统
关键命令:mkfs.ext4 /dev/sdb1
ext4 是最经典、稳定、通用,Linux 默认常见选择
btrfs 支持快照、子卷、校验、压缩等高级功能
xfs 大文件、大吞吐场景强,服务器常用
内核加载
在已经启动好的 Linux 里,内核就在:
/boot/vmlinuz-xxx
(即使是物理机,大概率也是叫 vmlinuz,z 表示 gzip,即压缩过,vm 表示虚拟内存技术,当时的革命性技术)
内核和普通程序是一样的,都是 ELF 文件,运行时都在内存,磁盘上都有一个对应的文件
区别是,内核常驻内存,内核由 bootloader 启动
如何找到内核:
/boot/loader/entries/arch.conf 是 systemd-boot 的一个启动项配置文件
UEFI 只知道先启动 BOOTAA64.EFI,也就是 systemd-boot,接下来要靠 arch.conf 告诉它:(1)加载哪个 Linux 内核(2)加载哪个 initramfs(3)内核启动时要带哪些参数
例如:
title Arch Linux ARM
linux /Image
initrd /initramfs-linux.img
options root=/dev/vda2 rw console=ttyAMA0,115200 console=tty0/Image 这里路径 systemd-boot 里的路径不是看 Linux 运行后的挂载点,而是看 ESP 分区内部的路径
即,我们之前提到了,你的内核实际上是拷贝到 ESP 里了,所以这里不管你怎么挂载 ESP,都是 /Image
bootloader 不知道 /boot 还是 /efi,它只知道 ESP 分区
/boot/loader/loader.conf 控制 systemd-boot 自己
initramfs
init - ram - fs
一个很小的临时根文件系统,被 bootloader 和内核一起加载到内存里
帮内核完成“找到真正根分区”的前置工作(真实根分区可能很复杂,在 NVMe 盘上,被加密了等等)
先用 initramfs 作为临时 /,加载必要模块、识别磁盘,最后切换到真正的根文件系统
这叫做 switch_root
mkinitcpio
mkinitcpio 把“内核启动早期需要的驱动、脚本、工具”打包成 initramfs-linux.img
我这次安装里没有手动跑 mkinitcpio,因为 Arch Linux ARM rootfs 已经自带了
HOOK
内核已经 Load 进入 RAM 之后,它需要加载 rootfs
它需要读取 ext4 的磁盘,但是磁盘驱动本身,在/lib/modules 里,这又是一个鸡蛋问题
所以此时,内核通过加载 initramfs 来解决鸡蛋问题
mkinitcpio 生成 initramfs 的过程,是按照 HOOK 顺序执行
工种按顺序进场:
- base → 搭脚手架(放 BusyBox、基本 shell 工具)
- udev → 接水电(放设备探测工具)
- autodetect → 量尺寸(检测当前硬件,只打包需要的驱动,缩小体积)
- modconf → 搬材料(放额外配置文件)
- block → 铺管道(放磁盘控制器驱动,比如 virtio_blk.ko)
- filesystems → 铺地板(放文件系统驱动,比如 ext4.ko)
- keyboard → 装门锁(放键盘驱动,如果你要用 LUKS 密码解锁磁盘)
- fsck → 验收(放 fsck 工具检查文件系统)
全部装好 → 打包成 initramfs → 完成
/etc/fstab
Linux 开机时的自动挂载表
哪个分区挂到 /
哪个分区挂到 /boot
用什么文件系统
bootloader 也会知道 rootfs 的 UUID
chroot bootctl
目标 Arch 被挂在/mnt
chroot /mnt /bin/bash 把 /mnt 暂时当成新的 /
bootctl --esp-path=/boot install
把 systemd-boot 的 EFI 程序复制到 ESP
/boot/EFI/BOOT/BOOTAA64.EFI 是 ARM64 UEFI 的默认 fallback 启动路径。即使 bootctl 没法写 NVRAM 启动项,UTM 的 UEFI 也能从这里找到 bootloader