小技巧:让内核“自带文件系统”:把 init 程序直接塞进内核二进制!

在内核还不支持读取磁盘、不支持文件系统时,如果使用一个临时的方式支持一个简单的文件系统? 今天教你一招——把文件系统直接‘编译进内核’! 无需磁盘、无需驱动,内核启动即执行 init,极致简洁!”


💡 问题:自制 OS 的第一道坎 —— “内核如何找到 init?”

当你写完第一个自制操作系统内核时,一定会遇到这个经典难题:

“我的内核已经运行了,但怎么加载并执行 init 进程呢?”

传统方案:


从磁盘读取
:需要实现文件系统、块设备驱动、分区表解析…

太复杂
:对于初学者来说,这简直是“地狱级”任务。

有没有更简单的办法?

当然有!


🧠 核心思路:把“文件系统”直接塞进内核二进制!

什么是 initramfs?

在 Linux 中, initramfs (Initial RAM Filesystem)是一个 内存中的根文件系统 ,它被压缩后嵌入到内核镜像中。内核启动时将其解压到内存,然后挂载为根文件系统,再执行 /sbin/init 。

我们不搞复杂的 initramfs,而是用一个 极简版的“内联文件系统” :

把 init 程序、配置文件等,直接以二进制数据的形式,作为“静态数据”编译进内核!


🛠️ 实现方法:用汇编或 C 语言将文件内容“硬编码”进内核

把文件系统塞内核二进制

创建文件系统文件结构

将内容打包为cpio格式

脚本文件: cpio_fs.sh

用法: bash cpio_fs.sh build

将cpio文件编译进内核elf文件


🔍 内核如何“读取”这些数据?

步骤 1:获取数据地址和大小

步骤 2:解析cpio文件格式

cpio (copy in / copy out)是一种用于归档和传输文件的 Unix 工具和文件格式。它最早出现在 Unix System III(1981 年)中,比 tar 更早被标准化(POSIX.1-1988 就包含了 cpio ),尽管在日常使用中不如 tar 流行,但它在 Linux 内核 initramfs、RPM 包管理器等关键系统组件中被广泛使用。


一、cpio 的工作原理

cpio 本身不直接遍历目录,而是 从标准输入读取文件路径列表 (通常由 find 或 echo 生成),然后:

-o
(copy-out)模式

:将这些文件打包成一个归档流,输出到 stdout。
-i
(copy-in)模式

:从 stdin 读取归档流,解包到文件系统。
-p
(pass)模式

:直接复制文件到指定目录(不生成归档)。

因此, cpio 的核心思想是: 归档 = 文件列表 + 文件内容的线性拼接 。


二、cpio 的归档格式

cpio 支持多种格式,主要分为三类:

  1. binary(旧格式,已废弃)

    非可移植,依赖字节序
    已淘汰,不推荐使用

  2. ASCII / “newc” 格式(最常用)

    可读的文本头 + 二进制数据
    被 Linux initramfs、RPM 等广泛采用
    文件名和元数据以
    ASCII 十六进制字符串
    存储
    支持长文件名(最多 256 字节,默认)
    所有字段固定长度(便于解析)

newc 格式的头部结构(每字段 8 字节,共 112 字节):

字段 长度 说明
c_magic 6 字节 魔数 “070701” (newc)或 “070702” (crc)
c_ino 8 字节 inode 号(十六进制 ASCII)
c_mode 8 字节 文件权限(如 0100644 )
c_uid 8 字节 用户 ID
c_gid 8 字节 组 ID
c_nlink 8 字节 硬链接数
c_mtime 8 字节 修改时间(Unix 时间戳)
c_filesize 8 字节 文件内容长度(字节)
c_devmajor 8 字节 设备主设备号(仅设备文件)
c_devminor 8 字节 设备次设备号
c_rdevmajor 8 字节 特殊文件的主设备号
c_rdevminor 8 字节 特殊文件的次设备号
c_namesize 8 字节 文件名长度(包含结尾 )
c_checksum 8 字节 校验和(newc 中通常为 0)

所有数值字段均为 8 字节 ASCII 十六进制字符串 (小端?不,是字符串!比如 1024 存为 “00000400” )

文件记录布局:

文件名后
必须
有 null 字符

文件名和内容之间
自动对齐到 4 字节边界
(用

填充)
文件内容后也
对齐到 4 字节边界

归档结束标志:

一个特殊文件名为
“TRAILER!!!”
的条目

c_filesize = 0
表示归档结束


  1. crc 格式(newc + 校验和)

    与 newc 类似,但
    c_checksum
    字段包含文件内容的 CRC32 校验和
    魔数为
    “070702”


创建和解包 cpio 归档

创建归档(newc 格式):

解包归档:

列出内容:

步骤 3:解析并执行 init

完整代码:


🎯 优势与适用场景

✅ 优势

极度简化

:无需磁盘驱动、文件系统、分区表
快速启动

:内核一启动就能执行 init,无需等待磁盘 I/O
调试友好

:所有代码都在一个二进制中,调试方便
可移植性强

:适用于任何能运行内核的环境(QEMU、物理机、嵌入式)

🎯 适用场景

自制操作系统学习

:初学者的最佳起点
嵌入式系统

:资源受限,需要最小化启动时间
引导加载程序

:在进入完整操作系统前提供简单功能
安全/恢复环境

:无需依赖外部存储


📦 实际应用:让内核“自带” init 进程

想象一下,你的内核二进制文件 kernel.bin 不仅包含内核代码,还包含了:

/sbin/init

(你的初始化程序)
/etc/fstab

(文件系统挂载表)
/bin/sh

(Shell)
/usr/bin/ls

(常用命令)

这一切都 编译进同一个文件 !

启动流程变成:

没有磁盘、没有驱动、没有文件系统——只有纯粹的内核和它的“内置文件”!


💡 高阶技巧:动态生成文件系统

如果你的 init 程序是用 C/C++ 编写的,可以写一个 构建脚本 ,自动将源文件编译成二进制,再嵌入到内核中:


🚫 注意事项

内存占用

:所有文件都加载到内存,不适合大文件
更新困难

:修改文件需重新编译内核
安全性

:所有代码都在内核中,一旦出错可能导致系统崩溃


✅ 总结:一个简单却强大的技巧

这个小技巧的核心思想是:

“不要去读取磁盘上的文件,而是把文件直接‘变成’内核的一部分。”

它完美解决了自制 OS 初期的“鸡生蛋、蛋生鸡”问题:

没有文件系统 → 无法读取 init
没有 init → 无法挂载文件系统

通过这个技巧,我们绕过了这个死循环,让内核 自给自足 。


关注我,带你从零开始,玩转操作系统开发!

最后修改: 2026年7月1日

作者

评论

发表评论

您的邮箱地址不会被公开。