谁说操作系统要一堆文件?一个 ELF 就够了
1. 引言:操作系统构建的范式转变
传统上,构建一个可运行的操作系统(即使是极简版本)是一项极其复杂的工程任务。开发者需要精通引导程序(如GRUB)、内核编译、文件系统制作、init进程编写以及各种硬件驱动集成。这个过程涉及数十个甚至上百个配置文件、脚本和源代码文件,学习曲线陡峭,调试困难。
本文介绍的自动化脚本彻底改变了这一范式。它的核心思想是:“应用即系统”。开发者只需专注于编写核心的C语言应用程序,并将其编译为静态链接的ELF(Executable and Linkable Format)可执行文件。剩下的所有工作——从创建可引导环境到打包成ISO镜像——全部由脚本自动完成。这极大地降低了操作系统开发的门槛,让开发者能够快速原型验证、创建专用嵌入式系统或教学演示。详细视频教程:操作系统构建器详细视频
我是一名初中生,传统开发操作系统对于我来说不现实,所以自己设计了原理和测试代码
2. 脚本核心功能与特性
2.1 核心工作流程
- 输入:用户提供一个静态链接的ELF文件(例如
myapp.elf)。 - 环境检测与依赖安装:脚本自动检测当前系统(Kali/Debian/Ubuntu),并安装必要的工具链(如
gcc、make、xorriso、grub-pc-bin)。 - 引导环境构建:自动创建GRUB引导配置、生成ISO 9660文件系统结构。
- 系统集成:将用户提供的ELF文件作为系统的“内核”或唯一的init进程,集成到文件系统中。
- 输出:生成一个完整的、可启动的ISO镜像文件(例如
myos.iso),可直接在虚拟机(如QEMU、VirtualBox)或实体机中引导运行。
2.2 技术栈与兼容性
- 宿主系统:Kali Linux, Debian, Ubuntu 及其衍生版。依赖标准的APT包管理器。
- 目标文件:静态链接的ELF可执行文件。这意味着所有库函数(如libc)都已编译进二进制文件内部,无需目标系统提供任何动态库。
- 暂不兼容:
- Windows系统(脚本基于Bash和Linux工具链)。
- 动态链接的ELF文件(开发中)。动态链接需要目标系统存在对应的共享库,增加了复杂性。
- 非ELF格式的可执行文件(如PE格式的Windows可执行文件)。
- 未来规划:下一步将兼容使用PyInstaller等工具打包的Python应用程序生成的动态ELF,通过自动打包依赖库来实现。
3. 与传统操作系统开发流程的对比
下表清晰地展示了本脚本带来的革命性简化:
| 对比维度 | 传统操作系统开发 | 本脚本方案 | 简化程度 |
|---|---|---|---|
| 核心工作 | 编写内核、驱动、引导程序、init系统、用户态程序等。 | 只需编写一个静态ELF应用程序。 | 从“建造整栋大楼”简化为“设计一个房间”。 |
| 文件数量 | 数十至数百个文件(.c, .h, .S, Makefile, 各种.conf, .cfg)。 | 1个文件(用户提供的.elf)。 | 数量级降低。 |
| 构建复杂度 | 需要手动配置工具链、交叉编译、链接脚本、处理硬件抽象层。 | 一键运行脚本,自动安装依赖并完成所有底层配置。 | 从专家级手工活变为自动化流水线。 |
| 调试难度 | 极高。问题可能出现在引导、内核、驱动、应用等任何层面。 | 集中。只需调试你自己的ELF应用程序逻辑。 | 问题域大幅收窄。 |
| 学习成本 | 需要掌握操作系统理论、汇编、硬件接口等大量知识。 | 只需掌握C语言和Linux基础编程。 | 门槛极低,适合快速原型和教学。 |
核心优势总结:传统开发如同从零开始造汽车,需要发动机、底盘、变速箱等所有部件。而本脚本方案如同提供一个“标准底盘”,开发者只需造一个“定制车厢”(ELF应用)放上去,就能立刻得到一辆能跑的车。
4. 使用教程:从C代码到可运行ISO
4.1 准备静态ELF文件
首先,编写一个简单的C程序并静态编译。这个程序将是你操作系统的“全部”。
/*
* my_os.c - 你的操作系统本体
* 编译: gcc -static -Os -s -o my_os my_os.c
* 然后上传 my_os 给 566.py 的 ELF 路径
*/
#include <unistd.h>
#include <sys/mount.h>
#include <sys/stat.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include <fcntl.h>
#include <termios.h>
#include <sys/wait.h>
/* ========== 基础工具 ========== */
static void write_all(int fd, const char *buf, size_t len) {
while (len > 0) {
ssize_t n = write(fd, buf, len);
if (n <= 0) { if (errno == EINTR) continue; return; }
buf += n; len -= n;
}
}
static void puts_s(const char *s) {
write_all(1, s, strlen(s));
}
/* ========== 终端 raw 模式 ========== */
static struct termios g_orig_termios;
static void restore_term(void) {
tcsetattr(0, TCSANOW, &g_orig_termios);
}
static void set_raw(void) {
tcgetattr(0, &g_orig_termios);
struct termios raw = g_orig_termios;
raw.c_lflag &= ~(ECHO | ICANON | ISIG | IEXTEN);
raw.c_iflag &= ~(ICRNL | INPCK | ISTRIP | IXON);
raw.c_cflag |= CS8;
raw.c_oflag &= ~OPOST;
raw.c_cc[VMIN] = 1;
raw.c_cc[VTIME] = 0;
tcsetattr(0, TCSANOW, &raw);
atexit(restore_term);
}
/* ========== 挂载文件系统 ========== */
static void mount_all(void) {
mkdir("/proc", 0755); mount("proc", "/proc", "proc", 0, NULL);
mkdir("/sys", 0755); mount("sysfs", "/sys", "sysfs", 0, NULL);
mkdir("/dev", 0755); mount("devtmpfs", "/dev", "devtmpfs", 0, NULL);
mkdir("/tmp", 0755); mount("tmpfs", "/tmp", "tmpfs", 0, NULL);
}
/* ========== 命令执行 ========== */
static void handle_cmd(const char *cmd) {
if (cmd[0] == '\0') return;
if (strcmp(cmd, "help") == 0) {
puts_s("Commands:\n"
" help show this\n"
" clear clear screen\n"
" mem show memory info\n"
" cpu show CPU info\n"
" uptime show uptime\n"
" echo <t> print text\n"
" fork fork a child process\n"
" panic trigger kernel panic (test only)\n");
}
else if (strcmp(cmd, "clear") == 0) {
write_all(1, "\x1b[2J\x1b[H", 7);
}
else if (strncmp(cmd, "echo ", 5) == 0) {
puts_s(cmd + 5);
puts_s("\n");
}
else if (strcmp(cmd, "mem") == 0) {
int fd = open("/proc/meminfo", O_RDONLY);
if (fd >= 0) {
char buf[512];
ssize_t n = read(fd, buf, sizeof(buf) - 1);
if (n > 0) { buf[n] = '\0'; write_all(1, buf, n); }
close(fd);
}
}
else if (strcmp(cmd, "cpu") == 0) {
int fd = open("/proc/cpuinfo", O_RDONLY);
if (fd >= 0) {
char buf[512];
ssize_t n = read(fd, buf, sizeof(buf) - 1);
if (n > 0) {
buf[n] = '\0';
char *p = strstr(buf, "model name");
if (p) {
char *nl = strchr(p, '\n');
if (nl) *nl = '\0';
puts_s(p); puts_s("\n");
}
}
close(fd);
}
}
else if (strcmp(cmd, "uptime") == 0) {
int fd = open("/proc/uptime", O_RDONLY);
if (fd >= 0) {
char buf[64];
ssize_t n = read(fd, buf, sizeof(buf) - 1);
if (n > 0) { buf[n] = '\0'; puts_s("uptime: "); write_all(1, buf, n); }
close(fd);
}
}
else if (strcmp(cmd, "fork") == 0) {
pid_t pid = fork();
if (pid == 0) {
puts_s(" [child] Hello from forked process!\n");
_exit(0);
} else if (pid > 0) {
char buf[32];
snprintf(buf, sizeof(buf), " [parent] child PID=%d\n", pid);
puts_s(buf);
waitpid(pid, NULL, 0);
puts_s(" [parent] child exited\n");
} else {
puts_s(" fork failed\n");
}
}
else if (strcmp(cmd, "panic") == 0) {
puts_s("Triggering panic in 1s...\n");
sleep(1);
int fd = open("/proc/sys/kernel/sysrq", O_WRONLY);
if (fd >= 0) { write(fd, "1", 1); close(fd); }
fd = open("/proc/sysrq-trigger", O_WRONLY);
if (fd >= 0) { write(fd, "c", 1); close(fd); }
}
else {
write_all(1, "Unknown: ", 9);
puts_s(cmd);
puts_s("\n");
}
}
/* ========== main ========== */
int main(void) {
/* 1. 挂载 */
mount_all();
/* 2. raw 模式 */
set_raw();
/* 3. 横幅 */
write_all(1, "\x1b[2J\x1b[H", 7);
puts_s("╔══════════════════════════════════╗\n");
puts_s("║ HelloOS - Your OS is Alive ║\n");
puts_s("║ Kernel: Linux 6.6.32 ║\n");
puts_s("║ Built with 566.py ║\n");
puts_s("╚══════════════════════════════════╝\n\n");
puts_s("Type 'help' for commands.\n\n");
/* 4. 主循环 - 读键盘 */
char line[256];
int len = 0;
puts_s("os> ");
for (;;) {
char c;
ssize_t n = read(0, &c, 1);
if (n != 1) continue;
if (c == '\r' || c == '\n') {
puts_s("\n");
line[len] = '\0';
handle_cmd(line);
len = 0;
puts_s("os> ");
}
else if (c == '\x7f' || c == '\b') {
if (len > 0) { len--; write_all(1, "\b \b", 3); }
}
else if (c == '\x03') { /* Ctrl+C */
puts_s("^C\nos> ");
len = 0;
}
else if (c >= 0x20 && c < 0x7f) {
if (len < 255) { line[len++] = c; write_all(1, &c, 1); }
}
}
return 0;
}
使用gcc进行静态编译:
gcc -static -Os -s -o myos.elf hello_os.c
使用 file 命令验证是否为静态链接:
file myos.elf
# 输出应包含 “statically linked”
4.2 运行构建脚本
假设脚本名为 build_os_iso.sh。将其置于与 myos.elf 相同的目录,并赋予执行权限。
点击elf或者命令行运行./elfos.elf运行程序
上传你编写好的elf文件
即可构建
4.3 测试运行
使用QEMU虚拟机快速测试生成的ISO:
qemu-system-x86_64 -cdrom myos.iso
5. 脚本实现原理浅析
5.1 引导的奥秘:GRUB与Multiboot
脚本利用GRUB 2支持Multiboot或Multiboot2协议。它配置GRUB将一个特殊的ELF文件(即你的应用)作为“内核”加载到内存并跳转执行。你的ELF文件的入口点(通常是 main)就成为了操作系统的起点。
5.2 静态链接的重要性
静态链接确保了你的程序不依赖目标系统上的任何动态库(如 libc.so.6)。在ISO引导的早期环境里,这些库根本不存在。静态编译将所需的所有代码都打包进一个文件,实现了真正的“开箱即跑”。
6. 总结与展望
这款自动化脚本代表了操作系统构建工具的一种新思路:极度简化、以应用为中心。
未来工作:
- 兼容动态ELF:通过分析Python打包产物(如PyInstaller生成的ELF)的依赖,自动将所需的
.so库打包进ISO,实现Python应用的“一键OS化”。 - 增强配置:允许用户通过配置文件自定义ISO元数据(如系统名称、版本)、引导参数等。
通过降低操作系统构建的复杂度,我们希望激发更多开发者探索系统底层、创造专用系统的热情。从编写一个简单的C程序开始,你的个人操作系统之旅,现在只需一条命令。
openEuler 是由开放原子开源基金会孵化的全场景开源操作系统项目,面向数字基础设施四大核心场景(服务器、云计算、边缘计算、嵌入式),全面支持 ARM、x86、RISC-V、loongArch、PowerPC、SW-64 等多样性计算架构
更多推荐
所有评论(0)