Mit6.S081笔记Lab1: Xv6 and Unix utilities 熟悉开发环境
课程地址:https://pdos.csail.mit.edu/6.S081/2020/schedule.html
Lab 地址:https://pdos.csail.mit.edu/6.S081/2020/labs/util.html
我的代码地址:https://github.com/Amroning/MIT6.S081/tree/util
相关翻译:http://xv6.dgs.zone/labs/requirements/lab1.html
Lab1: Xv6 and Unix utilities
这个实验主要是熟悉xv6开发环境,学会使用常见的几个系统调用来编写代码。
看其他大佬说这个lab和后面的lab没有太大关联,所以有些很难的直接cv了,不做详细记录,主要是熟悉开发环境
构建并运行xv6:$ make qemu
打印每个进程的信息:键盘键入Ctrl-p
退出qemu:Ctrl-a x
sleep (easy)
实现xv6的UNIX程序sleep
:您的sleep
应该暂停到用户指定的计时数。一个滴答(tick)是由xv6内核定义的时间概念,即来自定时器芯片的两个中断之间的时间。您的解决方案应该在文件*user/sleep.c*中
代码实现:
// user/sleep.c
#include "kernel/types.h"
#include "kernel/stat.h"
#include "user/user.h"
int main(int argc, char **argv) {
if(argc < 2) {
printf("usage: sleep <ticks>\n");
}
sleep(atoi(argv[1]));
exit(0);
}
需要在Makefile相关位置添加$U/_sleep\
UPROGS=\
$U/_cat\
$U/_echo\
$U/_forktest\
$U/_grep\
$U/_init\
$U/_kill\
$U/_ln\
$U/_ls\
$U/_mkdir\
$U/_rm\
$U/_sh\
$U/_stressfs\
$U/_usertests\
$U/_grind\
$U/_wc\
$U/_zombie\
$U/_sleep\ //添加 $U/_sleep\
$U/_pingpong\
$U/_primes\
$U/_find\
$U/_xargs\
pingpong (easy)
编写一个使用UNIX系统调用的程序来在两个进程之间“ping-pong”一个字节,请使用两个管道,每个方向一个。父进程应该向子进程发送一个字节;子进程应该打印“<pid>: received ping
”,其中<pid>
是进程ID,并在管道中写入字节发送给父进程,然后退出;父级应该从读取从子进程而来的字节,打印“<pid>: received pong
”,然后退出。您的解决方案应该在文件*user/pingpong.c*中。
练习使用管道,使用两个管道进行父子进程通信。代码实现:
// user/pingpong.c
#include "kernel/types.h"
#include "kernel/stat.h"
#include "user/user.h"
int main(int argc, char **argv) {
// 创建管道会得到一个长度为 2 的 int 数组
// 其中 0 为用于从管道读取数据的文件描述符,1 为用于向管道写入数据的文件描述符
int pp2c[2], pc2p[2];
pipe(pp2c); // 创建用于 父进程 -> 子进程 的管道
pipe(pc2p); // 创建用于 子进程 -> 父进程 的管道
if(fork() != 0) { // parent process
write(pp2c[1], "!", 1); // 1. 父进程首先向发出该字节s
close(pp2c[1]);//及时关闭写端
char buf;
read(pc2p[0], &buf, 1); // 2. 父进程发送完成后,开始等待子进程的回复
printf("%d: received pong\n", getpid()); // 5. 子进程收到数据,read 返回,输出 pong
wait(0);
} else { // child process
char buf;
read(pp2c[0], &buf, 1); // 3. 子进程读取管道,收到父进程发送的字节数据
printf("%d: received ping\n", getpid());
write(pc2p[1], &buf, 1); // 4. 子进程通过 子->父 管道,将字节送回父进程
close(pc2p[1]);//及时关闭写端
}
close(pp2c[0]);//关闭读端
close(pc2p[0]);
exit(0);
}
同样需要在Makefile相关位置添加 $U/_pingpong\
primes (moderate) / (hard)
使用管道编写prime sieve
(筛选素数)的并发版本。这个想法是由Unix管道的发明者Doug McIlroy提出的。您的解决方案应该在*user/primes.c*文件中
代码实现:
// user/primes.c
#include "kernel/types.h"
#include "kernel/stat.h"
#include "user/user.h"
// 一次 sieve 调用是一个筛子阶段,会从 pleft 获取并输出一个素数 p,筛除 p 的所有倍数
// 同时创建下一 stage 的进程以及相应输入管道 pright,然后将剩下的数传到下一 stage 处理
void sieve(int pleft[2]) { // pleft 是来自该 stage 左端进程的输入管道
int p;
read(pleft[0], &p, sizeof(p)); // 读第一个数,必然是素数
if(p == -1) { // 如果是哨兵 -1,则代表所有数字处理完毕,退出程序
exit(0);
}
printf("prime %d\n", p);
int pright[2];
pipe(pright); // 创建用于输出到下一 stage 的进程的输出管道 pright
if(fork() == 0) {
// 子进程 (下一个 stage)
close(pright[1]); // 子进程只需要对输入管道 pright 进行读,而不需要写,所以关掉子进程的输入管道写文件描述符,减少进程打开的文件描述符数量
close(pleft[0]); // 这里的 pleft 是*父进程*的输入管道,子进程用不到,关掉
sieve(pright); // 子进程以父进程的输出管道作为输入,开始进行下一个 stage 的处理。
} else {
// 父进程 (当前 stage)
close(pright[0]); // 同上,父进程只需要对子进程的输入管道进行写而不需要读,所以关掉父进程的读文件描述符
int buf;
while(read(pleft[0], &buf, sizeof(buf)) && buf != -1) { // 从左端的进程读入数字
if(buf % p != 0) { // 筛掉能被该进程筛掉的数字
write(pright[1], &buf, sizeof(buf)); // 将剩余的数字写到右端进程
}
}
buf = -1;
write(pright[1], &buf, sizeof(buf)); // 补写最后的 -1,标示输入完成。
wait(0); // 等待该进程的子进程完成,也就是下一 stage
exit(0);
}
}
int main(int argc, char **argv) {
// 主进程
int input_pipe[2];
pipe(input_pipe); // 准备好输入管道,输入 2 到 35 之间的所有整数。
if(fork() == 0) {
// 第一个 stage 的子进程
close(input_pipe[1]); // 子进程只需要读输入管道,而不需要写,关掉子进程的管道写文件描述符
sieve(input_pipe);
exit(0);
} else {
// 主进程
close(input_pipe[0]); // 同上
int i;
for(i=2;i<=35;i++){ // 生成 [2, 35],输入管道链最左端
write(input_pipe[1], &i, sizeof(i));
}
i = -1;
write(input_pipe[1], &i, sizeof(i)); // 末尾输入 -1,用于标识输入完成
}
wait(0); // 等待第一个 stage 完成。注意:这里无法等待子进程的子进程,只能等待直接子进程,无法等待间接子进程。在 sieve() 中会为每个 stage 再各自执行 wait(0),形成等待链。
exit(0);
}
find (moderate)
写一个简化版本的UNIX的find
程序:查找目录树中具有特定名称的所有文件,你的解决方案应该放在*user/find.c*
代码实现:
// user/find.c
#include "kernel/types.h"
#include "kernel/stat.h"
#include "user/user.h"
#include "kernel/fs.h"
void find(char *path, char *target) {
char buf[512], *p;
int fd;
struct dirent de;
struct stat st;
if((fd = open(path, 0)) < 0){
fprintf(2, "find: cannot open %s\n", path);
return;
}
if(fstat(fd, &st) < 0){
fprintf(2, "find: cannot stat %s\n", path);
close(fd);
return;
}
switch(st.type){
case T_FILE:
// 如果文件名结尾匹配 `/target`,则视为匹配
if(strcmp(path+strlen(path)-strlen(target), target) == 0) {
printf("%s\n", path);
}
break;
case T_DIR:
if(strlen(path) + 1 + DIRSIZ + 1 > sizeof buf){
printf("find: path too long\n");
break;
}
strcpy(buf, path);
p = buf+strlen(buf);
*p++ = '/';
while(read(fd, &de, sizeof(de)) == sizeof(de)){
if(de.inum == 0)
continue;
memmove(p, de.name, DIRSIZ);
p[DIRSIZ] = 0;
if(stat(buf, &st) < 0){
printf("find: cannot stat %s\n", buf);
continue;
}
// 不要进入 `.` 和 `..`
if(strcmp(buf+strlen(buf)-2, "/.") != 0 && strcmp(buf+strlen(buf)-3, "/..") != 0) {
find(buf, target); // 递归查找
}
}
break;
}
close(fd);
}
int main(int argc, char *argv[])
{
if(argc < 3){
exit(0);
}
char target[512];
target[0] = '/'; // 为查找的文件名添加 / 在开头
strcpy(target+1, argv[2]);
find(argv[1], target);
exit(0);
}
xargs (moderate)
编写一个简化版UNIX的xargs
程序:它从标准输入中按行读取,并且为每一行执行一个命令,将行作为参数提供给命令。你的解决方案应该在*user/xargs.c*
代码实现:
// user/xargs.c
#include "kernel/types.h"
#include "kernel/stat.h"
#include "user/user.h"
#include "kernel/fs.h"
// 带参数列表,执行某个程序
void run(char *program, char **args) {
if(fork() == 0) { // child exec
exec(program, args);
exit(0);
}
return; // parent return
}
int main(int argc, char *argv[]){
char buf[2048]; // 读入时使用的内存池
char *p = buf, *last_p = buf; // 当前参数的结束、开始指针
char *argsbuf[128]; // 全部参数列表,字符串指针数组,包含 argv 传进来的参数和 stdin 读入的参数
char **args = argsbuf; // 指向 argsbuf 中第一个从 stdin 读入的参数
for(int i=1;i<argc;i++) {
// 将 argv 提供的参数加入到最终的参数列表中
*args = argv[i];
args++;
}
char **pa = args; // 开始读入参数
while(read(0, p, 1) != 0) {
if(*p == ' ' || *p == '\n') {
// 读入一个参数完成(以空格分隔,如 `echo hello world`,则 hello 和 world 各为一个参数)
*p = '\0'; // 将空格替换为 \0 分割开各个参数,这样可以直接使用内存池中的字符串作为参数字符串
// 而不用额外开辟空间
*(pa++) = last_p;
last_p = p+1;
if(*p == '\n') {
// 读入一行完成
*pa = 0; // 参数列表末尾用 null 标识列表结束
run(argv[1], argsbuf); // 执行最后一行指令
pa = args; // 重置读入参数指针,准备读入下一行
}
}
p++;
}
if(pa != args) { // 如果最后一行不是空行
// 收尾最后一个参数
*p = '\0';
*(pa++) = last_p;
// 收尾最后一行
*pa = 0; // 参数列表末尾用 null 标识列表结束
// 执行最后一行指令
run(argv[1], argsbuf);
}
while(wait(0) != -1) {}; // 循环等待所有子进程完成,每一次 wait(0) 等待一个
exit(0);
}
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 全程不用写代码,我用AI程序员写了一个飞机大战
· DeepSeek 开源周回顾「GitHub 热点速览」
· MongoDB 8.0这个新功能碉堡了,比商业数据库还牛
· 记一次.NET内存居高不下排查解决与启示
· 白话解读 Dapr 1.15:你的「微服务管家」又秀新绝活了