目录

一、三级缓存和内存布局

二、CPU架构

(1)SMP对称对处理器架构

(2)NUMA非统一内存架构

三、RCU机制在内核中的体现

四、内存优化屏障

(1)编译器、CPU优化

(2)优化的问题和解决办法

(3)volatile关键字


        在学习Linux或者c++的时候,经常会看到缓存、内存、寄存器这样的字眼。只知道他们都是用于存储数据、指令的,且有效率之分,但是具体的框图并不了解。本篇文章以此为基础,延伸到CPU的架构等问题。

一、三级缓存和内存布局

        我们知道当前主流的计算机是符合冯诺依曼体系架构的。内存位于所有结构的中心,任何外设想要进行数据交换都必须先把数据、指令交给内存,然后分别从内存中读取。

        如下图所示:

有了这样一份基础架构,我们就能更好的理解CPU运行的时候,是如何从内存得到指令和数据的。

        即调度器把某个进程的PCB给到CPU,然后CPU根据PCB里面的地址找到物理内存要数据、指令,通过三级缓存和寄存器逐级交付,最终传递给CPU的过程。

二、CPU架构

(1)SMP对称对处理器架构

        SMP(Symmetric Multi - Processing)架构中,多个 CPU 核心共享统一的内存空间、I/O 设备等系统资源,并且所有 CPU 核心的地位平等,它们可以无差别地访问内存、外设等,操作系统可以将任务动态分配到任意一个 CPU 核心上执行。

优点

 
  • 易于编程:对于开发者来说,SMP 架构下的编程模型相对简单,因为所有 CPU 核心对系统资源的访问方式基本相同,不需要考虑太多复杂的资源分配和访问差异问题。
  • 负载均衡:操作系统能够方便地在各个 CPU 核心之间进行任务调度,实现负载均衡,充分利用各个核心的计算能力。
 

缺点

 
  • 内存访问瓶颈:随着 CPU 核心数量的增加,所有核心都访问同一个内存空间,会导致内存总线的竞争加剧,从而形成性能瓶颈。
  • 可扩展性受限:由于共享内存等资源的限制,当核心数量增加到一定程度时,性能提升不明显甚至会下降。

(2)NUMA非统一内存架构

        NUMA(Non - Uniform Memory Access)架构中,多个 CPU 核心被划分成不同的节点,每个节点都有自己的本地内存,同时也可以访问其他节点的内存,但访问本地内存的速度要比访问远程(其他节点)内存的速度快。操作系统需要考虑内存分配与 CPU 核心的位置关系,以提高性能。

优点

 
  • 高可扩展性:通过将内存分配到不同节点,减少了内存访问冲突,使得系统在增加 CPU 核心数量时,依然能够保持较好的性能扩展性。
  • 局部性原理利用:可以将数据和任务分配到对应的节点,充分利用本地内存访问速度快的优势,提高整体性能。
 

缺点

 
  • 编程复杂度增加:开发者需要考虑内存的本地性问题,在编程时需要手动管理内存分配,以确保数据在合适的节点上,否则可能导致性能下降。
  • 管理复杂:操作系统需要更复杂的资源管理和调度策略,来平衡各个节点的负载和优化内存访问。

三、RCU机制在内核中的体现

        RCU本质只是一种提高读写效率的锁。但是在Linux中广泛的用到。比如在task_struct中,我们曾说到有进程的ID、调度策略、用户地址空间指针等等各种成员。内核中存在着大量的链表结构,无论是调度器就绪队列,还是全局进程链表、子进程链表。这些链表操作往往涉及到大量的读,而对写性能要求不高。此时RCU机制就能发挥到极致。

        下面我们来看看内核中的RCU机制引申出的链表rcu操作。

