跳转至

Hw1 配套阅读:理解 NexOS 的 Shell 工作原理

阅读目标

  • 作为 Hw1 与实验一的配套阅读材料,帮助你理解 NexOS 用户态 Shell 的核心机制;
  • 理解 NexOS 用户态 Shell 的基本原理与整体执行流程;
  • 掌握 Linux/UNIX 常见系统调用(如 forkexecwaitpipedup)在 Shell 实现中的具体用法;
  • 能够独立阅读并分析 user/sh.c 源代码,实现包含内建命令、外部命令、重定向及管道功能的简易 Shell;
  • 学习编写 Makefile,并结合自动化测试方法对 Shell 功能进行验证与测试;
  • 为完成 Hw1 中与 Shell 相关的作业题,以及实验一中的后续实践任务打下基础。

章节一 NexOS Shell 源码阅读与原理

1.0 若干名词解释

1.0.1 Shell

Shell 是用户态的命令解释程序。它的作用不是“亲自完成所有工作”,而是:

  • 读取用户输入的一行命令;
  • 分析这行命令中包含的程序名、参数以及特殊符号;
  • 通过系统调用创建子进程,并在子进程中执行目标程序;
  • 在需要时负责搭建管道、设置输入输出重定向、等待子进程结束。

在 NexOS 中,Shell 本身也是一个普通的用户态程序,源文件位于 user/sh.c

1.0.2 内建命令与外部命令

  • 外部命令:以普通可执行文件的形式存在,例如 echocatwcgrepls 等。Shell 通过 fork + exec 启动它们,由子进程执行完成
  • 内建命令直接由 Shell 自己处理,而不是再去 exec 一个新程序。典型例子是 cdexit

为什么 cd 必须是内建命令?因为 cd 要修改的是 Shell 进程自身 的当前工作目录。若 Shell 只是 fork 出一个子进程,再让子进程去执行“切换目录”,那么当子进程退出后,父进程 Shell 的目录并不会改变。

1.0.3 文件描述符、标准输入输出与管道

在 NexOS 中,普通文件、控制台、管道都通过文件描述符(fd)访问。在 Hw1 与实验一相关内容中,最重要的三个约定是:

  • 0:标准输入(stdin)
  • 1:标准输出(stdout)
  • 2:标准错误(stderr)

大多数命令默认都从 fd=0 读取输入,向 fd=1 输出结果。因此:

  • 如果把文件接到 fd=0 上,就实现了输入重定向;
  • 如果把文件接到 fd=1 上,就实现了输出重定向;
  • 如果把一个进程的 fd=1 接到管道写端,再把另一个进程的 fd=0 接到管道读端,就实现了 | 管道。

1.1 NexOS 中 Shell 的启动过程

NexOS 启动完成后,内核会先进入第一个用户进程 init,其代码位于 user/init.c。该程序的核心逻辑非常简单:fork 一个子进程,并在子进程中执行 /sh

char *argv[] = {"sh", 0};
exec("/sh", argv);

因此,NexOS 中 Shell 的启动过程可以概括为:

  1. 内核启动后,进入用户态 init
  2. init 通过 fork() 创建子进程;
  3. 子进程调用 exec("/sh", argv),把自己替换成 Shell 程序;
  4. 父进程 init 调用 wait() 等待这个 Shell 结束;
  5. 若 Shell 退出,init 会再次 fork + exec 启动一个新的 Shell。

这意味着:在 NexOS 中输入 exit 后,当前 sh 会结束,但随后 init 会重新拉起一个新的 sh。因此你看到的现象通常不是“系统退出”,而是很快再次回到 Shell 提示符。

1.2 当前 user/sh.c 已实现的功能

课程给出的 user/sh.c 已经是一个能工作的最小 Shell。阅读该文件后,你可以发现它已经完成了如下工作:

  1. 使用 readline() 从标准输入读取一整行命令;
  2. 使用 parseargs() 按空格和制表符切分参数;
  3. 支持内建命令:
  4. help
  5. cd [dir]
  6. exit
  7. 使用 makepath() 处理命令路径:
  8. 若命令本身以 / 开头,则直接执行该绝对路径;
  9. 否则自动在前面补一个 /,例如把 echo 变成 /echo
  10. 对于普通外部命令,执行:
  11. fork() 创建子进程;
  12. 子进程中调用 exec(path, argv)
  13. 父进程中调用 wait(&status) 等待子进程结束。

也就是说,当前的 sh 已经支持如下最基本的交互:

$ hello
$ echo hi
$ ls
$ cd /
$ exit

但它还没有实现以下功能:

  • Shell 提示符中还不会显示当前目录;
  • 不支持管道 |
  • 不支持命令分隔符 ;
  • 不支持输入重定向 <
  • 不支持输出重定向 >>>
  • 不支持引号、转义、通配符等更复杂的 Shell 语法;
  • 不会像 Linux Shell 那样使用 PATH 环境变量搜索命令。

注意:本仓库已经提供了 user/kill.c,因此 kill 123 可以作为普通外部命令运行;它不像 cd 那样必须写成内建命令。