实验:Xv6与Unix工具

本实验将让你熟悉xv6及其系统调用。

启动xv6

你可以在Athena机器上或你自己的电脑上完成这些实验。如果你使用自己的电脑,请查看实验工具页面获取设置提示。

如果你使用Athena,你必须使用x86机器;也就是说,uname -a应该显示i386 GNU/Linuxi686 GNU/Linuxx86_64 GNU/Linux。你可以使用ssh -X athena.dialup.mit.edu登录公共Athena主机。我们已经在Athena上为你设置好了相应的编译器和模拟器。要使用它们,运行add -f 6.828。每次登录时你必须运行此命令(或将其添加到你的~/.environment文件中)。如果在编译或运行qemu时遇到不明错误,请检查你是否添加了课程locker。

获取实验的xv6源代码并切换到util分支:

$ git clone git://g.csail.mit.edu/xv6-labs-2020
Cloning into 'xv6-labs-2020'...
...
$ cd xv6-labs-2020
$ git checkout util
Branch 'util' set up to track remote branch 'util' from 'origin'.
Switched to a new branch 'util'

xv6-labs-2020仓库与书中的xv6-riscv略有不同;它主要增加了一些文件。如果你好奇,可以查看git日志:

$ git log

本实验及后续实验所需的文件通过Git版本控制系统分发。上面你切换到了一个包含为本实验定制的xv6版本的分支(git checkout util)。要了解更多关于Git的知识,请查看Git用户手册,或者你可能会发现这个面向计算机科学家的Git概述很有用。Git允许你跟踪对代码所做的更改。例如,如果你完成了其中一个练习,想要保存进度,你可以通过运行以下命令来提交你的更改:

$ git commit -am 'my solution for util lab exercise 1'
Created commit 60d2135: my solution for util lab exercise 1
 1 files changed, 1 insertions(+), 0 deletions(-)
$

你可以使用git diff命令来跟踪你的更改。运行git diff将显示自上次提交以来对代码的更改,而git diff origin/util将显示相对于初始xv6-labs-2020代码的更改。这里,origin/xv6-labs-2020是你为课程下载的初始代码所在的git分支名称。

编译并运行xv6:

$ make qemu
riscv64-unknown-elf-gcc    -c -o kernel/entry.o kernel/entry.S
riscv64-unknown-elf-gcc -Wall -Werror -O -fno-omit-frame-pointer -ggdb -DSOL_UTIL -MD -mcmodel=medany -ffreestanding -fno-common -nostdlib -mno-relax -I. -fno-stack-protector -fno-pie -no-pie   -c -o kernel/start.o kernel/start.c
...  
riscv64-unknown-elf-ld -z max-page-size=4096 -N -e main -Ttext 0 -o user/_zombie user/zombie.o user/ulib.o user/usys.o user/printf.o user/umalloc.o
riscv64-unknown-elf-objdump -S user/_zombie > user/zombie.asm
riscv64-unknown-elf-objdump -t user/_zombie | sed '1,/SYMBOL TABLE/d; s/ .* / /; /^$/d' > user/zombie.sym
mkfs/mkfs fs.img README  user/xargstest.sh user/_cat user/_echo user/_forktest user/_grep user/_init user/_kill user/_ln user/_ls user/_mkdir user/_rm user/_sh user/_stressfs user/_usertests user/_grind user/_wc user/_zombie 
nmeta 46 (boot, super, log blocks 30 inode blocks 13, bitmap blocks 1) blocks 954 total 1000
balloc: first 591 blocks have been allocated
balloc: write bitmap block at sector 45
qemu-system-riscv64 -machine virt -bios none -kernel kernel/kernel -m 128M -smp 3 -nographic -drive file=fs.img,if=none,format=raw,id=x0 -device virtio-blk-device,drive=x0,bus=virtio-mmio-bus.0

xv6 kernel is booting

hart 2 starting
hart 1 starting
init: starting sh
$ 

如果在提示符处输入ls,你应该看到类似以下的输出:

$ ls
.              1 1 1024
..             1 1 1024
README         2 2 2059
xargstest.sh   2 3 93
cat            2 4 24256
echo           2 5 23080
forktest       2 6 13272
grep           2 7 27560
init           2 8 23816
kill           2 9 23024
ln             2 10 22880
ls             2 11 26448
mkdir          2 12 23176
rm             2 13 23160
sh             2 14 41976
stressfs       2 15 24016
usertests      2 16 148456
grind          2 17 38144
wc             2 18 25344
zombie         2 19 22408
console        3 20 0
这些是mkfs包含在初始文件系统中的文件;大多数是你可以运行的程序。你刚刚运行了其中一个:ls

xv6没有ps命令,但是,如果你按Ctrl-p,内核将打印每个进程的信息。如果你现在尝试,你会看到两行:一行是init的,一行是sh的。

要退出qemu,按:Ctrl-a x.

评分与提交流程

你可以运行make grade来用评分程序测试你的解决方案。助教将使用相同的评分程序来为你的实验提交评分。另外,我们还会有实验的检查会议(参见评分政策)。

实验代码附带GNU Make规则以简化提交流程。在提交实验的最终更改之后,输入make handin来提交你的实验。有关如何提交的详细说明,请参见下方

sleep

为xv6实现UNIX程序sleep;你的sleep应该暂停用户指定的tick数。tick是xv6内核定义的时间概念,即定时器芯片两次中断之间的时间。你的解决方案应该在文件user/sleep.c中。

一些提示:

从xv6 shell运行该程序:

      $ make qemu
      ...
      init: starting sh
      $ sleep 10
      (nothing happens for a little while)
      $
    

如果你的程序在如上所示运行时暂停,则你的解决方案是正确的。运行make grade来确认你确实通过了sleep测试。

