这一章回答:从固件交棒到内核交出控制权,中间经过了什么;以及内核通过哪些"窗口"让人看见它。
机器刚上电时,内存是空的、磁盘不会自己读自己。必须有一段不依赖操作系统的代码,把操作系统从磁盘搬进内存并执行。这就是"引导"的全部意义。
引导完成后,内核接管一切:它管 CPU 调度、管内存、管磁盘、管网络。但内核是不能直接对话的(它在特权级),于是它开了一组文件系统窗口,让用户态能读它的状态、改它的行为。
现代 x86 机器上电后先跑 UEFI 固件(老机器是 BIOS)。UEFI 比 BIOS 聪明得多:它能读分区表、读文件系统。
关键概念 ESP(EFI System Partition):
- 一个 FAT32 格式的小分区,通常 100~512 MB,挂载点在
/boot/efi。 - 分区类型 GUID 固定(
C12A7328-F81F-11D2-BA4B-00A0C93EC93B),UEFI 靠它认出这是 ESP。 - 里面放的是
.efi可执行文件,比如 Debian 的\EFI\debian\grubx64.efi。 - UEFI 的启动项(NVRAM 里的一条记录)指向 ESP 中的某个
.efi文件。
$ efibootmgr -v # 查看/管理 UEFI 启动项
$ ls /boot/efi/EFI/ # 看 ESP 里装了哪些引导器
$ lsblk -f # 找 FAT32 + 挂载在 /boot/efi 的分区就是 ESP
⚠️ 常见事故:装了双系统后开机直接进 Windows,是因为 Windows 更新重写了启动顺序。用efibootmgr -o调回即可,不需要重装系统。
传统 BIOS + MBR 方案:MBR 前 446 字节存引导码 → 跳到分区里的 GRUB 第二阶段。容量受限、一个磁盘最多四个主分区,正在被淘汰,但嵌入式和老服务器上还能见到。
GRUB 是引导程序(bootloader)。它的工作就两件:给用户一个菜单,然后把内核加载进内存。
三处需要知道的位置:
| 文件/命令 | 作用 |
|---|---|
/boot/grub/grub.cfg |
生成的最终菜单配置。不要手改,会被覆盖 |
/etc/default/grub |
用户该改的地方(默认项、超时、内核参数) |
/etc/grub.d/ |
生成脚本,按序拼出 grub.cfg |
改完 GRUB_CMDLINE_LINUX_DEFAULT 后必须重新生成:
# Debian / Ubuntu
$ sudo update-grub
# RHEL / Fedora 系
$ sudo grub2-mkconfig -o /boot/grub2/grub.cfgGRUB 命令行(排障时会用到):
grub> ls # 列出 (hd0,gpt2) 这类设备
grub> set root=(hd0,gpt2)
grub> linux /vmlinuz root=/dev/nvme0n1p5 ro
grub> initrd /initrd.img
grub> boot
典型的 grub.cfg 条目长这样,注意 linux 那一行后面就是内核参数:
menuentry 'Debian GNU/Linux' {
linux /boot/vmlinuz-6.x root=UUID=xxxx-xxxx ro quiet splash
initrd /boot/initrd.img-6.x
}
GRUB 给了内核两样东西:内核镜像(vmlinuz)和初始化内存盘(initrd.img / initramfs)。
为什么需要 initramfs? 鸡生蛋问题:要挂载根文件系统,需要磁盘驱动;而磁盘驱动模块可能存在根文件系统里。于是先加载一个内存中的小系统,由它加载驱动,再切换到真正的根。
内核启动 → 挂载 initramfs 为临时 / → 跑 /init 脚本
→ 加载存储/LVM/加密驱动 → 找到真根 → switch_root → 执行 /sbin/init
$ lsinitramfs /boot/initrd.img-$(uname -r) | head # 看 initramfs 里有什么
$ sudo update-initramfs -u # 改完 fstab/crypttab 后重建内核参数是引导阶段最后的可调项,运行时可以查看:
$ cat /proc/cmdline
BOOT_IMAGE=/boot/vmlinuz-6.x root=UUID=... ro quiet splash最常见的几个参数:
| 参数 | 用途 |
|---|---|
root= |
指定根文件系统 |
ro / rw |
以只读/读写方式挂载根 |
quiet |
屏蔽启动日志(排障时删掉它) |
single / 1 |
进入单用户模式(救援) |
systemd.unit=rescue.target |
只起救援 target |
init=/bin/bash |
跳过 init 直接进 shell(终极救援) |
内核到底包含哪些功能?由编译时的配置决定。这套配置系统叫 Kconfig。
它的逻辑是:源码树里散布 Kconfig 文件,声明各种选项和依赖关系;make menuconfig 读取它们并生成交互界面;你的选择存进 .config;编译时按 .config 决定哪些编进内核(=y)、哪些编成模块(=m)、哪些不要(is not set)。
$ make menuconfig # 图形化配置(基于 ncurses)
$ make -j$(nproc) # 编译
$ make modules_install && make install
$ grep CONFIG_EXT4_FS .config # 查某个选项当前值顺带一提:Kconfig 这套"配置语言"太顺手了,被很多项目借用。Buildroot 就是典型 —— 一个用 Kconfig + Makefile 搭建嵌入式 Linux 的框架,
make menuconfig选包、选 libc、选内核,然后一键交叉编译出整个根文件系统。学习 Buildroot 时不要被它的界面骗了,它和内核是两套独立的 build 系统,只是共享了配置语言。现代内核还有
defconfig(按架构的默认配置,如x86_64_defconfig)和localmodconfig(按当前机器实际模块生成最小配置)。
内核把状态和接口挂成了文件系统,这叫伪文件系统(pseudo filesystem)——内容是内存里现算的,不占磁盘。
| 挂载点 | 名字 | 放什么 |
|---|---|---|
/proc |
procfs | 进程信息、内核参数、硬件信息 |
/sys |
sysfs | 设备模型、驱动、总线、电源管理 |
/dev |
devtmpfs | 设备节点(/dev/sda、/dev/null) |
/run |
tmpfs | 本次开机的运行时状态(PID 文件、socket) |
/sys/fs/cgroup |
cgroupfs | 资源控制组(见下节) |
$ cat /proc/cpuinfo | grep "model name" | head -1
$ cat /proc/meminfo | head -3
$ cat /proc/version
$ ls /sys/class/net/ # 每块网卡一个目录
$ cat /sys/class/net/eth0/mtu
$ cat /proc/loadavg这里有个认知升级:/proc 里的很多文件是可写的,写它就是直接改内核状态。
# 临时让内核转发数据包(重启失效)
$ echo 1 | sudo tee /proc/sys/net/ipv4/ip_forward
# 等价且更规范的写法(持久化见 07/09 章)
$ sudo sysctl -w net.ipv4.ip_forward=1/sys 是设备模型的直接映射,调试硬件问题时比任何工具都底层。比如调屏幕亮度:
$ ls /sys/class/backlight/
$ cat /sys/class/backlight/*/max_brightness内核不必把所有驱动都编进去,可以在运行时按需加载,这叫模块(module)。
$ lsmod # 当前已加载的模块
$ modinfo ext4 # 看某个模块的信息
$ sudo modprobe br_netfilter # 加载(会自动处理依赖)
$ sudo modprobe -r br_netfilter # 卸载
$ dmesg | tail -30 # 看内核日志(模块加载信息在这里)配置持久化(/etc 修改,
| 文件 | 作用 |
|---|---|
/etc/modules-load.d/*.conf |
开机自动加载哪些模块 |
/etc/modprobe.d/*.conf |
加载参数、黑名单(blacklist) |
cgroup(control group) 是内核把一组进程打包、统一限制资源用量的机制。它是容器技术的两大基石之一(另一个是 namespace,见 13)。
能限制什么:CPU 配额、内存上限、IO 带宽、进程数、设备访问权限。
两个版本:
| cgroup v1 | cgroup v2 | |
|---|---|---|
| 目录 | /sys/fs/cgroup/<子系统>/ 各自独立 |
/sys/fs/cgroup/ 统一层级 |
| 特点 | 每个控制器一棵树,能混搭但语义混乱 | 单一树 + cgroup.controllers,语义清晰 |
| 现状 | 老系统、部分容器运行时仍需要 | 现代发行版默认,Docker/K8s 都在切 |
$ mount | grep cgroup # 判断当前是 v1 还是 v2
$ systemd-cgls # 以树形看 cgroup 层级(systemd 是主要使用者)
$ systemd-cgtop # 按 cgroup 看资源占用
$ cat /sys/fs/cgroup/cgroup.controllers # v2:当前可用的控制器和 systemd 的关系:你写的每个 .service 都会被 systemd 自动放进一个 cgroup。所以 unit 文件里可以直接写资源限制,不用手工操作 cgroup 目录:
[Service]
MemoryMax=512M
CPUQuota=50%
TasksMax=64$ systemctl status nginx # 输出顶部的 CGroup 行就是它的位置
$ systemctl set-property nginx MemoryMax=1G按症状对号入座:
| 症状 | 第一步命令 | 说明 |
|---|---|---|
| 起不来,卡在黑屏/BIOS | efibootmgr -v |
检查 UEFI 启动项与 ESP |
| 进 grub rescue | grub-install + update-grub |
GRUB 找不到自己的模块,通常分区变动导致 |
| 内核 panic | 添加 quiet 之外的日志、看 dmesg |
驱动或根分区问题 |
| 找不到根分区 | 检查 /proc/cmdline 的 root= 和 initramfs |
换硬盘/UUID 变了没重建 initramfs |
| 某个硬件没驱动 | dmesg | grep -i error、lsmod、lspci -k |
看驱动是否加载 |
| 系统莫名 OOM | dmesg | grep -i oom、systemd-cgtop |
内存限制或泄漏 |
⚠️ 本仓库红线提醒:不要随意修改/etc/fstab、/etc/default/grub而不测试。改 fstab 前先确认设备标识用 UUID 而非/dev/sdX(顺序会变),改完用sudo mount -a验证再重启。
/proc里的文件不是"磁盘文件",ls -l显示的 0 字节和奇怪大小是正常的。initrd不是"根文件系统",它只是过渡用的内存盘。update-grub只是重新生成配置文件,真正的引导代码在 ESP/MBR 上,装引导器要用grub-install。- 删掉
/boot里的旧内核前先确认新内核能启动,这是经典的自杀操作。 sysctl -w重启即失效,持久化要写/etc/sysctl.d/*.conf。
- 交叉编译一个最小内核的完整实操步骤
- Buildroot 构建嵌入式根文件系统的完整流程
-
grub-mkconfig生成逻辑详解(/etc/grub.d/各脚本职责) - Secure Boot 与 MOK 证书链(自编译内核如何签名)
- systemd-boot 与 rEFInd 等替代引导器
- cgroup v2 手工创建控制组并限制资源的完整示例
- 内核中
/proc/sys与sysctl的映射规则细节 - LoongArch / ARM64 平台的引导差异