• 6.1810: Operating System Engineering <LEC 1>


    课程链接6.1810 / Fall 2023

    一、本节任务 

    实验环境:

    二、introduction and examples

    2.1 open(), read(), write(), close(), dup()

    使用 open 打开或创建文件,得到文件描述符 fd,再对 fd 进行 read 或者 write 操作。每个进程都默认打开三个文件描述符:0(standard input),1(standard output),2(standard error),所以当我们想把输入内容复制到输出,可以有如下程序:

    1. // ex1.c: copy input to output.
    2. #include "kernel/types.h"
    3. #include "user/user.h"
    4. int
    5. main()
    6. {
    7. char buf[64];
    8. while(1){
    9. int n = read(0, buf, sizeof(buf));
    10. if(n <= 0)
    11. break;
    12. write(1, buf, n);
    13. }
    14. exit(0);
    15. }

    或者使用 open 打开一个文件,向文件内写数据:

    1. // ex2.c: create a file, write to it.
    2. #include "kernel/types.h"
    3. #include "user/user.h"
    4. #include "kernel/fcntl.h"
    5. int
    6. main()
    7. {
    8. int fd = open("out", O_WRONLY | O_CREATE | O_TRUNC);
    9. printf("open returned fd %d\n", fd);
    10. write(fd, "ooo\n", 4);
    11. exit(0);
    12. }

    close() 系统调用会关闭对应的文件描述符,而 dup() 会复制一个文件描述符,被复制的文件描述符和原来的描述符指向相同的文件。 

    2.2 fork(), exec()

    使用 fork() 函数可以创建一个子进程,子进程相当于父进程的副本,包括指令和数据。fork 在子进程和父进程中都会返回。在子进程中,fork 返回子进程的 PID。在父进程中,fork 返回 0。如下:

    1. // ex3.c: create a new process with fork()
    2. #include "kernel/types.h"
    3. #include "user/user.h"
    4. int
    5. main()
    6. {
    7. int pid;
    8. pid = fork();
    9. printf("fork() returned %d\n", pid);
    10. if(pid == 0){
    11. printf("child\n");
    12. } else {
    13. printf("parent\n");
    14. }
    15. exit(0);
    16. }

    exec() 使用一个可执行文件替代当前进程,相当于让子进程去执行可执行文件的内容,一般 fork() 和 exec() 是一起使用的:

    1. #include "kernel/types.h"
    2. #include "user/user.h"
    3. // ex5.c: fork then exec
    4. int
    5. main()
    6. {
    7. int pid, status;
    8. pid = fork();
    9. if(pid == 0){
    10. char *argv[] = { "echo", "THIS", "IS", "ECHO", 0 };
    11. exec("echo", argv);
    12. printf("exec failed!\n");
    13. exit(1);
    14. } else {
    15. printf("parent waiting\n");
    16. wait(&status);
    17. printf("the child exited with status %d\n", status);
    18. }
    19. exit(0);
    20. }

    exec 会替换调用进程的内存,但将保留其文件打开表,从而使得调用 exec 来运行新程序可以实现I/O重定向:

    1. #include "kernel/types.h"
    2. #include "user/user.h"
    3. #include "kernel/fcntl.h"
    4. // ex6.c: run a command with output redirected
    5. int
    6. main()
    7. {
    8. int pid;
    9. pid = fork();
    10. if(pid == 0){
    11. close(1);
    12. open("out", O_WRONLY | O_CREATE | O_TRUNC);
    13. char *argv[] = { "echo", "this", "is", "redirected", "echo", 0 };
    14. exec("echo", argv);
    15. printf("exec failed!\n");
    16. exit(1);
    17. } else {
    18. wait((int *) 0);
    19. }
    20. exit(0);
    21. }

    这里先关掉了标准输出描述符 1,然后使用 open 打开 out 文件,因为 open 会选择最小的可使用的文件描述符,所以文件 out 对应的文件描述符为 1,再使用 exec 执行 echo程序,echo 程序就会把写到到标准输出上的内容写到文件 out 里。

    2.3 pipe()

    管道 pipe 是作为一对文件描述符公开给进程的一个小的内核缓冲区,一个用于读取,另一个用于写入(其实就是两个文件描述符指向同一个缓冲区,一个用来读,一个用来写)。将数据写入管道的一端,使该数据可以从管道的另一端读取。管道为流程提供了一种通信的方式。

    使用 pipe() 系统调用初始化一个管道,两个文件描述符放到数组里面,第一个文件描述符用于读,第二个用于写。使用管道可以让父进程和子进程通信,因为 fork 复制父进程的文件描述符表及其内存,所以子进程与父进程的打开文件表相同,故父子进程可以通过 pipe 的读写两端进行通信:

    1. #include "kernel/types.h"
    2. #include "user/user.h"
    3. // ex8.c: communication between two processes
    4. int
    5. main()
    6. {
    7. int n, pid;
    8. int fds[2];
    9. char buf[100];
    10. // create a pipe, with two FDs in fds[0], fds[1].
    11. pipe(fds);
    12. pid = fork();
    13. if (pid == 0) {
    14. // child
    15. write(fds[1], "this is ex8\n", 12);
    16. } else {
    17. // parent
    18. n = read(fds[0], buf, sizeof(buf));
    19. write(1, buf, n);
    20. }
    21. exit(0);
    22. }

    当 pipe 所有的写端被关闭后,使用 read() 系统调用会返回 0。

    相比于临时文件,管道的一大优势就是会自动清理自己。管道可以通过任意长的数据量。

    三、Lab util: Unix utilities

    安装实验工具链:

    sudo apt-get install git build-essential gdb-multiarch qemu-system-misc gcc-riscv64-linux-gnu binutils-riscv64-linux-gnu 

    clone 并进入实验仓库:

    1. git clone git://g.csail.mit.edu/xv6-labs-2023
    2. cd xv6-labs-2023

    编译并运行 xv6:

    make qemu

    成功运行会打印如下信息:

    1. xv6 kernel is booting
    2. hart 2 starting
    3. hart 1 starting
    4. init: starting sh
    5. $

    下面开始完成实验。 

    3.1 Sleep(easy)

    实现一个用户 sleep 函数,很简单,调用系统函数 sleep 就行: 

    1. #include "kernel/types.h"
    2. #include "user/user.h"
    3. /*
    4. * usage: sleep
    5. * to sleep for some tick
    6. */
    7. int main(int argc, char *argv[])
    8. {
    9. if (argc != 2)
    10. {
    11. fprintf(2, "usage: sleep \n");
    12. exit(1);
    13. }
    14. int ticks = atoi(argv[1]);
    15. sleep(ticks);
    16. exit(0);
    17. }

    3.2 pingpong(easy)

    实现父进程和子进程之间交换一个字节,使用前面的 pipe 即可实现父子进程的通信,因为 pipe 不是全双工的,所以这里我们用两个管道:

    1. #include "kernel/types.h"
    2. #include "user/user.h"
    3. int main(int argc, char *argv[])
    4. {
    5. int pid, n;
    6. int p1[2], p2[2];
    7. char buf[10];
    8. pipe(p1);
    9. pipe(p2);
    10. pid = fork();
    11. if (pid == 0)
    12. {
    13. /* child */
    14. close(p1[1]); // close pipe1's write port
    15. close(p2[0]);
    16. n = read(p1[0], buf, 1);
    17. if (n == 1)
    18. {
    19. fprintf(1, "%d: received ping\n", getpid());
    20. }
    21. write(p2[1], buf, 1);
    22. }
    23. else
    24. {
    25. /* parent */
    26. close(p1[0]);
    27. close(p2[1]);
    28. write(p1[1], "a", 1);
    29. n = read(p2[0], buf, 1);
    30. if (n == 1)
    31. {
    32. fprintf(1, "%d: received pong\n", getpid());
    33. }
    34. }
    35. exit(0);
    36. }

    3.3 primes(hard)

    实现Sieve质数算法,大致流程如下图,每个进程打印第一个输入的质数,然后使用第一个质数筛选一遍剩下的数(即不可被第一个质数整除),这样到达最后一个进程即可打印全部质数:

    代码:

    1. #include "kernel/types.h"
    2. #include "user/user.h"
    3. void run_process(int rd_pipe_fd)
    4. {
    5. int pipes[2];
    6. int num, n;
    7. n = read(rd_pipe_fd, &num, sizeof(num));
    8. /* 判断是否有输入 */
    9. if (n > 0)
    10. {
    11. /* 打印第一个质数 */
    12. fprintf(1, "prime %d\n", num);
    13. pipe(pipes);
    14. int pid = fork();
    15. if (pid > 0)
    16. {
    17. int num1;
    18. /* 筛选一遍输入的数 */
    19. while ((n = read(rd_pipe_fd, &num1, sizeof(num1))) != 0)
    20. {
    21. if (num1 % num != 0)
    22. write(pipes[1], &num1, sizeof(num1));
    23. }
    24. close(pipes[1]);
    25. close(rd_pipe_fd);
    26. run_process(pipes[0]);
    27. int status;
    28. wait(&status);
    29. exit(0);
    30. }
    31. }
    32. else
    33. {
    34. close(rd_pipe_fd);
    35. exit(0);
    36. }
    37. return;
    38. }
    39. int main(int argc, char *argv[])
    40. {
    41. int i;
    42. int pipes[2];
    43. pipe(pipes);
    44. for (i = 2; i <= 35; i++)
    45. {
    46. write(pipes[1], &i, sizeof(i));
    47. }
    48. close(pipes[1]);
    49. run_process(pipes[0]);
    50. exit(0);
    51. }

     3.4 find (moderate)

    实现 find 命令:

    1. #include "kernel/types.h"
    2. #include "kernel/stat.h"
    3. #include "user/user.h"
    4. #include "kernel/fs.h"
    5. #include "kernel/fcntl.h"
    6. char*
    7. fmtname(char *path)
    8. {
    9. char *p;
    10. // Find first character after last slash.
    11. for(p=path+strlen(path); p >= path && *p != '/'; p--)
    12. ;
    13. p++;
    14. return p;
    15. }
    16. void find(char *path, char *filename)
    17. {
    18. int fd;
    19. char buf[512], *p;
    20. struct stat st;
    21. struct dirent dt;
    22. /* open file */
    23. if ((fd = open(path, O_RDONLY)) < 0)
    24. {
    25. fprintf(2, "cannot open %s\n", path);
    26. return;
    27. }
    28. /* stat file */
    29. if (fstat(fd, &st) < 0)
    30. {
    31. fprintf(2, "cannot stat %s\n");
    32. close(fd);
    33. return;
    34. }
    35. switch (st.type)
    36. {
    37. case T_DEVICE:
    38. case T_FILE:
    39. if (strcmp(fmtname(path), filename) == 0)
    40. {
    41. fprintf(1, "%s\n", path);
    42. }
    43. break;
    44. case T_DIR:
    45. strcpy(buf, path);
    46. p = buf + strlen(buf);
    47. *p++ = '/';
    48. while ((read(fd, &dt, sizeof(dt))) == sizeof(dt))
    49. {
    50. if ((dt.inum == 0) || (strcmp(dt.name, ".") == 0) || (strcmp(dt.name, "..") == 0))
    51. continue;
    52. memmove(p, dt.name, DIRSIZ);
    53. p[DIRSIZ] = 0;
    54. find(buf, filename);
    55. }
    56. break;
    57. }
    58. close(fd);
    59. }
    60. int main(int argc, char *argv[])
    61. {
    62. if (argc != 3)
    63. {
    64. fprintf(2, "Usage: find \n");
    65. exit(1);
    66. }
    67. find(argv[1], argv[2]);
    68. exit(0);
    69. }

    3.5 xargs (moderate)

    因为不是所有指令都能直接读取标准输入作为参数,所以需要 xargs 进行转换:

    1. #include "kernel/types.h"
    2. #include "kernel/param.h"
    3. #include "user/user.h"
    4. int main(int argc, char *argv[])
    5. {
    6. if (argc < 2)
    7. {
    8. fprintf(2, "Usage: xargs ...");
    9. exit(1);
    10. }
    11. char buf[512], *p;
    12. int n, i;
    13. p = buf;
    14. /* read from the standard input */
    15. while ((n = read(0, p, sizeof(char))) > 0)
    16. {
    17. if (*p == '\n')
    18. {
    19. char *args[MAXARG];
    20. *p = '\0';
    21. for (i = 1; i < argc; i++)
    22. {
    23. args[i-1] = argv[i];
    24. }
    25. char *p2 = buf;
    26. char *ch_p;
    27. while ((ch_p = strchr(p2, ' ')) != 0 && ch_p < buf + strlen(buf))
    28. {
    29. *ch_p = '\0';
    30. args[i-1] = p2;
    31. i++;
    32. p2 = ch_p + 1;
    33. }
    34. args[i-1] = p2;
    35. if (fork() == 0)
    36. {
    37. exec(args[0], args);
    38. }
    39. wait(0);
    40. p = buf;
    41. }
    42. else
    43. {
    44. p++;
    45. }
    46. }
    47. exit(0);
    48. }

    最后所有 test 均通过: 

  • 相关阅读:
    hive和hbase的一些数据导入导出操作
    Linux用户与权限管理命令
    如何设计测试用例
    Python学习十:网络编程
    2023/10/05 部分汇编指令
    天宇优配|上架秒光 “3时代”的大额存单受宠
    算法练习(11):牛客在线编程07 动态规划
    深入 lerna 发包机制 —— lerna publish - 掘金
    第一篇】 - XiaoZaiMultiAutoAiDevices框架开源啦
    IBM车库创新:为科技创新头号工程打造共创引擎
  • 原文地址:https://blog.csdn.net/qq_51103378/article/details/134416818