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

简介: 有点标题党了,本意是想把对内核锁机制的一些实现细节记录下来,但多少反映了锁机制实现时的一些准则。本文讨论的锁机制主要指基于计数器的锁机制例如 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

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

critical area
---------------------
UNLOCK
---------------------
update counter

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

update counter
---------------------
read acquire (LOCK)
---------------------
critical area stay below the line

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

critical area stay above the line
---------------------
write release (UNLOCK)
---------------------
update counter

这就需要在 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() 这类的接口

相关文章
|
3月前
|
数据采集 Python
再谈re的应用
再谈re的应用
48 2
|
4月前
|
设计模式 程序员
故意把代码写得很烂,这样的 “防御性编程“ 可取吗?
故意把代码写得很烂,这样的 “防御性编程“ 可取吗?
|
4月前
|
调度
忙旋转:概念、用途及考量
【8月更文挑战第21天】
49 0
|
7月前
|
设计模式 IDE Java
谈谈过度设计:因噎废食的陷阱
本文探讨了设计模式在软件开发中的应用和争议,指出设计模式虽有助于应对软件复杂性,但在互联网快速迭代的背景下,可能会导致过度设计,增加理解和修改成本。文章分析了设计模式的缺陷,如开闭原则可能导致不易修改,最小知识原则可能导致理解困难。同时,文章强调了设计模式的重要性,指出它们可以提高代码的可理解性和模块的可维护性,并提出了通过函数式设计模式进行优化的示例。作者认为,设计模式需要随着业务演进而不断演进,同时提倡使用可调试的模块和模式演进来促进系统的成长性。文章最后提醒读者,要根据实际情况选择是否使用设计模式,避免因噎废食。
|
7月前
|
存储 人工智能 编译器
【重学C++】【指针】一文看透:指针中容易混淆的四个概念、算数运算以及使用场景中容易忽视的细节
【重学C++】【指针】一文看透:指针中容易混淆的四个概念、算数运算以及使用场景中容易忽视的细节
115 1
|
7月前
|
消息中间件 安全 算法
通透!从头到脚讲明白线程锁
线程锁在分布式应用中是重中之重,当谈论线程锁时,通常指的是在多线程编程中使用的同步机制,它可以确保在同一时刻只有一个线程能够访问共享资源,从而避免竞争条件和数据不一致性问题。
321 0
|
7月前
|
存储 缓存 Java
剑指JUC原理-10.并发编程大师的原子累加器底层优化原理(与人类的优秀灵魂对话)
剑指JUC原理-10.并发编程大师的原子累加器底层优化原理(与人类的优秀灵魂对话)
52 1
|
设计模式 数据库
理论篇|如何避免写出面条代码
理论篇|如何避免写出面条代码
160 0
理论篇|如何避免写出面条代码
|
安全
带你读《2022技术人的百宝黑皮书》——如何避免写重复代码:善用抽象和组合(2)
带你读《2022技术人的百宝黑皮书》——如何避免写重复代码:善用抽象和组合(2)
带你读《2022技术人的百宝黑皮书》——如何避免写重复代码:善用抽象和组合(2)
|
自然语言处理 JavaScript
再谈JS闭包
作用域 作用域嵌套 词法作用域(lexicsl scope) 闭包 闭包示例
214 0