基础知识都不包括xv6讲义里的知识。 讲义的下载链接 Sep 3, 2014
基础知识execv, strchr, access. 在parsecmd中使用到strchr:查找字符串s中首次出现字符c的位置。execv: // 函数原型 程序名 参数 int execv(const char *progname, char *const argv[]); //#include <unistd.h> access 检查调用进程是否可以对指定的文件执行某种操作。 // pathname :文件路径 // mode : 操作模式 int access(const char * pathname, int mode R_OK 测试读许可权 W_OK 测试写许可权 X_OK 测试执行许可权 F_OK 测试文件是否存在parsecmd已经实现了命令解析,其中应该是包括大量的字符串解析,我们暂且不深究。
命令解析器已经为你搭建好了execcmd,所以你必须唯一编写的代码为runcmd中的' 'case.
execv(ecmd.argv[0], ecmd.argv);基于以上实现,每次都需要键入bin/ls,而无法直接使用·ls。如果需要,可以实现支持PATH环境变量。简单实现PATH,通过设定可执行文件的根目录,以strcat 的方式实现,目前仅可使用/bin/下的可执行命令。
if (access(ecmd->argv[0], F_OK) == 0) { execv(ecmd->argv[0], ecmd->argv); } else { strcpy(path, root); strcat(path, ecmd->argv[0]); //make the absolute path of this command. for example, you type in "ls", then its absolute path is "/bin/ls" if(access(path, F_OK) == 0) //If this command can be found, then execute execv(path, ecmd->argv); else { fprintf(stderr, "%s: Command not found.\n", ecmd->argv[0]); } }I/O重定向内容在xv6的第10页I/O and File descriptors中进行了详细地说明, 其要点为:
文件描述符(fd)从零开始分配 0 -> 标准输入 1 -> 标准输出 2 -> 错误输出 每次申请文件描述符时, 总是从未分配的最小值开始。故,实现I/O的重定向,只需先释放对应的文件描述符,再通过打开需要重定向目标文件即可。
case '>': // it's not necessary to write. case '>' and '<' can be merge to one. case '<': rcmd = (struct redircmd*)cmd; //close standerd input/ output close(rcmd->fd); if (open(rcmd->file, rcmd->mode, S_IWUSR|S_IRUSR) < 0) { fprintf(stderr, "file:%s no exist\n",rcmd->file); exit(0); } //why don't we need to change type to ' '? rcmd->type = ' '; runcmd(rcmd->cmd); break;管道实现的效果可以由临时文件+重定向的方式实现, 所以第一印象会觉得重定向与管道有点像。但实际上区别还是很大,重定向实现的是改变输出或输入数据的来源,管道实现的是进程之间的通信。并且,两者在性能上也存在一定的差别。以下进行详细比较。
管道似乎没有临时文件强大,例如echo hello world | wc 同样能由临时文件的形式实现echo hello world >/tmp/a.txt; wc </tmp/a.txt
管道和临时文件至少有三个不同点:
管道可以自动清除自身;管道可以传输大数据流,临时文件需要提供足够存储空间进行临时存储;管道的读写可以同步。实现管道,一开始感觉无从下手。感觉管道实现是这个homework中最难的一部分。首先搞清pipe and dup 两个system call.
int pipe(int p[]) 建立一个缓冲区,并把缓冲区通过 fd 形式给程序调用。它将 p[0] 修改为缓冲区的读取端, p[1] 修改为缓冲区的写入端。int dup(int old_fd) 产生并返回与old_fd指向同一文件的fd。产生的 fd 总是空闲的最小 fd。 case '|': pipe(p); // pipe 0 read 1 write pcmd = (struct pipecmd*)cmd; // Your code here ... if (fork1() == 0) { // left pipe // standard output is redirected to write end of pipe close(1); dup(p[1]); close(p[0]); close(p[1]); runcmd(pcmd->left); } wait(&r); if (fork1() == 0) { // standard input is redirected to read end of pipe close(0); dup(p[0]); close(p[0]); close(p[1]); runcmd(pcmd->right); } close(p[0]); close(p[1]); wait(&r); break;实现代码如上所示,但一直不理解xv6第13页上为什么说:
因为读端会一直阻塞到不可能再有数据写入,所以在执行exec之前,子进程关闭pipe写端十分重要。
其实也就是不理解管道到底是什么样子,一直被之前的理念:认为会存在两个管道给误解了。实际上管道实现如下图所示,图片转自进程间的通信方式——pipe(管道)。
有了对上图的理解,对管道的具体实现也就基本上明白了。