MIT6s.081学习笔记——第一章入门篇

本次操作系统学习分为三部分

1.搭建环境,熟悉shell操作,跑第一章实验2023年9月4日

2.调试git,顺利上传push文件2023年9月5日

3.阅读第一章(视频,文字)完成实验,代码在后面2023年9月6-7日

  • 1.搭建环境,熟悉shell操作,跑第一章实验2023年9月4日

    Qemu 是纯软件实现的虚拟化模拟器,几乎可以模拟任何硬件设备,我们最熟悉的就是能够模拟一台能够独立运行操作系统的虚拟机,虚拟机认为自己和硬件打交道,但其实是和 Qemu 模拟出来的硬件打交道,Qemu 将这些指令转译给真正的硬件。

    # 按官方指南手册 安装必须的工具链
    $ sudo apt-get install git build-essential gdb-multiarch qemu-system-misc gcc-riscv64-linux-gnu binutils-riscv64-linux-gnu
    # 单独移除掉qemu的新版本, 因为不知道为什么build时候会卡壳
    $ sudo apt-get remove qemu-system-misc
    # 额外安装一个旧版本的qemu
    $ wget https://download.qemu.org/qemu-5.1.0.tar.xz
    $ tar xf qemu-5.1.0.tar.xz
    $ cd qemu-5.1.0
    $ ./configure --disable-kvm --disable-werror --prefix=/usr/local --target-list="riscv64-softmmu"
    
    ERROR: glib-2.48 gthread-2.0 is required to compile QEMU
    首先我们使用:apt-cache search glib2 看看应该安装哪个库
    sudo apt-get install libglib2.0-dev
    继续使用  apt-cache search pixman, 看看应该安装哪个库
    sudo apt-get install libpixman-1-dev
    
    $ make
    $ sudo make install
    
    # 克隆xv6实验仓库
    $ git clone git://g.csail.mit.edu/xv6-labs-2020
    $ cd xv6-labs-2020
    $ git checkout util
    
    # 进行编译
    $ make qemu
    # 编译成功并进入xv6操作系统的shell
    $ xv6 kernel is booting
    
    $ hart 2 starting
    $ hart 1 starting
    $ init: starting sh
    $(shell 等待用户输入...)
    
    # 尝试一下ls指令
    $ ls
    .              1 1 1024
    ..             1 1 1024
    README         2 2 2226
    xargstest.sh   2 3 93
    cat            2 4 23680
    echo           2 5 22536
    forktest       2 6 13256
    grep           2 7 26904
    init           2 8 23320
    kill           2 9 22440
    ln             2 10 22312
    ls             2 11 25848
    mkdir          2 12 22552
    rm             2 13 22544
    sh             2 14 40728
    stressfs       2 15 23536
    usertests      2 16 150160
    grind          2 17 36976
    wc             2 18 24616
    zombie         2 19 21816
    console        3 20 0
    

    至此,进入实验环境搭建完毕,进入该操作系统

    To quit qemu type: Ctrl-a x(按Ctrl-a松开后,再按x)

    代码makefile文件在linux的shell写,再进入Xv6

    $ make qemu

    westin@westin-virtual-machine:~/MIT/MIT LAB/xv6-labs-2020$ ./grade-lab-util sleep
    make: “kernel/kernel”已是最新。
    == Test sleep, no arguments == sleep, no arguments: OK (2.0s) 
    == Test sleep, returns == sleep, returns: OK (1.2s) 
    == Test sleep, makes syscall == sleep, makes syscall: OK (1.0s) 
    

    touch pingping//创建文件

    vim pingping//进入编写

    所有能够调用的系统函数都在**user/user.h**里. 一共大概就20来个, 先扫一眼有个印象. 具体需要的时候可以再跳转到具体实现文件里去读.

    vim的操作

    dd:删除游标所在的一整行(常用)

    d1G:删除光标所在到第一行的所有数据

    dG:删除光标所在到最后一行的所有数据

    写find文件容易出问题,qemu出故障

  • 2.调试git,顺利上传push文件2023年9月5日

  • 3.阅读第一章(视频,文字)代码在后面2023年9月6-7日

    • 一个进程可以使用 fork 系统调用创建一个新的进程

    • 调用普通函数时,你所调用的函数并没有对于硬件的特殊权限;触发系统调用到内核中,内核中的具体实现会具有这些特殊的权限,这样就能修改敏感的和被保护的硬件资源,比如访问硬件磁盘。

    • fork会拷贝当前进程的内存,并创建一个新的进程,这里的内存包含了进程的指令和数据。在原始的进程中,fork系统调用会返回大于0的整数,这个是新创建进程即子进程的ID。而在新创建的进程中,fork系统调用会返回0。除了拷贝内存以外,fork还会拷贝文件描述符表单

    • Shell通过fork创建一个新进程运行系统调用,不然exec之后就无了

    • fork 和 exec 是独立的调用。exec系统调用会从指定的文件中读取并加载指令,并替代当前调用进程的指令,我调用的是exec,实际上运行了echo,然后就没有然后了,只有出错了才返回。exec系统调用代价比较高,它需要访问文件系统,访问磁盘,分配内存,并读取磁盘中echo文件的内容到分配的内存中,分配内存又可能需要等待内存释放,所以先打印parent,再echo。

    • pipe是一个小的内核缓冲区

    • 操作系统应帮助应用程序获得高性能

    • 用户空间的程序会与Kernel中的文件系统交互,文件系统再与磁盘交互

    • 通过open获得的文件描述符,再调用fork系统调用,子进程能够访问到在fork之前创建的文件描述符fd,

    • 高阶的编程语言都离系统调用较远,它想要提供可以在多个操作系统上运行的可移植的环境,所以它们不能依赖特定的系统调用。

    • XV6运行在QEMU模拟器之上。使得在没有特定硬件的前提下运行XV6

    • make qemu,这条指令会编译并构建xv6内核和所有的用户进程,并将它们运行在QEMU模拟器下。

    • 当一个程序启动时,文件描述符0连接到console的输入,对应读,文件描述符1连接到了console的输出,对应写

    • 字节流就是一段连续的按照字节的长度读取的数据

    • 文件描述符本质上对应了内核中的一个表单数据。内核维护了每个运行进程的状态,并为每一个运行进程保存一个表单,表单的key是文件描述符。

    • cat sleep.c//显示文件内容

      //实验sleep
      #include "kernel/types.h"
      #include "user/user.h"
      
      int main(int argc, char *argv[]) {
      //int argc:一个整数类型的参数,表示命令行参数的数量。argc 是 "argument count" 的缩写,用于指示程序运行时传递给它的命令行参数的数量。
      //char *argv[]:一个字符指针数组,表示命令行参数的内容。argv 是 "argument vector" 的缩写,它包含了每个命令行参数的字符串。通常,argv[0] 存储的是程序的名称,而 argv[1]、argv[2] 等存储的是用户提供的参数。  
      	if (argc != 2) {
          fprintf(2, "usage: sleep [ticks num]\n");
      //如果命令行参数数量不正确,那么它使用 fprintf 函数向标准错误流输出一条错误消息。
      //错误消息是 "usage: sleep [ticks num]"    
      	exit(1);
        }
        // atoi sys call guarantees return an integer
        int ticks = atoi(argv[1]);
        int ret = sleep(ticks);
        exit(ret);
      }
      
      //实验pingpong
      #include "kernel/types.h"
      #include "user/user.h"
      
      int main(int argc, char *argv[]) {
        int pid;
        int pipes1[2], pipes2[2];
        char buf[] = {'a'};
        pipe(pipes1);
        pipe(pipes2);
      
        int ret = fork();
      
        // parent send in pipes1[1], child receives in pipes1[0]
        // child send in pipes2[1], parent receives in pipes2[0]
        // should have checked close & read & write return value for error, but i am lazy
        if (ret == 0) {
          // i am the child
          pid = getpid();
          close(pipes1[1]);
          close(pipes2[0]);
          read(pipes1[0], buf, 1);
      //read它会阻塞当前进程,直到从 pipes1[0] 中读取到至少一个字节的数据。
      //一旦有数据可读,read 调用将数据读'a'取到 buf[0] 中,并返回实际读取的字节数(在这里是 1)。
      //当read操作完成后,buf 数组中的字符 'a' 将被从管道中读取的字符新'a'覆盖掉
          printf("%d: received ping\n", pid);
          write(pipes2[1], buf, 1);
          exit(0);
        } else {
          // i am the parent
          pid = getpid();
          close(pipes1[0]);
          close(pipes2[1]);
          write(pipes1[1], buf, 1);
      //在父进程中,write 用于将存储在 buf 中的字符写入 pipes1 的写入端 pipes1[1]
      //从而向子进程发送"ping"消息的第一个字符,即"a"。
          read(pipes2[0], buf, 1);
          printf("%d: received pong\n", pid);
          exit(0);
        }
      }
      //父子进程通过两个管道实现了"ping-pong"效果,
      //即父进程发送"ping"给子进程,子进程接收到后发送"pong"给父进程。
      //这个示例演示了如何使用管道在不同进程之间进行通信。
      

      在这里插入图片描述

      //实验素数
      //馈送数字 2、3、4、...、1000 进入管道的左端:生产线中的第一个过程 消除 2 的倍数,第二个消除 3 的倍数,第三个消除 5 的倍数
      #include "kernel/types.h"
      #include "user/user.h"
      
      /*
       * Run as a prime-number processor
       * the listenfd is from your left neighbor
       */
      void runprocess(int listenfd) {
        int my_num = 0;
        int forked = 0;
        int passed_num = 0;
        int pipes[2];
        while (1) {
          int read_bytes = read(listenfd, &passed_num, 4);
      //read_bytes 将包含实际读取的字节数,可以用来检查读取是否成功以及读取了多少数据。
          // left neighbor has no more number to provide
          if (read_bytes == 0) {
            close(listenfd);
            if (forked) {
              // tell my children I have no more number to offer
              close(pipes[1]);
              // wait my child termination
              int child_pid;
              wait(&child_pid);
            }
            exit(0);
          }
      
          // if my initial read
          if (my_num == 0) {
            my_num = passed_num;
            printf("prime %d\n", my_num);
          }
      
          // not my prime multiple, pass along
          if (passed_num % my_num != 0) {
            if (!forked) {
              pipe(pipes);
              forked = 1;
              int ret = fork();
              if (ret == 0) {
                // i am the child
                close(pipes[1]);
                close(listenfd);
                runprocess(pipes[0]);
              } else {
                // i am the parent
                close(pipes[0]);
              **}
            }
      
            // pass the number to right neighbor
            write(pipes[1], &passed_num, 4);
          }
        }
      }
      
      int main(int argc, char *argv[]) {
        int pipes[2];
        pipe(pipes);
        for (int i = 2; i <= 35; i++) {
          write(pipes[1], &i, 4);//整数是4字节,因此写入4个字节的数据
      //&i 不是要写入的数据,而是用来指定一个存储在内存中的**整数** i 的地址。
      //write 系统调用实际上是将内存中的数据写入文件描述符,而不是将地址本身写入。
        }
        close(pipes[1]);
        runprocess(pipes[0]);
        exit(0);
      }
      /*父进程和子进程在 fork 后会共享文件描述符。意味着在子进程中修改了文件描述符
      会影响到父进程,反之亦然。这是因为子进程是父进程的复制,包括文件描述符表。
      本项目的fork出来的子进程的管道是越来越短的,但由于文件描述符相同,所以write会把数字传到每一个父子进程的管道中*/
      
      //实验find,找到当前和子目录下的所有指定文件
      #include "kernel/types.h"
      #include "kernel/fcntl.h"
      #include "kernel/stat.h"
      #include "kernel/fs.h"
      #include "user/user.h"
      
      /*
      	将路径格式化为文件名
      */
      char* fmt_name(char *path){
        static char buf[DIRSIZ+1];
        char *p;
      
        // Find first character after last slash.
        for(p=path+strlen(path); p >= path && *p != '/'; p--);
        p++;
        memmove(buf, p, strlen(p)+1);
        return buf;
      }
      //fmt_name 函数用于将路径格式化为文件名。它找到路径中的最后一个斜杠(/)后的字符,并将该字符后的部分作为文件名返回。
      /*
      	系统文件名与要查找的文件名,若一致,打印系统文件完整路径
      */
      void eq_print(char *fileName, char *findName){
      	if(strcmp(fmt_name(fileName), findName) == 0){
      		printf("%s\n", fileName);
      	}
      }
      /*
      	在某路径中查找某文件
      */
      void find(char *path, char *findName){
      	int fd;
      	struct stat st;	
      	if((fd = open(path, O_RDONLY)) < 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;
      	}
      	char buf[512], *p;	
      	struct dirent de;
      	switch(st.type){	
      		case T_FILE:
      			eq_print(path, findName);			
      			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)){
      				//printf("de.name:%s, de.inum:%d\n", de.name, de.inum);
      				if(de.inum == 0 || de.inum == 1 || strcmp(de.name, ".")==0 || strcmp(de.name, "..")==0)
      					continue;				
      				memmove(p, de.name, strlen(de.name));
      				p[strlen(de.name)] = 0;
      				find(buf, findName);
      			}
      			break;
      	}
      	close(fd);	
      }
      
      int main(int argc, char *argv[]){
      	if(argc < 3){
      		printf("find: find <path> <fileName>\n");
      		exit(0);
      	}
      	find(argv[1], argv[2]);
      	exit(0);
      }
      
          $echo > b//创建一个空文件b
          $mkdir a//创建一个a目录
          $echo > a/b//a目录下创建空文件b
          $find . b//在当前目录以及子目录下找到所有空文件b
      
      /*实验xargs,主要功能是读取标准输入中的文本行,将每行拆分为单词,
      并将这些单词作为参数传递给一个指定的命令来执行。*/
      #include "kernel/types.h"
      #include "user/user.h"
      
      int main(int argc, char *argv[]){
          int i;
          int j = 0;
          int k;
          int l,m = 0;
          char block[32];
          char buf[32];
      //buf 是一个用于保存单词的缓冲区,block 用于从标准输入读取数据。
          char *p = buf;
          char *lineSplit[32];
          for(i = 1; i < argc; i++){
              lineSplit[j++] = argv[i];
          }
      //将命令行参数(不包括程序名称本身)存储在 lineSplit 数组中,以备后续使用。
          while( (k = read(0, block, sizeof(block))) > 0){
              for(l = 0; l < k; l++){
                  if(block[l] == '\n'){
                      buf[m] = 0;
                      m = 0;
                      lineSplit[j++] = p;
                      p = buf;
                      lineSplit[j] = 0;
                      j = argc - 1;
                      if(fork() == 0){
                          exec(argv[1], lineSplit);
                      }                
                      wait(0);
                  }else if(block[l] == ' ') {
                      buf[m++] = 0;
                      lineSplit[j++] = p;
                      p = &buf[m];
                  }else {
                      buf[m++] = block[l];
                  }
              }
          }
          exit(0);
      }
      
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值