Linux:进程替换和知识整合
文章目录
- 进程程序替换
- 替换原理
- 进程替换的理解
- 环境变量与进程替换
- 命令行解释器
- 实现逻辑
进程程序替换
前面已经学习了子进程的创建,但是子进程的创建不管怎么说,都是父进程代码的一部分,那么实际上如果想要子进程执行新的程序呢?
也就是说,执行全新的代码和访问全新的数据,不再和父进程有瓜葛呢?这个时候就引入了关于进程替换的概念
替换原理

用fork创建子进程后执行的是和父进程相同的程序(但有可能执行不同的代码分支),子进程往往要调用一种exec函数以执行另一个程序。当进程调用一种exec函数时,该进程的用户空间代码和数据完全被新程序替换,从新程序的启动例程开始执行。调用exec并不创建新进程,所以调用exec前后该进程的id并未改变
进程替换的理解
首先演示基本用法:
单进程下的用法
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>int main()
{printf("execl begin:\n");execl("/usr/bin/ls", "ls", "-a", "-l", "-n", NULL);printf("execl end:\n");return 0;
}
调用结果:
[test@VM-16-11-centos 11-8]$ ./myprocess
execl begin:
total 28
drwxrwxr-x 2 1003 1003 4096 Nov 9 10:50 .
drwxrwxrwt 16 1003 1003 4096 Nov 8 20:40 ..
-rw-rw-r-- 1 1003 1003 74 Nov 8 20:41 Makefile
-rwxrwxr-x 1 1003 1003 8416 Nov 9 10:50 myprocess
-rw-rw-r-- 1 1003 1003 175 Nov 9 10:47 myprocess.c
从中看出,它的基本原理就是在进程中进行进程的替换
为什么最后输出的printf不被调用呢?
这是因为,执行到进程替换函数的时候,如果成功,整个进程的代码和数据都会被替换为所需替换的目标代码和数据,这样在后续执行的时候都会使用这份新的代码和数据,因此不会调用后续出现的代码
多进程版本的程序替换
将上述的代码更改为含有子进程的代码,具体如下:
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/wait.h>int main()
{pid_t id = fork();if(id == 0){// childprintf("pid:%d,begin to exec!\n",getpid());sleep(3);execl("/usr/bin/ls","ls","-a","-l",NULL);printf("pid:%d,end to exec!\n",getpid());}else {// fatherprintf("wait child\n");pid_t rid = waitpid(-1,NULL,0);if(rid > 0){printf("wait success\n");}}return 0;
}
实验结果如下:
[test@VM-16-11-centos 11-8]$ ./myprocess
wait child
pid:18212,begin to exec!
total 28
drwxrwxr-x 2 test test 4096 Nov 9 11:05 .
drwxrwxrwt 16 test test 4096 Nov 8 20:40 ..
-rw-rw-r-- 1 test test 74 Nov 8 20:41 Makefile
-rwxrwxr-x 1 test test 8672 Nov 9 11:05 myprocess
-rw-rw-r-- 1 test test 695 Nov 9 11:05 myprocess.c
wait success
从中看出多进程替换中增加了父进程对子进程的等待和回收的部分功能
那在多进程下应该如何理解进程替换呢?用下面图示的过程来演示:

从这里的进程替换中可以发掘出一些东西,替换的是进程,而不是代码,所以这里可以替换的内容有很多,甚至可以是Java写的程序运行起来的进程等等,看下面的实验
下面实现一个cpp程序
#include <iostream>int main()
{std::cout<<"this is a cpp program"<<std::endl;return 0;
}
对前面的程序进行修改
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/wait.h>int main()
{pid_t id = fork();if(id == 0){// childprintf("pid:%d,begin to exec!\n",getpid());sleep(3);execl("./cpptest","./cpptest",NULL);//execl("/usr/bin/ls","ls","-a","-l",NULL);printf("pid:%d,end to exec!\n",getpid());}else {// fatherprintf("wait child\n");pid_t rid = waitpid(-1,NULL,0);if(rid > 0){printf("wait success\n");}}return 0;
}
对Makefile进行修改
.PHONY:all
all:myprocess cpptestcpptest:cpptest.cc g++ -o $@ $^myprocess:myprocess.cgcc -o $@ $^
.PHONY:clean
clean:rm -rf myprocess cpptest
这里利用的是Makefile自带的自我推演能力,使用Makefile进行自我推演可以推演出,现在需要myprocess和cpptest,而这两个程序又会分别进行执行运行
此时进行运行,此时会做出如下的实验结果:
[test@VM-16-11-centos 11-8]$ ./myprocess
wait child
pid:23071,begin to exec!
this is a cpp program
wait success
从中不难看出,确实实现了进程的替换,而且替换的还是其余进程
这也就解释了在不同的公司中是可以存在分块进行构建模块功能的,最后都可以通过进程的形式链接起来
从某种意义来说,进程的替换已经可以被看成是一种系统调用了,站在系统的视角看内存中的所谓进程,实际上是一样的,系统高于一切,它可以对进程进行调度和分配
环境变量与进程替换
当进行进程替换的过程中,对于环境变量的角度来讲,是以什么样的情况进行的传递呢?
结论是:子进程对应的环境变量,是可以直接从父进程来的
对这个结论进行验证:
有关进程替换的一些函数

- execl函数,需要找到命令所在的文件目录,使用方法如下:
#include <stdio.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/wait.h>int main()
{pid_t id = fork();if(id == 0){// child// 进行进程替换execl("/usr/bin/ls", "ls", "-a", "-l", "-d", NULL);}else {// parent// 对子进程回收pid_t rid = waitpid(-1, NULL, 0);if(rid > 0){printf("wait success\n");}}return 0;
}
- execlp函数:会到系统默认的路径下寻找命令
execlp("ls", "ls", "-a", "-l", "-d", NULL);
- execle函数:用一个程序调用另外一个程序,但环境变量是自己的环境变量,不是系统的,通过获取环境变量查看
如何在进程中添加一个环境变量?用到的是putenv函数:
void *putenv(char *name)
由此可以写出下面的程序
#include <iostream>int main(int argc, char* argv[], char* env[])
{// 输出命令行参数for(int i = 0; argv[i]; i++){std::cout << i << "->" << argv[i] << std::endl;}std::cout << "##############" << std::endl;// 输出环境变量for(int i = 0; env[i]; i++){std::cout << i << "->" << env[i] << std::endl;}return 0;
}
上面是用于进程替换的函数,在这个基础上,对原程序进行修改
#include <stdio.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/wait.h>int main()
{// 在程序中新增环境变量char* myenv = "MYVAL1 = 11111111";putenv(myenv);pid_t id = fork();if(id == 0){// child// 进行进程替换execl("./myprocess", "myprocess", NULL);}else {// parent// 对子进程回收pid_t rid = waitpid(-1, NULL, 0);if(rid > 0){printf("wait success\n");}}
}
运行程序如下:

从中看出,在子进程中是出现了新增的这个环境变量的,由此可以基本验证,在父进程中添加的环境变量会继承到子进程中
那么父进程的父进程是谁呢?答案是bash,那么是不是在bash中添加的环境变量也会继承到子进程中?
再对上面的程序进行修改
#include <stdio.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/wait.h>int main(int argc, char* argv[], char* env[])
{// 输出环境变量for(int i = 0; env[i]; i++){printf("%d -> %s\n", i, env[i]);}// 在程序中新增环境变量char* myenv = {"MYVAL1 = 11111111","MYVAL2 = 22222222",NULL};putenv(myenv);pid_t id = fork();if(id == 0){// child// 进行进程替换execl("./mytest", "mytest", NULL);}else {// parent// 对子进程回收pid_t rid = waitpid(-1, NULL, 0);if(rid > 0){printf("wait success\n");}}
}

由此可以得出这样的一条线索化的示意图:

再次回到这张图

下面看execle函数
环境变量的传递方式?
前面的例子证明,子进程的环境变量是由父进程传递的,而execle函数就是一个显示传递环境变量的函数,它的第三个参数是envp[],实际上就是环境变量
那如何进行使用?看下面的程序
#include <stdio.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/wait.h>int main(int argc, char* argv[], char* env[])
{// 在程序中新增环境变量char* const myenv[] = {"MYVAL1 = 11111111","MYVAL2 = 22222222",NULL};pid_t id = fork();if(id == 0){// child// 进行进程替换execle("./mytest", "mytest", NULL, myenv);}else {// parent// 对子进程回收pid_t rid = waitpid(-1, NULL, 0);if(rid > 0){printf("wait success\n");}}return 0;
}
运行结果如下:

从中看出,通过这个函数可以把环境变量进行显示传递给子进程,并且是一种覆盖式传递
到此,有关进程替换的基本逻辑已经结束,那进程替换可以做什么实际的东西呢?
命令行解释器
在前面的认知中,命令行解释器,也就是bash,可以把用户在命令行中敲的命令转换成命令再输出,而实际上,这是一个逻辑很简单的过程:
bash程序相当于是一个一直在后台运行的程序,而当用户敲了一些命令行后,bash创建子进程,就将这些命令行转换为一个字符串数组,采用进程替换的方式就可以把要找的命令和选项替换到前台,那依据这个原理,其实我们自己也能实现一个命令行解释器:
实现逻辑
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/wait.h>#define NUM 1024
#define SIZE 64
#define SEP " "char cwd[1024];
char enval[1024];
int lastcode = 0;const char *getUsername()
{const char *name = getenv("USER");if(name) return name;else return "none";
}const char *getHostname()
{const char *hostname = getenv("HOSTNAME");if(hostname) return hostname;else return "none";
}const char *getCwd()
{const char *cwd = getenv("PWD");if(cwd) return cwd;else return "none";
}int getUserCommand(char *command, int num)
{printf("[%s@%s %s]# ", getUsername(), getHostname(), getCwd());char *r = fgets(command, num, stdin);if(r == NULL) return -1;command[strlen(command) - 1] = '\0';return strlen(command);
}void commandSplit(char *in, char *out[])
{int argc = 0;out[argc++] = strtok(in, SEP);while(out[argc++] = strtok(NULL, SEP));
}int execute(char *argv[])
{pid_t id = fork();if(id < 0) {return -1;}else if(id == 0){execvp(argv[0], argv);exit(1);}else{int status = 0;pid_t rid = waitpid(id, &status, 0);if(rid > 0){lastcode = WEXITSTATUS(status);}}return 0;
}void cd(const char *path)
{chdir(path);char tmp[1024];getcwd(tmp, sizeof(tmp));sprintf(cwd, "PWD=%s", tmp);putenv(cwd);
}int doBuildin(char *argv[])
{if(strcmp(argv[0], "cd") == 0){char *path = NULL;if(argv[1] == NULL) path = ".";else path = argv[1];cd(path);return 1;}else if(strcmp(argv[0], "export") == 0){if(argv[1] == NULL) return 1;strcpy(enval, argv[1]);putenv(enval); // ???return 1;}else if(strcmp(argv[0], "echo") == 0){char *val = argv[1] + 1;if(strcmp(val, "?") == 0){printf("%d\n", lastcode);lastcode = 0;}else{printf("%s\n", getenv(val));}return 1;}else if(0){}return 0;
}int main()
{while(1){char usercommand[NUM];char *argv[SIZE];// 1. 打印提示符&&获取用户命令字符串获取成功int n = getUserCommand(usercommand, sizeof(usercommand));if(n <= 0) continue;// 2. 分割字符串// "ls -a -l" -> "ls" "-a" "-l"commandSplit(usercommand, argv);// 3. check build-in commandn = doBuildin(argv);if(n) continue;// 4. 执行对应的命令execute(argv);}
}
相关文章:
Linux:进程替换和知识整合
文章目录 进程程序替换替换原理进程替换的理解 环境变量与进程替换命令行解释器实现逻辑 进程程序替换 前面已经学习了子进程的创建,但是子进程的创建不管怎么说,都是父进程代码的一部分,那么实际上如果想要子进程执行新的程序呢?…...
React组件在什么情况下会重新渲染
当我们使用React编写组件时,组件的重新渲染是一个重要的概念。重新渲染是指React组件在特定情况下会重新执行其渲染函数,更新用户界面以反映最新的数据。很多情况下,组件不必要的重新渲染会严重影响性能,所以要充分了解触发组件重…...
云ES容灾方案
一、ES集群可用性容灾 1.1 云ES集群可用性容灾(使用跨可用区实例) 云ES集群部署在三个可用区,单可用区故障,云ES集群依然可能对外提供服务;两个可用区故障,需要进行控制台切流(集群会自动切的选择主节点) 应用服务部署在二个可用区,单可用区故障,依然可对提供服务1.2 …...
Golang 中的 Context 包
简介 今天,我们将讨论 Go 编程中非常重要的一个主题:context 包。如果你现在觉得它很令人困惑,不用担心 — 在本文结束时,你将像专家一样处理 context! 想象一下,你在一个主题公园,兴奋地准备…...
nginx服务器
nginx反向代理 nginx 反向代理的好处: 提高访问速度 因为nginx本身可以进行缓存,如果访问的同一接口,并且做了数据缓存, nginx就直接可把数据返回,不需要真正地访问服务端,从而提高访问速度。 进行负载均衡…...
电脑常用快捷键
一、Win键(徽标键) 以下是Windows操作系统中使用Win键(徽标键)结合A-Z的所有快捷方式描述: - Win A:打开操作中心,访问系统通知、快速设置和其他功能 - Win B:将焦点设置到任务栏…...
吴恩达《机器学习》8-3->8-4:模型表示I、模型表示II
8.3、模型表示I 一、大脑神经网络的基本原理 为了构建神经网络模型,首先需要理解大脑中的神经网络是如何运作的。每个神经元都可以被看作是一个处理单元或神经核,它包含多个输入(树突)和一个输出(轴突)。…...
数据结构-二叉树力扣题
目录 1.相同的树 2.二叉树中查找值为x的节点 3.单值二叉树 4.对称二叉树 5.二叉树的前序遍历 6.另一颗树的子树 层序遍历: 7.二叉树遍历 8.判断二叉树是否是完全二叉树 一个特殊的性质: 1.相同的树 题目链接:力扣(LeetC…...
node 第十八天 中间件express-session实现会话密钥
express-session 文档 express-session 一个简单的express会话中间件 使用场景 在一个系统中, 需要维持一个临时的与登录态无关的会话密钥 比如登录系统后, 请求某一个接口, 接口的行为与登录态无关, 也就是说任何人对接口的访问…...
【机器学习基础】机器学习入门(1)
🚀个人主页:为梦而生~ 关注我一起学习吧! 💡专栏:机器学习 欢迎订阅!后面的内容会越来越有意思~ 💡专栏介绍: 本专栏的第一篇文章,当然要介绍一下了~来说一下这个专栏的开…...
赶快来!程序员接单必须知道的六大注意事项!!!
花花世界迷人眼,增加实力多搞钱!对于咱程序员来说,搞钱的最好办法就是网上接单了,相信也有不少小伙伴已经在尝试了吧!但是如何正确的搞钱呢?其中的注意事项你真的了解吗? 本期就和小编一起来看…...
【C++】日期类实现,与日期计算相关OJ题
文章目录 日期类的设计日期计算相关OJ题HJ73 计算日期到天数转换KY111 日期差值KY222 打印日期KY258 日期累加 在软件开发中,处理日期是一项常见的任务。为了方便地操作日期,我们可以使用C编程语言来创建一个简单的日期类。在本文中,我们将介…...
前端404页面的制作
1、背景 前端开发经常遇到输入路径不存在的问题,为此,把之前项目的404拿出来供大家参考。代码很简单,适合新手入手,效果如下: 2、代码引用的是element-plus框架 <template><div><el-result icon"…...
深兰科技轮腿家用AI机器人荣获“2023年度城市更新科创大奖”
近日,“2023金砖论坛第五季金立方城市更新科创大会”在上海举行,会上发布了《第12届金砖价值榜》,深兰科技研发出品的轮腿式家用AI机器人(兰宝),因其AI技术的创新性应用,荣获了“2023年度城市更新科创大奖”。 在10月2…...
669.修剪二叉树
原题链接:669.修剪二叉树 全代码: class Solution { public:TreeNode* trimBST(TreeNode* root, int low, int high) {if (root nullptr ) return nullptr;if (root->val < low) {TreeNode* right trimBST(root->right, low, high); // 寻找符合区间[l…...
论文绘图-机器学习100张模型图
在现代学术研究和技术展示中,高质量的图表和模型结构图是至关重要的。这尤其在机器学习领域更为显著,一个领域以其复杂的算法和复杂的数据结构而闻名。机器学习是一种使用统计技术使计算机系统能够从数据中学习和改进其任务执行的方法,而有效…...
PHP项目学习笔记-萤火商城-增加一个模块(表涉及到的操作和文件)
背景 是在store的后台添加一个页面,显示的如满意度调查的页面 在router.config.js里面配置一个新的菜单 路径:yoshop2.0-store\src\config\router.config.js 代码如下,很简单,定义了这菜单点击的时候进入的页面,和下面…...
如何用Java设计自动售货机?
如何用Java设计自动售货机?是大多在高级Java开发人员面试中经常被问到的好问题之一。在典型的编码面试中,你会得到一个问题描述来开发一个售货机,在有限的时间内,通常2到3小时内,你需要在Java中编写设计文档、工作代码和单元测试。这种Java面试的一个关键优势是可以一次测试候…...
JAVA数据代码示例
首先,我们需要导入一些必要的Java库 java import java.net.URL; import java.net.HttpURLConnection; import java.io.BufferedReader; import java.io.InputStreamReader; 然后,我们可以创建一个URL对象,表示我们要爬取的网页的URL。 jav…...
github常用搜索指令
一、常用搜索指令 以下指令可分开用,也可组合使用 根据关键字搜索 in:name xx继上一步:指定开发语言 language:Java in:name XX language:Java继上一步,指定更新日期 pushed:>2022-06-06 in:name XX language:Java pushed:>2022-0…...
SkyWalking 10.2.0 SWCK 配置过程
SkyWalking 10.2.0 & SWCK 配置过程 skywalking oap-server & ui 使用Docker安装在K8S集群以外,K8S集群中的微服务使用initContainer按命名空间将skywalking-java-agent注入到业务容器中。 SWCK有整套的解决方案,全安装在K8S群集中。 具体可参…...
【kafka】Golang实现分布式Masscan任务调度系统
要求: 输出两个程序,一个命令行程序(命令行参数用flag)和一个服务端程序。 命令行程序支持通过命令行参数配置下发IP或IP段、端口、扫描带宽,然后将消息推送到kafka里面。 服务端程序: 从kafka消费者接收…...
突破不可导策略的训练难题:零阶优化与强化学习的深度嵌合
强化学习(Reinforcement Learning, RL)是工业领域智能控制的重要方法。它的基本原理是将最优控制问题建模为马尔可夫决策过程,然后使用强化学习的Actor-Critic机制(中文译作“知行互动”机制),逐步迭代求解…...
2025年能源电力系统与流体力学国际会议 (EPSFD 2025)
2025年能源电力系统与流体力学国际会议(EPSFD 2025)将于本年度在美丽的杭州盛大召开。作为全球能源、电力系统以及流体力学领域的顶级盛会,EPSFD 2025旨在为来自世界各地的科学家、工程师和研究人员提供一个展示最新研究成果、分享实践经验及…...
以下是对华为 HarmonyOS NETX 5属性动画(ArkTS)文档的结构化整理,通过层级标题、表格和代码块提升可读性:
一、属性动画概述NETX 作用:实现组件通用属性的渐变过渡效果,提升用户体验。支持属性:width、height、backgroundColor、opacity、scale、rotate、translate等。注意事项: 布局类属性(如宽高)变化时&#…...
关于iview组件中使用 table , 绑定序号分页后序号从1开始的解决方案
问题描述:iview使用table 中type: "index",分页之后 ,索引还是从1开始,试过绑定后台返回数据的id, 这种方法可行,就是后台返回数据的每个页面id都不完全是按照从1开始的升序,因此百度了下,找到了…...
转转集团旗下首家二手多品类循环仓店“超级转转”开业
6月9日,国内领先的循环经济企业转转集团旗下首家二手多品类循环仓店“超级转转”正式开业。 转转集团创始人兼CEO黄炜、转转循环时尚发起人朱珠、转转集团COO兼红布林CEO胡伟琨、王府井集团副总裁祝捷等出席了开业剪彩仪式。 据「TMT星球」了解,“超级…...
【HTML-16】深入理解HTML中的块元素与行内元素
HTML元素根据其显示特性可以分为两大类:块元素(Block-level Elements)和行内元素(Inline Elements)。理解这两者的区别对于构建良好的网页布局至关重要。本文将全面解析这两种元素的特性、区别以及实际应用场景。 1. 块元素(Block-level Elements) 1.1 基本特性 …...
Mac下Android Studio扫描根目录卡死问题记录
环境信息 操作系统: macOS 15.5 (Apple M2芯片)Android Studio版本: Meerkat Feature Drop | 2024.3.2 Patch 1 (Build #AI-243.26053.27.2432.13536105, 2025年5月22日构建) 问题现象 在项目开发过程中,提示一个依赖外部头文件的cpp源文件需要同步,点…...
代理篇12|深入理解 Vite中的Proxy接口代理配置
在前端开发中,常常会遇到 跨域请求接口 的情况。为了解决这个问题,Vite 和 Webpack 都提供了 proxy 代理功能,用于将本地开发请求转发到后端服务器。 什么是代理(proxy)? 代理是在开发过程中,前端项目通过开发服务器,将指定的请求“转发”到真实的后端服务器,从而绕…...
