当前位置: 首页 > news >正文

深入理解Linux进程程序替换:从原理到实践

目录

前言:一个司空见惯的现象

一、什么是进程程序替换?

1. 当进程调用exec函数时,对进程有什么影响?

2.验证执行exec函数之后,代码不会继续后续的原代码

3.验证子进程的替换并不会创建新的进程

二、为什么需要进程程序替换?

三、exec函数族:替换的利器

1.函数家族概览

2.函数命名规律:

四、实战演示:各种exec函数的使用

1.execl 与execv  

1.1execl

1.2execv

2.execlp与execvp

2.1execlp

2.2 ececvp

3.execle 与execve

3.1ececle

3.2execve

4.验证父进程的环境变量是原封不懂传给子进程


前言:一个司空见惯的现象

当我们打开终端,输入 ls -la 并按下回车时,发生了什么? bash 进程并没有消失,而是 ls 程序神奇地运行了起来并显示了结果。这背后正是 进程程序替换的魔法。

本文将深入探讨Linux中进程程序替换的机制,带你理解这个强大而重要的概念。

一、什么是进程程序替换?

 用fork创建子进程,父进程需要子进程调执行另外的代码,当子进程往往要调用exec函数,以执行另一个程序

1. 当进程调用exec函数时,对进程有什么影响?

该进程的代码和数据完全被新程序替换,替换完之后,运行成功,原代码之后的代码不会运行,运行失败,原代码之后的代码任然会运行,并且子进程的pid并不会改变

原理:

将磁盘中的内存,加载入内存结构。

重新建立页表映射,谁执行程序替换,就重新建立谁的映射(下图为子进程建立)。

效果:让父进程和子进程彻底分离,并让子进程执行一个全新的程序。 

这里左边基本不发生变化,右边将磁盘的上的程序加载到内存,并和当前进程的页表,重新建立映射,这就是进程替换。

这个过程有没有创建新的进程呢?

没有,因为子进程的内核数据结构根本没变,只是重新建立了虚拟地址和物理地址之间的映射关系。内核数据结构没有发生任何变化,包括进程的pid都没变,说明没有创建子进程

2.验证执行exec函数之后,代码不会继续后续的原代码

linux代码:

#include <stdio.h>
#include <unistd.h>int main() 
{printf("pid: %d, exec command begin\n", getpid());// 用 execl 这个函数来调用系统的命令execl("/usr/bin/ls", "ls", "-a", "-l", NULL);printf("pid: %d, exec command end\n", getpid());return 0;
}

结果:

!!!原代码并没有输出 printf("pid: %d, exec command end\n", getpid());

3.验证子进程的替换并不会创建新的进程

代码:

#include <stdio.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <stdlib.h>
int main()
{pid_t id = fork(); // 创建子进程if (id == 0){// 这里是子进程printf("pid: %d, exec command begin\n", getpid());sleep(3);// 用 execl 这个函数来调用系统的命令execl("/usr/bin/ls", "ls", "-a", "-l", NULL);printf("pid: %d, exec command end\n", getpid());exit(1); // execl 这个函数如果执行成功,就不会来到这下面的语句,如果执行失败才会来到这里,所以我们这里就直接退出}else {// 这里是父进程pid_t rid = waitpid(-1, NULL, 0);if (rid > 0){printf("wait scuess, rid: %d\n", rid);}}return 0;
}

结果:

由父进程等待子进程,获取子进程pid可以知道,子进程的pid并没有变化,所以程序替换不会改变子进程的pid

二、为什么需要进程程序替换?

父进程创建子进程的目的有两种:

1.子进程执行父进程代码的一部分 

2.让子进程运行一个全新的程序

允许程序在运行时决定要加载和执行哪些其他程序。

三、exec函数族:替换的利器

Linux提供了exec系列函数来完成程序替换,它们都定义在<unistd.h>头文件中。

1.函数家族概览

 


2.函数命名规律:

l (list):参数以列表形式传递

v (vector):参数以数组形式传递

p(path):在PATH环境变量中查找程序

e (environment):可自定义环境变量

四、实战演示:各种exec函数的使用

小编用相近的函数来输出

1.execl 与execv  

1.1execl

列表参数

int execl(const char *path, const char *arg0, ..., (char *)NULL);

示例:

!!!需要以NULL结尾

// 执行 /bin/ls -l -a
execl("/bin/ls", "ls", "-l", "-a", NULL);
1.2execv

列表参数

int execv(const char *path, char *const argv[]);

示例:

!!!char * argv[]这个数组需要以NULL结尾

char *argv[] = {"ls", "-l", "-a", NULL};
execv("/bin/ls", argv);

2.execlp与execvp

这里只比execl和execv多了个p,p有啥用呢?简化!有了p之后,原本第一参数需要输入完整的路径,现在只需要输入那个程序(环境变量中有的)

2.1execlp

列表参数:

int execlp(const char *file, const char *arg0, ..., (char *)NULL);

示例:

// 执行 ls -l -a(自动在PATH中查找ls)
execlp("ls", "ls", "-l", "-a", NULL);
2.2 ececvp

列表参数

int execvp(const char *file, char *const argv[]);

示例:

char *argv[] = {"ls", "-l", "-a", NULL};
execvp("ls", argv);

3.execle 与execve

这里只比execl和execv多了个e,e有啥用呢?有了e,便可自己创建的环境变量,如果没有创建自己的环境变量,子进程会默认继承父进程的环境变量,父进程的环境变量是全局性的

3.1ececle

列表参数:

int execle(const char *path, const char *arg0, ..., (char *)NULL, char *const envp[]);

示例:

