【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 恢复到系统…...
【Linux】shell脚本忽略错误继续执行
在 shell 脚本中,可以使用 set -e 命令来设置脚本在遇到错误时退出执行。如果你希望脚本忽略错误并继续执行,可以在脚本开头添加 set e 命令来取消该设置。 举例1 #!/bin/bash# 取消 set -e 的设置 set e# 执行命令,并忽略错误 rm somefile…...

Redis相关知识总结(缓存雪崩,缓存穿透,缓存击穿,Redis实现分布式锁,如何保持数据库和缓存一致)
文章目录 1.什么是Redis?2.为什么要使用redis作为mysql的缓存?3.什么是缓存雪崩、缓存穿透、缓存击穿?3.1缓存雪崩3.1.1 大量缓存同时过期3.1.2 Redis宕机 3.2 缓存击穿3.3 缓存穿透3.4 总结 4. 数据库和缓存如何保持一致性5. Redis实现分布式…...
【Java学习笔记】Arrays类
Arrays 类 1. 导入包:import java.util.Arrays 2. 常用方法一览表 方法描述Arrays.toString()返回数组的字符串形式Arrays.sort()排序(自然排序和定制排序)Arrays.binarySearch()通过二分搜索法进行查找(前提:数组是…...
基于服务器使用 apt 安装、配置 Nginx
🧾 一、查看可安装的 Nginx 版本 首先,你可以运行以下命令查看可用版本: apt-cache madison nginx-core输出示例: nginx-core | 1.18.0-6ubuntu14.6 | http://archive.ubuntu.com/ubuntu focal-updates/main amd64 Packages ng…...

【第二十一章 SDIO接口(SDIO)】
第二十一章 SDIO接口 目录 第二十一章 SDIO接口(SDIO) 1 SDIO 主要功能 2 SDIO 总线拓扑 3 SDIO 功能描述 3.1 SDIO 适配器 3.2 SDIOAHB 接口 4 卡功能描述 4.1 卡识别模式 4.2 卡复位 4.3 操作电压范围确认 4.4 卡识别过程 4.5 写数据块 4.6 读数据块 4.7 数据流…...

2.Vue编写一个app
1.src中重要的组成 1.1main.ts // 引入createApp用于创建应用 import { createApp } from "vue"; // 引用App根组件 import App from ./App.vue;createApp(App).mount(#app)1.2 App.vue 其中要写三种标签 <template> <!--html--> </template>…...
全面解析各类VPN技术:GRE、IPsec、L2TP、SSL与MPLS VPN对比
目录 引言 VPN技术概述 GRE VPN 3.1 GRE封装结构 3.2 GRE的应用场景 GRE over IPsec 4.1 GRE over IPsec封装结构 4.2 为什么使用GRE over IPsec? IPsec VPN 5.1 IPsec传输模式(Transport Mode) 5.2 IPsec隧道模式(Tunne…...

Linux --进程控制
本文从以下五个方面来初步认识进程控制: 目录 进程创建 进程终止 进程等待 进程替换 模拟实现一个微型shell 进程创建 在Linux系统中我们可以在一个进程使用系统调用fork()来创建子进程,创建出来的进程就是子进程,原来的进程为父进程。…...
在Ubuntu24上采用Wine打开SourceInsight
1. 安装wine sudo apt install wine 2. 安装32位库支持,SourceInsight是32位程序 sudo dpkg --add-architecture i386 sudo apt update sudo apt install wine32:i386 3. 验证安装 wine --version 4. 安装必要的字体和库(解决显示问题) sudo apt install fonts-wqy…...
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.登…...