通过在小shell中实现多个功能,这个assignment将使我更熟悉Unix系统调用接口和shell
MIT 6.828 Operating System Engineering
做作业之前先熟悉OSMIT 6.828 book_xv6:Chapter 0
该作业的解释文档
下载sh.c
建立t.sh
文件1
2
3
4
5
6ls > y
cat < y | sort | uniq | wc > y1
cat y1
rm y1
ls | sort | uniq | wc
rm y
gcc,你可以编译骨架shell,如下所示:$ gcc sh.c
生成一个a.out文件,你可以运行:$ ./a.out <t.sh
此执行将打印错误消息,因为您尚未实现多个功能。在本作业的其余部分中,您将实现这些功能
分析
先看看主函数的行为1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22int
main(void)
{
static char buf[100];
int fd, r;
// Read and run input commands.
while(getcmd(buf, sizeof(buf)) >= 0){
if(buf[0] == 'c' && buf[1] == 'd' && buf[2] == ' '){
// Clumsy but will have to do for now.
// Chdir has no effect on the parent if run in the child.
buf[strlen(buf)-1] = 0; // chop \n
if(chdir(buf+3) < 0)
fprintf(stderr, "cannot cd %s\n", buf+3);
continue;
}
if(fork1() == 0)
runcmd(parsecmd(buf));
wait(&r);
}
exit(0);
}
循环调用getcmd
函数读入命令,如果是cd
命令,则先切换文件夹,因为如果在子进程切换将对父进程无效。确认不是cd
之后,创建子进程并调用关键函数runcmd()
,其中parsecmd(buf)
无非就是解析命令,里面就是一些字符串处理,不关心。
下面就我们要完成的代码片段1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41// Execute cmd. Never returns.
void
runcmd(struct cmd *cmd)
{
int p[2], r;
struct execcmd *ecmd;
struct pipecmd *pcmd;
struct redircmd *rcmd;
if(cmd == 0)
exit(0);
switch(cmd->type){
default:
fprintf(stderr, "unknown runcmd\n");
exit(-1);
case ' ':
ecmd = (struct execcmd*)cmd;
if(ecmd->argv[0] == 0)
exit(0);
fprintf(stderr, "exec not implemented\n");
// Your code here ...
break;
case '>':
case '<':
rcmd = (struct redircmd*)cmd;
fprintf(stderr, "redir not implemented\n");
// Your code here ...
runcmd(rcmd->cmd);
break;
case '|':
pcmd = (struct pipecmd*)cmd;
fprintf(stderr, "pipe not implemented\n");
// Your code here ...
break;
}
exit(0);
}
parsecmd
把命令分成了3个类型,分别是可执行命令,重定向命令,以及管道命令。也就是接下来要实现的
简单命令
该题的要求就是先调用当前目录下的可执行程序,如果不存在,就去/bin
文件夹下寻找,如果没有则报错
需要使用的就是execv
函数,它是exec
函数族的一个,exec函数族的作用就是根据pathname找到可执行文件,并用它取代调用进程的内容。虽然pid未改变,但是实际运行的内容已经不同。结合之前main函数中的内容,可以看出Shell执行某个命令实际上就是fork出一个子进程,然后把子进程替换为想要执行的程序。这个函数在小册子中也讲解过,可以通过man 3 exec
去查看函数解释
除此之外,还需要一个辅助函数access
,它的作用是检查能否对某个文件(pathname)执行某个操作(mode)。模式有:
- R_OK:测试读许可权
- W_OK:测试写许可权
- X_OK:测试执行许可权
- F_OK:测试文件是否存在
1 | case ' ': |
编译运行截图,嘻嘻嘻!
I/O重定向
The parser already recognizes ">" and "<", and builds a redircmd for you, so your job is just filling out the missing code in runcmd for those symbols. You might find the man pages for open and close useful
1 | case '>': |
思路就是先关闭程序原先的标准输入/输出,打开指定文件作为新的标准输入/输出。非常容易漏掉权限位,即open的第三个参数
编译运行截图,嘻嘻嘻!
管道
You might find the man pages for pipe, fork, close, and dup useful
,也就是说这次涉及的函数主要是pipe
、dup
、fork
、close
,通过输入前面创建的t.sh
批处理文件,来计算当前目录下文件的大小
1 | case '|': |
由于批处理文件的命令既有在/bin/
下,也有在/usr/bin
,因此之前的case需要修改
1 | case ' ': |
编译运行截图,嘻嘻嘻!