Linux内核:memory barrier(中)

简介: Linux内核:memory barrier

(2)Store buffer

我们考虑另外一个场景:在上一节中step e中的操作变成CPU 0对共享变量进行写的操作。这时候,写的性能变得非常的差,因为CPU 0必须要等到CPU n上的cacheline 数据传递到其cacheline之后,才能进行写的操作(CPU n上的cacheline 变成invalid状态,CPU 0则切换成exclusive状态,为后续的写动作做准备)。而从一个CPU的cacheline传递数据到另外一个CPU的cacheline是非常消耗时间的,而这时候,CPU 0的写的动作只是hold住,直到cacheline的数据完成传递。而实际上,这样的等待是没有意义的,因此,这时候cacheline的数据仍然会被覆盖掉。为了解决这个问题,多核系统中的cache修改如下:


image.png


这样,问题解决了,写操作不必等到cacheline被加载,而是直接写到store buffer中然后欢快的去干其他的活。在CPU n的cacheline把数据传递到其cache 0的cacheline之后,硬件将store buffer中的内容写入cacheline。


虽然性能问题解决了,但是逻辑错误也随之引入,我们可以看下面的例子:

我们假设a和b是共享变量,初始值都是0,可以被cpu0和cpu1访问。cpu 0的cache中保存了b的值(exclusive状态),没有a的值,而cpu 1的cache中保存了a的值,没有b的值,cpu 0执行的汇编代码是(用的是ARM汇编,没有办法,其他的都不是那么熟悉):


