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.c
char* const envp[] = { "MYENVP=UCanCMe!", NULL }; execle("./exec/ortherproc", "ortherproc",envp);
ortherproc.cc
for(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…...

突破不可导策略的训练难题:零阶优化与强化学习的深度嵌合
强化学习(Reinforcement Learning, RL)是工业领域智能控制的重要方法。它的基本原理是将最优控制问题建模为马尔可夫决策过程,然后使用强化学习的Actor-Critic机制(中文译作“知行互动”机制),逐步迭代求解…...

盘古信息PCB行业解决方案:以全域场景重构,激活智造新未来
一、破局:PCB行业的时代之问 在数字经济蓬勃发展的浪潮中,PCB(印制电路板)作为 “电子产品之母”,其重要性愈发凸显。随着 5G、人工智能等新兴技术的加速渗透,PCB行业面临着前所未有的挑战与机遇。产品迭代…...

MongoDB学习和应用(高效的非关系型数据库)
一丶 MongoDB简介 对于社交类软件的功能,我们需要对它的功能特点进行分析: 数据量会随着用户数增大而增大读多写少价值较低非好友看不到其动态信息地理位置的查询… 针对以上特点进行分析各大存储工具: mysql:关系型数据库&am…...

MFC内存泄露
1、泄露代码示例 void X::SetApplicationBtn() {CMFCRibbonApplicationButton* pBtn GetApplicationButton();// 获取 Ribbon Bar 指针// 创建自定义按钮CCustomRibbonAppButton* pCustomButton new CCustomRibbonAppButton();pCustomButton->SetImage(IDB_BITMAP_Jdp26)…...
Go 语言接口详解
Go 语言接口详解 核心概念 接口定义 在 Go 语言中,接口是一种抽象类型,它定义了一组方法的集合: // 定义接口 type Shape interface {Area() float64Perimeter() float64 } 接口实现 Go 接口的实现是隐式的: // 矩形结构体…...

定时器任务——若依源码分析
分析util包下面的工具类schedule utils: ScheduleUtils 是若依中用于与 Quartz 框架交互的工具类,封装了定时任务的 创建、更新、暂停、删除等核心逻辑。 createScheduleJob createScheduleJob 用于将任务注册到 Quartz,先构建任务的 JobD…...
测试markdown--肇兴
day1: 1、去程:7:04 --11:32高铁 高铁右转上售票大厅2楼,穿过候车厅下一楼,上大巴车 ¥10/人 **2、到达:**12点多到达寨子,买门票,美团/抖音:¥78人 3、中饭&a…...

《基于Apache Flink的流处理》笔记
思维导图 1-3 章 4-7章 8-11 章 参考资料 源码: https://github.com/streaming-with-flink 博客 https://flink.apache.org/bloghttps://www.ververica.com/blog 聚会及会议 https://flink-forward.orghttps://www.meetup.com/topics/apache-flink https://n…...

深度学习习题2
1.如果增加神经网络的宽度,精确度会增加到一个特定阈值后,便开始降低。造成这一现象的可能原因是什么? A、即使增加卷积核的数量,只有少部分的核会被用作预测 B、当卷积核数量增加时,神经网络的预测能力会降低 C、当卷…...
6️⃣Go 语言中的哈希、加密与序列化:通往区块链世界的钥匙
Go 语言中的哈希、加密与序列化:通往区块链世界的钥匙 一、前言:离区块链还有多远? 区块链听起来可能遥不可及,似乎是只有密码学专家和资深工程师才能涉足的领域。但事实上,构建一个区块链的核心并不复杂,尤其当你已经掌握了一门系统编程语言,比如 Go。 要真正理解区…...