xv6 Shell实验报告
实验任务:◀️Homework: shell (mit.edu)
1. Executing simple commands 执行简单命令
2. I/O redirection I/O 重定向
3. Implement pipes 实现管道
前言
定义命令类型 cmd为抽象类,execcmd为exec cmd,redircmd 为redirect(重定向) cmd,pipcmd 为 pipe(管道) cmd
struct cmd {
int type; // ' ' (exec), | (pipe), '<' or '>' for redirection
};
struct execcmd {
int type; // ' '
char *argv[MAXARGS]; // arguments to the command to be exec-ed
};
struct redircmd {
int type; // < or >
struct cmd *cmd; // the command to be run (e.g., an execcmd)
char *file; // the input/output file
int mode; // the mode to open the file with
int fd; // the file descriptor number to use for the file
};
struct pipecmd {
int type; // |
struct cmd *left; // left side of pipe
struct cmd *right; // right side of pipe
};
命令执行器 runcmd 为代码实现部位
void runcmd(struct cmd *cmd)
任务
执行简单命令
case ' ':
ecmd = (struct execcmd*)cmd;
if(ecmd->argv[0] == 0)
exit(0);
execvp(ecmd->argv[0], ecmd->argv);
fprintf(stderr, "exec not implemented\n");
// Your code here ...
break;
注意到这是普通命令类 execcmd
调用exec()执行命令
使用execvp可以调用环境变量
Reason:
系统命令在
\bin\
或\usr\bin
目录下,如果使用execv的话要给出完整的目录This prints a prompt and waits for input.
sh.c
prints as prompt6.828$
so that you don’t get confused with your computer’s shell. Now type to your shell:6.828$ ls
Your shell may print an error message (unless there is a program named
ls
in your working directory or you are using a version ofexec
that searchesPATH
). Now type to your shell:6.828$ /bin/ls
This should execute the program
/bin/ls
, which should print out the file names in your working directory. You can stop the 6.828 shell by typing ctrl-d, which should put you back in your computer’s shell.
exec函数没有返回,错误才返回⬇️
本地可执行程序
发现无法运行本地可执行文件,so➡️
char mypath[20]="./";
if(execvp(ecmd->argv[0], ecmd->argv)==-1){// 判断
strcat(mypath, ecmd->argv[0]);// 合成,注意是ecmd->argv[0]
execv(mypath, ecmd->argv);// 执行
}
对原代码做出调整,将路径添加到execv,ecmd->argv是 ** 类型!
tip:
execv 与 execl 互斥
l表示该函数取一个参数表,它与字母v互斥。
v表示该函数取一个argv [ ]矢量。
重定向
重定向输入输出
case '>':
case '<':
rcmd = (struct redircmd *)cmd;
// 打开文件进行输入/输出重定向
int file_fd = open(rcmd->file, rcmd->mode, 0666);
if (file_fd == -1) {
perror("open");
exit(EXIT_FAILURE);
}
// 关闭文件描述符 rcmd->fd
close(rcmd->fd);
// 将新打开的文件描述符与 rcmd->fd 关联
if (dup2(file_fd, rcmd->fd) == -1) {
perror("dup2");
exit(EXIT_FAILURE);
}
// 关闭不再需要的文件描述符
close(file_fd);
// 递归执行重定向后的命令
runcmd(rcmd->cmd);
break;
通过文件描述符控制输入输出,注意关闭其他输入输出文件描述符
使用dup2进行重定向
在shell的重定向功能中,(输入重定向”<”和输出重定向”>”)就是通过调用dup或dup2函数对标准输入和标准输出的操作来实现的
实现管道
case '|':
pcmd = (struct pipecmd*)cmd;
// Your code here ...
if(pipe(p) < 0)
{
fprintf(stderr,"Fail to create a pipe\n");
exit(0);
}
if(fork1() == 0){
close(1);
dup(p[1]);
close(p[0]);
close(p[1]);
runcmd(pcmd->left);
}
else{
close(0);
dup(p[0]);
close(p[0]);
close(p[1]);
runcmd(pcmd->right);
}
close(p[0]);
close(p[1]);
wait(&r);
break;
pipe(p)管道创建p[0]读、p[1]写
dup()重定向
fork创建管道的子进程,把整个命令看做一个管道命令,由管道可分出左右两个命令,再由runcmd自调用实现递归
注意一下wait函数的使用,因为管道是有顺序的,所以父进程是不能放任的。
正如递归父级调用需要等待子级return才能继续执行。
❗️❗️Warning:
在开始我将左右两个命令视为同级,就创建了两个子进程。也就使用两个if,然而这将会导致重复*(父进程被复制了两遍!!*
if(fork1() == 0){ close(1); dup(p[1]); close(p[0]); close(p[1]); runcmd(pcmd->left); } if(fork1() == 0){ close(0); dup(p[0]); close(p[0]); close(p[1]); runcmd(pcmd->right); }
应采用父进子出,或子进父出—> if… else…
总结
了解了linux的系统调用,通过抽象本质进行分类封装,从而使代码十分精简。
所有命令类型的子命令都采用了cmd类型,但实际可能是任何一种具体的命令类型。相当于在c语言里使用了类似面向对象的继承与多态的性质,所有的命令类型都继承自一个基础的结构体cmd。它们在实际命令执行的时候再根据type参数向下转型为真正的命令类型。这种技巧使代码变得非常简洁,命令的构造变得统一,构造命令时无需知道子命令是什么,便于递归构造命令串。
其他http://t.csdnimg.cn/PG4YL
参考