diff --git a/README.md b/README.md index ef2a0b7..ef69cb6 100644 --- a/README.md +++ b/README.md @@ -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 **为什么必须静态编译**: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] ...` | `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 -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 ` 可用(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 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` |