Files
LDD-LinuxDeviceDrivers/study/kernel/00-DESCRIPTION/LOCKING.md
T
2021-12-10 21:18:21 +08:00

24 KiB

title, date, author, tags, categories, thumbnail, blogexcerpt
title date author tags categories thumbnail blogexcerpt
锁机制 2021-02-15 00:32 gatieme
linux
tools
技术积累
虚拟化 & KVM 子系统

本作品采用知识共享署名-非商业性使用-相同方式共享 4.0 国际许可协议进行许可, 转载请注明出处, 谢谢合作

知识共享许可协议

因本人技术水平和知识面有限, 内容如有纰漏或者需要修正的地方, 欢迎大家指正, 鄙人在此谢谢啦

转载请务必注明出处, 谢谢, 不胜感激


日期 作者 GitHub CSDN BLOG
2021-02-15 成坚-gatieme AderXCoding/system/tools/fzf 使用模糊搜索神器 FZF 来提升办公体验 Using FZF to Improve Productivit

2

--------------- 重要功能和时间点 ---------------**

下文将按此目录分析 Linux 内核中 MM 的重要功能和引入版本:

--------------- 正文 ---------------**

1 SPINLOCK


Linux 中的 spinlock 机制 [一] - CAS 和 ticket spinlock

Linux 中的 spinlock 机制 [二] - MCS Lock

Linux 中的 spinlock 机制 [三] - qspinlock

Non-scalable locks are dangerous Non-scalable locks are dangerous Non-scalable locks are dangerous Scalable Locking

1.1 CAS(compare and swap) LOCK


CAS 的原理是, 将旧值与一个期望值进行比较, 如果相等, 则更新旧值.

这种是实现 spinlock 用一个整形变量表示, 其初始值为 1, 表示 available 的状态(可以被 1 用户独占).

  1. 当一个 CPU A 获得 spinlock 后, 会将该变量的值设为 0(不能被任何用户再占有), 之后其他 CPU 试图获取这个 spinlock 时, 会一直等待, 直到 CPU A 释放 spinlock, 并将该变量的值设为 1.

  2. 等待该 spinlock 的 CPU B 不断地把 「期望的值」 1 和 「实际的值」 (1 或者 0)进行比较(compare), 当它们相等时, 说明持有 spinlock 当前未被任何人持有(持有的 CPU 已经释放了锁或者锁一直未被任何人持有), 那么试图获取 spinlock 的 CPU 就会尝试将 "new" 的值(0) 写入 "p"(swap), 以表明自己成为占有了 spinlock, 成为新的 owner.

这里只用了 0 和 1 两个值来表示 spinlock 的状态, 没有充分利用 spinlock 作为整形变量的属性, 为此还有一种衍生的方法, 可以判断当前 spinlock 的争用情况. 具体规则是: 每个 CPU 在试图获取一个 spinlock 时, 都会将这个 spinlock 的值减1, 所以这个值可以是负数,而「负」的越多(负数的绝对值越大),说明当前的争抢越激烈。

存在的问题 基于CAS的实现速度很快,尤其是在没有真正竞态的情况下(事实上大部分时候就是这种情况), 但这种方法存在一个缺点:它是「不公平」的。 一旦spinlock被释放,第一个能够成功执行CAS操作的CPU将成为新的owner,没有办法确保在该spinlock上等待时间最长的那个CPU优先获得锁,这将带来延迟不能确定的问题。

1.2 ticket LOCK


https://lwn.net/Articles/267968

时间 作者 特性 描述 是否合入主线 链接
2007/11/01 Nick Piggin npiggin@suse.de ticket spinlocks for x86 X86 架构 ticket spinlocks 的实现. v1 ☑ 2.6.25-rc1(部分合入) PatchWork RFC
-------
PatchWork, PatchWork
2021/09/19 Guo Ren guoren@kernel.org riscv: locks: introduce ticket-based spinlock implementation riscv 架构 ticket spinlocks 的实现. v1 ☐ PatchWork

Linux中的spinlock机制[一] - CAS和ticket spinlock

1.3 MCS lock


MCS locks and qspinlocks

spinlock 的值出现变化时, 所有试图获取这个 spinlock 的 CPU 都需要读取内存, 刷新自己对应的 cache line, 而最终只有一个 CPU 可以获得锁, 也只有它的刷新才是有意义的. 锁的争抢越激烈(试图获取锁的CPU数目越多), 无谓的开销也就越大.

如果在 ticket spinlock 的基础上进行一定的修改, 让每个 CPU 不再是等待同一个 spinlock 变量, 而是基于各自不同的 per-CPU 的变量进行等待, 那么每个 CPU 平时只需要查询自己对应的这个变量所在的本地 cache line, 仅在这个变量发生变化的时候, 才需要读取内存和刷新这条 cache line, 这样就可以解决上述的这个问题.

要实现类似这样的 spinlock的 「分身」, 其中的一种方法就是使用 MCS lock. 试图获取一个 spinlock 的每个CPU, 都有一份自己的 MCS lock.

时间 作者 特性 描述 是否合入主线 链接
2015/04/24 Waiman Long Waiman.Long@hp.com qspinlock: a 4-byte queue spinlock with PV support X86 架构 qspinlocks 的实现. v16 ☑ 4.2-rc1 PatchWork RFC
-------
LORE v16 00/14
2014/01/21 Tim Chen tim.c.chen@linux.intel.com MCS Lock: MCS lock code cleanup and optimizations MCS LOCK 重构, 增加了新的文件 include/linux/mcs_spinlock.h v9 ☑ 3.15-rc1 LKML v6 0/6
-------
PatchWork v9 0/6, 关键 commit
2014/02/10 Peter Zijlstra peterz@infradead.org locking/core patches PV SPINLOCK v1 ☑ 4.2-rc1 PatchWork

1.4 qspinlock


1.4.1 X86


时间 作者 特性 描述 是否合入主线 链接
2008/08/28 Nick Piggin npiggin@suse.de queueing spinlocks? X86 架构 qspinlocks 的实现. RFC ☐ PatchWork RFC
2015/04/24 Waiman Long Waiman.Long@hp.com qspinlock: a 4-byte queue spinlock with PV support X86 架构 qspinlocks 的实现. v16 ☑ 4.2-rc1 PatchWork RFC
-------
LORE v16 00/14

1.4.2 ARM64


时间 作者 特性 描述 是否合入主线 链接
2017/04/10 Yury Norov ynorov@caviumnetworks.com arm64: queued spinlocks and rw-locks X86 架构 qspinlocks 的实现. RFC ☐ PatchWork RFC
2018/04/26 Will Deacon will.deacon@arm.com kernel/locking: qspinlock improvements qspinlocks 优化. v3 ☑ 4.18-rc1 LWN)
-------
LKML v3 00/14
2018/06/16 Will Deacon will.deacon@arm.com arm64: locking: Replace ticket lock implementation with qspinlock ARM64 架构 qspinlocks 的实现. RFC ☑ 4.19-rc1 PatchWork RFC

1.5 PV_SPINLOCK


PV qspinlock 原理

Hook 内核之 PVOPS

spinlock 在非虚拟化的环境下, 它是可以认为 CPU 不会被抢占的, 所以 A 拿锁干活, B 死等 A, A 干完自己的活, 就释放了, 中间不会被调度.

但是在虚拟化下, CPU 对应到 vcpu, 每个 vcpu 跟之前裸机上的进程调度一样, 所以 A 拿锁干活, 并不一定不会被抢占, 很有可能被调度走了, 因为 cpu 这时候还不知道 vcpu 在干嘛. B 死等 A, 但是 A 被调度了, 运行了 C, C 也要死等 A, 在一些设计不够好的系统里面, 这样就会变得很糟糕.

为了保证spinlock unlock的公平性, 有一种队列的spinlock, ticketlock, http://www.ibm.com/developerworks/cn/linux/l-cn-spinlock/这篇文章介绍的非常详细, 总之根据next, own, 来判断是否到自己了. 这样一种机制在裸机上是可以解决公平的问题, 但是放到虚拟化环境里, 它会使问题变得更糟. C必须等到B完成才可以, 如果中间B被调度了, 又开始循环了, 当然更糟的定义也是相对的, 如果vcpu的调度机制能够vcpu正在拿锁的话, 会怎样?

jeremy很早就写了一个pv ticketlock, 原理大概就是vcpu在拿锁了一段时间, 会放弃cpu, 并blocked, unlocked的时候会被唤醒, 这个针对PV制定的优化, 在vcpu拿不到锁的场景下, 并没有任何的性能损耗, 并且能够解决之前的问题, 但是当运行native linux的时候, 就会有性能损耗, 所以当时在config里面添加了一个编译选项CONFIG_PARAVIRT_SPINLOCK, 话说我们的系统里面, 这个是没打开的啊, 后面要再好好评估下

之后, 这个patch进行了改良, 在原有native linux的ticketlock的基础, 增加了一种模式, 通过检测cpu是否spinned一段时间, 判断是否要进入slow path, 之前的fast path的逻辑和原来保持不变, 进入slowpath后, 会在ticketlock里面置位, 并block vcpu, 等unlock的时候, 这个位会被clear, 因为占用了一个位, 所以能用的ticket少了一半.

这个方案在一些硬件(XEON x3450)上进行各种benchmark测试后, 结论是不再有任何的性能损耗.

