“我报名参加金石计划1期挑战——瓜分10万奖池,这是我的第6篇文章,点击查看活动详情”
前言
📫作者简介: 小明java问道之路,专注于研究计算机底层/Java/Liunx 内核,就职于大型金融公司后端高级工程师,擅长交易领域的高安全/可用/并发/性能的架构设计📫
🏆 CSDN专家博主/Java优质创作者/CSDN内容合伙人、InfoQ签约作者、阿里云签约专家博主、华为云专家、51CTO专家/TOP红人 🏆
🔥如果此文还不错的话,还请👍关注、点赞、收藏三连支持👍一下博主~
本文导读
本文讲解CPU角度的中断控制,CPU层面并行并发和中断控制的原理,现代CPU的缓存结构和架构图、CPU缓存一致性的源码原理,以及CPU如何通过编译器的屏障与指令实现系统屏障,经过内联汇编代码验证之后,证明上述所说的 Linux 内核用 volatile 关键字实现系统屏障(指令重排),加深对系统屏障的内核源码和原理的理解。
CPU总线
总线/缓存锁非常容易理解,我们回顾上图中描述的 CPU 架构图。
多个 core 也就是CPU核共享一个L3,然后L3后面就是内存,也就是使用同一个总线,我们来想想是不是可以对这根总线上锁,然后当CPU执行完一条指令后再解锁呢?当这个 CPU 在访存时不允许其他 CPU 再申请访问内存,如 CMPXCHG 指令,它是将寄存器的值和内存中的值比较并替换,那么如果我们直接写 CMPXCHGmemory,r,也就是将eax和memery地址的值比较,如果相同,则将r寄存器的内容放入 memory 地址中,将交换的值放入eax 中。想想这几个步骤,这将不会保证正在执行操作的CPU不会被其他CPU锁干扰,也就是这几个步骤不保证原子性。
那么如何做呢?如果写 LOCK CMPXCHG memory,r, 那么仅仅加一个 LOCK 前缀,但是这就够了,我们对这个操作上锁了,等我们这条指令操作完成后锁才释放,那么就能保证CPU这条操作满足原子性了。
我们知道 LOCK 前缀就是用来上锁的,那么想想我们对什么上锁,读者可能会说上述不是说了共享总线么,对总线上锁不就可以了
但是例如我通过一根控制总线拉高电平,这时其他 CPU 将不能够访存这就可以了,可是就像我们上述操作一样,仅仅对一个变量的地址上锁
那么有没有办法来缩小锁的力度,查看上图,我们的变量可是被加载到了CPU的高速缓存中,而且我们有 MESI协议呢?这就够了,我们可以看看如果变量在高速缓存中,那么我们通过 MESI协议 将其他CPU缓存的值变为 I 状态,这时其他 CPU 就无法再读取这个变量,于是它们就需要去访存,但是由于 MESI 规定了当有 CPU 在对这个变量操作的时候,其他 CPU 不能操作,直到 CPU 将值修改完成后置为 M 状态,因此可以通过 Forward 机制将修改的值转发给其他 CPU,这时状态变为 S,这也避免了其他 CPU 再去访问地址,因而极大地提高了效率。而且我们也将锁细粒度到了变量级别,就不用再去对总线上锁了。
总结
本文讲解CPU角度的中断控制,CPU层面并行并发和中断控制的原理,现代CPU的缓存结构和架构图、CPU缓存一致性的源码原理,以及CPU如何通过编译器的屏障与指令实现系统屏障,经过内联汇编代码验证之后,证明上述所说的 Linux 内核用 volatile 关键字实现系统屏障(指令重排),加深对系统屏障的内核源码和原理的理解。