细节是魔鬼——基于计数器的锁机制的实现准则

简介: 有点标题党了,本意是想把对内核锁机制的一些实现细节记录下来,但多少反映了锁机制实现时的一些准则。本文讨论的锁机制主要指基于计数器的锁机制例如 spinlock、mutex,不包括 RCU 这类锁机制。### parallesim在讨论各种锁机制之前,有必要讨论系统的并行度,即有哪些潜在的竞争场景1. 中断上下文与进程上下文对共享资源的访问,由于中断是异步进行的,因而中断与进程是并发执行

有点标题党了,本意是想把对内核锁机制的一些实现细节记录下来,但多少反映了锁机制实现时的一些准则。本文讨论的锁机制主要指基于计数器的锁机制例如 spinlock、mutex,不包括 RCU 这类锁机制。

parallesim

在讨论各种锁机制之前,有必要讨论系统的并行度,即有哪些潜在的竞争场景

  1. 中断上下文与进程上下文对共享资源的访问,由于中断是异步进行的,因而中断与进程是并发执行的,当中断上下文与进程上下文同时对共享资源进行访问时,就有可能形成竞争
  2. 在 UP 与 SMP 系统中,当处理器是可抢占的时,由于可抢占的特性,同一个处理器内进程与进程间是并发执行的
  3. 在 SMP 系统中,多处理器间的进程是严格意义上的并发执行的

parallesim from preemption

在单处理器上,进程之间的抢占是并行度的一大来源,为了排除进程抢占带来的并行度,在锁机制的实现过程中必须关闭抢占

parallesim from SMP

在 SMP 系统中,多处理器之间是严格并发执行的

目前内核中大部分锁机制使用 counter based locking 来排除多处理器之间的并行度,其原理是维护一个整型数据类型的 counter 计数器,计数器的值就表示可用资源的份数,在申请占用资源的时候计数器就加 1,在申请释放资源的时候计数器就减 1

counter based 的锁机制在实现时都需要考虑 atomic 与 barrier 两个维度

atomic

首先,counter based 的锁机制的核心都是整型数据类型的 counter 计数器,需要保证对计数的操作是 atomic 的

现代处理器架构一般都保证对整型数据类型的 load 或 store 操作原生是 atomic 的,但是锁机制中大量涉及的是 add/sub 即 RMW (Read-Modify-Write) 操作,但是处理器架构一般不能保证 RMW (Read-Modify-Write) 操作原生是 atomic 的

  • x86 架构下使用 LOCK 指令来实现 atomic RMW (read-modify-write)
  • ARM 架构下提供 LDREX/STREX 指令来实现 atomic RMW (read-modify-write)

acquire/release barrier

只有 atomic RMW (read-modify-write) 还不够,因为 memory reordering 可能会将 lock 操作之后的内存访问指令重排到 lock 操作之前执行

update counter
---------------------
LOCK
---------------------
critical area
AI 代码解读

也有可能将 unlock 操作之前的内存访问指令重排到 unlock 操作之后执行

critical area
---------------------
UNLOCK
---------------------
update counter
AI 代码解读

因而 counter based 的锁机制还需要实现 acquire/release 语义,从而确保 lock 操作之后的内存访问指令不会重排到 lock 操作之前执行

update counter
---------------------
read acquire (LOCK)
---------------------
critical area stay below the line
AI 代码解读

unlock 操作之前的内存访问指令不会重排到 unlock 操作之后执行

critical area stay above the line
---------------------
write release (UNLOCK)
---------------------
update counter
AI 代码解读

这就需要在 lock 操作的最后调用 acquire barrier,在 unlock 操作的最开始调用 release barrier,从而确保 lock/unlock 之间的内存访问指令一定位于 lock/unlock 之间

x86 架构下由于只存在 StoreLoad reorder,因而天生满足 acquire/release 语义,因而 x86 架构下以上这两个 barrier 的定义都为空

aarch64 架构下则使用 dmb 指令实现 acquire/release 语义

parallesim from IRQ

以上 atomic、barrier、preemption 三个维度实现的锁机制,不是中断安全的,即并不能排除中断带来的并行度

如果锁机制需要保护的共享资源不会被中断处理程序访问,即只是在 process context 之间共享,那么以上三个维度 atomic、barrier、preemption 实现的锁机制就完全够用了

而如果共享资源还会被中断处理程序访问,也就是共享资源实际上是在 process context 和 interrupt context 之间共享,那么由 process context 这一方发起的上锁的过程中,还必须关闭全局中断,例如 spinlock 就提供了 spin_lock_irq()/spin_unlock_irq() 这类的接口

目录
打赏
0
0
0
0
9
分享
相关文章
带你读《2022技术人的百宝黑皮书》——如何避免写重复代码:善用抽象和组合(4)
带你读《2022技术人的百宝黑皮书》——如何避免写重复代码:善用抽象和组合(4)
115 0
谈谈过度设计:因噎废食的陷阱
本文探讨了设计模式在软件开发中的应用和争议,指出设计模式虽有助于应对软件复杂性,但在互联网快速迭代的背景下,可能会导致过度设计,增加理解和修改成本。文章分析了设计模式的缺陷,如开闭原则可能导致不易修改,最小知识原则可能导致理解困难。同时,文章强调了设计模式的重要性,指出它们可以提高代码的可理解性和模块的可维护性,并提出了通过函数式设计模式进行优化的示例。作者认为,设计模式需要随着业务演进而不断演进,同时提倡使用可调试的模块和模式演进来促进系统的成长性。文章最后提醒读者,要根据实际情况选择是否使用设计模式,避免因噎废食。
深入理解死锁的原因、表现形式以及解决方法,对于提高Java并发编程的效率和安全性具有重要意义
【6月更文挑战第10天】本文探讨了Java并发编程中的死锁问题,包括死锁的基本概念、产生原因和解决策略。死锁是因线程间争夺资源导致的互相等待现象,常由互斥、请求与保持、非剥夺和循环等待条件引起。常见死锁场景包括资源请求顺序不一致、循环等待等。解决死锁的方法包括避免嵌套锁、设置锁获取超时、规定锁顺序、检测与恢复死锁,以及使用高级并发工具。理解并防止死锁有助于提升Java并发编程的效率和系统稳定性。
448 0
【重学C++】【指针】一文看透:指针中容易混淆的四个概念、算数运算以及使用场景中容易忽视的细节
【重学C++】【指针】一文看透:指针中容易混淆的四个概念、算数运算以及使用场景中容易忽视的细节
128 1
理论篇|如何避免写出面条代码
理论篇|如何避免写出面条代码
189 0
理论篇|如何避免写出面条代码
带你读《2022技术人的百宝黑皮书》——如何避免写重复代码:善用抽象和组合(2)
带你读《2022技术人的百宝黑皮书》——如何避免写重复代码:善用抽象和组合(2)
带你读《2022技术人的百宝黑皮书》——如何避免写重复代码:善用抽象和组合(2)
带你读《2022技术人的百宝黑皮书》——如何避免写重复代码:善用抽象和组合(5)
带你读《2022技术人的百宝黑皮书》——如何避免写重复代码:善用抽象和组合(5)
160 0
带你读《2022技术人的百宝黑皮书》——如何避免写重复代码:善用抽象和组合(3)
带你读《2022技术人的百宝黑皮书》——如何避免写重复代码:善用抽象和组合(3)
带你读《2022技术人的百宝黑皮书》——如何避免写重复代码:善用抽象和组合(1)
带你读《2022技术人的百宝黑皮书》——如何避免写重复代码:善用抽象和组合(1)
145 0

热门文章

最新文章