当前位置: 首页 > news >正文

Linux——进程的退出、等待与替换

文章目录

  • 一、进程退出
    • 1.退出场景
    • 2.常见退出方法
    • 3.退出码与退出信号
    • 4._exit函数与exit函数
  • 二、进程等待
    • 1.什么是进程等待
    • 2.为什么要有进程等待
      • 僵尸进程与孤儿进程
    • 3.如何进行进程等待
      • 3.1.`wait`
      • 3.2.`waitpid`
      • 3.3.获取status
      • 3.4.阻塞与非阻塞等待
  • 三、进程替换
    • 1.进程替换原理
    • 2.进程替换函数

一、进程退出

1.退出场景

进程退出一共就三种情况:

  • 进程正常退出,执行结果正确
  • 进程正常退出,执行结果错误
  • 进程异常退出

2.常见退出方法

  • main函数中return退出
  • 使用_exit函数退出
  • 使用exit函数退出

_exit和exit在下文详解。

3.退出码与退出信号

  • 退出码:标识程序的退出状态
  • 退出信号:当程序异常退出后,退出信号标记了进程异常退出的原因,如果是正常退出则退出信号为0。
    在这里插入图片描述
    在对于退出信号可以使用 kill -l 查询,如下:
    在这里插入图片描述

4._exit函数与exit函数

_exit是一个系统提供的接口,它的参数是一个int类型,需要传一个退出信号返回。
而exit是C语言提供的接口,它同样是让程序退出,需要传一个退出信号返回。

而_exit与exit一个很大的区别就是 _exit不会刷新缓冲区,而exit会刷新缓冲区,其中exit底层还是使用了_exit实现。

可以做一个简单的小测试:

在这里插入图片描述
注意:

  • 这里程序是从exit和_exit退出的,而不是从return 0退出。
  • 这里我有意在printf输出字符串后没有加\n,因为\n会让缓冲区刷新,会干扰测试。

二、进程等待

1.什么是进程等待

进程等待指的是父进程等待子进程结束。

在子进程结束后它的pcb不会立马释放,而是进入僵尸状态,让父进程回收。 当然如果父进程永远不来回收,那么子进程pcb就永远得不到释放,从而内存泄漏

而父进程在等待子进程退出这个过程就叫作进程等待。

2.为什么要有进程等待

  • 父进程创建子进程就是要子进程完成任务,所以父进程需要知道任务的完成情况,从而决定下一步要做什么。
  • ⼦进程退出,⽗进程如果不管不顾,就可能造成 僵⼫进程 的问题,进⽽造成内存泄漏
  • ⽗进程通过进程等待的⽅式,回收⼦进程资源,获取⼦进程退出信息(任务完成情况)。

僵尸进程与孤儿进程

  • 僵尸进程 : 是父进程一直都在,子进程退出了,但是父进程没有回收子进程,子进程保留 PID 和内核资源,状态为 Z(Zombie)。

风险:占用内核进程表条目,过多会导致系统无法创建新进程。
解决:修复父进程逻辑或终止父进程(僵尸会被 init 接管清理)。

  • 孤儿进程: 是父进程先退出了,子进程还在执行, 子进程被 init 进程(PID=1)接管,状态仍为 R/S 等活跃状态。

风险:自动变成后台进程,无直接危害,但需确保子进程能正确完成任务或终止。
解决:通常由 init 自动管理。

3.如何进行进程等待

3.1.wait

wait是一个用来进程等待的函数,使用它需要包含的头文件为 sys/types.h 和 sys/wait.h。函数声明如下:

pid_t wait(int *status);
  • 返回值:成功返回被等待进程pid,失败返回-1。
  • 参数:输出型参数,获取⼦进程退出状态,不关⼼则可以设置成为NULL。

3.2.waitpid

waitpid同样是一个用来进程等待的函数,它的功能要更多,使用它需要包含的头文件为 sys/types.h 和 sys/wait.h。函数声明如下:

pid_t waitpid(pid_t pid, int *status, int options);

返回值:

  • 当正常返回的时候waitpid返回收集到的⼦进程的进程ID;
  • 如果第三个参数设置了选项WNOHANG,那么调⽤中waitpid发现没有已退出的⼦进程可收集,否则返回0。
  • 如果调⽤中出错,则返回-1,这时errno会被设置成相应的值以指⽰错误所在;

参数:

  • 1.pid:(1)pid=-1,等待任⼀个⼦进程。与wait等效
    (2)pid>0,等待其进程ID为pid的⼦进程
  • 2.status:输出型参数,获取⼦进程退出状态,不关⼼则可以设置成为NULL。下面3.3.再细讲。
  • 3.options:默认为0,表⽰如果子进程没有结束需要等待。如果设置为WNOHANG,子进程没有结束则不需要等待,接着往下执行。

3.3.获取status

status可以得到进程的退出码和退出信号。

