Linux(9)——进程(控制篇——下)

目录

三、进程等待

1)进程等待的必要性

2)获取子进程的status

3)进程的等待方法

wait方法

waitpid方法 

多进程创建以及等待的代码模型  

非阻塞的轮训检测

四、进程程序替换

1)替换原理

2)替换函数

3)函数解释 

4)命名理解 


三、进程等待

1)进程等待的必要性

  1. 之前提过子进程退出,父进程如果不读取子进程的退出信息,就可能造成“僵尸进程”的问题,从而造成内存泄漏的问题。
  2. 再者,一旦子进程进入了僵尸状态,那就连kill -9都杀不亖他,因为没有谁能够杀亖一个死去的进程。
  3. 最后,父进程创建子进程是要获取子进程的完成任务的情况的。
  4. 父进程需要通过等待的方式来回收子进程的资源,获取子进程的退出信息。

2)获取子进程的status

下面进程等待使用的两个方法wait方法和waitpid方法都有一个status参数,这是一个输出型参数(输出型参数是函数中用于返回结果或修改调用者变量的参数,通常通过引用或指针实现。如void func(int *output)。),由操作系统进行填写。

如果向status中传递的是NULL,那就表示用户不关心子进程的退出状态。否则,操作系统会根据该参数,将子进程的退出信息反馈给父进程。

status不能简单的当作整形来看待,可以当作位图来看待,具体细节如下图(只研究status低16比特位):

我们从图中可见,status的低16比特位中,高8位表示进程的退出状态,即退出码。当进程被信息杀亖时,则低7位表示终止信息,第8位时core dump标志。

我们可以通一系列的位操作来得出进程的退出码和退出信号。

exitcCode = (status >> 8) & 0xFF; //退出码exitSignal = status & 0x7F;

对于这两个操作,系统提供了两宏来获取退出码以及退出信号。分别是:

  • WIFEXITED(status):用于查看是否是正常退出,本质是检查是否收到信号。
  • WEXITSTATUS(status):用于获取进程的退出码。
exitNormal = WIFEXITED(status); //是否正常退出exitCode = WEXITSTATUS(status); //获取退出码

敲黑板:

当一个进程是非正常退出的时候,那么该进程的退出码将毫无意义。

3)进程的等待方法

wait方法

函数类型:pid_t wait(int* status);

返回值:成功返回被等待进程pid,失败返回-1。

参数:输出型参数,获取子进程退出状态,不关心则可以设置成为NULL 

作用:等待任意子进程 

创建子进程后,父进程使用wait方法等待子进程,直到子进程的退出信息被读取,我们可以写个代码验证一下:

