第 1 课 镜像到底是什么?
你下载过的 ubuntu-24.04.iso、给树莓派烧录的 2024-xx.img、虚拟机里的 disk.vmdk —— 它们都叫"镜像"。镜像 = 把磁盘(或分区)上的每一个字节, 原样冻结到一个普通文件里。它不是压缩包, 而是一块"可以用软件模拟的硬盘"。
镜像 ≠ 安装包 ≠ 压缩包
- 磁盘镜像 (disk image): 逐字节快照, 里面甚至有"分区表"和"空扇区"。
.img/.iso属于这类。 - rootfs 归档: 只是根目录的文件树 (如
rootfs.tar.gz), 本身不能启动, 需要你装上内核和引导器。 - 容器镜像: Docker 那种分层文件系统快照, 靠宿主内核运行, 不自带内核。
进阶: 常见镜像后缀大图鉴
| 后缀 | 本质 | 典型用途 |
|---|---|---|
.iso | ISO 9660 / UDF 光盘文件系统镜像 (isohybrid 后可 dd 启动) | 刻录安装盘、Live 系统 |
.img / .raw | raw 磁盘或分区镜像 (见第 5 课) | 烧录树莓派/开发板、云平台导入 |
.qcow2 .vhd .vmdk .vdi | 带元数据的虚拟机磁盘容器格式 | KVM / Hyper-V / VMware / VirtualBox |
.wic | Yocto/BSP 产出的可启动整盘镜像 | 嵌入式开发板 |
.cpio.gz | initramfs 归档 (内存根文件系统) | 随内核加载的"临时 root" |
rootfs.tar.* | 纯目录树归档 —— 不是块设备镜像 | 需加工 (分区+引导) 才能启动 |
| OCI / Docker 镜像 | 分层文件系统快照, 不带内核 | 容器运行时 |
动手体检: 5 条命令看清一个陌生镜像
file mystery.img # 识别: DOS/MBR boot sector? Bootable ISO? fdisk -l mystery.img # 列出分区表与分区起止扇区 qemu-img info mystery.vmdk # 虚拟格式的大小/格式/快照信息 losetup -fP mystery.img # 映射成 loop 设备后即可挂载分区 virt-filesystems -a mystery.img --all # libguestfs: 不挂载直接看内容
📚 出处与延伸阅读 (L1)
- Wikipedia · Disk image (概念与历史): en.wikipedia.org/wiki/Disk_image
- QEMU 官方文档 · qemu-img(1): qemu.org/docs/master/tools/qemu-img.html
- libguestfs (virt-* 检查工具族): libguestfs.org
- OCI 容器镜像规范 (对比容器镜像): github.com/opencontainers/image-spec
第 2 课 发行版大不同: Ubuntu / Debian / 还有谁?
做镜像的第一步是选"底料"。所有发行版共享同一个 Linux 内核, 差别在 包管理器、基础库、发布节奏和官方制作工具链。
选底料速查表
| 发行版 | 包管理 | 最小 rootfs | 特点 / 适合 |
|---|---|---|---|
| Debian | apt / .deb | minbase ≈ 85 MB | 稳定性王者; debootstrap 官方亲儿子; 服务器镜像首选 |
| Ubuntu | apt / .deb | ≈ 120 MB | 基于 Debian 但更新潮; cloud-image 官方现成; 驱动/文档最多 |
| Alpine | apk | ≈ 5 MB 🪶 | musl libc + BusyBox; 容器镜像霸主; 但 glibc 软件兼容要费心 |
| Arch | pacman | ≈ 400 MB | 滚动更新, 永远最新; archiso 做 Live 系统极顺手 |
| Fedora | dnf / .rpm | ≈ 300 MB | 红帽系试验田; lorax 做 ISO; SELinux 默认开 |
| Buildroot/Yocto | 无 (源码编译) | 可做到 <10 MB | 嵌入式专用: 一个配置文件, make 出完整镜像, 全自动交叉编译 |
bookworm, Ubuntu 用 noble)。学会一个等于会两个。进阶: glibc vs musl —— 决定底料"体质"的隐藏差异
| glibc (Debian/Ubuntu/Fedora…) | musl (Alpine) | |
|---|---|---|
| 体积 | 数 MB~数十 MB (含 locale) | 几百 KB |
| 兼容性 | 业界标准, 闭源/预编译二进制通吃 | 少数 glibc 专有程序跑不了 (可装 gcompat 兼容层) |
| 国际化 | locale 体系完整 | 较简化 |
| 静态编译 | 可行但费劲 | 天然友好 (busybox 就是 musl 静态编译的) |
包管理器命令对照 (一次记住四家)
| 操作 | apt (Debian/Ubuntu) | dnf (Fedora/RHEL) | pacman (Arch) | apk (Alpine) |
|---|---|---|---|---|
| 安装 | apt install | dnf install | pacman -S | apk add |
| 搜索 | apt search | dnf search | pacman -Ss | apk search |
| 卸载 | apt purge | dnf remove | pacman -R | apk del |
| 升级 | apt update && apt upgrade | dnf upgrade | pacman -Syu | apk upgrade |
| 清缓存 | apt clean | dnf clean all | pacman -Sc | apk cache clean |
三种发布模式 & init 系统
- 固定版: Debian (约 2 年一大版)、RHEL、Ubuntu LTS —— 服务器首选, 求稳。
- 滚动版: Arch、openSUSE Tumbleweed、Void —— 永远最新, 求新求折腾。
- 声明式: NixOS —— 整个系统 (含镜像) 由一份配置文件生成, 可原子回滚, "配置即镜像"。
- init 系统: systemd 是绝对主流; Alpine 用 OpenRC, Void 用 runit, 极简镜像常用 busybox init —— 选底料时别忽略它。
偷懒正道: 官方现成镜像才是"最正宗"的起点
| 发行版 | 官方镜像入口 |
|---|---|
| Debian (generic cloud) | cloud.debian.org/images/cloud |
| Ubuntu (cloud / minimal) | cloud-images.ubuntu.com |
| Alpine (minirootfs ≈3MB) | alpinelinux.org/downloads |
| Fedora (Cloud / CoreOS) | alt.fedoraproject.org/cloud |
📚 出处与延伸阅读 (L2)
- Debian Wiki · Debootstrap: wiki.debian.org/Debootstrap
- Alpine Wiki · Installation / minirootfs: wiki.alpinelinux.org/wiki/Installation
- musl libc 项目主页: musl.libc.org
- NixOS 手册 (声明式系统): nixos.org/manual/nixos/stable
- cloud-init 官方文档: cloud-init.io
- DistroWatch (发行版信息汇总): distrowatch.com
第 3 课 动手: 制作一个镜像的完整流程
不管用哪个发行版、哪个工具, 手工制作磁盘镜像都是同一条流水线。理解了这 8 步, 所有构建工具 (mkosi / live-build / FAI / Packer) 在你眼里都是这条流水线的自动化包装。
完整示例: 从零做一个最小 Debian 12 镜像
# 0) 准备工具 sudo apt install debootstrap qemu-utils e2fsprogs parted grub-pc-bin # 1) 造一个 512MB 空磁盘文件 (稀疏文件, 瞬间完成) truncate -s 512M debian-min.img # 2) 分区: MBR + 一个占满的主分区 parted -s debian-min.img mklabel msdos mkpart primary ext4 1MiB 100% # 3) 挂到 loop 设备, 格式化, 挂载 sudo losetup -fP debian-min.img # 出现 /dev/loop0p1 sudo mkfs.ext4 -F /dev/loop0p1 sudo mount /dev/loop0p1 /mnt # 4) 安装最小 Debian 基础系统 (核心一步!) sudo debootstrap --arch=amd64 --variant=minbase bookworm /mnt \ http://deb.debian.org/debian # 5) chroot 进去配置 (绑定虚拟文件系统) for d in dev proc sys; do sudo mount --bind /$d /mnt/$d; done sudo chroot /mnt echo 'root:changeme' | chpasswd # ⚠ 记住密码! echo '/dev/sda1 / ext4 defaults 0 1' > /etc/fstab apt update apt install -y --no-install-recommends \ linux-image-amd64 systemd-sysv grub-pc locales exit # 6) 写 grub.cfg 并把引导器装进镜像 (在宿主机执行, 更安全) sudo chroot /mnt grub-mkconfig -o /boot/grub/grub.cfg sudo grub-install --boot-directory=/mnt/boot /dev/loop0 # 7) 收尾: 一定要 sync + 逆序卸载 sudo sync && sudo umount -R /mnt && sudo losetup -D # 8) 测试! 🚀 qemu-system-x86_64 -drive file=debian-min.img,format=raw -m 512
grub-install /dev/sda —— 那是你宿主机的真实硬盘! chroot 只切换了文件系统视图, /dev/sda 仍指向物理机。教程里第 6 步特意把 grub-install 留在宿主机执行, 目标是 /dev/loop0。制作时的 10 个注意点 (血泪清单)
- 分区方案要匹配固件: BIOS 配 MBR, UEFI 配 GPT + ESP (FAT32)。想两者通吃就做双引导支持。
- UUID 要写对: 用
blkid查分区 UUID, 写进/etc/fstab和 grub 的root=。设备名/dev/sda1在不同机器上会漂移, UUID 才是身份证。 - initramfs 必须包含根文件系统所需的驱动 (ext4、nvme、usb-storage、dm-crypt…) — 否则内核喊"找不到根分区"。
- 网络配置: 装上
systemd-networkd或ifupdown+ DHCP, 否则镜像开机是"孤岛"。 - 别把自己锁在门外: root 密码 / SSH 公钥 / cloud-init, 至少留一条活路。
- 时区与 locale:
dpkg-reconfigure tzdata, locale 只生成需要的。 - 卸载前先 sync: 页缓存没落盘就拔, 镜像直接损坏。逆序 umount。
- 先在 QEMU 里测, 再碰真机。QEMU 炸了只是浪费 2 秒, 真机炸了是重装系统。
- 保留串口控制台 (
console=ttyS0): 无头服务器/云主机排障的生命线。 - 固化成脚本: 上面 30 行 shell 存成
build.sh, 一条命令重现整条流水线 (这就是"基础设施即代码"的雏形)。
偷懒通道: 用现成的高级工具
| 工具 | 一句话定位 |
|---|---|
mmdebstrap | debootstrap 现代替代品, 不需要 root, 直接产出 tar/ext4, 自动清理 |
mkosi | systemd 官方"镜像生成器", 一个 INI 配置文件描述整条流水线 |
live-build | Debian 官方 Live ISO 构建器 (Ubuntu 同源) |
FAI / Packer | 批量无人值守装机 / 云厂商镜像工厂 |
Buildroot / Yocto | 嵌入式: 从源码全自动交叉编译出带内核+引导的完整镜像 |
进阶: UEFI 镜像怎么做 (与 BIOS 版的三处不同)
# ① 分区表换成 GPT, 额外切一个 ESP 分区 (建议 256~512MB) parted -s img mklabel gpt \ mkpart ESP fat32 1MiB 513MiB set 1 esp on \ mkpart root ext4 513MiB 100% mkfs.vfat -F32 /dev/loop0p1 mount /dev/loop0p1 /mnt/esp # ② chroot 里装 EFI 版 GRUB (包名不同!) apt install -y grub-efi-amd64 # ③ 引导器写进 ESP (--removable 让任何 UEFI 机器都能认) grub-install --efi-directory=/mnt/esp \ --boot-directory=/mnt/boot --removable --no-nvram
grub-efi-amd64-signed + shim-signed 组合, 要么在 BIOS 里关掉 Secure Boot。EF02), 否则 grub-install 直接报错。MBR 没这个问题。进阶: 别只用锤子 —— 高级构建工具示例
# mmdebstrap: 不需要 root, 按扩展名直接产出 tar/ext4/squashfs mmdebstrap --variant=minbase bookworm rootfs.tar # live-build: 官方 Live ISO 工厂 (Debian/Ubuntu) lb config --distribution bookworm --archive-areas "main contrib non-free non-free-firmware" echo "vim curl" > config/package-lists/00-my.list.chroot lb build # 产出既可刻录又可 dd 的 hybrid ISO # Buildroot: 嵌入式全自动 (menuconfig 配置 → make 出整机镜像) make menuconfig && make
制作注意点补遗 (进阶坑位)
- 分区对齐 1MiB: 对 SSD/eMMC 的擦除块友好; 老教程的 "cylinder 对齐" 已过时。
- ESP 别切太小: 内核更新会堆积旧版本, 建议 256~512MB, 最少 100MB。
- chroot 里的网络: debootstrap 借用宿主机
/etc/resolv.conf; 若被清掉, apt 直接失联。 - machine-id: 批量克隆镜像前清空
/etc/machine-id, 首次开机自动重新生成, 避免多机同 ID。 - 串口登录: 只加
console=ttyS0只能看输出, 想登录还要systemctl enable serial-getty@ttyS0。
📚 出处与延伸阅读 (L3)
- debootstrap(8) 官方手册页: manpages.debian.org/debootstrap
- mmdebstrap 手册页: manpages.debian.org/mmdebstrap
- mkosi 项目与配置参考: github.com/systemd/mkosi
- live-build 官方手册: live-team.pages.debian.net/live-manual
- Buildroot 用户手册: buildroot.org/downloads/manual/manual.html
- Yocto Project 文档: docs.yoctoproject.org
- FAI (Fully Automatic Installation): fai-project.org
- HashiCorp Packer 文档: developer.hashicorp.com/packer/docs
- GNU GRUB 手册 · grub-install: gnu.org/software/grub/manual/grub
- UEFI 规范: uefi.org/specifications
第 4 课 镜像瘦身: 从 1.2GB 打到 10MB
体积控制的原则一句话: 每一 MB 都要"审问" —— 你是谁? 谁需要你? 删了会怎样? 下面是漏斗, 从标准系统一路压到极限。
六个体积杀手锏 (附命令)
# ① 源头控制: 最小安装, 不许"推荐包"搭车 debootstrap --variant=minbase bookworm /mnt … apt install -y --no-install-recommends vim # ② 删缓存: apt 下载的 .deb 和软件索引 (通常省 200MB+) apt clean && rm -rf /var/lib/apt/lists/* # ③ 删文档: man 手册、说明、info rm -rf /usr/share/doc/* /usr/share/man/* /usr/share/info/* # ④ locale 精简: 只留需要的语言 localepurge # 或手动清空 /usr/lib/locale 与 /usr/share/locale # ⑤ 日志限额: 防止 journald 把镜像撑胖 mkdir -p /etc/systemd/journald.conf.d printf '[Journal]\nSystemMaxUse=50M\n' > /etc/systemd/journald.conf.d/size.conf # ⑥ Live 系统终极武器: squashfs 只读压缩 (zstd) mksquashfs rootfs/ filesystem.squashfs -comp zstd -Xdict-size 100%
/etc/initramfs-tools/initramfs.conf 里设 MODULES=dep (只装这台机器的驱动), 或用 dracut --hostonly。动辄省 100~200MB。fstrim -av, 再 qemu-img convert -O raw 旧.img 新.img 可以回收空洞。进阶: 让"以后装的包"天生不带文档 —— dpkg 路径排除
# Debian/Ubuntu: 从此每个 .deb 安装时自动跳过这些路径 path-exclude=/usr/share/man/* path-exclude=/usr/share/doc/* path-exclude=/usr/share/info/*
进阶: 定位"体积大头"比瞎删有效一百倍
du -xh --max-depth=1 / 2>/dev/null | sort -h | tail -15 # 典型元凶排行: /usr/lib/firmware · /usr/share · /var/cache · /usr/lib/modules rm -rf /lib/firmware/* # 嵌入式敢删: 省几百 MB (桌面机慎删!) journalctl --vacuum-size=20M # 立刻裁剪已积累的日志
进阶: 换零件 —— 用"小号替身"
- busybox 替代 coreutils:
ln -s /bin/busybox /bin/ls—— 1MB 顶上百个工具。 - dropbear 替代 openssh-server: 体积只有零头, 嵌入式标配。
- zram 代替 swap 分区: 压缩内存当交换, 省一个分区还更快。
- 容器世界同款思路: distroless / alpine base + 多阶段构建, 本质都是"只留运行所需"。
进阶: 压缩算法怎么选 (squashfs / initramfs)
| 算法 | 压缩率 | 解压速度 | 适合 |
|---|---|---|---|
| gzip | 中 | 快 | 兼容性万金油 |
| zstd | 高 | 极快 | 现代首选 (体积/速度双优) |
| xz | 最高 | 慢 | 发布产物, 追求极限体积 |
📚 出处与延伸阅读 (L4)
- dpkg 配置 · dpkg.cfg(5) 的 path-exclude: manpages.debian.org/dpkg
- BusyBox 官方: busybox.net
- squashfs-tools (mksquashfs 压缩选项): github.com/plougher/squashfs-tools
- 内核构建文档 · localmodconfig 裁剪内核: docs.kernel.org/kbuild
- zstd 官方: github.com/facebook/zstd
- journald 大小控制 journald.conf(5): freedesktop.org/software/systemd/man/journald.conf.html
第 5 课 HDD、RAW、qcow2… 镜像格式到底怎么选?
先把两个容易混的词掰开: "HDD 镜像"泛指"整块硬盘的镜像"(含分区表+引导), 而 raw 是一种具体的存储格式 —— 没有任何包装的原始字节流。raw 是所有格式里最"裸"的, 也是性能天花板。
全格式对比表
| 格式 | 所属生态 | 快照 | 压缩/精简置备 | 性能 | 一句话 |
|---|---|---|---|---|---|
| raw | 通用标准 | ✘ | 可稀疏 (文件系统支持时) | ★★★★★ | 裸字节: dd 烧盘、云导入、追求性能都用它 |
| qcow2 | QEMU/KVM | ✔ | ✔ | ★★★★☆ | 功能最多的开源格式 |
| vhd / vhdx | Hyper-V | ✔ | ✔ | ★★★★☆ | 微软系原生; vhdx 支持 64TB、抗断电 |
| vmdk | VMware | ✔ | ✔ | ★★★★☆ | VMware 生态通用货币 |
| vdi | VirtualBox | ✔ | ✔ | ★★★☆☆ | 教学/家用常客 |
三个必会操作
# ① 三种造 4G 空文件的方式, 占用完全不同: truncate -s 4G sparse.img # 稀疏文件: ls 4G, du 几乎 0 fallocate -l 4G real.img # 真实占盘: ls 4G, du 4G dd if=/dev/zero of=real.img bs=1M count=4096 # ② 查看任意镜像的真身 qemu-img info disk.qcow2 qemu-img convert -O qcow2 disk.raw disk.qcow2 # 格式互转/回收空洞 # ③ 把镜像当本地硬盘挂载 (raw 才能直接 loop) sudo losetup -fP disk.img && ls /dev/loop0p*
dd if=/dev/sda; 只 dd 分区 → dd if=/dev/sda1。进阶: qcow2 内部长什么样 (懂原理才敢用)
- Cluster (簇): 数据按 64KiB (默认) 为单位分配, 写多少占多少 —— 这就是精简置备的原理。
- L1/L2 映射表: 客户机地址 → 文件内偏移的二级页表, 和 CPU 页表一个思路。
- Backing file (模板链): 新镜像只存"与模板不同的块" —— 一份黄金模板, 一百个增量镜像。
- 快照: 内部快照
qemu-img snapshot -c snap1 disk.qcow2; 外部快照在新文件上叠加。 - 加密: qcow2 支持用 LUKS 加密整个镜像。
进阶: qemu-img 子命令全家桶
| 命令 | 作用 |
|---|---|
qemu-img create -f qcow2 b.qcow2 40G | 创建 (虚拟 40G, 实占 0) |
qemu-img convert -O qcow2 a.raw b.qcow2 | 格式转换 (跳过零块 = 顺手瘦身) |
qemu-img resize disk.qcow2 +10G | 扩容 (之后还得在系统里扩分区/文件系统) |
qemu-img rebase -u -b template.qcow2 delta.qcow2 | 换模板 / 移动 backing 链 |
qemu-img check / compare / map | 体检 / 对比 / 可视化分配图 |
进阶: 容器格式家族冷知识
| 格式 | 细节 |
|---|---|
| VHD | 上限 2TB; 旧 Hyper-V 与 Azure 经典镜像 |
| VHDX | 上限 64TB; 内置日志, 断电不易损坏; 支持 4K 扇区 |
| VMDK | 变体繁多: monolithicSparse (单文件) / twoGbMaxExtent (兼容老文件系统) / streamOptimized (OVA 发布用) |
| raw | 云平台导入常只认 raw/vmdk (如 AWS VM Import); dd 到盘即刻变成真硬盘 |
📚 出处与延伸阅读 (L5)
- qcow2 磁盘格式规范 (QEMU 源码随附): qemu · docs/interop/qcow2.rst
- Wikipedia · qcow2: en.wikipedia.org/wiki/Qcow2
- Microsoft Learn · Hyper-V / VHDX: learn.microsoft.com · Hyper-V
- Wikipedia · VHD (format): en.wikipedia.org/wiki/VHD_(format)
- AWS VM Import/Export 支持格式: docs.aws.amazon.com · vm-import
第 6 课 镜像想"成功启动", 必须有什么?
从按下电源到出现登录提示, 是一条严格的接力链。每一棒都有规定的交付物, 缺任何一棒, 启动就断在那里。记住这条链, 你就拥有了 80% 的排障能力。
可启动镜像的"通关必备"清单
| 必备件 | 放在哪 | 缺了会怎样 |
|---|---|---|
| ① 分区表 + 引导记录 | MBR 第 1 扇区, 或 GPT + ESP 分区 | 固件: "No bootable device" |
| ② 引导器 (GRUB/syslinux) | MBR 之后 / ESP 内的 .efi 文件 | 看不到 GRUB 菜单, 或 grub rescue> |
| ③ 内核 vmlinuz | /boot/vmlinuz-* | 引导器报 "no Linux kernel" |
| ④ initramfs | /boot/initrd.img-* | 内核 panic: 找不到根分区/驱动 |
| ⑤ 根文件系统 + 1 号进程 | rootfs 里的 /sbin/init | panic: "Attempted to kill init" |
| ⑥ root= 参数指对路 | grub.cfg 的内核命令行 | 挂载根分区失败 |
进阶: Secure Boot 下的启动信任链
开启 Secure Boot 的机器只认"有签名的引导程序"。发行版的解决方案是一条签名接力:
UEFI 固件 (信任微软根证书) └→ shim.efi # 微软第三方签名的小跳板, 存放允许名单 └→ grubx64.efi # 发行版私钥签名, 由 shim 校验 └→ vmlinuz # 发行版私钥签名, 引导器校验 # 自编译内核? 用 mokutil 导入你自己的 MOK 密钥, 或关掉 Secure Boot
进阶: 那些救命的内核参数
| 参数 | 什么时候需要它 |
|---|---|
rootwait / rootdelay=5 | 根分区在 USB/SD 等"慢设备"上, 内核没认出盘就挂根会 panic |
rootfstype=ext4 | 跳过自动探测, 直接指定文件系统类型 |
fsck.mode=force | 怀疑文件系统损坏, 强制开机全盘自检 |
module_blacklist=nvme | 某驱动有 bug, 黑名单掉它走另一条路 |
ipv6.disable=1 | 纯内网环境省心 |
进阶: 引导器不止 GRUB 一家
- systemd-boot: UEFI 专用极简引导器, systemd 自带, 配置就是几个文本文件。
- syslinux / isolinux: BIOS 与 Live ISO 时代的老将, 轻量但功能少。
- efibootmgr: 管理 UEFI 固件 NVRAM 启动项 —
efibootmgr -v查看,-o 0003,0001调顺序,-b 0003 -B删除坏项。
📚 出处与延伸阅读 (L6)
- Linux 内核文档 · 启动参数权威索引: docs.kernel.org/admin-guide/kernel-parameters.html
- UEFI 规范 (启动流程/ESP/Secure Boot 定义): uefi.org/specifications
- Arch Wiki · GRUB (bios_grub/ESP 实践): wiki.archlinux.org/title/GRUB
- Arch Wiki · EFISTUB 与 efibootmgr: wiki.archlinux.org/title/EFISTUB
- systemd-boot 文档: freedesktop.org/software/systemd/man/systemd-boot.html
第 7 课 极限挑战: 最小可启动镜像里有什么?
把"必备清单"砍到只剩骨头, 就得到答案: 引导器 + 内核 + 一个能当 1 号进程的 init。图形界面、包管理器、甚至完整的 /usr —— 全都是可选件。
5 分钟做出能启动的最小 Linux (BusyBox 魔法)
下面这套操作只依赖一个 1MB 的静态 busybox —— 做完你就有了一个真正能启动的 Linux, 没有 /usr, 没有包管理, 甚至没有磁盘:
# 1) 下载静态编译的 busybox (约 1MB) wget https://busybox.net/downloads/binaries/1.35.0-x86_64-linux-musl/busybox chmod +x busybox # 2) 搭一个 initramfs 目录树 mkdir -p initramfs/{bin,sbin,etc,proc,sys,dev} cp busybox initramfs/bin/ && cd initramfs for i in $(bin/busybox --list); do ln -s /bin/busybox bin/$i; done # 3) 写一个 init 脚本 —— 它将替代整个 systemd cat > init <<'EOF' #!/bin/sh mount -t proc none /proc mount -t sysfs none /sys echo "🎉 欢迎来到你亲手造的最小 Linux!" exec /bin/sh EOF chmod +x init # 4) 打包成 initramfs (cpio 格式) find . | cpio -o -H newc | gzip > ../initramfs.cpio.gz # 5) 启动! 内核直接从内存里的 initramfs 跑, 不需要任何磁盘 🤯 qemu-system-x86_64 -m 256 \ -kernel /boot/vmlinuz-$(uname -r) \ -initrd initramfs.cpio.gz \ -append "console=ttyS0" -nographic
distroless 镜像、路由器固件都是这个哲学。进阶: 一个 rootfs 的"合法最低配置"清单
| 文件/目录 | 为什么必须有 |
|---|---|
/sbin/init (或用内核参数 rdinit=/bin/sh) | 内核挂根后执行的第一个程序, 没它直接 panic |
/dev/console | 内核消息与标准 IO 的门 (devtmpfs 通常自动提供) |
/proc /sys /dev | 挂载点, 由 init 负责挂 procfs/sysfs/devtmpfs |
/etc/passwd /etc/group | 多数程序查用户名/UID 时没有它们会出错 |
/etc/inittab | busybox init 的"剧本" (systemd 不需要) |
| libc (静态内嵌或动态 .so) | 除静态程序外, 一切用户态程序的地基 |
进阶: busybox init 的 inittab 长这样
::sysinit:/bin/mount -t proc none /proc ::sysinit:/bin/mount -t sysfs none /sys ::sysinit:/bin/mount -t devtmpfs none /dev ::respawn:/sbin/getty -L ttyS0 115200 vt100 # 串口可登录 ::ctrlaltdel:/sbin/reboot
进阶: PID 1 的隐藏职责 (为什么 init 不只是个脚本)
- 收割僵尸进程: 孤儿进程全部托付给 1 号进程, 它不 wait 就泄漏 PID。
- 信号特殊户: PID 1 对默认信号处理有特权规则; 它一死, 内核直接 panic。
- 关机流程: 只有它负责按序停服务、卸载文件系统、通知内核重启。
- 容器同理:
docker run的入口程序就是 PID 1 —— 这就是容器要tini这类 init 的原因。
进阶: 最小镜像"段位"对比
| 段位 | 构成 | 体积 | 能干嘛 |
|---|---|---|---|
| 🥇 纯 initramfs | 内核 + busybox 内存根 | ~8MB | 救援 / 演示 / 嵌入式 |
| 🥈 Alpine 磁盘镜像 | GRUB + 内核 + minirootfs | ~30MB | 能登录、能 apk 装包的"真系统" |
| 🥉 Debian minbase | GRUB + 内核 + minbase rootfs | ~250MB | 正经服务器底子 |
| docker scratch + 静态程序 | 宿主内核 + 一个二进制 | ~2MB | 单一服务专用 |
📚 出处与延伸阅读 (L7)
- BusyBox 官方文档 (init 与 inittab 语法): busybox.net/downloads/BusyBox.html
- 内核文档 · initramfs 机制: docs.kernel.org/admin-guide/initrd.html
- 内核文档 · Early Userspace (内嵌 initramfs): docs.kernel.org · early-userspace
- distroless (Google, "只留一个程序"的镜像): github.com/GoogleContainerTools/distroless
- tini — 容器里正确的 PID 1: github.com/krallin/tini
第 8 课 启动失败? 系统化调试法
启动失败的排障核心是定位断点在接力链的哪一棒。别瞎试 —— 先按决策树走一遍:
各阶段故障的"现场取证"命令
# 场景: 内核 panic — VFS: Unable to mount root fs # 嫌疑: root= 写错 / initramfs 缺存储驱动 (nvme? usb?) / UUID 变了 # 取证1: 在 GRUB 菜单按 e, 把 linux 行改成: linux /boot/vmlinuz root=/dev/sda1 rw loglevel=7 console=ttyS0 # 取证2: 在 live 系统里核对 UUID blkid # 镜像里 root= 写的 UUID 与这里一致吗? lsinitramfs /boot/initrd.img-6.1 | grep -E 'ext4|nvme' # 场景: 系统起来了但服务起不来 / 卡在启动画面 journalctl -xb # 本次启动的完整日志 (红字是重点) journalctl -b -1 -p err # 上一次启动的错误级日志 systemctl --failed # 谁没起来 systemd-analyze blame # 启动慢的元凶排行 (彩蛋) # 虚拟机专属调试: 串口直出, 媲美真机串口线 qemu-system-x86_64 -drive file=disk.img,format=raw -m 512 \ -append "console=ttyS0 loglevel=7" -nographic
break=mount 内核参数 —— 在挂载根分区前主动掉进 initramfs shell, 现场检查驱动和设备; dracut 系用 rd.break。这是排"根分区挂载不上"的神器。进阶: 串口控制台三件套 (专业排障标配)
# ① /etc/default/grub — 让 GRUB 菜单也走串口 GRUB_TERMINAL="serial console" GRUB_SERIAL_COMMAND="serial --unit=0 --speed=115200" # ② 内核参数 — 注意: 最后一个 console= 才是 init 的输出终端 GRUB_CMDLINE_LINUX="console=tty0 console=ttyS0,115200" # ③ 串口上要能登录: 启用 getty systemctl enable serial-getty@ttyS0
进阶: QEMU 调试武器库 (比真机更好用的地方)
| 用法 | 能看到什么 |
|---|---|
qemu -nographic | 串口直连当前终端 (配 console=ttyS0) |
qemu -s -S + gdb vmlinuz | 单步调试内核: target remote :1234, 在 start_kernel 下断点 |
qemu -d int,cpu_reset -D q.log | 记录所有中断与 CPU 复位, 定位卡死 |
qemu -debugcon stdio (配 OVMF) | 把 UEFI 固件自己的调试日志打出来 |
进阶: 内核 panic 名句翻译
| 屏幕上的话 | 人话 |
|---|---|
VFS: Unable to mount root fs on unknown-block(0,0) | root= 指错了, 或 initramfs 缺对应的存储/文件系统驱动 |
No working init found. Try passing init= | 根挂上了, 但里面没有能跑的 init —— rootfs 不完整 |
Attempted to kill init! exitcode=… | init 启动后自己退出了 (脚本缺 shebang / 第一行就崩) |
dracut-initqueue timeout | initramfs 等不到根设备 (UUID 变了 / 驱动缺失 / 网络存储未就绪) |
进阶: Live USB + chroot 修复完整配方
mount /dev/sda2 /mnt # 挂坏系统的根分区 mount /dev/sda1 /mnt/boot/efi # ESP (UEFI 机器才有这步) for d in dev proc sys run; do mount --bind /$d /mnt/$d; done chroot /mnt grub-install ... && update-grub # 修引导 update-initramfs -u # 补驱动 fsck.ext4 -f /dev/sda2 # 查文件系统 (chroot 前在 live 里做更稳)
netconsole=<本机端口>@<本机IP>/eth0,<远端端口>@<远端IP>/<远端MAC>, 另一头用 nc -lu 514 收听。📚 出处与延伸阅读 (L8)
- 内核文档 · console= / earlycon 参数: docs.kernel.org · kernel-parameters
- systemd bootup(7) · rescue/emergency 目标流程: freedesktop.org/software/systemd/man/bootup.html
- systemd · serial-getty@.service: freedesktop.org · serial-getty@.service
- QEMU 文档 · gdbstub 内核调试 (-s/-S): qemu.org/docs/master/system/gdb.html
- 内核文档 · netconsole: docs.kernel.org/networking/netconsole.html
- Arch Wiki · General troubleshooting: wiki.archlinux.org/title/General_troubleshooting
第 9 课 读懂 POST 代码: 主板的"心电图"
POST = Power-On Self Test (加电自检)。固件在把控制权交给引导器之前, 要检查 CPU、内存、显卡、键盘……检查到谁卡住了, 它会把进度码写到 I/O 端口 0x80H。看懂这个码, 就知道硬件卡在哪一步。
常见蜂鸣码 (Award 风格, 各厂商略有差异)
| 声音 | 含义 | 先做什么 |
|---|---|---|
| 1 短声 | 自检通过, 正常启动 | 无需处理 |
| 连续短响 | 电源故障 | 查电源/供电 |
| 不断长响 | 内存条未插好 | 断电重插内存, 橡皮擦金手指 |
| 1 长 2 短 | 显卡/显示错误 | 重插显卡, 换显示输出接口 |
| 1 长 1 短 | 内存或主板错误 | 最小化法逐个排除 |
常见 Q-Code / Port 80H 码 (AMI Aptio 风格)
| 码 | 含义 | 卡住时的思路 |
|---|---|---|
00 | CPU 未开始执行 (或执行完毕) | 查 CPU 供电、主板 24pin、清 CMOS |
55 | 内存未安装 / 初始化失败 | 重插内存, 单条逐一试, 查 QVL 兼容列表 |
A2 | 正在检测 SATA/存储设备 | 拔掉坏盘/坏线再试 |
99 | Super IO 初始化 | 查键鼠/串口芯片, 换 USB 口 |
B2 | Legacy Option ROM 初始化 | 老显卡/RAID 卡的 ROM 卡死, 拔掉再试 |
D6 | 没找到显示输出设备 | 查显卡与线缆, 试核显输出 |
F1 | 等待用户确认错误 (按 F1) | 看屏幕! 上面写着具体错误 |
进阶: POST 到底在检什么 (顺序表)
| 阶段 | 检查 / 动作 | 失败表现 |
|---|---|---|
| ① CPU 复位 | CPU 执行复位向量处的固件代码 | 黑屏, 风扇不转或狂转 |
| ② 内存检测 | 逐条识别并"训练"内存 | 蜂鸣长响 / Q-Code 55 |
| ③ 显卡初始化 | 枚举 PCIe, 运行显卡 Option ROM | 蜂鸣 1长2短 / 有蜂鸣无画面 |
| ④ 设备枚举 | NVMe / SATA / USB 键鼠 | 卡 A2 (等存储设备) |
| ⑤ 引导设备选择 | 按启动顺序读 MBR / ESP | "No bootable device" |
| ⑥ 移交控制权 | 交给引导器 —— POST 使命结束 | 此后故障归软件管 (第 8 课) |
进阶: Debug 卡是怎么"偷听"的
POST 码的本质是固件向 I/O 端口 0x80 写一个字节 —— 纯粹写给诊断工具看, 不驱动任何硬件。Debug 卡监听总线上的这次写并锁存在数码管上; ISA 时代直连总线, 现在经 LPC/eSPI 桥片转发。两个冷知识: 极少数固件不写 80H, Debug 卡就没用; 开启 Ultra Fast Boot 跳过太多步骤时, 码会一闪而过。
进阶: 没有数码管的厂商怎么表达 POST
| 厂商 | 形式 | 举例 |
|---|---|---|
| ASUS | 板载两位 Q-Code LED | 55 内存 / A2 硬盘 |
| MSI | EZ Debug 四灯 (CPU→DRAM→VGA→BOOT) | DRAM 灯常亮 = 内存问题 |
| Dell / 笔电 | 电源灯琥珀/白色闪烁计数 | 如 2琥珀+3白 = 内存 (查厂商表) |
| 通用 | 蜂鸣器 beep code | 见上方蜂鸣码表 |
进阶: 最小化硬件法 (无码可读时的终极手段)
- 断电, 只留 CPU + 单条内存 + 电源, 拔掉所有硬盘/独显(改用核显)/USB 设备。
- 开机听蜂鸣看灯: 能点亮 → 逐件往回加, 加到谁坏谁背锅。
- 仍不亮 → 换内存插槽、橡皮擦金手指、换电源、清 CMOS (扣电池或跳线)。
- 终极一招: 主板放绝缘台"裸板点亮" (bench test), 排除机箱短路。
📚 出处与延伸阅读 (L9)
- Wikipedia · Power-on self-test (蜂鸣码历史与各厂表格): en.wikipedia.org/wiki/Power-on_self-test
- OSDev Wiki · POST (Port 80H 机制): wiki.osdev.org/POST
- AMI (American Megatrends) 官方支持: ami.com/en/support
- ASUS Q-Code 表 / MSI EZ Debug 说明: 见各主板用户手册附录 —— 最权威的码表永远在主板手册里
第 10 课 GRUB 参数: 启动行为的总遥控器
GRUB 菜单上每一项的背后是一行 linux /boot/vmlinuz root=… ro quiet。这行命令行直接控制内核行为 —— 改它 = 改系统启动方式。两个修改入口: 临时改(只影响本次) 和 永久改(写进配置)。
GNU GRUB version 2.12 ┌────────────────────────────────────────────┐ │ *Debian GNU/Linux │ │ Advanced options for Debian GNU/Linux │ │ Memory test (memtest86+) │ └────────────────────────────────────────────┘ Use ↑ and ↓ keys. Press 'e' to edit (改本次启动!) Press 'c' for GRUB command line (救命 Shell)
临时修改: 按 e 现场编辑 (重启即失效)
setparams 'Debian GNU/Linux' load_video insmod gzio linux /boot/vmlinuz-6.1.0 root=UUID=abcd-1234 ro quiet # ↑ 在这一行末尾随便加/删参数, Ctrl+X 或 F10 按"临时改"后的方式启动
永久修改: 改配置再生成
# ① 编辑默认参数 (这个文件是给人看的, grub.cfg 是生成物别手改!) GRUB_CMDLINE_LINUX_DEFAULT="quiet splash" # 正常模式附加参数 GRUB_CMDLINE_LINUX="console=ttyS0,115200" # 所有模式(含恢复模式)附加 GRUB_TIMEOUT=5 # 菜单等待秒数 GRUB_TERMINAL=console # 纯文本菜单 (服务器友好, 分辨率不坑) # ② 生成新配置 update-grub # Debian/Ubuntu grub2-mkconfig -o /boot/grub2/grub.cfg # RHEL/Fedora/openEuler
常用内核参数速查
| 参数 | 作用 | 典型场景 |
|---|---|---|
root=UUID=… / root=/dev/sda1 | 根分区在哪 | 改过分区后 panic |
ro / rw | 初始挂载只读/读写 | init=/bin/bash 时常配 rw |
quiet splash | 屏蔽日志 / 显示开机动画 | 生产环境安静; 排障时删掉它 |
loglevel=7 | 最详细内核日志 | 排障必配 |
init=/bin/bash | bash 当 1 号进程 | 忘记 root 密码 / 修 fstab |
systemd.unit=rescue.target | 只起最小服务 | 服务起不来时 |
nomodeset | 禁用内核显卡模式设置 | 花屏/黑屏/老显卡 |
console=ttyS0,115200 | 控制台重定向到串口 | 无头服务器/云/虚拟机 |
resume=/dev/sda2 | 休眠唤醒的 swap 分区 | 休眠失败时排查 |
break=mount (initramfs) | 挂根前掉进救援 shell | 根分区挂载失败取证 |
GRUB 命令行 ('c' 键) — 手动把系统点着
grub> ls # 看看有哪些盘和分区 (hd0) (hd0,msdos1) (hd1,gpt1) (hd1,gpt2) grub> ls (hd0,msdos1)/ # 找到 /boot 在哪个分区 grub> set root=(hd0,msdos1) grub> linux /boot/vmlinuz root=/dev/sda1 ro grub> initrd /boot/initrd.img grub> boot # 🚀 手动点火 # 进了系统记得 grub-install + update-grub 把它修好!
grub rescue> 提示符 = GRUB 找不到自己的模块。自救三连: ls 找分区 → set prefix=(hd0,gpt2)/boot/grub 和 set root=(hd0,gpt2) → insmod normal 然后 normal。进阶: GRUB 的三层身体结构
boot.img # 446 字节, 住 MBR —— 只干一件事: 把下一层装进内存 core.img # 住 MBR 缝隙 / BIOS boot 分区 / ESP —— 内含文件系统驱动 /boot/grub/ # 全部模块 + grub.cfg —— 菜单与功能真正住在这里 # UEFI: grubx64.efi 把 core+模块打包成单个文件放进 ESP # grub rescue> = core.img 活着但找不到 /boot/grub —— 所以自救靠 insmod normal
进阶: grub.cfg 语法一眼看懂
menuentry 'Debian GNU/Linux' --class debian { search --no-floppy --fs-uuid --set=root 1a2b-3c4d linux /boot/vmlinuz-6.1.0 root=UUID=1a2b-3c4d ro quiet initrd /boot/initrd.img-6.1.0 } # search 按 UUID 找分区再设为 $root —— 这就是为什么换磁盘后它能自适应
进阶: /etc/grub.d/ 与 grubenv
update-grub= 执行 /etc/grub.d/ 下的脚本拼出 grub.cfg:10_linux找本机内核,30_os-prober找其他系统,40_custom是你写自定义菜单的地方。grub-reboot '高级选项>…': 只下一次进指定项 (测试新内核神器, 状态写进 grubenv 小文件)。GRUB_CMDLINE_LINUX(连恢复模式一起带) vsGRUB_CMDLINE_LINUX_DEFAULT(仅正常模式) —— 别再混了。GRUB_DISABLE_LINUX_UUID=true: 让菜单写 /dev/sdX 而非 UUID (一般别开)。
进阶: 更多值得认识的内核参数
| 参数 | 作用 |
|---|---|
rdinit=/bin/sh | initramfs 阶段的 1 号进程 (init= 是真根文件系统的) |
panic=10 | panic 10 秒后自动重启 (配合 kdump 抓现场) |
systemd.debug_shell=1 | 在 tty9 常驻一个调试 shell (Ctrl+Alt+F9) |
mitigations=off | 关 CPU 漏洞缓解换性能 (安全自负) |
earlyprintk=efi,keep | 把内核最早期的输出也送到 UEFI 控制台取证 |
📚 出处与延伸阅读 (L10)
- GNU GRUB 官方手册 (全): gnu.org/software/grub/manual/grub
- 内核文档 · 内核参数权威索引: docs.kernel.org · kernel-parameters
- Arch Wiki · GRUB (配置变量与排障): wiki.archlinux.org/title/GRUB
- initramfs-tools(7) · break= 参数: manpages.debian.org/initramfs-tools
- dracut(8) · rd.* 参数族: github.com/dracutdevs/dracut
游戏大厅 🎮 六个游戏, 把知识打进肌肉记忆
每个游戏都对应上面的一课。获胜解锁勋章 + 45 XP, 期末考试值 100 XP!
🎢 启动流水线大挑战把 10 张乱序卡片按启动顺序点亮 · 对应第 6 课 🎢+45 XP
三次点错就断电重开。直觉不可靠 —— 靠你背下来的接力链!
📡 POST 侦探事务所蜂鸣码 & Q-Code 破译 6 连发 · 对应第 9 课 📡+45 XP
主板打来的摩尔斯电码, 你能破译吗? 连击有面子, 答对 5/6 出师。
🧪 GRUB 参数实验室按任务卡拼装内核命令行 · 对应第 10 课 ⚙️+45 XP
5 张任务卡: 忘记密码、显卡花屏、串口排障…… 从参数池选出正确组合, 点"启动!"验收。
linux /boot/vmlinuz
🪶 镜像瘦身大亨1200MB → 100MB 以内, 别踩事故卡 · 对应第 4 课 💎+45 XP
🧲 格式选择大师7 个真实场景, 选对磁盘格式 · 对应第 5 课 🧠+45 XP
🎓 终极毕业考试12 题全课程大混考 · 合格领证书 🎓+100 XP
从 10 节课里抽出的 12 道题, 答对 10 题合格。敢来吗?
成就系统 🏅 勋章墙
完成课程、打赢游戏即可点亮。集齐 17 枚, 你就是这条 Gai 最懂 Linux 镜像的企鹅。
附录 📋 一页速查表 (收藏这一节就够了)
— 制作 —————————————————————————————— truncate -s 2G img.raw # 造稀疏空盘 parted -s img.raw mklabel msdos|gpt mkpart primary ext4 1MiB 100% losetup -fP img.raw; mkfs.ext4 -F /dev/loop0p1; mount /dev/loop0p1 /mnt debootstrap --variant=minbase bookworm /mnt http://deb.debian.org/debian debootstrap noble /mnt http://archive.ubuntu.com/ubuntu # Ubuntu 版 grub-install --boot-directory=/mnt/boot /dev/loop0 qemu-system-x86_64 -drive file=img.raw,format=raw -m 512 — 瘦身 —————————————————————————————— apt clean && rm -rf /var/lib/apt/lists/* /usr/share/{doc,man,info} mksquashfs rootfs/ sys.squashfs -comp zstd qemu-img convert -O qcow2 a.raw b.qcow2 # 顺带回收空洞 — 排障 —————————————————————————————— GRUB 按 e → loglevel=7 去 quiet → Ctrl+X console=ttyS0,115200 + qemu -nographic # 串口看全程 init=/bin/bash · systemd.unit=rescue.target · break=mount journalctl -xb -p err · blkid · lsinitramfs Q-Code: 55 内存 · A2 硬盘 · F1 看屏幕 · Port 80H Debug 卡
附录 📚 参考文献与出处总汇
本站全部知识均可在下列一手资料中交叉验证。按"官方手册 > 发行版 Wiki > 社区教程"的置信度排序取用。
★ 权威手册与规范 (能当字典用)
| 资源 | 链接 | 覆盖本站哪些内容 |
|---|---|---|
| Linux 内核文档 · kernel-parameters | docs.kernel.org | L6/L8/L10 全部内核参数 |
| GNU GRUB 官方手册 | gnu.org/software/grub | L10 全部、L8 串口配置 |
| UEFI 规范 (UEFI Forum) | uefi.org/specifications | L6 启动流程 / ESP / Secure Boot |
| QEMU 文档 · qemu-img(1) / gdbstub | qemu.org/docs | L5 磁盘格式 / L8 调试 |
| systemd 手册 (bootup(7) 等) | freedesktop.org/systemd/man | L8 启动目标 / L7 PID 1 职责 |
| BusyBox 官方文档 | busybox.net | L7 最小镜像 / L2 静态编译 |
| Debian manpages 总库 | manpages.debian.org | debootstrap / dpkg / initramfs-tools |
发行版官方资源 (现成镜像从这里来)
| 资源 | 链接 | 用途 |
|---|---|---|
| Debian 云镜像 (generic) | cloud.debian.org/images/cloud | 现成可启动 qcow2/raw, L2/L3 |
| Ubuntu 云镜像 | cloud-images.ubuntu.com | 同上, 含 minimal 变体 |
| Alpine 下载 (minirootfs) | alpinelinux.org/downloads | ~3MB 极简 rootfs, L7 |
| Debian Wiki · Debootstrap | wiki.debian.org/Debootstrap | L3 手工制作权威教程 |
| Debian live-build 手册 | live-manual | L3 Live ISO 工厂 |
| Arch Wiki | wiki.archlinux.org | GRUB / Mkinitcpio / 排障实践宝库 |
| Fedora 文档 | docs.fedoraproject.org | dnf / lorax / SELinux |
构建与调试工具
| 工具 | 链接 | 本站位置 |
|---|---|---|
| mkosi (systemd 出品) | github.com/systemd/mkosi | L3 |
| Buildroot 用户手册 | buildroot.org/manual | L2 / L3 / L4 |
| Yocto Project 文档 | docs.yoctoproject.org | L2 / L3 (.wic 格式) |
| squashfs-tools | github.com/plougher/squashfs-tools | L4 压缩 |
| dracut | github.com/dracutdevs/dracut | L8 / L10 的 rd.* 参数族 |
| libguestfs (virt-* 工具) | libguestfs.org | L1 镜像体检 |
| cloud-init | cloud-init.io | L2 / L3 云镜像定制标准 |
| FAI / HashiCorp Packer | fai-project.org · packer docs | L3 批量镜像工厂 |
| NixOS 手册 (声明式) | nixos.org/manual | L2 发布模式 |
格式规范与硬件资料
| 资源 | 链接 | 本站位置 |
|---|---|---|
| qcow2 磁盘格式规范 (QEMU 源码随附) | qemu · docs/interop/qcow2.rst | L5 |
| Wikipedia · Disk image / MBR / GPT / qcow2 / VHD | wikipedia.org | L1 / L5 / L6 |
| Wikipedia · Power-on self-test | wikipedia.org/Power-on_self-test | L9 蜂鸣码 |
| OSDev Wiki · POST | wiki.osdev.org/POST | L9 Port 80H 机制 |
| 各主板用户手册 (ASUS Q-Code / MSI EZ Debug 码表) | 厂商支持页下载 | L9 码表的最终依据 |
| OCI 镜像规范 (容器镜像对比) | github.com/opencontainers/image-spec | L1 |