文章目录
1.进程替换原理
一个程序替换的函数:
#include <unistd.h>`
int execl(const char *path, const char *arg, ...);
示例1:
#include<stdio.h>
#include<sys/types.h>
#include<unistd.h>
#include<sys/wait.h>
#include<stdlib.h>
int main()
{
printf("before: I am a process: pid: %d , ppid:%d\n",getpid(),getppid());
sleep(5);
execl("/usr/bin/ls","ls","-l","-a",NULL);
printf("after: I am a process: pid: %d , ppid:%d\n",getpid(),getppid());
}
示例2:
#include<stdio.h>
#include<sys/types.h>
#include<unistd.h>
#include<sys/wait.h>
#include<stdlib.h>
int main()
{
pid_t id=fork();
if(id == 0)
{
//child
printf("before: I am a process: pid: %d , ppid:%d\n",getpid(),getppid());
sleep(5);
execl("/usr/bin/ls","ls","-l","-a",NULL);
printf("after: I am a process: pid: %d , ppid:%d\n",getpid(),getppid());
exit(0);
}
//father
pid_t ret=waitpid(id,NULL,0);
if(ret > 0)
{
printf("wait success, father pid:%d, ret id: %d\n",getpid(),ret);
sleep(5);
}
return 0;
}
代码运行结果如下:
从上面的代码运行结果,示例1中可以看出父进程使用execl程序替换函数,会直接替换程序的代码和数据;示例2中,父进程创建子进程使用execl程序替换函数,要替换子进程的代码和数据的时候,触发只读权限写实拷贝,OS会重新开辟空间存放替换新程序的代码和数据!
问题1:在两个示例中,为什么没有执行以下代码语句?
printf("after: I am a process: pid: %d , ppid:%d\n",getpid(),getppid());
程序替换成功之后,
exec*
后续的代码不会被执行;替换失败呢?才可能执行后续代码。exec*
函数,只有失败返回值,没有成功返回值!!
问题2:CPU是如何得知程序的入口地址?
Linux中形成的可执行程序,是有格式的,有一个描述信息的表,该表里面就描述了当前可能程序分了哪些段,包括代码段,数据段等,可执行(入口)地址就是表头地址!CPU把可执行程序加载进来的时候,首先获得表头地址(可执行程序地址),便可以执行程序!
问题3:程序替换有没有创建新的进程?
程序替换没有创建新进程,只是进行进程的程序代码和数据的替换工作!
替换原理总结
用
fork
创建子进程后执行的是和父进程相同的程序(但有可能执行不同的代码分支),子进程往往要调用一种exec
函数以执行另一个程序。当进程调用一种exec
函数时,该进程的用户空间代码和数据完全被新程序替换,从新程序的启动例程开始执行。调用exec
并不创建新进程,所以调用exec
前后该进程的id并未改变。
2.进程替换函数
其实有六种以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[]);
2.1execl函数
int execl(const char *path, const char *arg, ...);
execl
替换函数,参数path
中,/usr/bin/ls
作为路径传参,需要找到可执行程序的位置;execl
替换函数以链表的方式,把功能选项连接起来,则是要确定如何执行可执行程序!
#include<stdio.h>
#include<sys/types.h>
#include<unistd.h>
#include<sys/wait.h>
#include<stdlib.h>
int main()
{
pid_t id=fork();
if(id == 0)
{
//child
printf("before: I am a process: pid: %d , ppid:%d\n",getpid(),getppid());
sleep(5);
//替换函数
execl("/usr/bin/ls","ls","-l","-a",NULL);
printf("after: I am a process: pid: %d , ppid:%d\n",getpid(),getppid());
exit(0);
}
//father
pid_t ret=waitpid(id,NULL,0);
if(ret > 0)
{
printf("wait success, father pid:%d, ret id: %d\n",getpid(),ret);
sleep(5);
}
return 0;
}
代码运行结果如下:
使用命令行参数指令:
#ls -a -l
指令运行结果:
简记:execl
替换函数,arg
参数与命令行参数传递一致,命令行参数怎么写,在execl函数中就怎么传!只不过空格换逗号,加NULL
。
2.2execlp函数
int execlp(const char *file, const char *arg, ...);
示例:
int execl(const char *path, const char *arg, ...);
#include<stdio.h>
#include<sys/types.h>
#include<unistd.h>
#include<sys/wait.h>
#include<stdlib.h>
int main()
{
pid_t id=fork();
if(id == 0)
{
//child
printf("before: I am a process: pid: %d , ppid:%d\n",getpid(),getppid());
sleep(5);
//替换函数
execlp("ls","ls","-l","-a",NULL);
printf("after: I am a process: pid: %d , ppid:%d\n",getpid(),getppid());
exit(0);
}
//father
pid_t ret=waitpid(id,NULL,0);
if(ret > 0)
{
printf("wait success, father pid:%d, ret id: %d\n",getpid(),ret);
sleep(5);
}
return 0;
}
代码运行结果如下:
execlp
替换函数,函数中名中p
指的是环境变量PATH
,指令的默认路径,执行进程时系统会去默认路径寻找,执行系统提供的指令时不需要带上地址;参数file
中,ls
作为文件名传参,需要在默认路径下找到对应的可执行程序;"ls","-l","-a",NULL
,execl
替换函数以链表的方式,把功能选项连接起来,则是要确定如何执行可执行程序!
2.3execv函数
int execv(const char *path, char *const argv[]);
execv
替换函数名中的v
是指vector
(数组),把可执行程序的执行选项功能放在数组中,在数组中以NULL
为结尾!参数path
,把可执行程序的位置作为参数传递;参数argv
,把自定义选项功能数组作为参数传递!
示例:
#include<stdio.h>
#include<sys/types.h>
#include<unistd.h>
#include<sys/wait.h>
#include<stdlib.h>
int main()
{
pid_t id=fork();
if(id == 0)
{
//child
printf("before: I am a process: pid: %d , ppid:%d\n",getpid(),getppid());
sleep(5);
//数组参数
char* const myargv[]={
"ls",
"-a",
"-l",
NULL
};
//替换函数
execv("/usr/bin/ls",myargv);
printf("after: I am a process: pid: %d , ppid:%d\n",getpid(),getppid());
exit(0);
}
//father
pid_t ret=waitpid(id,NULL,0);
if(ret > 0)
{
printf("wait success, father pid:%d, ret id: %d\n",getpid(),ret);
sleep(5);
}
return 0;
}
代码运行结果如下:
2.4execvp函数
int execvp(const char *file, char *const argv[]);
execvp
替换函数名中的v
是指vector
(数组),把可执行程序的执行选项功能放在数组中,在数组中以NULL
为结尾;函数中名中p
指的是环境变量PATH
,执行系统程序时不需要带路径;参数file
,把可执行程序的位置作为参数传递;参数argv
,把自定义选项功能数组作为参数传递!
示例:
#include<stdio.h>
#include<sys/types.h>
#include<unistd.h>
#include<sys/wait.h>
#include<stdlib.h>
int main()
{
pid_t id=fork();
if(id == 0)
{
//child
printf("before: I am a process: pid: %d , ppid:%d\n",getpid(),getppid());
sleep(5);
//数组参数
char* const myargv[]={
"ls",
"-a",
"-l",
NULL
};
//替换函数
execvp("ls",myargv);
printf("after: I am a process: pid: %d , ppid:%d\n",getpid(),getppid());
exit(0);
}
//father
pid_t ret=waitpid(id,NULL,0);
if(ret > 0)
{
printf("wait success, father pid:%d, ret id: %d\n",getpid(),ret);
sleep(5);
}
return 0;
}
代码运行结果如下:
2.5execle函数
int execle(const char *path, const char *arg, ...,char *const envp[]);
execle
替换函数名是在‘execl’
函数基础上,新增加了envp
参数。可以传递‘environ’
环境变量参数,libc
中定义的全局变量environ
指向环境变量表,environ
没有包含在任何头文件中,所以在使用时 要用extern
声明。因为环境变量通常具有全局属性,子进程继承父进程的环境变量;也可以自己设置自定义新的环境变量数组,传递给子进程!
示例1:
//ohtherExe.cpp
#include <iostream>
using namespace std;
int main(int argc, char *argv[], char *env[])
{
cout << argv[0] << " begin running" << endl;
cout << "这是命令行参数: \n";
for(int i=0; argv[i]; i++)
{
cout << i << " : " << argv[i] << endl;
}
cout << "这是环境变量信息: \n";
for(int i = 0; env[i]; i++)
{
cout << i << " : " << env[i] << endl;
}
cout<<"xxxxxxxxxxxxxxxxx"<<endl;
return 0;
}
//mycommand.c
//新增
extern char** environ;
//替换函数
execle("./otherExe","otherExe","-a","-b","c",NULL,environ);
代码运行结果为:
示例2:
//mycommand.c
//新增部分
char *const myenv[] = {
"MYVAL=1111",
"MYPATH=/usr/bin/XXX",
NULL
};
//替换函数为
execle("./otherExe", "otherExe", "-a", "-w", "-v", NULL, myenv);
代码运行结果为:
2.6execve函数
int execve(const char *path, char *const argv[], char *const envp[]);
execve
替换函数名中的v
是指vector
(数组),把可执行程序的执行选项功能放在数组中,在数组中以NULL
为结尾;envp
参数,传递环境变量为参数。
示例:
char* const myargv[]={
"otherExe",
"-a",
"-l",
NULL
};
char* const myenv[]={
"MYVAL=111111",
"MYPATH=/usr/bin/xxx",
NULL
};
extern char** environ;
代码运行的结果为:
注意:
libc
中定义的全局变量environ
指向环境变量表,environ
没有包含在任何头文件中,所以在使用时要用extern
声明;我们如果想给子进程传递环境变量,有两种方式:①新增环境变量,在父进程中使用putenv
函数自己设置的环境变量;②彻底替换(覆盖)环境变量,使用带“e”
选项的exec
函数(execle/execve
函数),传递自己设置环境变量数组;
2.7跨语言调用程序
在上面的例子中,我们都是使用我们自己写的mycommand.c
程序调用系统的程序(指令),那我们是否可以调用自己所写的程序呢?答案是当然可以!
示例1:
python代码
#!/usr/bin/python3
print("hello Python!")
#include<stdio.h>
#include<sys/types.h>
#include<unistd.h>
#include<sys/wait.h>
#include<stdlib.h>
int main()
{
pid_t id=fork();
if(id == 0)
{
//child
printf("before: I am a process: pid: %d , ppid:%d\n",getpid(),getppid());
sleep(5);
//替换函数
execl("/usr/bin/python3", "python3", "test.py", NULL);
printf("after: I am a process: pid: %d , ppid:%d\n",getpid(),getppid());
exit(0);
}
//father
pid_t ret=waitpid(id,NULL,0);
if(ret > 0)
{
printf("wait success, father pid:%d, ret id: %d\n",getpid(),ret);
sleep(5);
}
return 0;
}
代码运行结果如下:
为什么我们可以用C语言程序调用Python可执行程序?
无论什么语言的可执行程序运行起来之后变成了进程,便可以通过在子进程中使用exec
系列函数替换达到调用的效果!
3.总结
①exec
系列函数如果调用成功,则加载新的程序从启动代码开始执行不再返回。如果调用出错则返回-1,所以exec
函数只有出错的返回值,而没有成功的返回值。
②命名理解记忆
l(list) : 表示参数采用列表
v(vector) : 参数用数组
p(path) : 有p自动搜索环境变量PATH
e(env) : 表示自己维护环境变量
③只有execve
函数是真正的系统调用,其它五个函数最终都调用execve
函数,所以execve在man手册 第2节,其它函数在man
手册第3节。这些函数之间的关系如下图所示: