操作系统实验Lab 1:Xv6 and Unix utilities(MIT 6.S081 FALL 2020)

Lab 1 Xv6 and Unix utilities

实验要求链接

Boot xv6 (easy)

实验目的

切换到 xv6-labs-2020 代码的 util 分支,并利用 QEMU 模拟器启动 xv6 系统。

实验步骤

使用下面的命令克隆 xv6-labs-2020 代码到本地。

git clone git://g.csail.mit.edu/xv6-labs-2020

使用下面的命令进入 xv6-labs-2020 代码目录。

cd xv6-labs-2020

使用下面的命令切换到 util 分支。

git checkout util

xv6-labs-2020 代码库与配套教材的 xv6-riscv 略有不同,它主要添加一些文件。如果你好奇,使用下面的命令查看 git 日志。如果不感兴趣可以忽略此步骤。

git log

接下来使用下面的命令编译并运行 xv6 系统。

make qemu

xv6 通过 QEMU 模拟器启动后,启动 shell 进程,最后你会看到下面的输出,说明你已经成功编译并运行 xv6 系统。

xv6 kernel is booting

hart 2 starting
hart 1 starting
init: starting sh
$ 

如果你输入 ls 命令,即创建了一个子进程 ls ,就会看到 xv6 目录下的文件,下面是输出结果。

$ 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 中按 Ctrl + p 会显示当前系统的进程信息。

1 sleep  init
2 sleep  sh

在 xv6 中按 Ctrl + a ,然后按 x 即可退出 xv6 系统。

sleep (easy)

实验目的

为 xv6 系统实现 UNIX 的 sleep 程序。你的 sleep 程序应该使当前进程暂停相应的时钟周期数,时钟周期数由用户指定。例如执行 sleep 100 ,则当前进程暂停,等待 100 个时钟周期后才继续执行。

实验要求及提示

  • 如果系统没有安装 vim 的请先使用命令 sudo apt install vim 安装 vim 编辑器。
  • 在开始实现程序前,阅读配套教材的第一章,其内容与实验内容息息相关。
  • 实现的程序应该命名为 sleep.c 并最后放入 user 目录下。
  • 可以查看 user 目录下的其他程序(如echo.c、grep.c和rm.c),以它们为参考,了解如何获取和传递给程序相应的命令行参数。
  • 如果用户传入参数有误,即没有传入参数或者传入多个参数,程序应该能打印出错误信息。
  • C 语言中的 atoi 函数可以将字符串转换为整数类型,在 xv6 中也已经定义了相同功能的函数。可以参考 user/ulib.c 或者参考机械工业出版社的《C程序设计语言(第2版·新版)》附录B.5。
  • 使用系统调用 sleep
  • 确保 main 函数调用 exit() 以退出程序。
  • 将你的 sleep 程序添加到 MakefileUPROGS 中。只有这步完成后, make qemu 才能编译你写的程序。
  • 完成上述步骤后,运行 make qemu 编译运行 xv6 ,输入 sleep 10 进行测试,如果 shell 隔了一段时间后才出现命令提示符,则证明你的结果是正确的,可以退出 xv6 运行 ./grade-lab-util sleep 或者 make GRADEFLAGS=sleep grade 进行单元测试。

实验思路

  1. 参考 user 目录下的其他程序,先把头文件引入,即 kernel/types.h 声明类型的头文件和 user/user.h 声明系统调用函数和 ulib.c 中函数的头文件。
  2. 编写 main(int argc,char* argv[]) 函数。其中,参数 argc 是命令行总参数的个数,参数 argv[]argc 个参数,其中第 0 个参数是程序的全名,其他的参数是命令行后面跟的用户输入的参数。
  3. 首先,编写判断用户输入的参数是否正确的代码部分。只要判断命令行参数不等于 2 个,就可以知道用户输入的参数有误,就可以打印错误信息。但我们要如何让命令行打印出错误信息呢?我们可以参考 user/echo.c ,其中可以看到程序使用了 write() 函数。 write(int fd, char *buf, int n) 函数是一个系统调用,参数 fd 是文件描述符,0 表示标准输入,1 表示标准输出,2 表示标准错误。参数 buf 是程序中存放写的数据的字符数组。参数 n 是要传输的字节数,调用 user/ulib.cstrlen() 函数就可以获取字符串长度字节数。通过调用这个函数,就能解决输出错误信息的问题啦。认真看了提示给出的所有文件代码你可能会发现,像在 user/grep.c 打印信息调用的是 fprintf() 函数,当然,在这里使用也没有问题,毕竟 fprintf() 函数最后还是通过系统调用 write() 。最后系统调用 exit(1) 函数使程序退出。按照惯例,返回值 0 表示一切正常,而非 0 则表示异常。
  4. 接下来获取命令行给出的时钟周期,由于命令行接收的是字符串类型,所以先使用 atoi() 函数把字符串型参数转换为整型。
  5. 调用系统调用 sleep 函数,传入整型参数,使计算机程序(进程、任务或线程)进入休眠。
  6. 最后调用系统调用 exit(0) 使程序正常退出。
  7. Makefile 文件中添加配置,照猫画虎,在 UPROGS 项中最后一行添加 $U/_sleep\ ,最后这项配置如下。
UPROGS=\
	$U/_cat\
	$U/_echo\
	$U/_forktest\
	$U/_grep\
	$U/_init\
	$U/_kill\
	$U/_ln\
	$U/_ls\
	$U/_mkdir\
	$U/_rm\
	$U/_sh\
	$U/_stressfs\
	$U/_usertests\
	$U/_grind\
	$U/_wc\
	$U/_zombie\
	$U/_sleep\

实验代码

// Lab Xv6 and Unix utilities
// sleep.c

// 引入声明类型的头文件
#include "kernel/types.h"
// 引入声明系统调用和 user/ulib.c 中其他函数的头文件
#include "user/user.h"

// int main(int argc,char* argv[])
// argc 是命令行总的参数个数  
// argv[] 是 argc 个参数,其中第 0 个参数是程序的全名,以后的参数是命令行后面跟的用户输入的参数
int
main(int argc, char *argv[])
{
    // 如果命令行参数不等于2个,则打印错误信息
    if (argc != 2)
    {
        // 系统调用 write(int fd, char *buf, int n) 函数输出错误信息
        // 参数 fd 是文件描述符,0 表示标准输入,1 表示标准输出,2 表示标准错误
        // 参数 buf 是程序中存放写的数据的字符数组
        // 参数 n 是要传输的字节数
        // 所以这里调用 user/ulib.c 的 strlen() 函数获取字符串长度字节数
        write(2, "Usage: sleep time\n", strlen("Usage: sleep time\n"));
        // 当然这里也可以使用 user/printf.c 中的 fprintf(int fd, const char *fmt, ...) 函数进行格式化输出
        // fprintf(2, "Usage: sleep time\n");
        // 退出程序
        exit(1);
    }
    // 把字符串型参数转换为整型
    int time = atoi(argv[1]);
    // 调用系统调用 sleep 函数,传入整型参数
    sleep(time);
    // 正常退出程序
    exit(0);
}

实验结果

在 xv6 中输入命令后一切符合预期。

$ sleep 10
$ sleep 1 1
Usage: sleep time
$ sleep 
Usage: sleep time

退出 xv6 运行单元测试。

./grade-lab-util sleep

提示:如果运行命令 ./grade-lab-util sleep/usr/bin/env: ‘python’: No such file or directory 错误,请使用命令 vim grade-lab-util,把第一行 python 改为 python3。如果系统没装 python3,请先安装 sudo apt-get install python3

通过测试样例。

make: 'kernel/kernel' is up to date.
== Test sleep, no arguments == sleep, no arguments: OK (0.9s) 
== Test sleep, returns == sleep, returns: OK (1.4s) 
== Test sleep, makes syscall == sleep, makes syscall: OK (0.9s) 

pingpong (easy)

实验要求

使用 UNIX 系统调用编写一个程序 pingpong ,在一对管道上实现两个进程之间的通信。父进程应该通过第一个管道给子进程发送一个信息 “ping”,子进程接收父进程的信息后打印 "<pid>: received ping" ,其中是其进程 ID 。然后子进程通过另一个管道发送一个信息 “pong” 给父进程,父进程接收子进程的信息然后打印 "<pid>: received pong" ,然后退出。

实验提示

  • 使用 pipe 创建管道。
  • 使用 fork 创建一个子进程。
  • 使用 read 从管道读取信息,使用 write 将信息写入管道。
  • 使用 getpid 获取当前 进程 ID
  • 将程序添加到 Makefile 中的 UPROGS
  • xv6 上的用户程序具有有限的库函数可供它们使用。你可以在 user/user.h 中查看,除系统调用外其他函数代码位于 user/ulib.cuser/printf.c 、和 user/umalloc.c 中。

