【操作系统xv6】学习记录5--实验1 Lab: Xv6 and Unix utilities

ref:https://pdos.csail.mit.edu/6.828/2020/xv6.html

实验:Lab: Xv6 and Unix utilities

环境搭建

实验环境搭建:https://blog.csdn.net/qq_45512097/article/details/126741793

搭建了1天,大家自求多福吧,哎。~搞环境真是折磨人

anyway,我搞好了:

开整实验

内容1:sleep

创建user/sleep.c

在Makefile中UPROGS下添加$U/_sleep

编写sleep.c

运行测试程序grade-lab-util并且指明要测试的函数。如 grade-lab-util sleep

c 复制代码
#include "kernel/types.h"
#include "kernel/stat.h"
#include "user/user.h"

void main(int argc, char *argv[])
{
  if(argc != 2){
    // 2 输出到哪里,输出内容,
    fprintf(2,"usage:sleep <time> \n");
    exit(1);
  }
  int sec = atoi(argv[1]);
  sleep(sec);  
  //printf("usage:sleep %d \n",sec);
  exit(0);
}

修改makefile,只需要改这一个地方

验证命令,要退出qume才能执行,否则会报错,

这种时候单项验证sleep

./grade-lab-util sleep
bash 复制代码
justin@DESKTOP-NIK28BI:~/vc6/xv6-labs-2020$ ./grade-lab-util sleep
fatal: detected dubious ownership in repository at '/mnt/d/code/vc6/xv6-labs-2020'
To add an exception for this directory, call:

        git config --global --add safe.directory /mnt/d/code/vc6/xv6-labs-2020
make: 'kernel/kernel' is up to date.
== Test sleep, no arguments == fatal: detected dubious ownership in repository at '/mnt/d/code/vc6/xv6-labs-2020'
To add an exception for this directory, call:

        git config --global --add safe.directory /mnt/d/code/vc6/xv6-labs-2020
sleep, no arguments: OK (1.3s)
== Test sleep, returns == sleep, returns: OK (0.8s)
== Test sleep, makes syscall == sleep, makes syscall: OK (1.0s)

内容2: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.

c 复制代码
#include "kernel/types.h"
#include "user/user.h"
#define RD 0 // pipe的read端
#define WR 1 // pipe的write端
int main(){
    char buf[10]; //缓冲区字符数组,存放传递的信息
    int fd_c2p[1]; // child->parent
    int fd_p2c[1]; // parent->child
    int ret1 = pipe(fd_c2p);
    int ret2 = pipe(fd_p2c);
    if(ret1==-1){
        printf("child->parent pipe");
        exit(1);
    }
    if(ret2==-1){
        printf("parent->child pipe");
        exit(1);
    }
    int pid = fork();
    if(pid==0){
        //子进程
        // 关闭父管道的写入端和子管道的读取端
        close(fd_p2c[WR]); // 显示关闭是为了严谨,不关闭不会报错
        close(fd_c2p[RD]); //
        read(fd_p2c[RD],buf,4);
        printf("child:%d received %s\n", getpid(),buf);
        write(fd_c2p[WR],"pong",4);
    }else if(pid>0){
        close(fd_p2c[RD]);
        close(fd_c2p[WR]);
        // 父进程
        write(fd_p2c[WR],"ping",4);
        int status;
        int child_id = wait(&status);
        printf("child:%d done!\n",child_id);
        read(fd_c2p[RD],buf,4);
        printf("parent:%d received %s\n", getpid(),buf);
        printf("parent:%d done!\n", getpid());
    }
    exit(0);
    return 0;
}
bash 复制代码
hart 1 starting
hart 2 starting
init: starting sh
$ pingpong
child:4 received ping
child:4 done!
parent:3 received pong
parent:3 done!

注意没有exit(0); 会出现乱码,原因未知,欢迎大神留言解惑:

bash 复制代码
hart 1 starting
hart 2 starting
init: starting sh
$ pingpong
child:4 received ping
usertrap(): unexpected scause 0x000000000000000d pid=4
            sepc=0x0000000000000100 stval=0x0000000000003f64
child:4 done!
parent:3 received pong
parent:3 done!
usertrap(): unexpected scause 0x000000000000000d pid=3
            sepc=0x0000000000000100 stval=0x0000000000003f64

内容3:Primes(素数,难度:Moderate/Hard)

YOUR JOB
使用管道编写prime sieve(筛选素数)的并发版本。这个想法是由Unix管道的发明者Doug McIlroy提出的。请查看这个网站(翻译在下面),该网页中间的图片和周围的文字解释了如何做到这一点。您的解决方案应该在user/primes.c文件中。

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

提示:

  • 请仔细关闭进程不需要的文件描述符,否则您的程序将在第一个进程达到35之前就会导致xv6系统资源不足。
  • 一旦第一个进程达到35,它应该使用wait等待整个管道终止,包括所有子孙进程等等。因此,主primes进程应该只在打印完所有输出之后,并且在所有其他primes进程退出之后退出。
  • 提示:当管道的write端关闭时,read返回零。
  • 最简单的方法是直接将32位(4字节)int写入管道,而不是使用格式化的ASCII I/O。
  • 您应该仅在需要时在管线中创建进程。
  • 将程序添加到Makefile中的UPROGS。

如果您的解决方案实现了基于管道的筛选并产生以下输出,则是正确的:

$ make qemu
...
init: starting sh
$ primes
prime 2
prime 3
prime 5
prime 7
prime 11
prime 13
prime 17
prime 19
prime 23
prime 29
prime 31
$

