MIT 6.s081 Lab1 Util

257 阅读4分钟

做这个实验之前,建议先读完课程提供的book-riscv-rev1的第一章,里面的内容对第一个实验非常有帮助,否则容易对实验内容摸不着头脑。

Boot xv6

很简单,照着课程的代码来就好了,就不多啰嗦了。

sleep

通过echo.c等文件知道怎么获取命令行中的参数,然后根据课程提示将建好的user/sleep.c加入Makefile里即可。

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

int
main(int argc, char *argv[]) //argc表示命令行中有多少个参数,argv是一个字符串数组,存储参数
{
	if(argc<=1) fprintf(2,"You should give a sleep number.\n");
	int sleepnum=atoi(argv[1]);//根据课程提示,直接使用提供的atoi
	sleep(sleepnum);
	exit(0);
}

pingpong

阅读book-riscv-rev1第一章的1.1和1.3,了解fork()pipe()的使用方式,这题就很简单了。

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

char buf[1024];

int
main()
{
  int p1[2],p2[2];//管道0读1写,  p1:父->子  p2:子->父
  pipe(p1);
  pipe(p2);
  int pid = fork();
  if(pid > 0){//父进程
    close(p1[0]);
    write(p1[1],"a",1);
    close(p1[1]);
    close(p2[1]);
    int res=read(p2[0],buf,1);
    close(p2[0]);
    if(res==1) printf("%d: received pong\n",getpid());
  }
  else if(pid == 0){//子进程
    close(p1[1]);
    int res=read(p1[0],buf,1);
    if(res==1) printf("%d: received ping\n",getpid());
    close(p2[0]);
    write(p2[1],"a",1);
    close(p2[1]);
  }
  else{
    printf("fork error\n");
  }
  exit(0);
}

primes

应该是lab1最难的一题,这题的主要任务是输出2-35中的素数。因为是从2开始的,2也是一个素数,所以可以使用素数筛法,除了负责传递数的第一个进程外,其他进程保存接收到的第一个数x,然后丢弃掉x的倍数,这样就能确保每个进程的接收到的第一个数都是素数。这样除了第一个进程外的其他进程的工作就一致了,如下图所示,print接收到的第一个数x,并丢弃x的倍数,再将剩下的传递给下一个进程。 image.png

思路明确后,这题的难点我认为还是在多进程和多管道的处理上,考虑到每个进程需要通过管道读取上一个进程传递的数和传递未丢弃的数给下一个进程,那一个处理管道的函数可以很好地解决这个问题。

然后说下这道题我踩过的几个坑吧。

  1. 不必要的管道文件描述符要及时关闭,xv6的资源不够,如果不及时关闭会爆掉,无法新建管道。
  2. 注意管道读取端关闭的时机,在父进程->子进程的管道中,一定要在fork()后再closep[0],否则子进程是没法读取的。
  3. 数字在管道中的传输不需要转化为字符串,通过管道传递地址比较方便。
#include "kernel/types.h"
#include "kernel/stat.h"
#include "user/user.h"

void process(int p[]){
  close(p[1]);
  int i;
  if(read(p[0],&i,sizeof(i))){
    printf("prime %d\n",i);
    int p1[2];
    pipe(p1);
    int j;
    if(fork()){
      close(p1[0]);//一定是在这里关闭,不然就会出错
      while(read(p[0],&j,sizeof(j))){
        if(j%i){
          write(p1[1],&j,sizeof(j));
        }
      }
      close(p[0]);
      close(p1[1]);
      wait(0);
    }
    else{
      process(p1);
    }
  }
  exit(0);
}

int
main(){
  int p[2];
  pipe(p);
  if(fork()){
    close(p[0]);
    for(int i=2;i<36;i++)
    	write(p[1],&i,sizeof(i));
    close(p[1]);
    wait(0);
  }
  else{
    process(p);
  }
  exit(0);
}

find

这题照着ls.c来写就好了,不过有一个坑就是lsfmtname()返回的是一个static char buf[DIRSIZ+1],这样会使得其返回值输出虽然是'b',但是strcmp(buf,'b')函数并不等于0,因为buf实际长度很大,所以fmtname()需要进行一定的修改。其他的和ls区别不大,注意一下访问子目录时不要递归访问...即可。

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

char*
fmtname(char *path)
{
  char *p;

  for(p=path+strlen(path); p >= path && *p != '/'; p--)
    ;
  p++;

  return p;
}

void
find(char *path,char *filename)
{
  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;
  }
  if(st.type == 1){
    if(strlen(path) + 1 + DIRSIZ + 1 > sizeof buf){
      printf("find: path too long\n");
    }
    else{
	    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;
	      }
	      char* cname = fmtname(buf);
	      if(st.type == 1 && strcmp(".",cname) != 0 && strcmp("..",cname) != 0){
	      	find(buf,filename);
	      }
	      else{
	      	if(strcmp(cname, filename)==0) printf("%s\n",buf);
	      }
	    }
    }
  }
  else{
    printf("find: path is nor a dir!\n");
  }
  close(fd);
}

int
main(int argc, char *argv[])
{
  if(argc < 3){
    printf("You should input menu and filename.\n");
    exit(0);
  }
  find(argv[1],argv[2]);
  exit(0);
}

xargs

这题的命令行比较怪,我一开始都没太看懂,多看了几个例子后才明白,|前的指令输出是xargs指令的额外参数。接下来就简单了,主要就是怎么读取标准输入,根据题目建议,一个一个的读取,读到'\n'后开一个子进程执行,再读取下一行,直到标准输入结束。

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

int
main(int argc, char *argv[]){
  if(argc < 2){
    printf("xargs: You should give some arguments.");
    exit(0);
  }
  int i;
  char *xargv[1024];
  for(i=0;i<argc-1;i++){
    xargv[i]=argv[i+1];
  }
  char buf[512];
  int res=1;
  char* p=buf;
  while(res){
    int word_st=0,word_ed=0;
    while(1){
      res=read(0,p,1);
      word_ed++;
      if(*p==' '||*p=='\n'||!res){
        *p=0;
        xargv[i]=&buf[word_st];        
        //printf("%d %d %s\n",word_st,word_ed,xargv[i]);
        i++;
        word_st=word_ed;
      }
      if(*p=='\n'||!res) break;
      //word_ed++;
      p++;
    }
    if(fork()==0){
      exec(xargv[0],xargv);
      printf("xargs: exec error.");
    }
    wait(0);
  }
  exit(0);
}