好吧, 说了这么多理论性的东西, 再来说下, 我们实际遇到的问题. 很早以前经常有windows用户的工单投诉, 说自己的vm里面cpu没有怎么使用, 为什么cpu显示百分之百.

由于是windows系统, 加上我是小白, 很难给出一些技术细节上的分析, 只能通过简单滴一些测试实验进行调查.

最后的结果, 就是windows很多核的情况下, 比如12、16, 在一个稍微有点load的物理机上面, 跑一些cpu压力, 就很容易出现cpu百分百的问题, 后面降core之后, 情况有所缓解. 后面大致的分析结果是, windows里面很多操作是用到spinlock的, 当一个core拿到锁, 事情没有做完, 被调度了, 这时其他的core也需要拿锁, 当core越来越多的时候, 情况就越来越糟, 最后看上去就大家都很忙, 但实际什么事情也没做.

目前来看, 已经有一种较为成熟的软件方法来解决类似问题, 期待后续是否会有硬件的一些特性来支持, 或许已经有了.

PV_SPINLOCKS 的合入引起了性能问题 Performance overhead of paravirt_ops on native identified, yinru

当开启了 CONFIG_PARAVIRT_SPINLOCKS 之后, queued_spin_lock_slowpath() 将作为宏函数被展开多份, 一份 native_queued_spin_lock_slowpath() 用于传统的 spinlock 场景, 一份 __pv_queued_spin_lock_slowpath() 用于虚拟化场景. 参见 v4.2: commit a23db284fe0d locking/pvqspinlock: Implement simple paravirt support for the qspinlock. 在启动阶段, 通过 PVOPS 机制动态的的将 spinlock 替换为内核实际所需的 spinlock 处理函数. 虚拟化 guest 中将在 kvm_spinlock_init() 中被替换为虚拟化场景所需的 __pv_queued_spin_lock_slowpath() 等函数.

时间 作者 特性 描述 是否合入主线 链接
2008/07/07 Raghavendra K T raghavendra.kt@linux.vnet.ibm.com Paravirtual spinlocks PV_SPINLOCK 实现. RFC ☑ 2.6.27-rc1 PatchWork RFC
2009/05/15 Raghavendra K T raghavendra.kt@linux.vnet.ibm.com x86: Fix performance regression caused by paravirt_ops on native kernels NA v13 ☑ 3.12-rc1 PatchWork v13
2013/08/09 Raghavendra K T raghavendra.kt@linux.vnet.ibm.com Paravirtualized ticket spinlocks PV_SPINLOCK 的 ticket lock 实现. v13 ☑ 3.12-rc1 PatchWork v13
2015/04/07 Waiman Long Waiman.Long@hp.com qspinlock: a 4-byte queue spinlock with PV support PV SPINLOCK v15 ☑ 4.2-rc1 PatchWork v15
2015/11/10 Waiman Long Waiman.Long@hpe.com locking/qspinlock: Enhance pvqspinlock performance PV SPINLOCK v10 ☑ 4.5-rc1 PatchWork v5
-------
PatchWork v10
2018/10/08 Raghavendra K T raghavendra.kt@linux.vnet.ibm.com Enable PV qspinlock for Hyper-V Hyper-V 的 PV spiclock 实现. v2 ☑ 4.20-rc1 PatchWork v2
2019/10/23 Zhenzhong Duan zhenzhong.duan@oracle.com Add a unified parameter "nopvspin" PV SPINLOCK v8 ☑ 5.9-rc1 PatchWork v8

1.5 NumaAware SPINLOCK


关于多核 CPU 自旋锁 (spinlock) 的优化

NUMA-aware qspinlocks

时间 作者 特性 描述 是否合入主线 链接
2019/08/24 H.J. Lu numa-spinlock 阿里实现的用户态 numa aware spinlock. GitLab
2019/08/24 sanidhya Scalable and Practical Locking with Shuffling Shuffling 锁实现了洗牌技术. 将等待锁的线程更指定的策略进行重新排序. 类似于通过定义的比较功能对 waiter 进行排序. 实现 NUMA 感知的唤醒和阻塞策略. 洗牌的动作通常不会在关键路径上执行. GitHub
2019/08/08 dozenow ShortCut: Accelerating Mostly-Deterministic Code Regions NA GitHub
2021/05/14 Alex Kogan alex.kogan@oracle.com Add NUMA-awareness to qspinlock NUMA 感知的 spinlock, 基于 CNA-compact-numa-aware-locks. v15 ☐ PatchWork v15, PatchWork v15,0/6 ARM

1.6 SPINlOCK DEBUG


