从零写 OS 内核-第九篇:用户态程序加载 —— ELF 解析与简易文件系统
“你的 OS 能 fork/exec,但程序从哪来? 今天,我们构建第一个文件系统,让内核能从‘磁盘’加载用户程序!”
在上一篇中,我们实现了 fork 和 exec ,但用户程序仍需 硬编码在内核镜像中 。 这显然不现实——真正的操作系统必须能 从存储设备加载任意程序 。
而这一切,依赖两大核心模块: ✅ 文件系统 —— 组织和管理磁盘上的文件 ✅ ELF 加载器 —— 解析并加载可执行文件到内存
今天,我们就来实现一个 极简但可用的文件系统(Initramfs 风格) ,并完成 ELF 程序加载 ,让你的 exec(“/bin/ls”) 真正工作!
🗂️ 一、为什么需要文件系统?
没有文件系统,你的 OS 就像一台没有硬盘的电脑—— 所有程序必须编译进内核,无法动态扩展。
文件系统的核心职责:
命名
:通过路径(如
/bin/ls
)访问文件
存储
:将文件内容映射到物理存储(磁盘/内存)
元数据
:记录文件大小、权限、类型等
💡 初期无需复杂磁盘驱动 ——我们可以用 Initramfs(初始内存文件系统) : 将所有文件打包进内核镜像,启动时加载到内存。
📦 二、Initramfs:最简文件系统实现
Initramfs 是 Linux 启动初期常用的内存文件系统,非常适合我们的场景。
设计思路:
- 将所有用户程序打包成一个二进制 blob
- 定义简单目录结构
(扁平或树形)
- 内核启动时解析 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 加载器
步骤:
- 从文件系统读取 ELF 文件到内核缓冲区
- 验证 ELF 魔数和类型
- 遍历 Program Headers,加载
PT_LOAD
段 - 设置入口地址
代码框架:
🔑 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)
评论