6.S081 Lab1 utilities

6.S081 Lab1 utilities

实验要求链接Lab1 utilities

0. Boot xv6

这个主要在6.S081 Lab0 搭建系统中,这里不再详细描述。

1. 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.

Some hints:

  • Look at some of the other programs in user/ to see how you can obtain the command-line arguments passed to a program. If the user forgets to pass an argument, sleep should print an error message.

  • The command-line argument is passed as a string; you can convert it to an integer using atoi (see user/ulib.c).

  • Use the system call sleep (see user/usys.S and kernel/sysproc.c).

  • Make sure main calls exit() in order to exit your program.

  • Add the program to UPROGS in Makefile and compile user programs by typing make fs.img.

  • Look at Kernighan and Ritchie’s book The C programming language (second edition) (K&R) to learn about C.

代码如下:利用atoi将传入参数转int,然后利用sleep系统调用完成sleep,注意错误处理。

#include "kernel/fcntl.h"
#include "kernel/stat.h"
#include "kernel/types.h"
#include "user/user.h"
// 利用atoi转int,然后利用sleep系统调用完成sleep
int main(int argc, char* argv[]) {
  if (argc != 2) {
    write(1, "err!\n", strlen("err!\n"));
    write(2, "error input\n", strlen("error input\n"));
    exit(1);
  } else {
    int time = atoi(argv[1]);
    sleep(time);
    exit(0);
  }
  exit(1);
}

代码运行:注意代码存放在xv6下的user/目录下,建议命名成XXX_sleep.c的形式(XXX代表用户名,我这里习惯用LEVI)。然后代码编译是通过修改Makefile实现的,在Makefile文件中,找到如下UPROGS,将 $U/_LEVI_sleep\添加到最后一列,然后make qemu,进入系统,就可以运行LEVI_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/_wc\
	$U/_zombie\
	$U/_cowtest\
	$U/_uthread\
	$U/_call\
	$U/_testsh\
	$U/_kalloctest\
	$U/_bcachetest\
	$U/_mounttest\
	$U/_crashtest\
	$U/_alloctest\
	$U/_LEVI_pingpong\
	$U/_LEVI_sleep\
	$U/_LEVI_primes\
	$U/_LEVI_xargs\
	$U/_LEVI_forktest\
	$U/_LEVI_exec_test\
	$U/_LEVI_fork_exec_test\
	$U/_LEVI_IO_redirect_test\

运行结果:

$ LEVI_sleep
err!
error input
$ LEVI_sleep 10 

2. pingpong – 进程通信,pipe的使用

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 sends by writing a byte to parent_fd[1] and the child receives it by reading from parent_fd[0]. After receiving a byte from parent, the child responds with its own byte by writing to child_fd[1], which the parent then reads. Your solution should be in the file user/pingpong.c.

Some hints:

  • Use pipe to create a pipe.
  • Use fork to create a child.
  • Use read to read from the pipe, and write to write to the pipe.

这里先插嘴将依据,什么是pipe:

A pipe is a small kernel buffer exposed to processes as a pair of file descriptors, one for reading and one for writing. Writing data to one end of the pipe makes that data available for reading from the other end of the pipe. Pipes provide a way for processes to communicate. – pipe是一个内核buffer,是一对儿fd,一个读fd,一个写fd,从一个fd写,可以从另一个fd读,pipe可以用于进程之间通信

pipe函数定义中的fd参数是一个大小为2的一个数组类型的指针。该函数成功时返回0,并将一对打开的文件描述符值填入fd参数指向的数组。失败时返回 -1并设置errno。 通过pipe函数创建的这两个文件描述符 fd[0] 和 fd[1] 分别构成管道的两端,往 fd[1] 写入的数据可以从 fd[0] 读出。并且 fd[1] 一端只能进行写操作,fd[0] 一端只能进行读操作,不能反过来使用。要实现双向数据传输,可以使用两个管道。pipe还有一个特性是读取的时候,这个pipe的写端口有引用,并且读的时候为空,那么读就会阻塞–直到写释放才会读。

代码

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

