所谓shell,就是外壳程序,是替用户和操作系统沟通的中间载体,然而shell也不是自己去执行用户的命令,他是通过创建子进程来替用户完成任务,这样即使任务没有完成,中间出现差错的话,也不会影响到shell。
了解shell的运行原理之后,我们就来编写一个我们自己的简易shell。
上面有说到,shell是通过创建子进程来执行代码的,然而一般创建的子进程都是会与父进程共享代码而数据以写时拷贝的形式各自私有,这样要想让子进程运行自己的代码,就得通过进程的程序替换。进程的程序替换有一套以exec开头的函数族:
int execl(const char *path,const char *arg,...);
int execlp(const char *file,const char *arg,...);
int execle(const char *path,const char *arg,...char *const envp[]);
int execv(const char *file,char *const argv[]);
int execve(const char *file,char *const argv[],char *const envp[]);
这些函数不带字母p的第一个参数都要传入文件的绝对路径,而带p的只要传入文件名;带l的则要求将新程序的每个命令行参数都当做参数传给它,它的参数是可变的,最后一个参数为NULL指针;带v的则应该先构造一个指向各参数的指针数组,然后将该数组的首地址作为参数传给它,当然最后一个指针也应该是NULL。
事实上只有execve是真正的系统调用,其他五个函数最终都是调用execve。所以execve函数在man手册第二节,其他都在第三节。
程序代码:
运行:
很显然我们自己实现的shell还是很粗糙,像命令行删除字符,重定向,管道等问题它都无法解决,但是相信在不久的将来我们一定能完善这些功能。
程序源码:
#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <unistd.h>
int main()
{
char cmd[128]; //创建接受键盘输入的缓冲区
while(1)
{
printf("[hm@localhost myshell]# ");
fflush(stdout); //从缓冲区强制刷出上面printf的内容
ssize_t _s = read(0, cmd, sizeof(cmd)-1); //从键盘读入信息
if(_s > 0)
{
cmd[_s-1] = '\0';
}
else
{
perror("read"); //读数据出错
return 1;
}
int i = 1;
char *_argv[32];
_argv[0] = cmd;
char *start = cmd;
while(*start)
{
if(isspace(*start)) //以空格作为命令之间的分割符
{
*start = '\0';
start++;
_argv[i] = start;
i++;
continue;
}
start++;
}
_argv[i] = NULL;
pid_t id = fork();
if(id <0)
{
perror("fork");
}
else if(id == 0)
{
execvp(_argv[0],_argv); //进程的程序替换函数
exit(1);
}
else
{
int status = 0;
pid_t ret = waitpid(id,&status,0); //父进程等待子进程的状态
if(ret > 0)
{
}
else
{
perror("wait");
}
}
}
}