前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >MIT_6.s081_Lab1:Xv6 and Unix utilities

MIT_6.s081_Lab1:Xv6 and Unix utilities

作者头像
用户7267083
发布2022-12-08 14:38:55
7100
发布2022-12-08 14:38:55
举报
文章被收录于专栏:sukuna的博客sukuna的博客

MIT_6.s081_Lab1:Xv6 and Unix utilities

于2022年3月1日2022年3月1日由Sukuna发布

运行环境:Ubuntu 20.04 qemu

在做6.s081的实验之前我们首先要先下载Xv6操作系统以及qemu虚拟机:

代码语言:javascript
复制
sudo apt-get install git build-essential gdb-multiarch qemu-system-misc gcc-riscv64-linux-gnu binutils-riscv64-linux-gnu 

调试的gbd工具使用方法:在Ubuntu的终端输入这个命令即可

记住端口号,是tcp::26000

另起一个窗口,输入下面命令:

输入 file ./kernel/kernel载入符号表,然后target remote loaclhost:26000即可:

Lab1_1:Boot xv6

运行并安全退出xv6系统:

运行的方法很简单:cd进xv6的文件夹里面,然后输入`make qemu`即可,输入完之后就是进入了xv6的系统里面了

退出的方法就是,先Ctrl+A,再输入x即可.

Lab1_2 sleep

本实验要为 xv6 实现 UNIX 程序 sleep; 您的睡眠应暂停用户指定的滴答数。 滴答是 xv6 内核定义的时间概念,即来自定时器芯片的两次中断之间的时间。

一些提示:

  • 在开始编码之前,请阅读xv6 书籍第 1 章。(中文版xv6 书籍)
  • 查看user/中的其他一些程序 (例如,user/echo.cuser/grep.cuser/rm.c)以了解如何获取传递给程序的命令行参数。
  • 如果用户忘记传递参数, sleep 应该打印错误消息。
  • 命令行参数作为字符串传递;您可以使用atoi将其转换为整数(请参阅 user/ulib.c)。
  • 使用系统调用sleep
  • 确保main调用exit()以退出您的程序。
  • 将你的睡眠程序添加到Makefile 中的UPROGS;完成后,make qemu将编译您的程序,您将能够从 xv6 shell 运行它。
  • 查看 Kernighan 和 Ritchie 的书*The C programming language (second edition)*(K&R)以了解 C。
代码语言:javascript
复制
#include "kernel/types.h"
#include "kernel/stat.h"
#include "user/user.h"

int main(int argc,char *argv[]){
  for(int i=0;!argv[i];i++){
    if(argv[1][i]>'9'||argv[1][i]<'0'){
      write(1, "error\n", 6);
      exit(-1);
    }
  }
  int times=atoi(argv[1]);
  sleep(times);
  exit(0);
}

首先我们知道在C语言中argc代表传入的参数数,argv是传入的参数.

第一个for循环就是判断这个是不是一个正常的数字(什么时候结束循环呢?),第二步用atoi函数转化成数字,最后执行系统调用sleep.

Lab1_3 pingpong

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

一些提示:

  • 使用管道创建管道。
  • 使用 fork 创建一个孩子。
  • 使用 read 从管道读取,并使用 write 写入管道。
  • 使用 getpid 查找调用进程的进程 ID。
  • 将程序添加到 Makefile 中的 UPROGS。
  • xv6 上的用户程序有一组有限的库函数可供它们使用。 可以在 user/user.h 中看到列表; 源(系统调用除外)位于 user/ulib.c、user/printf.c 和 user/umalloc.c。

我们可以认为pipe是一个Linux进程间通讯的一种方式,一个管道以一个两位的int类型数组构成,其中第一个元素是读端的接口编号,第二个元素是写端的接口编号.然后可以使用read和write来进行读取,注意管道的端口有读端口和写端口之分,读的时候只能从读端口读数据,写的时候只能从写的端口写数据.

有点像我们之前操作系统课上面学到的缓冲区的结构,缓冲区有两端,一端读一端写.

系统调用: 可以使用pipe(一个二位的数组)来初始化一个管道.经过pipe了之后,第一个元素就是一个读取的端口,第二个元素就是对应写入的端口, 可以使用read(读端口,读出来的元素写在哪里,长度)来从一个读的端口读出元素 可以使用write(写端口,写出来的元素写在哪里,长度)来把元素写进一个端口.

fork函数就是一次调用,两次返回,调用之后父进程和子进程都从获得函数的返回值开始继续往下运行,就像一条河流,遇到了一个分叉口,河流分成了两叉,这两叉都从分叉口开始继续往下流.在这里分叉口就像fork()调用,调用生成了两个分叉,两个分叉都从fork()调用结束后继续往下走.

代码语言:javascript
复制
#include "kernel/types.h"
#include "kernel/stat.h"
#include "user/user.h"

int main(){
  int p_filedes[2],s_filedes[2];
  pipe(p_filedes);
  pipe(s_filedes);
  char buf[4];
  if(fork()==0){
    read(p_filedes[0],buf,4);
    printf("%d: received %s\n",getpid(),buf);
    write(s_filedes[1],"pong",4);
  }else{
    write(p_filedes[1],"ping",4);
    read(s_filedes[0],buf,4);
    printf("%d: received %s\n",getpid(),buf);
  }
  exit(0);
}

函数的思路就是先初始化两根管道,接着父进程先read再写,子进程先写再read.

Lab1_4 prime

编写一个程序,完成一个并发版本的prime初筛,第一个进程负责把2-35范围内的素数输入进管道,对于每一个素数,我们都需要fork一个进程来接受管道的数字然后输出出来.

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

