实验说明原文:https://pdos.csail.mit.edu/6.828/2019/labs/util.html
第一个实验,按说是比较简单,但是做起来还是一波三折TAT
分为五个部分:
这个最简单,就是直接使用系统调用:
#include "user/user.h"
#include "kernel/types.h"
#include "kernel/stat.h"
void usage(){
printf("Usage: sleep \n");
printf("NUM must be non-negative\n");
exit();
}
int main(int argc,char* argv[]){
if(argc<2){
usage();
}
char c=argv[1][0];
if(c<'0'||c>'9'){
usage();
}
// this atoi implementation stops when encounters non-digit
int num=atoi(argv[1]);
printf("sleep %d ticks\n",num);
sleep(num);
exit();
}
printf("sleep %d ticks\n",num) 这一句不是要求的,是测试时加上的,但是加上也不会算你错~
这个是利用pipe,在父进程和子进程之间传递一个数据,因为只需要传递一次,所以也比较简单~
#include "user/user.h"
int main(){
int parent_fd[2];
int child_fd[2];
pipe(parent_fd);
pipe(child_fd);
if(fork()==0){
char p[8];
read(parent_fd[0],p,8);
printf("%d: received %s\n",getpid(),p);
char c[8]="pong";
write(child_fd[1],c,8);
}else{
char p[8]="ping";
write(parent_fd[1],p,8);
char c[8];
read(child_fd[0],c,8);
printf("%d: received %s\n",getpid(),c);
}
exit();
}
注意输出要和题目给出的实例一致,不然会报错~
产生32以内的素数,普通做法是很简单的,但是这里要求使用csp style,因为对这个不熟悉,对使用fork的并发编程也不熟悉,所以搞了蛮久才搞懂:
#include "kernel/types.h"
#include "kernel/stat.h"
#include "user/user.h"
#define R 0
#define W 1
int
main(int argc, char *argv[])
{
int numbers[100], cnt = 0, i;
int fd[2];
for (i = 2; i <= 35; i++) {
numbers[cnt++] = i;
}
// 注意fork是在这个循环内进行的
while (cnt > 0) {
pipe(fd);
if (fork() == 0) {
int prime, this_prime = 0;
// 关闭写的一端
close(fd[W]);
cnt = -1;
// 读的时候,如果父亲还没写,就会block
while (read(fd[R], &prime, sizeof(prime)) != 0) {
// 设置当前进程代表的素数,然后筛掉能被当前素数整除的数
if (cnt == -1) {
this_prime = prime;
cnt = 0;
} else {
// 把筛出来的接着放在number数组里?不对,这里cnt是重新从0开始计数的
if (prime % this_prime != 0) numbers[cnt++] = prime;
}
}
// printf("pid %d ,prime %d\n",getpid(),this_prime);
printf("prime %d\n",this_prime);
// 关闭读
close(fd[R]);
// WARN 注意!这里子进程并没有结束!子进程接下来继续执行while循环(cnt>0
// 然后接着fork,注意此时子进程的子进程会获得和子进程一样的cnt和numbers
// 也就是筛过的,而不是原始的
} else {
// 父进程里
close(fd[R]);
for (i = 0; i < cnt; i++) {
write(fd[W], &numbers[i], sizeof(numbers[0]));
}
close(fd[W]);
wait();
// 这个break,让父进程直接退出循环,从而结束了
// 即父进程只是起了往第一个子进程传原始数据的作用
break;
}
}
exit();
}
这个不难,但是稍微复杂一点,参考ls.c可以知道find大致该怎么写,搞清楚几个结构的关系,也能实现基本的查找了
optional是实现正则匹配,这个可以在grep里参考,但是我这里没有实现
/*
Usage: find path name
实现在给定路径的文件夹树中找到全部匹配name的
*/
#include "kernel/types.h"
#include "kernel/stat.h"
#include "user/user.h"
#include "kernel/fs.h"
// 注意,这个返回的是一个定长的字符数组,所以如果实际长度不足,后面有一段是空白,如果输出这个或者用这个作比较,是不符合要求
char*
fmtname(char *path)
{
// 这个函数的作用就是从一个文件的完整路径中取出文件名
// 比如: ./hello.c -> hello.c
static char buf[DIRSIZ+1];
char *p;
// Find first character after last slash.
for(p=path+strlen(path); p >= path && *p != '/'; p--)
;
p++;
// Return blank-padded name.
if(strlen(p) >= DIRSIZ)
return p;
memmove(buf, p, strlen(p));
memset(buf+strlen(p), ' ', DIRSIZ-strlen(p));
return buf;
}
void find(char* path,char *name){
// printf("find (%s,,fmt(path):%s,fmtlen:%d,%s)\n",path,fmtname(path),strlen(fmtname(path)),name);
char buf[512], *p=0;
int fd;
struct dirent de;
struct stat st;
if((fd = open(path, 0)) < 0){
fprintf(2, "ls: cannot open %s\n", path);
return;
}
if(fstat(fd, &st) < 0){
fprintf(2, "ls: cannot stat %s\n", path);
close(fd);
return;
}
switch(st.type){
case T_FILE:
if(strcmp((path),name)==0)
printf("%s\n",path);
break;
case T_DIR:
if(strlen(path) + 1 + DIRSIZ + 1 > sizeof buf){
printf("ls: path too long\n");
break;
}
strcpy(buf, path);
p = buf+strlen(buf);
// 这里不用管原来的path有没有/,对于ls来说,有几个都一样
*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("ls: cannot stat %s\n", buf);
continue;
}
// printf("p: %s\tde.name:%s\tbuf:%s\tfmtbuf:%s\n",p,de.name,buf,fmtname(buf));
if(strcmp(de.name,".")==0||strcmp(de.name,"..")==0){
continue;
}
if(strcmp(de.name,name)==0){
printf("%s/%s\n",path,name);
}
find(buf,name);
}
break;
}
close(fd);
}
int main(int argc,char* argv[]){
// 这里为了简单,假定一定按照usage使用
// 实际上如果只有一个参数,那么搜索路径为当前路径
if(argc<3){
exit();
}
find(argv[1],argv[2]);
exit();
}
前面几个都算是比较熟悉的命令,但是这一个没有用过,所以也有点费劲,文档里说的是:
read lines from standard input and run a command for each line, supplying the line as arguments to the command
阮一峰的这篇博客写的比较形象:http://www.ruanyifeng.com/blog/2019/08/xargs-tutorial.html
实际上就是把标准输入转化为后面命令的命令行参数
#include "kernel/types.h"
#include "kernel/stat.h"
#include "user/user.h"
#include "kernel/fs.h"
#define LINE 128
#define PARAMS 10
int main(int argc, char *argv[])
{
if (argc < 2)
{
exit();
}
printf("**********\n");
for (int i = 0; i < argc; i++)
{
printf("%d %s\n", i, argv[i]);
}
printf("**************\n");
char *cmd = argv[1];
// line是作为参数使用的
char line[LINE];
// char *p=gets(line,LINE);
char *params[PARAMS];
int index = 0;
// 例如:xargs echo hello,这样的,已经带了参数,就要把参数设置到
// 最终执行时需要的参数数组里
params[index++] = cmd;
for (int i = 2; i < argc; i++)
{
params[index++] = argv[i];
}
int n = 0;
while ((n = read(0, line, LINE)) > 0)
{
if (fork() == 0)
{
char *t = (char *)malloc(sizeof(char) * LINE);
int c = 0;
for (int i = 0; i < n; i++)
{
if (line[i] == '\n' || line[i] == ' ')
{
break;
}
t[c++] = line[i];
}
t[c] = '\0';
params[index++] = t;
printf("**********\n");
for (int i = 0; i < index; i++)
{
printf("%d %s\n", i, params[i]);
}
printf("**************\n");
exec(cmd, params);
printf("exec fail!\n");
exit();
}
else
{
wait();
}
}
exit();
}
上面***中间的是debug信息,这里要注意的就是字符串的处理,注意exec的参数char **,结尾都是\0
所以第一个实验完结~
red@red-vm-ubuntu:~/6s081/xv6-riscv-fall19$ ./grade-lab-util
make: 'kernel/kernel' is up to date.
sleep, no arguments: OK (1.0s)
sleep, returns: OK (0.8s)
sleep, makes syscall: OK (1.0s)
pingpong: OK (0.9s)
primes: OK (1.1s)
find, in current directory: OK (1.2s)
find, recursive: OK (1.5s)
xargs: OK (1.1s)
Score: 100/100