MIT6.S081 Lab1:Xv6 and Unix utilities

目录

前言:

1.1:启动xv6(Easy)

1.2:sleep(Easy)

1.2.1:sleep.c 

1.2.2:makefile

1.3:pingpong(Easy)

1.4:primes(Moderate/Hard)

1.5:find(Moderate)

1.6:xargs(Moderate)

前言:

        这节实验是MIT6.S08的2021版本的首个实验,主要是构建后续实验运行的环境,以及实现几个简单的小实验,大部分都挺简单的,唯一一个有难度的应该是1.4,主要是理解它的流程有点费劲。中文版的MIT6.S081实验指导书在这:http://xv6.dgs.zone/labs/requirements/lab1.html

1.1:启动xv6(Easy)

        这节按照实验指导书上的步骤来就行了,很简单。

        在xv6-labs-2021(这个目录根据你的实验版本来)的目录下运行make qemu,就可以构建并运行xv6了:

        在xv6环境下想要退出,可以再打开一个控制台,输入pkill -9 qemu即可。

1.2:sleep(Easy)

1.2.1:sleep.c 

        按照实验指导书上的提示,在user文件夹中新建一个sleep.c的文件,完成调用sleep的代码。按照以下步骤进行代码书写工作:

        1.参考其他/user/*.c文件,添加对应的头文件到sleep.c中。

        2.完成main()函数,main函数具有两个参数,argc代表argv[]数组的大小,也就是传入参数的个数;argv[]数组按顺序存储了传入main函数的参数。其中,argv[0]指向程序运行的全路径名,argv[1]表示实际的第一个参数,因此argc<=1表示没有任何参数传入main函数,可以直接打印错误。

        3.通过观察/user/user.h文件中sleep的定义,我们可以发现,调用sleep只需要传入一个参数即可,这个参数表示需要睡眠的事件:

// /user/user.h
int sleep(int);

在main函数中调用sleep系统调用,睡眠结束后调用exit(0)退出main函数完成代码。

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

int main(int argc, char *argv[])
{
    if (argc <= 1) {
        printf("error");
        exit(1);
    }
    // argv[1]是传入main的第一个参数,表示sleep的睡眠时间
    sleep(atoi(argv[1]));
    exit(0);
}

1.2.2:makefile

        关于makefile可以看这篇 :Makefile入门(超详细一文读懂)_我的小卷呀的博客-CSDN博客,sleep这个实验的makefile书写可以直接参考原本的代码,在UPROGS=\后面增加一条sleep的语句,其中UPROGS表示的就是/user文件夹。

UPROGS=\
	$U/_cat\
    ......
	$U/_zombie\
	$U/_sleep\

1.3:pingpong(Easy)

        这一小节实验和网络通信的内容差不多,就是创建一个管道,然后fork出一个子进程,利用管道实现父子进程之前的通信。

        和1.2一样,在/user目录下创建一个pingpong.c文件完成相应的代码。

        1.首先创建两个管道p1和p2,由于管道是半双工的,数据只能单向流动,因此需要两个管道分别负责数据从父到子和从子到父。

        2.fork()出一个子进程,并根据pid的值分别进入到父或子的处理流程中。

        3.管道具有以下特性:

如果有指向管道写端的文件描述符没关闭(管道写端的引用计数大于0),而持有管道写端的进程也没有向管道中写数据,这时有进程从管道读端读数据,那么管道中剩余的数据都被读取后,再次 read 会阻塞,直到管道中有数据可读了才读取数据并返回。

因此,当父进程利用管道p1向子进程写数据时,父进程要关闭管道的读端,子进程要关闭管道的写端,当子进程利用管道p2向父进程写数据时同理。

        4.按照实验指导书中传递数据的步骤,父进程应该先向子进程传输一个字节,子进程接收后打印信息并向父进程传输一个字节,父进程接收后打印信息,至此完成所有工作。

        5.如1.2一样,在makefile中添加pingpong

        代码如下:

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

int main()
{
    // 创建两个管道,p1负责将数据从父传到子,p2相反
    int p1[2], p2[2];
    pipe(p1);
    pipe(p2);
    // 创建一个缓冲区用来存储数据
    char buf[1];
    // 创建一个子进程
    int pid = fork();
    // 当pid == 0代表这是子进程,进入子进程的处理流程
    if (pid == 0) {
        // 关闭管道p1的写段和p2的读端
        close(p1[1]);
        close(p2[0]);

        read(p1[0], buf, sizeof buf);
        printf("%d: received ping\n", getpid());
        write(p2[1], buf, sizeof buf);
        exit(0);
    }
    else {
        // 关闭p1的读端和p2的写端
        close(p1[0]);
        close(p2[1]);

        write(p1[1], buf, sizeof buf);
        read(p2[0], buf, sizeof buf);
        printf("%d: received pong\n", getpid());
        exit(0);
    }
}

1.4:primes(Moderate/Hard)

1.5:find(Moderate)

        根据实验指导书上的提示,我们首先可以去查看/user/ls.c是如何读取目录的:

void
ls(char *path)
{
  char buf[512], *p;
  // 创建一个文件描述符用来描述文件
  int fd;
  // 创建一个目录项,存储文件的inode号和文件名
  struct dirent de;
  // 存储文件的基本信息比如文件的磁盘号、inode号、文件类型等
  struct stat st;
  
  // open()函数打开path路径指定的文件,返回一个文件描述符 
  if((fd = open(path, 0)) < 0){
    fprintf(2, "ls: cannot open %s\n", path);
    return;
  }
  
  // 将文件基本信息传入st中
  if(fstat(fd, &st) < 0){
    fprintf(2, "ls: cannot stat %s\n", path);
    close(fd);
    return;
  }
  
  // 根据文件的类型是FILE还是DIR进行不同的处理
  switch(st.type){
  case T_FILE:
    ......

  case T_DIR:
    ......
  }
  close(fd);
}

        仿造ls.c,我们可以把find的大致框架给写出来:

void find(char *curr_path, char *target) {
  char buf[512], *p;
  int fd;
  struct dirent de;
  struct stat st;
  
  if ((fd = open(curr_path, 0)) < 0) {
    fprintf(2, "find: cannot open %s\n", curr_path);
    return;
  }

  if (fstat(fd, &st) < 0) {
    fprintf(2, "find: cannot stat %s\n", curr_path);
    close(fd);
    return;
  }
  // 用于存储文件的名称
  char *f_name;
  
  switch (st.type) {

  case T_FILE:
    ......
  case T_DIR:
    ......
    close(fd);
    break;
  }
}

        接下来要完成的就是对于文件路径的处理,也就是switch语句块内部的逻辑,首先根据打开的文件是FILE还是DIR进入不同的分支进行处理。

        对于FILE,由于curr_path给出的是一个文件的路径,如/home/user/ls.c,我们要做的是对这个路径进行解析,将路径中的目录名舍去,只留下ls.c,并判断是否和target目标文件一致,如果一致,则直接输出完整路径curr_path:

char *basename(char *pathname) {
  char *prev = 0;
  char *curr = strchr(pathname, '/');
  while (curr != 0) {
    prev = curr;
    curr = strchr(curr + 1, '/');
  }
  return prev;
}

case T_FILE:
    // 接收返回回来的最后一个目录,如curr_path是/home/user/ls.c,则返回user/ls.c
    f_name = basename(curr_path);
    int match = 1;
    // 如果文件名为空,或者文件名和target不一致,则说明没找到目标文件
    if (f_name == 0 || strcmp(f_name + 1, target) != 0) {
      match = 0;
    }
    if (match)
      // 输出完整路径
      printf("%s\n", curr_path);
    close(fd);
    break;

  case T_DIR:

        如果是DIR文件的话,则循环读取当前目录下的文件,对于非.和..的路径,递归的调用find进入路径名下寻找目标文件:

case T_DIR:
    // 初始化buf
    memset(buf, 0, sizeof(buf));
    uint curr_path_len = strlen(curr_path);
    // 将完整的路径存入buf中
    memcpy(buf, curr_path, curr_path_len);
    buf[curr_path_len] = '/';
    p = buf + curr_path_len + 1;
    // 循环读取当前文件,如果读取到了非.和..的路径,则进入该路径再次find
    while (read(fd, &de, sizeof(de)) == sizeof(de)) {
      if (de.inum == 0 || strcmp(de.name, ".") == 0 ||
          strcmp(de.name, "..") == 0)
        continue;
      // DIRSIZ是文件名的最大长度,值为14
      memcpy(p, de.name, DIRSIZ);
      p[DIRSIZ] = 0;
      find(buf, target); 
    }
    close(fd);
    break;
  }

        完整代码如下:

/* retrieve the filename from whole path */
char *basename(char *pathname) {
  char *prev = 0;
  char *curr = strchr(pathname, '/');
  while (curr != 0) {
    prev = curr;
    curr = strchr(curr + 1, '/');
  }
  return prev;
}