它是如何同时储存退出码和退出信号呢?其实用了一个位图的思想。status是一个int类型一共占4*8个比特位。而这里用了它的低16位,如下:
在这里插入图片描述
所以退出码我们可以使用(status>>8)&0xFF获取,退出信号通过status&0x7F获取。当然系统给我们提供了WIFEXITED和WEXITSTATUS两个宏来做进程退出状态检查。功能如下:

  • WIFEXITED(status):若为正常终⽌⼦进程返回的状态,则为真(查看进程是否是正常退出)。
  • WEXITSTATUS(status):若WIFEXITED⾮零,提取⼦进程退出码(查看进程的退出码)。

3.4.阻塞与非阻塞等待

  • 阻塞等待:使用wait或使用waitpid第三个参数传0的话,父进程在等待子进程过程中,如果子进程没有结束,那么父进程就会一直等。直到子进程退出。
  • 非阻塞等待:父进程是很忙的,当子进程没有退出的时候也不能在那里干等着。所以正如刚才所讲,waitpid第三个参数中传入WNOHANG就能实现子进程还没退出就不等,继续做自己的其他工作,这就是非阻塞等待。当然刚才没有等到子进程结束,还得找个时间再等,要不然到时候子进程pcd就不能被回收。所以如果使用WNOHANG就需要重复的waitpid,这就是非阻塞轮询。

三、进程替换

1.进程替换原理

如下是一个程序替换的程序(execl函数使用在下文会讲解):

#include<stdio.h>
#include<unistd.h>
int main()
{execl("/usr/bin/ls","/usr/bin/ls","-la",NULL);//程序替换printf("hello linux\n");return 0;}

进程替换就是在一个进程执行中把该进程后续的内容替换成其他进程。要知道指令的本质就是一个可执行文件,它是用c语言写的。以上代码就相当于把ls这个程序的代码把后面的原代码覆盖掉, 所以其中pcd,虚拟地址空间,页表并没有改变,而是物理内存改变了。

注意:
(1) 进程替换并不会有新的进程生成。
(2)进程替换可以替换为任何进程,无论是什么语言,打个比方就是说,x写的程序,在执行过程中可以替换成y写的程序(x,y表示任意语言)。

2.进程替换函数

如下是一个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[]);

替换一个程序起码要知道这个程序的路径吧?需要知道它的名字吧?还需要知道命令行参数吧?

  • 第一个参数:需要替换的程序的路径。
  • 第二个参数:需要替换的程序的名字。
  • 第三个参数:给该程序传入命令行参数。
  • 第四个参数:如果有这个参数,需要传入自己组装的环境变量。如果没有改参数不用传,就用当前的环境变量。

上面函数的命名特点如下:

  • l(list):表⽰参数采⽤列表传入。
  • v(vector):参数⽤数组。
  • p(path):有p⾃动搜索环境变量PATH。
  • e(env):表⽰⾃⼰维护环境变量。

这些exec族的返回规则是一样的,如果执行成功没有返回值,如果调用失败返回-1。

http://www.dtcms.com/a/288983.html

相关文章:

  • ThinkSound:阿里开源首个“会思考”的音频生成模型——从“看图配音”到“听懂画面”的技术跃迁
  • C++ Primer(第5版)- Chapter 7. Classes -004
  • Dockerfile配置基于 Python 的 Web 应用镜像
  • 考研最高效的准备工作是什么
  • docker制作前端镜像
  • JVM-Java
  • 每日算法刷题Day50:7.20:leetcode 栈8道题,用时2h30min
  • 全面解析 JDK 提供的 JVM 诊断与故障处理工具
  • 零基础学习性能测试第二章-JVM如何监控
  • Android系统5层架构
  • 【论文笔记】OccluGaussian解决大场景重建中的区域遮挡问题
  • 5G NR PDCCH之信道编码
  • c#:管理TCP服务端发送数据为非16进制
  • 4、ubuntu | dify创建知识库 | 上市公司个股研报知识库
  • Python知识点4-嵌套循环break和continue使用死循环
  • 统计与大数据分析和数字经济:专业选择指南
  • LP-MSPM0G3507学习--07定时器之二定时节拍
  • 使用“桥接模式“,实现跨平台绘图或多类型消息发送机制
  • SpringBoot的介绍和项目搭建
  • 【C语言】字符串与字符函数详解(上)
  • C++ 详谈继承体系下的构造函数和析构函数
  • k8s:离线添加集群节点的相关组件安装与升级
  • GeoServer 信息泄漏漏洞复现(CVE-2025-27505)
  • 周志华《机器学习导论》第11章 特征选择与稀疏学习
  • 机器学习-数据预处理
  • 闲庭信步使用图像验证平台加速FPGA的开发:第二十六课——正弦波DDS的FPGA实现
  • leetcode75【经典动态规划】之:最长公共子序列
  • nginx源码解读-------整体架构
  • 30天打牢数模基础-LightGBM讲解
  • 网络地址和主机地址之间进行转换的类