目录
- 实验准备
- Tasks
- Task 1: Boot xv6
- Task 2: sleep
- Task 3: pingpong
- Task 4: primes
- Task 5: find
实验准备
这个 lab 用来学习尝试如何通过 system call 来实现常见的 shell 命令行程序,比如 ls
、sleep
、xargs
等。
- 实验官网
可以使用 docker 搭建实验环境:
docker pull debian:bullseye
docker run -it --name mit6.s081 -d debian:bullseye /bin/bash
然后按照实验官网的介绍,在 git 下载源代码并启动。
Tasks
Task 1: Boot xv6
这个 task 就是在 git 上下载源代码,并启动 xv6 系统:
$ make qemu
成功后就可以看到 OS 成功启动。
Task 2: sleep
本 task 以及接下来的 task,都是在 /user
目录下实现用户级程序。
这个 task 新建一个 user/sleep.c
,并通过调用 system call 来实现睡眠的功能,代码较为简单:
#include "kernel/types.h"
#include "kernel/stat.h"
#include "user/user.h"int
mian(int argc, char *argv[])
{if (argc < 2) {fprintf(2, "Usage: sleep ticks...\n");exit(1);}int ticks = atoi(argv[1]);sleep(ticks); // 系统调用的 sleep,声明在 `user/user.h` 中exit(0);
}
完成后在 Makefile 中的 UPROGS
里添加上 sleep。
测试:
./grade-lab-util sleep
Task 3: pingpong
通过该 task 学会 fork 一个子进程,并实现父进程与子进程的通信。
我们需要实现 fork 一个子进程,并让父进程向子进程发送一个字节,然后子进程再向父进程发送一个字节。
关键技术细节:
- 使用 pipe 函数建立用于进程间通信的通道:
int fds[2];
pipe(fds);
这里 fds[0]
用于 read,fds[1]
用于 write,每个进程在使用完某个 fd 后需要 close 掉 fd。
read()
函数的行为:在另一方未关闭写的 fd 时,自己读取且没有更多消息时会阻塞住;而在另一方关闭了写的 fd 且自己没有更多消息可以读时,会立刻返回 0.
代码实现:
#include "kernel/types.h"
#include "kernel/stat.h"
#include "user/user.h"int
main(int argc, char *argv[])
{int fds[2], pid, n, xstatus;enum { BYTE_SZ=1 };if (pipe(fds) != 0) {printf("pipe() failed\n");exit(1);}pid = fork();if (pid > 0) { // parent proc// 向子进程发送一个字节char parent_buf[BYTE_SZ];if (write(fds[1], "x", BYTE_SZ) != BYTE_SZ) {printf("pingpong oops 1\n");exit(1);}// 等待子进程结束wait(&xstatus);if (xstatus != 0) {exit(xstatus);}// 读取子进程发送过来的字节while ((n = read(fds[0], parent_buf, BYTE_SZ)) > 0) {if (parent_buf[0] != 'x') {printf("pingpong oops 2\n");exit(1);}printf("%d: received pong\n", getpid());close(fds[0]);close(fds[1]);exit(0);}} else if (pid == 0) { // child proc// 等待读取父进程发送的字节char child_buf[BYTE_SZ];while ((n = read(fds[0], child_buf, BYTE_SZ)) > 0) {if (child_buf[0] != 'x') {printf("pingpong oops 2\n");exit(1);}printf("%d: received ping\n", getpid());// 向父进程发送一个字节if (write(fds[1], "x", BYTE_SZ) != BYTE_SZ) {printf("pingpong oops 2\n");exit(1);}close(fds[0]);close(fds[1]);exit(0);}} else {printf("fork() failed\n");exit(1);}exit(0);
}
测试:
Task 4: primes
这里需要使用 fork 子进程和进程间通信的技术实现 2~35 以内质数的筛选。
思路如下:
大致就是:
- 第一个进程将 2~35 写入管道
- 然后 fork 第二个进程,逐个从管道中读出数字,再将符合条件的数字送入下一个管道给第三个进程
- 第三个进程类似…
代码实现如下:
#include "kernel/types.h"
#include "kernel/stat.h"
#include "user/user.h"#define INT_SIZE (sizeof(int))
#define MAX_LIMIT 35
#define READ_END 0
#define WRITE_END 1int
main(int argc, char *argv[])
{int fds1[2], fds2[2], n, p, xstatus, pid;int *left_fds = fds1; // 左通道int *right_fds = fds2; // 右通道if (pipe(left_fds) != 0 || pipe(right_fds) != 0) {printf("pipe() failed\n");exit(1);}// feed numbersfor (int i = 2; i < MAX_LIMIT; ++i) {write(left_fds[WRITE_END], &i, INT_SIZE);}close(left_fds[WRITE_END]);int first_loop = 1;while (1) {int is_success = read(left_fds[READ_END], &n, INT_SIZE); // read from leftif (is_success == 0) { // if read endclose(left_fds[READ_END]);close(right_fds[READ_END]);close(right_fds[WRITE_END]);if (first_loop != 1) {wait(&xstatus);exit(xstatus);} else {exit(0);}}// 如果是第一次进入 Loop,则需要打印 prime 并创建一个子进程if (first_loop == 1) {pid = fork();if (pid == 0) { // child procfirst_loop = 1;close(left_fds[READ_END]);close(left_fds[WRITE_END]);int *temp = left_fds;left_fds = right_fds;right_fds = temp;if (pipe(right_fds) != 0) {printf("pipe() failed\n");exit(1);}close(left_fds[WRITE_END]);continue;} else if (pid > 0) { // parent procfirst_loop = 0;p = n;printf("prime %d\n", p);continue;} else {printf("fork() failed\n");exit(1);}} else {if ((n % p) != 0) {write(right_fds[WRITE_END], &n, INT_SIZE);}}}
}
测试: