6.s081 学习实验记录(二)xv6 and unix utilities

文章目录

    • 一、boot xv6
    • 二、sleep
    • 三、pingpong
    • 四、primes
        • 串行流水线
        • 并行流水线
    • 五、find
    • 六、xargs

该实验主要用来熟悉xv6以及其系统调用
tips:

  • 如果git commit 提交的时候,编辑器不是vim,编辑 xxx/xv6-labs-2022/.git/config文件,添加如下内容:
    6.s081 学习实验记录(二)xv6 and unix utilities_第1张图片

一、boot xv6

实验目的:

  • 启动xv6系统,并使用提供的命令
  • ls,列出系统所有的文件
  • ctrl + p,打印当前运行的进程
  • ctrl + a,再按 x,退出qemu

实验结果:
6.s081 学习实验记录(二)xv6 and unix utilities_第2张图片

二、sleep

实验目的:

  • 通过xv6提供的sleep系统调用实现用户层的sleep函数,代码编写在user/sleep.c文件中
  • 需要将sleep添加到Makefile的UPROGS中,这样执行make qemu会将sleep编译
  • 函数调用路径:sleep.c —> 调用sleep()系统调用(函数定义在 user/user.h) ----> user/usys.S 中的 sleep() 系统调用实现
  • ./grade-lab-util sleep 进行验证(在虚拟机中执行,不是在xv6中执行)
  • 代码要求:缺少参数时需要报错

实验代码:

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


int
main(int argc, char *argv[]){
    int num;
    if(argc != 2){
        fprintf(2, "sleep must have 1 parameters!\n");
        exit(1);
    }
    num = atoi(argv[1]);
    printf("(nothing happens for a little while)\n");
    sleep(num);
    exit(0);
}

需要在Makefile中添加sleep的编译项:
6.s081 学习实验记录(二)xv6 and unix utilities_第3张图片

实验结果:
6.s081 学习实验记录(二)xv6 and unix utilities_第4张图片

三、pingpong

实验要求:

  • 编写一个pingpong程序,父进程发送一个字节给子进程,子进程收到后,打印 < PID>:received ping,然后写一个字节给父进程并退出,父进程收到后打印 < pid>:received pong,然后退出
  • 使用 pipe 创建一对管道
  • 使用 fork创建子进程
  • 使用read、write读写通道
  • 使用 getpid 获取进程ID

实验代码:

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


int
main(int argc, char *argv[]){
    char msg = 'm';
    int length = sizeof(msg);
    int p1[2]; //父-->子
    int p2[2]; //子-->父
    pipe(p1);
    pipe(p2);

    if(fork() == 0){
        close(p1[1]); //关闭父到子的管道写口
        close(p2[0]); //关闭子到父的管带读口
        //子进程
        if(read(p1[0], &msg, length) != length){
            printf("child read parent msg error\n");
            exit(1);
        }
        printf("%d: received ping\n", getpid());
        if(write(p2[1], &msg, length) != length){
            printf("child send msg to parent error\n");
            exit(1);
        }
        exit(0);
    }

    close(p1[0]);
    close(p2[1]);
    if(write(p1[1], &msg, length) != length){
        printf("parent send msg to child error\n");
        exit(1);
    }
    if(read(p2[0], &msg, length) != length){
        printf("parent read child msg error\n");
        exit(1);
    }
    printf("%d: received pong\n", getpid());

    //wait(0); //不关心子进程的退出状态
    exit(0);
}

实验结果:
6.s081 学习实验记录(二)xv6 and unix utilities_第5张图片
在这里插入图片描述

四、primes

实验要求:

  • 使用管道来计算2-35之间的所有素数
  • 父进程输入 2 -35
  • 对于每个素数,都会产生一个子进程,从管道接收数据,打印当前素数,并将剩下的素数写入另一个管道
  • 需要注意,fork的时候已打开的pipe的fd也会复制一份到子进程,因此需要注意关闭不需要的fd,否则将资源不够
  • 本质实现上,还是一个串行的流程,即进程1处理完将剩余数据输出给进程2,进程2接着过滤
  • 方案二为并行pipeline模式(如果有多个核的话)
    • 因为子进程会拷贝父进程打开的所有文件,而xv6每个进程最大支持打开16个文件,而父进程会等待子进程退出,导致子进程创建pipe失败,可以在子进程真正执行逻辑前,关闭某个fd之前的所有文件