实验思路

  1. 首先引入头文件 kernel/types.huser/user.h
  2. 开始编写 main 函数,定义两个文件描述符 ptoc_fdctop_fd 数组,创建两个管道, pipe(ptoc_fd)pipe(ctop_fd) ,一个用于父进程传递信息给子进程,另一个用于子进程传递信息给父进程。 pipe(pipefd) 系统调用会创建管道,并把读取和写入的文件描述符返回到 pipefd 中。 pipefd[0] 指管道的读取端, pipefd[1] 指管道的写入端。可以使用命令 man pipe 查看手册获取更多内容。
  3. 创建缓冲区字符数组,存放传递的信息。
  4. 使用 fork 创建子进程,子进程的 fork 返回值为 0 ,父进程的 fork 返回子进程的 进程 ID ,所以通过 if 语句就能让父进程和子进程执行不同的代码块。
  5. 编写父进程执行的代码块。使用 write() 系统调用传入三个参数,把字符串 "ping" 写入管道一,第一个参数为管道的写入端文件描述符,第二个参数为写入的字符串,第三个参数为写入的字符串长度。然后调用 wait() 函数等待子进程完成操作后退出,传入参数 0 或者 NULL ,不过后者需要引入头文件 stddef.h 。使用 read() 系统调用传入三个参数,接收从管道二传来的信息,第一个参数为管道的读取端文件描述符,第二个参数为缓冲区字符数组,第三个参数为读取的字符串长度。最后调用 printf() 函数打印当前进程 ID 以及接收到的信息,即缓冲区的内容。
  6. 编写子进程执行的代码块。使用 read() 系统调用传入三个参数,接收从管道一传来的信息,第一个参数为管道的读取端文件描述符,第二个参数为缓冲区字符数组,第三个参数为读取的字符串长度。然后调用 printf() 函数打印当前进程 ID 以及接收到的信息,即缓冲区的内容。最后使用 write() 系统调用传入三个参数,把字符串 "pong" 写入管道二,第一个参数为管道的写入端文件描述符,第二个参数为写入的字符串,第三个参数为写入的字符串长度。
  7. 最后调用 exit() 系统调用使程序正常退出。

实验代码

// Lab Xv6 and Unix utilities
// pingpong.c
#include "kernel/types.h"
#include "user/user.h"
#include "stddef.h"

int
main(int argc, char *argv[])
{
    int ptoc_fd[2], ctop_fd[2];
    pipe(ptoc_fd);
    pipe(ctop_fd);
    char buf[8];
    if (fork() == 0) {
        // child process
        read(ptoc_fd[0], buf, 4);
        printf("%d: received %s\n", getpid(), buf);
        write(ctop_fd[1], "pong", strlen("pong"));
    }
    else {
        // parent process
        write(ptoc_fd[1], "ping", strlen("ping"));
        wait(NULL);
        read(ctop_fd[0], buf, 4);
        printf("%d: received %s\n", getpid(), buf);
    }
    exit(0);
}

实验结果

Makefile 文件中, UPROGS 项追加一行 $U/_pingpong\ 。编译并运行 xv6 进行测试。

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

退出 xv6 ,运行单元测试检查结果是否正确。

./grade-lab-util pingpong

通过测试样例。

make: 'kernel/kernel' is up to date.
== Test pingpong == pingpong: OK (1.1s)

primes (moderate)/(hard)

实验要求

使用管道将 235 中的素数筛选出来,这个想法归功于 Unix 管道的发明者 Doug McIlroy 。链接中间的图片和周围的文字解释了如何操作。最后的解决方案应该放在 user/primes.c 文件中。
你的目标是使用 pipefork 来创建管道。第一个进程将数字 235 送入管道中。对于每个质数,你要安排创建一个进程,从其左邻通过管道读取,并在另一条管道上写给右邻。由于 xv6 的文件描述符和进程数量有限,第一个进程可以停止在 35

实验提示

  • 请关闭进程不需要的文件描述符,否则程序将在第一个进程到达 35 之前耗尽 xv6 资源。
  • 一旦第一个进程到达 35 ,它应该等到整个管道终止,包括所有的子进程,孙子进程等。因此,主进程只有在打印完所有输出后才能退出,即所有其他进程已退出后主进程才能退出。
  • 当管道的写入端关闭时, read 函数返回 0
  • 最简单的方式是直接将 32 位(4 字节)的整型写到管道中,而不是使用格式化的 ASCII I/O 。
  • 你应该根据需要创建进程。
  • 将程序添加到 Makefile 中的 UPROGS

实验思路

primes

  1. 首先打开提示给出的链接,阅读并观察上面给出的图。由图可见,首先将数字全部输入到最左边的管道,然后第一个进程打印出输入管道的第一个数 2 ,并将管道中所有 2 的倍数的数剔除。接着把剔除后的所有数字输入到右边的管道,然后第二个进程打印出从第一个进程中传入管道的第一个数 3 ,并将管道中所有 3 的倍数的数剔除。接着重复以上过程,最终打印出来的数都为素数。
  2. 参考 xv6 手册第一章的 Pipes 部分,其中的例子演示了运行程序 wc ,由此受到启发,可以使用文件描述符重定向技巧,避免 xv6 的文件描述符限制所带来的影响。所以,学习示例代码,先写一个重定向函数 mapping ,虽然叫重定向,但感觉还是称为映射比较好,原理就是关闭当前进程的某个文件描述符,把管道的读端或者写端的描述符通过 dup 函数复制给刚刚关闭的文件描述符,再把管道描述符关闭,节约了资源。
  3. 编写 main 函数,首先创建文件描述符和创建一个管道,再创建一个子进程,子进程把管道的写端口描述符映射到原来的描述符标准输出 1 上。循环通过 write 系统调用把 235 写入管道。父进程等待子进程把数据写入完毕后,父进程把管道的读端口描述符映射到原来的描述符标准输入 0 上。在编写一个 primes 函数,调用此函数实现递归筛选的过程。
  4. 编写 primes 函数。定义两个整型变量,存放从管道获取的数,定义管道描述符数组。从管道中读取数据,第一个数一定是素数,直接打印出来,然后创建另一个管道和子进程,子进程通过管道描述符映射,关闭不必要的文件描述符节约资源。再循环读取原管道中的数据,如果该数与原管道的第一个取模后不为 0 ,则再次写入刚刚创建的管道。父进程等待此子进程执行完毕,再次调用重定向函数关闭不必要的文件描述符,再递归调用 primes 函数重复以上筛选过程,即可将管道中所有素数打印出来。

实验代码

// Lab Xv6 and Unix utilities
// primes.c

#include "kernel/types.h"
#include "user/user.h"
#include "stddef.h"

// 文件描述符重定向(讲成映射比较好)
void
mapping(int n, int pd[])
{
  // 关闭文件描述符 n
  close(n);
  // 将管道的 读或写 端口复制到描述符 n 上
  // 即产生一个 n 到 pd[n] 的映射
  dup(pd[n]);
  // 关闭管道中的描述符
  close(pd[0]);
  close(pd[1]);
}

// 求素数
void
primes()
{
  // 定义变量获取管道中的数
  int previous, next;
  // 定义管道描述符数组
  int fd[2];
  // 从管道读取数据
  if (read(0, &previous, sizeof(int)))
  {
    // 第一个一定是素数,直接打印
    printf("prime %d\n", previous);
    // 创建管道
    pipe(fd);
    // 创建子进程
    if (fork() == 0)
    {
      // 子进程
      // 子进程将管道的写端口映射到描述符 1 上
      mapping(1, fd);
      // 循环读取管道中的数据
      while (read(0, &next, sizeof(int)))
      {
        // 如果该数不是管道中第一个数的倍数
        if (next % previous != 0)
        {
          // 写入管道
          write(1, &next, sizeof(int));
        }
      }
    }
    else
    {
      // 父进程
      // 等待子进程把数据全部写入管道
      wait(NULL);
      // 父进程将管道的读端口映射到描述符 0 上
      mapping(0, fd);
      // 递归执行此过程
      primes();
    }  
  }  
}

int 
main(int argc, char *argv[])
{
  // 定义描述符
  int fd[2];
  // 创建管道
  pipe(fd);
  // 创建进程
  if (fork() == 0)
  {
    // 子进程
    // 子进程将管道的写端口映射到描述符 1 上
    mapping(1, fd);
    // 循环获取 2 至 35
    for (int i = 2; i < 36; i++)
    {
      // 将其写入管道
      write(1, &i, sizeof(int));
    }
  }
  else
  {
    // 父进程
    // 等待子进程把数据全部写入管道
    wait(NULL);
    // 父进程将管道的读端口映射到描述符 0 上
    mapping(0, fd);
    // 调用 primes() 函数求素数
    primes();
  }
  // 正常退出
  exit(0);
}

实验结果

Makefile 文件中, UPROGS 项追加一行 $U/_primes\ 。编译并运行 xv6 进行测试。

$ 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
$

退出 xv6 ,运行单元测试检查结果是否正确。

./grade-lab-util primes

通过测试样例。

make: 'kernel/kernel' is up to date.
== Test primes == primes: OK (1.4s) 

find (moderate)

实验要求

编写一个简单的 UNIX find 程序,在目录树中查找包含特定名称的所有文件。你的解决方案应放在 user/find.c

实验提示

  • 查看 user/ls.c 了解如何读目录。
  • 使用递归查找子目录下的文件。
  • 不要递归 ".""..."
  • 文件系统的变化在 qemu 的运行中持续存在。使用 make clean 然后再 make qemu 让一个干净的文件系统运行。
  • 你需要使用 C 字符串。可以参考机械工业出版社的《C程序设计语言(第2版·新版)》第 5.5 节。
  • 请注意,比较字符串不能像在 Python 中使用 == 一样。请使用 strcmp() 函数。
  • 支持使用正则表达式匹配字符串,参照 user/grep.c 当中的正则匹配器。
  • 将程序添加到 MakefileUPROGS 中。

