【操作系统实验】6.s081回顾

344 阅读6分钟

开启掘金成长之旅!这是我参与「掘金日新计划 · 12 月更文挑战」的第29天,点击查看活动详情 这也是第34篇文章

前言

今天重新回顾了一下6.s081。印象中之前是做到lab3还是lab4就没有继续往下做了,因为在前面通关的实验中,对很多机制并没有真正理解,导致越到后面就越懵。但“回顾”指的并不是把实验任务重做一遍,而是发现一些之前没有留意的东西。

新收获

(可能大部分收获在读者看来是“就这?”,但实不相瞒,我之前做实验的时候真的并不知道,只能说前面的实验多少有点运气的成分;但同时,我个人觉得倒也不必悲观或者羞于承认,毕竟人对事物的认识就是这样一个逐步深入的过程。一开始可能对一些旁人觉得理所当然的事情感到一脸懵逼,无所适从;但某一天回过头看却会恍然大悟)

对项目结构的认识

在xv6-labs-2021目录下ls,可以看到如下内容: image.png

  • 为什么教程对于评测单个项目是这么写的:

image.png 因为grade-lab-util(lab1名称)是个经过编译的可执行文件。./指的是当前目录下;该可执行文件里面又有个sleep模块

  • gradelib.py是一个评测脚本,如果评测通不过是可以微调一下里面的代码 (不过适用于机器硬性条件实在达不到的情况,不然把考试参考答案改掉真的是自己骗自己) (不过也侧面反映出,评测的本质也就是一行行可读性强的代码么,之前觉得它是个神秘的黑盒子是因为还没有打开看过)

对功能添加流程的认识以及内容修改

在官方教程的example中,可以找到如下内容:

image.png

以其中的copy.c为例:

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

int main()
{
  char buf[64];

  while(1){
    int n = read(0, buf, sizeof(buf));
    if(n <= 0)
      break;
    write(1, buf, n);
  }
  exit(0);
}

将它写入user目录下,然后在Makefile目录下的UPROGS中添加相应字段,再make qemu编译, 之后再次查看user目录,就会发现生成了.asm(汇编文件),.o(编译后的可执行文件),.d(有default,dependency,dynamic三种解释,从文件的具体内容看,我认为这里是dependency)

而且当make clean 的时候,也会发现这些后缀文件信息被清除的字样。

image.png 由此可以猜测,如果在修改文件之后重新编译,如果新改的文件和原来的没有冲突,那么直接make qemu去覆盖是没问题的,否则就得先清除之前编译的结果,再全部重新编译。

示例代码优化——copy

在运行copy命令的时候,可以看到它设计得并不友好,没有考虑程序退出,必须通过ctrla-x强制退出整个xv6.所以将代码修改如下:

#include "kernel/types.h"
#include "user/user.h"
#include "stdbool.h"

char buf[64];

bool isContains(void){
int n=sizeof(buf)/sizeof(buf[0]);
for(int i=0;i<n;i++){
        if(buf[i]=='q') return true;
}
return false;
}

int main()
{
  while(1){
    int n = read(0, buf, sizeof(buf));
    if(n <= 0)
      break;
    write(1, buf, n);
    if(isContains()) break;
  }
  exit(0);
}

并重新编译。 可以看到输入q后会打印q并退出。

image.png

插曲

这中间有些小插曲。

  • 第一次我凭着对c语言的记忆写好了代码,但报了如下错误: image.png 据说是从C99标准开始才支持bool类型,如果是c语言,要手动加上头文件"stdbool.h"(C++则不用)
  • 对于 expected ';' '.' or ')' before 'buf',反正参数不多,代码短小,不会有修改的顾虑,索性换了种写法,不传参了,改成用全局变量。

其他功能的添加及修改

exec

// exec.c: replace a process with an executable file

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

int
main()
{
  char *argv[] = { "echo", "this", "is", "echo", 0 };

  exec("echo", argv);

  printf("exec failed!\n");

  exit(0);
}

运行结果:

image.png

解析

这一段代码的作用原理是什么呢?

