进程替换的核心概念
进程替换的目的
让子进程执行父进程的部分代码或全新程序(如/bin/ls)。
避免创建新进程,直接复用原有进程的进程控制块(PCB)和资源。
**代码与数据替换:**新程序的代码段和数据段覆盖原进程的对应区域,但进程ID(PID)保持不变。
写时拷贝:
代码段通常为只读,替换时直接覆盖。
数据段通过页表映射实现写时拷贝,避免影响父进程。
执行流程:
替换成功后,原进程后续代码不再执行;若失败,exec函数返回错误码
进程替换的实现原理
调用流程
使用fork()创建子进程,子进程调用exec系列函数(如execl、execve)加载新程序。
exec函数通过系统调用execve()(唯一内核级函数)实现,其他函数(如execl)为库函数封装。
内存管理
代码段:新程序的ELF格式代码加载到内存,覆盖原进程代码段。
数据段:通过页表更新,将新程序数据映射到内存,原数据保留(若未修改)。
堆栈与堆:新程序的堆栈和堆完全替换,与原进程隔离
进程替换函数详解
Linux提供了6个exec系列函数,六种以exec开头的函数,统称exec函数,核心差异在于参数传递方式:
#include <unistd.h>`
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 *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) : 有p自动搜索环境变量PATH
e(env) : 表示自己维护环境变量
execl函数
原型: int execl(const char *path, const char *arg, …)(以NULL结尾的变参列表)
参数:需指定程序路径,后续参数为命令行参数(arg[0]通常为程序名)。
功能:不支持环境变量传递,仅替换代码和数据段。
示例:execl(“/bin/ls”, “ls”, “-l”,“-a”, NULL)。
代码示例:
#include<stdio.h>
#include<unistd.h>
int main()
{
printf("Test_execl...begin\n");
execl("/usr/bin/ls","ls","-l","-a",NULL);
printf("Test_execl...end\n");
return 0;
}
代码运行效果:
输出:列出当前目录下的文件(类似 ls -l -a 的结果)
代码为什么不执行printf(“Test_execl…end\n”); 这条?
因为代码执行execl(“/usr/bin/ls”,“ls”,“-l”,“-a”,NULL); 只要替换成功了,进程就不会继续往下执行,所以不执行后面的代码。只要继续往后执行,就替换失败了。其中,替换过程没有创建新的进程,而是把原来进程的代码是数据替换了。
创建子进程的方式调用execl函数:
代码示例:
#include<stdio.h>
#include<unistd.h>
#include<stdlib.h>
#include<sys/types.h>
#include<sys/wait.h>
int main()
{
printf("Test_execl...begin\n");
pid_t id = fork();
if(id==0)
{
sleep(1);
execl("/usr/bin/ls","ls","-l","-a",NULL);
exit(1);
}
//父进程
int status = 0;
pid_t rid = waitpid(id,&status,0);
if(rid>0)
{
printf("father wait success, child exit code: %d\n",WEXITSTATUS(status));
}
printf("Test_execl...end\n");
return 0;
}
运行效果:
调用完子进程,用子进程替换,可以让子进程去替换全新的程序,等子进程退出后,继续执行父进程的退出代码。
execv函数
原型:int execv(const char *path, char *const argv[])
参数:需传递程序路径和参数数组(argv以NULL结尾)。
功能:参数以数组形式传递,不支持环境变量。
示例:
char *argv[] = {“ls”, “-l”, NULL};
execv(“/bin/ls”, argv);
代码示例:
#include<stdio.h>
#include<unistd.h>
#include<stdlib.h>
#include<sys/types.h>
#include<sys/wait.h>
int main()
{
printf("Test_execl...begin\n");
pid_t id = fork();
if(id==0)
{
sleep(1);
char* const argv[] =
{
(char*)"ls",
(char*)"-l",
(char*)"-a",
(char*)"--color",
NULL
};
execv("/usr/bin/ls",argv);
exit(1);
}
//父进程
int status = 0;
pid_t rid = waitpid(id,&status,0);
if(rid>0)
{
printf("father wait success, child exit code: %d\n",WEXITSTATUS(status));
}
printf("Test_execl...end\n");
return 0;
}
代码运行效果:
execvp()函数
原型:int execvp(const char *file, char *const argv[])
参数:仅指定文件名,通过PATH查找,参数以数组形式传递。
功能:结合execv和execlp的特点。
示例:
char *argv[] = {“ls”, “-l”, NULL};
execvp(“ls”, argv)
示例代码:
#include<stdio.h>
#include<unistd.h>
#include<stdlib.h>
#include<sys/types.h>
#include<sys/wait.h>
int main()
{
printf("Test_execl...begin\n");
pid_t id = fork();
if(id==0)
{
sleep(1);
char* const argv[] =
{
(char*)"ls",
(char*)"-l",
(char*)"-a",
(char*)"--color",
NULL
};
execvp("ls",argv);
exit(1);
}
//父进程
int status = 0;
pid_t rid = waitpid(id,&status,0);
if(rid>0)
{
printf("father wait success, child exit code: %d\n",WEXITSTATUS(status));
}
printf("Test_execl...end\n");
return 0;
}
代码运行效果:
execvpe()函数
原型:int execvpe(const char *file, char *const argv[], char *const envp[]);
代码示例:
execvpe.c文件
#include<stdio.h>
#include<unistd.h>
#include<stdlib.h>
#include<sys/types.h>
#include<sys/wait.h>
int main()
{
printf("Test_execl...begin\n");
pid_t id = fork();
if(id==0)
{
sleep(1);
char* const argv[] =
{
(char*)"mypragma",
NULL
};
char* const envp[] =
{
(char*)"HAHA=111111",
(char*)"HEHE=222222",
NULL
}
execvpe("./mypragma",argv,envp);
}
//父进程
int status = 0;
pid_t rid = waitpid(id,&status,0);
if(rid>0)
{
printf("father wait success, child exit code: %d\n",WEXITSTATUS(status));
}
printf("Test_execl...end\n");
return 0;
}
mypragma.cc文件
#include<iostream>
#include<unistd.h>
using namespace std;
int main(int argc, char* argv[],char* env[])
{
int i = 0;
for(;argv[i];i++)
{
printf("atgv[%d]:%s\n",i,argv[i]);
}
printf("--------------------------\n");
for(i = 0;env[i];i++)
{
printf("env[%d]:%s\n",i,env[i]);
}
printf("--------------------------\n");
cout<<"hello C++, I am a C++ pragma! pid:"<<getpid()<<endl;
cout<<"hello C++, I am a C++ pragma! pid:"<<getpid()<<endl;
cout<<"hello C++, I am a C++ pragma! pid:"<<getpid()<<endl;
cout<<"hello C++, I am a C++ pragma! pid:"<<getpid()<<endl;
return 0;
}
makefile文件:
.PHNOY:all
all:mypragma execvpe
mypragma:mypragma.cc
g++ -o $@ $^
execvpe:execvpe.c
gcc -o $@ $^
.PHONY:clean
clean:
rm -f execvpe
代码运行效果: