lab1: Util

sleep

这个简单,不多说了

  1. #include "kernel/types.h"
  2. #include "user/user.h"
  3. int main(int argc, char const *argv[])
  4. {
  5. if (argc != 2) { //参数错误
  6. fprintf(2, "usage: sleep <time>\n");
  7. exit(1);
  8. }
  9. sleep(atoi(argv[1]));
  10. exit(0);
  11. }

pingpong

使用两个管道进行父子进程通信,需要注意的是如果管道的写端没有close,那么管道中数据为空时对管道的读取将会阻塞。因此对于不需要的管道描述符,要尽可能早的关闭。

  1. #include "kernel/types.h"
  2. #include "user/user.h"
  3. #define RD 0 //pipe的read端
  4. #define WR 1 //pipe的write端
  5. int main(int argc, char const *argv[]) {
  6. char buf = 'P'; //用于传送的字节
  7. int fd_c2p[2]; //子进程->父进程
  8. int fd_p2c[2]; //父进程->子进程
  9. pipe(fd_c2p);
  10. pipe(fd_p2c);
  11. int pid = fork();
  12. int exit_status = 0;
  13. if (pid < 0) {
  14. fprintf(2, "fork() error!\n");
  15. close(fd_c2p[RD]);
  16. close(fd_c2p[WR]);
  17. close(fd_p2c[RD]);
  18. close(fd_p2c[WR]);
  19. exit(1);
  20. } else if (pid == 0) { //子进程
  21. close(fd_p2c[WR]);
  22. close(fd_c2p[RD]);
  23. if (read(fd_p2c[RD], &buf, sizeof(char)) != sizeof(char)) {
  24. fprintf(2, "child read() error!\n");
  25. exit_status = 1; //标记出错
  26. } else {
  27. fprintf(1, "%d: received ping\n", getpid());
  28. }
  29. if (write(fd_c2p[WR], &buf, sizeof(char)) != sizeof(char)) {
  30. fprintf(2, "child write() error!\n");
  31. exit_status = 1;
  32. }
  33. close(fd_p2c[RD]);
  34. close(fd_c2p[WR]);
  35. exit(exit_status);
  36. } else { //父进程
  37. close(fd_p2c[RD]);
  38. close(fd_c2p[WR]);
  39. if (write(fd_p2c[WR], &buf, sizeof(char)) != sizeof(char)) {
  40. fprintf(2, "parent write() error!\n");
  41. exit_status = 1;
  42. }
  43. if (read(fd_c2p[RD], &buf, sizeof(char)) != sizeof(char)) {
  44. fprintf(2, "parent read() error!\n");
  45. exit_status = 1; //标记出错
  46. } else {
  47. fprintf(1, "%d: received pong\n", getpid());
  48. }
  49. close(fd_p2c[WR]);
  50. close(fd_c2p[RD]);
  51. exit(exit_status);
  52. }
  53. }

primes

这个感觉还是有些难度的,它的思想是多进程版本的递归,不断地将左邻居管道中的数据筛选后传送给右邻居,每次传送的第一个数据都将是一个素数。

具体还是看代码吧,里面注释应该还是比较清楚的

  1. #include "kernel/types.h"
  2. #include "user/user.h"
  3. #define RD 0
  4. #define WR 1
  5. const uint INT_LEN = sizeof(int);
  6. /**
  7. * @brief 读取左邻居的第一个数据
  8. * @param lpipe 左邻居的管道符
  9. * @param pfirst 用于存储第一个数据的地址
  10. * @return 如果没有数据返回-1,有数据返回0
  11. */
  12. int lpipe_first_data(int lpipe[2], int *dst)
  13. {
  14. if (read(lpipe[RD], dst, sizeof(int)) == sizeof(int)) {
  15. printf("prime %d\n", *dst);
  16. return 0;
  17. }
  18. return -1;
  19. }
  20. /**
  21. * @brief 读取左邻居的数据,将不能被first整除的写入右邻居
  22. * @param lpipe 左邻居的管道符
  23. * @param rpipe 右邻居的管道符
  24. * @param first 左邻居的第一个数据
  25. */
  26. void transmit_data(int lpipe[2], int rpipe[2], int first)
  27. {
  28. int data;
  29. // 从左管道读取数据
  30. while (read(lpipe[RD], &data, sizeof(int)) == sizeof(int)) {
  31. // 将无法整除的数据传递入右管道
  32. if (data % first)
  33. write(rpipe[WR], &data, sizeof(int));
  34. }
  35. close(lpipe[RD]);
  36. close(rpipe[WR]);
  37. }
  38. /**
  39. * @brief 寻找素数
  40. * @param lpipe 左邻居管道
  41. */
  42. void primes(int lpipe[2])
  43. {
  44. close(lpipe[WR]);
  45. int first;
  46. if (lpipe_first_data(lpipe, &first) == 0) {
  47. int p[2];
  48. pipe(p); // 当前的管道
  49. transmit_data(lpipe, p, first);
  50. if (fork() == 0) {
  51. primes(p); // 递归的思想,但这将在一个新的进程中调用
  52. } else {
  53. close(p[RD]);
  54. wait(0);
  55. }
  56. }
  57. exit(0);
  58. }
  59. int main(int argc, char const *argv[])
  60. {
  61. int p[2];
  62. pipe(p);
  63. for (int i = 2; i <= 35; ++i) //写入初始数据
  64. write(p[WR], &i, INT_LEN);
  65. if (fork() == 0) {
  66. primes(p);
  67. } else {
  68. close(p[WR]);
  69. close(p[RD]);
  70. wait(0);
  71. }
  72. exit(0);
  73. }

