加分题-mybash的实现
- 使用fork,exec,wait实现mybash
- 写出伪代码,产品代码和测试代码
- 发表知识理解,实现过程和问题解决的博客(包含代码托管链接)
1、了解bash
的功能
2、了解fork
的功能
3、了解exec
的功能
3、了解wait
的功能
4、编写mybash
思路
shell程序的主循环分为以下几步:
- 用户键入命令
- shell启动新进程执行程序
- shell等待程序执行完毕
- 程序结束,shell完成一次主循环
因此我们要思考:
- 如何在一个程序中新建进程执行另一个程序
- 如何等待程序的结束
一个程序如何执行另一个程序
exec()函数簇包括7个和执行程序有关的函数,主要区别在于执行是按文件名还是路径名,传递参数的方式等,这里我使用的是execvp(),函数原型如下:
int execvp(const char *filename,char *const argv[]);
函数参数为一个文件名和一个参数列表。比如说,如果要在一个程序中运行ls程序,我们就可以调用函数execvp(“ls”,arglist),函数会在环境变量中搜索ls程序,并将命令参数arglist传递给ls程序
让原程序也能够存活
shell程序是一个无止境的循环,不能够执行一次命令就终止了,因此我们必须想办法让原程序执行命令后还能够继续等待命令的完成。所以我们要新建一个进程,让命令在这个新建的进程中调用execvp函数,这样我们的shell函数就不会被清除了。这里我们使用的系统调用就是fork()函数。
fork函数
调用fork函数能够复制调用该函数的进程(写时复制,只有产生写操作时会对将要写的部分进行复制)。复制产生新的进程后,我们再调用execvp函数,这样就能既保存shell程序和又能执行新程序。
那么我们如何判断哪个进程执行新程序呢?实际上fork的返回值就能判断进程是子进程还是父进程。
fork函数的返回值
fork函数很特殊的地方在于一次调用会产生两个返回值,父进程返回值为子进程的ID,子进程返回值为0。原因在于,父进程有可能有很多子进程,因此必须在调用子进程时获取子进程ID,而子进程只有一个父进程,可以通过调用getppid函数得到父进程ID。了解了父子进程返回值的不同,就能够通过判断返回值来决定进程是执行新程序还是等待子程序结束。
wait函数
如果只使用fork和execvp函数,shell程序不会等待子程序结束,而会自顾自地继续主循环,为了等待子进程的结束,我们要使用系统的wait函数。
linux系统中,当一个进程终止时,内核会向父进程发送SIGCHLD信号。系统默认是忽略SIGCHLD信号的,但是通过调用wait函数(还有waitpid函数),可以获取子进程的结束状态:
pid_t wait(int *statloc);
pid_t waitpid(pid_t pid,int *statloc,int options);
调用wait
函数后
-如果所有子进程都还在运行,则阻塞
- 如果一个子进程已经终止,正等待父进程获取其终止状态,则获取子进程终止状态并立即返回
- 如果没有子进程则立即出错返回
伪代码
while(1)
{
fgets(命令行输入);
if(内置的shell命令)
{
解释命令;
}
else if(可执行文件)
{
新的子进程加载并运行文件;
}
}
产品代码
#include <stdio.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <stdlib.h>
#include <string.h>
#define MAX 128
void eval(char *cmdline);
int parseline(char *buf, char **argv);
int builtin_command(char **argv);
int main()
{
char cmdline[MAX];
printf("Hello 20165334 bash!
");
while(1)
{
printf("> ");
fgets(cmdline, MAX, stdin);
if(feof(stdin))
exit(0);
eval(cmdline);
}
}
void eval(char *cmdline)
{
char *argv[MAX];
char buf[MAX];
int bg;
pid_t pid;
strcpy(buf,cmdline);
bg = parseline(buf,argv);
if(argv[0]==NULL)
return;
if(!builtin_command(argv))
{
if((pid=fork()) == 0)
{
if(execvp(argv[0],argv) < 0)
{
printf("%s : Command not found.
",argv[0]);
exit(0);
}
}
}
if(!bg)
{
int status;
if(waitpid(-1,&status,0) < 0)
printf("waitfg: waitpid error!");
}
else
{
printf("%d %s",pid, cmdline);
return;
}
}
int builtin_command(char **argv)
{
if(!strcmp(argv[0], "quit"))
exit(0);
if(!strcmp(argv[0],"&"))
return 1;
return 0;
}
int parseline(char *buf,char **argv)
{
char *delim;
int argc;
int bg;
buf[strlen(buf)-1]=' ';
while(*buf && (*buf == ' '))
buf++;
argc=0;
while( (delim = strchr(buf,' ')))
{
argv[argc++] = buf;
*delim= ' ';
buf = delim + 1;
while(*buf && (*buf == ' '))
buf++;
}
argv[argc] = NULL;
if(argc == 0)
return 1;
if((bg=(*argv[argc-1] == '&')) != 0)
argv[--argc] = NULL;
return bg;
}
测试代码
ls -a
git --version