[MIT6.S081]梦的开始 · Lab1 · Xv6 and Unix utilities

177 阅读4分钟

Lab1 讲义
声明:我的做法可能并非最优,请勿作为标准。望大家能一起讨论,共同进步。

正如Lab讲义中所述,将自己的代码分享并不是一个好的做法,因此我在本文及接下来的一系列文章中并不打算贴出我的源码,而是会记录自己在做每一题时遇到的一些bug或者奇奇怪怪的想法以及部分代码(但不可否认,对于自学这个课程且没人可以一起讨论的同学来说,若有一份“答案”能够在做Lab的过程中对照,既能在一定程度上避免在某题上完全卡住而无法进行前进,也能够与自己的代码相比较,从而引发自己的的实现是否还能有所优化的思考。因此还是非常感谢分享自己做Lab心得的同学们,给予了我许多帮助)。

Boot xv6 (easy)

第一个实验内容是配置Lab所需要的环境
我选择在Ubuntu20.04(Ubuntu20.04以下版本的)上完成这个Lab。 我还有一篇文章是关于我如何搭建自己的学习环境的,感兴趣的同学可以去看看哦。

sleep (easy)

这题确实比较简单,我就不多说了。我遇到的一个小问题是忘记了argv[i]是char类型而不是整型的,如果直接将argv[i]传递给sleep()系统调用,系统休眠的时间就不对了。

pingpong (easy)

第一版程序执行,$符号在子进程输出完成前就输出了,如code 1所示

# code 1
5: received ping
6:$  received pong

这是因为父进程在子进程结束前就结束了,shell得知pingpong进程结束,就输出了$符号。解决这个bug的方法是让父进程等待子进程结束后才结束自身。

primes (hard)

本题是多进程与递归的结合,确实蛮有趣的。 在这题中我主要有两个收获:

  1. 虽然我在学习操作系统的就知道使用fork()调用,会将父进程的所有东西复制一份给子进程,但在做Lab最开始,我并没有很明确地意识到:管道被复制后,就有四个文件描述符;
  2. 一开始不知道多余的管道文件描述符需要先关闭,即关闭父进程的管道读端与子进程的管道写端。由于read()调用会在管道的写端关闭后返回0,因此我们可以用关闭管道写端来标识输入的完成,而在子进程用read()调用的返回值为0得知父进程的输入结束。

最后我还在思考一个问题:递归函数的形参只传管道的读端与将两端都传递有什么不一样吗?哪个更好?

find (moderate)

xargs (moderate)

我在这题上时间主要花费在了两个地方:

  1. 字符串的处理。c语言与微处理器课程结束后,就没怎么使用过纯C,对于C语言的字符串处理已经生疏了,重新捡起来花费了一些时间;
  2. 如何将xargs命令的参数与管道传过来的数据结合,并作为实参传递给exec()调用。习惯了使用C++,因此我最开始的想法是能不能类似使用vector<string>这样,通过动态内存分配来获得大小刚刚好的二维char类型数组char**,但这样的做法是十分低效的。更好的做法是,估计每个参数可能的最长长度和参数的最大数量,为其分配固定大小的内存。因此一种做法是
char args[16][128];

如此一来,我们限定了每个参数长度不得超过127(最后一位放'\0'标识字符串结束),总参数个数不得超过15(最后一位要放0NULL表示参数输入结束,注意这里必须使用0来标识参数输入的结束,)那有没有更好的做法,能让其适应性更强呢?有,我们可以不限定二维数组第一维的大小,如此我们可以只估计所有参数最长的总字符数量,但如何给这个指针分配空间呢?我们可以先用一个一维数组来依次放入所有的参数,再将二维数组指针分别指向一维数组中各个参数的起始地址上。但此方法依旧限定了单个参数的最大长度不能超过127。 需要注意的是标准输出中同一行参数是传给同一次命令执行的,而下一行参数则应该在下一次命令调用时传入。

char *args[128];
char *ptr_args = args;
int i;
// 放入xargs的参数
for(i = 1; i < argc; i++) {
    *ptr_args = argv[i];
    ptr_args++;
}

char **ptr_atrs_tmp = ptr_args; 
char buf[2048];       // 一维数组依次存放一行里的所有参数
char *ptr_begin = buf;// 指向本参数的开头
char *ptr_end = buf;  // 指向本参数的结尾
while(read(0, ptr_end, 1) != 0) {
// 逐个字符读入
    if(*ptr_end == ' ' || *ptr_end == '\n') {
        *(ptr_args++) = ptr_begin;
        ptr_begin = ptr_end + 1;
        if(*ptr_end == '\n') {
            // 如果是换行符,说明已经完成读入这次命令的所有参数(注意,即使只有一行参数,最后一个字符也是换行符)
            *ptr_end = '\0';
            *ptr_args = 0; // 最后设置为NULL
            run(argv[1], args);			
            ptr_args = ptr_args_tmp;
            ptr_begin = buf;
            ptr_end = buf;
        }
        *ptr_end = '\0';
    }
    ptr_end++;
}

当然依旧不能忘记要在所有子进程结束后,再退出父进程。

参考

juejin.cn/post/702464…
同时,非常感谢所有分享自己做Lab心得的同学们!