shell
什么是shell?
shell就像是在linux中的一个媒婆,我们给shell命令,shell来告诉系统我们需要做什么,为我们牵线,实现调用操作系统提供给我们的接口,shell提供了你与操作系统之间通讯的方式。
linux内核(kernel),我们无法直接触碰到,而是通过shell,shell是操作系统最外面的一层。shell管理你与操作系统之间的交互:等待你输入,向操作系统解释你的输入,并且处理各种各样的操作系统的输出结果,解释给你看。
下图中红圈的东西即shell展现在我们面前的形式。
自己写简单shell的准备工作
fork()函数创建子进程
fork函数将运行着的程序分成2个(几乎)完全一样的进程,每个进程都启动一个从代码的同一位置开始执行的线程。这两个进程中的线程继续执行,就像是两个用户同时启动了该应用程序的两个副本。
fork()
举例请移步我的关于进程的博客
wait()函数
函数原型:
pid_t wait(int* status);
pid_t waitpid(pid_t pid, int* status, int options);
wait()
:等待任意一个子进程退出,若没有子进程退出,则一直阻塞等待
waitpid()
:可以等待指定子进程,也可以等待任意一个子进程退出,可以设置为非阻塞等待
阻塞:为了完成一个功能发起一个函数调用,如果没有完成这功能则一直挂起等待功能完成才返回
非阻塞:为了完成一个功能发起一个函数调用,如果现在不具备完成的条件,则立即返回不等待
选项:
- WNOHANG 如果没有子进程退出,则立刻报错返回,如果有则回收资源
- WUNTRACED 若子进程进入暂停状态,则马上返回,但子进程的结束状态不予以理会
进程退出状态码获取:在wait的参数中存储了子进程的退出原因以及退出码,而参数中只用了低16位两个字节用于存储这些信息
在这低16位中,高8位存储的是退出码,程序运行完毕退出才会有,低7位存储的是异常状况,第8位是core dump标志
如何获取
WIFEXITED和WEXITSTATUS一般配合使用
WIFEXTED(status) 这个宏用来获取是否正常退出,正常退出获得true
WEXITSTATUS(status) 该宏只可在WIFEXITED为true时使用,获取正常退出的状态码
WIFSIGNALED和WTERMSIG
WIFSIGNALED(status) 这个宏用来获取是否异常退出,异常退出获得true
WTERMSIG(status) 该宏只可在WIFSIGNALED为true时使用,获取异常退出的状态码
子进程替换代码——exec类函数
exec函数族作用是程序替换,如果替换成功,那么源代码的exec后的代码都不会运行,除非出错,也就是说如果在exec后写printf,不会有任何输出
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* envp[]);
int execv(const char* path, char* const argv[]);
int execvp(const char* file, char* const argv[]);
int execve(const char* path, char* const argv[], char *const envp[]);
这六个函数名字和参数都相近,用起来也是差不多,其中’l’(list)表示使用参数列表,‘v’(vector)表示参数使用数组,‘p’(path)表示能够自动搜索环境变量PATH,‘e’(env)表示需要自己维护环境变量:
- 参数为
const char* path
的,需要告诉操作系统程序文件的全路径
参数为const char* file
的,不需要告诉路径,只需要文件名即可,会自动到PATH中的路径下查找 - execle和execve不从父进程继承环境变量,由用户自己组织环境变量
其他函数(不以e结尾的函数)从父进程继承环境变量 - execl的三个函数更符合我们平时的使用习惯,以
ls -l
为例,使用时输入int execlp("ls", "ls", "-l");
execv的三个函数则用指针数组的形式
梳理shell的执行过程
- 获取终端输入
- 解析输入
- 创建一个子进程,子进程进行程序替换
- 父进程等待子进程运行完毕,收尸
代码
感谢孙雨墨大佬指出的bug,代码已经修改,向大佬学习
#include <stdio.h>
#include <stdlib.h>
#include <errno.h>
#include <string.h>
int argc = 0;
char* argv[32] = {NULL};
int div_commond(char* buf)
{
if (buf == NULL)
return -1;
argc = 0;
char* front = buf;
char* back = buf;
//不断分割命令成指针数组
while (1)
{
while (*front == ' ')
front++;
if (*front == '\0')
break;
back = front;
while (*back != ' ' && *back != '\0')
back++;
if (*back == '\0')
{
argv[argc++] = front;
break;
}
*back = '\0';
argv[argc++] = front;
front = back + 1;
}
argv[argc++] = NULL;
if (argc == 1)
return -1;
return 0;
}
int exec_cmd()
{
int pid = fork();
if (pid < 0)
{
perror("fork failed");
return -1;
}
else if (pid == 0)
{
//替换进程
execvp(argv[0], argv);
exit(0);
}
else
{
//wait用法,等待子进程退出,退出后一个状态码会存入statu
//用WIFEXITED这个宏来获取退出值是否是正常退出
int status;
wait(&status);
if (WIFEXITED(status) == 1)
printf("%s\n", strerror(WEXITSTATUS(status)));
}
return 0;
}
int main()
{
char buf[1024];
while (1)
{
memset(buf, 0x00, 1024);
printf("------shell>>>>>>");
//读取到\n为止,删除缓冲区数据
scanf("%[^\n]%*c", buf);
if (div_commond(buf) != 0)
{
char black_hall[100];
fgets(black_hall, 100, stdin);
printf("wrong commond\n");
continue;
}
exec_cmd();
}
return 0;
}