操作系统入门系列-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)代码如下:

c 复制代码
// 参考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 + 文件名"

shell 复制代码
./grade-lab-util sleep

测试机自主进行测试,如下图:

(5)总结:

在做实验之前我们需要知道有哪些函数我们是可以调用的,可以查看文件:"user/user.h"

c 复制代码
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)代码如下:

分为单管道实现和双管道实现

单管道实现:

c 复制代码
#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);
}

双管道实现:

c 复制代码
#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)代码如下:

c 复制代码
#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)代码如下:

c 复制代码
#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)代码如下:

c 复制代码
#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计划表进行学习

相关推荐
虾球xz7 分钟前
游戏引擎学习第55天
学习·游戏引擎
oneouto23 分钟前
selenium学习笔记(二)
笔记·学习·selenium
sealaugh3228 分钟前
aws(学习笔记第十九课) 使用ECS和Fargate进行容器开发
笔记·学习·aws
炭烤玛卡巴卡1 小时前
学习postman工具使用
学习·测试工具·postman
thesky1234561 小时前
活着就好20241224
学习·算法
蜗牛hb1 小时前
VMware Workstation虚拟机网络模式
开发语言·学习·php
汤姆和杰瑞在瑞士吃糯米粑粑2 小时前
【C++学习篇】AVL树
开发语言·c++·学习
虾球xz2 小时前
游戏引擎学习第58天
学习·游戏引擎
LuH11242 小时前
【论文阅读笔记】Scalable, Detailed and Mask-Free Universal Photometric Stereo
论文阅读·笔记
奶香臭豆腐3 小时前
C++ —— 模板类具体化
开发语言·c++·学习