ldr     r2, [pc, #28]   -------------------------- 取变量a的地址

ldr     r4, [pc, #20]   -------------------------- 取变量b的地址

mov     r3, #1

str     r3, [r2]           --------------------------a=1

str     r3, [r4]           --------------------------b=1


CPU 1执行的代码是:


      ldr     r2, [pc, #28]   -------------------------- 取变量a的地址

            ldr     r3, [pc, #20]  -------------------------- 取变量b的地址

start:     ldr     r3, [r3]          -------------------------- 取变量b的值

           cmp     r3, #0          ------------------------ b的值是否等于0?

           beq     start            ------------------------ 等于0的话跳转到start

           ldr     r2, [r2]          -------------------------- 取变量a的值


当cpu 1执行到--取变量a的值--这条指令的时候,b已经是被cpu0修改为1了,这也就是说a=1这个代码已经执行了,因此,从汇编代码的逻辑来看,这时候a值应该是确定的1。然而并非如此,cpu 0和cpu 1执行的指令和动作描述如下:

image.png


 对于硬件,CPU不清楚具体的代码逻辑,它不可能直接帮助软件工程师,只是提供一些memory barrier的指令,让软件工程师告诉CPU他想要的内存访问逻辑顺序。这时候,cpu 0的代码修改如下:


ldr     r2, [pc, #28]   -------------------------- 取变量a的地址

ldr     r4, [pc, #20]   -------------------------- 取变量b的地址

mov     r3, #1

str     r3, [r2]           --------------------------a=1

确保清空store buffer的memory barrier instruction

str     r3, [r4]           --------------------------b=1


这种情况下,cpu 0和cpu 1执行的指令和动作描述如下:

image.png


由于增加了memory barrier,保证了a、b这两个变量的访问顺序,从而保证了程序逻辑。

 

(3)Invalidate Queue

我们先回忆一下为何出现了stroe buffer:为了加快cache miss状态下写的性能,硬件提供了store buffer,以便让CPU先写入,从而不必等待invalidate ack(这些交互是为了保证各个cpu的cache的一致性)。然而,store buffer的size比较小,不需要特别多的store命令(假设每次都是cache miss)就可以将store buffer填满,这时候,没有空间写了,因此CPU也只能是等待invalidate ack了,这个状态和memory barrier指令的效果是一样的。

怎么解决这个问题?CPU设计的硬件工程师对性能的追求是不会停歇的。我们首先看看invalidate ack为何如此之慢呢?这主要是因为cpu在收到invalidate命令后,要对cacheline执行invalidate命令,确保该cacheline的确是invalid状态后,才会发送ack。如果cache正忙于其他工作,当然不能立刻执行invalidate命令,也就无法会ack。

怎么破?CPU设计的硬件工程师提供了下面的方法:

image.png


Invalidate Queue这个HW block从名字就可以看出来是保存invalidate请求的队列。其他CPU发送到本CPU的invalidate命令会保存于此,这时候,并不需要等到实际对cacheline的invalidate操作完成,CPU就可以回invalidate ack了。


同store buffer一样,虽然性能问题解决了,但是对memory的访问顺序导致的逻辑错误也随之引入,我们可以看下面的例子(和store buffer中的例子类似):


我们假设a和b是共享变量,初始值都是0,可以被cpu0和cpu1访问。cpu 0的cache中保存了b的值(exclusive状态),而CPU 1和CPU 0的cache中都保存了a的值,状态是shared。cpu 0执行的汇编代码是:


ldr     r2, [pc, #28]   -------------------------- 取变量a的地址

ldr     r4, [pc, #20]   -------------------------- 取变量b的地址

mov     r3, #1

str     r3, [r2]           --------------------------a=1

确保清空store buffer的memory barrier instruction

str     r3, [r4]           --------------------------b=1


CPU 1执行的代码是:


   ldr     r2, [pc, #28]   -------------------------- 取变量a的地址

            ldr     r3, [pc, #20]  -------------------------- 取变量b的地址

start:     ldr     r3, [r3]          -------------------------- 取变量b的值

           cmp     r3, #0          ------------------------ b的值是否等于0?

           beq     start            ------------------------ 等于0的话跳转到start

           ldr     r2, [r2]          -------------------------- 取变量a的值


这种情况下,cpu 0和cpu 1执行的指令和动作描述如下:


image.png


可怕的memory misorder问题又来了,都是由于引入了invalidate queue引起,看来我们还需要一个memory barrier的指令,我们将程序修改如下:


ldr     r2, [pc, #28]   -------------------------- 取变量a的地址

            ldr     r3, [pc, #20]  -------------------------- 取变量b的地址

start:     ldr     r3, [r3]          -------------------------- 取变量b的值

           cmp     r3, #0          ------------------------ b的值是否等于0?

           beq     start            ------------------------ 等于0的话跳转到start

确保清空invalidate queue的memory barrier instruction

           ldr     r2, [r2]          -------------------------- 取变量a的值

目录
相关文章
|
24天前
|
Linux C语言
Linux内核队列queue.h
Linux内核队列queue.h
|
2月前
|
Shell Linux C语言
【Shell 命令集合 系统设置 】⭐Linux 卸载已加载的内核模块rmmod命令 使用指南
【Shell 命令集合 系统设置 】⭐Linux 卸载已加载的内核模块rmmod命令 使用指南
34 1
|
24天前
|
存储 Linux
linux查看系统版本、内核信息、操作系统类型版本
linux查看系统版本、内核信息、操作系统类型版本
56 9
|
1月前
|
Ubuntu Linux
linux查看系统版本及内核信息
在Linux中检查系统版本和内核信息,可使用`uname -r`查看内核版本,`uname -a`获取详细信息,或者查看`/proc/version`。要了解发行版版本,尝试`lsb_release -a`(如果安装了)或查阅`/etc/os-release`。Red Hat家族用`/etc/redhat-release`,Debian和Ubuntu系用`/etc/issue`及相关文件。不同发行版可能需不同命令。
32 3
|
3天前
|
运维 监控 Linux
提升系统稳定性:Linux内核参数调优实战
【5月更文挑战第1天】 在运维领域,保障服务器的高效稳定运行是核心任务之一。Linux操作系统因其开源、可靠和灵活的特点被广泛应用于服务器中。本文将深入探讨通过调整Linux内核参数来优化系统性能,提升服务器的稳定性和响应能力。文章首先介绍了内核参数调优的必要性和基本原则,然后详细阐述了调优过程中的关键步骤,包括如何监控当前系统状态,确定性能瓶颈,选择合适的参数进行调优,以及调优后的测试与验证。最后,文中提供了一些常见问题的解决策略和调优的最佳实践。
19 5
|
4天前
|
算法 大数据 Linux
深入理解Linux内核的进程调度机制
【4月更文挑战第30天】操作系统的核心职能之一是有效地管理和调度进程,确保系统资源的合理分配和高效利用。在众多操作系统中,Linux因其开源和高度可定制的特点,在进程调度机制上展现出独特优势。本文将深入探讨Linux内核中的进程调度器——完全公平调度器(CFS),分析其设计理念、实现原理及面临的挑战,并探索未来可能的改进方向。
|
4天前
|
算法 Linux 调度
探索Linux内核:进程调度的奥秘
【4月更文挑战第30天】 在多任务操作系统中,进程调度是核心功能之一,它决定了处理器资源的分配。本文深入分析了Linux操作系统的进程调度机制,从调度器的基本原理到复杂的调度策略,以及它们如何影响系统性能和用户体验。通过剖析进程优先级、时间片分配以及实时性要求等方面,揭示了Linux如何在众多运行着的进程中做出快速而公平的决策,确保系统的高效与稳定运行。
|
4天前
|
算法 安全 Linux
深度解析:Linux内核内存管理机制
【4月更文挑战第30天】 在操作系统领域,内存管理是核心功能之一,尤其对于多任务操作系统来说更是如此。本文将深入探讨Linux操作系统的内核内存管理机制,包括物理内存的分配与回收、虚拟内存的映射以及页面替换算法等关键技术。通过对这些技术的详细剖析,我们不仅能够理解操作系统如何高效地利用有限的硬件资源,还能领会到系统设计中的性能与复杂度之间的权衡。
|
5天前
|
弹性计算 网络协议 Shell
自动优化Linux 内核参数
【4月更文挑战第29天】
5 1
|
5天前
|
弹性计算 网络协议 Linux
自动优化 Linux 内核参数
【4月更文挑战第28天】
13 0