从零写 OS 内核-第九篇:用户态程序加载 —— ELF 解析与简易文件系统

“你的 OS 能 fork/exec,但程序从哪来? 今天,我们构建第一个文件系统,让内核能从‘磁盘’加载用户程序!”

在上一篇中,我们实现了 fork 和 exec ,但用户程序仍需 硬编码在内核镜像中 。 这显然不现实——真正的操作系统必须能 从存储设备加载任意程序 。

而这一切,依赖两大核心模块: ✅ 文件系统 —— 组织和管理磁盘上的文件 ✅ ELF 加载器 —— 解析并加载可执行文件到内存

今天,我们就来实现一个 极简但可用的文件系统(Initramfs 风格) ,并完成 ELF 程序加载 ,让你的 exec(“/bin/ls”) 真正工作!


🗂️ 一、为什么需要文件系统?

没有文件系统,你的 OS 就像一台没有硬盘的电脑—— 所有程序必须编译进内核,无法动态扩展。

文件系统的核心职责:

命名

:通过路径(如
/bin/ls
)访问文件
存储

:将文件内容映射到物理存储(磁盘/内存)
元数据

:记录文件大小、权限、类型等

💡 初期无需复杂磁盘驱动 ——我们可以用 Initramfs(初始内存文件系统) : 将所有文件打包进内核镜像,启动时加载到内存。


📦 二、Initramfs:最简文件系统实现

Initramfs 是 Linux 启动初期常用的内存文件系统,非常适合我们的场景。

设计思路:

  1. 将所有用户程序打包成一个二进制 blob
  2. 定义简单目录结构

    (扁平或树形)

  3. 内核启动时解析 blob,构建内存中的文件索引

文件条目格式(简化版):

构建流程(Makefile):

📌 关键 :使用 –format=binary 将 user_files.bin 作为二进制数据链接进内核。


🔍 三、ELF 文件格式速览

ELF(Executable and Linkable Format)是 Unix 系统的标准可执行文件格式。

关键结构:

结构 作用
ELF Header 描述架构、入口地址、程序头偏移
Program Header Table 描述如何加载到内存( PT_LOAD 段)
Sections 编译链接用(运行时不需要)

我们关心的字段(32 位):

✅ 加载器只需解析 Program Headers,忽略 Sections!


⚙️ 四、实现 ELF 加载器

步骤:

  1. 从文件系统读取 ELF 文件到内核缓冲区
  2. 验证 ELF 魔数和类型
  3. 遍历 Program Headers,加载
    PT_LOAD
  4. 设置入口地址

代码框架:

🔑 map_user_pages :为用户虚拟地址分配物理页并更新页表。


📁 五、文件系统 API:内核如何“找”文件?

我们需要一个简单的 VFS(虚拟文件系统)层,初期只支持 Initramfs:

💡 exec 内部会调用 open + read 读取 ELF 文件 ,与用户程序共用同一套 VFS!


🧪 六、测试:真正的 exec(“/bin/ls”)

构建用户程序 :

内核中 :

运行效果:

✅ 成功从“磁盘”(内存中的 Initramfs)加载并运行用户程序!


⚠️ 七、局限与未来方向

当前实现仍很初级,但为后续打下基础:

问题 未来方案
只读 Initramfs 实现 ext2/FAT32 磁盘文件系统
无动态链接 实现 ELF 动态加载器(ld.so)
无文件权限 添加 inode 和权限位
无目录遍历 实现 opendir / readdir

🌱 下一步:将 Initramfs 替换为真正的磁盘文件系统(如 FAT32) ,支持从 SD 卡/硬盘加载程序!


💬 写在最后

文件系统与 ELF 加载器, 是连接 静态内核 与 动态用户世界 的桥梁。

今天你构建的这个简易 Initramfs, 正是 Linux 启动时 initrd 的雏形。

🌟 从此,你的操作系统真正“活”了起来——它能生长,能进化,能运行任何你编译的程序。


📬 动手挑战 : 添加一个新用户程序(如 echo ),打包进 Initramfs,并通过 exec 运行它。 欢迎在评论区分享你的文件列表!

👇 下一篇你想看: FAT32 文件系统实战 ,还是 时钟中断与抢占式调度 ?


#操作系统 #内核开发 #文件系统 #ELF #Initramfs #用户程序加载 #从零开始


📢 彩蛋 :关注后回复关键词 “elf” ,获取:
完整 ELF 加载器代码
Initramfs 构建脚本(Python)
* 用户程序链接脚本(user.ld)

最后修改: 2026年7月1日

作者

评论

发表评论

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