Lab2: system calls

在上一个实验室中,您使用系统调用编写了一些实用程序。在本实验室中,您将向xv6添加一些新的系统调用,这将帮助您了解它们是如何工作的,并使您了解xv6内核的一些内部结构。您将在以后的实验室中添加更多系统调用。

[!WARNING|label:Attention] 在你开始写代码之前,请阅读xv6手册《book-riscv-rev1》的第2章、第4章的第4.3节和第4.4节以及相关源代码文件:

  • 系统调用的用户空间代码在user/user.huser/usys.pl中。
  • 内核空间代码是kernel/syscall.hkernel/syscall.c
  • 与进程相关的代码是kernel/proc.hkernel/proc.c

要开始本章实验,请将代码切换到syscall分支:

  1. $ git fetch
  2. $ git checkout syscall
  3. $ make clean

如果运行make grade,您将看到测试分数的脚本无法执行tracesysinfotest。您的工作是添加必要的系统调用和存根(stubs)以使它们工作。

System call tracing(moderate)

[!TIP|label:YOUR JOB] 在本作业中,您将添加一个系统调用跟踪功能,该功能可能会在以后调试实验时对您有所帮助。您将创建一个新的trace系统调用来控制跟踪。它应该有一个参数,这个参数是一个整数“掩码”(mask),它的比特位指定要跟踪的系统调用。例如,要跟踪fork系统调用,程序调用trace(1 << SYS_fork),其中SYS_forkkernel/syscall.h中的系统调用编号。如果在掩码中设置了系统调用的编号,则必须修改xv6内核,以便在每个系统调用即将返回时打印出一行。该行应该包含进程id、系统调用的名称和返回值;您不需要打印系统调用参数。trace系统调用应启用对调用它的进程及其随后派生的任何子进程的跟踪,但不应影响其他进程。

我们提供了一个用户级程序版本的trace,它运行另一个启用了跟踪的程序(参见user/trace.c)。完成后,您应该看到如下输出:

  1. $ trace 32 grep hello README
  2. 3: syscall read -> 1023
  3. 3: syscall read -> 966
  4. 3: syscall read -> 70
  5. 3: syscall read -> 0
  6. $
  7. $ trace 2147483647 grep hello README
  8. 4: syscall trace -> 0
  9. 4: syscall exec -> 3
  10. 4: syscall open -> 3
  11. 4: syscall read -> 1023
  12. 4: syscall read -> 966
  13. 4: syscall read -> 70
  14. 4: syscall read -> 0
  15. 4: syscall close -> 0
  16. $
  17. $ grep hello README
  18. $
  19. $ trace 2 usertests forkforkfork
  20. usertests starting
  21. test forkforkfork: 407: syscall fork -> 408
  22. 408: syscall fork -> 409
  23. 409: syscall fork -> 410
  24. 410: syscall fork -> 411
  25. 409: syscall fork -> 412
  26. 410: syscall fork -> 413
  27. 409: syscall fork -> 414
  28. 411: syscall fork -> 415
  29. ...
  30. $

在上面的第一个例子中,trace调用grep,仅跟踪了read系统调用。321<<SYS_read。在第二个示例中,trace在运行grep时跟踪所有系统调用;2147483647将所有31个低位置为1。在第三个示例中,程序没有被跟踪,因此没有打印跟踪输出。在第四个示例中,在usertests中测试的forkforkfork中所有子孙进程的fork系统调用都被追踪。如果程序的行为如上所示,则解决方案是正确的(尽管进程ID可能不同)

提示:

  • MakefileUPROGS中添加$U/_trace
  • 运行make qemu,您将看到编译器无法编译user/trace.c,因为系统调用的用户空间存根还不存在:将系统调用的原型添加到user/user.h,存根添加到user/usys.pl,以及将系统调用编号添加到kernel/syscall.hMakefile调用perl脚本user/usys.pl,它生成实际的系统调用存根user/usys.S,这个文件中的汇编代码使用RISC-V的ecall指令转换到内核。一旦修复了编译问题(注:如果编译还未通过,尝试先make clean,再执行make qemu),就运行trace 32 grep hello README;但由于您还没有在内核中实现系统调用,执行将失败。
  • kernel/sysproc.c中添加一个sys_trace()函数,它通过将参数保存到proc结构体(请参见kernel/proc.h)里的一个新变量中来实现新的系统调用。从用户空间检索系统调用参数的函数在kernel/syscall.c中,您可以在kernel/sysproc.c中看到它们的使用示例。
  • 修改fork()(请参阅kernel/proc.c)将跟踪掩码从父进程复制到子进程。
  • 修改kernel/syscall.c中的syscall()函数以打印跟踪输出。您将需要添加一个系统调用名称数组以建立索引。

Sysinfo(moderate)

[!TIP|label:YOUR JOB] 在这个作业中,您将添加一个系统调用sysinfo,它收集有关正在运行的系统的信息。系统调用采用一个参数:一个指向struct sysinfo的指针(参见kernel/sysinfo.h)。内核应该填写这个结构的字段:freemem字段应该设置为空闲内存的字节数,nproc字段应该设置为state字段不为UNUSED的进程数。我们提供了一个测试程序sysinfotest;如果输出“sysinfotest: OK”则通过。

提示:

  • MakefileUPROGS中添加$U/_sysinfotest
  • 当运行make qemu时,user/sysinfotest.c将会编译失败,遵循和上一个作业一样的步骤添加sysinfo系统调用。要在user/user.h中声明sysinfo()的原型,需要预先声明struct sysinfo的存在:
  1. struct sysinfo;
  2. int sysinfo(struct sysinfo *);

一旦修复了编译问题,就运行sysinfotest;但由于您还没有在内核中实现系统调用,执行将失败。

  • sysinfo需要将一个struct sysinfo复制回用户空间;请参阅sys_fstat()(kernel/sysfile.c)和filestat()(kernel/file.c)以获取如何使用copyout()执行此操作的示例。
  • 要获取空闲内存量,请在kernel/kalloc.c中添加一个函数
  • 要获取进程数,请在kernel/proc.c中添加一个函数

可选的挑战

  • 打印所跟踪的系统调用的参数(easy)。
  • 计算平均负载并通过sysinfo导出(moderate)。