1 sleep(easy)

要求:为 xv6实现 UNIX 程序睡眠; 睡眠需要暂停一段用户指定的时间。刻度是由 xv6内核定义的时间概念,即定时器芯片两次中断之间的时间。解决的程序应该在 user/sleep. c 文件中。

 

一些小提示:

  • 查看user/中的其它程序,如echo.c,grep.c或rm.c,明白如何获取传递给程序的命令行参数。
  • 如果用户忘记传入参数,程序应打印一条错误信息
  • 命令行参数作为字符串传递,可以用atoi转换为整数(可参考ulib.c)
  • 需要使用系统调用sleep
  • user/user.h头文件中包含了sleep的C定义
  • 确保调用exit来退出程序
  • 完成解答后应将你的 sleep 程序添加到 Makefile 的 uPROGS 中,这样使用make qemu 才会编译你的程序,以后就可以在 xv6 shell 中运行它了。
  • 运行./grade-lab-util sleep来测试你的程序

解决:

  1. int main(int argc, char* argv[]){
  2. if(argc < 2){
  3. fprintf(2, "Usage: sleep ...\n");
  4. exit(1);
  5. }
  6. sleep(atoi(argv[1]));
  7. exit(0);
  8. }

对以上程序的说明:

  • main的参数列表中,argc表示命令行中传入的参数个数,argv数组则是以字符串形式保存传入的参数,默认传入的第一个参数为文件本身
  • fprintf指定输出到一个流文件中,函数原型为int fprintf( FILE *stream, const char *format, [ argument ]...),fprintf()函数根据指定的格式(format),向输出流(stream)写入数据(argument)。这里将错误通过fd2写入屏幕中的stderr。或者也可以使用write函数来打印错误信息,毕竟fprintf函数最终也是要通过系统调用write的。形式为write(int fd, char *buf, int n),参数 fd 是文件描述符,0 表示标准输入,1 表示标准输出,2 表示标准错误。参数 buf 是程序中存放写的数据的字符数组。参数 n 是要传输的字节数,调用 user/ulib.c 的 strlen() 函数就可以获取字符串长度字节数。

2 pingpong(easy)

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

 

一些提示:

  • 使用pipe来创建管道
  • 使用fork创建子进程
  • 使用read和write实现对管道的读和写
  • 对于当前进程使用getpid来获得子进程的PID
  • 在xv6中可使用的库函数有限,用户可以在user/user.h中看到这个列表;源代码(除系统调用外),位于ulib.c、printf.c和umalloc.c中。

     

    解决:
  1. int main(){
  2. int p[2];
  3. pipe(p);
  4. char s[10];
  5. int pid;
  6. pid = fork();
  7. if(pid > 0){
  8. //close(p[0]);
  9. //char* tmp = "ping";
  10. write(p[1], "ping", 5);
  11. wait(0);//如果不使用wait会导致程序无法正常结束
  12. read(p[0], s, 5);
  13. char* tmp = "pong";
  14. if(strcmp(tmp,s) == 0){
  15. printf("%d: received pong\n", getpid());
  16. }
  17. }else if(pid == 0){
  18. //close(p[1]);
  19. read(p[0], s, 5);
  20. int n = getpid();
  21. char* tmp = "ping";
  22. if(strcmp(s,tmp) == 0) {
  23. printf("%d: received ping\n", n);
  24. write(p[1], "pong", 5);
  25. }
  26. }else{
  27. exit(1);
  28. }
  29. exit(0);
  30. }

对以上程序的说明:

  • 父进程中写-读-打印,子进程中读-打印-写
  • 好像没有严格按照要求来,因为感觉一个管道就够用了

3 primes(moderate)

要求:使用管道编写一个基本筛选器的并发版本,将2-35的素数筛出来。程序应该使用pipe和fork来设置管道。第一个进程将数字2到35输入到管道中。对于每个素数,需要创建一个进程,该进程通过一个管道从左边的邻居读取数据,并通过另一个管道向右边的邻居写入数据。由于 xv6的文件描述符和进程数量有限,因此第一个进程的输入到35即可。相关提示文档在这里

 

一些提示:

  • 注意关闭进程不需要的文件描述符,否则程序将在第一个进程达到35之前耗尽资源来运行xv6。
  • 最开始的父进程要在所有子孙进程都退出之后才能退出。
  • 当管道的写端关闭时,read返回0
  • 最简单的方法是将32位int直接写入管道,而不是使用格式化的 ASCII I/O。

对帮助文档的说明:

里面大部分是讲历史和一些实验无关的东西,对于本实验的提示主要就是:首先将数字全部输入到最左边的管道,然后第一个进程打印出输入管道的第一个数 2 ,并将管道中所有 2 的倍数的数剔除。接着把剔除后的所有数字输入到右边的管道,然后第二个进程打印出从第一个进程中传入管道的第一个数 3 ,并将管道中所有 3 的倍数的数剔除。接着重复以上过程,最终打印出来的数都为素数。这个过程如下图所示:

实现:

  1. void primes(int p[]){
  2. int i;
  3. read(p[0], &i, sizeof(int));
  4. printf("prime %d\n", i);
  5. close(p[1]);//不关管道写端,会导致死循环输出0
  6. if(i == 31) exit(0);//只能以这样的方式来避免管道的非阻塞读,因为xv6里好像没法设置管道非阻塞模式
  7. int pp[2];
  8. pipe(pp);
  9. int pid = fork();
  10. if(pid > 0){
  11. int digit;
  12. close(pp[0]);
  13. while(1){
  14. if(read(p[0], &digit, sizeof(int)) < 1) {
  15. break;
  16. }
  17. if(digit%i != 0) write(pp[1], &digit, sizeof(int));
  18. }
  19. close(p[0]);
  20. close(pp[1]);
  21. wait(0);
  22. }else if(pid == 0){
  23. primes(pp);
  24. }else{
  25. exit(1);
  26. }
  27. exit(0);
  28. }
  29. int main(){
  30. int p[2];
  31. pipe(p);
  32. int pid = fork();
  33. if(pid > 0){
  34. close(p[0]);
  35. int i = 2;
  36. printf("prime %d\n", i);
  37. int j;
  38. for(j = i+1; j<=35; j++){
  39. if(j%i != 0){
  40. write(p[1], &j, sizeof(int));
  41. }
  42. }
  43. close(p[1]);
  44. wait(0);
  45. }else if(pid == 0){
  46. primes(p);
  47. }else{
  48. fprintf(2, "process create error!\n");
  49. exit(1);
  50. }
  51. exit(0);
  52. }

对以上程序的说明:

  • 设计思想不是很难,在main中首先创建一个管道,然后从父进程中把2-35传给子进程,从第一个子进程开始用函数primes递归去看每一个子进程。如果从左边的管道读到了值,第一个值就是我们要输出的素数,然后创建新的管道和子进程,并且把满足条件的数送入管道中,否则就直接退出子进程。
  • 对于每一个父进程,都要用wait等待子孙进程全部退出后才能exit
  • 注意在每个进程中,对于不需要使用的文件描述符要及时关上,否则会导致死循环的问题
  • 每个进程所拥有的管道读写文件描述符都是独立的, 并不是说父进程关闭写端子进程也对应关了

4 find(moderate)

要求:编写一个简单版本的 UNIX 查找程序: 查找指定目录下指定名称的文件。解决方案放在 user/find.c 文件中。

 

