从零开始写 OS 内核 - 运行 shell

Posted navi

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了从零开始写 OS 内核 - 运行 shell相关的知识,希望对你有一定的参考价值。

系列目录

  • 序篇
  • 准备工作
  • Bios 启动到实模式
  • GDT 与保护模式
  • 虚拟内存初探
  • 加载并进入 kernel
  • 显示与打印
  • 全局描述符表 GDT
  • 中断处理
  • 虚拟内存完善
  • 实现堆和 malloc
  • 第一个内核线程
  • 多线程运行与切换
  • 锁与多线程同步
  • 进入用户态
  • 进程的实现
  • 系统调用
  • 简单的文件系统
  • 加载可执行程序
  • 键盘驱动
  • 运行 shell

shell 命令行

这是本系列最后一篇了,为这个 OS 加一个用户界面 shell,这算是 Linux 编程中最入门的经典教科书项目了,网上也可以找到很多小教程。这里也不多浪费时间,仅展示一下它的核心部分:

void print_shell() {
  printf("bash> ");
}
while (1) {
  print_shell();
  while (1) {
    int32 c = read_char();
    if (c == \'\\n\') {
      run_program();
      break;
    } else if (c < 128) {
      printf(c);
    }
}

shell 本质上只是一个壳,正如它的名字,它提供一个和用户交互的命令行界面,不停地等待用户输入字符并反馈打印出来;一旦用户按下了回车键,那么表示需要运行之前输入的命令行,这在 run_program 函数里实现:

void run_program() {
  // Parse cmd and get program and args.
  // ..
  
  // (fork + exec) new prgoram.
  int32 pid = fork();
  if (pid < 0) {
    printf("fork failed");
  } else if (pid > 0) {
    // parent
    int32 status;
    wait(pid, &status);
  } else {
    // child
    int32 ret = exec(program, args_index, (char**)args);
    exit(ret);
  }
}

这里首先 parse 用户刚才敲回车之前输入的命令行字符串,解析出可执行程序名,以及参数。然后就是经典的 fork + exec 组合,运行这个程序。程序名和参数都会被传递到 exec 系统调用的处理函数 process_exec,那里会从磁盘上读取该用户可执行文件并执行。这里命令行输入的程序名都很简单,也没有什么路径的概念,因为我们使用的 naive_fs 只有一层结构,所有文件全在顶层,所以直接用文件名就可以了。

fork 后的 parent 进程会调用 wait 系统调用阻塞等待 child 结束。关于 wait 和 exit 这组系统调用,我没有在这个系列里详细展开,读者可以自行阅读源码。

kernel 启动任务

我们来看一下这个 kernel 启动的过程,后台开启了哪些任务,以及如何最终进入 shell 界面。本节代码在 src/task/scheduler.c 中。

首先启动 kernel main 进程/线程,它是最原始的祖先进程,会做这几件事情:

  • 创建 kernel 资源清理线程 kernel_clean_thread,这是一个后台线程,我用它专门做 process/thread 的资源最终回收工作,平时它是睡眠的,只有当有 process/thread 消亡需要清理时会唤醒;
  • 创建 init 进程以及线程 kernel_init_thread,它会成为第一个用户进程,运行用户程序 init;在 init 程序里,我创建了 shell 进程,然后 init 进程就进入阻塞;在实际的 Linux 系统中,真实的 init 进程应该还需要作为一个后台任务,专门负责等待接管回收所有的孤儿进程(Orphan Process),我这里就不实现了,感兴趣的同学可以查资料学习一下;
  • 上述两项工作完成后,这个原始线程就变成了 cpu_idle 线程,所谓 cpu idle 就是一条指令 hlt,它是在系统真的没有任何任务需要运行的情况才会被运行,它会使 CPU 进入一个低功耗运转的状态;

当然以上只是我个人启动 kernel 任务的实现方式,和 Linux 有点像但并不完全一致;这其实很随意的,这毕竟只是我们自己写的一个玩具 OS 而已,Linux 的方式也并非标准答案,只需要让系统各个重要的任务成功运行并调度起来就可以了。

以上是关于从零开始写 OS 内核 - 运行 shell的主要内容,如果未能解决你的问题,请参考以下文章

从零开始写 OS 内核 - 键盘驱动

从零开始写 OS 内核 - 加载可执行程序

完成一个简单的时间片轮转多道程序内核代码

uCOS-III 学习记录——任务时间片运行

如果又来了一个OS内核,你怎么看?

Linux内核分析—完成一个简单的时间片轮转多道程序内核代码