find

感觉没什么好说的0.0,代码基本上都是COPY的ls.c中的内容

  1. #include "kernel/types.h"
  2. #include "kernel/fs.h"
  3. #include "kernel/stat.h"
  4. #include "user/user.h"
  5. void find(char *path, const char *filename)
  6. {
  7. char buf[512], *p;
  8. int fd;
  9. struct dirent de;
  10. struct stat st;
  11. if ((fd = open(path, 0)) < 0) {
  12. fprintf(2, "find: cannot open %s\n", path);
  13. return;
  14. }
  15. if (fstat(fd, &st) < 0) {
  16. fprintf(2, "find: cannot fstat %s\n", path);
  17. close(fd);
  18. return;
  19. }
  20. //参数错误,find的第一个参数必须是目录
  21. if (st.type != T_DIR) {
  22. fprintf(2, "usage: find <DIRECTORY> <filename>\n");
  23. return;
  24. }
  25. if (strlen(path) + 1 + DIRSIZ + 1 > sizeof buf) {
  26. fprintf(2, "find: path too long\n");
  27. return;
  28. }
  29. strcpy(buf, path);
  30. p = buf + strlen(buf);
  31. *p++ = '/'; //p指向最后一个'/'之后
  32. while (read(fd, &de, sizeof de) == sizeof de) {
  33. if (de.inum == 0)
  34. continue;
  35. memmove(p, de.name, DIRSIZ); //添加路径名称
  36. p[DIRSIZ] = 0; //字符串结束标志
  37. if (stat(buf, &st) < 0) {
  38. fprintf(2, "find: cannot stat %s\n", buf);
  39. continue;
  40. }
  41. //不要在“.”和“..”目录中递归
  42. if (st.type == T_DIR && strcmp(p, ".") != 0 && strcmp(p, "..") != 0) {
  43. find(buf, filename);
  44. } else if (strcmp(filename, p) == 0)
  45. printf("%s\n", buf);
  46. }
  47. close(fd);
  48. }
  49. int main(int argc, char *argv[])
  50. {
  51. if (argc != 3) {
  52. fprintf(2, "usage: find <directory> <filename>\n");
  53. exit(1);
  54. }
  55. find(argv[1], argv[2]);
  56. exit(0);
  57. }

xargs

之前这个题目我是做错的,但是仍然通过了测试。需要注意的是题目中要求为每一行执行一个命令。之前刷力扣的时候处理字符串好几次用到了有限状态自动机,虽然写的代码比较多,但是只要搞清楚逻辑,这种方法反而比较容易写出来。

xv6中的echo命令并不能输出换行符,例如在xv6和linux中执行命令

xv6执行echo "1\n2"输出

  1. "1\n2"

linux执行echo -e "1\n2"输出(-e启用转义)

  1. 1
  2. 2

因此没有想到如何在xv6中验证xargs的正确性,于是我将类似(类似是因为头文件不同,且将exec替换为了execvp)的代码xargs.c在linux下编译并测试运行,如下图所示:第一条命令是使用linux中xargs的输出,第二条命令是使用自己写的xargs的输出,二者是一致的。

img

有限状态自动机主要就是一系列的状态转换,例如对于

  1. 1 \n 23
  2. 0123 456

