mits6.081_lab1

lab1

环境配置

1.安装依赖

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

本系统采用的是Vmvare + ubuntu20.04

2.克隆源码

git clone可能会有问题

直接上baidu网盘链接 自己copy到ubuntu中去

链接:https://pan.baidu.com/s/1jOxR5WIGsBzoJaxkP-m1Cg
提取码:r88b
–来自百度网盘超级会员V4的分享

源码目录解析

image-20220906142821897

kernel:内核源码,system call的实现

user:系统自带工具源码 shell、echo、cat等工具的实现

grade-lab-util:python实现的代码测试工具

Makefile:make配置文件

git分支说明

image-20220906143048838

课程笔记

实验

sleep实验

提供练手的实验 主要了解配置过程 其他的shell命令添加步骤同理

在写完 sleep.c 代码后,需要在 Makefile 文件中的 UPROGS 字段中添加 $U/_[xxx]\,然后才能使用 ./grade-lab-util 进行测试。

image-20220907184538242

代码

#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);
}

测试结果

image-20220907184912289

运行结果

image-20220907184954245

ping pong实验

管道练手题

// 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); // 创建用于 父进程 -> 子进程 的管道 parent->child
	pipe(pc2p); // 创建用于 子进程 -> 父进程 的管道
	
	if(fork() != 0) { // parent process
		write(pp2c[1], "!", 1); // 1. 父进程首先向发出该字节
		char buf;
		read(pc2p[0], &buf, 1); // 2. 父进程发送完成后,开始等待子进程的回复
		printf("%d: received pong\n", getpid()); // 5. 子进程收到数据,read 返回,输出 pong
		wait(0);
	} else { // child process  fork()返回0时 启动子进程
		char buf;
		read(pp2c[0], &buf, 1); // 3. 子进程读取管道,收到父进程发送的字节数据
		printf("%d: received ping\n", getpid());
		write(pc2p[1], &buf, 1); // 4. 子进程通过 子->父 管道,将字节送回父进程
	}
	exit(0);
}

运行结果

父发 子读 子输出ping 子发 父读 父输出pong

image-20220907212335659

primes实验

每一个 stage 以当前数集中最小的数字作为素数输出(每个 stage 中数集中最小的数一定是一个素数,因为它没有被任何比它小的数筛掉),并筛掉输入中该素数的所有倍数(必然不是素数),然后将剩下的数传递给下一 stage。最后会形成一条子进程链,而由于每一个进程都调用了 wait(0); 等待其子进程,所以会在最末端也就是最后一个 stage 完成的时候,沿着链条向上依次退出各个进程。

// 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);
}

运行结果

image-20220907213240330

find实验

基本原理与ls相同,基本上可以从ls.c改造

// 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);
}

运行结果

image-20220907213402267

xargs实验

编写 xargs 工具,从标准输入读入数据,将每一行当作参数,加入到传给 xargs 的程序名和参数后面作为额外参数,然后执行。

// 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);
}

运行结果

最后一行指令
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);
}


运行结果

![image-20220907213751660](https://img-blog.csdnimg.cn/img_convert/71ff8fa4e4e6345cea225af119d563e6.png)
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

-特立独行的猪-

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值