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

【Linux系统编程】五、进程创建 -- fork()

文章目录

  • 前言
  • Ⅰ. 重温fork函数
    • 一、fork()的概念
    • 二、如何理解fork()有两个返回值
  • Ⅱ.fork的常规用法
  • Ⅲ. fork调用失败的原因
  • Ⅳ. 写时拷贝
    • 为什么存在写时拷贝❓❓❓

在这里插入图片描述

前言

现阶段我们知道进程创建有如下两种方式,其实包括在以后的学习中这两种方式也是最常见的:

  • 命令行启动命令 (程序、指令等)
  • 通过程序自身 fork() 后产生的子进程

Ⅰ. 重温fork函数

一、fork()的概念

​ 在 linuxfork函数 是非常重要的 系统函数,它从已存在进程中创建一个新进程。新进程为子进程,而原进程为父进程。之前我们通过查 man 手册,知道了运用 fork() 函数,并且调用它需要包含 头文件 <unistd.h>

​ 如下是函数的声明和返回值:

#include <unistd.h> 
pid_t fork(void); // 返回值:子进程中返回0,父进程返回子进程id,出错返回-1

​ 但是之前我们一直有个问题没有解决,那么就是**为什么一个函数一个返回两个值?**有了之前的进程概念的知识,这里我们就可以来解释一下这个现象了!

二、如何理解fork()有两个返回值

父进程 fork,子进程是以父进程为模板,简单地说就是子进程的大部分属性和属性值是拷贝父进程的,而小部分是指子进程的调度时间要重置、子进程的 pidppid 以及兄弟的要重置。其中上面的 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中央处理器的管理员,主要负责完成做两件事情:

  1. 选择某些就绪进程来执行

  2. 打断某些执行的进程让它们变为就绪状态。

​ 利用 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]$ 

解决方法:

  1. 用命令 kill -9 -1 将进程全部杀死

  2. 重新增加一个用户使用

Ⅳ. 写时拷贝

当父子代码只读时,父子的代码和数据是共享的。但是任意一方试图写入时,便以写时拷贝的方式各自一份副本。

​ 写时拷贝是一种机制或者策略,好比打仗时的敌退我打,敌进我撤,它根据实时情况来完成既定规则。同理写时拷贝是根据父和子谁先写入的实时情况来完成拷贝的,它是一种延时操作的策略。

​ 具体步骤其实我们在讲进程地址空间的时候已经讲过了,细节见下图:

在这里插入图片描述

​ 这里要强调的是这里的写时拷贝是针对数据的写时拷贝,这里留一个疑问 ~~ 代码会发生类似的写时拷贝的问题吗?答案是会的,后面我们讲进程程序替换时候会讲到!

为什么存在写时拷贝❓❓❓

  • 写时拷贝是为了保证父子进程的独立性
  • 节省内存和系统资源,提高 fork 的效率,减少 fork 失败的概率

​ 父子进程创建时,所有数据直接各自拷贝一份不行吗 ???很明显,不使用写时拷贝也可以保证父子进程的独立性,为啥还要费劲使用写时拷贝。其根本原因是:

  1. 所有的数据,父进程和子进程并不是都必须写入数据,有可能它们仅仅需要读取,而此时的各自拷贝是没有意义的,而且会浪费内存和系统资源。
  2. fork 时,创建数据结构,如果还要将数据拷贝一份,那么 fork 的效率一定会降低。
  3. fork 本质就是向系统申请更多的内存资源,资源申请多了,fork 有可能就会失败。

在这里插入图片描述

相关文章:

【Linux系统编程】五、进程创建 -- fork()

文章目录 前言Ⅰ. 重温fork函数一、fork()的概念二、如何理解fork()有两个返回值 Ⅱ.fork的常规用法Ⅲ. fork调用失败的原因Ⅳ. 写时拷贝为什么存在写时拷贝❓❓❓ 前言 现阶段我们知道进程创建有如下两种方式&#xff0c;其实包括在以后的学习中这两种方式也是最常见的&#…...

深入解析 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个虚拟网络&#xff0c;相当于现实生活的交换机&#xff0c;名称vmnet0-vmnet19 没创建一个虚拟网络。对应在物理机会自动生成相应的虚拟网卡 该虚拟网卡用于和对应的虚拟网络中的虚拟机通信 二、虚拟网络的工作模式 1、nat模式 …...

神经辐射场(NeRF):从2D图像到3D场景的革命性重建

