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)
本题是多进程与递归的结合,确实蛮有趣的。 在这题中我主要有两个收获:
- 虽然我在学习操作系统的就知道使用fork()调用,会将父进程的所有东西复制一份给子进程,但在做Lab最开始,我并没有很明确地意识到:管道被复制后,就有四个文件描述符;
- 一开始不知道多余的管道文件描述符需要先关闭,即关闭父进程的管道读端与子进程的管道写端。由于read()调用会在管道的写端关闭后返回0,因此我们可以用关闭管道写端来标识输入的完成,而在子进程用read()调用的返回值为0得知父进程的输入结束。
最后我还在思考一个问题:递归函数的形参只传管道的读端与将两端都传递有什么不一样吗?哪个更好?
find (moderate)
xargs (moderate)
我在这题上时间主要花费在了两个地方:
- 字符串的处理。c语言与微处理器课程结束后,就没怎么使用过纯C,对于C语言的字符串处理已经生疏了,重新捡起来花费了一些时间;
- 如何将xargs命令的参数与管道传过来的数据结合,并作为实参传递给exec()调用。习惯了使用C++,因此我最开始的想法是能不能类似使用
vector<string>这样,通过动态内存分配来获得大小刚刚好的二维char类型数组char**,但这样的做法是十分低效的。更好的做法是,估计每个参数可能的最长长度和参数的最大数量,为其分配固定大小的内存。因此一种做法是
char args[16][128];
如此一来,我们限定了每个参数长度不得超过127(最后一位放'\0'标识字符串结束),总参数个数不得超过15(最后一位要放0即NULL表示参数输入结束,注意这里必须使用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心得的同学们!