【Linux进程控制】进程程序替换
目录
进程程序替换
替换函数
看现象
替换原理
多进程替换
exec*函数使用(部分),并且认识函数参数的含义
1.execl
2.execv
3.execvp
4.execvpe
execlp 和execlpe
替换函数总结
进程程序替换
替换函数
有六种以exec开头的函数,统称exec函数:
EXEC(3) Linux Programmer's Manual EXEC(3)
NAME
execl, execlp, execle, execv, execvp, execvpe - execute a file
SYNOPSIS
#include <unistd.h>
extern char **environ;
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 execvpe(const char *file, char *const argv[],
char *const envp[]);
Feature Test Macro Requirements for glibc (see feature_test_macros(7)):
execvpe(): _GNU_SOURCE
看现象
测试代码:
#include <stdio.h>
#include <unistd.h>
int main(){
printf("testexec ... begin!\n");
execl("/usr/bin/ls","ls","-a","-l",NULL);
printf("testexec ... end!\n");
return 0;
}
结果:
[wuxu@Nanyi lesson17]$ ./test
testexec ... begin!
total 56
drwxrwxr-x 2 wuxu wuxu 4096 Aug 25 15:18 .
drwx------ 11 wuxu wuxu 4096 Aug 24 19:49 ..
-rw-rw-r-- 1 wuxu wuxu 1 Aug 25 15:14 myprocess
-rw-rw-r-- 1 wuxu wuxu 182 Aug 25 15:18 myprocess.c
-rw-rw-r-- 1 wuxu wuxu 1809 Aug 24 21:34 task.c
-rwxrwxr-x 1 wuxu wuxu 8416 Aug 25 15:18 test
-rw-rw-r-- 1 wuxu wuxu 366 Aug 24 20:02 test1.c
-rw-rw-r-- 1 wuxu wuxu 934 Aug 24 20:16 test2.c
-rw-rw-r-- 1 wuxu wuxu 501 Aug 24 20:33 wait1.c
-rw-rw-r-- 1 wuxu wuxu 583 Aug 24 20:56 wait2.c
-rw-rw-r-- 1 wuxu wuxu 469 Aug 24 20:58 wait3.c
-rw-rw-r-- 1 wuxu wuxu 1407 Aug 24 21:24 wait4.c
通过观察我们发现:
◉ 第一个printf执行了
◉ ls命令被执行了
◉ 最后一个printf没有被执行
说明程序在execl
被ls
替换了,替换也是完完全全的,并不会执行后面的代码
替换原理
用fork创建子进程后执行的是和父进程相同的程序(但有可能执行不同的代码分支),子进程往往要调用一种exec函数以执行另一个程序。当进程调用一种exec函数时,该进程的用户空间代码和数据完全被新程序替换,从新程序的启动历程开始执行。调用exec并不创建新进程,所以调用exec前后该进程的id并未改变
站在被替换进程的角度:本质就是这个程序被加载到内存了;如何加载?exec*类似于一种Linux上的加载函数
多进程替换
fork创建子进程,让子进程自己去替换
创建子进程目的是让子进程完成任务:1️⃣ 让子进程执行父进程代码的一部分 2️⃣ 让子进程执行一个全新的程序
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/wait.h>
int main(){
printf("testexec ... begin!\n");
pid_t id = fork();
if(id == 0){
printf("child pid: %d\n", getpid());
sleep(2);
execl("/usr/bin/ls","ls","-a","-l",NULL);
exit(1);
}
// fahter
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("testexec ... end!\n");
return 0;
}
[wuxu@Nanyi lesson17]$ gcc -o test myprocess.c -std=c99
[wuxu@Nanyi lesson17]$ ./test
testexec ... begin!
child pid: 8233
total 56
drwxrwxr-x 2 wuxu wuxu 4096 Aug 25 16:29 .
drwx------ 11 wuxu wuxu 4096 Aug 24 19:49 ..
-rw-rw-r-- 1 wuxu wuxu 1 Aug 25 15:14 myprocess
-rw-rw-r-- 1 wuxu wuxu 540 Aug 25 16:29 myprocess.c
-rw-rw-r-- 1 wuxu wuxu 1809 Aug 24 21:34 task.c
-rwxrwxr-x 1 wuxu wuxu 8720 Aug 25 16:29 test
-rw-rw-r-- 1 wuxu wuxu 366 Aug 24 20:02 test1.c
-rw-rw-r-- 1 wuxu wuxu 934 Aug 24 20:16 test2.c
-rw-rw-r-- 1 wuxu wuxu 501 Aug 24 20:33 wait1.c
-rw-rw-r-- 1 wuxu wuxu 583 Aug 24 20:56 wait2.c
-rw-rw-r-- 1 wuxu wuxu 469 Aug 24 20:58 wait3.c
-rw-rw-r-- 1 wuxu wuxu 1407 Aug 24 21:24 wait4.c
father wait success, child exit code: 0
testexec ... end!
原理如图:即便是父子,也要保证独立性
exec*函数使用(部分),并且认识函数参数的含义
1.execl
函数原型,在前面我们已经使用过了,这里不过多介绍。关于exec*函数,我们不考虑它的返回值
int execl(const char *path, const char *arg, ...);
这里的 l 可以理解为list,path
传入绝对路径,arg
可变参数,依次传入命令,以及你想执行的指令,最后一个必须为NULL
2.execv
函数原型:
int execv(const char *path, char *const argv[]);
这里的 v 可以理解为vector,argv是一个指针数组,我们直接来使用
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/wait.h>
int main()
{
printf("testexec ... begin!\n");
pid_t id = fork();
if(id == 0)
{
printf("child pid: %d\n", getpid());
sleep(2);
char *const argv[] =
{
(char*)"ls",
(char*)"-l",
(char*)"-a",
(char*)"--color",
NULL
};
execv("/usr/bin/ls", argv);
exit(1);
}
// fahter
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("testexec ... end!\n");
return 0;
}
3.execvp
函数原型:
int execvp(const char *file, char *const argv[]);
用户可以不传要执行的文件的路径(但是文件名要传),直接告诉exec*,我要执行谁都行
p:查找这个程序,系统会自动在环境变量PATH中进行查找
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/wait.h>
int main()
{
printf("testexec ... begin!\n");
pid_t id = fork();
if(id == 0)
{
printf("child pid: %d\n", getpid());
sleep(2);
char *const argv[] =
{
(char*)"ls",
(char*)"-a",
(char*)"--color",
NULL
};
execvp("ls",argv);
exit(1);
}
// fahter
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("testexec ... end!\n");
return 0;
}
4.execvpe
函数原型:
int execvpe(const char *file, char *const argv[],char *const envp[]);
#define _GNU_SOURCE
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/wait.h>
int main()
{
printf("testexec ... begin!\n");
pid_t id = fork();
if(id == 0)
{
printf("child pid: %d\n", getpid());
sleep(2);
char *const argv[] =
{
(char*)"ls",
(char*)"-a",
(char*)"--color",
NULL
};
extern char** environ;
execvpe("ls",argv,environ);
exit(1);
}
// fahter
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("testexec ... end!\n");
return 0;
}
execlp 和execlpe
方法与上面相同,就不再一一介绍了
替换函数总结
函数名 | 参数格式 | PATH中可执行程序是否需要带绝对路径 | 是否使用当前环境变量 |
---|---|---|---|
execl | 列表 | 是 | 是 |
execlp | 列表 | 不是 | 是 |
execle | 列表 | 是 | 不是,需自己组装环境变量 |
execv | 数组 | 是 | 是 |
execvp | 数组 | 不是 | 是 |
execvpe | 数组 | 不是 | 不是,需自己组装环境变量 |
execve | 数组 | 是 | 不是,需自己组装环境变量 |
上面各个接口统称为加载器,它们为即将替换进来的可执行程序加载入参数列表、环境变量等信息。下面我们使用execvpe接口给自定义可执行程序传入命令行参数及环境变量,该可执行程序将会把命令行参数及环境变量打印至显示器
myprogma.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("argv[%d] : %s\n",i , argv[i]);
}
printf("-------------------------\n");
for(i = 0; env[i]; i++){
printf("env[%d]: %s\n",i , argv[i]);
}
printf("-------------------------\n");
cout << "hello C++, I am a C++ program! : " << getpid() << endl;
cout << "hello C++, I am a C++ program! : " << getpid() << endl;
cout << "hello C++, I am a C++ program! : " << getpid() << endl;
cout << "hello C++, I am a C++ program! : " << getpid() << endl;
return 0;
}
testfin.c:
#define _GNU_SOURCE
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/wait.h>
int main(){
printf("testexec ... begin! \n");
pid_t id = fork();
if(id == 0){
putenv("HHHH=111111111111111111");
// 我的父进程本身就有一批环境变量!!!, 从bash来
char *const argv[] =
{
(char*)"mypragma",
(char*)"-a",
(char*)"-b",
NULL
};
extern char** environ;
printf("child pid: %d \n",getpid());
sleep(2);
// execvpe("./myprogma",argv,environ);
execl("/usr/bin/python3","python3","test.py",NULL);
exit(1);
}
//father
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("testexec ... end!\n");
return 0;
}
test.py
print("hello python")
print("hello python")
print("hello python")
print("hello python")
print("hello python")
test.sh:
cnt=0
while [ $cnt -le 10 ]
do
echo "hello shell, cnt: ${cnt}"
let cnt++
done
编译运行:
其他几个例子也就不一一测试了
下图exec函数族一个完整的例子: