【Linux】进程的生命之旅——诞生、消逝与守候(fork/exit/wait)
🎬 个人主页:谁在夜里看海.
📖 个人专栏:《C++系列》《Linux系列》《算法系列》
⛰️ 一念既出,万山无阻
目录
📖一、进程创建
1.fork函数
📚高层封装特性
📚fork返回值
2.写时拷贝
3.调用失败
📚资源耗尽
📚进程数限制
📚内核限制
📖二、进程终止
1.退出场景
2.status退出码
3.退出方法
📚exit函数
📚_exit函数
📚main函数返回
📖三、进程等待
1.wait方法
📚语法
📚总结
2.waitpid方法
📚语法
📚总结
📖一、进程创建
1.fork函数
操作系统中进程的创建通常是通过系统调用实现的,在Linux中是通过fork(),它从已存在进程中创建一个新进程。新进程为子进程,而原进程为父进程。
#include<unistd.h> // 使用需包含头文件unistd.h
pid_t pid = fork(); // 在子进程中返回0,父进程中返回子进程pid,出错返回-1
fork是操作系统提供的一种高层封装,它抽象了进程创建的复杂过程,fork将底层的一系列操作封装在一个简单的系统调用中,屏蔽了许多复杂的细节:
📚高层封装特性
① 简化进程创建的步骤:
fork的调用接口非常简洁,只需要调用一次,系统会自动创建一个子进程并返回。父进程和子进程共享相同的代码,子进程可以继续从父进程的当前执行点运行。
② 屏蔽底层细节:
底层需要分配新的内存空间、复制父进程的状态、初始化子进程的资源,fork函数将这些细节全部封装起来。
③ 依赖操作系统:
fork的具体实现以来于操作系统内核,它负责管理进程表等关键数据结构,系统调用fork,当控制转移到内核中的fork代码后,内核做:
1. 分配新的内存块和内核数据结构给子进程
2. 将父进程部分数据结构内容拷贝给子进程
3. 添加子进程到系统进程列表当中
4. fork返回,开始调度器调dan'ddandan
当一个进程调用fork之后就会有两个二进制代码相同的进程,并且都能运行到相同的地方,各自开始往下走:
int main() {printf("Before: pid is %d, ppid is %d\n",getpid(),getppid());fork();printf("After: pid is %d, ppid is %d\n",getpid(),getppid());return 0;
}
❓这里为什么只有三行输出,子进程共享父进程的代码,并各自独立执行,应该是打印两次Before才对。分析打印结果,10226应该是父进程,确实打印了Before,而子进程10227没有打印Before,说明子进程并没有执行Before的代码,正如上面所说的,子进程继续从父进程的当前执行点运行,也就是从fork代码处往下执行,而fork之前的不会被执行:
所以,在一个进程调用fork之前,该进程单独执行,调用fork之后,父子两个进程执行流各自执行
📚fork返回值
在子进程中,fork返回0;
在父进程中,frok返回子进程pid。
❓这里提出一个问题,在父进程中fork返回值没有异议,因为fork函数是父进程调用的,自然会有返回值,但是在子进程中fork也有返回值,那么是不是子进程也调用了fork函数呢? 这不肯定,因为一个进程调用fork函数之后会创建出它的子进程,而子进程再调用fork函数再创建。。。这显然不对,所以子进程并没有调用fork函数,但是为什么会有fork函数的返回值呢?
✅上面说到,子进程是从父进程的执行点开始往下执行的,所以对上述问题合理的解释是:父进程创建子进程时的执行点在fork函数调用之后,返回之前,所以子进程往下执行也会有返回值产生
2.写时拷贝
父子进程的代码是共享的,所以它们往后执行相同的操作,那么它们的数据也是共享的吗?的确,在没有进行写入时,父子进程的数据也是共享的,只有当一方尝试对共享数据进行写入时,系统才会拷贝一份数据用于写入,这样既确保了资源的高效利用,又保证了父子进程间的独立性 。
3.调用失败
fork()调用失败通常于系统资源、权限、或操作系统限制有关,下面是常见的原因:
📚资源耗尽
当系统的资源不足时(如内存或进程表项不足),fork()会失败:
内存不足:操作系统需要为每个新进程分配内存,如果系统内存耗尽,
fork()
就会失败。进程表已满:每个进程都有一个进程控制块(PCB),操作系统维护一个进程表。如果系统中运行的进程数量已经达到限制,无法再为新进程分配进程控制块时,
fork()
会失败。堆栈空间不足:如果子进程的堆栈空间无法分配(尤其在某些嵌入式或资源受限的环境中),
fork()
也会失败。
📚进程数限制
大多数操作系统对一个用户或系统总共能创建的进程数有限制。若当前用户或系统已经达到了此限制,调用 fork()
时就会失败。
可以通过 ulimit -u
查看单个用户的最大进程数:
📚内核限制
内核的资源,如文件描述符和信号等,也可能导致 fork()
失败。例如,如果父进程持有太多打开的文件句柄,可能会达到系统文件描述符的限制。
📖二、进程终止
1.退出场景
进城退出场景无非下面三种:
①:代码运行完毕,结果正确
②:代码运行完毕,结果不正确
③:代码异常终止(没有运行完)
第一种情况自然是最好的,但是如果是另外两种情况,我们就需要进行额外处理,但是我们怎么才能知道进程退出是哪种情况呢(什么时候需要处理,什么时候不需要呢)?
这个时候就需要进程退出时,做一些标记(返回退出码),告知操作系统或程序员具体的退出情况
2.status退出码
status状态码用于表示进程的退出状态,提供了进程执行结果的信息,状态码遵循以下约定:
- 0:表示命令成功执行,没有错误发生。
- 非0:表示命令执行失败。具体的非0值表示不同类型的错误,具体含义通常与执行的程序或命令相关。例如:
- 1:一般性错误。
- 2:命令语法错误。
- 126:命令不能执行(权限问题)。
- 127:命令未找到。
- 128:命令因信号导致终止(例如,程序被 kill 命令中断)。
- 130:程序因接收到 Ctrl+C(SIGINT)信号而退出。
status通常被定义成整形,但是并不能当作一般的整形看待,而是要看作成位图:
我们有一个 32 位的 status
,其中高8位用于表示退出状态,低8位用于表示因信号退出的原因。
-
高8位(退出状态)可以有 256 种可能的退出码:
0
:正常退出。1
到127
:表示不同的错误。128
到255
:表示因信号终止,计算方式为128 + 信号编号
。 -
低8位(信号终止标志):
如果进程是由于信号终止的,那么低8位会记录相应的信号编号(例如,
SIGKILL
对应 9,SIGSEGV
对应 11)。如果进程不是由信号终止的,低8位通常为 0。
3.退出方法
进程退出的常见方法有:exit(),_exit()以及main()函数返回,下面依次进行介绍:
📚exit函数
exit(int status):这是进程正常终止的一种方式。调用exit()后,进程会清理其资源(文件描述符、内存等),并将状态码status返回给操作系统。当返回0时表示成功退出,返回非0表示出现错误。
在多线程程序中,exit()
会终止当前进程以及所有线程。
#include <stdio.h>
#include <stdlib.h>int main() {printf("This process will exit normally.\n");exit(0); // 正常退出,状态码为0
}
📚_exit函数
_exit(int status):这个函数与 exit()
很相似,但它不会执行标准库的清理操作(如缓冲区刷新等),直接终止进程。来看下面这段代码:
int main()
{printf("this is a process, pid is %d, ppid is %d",getpid(),getppid());exit(0);
}
调用exit时,正常打印;
int main()
{printf("this is a process, pid is %d, ppid is %d",getpid(),getppid());_exit(0);
}
❓调用_exit时,没有正常打印,这是为什么呢?
✅printf输出时如果没有加上\n,此时输出的内容会存在标准输出缓冲区中,并不会立刻显示在终端,而调用_exit函数时,由于它不会执行标准库的清理操作,所以缓冲区的内容就不会显示在终端
exit函数最后其实会调用_exit函数,只不过在调用之前,多做了如清理缓冲区的操作:
📚main函数返回
return
:在 main
函数中使用时,程序会结束并返回指定的退出状态码(通常为 0
表示成功,非 0
表示错误)。return
结束当前函数的执行,但如果在 main
函数中调用,它会导致程序退出。
return返回和exit调用的效果是一样的,其实他们本质上是等价的:return 0 等价于 exit(0)
只不过在main函数中用return返回作为程序终止的标志更符合函数的语义,可读性更强。
📖三、进程等待
之前的博客讲过,子进程退出,如果父进程不做任何处理,就会引发内存泄露(进程表等信息不会被清理),产生僵尸进程。 博客链接在此:详解僵尸进程于孤儿进程
那么避免僵尸进程的办法就是进程等待,父进程通过进程等待的方式,回收子进程资源,获取子进程退出信息。
1.wait方法
wait()是一个比较简化的系统调用,用于让父进程等待任意一个子进程的终止。wait()函数会阻塞父进程,直到有子进程终止,并且返回一个子进程的PID。
📚语法
#include <sys/wait.h>
pid_t wait(int *status);
status:用于返回子进程的退出状态。
返回值:如果调用成功,返回子进程的PID;如果没有则返回-1。
📚总结
1. 父进程调用wait()时会阻塞,直到有子进程结束并回收它的状态;
2. 如果有多个子进程退出,wait()返回任意一个子进程的PID;
3. 如果没有子进程,wait()会返回-1。
2.waitpid方法
waitpid()
是 wait()
的更为灵活和可控制的版本,允许父进程等待特定的子进程结束,或者通过指定参数进行更精细的控制。
📚语法
#include <sys/wait.h>
pid_t waitpid(pid_t pid, int *status, int options);
pid
:指定需要等待的子进程的 PID。可以取以下几种值:pid > 0
:等待指定 PID 的子进程。pid == -1
:等待同组进程中的任意子进程。
status
:与wait()
相同,保存子进程的退出状态。options
:控制行为的标志,常用的选项有:WNOHANG
:非阻塞模式,如果没有子进程退出,立即返回,而不是阻塞。WUNTRACED
:如果子进程已经停止(但没有退出),也返回。
- 返回值:
- 返回子进程的 PID,如果没有子进程或者发生错误,返回
-1
。 - 如果
status
中的退出状态有特殊状态(如退出信号),需要使用宏来解析。
- 返回子进程的 PID,如果没有子进程或者发生错误,返回
int main()
{pid_t pid = fork();if(pid == 0){// 子进程printf("this is child process,pid is %d,ppid is %d\n",getpid(),getppid());exit(20);}// 父进程printf("this is father process,pid is %d\n",getpid());int status;pid_t child_pid = waitpid(-1,&status,WNOHANG);printf("child process has exited,code is %d,pid is %d\n",WEXITSTATUS(status),child_pid);exit(0);
}
父进程调用 wait()
或 waitpid()
时,它会传递一个指向 status
变量的指针,用于写入子进程的退出状态,所以我们需要在外部定义一个status变量,并通过取地址的方式传入函数内部。
❓定义成其他变量名可以吗:完全可以!
✅变量名只是内存的一个标识符,是用户自定义的,wait()
或 waitpid()
只关心的是传递给它的地址,而不是变量的名字,只不过定义成status
这样代码更加易读。
❓定义成其他类型可以吗:不可以!
✅status
参数必须是一个指向 int
类型的指针。如果传递其他类型(例如 float*
或 char*
),程序可能会产生编译错误,这是因为 wait()
和 waitpid()
会在 status
指向的内存中写入整数值,用来存储子进程的退出状态。如果指针指向的类型不匹配,内存解释将出错。
上述代码中由于waitpid内部设置为WNOHANG模式,没有子进程返回时直接退出,不阻塞:
需要sleep(1)等待子进程退出后,waitpid才能接收到退出信息:
// 父进程printf("this is father process,pid is %d\n",getpid());int status;sleep(1);pid_t child_pid = waitpid(-1,&status,WNOHANG);printf("child process has exited,code is %d,pid is %d\n",WEXITSTATUS(status),child_pid);exit(0);
其中WEXITSTATUS是一个宏函数,用于解码退出状态,因为上面讲过,32位status的高8位存储退出状态,所以不能直接引用status查看,而要用一个宏函数进行解码。
📚总结
特性 | wait() | waitpid() |
等待目标 | 等待任意子进程的结束 | 可以指定特定的子进程(通过 pid 参数) |
阻塞与非阻塞 | 总是阻塞,直到至少有一个子进程结束 | 可以通过 WNOHANG 使其非阻塞 |
灵活性 | 较少灵活性,只能等待任何一个子进程 | 更灵活,可以等待指定的子进程或进程组 |
选项 | 没有额外选项 | 支持更多控制选项,如 WNOHANG |
返回值 | 返回一个子进程的 PID | 返回指定子进程的 PID,或者 -1 错误 |
错误处理 | 如果没有子进程,返回 -1 | 如果没有子进程,返回 -1 |
以上就是【进程的生命之旅——诞生、消逝与守候】的全部内容,欢迎指正~
码文不易,还请多多关注支持,这是我持续创作的最大动力!
相关文章:

【Linux】进程的生命之旅——诞生、消逝与守候(fork/exit/wait)
🎬 个人主页:谁在夜里看海. 📖 个人专栏:《C系列》《Linux系列》《算法系列》 ⛰️ 一念既出,万山无阻 目录 📖一、进程创建 1.fork函数 📚高层封装特性 📚fork返回值 2.写时拷…...

使用vcpkg自动链接tinyxml2时莫名链接其他库(例如boost)
使用vcpkg自动链接tinyxml2时莫名链接其他库(例如boost) vcpkg的自动链接功能非常方便,但在某些情况下会出现过度链接的问题。 链接错误症状 以tinyxml2为例,程序中调用tinyxml2的函数后,若vcpkg中同时存在opencv和…...

【去毛刺】OpenCV图像处理基础:腐蚀与膨胀操作入门
在数字图像处理中,形态学操作是一种常用的技术,用于提取图像中的特定形状或特征。其中,腐蚀(Erosion)和膨胀(Dilation)是两种基本的形态学运算。本文将通过一个简单的例子来演示如何使用Python中…...
道可云人工智能元宇宙每日资讯|第三届京西地区发展论坛成功召开
道可云元宇宙每日简报(2024年11月27日)讯,今日元宇宙新鲜事有: 工信部等十二部门印发《5G规模化应用“扬帆”行动升级方案》 11月25日,工业和信息化部等十二部门印发《5G规模化应用“扬帆”行动升级方案》。《方案》…...

若依框架部署在网站一个子目录下(/admin)问题(
部署在子目录下首先修改vue.config.js文件: 问题一:登陆之后跳转到了404页面问题,解决办法如下: src/router/index.js 把404页面直接变成了首页(大佬有啥优雅的解决办法求告知) 问题二:退出登录…...

【ue5】UE5运行时下载视频/UE5 runtime download video(MP4)
插件还是老朋友。 节点的content type要打对。 (参照表:MIME 类型(MIME Type)完整对照表 - 免费在线工具) 结果展示:...

对比C++,Rust在内存安全上做的努力
简介 近年来,越来越多的组织表示,如果新项目在技术选型时需要使用系统级开发语言,那么不要选择使用C/C这种内存不安全的系统语言,推荐使用内存安全的Rust作为替代。 谷歌也声称,Android 的安全漏洞,从 20…...
如何利用 Qt 的模块化架构组织大型项目
目录 1. 大型项目的架构设计 1.1 分层架构 1.2 事件驱动与异步架构 2. 模块划分与职责分离 2.1 功能模块划分 2.2 模块之间的依赖管理 3. 跨平台开发与模块复用 在大型软件项目中,随着代码量的增加和功能的扩展,项目的复杂度会显著提升。没有良好…...

探索Python词云库WordCloud的奥秘
文章目录 探索Python词云库WordCloud的奥秘1. 背景介绍:为何选择WordCloud?2. WordCloud库简介3. 安装WordCloud库4. 简单函数使用方法5. 应用场景示例6. 常见Bug及解决方案7. 总结 探索Python词云库WordCloud的奥秘 1. 背景介绍:为何选择Wo…...
MySQL根据idb文件恢复数据
首先得有对应表的idb文件以及建表语句 1.首先在新数据库建表 CREATE TABLE sys_menu (id bigint(20) NOT NULL,parent_id bigint(20) NULL DEFAULT NULL,name varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL,type int(11) NULL DEFAULT …...

hadoop-mapreduce词频统计
一、Map Reduce主要阶段 二、词频统计示例 0.MapReduce 词频统计(Word Count)示例图 1. Input 阶段(输入阶段) 输入数据是一段文本,如下: Hadoop is a big data framework. Hadoop can store vast data. Hadoop processes big …...

精心修炼Java并发编程(JUC)-volatile与synchronized关键字
volatile volatile 是 JVM 提供的 最轻量级的同步机制,中文意思是不稳定的,易变的,用 volatile 修饰变量是为了保证变量在多线程中的可见性,它表达的含义是:告诉编译器,对这个变量的读写,需要基…...
【ROS2】ROS2 与 ROS1 编码方式对比(Python实现)
目录 一、初始化和关闭节点二、发布者三、订阅者四、服务端五、客户端六、参数管理七、日志记录八、生命周期管理 ROS2 在 Python 编程中引入了一些新的概念和 API,这些变化使得代码更加模块化和易于维护。特别是 rclpy 库提供了更丰富的功能和更好的错误处理机制&a…...

ElasticSearch的下载和基本使用(通过apifox)
1.概述 一个开源的高扩展的分布式全文检索引擎,近乎实时的存储,检索数据 2.安装路径 Elasticsearch 7.8.0 | Elastic 安装后启动elasticsearch-7.8.0\bin里的elasticsearch.bat文件, 启动后就可以访问本地的es库http://localhost:9200/ …...
城市轨道交通运营控制指挥中心设计方案
为某城市轨道交通运营控制指挥中心(OCC)的设计提供方案时,我们需要考虑到多个方面的需求,包括系统架构、设备选择、功能实现、数据流与监控、通信管理等。以下是一个综合性的设计方案,涉及系统硬件和软件的选择、布局规划、安全性等方面,以确保指挥中心的高效运作、实时监…...

多目标优化算法:多目标河马优化算法(MOHOA)求解ZDT1、ZDT2、ZDT3、ZDT4、ZDT6,提供完整MATLAB代码
一、河马优化算法 河马优化算法(Hippopotamus optimization algorithm,HO)由Amiri等人于2024年提出的一种模拟自然界中河马觅食行为的新型群体智能优化算法。该算法由Mohammad Hussein Amiri等人于2024年2月发表在Nature旗下子刊《Scientifi…...
线程与进程的个人理解
进程(Process): 一个程序在执行时,操作系统为其分配的资源(如内存、CPU 时间等)构成了一个进程。每个进程都有自己的独立的地址空间、堆栈和局部变量,它们之间不共享内存(除非通过特…...

vscode的项目给gitlab上传
目录 一.创建gitlab帐号 二.在gitlab创建项目仓库 三.Windows电脑安装Git 四.vscode项目git上传 一.创建gitlab帐号 二.在gitlab创建项目仓库 图来自:Git-Gitlab中如何创建项目、创建Repository、以及如何删除项目_gitlab新建项目-CSDN博客) 三.Windows电脑安…...

企业微信定位打卡
废话少说:定位修改软件链接奉上 一、定位打卡原理 GPS定位:企业微信可以利用手机的GPS功能进行定位,这是一种基于卫星的定位技术,能够提供相对精确的位置信息,通常精确度在20米以内。这种方式耗电较大,且在…...
libaom 源码分析:码率控制介绍
码率控制 命令行码率控制选项:可以看到码率控制包括丢帧、resize、超分、码控模式、目标码率、目标上限下限(类似 x264、x265 中的 VBV)、码控偏置、GOP 码率等。Rate Control Options:--drop-frame=<arg> Temporal resampling threshold (buf %)--resize-mo…...
基于大模型的 UI 自动化系统
基于大模型的 UI 自动化系统 下面是一个完整的 Python 系统,利用大模型实现智能 UI 自动化,结合计算机视觉和自然语言处理技术,实现"看屏操作"的能力。 系统架构设计 #mermaid-svg-2gn2GRvh5WCP2ktF {font-family:"trebuchet ms",verdana,arial,sans-…...
Linux链表操作全解析
Linux C语言链表深度解析与实战技巧 一、链表基础概念与内核链表优势1.1 为什么使用链表?1.2 Linux 内核链表与用户态链表的区别 二、内核链表结构与宏解析常用宏/函数 三、内核链表的优点四、用户态链表示例五、双向循环链表在内核中的实现优势5.1 插入效率5.2 安全…...
脑机新手指南(八):OpenBCI_GUI:从环境搭建到数据可视化(下)
一、数据处理与分析实战 (一)实时滤波与参数调整 基础滤波操作 60Hz 工频滤波:勾选界面右侧 “60Hz” 复选框,可有效抑制电网干扰(适用于北美地区,欧洲用户可调整为 50Hz)。 平滑处理&…...
Cesium1.95中高性能加载1500个点
一、基本方式: 图标使用.png比.svg性能要好 <template><div id"cesiumContainer"></div><div class"toolbar"><button id"resetButton">重新生成点</button><span id"countDisplay&qu…...
C++中string流知识详解和示例
一、概览与类体系 C 提供三种基于内存字符串的流,定义在 <sstream> 中: std::istringstream:输入流,从已有字符串中读取并解析。std::ostringstream:输出流,向内部缓冲区写入内容,最终取…...

Maven 概述、安装、配置、仓库、私服详解
目录 1、Maven 概述 1.1 Maven 的定义 1.2 Maven 解决的问题 1.3 Maven 的核心特性与优势 2、Maven 安装 2.1 下载 Maven 2.2 安装配置 Maven 2.3 测试安装 2.4 修改 Maven 本地仓库的默认路径 3、Maven 配置 3.1 配置本地仓库 3.2 配置 JDK 3.3 IDEA 配置本地 Ma…...
ip子接口配置及删除
配置永久生效的子接口,2个IP 都可以登录你这一台服务器。重启不失效。 永久的 [应用] vi /etc/sysconfig/network-scripts/ifcfg-eth0修改文件内内容 TYPE"Ethernet" BOOTPROTO"none" NAME"eth0" DEVICE"eth0" ONBOOT&q…...

AI,如何重构理解、匹配与决策?
AI 时代,我们如何理解消费? 作者|王彬 封面|Unplash 人们通过信息理解世界。 曾几何时,PC 与移动互联网重塑了人们的购物路径:信息变得唾手可得,商品决策变得高度依赖内容。 但 AI 时代的来…...

篇章二 论坛系统——系统设计
目录 2.系统设计 2.1 技术选型 2.2 设计数据库结构 2.2.1 数据库实体 1. 数据库设计 1.1 数据库名: forum db 1.2 表的设计 1.3 编写SQL 2.系统设计 2.1 技术选型 2.2 设计数据库结构 2.2.1 数据库实体 通过需求分析获得概念类并结合业务实现过程中的技术需要&#x…...
TJCTF 2025
还以为是天津的。这个比较容易,虽然绕了点弯,可还是把CP AK了,不过我会的别人也会,还是没啥名次。记录一下吧。 Crypto bacon-bits with open(flag.txt) as f: flag f.read().strip() with open(text.txt) as t: text t.read…...