Linux进程观:简单性如何成就强大性(六)

1. 进程程序替换

fork() 之后,⽗⼦各⾃执⾏⽗进程代码的⼀部分如果⼦进程就想执⾏⼀个全新的程序呢?进程的程序
替换来完成这个功能! 程序替换是通过特定的接⼝,加载磁盘上的⼀个全新的程序(代码和数据),加载到调⽤进程的地址空间 中!

1.1. 替换原理

⽤fork创建⼦进程后执⾏的是和⽗进程相同的程序(但有可能执⾏不同的代码分⽀),⼦进程往往要调⽤⼀种exec函数以执⾏另⼀个程序。当进程调⽤⼀种exec函数时,该进程的⽤⼾空间代码和数据完全被新程序替换,从新程序的启动例程开始执⾏。调⽤exec并不创建新进程,所以调⽤exec前后该进程的id并未改变。

1.2. 替换函数

其实有六种以exec开头的函数,统称exec函数:

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[]);

1.2.1. 函数解释

  • 这些函数如果调⽤成功则加载新的程序从启动代码开始执⾏,不再返回。
  • 如果调⽤出错则返回-1。
  • 所以exec函数只有出错的返回值⽽没有成功的返回值。

1.2.2. 命名解释

这些函数原型看起来很容易混,但只要掌握了规律就很好记。

  • l(list):表示参数采用列表
  • v(vector):参数采用数组
  • p(PATH):自动搜索环境变量PATH
  • e(env):表示自己维护环境变量

|--------|------|-------|------------|
| 函数名 | 参数格式 | 是否带路径 | 是否使用当前环境变量 |
| execl | 列表 | 否 | 是 |
| execlp | 列表 | 是 | 是 |
| execle | 列表 | 否 | 自己传环境变量 |
| execv | 数组 | 否 | 是 |
| execvp | 数组 | 是 | 是 |
| execve | 数组 | 否 | 自己传环境变量 |

1.2.3. exec系列函数的举例

一. execl
cpp 复制代码
#include<stdio.h>
#include<sys/types.h>
#include<unistd.h>

int main()
{
	int id = fork();
	if(id == 0)
	{
		printf("我是一个子进程,我将被替换.");
		execl("/usr/bin/ls", "ls","-a","-l",NULL);
	}else{
		printf("我是父进程,我不进行进程替换\n");
	}
	return 0;
}

运行结果:

二. execlp
cpp 复制代码
#include<stdio.h>
#include<sys/types.h>
#include<unistd.h>

int main()
{
	int id = fork();
	if(id == 0)
	{
		printf("我是一个子进程,我将被替换.");
		execlp("ls", "ls","-a","-l",NULL);
	}else{
		printf("我是父进程,我不进行进程替换\n");
	}
	return 0;
}

运行结果:

三. execle
cpp 复制代码
#include<stdio.h>
#include<sys/types.h>
#include<unistd.h>

int main()
{
	char *const my_envp[]={"MYPATH=11225566"};
	int id = fork();
	if(id == 0)
	{
		printf("我是一个子进程,我将被替换.");
		execle("/usr/bin/ls", "ls","-a","-l",NULL, my_envp);
	}else{
		printf("我是父进程,我不进行进程替换\n");
	}
	return 0;
}

运行结果:

四. execv
cpp 复制代码
#include<stdio.h>
#include<sys/types.h>
#include<unistd.h>

int main()
{
	char *const my_envp[]={"MYPATH=11225566"};
	char *const my_argv[]={"ls","-a","-l",NULL};
	int id = fork();
	if(id == 0)
	{
		printf("我是一个子进程,我将被替换.");
		execv("/usr/bin/ls", my_argv);
	}else{
		printf("我是父进程,我不进行进程替换\n");
	}
	return 0;
}

运行结果:

五. execvp
cpp 复制代码
#include<stdio.h>
#include<sys/types.h>
#include<unistd.h>

int main()
{
	char *const my_envp[]={"MYPATH=11225566"};
	char *const my_argv[]={"ls","-a","-l",NULL};
	int id = fork();
	if(id == 0)
	{
		printf("我是一个子进程,我将被替换.");
		execvp("ls", my_argv);
	}else{
		printf("我是父进程,我不进行进程替换\n");
	}
	return 0;
}

运行结果:

六. execve
cpp 复制代码
#include<stdio.h>
#include<sys/types.h>
#include<unistd.h>

int main()
{
	char *const my_envp[]={"MYPATH=11225566"};
	char *const my_argv[]={"ls","-a","-l",NULL};
	int id = fork();
	if(id == 0)
	{
		printf("我是一个子进程,我将被替换.");
		execve("/usr/bin/ls", my_argv, my_envp);
	}else{
		printf("我是父进程,我不进行进程替换\n");
	}
	return 0;
}

运行结果:

事实上,只有execve是真正的系统调⽤,其它五个函数最终都调⽤execve,所以execve在man⼿册 第2节, 其它函数在man⼿册第3节。这些函数之间的关系如下图所⽰。
下图exec函数簇 ⼀个完整的例⼦:

相关推荐
小辰记事本1 小时前
从零读懂RoCEv2数据包构造:从WQE到线缆上的完整旅程
服务器·网络·网络协议·rdma
小鹏linux2 小时前
Ubuntu 22.04 部署开源免费具有精美现代web页面的Casdoor账号管理系统
linux·前端·ubuntu·开源·堡垒机
在角落发呆3 小时前
Linux转发配置:解锁网络互联的核心密码
linux·运维·网络
齐潇宇3 小时前
Zabbix 7 概述与配置
linux·zabbix·监控告警
裴东青4 小时前
10-实战:RuoYi-Cloud的自动化发布
运维·ci/cd·自动化
江公望5 小时前
Ubuntu htop命令,10分钟讲清楚
linux·服务器
哎呦,帅小伙哦5 小时前
Linux 时间:从原子钟到 clock_gettime 的每一面
linux·运维·服务器
sxgzzn5 小时前
新能源场站数智化转型:基于数字孪生与AI的智慧运维管理平台解析
大数据·运维·人工智能
张小姐的猫5 小时前
【Linux】多线程 —— 线程互斥
linux·运维·服务器·c++
CodeMartain5 小时前
Dify Windows 原生部署(无 Docker、纯本地)
运维·docker·容器