# DreamLife Linux 内核 + BusyBox 最小系统构建指南 > **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 # 打包产物 ``` --- ## 环境准备 ```bash sudo apt update 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 ``` > 若无 `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 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 ARCH=x86_64 defconfig # 叠加最小化/硬化片段 # (build.sh 内部用 awk 合并 kernel-minimal.fragment,效果等价于 merge_config) make ARCH=x86_64 olddefconfig ``` **相对 `x86_64_defconfig` 的关键改动(见 `kernel-minimal.fragment`):** | 类别 | 配置项 | 作用 | |------|--------|------| | 安全 | `# 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_(SECURITY_SELINUX|IKCONFIG|USER_NS|FORTIFY_SOURCE|DEVMEM|VIRTIO_BLK)=' .config ``` ### 3. 编译内核 ```bash cd /home/zmn/linux-6.12 make ARCH=x86_64 CC=gcc-14 -j$(nproc) # 成功标志:Kernel: arch/x86/boot/bzImage is ready ls -la arch/x86/boot/bzImage ``` ### 4. 配置 BusyBox ```bash cd /home/zmn/busybox make defconfig # 静态链接(initramfs 无 /lib 动态加载器) sed -i 's/^# CONFIG_STATIC is not set/CONFIG_STATIC=y/' .config sed -i 's/^CONFIG_STATIC=.*/CONFIG_STATIC=y/' .config # 禁用 tc(新版内核头文件已删除 CBQ 结构体,否则编译失败) sed -i 's/^CONFIG_TC=y/# CONFIG_TC is not set/' .config # 叠加裁剪片段(关闭 telnetd/httpd/ftpd/i2c/ubi 等) # 见 busybox-minimal.fragment,由 build.sh 自动合并 make oldconfig **安全**:出厂 rootfs **不带空口令**;用户/ root 密码在**首次开机交互**创建, > 并写入持久盘(若挂载了 `disk.img`)。 ### 7. init 脚本要点(首次登录 / 持久化) 当前 `/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 无 `disk.img` 时仍可 setup,但 `/etc` 在 RAM 中,**重启会重新进入创建向导**。 ### 7.1 首次开机流程(验收) ``` Run /init as init process 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 ``` 之后重启(`disk.img` 在):**跳过**向导,直接 `DreamLife-linux login:`。 登录成功后 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 ``` > 内核中保持 `=m` 的选项(如 netfilter NAT 扩展)必须装进 `/lib/modules`,否则 `modprobe` 无效。 > `build.sh` 的 `rootfs`/`busybox` 步骤会自动执行上述命令。 ### 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] ...` | `apt install` | `apk add ...` | | `dpkl remove [-y] ...` | `apt remove` | `apk del ...` | | `dpkl purge ...` | `apt purge` | `apk del ...`(别名) | | `dpkl search ` | `apt search` | `apk search ` | | `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 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` | 重新打包 | ### 常见错误对照 | 现象 / 报错 | 原因 | 解决 | |-------------|------|------| | `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(空洞文件,实际占用视写入量) | --- ## 验收清单 - [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 ` 可用(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** --- ## 附录:关闭 QEMU 的其他方式 - guest 内:`poweroff` / `reboot` / `halt`(`sbin/*` 已换成强制 `-f` wrapper, 直接走 `reboot(2)` 系统调用,不依赖 init 信号;`telinit` 垫片兜底) - 宿主:`Ctrl+A` 然后 `C` 然后 `quit` ## 附录:常见登录问题 | 现象 | 原因 | 解决 | |------|------|------| | 每次重启都要创建用户 | 未挂 `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 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` |