来说,第一行是待读取的字符串,第二行是字符下标,起始时状态为S_WAIT,状态转换如下

  1. 读取到0处的空格 状态由S_WAIT变为S_WAIT,继续等待参数到来(arg_beg前移)
  2. 读取到1处的字符1 状态由S_WAIT变为S_ARG,开始读取参数(arg_beg不动)
  3. 读取到2处的空格 状态由S_ARG变为S_ARG_END,储存参数地址(将lines[arg_beg]的地址存入x_argv中)
  4. 读取到3处的换行 状态由S_ARG_END变为S_LINE_ENDfork后执行程序
  5. ...以此类推
  1. #include "kernel/types.h"
  2. #include "user/user.h"
  3. #include "kernel/param.h"
  4. #define MAXSZ 512
  5. // 有限状态自动机状态定义
  6. enum state {
  7. S_WAIT, // 等待参数输入,此状态为初始状态或当前字符为空格
  8. S_ARG, // 参数内
  9. S_ARG_END, // 参数结束
  10. S_ARG_LINE_END, // 左侧有参数的换行,例如"arg\n"
  11. S_LINE_END, // 左侧为空格的换行,例如"arg \n""
  12. S_END // 结束,EOF
  13. };
  14. // 字符类型定义
  15. enum char_type {
  16. C_SPACE,
  17. C_CHAR,
  18. C_LINE_END
  19. };
  20. /**
  21. * @brief 获取字符类型
  22. *
  23. * @param c 待判定的字符
  24. * @return enum char_type 字符类型
  25. */
  26. enum char_type get_char_type(char c)
  27. {
  28. switch (c) {
  29. case ' ':
  30. return C_SPACE;
  31. case '\n':
  32. return C_LINE_END;
  33. default:
  34. return C_CHAR;
  35. }
  36. }
  37. /**
  38. * @brief 状态转换
  39. *
  40. * @param cur 当前的状态
  41. * @param ct 将要读取的字符
  42. * @return enum state 转换后的状态
  43. */
  44. enum state transform_state(enum state cur, enum char_type ct)
  45. {
  46. switch (cur) {
  47. case S_WAIT:
  48. if (ct == C_SPACE) return S_WAIT;
  49. if (ct == C_LINE_END) return S_LINE_END;
  50. if (ct == C_CHAR) return S_ARG;
  51. break;
  52. case S_ARG:
  53. if (ct == C_SPACE) return S_ARG_END;
  54. if (ct == C_LINE_END) return S_ARG_LINE_END;
  55. if (ct == C_CHAR) return S_ARG;
  56. break;
  57. case S_ARG_END:
  58. case S_ARG_LINE_END:
  59. case S_LINE_END:
  60. if (ct == C_SPACE) return S_WAIT;
  61. if (ct == C_LINE_END) return S_LINE_END;
  62. if (ct == C_CHAR) return S_ARG;
  63. break;
  64. default:
  65. break;
  66. }
  67. return S_END;
  68. }
  69. /**
  70. * @brief 将参数列表后面的元素全部置为空
  71. * 用于换行时,重新赋予参数
  72. *
  73. * @param x_argv 参数指针数组
  74. * @param beg 要清空的起始下标
  75. */
  76. void clearArgv(char *x_argv[MAXARG], int beg)
  77. {
  78. for (int i = beg; i < MAXARG; ++i)
  79. x_argv[i] = 0;
  80. }
  81. int main(int argc, char *argv[])
  82. {
  83. if (argc - 1 >= MAXARG) {
  84. fprintf(2, "xargs: too many arguments.\n");
  85. exit(1);
  86. }
  87. char lines[MAXSZ];
  88. char *p = lines;
  89. char *x_argv[MAXARG] = {0}; // 参数指针数组,全部初始化为空指针
  90. // 存储原有的参数
  91. for (int i = 1; i < argc; ++i) {
  92. x_argv[i - 1] = argv[i];
  93. }
  94. int arg_beg = 0; // 参数起始下标
  95. int arg_end = 0; // 参数结束下标
  96. int arg_cnt = argc - 1; // 当前参数索引
  97. enum state st = S_WAIT; // 起始状态置为S_WAIT
  98. while (st != S_END) {
  99. // 读取为空则退出
  100. if (read(0, p, sizeof(char)) != sizeof(char)) {
  101. st = S_END;
  102. } else {
  103. st = transform_state(st, get_char_type(*p));
  104. }
  105. if (++arg_end >= MAXSZ) {
  106. fprintf(2, "xargs: arguments too long.\n");
  107. exit(1);
  108. }
  109. switch (st) {
  110. case S_WAIT: // 这种情况下只需要让参数起始指针前移
  111. ++arg_beg;
  112. break;
  113. case S_ARG_END: // 参数结束,将参数地址存入x_argv数组中
  114. x_argv[arg_cnt++] = &lines[arg_beg];
  115. arg_beg = arg_end;
  116. *p = '\0'; // 替换为字符串结束符
  117. break;
  118. case S_ARG_LINE_END: // 将参数地址存入x_argv数组中同时执行指令
  119. x_argv[arg_cnt++] = &lines[arg_beg];
  120. // 不加break,因为后续处理同S_LINE_END
  121. case S_LINE_END: // 行结束,则为当前行执行指令
  122. arg_beg = arg_end;
  123. *p = '\0';
  124. if (fork() == 0) {
  125. exec(argv[1], x_argv);
  126. }
  127. arg_cnt = argc - 1;
  128. clearArgv(x_argv, arg_cnt);
  129. wait(0);
  130. break;
  131. default:
  132. break;
  133. }
  134. ++p; // 下一个字符的存储位置后移
  135. }
  136. exit(0);
  137. }