实验思路

  1. 根据提示,我们可以先阅读 user/ls.c 源码查看如何读目录。
  2. user/ls.c 中,有一个名为 fmtname() 的函数,其目的是将路径格式化为文件名,也就是把名字变成前面没有左斜杠 / ,仅仅保存文件名。
  3. user/ls.c 中,有一个名为 ls() 的函数,首先函数里面声明了需要用到的变量,包括文件名缓冲区、文件描述符、文件相关的结构体等等。其次使用 open() 函数进入路径,判断此路径是文件还是文件名。
  4. 如果是文件类型,则打印出文件信息,包括文件名,类型,Inode号,文件大小(单位为bytes)。如果是目录类型,则获取目录下的文件名,并依次输出文件信息。如果该文件还为路径,则循环遍历输出该路径下的文件信息。
  5. 可以把 user/ls.c 中的大部分代码重复利用,只要修改传入参数的数量,以及文件名之间的比较部分,就能完成此次实验。
  6. 下面的参考实验代码只使用了 find() 函数,与 ls() 函数一样,首先声明了文件名缓冲区、文件描述符和文件相关的结构体。其次试探是否能进入给定路径,然后调用系统调用获得一个已存在文件的模式,并判断其类型。如果该路径不是目录类型就报错。接着把绝对路径进行拷贝,循环获取路径下的文件名,与要查找的文件名进行比较,如果类型为文件且名称与要查找的文件名相同则输出路径,如果是目录类型则递归调用 find() 函数继续查找。

实验代码

// Lab Xv6 and Unix utilities
// find.c

#include "kernel/types.h"
#include "user/user.h"
#include "kernel/stat.h"
#include "kernel/fs.h"

// find 函数
void
find(char *dir, char *file)
{   
    // 声明 文件名缓冲区 和 指针
    char buf[512], *p;
    // 声明文件描述符 fd
    int fd;
    // 声明与文件相关的结构体
    struct dirent de;
    struct stat st;

    // open() 函数打开路径,返回一个文件描述符,如果错误返回 -1
    if ((fd = open(dir, 0)) < 0)
    {
        // 报错,提示无法打开此路径
        fprintf(2, "find: cannot open %s\n", dir);
        return;
    }

    // int fstat(int fd, struct stat *);
    // 系统调用 fstat 与 stat 类似,但它以文件描述符作为参数
    // int stat(char *, struct stat *);
    // stat 系统调用,可以获得一个已存在文件的模式,并将此模式赋值给它的副本
    // stat 以文件名作为参数,返回文件的 i 结点中的所有信息
    // 如果出错,则返回 -1
    if (fstat(fd, &st) < 0)
    {
        // 出错则报错
        fprintf(2, "find: cannot stat %s\n", dir);
        // 关闭文件描述符 fd
        close(fd);
        return;
    }

    // 如果不是目录类型
    if (st.type != T_DIR)
    {
        // 报类型不是目录错误
        fprintf(2, "find: %s is not a directory\n", dir);
        // 关闭文件描述符 fd
        close(fd);
        return;
    }

    // 如果路径过长放不入缓冲区,则报错提示
    if(strlen(dir) + 1 + DIRSIZ + 1 > sizeof buf)
    {
        fprintf(2, "find: directory too long\n");
        // 关闭文件描述符 fd
        close(fd);
        return;
    }
    // 将 dir 指向的字符串即绝对路径复制到 buf
    strcpy(buf, dir);
    // buf 是一个绝对路径,p 是一个文件名,通过加 "/" 前缀拼接在 buf 的后面
    p = buf + strlen(buf);
    *p++ = '/';
    // 读取 fd ,如果 read 返回字节数与 de 长度相等则循环
    while (read(fd, &de, sizeof(de)) == sizeof(de))
    {
        if(de.inum == 0)
            continue;
        // strcmp(s, t);
        // 根据 s 指向的字符串小于(s<t)、等于(s==t)或大于(s>t) t 指向的字符串的不同情况
        // 分别返回负整数、0或正整数
        // 不要递归 "." 和 "..."
        if (!strcmp(de.name, ".") || !strcmp(de.name, ".."))
            continue;
        // memmove,把 de.name 信息复制 p,其中 de.name 代表文件名
        memmove(p, de.name, DIRSIZ);
        // 设置文件名结束符
        p[DIRSIZ] = 0;
        // int stat(char *, struct stat *);
        // stat 系统调用,可以获得一个已存在文件的模式,并将此模式赋值给它的副本
        // stat 以文件名作为参数,返回文件的 i 结点中的所有信息
        // 如果出错,则返回 -1
        if(stat(buf, &st) < 0)
        {
            // 出错则报错
            fprintf(2, "find: cannot stat %s\n", buf);
            continue;
        }
        // 如果是目录类型,递归查找
        if (st.type == T_DIR)
        {
            find(buf, file);
        }
        // 如果是文件类型 并且 名称与要查找的文件名相同
        else if (st.type == T_FILE && !strcmp(de.name, file))
        {
            // 打印缓冲区存放的路径
            printf("%s\n", buf);
        } 
    }
}

