MIT6.S081学习历程 --lab1

lab1

一. sleep实验

要求:

Implement the UNIX program sleep for xv6; your sleep should pause for a user-specified number of ticks. A tick is a notion of time defined by the xv6 kernel, namely the time between two interrupts from the timer chip. Your solution should be in the file user/sleep.c.

其实就是让你在user/中运用sleep系统调用来实现一个程序, 写入sleep.c

提示:

最重要的就是使用user/ulib.c 的atoi函数来获得命令行参数

这里还是建议看一遍提示中要求看的一些.c文件, 就算你知道如何获得

AC代码

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


int main(int argc, char *argv[]) {
    if (argc == 1) {
        char *buf = "error!";
        write(1, buf, sizeof(buf));

        exit(1);
    }
    
    int t = atoi(argv[1]); //获得命令行输入,所以当前输入应该是“sleep x”
                           // x是一个数字,所以要获得第二个字符串
    sleep(t);
    exit(0);
}

不要忘了添加入Makefile 仿照之前的格式在152行添加即可

二.pingpong 实验

要求:

Write a program that uses UNIX system calls to ''ping-pong'' a byte between two processes over a pair of pipes, one for each direction. The parent should send a byte to the child; the child should print ": received ping", where is its process ID, write the byte on the pipe to the parent, and exit; the parent should read the byte from the child, print ": received pong", and exit. Your solution should be in the file user/pingpong.c.

使用 UNIX 系统调用通过一对管道在两个进程之间“pingpong”一个字节,一个管道用于每个方向。父进程应该向子进程发送一个字节;子进程应该打印“: received ping”,其中是它的进程 ID,将管道上的字节写入父进程,然后退出;父进程应该从子进程那里读取字节,打印“: received pong”,然后退出, 程序写入user/pingpong.c中。

提示:

主要就是要注意创建管道pipe, 创建子进程fork
readwrite系统调用
另外还有一个系统调用getpid, 可以获得当前进程的pid

知识点

  1. int pipe(int p[]) :pipe 函数用于创建一个管道,并将读取和写入的文件描述符分别存储在数组 pp[0]p[1] 中。
  2. close 函数用于关闭先前通过文件描述符 fd 打开的文件,并释放该文件描述符。
    函数原型为 int close(int fd),它接受一个整数参数 fd,表示要关闭的文件描述符。

AC代码

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

#define WRITE 1
#define READ 0


int main(int argc, char *argv[]) {
    if (argc != 1) {
        printf("dont input arguments\n");
    }

    int pipe_fa_to_ch[2];
    int pipe_ch_to_fa[2];
    char buf[8];

    pipe(pipe_fa_to_ch);
    pipe(pipe_ch_to_fa); // 创建两个管道,这里要知道pipe系统调用的用法和作用

    int pid = fork();

    if (pid < 0) {
        printf("error!\n");
        exit(1);
    } else if (pid == 0) {
        close(pipe_fa_to_ch[WRITE]); // 关闭父进程的写
        read(pipe_fa_to_ch[READ], buf, sizeof(buf)); // 从父进程读入到buf中
        close(pipe_fa_to_ch[READ]); // 关闭父进程的读

        close(pipe_ch_to_fa[READ]);
        write(pipe_ch_to_fa[WRITE], "pong\n", 5); // 将字节写到子进程中
        close(pipe_ch_to_fa[WRITE]); // 关闭子进程的写

        printf("%d: received %s", getpid(), buf);

        exit(0);
    } else {
        close(pipe_fa_to_ch[READ]); // 关闭父进程的读
        write(pipe_fa_to_ch[WRITE], "ping\n", 5); // 写入父进程
        close(pipe_fa_to_ch[WRITE]); // 关闭父进程的写

        wait((int *)0); //等待子进程结束

        close(pipe_ch_to_fa[WRITE]); // 关闭子进程的写
        read(pipe_ch_to_fa[READ], buf, sizeof(buf)); // 读入子进程的数据
        close(pipe_ch_to_fa[READ]); // 关闭子进程的读

        printf("%d: received %s", getpid(), buf); // 输出从子进程获得的数据

        exit(0);
    }

    exit(0);
}

