关于操作系统:从零开始写-OS-内核-加载可执行程序

9次阅读

共计 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 空间的,一旦开释了内存,前面就无奈再拜访这些参数了。

正文完
 0