实验代码:

串行流水线
#include "kernel/types.h"
#include "kernel/stat.h"
#include "user/user.h"

//素数判断使用素数表法,即当前的素数除以比他小的素数均不能整除
//如果一个数不能整除比它小的任何素数,那么这个数就是素数
//思想,素数 2 - 35 依次通过 pipe 流经各个线程,每个线程判断出第一个素数之后,从该数开始往下流
//每个线程都除以当前的cur_prime,并更新cur_prime,这样子每个线程只需要做一次除法即可
//因为传递到当前线程的所有输入都已经除过 cur_prime 之前的所有素数了(即类似于层层过滤)


int input[34]; //存储2到35
int cur_prime = 1; //当前要被除的素数
/**
可以简单实现为了一个基于pipe的并行处理框架
stream类型的为操作pipeline,每个元素都需要经过流水线操作
pipe则为元素pipeline,每个元素一个线程去单独处理,pipeline用于切分元素
因此需要一个切分元素的规则
*/
//检查元素是否符合要求
int check_elem(int elem);
//更新检查条件
void update_check(int elem);
//处理检查合格的元素
void process_elem(int elem);
void exec_pipeline(int pipe_rfd);

void init_pipeline(int len){
    int i = 0;
    //初始化输入
    for(i = 0; i < len; i++){
        input[i] = i + 2;
    }
    cur_prime = 1;
    //printf("[debug]init input done\n");
}

void start_pipeline(int* input, int len){
    int i = 0;
    int temp;
    int fd[2];
    int end = -1;
    pipe(fd);
    for(; i < len; i++){
        //向pipeline输入元素
        temp = input[i];
        write(fd[1], (char*)(&temp), 4);
    }
    write(fd[1], (char*)&end, 4);
    //启动子进程
    if(fork() == 0){
        exec_pipeline(fd[0]);
    } else {
        //父进程关闭读口
        close(fd[0]);
        close(fd[1]);
    }
	//等待子进程结束
	wait(0);
    exit(0);
}

//参数为上一个进程创建的pipe的读口
//先读后写
void exec_pipeline(int pipe_rfd){
    
    int temp;
    char buf[4];
    int end = -1; //结束符
    int fd[2];
    int elems = 0;

    pipe(fd);
    while(1){
        if(read(pipe_rfd, buf, 4) == 0){
            continue;
        }
		// 读取元素
        temp = *((int *)buf);
        //printf("[debug] child %d read elem:%d\n", getpid(), temp);
		// 如果为结束符,则跳出循环
        if(temp == -1){
            //printf("[debug] child %d read end\n", getpid());
            break;
        }
		// 检查元素是否合法
        if(!check_elem(temp)){
            //printf("[debug] child %d ignore %d\n", getpid(),temp);
            continue;
        }
		//第一个符合要求的元素,输出
        if(elems == 0){
            //确保每次分割,可以选择一次更新切分条件
            update_check(temp);
			process_elem(temp);
            elems = 1;
			continue;
        }
		//第二个符合要求的元素,输送给下一个进程
        if(elems > 0){
            //printf("[debug] child %d write elem:%d\n", getpid(), temp);
            write(fd[1], (char*)&temp, 4);
			elems++;
        }
    }
    close(pipe_rfd);
    if(elems > 1){
		// 如果有向子进程输送元素,则写结束符
         write(fd[1], (char*)&end, 4);
    }
    //printf("[debug] child %d exit\n", getpid());
    if(elems > 1 && fork() == 0){
        exec_pipeline(fd[0]);
    }
    close(fd[1]);
    close(fd[0]);
	wait(0);
    exit(0);
}

int main(){
    init_pipeline(34);
    start_pipeline(input, 34);
    exit(0);
}

int check_elem(int elem){
    if(elem == 2){
        return 1;
    }
    
    if(elem % cur_prime == 0){
        //可以整除
        return 0;
    }
    // 不可以整除,只更新一次素数数组,防止在一个进程中就把所有的素数都算出来了,平摊计算成本
    // 即第一个进程只计算 1,2 之后的第一个素数,其他数只能不能被2整除就过关
    return 1;
}

void update_check(int elem){
    cur_prime = elem;
}

void process_elem(int elem){
    printf("prime %d\n", elem);
}
并行流水线