三. primes 实验

要求

Write a concurrent version of prime sieve using pipes. This idea is due to Doug McIlroy, inventor of Unix pipes. The picture halfway down this page and the surrounding text explain how to do it. Your solution should be in the file user/primes.c.

Your goal is to use pipe and fork to set up the pipeline. The first process feeds the numbers 2 through 35 into the pipeline. For each prime number, you will arrange to create one process that reads from its left neighbor over a pipe and writes to its right neighbor over another pipe. Since xv6 has limited number of file descriptors and processes, the first process can stop at 35.

就是让你利用父子进程和管道的知识来筛选输出出1-36之间的质数,父进程向管道内写入1-36这些数字,子进程读取这些数字,然后进行判断

提示

除了一些老生常谈的提示之外,实验提示中还说了几点重要提示

  1. 注意不用的文件描述符一定要关闭,不然XV6太小了,会爆空间
  2. 父进程要等到子进程结束,所以可以使用wait
  3. 要让子进程不断地读取管道中的数据,直到管道为空,所以可以一直read, 如果返回0则代表管道为空

知识点

  1. int 是四个字节,所以可以读取/写入四个字节,当然也可以省事,第三个参数直接写sizeof(xx)
  2. 由实验三我们可以知道,这里是单方向通信,所以只需要创建一个管道,实验三是双向通信,所以要
    创建两个管道

AC代码

#include "kernel/types.h"
#include "kernel/stat.h"
#include "user/user.h"
#include <stdbool.h>

#define READ 0
#define WRITE 1

bool check(int x) { // 判断x是不是质数,只需要根号判断即可
	for (int i = 2; i <= x / i; i++) {
		if (x % i == 0) {
			return false;
		}
	}
	return true;
}

int main(int argc, char *argv[]) {
	int p[2];
	pipe(p); // 每一次都要创建管道创建管道
 
	int pid = fork(); // 创建子进程

	if (pid > 0) {	//如果当前是子进程, 应该将数字写入管道
		close(p[READ]);

		for (int i = 1; i <= 36; i++) { // 将1-36的数字写入管道
			int number = i;
			write(p[WRITE], &number, 4);
		}

		close(p[WRITE]); // 别忘了继续关闭文件描述符
		wait((int *)0); // 等待子进程结束

		exit(0);
	} else {
		close(p[WRITE]); // 关闭写端

		int nowNumber = 0;
		while (read(p[READ], &nowNumber, 4)) { // 一直从管道读, 然后判断是不是质数
			if (check(nowNumber)) {
				printf("prime %d\n", nowNumber);
			}
		}

		close(p[READ]); // 关闭读端 
		exit(0);
	}

	exit(0);
}

四. find实验

要求

Write a simple version of the UNIX find program: find all the files in a directory tree with a specific name. Your solution should be in the file user/find.c.

也就是寻找一个文件夹下所有特定名字的文件, 写在user/find.c

提示

提示中提到了user/ls.c, 可以进去学习关于如何读取文件夹
事实上实验代码也是根据ls.c的代码照猫画虎, 所以一定要认真看
要使用递归来完成对子文件夹的搜索, 这是因为我们目标文件夹中可能有子文件夹, 然后该特定名字的文件可能会同时出现在子文件夹和当前文件夹中

知识点

  1. strcmp() 来比较字符串
  2. 可以查看kernel/stat.h 中的stat结构体, 这个结构体用来存储文件的信息, 例如type 就是文件类型信息
  3. fstat(int fd, stat st) 可以将fd这个文件描述符指向的文件信息复制到st这个stat结构体中
  4. stat(char *path, stat st) 可以将path路径表示的某个文件信息复制到st中
  5. ls.c中的char *fmtname(char *path) 函数, 可以将./a.txt 这样的路径中的a.txt信息截取, 输出出来, 所以AC代码中会直接cv过来使用,
    不过要修改一个地方, 详情请看注释
  6. C语言中的mem系列函数, 要知道用法

