Lab1

相关资料

实验指导书:https://pdos.csail.mit.edu/6.S081/2020/labs/util.html
教材:https://pdos.csail.mit.edu/6.828/2020/xv6/book-riscv-rev1.pdf
实验一主要是熟悉 xv6 操作系统和它的一些系统调用,因此实验就挑了几个做。同时看看有哪些c的知识需要去进一步学习的。

sleep

xv6 系统写一个 sleep 程序,调用系统提供的sleep() 函数,让程序暂停指定数量的时钟tick
这个练习还是比较简单的。

//user/sleep.c
#include "kernel/types.h"//包含了各种数据类型
#include "user/user.h" //已经实现的各种系统调用

//规范的调用方法为:sleep tick数
int main(int argc, char* argv[]){
    if( argc != 2){ //提示正确的调用方法
        printf( "请使用正确的调用方法: %s <ticks>\n", argv[0]);
        exit(1);
    }

    char* arg = argv[1];
    int ticks = atoi(arg); //系统中sleep函数接受的是int参数
    printf( "系统将sleep%dticks\n", ticks);
    sleep(ticks);//调用已经实现的sleep函数:int sleep(int);
    printf("sleep完成!\n");
    exit(0);
}

完成后在Makefile文件中找到UPROGS,新增一项$U/_sleep\。

//测试方法1,直接执行以下命令
./grade-lab-util sleep
//得到结果
make: “kernel/kernel”已是最新。
== Test sleep, no arguments == sleep, no arguments: OK (1.1s) 
    (Old xv6.out.sleep_no_args failure log removed)
== Test sleep, returns == sleep, returns: OK (0.7s) 
== Test sleep, makes syscall == sleep, makes syscall: OK (1.0s) 
    (Old xv6.out.sleep failure log removed)
//测试方法2:重新通过make qemu启动内核,直接执行sleep命令进行测试
$ sleep 12 13
请使用正确的调用方法: sleep <ticks>
$ sleep  
请使用正确的调用方法: sleep <ticks>
$ sleep 10
系统将sleep10ticks
sleep完成!

pingpong

进程发送一个字符给子进程,子进程接收到后再发送回父进程,最后父进程接收到子进程的回复并打印消息。
代码如下

#include "kernel/types.h"//包含了各种数据类型
#include "user/user.h" //已经实现的各种系统调用


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

    //fd1是父进程向子进程通信,即写端由父进程控制,读端子进程控制
    //fd2:子进程向父进程通信,即写端由子进程控制,读端父进程控制
    //0用来读,1用来写
    int fd1[2], fd2[2]; 
    int ans1 = pipe(fd1);
    int ans2 = pipe(fd2);
        
    printf( "管道创建情况为:fd1:%d, fd2:%d\n", ans1, ans2);

    //创建子进程
    int st = fork();
    if(st != 0){
        //父进程
        //向管道写1个字节
        write(fd1[1],"a",1);
        close(fd1[1]);

        char buf = ' ';

        //读取一个字节进入buf
        read(fd2[0], &buf, 1);
        printf("%d: received pong\n", getpid());
        printf("after in parent buf : %c\n",buf);
        wait(0);
    }
    else if( st == 0){
        //进入子进程,fork创建的子进程共享父进程的代码和数据

        //子进程先读后写
        char buf = ' ';
        
        read(fd1[0], &buf, 1);
        printf("%d: received ping\n", getpid());
        printf("after in son buf : %c\n",buf);
        write(fd2[1], &buf, 1);
        close(fd2[1]);
    }
    close(fd1[0]);
    close(fd2[0]);
    exit(0);
}

测试结果

make: “kernel/kernel”已是最新。
== Test pingpong == pingpong: OK (0.9s) 
//////////////////////
管道创建情况为:fd1:0, fd2:0
4: received ping
after in son buf : a
3: received pong
after in parent buf : a

primes

实现管道并发版本的埃拉托斯特尼筛法(Sieve of Eratosthenes)。这个算法的基本思想是从2开始,不断地标记和删除该数的倍数,直到没有更多的数可以标记。这样,剩下的未被标记的数就是质数。正确性用反证法即可证明。
在本实验中的思路:

  • 1、一个线程读取带筛数据,将其第一个数据标记为质数,同时筛出所有该数的倍数。
  • 2、使用fork新建子线程,将筛完后的数据传入
  • 3、重复1、2