int
main(int argc, char *argv[])
{
    // 如果参数个数不为 3 则报错
    if (argc != 3)
    {
        // 输出提示
        fprintf(2, "usage: find dirName fileName\n");
        // 异常退出
        exit(1);
    }
    // 调用 find 函数查找指定目录下的文件
    find(argv[1], argv[2]);
    // 正常退出
    exit(0);
}

实验结果

Makefile 文件中, UPROGS 项追加一行 $U/_find\ 。编译并运行 xv6 进行测试。

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

退出 xv6 ,运行单元测试检查结果是否正确。

./grade-lab-util find

通过测试样例。

make: 'kernel/kernel' is up to date.
== Test find, in current directory == find, in current directory: OK (1.4s) 
== Test find, recursive == find, recursive: OK (1.0s) 

xargs (moderate)

实验要求

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

实验提示

  • 使用 fork()exec() 在每一行输入上调用命令。在 parent 中使用 wait() 等待 child 完成命令。
  • 要读取单个输入行,请一次读取一个字符,直到出现换行符('\n')。
  • kernel/param.h 声明了 MAXARG ,如果你需要声明一个 argv 数组,这可能很有用。
  • 将程序添加到 MakefileUPROGS 中。
  • 文件系统的变化在 qemu 的运行中持续存在。使用 make clean 然后再 make qemu 让一个干净的文件系统运行。

实验思路

  1. 根据提示,我们需要调用 fork() 创建子进程,和调用 exec() 执行命令。我们知道要从标准输入中读取行并为每行运行一个命令,且将该行作为命令的参数。即把输入的字符放到命令后面,然后调用 exec() 。我们可以依次处理每行,根据空格符和换行符分割参数,调用子进程执行命令。
  2. 首先,我们定义一个字符数组,作为子进程的参数列表,其大小设置为 kernel/param.h 中定义的 MAXARG ,用于存放子进程要执行的参数。而后,建立一个索引便于后面追加参数,并循环拷贝一份命令行参数,即拷贝 xargs 后面跟的参数。创建缓冲区,用于存放从管道读出的数据。
  3. 然后,循环读取管道中的数据,放入缓冲区,建立一个新的临时缓冲区存放追加的参数。把临时缓冲区追加到子进程参数列表后面。并循环获取缓冲区字符,当该字符不是换行符时,直接给临时缓冲区;否则创建一个子进程,把执行的命令和参数列表传入 exec() 函数中,执行命令。当然,这里一定要注意,父进程一定得等待子进程执行完毕。

实验代码

// Lab Xv6 and Unix utilities
// xargs.c

#include "kernel/types.h"
#include "kernel/stat.h"
#include "user/user.h"
#include "kernel/param.h"

#define MAXN 1024

int
main(int argc, char *argv[])
{
    // 如果参数个数小于 2
    if (argc < 2) {
        // 打印参数错误提示
        fprintf(2, "usage: xargs command\n");
        // 异常退出
        exit(1);
    }
    // 存放子进程 exec 的参数
    char * argvs[MAXARG];
    // 索引
    int index = 0;
    // 略去 xargs ,用来保存命令行参数
    for (int i = 1; i < argc; ++i) {
        argvs[index++] = argv[i];
    }
    // 缓冲区存放从管道读出的数据
    char buf[MAXN] = {"\0"};
    
    int n;
	// 0 代表的是管道的 0,也就是从管道循环读取数据
    while((n = read(0, buf, MAXN)) > 0 ) {
        // 临时缓冲区存放追加的参数
		char temp[MAXN] = {"\0"};
        // xargs 命令的参数后面再追加参数
        argvs[index] = temp;
        // 内循环获取追加的参数并创建子进程执行命令
        for(int i = 0; i < strlen(buf); ++i) {
            // 读取单个输入行,当遇到换行符时,创建子线程
            if(buf[i] == '\n') {
                // 创建子线程执行命令
                if (fork() == 0) {
                    exec(argv[1], argvs);
                }
                // 等待子线程执行完毕
                wait(0);
            } else {
                // 否则,读取管道的输出作为输入
                temp[i] = buf[i];
            }
        }
    }
    // 正常退出
    exit(0);
}

实验结果

Makefile 文件中, UPROGS 项追加一行 $U/_xargs\ 。编译并运行 xv6 进行测试。

$ make clean
...
$ make qemu
...
init: starting sh
$ echo hello too | xargs echo bye
bye hello too

退出 xv6 ,运行单元测试检查结果是否正确。

./grade-lab-util xargs

通过测试样例。

make: 'kernel/kernel' is up to date.
== Test xargs == xargs: OK (1.8s) 