char *envp[] = {"MY_VAR=hello", "PATH=/usr/bin", NULL};
execle("/bin/bash", "bash", "-c", "echo $MY_VAR", NULL, envp);
3.2execve

列表参数:

int execvpe(const char *file, char *const argv[], char *const envp[]);

示例:

char *argv[] = {"bash", "-c", "echo $CUSTOM_VAR", NULL};
char *envp[] = {"CUSTOM_VAR=test_value", "PATH=/bin", NULL};
execvpe("bash", argv,envp);

4.验证父进程的环境变量是原封不懂传给子进程

例子是网上找的!!!
代码:

#define _POSIX_C_SOURCE 200112L
#define _GNU_SOURCE#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <sys/wait.h>
#include <sys/types.h>extern char **environ;// 打印环境变量的函数
void print_environment(const char *title) {printf("=== %s ===\n", title);for (char **env = environ; *env != NULL; env++) {printf("%s\n", *env);}printf("====================\n\n");
}int main() {// 1. 先打印父进程的原始环境变量print_environment("父进程原始环境变量");// 2. 添加一个新的环境变量到父进程setenv("PARENT_VAR", "parent_value", 1);setenv("SHARED_VAR", "original_value", 1);printf("在父进程中设置环境变量后:\n");printf("PARENT_VAR=%s\n", getenv("PARENT_VAR"));printf("SHARED_VAR=%s\n", getenv("SHARED_VAR"));printf("\n");// 3. 再次打印父进程环境变量print_environment("父进程设置变量后环境变量");pid_t pid = fork();if (pid == 0) {// 子进程printf("子进程开始执行...\n");// 4. 打印子进程继承的环境变量print_environment("子进程继承的环境变量");printf("子进程中读取环境变量:\n");printf("PARENT_VAR=%s\n", getenv("PARENT_VAR"));printf("SHARED_VAR=%s\n", getenv("SHARED_VAR"));printf("\n");// 5. 在子进程中修改环境变量setenv("CHILD_VAR", "child_value", 1);setenv("SHARED_VAR", "modified_by_child", 1);printf("子进程修改环境变量后:\n");printf("PARENT_VAR=%s\n", getenv("PARENT_VAR"));printf("SHARED_VAR=%s\n", getenv("SHARED_VAR"));printf("CHILD_VAR=%s\n", getenv("CHILD_VAR"));printf("\n");// 6. 使用execvpe执行env命令,显示最终环境变量char *argv[] = {"env", NULL};char *envp[] = {"CUSTOM_VAR=custom_value","PATH=/usr/bin:/bin","TERM=xterm",NULL};printf("使用execvpe执行env命令...\n");execvpe("env", argv, envp);// 如果execvpe失败perror("execvpe failed");exit(1);} else if (pid > 0) {// 父进程wait(NULL); // 等待子进程结束printf("\n父进程在子进程结束后:\n");printf("PARENT_VAR=%s\n", getenv("PARENT_VAR"));printf("SHARED_VAR=%s\n", getenv("SHARED_VAR"));// 检查CHILD_VAR是否存在char *child_var = getenv("CHILD_VAR");printf("CHILD_VAR=%s\n", child_var ? child_var : "(null)");printf("\n验证:子进程的环境修改不影响父进程\n");} else {perror("fork failed");return 1;}return 0;
}

结果:

配置后,我们能在环境变量找到它吗?

父进程修改后子进程会继承吗?答案是会!!!

子进程环境变量的改变会影响父进程吗?不会!!!

当我们进行程序替换的时候,子进程的环境变量是从父进程继承过来的,父进程的换将变量是从哪里来的呢?是从shell来的

http://www.dtcms.com/a/349947.html

相关文章:

  • Elasticsearch JVM调优:核心参数与关键技巧
  • Git克隆时遇到“Filename too long“错误的完美解决方案
  • 代理设计模式
  • 俄罗斯情报机构推出新型安卓恶意软件,伪装成杀毒软件
  • SciPy科学计算与应用:SciPy入门与应用-科学计算与NumPy协同实践
  • 工业异常检测大模型(1)数据集、方法
  • 【git使用场景】本地仓库与远程仓库存在独立历史
  • Vulkan 学习路线图
  • Git 怎么仓库迁移?error: remote origin already exists.怎么解决
  • 定时器的原理
  • TensorFlow 深度学习 | Dataset API 数据读取详解
  • Open3D入门指南:3D数据处理与可视化利器
  • 初识神经网络——《深度学习入门:基于Python的理论与实现》
  • 昆仑万维开源 Matrix-3D大模型,正在开启“造物主”模式
  • 【智慧城市】2025年中国地质大学(武汉)暑期实训优秀作品(2):智慧城市西安与一带一路
  • pytest 并发执行用例(基于受限的测试资源)
  • imx6ull-驱动开发篇40——Linux RTC 驱动简介
  • 一道MySQL笔试题: 输出 100 以内质数
  • VIVO/OPPO手机,显示5G开关
  • 【SystemUI】锁屏来通知默认亮屏Wake模式
  • Mac 菜单栏多合一工具自荐:FancyTool
  • LeetCode算法日记 - Day 22: 提莫攻击、Z字形变换
  • 电影感人文街拍摆摊纪实摄影后期Lr调色教程,手机滤镜PS+Lightroom预设下载!
  • 从手术室到街头摄像头:多模态融合如何让AI“看得懂”万物?
  • 搭建ftp服务器(主动模式,被动模式)
  • Canvas 动态高度文本图片生成器
  • Linux 详谈Ext系列⽂件系统(一)
  • 嵌入式(ARM方向)面试常见问题及解答
  • 【ARM】MDK在debug模式下断点的类型
  • blazor 学习笔记--vscode debug