济南城乡建设委员会网站/网站建设网络推广平台
🥁作者: 华丞臧.
📕专栏:【LINUX】
各位读者老爷如果觉得博主写的不错,请诸位多多支持(点赞+收藏+关注
)。如果有错误的地方,欢迎在评论区指出。
推荐一款刷题网站 👉 LeetCode刷题网站
文章目录
- 一、进程替换
- 1.1 替换原理
- 1.2 替换函数
- 二、exec函数
- 2.1 execl
- 2.2 execv
- 2.3 execlp
- 2.5 execle
- 2.4 execvp
- 2.5 execve
- 补充快捷键
- 批量化注释
- 批量化取消注释
一、进程替换
前面我们学习了如何创建子进程,也知道了子进程执行的是父进程的代码片段;那么如果我们想让创建出来的子进程执行全新的程序呢?这时候就需要进程的程序替换。
一般在Linux编程的时候,需要子进程做两类事情:
- 让子进程执行父进程的代码片段(服务器代码);
- 让子进程执行磁盘中的一个全新的程序,如:shell可以让客户端执行其他人写的代码。
1.1 替换原理
用fork创建子进程后执行的是和父进程相同的程序(但有可能执行不同的代码分支),子进程往往要调用一种exec函数以执行另一个程序。当进程调用一种exec函数时,该进程的用户空间代码和数据完全被新程序替换,从新程序的启动例程开始执行。调用exec并不创建新进程,所以调用exec前后该进程的id并未改变。
程序替换原理总结:
- 将磁盘中的程序,加载到内存结构;
- 重新建立页表映射,谁执行程序替换,就重新建立谁的页表映射效果;让我们的父进程和子进程彻底分离,并让子进程执行一个全新的程序。
注意在程序替换的过程中没有创建新的进程,这个过程指的是程序替换的过程,此时子进程已经创建好了不算新创建的进程。
1.2 替换函数
Linux中有六种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[]);
函数解释:
- 这些函数如果调用成功则加载新的程序从启动代码开始执行,不再返回;
- 如果调用出错则返回-1。
- 所以exec函数只有出错的返回值而没有成功的返回值。
命名理解:
这些函数原型看起来很容易混,但只要掌握了规律就很好记。
- l(list):表示参数采用列表;
- v(vector):参数用数组
- p(path):有p自动搜索环境变量PATH;
- e(env):表示自己维护环境变量;
函数名 | 参数格式 | 是否带路径 | 是否使用当前环境变量 |
---|---|---|---|
execl | 列表 | 不是 | 是 |
execlp | 列表 | 是 | 是 |
execle | 列表 | 不是 | 不是,必须自己组装环境变量 |
execv | 数组 | 不是 | 是 |
execvp | 数组 | 是 | 是 |
execve | 数组 | 不是 | 不是,须自己组装环境变量 |
二、exec函数
接下来我们挑几个exec函数来说明其使用方式。
2.1 execl
int execl(const char *path, const char *arg, ...);
说明:
path
:程序所在的路径。arg
:替换的程序名。...
:可变参数列表,这个我们在使用printf和scanf的时候见过。- execl函数替换失败返回-1;
首先使用exec函数之前,我们需要知道如果想执行一个全新的程序,需要做下面两件事:
- 找到执行程序路径;
- 程序可能携带选项进行执行,也可以不携带;
这里我们可以参照Linux上的命令行是如何使用的,如下:
Linux中的指令本质上就是程序,所以命令行怎么写我们程序携带选项时传参就怎么写。既然Linux中的指令也是程序,我们首先来试试用指令来替换我们写的程序,代码如下:
#include <stdio.h>
#include <unistd.h>int main()
{printf("进程pid:%d\n", getpid());// arg表示目标程序名,而...参数列表必须以NULL结尾execl("/usr/bin/ls", "ls", NULL); //不带参数//execl("/usr/bin/ls", "ls", "-al", NULL); //带参数printf("进程替换成功\n");return 0;
}
不带参数的如下图:
带参数的如下图:
观察上述两幅图片,我们发现代码中execl下面的那句 printf
并没有执行;这是因为一旦替换成功,会将当前进程的代码和数据全部替换了,因此执行完execl函数进程中的代码和数据已经全部被替换了。
程序替换函数也有返回值,为int
类型;这个返回值不需要判断,一旦进程替换成功就不会有返回值,而替换失败必然会继续向后执行,这个返回值最多让我们知道是什么原因导致替换失败。
使用fork创建子进程,再将子进程进行程序替换,会不会影响父进程呢?
- 答案是不会,下面我们来实验其过程。
#include <unistd.h>
#include <stdlib.h>
#include <sys/types.h>int main()
{printf("进程pid:%d\n", getpid());pid_t id = fork();if(id == 0){//childprintf("这是子进程pid:%d,ppid:%d\n", getpid(), getppid());execl("/usr/bin/ls", "ls", "-al", NULL);exit(1);}//parentprintf("这是父进程pid:%d,ppid:%d\n", getpid(), getppid());pid_t ret = waitpid(id, NULL, 0);if(ret > 0) printf("子进程回收成功,ret:%d\n", ret);return 0;
}
可以看到子进程进行程序替换不会影响父进程,因为进程具有独立性,在数据层面发生写时拷贝:当程序替换的时候,可以理解为代码和数据都发生了写时拷贝,完成了父子进程的分离。
2.2 execv
int execv(const char *path, char *const argv[]);
函数说明:
path
:目标程序的路径;argv
:数组用来传参;- 替换失败返回-1,其中
v
表示数组(vector)。
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <sys/types.h>int main()
{printf("进程pid:%d\n", getpid());pid_t id = fork();if(id == 0){//childprintf("这是子进程pid:%d,ppid:%d\n", getpid(), getppid());// 使用argv进行传参,数组中第一个元素argv[0]必须为目标程序名//argv也必须以NULL结尾//char *const myenv[] = {// (char*)"ls",// (char*)"-a",// (char*)"-l",// NULL//};char *const myenv[] = {(char*)"pwd",NULL};//execv("/usr/bin/ls", myenv);execv("/usr/bin/pwd", myenv);exit(1);}//parentprintf("这是父进程pid:%d,ppid:%d\n", getpid(), getppid());pid_t ret = waitpid(id, NULL, 0);if(ret > 0) printf("子进程回收成功,ret:%d\n", ret);return 0;
}
2.3 execlp
int execlp(const char *file, const char *arg, ...);
函数说明:
- file:目标程序名;
- arg:参数,传目标程序名;
...
:可变参数列表;- 函数命名带p表示可以不带路径,只需要说明目标程序名;系统会通过环境变量
PATH
查找。 - 替换失败返回-1。
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <sys/types.h>int main()
{printf("进程pid:%d\n", getpid());pid_t id = fork();if(id == 0){//childprintf("这是子进程pid:%d,ppid:%d\n", getpid(), getppid()); // 使用execlp,可以不带路径:// 其中两个ls含义不同// 第一个为供系统查找,后面一个加上选项表示如何执行execlp("ls", "ls", "-a", NULL);exit(1);}//parentprintf("这是父进程pid:%d,ppid:%d\n", getpid(), getppid());pid_t ret = waitpid(id, NULL, 0);if(ret > 0) printf("子进程回收成功,ret:%d\n", ret);return 0;
}
2.5 execle
int execle(const char *path, const char *arg, ...,char *const envp[]);
函数说明:
path
:目标程序所在路径;arg
:参数,传目标程序名;...
:可变参数列表;envp
:用户传给目标程序的环境变量;- 失败返回-1。
使用exec函数也可以替换用户自己写的程序:
//replace.c
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <sys/types.h>int main()
{printf("进程pid:%d\n", getpid());pid_t id = fork();if(id == 0){//childprintf("这是子进程pid:%d,ppid:%d\n", getpid(), getppid());char *const env_[] = {(char*)"MYPATH=HELLOWORLD",NULL};execle("./mytest", "mytest", NULL, env_);exit(1);}//parentprintf("这是父进程pid:%d,ppid:%d\n", getpid(), getppid());pid_t ret = waitpid(id, NULL, 0);if(ret > 0) printf("子进程回收成功,ret:%d\n", ret);return 0;
}//mytest.cpp
#include <iostream>
#include <stdlib.h>using namespace std;int main()
{cout << "PATH: " << getenv("PATH") << endl;//cout << "MYPATH: " << getenv("MYPATH") << endl;cout << "hello world1" << endl;cout << "hello world2" << endl;cout << "hello world3" << endl;cout << "hello world4" << endl;cout << "hello world5" << endl;return 0;
}
如上图,当直接使用execle函数时,进程替换成功了但是运行时进程崩溃了,可以看到此时进程中找不到PATH这个环境变量,那么MYPATH(这是execle函数的传参)呢?
因此可以得出结论:带e
的exec函数会添加环境变量给目标进程,执行的是覆盖式的。
//这里是使用execl替换代码片段
if(id == 0){//childprintf("这是子进程pid:%d,ppid:%d\n", getpid(), getppid());execl("./mytest", "mytest", NULL);exit(1);}
使用execl函数PATH环境变量可以正常打印。
如果想要将系统的环境变量保存并且也可以使用用户自己的环境变量,可以使用export指令添加环境变量:
//命令行
export MYPATH="HELLO WORLD"
2.4 execvp
int execvp(const char *file, char *const argv[]);
函数说明:
file
:目标程序名;argv
:按照命令行参数格式统一将程序名和选项字符串放入数组中;- 命名带
p
表示会使用环境变量PATH。
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <sys/types.h>int main()
{printf("进程pid:%d\n", getpid());pid_t id = fork();if(id == 0){//childprintf("这是子进程pid:%d,ppid:%d\n", getpid(), getppid());char *const myenv[] = {(char*)"ls",(char*)"-a",(char*)"-l",NULL};execvp("ls", myenv);exit(1);}//parentprintf("这是父进程pid:%d,ppid:%d\n", getpid(), getppid());pid_t ret = waitpid(id, NULL, 0);if(ret > 0) printf("子进程回收成功,ret:%d\n", ret);return 0;
}
2.5 execve
与其他的exec函数不同的是:execve函数是系统调用接口。
int execve(const char *path, char *const argv[], char *const envp[]);
前面学习的那些exec函数基本都是对系统调用接口直接或者间接的封装,适应与不同的适用场景。
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <sys/types.h>int main()
{printf("进程pid:%d\n", getpid());pid_t id = fork();if(id == 0){//childprintf("这是子进程pid:%d,ppid:%d\n", getpid(), getppid());char *const myenv[] = {(char*)"ls",(char*)"-a",(char*)"-l",NULL};char *const env_[] = {(char*)"MYPATH=HELLOWORLD",NULL};execve("./mytest", myenv, env_);exit(1);}//parentprintf("这是父进程pid:%d,ppid:%d\n", getpid(), getppid());pid_t ret = waitpid(id, NULL, 0);if(ret > 0) printf("子进程回收成功,ret:%d\n", ret);return 0;
}
补充快捷键
批量化注释
ctrl + v
(按一次即可),从需要注释的第一行开始,然后使用HJKL上下左右键选中区域;如果vim被配置过,键盘上的箭头上下左右功能可能改变,因此建议使用HJKL。
- 切换大写输入
I
,输入//,再按下Esc就可以完成批量化注释。
批量化取消注释
-
ctrl + v
(按一次即可),从需要取消注释的第一行开始,然后使用HJKL上下左右键选中区域;
-
输入
d
就可以完成批量化取消注释。
相关文章:

[Linux]进程替换
🥁作者: 华丞臧. 📕专栏:【LINUX】 各位读者老爷如果觉得博主写的不错,请诸位多多支持(点赞收藏关注)。如果有错误的地方,欢迎在评论区指出。 推荐一款刷题网站 👉 LeetCode刷题网站 文…...

常见的锁策略面试题
你是怎么理解乐观锁和悲观锁的,具体怎么实现呢? 悲观锁认为多个线程访问同一个共享变量冲突的概率较大, 会在每次访问共享变量之前都去真正加锁 乐观锁认为多个线程访问同一个共享变量冲突的概率不大. 并不会真的加锁, 而是直接尝试访问数据. 在访问的…...

设计师一定要知道这几个网站,解决你80%的设计素材。
本期推荐一波设计师必备的设计素材网站,设计党赶紧马住!能解决你日常设计中80%的素材。 1、菜鸟图库 菜鸟图库-免费设计素材下载 这是一个为新手设计师提供免费素材的设计网站,站内有超多平面模板、海报、UI设计、电商设计等相关素材&#x…...

QT基础入门
学习视频:QT开发概述_哔哩哔哩_bilibili 1.QT开发概述 1.什么是QT QT是一个1991年由Qt Company开发的跨平台C图形用户界面应用程序开发框架。它既可以开发GUI程序,也可用于开发非GUI程序,比如控制台工具和服务器。Qt是面向对象的框架&#…...

高数不定积分72题解答
题目来源:这72道积分题目会积了,绝对是高高手 作者: 湖心亭看雪 第一题 原式∫15x3dx15∫15x3d(5x3)15ln(5x3)C\begin{aligned} \text{原式}&\int \frac{1}{5x3}dx \\ &\frac{1}{5} \int\frac{1}{5x3}d(5x3) \\ &\frac{1}{5} ln…...

基于北方苍鹰算法优化LSTM(NGO-LSTM)研究(Matlab代码实现)
💥💥💞💞欢迎来到本博客❤️❤️💥💥 🏆博主优势:🌞🌞🌞博客内容尽量做到思维缜密,逻辑清晰,为了方便读者。 ⛳️座右铭&a…...

Linux内核启动(理论,0.11版本)分段与分页
为什么要虚拟内存 我们知道,在之前上微机原理时,我们的程序是可以直接访问内存的,而且访问的是直接的物理内存,在实模式下,寄存器是16位的,数组总线(data bus)是16位的,…...

数据与C(字符串)
目录 一.概念引入 二.字符串(数组存储,必须以\0结尾) 三.错误示范 四.strlen()和sizeof()相对于字符串的不同 一.概念引入 “a”,a哪个是字符哪个又是字符串,嘿嘿不用猜了 我们在上一章中说过&#x…...

Python+Go实践(电商架构三)
文章目录服务发现集成consul负载均衡负载均衡算法实现配置中心nacos服务发现 我们之前的架构是通过ipport来调用的python的API,这样做的弊端是 如果新加一个服务,就要到某个服务改web(go)层的调用代码,配置IP/Port并发…...

基于 MySQL 排它锁实现分布式可重入锁解决方案
一、MySQL 排它锁和共享锁 在进行实验前,先来了解下MySQL 的排它锁和共享锁,在 MySQL 中的锁分为表锁和行锁,在行锁中锁又分成了排它锁和共享锁两种类型。 1. 排它锁 排他锁又称为写锁,简称X锁,是一种悲观锁&#x…...

【大数据】Hadoop-HA-Federation-3.3.1集群高可用联邦安装部署文档(建议收藏哦)
背景概述 单 NameNode 的架构使得 HDFS 在集群扩展性和性能上都有潜在的问题,当集群大到一定程度后,NameNode 进程使用的内存可能会达到上百 G,NameNode 成为了性能的瓶颈。因而提出了 namenode 水平扩展方案-- Federation。 Federation 中…...

【设计模式之美 设计原则与思想:面向对象】14 | 实战二(下):如何利用面向对象设计和编程开发接口鉴权功能?
在上一节课中,针对接口鉴权功能的开发,我们讲了如何进行面向对象分析(OOA),也就是需求分析。实际上,需求定义清楚之后,这个问题就已经解决了一大半,这也是为什么我花了那么多篇幅来讲…...

工作技术小结
2023/1/31 关于后端接口编写小结 1,了解小程序原型图流程和细节性的东西 2,数据库关联结构仔细分析,找到最容易查询的关键字段,标语表之间靠什么关联 2023/2/10 在web抓包过程中,如果要实现批量抓取,必须解…...

无重复字符的最长子串-力扣3-java
一、题目描述给定一个字符串 s ,请你找出其中不含有重复字符的 最长子串 的长度。示例 1:输入: s "abcabcbb"输出: 3 解释: 因为无重复字符的最长子串是 "abc",所以其长度为 3。示例 2:输入: s "bbbbb"输出: 1解释: 因为…...

java ssm高校教材管理平台 idea maven
设计并且实现一个基于JSP技术的高校教材管理平台的设计与实现。采用MYSQL为数据库开发平台,SSM框架,Tomcat网络信息服务作为应用服务器。高校教材管理平台的设计与实现的功能已基本实现,主要学生、教材管理、学习教材、教材入库、教材领取、缴…...

【Python学习笔记】25.Python3 输入和输出(1)
前言 在前面几个章节中,我们其实已经接触了 Python 的输入输出的功能。本章节我们将具体介绍 Python 的输入输出。 输出格式美化 Python两种输出值的方式: 表达式语句和 print() 函数。 第三种方式是使用文件对象的 write() 方法,标准输出文件可以用…...

C++复习笔记8
泛型编程:编写的是与类型无关的通用代码,是代码复用的一种手段,模板是泛型编程的基础。 1.函数模板:类型参数化,增加代码复用性。例如对于swap函数,不同类型之间进行交换都需要进行重载,但是函数…...

RabbitMQ入门
目录1. 搭建示例工程1.1. 创建工程1.2. 添加依赖2. 编写生产者3. 编写消费者4. 小结需求 官网: https://www.rabbitmq.com/ 需求:使用简单模式完成消息传递 步骤: ① 创建工程(生成者、消费者) ② 分别添加依赖 ③ 编…...

【计算机网络】Linux环境中的TCP网络编程
文章目录前言一、TCP Socket API1. socket2. bind3. listen4. accept5. connect二、封装TCPSocket三、服务端的实现1. 封装TCP通用服务器2. 封装任务对象3. 实现转换功能的服务器四、客户端的实现1. 封装TCP通用客户端2. 实现转换功能的客户端五、结果演示六、多进程版服务器七…...

idekCTF 2022 比赛复现
Readme 首先 []byte 是 go 语言里面的一个索引,比如: package mainimport "fmt"func main() {var str string "hello"var randomData []byte []byte(str)fmt.Println(randomData[0:]) //[104 101 108 108 111] }上面这串代码会从…...

jvm的类加载过程
加载 通过一个类的全限定名获取定义此类的二进制字节流将这个字节流所代表的静态存储结构转化为方法区的运行时数据结构在内存中生成一个代表这个类的java.lang.Class对象,作为方法区这个类的各种数据的访问入口链接 验证 验证内容的合法性准备 把方法区的静态变量初…...

VOC数据增强与调整大小
数据增强是针对数据集图像数量太少所采取的一种方法。 博主在实验过程中,使用自己的数据集时发现其数据量过少,只有280张,因此便想到使用数据增强的方式来获取更多的图像信息。对于图像数据,我们可以采用旋转等操作来获取更多的图…...

Linux 安装jenkins和jdk11
Linux 安装jenkins和jdk111. Install Jdk112. Jenkins Install2.1 Install Jenkins2.2 Start2.3 Error3.Awakening1.1 Big Data -- Postgres4. Awakening1. Install Jdk11 安装jdk11 sudo yum install fontconfig java-11-openjdk 2. Jenkins Install 2.1 Install Jenkins 下…...

Pandas——Series操作【建议收藏】
pandas——Series操作 作者:AOAIYI 创作不易,觉得文章不错或能帮助到你学习,可以点赞收藏评论哦 文章目录pandas——Series操作一、实验目的二、实验原理三、实验环境四、实验内容五、实验步骤1.创建Series2.从具体位置的Series中访问数据3.使…...

JUC并发编程Ⅰ -- Java中的线程
文章目录线程与进程并行与并发进程与线程应用应用之异步调用应用之提高效率线程的创建方法一:通过继承Thread类创建方法二:使用Runnable配合Thread方法三:使用FutureTask与Thread结合创建查看进程和线程的方法线程运行的原理栈与栈帧线程上下…...

基于vue-admin-element开发后台管理系统【技术点整理】
一、Vue点击跳转外部链接 点击重新打开一个页面窗口,不覆盖当前的页面 window.open(https://www.baidu.com,"_blank")"_blank" 新打开一个窗口"_self" 覆盖当前的窗口例如:导入用户模板下载 templateDownload() {wi…...

【C语言学习笔记】:通讯录管理系统
系统中需要实现的功能如下: ✿ 添加联系人:向通讯录中添加新人,信息包括(姓名、性别、年龄、联系电话、家庭住址)最多记录1000人 ✿ 显示联系人:显示通讯录中所有的联系人信息 ✿ 删除联系人:按…...

开关电源环路稳定性分析(10)——OPA和OTA型补偿器传递函数
大家好,这里是大话硬件。 在前面9讲的内容中将开关电源环路分析进行了梳理,我相信很多人即使都看完了,应该还是不会设计,而且还存在几个疑问。比如我随便举几个: 开关电源的带宽怎么设定?开关电源精度和什…...

2.11知识点整理(关于pycharm,python,pytorch,conda)
pycharm 设置anaconda环境: File -> Settings->选择左侧的project xxx再选择打开Project Interpreter页->选择add添加解释器->添加Anaconda中Python解释器(Anaconda安装目录下的python.exe) (选择existing environment ÿ…...

Linux服务器开发-2. Linux多进程开发
文章目录1. 进程概述1.1 程序概览1.2 进程概念1.3 单道、多道程序设计1.4 时间片1.5 并行与并发1.6 进程控制块(PCB)2. 进程的状态转换2.1 进程的状态2.2 进程相关命令查看进程实时显示进程动态杀死进程进程号和相关函数3. 进程的创建-fork函数3.1 进程创…...