方案二:

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

//素数判断使用素数表法,即当前的素数除以比他小的素数均不能整除
//如果一个数不能整除比它小的任何素数,那么这个数就是素数
//思想,素数 2 - 35 依次通过 pipe 流经各个线程,每个线程判断出第一个素数之后,从该数开始往下流
//每个线程都除以当前的cur_prime,并更新cur_prime,这样子每个线程只需要做一次除法即可
//因为传递到当前线程的所有输入都已经除过 cur_prime 之前的所有素数了(即类似于层层过滤)


int input[34]; //存储2到35
int cur_prime = 1; //当前要被除的素数
/**
可以简单实现为了一个基于pipe的并行处理框架
stream类型的为操作pipeline,每个元素都需要经过流水线操作
pipe则为元素pipeline,每个元素一个线程去单独处理,pipeline用于切分元素
因此需要一个切分元素的规则
*/
//检查元素是否符合要求
int check_elem(int elem);
//更新检查条件
void update_check(int elem);
//处理检查合格的元素
void process_elem(int elem);
void exec_pipeline(int pipe_rfd);

void init_pipeline(int len){
    int i = 0;
    //初始化输入
    for(i = 0; i < len; i++){
        input[i] = i + 2;
    }
    cur_prime = 1;
    //printf("[debug]init input done\n");
}

void start_pipeline(int* input, int len){
    int i = 0;
    int temp;
    int fd[2];
    int end = -1;
    pipe(fd);
    for(; i < len; i++){
        //向pipeline输入元素
        temp = input[i];
        write(fd[1], (char*)(&temp), 4);
    }
    write(fd[1], (char*)&end, 4);
    //启动子进程
    if(fork() == 0){
        exec_pipeline(fd[0]);
    } else {
        //父进程关闭读口
        close(fd[0]);
        close(fd[1]);
    }
	//等待子进程结束
	wait(0);
    exit(0);
}

//参数为上一个进程创建的pipe的读口
//先读后写
void exec_pipeline(int pipe_rfd){
    int i = 3;
    int temp;
    char buf[4];
    int end = -1; //结束符
    int fd[2];
    int elems = 0; //记录当前进程通过检查的elem个数
    //由于xv6打开文件限制,在此处关闭之前从父进程继承来的无用的fd
    for(; i < pipe_rfd; i++){
        close(i);
    }
    pipe(fd);
    while(1){
        if(read(pipe_rfd, buf, 4) == 0){
            continue;
        }
        //printf("fds:%d,%d,%d\n", pipe_rfd, fd[0], fd[1]);
		// 读取元素
        temp = *((int *)buf);
        //printf("[debug] child %d read elem:%d\n", getpid(), temp);
		// 如果为结束符,则跳出循环
        if(temp == -1){
            //printf("[debug] child %d read end\n", getpid());
            break;
        }
		// 检查元素是否合法
        if(!check_elem(temp)){
            //printf("[debug] child %d ignore %d\n", getpid(),temp);
            continue;
        }
		//第一个符合要求的元素,输出
        if(elems == 0){
            //确保每次分割,可以选择一次更新切分条件
            update_check(temp);
			process_elem(temp);
            elems = 1;
			continue;
        }
		//第二个符合要求的元素,输送给下一个进程
        if(elems > 0){
            //printf("[debug] child %d write elem:%d\n", getpid(), temp);
            write(fd[1], (char*)&temp, 4);
            // 创建子进程,进行下一个阶段的处理
            if(elems == 1){
            	// 有元素输送给下一个阶段,创建新的子进程
                if(fork() == 0){
                    exec_pipeline(fd[0]);
                }
            }
            elems++;
        }
    }
    close(pipe_rfd);
    if(elems > 1){
		// 如果有向子进程输送元素,则写结束符
         write(fd[1], (char*)&end, 4);
    }
    //printf("[debug] child %d exit\n", getpid());
    close(fd[1]);
    close(fd[0]);
	wait(0);
    exit(0);
}

int main(){
    init_pipeline(34);
    start_pipeline(input, 34);
    exit(0);
}

int check_elem(int elem){
    if(elem == 2){
        return 1;
    }
    
    if(elem % cur_prime == 0){
        //可以整除
        return 0;
    }
    // 不可以整除,只更新一次素数数组,防止在一个进程中就把所有的素数都算出来了,平摊计算成本
    // 即第一个进程只计算 1,2 之后的第一个素数,其他数只能不能被2整除就过关
    return 1;
}