题外话: IO调试还是好用啊(acmer习惯)

AC代码

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

char* fmtname(char *path) // 照搬ls.c的fmtname函数,意义为获得地址的最后一个/之后的内容
{
  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));
  buf[strlen(p)] = 0; // 这里要修改一下, 0代表终止
  return buf;
}


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){        // 将fd描述的文件信息复制到st中
        fprintf(2, "find: cannot stat %s\n", path);
        close(fd);
        return;
      }

      switch(st.type) { // 分类讨论st的type
      case T_DEVICE:
      case T_FILE: // 表明这是一个文件
      // fprintf(2, "fmtpath=%s, fileName=%s\n", fmtname(path), fileName);
      // fprintf(2, "%d\n", strcmp(fmtname(path), fileName));

          if (strcmp(fmtname(path), fileName) == 0) { // 看看是否与目标文件名相同
              // fprintf(2, "!\n");
        fprintf(2, "%s\n", path);
          }
          break;
      case T_DIR: // 表明这是一个文件夹
          if (strlen(path) + 1 + DIRSIZ + 1 > sizeof(buf)) { // 如果过长的话可以提示一下
              fprintf(2, "find: path too long\n");
              break;
          }

          strcpy(buf, path); // 将路径拷贝到buf中
          p = buf + strlen(buf);
          *p++ = '/'; // 这里的意思其实是在buf之后继续接地址

          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;

              if (stat(buf, &st) < 0) {
                  printf("find: cannot stat %s\n", buf);
                  continue;
              }

              find(buf, fileName);
          }
          break;
      }

      close(fd);
}


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

    exit(0);
}

五. xargs 实验

要求:

Write a simple version of the UNIX xargs program: read lines from the standard input and run a command for each line, supplying the line as arguments to the command. Your solution should be in the file user/xargs.c.

要实现UNIX的xargs 指令, 其实是简化版指令, 关于什么是xargs 可以参考下面的博客
http://www.ruanyifeng.com/blog/2019/08/xargs-tutorial.html

提示:

可以参考kernel/param.h学习如何声明argv数组(看一下argv数组的合法大小)

知识点:

  1. 我们知道xargs 其实是不断地接受命令行参数, 然后再调用当前的命令来执行, 所以可以fork()一个子进程来运行命令,
    \n 或者 作为分隔符
  2. 运行命令可以使用exec() 指令
  3. read() 可以返回读取到的字节长度
  4. 动态申请内存char *arg = (char *)malloc(sizeof(char *))

AC代码

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


int main(int argc, char *argv[]) {
    char *param[MAXARG]; // 可以查看param.h, exec最大长度
    char line[1024];
    int idx = 0;
    for (int i = 1; i < argc; i++) {
        param[idx++] = argv[i];
    }

    int n = 0;
    char *cmd = argv[1];
    while ((n = read(0, line, sizeof(line))) > 0) {
        // fprintf(1, "!!! %s\n", line);
        int pid = fork();
        if (pid == 0) {
            // 子进程
            char *arg = (char *)malloc(sizeof(line));
            int cur = 0;

            for (int i = 0; i < n; i++) {
                if (line[i] == ' ' || line[i] == '\n') {
                    arg[cur] = 0;
                    cur = 0;
                    param[idx++] = arg;
                    arg = (char *)malloc(sizeof(line));
                } else {
                    arg[cur++] = line[i];
                }
            }

            param[idx] = 0;
            exec(cmd, param);
            exit(0);

        } else {
            wait((int *)0);
            exit(0);
            
        }
    }


    exit(0);
}

posted @ 2023-11-07 12:02  Xingon2356  阅读(10)  评论(0编辑  收藏  举报