跳转至

实验实现

提示

这里,我们将对一些实现的细节做一定提醒,但是完整的流程需要自己把握,我们 不会给出每一步实现步骤 。因此,最重要的还是阅读实验原理内容,得出自己的解决方案,然后参考"实验实现"这部分的内容。

值得注意的是,xv6的代码很值得参考,可以看看其他系统调用和功能是怎么实现的。同时,还可以使用 printf 或者 gdb 等方法查看具体的执行情况。此外,还可以阅读 xv6 指导书,看看背后的设计机理。

如果在实现过程中遇到困难,可以 先尝试回答任务四 提出的问题,它们或许可以帮助你更好地理解。

1. 任务一:进程信息收集

  具体要求:在exit系统调用当中寻找合适的输出时间点,在相应的函数内进行父子进程信息的打印。

1.1 流程

  首先切换分支:

  • 参考这个指南,切换到syscall分支并同步上游仓库;

  在内核部分:

  • 参考exit系统调用工作流程,阅读proc.c: exit(int)相关源码,在合适的位置使用exit_info输出。
  • 然后启动xv6,在shell输入 exittest ,要能满足实验概述中提到的输出。

2. 任务二:wait系统调用的非阻塞选项实现

  具体要求:更改原有的wait系统调用,添加新参数,实现wait系统调用的非阻塞选项。

2.1 流程

  在用户部分:

  • 由于我们已经将用户态的wait接口做好了更改,同学们无需更改用户部分代码。

  在内核部分:

  • 更改内核部分的wait接口定义(在defs.h中),以及相应的函数接口(proc.c: wait(uint64));
  • sysproc.c: sys_wait(void)函数中获取用户态传入的新参数;
  • proc.c: wait(uint64, int)中实现非阻塞逻辑。
  • 然后启动xv6,在shell输入 waittest ,要能满足实验概述中提到的输出。

3. 任务三:实现yield系统调用

  该实验代码量约为30行

3.1 流程

  在用户部分:

  在内核部分:

  • syscall.h中增加一个新的系统调用号,具体值你自己决定;
  • syscall.c中增加一个extern函数声明,以及在syscalls数组中增加一项(参考系统调用的分发和实现:解耦合);
  • sysproc.c中增加一个sys_yield函数,在该函数中:
    • 获取当前正在执行的进程PCB(参考mycpu和myproc)
    • 打印出该进程对应的内核线程在进行上下文切换时,上下文被保存到的地址区间(参考上下文切换);
    • 打印出该进程的用户态陷入内核态时PC的值(参考trapframe);
    • 根据调度器的工作方式模拟一次调度,找到下一个RUNNABLE的进程,同样打印相关信息(参考调度器线程的工作方式)。首先需要在proc.h文件末尾新增extern声明全局进程表,然后在sys_yield函数中从当前进程起,环形遍历全局进程表,在这个过程中记得注意锁的获取和释放。
    • 然后将当前进程挂起,XV6内核态已经帮我们实现了一个yield函数了。

C语言指针加法

指针加法的操作会根据指针所指向的数据类型来计算。假设你有一个指向某类型的指针 ptr,并且你执行了 ptr + n,这并不是将指针的地址加上 n,而是将指针的地址加上 n * sizeof(指针所指向的数据类型)。

  最后,运行./grade-lab-syscall yield测试。该脚本会启动qemu并且运行yieldtest测试进行输出匹配。建议你阅读一下user/yieldtest.c

4. 任务四:回答问题

回答问题

(1) 阅读kernel/syscall.c,试解释函数 syscall() 如何根据系统调用号调用对应的系统调用处理函数(例如sys_fork)?syscall() 将具体系统调用的返回值存放在哪里?

(2) 阅读kernel/syscall.c,哪些函数用于传递系统调用参数?试解释argraw()函数的含义。

(3) 阅读kernel/proc.cproc.h,进程控制块存储在哪个数组中?进程控制块中哪个成员指示了进程的状态?一共有哪些状态?

(4) 在任务一当中,为什么子进程(4、5、6号进程)的输出之前会 稳定的 出现一个$符号?(提示:shell程序(sh.c)中什么时候打印出$符号?)
q4

(5) 在任务三当中,我们提到测试时需要指定CPU的数量为1,因为如果CPU数量大于1的话,输出结果会出现乱码,这是为什么呢?(提示:多核心调度和单核心调度有什么区别?)