void update_check(int elem){
    cur_prime = elem;
}

void process_elem(int elem){
    printf("prime %d\n", elem);
}

参考:

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

void find_prime(int *input, int num){
	if(num == 1){
		printf("prime %d\n", *input);
		return;
	}
	int p[2],i;
	int prime = *input;
	int temp;
	printf("prime %d\n", prime);
	pipe(p);
    for(i = 0; i < num; i++){
        temp = *(input + i);
        write(p[1], (char *)(&temp), 4);
    }

	close(p[1]);
	// 父进程没有关闭pipe[0]
	if(fork() == 0){
		int counter = 0;
		char buffer[4];
		while(read(p[0], buffer, 4) != 0){
			temp = *((int *)buffer);
			if(temp % prime != 0){
				*input = temp;
				input += 1;
				counter++;
			}
		}
		find_prime(input - counter, counter);
		exit(0);
    }
	wait(0);
}

int main(){
    int input[34];
	int i = 0;
	for(; i < 34; i++){
		input[i] = i+2;
	}
	find_prime(input, 34);
    exit(0);
}

实验结果:
在这里插入图片描述

五、find

实验要求:

  • 编写 find 的简易版,可以查找具有特定名称的所有文件
  • 使用递归,查找子目录
  • 不要递归查找 . 和 ..目录

实验代码:

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

void find(char* path, char* name);

int
main(int argc, char *argv[])
{
  if(argc < 2){
    printf("args must have path and name\n");
    exit(0);
  }
  char* path = argv[1];
  char* filename = argv[2];
  find(path, filename);
  exit(0);
}

void find(char* path, char* name){
  int fd;
  struct dirent de;
  struct stat st;
  char buf[512], *p; //buf存储路径,p指向路径的末尾

  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_DEVICE:
    case T_FILE:
      fprintf(2, "find: %s is not a path\n", path);
      close(fd);
      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++ = '/';   // path ---> path/
      
      //读取每个目录项
      while(read(fd, &de, sizeof(de)) == sizeof(de)){
        if(de.inum == 0)
          continue;
        //需要去除 .和 ..
        if(strcmp(de.name, ".") == 0 || strcmp(de.name, "..") == 0){
            //printf("find: file %s\n", de.name);
            continue;
        }
        // 拼接路径  path/filename
        memmove(p, de.name, DIRSIZ);
        p[DIRSIZ] = '\0'; //添加结束符
        //读取文件的具体信息
        if(stat(buf, &st) < 0){
          printf("find: cannot stat %s\n", buf);
          continue;
        }

        //printf("xxx:%s, %d\n", buf, st.type);

        if(st.type == T_FILE || st.type == T_DEVICE){
          //如果文件名相同
          if(strcmp(name, de.name) == 0){
            printf("%s\n", buf);
          }
        } else {
          //是目录,递归查询
          find(buf, name);
        }
      }
      close(fd);
      break;
  } 
}



实验结果:
在这里插入图片描述

六、xargs

实验要求:

  • 将前一个命令的输出附加到后一个命令上
  • echo hello too | xargs echo bye,则echo hello too的输出 hello too,作为附加参数给 echo bye指令

实验代码:

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

int main(int argc, char *argv[]){
    int i;
    int cmd_len = 0;
    int buf_len;
    char buf[32];
    char *cmd[MAXARG];
    //读取 xargs 后面的参数,即最终要执行的命令
    for(i = 1; i < argc; i++){
        cmd[cmd_len++] = argv[i];      
    }
    //read读取管道的输出,即面前的命令的执行结果,并进行处理
    while((buf_len = read(0, buf, sizeof(buf))) > 0){
        
        char xargs[32] = {"\0"}; //追加参数
        cmd[cmd_len] = xargs;

        for(i = 0; i < buf_len; i++){
            if(buf[i] == '\n'){
                if(fork() == 0){
                    exec(argv[1], cmd);
                }                
                wait(0);
            } else {
                xargs[i] = buf[i];
            }
        }
    }
    exit(0);
}

实验结果:
在这里插入图片描述

你可能感兴趣的:(个人,unix,学习,linux)