共计 1849 个字符,预计需要花费 5 分钟才能阅读完成。
系列目录
- 序篇
- 筹备工作
- BIOS 启动到实模式
- GDT 与保护模式
- 虚拟内存初探
- 加载并进入 kernel
- 显示与打印
- 全局描述符表 GDT
- 中断解决
- 虚拟内存欠缺
- 实现堆和 malloc
- 第一个内核线程
- 多线程运行与切换
- 锁与多线程同步
- 进入用户态
- 过程的实现
- 零碎调用
- 简略的文件系统
- 加载可执行程序
- 键盘驱动
- 运行 shell
exec 零碎调用
有了后面几篇对于零碎调用和文件系统的铺垫,本篇来实现 exec
零碎调用,其实曾经是万事俱备了。exec
的应用想必你应该相熟,它在过程里调用后会读取给定的可执行文件,而后用这个文件里的程序笼罩以后 process 运行,这实际上就是实现了从磁盘上启动运行一个新程序的过程。
筹备用户程序
首先咱们须要筹备几个用户程序,并将它们写入上一篇中咱们定制的那个 naive_fs 磁盘镜像中去。我在我的项目里创立了一个 user 目录,并在外面也加了 user/src 目录寄存用户程序的源文件,以及 user/prog
用以寄存编译链接后的可执行二进制。例如咱们能够简略地写一个用户程序:
int main(int argc, char** argv) {while (1) {}}
它非常简单只是一个死循环。当然你也能够写一个打印性能的程序,不过这里须要先实现打印,留神这是用户态的打印,你必须在让 kernel 提供一个打印性能的零碎调用,比方叫 print
,供用户调用。我将这个性能封装在了 src/common/stdio.c
的 printf 函数里,它底层会应用 print
零碎调用。这样相似于 C 规范库,它会被 link 到用户程序二进制里。
例如我写了一个用户程序 hello,外面用到了打印性能:
#include "common/common.h"
#include "common/stdio.h"
#include "syscall/syscall.h"
int main(uint32 argc, char* argv[]) {printf("start user app: hello\n");
printf("argc = %d\n", argc);
for (uint32 i = 0; i < argc; i++) {printf("argv[%d] = %s\n", i, argv[i]);
}
return 0;
}
编译链接生成用户二进制程序后,就能够应用前一篇提到的 disk_image_writer 函数将它们写入磁盘镜像。
加载程序并 exec
接下来咱们能够实现 exec
零碎调用了,kernel 里的实现局部在 process_exec 函数。
首先是读取二进制文件,这里用到上一篇实现的文件系统的接口:
// Get elf binary file stat.
file_stat_t stat;
if (stat_file(path, &stat) != 0) {monitor_printf("Command %s not found\n", path);
return -1;
}
// Read elf binary file.
uint32 size = stat.size;
char* read_buffer = (char*)kmalloc(size);
if (read_file(path, read_buffer, 0, size) != size) {monitor_printf("Failed to load cmd %s\n", path);
kfree(read_buffer);
return -1;
}
而后就是解析 elf 文件,这个在加载并进入 kernel 里曾经实现过,这里用 C 语言重写一下,看起来会更分明一点,代码在 src/elf/elf.c,它会将 elf 程序的各个 section 加载到内存,而后返回程序的入口地址。
接下来就是废除原来的 process 的所有资源,因为 exec 是占据式的,原来的程序会被齐全代替。这里次要是两项工作:
- 清理原来 process 的所有 threads,除了以后 thread;
- 开释原来的 user 空间的所有虚拟内存;
而后创立一个新的 thread,并使之以咱们刚加载的新的 elf 二进制的入口函数为 entry 开始运行,这个新的 thread 就是新程序开始运行的主线程。
至于以后的执行线程,在 process_exec 的结尾,会间接调用 schedule_thread_exit
函数进入沦亡。
总结
本篇比较简单,次要是咱们各项筹备工作都曾经做的很充沛了,exec
的实现只是一个拼接工作。不过有一些细节还是要留神的,比如说 exec 的参数须要提前 copy 到内核中,而后再开释 user 空间的虚拟内存,因为传进来的参数本来是存储在 user 空间的,一旦开释了内存,前面就无奈再拜访这些参数了。