// 安全的节点插入(使用内存屏障)
void safe_add_node(struct my_node *new) {// 1. 完成所有数据初始化new->data = 100;// 2. 使用写内存屏障(确保之前的写操作对其他CPU可见)smp_wmb();// 3. 原子更新链表指针(使新节点对读操作可见)list_add_tail_rcu(&new->list, &my_list);
}// 安全的节点删除(使用内存屏障)
void safe_delete_node(struct my_node *node) {// 1. 原子更新链表指针(从链表移除节点)list_del_rcu(&node->list);// 2. 使用读内存屏障(确保后续同步操作的顺序)smp_rmb();// 3. 等待宽限期结束(确保没有读操作引用旧节点)synchronize_rcu();// 4. 安全释放内存kfree(node);
}// 安全的链表遍历(无锁读)
void safe_traverse(void) {struct my_node *node;rcu_read_lock();  // 标记读临界区开始// 使用 RCU 安全遍历宏(确保指针解引用安全)list_for_each_entry_rcu(node, &my_list, list) {// 读操作期间,数据可能被修改,但保证可见性顺序printk("Data: %d\n", node->data);}rcu_read_unlock();  // 标记读临界区结束
}

        可以看到这个rcu插入链表节点的函数,不仅仅是更新了链表的节点,还调用了内存屏障函数,保证了编译器和CPU的优化不会乱序,让别的进程要么看到数据最新的新节点,要么看不到该节点,不存在已经插入到链表中而数据后更新的问题。

四、内存优化屏障

(1)编译器、CPU优化

        我们写好了一个程序交给编译器编译、或者交给CPU运行的时候,可能与我们想象中一条条地执行不同,无论是编译器还是CPU都会采取一定的优化策略,让性能进一步提高。

        比如编译器优化:把显而易见的代码直接求出结果,编译成二进制。这样能减少CPU运行的时间。

优化原理

        把显而易见的步骤直接在编译环节得到结果,减少CPU运行次数。

 
  • 减少循环次数(原循环执行 n 次,展开后执行 n/4 次),降低分支预测失败和循环开销(如计数器更新、条件判断)。
  • 增加指令级并行(ILP):多条加法指令可同时在 CPU 流水线中执行。

        再比如CPU运行优化:

优化原理

        CPU从内存读取一个数据需要时间,这个时间如果傻等就白白浪费了,所以选择同时读取一堆,重叠掉这个等待时间。

 
  • SIMD(单指令多数据):利用 CPU 的向量寄存器(如 AVX2 的 256 位寄存器)同时处理多个数据。
  • 数据并行:一条指令完成 8 个浮点数加法,吞吐量提升 8 倍(理论值)。

(2)优化的问题和解决办法

        CPU和编译器的乱序优化,本质上是为了提高运行效率。但是也导致了一个结果:可能导致不同线程错误的读到类似空指针的错误。

举个例子:

// 期望的链表插入函数
void incorrect_add_node(struct my_node *new) {// 初始化新节点new->data = 42;// 设置数据字段(可能被重排序到指针更新之后)new->data = 100;  // 危险:可能在节点可见后才被更新// 将新节点连接到链表中list_add_tail_rcu(&new->list, &my_list);}

被优化后的:

// 错误的链表插入函数(无内存屏障)
void incorrect_add_node(struct my_node *new) {// 初始化新节点new->data = 42;// 先将新节点连接到链表中list_add_tail_rcu(&new->list, &my_list);// 然后设置数据字段(可能被重排序到指针更新之后)new->data = 100;  // 危险:可能在节点可见后才被更新
}

即使关闭了所有的优化,还是有可能出现问题:

正确的做法是在中间部分添加内存屏障:这里保证了先初始化数据,再更新链表指针。让其他线程要么看不到更新的链表节点,要么看到就是数据已经更新的完全体。

// 正确的链表插入函数(使用内存屏障)
void correct_add_node(struct my_node *new) 
{// 1. 先完成所有数据初始化new->data = 100;// 2. 使用写内存屏障确保数据初始化对其他CPU可见smp_wmb();  // 写内存屏障,确保之前的写操作都完成// 3. 最后更新链表指针,使新节点对读操作可见list_add_tail_rcu(&new->list, &my_list);
}

        最后,Linux中的内存屏障有许多种,其中通用屏障开销最大,我们最好根据请款选择合适的内存屏障。

(3)volatile关键字

        正如我们上面所说的优化有乱序的问题,CPU在读取某个变量的值的时候也会存在优化。当寄存器中恰好存储的就是该变量的值的时候(频繁访问某一个变量很有可能出现这种情况),cpu往往会直接从该寄存器中读取。

        而寄存器更新的原则是覆盖式更新,即读新的数据,寄存器中原本没有,于是从L1、L2等缓存中覆盖写入到寄存器。如果该变量刚好就在寄存器,则可能读到旧值(缓存中的值已经发生了改变)。

        volatile关键字的作用就是,禁止编译器把变量缓存到寄存器中,强制要求cpu每次必须在内存/缓存中读取该变量的值,从而保证了值的实时性。

        不过他仅仅是解决 “编译器和硬件缓存导致的旧值读取” 的轻量级工具,但仅适用于简单场景。复杂的多线程 / 硬件同步,必须用更强大的原语(原子操作、内存屏障、锁)。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。
如若转载,请注明出处:http://www.pswp.cn/bicheng/89380.shtml
繁体地址,请注明出处:http://hk.pswp.cn/bicheng/89380.shtml
英文地址,请注明出处:http://en.pswp.cn/bicheng/89380.shtml

如若内容造成侵权/违法违规/事实不符,请联系英文站点网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

HarmonyOS从入门到精通:动画设计与实现之九 - 实用动画案例详解(下)

HarmonyOS动画开发实战(九):实用动画案例详解(下) 在上篇中,我们围绕加载动画、点赞反馈、下拉刷新等核心交互场景,探讨了如何通过动画提升用户体验。本篇将聚焦界面元素动效与特殊场景动画&…

Node.js 聊天内容加密解密实战教程(含缓存密钥优化)

一、技术方案说明 本方案采用以下技术组合: 加密算法:AES-256-GCM(认证加密,防止篡改)密钥派生:PBKDF2(10万次迭代)缓存机制:内存缓存 定期轮换安全特性:随机…

信息安全基础专业面试知识点(上:密码学与软件安全)

密码学DES加密流程56比特长度的密钥K, 分组长度64比特,密文64比特初始置换 (IP):将输入的64位明文块进行置换,打乱其顺序。分成左右两半: 将置换后的64位数据分成左右两部分,每部分32位。16轮迭代加密: 这是DES的核心&#xff0c…

Windows Server 2025 黄金dMSA攻击漏洞:跨域攻击与持久化访问风险分析

网络安全研究人员近日披露了Windows Server 2025中委托管理服务账户(dMSA,Delegated Managed Service Accounts)存在的"关键设计缺陷"。据Semperis公司向The Hacker News提供的报告显示:"该漏洞可能导致高危害攻击…

解锁数据分析:从基础概念到核心指标的全面指南

在数字化时代,数据已成为驱动业务决策的核心力量。无论是运营一款 APP、管理一家便利店,还是优化在线教育课程,理解数据的本质与关键指标都至关重要。本文将从数据的基本概念出发,拆解运营全流程中的核心指标,并分享数…

DiffPy-CMI详细安装教程

本章教程,主要记录安装DiffPy-CMI的具体安装步骤。 DiffPy-CMI 是一个复杂建模框架,是高度灵活的 Python 模块库,专为晶体、纳米材料及非晶态材料的纳米结构建模而设计。 注意:DiffPy-CMI只支持在Linux和Mac上安装,Windows上是不支持的。 一、准备工作 需要准备一台Linux或…

中国各省市县坡度数据(Tif/Excel)

数据简介 昨天我们分享了中国120m精度的DEM数据(见前文),今天我们根据该数据计算中国的坡度数据,并根据中国省市县行政区划数据将其统计各省市县坡度的最大、最小以及平均值,方便大家研究使用。 基于中国120米精度DEM生成的坡度数据&#xff…

09-three.js Materials

Three.js Journey — Learn WebGL with Three.jsThe ultimate Three.js course whether you are a beginner or a more advanced developerhttps://threejs-journey.com/?cp3 MeshBasicMaterial 添加3个网格体: /*** Object*/ // MashBasicMaterial const mater…

Netty介绍和基本代码演示

什么是Netty?Netty是一个基于Java NIO的异步事件驱动的网络应用框架,主要用于快速开发高性能、高可靠性的网络服务器和客户端程序。它简化了网络编程的复杂性,提供了丰富的协议支持,被广泛应用于各种高性能网络应用中。为什么选择…

[BrowserOS] Nxtscape浏览器核心 | 浏览器状态管理 | 浏览器交互层

第三章:Nxtscape浏览器核心 欢迎回来! 在前两章中,我们了解了名为专用AI代理的专家团队及其管理者AI代理协调器,它们协同解析需求并规划执行步骤。 但这些代理与协调器实际运行的平台是什么?答案正是本章的核心——…

时序数据库处理的时序数据独特特性解析

时序数据(Time-Series Data)作为大数据时代增长最快的数据类型之一,正在物联网、金融科技、工业监控等领域产生爆炸式增长。与传统数据相比,时序数据具有一系列独特特性,这些特性直接影响了时序数据库(Time…

uniapp各端通过webview实现互相通信

目前网上,包括官方文档针对uniapp的webview的内容都是基于vue2的,此文章基于vue3的composition API方式网页对网页 由于uniapp中的webview只支持引入h5页面,不支持互相通信,所以要条件编译,用iframe导入页面&#xf…

【Vue】tailwindcss + ant-design-vue + vue-cropper 图片裁剪功能(解决遇到的坑)

1.安装 vue-cropper pnpm add vue-cropper1.1.12.使用 vue-cropper <template><div class"user-info-head" click"editCropper()"><img :src"options.img" title"点击上传头像" class"img-circle" /><…

【Java】【力扣】101.对称二叉树

思路递归大问题&#xff1a;对比 左 右 是否对称参数 左和右todo 先凑合看代码/*** Definition for a binary tree node.* public class TreeNode {* int val;* TreeNode left;* TreeNode right;* TreeNode() {}* TreeNode(int val) { this.val val; }* …

前端 oidc-client 静默刷新一直提示:Error: Frame window timed out 问题分析与解决方案

引言 在现代前端开发中&#xff0c;OAuth 2.0 和 OpenID Connect (OIDC) 协议已成为身份验证和授权的标准解决方案。oidc-client-js 是一个流行的 JavaScript 库&#xff0c;用于在前端应用中实现 OIDC 协议。其中&#xff0c;静默刷新&#xff08;Silent Renew&#xff09;是一…

DAY02:【ML 第一弹】KNN算法

一、算法简介 1.1 算法思想 如果一个样本在特征空间中的 k 个最相似的样本中的大多数属于某一个类别&#xff0c;则该样本也属于这个类别。 1.2 样本相似性 样本都是属于一个任务数据集的&#xff0c;样本距离越近则越相似。 二维平面上点的欧氏距离 二维平面上点 a(x1,y1)a(x_…

wpf 实现窗口点击关闭按钮时 ​​隐藏​​ 而不是真正关闭,并且只有当 ​​父窗口关闭时才真正退出​​ 、父子窗口顺序控制与资源安全释放​

文章目录实现方法**方法 &#xff1a;重写 OnClosing 方法****子窗口&#xff08;SettingView&#xff09;代码****父窗口&#xff08;MainWindow&#xff09;代码****关键点****适用场景**为什么if (Owner null || !Owner.IsLoaded)能够判断父窗口已经关闭**1. Owner null 检…

硬件设计学习DAY4——电源完整性设计:从概念到实战

每日更新教程&#xff0c;评论区答疑解惑&#xff0c;小白也能变大神&#xff01;" 目录 一.电源完整性 1.1电源完整性的核心概念 1.2电源完整性的三个关键目标 1.3地弹现象的通俗解释 1.4总结要点 二.电源分配网络&#xff08;PDN&#xff09;的作用 电源与GND网络…

QT跨平台应用程序开发框架(8)—— 多元素控件

目录 一&#xff0c;关于多元素控件 二&#xff0c;QListWidget 2.1 主要方法 2.2 实现新增删除 三&#xff0c;Table Widget 3.1 主要方法 3.2 代码演示 四&#xff0c;Tree Widget 4.1 主要方法 4.2 代码演示 一&#xff0c;关于多元素控件 多元素控件就是一个控件里面包含了…

【React Native】环境变量和封装 fetch

环境变量和封装fetch 环境变量 一般做开发&#xff0c;都会将接口地址配置到环境变量里。在Expo建的项目里&#xff0c;也可以使用环境变量。 在项目根目录新建一个.env文件&#xff0c;里面添加上&#xff1a; EXPO_PUBLIC_API_URLhttp://localhost:3000如果你用手机真机等…