开启掘金成长之旅!这是我参与「掘金日新计划 · 12 月更文挑战」的第29天,点击查看活动详情 这也是第34篇文章
前言
今天重新回顾了一下6.s081。印象中之前是做到lab3还是lab4就没有继续往下做了,因为在前面通关的实验中,对很多机制并没有真正理解,导致越到后面就越懵。但“回顾”指的并不是把实验任务重做一遍,而是发现一些之前没有留意的东西。
新收获
(可能大部分收获在读者看来是“就这?”,但实不相瞒,我之前做实验的时候真的并不知道,只能说前面的实验多少有点运气的成分;但同时,我个人觉得倒也不必悲观或者羞于承认,毕竟人对事物的认识就是这样一个逐步深入的过程。一开始可能对一些旁人觉得理所当然的事情感到一脸懵逼,无所适从;但某一天回过头看却会恍然大悟)
对项目结构的认识
在xv6-labs-2021目录下ls,可以看到如下内容:
- 为什么教程对于评测单个项目是这么写的:
因为grade-lab-util(lab1名称)是个经过编译的可执行文件。./指的是当前目录下;该可执行文件里面又有个sleep模块
- gradelib.py是一个评测脚本,如果评测通不过是可以微调一下里面的代码 (不过适用于机器硬性条件实在达不到的情况,不然把考试参考答案改掉真的是自己骗自己) (不过也侧面反映出,评测的本质也就是一行行可读性强的代码么,之前觉得它是个神秘的黑盒子是因为还没有打开看过)
对功能添加流程的认识以及内容修改
在官方教程的example中,可以找到如下内容:
以其中的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 的时候,也会发现这些后缀文件信息被清除的字样。
由此可以猜测,如果在修改文件之后重新编译,如果新改的文件和原来的没有冲突,那么直接
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并退出。
插曲
这中间有些小插曲。
- 第一次我凭着对c语言的记忆写好了代码,但报了如下错误:
据说是从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);
}
运行结果:
解析
这一段代码的作用原理是什么呢?
乍一看不理解也没关系,可以通过对代码进行修改并查看输出结果来反向推断。
- 如果将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,会显示:
由此可以推断:
- 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);
}
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);
}
可以看到很规整的结果,比如这样:
也可以看到这种现象
个人觉得这确实间接反映了父进程fork出子进程后,两者各自为王的实质。
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相比,它少了右边的数字
ls中相应代码如下:
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后它会消失)
目前暂时不知道该怎么打开它,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);
}
管道的内容我之前的笔记中也提过了,这里不再赘述。
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
后面这几个命令似乎都没什么好改的(改的意义不大),所以只是贴了一下运行结果,然后简单分析了一下。