时间 作者 特性 描述 是否合入主线 链接
2021/05/14 Alex Kogan alex.kogan@oracle.com x86, locking/qspinlock: Allow lock to store lock holder cpu number struct raw_spinlock 中有 owner_cpu, 但是需要开启 CONFIG_DEBUG_SPINLOCK, 这个选项对 spinlock 的性能影响较大. 这个补丁集修改 x86 的 qspinlock 和 qrwlock 代码, 允许它在可行的情况下将锁持有者的 cpu 号 (qrwlock 的锁写入器 cpu 号) 存储在锁结构本身, 这对调试和崩溃转储分析很有用. 通过定义宏 __cpu_number_sadd1 (用于 qrwlock) 和 __cpu_number_sadd2 (用于 qspinlock), 以达到饱和的 + 1 和 + 2 cpu 数, 可以在 qspinlock 和 qrwlock 的锁字节中使用. 可以在每个体系结构的基础上启用该功能, 当前只提供了 x86 下的实现. v2 ☐ PatchWork v2,0/5

2 RWSEM


2.1 RWSEM


时间 作者 特性 描述 是否合入主线 链接
2020/11/21 Waiman Long longman@redhat.com locking/rwsem: Rework reader optimistic spinning 当读者的评论部分很短, 周围没有那么多读者时, 读者乐观旋转(osq_lock)是有帮助的. 它还提高了读者获得锁的机会, 因为写入器乐观旋转对写入器的好处远远大于读者. 由于提交d3681e269fff ("locking/rwsem: Wake up almost all reader in wait queue"), 所有等待的reader都会被唤醒, 这样它们就都能获得读锁并并行运行. 当竞争的读者数量很大时, 允许读者乐观自旋很可能会导致读者碎片, 多个较小的读者组可以以顺序的方式(由写入器分隔)获得读锁. 这降低了读者的并行性. 解决这个缺点的一种可能方法是限制能够进行乐观旋转的读者的数量(最好是一个). 这些读者作为等待队列中所有等待的读者的代表, 因为一旦获得锁, 它们将唤醒所有等待的读者. v2 ☐ PatchWork v2,0/5

2.2 PER-CPU RWSEM


时间 作者 特性 描述 是否合入主线 链接
2012/08/31 Mikulas Patocka mpatocka@redhat.com Fix a crash when block device is read and block size is changed at the same time NA v2 ☑ 3.8-rc1 PatchWork 0/4
2020/11/07 Oleg Nesterov oleg@redhat.com percpu_rw_semaphore: reimplement to not block the readers unnecessarily NA v2 ☑ 3.8-rc1 PatchWork v2,0/5, PatchWork
-------
PatchWork
2020/11/18 Oleg Nesterov oleg@redhat.com percpu_rw_semaphore: lockdep + config NA v1 ☑ 3.8-rc1 PatchWork 0/3

3 MUTEX


时间 作者 特性 描述 是否合入主线 链接
2013/4/17 Waiman Long Waiman.Long@hp.com mutex: Improve mutex performance by doing less atomic-ops & better spinning NA v4 ☑ 3.10-rc1 LKML 0/3 v2
-------
LKML v4 0/4

4 membarrier


时间 作者 特性 描述 是否合入主线 链接
2017/10/19 Raghavendra K T raghavendra.kt@linux.vnet.ibm.com membarrier: Provide register expedited private command 引入 MEMBARRIER_CMD_REGISTER_PRIVATE_EXPEDITED. v6 ☑ 4.14-rc6 PatchWork v5
-------
PatchWork v6
2017/11/21 Raghavendra K T raghavendra.kt@linux.vnet.ibm.com membarrier: Provide core serializing command 引入 MEMBARRIER_CMD_REGISTER_PRIVATE_EXPEDITED. v6 ☑ 4.16-rc1 PatchWork v5
-------
PatchWork v6
2018/01/29 Raghavendra K T raghavendra.kt@linux.vnet.ibm.com membarrier: Provide core serializing command 引入 MEMBARRIER_CMD_REGISTER_PRIVATE_EXPEDITED. v6 ☑ 4.16-rc1 PatchWork v5
-------
PatchWork v6

5 RC]()

What is RCU, Fundamentally?

What is RCU? Part 2: Usage

时间 作者 特性 描述 是否合入主线 链接
2019/06/01 "Joel Fernandes (Google)" joel@joelfernandes.org Harden list_for_each_entry_rcu() and family 本系列增加了一个新的内部函数rcu_read_lock_any_held(), 该函数在调用这些宏时检查reader节是否处于活动状态. 如果不存在reader section, 那么list_for_each_entry_rcu()的可选第四个参数可以是一个被计算的lockdep表达式(类似于rcu_dereference_check()的工作方式). . RFC ☑ 5.4-rc1 PatchWork RFC,0/6

6 FUTEX


FUTEX2's sys_futex_waitv() Sent In For Linux 5.16 To Help Linux Gaming