#include <stdio.h>    
#include <stdlib.h>    
#include <unistd.h>    
#include <sys/wait.h>    
#include <sys/types.h>    int main()    
{    pid_t id = fork();    if(id == 0){ //子进程    int count = 10;    while(count--)    {    printf("我是子进程,PID:%d, PPID:%d\n", getpid(), getppid());    sleep(1);    }    exit(0);    }    //父进程                                                                                                                                                               int status = 0;    pid_t ret = wait(&status);    if(ret > 0){    printf("等待成功...\n");    if(WIFEXITED(status)){    printf("退出码:%d\n", WEXITSTATUS(status));    }    }    sleep(3);    return 0;    
}

然后我们可以在开一个会话用来监控进程的状态:

while :; do ps axj | head -1 && ps axj | grep test | grep -v grep;echo "============================================================";sleep 1;done

 在下面这图中我们可以看到,当子进程退出,父进程读取到了子进程的退出信息时,子进程就不会变成僵尸状态了。

waitpid方法 

函数原型:pid_t waitpid(pid_t pid, int *status, int options);

返回值:

  1. 当正常返回的时候waitpid返回收集到的子进程的进程ID;
  2. 如果设置了选项WNOHANG(option),而调用中waitpid发现没有已退出的子进程可收集,则返回0;
  3. 如果调用中出错,则返回-1,这时errno会被设置成相应的值以指示错误所在; 

参数:

  1. pid:当pid=-1,等待任意一个子进程,与wait等效。当pid>0.等待其进程ID与pid相等的子进程。
  2. status:输出型参数,用来获取子进程的退出状态,不关心可以设置成NULL。
  3. options:默认为0,表示阻塞等待;当设置为WNOHANG时,若pid指定的子进程没有结束,则waitpid()函数返回0,不予以等待。若正常结束,则返回该子进程的ID。

返回值:等待任意子进程(可以指定)退出 

我们可以写个代码来验证一下,创建子进程后,父进程可以使用waitpid函数一直等待子进程,直到子进程退出后读取子进程的退出信息。

#include <stdio.h>    
#include <stdlib.h>    
#include <unistd.h>    
#include <sys/wait.h>    
#include <sys/types.h>    int main()    
{    pid_t id = fork();    if(id == 0){ //子进程    int count = 10;    while(count--)    {    printf("我是子进程,PID:%d, PPID:%d\n", getpid(), getppid());    sleep(1);    }    exit(0);    }    //父进程    int status = 0;    //pid_t ret = wait(&status);    pid_t ret = waitpid(id, &status, 0);    if(ret >= 0){    printf("等待成功...\n");    if(WIFEXITED(status)){    printf("退出码:%d\n", WEXITSTATUS(status));    }else{    printf("被信号杀?:%d\n",status & 0x7F);    }                                                                            }    sleep(3);    return 0;    
}

在父进程运行过程中,我们可以使用kill -9命令来将子进程杀亖,这个时候父进程也能成功等待子进程。

敲黑板:

被信号杀亖的进程的退出码是没有意义的。

多进程创建以及等待的代码模型  

上面演示的都是父进程的创建以及等待一个子进程,那么接下来我们可以同时创建多个子进程,然后让父进程进程依次等待子进程退出。

下面我们可以同时创建10个子进程,将子进程的pid放到一个id数组中,并将这10个子进程的退出时的退出码设置为该子进程pid对应数组中的下标,之后父进程使用waitpid等待这10个子进程。

#include <stdio.h>    
#include <stdlib.h>    
#include <unistd.h>    
#include <sys/types.h>    
#include <sys/wait.h>    int main()    
{    pid_t ids[10];    for(int i = 0; i < 10; i++){    pid_t id = fork();    if(id == 0){    printf("子进程创建成功:PID:%d\n", getpid());    sleep(3);    exit(i);    }    ids[i] = id;    }    for(int i = 0; i < 10; i++){    int status = 0;    pid_t ret = waitpid(ids[i], &status, 0);    if(ret >= 0){    printf("等待子进程成功:PID:%d\n", ids[i]);    if(WIFEXITED(status)){    printf("退出码:%d\n", WEXITSTATUS(status));    }    else{    printf("被信号杀亖:%d\n", status & 0x7F);    }    }    }                                                                                                                                                                               return 0;    
} 

运行完代码,我发现父进程同时创建了了多个子进程,当子进程退出后,父进程再以此读取这些子进程的退出信息。 

杀亖进程,父进程依然可以获取子进程的退出信息。

非阻塞的轮训检测

上面的方案,其实是有缺点的,那就是在父进程等待子进程的时候,父进程什么也干不了,这样的等待就是阻塞等待。

而实际上,我们是可以让我们的父进程在等待子进程退出的过程中做一些自己的事情的,这样的等待就是非阻塞等待了。

其实想要实现非阻塞等待也很简单,我们在上面说waitpid时就提过了,在想这个函数第三个参数传入WNOHANG时就可以使得在等待子进程时,如果waitpid函数直接返回0,就不予等待,而等待的子进程若是正常结束,就返回该子进程的pid。

比如,父进程可以选择地调用wait函数,若是等待的子进程还没有退出,那么父进程就可以先去做一些其他的事情了,过一段时间在调用waitpid函数读取子进程的退出信息。

#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){    int count = 3;    while(count--){    printf("子进程在运行:PID:%d, PPID:%d\n", getpid(), getppid());    sleep(3);    }    exit(0);    }    while(1){    int status = 0;    pid_t ret = waitpid(id, &status, WNOHANG);    if(ret > 0){    printf("等待子进程成功。\n");    printf("退出码是:%d\n", WEXITSTATUS(status));    break;    }    else if(ret == 0){    printf("父进程做了其他事情。\n");    sleep(1);    }    else{    printf("等待错误。\n");    break;    }    }    return 0;                                                                          
}

代码的运行结果就是,父进程每隔一段时间就去看看子进程是否退出,如果没就去做自己的事情,知道子进程退出后读取子进程退出的信息。

 

四、进程程序替换

1)替换原理

