• 操作系统入门系列-MIT6.828(操作系统工程)学习笔记(三)---- xv6初探与实验一(Lab: Xv6 and Unix utilities)


    系列文章目录

    操作系统入门系列-MIT6.S081(操作系统)学习笔记(一)---- 操作系统介绍与接口示例
    操作系统入门系列-MIT6.828(操作系统工程)学习笔记(二)----课程实验环境搭建(wsl2+ubuntu+quem+xv6)
    操作系统入门系列-MIT6.828(操作系统工程)学习笔记(三)---- xv6初探与实验一(Lab: Xv6 and Unix utilities)



    前言

    MIT 6.828课程资料与进度计划表
    完成第一节课的学习后,按照课程的计划进度表,应当阅读xv6文档的第一章节,以及完成实验1。
    本文主要内容就是对xv6文档(2023年版)和实验(2023年版)的讲解
    xv6英文文档—2023
    xv6中文文档—2013
    xv6实验1原文


    一、xv6文档第一章

    1. 引论

    xv6操作系统提供 Unix 操作系统中的基本接口(由 Ken Thompson 和 Dennis Ritchie 引入),同时模仿 Unix 的内部设计,包括 BSD,Linux,Mac OS X,Solaris (甚至 Microsoft Windows 在某种程度上)都有类似 Unix 的接口,理解 xv6 是理解这些操作系统的一个良好起点。
    首先需要对操作系统有一个系统结构的大致概念,如下图:
    在这里插入图片描述
    如图所示,操作系统的核心就是kernel。kernel起到了桥梁的作用,连接各种应用程序和底层硬件资源。从软件编程者的角度来看,程序员就是使用各种kernel提供的系统调用函数,来实现各种各样的功能。xv6的系统调用函数如下图所示:
    在这里插入图片描述
    后面的实验就是需要调用这些系统调用函数来实现相应的功能,比如在Linux里面常用的find、xargs等命令的简易版。
    在之前的文章中已经将第一章的部分进行了讲解,详情见:
    操作系统入门系列-MIT6.S081(操作系统)学习笔记(一)---- 操作系统介绍与接口示例

    二、实验一

    1.启动xv6

    详见xv6环境的配置和启动
    操作系统入门系列-MIT6.828(操作系统工程)学习笔记(二)----课程实验环境搭建(wsl2+ubuntu+quem+xv6)
    (1)实验系统目录:
    整个实验代码的文件目录如下,我们需要关注:

    1.绿色的”grade-lab-util“可执行文件,它是测试机,用来测试你的实验代码正确性。
    2.kernel文件夹,是xv6的kernel代码
    3.Makefile是用来编译内核的,需要在这里添加参数,我们写的实验代码才能编译进去
    4.user文件夹是我们添加自己实验代码的地方,也就是用户空间	
    

    在这里插入图片描述
    (2)Makefile添加文件
    在这里插入图片描述
    (3)退出xv6
    使用”ctrl+a“,后按x退出
    (4)测试机打分
    需要在lab目录中添加time.txt文件,里面写一个整数,代表你的实验花了多少小时。
    在这里插入图片描述

    2.实现休眠函数

    (1)原题如下:
    在这里插入图片描述
    (2)大致内容为:
    调用“系统调用函数——sleep()”(一次sleep()系统暂停一个tick),实现输入数字,系统暂停该数字次数的ticks。
    参数错误时,要有错误打印
    (3)代码如下:

    // 参考echo.c、grep.c、rm.c的头文件引用
    #include "kernel/types.h"//声明数据类型
    #include "kernel/stat.h"//声明文件数据结构
    #include "kernel/fcntl.h"//open函数的模式参数申明
    #include "user/user.h"//声明各种系统调用函数、以及有用的库函数
    
    
    int main(int argc, char *argv[])
    {
        int number;
    
        //参数数量不对,打印错误信息
        //参数还有其他错误情况,此处仅考虑数量问题
        if(argc <= 1)
        {
            fprintf(2, "usage: sleep number\n");
            exit(1);
        }
    
        number = atoi(argv[1]);//字符串转整数
        sleep(number);//系统调用
    
        exit(0);
    }
    

    (4)结果如下:
    使用官方提供的测试机程序,在lab目录下面运行"./grade-lab-util + 文件名"

    ./grade-lab-util sleep
    

    测试机自主进行测试,如下图:
    在这里插入图片描述
    (5)总结:
    在做实验之前我们需要知道有哪些函数我们是可以调用的,可以查看文件:“user/user.h”

    struct stat;
    
    // system calls
    int fork(void);
    int exit(int) __attribute__((noreturn));
    int wait(int*);
    int pipe(int*);
    int write(int, const void*, int);
    int read(int, void*, int);
    int close(int);
    int kill(int);
    int exec(const char*, char**);
    int open(const char*, int);
    int mknod(const char*, short, short);
    int unlink(const char*);
    int fstat(int fd, struct stat*);
    int link(const char*, const char*);
    int mkdir(const char*);
    int chdir(const char*);
    int dup(int);
    int getpid(void);
    char* sbrk(int);
    int sleep(int);
    int uptime(void);
    
    // ulib.c
    int stat(const char*, struct stat*);
    char* strcpy(char*, const char*);
    void *memmove(void*, const void*, int);
    char* strchr(const char*, char c);
    int strcmp(const char*, const char*);
    void fprintf(int, const char*, ...);
    void printf(const char*, ...);
    char* gets(char*, int max);
    uint strlen(const char*);
    void* memset(void*, int, uint);
    void* malloc(uint);
    void free(void*);
    int atoi(const char*);
    int memcmp(const void *, const void *, uint);
    void *memcpy(void *, const void *, uint);
    

    3.实现父子进程pingpong打印

    (1)原题如下:
    在这里插入图片描述
    (2)大致内容为:
    实现过程:父进程向子进程发送ping,子进程打印该信息,后子进程向父进程发送pong,父进程再打印该信息
    实现结果为:
    在这里插入图片描述

    (3)代码如下:
    分为单管道实现和双管道实现
    单管道实现:

    #include "kernel/types.h"
    #include "kernel/stat.h"
    #include "kernel/fcntl.h"
    #include "user/user.h"
    
    int main(int argc, char *argv[])
    {
        int p[2];
        char buf[512];//用来接收信息的buffer
    
        pipe(p);//父 <=======> 子
    
        //子进程执行if,父进程执行else
        if(fork() == 0)
        {
            read(p[0], buf, sizeof(buf));//阻塞,直到父进程写入管道p内容,后读取父进程的信息
            fprintf(1,"%d: received %s\n", getpid(), buf);//打印父进程的信息
            write(p[1], "pong", 5);//使用p的写通道向父进程发送信息
            close(p[0]);//关闭读通道
            close(p[1]);//关闭写通道
            exit(0);//子进程退出
        }
        else
        {
            write(p[1], "ping", 5);//父进程通过p的写通道向子进程发送信息
            wait((int *)0);//等待子进程结束
            read(p[0], buf, sizeof(buf));//读取p管道中子进程的信息
            fprintf(1,"%d: received %s\n", getpid(), buf);//打印子进程的信息
            close(p[0]);//关闭读通道
            close(p[1]);//关闭写通道
        }
    
        exit(0);
    }
    

    双管道实现:

    #include "kernel/types.h"
    #include "kernel/stat.h"
    #include "kernel/fcntl.h"
    #include "user/user.h"
    
    int main(int argc, char *argv[])
    {
        int p[2];
        int p2[2];
        char buf[512];//用来接收信息的buffer
    
        //向内核申请两个管道
        pipe(p);//父进程写,子进程读:父 ——————> 子
        pipe(p2);//子进程写,父进程读:子 ——————> 父
    
        //子进程执行if,父进程执行else
        if(fork() == 0)
        {
            close(p[1]);//关闭p的写通道,以便于父进程写完可以直接读取
            read(p[0], buf, sizeof(buf));//阻塞,直到父进程写入管道p内容,后读取父进程的信息
            fprintf(1,"%d: received %s\n", getpid(), buf);//打印父进程的信息
            close(p[0]);//关闭p的读通道
            write(p2[1], "pong", 5);//使用p2的写通道向父进程发送信息
            close(p2[1]);//写完关闭写通道
            close(p2[0]);//关闭p2的读通道
            exit(0);//子进程退出
        }
        else
        {
            write(p[1], "ping", 5);//父进程通过p的写通道向子进程发送信息
            close(p[1]);//写完关闭写通道
            close(p2[1]);//关闭p2的写通道,以便于子进程写完可以直接读取
            read(p2[0], buf, sizeof(buf));//阻塞,直到子进程写入管道p2内容,读取子进程的信息
            fprintf(1,"%d: received %s\n", getpid(), buf);//打印子进程的信息
            close(p[0]);//关闭p的读通道
            close(p2[0]);//关闭p2的读通道
        }
    
        exit(0);//结束程序
    }
    

    (4)结果如下:
    在这里插入图片描述
    (5)总结:

    4.使用pipeline实现质数检测

    (1)原题如下:
    在这里插入图片描述
    (2)大致内容为:
    使用pipeline+多进程来检测35以内的质数检测
    这个过程比较难想,但是在纸上推演一下流程就懂了,参考文献如下:Bell Labs and CSP Threads

    参考伪代码:
    在这里插入图片描述
    参考图:
    在这里插入图片描述
    (3)代码如下:

    #include "kernel/types.h"
    #include "kernel/stat.h"
    #include "kernel/fcntl.h"
    #include "user/user.h"
    
    
    void child(int * p)
    {
        int p1[2];
        char num[35];
        int prime;
        int i;
        int ret;
        int number;
    
        pipe(p1);
    
        close(p[1]);
        number = 0;
        for(i=0;i<35;i++)
        {
            ret = read(p[0], num+i, 1);
            if(ret == 0)
            {
                break;
            }
            number++;
        }
    
        if(number == 0)
        {
            exit(0);
        }
    
        prime = num[0];
        printf("prime %d\n", prime);
        close(p[0]);
    
        if(fork() == 0)
        {
            child(p1);
        }
        else
        {
            close(p1[0]);
            for(i=1;i<number;i++)
            {
                if((num[i]%prime)!=0)
                {
                    write(p1[1], num+i, 1);
                }
            }
            close(p1[1]);
            wait((int *)0);
        }
    
        exit(0);
    }
    
    
    int main()
    {
        int prime = 2;
        int p[2];
        int i;
    
        pipe(p);
    
        if(fork() == 0)
        {
            child(p);
        }
        else
        {
            close(p[0]);
            printf("prime %d\n", prime);
            for(i=3;i<=35;i++)
            {
                if((i%prime)!=0)
                {
                    write(p[1], &i, 1);
                }
            }
            close(p[1]);
            wait((int *)0);
        }
    
    
        exit(0);
    }
    

    (4)结果如下:
    在这里插入图片描述
    在这里插入图片描述
    (5)总结:
    核心是算法题,载体是进程树之间使用pipeline进行读写控制

    5.实现find函数(文件查找)

    (1)原题如下:
    在这里插入图片描述
    (2)大致内容为:
    实现目录下搜索文件的功能,要求能够找到该文件夹目录下以及其子目录下的指定文件名,输出所有满足文件的路径。(不要求实现正则表达式)(!!!记得规避".“与”…"子文件)(如何访问文件夹可以参考文件ls.c的实现)

      "  .  "  表示当前目录
      "  ..  "  表示当前目录的上一级目录。
      "  ./  "  表示当前目录下的某个文件或文件夹,视后面跟着的名字而定
      "  ../  "   表示当前目录上一级目录的文件或文件夹,视后面跟着的名字而定。
    

    在这里插入图片描述
    (3)代码如下:

    #include "kernel/types.h"
    #include "kernel/stat.h"
    #include "kernel/fcntl.h"
    #include "kernel/fs.h"
    #include "user/user.h"
    
    char* fmtname(char *path)
    {
      char *p;
    
      // Find first character after last slash.
      for(p=path+strlen(path); p >= path && *p != '/'; p--)
        ;
      p++;
    
      return p;
    }
    
    int find(char *name, char *p)
    {
        int fd;
        int path=0;
        char buf[512];
        struct dirent de;
        struct stat st;
    
        fd = open(p, O_RDONLY);
        if(fd < 0)
        {
            fprintf(2, "fd: can not open %s\n");
            exit(1);
        }
        strcpy(buf, p);
        p = buf+strlen(buf);
        *p++ = '/';
        // printf("%d  %s\n", fd, buf);
    
        while(read(fd, &de, sizeof(de)) == sizeof(de))
        {
            if(de.inum == 0)
            {
                continue;
            }
    
            memmove(p, de.name, DIRSIZ);
            p[DIRSIZ] = 0;
    
            // printf("%s\n", p);
    
            if(stat(buf, &st) < 0)
            {
                printf("ls: cannot stat %s\n", buf);
                continue;
            }
    
            // printf("%s %s %d\n", buf, fmtname(buf), st.type);
            // printf("%d %d %d\n", strcmp(fmtname(buf), name), strcmp(fmtname(buf), "."), strcmp(".", "."));
    
            if (st.type == T_FILE && (strcmp(fmtname(buf), name) == 0))
            {
                printf("%s\n", buf);
                path = 1;
            } 
    
            if (st.type == T_DIR && (strcmp(fmtname(buf), ".") != 0) && (strcmp(fmtname(buf), "..") != 0))
            {
                path = find(name, buf);
            }   
        }
    
        return path;
    }
    
    
    int main(int argc, char *argv[])
    {
        int path;
    
        if(argc < 3)
        {
            fprintf(2, "fd: find [dir] [filename]\n");
            exit(1);
        }
    
        path = find(argv[2], argv[1]);
        if(path == 0)
        {
            fprintf(1, "can not find such file\n");
        }
    
        exit(0);
    }
    

    (4)结果如下:
    在这里插入图片描述
    (5)总结:
    核心知识点是xv6系统下的文件结构体与文件夹结构体,使用递归方法进行实现。

    6.实现xargs函数

    (1)原题如下:
    在这里插入图片描述
    (2)大致内容为:
    实现xargs命令,xargs命令很难简单理解,建议先查一下命令的用法。
    xargs 命令教程
    (3)代码如下:

    #include "kernel/types.h"
    #include "kernel/stat.h"
    #include "kernel/fcntl.h"
    #include "user/user.h"
    #include "kernel/param.h"
    
    
    int getcmd(char *buf, char**arg, int arg_num)
    {
        int i;
        int j;
        j = 0;
        arg[arg_num] = (char *)malloc(30 * sizeof(char));
    
    
        for(i=0; i<512; i++)
        {
            if((buf[i] == ' ') || (buf[i] == 10) || (buf[i] == '\0') || (buf[i] == '\n'))
            {
                // printf("%c %d\n", buf[i], buf[i]);
                arg_num++;
                if(buf[i] == 10)
                {
                    // printf("argmun: %d\n", arg_num);
                    return arg_num;
                }
                if(buf[i] == '\0')
                {
                    // printf("argmun: %d\n", arg_num);
                    return arg_num;
                }
                arg[arg_num] = (char *)malloc(30 * sizeof(char));
                // printf("111\n");
                // printf("%s %p kkk\n", arg[arg_num], arg[arg_num]);
                j = 0;
            }
            else
            {
                // printf("%d %d %c %d\n", j, i, buf[i], arg[arg_num][j]);
                arg[arg_num][j] = buf[i];
                // printf("%d %c \n", j, arg[arg_num][j]);
                j++;
            }
        }
    
        return arg_num;
    }
    
    
    int main(int argc, char *argv[])
    {
        char buf[512];
        int num = 0;
        int ret;
        int cmd = 0;
        char *arg[MAXARG];
        int arg_num;
        arg_num = argc - 1;
        
        int i = 0;
        for(i=0; i<arg_num; i++)
        {
            arg[i] = argv[i+1];
        }
    
        if(argc < 2)
        {
            fprintf(2, "xarg: xarg [process] [args...]\n");
            exit(1);
        }
    
        int j = 0;
        while(1)
        {
            ret = read(0, buf+j, 1);
            if(ret != 1)
            {
                break;
            }
            else if(buf[j] == '\n')
            {
                // printf("read number is : %d\n%s\n", num, buf);
                j = 0;
                num = 0;
                cmd = getcmd(buf, arg, arg_num);
                // printf("\n\n%d %d\n", cmd, arg_num);
                // printf("%s\n%s\n%s\n%s\n", arg[0], arg[1], arg[2], arg[3]);
                if(cmd == arg_num)
                {
                    printf("%d %d\n", cmd, arg_num);
                    printf("no content of stdin\n");
                }
                else
                {
                    if(fork() == 0)
                    {
                        exec(argv[1], arg);
                        fprintf(2, "exec failed\n");
                        exit(1);
                    }
                    else
                    {
                        wait((int *)0);
                    }
                }
            }
            else
            {
                j++;
                num++;
            }
        }
        
        for(i=0; i<cmd; i++)
        {
            free(arg[i]);
        }
        exit(0);
    }
    

    (4)结果如下:
    在这里插入图片描述
    (5)总结:
    在实现字符串的单词分割的时候,对于指针的操作出现问题。
    char **arg的arg[]元素应该动态分配空间,而不是讲temp数组的指针头直接赋值。


    总结

    课程每年可能有变化,本文按照最新的2023计划表进行学习

  • 相关阅读:
    Zookeeper经典应用场景实战
    postman配置中文
    importlib.metadata.version无法在pyinstaller打包后正常工作?
    小学生C++趣味编程 视频集
    微信小程序开发18 持续在线:如何借助云应用持续运行队列消费者?
    P2072 宗教问题
    vue3总结(未完~)
    Java面向对象编程
    [附源码]Python计算机毕业设计Django学生疫情防控信息填报系统
    Python3极简教程(一小时学完)上
  • 原文地址:https://blog.csdn.net/weixin_52042488/article/details/139096440