/* recursive */
void find(char *curr_path, char *target) {
  char buf[512], *p;
  int fd;
  struct dirent de;
  struct stat st;

  if ((fd = open(curr_path, 0)) < 0) {
    fprintf(2, "find: cannot open %s\n", curr_path);
    return;
  }

  if (fstat(fd, &st) < 0) {
    fprintf(2, "find: cannot stat %s\n", curr_path);
    close(fd);
    return;
  }
  char *f_name;
  switch (st.type) {

  case T_FILE:
    // 接收返回回来的最后一个目录,如curr_path是/home/user/ls.c,则返回user/ls.c
    f_name = basename(curr_path);
    int match = 1;
    // 如果文件名为空,或者文件名和target不一致,则说明没找到目标文件
    if (f_name == 0 || strcmp(f_name + 1, target) != 0) {
      match = 0;
    }
    if (match)
      // 输出完整路径
      printf("%s\n", curr_path);
    close(fd);
    break;

  case T_DIR:
    // 初始化buf
    memset(buf, 0, sizeof(buf));
    uint curr_path_len = strlen(curr_path);
    // 将完整的路径存入buf中
    memcpy(buf, curr_path, curr_path_len);
    buf[curr_path_len] = '/';
    p = buf + curr_path_len + 1;
    // 循环读取当前文件,如果读取到了非.和..的路径,则进入该路径再次find
    while (read(fd, &de, sizeof(de)) == sizeof(de)) {
      if (de.inum == 0 || strcmp(de.name, ".") == 0 ||
          strcmp(de.name, "..") == 0)
        continue;
      // DIRSIZ是文件名的最大长度,值为14
      memcpy(p, de.name, DIRSIZ);
      p[DIRSIZ] = 0;
      find(buf, target); 
    }
    close(fd);
    break;
  }
}