用fork创建子进程后执行的是和父进程相同的程序(但有可能执行不同的代码分支),子进程往往要调用一种exec函数以执行另一个程序。当进程调用一种exec函数时,该进程的用户空间代码和数据完全被新程序替换,从新程序的启动例程开始执行。调用exec并不创建新进程,所以调用exec前后该进程的id并未改变

 回答两个关于进程程序替换的文题:

问题一:

当进行进程替换时,有没有创建新的进程呢?

进程程序替换之后,该进程的PCB、进程的地址空间以及页表等数据结构都没有发生改变,而只是对原来在物理内存上的代码和数据进行了替换,所有根本没有创建新的进程,而且进程的程序替换前后其pid也是没有改变的。

问题二:

子进程进行进程的程序替换时,是否会影响父进程的代码和数据呢?

不会影响,一开始子进程被父进程创建时代码和数据是和父进程共享的,但是一旦子进程要进行程序替换操作,就意味着子进程需要对代码和数据进行写入操作了,这时就需要对父进程的代码和数据进行拷贝了(写时拷贝),从这里开始子进程和父进程的代码和数据就分离了,所有子进程进行进程的程序替换时不会影响父进程的代码和数据。

2)替换函数

这里的替换函数都是以exec开头的,所以称之为exec函数,总共有六种:

1️⃣int execl(const char *path,const char *arg,...); 

相关参数的说明:第一参数是要执行程序的路径,第二个参数是可变参数列表,表示具体如何执行这个程序,同时以NULL结尾。

写一个执行ls程序:

execl("/usr/bin/ls", "ls", "-l", NULL);

2️⃣int execlp(const char *file, const char *arg,...); 

相关参数的说明:第一个参数是要执行程序的名字,第二个参数是可变参数列表,表示你要如何执行,也是以NULL结尾。

写一个执行ls程序:

execle("ls", "ls", "-l", NULL); 

3️⃣int execle(const char *path, const char *arg,..., char *const envp[]); 

相关参数的说明:第一个参数是要执行的程序的路径,第二个参数是可变参数列表,表示如何执行这个程序,也是以NULL结尾。第三个参数是自己设置的环境变量。

这里我们可以设置MYVAL的环境变量,在test中可以使用这个环境变量了。

char* myenp[] = {"MYVAL=2025", NULL};
execle("./test", "test", NULL, myenvp);

4️⃣int execv(const char *path, char *const argv]); 

相关参数的说明:第一个参数是要执行程序的路径,第二个参数是一个指针数组,数组中的就是你要如何执行这个程序,数组也是以NULL结尾的。

写一个ls程序:

char* myargv[] = {"ls", "-l", NULL);
execv("/usr/bin/ls", myargv);

5️⃣int execvp(const char *file,char *const argv[]);

相关参数的说明:第一个是要执行的程序的名字,第二个参数是一个指针数组, 数组中的就是你要如何执行这个程序,数组也是以NULL结尾的。

写一个ls程序:

char* myargv[] = {"ls", "-l", NULL};
execvp("ls", myargv);

6️⃣int execve(const char *path,  char *const argvl],  char *const envp[]);

相关参数的说明: 第一个参数是要执行程序的路径,第二个参数是一个指针数组,数组中的就是你要如何执行这个程序,数组也是以NULL结尾的,第三个参数是自己设置的环境变量。

这里我们可以设置MYVAL的环境变量,在test中可以使用这个环境变量了。

char* myargv[] = {"mycmd", NULL};
char* myenvp[] = {"MYVAL=2025", NULL};
execve("./test", test, myenvp);

3)函数解释 

  1. 这些函数如果调用成功则加载新的程序从启动代码开始执行,不再返回。
  2. 如果调用出错则返回-1

所以exec函数只有出错的返回值而没有成功的返回值。

4)命名理解 

其实我们仔细观察就会发现这六个函数都是有规律的,只要掌握了规律是很好记的。

  1. l(list):表示参数采用列表的形式
  2. v(vector):参数用数组的形式
  3. p(path):有p自动搜索环境变量PATH
  4. e(env):表示自己维护环境变量