神经辐射场&#xff08;NeRF&#xff09;&#xff1a;从2D图像到3D场景的革命性重建 引言 在计算机视觉和图形学领域&#xff0c;如何从有限的2D图像中高效且准确地重建真实的3D场景&#xff0c;一直是一个重要的研究方向。传统的3D重建方法&#xff0c;如多视角几何、点云重建…...

深入解析AI技术原理

序言 在当今数字化时代,人工智能(AI)已经成为科技领域最炙手可热的话题之一。从智能家居到自动驾驶汽车,从医疗诊断到金融风险预测,AI的应用无处不在。然而,对于许多人来说,AI背后的技术原理仍然充满了神秘色彩。本文将深入探讨AI的核心技术原理,从基础理论到前…...

PDF 2.0 的新特性

近来闲来无事&#xff0c;就想着把PDF的新标准研究研究&#xff0c;略有所得&#xff0c;和大家分享一下。 ‌PDF 2.0的主要新特性包括更高级的加密算法、改进的数字签名和权限管理机制、增强了对非罗马字符的支持&#xff0c;以及扩展了标签架构和3D建模语言“PRC”的支…...

Matlab机械手碰撞检测应用

本文包含三个部分&#xff1a; Matlab碰撞检测的实现URDF文件的制作机械手STL文件添加夹爪 一.Matlab碰撞检测的实现 首先上代码 %% 检测在结构环境中机器人是否与物体之间发生碰撞情况&#xff0c;如何避免&#xff1f; % https://www.mathworks.com/help/robotics/ug/che…...

(root) Additional property include:is not allowed

参考&#xff1a;执行docker compose命令出现 Additional property include is not allowed_(root) additional property include is not allowed-CSDN博客 原因是docker-compose的版本太低&#xff0c;下载最新的替换即可。 第一次2.6.x版本改成了2.19.x不够高&#xff0c;所…...

react 18父子组件通信

在React 18中&#xff0c;父子组件之间的通信方式与之前的版本基本相同&#xff0c;主要可以通过以下几种方式实现&#xff1a; 1. Props&#xff08;属性&#xff09; 父组件向子组件传递数据&#xff1a; 父组件通过属性&#xff08;props&#xff09;向子组件传递数据&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 中&#xff0c;scope 属性用于指定依赖项的可见性及其在构建生命周期中的用途。不同的 scope 类型能够影响依赖项的编译和运行阶段。以下是 Maven 中常用的 scope 类型及其解析&#xff1a; compile&#xff08;默认值&#xff09;&#xff1a; 这是默认的作用域。如果…...

vue3:点击子组件进行父子通信

问&#xff1a; 子组件怎么和爷爷组件通信 回答&#xff1a; 在Vue 3中&#xff0c;子组件和爷爷组件之间的通信可以通过事件冒泡和状态管理来实现。你可以使用Vue的事件系统来传递事件&#xff0c;或者使用全局状态管理库如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部的珩智、昊天、薛超撰写&#xff0c;深入介绍了Java即时编译器&#xff08;JIT&#xff09;的原理及在美团的实践经验。 Java执行过程与即时编译器概述 Java执行过程&#xff1a;Java…...

使用 Ollama 在 Windows 环境部署 DeepSeek 大模型实战指南

文章目录 前言Ollama核心特性 实战步骤安装 Ollama验证安装结果部署 DeepSeek 模型拉取模型启动模型 交互体验命令行对话调用 REST API 总结个人简介 前言 近年来&#xff0c;大语言模型&#xff08;LLM&#xff09;的应用逐渐成为技术热点&#xff0c;而 DeepSeek 作为国产开…...

算法基础之八大排序

文章目录 概要1. 冒泡排序&#xff08;Bubble Sort&#xff09;2. 选择排序&#xff08;Selection Sort&#xff09;3. 插入排序&#xff08;Insertion Sort&#xff09;4. 希尔排序&#xff08;Shell Sort&#xff09;5. 归并排序&#xff08;Merge Sort&#xff09;6. 快速排…...

使用TensorFlow和Keras构建卷积神经网络:图像分类实战指南

使用TensorFlow和Keras构建卷积神经网络&#xff1a;图像分类实战指南 一、前言&#xff1a;为什么选择CNN进行图像分类&#xff1f; 在人工智能领域&#xff0c;图像分类是计算机视觉的基础任务。传统的机器学习方法需要人工设计特征提取器&#xff0c;而深度学习通过卷积神经…...

