Shell 能解析命令了,但还没有多少命令可以执行。本章实现几个基础工具——ls 列出目录、cat 显示文件、cp 复制文件、echo 写入文件——让系统具备基本的文件操作能力。这些工具本质上是文件系统 API 的应用层封装。
重要说明:本章是内核演示程序,不是真正的用户态工具。这些工具作为内核线程实现,直接调用内核文件系统 API,目的是展示工具的核心工作原理,而非实现完整的用户态程序。
用户空间工具概述
用户空间工具是操作系统中用户与系统交互的重要接口。Linux 系统中的 ls、cat、cp、echo 等命令,本质上是独立的可执行程序,通过系统调用接口访问内核服务。理解这些工具的实现原理,有助于我们:
- 掌握文件系统 API 的实际应用
- 理解命令行工具的基本工作流程
- 区分内核态与用户态程序的差异
- 为后续实现真正的用户态工具打下基础
工具实现概览
工具函数实现
在真正的操作系统中,ls、cat、cp、echo 等工具是独立的可执行文件,运行在用户态,通过系统调用与内核交互。但在我们的学习阶段,直接在内核中模拟这些工具可以:
- 避免复杂的用户态程序构建流程
- 专注于工具的核心逻辑而非系统调用机制
- 快速演示文件系统 API 的使用方法
- 为后续章节实现真正的用户态工具做准备
每个工具函数都是一个简单的 C 函数,直接调用内核的文件系统 API(fs_* 函数)完成文件操作:
这些工具函数通过演示任务(demo_task)被调用,按照预设的流程执行文件操作,展示工具的实际效果。
内存文件系统
本章使用 SimpleFS,这是一个基于内存的文件系统实现。与基于磁盘的文件系统相比,内存文件系统具有:
- 更快的读写速度
- 更简单的实现逻辑
- 无需处理磁盘 I/O 的复杂性
- 适合演示和学习环境
SimpleFS 在内存中维护文件和目录的元数据,所有文件操作都在内存中完成。这使得工具演示更加快速和稳定,也避免了磁盘驱动可能引入的复杂性。
代码实现
文件结构
18.userspace-tools/├── boot/│ ├── mbr.S│ └── loader.S├── kernel/│ └── kernel.c # 工具演示函数└── Makefile注意:与真正用户态工具不同,本章没有 bin/ 或 libc/ 目录,所有工具代码都直接在内核源文件中。
// 文件系统 API 使用的数据结构typedef struct { char name[256]; uint8_t type; // FILE_TYPE_DIRECTORY 或 FILE_TYPE_FILE // ... 其他字段} dirent_t;tool_ls - 列出目录内容
static void tool_ls(const char *path){ int dir = fs_opendir(path ? path : "/"); if (dir < 0) { vga_printf("ls: cannot open '%s'\n", path); return; }
dirent_t *de; while ((de = fs_readdir(dir)) != NULL) { vga_printf("%-4s %s\n", de->type == FILE_TYPE_DIRECTORY ? "dir" : "file", de->name); } fs_closedir(dir);}解析:
- 使用
fs_opendir打开目录,失败时输出错误信息 - 通过
fs_readdir逐个读取目录项,循环直到返回 NULL - 对每个目录项,根据类型显示 “dir” 或 “file”,并输出文件名
- 最后使用
fs_closedir关闭目录 - 默认路径为 ”/“,如果传入参数为 NULL
tool_cat - 显示文件内容
static void tool_cat(const char *path){ int fd = fs_open(path, FS_OPEN_READ); if (fd < 0) { vga_printf("cat: %s: not found\n", path); return; }
char buf[128]; ssize_t n; while ((n = fs_read(fd, buf, sizeof(buf) - 1)) > 0) { buf[n] = '\0'; vga_printf("%s", buf); } fs_close(fd);}解析:
- 使用
fs_open以只读模式打开文件 - 创建 128 字节的缓冲区,逐块读取文件内容
- 每次读取后在缓冲区末尾添加 ‘\0’,确保字符串正确终止
- 使用
vga_printf输出文件内容 - 最后关闭文件描述符
tool_cp - 复制文件
static void tool_cp(const char *src, const char *dst){ int sfd = fs_open(src, FS_OPEN_READ); if (sfd < 0) { vga_printf("cp: %s: not found\n", src); return; } int dfd = fs_open(dst, FS_OPEN_WRITE | FS_OPEN_CREATE); if (dfd < 0) { fs_close(sfd); vga_printf("cp: cannot create %s\n", dst); return; } char buf[256]; ssize_t n; while ((n = fs_read(sfd, buf, sizeof(buf))) > 0) { fs_write(dfd, buf, n); } fs_close(sfd); fs_close(dfd); vga_printf("cp: %s -> %s\n", src, dst);}解析:
- 打开源文件(只读)和目标文件(写入+创建)
- 使用 256 字节缓冲区进行块拷贝
- 逐块读取源文件内容,写入目标文件
- 注意错误处理:打开目标文件失败时需要关闭源文件
- 最后关闭两个文件描述符并输出复制成功信息
tool_echo_to_file - 写入文件
static void tool_echo_to_file(const char *text, const char *path){ int fd = fs_open(path, FS_OPEN_WRITE | FS_OPEN_CREATE); if (fd < 0) { vga_printf("echo: cannot open %s\n", path); return; } fs_write(fd, text, strlen(text)); fs_write(fd, "\n", 1); fs_close(fd);}解析:
- 以写入模式打开文件(如果不存在则创建)
- 使用
strlen计算文本长度 - 先写入文本内容,再写入换行符
- 简洁的实现,适合演示文件写入操作
演示任务 - 整合所有工具
static void demo_task(void *arg){ vga_printf("\n--- Userspace Tools Demo ---\n\n");
/* 创建目录结构 */ vga_printf("== mkdir ==\n"); fs_mkdir("/home", FS_PERM_READ | FS_PERM_WRITE); fs_mkdir("/home/user", FS_PERM_READ | FS_PERM_WRITE); fs_mkdir("/tmp", FS_PERM_READ | FS_PERM_WRITE); vga_printf("Created /home, /home/user, /tmp\n\n");
/* echo > file */ vga_printf("== echo > file ==\n"); tool_echo_to_file("Hello from MyOS", "/home/user/hello.txt"); tool_echo_to_file("System configuration", "/home/user/config.txt"); tool_echo_to_file("Temporary data", "/tmp/temp.txt"); vga_printf("Created 3 files\n\n");
/* ls */ vga_printf("== ls / ==\n"); tool_ls("/"); vga_printf("\n== ls /home/user ==\n"); tool_ls("/home/user");
/* cat */ vga_printf("\n== cat /home/user/hello.txt ==\n"); tool_cat("/home/user/hello.txt");
/* cp */ vga_printf("\n== cp ==\n"); tool_cp("/home/user/hello.txt", "/tmp/hello_copy.txt");
/* verify copy */ vga_printf("\n== cat /tmp/hello_copy.txt ==\n"); tool_cat("/tmp/hello_copy.txt");
/* ls to verify */ vga_printf("\n== ls /tmp ==\n"); tool_ls("/tmp");
vga_printf("\n=== All Userspace Tools Demo Complete ===\n"); while (1) schedule_yield();}解析:
- 演示任务按照逻辑顺序执行所有工具
- 先创建目录结构(/home, /home/user, /tmp)
- 使用 echo 创建三个测试文件
- 使用 ls 列出根目录和用户目录
- 使用 cat 显示文件内容
- 使用 cp 复制文件
- 验证复制结果(cat 和 ls)
- 最后进入无限循环,定期让出 CPU
与真正用户态工具的区别
| 特性 | 本章实现 | 真正的用户态工具 |
|---|---|---|
| 运行环境 | 内核态(Ring 0) | 用户态(Ring 3) |
| 调用方式 | 直接调用 fs_* API | 通过系统调用(syscall) |
| 文件组织 | 内核代码一部分 | bin/ 目录下的独立可执行文件 |
| 库支持 | 直接使用内核函数 | 使用 libc 标准库 |
| 参数解析 | 函数参数(const char*) | argc/argv 命令行参数 |
| 输出方式 | vga_printf | printf/write 到标准输出 |
| 权限检查 | 跳过(内核态) | 内核在系统调用中检查 |
| 错误处理 | 简单的错误输出 | 更完善的错误处理和退出码 |
为什么这样设计?
本章的设计是有意为之,目的是:
- 降低复杂度:避免涉及用户态程序构建、系统调用、内存保护等复杂概念
- 聚焦核心逻辑:专注于文件操作的基本流程,而非系统调用的实现细节
- 教学目的:让读者先理解工具的工作原理,再逐步过渡到完整的用户态实现
- 渐进式学习:为后续章节实现真正的用户态工具和系统调用打好基础
运行与验证
编译运行
cd 18.userspace-toolsmake clean && make all && make run预期输出
运行后,你应该看到以下输出:
=== Chapter 18: Userspace Tools ===
Starting scheduler...
--- Userspace Tools Demo ---
== mkdir ==Created /home, /home/user, /tmp
== echo > file ==Created 3 files
== ls / ==dir homedir tmp
== ls /home/user ==file hello.txtfile config.txt
== cat /home/user/hello.txt ==Hello from MyOS
== cp ==cp: /home/user/hello.txt -> /tmp/hello_copy.txt
== cat /tmp/hello_copy.txt ==Hello from MyOS
== ls /tmp ==file temp.txtfile hello_copy.txt
=== All Userspace Tools Demo Complete ===踩坑记录
-
为什么不用系统调用?
- 系统调用需要用户态和内核态的完整切换机制,涉及 TSS、特权级转移等复杂概念。本章专注于文件操作的基本流程,将系统调用的实现放在下一章。
-
为什么工具函数是 static 的?
- 这些函数仅供演示任务内部使用,不需要外部链接,使用 static 可以避免命名冲突。
-
如何扩展更多工具?
- 可以参考现有工具的实现模式,添加新的函数(如 tool_rm、tool_mv 等),在 demo_task 中调用即可。
-
缓冲区大小为什么是 128/256?
- 这些是演示用的固定大小缓冲区。在实际系统中,应该使用更大的缓冲区(如 4KB 或 8KB)来提高 I/O 效率。
-
错误处理为什么这么简单?
- 演示代码为了简洁,只输出错误信息而不进行复杂的错误恢复。真正的用户态工具需要更完善的错误处理和退出码机制。
小结
ls、cat、cp、echo 四个工具围绕文件系统 API 构建,核心流程是打开→读写→关闭。这些工具运行在内核态,核心逻辑与真正的用户态工具相似,但缺少权限检查和并发安全。下一章将把所有组件集成为一个可启动的完整系统——从散落的模块到统一的启动镜像。 5. 本章是内核演示,真正的用户态工具需要系统调用支持
参考
支持与分享
如果这篇文章对你有帮助,欢迎支持作者或分享给更多人
部分信息可能已经过时






