MIT6.828学习之homework2:shell

之前同学提起,能区分得开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的执行流程:

  1. shell 执行 getcmd 获得用户输入的命令
  2. shell 执行 fork 创建一个shell进程的copy,然后shell进入wait状态
  3. shell 执行 runcmd 运行用户的命令
  4. runcmd函数调用exec系统调用加载适当的函数如:echo
  5. 在函数(echo) 的结束,有exit系统调用返回shell,shell从wait中退出
    谢谢卖萌的弱渣

命令有5类:

执行命令 符号: “ ”
重定向命令 > 或者 <
列表命令,也就是多个命令 符号是分号 ;
管道命令,需要先建立管道 符号是 |
返回命令(这个不知道是干嘛的),符号是 &

整个函数的核心函数有两个,一个是parsecmd,一个是runcmd
当读入命令的时候,首先进行解析
parsecmd的核心函数是parseline
这个函数是递归的,执行流程:

首先执行parsepipe,检测是否有管道命令,有的话建立管道连接
检测命令中是否有&,也就是返回命令,有的话用parsepipe的返回值生成一个新的backcmd
检测是否有;,也就是是否有多条命令分别要执行,有的话,递归调用parseline,将所有的命令分别解析之后连接起来

谢谢bdhmwz

关键与难点:弄清主要几个函数的意义与用法,尤其是参数的意义。还得懂文件描述符

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表,用于记录与其他文件的关系

在这里插入图片描述谢谢 sky_Mata的图以及介绍

这位大哥说的很好,图也很清晰,但是我还是不明白怎么写完这个管道命令,于是我又读了别人的代码,画出了我的理解:
在这里插入图片描述于是我写出了这段代码

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()函数里的第八行处用到了

主要参考

用到的函数介绍
详细介绍
chapter 0介绍
关于管道的介绍

  • 8
    点赞
  • 50
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值