一篇长文简短介绍,Linux内核——内存屏障(建议收藏)

在阅读很多底层的代码时,经常会碰到一个所谓内存屏障的概念,经常搞得一头雾水。本文将对这个概念进行一个系统的介绍。

一、为什么需要内存屏障

内存屏障的引入,本质上是由于CPU重排序指令引起的。重排序问题无时无刻不在发生,主要源自以下几种场景:

  1. 编译器编译时的优化;
  2. 处理器执行时的多发射和乱序优化;
  3. 读取和存储指令的优化;
  4. 缓存同步顺序(导致可见性问题)。

下面分别解释一下:

编译器优化

编译器在不改变单线程程序语义的前提下,也就是保证单线程程序执行结果正确的情况下,可以重新安排语句的执行顺序。编译器在优化的时候是不知道当前程序是在哪个线程中执行的,因此它只能保证单线程的正确性。

例如,有如下程序:

if (a)
    b = a;
else
    b = 42;

在经过编译器优化后可能变成:

b = 42;
if (a)
    b = a;

这种优化在单线程下是没有问题的,但是如果有另外一个线程要读取变量b的值时,有可能会有问题。前面的程序只有当变量a的值为0时,才会将b赋值42,后面的程序无论变量a的值是多少,都有一段时间会将b赋值为42。

造成这个问题的原因是,编译器优化的时候只注重“结果”,不注重“过程”。这种优化在单线程程序中没有问题,代码一直都是自己运行,只要结果对就可以了,但是在多线程程序下,代码执行过程中的某些状态可能会对别的线程产生影响,这个是编译器优化无法考虑到的。

处理器执行时的多发射和乱序优化

现代处理器基本上都是支持多发射的,也就是在一个指令周期内可以同时执行多条指令。但是,处理器的资源就那么多,可能不能同时满足处理这些指令的要求。比如,处理器就只有一个加法器,如果同时有两条指令都需要算加法,那么有一条指令必须等待。如果这时候再下一条指令是读取指令,并且和前两条指令无关,那么这条指令将在前面某条加法指令之前完成。还有一种可能,就是前后指令之间具有相关性,比如对同一个地址先读取再写入,后面的写入操作必须等待前面的读取操作完成后才能执行。但是如果这时候第三条指令是写入一个无关的地址,那它可以在前面的写入操作之前被执行,执行顺序再次被打乱了。

所以,一般情况下指令乱序并不是CPU在执行指令之前刻意去调整顺序。CPU总是顺序的去内存里面取指令,然后将其顺序的放入指令流水线。但是指令执行时的各种条件,指令与指令之间的相互影响,可能导致顺序放入流水线的指令,最终不是按照放入的顺序执行完成,在外边看起来仿佛是“乱序”一样,这就是所谓的“顺序流入,乱序流出”。

内核资料直通车:Linux内核源码技术学习路线+视频教程代码资料

学习直通车:进程管理/内存管理/网络协议/设备驱动/文件系统/腾讯课堂

读取和存储指令的优化

CPU有可能根据情况,将相临的两条读取或写入操作合并成一条。

例如,对于如下的两条读取操作:

X = *A; Y = *(A + 4);

可能被合并成一条读取操作:

{X, Y} = LOAD {*A, *(A + 4) };

同样的,对于如下两条写入操作:

*A = X; *(A + 4) = Y;

有可能会被合并成一条:

STORE {*A, *(A + 4) } = {X, Y};

以上这几种情况,由于编译器或CPU,出于“优化”的目的,按照某种规则将指令重新排序的行为称作“真”重排序。不同的是,编译器重排序是在编译程序时进行的,一旦编译成功后执行次序就定下来了。而后面几种是在CPU运行程序时实时进行的,CPU架构不同可能起到的效果完全不同。

编译器或CPU在执行各种优化的时候,都有一些必须的前提,就是至少在单一CPU上执行不能出现问题。有一些数据访问明显是相互依赖的,就不能打乱它们的执行顺序。比如:

1)在一个给定的CPU上,有依赖的内存访问:

比如如下两条指令:

A = Load B;
C = Load *A

第二条加载指令的地址是由第一条指令加载的,第二条指令要能正确执行,必须要等到第一条指令执行完成后才行,也就是说第二条指令依赖于第一条指令。这种情况下,无论如何处理器是不会打乱这两条指令的执行次序的。不过,有可能会在这两条指令间插入别的指令,但必须保证第二条指令在第一条指令执行完后才能执行。

2)在一个给定的CPU上,交叉的加载和存储操作,它们访问的内存地址有重叠:

例如,先存储后加载同一个内存地址上的内容:

Store *X = A;
B = Load *X;

或者先加载后读取同一个内存地址上的内容:

A = Load *X;
Store *X = B;

对同一个内存地址的存取,如果两条指令执行次序被打乱了,那肯定会发生错误。但是,如果是两条加载或两条存储指令(中间没有加载),哪怕是对同一个内存地址的操作,也可能由于优化产生变化。

有了上面两条限制,很容易想到,那如果所有加载或存储指令都没有相关性呢?这时候就要看CPU的心情了,可以以任何次序被执行,可以完全不按照它们在程序中出现的次序。

技术交流免费领取资料Q群977878001

缓存同步顺序

上面的几种情况都比较好

  • 0
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
Linux使用虚拟内存管理机制来对物理内存进行管理。通过虚拟内存管理,Linux欺骗用户程序,使每个程序都有4GB的虚拟内存寻址空。这种管理机制使用了一些数据结构来抽象内存管理操作,例如分配和释放内存。其中,用户空间内存数据结构用于管理用户空间的虚拟内存区域。对于内核管理系统中的物理内存Linux使用以页为最小分配单位的方式进行管理,这样可以更方便地管理物理内存。虽然每页的大小通常比实际内存使用的内存要大,但对于一些行为所需的内存,例如文件描述符、进程描述符和虚拟内存描述符,它们的内存占用量很小,甚至不到一页的大小。<span class="em">1</span><span class="em">2</span><span class="em">3</span> #### 引用[.reference_title] - *1* *2* [万字长文,别再说你不懂Linux内存管理了,30 张图给你安排的明明白白](https://blog.csdn.net/itcodexy/article/details/109574799)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v93^chatsearchT3_1"}}] [.reference_item style="max-width: 50%"] - *3* [【Linux内存管理机制](https://blog.csdn.net/weixin_46401837/article/details/122497714)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v93^chatsearchT3_1"}}] [.reference_item style="max-width: 50%"] [ .reference_list ]

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值