一些提示:

  • 查看 user/ls.c 以了解如何读取目录。
  • 使用递归的方式以实现find访问到子目录
  • 不要循环递归“.”(当前目录)和“..”(父目录)
  • 对文件系统的更改在 qemu 运行期间保持不变; 要获得一个干净的文件系统,先运行 make clean,然后使用 make qemu
  • 关于C字符串的部分可以参考 《C程序设计语言》 例如第5.5节
  • 注意不能像 Python 那样用 == 比较字符串,而是使用 strcmp ()

     

    实现:
  1. void find(char* path, char* target){
  2. char buf[512], *p;
  3. int fd;
  4. struct dirent de;
  5. struct stat st;
  6. if((fd = open(path, 0)) < 0){
  7. fprintf(2, "find: cannot open %s\n", path);
  8. return;
  9. }
  10. if(fstat(fd, &st) < 0){
  11. fprintf(2, "find: cannot stat %s\n", path);
  12. close(fd);
  13. return;
  14. }
  15. if(st.type != T_DIR){
  16. fprintf(2, "find: %s is not a directory\n", path);
  17. close(fd);
  18. return;
  19. }
  20. if(strlen(path) + 1 + DIRSIZ + 1 > sizeof buf){
  21. printf("find: path too long\n");
  22. close(fd);
  23. return;
  24. }
  25. //把path拷贝到buf中,在要查找的路径后面加上/
  26. strcpy(buf, path);
  27. p = buf+strlen(buf);
  28. *p++ = '/';
  29. //读取目录中每个文件的信息并检查
  30. while(read(fd, &de, sizeof(de)) == sizeof(de)){
  31. //暂不清楚是用来判断啥的,但是不加会递归有问题
  32. if(de.inum == 0) continue;
  33. //对于.和..直接跳过
  34. if(!strcmp(de.name, ".") || !strcmp(de.name, "..")){
  35. continue;
  36. }
  37. //把文件名拷贝给p,也等同于设置了新的路径
  38. memmove(p, de.name, DIRSIZ);
  39. //设置字符串结束位置
  40. p[DIRSIZ] = 0;
  41. if(stat(buf, &st) < 0){
  42. printf("find: cannot stat %s\n", buf);
  43. continue;
  44. }
  45. if(st.type == T_DIR){
  46. find(buf, target);
  47. }
  48. if(st.type == T_FILE && !strcmp(de.name, target)){
  49. printf("%s\n", buf);
  50. }
  51. }
  52. close(fd);
  53. }
  54. int main(int argc, char* argv[])
  55. {
  56. if(argc != 3){
  57. fprintf(2, "Format like: find . b\n");
  58. exit(1);
  59. }else{
  60. find(argv[1], argv[2]);
  61. }
  62. exit(0);
  63. }

对以上程序的补充:

  • 整体设计思想参照ls的实现,在main函数中判断输入的参数是否满足条件,当输入参数不是三个的时候,报错并退出,否则调用find函数去找;
  • 在find函数中,首先写退出条件:

    如果open当前路径失败,直接退出;

    open成功后进入给定的路径,调用stat读取文件描述符的状态信息复制给结构体st,复制失败,直接退出;

    如果当前路径读到的(st.type)是文件属性,直接退出,因为我们的参数是在某个目录下找一个文件;

    路径大小超过设置的文件名缓冲区大小,直接退出(ls中就对这一点进行了判断);

    接着把绝对路径进行拷贝,循环获取路径下的文件名,与要查找的文件名进行比较,如果类型为文件且名称与要查找的文件名相同则输出路径,如果是目录类型则递归调用 find() 函数继续在这个目录下查找。
  • strcmp(const char* str1, const char* str2)函数,比较str1和str2,str1>str2返回大于0的数,=返回0,<返回小于0的数
  • fstat和stat都是用来获取相关文件状态信息的:

    int fstat(int fd, struct stat *st),将文件描述符fd所指文件的状态信息复制给第二个参数,成功就返回0,否则返回-1;

    int stat(const char *path, struct stat *st);与fstat的区别在于第一个参数的要求是文件全路径,而fstat需要的是open后得到的文件描述符fd,返回值的情况和fstat是一样的。

5 xargs(moderate)

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

 

一些提示:

  • 使用 fork 和 exec 在每行输入中调用命令。在父进程中使用 wait 等待子级完成命令
  • 若要读取单个输入行,请一次读取一个字符,直到出现换行符(‘\n’),标准输入最后是有一个回车的
  • 如果你需要声明一个argv数组,kernel/param.h头文件中声明的MAXARG或许会有用

     

    对几个概念的区分:
  • 命令:我们在命令行里输入的那一整行东西整体叫做命令
  • 命令行参数:mkdir a b c命令中的a b c是mkdir所接收的参数,cd a命令中a是cd所接收的参数。
  • 标准化输入:我们输入一个命令后,等待我们继续输入的就是标准化输入,如grep a就是当我们的输入和a有匹配的时候,打印该输入。
  • 标准化输出:shell执行一个命令输出的东西
  • 管道符|:将符号前面命令的输入作为后面命令的命令行参数,如:
  1. $ echo hello too | xargs echo bye
  2. bye hello too

在这个命令中前一条命令打印出"hello too"来传给后一条命令,xargs在这里的作用是让它成为echo这条指令的命令行参数,于是最终的打印结果就是"bye hello too"如果不加xargs,那么"hello to"是要作为echo的标准化输入的。

 

实现:

  1. int main(int argc, char* argv[])
  2. {
  3. sleep(10);
  4. char buf[MAXARG];
  5. read(0, buf, MAXARG);
  6. //printf("%s\n", buf);
  7. char* argvs[MAXARG];
  8. int ct = 0;
  9. for(int i=1; i<argc; i++){
  10. argvs[ct++] = argv[i];
  11. }
  12. char *p = buf;
  13. //每遇到一个换行符,就开一个子进程把这个换行符之前的参数执行掉
  14. for(int i=0;i<MAXARG;i++){
  15. if(buf[i] == '\n'){
  16. //换行符设置为0,因为字符串是读到0结束的
  17. buf[i] = 0;
  18. int pid = fork();
  19. if(pid == 0){
  20. argvs[ct++] = p;
  21. exec(argvs[0], argvs);
  22. exit(0);
  23. }else if(pid > 0){
  24. wait(0);
  25. //让p指向换行符后面
  26. p = &buf[i+1];
  27. }
  28. }
  29. }
  30. exit(0);
  31. }

对以上程序的补充:

  • 根据实验目的,可以将xargs的实现分成三步:

    1.用read把前一条命令的结果读出来并保存

    2.把第二条命令的参数列表和前一条命令的结果合并

    3.用exec加载第二条命令
  • 加sleep(10)的原因是在测试find . b | xargs grep hello的时候,find操作比较慢,以致于xargs在执行的时候还没有得到find的标准输出,加上sleep后就可以通过了。

6 实验测试

注意要自己添加一个time.txt文件,记录你所花费的时间,不然最终的测评会在Cannot find time.txt那里扣一分

7 参考

樊潇的博客

b站up主阿苏EEer创建的笔记

mit6.s081 lab1:Unix Utilities的更多相关文章

  1. MIT6.S081/6.828 实验1:Lab Unix Utilities

    Mit6.828/6.S081 fall 2019的Lab1是Unix utilities,主要内容为利用xv6的系统调用实现sleep.pingpong.primes.find和xargs等工具.本 ...

  2. 【MIT6.S081/6.828】手把手教你搭建开发环境

    目录 1. 简介 2. 安装ubuntu20.04 3. 更换源 3.1 更换/etc/apt/sources.list文件里的源 3.2 备份源列表 3.3 打开sources.list文件修改 3 ...

  3. mit6.830 - lab1 - 存储模型 - 题解

    1.Intro github : https://github.com/CreatorsStack/CreatorDB lab1实现数据库基本的存储逻辑结构,具体包括:Tuple,TupleDesc, ...

  4. 6.828 lab1 bootload

    MIT6.828 lab1地址:http://pdos.csail.mit.edu/6.828/2014/labs/lab1/ 第一个练习,主要是让我们熟悉汇编,嗯,没什么好说的. Part 1: P ...

  5. Linux/UNIX 下 “command not found” 原因分析及解决

    在使用 Linux/UNIX 时,会经常遇到 "command not found" 的错误,就如提示的信息,Linux /UNIX 没有找到该命令.原因无外乎你命令拼写错误或 L ...

  6. Linux监控工具介绍系列——OSWatcher Black Box

      OSWatcher Balck Box简介 OSWatcher Black Box (oswbb)是Oracle开发.提供的一个小巧,但是实用.强大的系统工具,它可以用来抓取操作系统的性能指标,用 ...

  7. Linux根文件系统分析之init和busybox

    Hi,大家好!我是CrazyCatJack.今天给大家讲解Linux根文件系统的init进程和busybox的配置及编译. 先简单介绍一下,作为一个嵌入式系统,要想在硬件上正常使用的话.它的软件组成大 ...

  8. 给Android系统安装busybox

    转自:http://blog.csdn.net/lxgwm2008/article/details/38925051 busybox号称Linux平台的瑞士军刀,它集成了100多个最常用的Linux命 ...

  9. OSWatcher Black Box

    Linux监控工具介绍系列--OSWatcher Black Box OSWatcher Balck Box简介 OSWatcher Black Box (oswbb)是Oracle开发.提供的一个小 ...

  10. Web安全工具大汇聚

    http://www.owasp.org/index.PHP/Phoenix/Tools http://sebug.net/paper/other/Web安全工具大汇聚.txt =========== ...

随机推荐

  1. SNN_SRM模型

    # SRM模型 ## 早期SRM模型 Spike Response Modul(SRM)模型将传统的LIF微分模型换成了一个关于输入.输出的脉冲函数,可以将脉冲神经网络简化为第二代神经网络. 基本公式 ...

  2. 2023-11-11:用go语言,字符串哈希+二分的例题。 给定长为 n 的源串 s,以及长度为 m 的模式串 p, 要求查找源串中有多少子串与模式串匹配, s‘ 与 s 匹配,当且仅当 s‘ 与 s

    2023-11-11:用go语言,字符串哈希+二分的例题. 给定长为 n 的源串 s,以及长度为 m 的模式串 p, 要求查找源串中有多少子串与模式串匹配, s' 与 s 匹配,当且仅当 s' 与 s ...

  3. RLHF · PBRL | SURF:使用半监督学习,对 labeled segment pair 进行数据增强

    论文名称:SURF: Semi-supervised reward learning with data augmentation for feedback-efficient preference- ...

  4. Atcoder abc 221 E - LEQ

    原题链接:E - LEQ 思路: 题目要求对于从数组1~n找出所有符合开头数字小于等于结尾数字的子序列,\(A' = (A_1', A_2', ... , A_k')\),满足\(A_1' \leq ...

  5. 递归+记忆化递归+DP:斐波那契数列

    递归:算法复杂度O(2^N) 1 int fib(int n) 2 { 3 if (n == 0) 4 { 5 return 0; 6 } 7 if (n == 1) 8 { 9 return 1; ...

  6. python列表排序之sort(),sorted()和reverse()

    sort() 正序 sort()可以按字母的顺序来对列表进行永久性排序(改变列表自身的排序): list_1 = ['one', 'two', 'three', 'four', 'five'] pri ...

  7. git推送时报错:fatal: unable to access 'https://github.com/xxx/xxx.git/': Failed to connect to 127.0.0.1 port 31181 after 2063 ms: Connection refused

    一.报错原因 1.因为git在拉取或者提交项目时,中间会有git的http和https代理,但是我们本地环境本身就有SSL协议了,所以取消git的https代理即可,不行再取消http的代理. 2.当 ...

  8. 《最新出炉》系列初窥篇-Python+Playwright自动化测试-35-处理web页面定位toast-上篇

    1.简介 在使用appium写app自动化的时候介绍toast的相关元素的定位,在Web UI测试过程中,也经常遇到一些toast(出现之后一闪而过,不留下一点点痕迹),那么这个toast我们这边如何 ...

  9. 8 HTTP 的请求方法

    目录 标准请求方法 GET/HEAD GET 方法 HEAD方法 POST/PUT POST PUT 非常用方法 DELETE 方法 CONNECT 方法 OPTIONS 方法 TRACE 方法 拓展 ...

  10. Storm 集群的搭建及其Java编程进行简单统计计算

    一.Storm集群构建 编写storm 与 zookeeper的yml文件 storm yml文件的编写 具体如下: version: '2' services: zookeeper1: image: ...