Linux:进程控制(三)——进程程序替换
目录
一、概念
二、使用
1.单进程程序替换
2.多进程程序替换
3.exec接口
4.execle
一、概念
- 背景
当前进程在运行的时候,所执行的代码来自于自己的源文件。使用fork创建子进程后,子进程执行的程序中代码内容和父进程是相同的,如果子进程想要执行其他程序的代码呢?
- 概念
子进程通过调用一类exec接口来执行另一个程序,这种操作称为程序替换。
- 原理
如下是一个进程的信息蓝图。
现在,这个进程想要执行其他程序。
调用exec这类函数所做的工作就是,将其他程序的代码和数据覆盖式的写入到之前这个程序代码和数据的物理内存空间中,也可能开辟新的空间用来存储,或许还会修改页表的映射关系,总之,这个操作的结果就是,发生替换后,CPU在执行这个进程的时候,代码和数据已经是其他程序的。
- 程序替换而不是进程替换
程序替换过程中,只是将程序的代码和数据做了替换,并不是替换进程,所以,这个过程并没有创建新的进程,进程的PID不会发生变化。
二、使用
程序替换过程需要从外设加载数据到内存,因此程序替换这个工作一定是由操作系统来执行的,所以程序替换必然会使用系统调用,先介绍一下相关的接口函数。
查看man手册,发现有6个接口是语言函数。
man execl
EXEC(3) Linux Programmer's Manual NAMEexecl, execlp, execle, execv, execvp, execvpe - execute a fileSYNOPSIS#include <unistd.h>extern char **environ;int execl(const char *pathname, const char *arg, .../* (char *) NULL */);int execlp(const char *file, const char *arg, .../* (char *) NULL */);int execle(const char *pathname, const char *arg, .../*, (char *) NULL, char *const envp[] */);int execv(const char *pathname, char *const argv[]);int execvp(const char *file, char *const argv[]);int execvpe(const char *file, char *const argv[],char *const envp[]);
在man手册中查找系统调用,发现还有一个接口是系统调用。
man execve
EXECVE(2) Linux Programmer's Manual NAMEexecve - execute programSYNOPSIS#include <unistd.h>int execve(const char *pathname, char *const argv[],char *const envp[]);
1.单进程程序替换
以这个函数举例:
int execl(const char *pathname, const char *arg, ... (char *) NULL);
//第一个参数是待执行程序的路径
//第二个参数是const char * arg
//第三个参数是···即可变参数
//第二个参数和第三个参数(不止一个)是程序的执行选项,传参方式类似于命令行传参
//比如指令 ls -a -l -n
//传参为 "ls","-a","-l","-n"
//注意,参数最终以NULL结尾,不是"NULL"
编写源文件如下。
#include <stdio.h>
#include <unistd.h>
#include <sys/types.h>
#include <stdlib.h>int main()
{printf("exec Before\n");execl("/usr/bin/ls","ls","-l","-a","-n",NULL);printf("exec End\n");return 0;
}
exec Before
总计 36
drwxrwxr-x 2 1000 1000 4096 10月 9 16:30 .
drwxrwxr-x 13 1000 1000 4096 10月 9 10:23 ..
-rw-rw-r-- 1 1000 1000 85 10月 9 10:23 Makefile
-rwxrwxr-x 1 1000 1000 17224 10月 9 16:30 myprocess
-rw-rw-r-- 1 1000 1000 207 10月 9 16:15 myprocess.c
- 疑问,程序中最后一行没有打印,原因是什么?
调用exec类函数完成程序替换后,当前程序的剩余代码都不会再被执行,因为此时执行的代码已经是另外一个程序的。
- 关于exec这类函数的返回值
程序替换成功,则没有返回值,转而执行另外的程序。只有程序替换失败时,才会返回-1,并且设置错误码,如此,调用exec函数后可以直接加一行退出程序的代码,因为程序替换失败时当前程序的运行必然不合预期。
RETURN VALUEThe exec() functions return only if an error has occurred. The return value is -1, and errno is set to indicate the error.
execl("/usr/bin/ls","ls","-l","-a","-n",NULL);
exit(1);
- 验证程序替换不会创建新进程
编写代码如下:
utocoo@utocoo-virtual-machine:~/Desktop/linux/241009$ ll
总计 16
drwxrwxr-x 2 utocoo utocoo 4096 10月 10 10:21 ./
drwxrwxr-x 13 utocoo utocoo 4096 10月 9 10:23 ../
-rw-rw-r-- 1 utocoo utocoo 85 10月 9 10:23 Makefile
-rw-rw-r-- 1 utocoo utocoo 259 10月 10 10:21 myprocess.c
//myprocess.c
#include <stdio.h>
#include <unistd.h>
#include <sys/types.h>
#include <stdlib.h>int main()
{printf("exec Before\n");printf("I am process,PID:%d\n",getpid());sleep(5);execl("/usr/bin/top","top",NULL);exit(1);printf("exec End\n");return 0;
}
执行如下这条指令,在监视窗口观察PID。
while :; do ps ajx | head -1 && ps ajx | grep myprocess | grep -v grep;sleep 1; done
编译运行可执行程序myprocess,监视窗口打印结果如下。
PPID PID PGID SID TTY TPGID STAT UID TIME COMMANDPPID PID PGID SID TTY TPGID STAT UID TIME COMMANDPPID PID PGID SID TTY TPGID STAT UID TIME COMMAND2295 3476 3476 2295 pts/0 3476 S+ 1000 0:00 ./myprocessPPID PID PGID SID TTY TPGID STAT UID TIME COMMAND2295 3476 3476 2295 pts/0 3476 S+ 1000 0:00 ./myprocessPPID PID PGID SID TTY TPGID STAT UID TIME COMMAND2295 3476 3476 2295 pts/0 3476 S+ 1000 0:00 ./myprocessPPID PID PGID SID TTY TPGID STAT UID TIME COMMAND2295 3476 3476 2295 pts/0 3476 S+ 1000 0:00 ./myprocessPPID PID PGID SID TTY TPGID STAT UID TIME COMMANDPPID PID PGID SID TTY TPGID STAT UID TIME COMMANDPPID PID PGID SID TTY TPGID STAT UID TIME COMMAND
PID为3476,但是在程序替换后,捕捉不到PID了,原因是可执行程序的名字发生了变化,执行这条语句再来对比PID。
while :; do ps ajx | head -1 && ps ajx | grep top | grep -v grep;sleep 1; done
PPID PID PGID SID TTY TPGID STAT UID TIME COMMAND
2295 3476 3476 2295 pts/0 3476 S+ 1000 0:00 top
结果符合预期,PID并未发生变化,即没有创建新的进程。
- 创建一个进程时,先创建PCB、进程地址空间、页表等内容,再将磁盘中的代码和数据加载到内存中。
程序替换时,由于并不需要创建新的进程,所以只需要将新的程序代码和数据加载到内存中即可。于是,更进一步理解,操作系统在将程序的代码和数据加载到内存中时,是通过程序替换完成的。
2.多进程程序替换
- 操作举例
编写代码如下。
int main()
{pid_t id = fork();if(id == 0){printf("exec Before\n");printf("I am child process,PID:%d\n",getpid());sleep(3);execl("/usr/bin/ls","ls","-l",NULL);exit(1);printf("exec End\n");}sleep(1);pid_t rid = waitpid(id,NULL,0);if(rid>0){printf("wait success!\n");}return 0;}
程序替换的场景,更多的是创建子进程作程序替换,原因很简单,父进程可以获取到程序替换的结果。
当子进程作程序替换时,此时子进程共享的还是父进程的代码数据,因此发生写时拷贝,不仅仅是数据发生改变,代码也会发生变化。
- Shell是如何运行指令的?
指令即一个程序,Shell正在运行时,输入指令后,创建一个子进程,Shell等待子进程(waitpid),子进程此时共享Shell的代码,子进程作程序替换,替换的目标程序就是输入的指令,执行完毕,同时Shell也能获取到子进程的执行结果。
3.exec接口
前面已经说明了,exec类接口中,语言函数6个,而系统调用只有一个,不难总结出来,函数内部都是封装了系统调用的。
这些接口的功能大致类似,都是完成程序替换,只是用法传参有所差异。
先来介绍一下6个接口函数。
int execl(const char *pathname, const char *arg, .../* (char *) NULL */);
int execlp(const char *file, const char *arg, .../* (char *) NULL */);
int execle(const char *pathname, const char *arg, .../*, (char *) NULL, char *const envp[] */);
int execv(const char *pathname, char *const argv[]);
int execvp(const char *file, char *const argv[]);
int execvpe(const char *file, char *const argv[],char *const envp[]);
它们都以exec*开头,只是后缀有所区别,有l 、p、e、v。
在介绍函数用法之前,先来区分它们后缀,因为不同的后缀就表示了它们不同的用法。
l(list) : 表示参数采用列表
v(vector) : 参数用数组
p(path) : 有p自动搜索环境变量PATH
e(env) : 表示自己维护环境变量
你可能对这些后缀的意义不明所以,下面来看用法。
- execlp
int execlp(const char *file, const char *arg, ... /* (char *) NULL */);
这个函数包含两个后缀l、p。
包含p,说明带环境变量,在程序替换时,目标程序可以只给出名字,不用给出全部路径,因为环境变量已经包含了一部分,比如系统的指令。
list则说明需要列表式的传参。
execlp("ls","ls","-l","-a",NULL);
值得一提的是,前两个参数一模一样,但是并不冲突,二者的意义不一样,第一个参数表示目标程序,第二个参数(不止一个)表示执行目标程序的方式,其他参数"-l"、"-a"含义上也是第二个参数。
- execv
int execv(const char *pathname, char *const argv[]);
关于argv这个指针数组,这一文中Linux:环境变量介绍main函数的参数时也提到了。这个数组的元素是一个个的指针,每一个指针指向一个字符串,这些字符串其实就是原来使用execl传参时的字符串。
char* const argv[]={(char*)"ls",(char*)"-l",(char*)"-a",NULL};
execv("/usr/bin/ls",argv);
- execvp
int execvp(const char *file, char *const argv[]);
execvp("ls",argv);
4.execle
int execle(const char *pathname, const char *arg, ...
/*, (char *) NULL, char *const envp[] */);
在介绍这个函数用法之前,要给出一个结论:exec*类函数,可以替换系统的指令,也可以替换任何程序,比如cpp、python、java程序。(C++程序的源文件后缀有.cc、.cpp、.cxx)
即,程序替换可能发生的情况:使用C语言程序运行的进程,创建子进程后,子进程程序可能被替换为Java程序,等等类似的情况,从这一层面看,程序替换的意义重大!!!
发生上面所述情况的原因只有一个,就是无论是何种语言编写的程序,在运行之后,都是由操作系统统一管理的进程!!!
在替换这一层,只有被区分为代码和数据的二进程内容,没有语言的差异,因此替换只是二进制文本被替换。
使用Makefile文件时,由于make指令只生成第一个可执行程序,因此,想要一次编译链接多个源文件,可以使用下面这样的方式。
.PHONY:all all:mytest myprocessmytest:mytest.ccg++ -o $@ $^ -g -std=c++11 myprocess:myprocess.cgcc -o $@ $^ -g -std=c99 .PHONY:clean clean:rm -f myprocess
在 Linux:环境变量一文中,总结出环境变量可以被子进程继承,环境变量具备了全局属性。
现在将这些联系起来,操作系统启动Bash程序,等待命令行输入,命令行输入指令,Bash将输入的指令字符串作为exec函数的参数,然后子进程作程序替换,这些参数传给了目标程序的main函数。
但是环境变量并不是通过传参这样的方式传递的。
编写代码如下,子进程程序被替换为由mytest.cc生成的C++程序,在mytest.cc中打印环境变量,得到结果符合预期。但是在替换程序时调用exec函数时并没有传环境变量的参数。
//myprocess.c程序替换时没有传环境变量的参数
execl("./mytest","mytest",NULL);
//mytest.cc打印环境变量
#include <iostream>
#include <unistd.h>
using namespace std;
int main()
{for(int i =0;environ[i];++i){cout <<"environ["<< i << "]" << ":"<< environ[i] << endl;}return 0;
}
//打印结果是有环境变量的
exec Before
I am process,PID:2819
environ[0]:SHELL=/bin/bash
environ[1]:SESSION_MANAGER=local/utocoo-virtual-machine:@/tmp/.ICE-unix/1578,unix/utocoo-virtual-machine:/tmp/.ICE-unix/1578
environ[2]:QT_ACCESSIBILITY=1
environ[3]:COLORTERM=truecolor
······
在Linux:地址分区一文中,我有写到,命令行参数、环境变量在虚拟地址空间中的大致位置。
一个进程的PCB信息包含着虚拟地址空间、页表等内容。当子进程由父进程创建后,子进程有着自己的PCB、虚拟地址空间等,而程序替换不会将物理内存中的环境变量数据替换,因此,子进程是通过继承虚拟地址空间的方式继承全局环境变量。
- 拥有自己的环境变量
当前进程myprocess想要拥有一个自己的环境变量,可以用函数putenv来实现,这个环境变量会被myprocess的子进程继承,但是它的父进程bash则不包含这个环境变量。
在myprocess.c的源文件中添加下面一行代码用来导出环境变量。
putenv("NewENV=~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~");
在命令行中查看是否有环境变量NewENV,打印结果说明bash(myprocess的父进程)没有这个环境变量。
utocoo@utocoo-virtual-machine:~/Desktop/linux/241009$ echo $NewEnvutocoo@utocoo-virtual-machine:~/Desktop/linux/241009$
运行myprocess后,做程序替换操作,打印环境变量,发现子进程会继承环境变量。
······
environ[53]:LC_NUMERIC=zh_CN.UTF-8
environ[54]:_=./myprocess
environ[55]:OLDPWD=/home/utocoo/Desktop/linux
environ[56]:NewENV=~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
新增环境变量会被子进程继承,但不影响父进程的环境变量。
现在我们清楚的知道,每一个进程的环境变量都会继承于父进程,如果现在要求新建的一个子进程拥有全新的环境变量,不继承父进程的环境变量,要如何做呢?
使用带e的exec*接口。
编写代码如下。
//myprocess.c
int main()
{printf("I am process,PID:%d\n",getpid());sleep(3);char* const env[]={(char*)"1+1=2",(char*)"2+2=3",NULL};pid_t id = fork();if(id ==0){printf("exec Before\n");execle("./mytest","mytest",NULL,env);exit(1);printf("exec End\n");}pid_t rid = waitpid(id,NULL,0);return 0;
}
程序运行结果如下,结果显示子进程没有继承父进程的环境变量。
utocoo@utocoo-virtual-machine:~/Desktop/linux/241009$ ./myprocess
I am process,PID:3353
exec Before
environ[0]:1+1=2
environ[1]:2+2=3
这就是带e的exec接口的使用场景。
相关文章:

Linux:进程控制(三)——进程程序替换
目录 一、概念 二、使用 1.单进程程序替换 2.多进程程序替换 3.exec接口 4.execle 一、概念 背景 当前进程在运行的时候,所执行的代码来自于自己的源文件。使用fork创建子进程后,子进程执行的程序中代码内容和父进程是相同的,如果子进…...

LeetCode279:完全平方数
题目链接:279. 完全平方数 - 力扣(LeetCode) 代码如下 class Solution { public:int numSquares(int n) {vector<int> dp(n 1, INT_MAX);dp[0] 0;for(int i 1; i * i < n; i){for(int j i * i; j < n; j){dp[j] min(dp[j …...

python爬虫--某动漫信息采集
python爬虫--tx动漫 一、采集主页信息二、采集详情页信息三、代码供参考一、采集主页信息 略。 二、采集详情页信息 如上图所示,使用xpath提取详情页的标题、作者、评分、人气、评论人数等数据。 三、代码供参考 import csv import time import random import requests fr…...

使用Rollup.js快速开始构建一个前端项目
Rollup 是一个用于 JavaScript 项目的模块打包器,它将小块代码编译成更大、更复杂的代码,例如库或应用程序。Rollup 对代码模块使用 ES6 模块标准,它支持 Tree-shaking(摇树优化),可以剔除那些实际上没有被…...

10.15学习
1.程序开发的步骤 定义程序的目标→设计程序→编写代码(需要选择语言,一种语言对应一种编译器)→编译→运行程序→测试和调试程序→维护和修改程序 2.ANSI/ISO C标准 1989年ANSI批准通过,1990年ISO批准通过,因此被称…...

mongodb-7.0.14分片副本集超详细部署
mongodb介绍: 是最常用的nosql数据库,在数据库排名中已经上升到了前六。这篇文章介绍如何搭建高可用的mongodb(分片副本)集群。 环境准备 系统系统 BC 21.10 三台服务器:192.168.123.247/248/249 安装包:…...

C++运算出现整型溢出
考虑如下代码: int aINT_MAX; int b 1; long c ab; 这段代码没有编过! 原因是a和b都是int型,相加之后会溢出。 请记住,c语言没有赋值,只有表达式,右侧会存在一个暂存的int保存ab的值,而明…...

LeetCode岛屿数量
题目描述 给你一个由 1(陆地)和 0(水)组成的的二维网格,请你计算网格中岛屿的数量。 岛屿总是被水包围,并且每座岛屿只能由水平方向和/或竖直方向上相邻的陆地连接形成。 此外,你可以假设该网…...

Karmada核心概念
以下内容为翻译,原文地址 Karmada 是什么? | karmada 一、Karmada核心概念 一)什么是Karmada 1、Karmada:开放,多云,多集群Kubernetes业务流程 Karmada (Kubernetes Armada)是一个Kubernetes管理系统&…...

Rust 与生成式 AI:从语言选择到开发工具的演进
在现代软件开发领域,Rust 语言正在逐步崭露头角,尤其是在高性能和可靠性要求较高的应用场景。与此同时,生成式 AI 的崛起正在重新塑造开发者的工作方式,从代码生成到智能调试,生成式 AI 的应用正成为提升开发效率和质量…...

Python爬虫高效数据爬取方法
大家好!今天我们来聊聊Python爬虫中那些既简洁又高效的数据爬取方法。作为一名爬虫工程师,我们总是希望用最少的代码完成最多的工作。下面我ll分享一些在使用requests库进行网络爬虫时常用且高效的函数和方法。 1. requests.get() - 简单而强大 requests.get()是我们最常用的…...

C语言之扫雷小游戏(完整代码版)
说起扫雷游戏,这应该是很多人童年的回忆吧,中小学电脑课最常玩的必有扫雷游戏,那么大家知道它是如何开发出来的吗,扫雷游戏背后的原理是什么呢?今天就让我们一探究竟! 扫雷游戏介绍 如下图,简…...

Spring WebFlux 响应式概述(1)
1、响应式编程概述 1.1、响应式编程介绍 1.1.1、为什么需要响应式 传统的命令式编程在面对当前的需求时的一些限制。在应用负载较高时,要求应用需要有更高的可用性,并提供低的延迟时间。 1、Thread per Request 模型 比如使用Servlet开发的单体应用&a…...

Unity游戏通用框架——事件的订阅和发布(观察者模式)
在游戏开发的基本思想中,逻辑与表现的分离极为重要,相互之间并不关心具体实现,只注册对应的事件,有事件发生时才调用相应的函数 事件管理器 using System.Collections; using System.Collections.Generic;public class event_ma…...

将 Ubuntu 系统中的 **swap** 空间从 2GB 扩展到 16GB
要将 Ubuntu 系统中的 swap 空间从 2GB 扩展到 16GB,可以按照以下步骤操作: 1. 关闭现有 Swap 文件 首先需要禁用当前的 swap 文件,以便重新调整其大小。 sudo swapoff -a2. 删除旧的 Swap 文件 假设当前的 swap 文件位于 /swapfile&…...

流程图 LogicFlow
流程图 LogicFlow 官方文档:https://site.logic-flow.cn/tutorial/get-started <script setup> import { onMounted, ref } from vue import { forEach, map, has } from lodash-es import LogicFlow, { ElementState, LogicFlowUtil } from logicflow/core …...

Mac通过键盘选取内容
问题: 我们在使用键盘的时候经常懒得动手去拿鼠标了,并且熟练使用键盘可以提高我们的工作效率,比如在我们需要复制内容的时候,可以仅仅通过键盘来选取想要的内容; 解决: 将鼠标光标移动到想要选取的内容…...

如何通过OpenCV实现图像融合拼接?
图像拼接的意义 2024年了,谈论图像拼接,不算新事物,我们这里探讨图像拼接,主要探讨图像拼接的意义、难点和大概的实现思路。图像拼接可以突破设备视野限制,通过拼接低分辨率图像获得高分辨率图像。 扩展视野ÿ…...

Qt5.14.2 安装详细教程(图文版)
Qt 是一个跨平台的 C 应用程序开发框架,主要用于开发图形用户界面(GUI)程序,但也支持非 GUI 程序的开发。Qt 提供了丰富的功能库和工具,使开发者能够在不同平台上编写、编译和运行应用程序,而无需修改代码。…...

深圳市步步精科技有限公司荣获发明专利,彰显技术研发实力
2024年8月13日,深圳市步步精科技有限公司(BBJconn)正式获得了其新开发的防水连接器专利,授权公告号为CN 118352837 B。这项技术的突破标志着公司在连接器领域的持续创新,进一步巩固了其行业领先地位。 专利技术概述 此…...

std::function的概念和使用方法
一、概念 std::function是 C 标准库中的一个模板类,定义在<functional>头文件中。它是一种通用的多态函数包装器,其实例能够对任何可调用对象进行存储、复制和调用操作,这些可调用对象包括普通函数、函数指针、成员函数指针、函数对象…...

OpenAI的Swarm是一个实验性质的多智能体编排框架
先上文档,然后解释,然后是代码 OpenAI的Swarm是一个实验性质的多智能体编排框架,旨在简化多智能体系统的构建、编排和部署。以下是对Swarm的详细介绍: 一、核心概念和特点 智能体(Agent): Swar…...

简易STL实现 | Map 的实现
提供了键值对的存储机制,处理 具有唯一键的关联数据 1、特性 键值对存储:std::map 通过键值对的形式 存储数据,其中每个键 都是唯一的,并且 与一个值相关联 自动排序:std::map 内部 使用一种平衡二叉搜索树…...

`concurrent.futures` 是 Python 标准库中的一个模块
先来看文档 concurrent.futures 是 Python 标准库中的一个模块,它提供了一个高级接口来异步执行代码,使用线程或进程池来并行运行任务。这个模块提供了两种主要的池类型:ThreadPoolExecutor 和 ProcessPoolExecutor,以及一个通用的…...

PicoQuant GmbH公司Dr. Christian Oelsner到访东隆科技
昨日,德国PicoQuant公司的光谱和显微应用和市场专家Dr.Christian Oelsner莅临武汉东隆科技有限公司。会议上Dr. Christian Oelsner就荧光寿命光谱和显微技术的最新研究和应用进行了深入的交流与探讨。此次访问不仅加强了两家公司在高科技领域的合作关系,…...

leetcode128最长连续序列 golang版
题目描述 题目:给定一个未排序的整数数组 nums 找出数字连续的最长序列,不要求序列 元素在原数组中连续 的长度 请你设计并实现时间复杂度为On的算法解决此问题 示例 1: 输入:nums [100,4,200,1,3,2] 输出:4 解释&…...

【OpenCV】(六)—— 阈值处理
阈值处理(Thresholding)用于将灰度图像转换为二值图像。通过设定一个或多个阈值,可以将图像中的像素分为不同的类别,通常用于分割前景和背景、简化图像、去除噪声等任务。OpenCV 提供了多种阈值处理方法,下面介绍基本阈…...

重学SpringBoot3-集成Redis(九)之共享Session
更多SpringBoot3内容请关注我的专栏:《SpringBoot3》 期待您的点赞👍收藏⭐评论✍ 重学SpringBoot3-集成Redis(九)之共享Session 1. 为什么需要 Session 共享2. Spring Session 和 Redis 的集成2.1. 引入依赖2.2. 配置 Redis 连接…...

Linux:信号保存与处理
使用kill -l命令查看信号: 信号量和信号确实一点关系没有 信号是操作系统发出的进程与进程之间的通知于中断,是进程之间时间异步通知的一种方式 先了解同步通信:同步通信是一种比特同步通信技术,要求发收双方具有同频同相的同步…...

工具方法 - 可选的一些AI聊天机器人
1, ChatGPT OpenAI https://chatgpt.com/ 2, Microsoft Copilot Microsoft Copilot: 你的 AI 助手 Microsoft Copilot: 你的 AI 助手 3, HuggingChat Hugging Face – The AI community building the future. https://huggingface.co/ https://huggingface.co/chat/ 4,…...