xv6源码分析 002
今晚我们就来看看xv6提供的应用程序,主要是sh.c
和umalloc.c
这两个文件
sh.c
是xv6用来进行交互的应用程序,怎么说呢?我们可以将xv6的shell看成是一个代理(proxy),我们将我们需要执行的命令交给这个帮我们代理的对象(shell),然后它再帮我们真正去做这件事情,然后将处理的结果返回给我们。这一层代理替我们与操作系统的用户态进行进行交互,我们我们不需要将写各种的函数来将我们的命令传递到内核,等内核处理完之后,再写各种的函数获取内核的处理的结果。这是一种很好的抽象,在一些业务型编程语言中通常叫做代理模式(proxy pattern),比如,java,go(go很抽象,能业务,也能做云原生,我现在也在学,写起来跟python一样爽)。
umalloc.c
这是一个内存配置器,相当于linux中提供的POSIX api的malloc
和free
一样,但是我看了一下这个xv6中的实现,好像是一个用户态的内存池,并不是内核态的;但是POSIX api的malloc
和free
是一个系统调用,所以它的内存池是维护在内核的(这里我是猜的,但是应该差不多,因为我并没有看过这POSIX的实现的源码)。了解一下内存池的实现,对我们水平的提高也是有帮助的。另外我在给大家讲一下内存池实现的两种形式。
ok,现在我们来看看sh.c
吧。
sh.c
这个文件的代码还是比较多的,我们还是采取自顶向下的方式来看代码。
在看具体的函数之前我们需要先熟悉一下相关的数据结构和相关的宏常量
我们在看看定义的宏常量
-
definition variable
可以看到这几个常量分别对应着下面的几个结构,
// Parsed command representation #define EXEC 1 #define REDIR 2 #define PIPE 3 #define LIST 4 #define BACK 5 #define MAXARGS 10
-
data structure
命令的类型:
type
就代表的是系统调用的类型,就是上面定义的那几个宏变量struct cmd { int type; };
execcmd
:这个应该是执行
exec
系统调用的时候传递的参数,直接将它们打包在一起了,后面的几个结构体也是一样struct execcmd { int type; char *argv[MAXARGS]; // MAXARGS是定义在kernel/param.h中的一个宏 // 常量,规定了命令参数个数的最大值 char *eargv[MAXARGS]; };
redircmd
:这个直接从字面意思来看,
re
、dir
就知道是读取一个文件的系统调用struct redircmd { int type; // 这个type跟cmd里面的type的不同,这里先注意一下 struct cmd *cmd; char *file; // 文件名 char *efile; int mode; // 文件开的模式 int fd; };
pipecmd
:一眼管道
struct pipecmd { int type; struct cmd *left; // 管道的左端, struct cmd *right; // 管道的右端,具体功能我们看具体函数的实现 };
listcmd
:这个不清楚,先对这个结构有个大致的印象,后面再看在哪里用到了这个结构
struct listcmd { int type; struct cmd *left; struct cmd *right; };
backcmd
:同上
struct backcmd { int type; struct cmd *cmd; };
结合上面的宏定义,我们可以做初步的推断——xv6中的shell将系统调用大致分为5类,不同的系统调用的参数会打包对应的结构中进行统一处理。
现在我们看看main.c
函数。
-
main.c
int main(void) { static char buf[100]; int fd; // Ensure that three file descriptors are open. while((fd = open("console", O_RDWR)) >= 0){ if(fd >= 3){ close(fd); break; } } // Read and run input commands. while(getcmd(buf, sizeof(buf)) >= 0){ if(buf[0] == 'c' && buf[1] == 'd' && buf[2] == ' '){ // Chdir must be called by the parent, not the child. buf[strlen(buf)-1] = 0; // chop \n if(chdir(buf+3) < 0) fprintf(2, "cannot cd %s\n", buf+3); continue; } if(fork1() == 0) runcmd(parsecmd(buf)); wait(0); } exit(0); }
main函数主要由两个大循环构成,但是工作的主要是第二个大循环。
第一个循环:这个循环调用
open
打开一个叫console
的文件,这是一个可执行文件。这个循环确保一定能够启动打开这个可执行文件。console.c
的话我们明天在看。第二个循环:这个循环调用
getcmd
先阻塞等待用户的输入,然后检查这个命令是不是cd
;如果是的话,shell就必须自己处理这个命令;如果不是的话,shell就会fork()
一个子进程,来执行相应的命令——fork完之后,父进程调用wait(0)
等待子进程,子进程执行parsecmd()
对命令进行解析,并将解析结构放入结构体中传递到runcmd()
中执行。这里为什么子进程不用调用exit(0)
或者break
呢?大家可以思考一下。在main函数中调用到了这个三个函数:
getcmd()
runcmd()
parsescmd()
我们逐个看看吧。
-
getcmd()
int getcmd(char *buf, int nbuf) { fprintf(2, "$ "); memset(buf, 0, nbuf); // 重置缓冲区 gets(buf, nbuf); // 读取命令,内部调用系统调用read if(buf[0] == 0) // EOF return -1; return 0; }
memset
和gets
都是xv6提供的库函数,在ulib.c
中,大家可以去看看,里面的函数的都很极致的,因为都是大佬写的,而且在面试的时候,面试佬可能会让你实现一些api。这里我们直接跳过。 -
parsecmd()
这个代码有点烧,大家先看看,到点了该睡觉了xdm,这个命令解析器的结构是一个流水线式的解析,我明天晚上在将流程图化画出来。下面是全部的代码了,可以结合源码先看看(我用的是
util
分支的源码)不知道怎么搞到源码的兄弟可以看这里https://gitee.com/CZY-3101298914/mit6.-s086.git。struct cmd* parsecmd(char *s) { char *es; struct cmd *cmd; es = s + strlen(s); cmd = parseline(&s, es); peek(&s, es, ""); if(s != es){ fprintf(2, "leftovers: %s\n", s); panic("syntax"); } nulterminate(cmd); return cmd; }
struct cmd* parseline(char **ps, char *es) { struct cmd *cmd; cmd = parsepipe(ps, es); while(peek(ps, es, "&")){ gettoken(ps, es, 0, 0); cmd = backcmd(cmd); } if(peek(ps, es, ";")){ gettoken(ps, es, 0, 0); cmd = listcmd(cmd, parseline(ps, es)); } return cmd; }
struct cmd* parsepipe(char **ps, char *es) { struct cmd *cmd; cmd = parseexec(ps, es); if(peek(ps, es, "|")){ gettoken(ps, es, 0, 0); cmd = pipecmd(cmd, parsepipe(ps, es)); } return cmd; }
struct cmd* parseexec(char **ps, char *es) { char *q, *eq; int tok, argc; struct execcmd *cmd; struct cmd *ret; if(peek(ps, es, "(")) return parseblock(ps, es); ret = execcmd(); cmd = (struct execcmd*)ret; argc = 0; ret = parseredirs(ret, ps, es); while(!peek(ps, es, "|)&;")){ if((tok=gettoken(ps, es, &q, &eq)) == 0) break; if(tok != 'a') panic("syntax"); cmd->argv[argc] = q; cmd->eargv[argc] = eq; argc++; if(argc >= MAXARGS) panic("too many args"); ret = parseredirs(ret, ps, es); } cmd->argv[argc] = 0; cmd->eargv[argc] = 0; return ret; }
struct cmd* execcmd(void) { struct execcmd *cmd; cmd = malloc(sizeof(*cmd)); memset(cmd, 0, sizeof(*cmd)); cmd->type = EXEC; return (struct cmd*)cmd; }
struct cmd*
execcmd(void)
{
struct execcmd *cmd;
cmd = malloc(sizeof(*cmd));
memset(cmd, 0, sizeof(*cmd));
cmd->type = EXEC;
return (struct cmd*)cmd;
}