Homewoek 2:shell
下载 6.828 shell 并阅读代码。shell
包含两个主要部分:解析shell命令和实现。
将下列指令复制到 t.sh
:
ls > y
cat < y | sort | uniq | wc > y1
cat y1
rm y1
ls | sort | uniq | wc
rm y
编译 sh.c
,并执行指令:
此执行将打印错误消息,因为尚未实现上述功能。在本任务的其余部分中,将实现这些功能。
实现命令
补充 runcmd
中空缺的代码。使用 man 3 exec
查看 execv
手册。
int execv (const char * path, char * const argv[]);
:用来执行参数 path 字符串所代表的文件路径, 与execl()
不同的地方在于execv()
只需两个参数, 第二个参数利用数组指针来传递给执行文件。
该函数如果执行成功,则不会返回;若执行出错,会返回 -1 ,具体的错误代码可以通过全局变量 errno
查看,还可以通过 stderr
得到具体的错误描述字符串。
int chdir(const char *
path);
:函数应使path参数指向的路径名所命名的目录成为当前工作目录;也就是说,路径的起点搜索不是以 “ /” 开头的路径名。转换成功返回 0 ;否则返回 -1 且当前目录不变。
来自https://blog.csdn.net/a747979985/article/details/95094094
本次 shell
作业执行可执行文件主要是通过该函数来实现。
这次作业真的让我一头雾水,参考了如下博客,对 shell
大概的执行逻辑与代码实现有了头绪。
- Implement a Shell by yourself -- MIT xv6 shell :这篇博客分析了实现的过程,对从整体去把握
shell
的运行流程很有帮助。其次,他的实现代码运用了access()
函数来检查权限,代码很简洁,但是不易理解。 - 【xv6学习之HW1】shell :这篇博文对上述博文以及代码进行了进一步解析,讲解了
access()
函数以及权限判断各个定义,有助于去理解上一篇博客的代码。
这是我的代码 https://github.com/a74731248/journal-of-mit-6.828/blob/master/homework/HW1/sh.c 。我在代码中把自己的理解作为注释加了上去,挺详细的,希望对大家有帮助。
实现部分
execcmd
:
// 寻找可执行文件
if(execv(ecmd->argv[0], ecmd->argv) == -1)
{
chdir("/bin/");
if(execv(ecmd->argv[0], ecmd->argv) == -1)
{
chdir("/usr/bin/");
execv(ecmd->argv[0], ecmd->argv);
}
}
fprintf(stderr, "exec %s failed.
", ecmd->argv[0]);
_exit(0);
redircmd
:
close(rcmd->fd);
if(open(rcmd->file, rcmd->flags, S_IRUSR | S_IWUSR) == -1)
{
fprintf(stderr, "file %s can't find
", rcmd->file);
_exit(0);
}
pipecmd
:
if(pipe(p) < 0)
{
fprintf(stderr, "call syscall pipe() failed in line %d
", __LINE__);
_exit(0);
}
if(fork1() == 0)
{
// 子进程输出绑定到管道的 fd1
close(1);
dup(p[1]);
close(p[0]);
close(p[1]);
// 执行管道左侧的命令
runcmd(pcmd->left);
}
if(fork1() == 0)
{
// 绑定管道的 fd0 到子进程的输入
close(0);
dup(p[0]);
close(p[0]);
close(p[1]);
// 运行管道右侧命令
runcmd(pcmd->right);
}
close(p[0]);
close(p[1]);
// 这里我不太理解
wait(&r);
wait(&r);
问题记录
记录一下自己实现过程中的理解以及疑问(管道通信部分):
理解:
管道左侧的程序需用使用子进程1去执行,它需要从标准输入中读取参数,然后将程序运行结果输出到管道中去,故需要关闭子进程1的 fd1 ,然后将管道的写端口(p[1]) 拷贝到 fd1 中,从而实现子进程1输出到管道中。
管道右侧的程序需要用子进程2去执行,它需要从管道中读取参数,故关闭子进程2的 fd0 ,然后将管道的读端口(p[0])拷贝到 fd0 中,从而实现子进程2从管道中读取参数。
疑问:
为什么每次都需要关闭管道的描述符(即 close(p[0]); close(p[1]);
)?
是因为管道对应的描述符已经重定向了,避免管道的内容受到其他进程的影响吗?
总结
- 了解了
fork、dup、close、pipe
等系统调用的使用。 - 了解了
shell
是如何实现输入命令的分解。 - 利用
execv
实现了exec
指令,使得shell
能够加载并执行文件(命令)。 - 大致理解了进程之间的管道通信机制,通过文件描述符拷贝实现进程输入输出重定向。
- 实现了进程之间的管道通信。