Xv6 and Unix utilities

好哦,完成 mit 6.1810 2023 中 lab :Xv6 and Unix utilities了,准备开始下一章的学习了,xv6还是挺好玩的

sleep

根据题目描述,和 user/ 中的其他程序的逻辑可以发现 命令行参数传递是通过 main的argc 和argv去传递的,其中argc 中存储着参数的个数,argv存储着参数名,没有参数的时候argc = 1,argv[0] = program_name。
然后在 “user/user.h” 中有sleep 函数
所以 只需要直接调用sleep函数就好了, 把time上限也做一个设置(防止睡死过去了

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

int main(int argc, char *argv[]){
    
    int time = 0;

    if (argc == 1 || argc > 2){
        printf("Error: Please provide the number of seconds to sleep\n e.g. sleep 5\n");
        exit(1);
    }

    time = atoi(argv[1]);
    if (time > 0 && time < 0x1000){
        printf("Zzzzzzzzz.....\n");
        sleep(time);
    }
    exit(0);
}

然后在Makefile中 添加 sleep这个文件 就好了

UPROGS=\
	$U/_cat\
	$U/_echo\
	$U/_forktest\
	$U/_grep\
	$U/_init\
	$U/_kill\
	$U/_ln\
	$U/_ls\
	$U/_mkdir\
	$U/_rm\
	$U/_sh\
	$U/_stressfs\
	$U/_usertests\
	$U/_grind\
	$U/_wc\
	$U/_zombie\
	$U/_sleep\

pingpong

通过阅读资料可以发现,pingpong 可以分解为两个问题,分别是 如何创建子进程,父子进程之间如何通信
创建子进程可以使用 fork() 函数创建,进程之间通信可以使用pipe 这个函数去创建一个管道,管道的本质就是内存共享,不同进程可以共享同一块内存
管道是单向的,其中 int pN[2] = pipe() 这段代码中,pN[0] 是 读端,pN[1] 是写端,所以 两个进程互相通信 需要两个管道

那 如何判断父子进程呢? 可以通过 fork的返回值来判断,为了保证执行顺序按照 ping - > pong 这个顺序去执行,在 ping 后 父进程应该等待子进程 senddata过来 才输出pong,这时候可以使用wait函数来解决这个问题

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

int main(int argc, char *argv[]){

    // pN[0] read pN[1] write
    int p1[2]; // pipe from parent to child 
    int p2[2]; // pipe from child to parent
    
    pipe(p1);
    pipe(p2);

    
    int pid = fork();
    if (pid > 0){
        char byte = 'A';
        write(p1[1],&byte,1);

        wait(0); // wait child process exit
        read(p2[0],&byte,1);

        if(byte == 'B'){
            printf("%d: received pong\n", getpid());
        }

        close(p1[1]);
        close(p2[0]);
        exit(0);
    }
    else if (pid == 0){
        char buf;
        read(p1[0],&buf,1);

        if(buf == 'A'){
            printf("%d: received ping\n", getpid());
            buf = 'B';
            write(p2[1],&buf,1);
        }
        close(p1[0]);
        close(p2[1]);
        exit(0);
    }
    else{
        printf("Error: fork error!\n");
        close(p1[0]);
        close(p1[1]);
        close(p2[0]);
        close(p2[1]);
        exit(0);
    }
}

primes

这个题挺有意思的,素数筛,不过想到一种更简单的方法,筛的逻辑在子进程里面实现就好了

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

void sieve(int read_fd) {
    int p;
    
    if (read(read_fd, &p, sizeof(int)) == 0) {
        close(read_fd);
        exit(0);
    }
    printf("prime %d\n", p);

    int next_read_fd[2];
    pipe(next_read_fd);

    if (fork() == 0) {
        close(next_read_fd[1]);
        sieve(next_read_fd[0]);
    } else {
        close(next_read_fd[0]);
        int num;
        while (read(read_fd, &num, sizeof(int)) != 0) {
            if (num % p != 0) {
                write(next_read_fd[1], &num, sizeof(int));
            }
        }
        close(next_read_fd[1]);
        close(read_fd);
        wait(0);
        exit(0);
    }
}

int main(int argc, char *argv[]){

    int nums[34];
    for (int i = 0; i < 34; i++) {
        nums[i] = i + 2;
    }

    int initial_fd[2];
    pipe(initial_fd);

    if (fork() == 0) {
        close(initial_fd[1]);
        sieve(initial_fd[0]);
    } else {
        close(initial_fd[0]);
        for (int i = 0; i < 34; i++) {
            write(initial_fd[1], &nums[i], sizeof(int));
        }
        close(initial_fd[1]);
        wait(0);
        exit(0);
    }

    return 0;
}

find

find,想到的搜索策略是bfs?从传过来的path判断 path里每个项的类型,如果是dir就递归下去,如果是file就判断file name是不是要find的目标,如果是. ..就跳过,所以能得到几个新的问题,怎么样获取path里的条目有什么,这些条目的类型是什么

ls.c source code

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

char*
fmtname(char *path)
{
  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
ls(char *path)
{
  char buf[512], *p;
  int fd;
  struct dirent de;
  struct stat st;

  if((fd = open(path, O_RDONLY)) < 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_DEVICE:
  case T_FILE:
    printf("%s %d %d %l\n", fmtname(path), st.type, st.ino, st.size);
    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);
    *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("%s %d %d %d\n", fmtname(buf), st.type, st.ino, st.size);
    }
    break;
  }
  close(fd);
}

