首页 > 其他分享 >MIT6.S081笔记:Lab Xv6 And Unix Utilities

MIT6.S081笔记:Lab Xv6 And Unix Utilities

时间:2022-11-10 22:13:03浏览次数:80  
标签:xargs name MIT6 Utilities Lab 管道 argv path buf

关于 MIT 6.S081

这门课的前身是 MIT 著名的课程 6.828,MIT 的几位教授为了这门课曾专门开发了一个基于 x86 的教学用操作系统 JOS,被众多名校作为自己的操统课程实验。但随着 RISC-V 的横空出世,这几位教授又基于 RISC-V 开发了一个新的教学用操作系统 xv6,并开设了 MIT6.S081 这门课。由于 RISC-V 轻便易学的特点,学生不需要像此前 JOS 一样纠结于众多 x86 “特有的”为了兼容而遗留下来的复杂机制,而可以专注于操作系统层面的开发。

代码环境

Xv6 是在 x86 处理器上( x 即指 x86 )用 ANSI 标准 C 重新实现的 Unix 第六版(Unix V6,通常直接被称为 V6)。

学习笔记

记录学习这门课时的一些笔记,包括阅读相关材料的一些摘录,以及完成 lab 时的思路和代码。

Operating System Interfaces

这章主要介绍了操作系统提供的一些接口,通过这些接口,让用户可以去执行内核中的函数。这些函数称为系统调用

img

shell 的工作原理

以 lab 中的 user/sh.c 为例
main 函数经过一些列的环境检查,fork 出一个子进程,子进程调用 exec 执行 shell 中给出的命令。

  • getcmd 函数接受命令行中的语句,如 ls / 。
  • 子进程执行 runcmd("ls /") 函数,父进程执行 wati(0) 函数等待子进程结束,并回收资源。
  • 子进程在 runcmd 函数中执行到 exec(ls, /) 系统调用函数,完成命令。

过程如下图所示:

img

img

img

命令行中管道符工作原理

管道是内核中的一块 buffer , 以一对文件描述符的方式暴露给进程。

int p[2];
pipe(p);
p[0] // 负责将从管道读出数据
p[1] // 负责从管道写入数据

为了实现下图中划红线的命令:
img

xv6 的 shell 使用如下代码实现:

img

为了更好地理解(user/sh.c 100),也就是上图中所示的代码,写了下图近似代码通过打印日志来分析执行流程。

img

通过输出知道代码的执行过程的进程的父子关系如下图所示:

img

  • 把标准输出关联到管道p[1],运行管道符左侧的命令,grep fork sh.c 会将结果写入的标准输出,但实际上是写到了 p[1];
  • 把标准输入关联到管道pp[0],运行管道符右侧的命令(对于grep fork sh.c),其没有右侧命令,所以结束返回,如果有管道符如(b | c),则会继续创建子进程进行处理。
  • 把标准输入关联到管道p[0],运行管道符右侧的命令(也就是 wc -l),wc -l 会从标准输出读入数据,但其实是从 p[0] 读入。

Other Note

这段讨论的是不使用子进程来执行 | 左右两边的命令,这么做会需要额外的代码来保证左右两边的命令执行的先后顺序。
img

Lab Unix Utilities

这个 lab 的主要任务是实现一些 xv6 的应用层工具。

sleep

实现 sleep 命令,实现代码如下图所示

img

在第 26 行会执行 sleep 系统调用,用于进入到系统的内核态执行 sleep 相关的内核函数,来完成进程的睡眠功能。

pingpong

申请 2 对管道,用于两个进程直接的通信。原理如下图所示:

img

img

注意关闭不使用的管道,xv6 所能使用的文件描述符有限。

primes

img

img

img

find

img
需要注意的是最后一行,当 dirent 的 inum == 0 的时候,说明这个目录是空的。

img
memove() 函数用于复制字符串,当源字符串和目的字符串的地址空间有重合的时候,可以保证源字符能不被破坏的复制到目的字符串。复制完成后,源字符串中的内容发生改变。

代码可仿照 ls.c 来进行实现。注意当遍历到子文件夹时,需要进行递归处理。

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


