diff options
Diffstat (limited to '')
3 files changed, 337 insertions, 0 deletions
diff --git a/Documentation/translations/zh_CN/locking/index.rst b/Documentation/translations/zh_CN/locking/index.rst new file mode 100644 index 000000000..f0b107076 --- /dev/null +++ b/Documentation/translations/zh_CN/locking/index.rst @@ -0,0 +1,43 @@ +.. SPDX-License-Identifier: GPL-2.0 +.. include:: ../disclaimer-zh_CN.rst + +:Original: Documentation/locking/index.rst + +:翻译: + + 唐艺舟 Tang Yizhou <tangyeechou@gmail.com> + +== +锁 +== + +.. toctree:: + :maxdepth: 1 + + mutex-design + spinlocks + +TODOList: + + * locktypes + * lockdep-design + * lockstat + * locktorture + * rt-mutex-design + * rt-mutex + * seqlock + * ww-mutex-design + * preempt-locking + * pi-futex + * futex-requeue-pi + * hwspinlock + * percpu-rw-semaphore + * robust-futexes + * robust-futex-ABI + +.. only:: subproject and html + + Indices + ======= + + * :ref:`genindex` diff --git a/Documentation/translations/zh_CN/locking/mutex-design.rst b/Documentation/translations/zh_CN/locking/mutex-design.rst new file mode 100644 index 000000000..6aad54372 --- /dev/null +++ b/Documentation/translations/zh_CN/locking/mutex-design.rst @@ -0,0 +1,145 @@ +.. SPDX-License-Identifier: GPL-2.0 +.. include:: ../disclaimer-zh_CN.rst + +:Original: Documentation/locking/mutex-design.rst + +:翻译: + + 唐艺舟 Tang Yizhou <tangyeechou@gmail.com> + +================ +通用互斥锁子系统 +================ + +:初稿: + + Ingo Molnar <mingo@redhat.com> + +:更新: + + Davidlohr Bueso <davidlohr@hp.com> + +什么是互斥锁? +-------------- + +在Linux内核中,互斥锁(mutex)指的是一个特殊的加锁原语,它在共享内存系统上 +强制保证序列化,而不仅仅是指在学术界或类似的理论教科书中出现的通用术语“相互 +排斥”。互斥锁是一种睡眠锁,它的行为类似于二进制信号量(semaphores),在 +2006年被引入时[1],作为后者的替代品。这种新的数据结构提供了许多优点,包括更 +简单的接口,以及在当时更少的代码量(见缺陷)。 + +[1] https://lwn.net/Articles/164802/ + +实现 +---- + +互斥锁由“struct mutex”表示,在include/linux/mutex.h中定义,并在 +kernel/locking/mutex.c中实现。这些锁使用一个原子变量(->owner)来跟踪 +它们生命周期内的锁状态。字段owner实际上包含的是指向当前锁所有者的 +`struct task_struct *` 指针,因此如果无人持有锁,则它的值为空(NULL)。 +由于task_struct的指针至少按L1_CACHE_BYTES对齐,低位(3)被用来存储额外 +的状态(例如,等待者列表非空)。在其最基本的形式中,它还包括一个等待队列和 +一个确保对其序列化访问的自旋锁。此外,CONFIG_MUTEX_SPIN_ON_OWNER=y的 +系统使用一个自旋MCS锁(->osq,译注:MCS是两个人名的合并缩写),在下文的 +(ii)中描述。 + +准备获得一把自旋锁时,有三种可能经过的路径,取决于锁的状态: + +(i) 快速路径:试图通过调用cmpxchg()修改锁的所有者为当前任务,以此原子化地 + 获取锁。这只在无竞争的情况下有效(cmpxchg()检查值是否为0,所以3个状态 + 比特必须为0)。如果锁处在竞争状态,代码进入下一个可能的路径。 + +(ii) 中速路径:也就是乐观自旋,当锁的所有者正在运行并且没有其它优先级更高的 + 任务(need_resched,需要重新调度)准备运行时,当前任务试图自旋来获得 + 锁。原理是,如果锁的所有者正在运行,它很可能不久就会释放锁。互斥锁自旋体 + 使用MCS锁排队,这样只有一个自旋体可以竞争互斥锁。 + + MCS锁(由Mellor-Crummey和Scott提出)是一个简单的自旋锁,它具有一些 + 理想的特性,比如公平,以及每个CPU在试图获得锁时在一个本地变量上自旋。 + 它避免了常见的“检测-设置”自旋锁实现导致的(CPU核间)缓存行回弹 + (cacheline bouncing)这种昂贵的开销。一个类MCS锁是为实现睡眠锁的 + 乐观自旋而专门定制的。这种定制MCS锁的一个重要特性是,它有一个额外的属性, + 当自旋体需要重新调度时,它们能够退出MCS自旋锁队列。这进一步有助于避免 + 以下场景:需要重新调度的MCS自旋体将继续自旋等待自旋体所有者,即将获得 + MCS锁时却直接进入慢速路径。 + +(iii) 慢速路径:最后的手段,如果仍然无法获得锁,该任务会被添加到等待队列中, + 休眠直到被解锁路径唤醒。在通常情况下,它以TASK_UNINTERRUPTIBLE状态 + 阻塞。 + +虽然从形式上看,内核互斥锁是可睡眠的锁,路径(ii)使它实际上成为混合类型。通过 +简单地不中断一个任务并忙着等待几个周期,而不是立即睡眠,这种锁已经被认为显著 +改善一些工作负载的性能。注意,这种技术也被用于读写信号量(rw-semaphores)。 + +语义 +---- + +互斥锁子系统检查并强制执行以下规则: + + - 每次只有一个任务可以持有该互斥锁。 + - 只有锁的所有者可以解锁该互斥锁。 + - 不允许多次解锁。 + - 不允许递归加锁/解锁。 + - 互斥锁只能通过API进行初始化(见下文)。 + - 一个任务不能在持有互斥锁的情况下退出。 + - 持有锁的内存区域不得被释放。 + - 被持有的锁不能被重新初始化。 + - 互斥锁不能用于硬件或软件中断上下文,如小任务(tasklet)和定时器。 + +当CONFIG DEBUG_MUTEXES被启用时,这些语义将被完全强制执行。此外,互斥锁 +调试代码还实现了一些其它特性,使锁的调试更容易、更快速: + + - 当打印到调试输出时,总是使用互斥锁的符号名称。 + - 加锁点跟踪,函数名符号化查找,系统持有的全部锁的列表,打印出它们。 + - 所有者跟踪。 + - 检测自我递归的锁并打印所有相关信息。 + - 检测多任务环形依赖死锁,并打印所有受影响的锁和任务(并且只限于这些任务)。 + + +接口 +---- +静态定义互斥锁:: + + DEFINE_MUTEX(name); + +动态初始化互斥锁:: + + mutex_init(mutex); + +以不可中断方式(uninterruptible)获取互斥锁:: + + void mutex_lock(struct mutex *lock); + void mutex_lock_nested(struct mutex *lock, unsigned int subclass); + int mutex_trylock(struct mutex *lock); + +以可中断方式(interruptible)获取互斥锁:: + + int mutex_lock_interruptible_nested(struct mutex *lock, + unsigned int subclass); + int mutex_lock_interruptible(struct mutex *lock); + +当原子变量减为0时,以可中断方式(interruptible)获取互斥锁:: + + int atomic_dec_and_mutex_lock(atomic_t *cnt, struct mutex *lock); + +释放互斥锁:: + + void mutex_unlock(struct mutex *lock); + +检测是否已经获取互斥锁:: + + int mutex_is_locked(struct mutex *lock); + +缺陷 +---- + +与它最初的设计和目的不同,'struct mutex' 是内核中最大的锁之一。例如:在 +x86-64上它是32字节,而 'struct semaphore' 是24字节,rw_semaphore是 +40字节。更大的结构体大小意味着更多的CPU缓存和内存占用。 + + +何时使用互斥锁 +-------------- + +总是优先选择互斥锁而不是任何其它锁原语,除非互斥锁的严格语义不合适,和/或临界区 +阻止锁被共享。 diff --git a/Documentation/translations/zh_CN/locking/spinlocks.rst b/Documentation/translations/zh_CN/locking/spinlocks.rst new file mode 100644 index 000000000..2017c01f0 --- /dev/null +++ b/Documentation/translations/zh_CN/locking/spinlocks.rst @@ -0,0 +1,149 @@ +.. SPDX-License-Identifier: GPL-2.0 +.. include:: ../disclaimer-zh_CN.rst + +:Original: Documentation/locking/spinlocks.rst + +:翻译: + + 唐艺舟 Tang Yizhou <tangyeechou@gmail.com> + +========== +加锁的教训 +========== + +教训 1:自旋锁 +============== + +加锁最基本的原语是自旋锁(spinlock):: + + static DEFINE_SPINLOCK(xxx_lock); + + unsigned long flags; + + spin_lock_irqsave(&xxx_lock, flags); + ... 这里是临界区 .. + spin_unlock_irqrestore(&xxx_lock, flags); + +上述代码总是安全的。自旋锁将在 _本地_ 禁用中断,但它本身将保证全局锁定。所以它 +将保证在该锁保护的区域内只有一个控制线程。即使在单处理器(UP)下也能很好的工作, +所以代码 _不_ 需要担心UP还是SMP的问题:自旋锁在两种情况下都能正常工作。 + + 注意!自旋锁对内存的潜在影响由下述文档进一步描述: + + Documentation/memory-barriers.txt + + (5) ACQUIRE operations. + + (6) RELEASE operations. + +上述代码通常非常简单(对大部分情况,你通常需要并且只希望有一个自旋锁——使用多个 +自旋锁会使事情变得更复杂,甚至更慢,而且通常仅仅在你 **理解的** 序列有被拆分的 +需求时才值得这么做:如果你不确定的话,请不惜一切代价避免这样做)。 + +这是关于自旋锁的唯一真正困难的部分:一旦你开始使用自旋锁,它们往往会扩展到你以前 +可能没有注意到的领域,因为你必须确保自旋锁正确地保护共享数据结构 **每一处** 被 +使用的地方。自旋锁是最容易被添加到完全独立于其它代码的地方(例如,没有人访问的 +内部驱动数据结构)的。 + + 注意!仅当你在跨CPU核访问时使用 **同一把** 自旋锁,对它的使用才是安全的。 + 这意味着所有访问共享变量的代码必须对它们想使用的自旋锁达成一致。 + +---- + +教训 2:读-写自旋锁 +=================== + +如果你的数据访问有一个非常自然的模式,倾向于从共享变量中读取数据,读-写自旋锁 +(rw_lock)有时是有用的。它们允许多个读者同时出现在同一个临界区,但是如果有人想 +改变变量,它必须获得一个独占的写锁。 + + 注意!读-写自旋锁比原始自旋锁需要更多的原子内存操作。除非读者的临界区很长, + 否则你最好只使用原始自旋锁。 + +例程看起来和上面一样:: + + rwlock_t xxx_lock = __RW_LOCK_UNLOCKED(xxx_lock); + + unsigned long flags; + + read_lock_irqsave(&xxx_lock, flags); + .. 仅读取信息的临界区 ... + read_unlock_irqrestore(&xxx_lock, flags); + + write_lock_irqsave(&xxx_lock, flags); + .. 读取和独占写信息 ... + write_unlock_irqrestore(&xxx_lock, flags); + +上面这种锁对于复杂的数据结构如链表可能会有用,特别是在不改变链表的情况下搜索其中 +的条目。读锁允许许多并发的读者。任何希望 **修改** 链表的代码将必须先获取写锁。 + + 注意!RCU锁更适合遍历链表,但需要仔细注意设计细节(见Documentation/RCU/listRCU.rst)。 + +另外,你不能把读锁“升级”为写锁,所以如果你在 _任何_ 时候需要做任何修改 +(即使你不是每次都这样做),你必须在一开始就获得写锁。 + + 注意!我们正在努力消除大多数情况下的读-写自旋锁的使用,所以请不要在没有达成 + 共识的情况下增加一个新的(相反,请参阅Documentation/RCU/rcu.rst以获得完整 + 信息)。 + +---- + +教训 3:重新审视自旋锁 +====================== + +上述的自旋锁原语绝不是唯一的。它们是最安全的,在所有情况下都能正常工作,但部分 +**因为** 它们是安全的,它们也是相当慢的。它们比原本需要的更慢,因为它们必须要 +禁用中断(在X86上只是一条指令,但却是一条昂贵的指令——而在其他体系结构上,情况 +可能更糟)。 + +如果你有必须保护跨CPU访问的数据结构且你想使用自旋锁的场景,你有可能使用代价小的 +自旋锁版本。当且仅当你知道某自旋锁永远不会在中断处理程序中使用,你可以使用非中断 +的版本:: + + spin_lock(&lock); + ... + spin_unlock(&lock); + +(当然,也可以使用相应的读-写锁版本)。这种自旋锁将同样可以保证独占访问,而且 +速度会快很多。如果你知道有关的数据只在“进程上下文”中被存取,即,不涉及中断, +这种自旋锁就有用了。 + +当这些版本的自旋锁涉及中断时,你不能使用的原因是会陷入死锁:: + + spin_lock(&lock); + ... + <- 中断来临: + spin_lock(&lock); + +一个中断试图对一个已经锁定的变量上锁。如果中断发生在另一个CPU上,不会有问题; +但如果中断发生在已经持有自旋锁的同一个CPU上,将 _会_ 有问题,因为该锁显然永远 +不会被释放(因为中断正在等待该锁,而锁的持有者被中断打断,并且无法继续执行, +直到中断处理结束)。 + +(这也是自旋锁的中断版本只需要禁用 _本地_ 中断的原因——在发生于其它CPU的中断中 +使用同一把自旋锁是没问题的,因为发生于其它CPU的中断不会打断已经持锁的CPU,所以 +锁的持有者可以继续执行并最终释放锁)。 + + Linus + +---- + +参考信息 +======== + +对于动态初始化,使用spin_lock_init()或rwlock_init()是合适的:: + + spinlock_t xxx_lock; + rwlock_t xxx_rw_lock; + + static int __init xxx_init(void) + { + spin_lock_init(&xxx_lock); + rwlock_init(&xxx_rw_lock); + ... + } + + module_init(xxx_init); + +对于静态初始化,使用DEFINE_SPINLOCK() / DEFINE_RWLOCK()或 +__SPIN_LOCK_UNLOCKED() / __RW_LOCK_UNLOCKED()是合适的。 |