乍一看不理解也没关系,可以通过对代码进行修改并查看输出结果来反向推断。

  • 如果将argv数组中的第一个echo和exec中的“echo”都换成"hello",会提示exec failed;
  • 将exec改回“echo”又可以正常输出 this is echo
  • 在2的基础上将argv中的第二个echo改成hello,会输出 this is hello
  • 在3的基础上将argv的第一个hello改为任意值,比如hehe,输出结果依然是 this is hello
  • 在4的基础上将exec的echo换成ls,会显示: image.png 由此可以推断:
  • argv第1位起到最后一位占位0之间的字符就是输出结果(第0位是什么不重要)
  • exec的第一个参数是调用的别的命令的名称,比如echo,ls等等。之前的hello不是一个合法的命令,自然会fail
修改版

把输出改成我想要的风格

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

int
main()
{
  char *argv[] = { "hehe", "I", "try", "to","modify","an","operating","system", 0 };

  exec("echo", argv);

  printf("exec failed!\n");

  exit(0);
}

image.png

fork

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

int
main()
{
  int pid;

  pid = fork();

  printf("fork() returned %d\n", pid);

  if(pid == 0){
    printf("child\n");
  } else {
    printf("parent\n");
  }
  
  exit(0);
}

可以看到很规整的结果,比如这样: image.png 也可以看到这种现象 image.png 个人觉得这确实间接反映了父进程fork出子进程后,两者各自为王的实质。

image.png

ls

xv6的代码中原本就有了ls,相比之下,教程中的ls代码显得很简单。

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

// list.c: list file names in the current directory

struct dirent {
  ushort inum;
  char name[14];
};

int
main()
{
  int fd;
  struct dirent e;

  fd = open(".", 0);
  while(read(fd, &e, sizeof(e)) == sizeof(e)){
    if(e.name[0] != '\0'){
      printf("%s\n", e.name);
    }
  }
  exit(0);
}

运行后会发现,和xv6中自有的ls相比,它少了右边的数字

image.png image.png

ls中相应代码如下:

image.png

open

// open.c: create a file, write to it.

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

int
main()
{
  int fd = open("output.txt", O_WRONLY | O_CREATE);
  write(fd, "ooo\n", 4);

  exit(0);
}

运行后没有显示输出任何东西,不过ls后会看到确实多了个output.txt文件 (这是个临时文件,重新编译xv6后它会消失) image.png 目前暂时不知道该怎么打开它,xv6不支持vi和vim呢,echo也不能把文件里的内容打出来。(之后可能会自己写一个打印文件的命令。)

2个pipe

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

int
main()
{
  int fds[2];
  char buf[100];
  int n;

  // create a pipe, with two FDs in fds[0], fds[1].
  pipe(fds);
  
  write(fds[1], "this is pipe1\n", 14);
  n = read(fds[0], buf, sizeof(buf));

  write(1, buf, n);

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

// pipe2.c: communication between two processes

int
main()
{
  int n, pid;
  int fds[2];
  char buf[100];
  
  // create a pipe, with two FDs in fds[0], fds[1].
  pipe(fds);

  pid = fork();
  if (pid == 0) {
    write(fds[1], "this is pipe2\n", 14);
  } else {
    n = read(fds[0], buf, sizeof(buf));
    write(1, buf, n);
  }

  exit(0);
}

image.png

管道的内容我之前的笔记中也提过了,这里不再赘述。

redirect

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

// redirect.c: run a command with output redirected

int
main()
{
  int pid;

  pid = fork();
  if(pid == 0){
    close(1);
    open("output.txt", O_WRONLY|O_CREATE);

    char *argv[] = { "echo", "this", "is", "redirected", "echo", 0 };
    exec("echo", argv);
    printf("exec failed!\n");
    exit(1);
  } else {
    wait((int *) 0);
  }

  exit(0);
}

将输出结果重定向到output.txt

后面这几个命令似乎都没什么好改的(改的意义不大),所以只是贴了一下运行结果,然后简单分析了一下。

参考资料

6.s081/Fall 2021