函数名参数格式是否带路径是否使用当前环境变量
execl列表不是
execlp列表
execle列表不是不是,须自己组装环境变量
execv数组不是
execvp数组
execve数组不是不是,须自己组装环境变量

事实上,我们打开man手册就可以发现execve在man手册第2节其它函数在man手册第3节。也就是 只有execve是真正的系统调用,其它五个函数最终都调用execve。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.pswp.cn/bicheng/83555.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

Datatable和实体集合互转

1.使用已废弃的 JavaScriptSerializer&#xff0c;且反序列化为弱类型 ArrayList。可用但不推荐。 using System; using System.Collections; using System.Collections.Generic; using System.Data; using System.Linq; using System.Reflection; using System.Web; using Sy…

阿里云服务器ECS详解:云服务器是什么,云服务器优势和应用场景及参考

云服务器ECS是阿里云众多云产品中&#xff0c;最受用户关注的产品&#xff0c;阿里云服务器提供多样化的计算能力&#xff0c;支持x86、Arm架构&#xff0c;涵盖CPU、GPU等多种服务器类型&#xff0c;满足各种用户需求。其便捷易用特性包括分钟级交付、通用API和性能监控框架&a…

【Oracle】游标

个人主页&#xff1a;Guiat 归属专栏&#xff1a;Oracle 文章目录 1. 游标基础概述1.1 游标的概念与作用1.2 游标的生命周期1.3 游标的分类 2. 显式游标2.1 显式游标的基本语法2.1.1 声明游标2.1.2 带参数的游标 2.2 游标的基本操作2.2.1 完整的游标操作示例 2.3 游标属性2.3.1…

pikachu靶场通关笔记11 XSS关卡07-XSS之关键字过滤绕过(三种方法渗透)