这样就形成了一条进程链:主进程:生成 n ∈ [2,35] -> 子进程1:筛掉所有 2 的倍数 -> 子进程2:筛掉所有 3 的倍数 -> .....,最后依链退出即可
本实验的其他难点在指导书中也有说明:
文件描述符耗尽的情况,由于fork创建子进程的操作,会把父进程的文件描述符也都复制给子进程,如果不及时关闭,文件描述符就会耗尽了。解决方法有两种:

  • 关闭管道的两个方向中不需要用到的方向的文件描述符(在具体进程中将管道变成只读/只写)。原理在于每个进程从左侧的读入管道中只需要读数据,并且只需要写数据到右侧的输出管道,所以可以把左侧管道的写描述符,以及右侧管道的读描述符关闭,而不会影响程序运行。而文件描述符是进程独立的,比如,父进程的管道pipe[2]在fork作用下复制给了子进程,在子进程中关闭了pipe[1]写端,并不会影响父进程在pipe[1]写数据。管道的写端有一个引用计数,只有当所有指向写端的文件描述符都关闭时,管道的写端才会真正关闭。子进程关闭它的写端只是减少了引用计数但父进程的写端仍然打开,所以管道的写端不会关闭。读端同理。
  • 子进程创建后,关闭父进程与祖父进程之间的文件描述符(因为子进程并不需要用到之前的管道)

有关管道的知识:https://blog.csdn.net/puppy_1mo/article/details/145785107;
https://blog.csdn.net/sushhsishdgsusk/article/details/135297011
代码如下:

#include "kernel/types.h"//包含了各种数据类型
#include "user/user.h" //已经实现的各种系统调用

//为了实现数据的传输,接收父进程文件描述符作为参数
void sieve(int fd_left[2]){
    int flag;
    read( fd_left[0], &flag, sizeof(flag));
    if( flag == -1 )
        exit(0);

    //接收到的第一个数肯定是质数,输出它
    printf("prime %d\n", flag);

    //往子进程写的管道
    int fd_right[2];
    pipe(fd_right);

    if( fork() == 0){
        //子进程
        close(fd_right[1]);
        close(fd_left[0]);
        sieve(fd_right);
    }
    else{
        close(fd_right[0]);
        //从左邻居一直读数据,直到接收到终止信号
        int buf;
        while ( read(fd_left[0], &buf, sizeof(buf)) && buf != -1){
            if( buf % flag != 0){ //不是本轮筛掉的数
                //传给子进程
                write(fd_right[1], &buf, sizeof(buf));
            }
        }
        //flag 为 -1
        buf = -1;
        write(fd_right[1], &buf, sizeof(buf));
        wait(0);
        exit(0);
    }
}

int main(int argc, char* argv[]){
    //主进程,输入2-35
   int fd0[2];
   pipe(fd0);

    if( fork() == 0 ){
        //进程链的下一个子进程
        close(fd0[1]); //子进程用不到该管道的写端
        sieve(fd0);
        exit(0);
    }
    else{
        //父进程
        close(fd0[0]);//父进程只写
        int i = 0;
        for(i = 2; i <= 35; ++i){
            write(fd0[1], &i, sizeof(i) );
        }
        //写入结束
        i = -1;
        write(fd0[1], &i, sizeof(i));
    }
    wait(0); //等待子进程结束再退出
    exit(0);
}

测试结果

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

find

给定一个初始路径和目标文件名, 要不断递归的扫描找到所有子目录前匹配的文件全路径. 实验手册说可以模仿ls.c的实现来读文件,因为这一点整体来说还是不难。但是很多c文件操作还是不太熟悉。

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

void find(char* path, const char* name){
    char buf[512], *p;      //路径缓冲区
    int fd;             //文件描述符
    struct dirent de;   //目录条目结构体
    struct stat st;     //文件状态结构体

    //打开目录
    if((fd = open(path, 0)) < 0){
        printf( "ls: cannot open %s\n", path);
        return;
    }
    //获取目录状态信息
    if(fstat(fd, &st) < 0){
        printf( "ls: cannot stat %s\n", path);
        close(fd);
        return;
    }
    //分别处理path为文件或者目录的情况
    switch(st.type){
        //为文件
        case T_FILE:
        //判断path的最后strlen(name)个字符是否与name相等
            if( strcmp(path+strlen(path)-strlen(name), name) == 0 )
                printf("%s\n", path);

            break;
        //为目录
        case T_DIR:
            if( strlen(path) + 1 + DIRSIZ + 1 > sizeof (buf) ){
                 printf("find: 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;
                //跳过 "." 和 ".."
                if( strcmp(de.name, ".")==0 || strcmp(de.name, "..")==0 )
                    continue;  
                //使用memmove避免内存交织
                memmove(p, de.name, DIRSIZ);
                p[ DIRSIZ ] = 0;
                find(buf, name);
            }
            break;
        }
        close(fd);
}


int main(int argc, char* argv[]){
    //find 目录 文件名
    if( argc != 3){
        printf("请正确使用调用方法: find [dictionary] [file name]\n");
        exit(1);
    }
   

    find( argv[1], argv[2]);
    exit(0);
}

测试结果

make: “kernel/kernel”已是最新。
== Test find, in current directory == find, in current directory: OK (0.5s) 
== Test find, recursive == find, recursive: OK (1.0s) 
posted @ 2025-06-20 10:51  名字好难想zzz  阅读(26)  评论(0)    收藏  举报