1.6:xargs(Moderate)

        这一小节的实验室要用xargs命令将echo后的字符连接起来,首先按照题目的意思,在kernel/param.h声明MAXARG:

#define MAXARG       32 

        按照实验指导书的要求,我们需要先将echo后的参数已经xargs echo后的参数读取:

  // 声明一个buf,存储标准输入
  char buf[buf_size];
  int line = 0;
  // 声明一个数组,用来存储xargs echo后的字符
  char *xargv[MAXARG] = {0};

  // 依次将命令行中输入的参数放到xargv数组中,argv[1]为实际的第一个参数,因此i从1开始
  for (int i = 1; i < argc; ++i) {
    xargv[i - 1] = argv[i];
  }

  // 读取标准输入,并存储到buf中
  int len = read(0, buf, sizeof buf);

        考虑到参数可能存在\n分行的情况,需要扫描一遍读取到的数据,并分段存储:

for (int i = 0; i < len; ++i) {
      // 由于如果遇到\n需要换行输出,因此要存储行数
      if (buf[i] == '\n') {
          ++line;
      }
  }
  // 储存每一行的字符,也就是两个\n之间的字符
  char output[line][128];

  int l = 0, index = 0; 
  for (int i = 0; i < len; ++i) {
      // 存储该行数据
      output[l][index++] = buf[i];
      // 遇到换行符则进入下一行,令当前行l+1,令当前行的索引index=0
      if (buf[i] == '\n') {
          output[l][index - 1] = 0; 
          ++l;
          index = 0;
      }
  }

        最后,将读取到的数据按段连接到xargv后面即可:

  int out_line = 0, size = sizeof(xargv);
  while (out_line < line) {
    xargv[size] = output[out_line++]; // 将每一行数据都分别拼接在原命令参数后
    if (fork() == 0) { 
        // 子进程执行输出
        exec(argv[1], xargv);
        exit(0);
    }
    wait(0);
  }

        完整代码如下:

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

#define buf_size 512

int main(int argc, char *argv[]) {
  // 声明一个buf,存储标准输入
  char buf[buf_size];
  int line = 0;
  // 声明一个数组,用来存储xargs echo后的字符
  char *xargv[MAXARG] = {0};

  // 依次将命令行中输入的参数放到xargv数组中,argv[1]为实际的第一个参数,因此i从1开始
  for (int i = 1; i < argc; ++i) {
    xargv[i - 1] = argv[i];
  }

  // 读取标准输入,并存储到buf中
  int len = read(0, buf, sizeof buf);

  for (int i = 0; i < len; ++i) {
      // 由于如果遇到\n需要换行输出,因此要存储行数
      if (buf[i] == '\n') {
          ++line;
      }
  }
  // 储存每一行的字符,也就是两个\n之间的字符
  char output[line][128];

  int l = 0, index = 0; 
  for (int i = 0; i < len; ++i) {
      // 存储该行数据
      output[l][index++] = buf[i];
      // 遇到换行符则进入下一行,令当前行l+1,令当前行的索引index=0
      if (buf[i] == '\n') {
          output[l][index - 1] = 0; 
          ++l;
          index = 0;
      }
  }

  // out_line表示当前输出的是第几行,size表示的是xargv的总长
  int out_line = 0, size = sizeof(xargv);
  while (out_line < line) {
    xargv[size] = output[out_line++]; // 将每一行数据都分别拼接在原命令参数后
    if (fork() == 0) { 
        // 子进程执行输出
        exec(argv[1], xargv);
        exit(0);
    }
    wait(0);
  }
  exit(0);
}

你可能感兴趣的:(MIT6.S081(2021),服务器,c++,linux)