【Linux】进程程序替换
程序替换
如今我们已经了解了fork
出来的子进程的完整生命周期了,也曾经在认识fork
时提到过,命令行(以bash为例)的实现中大量运用到了fork
函数,大多数指令都是bash通过创建子进程去执行该命令的。bash就像一个任务处理平台,接受用户下达的指令,然后通过创建子进程并将该指令交由子进程完成。既能完成用户下达的命令,又能确保自生不会因指令下达有误而出错导致bash出问题。
同时,bash作为内核的解释器,将使用者的命令翻译给核心(kernel)处理,可以在外层(shell)就将恶意,错误的请求拒接,保证了内核的安全,所以这种设计是很优秀的。
而子进程要执行父进程下派的指令就需要依赖exec系列函数
进行程序替换。
exec函数命令理解
exec系列函数有六个,都为库函数;个数虽然有点多,但是只要结合名字理解还是很好记忆的。
实际上一共有七大程序替换函数,除了上面的六个库函数,还有一个系统级别的替换函数——execve
,而上面六个都是对execve
的封装,程序替换真正依靠的是execve
函数名 | 参数格式 | 是否需要指明路径 | 是否使用当前环境变量 |
---|---|---|---|
execl | 列表 | 是 | 是 |
execlp | 列表 | 否 | 是 |
execle | 列表 | 是 | 不是,必须自己组装 |
execv | 数组 | 是 | 是 |
execvp | 数组 | 否 | 是 |
execvpe | 数组 | 否 | 不是,必须自己组装 |
execve | 数组 | 是 | 不是,必须自己组装 |
- execve是系统级函数
这些函数原型看起来很容易混,但只要掌握了规律就很好记。
l
为list
: 表示参数采用列表组织v
为vector
: 表示参数用数组组织p
为path
: 自动搜索环境变量PATH,即不需要指明路径,只需要文件名即可e
为env
: 表示自己维护环境变量
注意: 带p
的替换函数可以不用带路径,只需要程序名即可;但是所替换的程序的所处路径必须在PATH
环境变量中,否则无法执行。
函数解释:
- 这些函数如果调用成功则加载新的程序从启动代码开始执行,不再返回。
- 如果调用出错则返回-1
- 所以exec函数只有出错的时候返回值,而没有成功的返回值
替换原理
用fork创建子进程后执行的是和父进程相同的程序(但有可能执行不同的代码分支),子进程往往要调用一种exec函数以执行另一个程序。当进程调用一种exec函数时,该进程的用户空间代码和数据完全被新程序替换,从新程序的启动例程开始执行。调用exec并不创建新进程,所以调用exec前后该进程的id并未改变。
- 注意,fork创建子进程后才进行替换,所以调用exec前后该进程的id并未改变。
七大替换函数
以下替换函数的头文件都为#include <unistd.h>
。
带l,也就是参数以列表形式组织的替换函数中,会发现第三个参数为...
,这是可变参数列表
实际上我们早就接触过可变参数列表——printf
可变参数列表允许不同类型,不限个数的参数。
为配合exec的演示,采用下面代码演示。
演示模板:
int main()
{
char* const argv[]=
{
"mytest",
NULL
};
char* const env[]=
{
"Myvalue=1030",
NULL
};
pid_t pid = fork();
if(pid<0)
{
perror("fork");
exit(-2);
}
else if(pid==0)
{
//child
int exid;
//exid= execle("./mytest","mytest",NULL,env);//*不带选项执行,但NULL一定记得要有
exid = execvpe("./mytest",argv,env);//虽然是PATH,但是指定路径也行。
//exid = execve("./mytest",argv,env);//系统调用,其余六个exec函数都是库函数,都是对execve的封装
printf("execle failed exid:%d\n",exid);
exit(exid);//-1
}
//father
int status=0;
pid_t rid=waitpid(pid,&status,0);
if(rid>0)
{
printf("waitpid success pid:%d\n",rid);
if(WIFEXITED(status))
{
printf("exit code:%d\n",WEXITSTATUS(status));
}
else
{
printf("kill by sig:%d\n",status&0x7f);
}
}
return 0;
}
execl
int execl(const char *pathname, const char *arg, ... /* (char *) NULL */);
返回值:
- 失败返回-1,成功没有返回值。
参数:
- 1:待替换程序的路径,如
ls
:/usr/bin/ls
- 2:待替换程序的名称,如
ls
- 3:待替换程序的选项,如
-l -a
- 注意可变参数列表处必须以NULL结尾,即使替换的程序不带选项也必须要有NULL。
使用:
exid=execl("/usr/bin/ls", "ls","-l", NULL);
execlp
int execlp(const char *file, const char *arg, .../* (char *) NULL */);
返回值:
- 失败返回-1,成功没有返回值。
参数:
- 1:待替换程序的名称,如
ls
- 2:待替换程序的名称,如
ls
- 3:待替换程序的选项,如
-l -a
- 注意可变参数列表处必须以NULL结尾,即使替换的程序不带选项也必须要有NULL。
带p的能够自己查找路径,只需要提供替换程序的名称即可,比较好用。
使用:
exid=execlp("ls", "ls","-l", NULL);//推荐
execv
int execv(const char *pathname, char *const argv[]);
参数2所需的指针数组:
char*const myargv[]=//ls -l
{
"ls",
"-l",
NULL
};
返回值:
- 失败返回-1,成功没有返回值。
参数:
- 1:待替换程序的路径,如
ls
:/usr/bin/ls
- 2:包含所替换程序选项的指针数组,如
char*const myargv[]
- 该指针数组也必须以NULL结尾。
使用:
exid=execv("/usr/bin/ls", myargv);
execvp
int execvp(const char *file, char *const argv[]);
参数2所需的指针数组:
char*const myargv[]=//ls -l
{
"ls",
"-l",
NULL
};
返回值:
- 失败返回-1,成功没有返回值。
参数:
- 1:待替换程序的名称,如
ls
- 2:包含所替换程序选项的指针数组,如
char*const myargv[]
- 该指针数组也必须以NULL结尾。
使用:
exid=execvp("ls", myargv);
剩余的三个程序替函数都需要自己配置环境变量,为了更好演示,采用另一个演示模板;同时,替换的程序换成我们自己所写的 mytest.cpp
mytest
#include<unistd.h>
#include<sys/types.h>
#include <iostream>
using namespace std;
int main(int argc,char*const argv[],char* const env[])
{
printf("This is mytest pid:%d ppid:%d\n",getpid(),getppid());
printf("Myvalue=%s\n",getenv("Myvalue"));
exit(0);
}
演示模板:
extern char** environ;
int main()
{
pid_t pid=fork();
if(pid<0)
{
perror("fork");
exit(-1);
}
if(pid==0)
{
//child
char*const myargv[]=//ls -l
{
"ls",
"-l",
NULL
};
int exid;
//exid=execl("/usr/bin/ls", "ls","-l", NULL);
//exid=execlp("ls", "ls","-l", NULL);//推荐
//exid=execv("/usr/bin/ls", myargv);
exid=execvp("ls", myargv);
printf("exec failed exid:%d\n",exid);
exit(-1); // 如果子进程有此退出码,说明替换失败
}
int status=0;
pid_t rid=waitpid(pid,&status,0);
if (rid > 0)
{
printf("waitpid success pid:%d\n", rid);
if (WIFEXITED(status))
{
printf("exit code:%d\n", WEXITSTATUS(status));
}
else
{
printf("kill by sig:%d\n", status & 0x7f);
}
}
return 0;
}
execle
int execle(const char *pathname, const char *arg, ... /* (char *) NULL*/, char *const envp[] );
返回值:
- 失败返回-1,成功没有返回值。
自设环境变量表,也要以NULL结尾。
char* const env[]={"Myvalue=1030",NULL};
参数:
- 1:待替换程序的路径,如
mytest
:./mytest
- mytest为自己所写程序;mytest在当前路径下
- 2:待替换程序的名称,如
mytest
- 3:待替换程序的选项:此处为自己的程序,没有选项可带,但是可变参数列表一定要以NULL结尾。
- 4:要替换的环境变量表;本质也是指针数组。
- man手册有点bug,应该是四个参数
使用:
exid= execle("./mytest","mytest",NULL, env);//可以不带选项执行,但NULL一定记得要有
可以看到在替换的程序中的环境变量确实有我们传进去的Myvalue
。
execvpe
int execvpe(const char *file, char *const argv[], char *const envp[]);
char* const argv[]=
{
"mytest",
NULL
};
返回值:
- 失败返回-1,成功没有返回值。
参数:
- 1:待替换程序的名称
- 2:存放对应替换程序所使用的命令行参数的指针数组,如
char*const myargv[]
- 该指针数组也必须以NULL结尾。
- 3:所要传入的环境变量表,以NULL结尾
注意:虽然第一个参数可以是名称,但这只是对于已处于环境变量PATH路径中的程序而言,如果替换的程序的路径不在PATH中,请使用绝对路径。
使用:
exid = execvpe("./mytest",argv,env);//虽然是PATH,但是指定路径也行。
mytest
不在环境变量PATH中
execve
int execve(const char *pathname, char *const argv[], char *const envp[]);
返回值:
- 失败返回-1,成功没有返回值。
参数:
- 1:待替换程序的路径
- 2:存放对应替换程序所使用的命令行参数的指针数组,如
char*const myargv[]
- 该指针数组也必须以NULL结尾。
- 3:所要传入的环境变量表,以NULL为结尾
使用:
exid = execve("./mytest",argv,env);
以上就是进程替换的相关函数,关系如下:
了解这些以后,再加上之前的知识,我们也可以做一个简易版的bash命令行。
原文地址:https://blog.csdn.net/Mesar33/article/details/143535426
免责声明:本站文章内容转载自网络资源,如本站内容侵犯了原著者的合法权益,可联系本站删除。更多内容请关注自学内容网(zxcms.com)!