上节中已经对后台作业进行了简单处理,基本上要实现的功能已经完了,下面回过头来,对代码进行一个调整,把写得不好的地方梳理一下,
给代码加入适当的注释,这种习惯其实是比较好了,由于在开发的时候时间都比较紧,都只是想办法去尽快实现,而肯定会有一些代码是写得不太好的,所以有时间的话最好是从头至尾将整个代码进行梳理,也许在梳理的过程中会发现许多不足的地方,好了,下面开始:
![](https://i-blog.csdnimg.cn/blog_migrate/b912058f4c8926ce671feed0f5694648.png)
而这个信号安装函数是在init.c中实现的:
![](https://i-blog.csdnimg.cn/blog_migrate/27bef6893f815fe93799af5719bc8d63.png)
接下来进行shell循环:
![](https://i-blog.csdnimg.cn/blog_migrate/bd37c9b2ba4047053db57fa07de023de.png)
它的实现是在parse.c中:
![](https://i-blog.csdnimg.cn/blog_migrate/5c32085a6df466d8ac34f74e7b9fcb01.png)
如注释所示,可以挪至init.c中:
![](https://i-blog.csdnimg.cn/blog_migrate/714c739274f6c84053d585ea5bd467b2.png)
接下来,获取命令:
![](https://i-blog.csdnimg.cn/blog_migrate/8617147316c263efcc9e2d6068633d8e.png)
![](https://i-blog.csdnimg.cn/blog_migrate/dfa2c72246360b888ae9f9b345c4c25e.png)
然后解析命令:
![](https://i-blog.csdnimg.cn/blog_migrate/d118e2c4f15c372ada65a491fbff2a8c.png)
接下来的这句,是为了测试,在发布时可以注释掉了:
![](https://i-blog.csdnimg.cn/blog_migrate/42f395f58461da3a5e44eb91e4a8be57.png)
最后执行命令:
![](https://i-blog.csdnimg.cn/blog_migrate/ad3484d38ae7a3664d8a65734001b1c7.png)
这个方法里面的代码有点乱,下面将其实现抽取到另外一个文件中,使得该函数要看起来清爽一些:
![](https://i-blog.csdnimg.cn/blog_migrate/879f8fa2942cf4b5a80d34fe677093e8.png)
![](https://i-blog.csdnimg.cn/blog_migrate/37f5ec95722f7b8fdc738aec92aad443.png)
![](https://i-blog.csdnimg.cn/blog_migrate/03758ea2d34ba5fbb577ec7a501b703c.png)
![](https://i-blog.csdnimg.cn/blog_migrate/83f522414d15eb6f9619113cab932ffc.png)
其实现execute.c:
#include "execute.h"
#include "def.h"
#include "externs.h"
#include <unistd.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <linux/limits.h>
#include <fcntl.h>
void forkexec(int i){
pid_t pid;
pid = fork();
if(pid == -1) {
/* 创建进程失败了 */
ERR_EXIT("fork");
}
if(pid > 0) {
/* 父进程 */
if (backgnd == 1)
printf("%d\n", pid);
lastpid = pid;
} else if(pid == 0) {
/* 子进程 */
/* 表示将第一条简单命令的infd重定向至/dev/null,其中cmd[i].infd == 0只有可能是第一条简单命令 */
/* 当第一条命令试图从标准输入获取数据的时候立既返回EOF */
if(cmd[i].infd == 0 && backgnd == 1){
//屏蔽后台作业,因为没有实现作业控制
cmd[i].infd = open("/dev/null", O_RDONLY);
}
/* 将第一个简单命令进程作为进程组组长 */
if(i == 0){
setpgid(0, 0);
}
if(cmd[i].infd != 0){
//说明该命令的输入是指向管道的读端
close(0);
dup(cmd[i].infd);
}
if(cmd[i].outfd != 1){
//说明该命令的输出指向的是管道的写端
close(1);
dup(cmd[i].outfd);
}
/* 关闭3以上的所有文件描述符 */
/*int i;
for(i=3; i<OPEN_MAX; ++i){
close(i);
}*/
/*前台作业能够接收SIGINT,SIGQUIT信号,这两个信号就要恢复成默认操作*/
if(backgnd == 0){//非后台作业
signal(SIGINT, SIG_DFL);
signal(SIGQUIT, SIG_DFL);
}
/* 开始替换进程 */
execvp(cmd[i].args[0], cmd[i].args);
/* 如果执行到这句,则证明替换失败了 */
exit(EXIT_FAILURE);
}
}
int execute_disk_command(void){
/* ls | grep init | wc -w */
if(cmd_count == 0) {
return 0;
}
if(infile[0] != '\0'){
cmd[0].infd = open(infile, O_RDONLY);
}
if(outfile[0] != '\0'){
if(append)//说明是以追加的方式
cmd[cmd_count-1].outfd = open(outfile, O_WRONLY | O_CREAT | O_APPEND, 0666);
else
cmd[cmd_count-1].outfd = open(outfile, O_WRONLY | O_CREAT | O_TRUNC, 0666);
}
/* 因为后台作业不会调用wait等待子进程退出,为避免僵尸进程,可以忽略SIGCHLD信号 */
if(backgnd == 1){
signal(SIGCHLD, SIG_IGN);
}else{
signal(SIGCHLD, SIG_DFL);
}
int i;
/* 管道描述符 */
int fds[2];
int fd;
for(i=0; i<cmd_count; ++i){
/* 如果不是最后一条命令,则需要创建管道 */
if(i < cmd_count-1){
pipe(fds);
/* 第一条命令的输出不再是标准输出,而是管道的写端 */
cmd[i].outfd = fds[1];
/* 第二条命令的输入不再是标准输入,而是管道的读端 */
cmd[i+1].infd = fds[0];
}
/* 创建一个进程,并且替换成系统命令 */
forkexec(i);
if((fd = cmd[i].infd) != 0)
close(fd);
if((fd = cmd[i].outfd) != 1)
close(fd);
}
if(backgnd == 0){//如果是非后台作业
while(wait(NULL) != lastpid)
;
}
}
将其forkexec函数也抽取到execute.c文件中,下面来进行编译一下:
![](https://i-blog.csdnimg.cn/blog_migrate/2321ef98520c756c807889ce6bdc4774.png)
![](https://i-blog.csdnimg.cn/blog_migrate/d66353c1c6ae157b16146970a4962aea.png)
另外在编译成,需要修改一下Makefile:
![](https://i-blog.csdnimg.cn/blog_migrate/7ea08f3614a2f935c9ecaa7929263f0f.png)
修改这么多后下面编译一下:
![](https://i-blog.csdnimg.cn/blog_migrate/cd9dfd3c0648eebabc62c46584019e22.png)
好了,对于上面的实现都解释的外部命令,那对于系统的内部命令还需要兼容,下面主要是来实现内部命令的解析:
首先要判断是否是内部命令,如果是,则执行内部命令:
![](https://i-blog.csdnimg.cn/blog_migrate/d77ded724955a0ee72655b3d8cda2a20.png)
![](https://i-blog.csdnimg.cn/blog_migrate/1293ace8112ef1fa1b6dfa8f0a874959.png)
![](https://i-blog.csdnimg.cn/blog_migrate/3a2fde344f8589dbc74d5799af26cdfa.png)
![](https://i-blog.csdnimg.cn/blog_migrate/6304fcff56bfa439308665a83e8fd71e.png)
另外需要将builtin.h包含在parse.c文件中:
![](https://i-blog.csdnimg.cn/blog_migrate/b2f1a874d10e2ced60f3104963c8808e.png)
下面来编译一下:
![](https://i-blog.csdnimg.cn/blog_migrate/4578c3985ae75870885eaa7362142bed.png)
说明忘了将builtin.o文件加入到Makefile中了,修改并再编译:
![](https://i-blog.csdnimg.cn/blog_migrate/f924c7209e5f288a8d039f83270c822d.png)
![](https://i-blog.csdnimg.cn/blog_migrate/016d6f839b003baa38ed2058136dd1cf.png)
一大堆错,不用着急,一个个解决,首先builtin.c文件中也需要用到check函数,而之前是定义在parse.c中,这时应该将其定义在parse.h中,让builtin.c来包含它既可,另外还需包含一些系统头文件:
![](https://i-blog.csdnimg.cn/blog_migrate/0d6a4939718ecec17f8f6991cd3126ab.png)
![](https://i-blog.csdnimg.cn/blog_migrate/6016c0eb5678eeecd9ae695435c45fc9.png)
在builtin.c中去包含parse.h文件:
![](https://i-blog.csdnimg.cn/blog_migrate/44e14e2446f69af51aff3610420416dc.png)
下面再来make并执行:
![](https://i-blog.csdnimg.cn/blog_migrate/a7e5a78774046c63cb76affe6101a648.png)
思考一个问题:系统有大量的内部命令,那是不是每解析一个内部命令,我们都要在builtin.c中加一个判断语句,这样会造成builtin函数会越来越庞大,所以这种实现方式还是不太灵活,下面改用数组来避免这种情况的发生:
![](https://i-blog.csdnimg.cn/blog_migrate/b1c0aae91d821708f60721feae4d2892.png)
![](https://i-blog.csdnimg.cn/blog_migrate/3193b6b6b28ca2dd58259f3e77100949.png)
最后builtin.c的代码如下:
#include "builtin.h"
#include "parse.h"
#include "externs.h"
#include <stdlib.h>
#include <stdio.h>
typedef void (*CMD_HANDLER)(void);
typedef struct builtin_cmd
{
char *name;
CMD_HANDLER handler;
} BUILTIN_CMD;
void do_exit(void);
void do_cd(void);
void do_type(void);
BUILTIN_CMD builtins[] =
{
{"exit", do_exit},
{"cd", do_cd},
{"type", do_type},
{NULL, NULL}
};
/*
* 内部命令解析
* 返回1表示为内部命令,0表示不是内部命令
*/
int builtin(void)
{
/*
if (check("exit"))
do_exit();
else if (check("cd"))
do_cd();
else
return 0;
return 1;
*/
int i = 0;
int found = 0;
while (builtins[i].name != NULL)
{
if (check(builtins[i].name))
{
builtins[i].handler();
found = 1;
break;
}
i++;
}
return found;
}
void do_exit(void)
{
printf("exit\n");
exit(EXIT_SUCCESS);
}
void do_cd(void)
{
printf("do_cd ... \n");
}
void do_type(void)
{
printf("do_type ... \n");
}
编译运行:
![](https://i-blog.csdnimg.cn/blog_migrate/3b3af08e92c25409d2e430cd1aaec9e3.png)
好了,关于内部命令的具体实现这里就不多说了,主要是还是实现其原理,这样自己的一个小型的shell程序就已经完成了,跟系统的shell程序还相差很多,但是通过这个程序足以可以将之前学的知识给串接起来, 达到一个很好的练习的目的,关于小型shell程序最终就实现到这,东西还是比较多,需好好消化。
【说明】:由于linux系统命令太多太多,下面只是简单实现几个,做一个范例,重点是知道其实现原理。
下面列一下该程序中使用到的各个文件的作用:
main.c----主调程序
def:h----定义常量,结构体
externs.h----定义extern的变量
init.h/init.c----做一些初始化操作
parse.h/parse.c----做命令的解析
execute.h/execute.c----做外部命令的执行
builtin.h/builtin.c----做内部命令的执行【只实现其原理】