【Linux系统编程】五、进程创建 -- fork()
文章目录
- 前言
- Ⅰ. 重温fork函数
- 一、fork()的概念
- 二、如何理解fork()有两个返回值
- Ⅱ.fork的常规用法
- Ⅲ. fork调用失败的原因
- Ⅳ. 写时拷贝
- 为什么存在写时拷贝❓❓❓
前言
现阶段我们知道进程创建有如下两种方式,其实包括在以后的学习中这两种方式也是最常见的:
- 命令行启动命令 (程序、指令等)
- 通过程序自身
fork()后产生的子进程
Ⅰ. 重温fork函数
一、fork()的概念
在 linux 中 fork函数 是非常重要的 系统函数,它从已存在进程中创建一个新进程。新进程为子进程,而原进程为父进程。之前我们通过查 man 手册,知道了运用 fork() 函数,并且调用它需要包含 头文件 <unistd.h>。
如下是函数的声明和返回值:
#include <unistd.h>
pid_t fork(void); // 返回值:子进程中返回0,父进程返回子进程id,出错返回-1
但是之前我们一直有个问题没有解决,那么就是**为什么一个函数一个返回两个值?**有了之前的进程概念的知识,这里我们就可以来解释一下这个现象了!
二、如何理解fork()有两个返回值
父进程 fork 时,子进程是以父进程为模板,简单地说就是子进程的大部分属性和属性值是拷贝父进程的,而小部分是指子进程的调度时间要重置、子进程的 pid、ppid 以及兄弟的要重置。其中上面的 PCB、地址空间、页表都在内核里由操作系统维护的,这也就意味着我们只需要调用操作系统提供的接口 fork,而具体工作细节由操作系统完成。
那其中 为什么 fork() 给父进程返回 子进程的pid,给子进程返回 0 呢???因为我们知道父进程和子进程的关系就是一对多的关系,每个子进程只能有一个孩子,而每个父进程可以有多个子进程,这个 返回值的意义就是为了标识它们的关系!
我们还要知道 fork() 函数是在用户空间中被我们调用的,但是其实现是在内核空间中由操作系统实现的!
fork() 的实现思路,大致如下:
1、给子进程分配新的内存块和内核数据结构(PCB、进程地址空间、页表等,并构建对应的映射关系);
2、将父进程的部分数据结构内容拷贝至父进程;
3、把子进程添加到系统进程列表中;
4、fork 返回,调度器开始调度。

从上图我们知道 既然在 fork 函数 return 之前,就已经有了父子两个进程,父子两个执行流分别执行,所以会给父进程返回子进程的PID,给子进程返回0,失败则返回-1。
让我们看看下面的代码程序:
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
int main()
{ pid_t pid; printf("Before: pid is %d\n", getpid()); if((pid = fork()) == -1) // 判断是否返回的是失败值{perror("fork()");exit(1);}printf("After:pid is %d, fork return %d\n", getpid(), pid); sleep(1); return 0;
} 调用结果:
[liren@VM-8-2-centos process]$ ./mypro
Before: pid is 20052
After:pid is 20052, fork return 20053
After:pid is 20053, fork return 0
从上述结果可以看到原来只有父进程一个也就是 20052,但是 fork 之后又产生了子进程 20053!
🔴 注意,fork 之后,谁先执行完全由调度器决定。
调度器是CPU中央处理器的管理员,主要负责完成做两件事情:
选择某些就绪进程来执行
打断某些执行的进程让它们变为就绪状态。
利用 fork 返回值的这个特性,我们可以用变量 id 接收返回值,根据 fork 返回值不同让父子进程执行不同的代码,这个我们之前也讲过啦,简单过一下就好!
#include <stdio.h>
#include <unistd.h>
int grobal_val = 100;
int main()
{pid_t id = fork();if(id == 0){printf("子进程:pid = %d,ppid = %d | grobal_val = %d, &grobal_val = %p\n",getpid(), getppid(), grobal_val, &grobal_val);}else if(id > 0){printf("父进程:pid = %d,ppid = %d | grobal_val = %d, &grobal_val = %p\n", getpid(), getppid(), grobal_val, &grobal_val);sleep(1);}else {printf("fork error\n");return 1;}return 0;
}调用结果:
[liren@VM-8-2-centos process]$ ./mypro
父进程:pid = 18199,ppid = 16903 | grobal_val = 100, &grobal_val = 0x60105c
子进程:pid = 18200,ppid = 18199 | grobal_val = 100, &grobal_val = 0x60105c
Ⅱ.fork的常规用法
- 一个父进程希望复制自己,使得 父子进程同时执行不同的代码段。例如,父进程等待客户端请求,生成子进程来处理请求。
- 让一个进程执行一个不同的程序。例如子进程从
fork返回后,调用exec函数。(这个会在进程替换中学习)
Ⅲ. fork调用失败的原因
fork 是操作系统级别的接口,所以失败的原因一定是系统级别的原因。
- 系统中已经存在太多的进程了。
- 实际用户创建的进程超过了限制。
这段代码是测试你的用户能跑好多个进程,但是不建议跑。因为跑了之后就会影响 bash,会导致系统出错!代码如下:
#include <stdio.h>
#include <unistd.h>
int main()
{int cnt = 0;while(1){int ret = fork();if(ret < 0){printf("fork error!, cnt: %d\n", cnt);break;}else if(ret == 0){// 子进程不断循环while(1) {printf("子进程:pid = %d,ppid = %d\n",getpid(), getppid());sleep(1);}}// 父进程不断循环不断产生子进程cnt++;}return 0;
}调用结果:
子进程:pid = 1706,ppid = 27353
子进程:pid = 32351,ppid = 27353
子进程:pid = 32348,ppid = 27353
子进程:pid = 32347,ppid = 27353
子进程:pid = 32331,ppid = 27353
............
^C
[liren@VM-8-2-centos process]$
解决方法:
-
用命令
kill -9 -1将进程全部杀死 -
重新增加一个用户使用
Ⅳ. 写时拷贝
当父子代码只读时,父子的代码和数据是共享的。但是任意一方试图写入时,便以写时拷贝的方式各自一份副本。
写时拷贝是一种机制或者策略,好比打仗时的敌退我打,敌进我撤,它根据实时情况来完成既定规则。同理写时拷贝是根据父和子谁先写入的实时情况来完成拷贝的,它是一种延时操作的策略。
具体步骤其实我们在讲进程地址空间的时候已经讲过了,细节见下图:

这里要强调的是这里的写时拷贝是针对数据的写时拷贝,这里留一个疑问 ~~ 代码会发生类似的写时拷贝的问题吗?答案是会的,后面我们讲进程程序替换时候会讲到!
为什么存在写时拷贝❓❓❓
- 写时拷贝是为了保证父子进程的独立性。
- 节省内存和系统资源,提高
fork的效率,减少fork失败的概率。
父子进程创建时,所有数据直接各自拷贝一份不行吗 ???很明显,不使用写时拷贝也可以保证父子进程的独立性,为啥还要费劲使用写时拷贝。其根本原因是:
- 所有的数据,父进程和子进程并不是都必须写入数据,有可能它们仅仅需要读取,而此时的各自拷贝是没有意义的,而且会浪费内存和系统资源。
fork时,创建数据结构,如果还要将数据拷贝一份,那么fork的效率一定会降低。fork本质就是向系统申请更多的内存资源,资源申请多了,fork有可能就会失败。

相关文章:
【Linux系统编程】五、进程创建 -- fork()
文章目录 前言Ⅰ. 重温fork函数一、fork()的概念二、如何理解fork()有两个返回值 Ⅱ.fork的常规用法Ⅲ. fork调用失败的原因Ⅳ. 写时拷贝为什么存在写时拷贝❓❓❓ 前言 现阶段我们知道进程创建有如下两种方式,其实包括在以后的学习中这两种方式也是最常见的&#…...
深入解析 STM32 GPIO:结构、配置与应用实践
理解 GPIO 的工作原理和配置方法是掌握 STM32 开发的基础,后续的外设(如定时器、ADC、通信接口)都依赖于 GPIO 的正确配置。 目录 一、GPIO 的基本概念 二、GPIO 的主要功能 三、GPIO 的内部结构 四、GPIO 的工作模式 1. 输入模式 2. 输出模式 3. 复用功能模式 4. 模…...
深入探究 C++17 std::is_invocable
文章目录 一、引言二、std::is_invocable 概述代码示例输出结果 三、std::is_invocable 的工作原理简化实现示例 四、std::is_invocable 的相关变体1. std::is_invocable_r2. std::is_nothrow_invocable 和 std::is_nothrow_invocable_r 五、使用场景1. 模板元编程2. 泛型算法 …...
Vmware网络模式
一、Vmware虚拟网络 Vmware共支持创建20个虚拟网络,相当于现实生活的交换机,名称vmnet0-vmnet19 没创建一个虚拟网络。对应在物理机会自动生成相应的虚拟网卡 该虚拟网卡用于和对应的虚拟网络中的虚拟机通信 二、虚拟网络的工作模式 1、nat模式 …...
神经辐射场(NeRF):从2D图像到3D场景的革命性重建
神经辐射场(NeRF):从2D图像到3D场景的革命性重建 引言 在计算机视觉和图形学领域,如何从有限的2D图像中高效且准确地重建真实的3D场景,一直是一个重要的研究方向。传统的3D重建方法,如多视角几何、点云重建…...
深入解析AI技术原理
序言 在当今数字化时代,人工智能(AI)已经成为科技领域最炙手可热的话题之一。从智能家居到自动驾驶汽车,从医疗诊断到金融风险预测,AI的应用无处不在。然而,对于许多人来说,AI背后的技术原理仍然充满了神秘色彩。本文将深入探讨AI的核心技术原理,从基础理论到前…...
PDF 2.0 的新特性
近来闲来无事,就想着把PDF的新标准研究研究,略有所得,和大家分享一下。 PDF 2.0的主要新特性包括更高级的加密算法、改进的数字签名和权限管理机制、增强了对非罗马字符的支持,以及扩展了标签架构和3D建模语言“PRC”的支…...
Matlab机械手碰撞检测应用
本文包含三个部分: Matlab碰撞检测的实现URDF文件的制作机械手STL文件添加夹爪 一.Matlab碰撞检测的实现 首先上代码 %% 检测在结构环境中机器人是否与物体之间发生碰撞情况,如何避免? % https://www.mathworks.com/help/robotics/ug/che…...
(root) Additional property include:is not allowed
参考:执行docker compose命令出现 Additional property include is not allowed_(root) additional property include is not allowed-CSDN博客 原因是docker-compose的版本太低,下载最新的替换即可。 第一次2.6.x版本改成了2.19.x不够高,所…...
react 18父子组件通信
在React 18中,父子组件之间的通信方式与之前的版本基本相同,主要可以通过以下几种方式实现: 1. Props(属性) 父组件向子组件传递数据: 父组件通过属性(props)向子组件传递数据&am…...
FastReport 加载Load(Stream) 模板内包含换行符不能展示
如下代码 当以FastReport 载入streams时 当模板内包含换行符时会导致不能正常生成pdf System.Xml.XmlDocument newFrxXml new System.Xml.XmlDocument(); newFrxXml.Load(fileName);FastReport.Report report new FastReport.Report();using (var memStream new MemoryStre…...
Maven 中常用的 scope 类型及其解析
在 Maven 中,scope 属性用于指定依赖项的可见性及其在构建生命周期中的用途。不同的 scope 类型能够影响依赖项的编译和运行阶段。以下是 Maven 中常用的 scope 类型及其解析: compile(默认值): 这是默认的作用域。如果…...
vue3:点击子组件进行父子通信
问: 子组件怎么和爷爷组件通信 回答: 在Vue 3中,子组件和爷爷组件之间的通信可以通过事件冒泡和状态管理来实现。你可以使用Vue的事件系统来传递事件,或者使用全局状态管理库如Vuex或Pinia。以下是一个使用事件冒泡的示例&…...
Composo:企业级AI应用的质量守门员
在当今快速发展的科技世界中,人工智能(AI)的应用已渗透到各行各业。然而,随着AI技术的普及,如何确保其可靠性和一致性成为了企业面临的一大挑战。Composo作为一家致力于为企业提供精准AI评估服务的初创公司,通过无代码和API双模式,帮助企业监测大型语言模型(LLM)驱动的…...
Jackson扁平化处理对象
POJO对象 Data public class People {private PeopleInfo peopleInfo;private List<String> peopleIds;private Map<String, String> peopleMap;Datapublic static class PeopleInfo {private String name;private String address;} }JSON序列化处理 直接将对象进…...
Java即时编译器(JIT)的原理及在美团的实践经验
基本功 | Java即时编译器原理解析及实践 - 美团技术团队 这篇文章由美团AI平台/搜索与NLP部的珩智、昊天、薛超撰写,深入介绍了Java即时编译器(JIT)的原理及在美团的实践经验。 Java执行过程与即时编译器概述 Java执行过程:Java…...
使用 Ollama 在 Windows 环境部署 DeepSeek 大模型实战指南
文章目录 前言Ollama核心特性 实战步骤安装 Ollama验证安装结果部署 DeepSeek 模型拉取模型启动模型 交互体验命令行对话调用 REST API 总结个人简介 前言 近年来,大语言模型(LLM)的应用逐渐成为技术热点,而 DeepSeek 作为国产开…...
算法基础之八大排序
文章目录 概要1. 冒泡排序(Bubble Sort)2. 选择排序(Selection Sort)3. 插入排序(Insertion Sort)4. 希尔排序(Shell Sort)5. 归并排序(Merge Sort)6. 快速排…...
使用TensorFlow和Keras构建卷积神经网络:图像分类实战指南
使用TensorFlow和Keras构建卷积神经网络:图像分类实战指南 一、前言:为什么选择CNN进行图像分类? 在人工智能领域,图像分类是计算机视觉的基础任务。传统的机器学习方法需要人工设计特征提取器,而深度学习通过卷积神经…...
音频进阶学习十一——离散傅里叶级数DFS
文章目录 前言一、傅里叶级数1.定义2.周期信号序列3.表达式DFSIDFS参数含义 4.DFS公式解析1)右边解析 T T T、 f f f、 ω \omega ω的关系求和公式N的释义求和公式K的释义 e j ( − 2 π k n N ) e^{j(\frac{-2\pi kn}{N})} ej(N−2πkn)的释义 ∑ n 0 N − 1 e…...
树莓派超全系列教程文档--(61)树莓派摄像头高级使用方法
树莓派摄像头高级使用方法 配置通过调谐文件来调整相机行为 使用多个摄像头安装 libcam 和 rpicam-apps依赖关系开发包 文章来源: http://raspberry.dns8844.cn/documentation 原文网址 配置 大多数用例自动工作,无需更改相机配置。但是,一…...
在rocky linux 9.5上在线安装 docker
前面是指南,后面是日志 sudo dnf config-manager --add-repo https://download.docker.com/linux/centos/docker-ce.repo sudo dnf install docker-ce docker-ce-cli containerd.io -y docker version sudo systemctl start docker sudo systemctl status docker …...
java 实现excel文件转pdf | 无水印 | 无限制
文章目录 目录 文章目录 前言 1.项目远程仓库配置 2.pom文件引入相关依赖 3.代码破解 二、Excel转PDF 1.代码实现 2.Aspose.License.xml 授权文件 总结 前言 java处理excel转pdf一直没找到什么好用的免费jar包工具,自己手写的难度,恐怕高级程序员花费一年的事件,也…...
2021-03-15 iview一些问题
1.iview 在使用tree组件时,发现没有set类的方法,只有get,那么要改变tree值,只能遍历treeData,递归修改treeData的checked,发现无法更改,原因在于check模式下,子元素的勾选状态跟父节…...
华为OD机试-食堂供餐-二分法
import java.util.Arrays; import java.util.Scanner;public class DemoTest3 {public static void main(String[] args) {Scanner in new Scanner(System.in);// 注意 hasNext 和 hasNextLine 的区别while (in.hasNextLine()) { // 注意 while 处理多个 caseint a in.nextIn…...
高危文件识别的常用算法:原理、应用与企业场景
高危文件识别的常用算法:原理、应用与企业场景 高危文件识别旨在检测可能导致安全威胁的文件,如包含恶意代码、敏感数据或欺诈内容的文档,在企业协同办公环境中(如Teams、Google Workspace)尤为重要。结合大模型技术&…...
【HTTP三个基础问题】
面试官您好!HTTP是超文本传输协议,是互联网上客户端和服务器之间传输超文本数据(比如文字、图片、音频、视频等)的核心协议,当前互联网应用最广泛的版本是HTTP1.1,它基于经典的C/S模型,也就是客…...
css3笔记 (1) 自用
outline: none 用于移除元素获得焦点时默认的轮廓线 broder:0 用于移除边框 font-size:0 用于设置字体不显示 list-style: none 消除<li> 标签默认样式 margin: xx auto 版心居中 width:100% 通栏 vertical-align 作用于行内元素 / 表格单元格ÿ…...
【Java学习笔记】BigInteger 和 BigDecimal 类
BigInteger 和 BigDecimal 类 二者共有的常见方法 方法功能add加subtract减multiply乘divide除 注意点:传参类型必须是类对象 一、BigInteger 1. 作用:适合保存比较大的整型数 2. 使用说明 创建BigInteger对象 传入字符串 3. 代码示例 import j…...
Mysql8 忘记密码重置,以及问题解决
1.使用免密登录 找到配置MySQL文件,我的文件路径是/etc/mysql/my.cnf,有的人的是/etc/mysql/mysql.cnf 在里最后加入 skip-grant-tables重启MySQL服务 service mysql restartShutting down MySQL… SUCCESS! Starting MySQL… SUCCESS! 重启成功 2.登…...
