首页 > 其他分享 >MIT 6.S081入门lab1 操作系统及其接口

MIT 6.S081入门lab1 操作系统及其接口

时间:2024-01-25 14:24:12浏览次数:38  
标签:err int S081 pipe lab1 进程 include buf MIT

MIT 6.S081入门lab1 操作系统及其接口

一、参考资料阅读与总结

1.xv6 book书籍阅读(操作系统接口)

a.总览

  • 操作系统的任务: 多个程序之间共享计算机(计算机的硬件管理+任务调度)
  • 操作系统接口: 使用系统调用,调用内核服务为用户端程序提供给服务(即实现对进程的调度和硬件的管理)
  • 操作系统权限管理: 内核使用CPU提供的硬件保护机制(MMU)保证硬件(尤其是内存)的安全

b.进程和内存(fork、exec)

  • 进程使用fork创建进程: 本质是使用pid来判断进程的状态(子进程pid为0,父进程pid > 0)。涉及函数fork
    注意: 通过系统调用fork,父进程的内存和文件描述符表都会被拷贝一份给新的子进程
  • exec系统调用: 本质是在读取可执行文件中的内存镜像并替换到调用进程的内存空间,其需要文件地址(需文件系统)和参数值,使用ELF格式。涉及函数exec
    注意: exec完全替换调用进程的内存,但保留当前进程的文件描述符表
  • shell执行程序: 其位于用户空间、通过getcmd读取输入、并使用fork创建子进程,使用parsecmd处理格式并运行runcmd执行命令,父进程原地等待子进程完成工作。

c. I/O和文件描述符(read()、write() 、close()、open()、dup())

  • 文件描述符(文件): 抽象文件、管道、设备之间的差异->字节驱动程序;每个进程都有着一个从0开始的私有fd,0:标准输入、1:标准输出、2:标准错误。
  • 输入输出(I/O): read()、write() 、close()、open():写入、写出、释放文件、打开文件(模式)。
  • I/O重定向: 本质是fd操作和fork的相互作用
    注意:子进程的文件描述符的重分配并不会影响父进程的文件描述符;每个文件描述符下对应文件的偏移量,在父进程和子进程中是共享的
  • 系统调用dup(): 可以理解为fd的别名。
    注意: 通过过fork或dup的系统调用,从一个相同的原始文件描述符派生出来的两个文件描述符,一定会共享同一个文件的偏移量。否则,文件描述符不会共享偏移量。

