【C++高并发服务器WebServer】-1:Linux中父子进程fork创建及关系、GDB多进程调试

本文目录
- 一、进程创建
- 二、GDB多进程调试
一、进程创建
在Linux中输入man 2 fork可以查看man文档中的fork的相关函数信息。
fork的作用就是创建一个子进程。

通过fork我们可以知道,创建子进程的时候,复制父进程的信息。
我们看看翻译的man文档信息:
- 子进程和父进程在独立的内存空间中运行。在调用 fork() 时,两个内存空间的内容是相同的。其中一个进程进行的写操作、文件映射以及取消映射不会影响另一个进程。
首先我们来说说,在 fork() 调用时,子进程和父进程的内存空间内容是相同的,这是因为 fork() 的实现机制是通过“写时复制”(Copy-On-Write,简称 COW)来实现的。
- 内存空间的复制
首先,fork() 的目标是创建一个与父进程几乎完全相同的子进程。为此,它需要复制父进程的内存空间,包括代码段、数据段、堆和栈等。在调用 fork() 的瞬间,子进程的内存空间内容与父进程完全一致。当然,上面两句话指的是虚拟地址空间。
在 fork() 后,父子进程会有相同的虚拟地址空间映射,父进程的数据和堆栈的内容在物理内存中不会立即复制。实际上,操作系统使用了页表(paging)来标记这些内存页面为只读,且标记为共享的。
- 写时复制(Copy-On-Write)机制
虽然内存空间的内容在 fork() 时是相同的,但实际的物理内存并没有完全复制。操作系统采用了“写时复制”技术来优化资源使用。
在 fork() 调用后,父进程和子进程共享相同的物理内存页面。只有当父进程或子进程中的任意一个对内存进行写操作时,操作系统才会真正复制该页面的内容到一个新的物理页面,并将修改后的页面分配给进行写操作的进程。如果父进程和子进程都没有对内存进行写操作,它们将继续共享相同的物理页面。
当父进程或子进程尝试修改某一共享的内存页时,操作系统才会进行实际的物理内存复制。这意味着只有在进程写入某个页面时,内核才会为该页面分配新的物理内存,并将原页面的内容复制到新的物理内存位置,从而保证父子进程的内存内容互不影响。
所以由于 fork() 的目标是创建一个与父进程完全相同的子进程,因此在调用 fork() 的瞬间,子进程的内存空间内容必须与父进程一致。这是通过逻辑上的“复制”实现的(虚拟地址空间),而物理内存的实际复制则通过写时复制机制延迟到真正需要修改内存时才进行。
- “运行在不同的内存空间”(The child process and the parent process run in separate memory spaces)
这句话的核心在于“内存空间”(memory space)的概念。内存空间指的是进程的虚拟内存空间(virtual memory space),而不是物理内存(physical memory)。每个进程都有自己的虚拟内存空间,这是操作系统为进程分配的逻辑地址空间。虚拟内存空间是独立的,每个进程都无法直接访问其他进程的虚拟内存空间。这种隔离是现代操作系统实现进程隔离和保护的基础。
当 fork() 创建子进程时,子进程会获得一个全新的虚拟内存空间。虽然这个虚拟内存空间的内容在创建时与父进程的虚拟内存空间内容相同,但它们是完全独立的。子进程无法直接访问父进程的虚拟内存空间,反之亦然。
虽然父进程和子进程运行在不同的虚拟内存空间中,但它们的内存内容在 fork() 调用时是相同的。一是因为逻辑上的复制:在 fork() 调用时,子进程的虚拟内存空间被初始化为与父进程的虚拟内存空间内容一致。这意味着子进程的代码段、数据段、堆和栈等在逻辑上与父进程相同。
二是因为写时复制(Copy-On-Write):操作系统通过写时复制技术优化了内存的使用。虽然虚拟内存空间的内容在逻辑上是相同的,但物理内存页面并不会立即复制。只有当父进程或子进程对某个页面进行写操作时,操作系统才会真正复制该页面的内容到新的物理内存中。所以只需要明确:在 fork() 调用时,子进程的虚拟内存空间的内容与父进程相同,但这种相同是逻辑上的,物理内存的复制是通过写时复制机制实现的。
在linux中新建c++文件,运行下面代码,gcc fork.c -o fork和./fork。
/*pid_t fork(void):作用:创建子进程。返回值;fork会返回两次:一次在父进程中,一次在子进程中。在父进程中,返回子进程的ID;在子进程中,返回0.如何区分父进程和子进程,就可以通过fork返回值。如果在父进程中返回-1,表示创建子进程失败,并且设置对应的error(当系统的进程数达到了系统上限或者内存不足的时候,就会失败)*/# include<sys/types.h>
# include<unistd.h>
# include<stdio.h>
int main(){pid_t pid = fork(); //这个pid是int类型的//因为fork会返回两个,分别对应两个进程//判断是父进程还是子进程if (pid>0){printf("pid: %d\n", pid);printf("我是父进程,pid : %d,ppid: %d\n",getpid(),getppid());}else if(pid==0){printf("我是子进程,pid : %d,ppid: %d\n",getpid(),getppid());}for(int i=0;i<5;i++){printf("i : %d, pid : %d \n",i,getpid());sleep(1);}return 0;
}
输出如下:
当前的pid是fork程序33228,ppid是32535(这个是我的连接linux终端bash的进程id,然后子进程id是33228,能够对应上。)
pid: 33228
我是父进程,pid : 33227,ppid: 32535
i : 0, pid : 33227
我是子进程,pid : 33228,ppid: 33227
i : 0, pid : 33228
i : 1, pid : 33227
i : 1, pid : 33228
i : 2, pid : 33227
i : 2, pid : 33228
i : 3, pid : 33227
i : 3, pid : 33228
i : 4, pid : 33228
i : 4, pid : 33227
从上面也可以看出,是父进程和子进程交替在运行for循环,从这一点也可以看出cpu是时间片分给父子进程交替运行。
并且父子进程都在运行for,也可以看出父子进程共享了代码。
下图是父子进程虚拟地址空间示意图,fork以后,子进程的用户区数据和父进程一样(因为是拷贝过来的),内核区也会拷贝过来,但是内核区的pid不一样。
父进程执行 pid_t pid = fork() 得到的返回值,是在栈空间,得到的返回值是子进程的pid(比如33228),但是子进程clone得到的虚拟地址空间中的栈空间所得到的pid是0,但是linux内核中子进程的pid是33228(注意返回的id和自身的pid)。

