进程间程序替换和minishell
一、进程间程序替换的原理:
1.用fork创建子进程后执行的是和父进程相同的程序(但有可能执行不同的代码分支),这样就达不到创建进程的目的。
2.子进程往往要调用一种exec函数以执行另一个程序。当进程调用一种exec函数时,该进程的用户空间代码和数据完全被新程序替换,从新程序的启动例程开始执行。
3.调用exec并不创建新进程,只是把进程的数据和代码替换一下,让其执行别的逻辑,所以调用exec前后该进程的pid并未改变。
如图所示,有一个进程创建出的子进程本来是拷贝父进程的PCB,如果不进行分流,那么父子进程将会执行一摸一样的逻辑,所以现在用exec函数族把子进程的数据段和代码段替换掉,让其执行别的逻辑
二、exec函数族的用法及
1.execl函数:
int execl(const char *path, const char *arg, ...);
参数 | 解释 |
---|---|
const char *path | 带路径的可执行程序(“/usr/bin/ls”) |
const char *arg | 给可执行程序传递的参数,但必须是以可执行程序名作为第一个参数(ls -a -l) |
… | 可变参数列表,必须以NULL结尾,作为终止符 |
2.execlp函数
int execlp(const char *file, const char *arg, ...);
参数 | 解释 |
---|---|
const char *file | 可执行程序的名称,但是注意该可执行程序必须在PATH环境变量中可以被找到,否者就会替换失败 |
const char *arg | 给可执行程序传递的参数,但必须是以可执行程序名作为第一个参数(ls -a -l) |
… | 可变参数列表,必须以NULL结尾,作为终止符 |
总结一下exec函数族中代p和不带p的区别:如果带p,说明进程运行时会去环境变量中寻找,第一个参数直接传递可执行程序名即可。对于不带p,第一个参数必须指明可执行程序的路径。
3.execle函数
int execle(const char *path, const char *arg, ...,char *const envp[]);
参数 | 解释 |
---|---|
const char *path | 带路径的可执行程序 |
const char *arg | 给可执行程序传递的参数,但必须是以可执行程序名作为第一个参数(ls -a -l) |
… | 可变参数列表,必须以NULL结尾,作为终止符 |
char *const envp[] | 程序员自己组建环境变量,如果没有则认为当前进程没有环境变量,注意⚠️环境变量也是以NULL结尾的 |
4.execv函数
总结一下带e和不带e的区别:如果函数带e表示需要自己组装环境变量。不带e则不需要自己组装环境变量
int execv(const char *path, char *const argv[]);
参数 | 解释 |
---|---|
const char *path | 带路径的可执行程序 |
const char *argv[] | 给可执行程序传递的参数,但必须是以可执行程序名作为第一个参数,以NULL结尾 |
5.execvp函数
int execvp(const char *file, char *const argv[]);
参数 | 解释 |
---|---|
const char *file | 可执行程序名,不带路径 |
char *const argv[] | 给可执行程序传递的参数,但必须是以可执行程序名作为第一个参数,以NULL结尾 |
6.execve函数
int execve(const char *path, char *const argv[], char *const envp[]);
参数 | 解释 |
---|---|
const char *path | 带路径的可执行程序名 |
char *const argv[] | 给可执行程序传递的参数,但必须是以可执行程序名作为第一个参数,以NULL结尾 |
char *const envp[] | 程序员自己组建环境变量,如果没有则认为当前进程没有环境变量,注意⚠️环境变量也是以NULL结尾的 |
用下面的方法方便理解记忆:
- l(list) : 表示参数采用列表
- v(vector) : 参数用数组
- p(path) : 有p自动搜索环境变量PATH
- e(env) : 表示自己维护环境变量
#include <unistd.h>
int main()
{
char *const argv[] = {"ps", "-ef", NULL};
char *const envp[] = {"PATH=/bin:/usr/bin", "TERM=console", NULL};
execl("/bin/ps", "ps", "-ef", NULL);
// 带p的,可以使用环境变量PATH,无需写全路径
execlp("ps", "ps", "-ef", NULL);
// 带e的,需要自己组装环境变量
execle("ps", "ps", "-ef", NULL, envp);
execv("/bin/ps", argv);
// 带p的,可以使用环境变量PATH,无需写全路径
execvp("ps", argv);
// 带e的,需要自己组装环境变量
execve("/bin/ps", argv, envp);
exit(0);
}
三、它们之间的联系
1.联系:
从上图可以看到所有的库函数exec底层都是调用系统调用函数exeve函数
2.对于它们返回值的理解:
- 这些函数如果调用成功则加载新的程序从启动代码开始执行,不再返回。
- 如果调用出错则返回-1。
- 所以exec函数只有出错的返回值而没有成功的返回值
四、简易的shell
#include <stdio.h>
#include <sys/wait.h>
#include <ctype.h>
#include <stdlib.h>
#include <string.h>
#include <fcntl.h>
#include <unistd.h>
char g_command[1024];
//该函数的功能是获取命令行输入的数据
int GetCommand()
{
//因为开始获得的内存中的内容是不定的,所以使用前要先初始化
memset(g_command,'\0',sizeof(g_command));
//这里需要为'\0'留一个位置,不能把g_command全部用来读取内容,否者就没有结束标志,容易发生内存访问越界
if(fgets(g_command,sizeof(g_command)-1,stdin)==NULL)
{
printf("fgets is error!\n");
return -1;
}
//printf("g_command:%s\n",g_command);
return 0;
}
//解析字符串
char** DealCommand(char* command)
{
if(command==NULL&&*command=='\0')
{
printf("dealcommand is error\n");
return NULL;
}
//用来保存命令
static char* argv[1024]={0};
int argc=0;
while(*command)
{
//isspace函数用来去掉多余的空格
//isspace的返回值:如果当前字符是空格返回1,否则返回0
//注意'\0'在issapce函数中不算空格的,所以要进行判断
while(!isspace(*command)&&*command!='\0')
{
argv[argc]=command;
argc++;
//去找下一个空格
while(!isspace(*command)&&*command!='\0')
{
command++;
}
*command='\0';
}
command++;
}
argv[argc]=NULL;
//for(int i=0;i<argc;i++)
//{
// printf("%d:%s ",i,argv[i]);
//}
//printf("\n");
return argv;
}
//进行程序替换
int exec()
{
char** argv=DealCommand(g_command);
pid_t pid =fork();
if(pid<0)
{
printf("foek is error!\n");
return -1;
}
else if(pid==0)
{
//child
//如果argc中为NULL,就直接返回
if(argv[0]==NULL)
{
exit(-1);
}
//进行替换,execvp第一个参数是可执行程序名,第二个参数是该可执行程序的参数组成的数组
execvp(argv[0],argv);
//execl("/usr/bin/ls","ls","-a",NULL);
}
else
{
//father
waitpid(pid,NULL,0);
}
return 0;
}
int main()
{
//循环读数据
while(1)
{
printf("[dev@localhost dev]$ ");
int ret = GetCommand();
if(ret == -1)
{
//如果读取失败,继续循环读取,不能直接break调
continue;
}
//处理解析数据
//char** argv = DealCommand(g_command);
//进行替换
//exec(argv);
exec();
}
return 0;
}