音频进阶学习十一——离散傅里叶级数DFS

文章目录 前言一、傅里叶级数1.定义2.周期信号序列3.表达式DFSIDFS参数含义 4.DFS公式解析1&#xff09;右边解析 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…...

地震勘探——干扰波识别、井中地震时距曲线特点

目录 干扰波识别反射波地震勘探的干扰波 井中地震时距曲线特点 干扰波识别 有效波&#xff1a;可以用来解决所提出的地质任务的波&#xff1b;干扰波&#xff1a;所有妨碍辨认、追踪有效波的其他波。 地震勘探中&#xff0c;有效波和干扰波是相对的。例如&#xff0c;在反射波…...

Unity3D中Gfx.WaitForPresent优化方案

前言 在Unity中&#xff0c;Gfx.WaitForPresent占用CPU过高通常表示主线程在等待GPU完成渲染&#xff08;即CPU被阻塞&#xff09;&#xff0c;这表明存在GPU瓶颈或垂直同步/帧率设置问题。以下是系统的优化方案&#xff1a; 对惹&#xff0c;这里有一个游戏开发交流小组&…...

【入坑系列】TiDB 强制索引在不同库下不生效问题

文章目录 背景SQL 优化情况线上SQL运行情况分析怀疑1:执行计划绑定问题?尝试:SHOW WARNINGS 查看警告探索 TiDB 的 USE_INDEX 写法Hint 不生效问题排查解决参考背景 项目中使用 TiDB 数据库,并对 SQL 进行优化了,添加了强制索引。 UAT 环境已经生效,但 PROD 环境强制索…...

【Redis技术进阶之路】「原理分析系列开篇」分析客户端和服务端网络诵信交互实现(服务端执行命令请求的过程 - 初始化服务器)

服务端执行命令请求的过程 【专栏简介】【技术大纲】【专栏目标】【目标人群】1. Redis爱好者与社区成员2. 后端开发和系统架构师3. 计算机专业的本科生及研究生 初始化服务器1. 初始化服务器状态结构初始化RedisServer变量 2. 加载相关系统配置和用户配置参数定制化配置参数案…...

MVC 数据库

MVC 数据库 引言 在软件开发领域,Model-View-Controller(MVC)是一种流行的软件架构模式,它将应用程序分为三个核心组件:模型(Model)、视图(View)和控制器(Controller)。这种模式有助于提高代码的可维护性和可扩展性。本文将深入探讨MVC架构与数据库之间的关系,以…...

oracle与MySQL数据库之间数据同步的技术要点

Oracle与MySQL数据库之间的数据同步是一个涉及多个技术要点的复杂任务。由于Oracle和MySQL的架构差异&#xff0c;它们的数据同步要求既要保持数据的准确性和一致性&#xff0c;又要处理好性能问题。以下是一些主要的技术要点&#xff1a; 数据结构差异 数据类型差异&#xff…...

Linux云原生安全:零信任架构与机密计算

Linux云原生安全&#xff1a;零信任架构与机密计算 构建坚不可摧的云原生防御体系 引言&#xff1a;云原生安全的范式革命 随着云原生技术的普及&#xff0c;安全边界正在从传统的网络边界向工作负载内部转移。Gartner预测&#xff0c;到2025年&#xff0c;零信任架构将成为超…...

Rust 异步编程

Rust 异步编程 引言 Rust 是一种系统编程语言,以其高性能、安全性以及零成本抽象而著称。在多核处理器成为主流的今天,异步编程成为了一种提高应用性能、优化资源利用的有效手段。本文将深入探讨 Rust 异步编程的核心概念、常用库以及最佳实践。 异步编程基础 什么是异步…...

Swagger和OpenApi的前世今生

Swagger与OpenAPI的关系演进是API标准化进程中的重要篇章&#xff0c;二者共同塑造了现代RESTful API的开发范式。 本期就扒一扒其技术演进的关键节点与核心逻辑&#xff1a; &#x1f504; 一、起源与初创期&#xff1a;Swagger的诞生&#xff08;2010-2014&#xff09; 核心…...

Go 并发编程基础:通道(Channel)的使用

在 Go 中&#xff0c;Channel 是 Goroutine 之间通信的核心机制。它提供了一个线程安全的通信方式&#xff0c;用于在多个 Goroutine 之间传递数据&#xff0c;从而实现高效的并发编程。 本章将介绍 Channel 的基本概念、用法、缓冲、关闭机制以及 select 的使用。 一、Channel…...