void find(char* path, char* file_name) {
    char buf[512], *p;
    int fd;
    struct dirent de;
    struct stat st;

    if((fd = open(path, 0)) < 0) {
        fprintf(2, "find: cannot open %s\n", path);
        return;
    }

    if(fstat(fd, &st) < 0) {
        fprintf(2, "find: cannot stat %s\n", path);
        close(fd);
        return;
    }


    while (read(fd, &de, sizeof(de)) == sizeof(de)) {
        strcpy(buf, path);
        p = buf + strlen(buf);
        *p++ = '/';

        if (de.inum == 0) {
            continue;
        }

        memmove(p, de.name, DIRSIZ); // full path ex. path/item_name
        p[DIRSIZ] = 0;
        if(stat(buf, &st) < 0) {
            printf("find: cannot stat %s\n", buf);
            continue;
        }
        
        switch(st.type) {
            case T_FILE: {
                if (strcmp(de.name, file_name) ==0) { // find the file
                    printf("%s\n", buf);
                }
                break;
            }
            case T_DIR: {
                if (strcmp(de.name, ".") != 0 && strcmp(de.name, "..") != 0) {
                    find(buf, file_name); // recursive
                }
                break;
            }
        }
    }
    close(fd);
}

int main(int argc, char *argv[])
{
    if (argc != 3) {
        printf("There should be one path argument and one filename argument\n");
        exit(0);
    }
    
    char* path_name = argv[1];
    char* file_name = argv[2];
    find(path_name, file_name);
    exit(0);
}

xargs

从标准输入读入一行数据,作为 xargs 命令后的命令的输入。

echo hello too | xargs echo bye
# 在终端打印 byte hello too
  • 如上所示代码,从左往右执行。执行完 echo hello too 会向标准输入写入 hello too,由于后接管道符,所以标准输入被重定向到一个管道当中。
  • 随后执行 xargs 命令,其实现代码中的 argc 为 3,argv[] 为 "xargs", "echo", "byte"。
  • 由于 xargs 要实现的功能为将前一个命令写入管道的字符作为其输入。
  • 所以当执行 xargs 程序时,其实际的 argv 数组为 "xargs", "echo", "byte", "hello", "too"。
  • 这个程序的实现最核心的是要为 xargs 命令组装新的 argv 数组。然后 fork 出一个子进程调用 exec 执行 argv[1] 所使用的命令(这里是 echo),然后将其新的参数数组 argv 也传入 exec 函数中。
#include "kernel/types.h"
#include "kernel/stat.h"
#include "kernel/param.h"
#include "user/user.h"

#define MAXLEN 100

int main(int argc, char* argv[])
{
    if (argc <= 1) {
        fprintf(2, "illegal argument number\n");
    }

    char* cmd = argv[1]; // 记录命令行输入的命令
    char buf;
    char argv_list[MAXARG][MAXLEN];
    char* new_argv_list[MAXARG]; // 记录拼接完后的新参数列表

    while(1) {
        memset(argv_list, 0, MAXARG * MAXLEN);

        for (int i = 1; i < argc; ++i) {
            strcpy(argv_list[i-1], argv[i]); // 将除命令名之外的字符串,复制给 argv_list
        }

        int cur_argc = argc - 1;
        int offset = 0;
        int is_read = 0;

        while((is_read = read(0, &buf, 1))) > 0) { // 逐个读入字符
            if (buf == ' ') {
                cur_argc++;
                offset = 0;
                continue;
            }

            if(buf == '\n') {
                break; // 读完当前行
            }

            if (offset == MAXLEN) {
                fprintf(2, "parameter too long\n");
            }

            if (cur_argc == MAXARG) {
                fprintf(2, "too many argument\n");
            }
            // 将从 stdin中读取的参数,填到原 xargs 命令的参数后面
            argv_list[cur_argc][offset++] = buf;
        }

        if (is_read <= 0) {
            break; // 管道符之前的命令执行结果读取结束
        }

        // 组装新的 char* argv[] 参数
        for (int i = 0; i <= cur_argc; ++i) {
            new_argv_list[i] = argv_list[i];
        }

        if (fork() == 0) {
            exec(cmd, new_argv_list);
            exit(1);
        }

        wait(0);
    }

    exit(0);
}

References

词汇

the downside of 缺点

标签:xargs,name,MIT6,Utilities,Lab,管道,argv,path,buf
From: https://www.cnblogs.com/qwerty-ll/p/16855574.html

相关文章