虚拟地址空间(Virtual Address Space)是现代计算机操作系统中一个非常重要的概念,它是操作系统为每个进程分配的逻辑地址空间。简单来说,虚拟地址空间是一个进程能够访问的内存地址范围,但这些地址并不是直接映射到物理内存(实际的硬件内存)的地址,而是通过操作系统和硬件的联合管理,映射到物理内存或其他存储资源的逻辑地址。
虚拟地址空间通常由以下几部分组成:
代码段(Code Segment):存放程序的可执行代码。
数据段(Data Segment):存放程序的全局变量和静态变量。
堆(Heap):用于动态分配内存(如通过 malloc() 或 new 分配的内存)。
栈(Stack):用于存储函数调用时的局部变量、函数参数和返回地址等。
共享库(Shared Libraries):存放程序运行时加载的动态链接库(如 .so 文件)。
未映射区域(Unmapped Regions):未分配给任何用途的内存区域,通常用于未来扩展。
虚拟地址空间是现代操作系统实现内存管理、进程隔离和内存保护的核心机制。它使得每个进程都能在一个独立的、安全的环境中运行,同时允许操作系统灵活地管理物理内存资源。
简单来说,虚拟地址空间是一个进程的“私有内存世界”,它通过操作系统和硬件的支持,将逻辑地址与物理地址分离,从而实现高效的内存管理和进程隔离。
所以总结一下就是:内核此时并不复制整个进程的地址空间,而是让父子进程共享同一个地址空间,只要需要写入的时候才会复制地址空间,从而使得各个进程拥有各自的地址空间,也就是说,资源的复制是在写入的时候才会进行(读时共享,写时拷贝)。fork产生的子进程与父进程相同的文件描述符指向相同的文件夹,且用户区的数据相同。
二、GDB多进程调试
使用GDB进行调试的时候, 默认只能跟踪一个进程,可以在fork函数调用之前,通过指令设置GDB调试工具跟踪父进程或者跟踪子进程,默认跟踪父进程。
set follow-fork-mode [parent(默认)|child] 命令是设置调试父进程或者子进程。
设置调试模式:`set detach-on-fork [on|off],默认为on,表示调试当前进程的时候其他进程继续运行,如果为off,则调试当前进程的时候,其他进程被GDB挂起。
查看调试的进程:info inferiors, 切换当前调试的进程:inferior id,使进程脱离GDB调试:detech inferiors id。
# include<sys/types.h>
# include<unistd.h>
# include<stdio.h>
int main(){printf("begin\n");//判断是父进程还是子进程if (fork()>0){printf("我是父进程,pid : %d,ppid: %d\n",getpid(),getppid());int i;for(i=0;i<10;i++){printf("i=%d\n",i);sleep(1);}}else{printf("我是子进程,pid : %d,ppid: %d\n",getpid(),getppid());int j;for(j=0;j<10;j++){printf("i=%d\n",j);sleep(1);}}return 0;
}
输入命令进行编译调试:gcc hello.c -o hello -g,然后通过gdb hello进行调试。
在12行、19行插入断点。


然后输入r、n(next)、n,就会发现下面的调试过程,也就是gdb默认调试父进程,然后子进程的代码会走掉。

在gdb环境下,通过命令show follow-fork-mode可以查看现有的fork模式,我们通过set follow-fork-mode child来跟踪子进程。
这个时候再继续run,就会发现断点在19行停住了。

通过info inferiors可以查看调试进程,并且inferior 2切换调试的进程。

相关文章:
【C++高并发服务器WebServer】-1:Linux中父子进程fork创建及关系、GDB多进程调试
本文目录 一、进程创建二、GDB多进程调试 一、进程创建 在Linux中输入man 2 fork可以查看man文档中的fork的相关函数信息。 fork的作用就是创建一个子进程。 通过fork我们可以知道,创建子进程的时候,复制父进程的信息。 我们看看翻译的man文档信息&am…...
C语言数组详解:从基础到进阶的全面解析
在C语言中,数组是一种基本的数据结构,用于存储多个相同类型的数据。数组的引入使得C语言能够高效地存储和操作大量数据。在任何一个C语言程序中,数组都发挥着极其重要的作用。无论是在算法实现、数据存储、还是在复杂程序的设计中,…...
docker的前世今生
docker来自哪里? 从我们运维部署的历史来看,宿主机从最初的物理机到虚拟机,再到docker,一步步演进到现在。技术演进其实是为了解决当前技术的痛点,那我们来看看有哪些痛点以及如何克服痛点的。 物理机 一般来说&…...
python实现施瓦茨-克里斯托费尔【全网首个】根据用户输入推测函数
上代码: from sympy import symbols, integrate, simplify from sympy.plotting import plotn int(input("n:")) if n < 2:print("Error: Must n > 2") i 0 a [] aef [] A [] x, y symbols(x y) z, w symbols(z w)while i < n…...
c语言中的数组(上)
数组的概念 数组是⼀组相同类型元素的集合; 数组中存放的是1个或者多个数据,但是数组元素个数不能为0。 数组中存放的多个数据,类型是相同的。 数组分为⼀维数组和多维数组,多维数组⼀般⽐较多⻅的是⼆维数组。 数组创建 在C语言…...
Unity3D仿星露谷物语开发25之创建时钟界面
1、目标 在时钟界面显示当前时钟信息,同时设置特殊按钮可以快速推进时间用于测试。 2、创建GameClock.cs脚本 在Assets -> Scripts -> TimeSystem目录下创建GameClock.cs脚本。 代码如下: using System.Collections; using System.Collections…...
数据结构测试题1
一、选择题: 1.若长度为n的钱性表采用顺序存储结构,删除它的第i数据元素之前,需要先依次向前移动( )个数据元素。( C ) A .n-i B.ni C.n-i-1 D.n-i1 2.在单链表中,已知q指的结点是p指的结点的直接前驱结点&am…...
android wifi AsyncChannel(WifiManager和WifiP2pManager)
AynscChannel的讲解 [Android]AsyncChannel介绍-CSDN博客 WifiP2pManager里的channel的使用理解 WifiP2pManager.java public void createGroup(Channel c, ActionListener listener) {checkChannel(c);c.mAsyncChannel.sendMessage(CREATE_GROUP, WifiP2pGroup.NETWORK_ID_PE…...
【Image Captioning】DynRefer
DynRefer是由中国科学院大学于2024年提出的用于1种用于区域级多模态任务的模型。DynRefer 通过模拟人类视觉认知过程,显著提升了区域级多模态识别能力。通过引入人眼的动态分辨率机制, 能够以同时完成区域识别、区域属性检测和区域字幕生成任务。 文章链…...
Midjourney基础-常用修饰词+权重的用法大全
用好修饰词很关键 Midjourney要用除了掌握好提示词的写法,按照上一篇《做Midjourney最好图文教程-提示词公式以及高级参数讲解》画面主体 场景氛围 主体行为 构图方式 艺术风格 图像质量。 要画出有质感的内容我们必须要掌握好“修饰词”,这些修饰…...
没有屋檐的房子-023粪堆旁边的舞蹈
爱美是天性,贫苦的农村人也一样,贫苦的时代也一样。 本世纪,广场舞在华夏大地遍地开花,甚至都传到了外面。但是广场舞这种舞蹈形式并不是互联网时代的特产,也不是电声设备日益高级和普及时代的特产,更不是大…...
基于Docker的Kafka分布式集群
目录 1. 说明 2. 服务器规划 3. docker-compose文件 kafka{i}.yaml kafka-ui.yaml 4. kafka-ui配置集群监控 5. 参数表 6. 测试脚本 生产者-异步生产: AsyncKafkaProducer1.py 消费者-异步消费: AsyncKafkaConsumer1.py 7. 参考 1. 说明 创建一个本地开发环境所需的k…...
【博客之星】年度总结:在云影与墨香中探寻成长的足迹
🐇明明跟你说过:个人主页 🔖行路有良友,便是天堂🔖 目录 一、年度回顾 1、创作历程 2、个人成长 3、个人生活与博客事业 二、技术总结 1、赛道选择 2、技术工具 3、实战项目 三、前景与展望 1、云原生未来…...
SpringBoot的Swagger配置
一、Swagger配置 1.添加依赖 <dependency><groupId>com.github.xiaoymin</groupId><artifactId>knife4j-spring-boot-starter</artifactId><version>3.0.2</version> </dependency> 2.修改WebMvcConfig Slf4j Configurat…...
machine learning knn算法之使用KNN对鸢尾花数据集进行分类
通过导入必要的scikit-learn导入必要的库,加载给定的数据,划分测试集和训练集之后训练预测和评估即可 具体代码如下: import numpy as np from sklearn.datasets import load_iris from sklearn.model_selection import train_test_split f…...
C语言练习(16)
猴子吃桃问题。猴子第一天摘下若干个桃子,当即吃了一半,还不过瘾,又多吃了一个。第二天早上又将剩下的桃子吃掉一半,又多吃了一个。以后每天早上都吃了前一天剩下的一半加一个。到第10天早上想再吃时,见只剩一个桃子了…...
SOAFEE 技术研讨会:汽车软件定义与自动驾驶技术探讨
在本次技术研讨会上,来自汽车与科技领域的专家们围绕汽车软件定义及自动驾驶技术展开了深入交流与探讨。从 SOAFEE 蓝图计划的创新性理念,到 Autoware 开源项目及 Open AD Kit 在实际应用中的探索,再到 Edge Workload Abstraction and Orches…...
R语言学习笔记之开发环境配置
一、概要 整个安装过程及遇到的问题记录 操作步骤备注(包含遇到的问题)1下载安装R语言2下载安装RStudio3离线安装pacman提示需要安装Rtools4安装Rtoolspacman、tidyfst均离线安装完成5加载tidyfst报错 提示需要安装依赖,试错逐步下载并安装…...
多版本并发控制:MVCC的作用和基本原理
多版本并发控制:MVCC的作用和基本原理 1、MVCC简介1.1 快照读与当前读的区别1.1.1 快照读1.1.2 当前读 1.2 数据库的读写问题1.3 MVCC的作用 2、MVCC实现原理之ReadView2.1 什么是ReadView2.2 ReadView的设计思路2.3 MVCC整体操作流程 1、MVCC简介 1.1 快照读与当前…...
ubuntu18.04安装nvm管理本机node和npm
ubuntu18.04安装nvm管理本机node和npm nvm的使用方法1. 安装nvm2. 加载nvm3. 安装执行版本4. 设置默认版本(可选)5. 检查:6. 将配置加入到shell配置文件中(默认已经加入) 如果系统全局的 Node.js 存在,但被 nvm 覆盖了,可以通过禁用或卸载 nvm 恢复到系统…...
web vue 项目 Docker化部署
Web 项目 Docker 化部署详细教程 目录 Web 项目 Docker 化部署概述Dockerfile 详解 构建阶段生产阶段 构建和运行 Docker 镜像 1. Web 项目 Docker 化部署概述 Docker 化部署的主要步骤分为以下几个阶段: 构建阶段(Build Stage):…...
超短脉冲激光自聚焦效应
前言与目录 强激光引起自聚焦效应机理 超短脉冲激光在脆性材料内部加工时引起的自聚焦效应,这是一种非线性光学现象,主要涉及光学克尔效应和材料的非线性光学特性。 自聚焦效应可以产生局部的强光场,对材料产生非线性响应,可能…...
【OSG学习笔记】Day 18: 碰撞检测与物理交互
物理引擎(Physics Engine) 物理引擎 是一种通过计算机模拟物理规律(如力学、碰撞、重力、流体动力学等)的软件工具或库。 它的核心目标是在虚拟环境中逼真地模拟物体的运动和交互,广泛应用于 游戏开发、动画制作、虚…...
Java 8 Stream API 入门到实践详解
一、告别 for 循环! 传统痛点: Java 8 之前,集合操作离不开冗长的 for 循环和匿名类。例如,过滤列表中的偶数: List<Integer> list Arrays.asList(1, 2, 3, 4, 5); List<Integer> evens new ArrayList…...
全球首个30米分辨率湿地数据集(2000—2022)
数据简介 今天我们分享的数据是全球30米分辨率湿地数据集,包含8种湿地亚类,该数据以0.5X0.5的瓦片存储,我们整理了所有属于中国的瓦片名称与其对应省份,方便大家研究使用。 该数据集作为全球首个30米分辨率、覆盖2000–2022年时间…...
[ICLR 2022]How Much Can CLIP Benefit Vision-and-Language Tasks?
论文网址:pdf 英文是纯手打的!论文原文的summarizing and paraphrasing。可能会出现难以避免的拼写错误和语法错误,若有发现欢迎评论指正!文章偏向于笔记,谨慎食用 目录 1. 心得 2. 论文逐段精读 2.1. Abstract 2…...
基础测试工具使用经验
背景 vtune,perf, nsight system等基础测试工具,都是用过的,但是没有记录,都逐渐忘了。所以写这篇博客总结记录一下,只要以后发现新的用法,就记得来编辑补充一下 perf 比较基础的用法: 先改这…...
重启Eureka集群中的节点,对已经注册的服务有什么影响
先看答案,如果正确地操作,重启Eureka集群中的节点,对已经注册的服务影响非常小,甚至可以做到无感知。 但如果操作不当,可能会引发短暂的服务发现问题。 下面我们从Eureka的核心工作原理来详细分析这个问题。 Eureka的…...
STM32---外部32.768K晶振(LSE)无法起振问题
晶振是否起振主要就检查两个1、晶振与MCU是否兼容;2、晶振的负载电容是否匹配 目录 一、判断晶振与MCU是否兼容 二、判断负载电容是否匹配 1. 晶振负载电容(CL)与匹配电容(CL1、CL2)的关系 2. 如何选择 CL1 和 CL…...
微服务通信安全:深入解析mTLS的原理与实践
🔥「炎码工坊」技术弹药已装填! 点击关注 → 解锁工业级干货【工具实测|项目避坑|源码燃烧指南】 一、引言:微服务时代的通信安全挑战 随着云原生和微服务架构的普及,服务间的通信安全成为系统设计的核心议题。传统的单体架构中&…...