Lab 1 所有实验测试

退出 xv6 ,运行整个 Lab 1 测试,检查结果是否正确。

$ make grade
...
== Test sleep, no arguments == 
$ make qemu-gdb
sleep, no arguments: OK (2.4s) 
== Test sleep, returns == 
$ make qemu-gdb
sleep, returns: OK (1.2s) 
== Test sleep, makes syscall == 
$ make qemu-gdb
sleep, makes syscall: OK (0.8s) 
== Test pingpong == 
$ make qemu-gdb
pingpong: OK (1.0s) 
== Test primes == 
$ make qemu-gdb
primes: OK (1.3s) 
== Test find, in current directory == 
$ make qemu-gdb
find, in current directory: OK (0.6s) 
== Test find, recursive == 
$ make qemu-gdb
find, recursive: OK (3.0s) 
== Test xargs == 
$ make qemu-gdb
xargs: OK (3.9s) 
== Test time == 
time: OK 
Score: 100/100
  • 48
    点赞
  • 161
    收藏
    觉得还不错? 一键收藏
  • 9
    评论
Shell Programming in Unix, Linux and OS X is a thoroughly updated revision of Kochan and Wood’s classic Unix Shell Programming tutorial. Following the methodology of the original text, the book focuses on the POSIX standard shell, and teaches you how to develop programs in this useful programming environment, taking full advantage of the underlying power of Unix and Unix-like operating systems. After a quick review of Unix utilities, the book’s authors take you step-by-step through the process of building shell scripts, debugging them, and understanding how they work within the shell’s environment. All major features of the shell are covered, and the large number of practical examples make it easy for you to build shell scripts for your particular applications. The book also describes the major features of the Korn and Bash shells. Learn how to… Take advantage of the many utilities provided in the Unix system Write powerful shell scripts Use the shell’s built-in decision-making and looping constructs Use the shell’s powerful quoting mechanisms Make the most of the shell’s built-in history and command editing capabilities Use regular expressions with Unix commands Take advantage of the special features of the Korn and Bash shells Identify the major differences between versions of the shell language Customize the way your Unix system responds to you Set up your shell environment Make use of functions Debug scripts Table of Contents Chapter 1 A Quick Review of the Basics Chapter 2 What Is the Shell? Chapter 3 Tools of the Trade Chapter 4 And Away We Go Chapter 5 Can I Quote You on That? Chapter 6 Passing Arguments Chapter 7 Decisions, Decisions Chapter 8 'Round and 'Round She Goes Chapter 9 Reading and Printing Data Chapter 10 Your Environment Chapter 11 More on Parameters Chapter 12 Loose Ends Chapter 13 Rolo Revisited Chapter 14 Interactive and Nonstandard Shell Features Appendix A: Shell Summary Appendix B: For More Information
WinZip System Utilities Suite 是著名的软件公司 WinZip 发布的一款非常好用的系统优化清理工具套装,包含了 24 种优化清理工具,可以帮助你轻松优化清理操作系统! 系统优化工具 WinZip System Utilities Suite 中文版系统优化工具 WinZip System Utilities Suite 中文版 WinZip System Utilities Suite在安装完毕后双击桌面图标即可启动该软件,软件在主界面的状态栏中显示软件的几大功能使用状态,如系统安全,系统清理,系统优化状态!在软件左 侧的功能菜单中,软件分别提供了注册表清理优化工具,硬盘清理优化,系统优化,文件备份还原等多种功能,用户如要使用注册表清理优化功能,点击该按钮后即 可看到软件右侧的操作功能! 如要使用硬盘清理和优化工具,则点击软件左侧的DISK CLEANER按钮即可看到软件右侧的操作界面,分别为系统清理,硬盘优化,硬盘工具等等,用户如要使用点击相关的功能按钮即可!在系统优化功能中,软件 还提供了游戏优化工具,内存优化工具,驱动更新工具,用户可点击相关的功能按钮,软件即会跳出操作窗口,用户可根据软件提示来完成各项功能! 同样软件还提供了系统备份还原功能,用户在点击BACKUP RECOVERY按钮即可看到软件右侧的还原功能和强行删除功能,用户可对丢失的文件进行扫描还原,也可以通过强行删除工具来强行删除文件!另外 WinZip System Utilities Suite 还提供了隐私保护功能,在软件的功能中提供了隐私清除和加密功能,用户除了可以对系统中可能存在的隐私文件进行清除,也可以利用加密软件来对其 进行加密! 智能计算机护理 电脑系统作为电脑组成的一个重要部分,时刻影响到电脑运行性能,而我们在使用电脑时候,就必须定期对系统注册表进行维护,以保持系统的高效性,如注册表优化清理,硬盘优化清理,系统安全扫描等等。 磁盘清理器和优化器 WinZip System Utilities Suite 在安装完毕后双击桌面图标即可启动该软件,软件在主界面的状态栏中显示软件的几大功能使用状态,如系统安全,系统清理,系统优化状态!在软件左侧的功能菜单中,软件分别提供了注册表清理优化工具,硬盘清理优化,系统优化,文件备份还原等多种功能,用户如要使用注册表清理优化功能,点击该按钮后即可看到软件右侧的操作功能! Windows 优化器 如要使用硬盘清理和优化工具,则点击软件左侧的DISK CLEANER按钮即可看到软件右侧的操作界面,分别为系统清理,硬盘优化,硬盘工具等等,用户如要使用点击相关的功能按钮即可!在系统优化功能中,软件还提供了游戏优化工具,内存优化工具,驱动更新工具,用户可点击相关的功能按钮,软件即会跳出操作窗口,用户可根据软件提示来完成各项功能! 安全性与隐私 同样软件还提供了系统备份还原功能,用户在点击BACKUP RECOVERY按钮即可看到软件右侧的还原功能和强行删除功能,用户可对丢失的文件进行扫描还原,也可以通过强行删除工具来强行删除文件!另外WinZip System Utilities Suite还提供了隐私保护功能,在软件的功能中提供了隐私清除和加密功能,用户除了可以对系统中可能存在的隐私文件进行清除,也可以利用加密软件来对其进行加密!
### 回答1: :xv6是一个基于Unix的操作系统,它是一个教学用途的操作系统,旨在教授操作系统的基本概念和实现。它是在MIT的x86架构上开发的,包括了Unix的一些基本功能,如进程管理、文件系统、内存管理等。xv6的源代码是公开的,可以用于学习和研究。 Unix utilitiesUnix操作系统中的一些基本工具,如ls、cd、cp、mv、rm等。这些工具可以帮助用户管理文件和目录,执行各种操作。这些工具的实现是基于Unix的系统调用,可以通过编写C程序来调用这些系统调用实现相应的功能。这些工具是Unix操作系统的基础,也是其他操作系统的参考。 ### 回答2: lab: xv6 and unix utilities 实验是一项旨在帮助学生深入理解操作系统和 Unix 工具使用的实验。该实验分为两个部分,第一部分教授学生如何构建和运行 xv6 操作系统;第二部分则重点教授 Unix 工具的使用。 在 xv6 操作系统部分,学生将学习到操作系统内核的基本结构和实现原理。实验将引导学生理解内存管理、进程调度、系统调用等关键操作系统概念。此外,学生还将学习如何编写简单的 shell 以及如何通过修改 xv6 内核代码来实现新的系统调用和功能。 在 Unix 工具部分,学生将探索 Unix 系统中广泛使用的常见工具。这些工具包括 vi 编辑器、grep、awk、sed 等。实验将介绍这些工具的基本使用方法以及它们在处理文本和数据时的实际应用。这部分实验还将让学生深入了解 shell 和 shell 脚本的编写,帮助他们在 Unix 环境中轻松地编写脚本和自动化任务。 lab: xv6 and unix utilities 实验对计算机科学专业的学生具有重要意义。通过完成这个实验,学生将建立起对操作系统和 Unix 工具的深入理解,为他们成为一名优秀的软件工程师奠定坚实的基础。同时,这个实验还将为学生提供实践经验,让他们能够将所学知识应用到真实的软件开发和运维中。 ### 回答3: Lab: xv6 and Unix Utilities是一个计算机科学领域的实验,旨在让学生深入了解Unix操作系统以及操作系统本身的自我管理机制。在这个实验中,学生需要从零开始构建一个类似于Unix的操作系统,在这个操作系统中,学生需要设计一些基本命令,例如ls,cat,grep等等,并且将它们与系统的底层API结合起来,以实现各种功能。此外,学生还需要了解和探索xv6这个开发工具,它是一个轻量级基于Unix的操作系统实现,具有一定的可移植性和简洁性,因此,它可以作为一个基础框架来实现一个完整的Unix操作系统。 这个实验的目标是让学生了解Unix的基本命令结构和API,以及操作系统内部的一些基本机制,例如进程管理,文件系统交互以及进程通信等等。此外,通过实现这些命令,学生还可以学到一些基本的C语言编程技能,例如文件操作,字符串处理以及进程管理等等。还可以学习到如何使用Git等版本控制工具,以及如何进行调试和测试代码的技巧。 在整个实验过程中,学生需要有较强的自我管理能力和综合运用能力,因为在实现这些命令的同时,他们还需要和其他团队成员进行交流和合作,以及不断改进和完善他们的代码。总之,这个实验是一个非常有趣且富有挑战性的计算机科学课程,通过完成这个实验,学生可以更好地了解操作系统的构造和运作机制,以及如何设计和开发高效的系统级应用程序。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 9
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值