d.管道(pipe)

  • 管道: 其本质是一个小的内核缓冲区,是进程间通信的其中一种方式。对于进程来说是一对文件fd,一个写入一个读出(半双工)
    注意: 进程间通信IPC,有两大基本模型:共享内存和消息传递;基于共享内存模型的方式需要内核的干预较少,开销更小,速度更快;基于消息传递模型的方式则适合用于传递小规模数据,而且在分布式系统中易于实现。
  • I/O重定向: 通过dup和pipe的操作可以完成对对标准I/O的重定向,同时,由于为匿名管道,因此xv6中使用pipe创建的管道只能被有亲缘的进程打开。
  • 管道使用注意: 管道在使用后要及时关闭,因为管道的操作是阻塞的;0为读、1为写。
  • shell中管道的应用: 在(user/sh.c:100)中我们可以看到,在获取到管道信息 | 后,shell创建子进程1,将左边命令的输出重定向为管道输入;并创建子进程2,将右边命令的输入重定向为管道输出。
  • 和其他方式的对比:
    与共享内存:
    数据拷贝:管道方式中,数据拷贝从一个进程的用户缓冲区->内核缓冲区->内存,然后再从内存->内核缓冲区->另一个进程的用户缓冲区;而对于共享内存而言,数据拷贝时不需要经过中间的内核缓冲区。(是否需要内核缓冲区);
    内部实现:管道方式中,内部是一个循环队列,可以连续传输比较大的数据;而共享内存就只是一片内存区域,每次写入的大小都是固定的。(传输模式:循环队列-内存区域);
    数据读取:管道方式中,读写数据只能顺序读写,而且也不能反复读取同一数据;共享内存就没有这些限制。(读取限制:顺序<->任意);
    封装性:管道方式具有完备的消息传递和通知机制;共享内存还需要搭配如互斥锁、信号量等同步工具一同使用。(管道为封装好的机制)
    与临时文件:
    管道自动清理相关资源(分配的内核缓冲区),而临时文件则会保留,需要另外清理。(是否自动清理
    管道可以传输任意长的字节流,文件重定向则需要磁盘上有足够空间。(空间属性:内核缓冲区<->磁盘文件
    管道左右两边的程序是可以并行执行的,而文件方式则必须等待第一个命令的完成。(同步执行:非阻塞<->阻塞
    如果你正在实现进程间通信,管道的阻塞读写会比文件的非阻塞语义更有效。
  • Unix风格文件系统: 文件系统接口+ / 根目录+ . 当前目录+.. 上一级目录。
  • 文件名称: links:文件名+inode指针;inode:文件元数据,包括文件类型、长度、磁盘位置、links数量等。
  • 文件操作: mkdir():创建新目录;mknod():创建设备文件;fstat():抽取inode信息到stat中;link():创建新link;unlink():删除link[可用于与open结合创建临时文件]
  • cd实现: 由于cd操作牵扯到改变进程当前目录,因此不能在子京城中处理,其被内嵌到shell中。

f.总结、xv6与真实操作系统的差异

  • Unix贡献核心: 提供了一个强大的抽象,将底层的资源抽象为统一的文件

二、涉及函数

  • shell.c: main->getcmd->(父进程等待)子进程fork1->parsecmd解析命令->runcmd根据类型运行命令
  • ls.c:
    通过fd判定stat的Type种类判定一个路径是文件还是目录;
    使用读取DIRSIZ大小的字节来遍历子文件/子文件目录;
    需要使用buf构造完整路径

三、课程视频观看笔记

  • 操作系统的任务: 抽象硬件资源、硬件多用复用、进程隔离性、文件共享、访问控制系统、性能良好、用途广泛。
  • 操作系统构成: 内核空间(文件系统、进程管理、内存管理、访问控制等)+接口()+用户空间
  • API与函数的不同: API使用的是内核权限,因此可以修改硬件。
  • 代码阅读:copy.c、open.c、fork.c
  • 文件描述符的原理:将其索引到内核中一个维护状态进程的表中(每个进程一张表),这个表表示了每个进程中存在的文件描述符。
  • (fork)父进程和子进程的异同: 有相同的代码、数据段和栈,但是地址空间不同,pid不同(xv6);注意fd是复制的
  • (exec)在程序内执行外部文件: 覆盖当前进程(内存层级,可以理解为ps的覆盖+跳转),并使用命令行参数传递;注意fd是保留的
  • fork和exec互动: 运行fork创建子进程,之后在子进程中使用exec执行目标程序(shell执行)。同时父进程可以使用wait查看子进程的退出状态。
  • 父进程和子进程的相互等待关系: 父进程可以使用wait实现对子进程的等待并返回子进程id,而子进程无法等待父进程。
  • I/O重定向技巧: 使用fork创建子进程,之后使用close+open实现对fd的IO重定向,最后使用exec执行需要的程序。

四、完成lab及其代码

sleep.c
#include "kernel/types.h"
#include "kernel/stat.h"
#include "user/user.h"

int
main(int argc, char *argv[])
{
  if(argc != 2)
  {
    fprintf(2, "Usage: sleep <ticks>\n");
    exit(1);
  }

  int ret = sleep(atoi(argv[1]));
  exit(ret);
}
pingping.c
#include "kernel/types.h"
#include "kernel/stat.h"
#include "user/user.h"

int
main(int argc, char *argv[])
{
    int pid;
    char buf[]= {'m'};
    int err;
    // if(argc != 1)
    // {
    //     printf("Usage: pingpong\n");
    //     exit(1);
    // }
    int pipe_P2C[2], pipe_C2P[2];
    pipe(pipe_P2C);
    pipe(pipe_C2P);

    int ret = fork();

    if (ret == 0) {
        // child 
        pid = getpid();
        err = close(pipe_P2C[1]);
        if (err == -1) 
        {
            printf("child close pipe_P2C fail\n");
        }
        err = close(pipe_C2P[0]);
        if (err == -1) 
        {
            printf("child close pipe_C2P fail\n");
        }
        err = read(pipe_P2C[0], buf, 1);
        if (err == -1) 
        {
            printf("child read pipe_P2C fail\n");
        }
        printf("%d: received ping\n",pid);
        err = write(pipe_C2P[1], buf, 1);
        if (err == -1) 
        {
            printf("child write pipe_C2P fail\n");
        }
        exit(0);
    } 
    else 
    {
        // parent
        pid = getpid();
        err = close(pipe_C2P[1]);
        if (err == -1) 
        {
            printf("parent close pipe_C2P fail\n");
        }
        err = close(pipe_P2C[0]);
        if (err == -1) 
        {
            printf("parent close pipe_P2C fail\n");
        }

        err = write(pipe_P2C[1],buf, 1);
        if (err == -1) 
        {
            printf("parent write pipe_P2C fail\n");
        }

       err = read(pipe_C2P[0], buf, 1);
        if (err == -1) 
        {
            printf("parent read pipe_C2P fail\n");
        }
        printf("%d: received pong\n",pid);

        exit(0);
    }
}
prime.c
#include "kernel/types.h"
#include "kernel/stat.h"
#include "user/user.h"

#define READ 0
#define WRITE 1
#define STDIN 0
#define STOUT 1
#define  STERR 2
#define PRIMES_NUMS 35
#define END_SIGNAL -1

int primes_process(int listenfd)
{
    int ret;
    int i_primeNums;
    int writePipe[2]; //写管道
    int buf;

    read(listenfd, &i_primeNums, sizeof(i_primeNums));
    if (i_primeNums == END_SIGNAL) 
    {
        exit(0);
    }
    fprintf(STOUT, "prime %d\n", i_primeNums);
    
    pipe(writePipe);
    
    ret = fork();
    if(ret == 0)
    {
        close(writePipe[WRITE]);
        close(listenfd);
        primes_process(writePipe[READ]);
        exit(0);
    }
    else
    {
        close(writePipe[READ]);
        while (read(listenfd, &buf, sizeof(buf)) && buf != END_SIGNAL) 
        {
            if(buf % i_primeNums != 0)
            {
                write(writePipe[WRITE], &buf, sizeof(buf));
            }
        }
        buf = -1;
        write(writePipe[WRITE], &buf, sizeof(buf));
        wait(0);
        exit(0);
    }
}

int
main(int argc, char *argv[])
{
    int err;
    if(argc != 1)
    {
        printf("Usage: primes\n");
        exit(1);
    }
    int i;
    int pipe_p[2];
    
    err = pipe(pipe_p);
    if (err == -1) {
        fprintf(STERR,"pipe create fail\n");
        exit(1);
    }
    int ret = fork();
    if (ret == 0) 
    {
        // child
        close(pipe_p[WRITE]);
        primes_process(pipe_p[READ]);
        exit(0);
    } else
    {
        // parent
        close(pipe_p[READ]);
        for ( i = 2; i <= PRIMES_NUMS; i++) 
        {
            write(pipe_p[WRITE], &i, sizeof(i));
        }
        i = END_SIGNAL;
        write(pipe_p[WRITE], &i, sizeof(i));
        close(pipe_p[WRITE]);
        wait(0);
        exit(0);
    }
}
find.c
#include "kernel/types.h"
#include "kernel/stat.h"
#include "user/user.h"
#include "kernel/fs.h"

void 
find(char *path, char *target)
{
    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;
  }

    switch(st.type){
    case T_FILE:
    if (strcmp(path+strlen(path) - strlen(target), target) == 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;
        memmove(p, de.name, DIRSIZ);
        p[DIRSIZ] = 0;
        if(stat(buf, &st) < 0){
        printf("find: cannot stat %s\n", buf);
        continue;
        }
        if(strcmp(buf+strlen(buf) - 2, "/." ) != 0 && strcmp(buf+strlen(buf) - 3, "/..") != 0)
        {
            find(buf, target);
        }
    }
    break;
    }
    close(fd);
}

int
main(int argc, char *argv[])
{
    char target[512];
    if(argc != 3 )
    {
        printf("Usage: find [path] [target filename]\n");
        exit(1);
    }
    target[0] = '/';
    strcpy(target+1, argv[2]);
    find(argv[1], target);

    exit(0);
}
xarg.c
// Shell.

#include "kernel/types.h"
#include "user/user.h"
#include "kernel/fcntl.h"
#include "kernel/param.h"
#include "user/user.h"

// Parsed command representation
#define   BUF_SIZE 1024

int
main(int argc, char* argv[]) 
{
  int pid, bufIndex = 0;
  char bufp, buf[BUF_SIZE+1] = {0}, *xargv[MAXARG] = {0};

  if(argc < 2) 
  {
    fprintf(2, "Useage: xargs <command> \n");
    exit(1);
  }

// read  argv
  for (int i = 1; i < argc; i++)
  {
    xargv[i - 1] = argv[i];
  }
// read stdin
  while(read(0,&bufp, 1) > 0)
  {
    if (bufp == '\n') {
      buf[bufIndex] = '\0';

      if((pid = fork()) < 0) {
        fprintf(2, "fork error \n");
        exit(1);
      } else if ( pid == 0)
      {
        //child
        xargv[argc - 1] = buf;
        xargv[argc] = '\0';
        exec(argv[1], xargv);
      } else
      {
        //parent
        wait(0);
        bufIndex = 0;
      }
    }
    else
    {
      buf[bufIndex++] = bufp;
    }
  }
  exit(0);
}
核心难点在与xarg.c饥饿prime.c,一个是对字符串的操作、一个是算法递归的理解。

参考文献:

2020版xv6手册:https://pdos.csail.mit.edu/6.S081/2020/xv6/book-riscv-rev1.pdf
xv6手册与代码笔记:https://zhuanlan.zhihu.com/p/350949057
xv6阅读笔记:https://ghostasky.github.io/2022/07/12/XV6/
xv6手册中文版:http://xv6.dgs.zone/tranlate_books/book-riscv-rev1/c1/s3.html
28天速通MIT 6.S081操作系统公开课:https://zhuanlan.zhihu.com/p/632281381
MIT6.s081操作系统笔记:https://juejin.cn/post/7005116617478668296

标签:err,int,S081,pipe,lab1,进程,include,buf,MIT
From: https://www.cnblogs.com/David-Dong/p/17983576

相关文章

  • SUBMIT指定用户名错误
    1、SUBMIT说明 在ABAP中,SUBMIT关键字用于运行另一个ABAP程序。通过SUBMIT关键字,可以在当前程序内部调用其他程序,而无需关闭当前程序。SUBMIT语句的一般语法如下:"--------------------@斌将军--------------------SUBMIT<program>[VIASELECTION-SCREEN|USINGSELECTION-SE......
  • Linux ulimit详解
    ulimit命令用来设置系统的资源限制。1.可以使用ulimit-a来查看系统各种资源的限制。如下:>>>ulimit-acorefilesize(blocks,-c)0datasegsize(kbytes,-d)unlimitedschedulingpriority(-e)0filesize(blocks,......
  • Apache DolphinScheduler社区新晋Committer:伏长海的开源之旅
    **文章作者:**伏长海文章整理:曾辉个人介绍大家好,我是伏长海,目前在珍岛集团担任大数据开发工程师职位!**GitHubID:**fuchanghai在算法平台后端的研究领域耕耘了三年,任务调度方面也有一年半的深入探索。闲暇时,平时喜欢睡懒觉,偶尔阅读书籍,以此充实自己的生活。为社区做了哪些贡......
  • MIT6.830-Lab1
    TupleDesc类TupleDesc类用来存储表结构,使用静态内部类TDItem封装字段类型和字段名称。Tuple类Tuple类用来存储具体的数据行,使用Filed接口数组存放不同字段类型的数据,使用TupleDesc成员变量存放与该数据行关联的表结构信息,使用RecordId成员变量存放该数据行的行号和所处的数......
  • [MIT 6.S081] Lab: system calls
    Lab:systemcalls前言这次实验是实现内核中的两个syscall:trace和sysinfo。回顾一下第三节课,用户态的进程想要执行某个系统调用,例如exex(init,argv),首先会将init和argv的地址分别存放在a0和a1寄存器中,然后a7存放所要调用的系统调用,最后执行ecall。之后要结......
  • [MIT 6.S081] Lab: Xv6 and Unix utilities
    Lab:Xv6andUnixutilitiesGradesleepsleep格式如下sleep5这边需要使用kernal/stat.h中的sleep系统调用,并将参数转化为传入。#include"kernel/types.h"#include"kernel/stat.h"#include"user/user.h"intmain(intargc,char*argv[]){if(......
  • linux修改max user processes limits
    突破ulimit限制修改普通用户单个用户可同时运行的最大进程数(默认为4096)[root@xxxdevops]#cat/etc/security/limits.d/20-nproc.conf#Defaultlimitfornumberofuser'sprocessestoprevent#accidentalforkbombs.#Seerhbz#432903forreasoning.*......
  • 封装的子组件,才fromitem中,子组件的变化,如何被form监听到。
    子组件,应该有个onChange事件。问题:在嵌入自定义组件时,需要将子组件的onChange方法暴露出去。原因:1.form会监听的onChange方法和value变量(form是的一个属性)2.其它基于封装的组件如:,自定义的方法名必须叫onChange,变量名必须叫value,否则自定义组件的状态变化不能被form表单的onCha......
  • mitmproxy
    mitmproxy是一个代理工具(软件安装或Python模块安装),实现代理请求(拦截请求或修改请求)这里介绍python的模块使用这里强烈推荐这个安装第三方库的软件:链接:https://pan.baidu.com/s/1L56TY68VNrw54go8eTxjkg?pwd=pg22提取码:pg22pipinstallmitmproxy基本就没有报错了。启动启动mit......
  • vue+tsc+noEmit导致打包报TS类型错误问题及解决方法
    当我们新建vue3项目,package.json文件会自动给我添加一些配置选项,这写选项基本没有问题,但是在实际操作过程中,当项目越来越复杂就会出现问题。本文列举一个目前我遇到的一个问题:打包后报了一堆TS类型错误,怎么消除这些错误?项目环境:Vue3+Vite+TS当项目进行打包时候,突然发现终端......