考虑所有小于1000的素数的生成。Eratosthenes的筛选法可以通过执行以下伪代码的进程管线来模拟:

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
p = 从左邻居中获取一个数
print p
loop:
    n = 从左邻居中获取一个数
    if (n不能被p整除)
        将n发送给右邻居

生成进程可以将数字2、3、4、...、1000输入管道的左端:行中的第一个进程消除2的倍数,第二个进程消除3的倍数,第三个进程消除5的倍数,依此类推。

这是我调试版本的代码,没有在xv6中打印,可以在任何linux中运行。

取数据:lpipe,写数据rpipe, 从图中可以看到,每个矩形的操作是重复的,所以应该用递归调用function primes,

而且每次递归调用,生成新的子进程,继续递归调用。

c 复制代码
#include<unistd.h>
#include<stdlib.h>
#include<stdio.h>
#include <sys/types.h>
#include <sys/wait.h>
#define RD 0
#define WR 1
/**
 * @brief 寻找素数
 * @param lpipe 左邻居管道
 */
void primes(int lpipe[2]){ //接收的参数当左通道用
    close(lpipe[WR]);
    int first;
    if(read(lpipe[RD],&first,sizeof(int))==sizeof(int)){ // 读到了【左管道】(第一次是main)送过来的数据
        printf("prime %d\n",first);
        printf("get first:%d\n",first);
        // 定义右管道
        int rpipe[2];
        pipe(rpipe); // 生成右管道,做数据过滤
        int data;
        while(read(lpipe[RD], &data, sizeof(int))==sizeof(int)){
            if (data % first){// 除尽的过滤掉,除不尽的留下,放到右管道 
                write(rpipe[WR], &data, sizeof(int));
            }
        }
        if (fork() == 0) {
            primes(rpipe);    // 递归的思想,但这将在一个新的进程中调用
        } else {
            close(rpipe[WR]);// 关闭右管道的写
            wait(0);//等待回收子进程
        }
    }
    
     

    

}
int main(){
    int p[2];
    pipe(p);
    int res;
    for (size_t i = 2; i <= 35; i++)
    {
        // 依次写入初始数据 2,3,4,...35
        res = write(p[WR],&i,sizeof(size_t));
        printf("pid:%d, send:%d, res:%d\n",getpid(),i,res);
    }
    if (fork()==0)
    {   //子进程做筛选素数操作,是递归筛选
        primes(p); 
    }else{
      //父进程啥也不干,等着回收子进程即可
      close(p[WR]); // 这里写关不关闭无所谓,但是谨慎起见,还是关闭为好
      close(p[RD]);//这里必须要关闭读,否则子进程无法从管道读取数据。
      wait(0);  
    }
    exit(0);  
    
}

main函数的 close(p[RD]);//这里必须要关闭读,否则子进程无法从管道读取数据。

读操作只能确保开放给一个进程!这个题目是很难的!

4.find(难度:Moderate)

YOUR JOB

写一个简化版本的UNIX的find程序:查找目录树中具有特定名称的所有文件,你的解决方案应该放在user/find.c

5.xargs(难度:Moderate)

YOUR JOB

编写一个简化版UNIX的xargs程序:它从标准输入中按行读取,并且为每一行执行一个命令,将行作为参数提供给命令。你的解决方案应该在user/xargs.c

下面的例子解释了xargs的行为

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

注意,这里的命令是echo bye,额外的参数是hello too,这样就组成了命令echo bye hello too,此命令输出bye hello too

请注意,UNIX上的xargs进行了优化,一次可以向该命令提供更多的参数。 我们不需要您进行此优化。 要使UNIX上的xargs表现出本实验所实现的方式,请将-n选项设置为1。例如

$ echo "1\n2" | xargs -n 1 echo line
line 1
line 2
$

提示:

  • 使用fork和exec对每行输入调用命令,在父进程中使用wait等待子进程完成命令。
  • 要读取单个输入行,请一次读取一个字符,直到出现换行符('\n')。
  • kernel/param.h声明MAXARG,如果需要声明argv数组,这可能很有用。
  • 将程序添加到Makefile中的UPROGS。
  • 对文件系统的更改会在qemu的运行过程中保持不变;要获得一个干净的文件系统,请运行make clean,然后make qemu
  • xargs、find和grep结合得很好

ref:https://xv6.dgs.zone/labs/requirements/lab1.html

相关推荐
王俊山IT20 分钟前
C++学习笔记----10、模块、头文件及各种主题(一)---- 模块(5)
开发语言·c++·笔记·学习
Mephisto.java1 小时前
【大数据学习 | kafka高级部分】kafka中的选举机制
大数据·学习·kafka
南宫生2 小时前
贪心算法习题其三【力扣】【算法学习day.20】
java·数据结构·学习·算法·leetcode·贪心算法
武子康3 小时前
大数据-212 数据挖掘 机器学习理论 - 无监督学习算法 KMeans 基本原理 簇内误差平方和
大数据·人工智能·学习·算法·机器学习·数据挖掘
使者大牙3 小时前
【大语言模型学习笔记】第一篇:LLM大规模语言模型介绍
笔记·学习·语言模型
As977_3 小时前
前端学习Day12 CSS盒子的定位(相对定位篇“附练习”)
前端·css·学习
ajsbxi3 小时前
苍穹外卖学习记录
java·笔记·后端·学习·nginx·spring·servlet
Rattenking3 小时前
React 源码学习01 ---- React.Children.map 的实现与应用
javascript·学习·react.js
dsywws4 小时前
Linux学习笔记之时间日期和查找和解压缩指令
linux·笔记·学习
道法自然04024 小时前
Ethernet 系列(8)-- 基础学习::ARP
网络·学习·智能路由器