6.1810: Operating System Engineering Lab: Xv6 and Unix utilities By:Haostart

前言

开学后要为一年后找实习做准备了,准备打一下基础,就做一下MIT-6.S081,是2022版的,地址如下
6.1810: Operating System Engineering Lab

Lab

做实验前一定要清楚Xv6的系统调用有哪些!!!
6.1810: Operating System Engineering Lab: Xv6 and Unix utilities By:Haostart_第1张图片
做实验前一定要清楚Xv6的系统调用有哪些!!!

1. sleep

由于系统调用里已经有sleep了,所以处理一些其它细节即可.
注意参数量和合法性判断.
代码如下:

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

int main(int argc, char *argv[])
{
  if(argc != 2){
    fprintf(2, "sleep: missing operand\n");
    exit(1);
  }
  int time = atoi(argv[1]);//字符串数字类型转int型
  sleep(time);
  exit(0);
}

qemu的libc中有atoi实现:
6.1810: Operating System Engineering Lab: Xv6 and Unix utilities By:Haostart_第2张图片

2. pingpong

编写一个程序,使两个进程通过管道进行一次 “ping-pong”,即父进程向子进程发送一个字节,子进程读出后打印:received ping,随后再向父进程发送一个字节,父进程读入后打印:received pong,最后退出

做这个实验前必须要了解一下pipe,具体pipe实现可见 kernel/pipe.c 看下面的程序例子帮助理解

6.1810: Operating System Engineering Lab: Xv6 and Unix utilities By:Haostart_第3张图片
6.1810: Operating System Engineering Lab: Xv6 and Unix utilities By:Haostart_第4张图片

下面是book-riscv-rev3对程序的相关说明

该程序调用 pipe,创建一个新的管道,并将读取和写入文件描述符记录在数组 p 中。在 fork 之后,父进程和子进程都有引用管道的文件描述符。子进程调用 close 和 dup,将文件描述符零指向管道的读取端,关闭 p 中的文件描述符,并调用 exec 运行 wc。当 wc 从其标准输入读取时,它实际上是从管道读取的。父进程关闭管道的读取端,向管道写入数据,然后关闭写入端。
如果没有可用的数据,对管道的读取将等待数据被写入或者所有引用写入端的文件描述符被关闭;在后一种情况下,读取将返回 0,就像到达数据文件的末尾一样。读取阻塞直到不可能再有新数据到达是管道的一个原因,这也是为什么在执行上面的 wc 之前子进程重要地关闭管道的写入端的原因:如果 wc 的文件描述符之一指向了管道的写入端,wc 将永远不会看到文件结束符。

仿照这个例子,我们可以创建管道并进行一些简单的操作:

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

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

    int p[2],pid;
    char buff[2];

    pipe(p);	//系统调用,创建管道

    if(fork()==0){	// =0 为子进程
        pid=getpid();	//得到进程id

        read(p[0],buff,1);
        printf("%d: received ping\n", pid);
        close(p[0]);
        write(p[1],buff,1);
        close(p[1]);
        // printf("%d: send pong\n", pid);
        exit(0);
    }
    else{
        pid=getpid();
        write(p[1],buff+1,1);	//父进程写入管道
        close(p[1]);
        // printf("%d: send ping\n", pid);
        wait(0);	//等待子进程结束
        read(p[0],buff+1,1);
        close(p[0]);
        printf("%d: received pong\n", pid);
        exit(0);

    }
}

3. primes

编写一个使用管道实现的并发版本的素数筛法,这个想法是由Unix管道的发明者Doug McIlroy提出的。您的解决方案应该在文件user/primes.c中。

您的目标是使用管道和fork来建立管道线。第一个进程将数字2到35输入管道。对于每个素数,您将安排创建一个进程,该进程从其左侧邻居通过一个管道读取,并通过另一个管道写入其右侧邻居。由于xv6有限的文件描述符和进程数,第一个进程可以在35处停止。

一些提示:

  1. 要小心关闭进程不需要的文件描述符,否则在第一个进程到达35之前,您的程序会耗尽xv6的资源。
  2. 一旦第一个进程到达35,它应该等待整个管道终止,包括所有子进程、孙子进程等。因此,主要的素数进程只能在所有输出已被打印并且所有其他素数进程已退出后退出。
  3. 提示:当管道的写入端关闭时,read函数会返回零。
  4. 最简单的方法是直接向管道写入32位(4字节)整数,而不是使用格式化的ASCII I/O。
  5. 只有在需要时才应该创建管道中的进程。
  6. 将程序添加到Makefile中的UPROGS

第一次遇到这种,没有过算法经历很难搞,后说

4. find

编写一个简单版本的 UNIX 查找程序:查找目录树中特定名称的所有文件

写这个之前务必熟悉 ls.c 文件的代码,特别是ls函数的实现.
6.1810: Operating System Engineering Lab: Xv6 and Unix utilities By:Haostart_第5张图片
了解到如何得到文件各类信息后,就是实现问题了,我这里是使用了字符串匹配算法

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

//判断字符串包含
int
containsSubstring(const char *path, const char *target) {
    int pathLength = strlen(path);
    int targetLength = strlen(target);

    for (int i = 0; i <= pathLength - targetLength; i++) {
        int j;
        for (j = 0; j < targetLength; j++) {
            if (path[i + j] != target[j]) {
                break;
            }
        }
        if (j == targetLength) {
            return 1; // 找到了目标字符串
        }
    }
    return 0; // 没有找到目标字符串
}