int main() {
  int p_fd[2], c_fd[2];  // parent & child fd
  // char* buf = (int *)malloc(sizeof(char)*64); //这个也可以
  char buffer[16];  //缓冲。
  pipe(p_fd);
  pipe(c_fd);
  int pid = fork();
  printf("file_descriptors: %d %d %d %d \n", p_fd[0], p_fd[1], c_fd[0],
         c_fd[1]);
  if (pid == 0) {
    // close(p_f[1]);
    read(p_fd[0], buffer, 4);
    printf("child %d: received %s \n", getpid(), buffer);
    write(c_fd[1], "pong", 4);
    // close(c_fd[1]);
    exit(0);
  } else if (pid > 0) {
    write(p_fd[1], "ping", 4);
    // wait(0);  //必须要加这个,wait等待子进程退出,才能执行
    // -->其实上一句没必要,因为pipe的特性,读取的时候,这个pipe的写端口有引用,并且读的时候为空,那么读就会阻塞,
    // 因此无论何时,都是首先执行parent
    read(c_fd[0], buffer, 4);
    printf("parent %d: received %s \n", getpid(), buffer);
  } else {
    printf("fork err!\n");
    exit(1);
  }
  exit(0);
}

运行结果

$ LEVI_pingpong
file_descriptors: 3 4 5 6 
file_descriptors: 3 4 5 6 
child 5: received ping 
parent 4: received pong 

可以看到,文件描述符从3开始递增,并且由于pipe特性,在parent写之前,子进程的read始终被阻塞,因此永远是parent先写,child后读。

其中代码调用了pipe()其源码如下(其中asm volatile是汇编内联)-- 这里的sys_pip是函数地址是42…,具体暂时不再深究。

#define SYS_pipe        42
#define TRAP0(f, p1, p2, p3) __trap0(f, (int)(p1), (int)(p2), (int)(p3))

// Perform a system call.
// Unused parameters should be set to 0.
int __trap0(unsigned long func, unsigned long p1, unsigned long p2, unsigned long p3)
{
  int ret = 0;
  asm volatile ("nop\n\tor %%4,%%0,%0" : : "r"(func));
  asm volatile ("nop\n\tor %%5,%%0,%0" : : "r"(p1));
  asm volatile ("nop\n\tor %%6,%%0,%0" : : "r"(p2));
  asm volatile ("nop\n\tor %%7,%%0,%0" : : "r"(p3));
  asm volatile ("nop\n\tor %%11,%%0,%0" : : "r"(func));
  asm volatile ("syscall\n\tnop\n\tor %0,%%0,%%2" : "=r"(ret));
  return ret;
}

// pipe 源码
int pipe (int *fd) {
  return TRAP0 (SYS_pipe, fd, 0, 0);
}

3. 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.

Some hints:

  • Be careful to close file descriptors that a process doesn’t need, because otherwise your program will run xv6 out of resources before the first process reaches 35.
  • Once the first process reaches 35, you should arrange that the pipeline terminates cleanly, including all children (Hint: read will return an end-of-file when the write-side of the pipe is closed). — exit(0)!
  • It’s simplest to directly write 32-bit ints to the pipes, rather than using formatted ASCII I/O.
  • You should create the processes in the pipeline as they are needed.

Your solution is correct if it produces the following output:

思路,主要是参考链接this page的介绍:

A generating process can feed the numbers 2, 3, 4, …, 1000 into the left end of the pipeline: the first process in the line eliminates the multiples of 2, the second eliminates the multiples of 3, the third eliminates the multiples of 5, and so on:

p = get a number from left neighbor
print p
loop:
    n = get a number from left neighbor
    if (p does not divide n)
        send n to right neighbor

(如果图片加载不出来,请看this page的图片)

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-HzPXPXeE-1656339824793)(https://gitee.com/LEVI-Tempest/picgo-typora/raw/master/image-20220627093448545.png)][外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-8hWDeePr-1656339824795)(F:\Typora_Figures\6.S081 Lab1 utilities\sieve-16562937442227.gif)]

即,每次从left neighbor得到n, 如果p不能被n整除,就将n送到right neighbor。

代码

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

// 使用递归函数来实现
void primes(int* nums, int cnt) {
  // 无论如何都先输出
  int cur_prime = nums[0];  // 保存起来后面有用
  printf("prime %d", cur_prime);
  // 递归结束
  if (cnt == 1) {
    return;
  }

  // 考虑right & left neighbor
  // (注意看图,第一个进程应该是只有写的份儿--先开写进程)
  int fd[2];
  char* buf[4];
  pipe(fd);
  int right_beighbor_pid = fork();
  if (right_beighbor_pid == 0) {
    for (int i = 0; i < cnt; ++i) {
      write(fd[1], (char*)&nums[i], 4);
    }
    // right neighbor完成使命,结束
    exit(0);
  }

  // 根据要求,关闭不再使用的fd[1]
  // Be careful to close file descriptors that a process doesn't need,
  // because otherwise your program will run xv6 out of resources before the
  // first process reaches 35.)
  close(fd[1]);

  int left_neighbor_pid = fork();
  int new_cnt = 0;
  if (left_neighbor_pid == 0) {
    while (read(fd[0], buf, 4)) {
      if (*((int*)buf) % cur_prime != 0) {
        // 这里还是看了去年的实现。。。。 -- 用nums保存
        //这里不能用*nums. 要在之前就保存下来
        ++new_cnt;
        *nums = *((int*)buf);
        ++nums;
      }
    }
    primes((nums - new_cnt), new_cnt);
    exit(0);
  }
  wait((int*)0);
  wait((int*)0);
  close(fd[0]);
  exit(0);
}

int main(int argc, char* argv[]) {
  int nums[34];
  int i;
  for (i = 0; i < 34; ++i) nums[i] = i + 2;
  primes(nums, 34);
  exit(0);
}

运行结果(忘记换行了,但是这样正好比较短)

$ LEVI_primes2
prime 2prime 3prime 5prime 7prime 11prime 13prime 17prime 19prime 23prime 29prime 31$ 

4. find

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

Some hints:

  • Look at user/ls.c to see how to read directories.
  • Use recursion to allow find to descend into sub-directories.
  • Don’t recurse into “.” and “…”.
  • Changes to the file system persist across runs of qemu; to get a clean file system run make clean and then make qemu.
  • You’ll need to use C strings. Have a look at K&R (the C book), for example Section 5.5.

Optional: support regular expressions in name matching. grep.c has some primitive support for regular expressions. – 这个暂时还是不实现了…下次一定

Your solution is correct if produces the following output (when the file system contains a file a/b):

$ make qemu
...
init: starting sh
$ mkdir a
$ echo > a/b
$ find . b
./a/b
$ 

首先要看ls.c(一定要看,这个代码自己写还是挺困难的,但是看了ls.c就很简单了-- 代码几乎可以完全复用,只需要简单改动就可以)

// ls.c
void ls(char *path) {
  char buf[512], *p;
  int fd;
  struct dirent de;
  struct stat st;

  if ((fd = open(path, 0)) < 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_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);
}

其中先熟悉两个结构体:

// 为了获取某文件夹目录内容,所使用的结构体。
struct dirent {
  ushort inum;
  char name[DIRSIZ];
};

// 获取文件(普通文件,目录,管道,socket,字符,块)的属性
#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
};

最终的find代码

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

/*
    想法:简单模改一下就行,感觉跟ls.c
   的功能非常相似,区别在于find是匹配字符串,ls是输出名称 
*/



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

  // 这里基本是完全复制ls.c的内容 --
  // 打开目录,如果失败,或者文件类型不是目录,或者目录长度超过定义大小,就报错并ret
  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 != T_DIR) {
    fprintf(2, "find: the first parameter %s must be a directory!\n", path);
    close(fd);
    return;
  }
  // 目录长度超过最长目录大小
  if (strlen(path) + 1 + DIRSIZ + 1 > sizeof buf) {
    printf("find: path too long\n");
    // break;
  }

  strcpy(buf, path);
  p = buf + strlen(buf);
  // 这里是*(p++)的意思
  *p++ = '/';
  if (1) {
    // 输出一下当前路径名
    printf("cur path = ");
    write(1, buf, strlen(buf));
    printf("\n");
  }

  while (read(fd, &de, sizeof(de)) == sizeof(de)) {
    if (de.inum == 0) continue;
    // 不递归'.' 和 '..'
    if (!strcmp(de.name, ".") || !strcmp(de.name, ".."))
      continue;
    // 把文件名复制到p处
    memmove(p, de.name, DIRSIZ);
    p[DIRSIZ] = 0;
    if (stat(buf, &st) < 0) {
      printf("ls: cannot stat %s\n", buf);
      continue;
    }
    // 如果是目录类型,就递归查找
    if (st.type == T_DIR) {
      find(buf, filename);
    } else if (st.type == T_FILE && !strcmp(de.name, filename)) {
      // 打印buf存放的路径
      printf("%s\n", buf);
    }
  }

  close(fd);
  return;
}
int main(int argc, char *argv[]) {
  if (argc < 2) {
    printf("find <dir> <fileName> or find <crrPath> <filename>!\n");
    exit(0);
  } else if (argc == 2) {
    find(".", argv[1]);
    exit(0);
  } else if (argc == 3) {
    find(argv[1], argv[2]);
    exit(0);
  } else {
    printf(
        "Sorry, only support find <dir> <fileName> or find <crrPath> "
        "<filename> for now.\n");
    exit(0);
  }
  exit(0);
}

运行结果:

# 先创建output.txt才能查找
$ ls > output.txt

$ LEVI_find output.txt
cur path = ./
./output.txt

5. xargs

Write a simple version of the UNIX xargs program: read lines from 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.

The following example illustrates xarg’s behavior:

$ xargs echo bye
hello too
bye hello too
ctrl-d
$

Note that the command here is “echo bye” and the additional arguments are “hello too”, making the command “echo bye hello too”, which outputs “bye hello too”.

Some hints:

  • Use fork and exec system call to invoke the command on each line of input. Use wait in the parent to wait for the child to complete running the command.
  • Read from stdin a character at the time until the newline character (‘\n’).
  • kernel/param.h declares MAXARG, which may be useful if you need to declare an argv.
  • Changes to the file system persist across runs of qemu; to get a clean file system run make clean and then make qemu.

xargs, find, and grep combine well:

$ find . b | xargs grep hello

will run “grep hello” on each file named b in the directories below “.”.

To test your solution for xargs, run the shell script xargstest.sh. Your solution is correct if it produces the following output:

  $ make qemu
  ...
  init: starting sh
  $ sh < xargstest.sh
  $ $ $ $ $ $ hello
  hello
  hello
  $ $   

想法非常简单,就是保存参数,按照每行,fork之后调用exec。(可以参考6.S081-1系统调用中的5. exec)

xargs 每次只执行一小段程序(仅对一行的数据进行exec)

代码如下

#include "kernel/types.h"
#include "kernel/stat.h"
#include "user/user.h"
// 提示中说要引用的
#include "kernel/param.h"

// #define MAXARG 32
#define MAX_BUF_SIZE 1000

// 想法非常简单,就是保存参数,按照每行,fork之后调用exec
void run_exec(char** exec_argv) {
    int pid, status;
    pid = fork();
    if (pid == 0) {
        printf("execing %s\n", exec_argv[0]);
        exec(exec_argv[0], exec_argv);
        printf("err in exec %s\n", exec_argv[0]);
        fprintf(2, "err in exec %s\n", exec_argv[0]);
        exit(1);
    }
    wait(&status);
    return;
}

int main(int argc, char* argv[])
{
    if (argc == 1) {
        fprintf(2, "argc must >= 2\n");
        exit(1);
    }
    // read 缓存
    char buf[MAX_BUF_SIZE];
    // 需要运行的进程的参数
    char* exec_argv[MAXARG];
    
    // 这个指针用来指向下行的数据,每次读取一行都会覆盖当前行
    char** exec_argv_cur_ptr = exec_argv;
    for (int i = 1; i < argc; ++i) {
        *(exec_argv_cur_ptr++)  = argv[i];
    }
    // 从标准输入读取数据
    int read_nums = 0;
    while ((read_nums = read(0, buf, MAX_BUF_SIZE)) > 0){
        char input_args[MAX_BUF_SIZE] = {'\0'};
        *(exec_argv_cur_ptr) = input_args;

        // 寻找换行(并对这行数据执行)
        for (int i = 0; i < strlen(buf); ++i) {
            if (buf[i] == '\n') {
                run_exec(exec_argv);
            } else {
                input_args[i] = buf[i];
            }
        }


    }
    
    exit(0);
}

运行结果

$ LEVI_xargs echo 1
2
execing echo
1 2

按ctrl + d退出LEVI_xargs

后记:终于搞完了,这个Lab1弄了一整天的时间 – 主要是指针操作啥的不要弄错了

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值