Skip to content

Latest commit

 

History

History
252 lines (184 loc) · 10.8 KB

File metadata and controls

252 lines (184 loc) · 10.8 KB

01. 引导与内核

这一章回答:从固件交棒到内核交出控制权,中间经过了什么;以及内核通过哪些"窗口"让人看见它。

这一层解决什么问题

机器刚上电时,内存是空的、磁盘不会自己读自己。必须有一段不依赖操作系统的代码,把操作系统从磁盘搬进内存并执行。这就是"引导"的全部意义。

引导完成后,内核接管一切:它管 CPU 调度、管内存、管磁盘、管网络。但内核是不能直接对话的(它在特权级),于是它开了一组文件系统窗口,让用户态能读它的状态、改它的行为。

一、引导链

1.1 固件阶段:UEFI 与 ESP

现代 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 第二阶段。容量受限、一个磁盘最多四个主分区,正在被淘汰,但嵌入式和老服务器上还能见到。

1.2 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.cfg

GRUB 命令行(排障时会用到):

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
}

1.3 内核与 initramfs

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(终极救援)

1.4 内核的编译与配置:Kconfig

内核到底包含哪些功能?由编译时的配置决定。这套配置系统叫 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:内核的资源管理

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 平台的引导差异

← 上一章:总览 · 返回目录 · 下一章:系统初始化与进程管理 →