int
main(int argc, char *argv[])
{
  int i;

  if(argc < 2){
    ls(".");
    exit(0);
  }
  for(i=1; i<argc; i++)
    ls(argv[i]);
  exit(0);
}

有两个 比较关键的函数和结构体
stat 和 fstat 这两个函数

嗯,通过阅读ls.c可以发现想要获得一个条目是目录 还是文件,可以通过 int fd = open(“current_dir”,0);struct stat st; fstat(fd,&st); 来获得
这是struct stat的定义

#define T_DIR     1   // Directory
#define T_FILE    2   // File
#define T_DEVICE  3   // Device

struct stat {
  int dev;     // File system's disk device
  uint ino;    // Inode number
  short type;  // Type of file
  short nlink; // Number of links to file
  uint64 size; // Size of file in bytes
};

只需要判断 stat.type就能知道条目的类型是 directory 还是file 还是 Device
可以通过读fd的方式来获得条目的信息,用struct dirent 去存储条目的信息

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) {
            fprintf(2, "Error: findcannot stat %s\n", buf);
            continue;
        }
  

struct dirent的定义

// Directory is a file containing a sequence of dirent structures.
#define DIRSIZ 14

struct dirent {
  ushort inum;
  char name[DIRSIZ];
};

通过 dirent.name 可以获得条目的名字

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

void find(char *path, char *filename) {
    char buf[512], *p;
    int fd;
    struct dirent de;
    struct stat st;

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

    // get directory statistics
    if(fstat(fd, &st) < 0) {
        fprintf(2, "Error: find cannot stat %s\n", path);
        close(fd);
        return;
    }

    // ensure it is a directory
    if(st.type != T_DIR) {
        fprintf(2, "Error: find %s is not a directory\n", path);
        close(fd);
        return;
    }

    // read the directory entries
    strcpy(buf, path);
    p = buf + strlen(buf);
    *p++ = '/';
    
    // get each item
    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) {
            fprintf(2, "Error: findcannot stat %s\n", buf);
            continue;
        }
        // Skip "." and ".."
        if(strcmp(de.name, ".") == 0 || strcmp(de.name, "..") == 0)
            continue;
        if(st.type == T_DIR) {
            // find sub directory
            find(buf, filename);
        } else if(st.type == T_FILE) {
            // find the file
            if(strcmp(de.name, filename) == 0) {
                printf("%s\n", buf);
            }
        }
    }
    close(fd);
}

int main(int argc, char *argv[]) {

    if(argc < 3) {
        fprintf(2, "e.g. find <path> <filename>\n");
        exit(1);
    }
    find(argv[1], argv[2]);

    exit(0);
}

xargs

通过分析他给的样例加搜索可以发现 xargs是一个批处理 args的命令,其作用是从 前一个进程的输出中args,每个arg以 ‘ ‘ ‘\n’分割,批量的处理这些arg
| 符号在shell中可以做到将前一个进程的 fd 1 重定向到后一个进程的fd 0中,本质上是通过dup 通过交换进程的文件描述符实现的

所以处理的策略是,从标准输入中读取数据,读到\n或者 ‘ ‘表示读完一个arg了,用fork去创建一个子进程,将args传递过去执行

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

#define MAXARGS 0x20
#define BUFSIZE 0x500

int main(int argc, char *argv[]) {

    char buf[BUFSIZE];
    int input_data_len = 0;
    char *args[MAXARGS];
    int i;

    if (argc == 1) {
        fprintf(2, "e.g. find . | xargs file\n");
        exit(1);
    }

    if (argc > MAXARGS - 5) {
        fprintf(2, "Error: too many arguments\n");
        exit(1);
    }

    for (i = 0; i < argc; i++) {
        args[i] = argv[i];
    }

    while (read(0, buf + input_data_len, 1) == 1) {

        if (buf[input_data_len] == '\n' || buf[input_data_len] == ' ') {
            buf[input_data_len] = 0;
            args[argc] = buf;
            args[argc + 1] = 0;

            if (fork() == 0) {
                exec(argv[1], args + 1);
                fprintf(2, "exec failed\n");
                exit(1);
            } else {
                wait(0);
            }

            input_data_len = 0;
        } else {
            input_data_len++;
            if (input_data_len >= BUFSIZE - 1) {
                fprintf(2, "Error: input too long\n");
                exit(1);
            }
        }
    }

    if (input_data_len > 0) {
        buf[input_data_len] = 0;
        args[argc] = buf;
        args[argc + 1] = 0;

        if (fork() == 0) {
            exec(argv[1], args);
            fprintf(2, "exec failed\n");
            exit(1);
        } else {
            wait(0);
        }
    }

    exit(0);
}
⬆︎TOP