基本的思路在下面,每一个进程对应一个素数,主进程负责传输2-34的数据给子进程们,每个进程对应一个素数,如果这个数%这个素数不为0的话就可以传给下一级的进程,如果没有下一级的进程那么fork一个新的进程,这个数一定是素数,这个进程就会接着处理来自左边邻居的数据,处理的方式.这样子每一个进程就像一个筛子,筛选不可能是素数的数.

总的来说主进程的数据首先从左到右到第一个子进程,判断能不能被2除,不可以就继续从左到右交给下一个子进程,判断能不能被3除…,如果下一个子进程是不存在的,那么新建一个进程,这个进程就代表对应数.

Lab1_5 find

编写一个简单版本的 UNIX 查找程序:查找目录树中具有特定名称的所有文件。给定对应的文件名以及文件名在目录,找到文件名的位置.

  • 查看 user/ls.c 以了解如何读取目录。
  • 使用递归允许 find 访问到子目录。
  • 不要递归到“.” 和 ”..”。
  • 对文件系统的更改在 qemu 运行中持续存在; 要获得一个干净的文件系统,请运行 make clean 然后 make qemu。
代码语言:javascript
复制
struct dirent {
  ushort inum;
  char name[DIRSIZ];
};

上面是文件系统中关于目录文件内容的说明,inum说明这个文件占用了几个inode.该操作系统类似于Linux系统,磁盘分成磁盘块,磁盘块的一些相关控制信息就储存在存放在内存中的inode.文件系统获得文件,先找到文件的file结构,根据file结构找到inode,再根据inode找到磁盘块.

说白了目录文件存储的就是一堆dirent类型的结构体.

下面就是stat信息,stat信息存放了文件的一些控制信息,比如说链接信息,大小和类型之类的.在我们利用open打开文件后,open函数会返回一个数字,我们再利用fstat这个调用找到stat控制块.

代码语言:javascript
复制
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
};

我们先来看看xv6是如何完成对ls指令的支持的?

首先第一个函数:根据文件的的路径名提取出文件的名字,就是从后往前遍历,找到第一个‘/’,这之间的那一部分就是文件的名字.

接着就是ls函数,ls函数中只需要提供当前的path,找到path里面的所有文件即可.首先先打开当前path对应的文件(Linux内部目录文件和普通的文件都是文件),再利用fstat系统调用找到stat的值.由于目录文件里面就是连续地存储了一堆dirent类型的结构体,那我们可以把目录文件的内容当成一个struct dirent[MAX](结构体数组,一个结构体一个结构体地去读)

最后就是main函数,就是看看输入的指令罢了

代码语言:javascript
复制
#include "kernel/types.h"
#include "kernel/stat.h"
#include "user/user.h"
#include "kernel/fs.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, 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);
}

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);

}

这个时候我们就可以对ls指令稍微修改修改,ls只用找当前目录第一层的所有文件,那么我们也是找当前目录的所有文件,对于目录文件,我们继续往下搜索这个目录,对于普通文件,我们只用看名字是否匹配即可.

Lab1_6 xargs

这个指令就是我们要把若干条指令合并在一块进行执行.其中前面指令的standard out会作为下一条的指令一个输入来进行执行.

举个例子:前面指令的hello too作为standard out作为下一条指令的输入.

代码语言:javascript
复制
$ echo hello too | xargs echo bye
$ bye hello too
  • 使用 fork 和 exec 对每一行输入调用命令。 在父级中使用 wait 等待子级完成命令。
  • 要读取单行输入,请一次读取一个字符,直到出现换行符 (‘\n’)。
  • kernel/param.h 声明了 MAXARG,如果您需要声明 argv 数组,这可能很有用。
  • 对文件系统的更改在 qemu 运行中持续存在; 要获得一个干净的文件系统,请运行 make clean 然后 make qemu。
  • 将程序添加到 Makefile 中的 UPROGS。

首先第一步把指令xargs给删除掉:然后把标准输出(来源:0)获取下来,放入最后一个参数中进行执行.

代码语言:javascript
复制
#include "kernel/types.h"
#include "kernel/param.h"
#include "user/user.h"

int main(int argc, char *argv[]){
    char* argvs[MAXARG];
    for(int i=1;i<argc;i++){
        argvs[i-1]=argv[i];
    }
    char buf[512];
    int index;
    int read_len;
    while(1){
        index = -1;
        do{
            index++;
            read_len=read(0,&buf[index],sizeof(char));
        }while(read_len>0&&buf[index]!='\n');
        if(read_len==0&&index==0){
            break;
        }
        buf[index]='\0';
        argvs[argc-1]=buf;
         if (fork() == 0) {
            exec(argvs[0], argvs);
            exit(0);
        } else {
            wait(0);
        }

    }
    exit(0);
}
本文参与 腾讯云自媒体分享计划,分享自作者个人站点/博客。
原始发表:2022年3月1日,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • MIT_6.s081_Lab1:Xv6 and Unix utilities
    • Lab1_1:Boot xv6
      • Lab1_2 sleep
        • Lab1_3 pingpong
          • Lab1_4 prime
            • Lab1_5 find
              • Lab1_6 xargs
              相关产品与服务
              文件存储
              文件存储(Cloud File Storage,CFS)为您提供安全可靠、可扩展的共享文件存储服务。文件存储可与腾讯云服务器、容器服务、批量计算等服务搭配使用,为多个计算节点提供容量和性能可弹性扩展的高性能共享存储。腾讯云文件存储的管理界面简单、易使用,可实现对现有应用的无缝集成;按实际用量付费,为您节约成本,简化 IT 运维工作。
              领券
              问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档