请注意,make grade会运行所有测试,包括下面作业的测试。如果你只想运行一个作业的评分测试,请输入:

     $ ./grade-lab-util sleep
   
这将运行匹配"sleep"的评分测试。或者,你可以输入:
     $ make GRADEFLAGS=sleep grade
   
效果相同。

pingpong

编写一个程序,使用UNIX系统调用通过一对管道在两个进程之间"乒乓"一个字节,每个方向一个管道。父进程应该向子进程发送一个字节;子进程应该打印"<pid>: received ping",其中<pid>是其进程ID,将字节通过管道写给父进程,然后退出;父进程应该从子进程读取字节,打印"<pid>: received pong",然后退出。你的解决方案应该在文件user/pingpong.c中。

一些提示:

从xv6 shell运行该程序,它应该产生以下输出:

    $ make qemu
    ...
    init: starting sh
    $ pingpong
    4: received ping
    3: received pong
    $
  

如果你的程序在两个进程之间交换了一个字节并产生了如上所示的输出,则你的解决方案是正确的。

primes /

使用管道编写素数筛的并发版本。这个想法来自Doug McIlroy,Unix管道的发明者。这个页面中间的图片及其周围的文字解释了如何实现。你的解决方案应该在文件user/primes.c中。

你的目标是使用pipefork来设置管道。第一个进程将数字2到35送入管道。对于每个素数,你将安排创建一个进程,该进程通过管道从其左邻居读取数据,并通过另一个管道向其右邻居写入数据。由于xv6的文件描述符和进程数量有限,第一个进程可以在35处停止。

一些提示:

如果它实现了基于管道的筛法并产生以下输出,则你的解决方案是正确的:

    $ make qemu
    ...
    init: starting sh
    $ primes
    prime 2
    prime 3
    prime 5
    prime 7
    prime 11
    prime 13
    prime 17
    prime 19
    prime 23
    prime 29
    prime 31
    $
  

find

编写UNIX find程序的简单版本:查找目录树中所有具有特定名称的文件。你的解决方案应该在文件user/find.c中。

一些提示:

如果产生以下输出(当文件系统包含文件ba/b时),则你的解决方案是正确的:

    $ make qemu
    ...
    init: starting sh
    $ echo > b
    $ mkdir a
    $ echo > a/b
    $ find . b
    ./b
    ./a/b
    $ 
  

xargs

编写UNIX xargs程序的简单版本:从标准输入读取行,并为每一行运行一个命令,将该行作为命令的参数。你的解决方案应该在文件user/xargs.c中。

以下示例展示了xargs的行为:
    $ echo hello too | xargs echo bye
    bye hello too
    $
  
注意这里的命令是"echo bye",额外的参数是"hello too",组成命令"echo bye hello too",输出"bye hello too"。

请注意,UNIX上的xargs做了一个优化,它会一次向命令传递多个参数。我们不希望你做这个优化。要使UNIX上的xargs按照本实验的要求运行,请使用-n选项设置为1。例如

    $ echo "1\n2" | xargs -n 1 echo line
    line 1
    line 2
    $
  

一些提示:

xargs、find和grep组合使用效果很好:

  $ find . b | xargs grep hello
  
将在"."以下目录中每个名为b的文件上运行"grep hello"。

要测试你的xargs解决方案,请运行shell脚本xargstest.sh。如果产生以下输出,则你的解决方案是正确的:

  $ make qemu
  ...
  init: starting sh
  $ sh < xargstest.sh
  $ $ $ $ $ $ hello
  hello
  hello
  $ $   
  
你可能需要回去修复find程序中的bug。输出中有很多$,因为xv6 shell没有意识到它在处理来自文件的命令而不是来自控制台的命令,所以为文件中的每个命令打印一个$

提交实验

实验到此完成。确保你通过了所有make grade测试。如果本实验有问题,别忘了在answers-lab-name.txt中写下你的答案。提交你的更改(包括添加answers-lab-name.txt),然后在实验目录中输入make handin来提交你的实验。

花费时间

创建一个新文件time.txt,在其中放入一个整数,表示你花在本实验上的小时数。别忘了git addgit commit该文件。

提交

你将使用
提交网站提交你的作业。在提交任何作业或实验之前,你需要从提交网站申请一次API密钥。

在提交实验的最终更改之后,输入make handin来提交你的实验。

$ git commit -am "ready to submit my lab"
[util c2e3c8b] ready to submit my lab
 2 files changed, 18 insertions(+), 2 deletions(-)

$ make handin
tar: Removing leading `/' from member names
Get an API key for yourself by visiting https://6828.scripts.mit.edu/2020/handin.py/
Please enter your API key: XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
  % Total    % Received % Xferd  Average Speed   Time    Time     Time  Current
                                 Dload  Upload   Total   Spent    Left  Speed
100 79258  100   239  100 79019    853   275k --:--:-- --:--:-- --:--:--  276k
$
make handin会将你的API密钥存储在myapi.key中。如果你需要更改API密钥,只需删除此文件,然后让make handin重新生成(myapi.key不能包含换行符)。

如果你运行make handin时有未提交的更改或未跟踪的文件,你将看到类似以下的输出:

 M hello.c
?? bar.c
?? foo.pyc
Untracked files will not be handed in.  Continue? [y/N]
检查上述行,确保你的实验解决方案所需的所有文件都被跟踪,即不在以??开头的行中列出。你可以使用git add filenamegit跟踪你创建的新文件。

如果make handin不能正常工作,请尝试修复curl或Git命令的问题。或者你可以运行make tarball。这将为你制作一个tar文件,然后你可以通过我们的web界面上传。

可选挑战练习