之前同学提起,能区分得开xv6与JOS吗?才发现真不知道,赶紧查了查:
1.extended inspection of xv6, a traditional O/S
xv6,一个传统的操作系统的扩展的分析
2.Lab: JOS, a small O/S for x86 in an exokernel style
实验:JOS,为x86平台所写的一个小型的微内核操作系统
3.x86泛指一系列基于Intel 8086且向后兼容的中央处理器指令集架构
谢谢马如风
主要得明白文件描述符、重定向、管道、runcmd()、parsecmd()
作业内容:补全shell代码
什么是shell呢?
Shell 是一个
应用程序
,它连接了用户和 Linux 内核,让用户能够更加高效、安全、低成本地使用 Linux 内核,这就是 Shell的本质。
Shell 程序本身的功能是很弱的,比如文件操作、输入输出、进程管理等都得依赖内核。我们运行一个命令,大部分情况下 Shell都会去调用内核暴露出来的接口(系统调用
),这就是在使用内核,只是这个过程被 Shell 隐藏了起来
谢谢仁兄
xv6 shell的执行流程:
- shell 执行 getcmd 获得用户输入的命令
- shell 执行 fork 创建一个shell进程的copy,然后shell进入wait状态
- shell 执行 runcmd 运行用户的命令
- runcmd函数调用exec系统调用加载适当的函数如:echo
- 在函数(echo) 的结束,有exit系统调用返回shell,shell从wait中退出
谢谢卖萌的弱渣
命令有5类:
执行命令 符号: “ ” 重定向命令 > 或者 < 列表命令,也就是多个命令 符号是分号 ; 管道命令,需要先建立管道 符号是 | 返回命令(这个不知道是干嘛的),符号是 &
整个函数的
核心
函数有两个,一个是parsecmd,一个是runcmd
当读入命令的时候,首先进行解析
parsecmd的核心函数是parseline
这个函数是递归的,执行流程:首先执行parsepipe,检测是否有管道命令,有的话建立管道连接 检测命令中是否有&,也就是返回命令,有的话用parsepipe的返回值生成一个新的backcmd 检测是否有;,也就是是否有多条命令分别要执行,有的话,递归调用parseline,将所有的命令分别解析之后连接起来
关键与难点:弄清主要几个函数的意义与用法,尤其是参数的意义。还得懂文件描述符
1.涉及Shell 命令
ls > y 把ls结果输出到文件y中 cat cat命令的用途是连接文件或标准输入并打印。这个命令常用来显示文件内容,或者将几个文件连接起来显示,或者从标准输入读取内容并显示,它常与重定向符号配合使用 一次显示整个文件:cat filename 从键盘创建一个文件:cat > filename 只能创建新文件,不能编辑已有文件 将几个文件合并为一个文件:cat file1 file2 > file uniq uniq命令常用语报告或者消除文件中的重复内容,一般与sort命令结合使用 wc wc命令的功能为统计指定文件中的字节数、字数、行数, 并将统计结果显示输出 ls > y cat < y | sort | uniq | wc > y1 功能:把当前目录ls的结果存到y中,然后读取y的内容,然后将y的内容排序,去掉重复,然后统计字数,行数.并把结果保存到y1
什么是Unix Pipeline
举个例子: ls -1 | grep p | more 只列出来还有字母p的当前目录下的文件
Pipeline 使用”|“来区分多个命令,从左到右,前一个命令的结果是后一个命令的输入
谢谢仁兄
2.int execv(const char *progname, char *const argv[]);
execv会停止执行当前的进程,并且以progname应用进程替换被停止执行的进程,进程ID没有改变。
- progname: 被执行的应用程序。
- argv: 传递给应用程序的参数列表, 注意,这个数组的第一个参数应该是应用程序名字本身,并且最后一个参数应该为NULL,不参将多个参数合并为一个参数放入数组。
- 返回值:如果应用程序正常执行完毕,那么execv是永远不会返回的;当execv在调用进程中返回时,那么这个应用程序应该出错了(可能是程序本身没找到,权限不够…), 此时它的返回值应该是
-1
,具体的错误代码可以通过全局变量errno
查看,还可以通过stderr
得到具体的错误描述字符串:
谢谢卖萌的弱渣:
3. extern char strchr(char *str,char character)
从字符串str中寻找字符character第一次出现的位置。参数说明:str为一个字符串的指针,character为一个待查找字符。 所在库名:#include <string.h>
4.open 函数原型:int open(const char *pathname, int flags, mode_t mode);
- flags must include one of the following access modes: O_RDONLY, O_WRONLY, or O_RDWR.
- mode: 创建文件时设定的其他用户权限: S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH;
- 返回值:调用成功时返回一个文件描述符fd;调用失败时返回-1,并修改errno
5.dup 函数原型: int dup(int oldfd);
函数说明: create a copy of the file descriptor oldfd。dup出来的新fd共享之前fd的offset
6.int gettoken(chasr **ps, char *es, char **q, char **eq)
// 把地址ps到es的字符串中的变量找到,并存到q到eq的地址去
7.int peek(char **ps, char *es, char *toks)
//判断从地址ps到es的字符串是否含有toks里面的字符
8.char *mkcopy(char *s, char *es)
// s指向需要拷贝的字符串头,es指向需要拷贝的字符串结尾. 这个函数拷贝从s到es的字符串,然后返回拷贝的地址。
9.struct cmd* parsecmd(char *s)
// 解析命令把buffer里的命令包装成可执行的数据结构struct cmd
其他函数:
int chdir(const char * path);
用户将当前的工作目录改变成以参数路径所指的目录。
char *strcat(char *dest, const char *src)
把 src 所指向的字符串追加到 dest 所指向的字符串的结尾。
char *strerror(int errnum)
从内部数组中搜索错误号 errnum,并返回一个指向错误消息字符串的指针。errnum – 错误号,通常是 errno
,是个整型。
代码补全
case ’ ':
ecmd = (struct execcmd*)cmd;
if(ecmd->argv[0] == 0)
_exit(0);
//The hard part is figuring out what execv and its parameters mean
//So that I can modify the path
if(execv(ecmd->argv[0],ecmd->argv)==-1){
char mypath[20]="/bin/";
strcat(mypath,ecmd->argv[0]);
if(execv(mypath,ecmd->argv)==-1){
strcpy(mypath,"/usr/bin/");// I write "/user/bin/" and wrong...
strcat(mypath, ecmd->argv[0]);
if(execv(mypath,ecmd->argv)==-1){
fprintf(stderr, "Command %s can't find\n", ecmd->argv[0]);
_exit(0);
}
}
}
case ‘<’:
case ‘>’:
rcmd = (struct redircmd*)cmd;
close(rcmd->fd);
if(open(rcmd->file, rcmd->flags, S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH)<0){
fprintf(stderr, "file %s can't find\n", rcmd->file);
_exit(0);
}
runcmd(rcmd->cmd);
break;
case ‘|’:
最难的最有用的就是管道命令了,可以很深刻的理解文件描述符,也能非常好的明白文件重定向
文件描述符是一个int型数,它就像一个文件的索引一样,或者是一个文件指针。按我的理解,每个文件都会有一个自己的fd
,用于被其他文件使用,还会有一张fd表
,用于记录与其他文件的关系
这位大哥说的很好,图也很清晰,但是我还是不明白怎么写完这个管道命令,于是我又读了别人的代码,画出了我的理解:
于是我写出了这段代码
pcmd = (struct pipecmd*)cmd;
//fprintf(stderr, "pipe not implemented\n");
// Your code here ...
if(pipe(p)<0){
fprintf(stderr,"Fail to create a pipe\n");
_exit(0);
}
if(fork1()==0){
//child one:read from stdin(0), write to the right end of pipe(p[1])
close(1);
dup(p[1]);
close(p[0]);
close(p[1]);
runcmd(pcmd->left);
}
if(fork1()==0){
//child two:read from the left end of pipe(p[0]), write to stdout(1)
close(0);
dup(p[0]);
close(p[0]);
close(p[1]);
runcmd(pcmd->right);
}
close(p[0]);
close(p[1]);
wait(&r); //the position of wait I have a little confusion
wait(&r);
break;
自问自答
1.为什么我又没有写ls,cat,却可以exec它们,它们就是系统调用接口吗?
答:我认为这些是linux封装好的命令,不是系统调用,但是它们会调用很多系统调用来实现自己的功能。
2.为什么明明cmd结构体只有个type,赋值给其他结构体redircmd、execcmd等却可以把其他参数补全?
答:我认为在main()函数中的runcmd(parsecmd(buf))中,执行parsecmd后就已经根据cmd->type参数返回了相对应的结构体类型,通过struct cmd *这种基类
结构体指针来存是个很聪明的选择
3.分析命令的过程还得仔细弄清楚!如“echo “6.828 is cool” > x.txt"从输入到运行下来,是怎么样的?
首先getcmd,将整条命令存到buf里,buf=“echo “6.828 is cool” > x.txt”
然后判断是不是cd命令,这里不是
然后fork1创建一个子进程,子进程里通过parsecmd将buf解析赋值给execcmd结构体
最后执行runcmd(cmd)
(其实parsecmd内部具体怎么搞得我还是比较模糊)
4.fork?
fork就是当前进程创建一个子进程,然后父进程等待子进程的执行。
子进程和父进程有着相同的文件描述符,数据也相同,只是存储的内存不同。
fork在子进程和父进程中都会有返回值,在父进程中返回子进程的pid,子进程中返回0。
5.每执行一条命令都会开辟一个进程吗?
答:不是吧,得fork才会创建新进程。main函数中fork了,管道命令中fork了两次。
6.parseredir在哪里引用了?
答:终于找到了,在parseexec()函数里的第八行处用到了