Skip to content

Xv6 and Unix utilities

Giovanna

About 1902 wordsAbout 6 min

2024-07-29

Lab: Xv6 and Unix utilities (mit.edu)

补充内容


Boot xv6

git clone git://g.csail.mit.edu/xv6-labs-2021
cd xv6-labs-2021
git checkout util
make qemu

xv6 kernel is booting!

sleep

任务描述:实现sleep程序,暂停用户所指定的ticks。

先看一下其它程序一般是怎么写的,可以发现这样的框架:

#include "kernel/types.h"
#include "kernel/stat.h"
#include "user/user.h"

int main(int argc, char *argv[])
{
	// 实现功能
	
	exit(0);
}
  • argc:输入的参数的个数
  • *argv[]:输入的参数的内容,是string,可以通过atoi转化为整型

查看user/ulib.c学习atoi的用法,atoi接受一个字符串返回一个int。

tmp8A15.png

阅读kernel/sysproc.c,找到sys_sleep函数:

tmp7792.png

查看user/user.h可以看到sleep的调用方式为int sleep(int);

可以动手写sleep啦!

#include "kernel/types.h"
#include "kernel/stat.h"
#include "user/user.h"

int main(int argc, char *argv[])
{
	if(argc < 2){
		fprintf(2, "Usage: sleep ticks\n");
		exit(1);
	}

	sleep(atoi(argv[1]));
	
	exit(0);
}

另一个需要注意的点是在实现对应功能后,需要更新Makefile的UPROGS部分。例如,实现了sleep.c后要在UPROGS处追加:$U/_sleep\

运行测试程序:

