✨个人主页:****Yohifo
🎉所属专栏:****Linux学习之旅
🎊每篇一句:****图片来源
🎃操作环境:****CentOS 7.6 阿里云远程服务器
- Good judgment comes from experience, and a lot of that comes from bad judgment.- 好的判断力来自经验,其中很多来自糟糕的判断力。
文章目录
🌇前言
**
子进程
在被创建后,共享的是
父进程
的代码,如果想实现自己的逻辑就需要再额外编写代码,为了能让
子进程
执行其他任务,可以把当前
子进程
的程序替换为目标程序,此时需要用到
Linux
进程程序替换相关知识**
**
子进程
替换为其他程序后,无法再执行原有程序,但
进程
始终为同一个**
**火爆全网的
ChatGTP
能否替换 “人类” ?**
🏙️正文
1、为何要进行程序替换?
在学习相关函数前,先要弄清楚为何要进行程序替换?
- 将运行中的程序看作一个
任务处理平台
- 由我们发出指令,交给
任务处理平台
去完成 - 因为每次发出的指令都可能不相同,所以
任务处理平台
中的代码不能固化 - 为了解决这个问题,
任务处理平台
可以通过创建子进程,让子进程完成对应指令 - 子进程实现对应指令依赖于程序替换
总结:程序替换的目的是让子进程帮我们执行特定任务
就像汽车拥有各种各样的轮胎,如越野时需要换上路面兼容性更好、更耐造的越野胎;日常家用时,舒适性更好、胎噪更小的轮胎显然就更合适了,针对不同的使用场景替换不同的轮胎,程序替换时也是这么个意思,执行特定任务
**
shell
外壳中的
bash
就是一个任务处理平台,当我们发出指令,如
ls
、
pwd
、
touch
等指令时后,
bash
会创建子进程,将其替换为对应的指令程序并执行任务,就能实现各种指令**
进程程序替换图解
Linux
中的指令都是用C语言
写的可执行程序,所以可以进行替换bash
运行后,输入指令
本质上就是在进行程序替换
关于简易版
bash
的实现方法,将在下篇文章中揭晓
2、七大替换函数
**进程程序替换函数共有七个,其中六个都是在调用函数6,因此函数6
execve
才是真正的系统级接口**
各种替换函数间的关系
**这些函数都属于
exec
替换家族,所以它们的返回值都一样**
注意:**这七个函数只有在程序替换失败后才会有返回值,返回
-1
,程序替换成功后不返回**
程序都已经替换成功,后续代码也都将被替换,所以成功后的返回值也就没意义了
2.1、函数1 execl
**首先是最简单的替换函数
execl
**
#include<unistd.h>intexecl(constchar* path,constchar* arg,...);
函数解读
- 返回值:替换失败返回
-1
- 参数1:待替换程序的路径,如
/usr/bin/ls
- 参数2:待替换程序的名称,如
ls
- 参数3~N:待替换程序的选项,如
-a -l
等,最后一个参数为NULL
,表示选项传递结束 ...
表示可变参数列表,可以传递多个参数
注意:**参数选项传递结束或不传递参数,都要在最后加上
NULL
,类似于字符串的
'\0'
**
#include<stdio.h>#include<unistd.h>intmain(){//execl 函数printf("程序替换前,you can see me\n");int ret =execl("/usr/bin/ls","ls","-a","-l",NULL);//程序替换多发生于子进程,也可以通过子进程的退出码来判断是否替换成功if(ret ==-1)printf("程序替换失败!\n");printf("程序替换后,you can see me again?\n");return0;}
可以看出,**函数
execl
中的
命令+选项+NULL
是以
链式
的方式进行传递的**
2.2、函数2 execv
**替换函数
execv
是以顺序表
vector
的方式传递
参数2~N
的**
#include<unistd.h>intexecv(constchar* path,char*const argv[]);
函数解读
- 返回值:替换失败返回
-1
- 参数1:待替换程序的路径,如
/usr/bin/ls
- 参数2:待替换程序名及其命名构成的
指针数组
,相当于一张表
注意:**虽然
execv
只需传递两个参数,但在创建
argv
表时,最后一个元素仍然要为
NULL
**
#include<stdio.h>#include<stdlib.h>//exit 函数头文件#include<unistd.h>#include<sys/types.h>#include<sys/wait.h>intmain(){//execv 函数pid_t id =fork();if(id ==0){printf("子进程创建成功 PID:%d PPID:%d\n",getpid(),getppid());char*const argv[]={"ls","-a","-l",NULL};//argv 表,实际为指针数组execv("/usr/bin/ls", argv);printf("程序替换失败\n");exit(-1);//如果子进程有此退出码,说明替换失败}int status =0;waitpid(id,&status,0);//父进程阻塞等待if(WEXITSTATUS(status)!=255){printf("子进程替换成功,程序正常运行 exit_code:%d\n",WEXITSTATUS(status));}else{printf("子进程替换失败,异常终止 exit_code:%d\n",WEXITSTATUS(status));}return0;}
正常运行的情况
**错误运行的情况,改变
path
**
execv("/usr/bin", argv);//故意提供错误路径
与
execl
函数不同,**
execv
是以表的形式进行参数传递的**
2.3、函数3 execlp
**可能有的人觉得写
path
路径很麻烦,还有可能会写错,那么能否换成
自动挡
替换呢?**
答案是可以的,**
execlp
函数在进行程序替换时,可以不用写
path
路径**
#include<unistd.h>intexeclp(constchar* file,constchar* arg,...);
函数解读
- 返回值:替换失败返回
-1
- 参数1:待替换程序名,如
ls
、pwd
、clear
- 参数2~N:可变参数列表,为命令的选项
**
execlp
就像是
execl
的升级版,可以自动到
PATH
变量中查找程序**
注意:**只能在环境变量表中的
PATH
变量中搜索,如果待程序路径没有在
PATH
变量中,是无法进行替换的**
#include<stdio.h>#include<stdlib.h>//exit 函数头文件#include<unistd.h>#include<sys/types.h>#include<sys/wait.h>intmain(){//execlp 函数pid_t id =fork();if(id ==0){printf("you can see me\n");execlp("ls","ls","-a","-l",NULL);//程序替换printf("you can see me again?");exit(-1);}int status =0;waitpid(id,&status,0);//等待阻塞if(WEXITSTATUS(status)!=255)printf("子进程替换成功 exit_code:%d\n",WEXITSTATUS(status));elseprintf("子进程替换失败 exit_code:%d\n",WEXITSTATUS(status));return0;}
**使用
execlp
替换程序更加方便,只要待替换程序路径位于
PATH
中,就不会替换失败**
2.4、函数4 execvp
**
execv
加个
p
也能实现自动查询替换,即
execvp
**
#include<unistd.h>intexecvp(constchar* file,char*const argv[]);
函数解读
- 返回值:替换失败返回
-1
- 参数1:待替换程序名,需要位于
PATH
中 - 参数2:待替换程序名及其命名构成的
指针数组
#include<stdio.h>#include<stdlib.h>//exit 函数头文件#include<unistd.h>#include<sys/types.h>#include<sys/wait.h>intmain(){//execvp 函数pid_t id =fork();if(id ==0){printf("子进程创建成功 PID:%d PPID:%d\n",getpid(),getppid());char*const argv[]={"ls","-a","-l",NULL};execvp("ls", argv);printf("程序替换失败\n");exit(-1);//如果子进程有此退出码,说明替换失败}int status =0;waitpid(id,&status,0);//父进程阻塞等待if(WEXITSTATUS(status)!=255){printf("子进程替换成功,程序正常运行 exit_code:%d\n",WEXITSTATUS(status));}else{printf("子进程替换失败,异常终止 exit_code:%d\n",WEXITSTATUS(status));}return0;}
**假若参数1
file
的路径不在
PATH
中,程序会替换错误**
execvp("a.out", argv);
**如果想替换自己写的程序,那么只需要将路径添加至
PATH
中即可**
2.5、函数5 execle
**
e
表示
env
环境变量表,可以将自定义或当前程序中的环境变量表传给待替换程序**
#include<unistd.h>intexecl(constchar* path,constchar* arg,...,char*const envp[]);
函数解读
- 最后一个参数:替换成功后,待替换程序的环境变量表,可以自定义
char*const myenv[]={"myval=100",NULL};//自定义环境变量表execle("./other/CPP",NULL, myenv);//程序替换
**替换为自己写的程序
CPP
**
//当前源文件为 test.cc 即 C++源文件// .xx 后缀也可以表示 C++源文件#include<iostream>usingnamespace std;externchar** environ;//声明环境变量表intmain(){int pos =0;//只打印5条while(environ[pos]&& pos <5){
cout << environ[pos++]<< endl;}return0;}
按照预期替换程序并传入自定义环境变量表后
可以看到,**程序
CPP
中的环境变量表变成了自定义环境变量,即只有一个环境变量
myval=100
**
改变
execle
最后一个参数,传入默认环境变量表
externchar** environ;execle("./other/CPP",NULL, environ);//继承环境变量表
结论:如果主动传入环境变量后,待替换程序中的原环境变量表将被覆盖
**现在可以理解为什么在
bash
中创建程序并运行,程序能继承
bash
中的环境变量表了**
- 在
bash
下执行程序,等价于在bash
下替换子进程为指定程序,并将bash
中的环境变量表environ
传递给指定程序使用 - 其他没有带
e
的替换函数,默认传递当前程序中的环境变量表
2.6、函数6 execve
**
execve
是系统真正提供的程序替换函数,其他替换函数都是在调用
execve
**
比如
execl
相当于将链式信息转化为argv
表,供execve
参数2使用execlp
相当于在PATH
中找到目标路径信息后,传给execve
参数1使用execle
的envp
最终也是传给execve
中的参数3
#include<unistd.h>intexecve(constchar* filename,char*const argv[],char*const envp[]);
函数解读
- 返回值:替换失败返回
-1
- 参数1:待替换程序的路径
- 参数2:待替换程序名及其参数组成的
argv
表 - 参数3:传递给待替换程序的环境变量表
**替换
ls -a -l
程序**
externchar** environ;execve("/usr/bin/ls", argv, environ);
**替换为自定义程序
CPP
**
externchar** environ;execve("./other/CPP", argv, environ);
**替换函数除了能替换为
C++
编写的程序外,还能替换为其他语言编写的程序,如
Java
、
Python
、
PHP
等等,虽然它们在语法上各不相同,但在 OS 看来都属于
可执行程序
,数据位于
代码段
和
数据段
,直接替换即可**
**系统级接口是不分语言的,因为不论什么语言最终都需要调用系统级接口,比如文件流操作中的
open
、
close
、
write
等函数,无论什么语言的文件流操作函数都需要调用它们**
2.7、函数7 execvpe
**对
execvp
的再一层封装,使用方法与
execvp
一致,不过最后一个参数可以传递环境变量表**
#include<unistd.h>intexecvpe(constchar* file,char*const argv[],char*const envp[]);
函数解读
- 返回值:替换失败返回
-1
- 参数1:待替换程序名,需要位于
PATH
中 - 参数2:待替换程序名及其命名构成的
指针数组
- 参数3:传递给待替换程序的环境变量表
externchar** environ;execvpe("ls", argv, environ);
3、补充
最后再补充一些关于程序替换的知识
3.1、函数名记忆
**七大替换函数按
程序名+选项
传递方式可以分为两组**
- 列表:
execl
、execlp
、execle
- 顺序:
execv
、execvp
、execve
、execvpe
可以看出,**列表传递中必有
l
,顺序传递则必有
v
**,函数名中字符的含义如下
exec
该函数隶属于程序替换家族l
即list
,列表传递v
为vector
,顺序传递p
表示PATH
,根据程序名自动在PATH
中查找e
则是environ
,是否手动传递环境变量表
3.2、替换现象
**子进程程序替换后,并不会创建新进程,而是对原有程序中的
数据
和
代码
进行修改,可以通过替换以下程序观察**
#include<iostream>#include<unistd.h>usingnamespace std;intmain(){while(1){
cout <<"程序替换成功";
cout <<" PID:"<<getpid()<<" PPID:"<<getppid()<< endl;sleep(1);}return0;}
可以看到在进行程序替换后,子进程和待替换程序为同一个进程
- 这就表明程序替换并不是进程替换
- 因为是同一个进程,所以对父进程没有任何影响,体现了进程间的独立性
**在子进程执行程序替换前,子进程和父进程共享一份只读区域的数据,但因为发生了程序替换,触发
写时拷贝
机制,令子进程读取另一块区域的数据**
写时拷贝
在只读数据区也能触发,因为不能影响到父进程
🌆总结
**以上就是本篇关于
Linux
进程程序替换的相关内容了,在本文中,我们知道了进行程序替换的目的,学习使用了程序替换相关的七大函数,最后还观察了程序替换后的神奇现象,在学完这些知识后,我们就可以实现一个简单的
bash
,体验一下在自己程序中输入指令操控
Linux
的奇妙体验**
如果你觉得本文写的还不错的话,期待留下一个小小的赞👍,你的支持是我分享的最大动力!
如果本文有不足或错误的地方,随时欢迎指出,我会在第一时间改正
相关文章推荐
Linux进程控制【创建、终止、等待】
===============
Linux进程学习【进程地址】
Linux进程学习【环境变量】
Linux进程学习【进程状态】
Linux进程学习【基本认知】
版权归原作者 Yohifo 所有, 如有侵权,请联系我们删除。