目录 一、源码分析 1、进入靶场 2、代码审计 3、攻击思路 二、渗透实战 1、探测过滤信息 2、注入Payload1 3、注入Payload2 4、注入Payload3 本系列为通过《pikachu靶场通关笔记》的XSS关卡(共10关&#xff09;渗透集合&#xff0c;通过对XSS关卡源码的代码审计找到安…

XML 元素:基础、应用与优化

XML 元素:基础、应用与优化 引言 XML(可扩展标记语言)作为一种数据交换的标准格式,广泛应用于互联网数据交换、数据存储等领域。XML 元素是 XML 文档的核心组成部分,本文将深入探讨 XML 元素的概念、特性、应用以及优化方法。 一、XML 元素概述 1.1 XML 元素的定义 X…

【Axure高保真原型】交通事故大屏可视化分析案例

今天和大家分享交通事故大屏可视化分析案例的原型模板&#xff0c;包括饼图分类分析、动态显示发生数、柱状图趋势分析、中部地图展示最新事故发现地点和其他信息、右侧列表记录发生事故的信息…… 通过多种可视化图表展示分析结果&#xff0c;具体效果可以点击下方视频观看或…

HCIP(BGP基础)

一、BGP 基础概念 1. 网络分类与协议定位 IGP&#xff08;内部网关协议&#xff09;&#xff1a;用于自治系统&#xff08;AS&#xff09;内部路由&#xff0c;如 RIP、OSPF、EIGRP&#xff0c;关注选路效率、收敛速度和资源占用。EGP&#xff08;外部网关协议&#xff09;&a…

【HarmonyOS 5】 ArkUI-X开发中的常见问题及解决方案

一、跨平台编译与适配问题 1. 平台特定API不兼容 ‌问题现象‌&#xff1a;使用Router模块的replaceUrl或startAbility等鸿蒙专属API时&#xff0c;编译跨平台工程报错cant support crossplatform application。 ‌解决方案‌&#xff1a; 改用ohos.router的跨平台封装API&a…

Matlab2018a---安装教程

目录 壹 | 引 言 贰 | 安装环境 叁 | 安 装 肆 | 结 语 壹 | 引 言 大家好&#xff0c;我是子正。 最近想学习一下DSP数字信号处理有关的知识&#xff0c;要用到Matlab进行数据处理&#xff0c;于是又重新把Matlab捡了回来; 记得上学那会儿用的还是Matlab2012a&#xff…

分布式流处理与消息传递——Kafka ISR(In-Sync Replicas)算法深度解析

Java Kafka ISR&#xff08;In-Sync Replicas&#xff09;算法深度解析 一、ISR核心原理 #mermaid-svg-OQtnaUGNQ9PMgbW0 {font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}#mermaid-svg-OQtnaUGNQ9PMgbW0 .error-icon{fill:#55222…

ARM GIC V3概述

中断类型 locality- specific peripheral interrupt&#xff08;LPI&#xff09;&#xff1a;LPI是一个有针对性的外设中断&#xff0c;通过affinity路由到特定的PE。 为非安全group1中断边沿触发可以通过its进行路由没有active状态&#xff0c;所以不需要明确的停用操作LPI总…

蓝桥杯国赛训练 day1

目录 k倍区间 舞狮 交换瓶子 k倍区间 取模后算组合数就行 import java.util.HashMap; import java.util.Map; import java.util.Scanner;public class Main {static Scanner sc new Scanner(System.in);public static void main(String[] args) {solve();}public static vo…

安装和配置 Nginx 和 Mysql —— 一步一步配置 Ubuntu Server 的 NodeJS 服务器详细实录6

前言 昨天更新了四篇博客&#xff0c;我们顺利的 安装了 ubuntu server 服务器&#xff0c;并且配置好了 ssh 免密登录服务器&#xff0c;安装好了 服务器常用软件安装, 配置好了 zsh 和 vim 以及 通过 NVM 安装好Nodejs&#xff0c;还有PNPM包管理工具 。 作为服务器的运行…

鸿蒙版Taro 搭建开发环境

鸿蒙版Taro 搭建开发环境 一、配置鸿蒙环境 下载安装 DevEco 建议使用最新版本的 IDE&#xff0c;当前为 5.0.5Release 版本。 二、创建鸿蒙项目 打开 DevEco&#xff0c;点击右上角的 Create Project&#xff0c;在 Application 处选择 Empty Ability&#xff0c;点击 Ne…

Could not get unknown property ‘mUser‘ for Credentials [username: null]

最近遇到jekins打包报错&#xff1a; Could not get unknown property mUser for Credentials [username: null] of type org.gradle.internal.credentials.DefaultPasswordCredentials_Decorated。 项目使用的是gradle&#xff0c;通过pipeline打docker包&#xff1b;因为ma…

Spring Boot + MyBatis-Plus 读写分离与多 Slave 负载均衡示例

Spring Boot + MyBatis-Plus 读写分离与多 Slave 负载均衡示例 一、项目结构 src/main/java/com/example/demo/ ├── config/ │ ├── DataSourceConfig.java # 数据源配置 │ ├── MyBatisPlusConfig.java # MyBatis-Plus配置 ├── constant/ │…

android binder(1)基本原理

一、IPC 进程间通信&#xff08;IPC&#xff0c;Inter-Process Communication&#xff09;机制&#xff0c;用于解决不同进程间的数据交互问题。 不同进程之间用户地址空间的变量和函数是不能相互访问的&#xff0c;但是不同进程的内核地址空间是相同和共享的&#xff0c;我们可…

高密爆炸警钟长鸣:AI为化工安全戴上“智能护盾”

一、高密爆炸&#xff1a;一声巨响&#xff0c;撕开化工安全“伤疤” 2025年5月27日&#xff0c;山东高密友道化学有限公司的车间爆炸声&#xff0c;像一把利刃划破了化工行业的平静。剧烈的冲击波将车间夷为平地&#xff0c;黑色蘑菇云腾空而起&#xff0c;刺鼻的化学气味弥漫…

双擎驱动:华为云数字人与DeepSeek大模型的智能交互升级方案

一、技术融合概述 华为云数字人 华为云数字人&#xff0c;全称&#xff1a;数字内容生产线 MetaStudio。数字内容生产线&#xff0c;提供数字人视频制作、视频直播、智能交互、企业代言等多种服务能力&#xff0c;使能千行百业降本增效。另外&#xff0c;数字内容生产线&#…

Linux运维笔记:1010实验室电脑资源规范使用指南

文章目录 一. 检查资源使用情况&#xff0c;避免冲突1. 检查在线用户2. 检查 CPU 使用情况3. 检查 GPU 使用情况4. 协作建议 二. 备份重要文件和数据三. 定期清理硬盘空间四. 退出 ThinLinc 时注销&#xff0c;释放内存五. 校外使用时配置 VPN注意事项 总结 实验室的电脑配备了…