上篇文章:Linux操作系统2-进程控制2(进程等待,waitpid系统调用,阻塞与非阻塞等待)-CSDN博客
本篇代码Gitee仓库:Linux操作系统-进程的程序替换学习 · d0f7bb4 · 橘子真甜/linux学习 - Gitee.com
本篇重点:进程替换
目录
[一. 什么是进程替换?](#一. 什么是进程替换?)
[二. 进程替换函数常用的函数](#二. 进程替换函数常用的函数)
[2.1 execl](#2.1 execl)
[a 进程替换覆盖指定位置后面的代码](#a 进程替换覆盖指定位置后面的代码)
[b 进程替换不会影响父进程](#b 进程替换不会影响父进程)
[2.2 execlp](#2.2 execlp)
[2.3 execv/execvp](#2.3 execv/execvp)
[2.4 execle](#2.4 execle)
[2.5 execve 系统调用](#2.5 execve 系统调用)
[三. 进程替换总结](#三. 进程替换总结)
[四. 进程替换可以执行任何后端语言!](#四. 进程替换可以执行任何后端语言!)
一. 什么是进程替换?
我们知道,使用fork函数可以创建子进程。我们创建子进程的目的是什么?
1 让子进程执行父进程的一部分代码(比如执行父进程处于磁盘中的代码)
2 我们希望让子进程执行一个全新的进程,去完成一个不同的功能
我们让子进程去执行新程序的数据和代码 -> 进程的程序替换
二. 进程替换函数常用的函数
常见的函数如下:
cpp
#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[]);
是不是有点眼花缭乱?具体使用如下
2.1 execl
a 进程替换覆盖指定位置后面的代码
cpp
int execl(const char *path, const char *arg, ...);
//path 用于找到程序(程序的路径,ls的路径是 /usr/bin/ls)
//arg 这个命令是怎么执行的(比如ls命令的执行方式就是单独的 ls)
//... 这个命令需要的参数是什么(比如ls可以带参数 -a -l -i 等)
//返回值,失败返回-1,并且设置错误码
我们使用execl去替换一个 ls命令。代码如下:
cpp
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
int main()
{
// 使用execl去替换ls完成 ls -a -l --color=auto
printf("process is running!\n");
//第一个参数,要执行哪个路径,就填这个路径
//第二个参数,程序怎么执行,就怎么填
//后面的参数,执行这个程序需要带的参数,就一个一个地填这些参数
//最好使用NULL结尾
int n = execl("/usr/bin/ls", "ls", "-a", "-l", "--color=auto", NULL);
//使用系统调用或者涉及底层的C库函数,需要判断错误返回
perror("execl");
//由于执行execl之后,代码被覆盖,以下的代码不会被运行!
printf("process is running!\n");
printf("process is running!\n");
printf("process is running!\n");
printf("process is running!\n");
printf("process is running!\n");
return 0;
}
编译运行结果如下:
execl之后的代码没有被执行的原因是:进程替换的时候没有创建新进程,而是将指定的程序和代码加载到指定的位置
这样会覆盖后面的代码,所以后面printf就不会执行了!
b 进程替换不会影响父进程
进程替换会覆盖指定位置后面的代码,不过我们在子进程中使用进程替换不会影响父进程的代码和数据。
这个原因是父子进程之间有写实拷贝,由于父子进程之间的写实拷贝,一旦子进程尝试写入,OS就会给子进程开辟一份空间用于保存子进程的数据和代码。这样一来,子进程进程替换就不会影响父进程。
测试代码:
cpp
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/wait.h>
int main()
{
// 使用execl去替换ls完成 ls -a -l --color=auto
printf("1 process is running!\n");
pid_t id = fork();
if (id == 0)
{
int n = execl("/usr/bin/ls", "ls", "-a", "-l", "--color=auto", NULL);
perror("execl");
// 如果替换失败
exit(-1);
}
// 由于写实拷贝,父进程不受子进程进程替换影响
printf("2 process is running!\n");
int status = 0;
int subid = waitpid(id, &status, 0);
if (subid > 0)
{
printf("child exit code:%d child exit signal\n", (status >> 8) & 0xff, status & 0x7f);
}
return 0;
}
测试结果:
2.2 execlp
cpp
int execlp(const char *file, const char *arg, ...);
p:path,只要输入替换的程序,会自动去环境变量中进行查找。无需告诉路径
测试代码:
cpp
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/wait.h>
int main()
{
// 使用execl去替换ls完成 ls -a -l --color=auto
printf("1 process is running!\n");
pid_t id = fork();
if (id == 0)
{
//p:path,只要输入替换的程序,会自动去环境变量中进行查找。无需告诉路径
execlp("ls", "ls", "-a", "-l", "--color=auto", NULL);
perror("execl");
// 如果替换失败
exit(-1);
}
// 由于写实拷贝,父进程不受子进程进程替换影响
printf("2 process is running!\n");
int status = 0;
int subid = waitpid(id, &status, 0);
if (subid > 0)
{
printf("child exit code:%d child exit signal\n", (status >> 8) & 0xff, status & 0x7f);
}
return 0;
}
测试结果:
2.3 execv/execvp
cpp
//v:vector:可以将所有的执行参数放入数组,进行统一传入,不用可变参数传参
int execv(const char *path, char *const argv[]);
// v:vector p:文件名
int execvp(const char *file, char *const argv[]);
execv 举例:
cpp
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/wait.h>
int main()
{
// 使用execl去替换ls完成 ls -a -l --color=auto
printf("1 process is running!\n");
pid_t id = fork();
if (id == 0)
{
char * argv_[] = {"ls", "-a", "-l", "--color=auto", NULL};
execv("/usr/bin/ls", argv_);
perror("execv");
// 如果替换失败
exit(-1);
}
// 由于写实拷贝,父进程不受子进程进程替换影响
printf("2 process is running!\n");
int status = 0;
int subid = waitpid(id, &status, 0);
if (subid > 0)
{
printf("child exit code:%d child exit signal\n", (status >> 8) & 0xff, status & 0x7f);
}
return 0;
}
测试结果:
execvp 加上了p:我们只需给出名字,会去环境变量中自动查找
测试代码:
cpp
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/wait.h>
int main()
{
// 使用execl去替换ls完成 ls -a -l --color=auto
printf("1 process is running!\n");
pid_t id = fork();
if (id == 0)
{
char * argv_[] = {"ls", "-a", "-l", "--color=auto", NULL};
execvp("ls", argv_);
perror("execvp");
// 如果替换失败
exit(-1);
}
// 由于写实拷贝,父进程不受子进程进程替换影响
printf("2 process is running!\n");
int status = 0;
int subid = waitpid(id, &status, 0);
if (subid > 0)
{
printf("child exit code:%d child exit signal\n", (status >> 8) & 0xff, status & 0x7f);
}
return 0;
}
测试结果:
cpp
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/wait.h>
int main()
{
// 使用execl去替换ls完成 ls -a -l --color=auto
printf("1 process is running!\n");
pid_t id = fork();
if (id == 0)
{
char * argv_[] = {"ls", "-a", "-l", "--color=auto", NULL};
execvp("ls", argv_);
perror("execvp");
// 如果替换失败
exit(-1);
}
// 由于写实拷贝,父进程不受子进程进程替换影响
printf("2 process is running!\n");
int status = 0;
int subid = waitpid(id, &status, 0);
if (subid > 0)
{
printf("child exit code:%d child exit signal\n", (status >> 8) & 0xff, status & 0x7f);
}
return 0;
}
测试结果
2.4 execle
cpp
int execle(const char *path, const char *arg, ...,char *const envp[]); //e:传入自定义环境变量,
e:传入自定义环境变量
我们定义另一个C程序mybin,让这个程序打印环境变量。然后我们在子进程中替换为mybin
这样我们的子进程就能够打印我们在execle函数中输入的环境变量了
cpp
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/wait.h>
int main()
{
// 使用execl去替换ls完成 ls -a -l --color=auto
printf("1 process is running!\n");
pid_t id = fork();
if (id == 0)
{
//注意在最后加上NULL
char *const env_[] = {"AA=11", "BB=22", "CC=33", "YZC=Hello world!", NULL};
// 加入命令行参数env
execle("./mybin", "./mybin", NULL, env_);
perror("execle");
// 如果替换失败
exit(-1);
}
// 由于写实拷贝,父进程不受子进程进程替换影响
printf("2 process is running!\n");
int status = 0;
int subid = waitpid(id, &status, 0);
if (subid > 0)
{
printf("child exit code:%d child exit signal\n", (status >> 8) & 0xff, status & 0x7f);
}
return 0;
}
运行结果如下:
我们换成系统环境变量
cpp
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/wait.h>
int main()
{
// 使用execl去替换ls完成 ls -a -l --color=auto
printf("1 process is running!\n");
pid_t id = fork();
if (id == 0)
{
//注意在最后加上NULL
char *const env_[] = {"AA=11", "BB=22", "CC=33", "YZC=Hello world!", NULL};
// 加入命令行参数env
extern char** environ;
execle("./mybin", "./mybin", NULL, environ);
perror("execle");
// 如果替换失败
exit(-1);
}
// 由于写实拷贝,父进程不受子进程进程替换影响
printf("2 process is running!\n");
int status = 0;
int subid = waitpid(id, &status, 0);
if (subid > 0)
{
printf("child exit code:%d child exit signal\n", (status >> 8) & 0xff, status & 0x7f);
}
return 0;
}
运行结果:
当然我们也能够在 命令行参数获取环境变量
2.5 execve 系统调用
上面的exec函数都是对execve系统调用的封装
cpp
//2号手册,这个才是真正的系统调用(上面的都是函数封装)
int execve(const char *path, char *const argv[], char *const envp[]);
cpp
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/wait.h>
int main()
{
// 使用execl去替换ls完成 ls -a -l --color=auto
printf("1 process is running!\n");
pid_t id = fork();
if (id == 0)
{
// 注意在最后加上NULL
char *const env_[] = {"AA=11", "BB=22", "CC=33", "YZC=Hello world!", NULL};
char *const argv_[] = {"./mybin"};
execve("./mybin", argv_, env_);
perror("execve");
// 如果替换失败
exit(-1);
}
// 由于写实拷贝,父进程不受子进程进程替换影响
printf("2 process is running!\n");
int status = 0;
int subid = waitpid(id, &status, 0);
if (subid > 0)
{
printf("child exit code:%d child exit signal\n", (status >> 8) & 0xff, status & 0x7f);
}
return 0;
}
测试结果:
三. 进程替换总结
我们知道程序必须加载到内存中才能执行。这是由冯诺依曼计算机体系结构决定的。
在Linux中就是通过exec*函数来加载程序的!
那么是exec函数先加载还是main函数先执行?
exec函数先加载,然后main函数再执行
我们可以发现exec函数的参数和main函数的参数很像!
所以是exec函数先加载路径,参数,环境变量在执行main函数
四. 进程替换可以执行任何后端语言!
cpp
#include <iostream>
using namespace std;
int main()
{
cout << "Hello C++!" << endl;
return 0;
}
mytest.c
cpp
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/wait.h>
int main()
{
// 使用execl去替换ls完成 ls -a -l --color=auto
printf("1 process is running!\n");
pid_t id = fork();
if (id == 0)
{
// 注意在最后加上NULL
char *const env_[] = {"AA=11", "BB=22", "CC=33", "YZC=Hello world!", NULL};
char *const argv_[] = {"./myCppBin"};
//执行C程序
//execve("./mybin", argv_, env_);
//执行C++
execve("./myCppBin", argv_, env_);
perror("execve");
// 如果替换失败
exit(-1);
}
// 由于写实拷贝,父进程不受子进程进程替换影响
printf("2 process is running!\n");
int status = 0;
int subid = waitpid(id, &status, 0);
if (subid > 0)
{
printf("child exit code:%d child exit signal\n", (status >> 8) & 0xff, status & 0x7f);
}
return 0;
}