//这里就是简单的返回文件名
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), 0, DIRSIZ-strlen(p));
//   printf("buf:%s\n",buf);
  return buf;
}

void
find(char *path,char *target)
{
  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){
    fprintf(2, "find: cannot stat %s\n", path);
    close(fd);
    return;
  }

  switch(st.type){
    
  case T_DEVICE:
    // printf("it's a device.\n");
  case T_FILE:
    // printf("it's a file %s your find is %s\n",fmtname(path),target);
    if (containsSubstring(fmtname(path),target)) {
      printf("%s\n", path);
    }

    break;

  case T_DIR:
    while(read(fd, &de, sizeof(de)) == sizeof(de)){
        if(de.inum == 0)
            continue;
        if (strcmp(de.name, ".") == 0 || strcmp(de.name, "..") == 0)
            continue;
        // 构建新的路径
        strcpy(buf, path);
        p=buf+strlen(path);
        *p++='/';
        strcpy(p, de.name);
        // printf("%s\t\t%d\n",buf,strlen(buf));
        // strcat(buf, de.name);
        find(buf,target);	//递归实现
        memset(buf, 0, sizeof(buf));	//注意清空
    }
    break;
  }
  close(fd);
}

int
main(int argc, char *argv[])
{
  if(argc == 2){
    find(".",argv[1]);
    exit(0);
  }
  else if(argc==3){
    find(argv[1], argv[2]);
    exit(0);
  }
  else{
    fprintf(2, "Usage: find  \n");
    exit(1);
  }
}

5. xargs

编写一个简单版本的UNIX xargs程序。xargs是组合多个命令的工具,简单来说,它可以通过管道接受字符串,并将接收到的字符串通过空格分割成许多参数,然后再将参数传递给其后面的命令,作为后面命令的命令行参数

在Unix-like操作系统的Shell中,管道符 |
用于创建管道(Pipe),它允许将一个命令的标准输出(stdout)连接到另一个命令的标准输入(stdin),从而实现进程间通信和数据传输。

原理如下:

  1. 当Shell解释器遇到 | 符号时,它会创建一个管道。

  2. Shell将要执行的命令分为两部分:左侧命令和右侧命令。左侧命令的标准输出被重定向到管道的写入端(管道的输出),右侧命令的标准输入被重定向到管道的读取端(管道的输入)。

  3. 接下来,Shell创建两个子进程,一个用于执行左侧命令,另一个用于执行右侧命令。

  4. 左侧命令的输出被写入管道,而右侧命令则从管道中读取输入数据。

  5. 这两个子进程并行运行,左侧命令生成的数据会通过管道传递给右侧命令,从而实现数据流的传输。

  6. 当左侧命令执行完毕后,它会关闭管道的写入端,这将触发右侧命令的结束条件。右侧命令会继续读取管道中的数据,直到没有更多数据可读。

  7. 当右侧命令执行完毕后,Shell会等待两个子进程都结束,并且会等待它们的退出状态。

简单来说就是左边的输出会变成右边的输入.
比如

$ echo hello too | xargs echo bye
bye hello too
$

左边输出hello too,附加到echo bye后面,变成echo bye hello too
下面是可以参考的代码

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

#define MAXARGS 128
#define MAXLEN  128

int main(int argc, char *argv[]) {
    if (argc < 2) {
        fprintf(2, "Usage: %s [command [initial-arguments]]\n", argv[0]);
        exit(1);
    }

    char *buf[MAXARGS];
    int j = 0;  
    for (int i = 1; i < argc; i++)
        buf[j++] = argv[i];     //这是原始的参数
    for (; j < MAXARGS; j++)    //为管道输入的参数准备内存空间
        buf[j] = (char *)malloc(sizeof(MAXLEN)); // Allocate memory for each argument

    j = argc - 1;   //表示管道输入的字符串的起始位置
    char buff;
    
    int cmd=0;

    while (1){	
      int m = 0;    //m为字符串字符索引
      while ((cmd=read(0, &buff, 1)) != 0) {
          if (buff == ' ') {	//空格则到下一个字符串
              buf[j++][m] = 0;
              m = 0;
          } else if (buff == '\n') {	//换行退出,每当读到\n就执行一条命令
              buf[j++][m] = 0;
              m=0;
              break;
          } else {		//参数字符读取
              buf[j][m++] = buff;
          }
      }
      buf[j] = 0;
      j=argc-1;		//字符串索引初始化
      int pid;
      if ((pid = fork()) == 0) {
        exec(buf[0], buf);
          
      } else if (pid < 0) {
          // Fork failed
          exit(1);
      } else {
          // Parent process
          wait(0);
      }
        if(cmd<=0)	//全部读取完毕,退出
        break;
    }

    exit(0);
}

make grade结果如下(不会primes埃氏筛):

6.1810: Operating System Engineering Lab: Xv6 and Unix utilities By:Haostart_第6张图片
6.1810: Operating System Engineering Lab: Xv6 and Unix utilities By:Haostart_第7张图片

总结

算是又一次熟悉了C语言的基础语法,特别是字符串相关的操作.
Xv6的系统调用简洁明了,结构清晰.适合初学者学习.
在算法方面我还有很大不足.6.1810: Operating System Engineering Lab: Xv6 and Unix utilities By:Haostart_第8张图片

你可能感兴趣的:(操作系统,unix,linux,risc-v)