在华南理工大学的第五个年头,学OS已经第三年了,纸上得来终觉浅啊!所以决定动手完成一下MIT 6.S081的实验。 关于6.S081的美誉我也不赘述了。
实验使用win10 + wsl2 Ubuntu 20.04完成。
本实验共有六个内容,都是自己通过调用系统函数实现一些工具函数,包括:
ping
,子进程给父进程发消息,父进程收到后打印pong
亲测成功!【MIT6.S081/6.828】手把手教你搭建开发环境
Implement the UNIX program sleep for xv6; your sleep should pause for a user-specified number of ticks. A tick is a notion of time defined by the xv6 kernel, namely the time between two interrupts from the timer chip. Your solution should be in the file user/sleep.c.
先读xv6 book的第一章
看e.g., user/echo.c
, user/grep.c
, and user/rm.c
,看看人家是怎么读入参数的
如果忘记传参,程序需要报错。
atoi
(see user/ulib.c). 可以将默认传参的string转为int
使用系统调用sleep
.
看xv6的核心代码kernel/sysproc.c
如何实现sys_sleep
确保main最后调用了exit
在makefile里面,UPROGS加入sleep程序。这样make qemu会编译你的sleep并且可以命令行执行。
第一个程序比较简单,就是简单的包装一下系统调用sys_sleep()
,这个程序主要是为了熟悉一下做lab的流程:
user/sleep.c
Makefile
中UPROGS
下添加$U/_sleep\
sleep.c
grade-lab-util
并且指明要测试的函数。如 grade-lab-util sleep
#include "kernel/types.h"
#include "kernel/stat.h"
#include "user/user.h"
void main(int argc, char *argv[])
{
if (argc < 2)
{
fprintf(2, "Usage: %s \n" , argv[0]);
exit(1);
}
if (sleep(atoi(argv[1])) < 0)
{
fprintf(2, "Sleep error\n");
}
exit(0);
}
调用成功的话会显示:
Write a program that uses UNIX system calls to ‘‘ping-pong’’ a byte between two processes over a pair of pipes, one for each direction.
Your solution should be in the file user/pingpong.c
.
pipe
to create a pipe. 我们知道管道是单向的,所以要有两条管道,一个是父进程到子进程,一个是子进程到父进程。如何用管道在父子进程之间通信呢?老生常谈的问题了。
fork
to create a child. 写个if判断是否是子进程read
to read from the pipe, and write
to write to the pipe.getpid
to find the process ID of the calling process.#include "kernel/types.h"
#include "user/user.h"
#define BUFFER_SIZE 16
#define INDEX_READ 0
#define INDEX_WRITE 1
int main(int argc, char *argv[])
{
int fds_p2c[2]; //父进程到子进程
int fds_c2p[2]; //子进程到父进程
//创建两个管道
pipe(fds_c2p);
pipe(fds_p2c);
int pid;
pid = fork(); //fork出子进程
//子进程
if (pid == 0)
{
//通信方向:子进程到父进程,子进程不需要读
close(fds_c2p[INDEX_READ]);
//通信方向:父进程到子进程,子进程不需要写
close(fds_p2c[INDEX_WRITE]);
//step2:子进程收到ping,打印
char buf[BUFFER_SIZE];
if (read(fds_p2c[INDEX_READ], buf, 1) == 1)
{
printf("%d: received ping\n", getpid());
}
//step3: 子进程继续向父进程发出信号
write(fds_c2p[INDEX_WRITE], "f", 1);
exit(0);
}
//父进程
else
{
//通信方向:子进程到父进程,父进程不需要写
close(fds_c2p[INDEX_WRITE]);
//通信方向:父进程到子进程,父进程不需要读
close(fds_p2c[INDEX_READ]);
//step1: 父进程给子进程发ping
write(fds_p2c[INDEX_WRITE], "f", 1);
//step4:父进程读取子进程发出的ping
char buf[BUFFER_SIZE];
if (read(fds_c2p[INDEX_READ], buf, 1) == 1)
{
printf("%d: received pong\n", getpid());
}
}
//父进程结束
exit(0);
}
Write a concurrent version of prime sieve using pipes. This idea is due to Doug McIlroy, inventor of Unix pipes. The picture halfway down this page and the surrounding text explain how to do it. Your solution should be in the file user/primes.c
.
Your goal is to use pipe
and fork
to set up the pipeline.
Since xv6 has limited number of file descriptors and processes, the first process can stop at 35.
read
函数返回0,如果管道写的那一侧已经关闭了。这道题,最关键的理解看下面这张图:
这张图一共打印了这几个数字:2,3,5,7,11
有几个重要的理解:
想想内容2的pingpong,肯定是先pipe再fork。
提示让我们谨慎关闭不要的进程,但同时第一个进程需要等待所有进程的结束。所以不可以不等待,直接退出。每个父进程使用wait(0)去等待子进程完成。
#include "kernel/types.h"
#include "user/user.h"
#define INDEX_READ 0
#define INDEX_WRITE 1
//创建函数是因为会反复调用这个函数(递归)
void child(int fds_p2c[])
{
//通讯方向:父进程到子进程,子进程不需要写
close(fds_p2c[INDEX_WRITE]);
int i;
if (read(fds_p2c[INDEX_READ], &i, sizeof(i)) == 0)
{
//如果已经读不到数字了,说明已经全部素数都输出完毕了。这个时候子进程可以关闭管道,直接退出。
close(fds_p2c[INDEX_READ]);
exit(0);
}
printf("prime %d\n", i);
int num = 0;
//子进程到孙子进程的管道
int fds_c2gc[2];
pipe(fds_c2gc);
int pid;
//孙子进程,递归调用本函数
if ((pid = fork()) == 0)
{
child(fds_c2gc);
}
//子进程
else
{
//通讯方向:子进程到孙子进程,子进程不需要读
close(fds_c2gc[INDEX_READ]);
while (read(fds_p2c[INDEX_READ], &num, sizeof(num)) > 0)
{
//如果可以不整除才发出去
if (num % i != 0)
{
write(fds_c2gc[INDEX_WRITE], &num, sizeof(num));
}
}
close(fds_c2gc[INDEX_WRITE]);
//一样要等待所有的子进程结束
wait(0);
}
//子进程结束
exit(0);
}
int main(int argc, char *argv[])
{
int fds_p2c[2]; //父进程到子进程
pipe(fds_p2c);
int pid;
//子进程
if ((pid = fork()) == 0)
{
child(fds_p2c);
}
//父进程
else
{
//通讯方向:父进程到子进程,子进程不需要读
close(fds_p2c[INDEX_READ]);
for (int i = 2; i <= 35; i++)
{
//todo: 这里居然是传i的地址!
write(fds_p2c[INDEX_WRITE], &i, sizeof(i));
}
//必须关闭管道,否则子进程在read函数阻塞
close(fds_p2c[INDEX_WRITE]);
//等待子进程结束
wait(0);
}
//父进程结束
exit(0);
}
Write a simple version of the UNIX find program: find all the files in a directory tree with a specific name. Your solution should be in the file user/find.c
.
user/ls.c
怎么读取文件夹.
和..
strcmp()
来比较c string这部分代码涉及很多的字符串处理,我很不熟悉,copy了不少。这两部分的不熟悉:
参考了别人的代码,但写了很多注释。
#include "kernel/types.h"
#include "kernel/fs.h"
#include "user/user.h"
#include "kernel/stat.h"
char *fmtname(char *path)
{
char *p;
for (p = path + strlen(path); p >= path && *p != '/'; p--)
;
p++;
return p;
}
void equal_print(char *path, char *findname)
{
if (strcmp(fmtname(path), findname) == 0)
printf("%s\n", path);
}
void find(char *dir_name, char *file_name)
{
int fd;
if ((fd = open(dir_name, 0)) < 0)
{
fprintf(2, "ls: cannot open %s\n", dir_name);
return;
}
struct stat st;
if (fstat(fd, &st) < 0)
{
fprintf(2, "ls: cannot stat %s\n", dir_name);
close(fd);
return;
}
//循环判断
struct dirent de;
//buf是用来记录文件前缀的,这样才会打印出之前的目录
char buf[512], *p;
switch (st.type)
{
case T_FILE:
equal_print(dir_name, file_name);
break;
case T_DIR:
if (strlen(dir_name) + 1 + DIRSIZ + 1 > sizeof buf)
{
printf("find: path too long\n");
break;
}
//将path复制到buf里
strcpy(buf, dir_name);
//p为一个指针,指向buf(path)的末尾
p = buf + strlen(buf);
//在末尾添加/ 比如 path为 a/b/c 经过这步后变为 a/b/c/<-p
*p++ = '/';
// 如果是文件夹,则循环读这个文件夹里面的文件
while (read(fd, &de, sizeof(de)) == sizeof(de))
{
if (de.inum == 0 || (strcmp(de.name, ".") == 0) || (strcmp(de.name, "..") == 0))
continue;
//拼接出形如 a/b/c/de.name 的新路径(buf)
memmove(p, de.name, DIRSIZ);
p[DIRSIZ] = 0;
//递归查找
find(buf, file_name);
}
break;
}
close(fd);
}
int main(int argc, char *argv[])
{
if (argc != 3)
{
printf("Usage: find \n" );
exit(-1);
}
find(argv[1], argv[2]);
exit(0);
}
Write a simple version of the UNIX xargs program: read lines from the standard input and run a command for each line, supplying the line as arguments to the command. Your solution should be in the file user/xargs.c
.
Please note that xargs on UNIX makes an optimization where it will feed more than argument to the command at a time. We don’t expect you to make this optimization. To make xargs on UNIX behave the way we want it to for this lab, please run it with the -n option set to 1.
You may have to go back and fix bugs in your find program. The output has many $
because the xv6 shell doesn’t realize it is processing commands from a file instead of from the console, and prints a $
for each command in the file.
xargs用得少,可以先学习一下。Linux xargs 命令
真是考到我不会的东西了,等我用的多再回来写这个。
做这个实验工作量还是很大的,需要耐心和信心,慢工出细活。
欢迎大家留言交流。