./grade-lab-util sleep`

tmp1641.png

AC!

pingpong

任务描述:创建一个管道,创建一个子进程,父进程向子进程发送一个字节,子进程收到该字节后打印<pid>: received ping,然后向父进程发送该字节,父进程接收到该字节后打印<pid>: received pong

需要使用到的方法:

// user/user.h
int fork(void);
int pipe(int*);
int write(int, const void*, int);
int read(int, void*, int);
int getpid(void);
void close(int);

我一开始没注意到管道应该是单向通信的,两边都又写又读只用一个管道,但是是不是因为我并有没有同时写同时读所以可以实现?

不应该呀,这就是不对的,还是应该用两个管道来实现。

实现的代码如下:

#include "kernel/types.h"
#include "kernel/stat.h"
#include "user/user.h"

int main(int argc, char *argv[])
{
	int pid;
	char byte;
	int p1[2], p2[2];
	pipe(p1);
	pipe(p2);
	
	pid = fork();
	if(pid == 0){
		close(p1[1]);
		close(p2[0]);
		read(p1[0], &byte, 1);
		close(p1[0]);
		printf("%d: received ping\n", getpid());
		write(p2[1], &byte, 1);
		close(p2[1]);
	}
	else{
		close(p1[0]);
		close(p2[1]);
		write(p1[1], &byte, 1);
		close(p1[1]);
		read(p2[0], &byte, 1);
		close(p2[0]);
		printf("%d: received pong\n", getpid());
	}
	exit(0);
}

思考:父子进程是如何通过管道相互等待的?

缓冲区为空且写端未全部关闭时,read会阻塞。

primes

任务描述:父进程向子进程发送2-35,子进程打印接收到的第一个数x(素数),创建另一个子进程,其余数如果不能被x整除就发送给它的子进程。(就是用管道和进程模拟筛素数)

tmp651C.png

第一个进程需要把2-35放入管道然后等待子进程完成后退出,而其它子进程孙子进程的操作都是相同的,因此将其封装为一个函数。该函数需要实现的功能:先从父进程处接收一个int,如果管道为空则直接退出,不空则输出接收到的数x,然后继续从父进程那里取数,如果不会被x整除就发给子进程,取完后等待子进程完成然后退出。

#include "kernel/types.h"
#include "kernel/stat.h"
#include "user/user.h"

void prime(int *fd){
	close(fd[1]);

	int buf;
	if(read(fd[0], &buf, 4) == 0) exit(0);
	
	int x = buf;
	printf("prime %d\n", x);

	int fd1[2];
	pipe(fd1);
	
	if(fork() == 0) prime(fd1);
	else{
		close(fd1[0]);
		while(read(fd[0], &buf, 4)){
			if(buf % x) write(fd1[1], &buf, 4);
		}
		close(fd[0]);
		close(fd1[1]);
		wait(0);
	}
	exit(0);
}

int main(int argc, char *argv[])
{
	int buf;
	
	int fd[2];
	pipe(fd);
	
	if(fork() == 0) prime(fd);
	else {
		close(fd[0]);
		for(buf = 2; buf <= 35; buf++)
			write(fd[1], &buf, 4);
		close(fd[1]);
		wait(0);
	}
	exit(0);
}

一开始理解有误,直接写了个判素数父进程判完直接扔给子进程输出了,但是也水过了测试哈哈哈。

find

任务描述:输出所有指定文件夹下特定名称文件的路径。

参考/user/ls.c中对于文件的处理,在此基础上完成该功能的代码。

tmpDFF2.png

因为我觉得写错误判断很烦,并且使得代码的核心逻辑看起来不够清晰,所以没写错误判断。

默认一开始的path是个文件夹。遍历该文件夹下的所有文件和文件夹,如果是文件判断名称是否符合是则输出,如果是文件夹则递归。

#include "kernel/types.h"
#include "kernel/stat.h"
#include "user/user.h"
#include "kernel/fs.h"

void find(char *path, char * file)
{
    char buf[512], *p;
	int fd;
	struct dirent de;
	struct stat st;
    
	fd = open(path, 0);
    fstat(fd, &st);
    
    strcpy(buf, path);
	p = buf+strlen(buf);
	*p++ = '/';
	
	while(read(fd, &de, sizeof(de)) == sizeof(de)){
		if(de.inum == 0 || strcmp(de.name, '.') == 0 || strcmp(de.name, '..') == 0)
			continue;
			
		memmove(p, de.name, DIRSIZ);
		p[DIRSIZ] = 0;
		stat(buf, &st);
		
		if(st.type == T_FILE){
			if(strcmp(de.name, file) == 0)
				printf("%s\n", buf);
		}
		else if(st.type == T_DIR)
			find(buf, file);
    }
    close(fd);
}

int main(int argc, char *argv[])
{
    find(argv[1], argv[2]);
	exit(0);
}

不要自以为是!st.type除了T_FILE和T_DIR以外可能还有其他值,不要直接else了!(因为这个递归老是停不下来)

xargs

任务描述:将标准输入作为参数一起输入到xargs后面跟的命令中,如果标准输入有多行那就需要执行多次命令。

xargs 命令详解

举个例子:

echo "1\n2" | xargs echo line

相当于执行:

echo line 1
echo line 2

argv还是和以前一样,是一个首元素是"xargs"的字符串数组。所以第二个参数就是这一批命令的类型,往后的参数就是这批指令都有的参数。然后处理标准输入,一个字符一个字符地读,参数用空格分开,不同命令用换行分开。读到换行符时就把这个参数读完的命令给处理了,fork一个子进程exec这个命令,父进程wait子进程。

坏!考验我最烦的字符串处理!

#include "kernel/types.h"
#include "kernel/stat.h"
#include "user/user.h"
#include "kernel/param.h"

#define MAXLEN 100

int main(int argc, char *argv[]){
	char param[MAXARG][MAXLEN];
	char *op=argv[1];  // 执行的操作
	char *m[MAXARG];

	// 原有的参数
	for(int i=1; i<argc; i++){
		strcpy(param[i-1], argv[i]);
	}

	// 处理标准输入
	while(1){
		int count=argc-1;
		memset(param[count], 0, (MAXARG-count)*MAXLEN);
		int r;
		int cur=0;
		char bf;
		while((r=read(0, &bf, 1)) > 0 && bf!='\n'){
			if(bf == ' '){
				count++;
				cur=0;
			} else {
				param[count][cur++]=bf;
			}
		}
		if(r<=0) break;  // EOF
		
		// exec第二个参数类型要求为**char
		for(int i=0; i<MAXARG-1; i++)
			m[i]=param[i];
		m[MAXARG-1]=0;
		
		if(fork()==0){
			exec(op, param);
			exit(0);
		} else {
			wait(0);
		}
	}
	exit(0);
}

The End

tmp6E6.png

一开始总是困难的,熟悉实验的流程,熟悉Linux系统,熟悉各种各样的命令……

在不少课上听闻大名的pipe,因为考试不考也没怎么去了解过,通过pingpong和primes才算是知道这是个什么东西。还有只会考调用多少次的fork,死去的记忆突然开始攻击我了。。

C语言一般也是指写写算法题一般也用不上指针以及一些和系统调用有关的命令,所以在功能的实现上对于我来说也是有一定难度的。

而更难的是理解题干想让我干啥!那个xargs我是真的看了好久又查了一些资料才知道这是想让我干啥。而且我还对于它的输入产生了一些误解走了好些弯路,最后偷看别人博客才写出来。

以及,一定要记得修改Makefile,我真傻呀跟我说命令失败了我也没反应过来是这个原因。