更新构建

This commit is contained in:
Cx330 committed 2026-10-14 09:51:02 +08:00
1 parent a29929b378
commit bbc985f339
1 file changed
+535 -268
+535 -268
View File
@@ -1,7 +1,30 @@
# Linux 内核 + BusyBox 最小系统构建指南
# DreamLife Linux 内核 + BusyBox 最小系统构建指南
> 基于 Linux 6.12 + BusyBox,使用 QEMU 启动的最小根文件系统。
> 本文档记录从零构建到成功进入 shell 的完整过程,包含故障排查。
> **Linux 6.12 + BusyBox + QEMU** 的完整最小根文件系统。
> 本文档对应 `build.sh` 一键构建流程,包含配置说明、打包规范与故障排查。
---
## 目录结构
```
/home/zmn/
├── linux-6.12/ # 内核源码(./build.sh kernel-up 自动滚动到最新 6.12.x)
├── busybox/ # BusyBox 源码
├── rootfs/ # 根文件系统(打包源)
│ ├── init # 自定义 PID 1(挂载 + 首次建号 + 登录循环)
│ ├── bin/busybox # 静态链接 BusyBox
│ ├── bin/fastfetch # musl 动态 fastfetch
│ ├── etc/ # passwd/group/shadow/hostname/profile/.../os-release
│ ├── home/ # 普通用户 home(首次 setup 创建)
│ └── lib/modules/ # 内核 .ko 模块(modules_install 产物)
│ └── lib/ld-musl-x86_64.so.1 # musl 加载器(fastfetch)
├── kernel-minimal.fragment # 内核配置增量片段
├── busybox-minimal.fragment # BusyBox 裁剪片段
├── build.sh # 一键构建脚本
├── disk.img # 持久盘(virtio-blk ext4,build.sh 生成)
└── initramfs.cpio.gz # 打包产物
```
---
@@ -13,19 +36,57 @@ sudo apt install -y build-essential libncurses-dev bison flex \
libssl-dev libelf-dev git bc rsync cpio gzip wget gcc-14 qemu-system-x86
```
目录结构:
```
/home/zmn/
├── linux-6.12/ # 内核源码
├── busybox/ # BusyBox 源码
├── rootfs/ # 根文件系统
└── initramfs.cpio.gz # 打包后的 initramfs
```
> 若无 `gcc-14`,`build.sh` 会自动回退到系统 `gcc`。
> 可用 `CC=gcc ./build.sh` 显式指定编译器。
---
## 第一步:下载并解压内核源码
## 一键构建(推荐)
```bash
cd /home/zmn
chmod +x build.sh
./build.sh # 全量:kernel + busybox + rootfs + initramfs
```
分步执行:
```bash
./build.sh kernel # 内核配置 + 编译 + 打包 initramfs(当前源码)
./build.sh kernel-up # 内核滚动更新:拉最新点版本 + 重编 + 刷模块 + 打包(KERNEL_SERIES=7.2 换大版本)
./build.sh busybox # BusyBox 配置 + 编译 + 安装 rootfs + 模块 + 打包
./build.sh rootfs # 仅重装 rootfs/模块并打包
./build.sh mylogin # 仅编译 mylogin(密码显示 *)并打包
./build.sh fastfetch # 仅安装 fastfetch(musl 链接)并打包
./build.sh apk # 仅安装包管理器(dpkl/apt 风格 + apk 兼容)+ 密钥/仓库/DHCP 脚本并打包
./build.sh initramfs # 仅重新打包
./build.sh disk # 仅生成/检查 disk.img(64MB ext4)
```
环境变量:
| 变量 | 默认值 | 说明 |
|------|--------|------|
| `CC` | `gcc-14`(缺失则 `gcc`) | C 编译器 |
| `JOBS` | `$(nproc)` | 并行度 |
| `KERNEL_DIR` | `/home/zmn/linux-6.12` | 内核源码(**目录名固定**,即使 `KERNEL_SERIES` 换系列也不改名,QEMU/测试均引用此路径) |
| `KERNEL_SERIES` | `6.12` | kernel-up 跟随的滚动系列,自动路由镜像 `v6.x`/`v7.x` 目录;换系列示例:`KERNEL_SERIES=7.2 ./build.sh kernel-up`(TUNA 实测最新 `7.2.8`) |
| `KERNEL_CACHE` | `/home/zmn/.cache/kernel` | 内核 tarball / 旧 config 备份缓存 |
| `BB_DIR` | `/home/zmn/busybox` | BusyBox 源码 |
| `ROOTFS` | `/home/zmn/rootfs` | 根文件系统 |
| `INITRAMFS` | `/home/zmn/initramfs.cpio.gz` | 打包输出 |
| `DISK` | `/home/zmn/disk.img` | 持久盘镜像路径 |
| `DISK_SIZE_MB` | `64` | 持久盘大小(MB) |
| `FF_CACHE` | `/home/zmn/.cache/fastfetch` | fastfetch 下载/构建缓存 |
| `FF_VER` | `2.68.1` | fastfetch 版本 |
| `APK_CACHE` | `/home/zmn/.cache/apk` | apk 静态包/密钥下载缓存 |
| `ALPINE_MIRROR` | `http://mirrors.tuna.tsinghua.edu.cn/alpine` | Alpine 仓库镜像(国内源;写入 `etc/apk/repositories`) |
---
## 手动构建步骤(原理)
### 1. 下载并解压内核
```bash
cd /home/zmn
@@ -33,327 +94,533 @@ wget https://mirrors.tuna.tsinghua.edu.cn/kernel/v6.x/linux-6.12.tar.xz
tar -xf linux-6.12.tar.xz
```
---
> 以上是首次手工获取;之后升级内核直接用 `./build.sh kernel-up`
> (自动查最新 6.12.x 点版本、携带 `.config` 换树、重编 + 刷模块 + 重打包)。
## 第二步:配置内核
### 2. 配置内核
```bash
cd /home/zmn/linux-6.12
make x86_64_defconfig
make ARCH=x86_64 defconfig
# 叠加最小化/硬化片段
# (build.sh 内部用 awk 合并 kernel-minimal.fragment,效果等价于 merge_config)
make ARCH=x86_64 olddefconfig
```
### 关键配置项
**相对 `x86_64_defconfig` 的关键改动(见 `kernel-minimal.fragment`):**
`x86_64_defconfig` 已默认启用以下必需选项(可用 `grep` 验证):
| 配置项 | 作用 |
|--------|------|
| `CONFIG_SERIAL_8250=y` | 串口驱动 |
| `CONFIG_SERIAL_8250_CONSOLE=y` | 串口控制台(`console=ttyS0` 依赖此项) |
| `CONFIG_BLK_DEV_INITRD=y` | initramfs 支持(`-initrd` 依赖此项) |
| `CONFIG_RD_GZIP=y` | gzip 压缩的 initrd 解压支持 |
| `CONFIG_DEVTMPFS=y` | `/dev` 自动挂载 |
| `CONFIG_PRINTK=y` | 内核日志输出 |
| 类别 | 配置项 | 作用 |
|------|--------|------|
| 安全 | `# CONFIG_SECURITY_SELINUX is not set` | rootfs 无 SELinux 策略,默认关闭避免启动告警 |
| 硬化 | `CONFIG_FORTIFY_SOURCE=y` / `HARDENED_USERCOPY=y` / `SLAB_FREELIST_HARDENED=y` | 内存与拷贝加固 |
| 硬化 | `# CONFIG_DEVMEM` / `# CONFIG_PROC_KCORE` | 关闭 /dev/mem、/proc/kcore 攻击面 |
| 可观测 | `CONFIG_IKCONFIG=y` + `IKCONFIG_PROC=y` | 运行时 `/proc/config.gz` |
| 命名空间 | `CONFIG_USER_NS=y` | 支持 `unshare -U` |
| 调试裁剪 | 关闭 `DEBUG_FS` / `FTRACE` / `KPROBES` / `KEXEC` | 减小体积与攻击面 |
| 驱动裁剪 | 关闭 Wireless / USB host / SCSI / SATA / CDROM / NFS / 9P / VFAT | QEMU 串口启动用不到 |
| 保留 | `VIRTIO_BLK/NET/PCI`、`E1000`、`EXT4_FS`、串口控制台 | 可选网卡/磁盘仍可用 |
验证:
```bash
grep -E 'CONFIG_(SERIAL_8250_CONSOLE|BLK_DEV_INITRD|RD_GZIP|DEVTMPFS|PRINTK)=' .config
grep -E 'CONFIG_(SECURITY_SELINUX|IKCONFIG|USER_NS|FORTIFY_SOURCE|DEVMEM|VIRTIO_BLK)=' .config
```
> **注意**:不要用 `make tinyconfig`,它几乎关闭所有驱动,导致没有串口控制台输出,
> QEMU 只会显示一个闪烁的光标。
---
## 第三步:编译内核
### 3. 编译内核
```bash
cd /home/zmn/linux-6.12
make CC=gcc-14 -j$(nproc)
```
编译成功标志:
```
Kernel: arch/x86/boot/bzImage is ready (#1)
```
验证产物:
```bash
make ARCH=x86_64 CC=gcc-14 -j$(nproc)
# 成功标志:Kernel: arch/x86/boot/bzImage is ready
ls -la arch/x86/boot/bzImage
# 应输出类似:-rw-r--r-- 1 zmn zmn 13583360 ... bzImage
```
---
## 第四步:下载并配置 BusyBox
### 4. 配置 BusyBox
```bash
cd /home/zmn
git clone --depth 1 https://git.busybox.net/busybox
cd busybox
cd /home/zmn/busybox
make defconfig
```
### 关键配置:静态编译
```bash
# 开启静态链接(不依赖 libc.so 等共享库)
# 静态链接(initramfs 无 /lib 动态加载器)
sed -i 's/^# CONFIG_STATIC is not set/CONFIG_STATIC=y/' .config
# 禁用 tc(新版内核头文件已删除 CBQ 结构体,会导致编译失败)
sed -i 's/^CONFIG_STATIC=.*/CONFIG_STATIC=y/' .config
# 禁用 tc(新版内核头文件已删除 CBQ 结构体,否则编译失败)
sed -i 's/^CONFIG_TC=y/# CONFIG_TC is not set/' .config
grep -E '^CONFIG_STATIC=|^CONFIG_TC=' .config
# 应输出:
# CONFIG_STATIC=y
# # CONFIG_TC is not set
# 叠加裁剪片段(关闭 telnetd/httpd/ftpd/i2c/ubi 等)
# 见 busybox-minimal.fragment,由 build.sh 自动合并
make oldconfig </dev/null
grep -E '^CONFIG_STATIC=|^# CONFIG_TC is not set' .config
```
> **为什么必须静态编译**:initramfs 中没有 `/lib`、`/lib64`,
> 动态链接的 BusyBox 在启动时会因找不到 `ld-linux-x86-64.so.2` 而失败,
> 内核 panic,屏幕上什么都看不到。
>
> **为什么必须禁用 tc**:`make defconfig` 默认开启 `CONFIG_TC=y`,
> 但 `networking/tc.c` 引用的 `TCA_CBQ_*` 等结构体已在新版内核头文件中删除,
> 会导致编译报错 `TCA_CBQ_MAX undeclared`。
---
## 第五步:编译 BusyBox
### 5. 编译并安装 BusyBox
```bash
cd /home/zmn/busybox
make CC=gcc-14 -j$(nproc)
```
编译成功后验证:
```bash
file busybox
# 应输出:ELF 64-bit LSB executable, x86-64, ... statically linked
```
---
## 第六步:构建根文件系统
### 6.1 创建目录结构
```bash
mkdir -p /home/zmn/rootfs/{bin,sbin,etc,proc,sys,dev,tmp,root,usr/bin,usr/sbin}
```
### 6.2 安装 BusyBox
```bash
cd /home/zmn/busybox
file busybox # 必须显示 statically linked
make CONFIG_PREFIX=/home/zmn/rootfs install
```
验证:
### 6. rootfs 基础文件
```bash
file /home/zmn/rootfs/bin/busybox
# 应输出:statically linked
```
`/etc` 至少包含(仓库中已提供):
### 6.3 编写 init 脚本
用 `cat` 写入 `/home/zmn/rootfs/init`:
```bash
cat > /home/zmn/rootfs/init << 'EOF'
#!/bin/sh
mount -t proc none /proc
mount -t sysfs none /sys
mount -t devtmpfs none /dev
hostname minimal-linux
exec /bin/sh
EOF
```
赋予执行权限:
```bash
chmod +x /home/zmn/rootfs/init
```
验证:
```bash
ls -la /home/zmn/rootfs/init
# 应输出:-rwxr-xr-x ... /home/zmn/rootfs/init
```
### 6.4 init 脚本说明
| 命令 | 作用 |
| 文件 | 作用 |
|------|------|
| `mount -t proc` | 挂载进程信息系统,`ps`、`/proc` 依赖它 |
| `mount -t sysfs` | 挂载 sysfs,`/sys` 依赖它 |
| `mount -t devtmpfs` | 挂载设备文件系统,自动创建 `/dev/ttyS0` 等设备节点 |
| `exec /bin/sh` | 用 shell 替换当前进程,进入交互式命令行 |
| `etc/passwd` | 用户名/UID/shell(`login` 依赖);root 初始 `x`(口令在 shadow) |
| `etc/group` | 组信息(含 `users`) |
| `etc/shadow` | 口令;**root 初始 `*` 锁定**,首次 setup 时设置 |
| `etc/hostname` | 主机名 |
| `etc/hosts` | 本地解析 |
| `etc/resolv.conf` | DNS(有网时可用) |
| `etc/os-release` | 发行版标识(fastfetch 的 OS 字段读取此处) |
| `etc/profile` | 登录 shell 的 `PATH`/`PS1`(不覆盖 `HOME`);交互登录时清屏并运行 `fastfetch` |
| `bin/mylogin` | 自定义登录程序:密码逐字显示 `*`(`mylogin.c` 编译) |
| `bin/sudo` | 轻量 sudo:验证**当前用户**密码(显示 `*`)后以 root 执行;需在 `wheel` 组 |
| `bin/busybox` | **setuid root**(`chmod 4755`),否则 `su` 不可用 |
| `etc/issue` / `motd` | 登录前提示 |
| `etc/init.d/rcS` | 可选(busybox `init` 路径) |
---
> **安全**:出厂 rootfs **不带空口令**;用户/ root 密码在**首次开机交互**创建,
> 并写入持久盘(若挂载了 `disk.img`)。
## 第七步:打包 initramfs
### 7. init 脚本要点(首次登录 / 持久化)
```bash
cd /home/zmn/rootfs
find . | cpio -o -H newc 2>/dev/null | gzip > /home/zmn/initramfs.cpio.gz
```
当前 `/init` 完成:
验证:
1. 挂载 `proc`、`sysfs`、`devtmpfs`、`devpts`、`tmpfs` → `/tmp` 与 `/run`
2. 兜底创建 `console`/`ttyS0`/`null`/`urandom` 节点;`exec` 到 console 作为 stdio
3. 拉起 `lo` 与所有网卡;读 hostname;`depmod`;`syslogd`/`klogd`
4. **持久盘**:等待 `/dev/vda`,挂载到 `/data`(必要时 `mkfs.ext2`);
将 `/data/etc` **bind 覆盖 `/etc`** → 用户与口令重启不丢
5. **首次开机向导**(无 `/etc/.user_setup_done` 时):
- **先**设置 **root 密码**(两次确认,≥6 位;逐字显示 `*`)
- 再输入用户名(校验字符集,禁止 `root`/重复)并设置用户密码(同样显示 `*`)
- `chpasswd` / `adduser` 写入;用户加入 `wheel`(供 `sudo`);写标记文件
6. **登录循环**:`setsid cttyhack /bin/mylogin`(自定义登录,密码显示 `*`)
缺 `mylogin` 时回退 BusyBox `getty`+`login`(密码不可见)
**不**直接进 root shell
```bash
ls -la /home/zmn/initramfs.cpio.gz
zcat /home/zmn/initramfs.cpio.gz | cpio -t 2>/dev/null | grep '^init$'
# 应输出:init
```
无 `disk.img` 时仍可 setup,但 `/etc` 在 RAM 中,**重启会重新进入创建向导**。
---
## 第八步:QEMU 启动
```bash
qemu-system-x86_64 \
-kernel /home/zmn/linux-6.12/arch/x86/boot/bzImage \
-initrd /home/zmn/initramfs.cpio.gz \
-append "console=ttyS0 rdinit=/init" \
-nographic \
-m 256M
```
### 参数说明
| 参数 | 作用 |
|------|------|
| `-kernel` | 指定内核 bzImage 路径 |
| `-initrd` | 指定 initramfs 路径 |
| `console=ttyS0` | 内核日志输出到串口(配合 `-nographic` 显示在终端) |
| `rdinit=/init` | 指定 initramfs 中的初始进程为 `/init` |
| `-nographic` | 不使用图形窗口,串口重定向到当前终端 |
| `-m 256M` | 分配 256MB 内存 |
### 启动成功标志
滚动的内核日志结束后出现:
### 7.1 首次开机流程(验收)
```
Run /init as init process
/bin/sh: can't access tty; job control turned off
~ #
Welcome to DreamLife Linux (BusyBox)
Storage: /dev/vda -> /data (persistent /etc)
==============================================
First boot: set root password, then create a user
==============================================
Set the root account password (needed for su / root login).
Root password: ******
Confirm Root password: ******
Root password set.
Username: alice
Password: *******
Confirm Password: *******
Created user 'alice'.
DreamLife-linux login: alice
Password: *******
[alice@DreamLife-linux ~]$ # 自动清屏 + fastfetch
```
`~ #` 即为 shell 提示符,此时可以输入命令:
之后重启(`disk.img` 在):**跳过**向导,直接 `DreamLife-linux login:`。
```
~ # uname -r
6.12.0
~ # ls
bin etc linuxrc root sys usr
dev init proc sbin tmp
登录成功后 shell 读取 `/etc/profile`:**清屏**并自动运行 `fastfetch`,再进入提示符。
> 注意:`/etc` 在有盘时 bind 自 `/data/etc`。若旧 `disk.img` 里仍是旧 profile,
> 需 `rm disk.img && ./build.sh disk` 重置,或手动改 `/data/etc/profile`。
### 8. 安装内核模块到 rootfs
```bash
make -C linux-6.12 modules_install INSTALL_MOD_PATH=/home/zmn/rootfs
depmod -b /home/zmn/rootfs 6.12.0
```
### 退出 QEMU
> 内核中保持 `=m` 的选项(如 netfilter NAT 扩展)必须装进 `/lib/modules`,否则 `modprobe` 无效。
> `build.sh` 的 `rootfs`/`busybox` 步骤会自动执行上述命令。
- 方法一:shell 中输入 `poweroff`
- 方法二:按 `Ctrl+A` 再按 `X`
### 8.1 安装 fastfetch(musl 动态链接)
```bash
./build.sh fastfetch
```
`step_fastfetch` 流程(缓存于 `FF_CACHE`,避免重复下载):
1. 下载官方 `fastfetch-musl-amd64.tar.gz`(`FF_VER`)
2. `strip` 后装入 `rootfs/bin/fastfetch`
3. 下载 musl `1.2.5` 源码本地编译,取 `libc.so` 装为:
- `rootfs/lib/libc.musl-x86_64.so.1`
- `rootfs/lib/ld-musl-x86_64.so.1` → 符号链接到上者
4. 用 loader 直接执行 `--version` 做冒烟测试
5. 随后 `step_initramfs` 重新打包
登录后运行:
```
[alice@DreamLife-linux ~]$ fastfetch
alice@DreamLife-linux
---------------------
OS: DreamLife Linux (BusyBox) x86_64
Kernel: Linux 6.12.0
...
```
> **说明**:根文件系统无 glibc/ld-linux,故采用 musl 官方二进制 + 自带 musl loader。
> fastfetch 部分高级模块依赖 `dlopen` 加载宿主共享库,在本最小系统中会自动降级/跳过。
### 8.2 安装包管理器 dpkl(apt 风格,底层 apk)+ 网络(DHCP)
```bash
./build.sh apk
```
`step_apk` 流程:
1. 下载 `APKINDEX.tar.gz` 解析当前版本号(`apk-tools-static`、`alpine-keys`)
2. 下载两个 `.apk`(本质是 tar.gz)到 `APK_CACHE` 并解包:
- `sbin/apk.static` → `rootfs/usr/libexec/apk`(Alpine 官方静态二进制,
移出 PATH;本系统已带 musl loader 故可直接运行)
- `etc/apk/keys/*.rsa.pub` → `rootfs/etc/apk/keys/`(仓库签名公钥)
3. 写 `rootfs/sbin/dpkl`:**apt 风格前端**(见下方用法表),
并建软链 `sbin/apk` → `dpkl`(旧习惯/`apk` 原生命令透传兼容)
4. 写 `rootfs/etc/apk/repositories`(main + community,纯 HTTP;
**默认国内源清华 TUNA**,可用 `ALPINE_MIRROR` 换成阿里云/华为云等,或官方 `http://dl-cdn.alpinelinux.org/alpine`)
5. 预建包数据库:`/usr/libexec/apk --root rootfs --arch x86_64 add --initdb --usermode`
(生成 `lib/apk/db/{installed,scripts.tar.gz,triggers}` 与 `etc/apk/{arch,world}`,
否则 guest 内首次使用会报 `Failed to open apk database`)
6. 写 `rootfs/usr/share/udhcpc/default.script`(BusyBox DHCP 钩子,见下)
7. 处理 `etc/hosts`:仅当镜像是官方 dl-cdn 时固定可达 IP;
用国内镜像时自动清掉历史固定行(见“已知网络坑”)
**网络**:QEMU 命令已默认带用户模式网卡
(`-netdev user,id=n0 -device virtio-net-pci,netdev=n0`)。init 挂盘后后台执行
`udhcpc -i eth0`,脚本下发 `10.0.2.15/24`、网关 `10.0.2.2`,并把 DNS
`10.0.2.3` 写入 `/etc/resolv.conf`(随 `/etc` bind 持久化)。
**持久化策略(init 的 `PERSIST=1` 分支:首启种子 + bind 挂载)**:
| 目录 | 首启种子 | 说明 |
|------|----------|------|
| `/etc` → `/data/etc` | `cp -a /etc/.` | 账号、仓库、DNS |
| `/usr` → `/data/usr` | `cp -a /usr/.` | apk 安装的可执行文件/库 |
| `/var` → `/data/var` | `cp -a /var/.` | apk 缓存、日志 |
| `/lib/apk` → `/data/lib/apk` | `cp -a /lib/apk/.` | **只**绑 apk 数据库;`/lib` 其余部分保持 initramfs 版本(内核模块/加载器不被磁盘版覆盖) |
**镜像选择(默认国内源)**:
- 默认 `http://mirrors.tuna.tsinghua.edu.cn/alpine`(清华 TUNA,纯 HTTP 直连,
guest 内 busybox wget 无需 CA 证书;阿里云、华为云亦支持纯 HTTP,实测均 200)。
- 本网络可用性实测:TUNA / 阿里云 / 华为云 纯 HTTP 200;中科大 USTC 403。
- 换源:`ALPINE_MIRROR=http://mirrors.aliyun.com/alpine ./build.sh apk` 后重打包。
换回官方源时 `step_apk` 会自动补 hosts 固定行,换用国内镜像时自动清除。
**已知网络坑(切回官方源时才会遇到)**:
- `dl-cdn.alpinelinux.org`(fastly)的 DNS 会给出若干从本网络黑洞的 IP
(`23.235.x`、`104.156.x` 不通 80 端口),apk 先撞上会超时失败 →
`step_apk` 检测到镜像为 dl-cdn 时自动把两个可达 IP 写入 `etc/hosts`
(musl 先查 hosts 再 DNS)。
- init 里把 `tcp_syn_retries` 调成 `2`(黑洞 SYN 快速失败;默认要等 ~127s),
对国内源无副作用。
- 速度参考:官方源在本网络仅约 25 KB/s,`apk update`(两个索引约 3MB)约 2 分钟;
TUNA 实测两个索引各约 1 秒(`apk update` 秒级完成)。
guest 内用法(`dpkl` = apt 风格命令,底层为 Alpine apk):
| dpkl(apt 风格) | 对应 apt 命令 | 实际执行 |
|------------------|---------------|----------|
| `dpkl update` | `apt update` | `apk update`(刷新索引) |
| `dpkl upgrade` | `apt upgrade` | `apk upgrade` |
| `dpkl install [-y] <pkg>...` | `apt install` | `apk add <pkg>...` |
| `dpkl remove [-y] <pkg>...` | `apt remove` | `apk del <pkg>...` |
| `dpkl purge <pkg>...` | `apt purge` | `apk del <pkg>...`(别名) |
| `dpkl search <kw>` | `apt search` | `apk search <kw>` |
| `dpkl show [pkg]...` | `apt show` | `apk info [pkg]...` |
| `dpkl list` | `apt list` | `apk info`(列已安装) |
| `dpkl clean` | `apt clean` | 清空 `/var/cache/apk` 下载缓存 |
| `dpkl version` | — | 显示 `dpkl, version 3.0.8-r0 ...` |
| `dpkl help` | `apt --help` | 内置帮助 |
- `-y` / `--yes` / `--assume-yes` / `--no-install-recommends` 会被接受并忽略
(apk 本来就是非交互的),照搬 apt 肌肉记忆不会报错。
- **`apk` 仍可用**:它是指向 `dpkl` 的软链,`apk add/del/info/...` 等原生命令
原样透传;真正的 apk 二进制在 `/usr/libexec/apk`(不在 PATH)。
- 典型流程:
```
dpkl update
dpkl install -y tree
dpkl list
dpkl remove tree
```
**滚动更新(Arch 式,`latest-stable` 稳定分支)**:
日常两连击即可把系统推到最新:
```
dpkl update # 刷新索引(TUNA 秒级)
dpkl upgrade # 全量升级所有已安装包(dpkl full-upgrade / dist-upgrade 为其别名)
```
- **会滚动的**:apk 数据库管理的包(`dpkl install` 装的一切,含 `tree`、`musl` 及
依赖),始终跟 `latest-stable` 小版本走(当前 3.22.x 系)。
- **不会滚动的**(宿主/自编,apk 管不到):
- 内核 → **`./build.sh kernel-up`**:自动查 TUNA 最新 `KERNEL_SERIES.x` 点版本
(实测 6.12.0 → 6.12.111),下载 → 携带 `.config` 换源码树 → 重编 →
刷 `/lib/modules` → 重打 initramfs → 重启 QEMU 即生效(`uname -r` 变化);
已是最新则增量重编(~5 分钟)。`./build.sh kernel` 仅重编当前源码。
换到全镜像最新大版本:`KERNEL_SERIES=7.2 ./build.sh kernel-up`(自动用 `v7.x` 目录,
实测最新 `7.2.8`);回滚换回 `KERNEL_SERIES=6.12` 即可,旧 config 备份在
`~/.cache/kernel/config-*.bak`。**生效需退出 QEMU 重新启动**(guest 内 `reboot`
复用启动时已载入 fw_cfg 的旧 bzImage),`uname -r` 验证。
- BusyBox、mylogin、sudo、fastfetch、dpkl、init → `./build.sh <对应步骤>`
- **musl 滚动安全**(已实测):`dpkl install` 拉入的 apk 版 musl 会接管
`/lib/ld-musl-x86_64.so.1`,此时宿主编译的 fastfetch(musl 动态链接)仍正常
运行(测试项 `fastfetch runs on apk-installed musl`)。
- **不要装这三个包**:`busybox`、`alpine-baselayout`、`apk-tools` —— 会覆盖
自编 BusyBox / 重建标准目录结构,破坏本系统底座。
- 64MB 小盘:升级/安装的 `.apk` 留在 `/var/cache/apk`,反复滚动后记得
`dpkl clean`(apt clean 同款)防撑满。
> Alpine 包是 musl 动态链接,本系统 loader 路径兼容,可直接运行。
> `tree` 会自动拉入 `musl` 依赖,两启实测均正常(boot1 的 alpine musl 写在
> 内存盘 `/lib`,boot2 回退 initramfs 自带 loader,musl ABI 兼容)。
> 与 BusyBox 直接冲突的包(`busybox`、`alpine-baselayout` 等)慎装。
---
### 9. 打包 initramfs(规范)
```bash
cd /home/zmn/rootfs
find . -print0 \
| cpio --null -o -H newc --owner root:root --reproducible \
| gzip -9 > /home/zmn/initramfs.cpio.gz
```
| 要点 | 原因 |
|------|------|
| `--owner root:root` | 解压后属主为 root,避免 uid=1000 文件导致权限问题 |
| `--reproducible` + 稳定目录序 | 构建可复现 |
| `gzip -9` | 最大压缩 |
| `-H newc` | 内核要求的 cpio 格式 |
| `find . -print0` | 正确处理特殊文件名 |
验证:
```bash
ls -la initramfs.cpio.gz
zcat initramfs.cpio.gz | cpio -t | grep -E '^(\./)?init$'
zcat initramfs.cpio.gz | cpio -tv | head # 属主应为 root root
```
### 10. QEMU 启动(推荐:带持久盘)
```bash
# build.sh 会在打包后自动生成 disk.img(64MB ext4)
qemu-system-x86_64 \
-kernel /home/zmn/linux-6.12/arch/x86/boot/bzImage \
-initrd /home/zmn/initramfs.cpio.gz \
-drive file=/home/zmn/disk.img,if=virtio,format=raw \
-netdev user,id=n0 -device virtio-net-pci,netdev=n0 \
-append "console=ttyS0 quiet loglevel=3 rdinit=/init" \
-display none -serial mon:stdio \
-m 256M
```
| 参数 | 作用 |
|------|------|
| `-kernel` | bzImage |
| `-initrd` | gzip 压缩的 cpio initramfs |
| `-drive ...,if=virtio` | virtio-blk 持久盘 → 账号存 `/etc`(bind 自 `/data/etc`) |
| `-netdev user ... virtio-net-pci` | 用户模式网卡(DHCP:10.0.2.15,网关/DNS 10.0.2.2/3) |
| `console=ttyS0` | 内核日志走串口 |
| `quiet` / `loglevel=3` | 屏蔽内核启动跑码(`dmesg` 仍可查看);init 内再 `dmesg -n 1` |
| `rdinit=/init` | 指定 initramfs 中的 PID 1 |
| `-display none` | 关闭 VGA 窗口 → **SeaBIOS 横幅不再转到终端** |
| `-serial mon:stdio` | 串口接到终端;`Ctrl+A` `C` 进 QEMU monitor,`quit` 退出 |
可选:
```bash
# 加速(宿主支持 KVM 时)
-enable-kvm -cpu host
```
**启动成功标志:**
```
DreamLife Linux BusyBox · starting ← 开机动画(spinner)
Welcome to DreamLife Linux (BusyBox)
Storage: /dev/vda -> /data (persistent /etc /usr /var /lib/apk)
==============================================
First boot: create a user and set passwords
==============================================
Username: ...
DreamLife-linux login:
```
登录后:
```
[alice@DreamLife-linux /]$ id
uid=1000(alice) gid=1000(alice) ...
```
此时应具备:
- 首次出现创建向导;完成后回到 `login:`(**不**直进 shell)
- 无 `job control turned off` 警告
- `hostname` 显示 `DreamLife-linux`
- `ps`/`id` 显示用户名(`/etc/passwd` 生效)
- `zcat /proc/config.gz`、`ls /lib/modules/$(uname -r)` 可用
**重置账号/已装软件包**:关机后 `rm -f /home/zmn/disk.img`,再 `./build.sh disk`
(或全量)。注意:apk 包数据库与装进 `/usr` 的包也在盘上,删盘一并重置。
退出:guest 内 `poweroff`,或宿主 `Ctrl+A` 再 `C` 再 `quit`。
---
## 目录与文件清单(rootfs 必备)
```
rootfs/
├── init # 自定义 PID 1(可执行)
├── bin/busybox # 静态 busybox + 符号链接
├── bin/fastfetch # musl 动态 fastfetch(step_fastfetch 安装)
├── bin/mylogin bin/sudo
├── sbin/dpkl # apt 风格包管理前端(step_apk 安装)
├── sbin/apk # 软链 → dpkl(旧命令透传兼容)
├── usr/libexec/apk # apk.static 真身(不在 PATH,dpkl 内部调用)
├── sbin/telinit # telinit 垫片 → poweroff/reboot -f
├── sbin/poweroff 等 # wrapper:busybox poweroff -f(见故障排查)
├── usr/share/udhcpc/default.script # DHCP 钩子(step_apk 写入)
├── sbin/ usr/bin/ usr/sbin/
├── etc/
│ ├── passwd group shadow hostname hosts # 用官方源时才含 dl-cdn IP 固定
│ ├── resolv.conf profile fstab issue motd os-release
│ ├── apk/ # repositories + keys/(Alpine 签名公钥)+ arch + world
│ ├── inittab # 可选(busybox init 路径)
│ └── init.d/rcS
├── lib/apk/db/ # 预建 apk 包数据库(installed/triggers/scripts.tar.gz)
├── lib/modules/6.12.0/ # .ko + modules.dep
├── lib/ld-musl-x86_64.so.1 # musl 动态加载器(fastfetch/apk 用)
├── lib/libc.musl-x86_64.so.1
├── proc/ sys/ dev/ dev/pts/ tmp/ run/ root/ var/log/
```
---
## 故障排查
### 症状:只有一个闪烁的光标,其他什么都没有
### 只有闪烁光标 / 无任何输出
按以下顺序排查:
| 序号 | 检查项 | 排查命令 | 修复方法 |
|------|--------|----------|----------|
| 1 | bzImage 是否存在 | `ls -la arch/x86/boot/bzImage` | 重新编译内核 |
| 2 | 是否启用了串口控制台 | `grep SERIAL_8250_CONSOLE .config` | 改用 `x86_64_defconfig` |
| 3 | 是否启用了 initrd 支持 | `grep BLK_DEV_INITRD .config` | 改用 `x86_64_defconfig` |
| 4 | BusyBox 是否静态链接 | `file rootfs/bin/busybox` | 开启 `CONFIG_STATIC=y` 重编 |
| 5 | init 是否存在且可执行 | `ls -la rootfs/init` | `chmod +x rootfs/init` |
| 6 | initramfs 是否包含 init | `zcat initramfs.cpio.gz \| cpio -t \| grep init` | 重新打包 |
| 序号 | 检查 | 命令 | 修复 |
|------|------|------|------|
| 1 | bzImage 存在 | `ls arch/x86/boot/bzImage` | 重编内核 |
| 2 | 串口控制台 | `grep SERIAL_8250_CONSOLE .config` | 用 defconfig 起底 |
| 3 | initrd 支持 | `grep BLK_DEV_INITRD .config` | 开启 `BLK_DEV_INITRD` |
| 4 | busybox 静态 | `file rootfs/bin/busybox` | `CONFIG_STATIC=y` 重编 |
| 5 | init 可执行 | `ls -la rootfs/init` | `chmod +x` |
| 6 | 打包含 init | `zcat initramfs.cpio.gz \| cpio -t \| grep init` | 重新打包 |
### 常见错误对照
| 错误信息 | 原因 | 解决 |
|----------|------|------|
| `qemu: could not open kernel file` | bzImage 不存在 | 编译内核 |
| `Run /init as init process` 后无输出 | init 不可执行或 BusyBox 动态链接缺库 | 检查权限、改静态编译 |
| `tc.c: error: 'TCA_CBQ_MAX' undeclared` | `CONFIG_TC=y` 但内核已删除 CBQ 结构体 | 禁用 `CONFIG_TC` 后重编 |
| `busybox: cannot open ... No such file or directory` | 上次编译失败,二进制未生成 | 修复编译错误后重新 `make` |
| `can't access tty; job control turned off` | 正常现象,无需处理 | 忽略 |
| 内核 panic `No working init found` | initramfs 未加载或 init 路径错误 | 检查 `rdinit=` 和 `BLK_DEV_INITRD` |
| 终端完全没有内核日志 | 缺少串口控制台支持 | 检查 `SERIAL_8250_CONSOLE` |
| 现象 / 报错 | 原因 | 解决 |
|-------------|------|------|
| `can't access tty; job control turned off` | 未挂 devpts 或未用 cttyhack | 检查 `/init` 是否含 `devpts` + `setsid cttyhack` |
| `tc.c: 'TCA_CBQ_MAX' undeclared` | `CONFIG_TC=y` 与新内核头不兼容 | 禁用 `CONFIG_TC` |
| 内核 panic `No working init found` | initramfs 缺 init 或路径错 | 检查 `rdinit=` 与权限 |
| SELinux 启动刷大量 AVC/无 policy 告警 | 默认启用 SELinux 但无策略 | `# CONFIG_SECURITY_SELINUX is not set` 后重编 |
| `modprobe: can't change directory to ...` | 未 `modules_install` | `make modules_install INSTALL_MOD_PATH=rootfs` |
| `ps`/`id` 显示数字 UID | 缺 `/etc/passwd` | 放入 `rootfs/etc/passwd` |
| `zcat /proc/config.gz` 不存在 | 未开 IKCONFIG | `CONFIG_IKCONFIG=y` + `IKCONFIG_PROC=y` |
| initramfs 文件属主非 root | 打包未加 `--owner` | `cpio --owner root:root` |
| 无线/USB 等驱动“编了但 QEMU 用不到” | defconfig 过大 | 叠加 `kernel-minimal.fragment` |
### apk / 网络 / 关机
| 现象 / 报错 | 原因 | 解决 |
|-------------|------|------|
| `dpkl: command not found` | 未执行 apk 步骤 | `./build.sh apk` 后重打包 |
| `dpkl upgrade` 后 `uname -r` 不变 | **内核不归 apk 管**(QEMU `-kernel` 从宿主加载 bzImage) | 宿主跑 `./build.sh kernel-up`,然后重启 QEMU |
| `Failed to open apk database` | 镜像里没有预建数据库 | `./build.sh apk`(内部 `--initdb`)后重打包 |
| `apk update: Operation timed out` 或一直卡住 | 镜像是官方 dl-cdn 且 DNS 给出黑洞 IP | `./build.sh apk`(默认 TUNA 国内源,无需固定;用 dl-cdn 时会自动写 `etc/hosts` 固定行);老 disk 删掉重来或 root 改 guest `/etc/hosts` |
| `apk update` 花 1~2 分钟 | 镜像是官方源且出网慢(实测约 25 KB/s) | 换国内源:`ALPINE_MIRROR=http://mirrors.tuna.tsinghua.edu.cn/alpine ./build.sh apk`(TUNA/阿里云/华为云均实测可用,USTC 403) |
| guest 无 IP / DNS 不通 | QEMU 没加网卡,或 udhcpc 未跑 | 加 `-netdev user,id=n0 -device virtio-net-pci,netdev=n0`;root 下手动 `udhcpc -i eth0` |
| `poweroff` 立刻回到提示符、QEMU 不退出 | 老镜像的 poweroff 只给 PID1 发信号(自定义 init 不处理,信号被内核丢弃) | `./build.sh rootfs` 重打包(`sbin/poweroff` 已换成 `busybox poweroff -f` wrapper);应急 `poweroff -f` |
### 体积参考(本仓库一次构建)
| 产物 | 约略大小 |
|------|----------|
| bzImage(精简后) | ~10 MB |
| busybox(静态) | ~2.4 MB |
| fastfetch(musl strip 后) | ~2.9 MB(+ musl libc ~0.8 MB) |
| apk(静态 apk.static) | ~5.2 MB |
| initramfs.cpio.gz | ~5.9 MB |
| disk.img(64MB ext4) | 64 MB(空洞文件,实际占用视写入量) |
---
## 完整构建脚本
## 验收清单
将以下内容保存为 `build.sh`,可一键完成整个构建流程:
- [x] `./build.sh` 可一键完成全部步骤
- [x] rootfs 具备 `/etc` 基础文件
- [x] init 挂载 devpts/tmpfs,shell 支持 job control
- [x] 内核关闭 SELinux,开启 IKCONFIG/USER_NS/常见硬化
- [x] `=m` 模块安装到 `/lib/modules` 并生成 `modules.dep`
- [x] BusyBox 静态链接,裁剪无用网络服务
- [x] 打包 `--owner root:root` + `gzip -9`
- [x] **首次开机交互创建用户 + root 密码**
- [x] **创建后回到登录提示符,用新用户 / root 登录**
- [x] **`disk.img` 持久化 `/etc`:重启跳过创建向导**
- [x] 无 `disk.img` 时行为可预期(每次重启重新 setup)
- [x] **登录后 `fastfetch` 可用,OS 显示 DreamLife Linux**
- [x] **登录后自动清屏并展示 fastfetch**
- [x] **密码输入(setup + login + sudo)逐字显示 `*`,不回显明文**
- [x] **`sudo <cmd>` 可用(wheel 组 + 当前用户密码);`su` 可用(busybox SUID)**
- [x] **无 SeaBIOS/内核跑码,开机动画后进欢迎页**
- [x] **`./build.sh apk`:装 apk(静态)+ 签名密钥 + 仓库 + 预建包数据库 + DHCP 脚本**
- [x] **开机自动 DHCP:`10.0.2.15/24`、网关 `10.0.2.2`、DNS 写入 `resolv.conf`(持久)**
- [x] **`dpkl update` + `dpkl install -y tree` 成功(apt 风格),`tree`(musl 动态)可执行**
- [x] **`dpkl search/show/list/version` 可用;`apk` 软链透传兼容(`apk info` 正常)**
- [x] **滚动更新:`dpkl upgrade` 全量升级 exit 0;`dpkl clean` 清缓存可用**
- [x] **内核滚动:`./build.sh kernel-up` 6.12.0 → 6.12.111 → 7.2.8(`KERNEL_SERIES=7.2` 跨大版本,配置自动迁移),`uname -r` 对账通过**
- [x] **apk 版 musl 接管 loader 后 fastfetch(宿主 musl 二进制)仍正常运行**
- [x] **`/usr` `/var` `/lib/apk` bind 持久化:重启后 `tree` 与包数据库仍在(`dpkl list`)**
- [x] **`poweroff` 正常关机退出 QEMU,`reboot` 正常重启**
- [x] **自动化验收 `bash run_apk_test.sh` → 28/28 passed**
```bash
#!/bin/bash
set -e
---
KERNEL_DIR=/home/zmn/linux-6.12
BB_DIR=/home/zmn/busybox
ROOTFS=/home/zmn/rootfs
INITRAMFS=/home/zmn/initramfs.cpio.gz
## 附录:关闭 QEMU 的其他方式
echo "=== [1/6] 配置内核 ==="
cd "$KERNEL_DIR"
make x86_64_defconfig
- guest 内:`poweroff` / `reboot` / `halt`(`sbin/*` 已换成强制 `-f` wrapper,
直接走 `reboot(2)` 系统调用,不依赖 init 信号;`telinit` 垫片兜底)
- 宿主:`Ctrl+A` 然后 `C` 然后 `quit`
echo "=== [2/6] 编译内核 ==="
make CC=gcc-14 -j"$(nproc)"
test -f arch/x86/boot/bzImage && echo "bzImage OK"
## 附录:常见登录问题
echo "=== [3/6] 配置 BusyBox(静态编译,禁用 tc)==="
cd "$BB_DIR"
make defconfig
sed -i 's/^# CONFIG_STATIC is not set/CONFIG_STATIC=y/' .config
sed -i 's/^CONFIG_TC=y/# CONFIG_TC is not set/' .config
grep -E '^CONFIG_STATIC=|^CONFIG_TC=' .config
echo "=== [4/6] 编译 BusyBox ==="
make CC=gcc-14 -j"$(nproc)"
file busybox | grep -q "statically linked" && echo "static OK"
echo "=== [5/6] 安装到 rootfs 并写 init ==="
mkdir -p "$ROOTFS"/{bin,sbin,etc,proc,sys,dev,tmp,root,usr/bin,usr/sbin}
make CONFIG_PREFIX="$ROOTFS" install
cat > "$ROOTFS/init" << 'INITEOF'
#!/bin/sh
mount -t proc none /proc
mount -t sysfs none /sys
mount -t devtmpfs none /dev
hostname minimal-linux
exec /bin/sh
INITEOF
chmod +x "$ROOTFS/init"
echo "=== [6/6] 打包 initramfs ==="
cd "$ROOTFS"
find . | cpio -o -H newc 2>/dev/null | gzip > "$INITRAMFS"
echo "Done: $INITRAMFS"
echo ""
echo "启动命令:"
echo "qemu-system-x86_64 -kernel $KERNEL_DIR/arch/x86/boot/bzImage -initrd $INITRAMFS -append \"console=ttyS0 rdinit=/init\" -nographic -m 256M"
```
运行:
```bash
chmod +x build.sh
./build.sh
```
| 现象 | 原因 | 解决 |
|------|------|------|
| 每次重启都要创建用户 | 未挂 `disk.img` | 加 `-drive file=disk.img,if=virtio,format=raw` |
| `login: ` 后密码被拒 | 向导中密码不一致/太短;或 root 仍为 `*` | 重跑 setup;或 `rm disk.img` 重来 |
| 无 `DreamLife-linux login:` | init 被截断/未更新 | `./build.sh rootfs` 后确认 init 含 `first_boot_setup` |
| `adduser: not found` | 未装 loginutils | 确认 `CONFIG_ADDUSER=y` 后 `./build.sh busybox` |
| `fastfetch: not found` | 未执行 fastfetch 步骤 | `./build.sh fastfetch`(会自动下载并重新打包) |
| `fastfetch: can't load interpreter` / loader 报错 | 缺 musl loader | 确认 `rootfs/lib/ld-musl-x86_64.so.1` 存在后重新打包 |
| 密码输入完全不可见(无 `*`) | 使用了旧 getty/login 或旧 profile | `./build.sh rootfs` 后确认有 `bin/mylogin`;`rm disk.img` 重置 |
| `Login incorrect` 但密码正确 | mylogin 未编译进 initramfs 或 shadow 解析旧版本 | `./build.sh mylogin && ./build.sh initramfs` |
| `sudo: not found` | 未编译 sudo 或未重新打包 | `./build.sh mylogin && ./build.sh initramfs` |
| `sudo: ... is not in the wheel group` | 老 disk 上的用户未加入 wheel | guest 内 root 执行 `addgroup <user> wheel`,或 `rm disk.img` 重置 |
| `su: must be suid to work properly` | busybox 未 SUID | `./build.sh mylogin`(会 `chmod 4755` busybox)后重打包 |
| 终端仍出现 `SeaBIOS (...)` | 仍用 `-nographic` | 改为 `-display none -serial mon:stdio` |