Linux进程控制(2)
Linux进程控制(2)
📟作者主页:慢热的陕西人
🌴专栏链接:Linux
📣欢迎各位大佬👍点赞🔥关注🚓收藏,🍉留言
本博客主要内容讲解了进程等待收尾内容和进程的程序替换,以及进程程序替换的原理,进程程序替换的7个重要接口
文章目录
- Linux进程控制(2)
- 1.进程等待(续)
- 2.进程程序替换
- 2.1 程序替换是如何完成的---单线程版
- 2.2程序替换的原理
- 2.3引入多进程,使用所有程序替换的接口
- 熟悉所有的替换程序接口(7个)
1.进程等待(续)
我们稍微改造一下,之前进程等待的时候,父进程不要阻塞等待的代码,让父进程真正的去运行一些任务。
我们采用函数回调的方式,让父进程在等待子进程的时候也可以去运行自己的一些任务!
#include<stdio.h>
#include<string.h>
#include<unistd.h>
#include<sys/types.h>
#include<sys/wait.h>
#include<stdlib.h>#define TASK_NUM 10//预设一批任务
void sync_disk()
{printf("这是一个刷新数据的任务\n");
}void sync_log()
{printf("这是一个同步日志的任务\n");
}void net_send()
{printf("这是一个网络发送的任务\n");
} //保存相关的任务
typedef void (*func_t)(); //定义了一个函数指针类型
func_t orther_task[TASK_NUM] = {NULL}; //装载任务
int Load_Task(func_t fuc)
{int i = 0;for(; i < TASK_NUM; ++i){if(orther_task[i] == NULL) break;}if(TASK_NUM == i) return -1;else orther_task[i] = fuc;return 0;
}//初始化函数指针数组
void Init_Task()
{for(int i = 0; i < TASK_NUM; ++i) orther_task[i] = NULL;Load_Task(sync_disk); Load_Task(sync_log);Load_Task(net_send);
}void Run_Task()
{for(int i = 0; i < TASK_NUM; ++i){if(orther_task[i] == NULL) continue;else orther_task[i]();}
}int main()
{pid_t id = fork();if(id == 0){//子进程int cnt = 5;while(cnt){printf("我是子进程,我还活着呢,我还有%dS,我的pid:%d,我的ppid:%d\n", cnt--, getpid(), getppid());sleep(1);}exit(0);}Init_Task();while(1){int status = 0;pid_t ret_id = waitpid(id, &status, WNOHANG);// 夯住了if(ret_id < 0){printf("waitpid_error\n");}else if(ret_id == 0){Run_Task(); sleep(1);continue;}else{printf("我是父进程,我等待成功了,我的pid:%d,我的ppid:%d, ret_id: %d, child exit code: %d, child exit signal:%d\n",getpid(), getppid(), ret_id, (status >> 8)&0xFF, status & 0x7F);exit(0);}sleep(1); }return 0;
}
运行结果:

继续改进,我们之前获取进程退出码的时候是使用(status >> 8)& 0xFF的方式来进行获取的,那么实际上C库也给我们提供了两个宏来帮助我们获取进程的退出码:
status:
WIFEXITED(status): 若为正常终止子进程返回的状态,则为真。(查看进程是否是正常退出)
WEXITSTATUS(status): 若WIFEXITED非零,提取子进程退出码。(查看进程的退出码)
我们用这两个宏来优化一下我们等待成功,也就是子进程结束的时候的代码:
else { //等待成功 if(WIFEXITED(status)) { //正常退出 printf("wait success, child exit code :%d", WEXITSTATUS(status)); } else { //异常退出 printf("wait success, child exit signal :%d", status & 0x7F); } exit(0); }
正常退出:

异常退出:我们尝试在父进程等待的时候杀掉子进程:

2.进程程序替换
我们为什么需要创建子进程?为了让子进程帮我执行特定的任务;
①让子进程执行父进程的一部分代码;
②如果子进程指向一段全新的代码呢?这时候我们就需要进程的程序替换!
也是为什么需要进程的程序替换。
2.1 程序替换是如何完成的—单线程版
代码:
#include<stdio.h>
#include<stdlib.h>
#include<unistd.h> int main()
{ printf("begin......\n"); printf("begin......\n"); printf("begin......\n"); printf("begin......\n"); printf("begin......\n"); execl("/bin/ls", "ls", "-a", "-l", NULL); printf("end........\n"); printf("end........\n"); printf("end........\n"); printf("end........\n"); printf("end........\n"); return 0;
}
运行结果:
那么我们可以看到这个进程运行了开始的begin....,然后运行了ls,但是后面的end...却不见了。
这是因为发生了进程的程序的替换,简要的原理就是,操作系统通过提供的地址/bin/ls从磁盘中拿出ls然后选到指定的文件ls,在输入一些参数-a, -l,以NULL表示结束。

2.2程序替换的原理
操作系统不动当前进程的内核数据结构,而是去磁盘内部拿到要替换的数据和代码,将我们当前进程的数据和代码替换掉。
当进程调用一种exec函数时,该进程的用户空间代码和数据完全被新程序替换,从新程序的启动例程开始执行。
所以进程的程序替换,是没有创建新的进程的。

①站在进程的角度
操作系统,帮我们在磁盘内部找到我们要替换的数据和代码,替换进程的数据和代码。
②站在程序的角度
这个程序被加载了,所以我们也称execl这类函数为加载器。
我们在回到一开始,为什么我们程序后面的end.....却没有打印出来?

原因是当我们加载程序替换的时候,新的数据和代码就进入了进程,当前进程后续没有没运行的代码就成为了老代码,直接被替换了,没有机会执行了。
所以进程的程序替换是整体替换,而不是局部替换。
所以我们接下来引入多进程的程序替换
2.3引入多进程,使用所有程序替换的接口
例程:
#include<stdio.h>
#include<stdlib.h>
#include<unistd.h>
#include<sys/types.h>
#include<sys/wait.h> int main()
{ pid_t id = fork(); if(id == 0) { //child printf("我是子进程:%d", getpid()); execl("/bin/ls", "ls", "-a", "-l", NULL); } sleep(2); //father waitpid(id, NULL, 0); printf("我是父进程:%d\n", getpid()); return 0;
}
运行结果:
我们看到execl之后父进程的内容也被运行了
因为进程的独立性,所以进程的程序替换只会影响调用程序替换的子进程。
子进程加载新程序的时候,是需要进行程序替换的,发生写时拷贝(子进程执行的可是全新的代码啊,新的代码,所以代码区也可以发生写时拷贝)

那么对于execl这类加载函数,它有没有返回值呢?
答案是分情况:
①替换成功是没有返回值的
②替换失败是有返回值的
-1原因是:假设替换替换成功了,那么我们该进程中的代码和数据,都会被替换成新的代码和数据,那么我们之前的返回值也就不复存在了,并且我们也不需要返回值了。
替换失败的情况下,进程之前的代码和数据还是存在的,那么我们的返回值也是存在的,从而可以返回。
所以我们调用了加载函数之后我们不用去判断它是否加载成功,只需要在函数后面返回异常即可。
失败的例程:
#include<stdio.h>
#include<stdlib.h>
#include<unistd.h>
#include<sys/types.h>
#include<sys/wait.h> int main()
{ pid_t id = fork(); if(id == 0) { //child printf("我是子进程:%d\n", getpid()); execl("/bin/lsss", "lsss", "-a", "-l", NULL); exit(1); } sleep(2); //father int status; waitpid(id,&status, 0); printf("我是父进程:%d, child exit signal:%d\n", getpid(), WEXITSTATUS(status)); return 0;
}

熟悉所有的替换程序接口(7个)
①int execl(const char *path, const char *arg, ...);
例如:
execl("/bin/ls", "ls", "-a", "-l", NULL);
l代表list;
path:路径,也就是告诉操作系统,你要用来替换的程序,在磁盘的哪个路径,例程里的/bin/ls;
arg:文件,是你要用来替换的文件名,例程里面的ls。其中的
...是可变参数列表,例程中的那些参数-a , -l。其中最后我们要特别的输入一个
NULL参数,告诉函数参数结束了;
②int execv(const char *path, char *const argv[]);
相比较第一个函数的差别就是,第一个函数要求我们一个一个的去传参数,而第二个要求我们直接用数组的形式去传,但是原则也是一样的,数组的最后一个元素也要置成
NULL。我们在子进程内部调用的时候是这样的:
先创建一个数组,将这些参数一个一个放进去,再传给
execv即可那么其实
v就是vector就是数组的意思;char* const argv[] = { "ls", "-a", "-l", NULL }; // execl("/bin/lsss", "lsss", "-a", "-l", NULL); execv("/bin/ls", argv); exit(1); }运行结果:
③int execlp(const char *file, const char *arg, ...);
p:当我们指定执行程序的时候,只需要指定程序名即可,系统会自动在**环境变量PATH**中查找。也就是说我们要用于替换的程序,必须在环境变量
PATH中,或者说我们在环境变量PATH中设置过;execlp("ls", "ls", "-a", "-l", NULL);
那么其中的两个
ls是不一样的,一个是文件名,一个是参数。
④int execvp(const char *file, char *const argv[]);
v:表示参数以数组的形式传入;
p:表示在环境变量PATH中去寻找用于替换的文件;char* const argv[] = { "ls", "-a", "-l", NULL }; execvp("ls", argv);运行结果:
⑤int execle(const char *path, const char *arg, ..., char * const envp[]);
envp[]:叫做自定义环境变量,当我们不想使用系统默认的环境变量的时候,这个时候我们就传递一个envp比如我们现在要让我们的调用
exec目录下的ortherproc来替换myproc的子进程的后续代码
先用
execl尝试一下:execl("./exec/ortherproc", "ortherproc", NULL);运行结果:
换成的动态的效果再看看:
下来我们尝试用
execle来实现一下:
proc.cchar* const envp[] = { "MYENVP=UCanCMe!", NULL }; execle("./exec/ortherproc", "ortherproc",envp);
ortherproc.ccfor(int i = 0; i < 5; ++i) { cout << "我是另一个程序,我的PID是 :" << getpid() << endl; cout << "MYENVP: " << (getenv("MYENVP")==NULL ? "NULL" : getenv("MYENVP")) << endl; cout << "PATH: " << (getenv("PATH") == NULL ? "NULL" : getenv("PATH")) << endl; sleep(1); }运行结果:
我们看到自定环境变量打印出来了,但是操作系统内部的环境变量却不见了,所以我们可以得到一个结论:
自定义环境变量覆盖了,默认的环境变量;
我们传默认的环境变量试试:
extern char ** environ;execle("./exec/ortherproc", "ortherproc",NULL , environ);运行结果:
那么如果我们两个都要呢,那么有一个接口
putenv给我们提供了一个将自定义环境变量追加到进程的默认环境变量的方法:接下来我们尝试一下putenv("MYENVP=UCanCMe"); execle("./exec/ortherproc", "ortherproc", NULL, environ);运行结果:
插播一段:
我们知道环境变量具有全局属性,可以被子进程继承下去,那么操作系统是怎么办到的?
只需要用
execle的最后一个参数传过去即可!那么我们是不是不需要
putenv也能实现两个都能被子进程读取到呢?我们直接把自定义的环境变量
export到bash中试试:[mi@lavm-5wklnbmaja lesson6]$ export MYENVP=UCanCMe [mi@lavm-5wklnbmaja lesson6]$ echo $MYENVP UCanCMe运行结果:
我们发现是可行的,自定义环境变量-----> bash ----->父进程------>子进程
⑥int execvpe(const char *file, char *const argv[], char *const envp[]);
p:不需要指定路径,只要在环境变量内部即可;
v:参数以数组的形式传入;
e:环境变量数组传入;使用方法都与上面的类似。
⑦int execve(const char *filename, char *const argv[], char *const envp[]);
这个接口也不用过多介绍了,使用方法都是一样的。
那么我们需要注意的是,在linux的man手册中将区域六个接口都放在了
3号手册,唯独这个却放在了2号手册。其实操作系统只给我们提供了一个程序替换的接口
execve,剩下的几个接口都是由这个接口封装出来的。
并且我们程序替换的时候不仅可以替换C语言的,甚至其他的语言都可以替换,我上面的例子也做到了用C++替换,因为这些代码都是交给操作系统来处理的而不是编译器,所以不论是什么语言都是可以替换的!
到这本篇博客的内容就到此结束了。
如果觉得本篇博客内容对你有所帮助的话,可以点赞,收藏,顺便关注一下!
如果文章内容有错误,欢迎在评论区指正

相关文章:
Linux进程控制(2)
Linux进程控制(2) 📟作者主页:慢热的陕西人 🌴专栏链接:Linux 📣欢迎各位大佬👍点赞🔥关注🚓收藏,🍉留言 本博客主要内容讲解了进程等待收尾内容和进程的程序…...
Android Glide transform旋转rotate圆图CircleCrop,Kotlin
Android Glide transform旋转rotate圆图CircleCrop,Kotlin import android.graphics.Bitmap import android.os.Bundle import android.util.Log import android.widget.ImageView import androidx.appcompat.app.AppCompatActivity import com.bumptech.glide.load…...
如何让群晖Audio Station公开共享的本地音频公网可访问?
文章目录 1. 本教程使用环境:2. 制作音频分享链接3. 制作永久固定音频分享链接: 之前文章我详细介绍了如何在公网环境下使用pc和移动端访问群晖Audio Station: 公网访问群晖audiostation听歌 - cpolar 极点云 群晖套件不仅能读写本地文件&a…...
生态环境领域基于R语言piecewiseSEM结构方程模型
结构方程模型(Sructural Equation Modeling,SEM)可分析系统内变量间的相互关系,并通过图形化方式清晰展示系统中多变量因果关系网,具有强大的数据分析功能和广泛的适用性,是近年来生态、进化、环境、地学、…...
spring boot+netty 搭建MQTT broken
一、项目结构 二、安装依赖 <!-- netty包 --><dependency><groupId>io.netty</groupId><artifactId>netty-all</artifactId><version>4.1.75.Final</version></dependency><!-- 常用JSON工具包 --><…...
从零开始搭建React+TypeScript+webpack开发环境-使用iconfont构建图标库
创建iconfont项目 进入iconfont官网,完成注册流程,即可创建项目。 无法访问iconfont可尝试将电脑dns改为阿里云镜像223.5.5.5和223.6.6.6 添加图标 在图标库里选择图标,加入购物车 将图标添加到之前创建的项目中 生成代码 将代码配置到项目…...
微服务之初始微服务
文章目录 一、服务架构演变1.单体架构2.分布式架构 二、认识微服务三、总结四、微服务技术对比五、SpringCloud注意 一、服务架构演变 1.单体架构 单体架构:将业务的所有功能集中在一个项目中开发,打成一个包部署。 优点: 架构简单部署成本…...
大口径智能水表支持最高水流量是多少?
随着科技的不断发展,我国城市化进程的加快,水资源管理日益受到重视。作为一种先进的用水计量设备,大口径智能水表凭借其高精度、低误差、远程抄表等优点,在市场上备受青睐。那么接下来,小编就来为大家详细的介绍一下大…...
在Spring Boot中使用MyBatis访问数据库
MyBatis,这个对各位使用Java开发的开发者来说还是蛮重要的,我相信诸位在企业开发项目的时候,大多数采用的是Mybatis。使用MyBatis帮助我们解决各种问题,实际上这篇文章,基本上默认为可以跳过的一篇,但是为了…...
懒羊羊闲话2
前言: 笔者谈不上是某个领域的高手,也不是大厂的某个神秘高手,一直游离于小型公司,写下这篇文章献给那些无法接触到好的学习环境,苦恼自己原地踏步的coder。 1、如何快速熟悉某个行业 作为一个编码多年的程序员&#…...
多路转接(上)——select
目录 一、select接口 1.认识select系统调用 2.对各个参数的认识 二、编写select服务器 1.两个工具类 2.网络套接字封装 3.服务器类编写 4.源文件编写 5.运行 一、select接口 1.认识select系统调用 int select(int nfds, fd_set readfds, fd_set writefds, fd_set ex…...
基于SSM的图书管理借阅系统设计与实现
末尾获取源码 开发语言:Java Java开发工具:JDK1.8 后端框架:SSM 前端:采用JSP技术开发 数据库:MySQL5.7和Navicat管理工具结合 服务器:Tomcat8.5 开发软件:IDEA / Eclipse 是否Maven项目&#x…...
Python的内存优化
在Python中,内存管理和优化是一个复杂的话题,因为它涉及到Python解释器的内部机制,特别是Python的垃圾收集和内存分配策略。Python通过自动垃圾收集机制管理内存,主要包括引用计数和标记-清除算法。 Python内存管理机制ÿ…...
蓝桥杯-回文日期[Java]
目录: 学习目标: 学习内容: 学习时间: 题目: 题目描述: 输入描述: 输出描述: 输入输出样例: 示例 1: 运行限制: 题解: 思路: 学习目标: 刷蓝桥杯题库日记 学习内容: 编号498题目回文日期难度…...
acwing算法基础之搜索与图论--树与图的遍历
目录 1 基础知识2 模板3 工程化 1 基础知识 树和图的存储:邻接矩阵、邻接表。 树和图的遍历:dfs、bfs。 2 模板 树是一种特殊的图(即,无环连通图),与图的存储方式相同。 对于无向图中的边ab,…...
前端uniapp请求真是案例(带源码)
目录 案例一案例二最后 案例一 <template><view class"box"><!-- <view class"title-back" click"backPrivious"><</view> --><!-- <view class"title-back" click"backPrivious"…...
MySQL -- mysql connect
MySQL – mysql connect 文章目录 MySQL -- mysql connect一、Connector/C 使用1.环境安装2.尝试链接mysql client 二、MySQL接口1.初始化2.链接数据库3.下发mysql命令4.获取执行结果5.关闭mysql链接6.在C语言中连接MySQL 三、MySQL图形化界面推荐 使用C接口库来进行连接 一、…...
如何用AI帮你下载安卓源码
以Android 11源码下载流程图如下所示: 1. 安装Git和Repo工具 2. 创建一个工作目录 3. 初始化仓库并下载源码 4. 切换到指定的分支 5. 编译源码 具体步骤如下: 安装Git和Repo工具:在Linux或Mac上,可以通过终端运行以下命令安装Gi…...
第三章:人工智能深度学习教程-基础神经网络(第三节-Tensorflow 中的多层感知器学习)
在本文中,我们将了解多层感知器的概念及其使用 TensorFlow 库在 Python 中的实现。 多层感知器 多层感知也称为MLP。它是完全连接的密集层,可将任何输入维度转换为所需的维度。多层感知是具有多个层的神经网络。为了创建神经网络,我们将神…...
Python的版本如何查询?
要查询Python的版本,可以使用以下方法之一: 1.在命令行中使用python --version命令。这会显示安装在计算机上的Python解释器的版本号。 # Author : 小红牛 # 微信公众号:wdPython2.在Python脚本中使用import sys语句,然后打印sy…...
为什么需要建设工程项目管理?工程项目管理有哪些亮点功能?
在建筑行业,项目管理的重要性不言而喻。随着工程规模的扩大、技术复杂度的提升,传统的管理模式已经难以满足现代工程的需求。过去,许多企业依赖手工记录、口头沟通和分散的信息管理,导致效率低下、成本失控、风险频发。例如&#…...
c++ 面试题(1)-----深度优先搜索(DFS)实现
操作系统:ubuntu22.04 IDE:Visual Studio Code 编程语言:C11 题目描述 地上有一个 m 行 n 列的方格,从坐标 [0,0] 起始。一个机器人可以从某一格移动到上下左右四个格子,但不能进入行坐标和列坐标的数位之和大于 k 的格子。 例…...
【AI学习】三、AI算法中的向量
在人工智能(AI)算法中,向量(Vector)是一种将现实世界中的数据(如图像、文本、音频等)转化为计算机可处理的数值型特征表示的工具。它是连接人类认知(如语义、视觉特征)与…...
uniapp微信小程序视频实时流+pc端预览方案
方案类型技术实现是否免费优点缺点适用场景延迟范围开发复杂度WebSocket图片帧定时拍照Base64传输✅ 完全免费无需服务器 纯前端实现高延迟高流量 帧率极低个人demo测试 超低频监控500ms-2s⭐⭐RTMP推流TRTC/即构SDK推流❌ 付费方案 (部分有免费额度&#x…...
深度学习习题2
1.如果增加神经网络的宽度,精确度会增加到一个特定阈值后,便开始降低。造成这一现象的可能原因是什么? A、即使增加卷积核的数量,只有少部分的核会被用作预测 B、当卷积核数量增加时,神经网络的预测能力会降低 C、当卷…...
html css js网页制作成品——HTML+CSS榴莲商城网页设计(4页)附源码
目录 一、👨🎓网站题目 二、✍️网站描述 三、📚网站介绍 四、🌐网站效果 五、🪓 代码实现 🧱HTML 六、🥇 如何让学习不再盲目 七、🎁更多干货 一、👨…...
技术栈RabbitMq的介绍和使用
目录 1. 什么是消息队列?2. 消息队列的优点3. RabbitMQ 消息队列概述4. RabbitMQ 安装5. Exchange 四种类型5.1 direct 精准匹配5.2 fanout 广播5.3 topic 正则匹配 6. RabbitMQ 队列模式6.1 简单队列模式6.2 工作队列模式6.3 发布/订阅模式6.4 路由模式6.5 主题模式…...
JVM虚拟机:内存结构、垃圾回收、性能优化
1、JVM虚拟机的简介 Java 虚拟机(Java Virtual Machine 简称:JVM)是运行所有 Java 程序的抽象计算机,是 Java 语言的运行环境,实现了 Java 程序的跨平台特性。JVM 屏蔽了与具体操作系统平台相关的信息,使得 Java 程序只需生成在 JVM 上运行的目标代码(字节码),就可以…...
C#学习第29天:表达式树(Expression Trees)
目录 什么是表达式树? 核心概念 1.表达式树的构建 2. 表达式树与Lambda表达式 3.解析和访问表达式树 4.动态条件查询 表达式树的优势 1.动态构建查询 2.LINQ 提供程序支持: 3.性能优化 4.元数据处理 5.代码转换和重写 适用场景 代码复杂性…...
基于Java+VUE+MariaDB实现(Web)仿小米商城
仿小米商城 环境安装 nodejs maven JDK11 运行 mvn clean install -DskipTestscd adminmvn spring-boot:runcd ../webmvn spring-boot:runcd ../xiaomi-store-admin-vuenpm installnpm run servecd ../xiaomi-store-vuenpm installnpm run serve 注意:运行前…...











