mirror of
https://github.com/MintCN/linux-insides-zh.git
synced 2026-04-25 11:11:20 +08:00
Update linux-sync-4.md
更新部分建議from mudongliang
This commit is contained in:
@@ -4,17 +4,17 @@
|
||||
Introduction
|
||||
--------------------------------------------------------------------------------
|
||||
|
||||
这是本章的第四部份 [chapter](https://xinqiu.gitbooks.io/linux-insides-cn/content/SyncPrim/index.html) ,本章描述了内核中的同步原语,且在之前的部分我们介绍完了 [自旋锁](https://en.wikipedia.org/wiki/Spinlock) 和 [信号量](https://en.wikipedia.org/wiki/Semaphore_%28programming%29) 两种不同的同步原语。我们将在这个章节持续学习 [同步原语](https://en.wikipedia.org/wiki/Synchronization_%28computer_science%29),并考虑另一简称 [互斥锁 (mutex)](https://en.wikipedia.org/wiki/Mutual_exclusion) 的同步原语,全名 `MUTual EXclusion`。
|
||||
这是本章的第四部分 [chapter](https://xinqiu.gitbooks.io/linux-insides-cn/content/SyncPrim/index.html) ,本章描述了内核中的同步原语,且在之前的部分我们介绍完了 [自旋锁](https://en.wikipedia.org/wiki/Spinlock) 和 [信号量](https://en.wikipedia.org/wiki/Semaphore_%28programming%29) 两种不同的同步原语。我们将在这个章节持续学习 [同步原语](https://en.wikipedia.org/wiki/Synchronization_%28computer_science%29),并考虑另一简称 [互斥锁 (mutex)](https://en.wikipedia.org/wiki/Mutual_exclusion) 的同步原语,全名 `MUTual EXclusion`。
|
||||
|
||||
|
||||
与本 [书](https://xinqiu.gitbooks.io/linux-insides-cn/content) 前面所有章节一样,我们将先尝试从理论面来探究此同步原语,在此基础上再探究Linux内核所提供用来操作 `互斥锁` 的 [API](https://en.wikipedia.org/wiki/Application_programming_interface)。
|
||||
与 [本书](https://xinqiu.gitbooks.io/linux-insides-cn/content) 前面所有章节一样,我们将先尝试从理论面来探究此同步原语,在此基础上再探究Linux内核所提供用来操作 `互斥锁` 的 [API](https://en.wikipedia.org/wiki/Application_programming_interface)。
|
||||
|
||||
让我们开始吧。
|
||||
|
||||
`互斥锁` 的概念
|
||||
--------------------------------------------------------------------------------
|
||||
|
||||
从前一 [部份](https://xinqiu.gitbooks.io/linux-insides-cn/content/SyncPrim/linux-sync-3.html),我们已经很熟悉同步原语 [信号量](https://en.wikipedia.org/wiki/Semaphore_%28programming%29)。其表示为:
|
||||
从前一 [部分](https://xinqiu.gitbooks.io/linux-insides-cn/content/SyncPrim/linux-sync-3.html),我们已经很熟悉同步原语 [信号量](https://en.wikipedia.org/wiki/Semaphore_%28programming%29)。其表示为:
|
||||
|
||||
```C
|
||||
struct semaphore {
|
||||
@@ -24,7 +24,7 @@ struct semaphore {
|
||||
};
|
||||
```
|
||||
|
||||
此结构体包含 [锁](https://en.wikipedia.org/wiki/Lock_%28computer_science%29) 的状态和一由等待者所构成的列表。根据 `count` 字段的值,`semaphore` 可以向希望访问该资源的多个进程提供对该资源的访问。[互斥锁](https://en.wikipedia.org/wiki/Mutual_exclusion) 的概念与 [信号量](https://en.wikipedia.org/wiki/Semaphore_%28programming%29) 相似,却有着一些差异。`信号量` 和 `互斥锁` 同步原语的主要差异在于 `互斥锁` 具有更严格的语义。不像 `信号量`,单一时间一 `互斥锁` 只能由一个 [进程](https://en.wikipedia.org/wiki/Process_%28computing%29) 持有,并且只有该 `互斥锁` 的 `持有者` 能够对其进行释放或解锁的动作。另外的差异是 `锁` 的 [API](https://en.wikipedia.org/wiki/Application_programming_interface) 实现,`信号量` 同步原语强置重新排程 (rescheduling) 在等待列表中的进程。`互斥锁 API` 的实现则允许避免这种状况,进而减少这种昂贵的 [上下文切换](https://en.wikipedia.org/wiki/Context_switch) 操作。
|
||||
此结构体包含 [锁](https://en.wikipedia.org/wiki/Lock_%28computer_science%29) 的状态和一由等待者所构成的列表。根据 `count` 字段的值,`semaphore` 可以向希望访问该资源的多个进程提供对该资源的访问。[互斥锁](https://en.wikipedia.org/wiki/Mutual_exclusion) 的概念与 [信号量](https://en.wikipedia.org/wiki/Semaphore_%28programming%29) 相似,却有着一些差异。`信号量` 和 `互斥锁` 同步原语的主要差异在于 `互斥锁` 具有更严格的语义。不像 `信号量`,单一时间一 `互斥锁` 只能由一个 [进程](https://en.wikipedia.org/wiki/Process_%28computing%29) 持有,并且只有该 `互斥锁` 的 `持有者` 能够对其进行释放或解锁的动作。另外的差异是 `锁` 的 [API](https://en.wikipedia.org/wiki/Application_programming_interface) 实现,`信号量` 同步原语强置重新调度 (rescheduling) 在等待列表中的进程。`互斥锁 API` 的实现则允许避免这种状况,进而减少这种昂贵的 [上下文切换](https://en.wikipedia.org/wiki/Context_switch) 操作。
|
||||
|
||||
`互斥锁` 同步原语由如下结构呈现在Linux内核中:
|
||||
|
||||
@@ -52,17 +52,17 @@ struct mutex {
|
||||
|
||||
`互斥锁` 结构的下面两个字段 - `wait_lock` 和 `wait_list` 分别是用来保护 `等待队列` 的 [自旋锁](https://github.com/torvalds/linux/blob/master/include/linux/mutex.h),以及由某个锁的等待者们所构成的 `等待队列` 列表。你可能注意到了,`互斥锁` 和 `信号量` 结构体的相似处到此结束。剩余的 `互斥锁` 结构体字段,则如同我们所见,取决于Linux内核的不同配置选项。
|
||||
|
||||
第一个字段 - `owner` 代表持有该锁的 [进程](https://en.wikipedia.org/wiki/Process_%28computing%29)。正如我们看到的,此字段是否存在于 `mutex` 结构体中取决于 `CONFIG_DEBUG_MUTEXES` 或 `CONFIG_MUTEX_SPIN_ON_OWNER` 的内核配置选项。这个字段和下个 `osq` 字段的主要用途在于支援我们之后会介绍的`乐观自旋 (optimistic spinning)` 功能。最后两个字段 - `magic` 和 `dep_map` 仅被用于 [除错](https://en.wikipedia.org/wiki/Debugging) 模式。`magic` 字段用来存储跟 `互斥锁` 用来除错的相关资讯,而第二个字段 - `lockdep_map` 则在Linux内核的 [锁验证器 (lock validator)](https://www.kernel.org/doc/Documentation/locking/lockdep-design.txt) 中被使用。
|
||||
第一个字段 - `owner` 代表持有该锁的 [进程](https://en.wikipedia.org/wiki/Process_%28computing%29)。正如我们看到的,此字段是否存在于 `mutex` 结构体中取决于 `CONFIG_DEBUG_MUTEXES` 或 `CONFIG_MUTEX_SPIN_ON_OWNER` 的内核配置选项。这个字段和下个 `osq` 字段的主要用途在于支援我们之后会介绍的`乐观自旋 (optimistic spinning)` 功能。最后两个字段 - `magic` 和 `dep_map` 仅被用于 [调试](https://en.wikipedia.org/wiki/Debugging) 模式。`magic` 字段用来存储跟 `互斥锁` 用来调试的相关资讯,而第二个字段 - `lockdep_map` 则在Linux内核的 [锁验证器 (lock validator)](https://www.kernel.org/doc/Documentation/locking/lockdep-design.txt) 中被使用。
|
||||
|
||||
我们已探究完 `互斥锁` 的结构,现在,我们可以开始思考此同步原语是如何在Linux内核中运作的。你可能会猜,一个想获取锁的进程,在允许的情况下,就直接对 `mutex->count` 字段做递减的操作来试图获取锁。而如果进程希望释放一个锁,则递增该字段的值即可。大致上没错,但也正如你可能猜到的那样,在Linux内核中这可能不会那么简单。
|
||||
|
||||
事实上,当一进程在试图获取 `互斥锁` 时,有三种可能的路径:
|
||||
事实上,当某进程在试图获取 `互斥锁` 时,有三种可能的路径:
|
||||
|
||||
* `fastpath`;
|
||||
* `midpath`;
|
||||
* `slowpath`.
|
||||
|
||||
会采取哪种路径,将根据当前 `mutex` 的状态而定。第一条路径或 `fastpath` 是最快的,正如你从名称中可以领会到的那样。这种情况下的所有事情都很简单。因 `互斥锁` 还没被任何人获取,其 `mutex` 结构中的`count`字段可以直接被进行递减操作。在释放该 `互斥锁` 的情况下,算法是类似的,进程对该 `互斥锁` 结构中的 `count` 字段进行递增的操作即可。当然,所有的这些操作都必须是 [元子](https://en.wikipedia.org/wiki/Linearizability) 的。
|
||||
会采取哪种路径,将根据当前 `mutex` 的状态而定。第一条路径或 `fastpath` 是最快的,正如你从名称中可以领会到的那样。这种情况下的所有事情都很简单。因 `互斥锁` 还没被任何人获取,其 `mutex` 结构中的`count`字段可以直接被进行递减操作。在释放该 `互斥锁` 的情况下,算法是类似的,进程对该 `互斥锁` 结构中的 `count` 字段进行递增的操作即可。当然,所有的这些操作都必须是 [原子](https://en.wikipedia.org/wiki/Linearizability) 的。
|
||||
|
||||
是的,这看起来很简单。但如果一个进程想要获取一个已经被其他进程持有的 `互斥锁` 时,会发生什么事?在这种情况下,控制流程将被转由第二条路径决定 - `midpath`。`midpath` 或 `乐观自旋` 会在锁的持有者仍在运行时,对我们已经熟悉的 [MCS lock](http://www.cs.rochester.edu/~scott/papers/1991_TOCS_synch.pdf) 进行 [循环](https://en.wikipedia.org/wiki/Spinlock) 操作。这条路径只有在没有其他更高优先级的进程准备运行时执行。这条路径被称作 `乐观` 是因为等待的进程不会被睡眠或是调度。这可以避免掉昂贵的 [上下文切换](https://en.wikipedia.org/wiki/Context_switch).
|
||||
|
||||
@@ -78,7 +78,7 @@ struct mutex_waiter {
|
||||
};
|
||||
```
|
||||
|
||||
此结构定义在[include/linux/mutex.h](https://github.com/torvalds/linux/blob/master/include/linux/mutex.h) 头文件,且使用上可能会被睡眠。在研究Linux内核提供用来操作 `互斥锁` 的 [API](https://en.wikipedia.org/wiki/Application_programming_interface) 前,让我们先研究 `mutex_waiter` 结构。如果你有阅读此章节的 [前一部份](https://xinqiu.gitbooks.io/linux-insides-cn/content/SyncPrim/linux-sync-3.html),你可以能会注意到 `mutex_waiter` 结构与 [kernel/locking/semaphore.c](https://github.com/torvalds/linux/blob/master/kernel/locking/semaphore.c) 源代码中的 `semaphore_waiter` 结构相似:
|
||||
此结构定义在[include/linux/mutex.h](https://github.com/torvalds/linux/blob/master/include/linux/mutex.h) 头文件,且使用上可能会被睡眠。在研究Linux内核提供用来操作 `互斥锁` 的 [API](https://en.wikipedia.org/wiki/Application_programming_interface) 前,让我们先研究 `mutex_waiter` 结构。如果你有阅读此章节的 [前一部分](https://xinqiu.gitbooks.io/linux-insides-cn/content/SyncPrim/linux-sync-3.html),你可以能会注意到 `mutex_waiter` 结构与 [kernel/locking/semaphore.c](https://github.com/torvalds/linux/blob/master/kernel/locking/semaphore.c) 源代码中的 `semaphore_waiter` 结构相似:
|
||||
|
||||
```C
|
||||
struct semaphore_waiter {
|
||||
@@ -88,7 +88,7 @@ struct semaphore_waiter {
|
||||
};
|
||||
```
|
||||
|
||||
它也包含 `list` 和 `task` 字段,用来表示该互斥锁的等待队列。这之间有一个差异是 `mutex_waiter` 没有 `up` 字段,但却有一个可以根据内核设定`CONFIG_DEBUG_MUTEXES` 选项而存在的 `magic` 字段,它能用来存储一些在除错`互斥锁` 问题时有用的资讯。
|
||||
它也包含 `list` 和 `task` 字段,用来表示该互斥锁的等待队列。这之间有一个差异是 `mutex_waiter` 没有 `up` 字段,但却有一个可以根据内核设定`CONFIG_DEBUG_MUTEXES` 选项而存在的 `magic` 字段,它能用来存储一些在调试`互斥锁` 问题时有用的资讯。
|
||||
|
||||
现在我们知道了什么是 `互斥锁` 以及他是如何在Linux内核中呈现的。在这种情况下,我们可以开始一窥Linux内核所提供用来操作 `互斥锁` 的 [API](https://en.wikipedia.org/wiki/Application_programming_interface) 。
|
||||
|
||||
@@ -97,7 +97,7 @@ struct semaphore_waiter {
|
||||
|
||||
OK,在前面的章节我们已经了解什么是 `互斥锁` 原语,而且看过了Linux内核中用来呈现 `互斥锁` 的 `互斥锁` 结构。现在是时后开始研究用来操弄互斥锁的 [API](https://en.wikipedia.org/wiki/Application_programming_interface) 了。详细的 `互斥锁` API 被记录在 [include/linux/mutex.h](https://github.com/torvalds/linux/blob/master/include/linux/mutex.h) 头文件。一如既往,在考虑如何获取和释放一 `互斥锁` 之前,我们需要知道如何初始化它。
|
||||
|
||||
初始化 `互斥锁` 有两种方法,第一种是静态地。为此,Linux内核提供以下宏:
|
||||
初始化 `互斥锁` 有两种方法,第一种是静态初始化。为此,Linux内核提供以下宏:
|
||||
|
||||
```C
|
||||
#define DEFINE_MUTEX(mutexname) \
|
||||
@@ -117,7 +117,7 @@ OK,在前面的章节我们已经了解什么是 `互斥锁` 原语,而且
|
||||
|
||||
这个宏被定义在 [相同的](https://github.com/torvalds/linux/blob/master/include/linux/mutex.h) 头文件,并且我们可以了解到它初始化了 `mutex` 解构体中的字段。`count` 被初始化为 `1` ,这代表该互斥锁状态为 `无锁` 。`wait_lock` [自旋锁](https://en.wikipedia.org/wiki/Spinlock) 被初始化为无锁状态,而最后的栏位 `wait_list` 被初始化为空的 [双向列表](https://xinqiu.gitbooks.io/linux-insides-cn/content/DataStructures/linux-datastructures-1.html)。
|
||||
|
||||
第二种做法允许我们动态初始化一个 `互斥锁`。为此我们需要呼叫在 [kernel/locking/mutex.c](https://github.com/torvalds/linux/blob/master/kernel/locking/mutex.c) 源码文件的 `__mutex_init` 函数。事实上,大家很少直接呼叫 `__mutex_init` 函数。取而代之,我们使用下面的 `mutex_init` :
|
||||
第二种做法允许我们动态初始化一个 `互斥锁`。为此我们需要调用在 [kernel/locking/mutex.c](https://github.com/torvalds/linux/blob/master/kernel/locking/mutex.c) 源码文件的 `__mutex_init` 函数。事实上,大家很少直接调用 `__mutex_init` 函数。取而代之,我们使用下面的 `mutex_init` :
|
||||
|
||||
```C
|
||||
# define mutex_init(mutex) \
|
||||
@@ -128,7 +128,7 @@ do { \
|
||||
} while (0)
|
||||
```
|
||||
|
||||
我们可以看到 `mutex_init` 宏定义了 `lock_class_key` 并且呼叫 `__mutex_init` 函数。让我们看看这个函数的实现:
|
||||
我们可以看到 `mutex_init` 宏定义了 `lock_class_key` 并且调用 `__mutex_init` 函数。让我们看看这个函数的实现:
|
||||
|
||||
```C
|
||||
void
|
||||
@@ -148,10 +148,10 @@ __mutex_init(struct mutex *lock, const char *name, struct lock_class_key *key)
|
||||
如我们所见,`__mutex_init` 函数接收三个参数:
|
||||
|
||||
* `lock` - 互斥锁本身;
|
||||
* `name` - 除错用的互斥锁名称;
|
||||
* `name` - 调试用的互斥锁名称;
|
||||
* `key` - [锁验证器](https://www.kernel.org/doc/Documentation/locking/lockdep-design.txt)用的key.
|
||||
|
||||
在 `__mutex_init` 函数的一开始,我们可以看到 `互斥锁` 状态被初始化。我们透过 `atomic_set` 函数元子地将给定的变量赋予给定的值来将 `互斥锁` 的状态设为 `无锁`。之后我们可以看到 `自旋锁` 被初始化为解锁状态,这将保护 `互斥锁` 的`等待队列`,之后始化 `互斥锁` 的 `等待队列`。 之后我们清除该 `锁` 的持有者并且透过呼叫 [include/linux/osq_lock.h](https://github.com/torvalds/linux/blob/master/include/linux/osq_lock.h) 头文件中的 `osq_lock_init` 函数来初始化乐观队列(optimistic queue)。 而此函数也只是将乐观队列的的tail设定为无锁状态:
|
||||
在 `__mutex_init` 函数的一开始,我们可以看到 `互斥锁` 状态被初始化。我们通过 `atomic_set` 函数原子地将给定的变量赋予给定的值来将 `互斥锁` 的状态设为 `无锁`。之后我们可以看到 `自旋锁` 被初始化为解锁状态,这将保护 `互斥锁` 的`等待队列`,之后始化 `互斥锁` 的 `等待队列`。 之后我们清除该 `锁` 的持有者并且通过调用 [include/linux/osq_lock.h](https://github.com/torvalds/linux/blob/master/include/linux/osq_lock.h) 头文件中的 `osq_lock_init` 函数来初始化乐观队列(optimistic queue)。 而此函数也只是将乐观队列的的tail设定为无锁状态:
|
||||
|
||||
```C
|
||||
static inline bool osq_is_locked(struct optimistic_spin_queue *lock)
|
||||
@@ -160,7 +160,7 @@ static inline bool osq_is_locked(struct optimistic_spin_queue *lock)
|
||||
}
|
||||
```
|
||||
|
||||
在 `__mutex_init` 函数的结尾阶段,我们可以看到它呼叫了 `debug_mutex_init` 函数,不过就如同我在此 [章节](https://xinqiu.gitbooks.io/linux-insides-cn/content/SyncPrim/) 前面提到的那样,我们并不会在此章节讨论除错相关的内容。
|
||||
在 `__mutex_init` 函数的结尾阶段,我们可以看到它调用了 `debug_mutex_init` 函数,不过就如同我在此 [章节](https://xinqiu.gitbooks.io/linux-insides-cn/content/SyncPrim/) 前面提到的那样,我们并不会在此章节讨论调试相关的内容。
|
||||
|
||||
在 `互斥锁` 结构被初始化后,我们可以继续研究 `互斥锁` 同步原语的 `上锁` 和 `解锁` API。`mutex_lock` 和 `mutex_unlock` 函数的实现位于 [kernel/locking/mutex.c](https://github.com/torvalds/linux/blob/master/kernel/locking/mutex.c) 源码文件。首先,让我们从 `mutex_lock` 的实现开始吧。代码如下:
|
||||
|
||||
@@ -173,11 +173,11 @@ void __sched mutex_lock(struct mutex *lock)
|
||||
}
|
||||
```
|
||||
|
||||
我们可以从 [include/linux/kernel.h](https://github.com/torvalds/linux/blob/master/include/linux/kernel.h) 头文件的 `mutex_lock` 函数开头看到 `might_sleep` 宏被调用。该宏的实现取决于 `CONFIG_DEBUG_ATOMIC_SLEEP` 内核配置选项,如果这个选项被启用,该宏会在 [元子](https://en.wikipedia.org/wiki/Linearizability) 上下文中执行时打印栈追踪。此宏为除错用途上的帮手,除此之外,这个宏什么都没做。
|
||||
我们可以从 [include/linux/kernel.h](https://github.com/torvalds/linux/blob/master/include/linux/kernel.h) 头文件的 `mutex_lock` 函数开头看到 `might_sleep` 宏被调用。该宏的实现取决于 `CONFIG_DEBUG_ATOMIC_SLEEP` 内核配置选项,如果这个选项被启用,该宏会在 [原子](https://en.wikipedia.org/wiki/Linearizability) 上下文中执行时打印栈追踪。此宏为调试用途上的帮手,除此之外,这个宏什么都没做。
|
||||
|
||||
在 `might_sleep` 宏之后,我们可以看到 `__mutex_fastpath_lock` 函数被呼叫。此函数是體系結構相關的,并且因为我们此书探讨的是 [x86_64](https://en.wikipedia.org/wiki/X86-64) 体系结构, \ `__mutex_fastpath_lock` 的实现部份位于 [arch/x86/include/asm/mutex_64.h](https://github.com/torvalds/linux/blob/master/arch/x86/include/asm/mutex_64.h) 头文件。正如我们从`__mutex_fastpath_lock` 函数名称可以理解的那样,此函数将尝试透过 fast path 试图获取一个锁,或者换句话说此函数试图递减一个给定互斥锁的 `count` 字段。
|
||||
在 `might_sleep` 宏之后,我们可以看到 `__mutex_fastpath_lock` 函数被调用。此函数是体系结构相关的,并且因为我们此书探讨的是 [x86_64](https://en.wikipedia.org/wiki/X86-64) 体系结构, \ `__mutex_fastpath_lock` 的实现部分位于 [arch/x86/include/asm/mutex_64.h](https://github.com/torvalds/linux/blob/master/arch/x86/include/asm/mutex_64.h) 头文件。正如我们从`__mutex_fastpath_lock` 函数名称可以理解的那样,此函数将尝试通过 fast path 试图获取一个锁,或者换句话说此函数试图递减一个给定互斥锁的 `count` 字段。
|
||||
|
||||
`__mutex_fastpath_lock` 函数的实现由两部份组成。第一部份是 [内联汇编](https://xinqiu.gitbooks.io/linux-insides-cn/content/Theory/linux-theory-3.html) 。让我们看看:
|
||||
`__mutex_fastpath_lock` 函数的实现由两部分组成。第一部分是 [内联汇编](https://xinqiu.gitbooks.io/linux-insides-cn/content/Theory/linux-theory-3.html) 。让我们看看:
|
||||
|
||||
```C
|
||||
asm_volatile_goto(LOCK_PREFIX " decl %0\n"
|
||||
@@ -199,7 +199,7 @@ asm_volatile_goto(LOCK_PREFIX " decl %0\n"
|
||||
#define LOCK_PREFIX LOCK_PREFIX_HERE "\n\tlock; "
|
||||
```
|
||||
|
||||
正如我们从前面部分已经知道的那样,该指令允许 [元子地](https://en.wikipedia.org/wiki/Linearizability) 执行被它前缀修饰的指令 。所以,在我们的汇编语句的第一步中,我们尝试将给定的 `mutex->counter` 的字段递减。再下一步,如 `mutex->counter` 在被递减完后的值为非负数,那么 [jns](http://unixwiz.net/techtips/x86-jumps.html) 指令将会执行跳转到 `exit` 标签所在处。`exit` 标签是 `__mutex_fastpath_lock` 函数的第二部份,而它仅仅是指到该函数的出口:
|
||||
正如我们从前面部分已经知道的那样,该指令允许 [原子地](https://en.wikipedia.org/wiki/Linearizability) 执行被它前缀修饰的指令 。所以,在我们的汇编语句的第一步中,我们尝试将给定的 `mutex->counter` 的字段递减。再下一步,如 `mutex->counter` 在被递减完后的值为非负数,那么 [jns](http://unixwiz.net/techtips/x86-jumps.html) 指令将会执行跳转到 `exit` 标签所在处。`exit` 标签是 `__mutex_fastpath_lock` 函数的第二部分,而它仅仅是指到该函数的出口:
|
||||
|
||||
```C
|
||||
exit:
|
||||
@@ -212,7 +212,7 @@ exit:
|
||||
fail_fn(v);
|
||||
```
|
||||
|
||||
将在内联汇编后被调用。`fail_fn` 是`__mutex_fastpath_lock` 函数的第二个参数,其为一指标指到用来获取给定锁的 `midpath/slowpath` 路径函数。我们的这个例子中`fail_fn` 是 `__mutex_lock_slowpath` 函数。在我们一窥 `__mutex_lock_slowpath` 函数的实现前,我们先把 `mutex_lock` 函数的实现看完。在最简单的情况下,锁会被一进程透过执行 `__mutex_fastpath_lock` 路径后成功被获取。这种情况下,我们只差在 `mutex_lock`的结尾呼叫
|
||||
将在内联汇编后被调用。`fail_fn` 是`__mutex_fastpath_lock` 函数的第二个参数,其为一指针指到用来获取给定锁的 `midpath/slowpath` 路径函数。我们的这个例子中`fail_fn` 是 `__mutex_lock_slowpath` 函数。在我们一窥 `__mutex_lock_slowpath` 函数的实现前,我们先把 `mutex_lock` 函数的实现看完。在最简单的情况下,锁会被某进程通过执行 `__mutex_fastpath_lock` 路径后成功被获取。这种情况下,我们仅在 mutex_lock 结尾调用即可。
|
||||
|
||||
```C
|
||||
mutex_set_owner(lock);
|
||||
@@ -227,7 +227,7 @@ static inline void mutex_set_owner(struct mutex *lock)
|
||||
}
|
||||
```
|
||||
|
||||
另外一种情况,让我们研究一进程因为锁已被其它进程持有,而无法顺利获得的情况,我们已经知道在这种情境下 `__mutex_lock_slowpath` 函数会被调用,让我们开始研究这个函数。此函数被定义在 [kernel/locking/mutex.c](https://github.com/torvalds/linux/blob/master/kernel/locking/mutex.c) 源码文件,并且这个函数由 `container_of` 宏开头,意图透过 `__mutex_fastpath_lock` 给的互斥锁状态变量来获得互斥锁本身:
|
||||
另外一种情况,让我们研究某进程因为锁已被其它进程持有,而无法顺利获得的情况,我们已经知道在这种情境下 `__mutex_lock_slowpath` 函数会被调用,让我们开始研究这个函数。此函数被定义在 [kernel/locking/mutex.c](https://github.com/torvalds/linux/blob/master/kernel/locking/mutex.c) 源码文件,并且这个函数由 `container_of` 宏开头,意图通过 `__mutex_fastpath_lock` 给的互斥锁状态变量来获得互斥锁本身:
|
||||
|
||||
```C
|
||||
__visible void __sched
|
||||
@@ -240,7 +240,7 @@ __mutex_lock_slowpath(atomic_t *lock_count)
|
||||
}
|
||||
```
|
||||
|
||||
之后将获得的 `互斥锁` 带入函数 `__mutex_lock_common` 进行调用。 `__mutex_lock_common` 函数一开始会关闭 [抢占](https://en.wikipedia.org/wiki/Preemption_%28computing%29) 直到下次的重新排程:
|
||||
之后将获得的 `互斥锁` 带入函数 `__mutex_lock_common` 进行调用。 `__mutex_lock_common` 函数一开始会关闭 [抢占](https://en.wikipedia.org/wiki/Preemption_%28computing%29) 直到下次的重新调度:
|
||||
|
||||
```C
|
||||
preempt_disable();
|
||||
@@ -255,7 +255,7 @@ if (mutex_optimistic_spin(lock, ww_ctx, use_ww_ctx)) {
|
||||
}
|
||||
```
|
||||
|
||||
首先 `mutex_optimistic_spin` 函数检查我们需不需要被重新排程,或者换句话说,没有其他优先级更高的任务可以运行。 如果这个检查成立我们就将当前 循环者(spinner) 更新至 `MCS` 锁的等待队列。这种情况下互斥锁的获取在某个时间只会由一个循环者获得:
|
||||
首先 `mutex_optimistic_spin` 函数检查我们需不需要被重新调度,或者换句话说,没有其他优先级更高的任务可以运行。 如果这个检查成立我们就将当前 循环者(spinner) 更新至 `MCS` 锁的等待队列。这种情况下互斥锁的获取在某个时间只会由一个循环者获得:
|
||||
|
||||
```C
|
||||
osq_lock(&lock->osq)
|
||||
@@ -280,7 +280,7 @@ while (true) {
|
||||
}
|
||||
```
|
||||
|
||||
并试图获取该锁。首先我们尝试获取该锁的持有者资讯,如果持有者存在 (在进程已释放互斥锁的情况下可能不存在),我们就在持有者释放锁前于 `mutex_spin_on_owner` 函数中等待。如果在等待锁持有者的过程中遭遇了高优先级任務,我们就离开循环进入睡眠。在另外一种情况下,该锁被进程释放,那我们就试图透过 `mutex_try_to_acquired` 来获取该锁。如果这个操作顺利完成,我们就为该互斥锁设定新的持有者,将我们自身从`MCS` 等待队列中移除,并从 `mutex_optimistic_spin` 函数中离开。至此锁就被一进程获取完成,我们接着开启 [抢占](https://en.wikipedia.org/wiki/Preemption_%28computing%29) 并从 `__mutex_lock_common` 函数中离开:
|
||||
并试图获取该锁。首先我们尝试获取该锁的持有者资讯,如果持有者存在 (在进程已释放互斥锁的情况下可能不存在),我们就在持有者释放锁前于 `mutex_spin_on_owner` 函数中等待。如果在等待锁持有者的过程中遭遇了高优先级任務,我们就离开循环进入睡眠。在另外一种情况下,该锁被进程释放,那我们就试图通过 `mutex_try_to_acquired` 来获取该锁。如果这个操作顺利完成,我们就为该互斥锁设定新的持有者,将我们自身从`MCS` 等待队列中移除,并从 `mutex_optimistic_spin` 函数中离开。至此锁就被某进程获取完成,我们接着开启 [抢占](https://en.wikipedia.org/wiki/Preemption_%28computing%29) 并从 `__mutex_lock_common` 函数中离开:
|
||||
|
||||
```C
|
||||
if (mutex_optimistic_spin(lock, ww_ctx, use_ww_ctx)) {
|
||||
@@ -346,7 +346,7 @@ for (;;) {
|
||||
}
|
||||
```
|
||||
|
||||
这边我们再尝试获取一次锁,如果成功的话就离开。是的,我们会在循环前的那次尝试失败后立再一次的尝试获取锁。我们需要这样做,以确保一旦锁在之后被解锁时,我们会被唤醒。 除此之外,它还允许我们在睡眠后获得锁。在另一种情况下,我们检查当前进程是否有pending [信号](https://en.wikipedia.org/wiki/Unix_signal),如果进程在等待锁获取期间被 `信号` 中断,则退出。在迭代的结尾因为我们未能成功获取锁,所以我们将任务的状态设为 `TASK_UNINTERRUPTIBLE` 并透过呼叫 `schedule_preempt_disabled` 函数去睡眠。
|
||||
这边我们再尝试获取一次锁,如果成功的话就离开。是的,我们会在循环前的那次尝试失败后立再一次的尝试获取锁。我们需要这样做,以确保一旦锁在之后被解锁时,我们会被唤醒。 除此之外,它还允许我们在睡眠后获得锁。在另一种情况下,我们检查当前进程是否有pending [信号](https://en.wikipedia.org/wiki/Unix_signal),如果进程在等待锁获取期间被 `信号` 中断,则退出。在迭代的结尾因为我们未能成功获取锁,所以我们将任务的状态设为 `TASK_UNINTERRUPTIBLE` 并通过调用 `schedule_preempt_disabled` 函数去睡眠。
|
||||
|
||||
这就是全部,我们已经考虑了进程获取锁时可能通过的所有三条路径。现在让我们来研究下 `mutex_unlock` 是如何被实现的。将被一个希望释放锁的进程调用 `mutex_unlock`, `__mutex_fastpath_unlock` 也将被从 [arch/x86/include/asm/mutex_64.h](https://github.com/torvalds/linux/blob/master/arch/x86/include/asm/mutex_64.h) 头文件中调用:
|
||||
|
||||
@@ -374,7 +374,7 @@ exit:
|
||||
}
|
||||
```
|
||||
|
||||
事实上, 这之中只有一个区别,我们递增 `mutex->count` 字段。这个操作过后锁将呈现 `无锁` 状态。随着 `互斥锁` 释放,如等待队列有条目的话我们必须将其更新。在这种情况下,`fail_fn` 函数会被调用,也就是 `__mutex_unlock_slowpath`。`__mutex_unlock_slowpath` 函数仅是从给定的`mutex->count` 来获取 `mutex` 实例,并调用 `__mutex_unlock_common_slowpath` 函数:
|
||||
事实上, 其中只有一个区别,我们递增 `mutex->count` 字段。这个操作过后锁将呈现 `无锁` 状态。随着 `互斥锁` 释放,如等待队列有条目的话我们必须将其更新。在这种情况下,`fail_fn` 函数会被调用,也就是 `__mutex_unlock_slowpath`。`__mutex_unlock_slowpath` 函数仅是从给定的`mutex->count` 来获取 `mutex` 实例,并调用 `__mutex_unlock_common_slowpath` 函数:
|
||||
|
||||
```C
|
||||
__mutex_unlock_slowpath(atomic_t *lock_count)
|
||||
@@ -403,14 +403,12 @@ if (!list_empty(&lock->wait_list)) {
|
||||
* `mutex_lock_killable`;
|
||||
* `mutex_trylock`.
|
||||
|
||||
以及对应相同前缀的 `unlock` 函数. 我们就不在此解释这些 `API` 了, 因为他们跟 `信号量` 所提供的 `API` 类似。你可以透过阅读 [前一部份](https://xinqiu.gitbooks.io/linux-insides-cn/content/SyncPrim/linux-sync-3.html) 来了解更多。
|
||||
|
||||
以上。
|
||||
以及对应相同前缀的 `unlock` 函数. 我们就不在此解释这些 `API` 了, 因为他们跟 `信号量` 所提供的 `API` 类似。你可以通过阅读 [前一部分](https://xinqiu.gitbooks.io/linux-insides-cn/content/SyncPrim/linux-sync-3.html) 来了解更多。
|
||||
|
||||
总结
|
||||
--------------------------------------------------------------------------------
|
||||
|
||||
至此我们结束了Linux内核 [同步原语](https://en.wikipedia.org/wiki/Synchronization_%28computer_science%29) 章节的第四部份。在这部份我们见到了新的同步原语 - `互斥锁`。就理论上来说,此同步原语与 [信号量](https://en.wikipedia.org/wiki/Semaphore_%28programming%29) 非常相似。事实上,`互斥锁` 代表着二进制信号量。但它的实现与Linux内核中 `信号量` 实现并不同。在下一部分中,我们将继续深入研究Linux内核中的同步原语。
|
||||
至此我们结束了Linux内核 [同步原语](https://en.wikipedia.org/wiki/Synchronization_%28computer_science%29) 章节的第四部分。在这部分我们见到了新的同步原语 - `互斥锁`。就理论上来说,此同步原语与 [信号量](https://en.wikipedia.org/wiki/Semaphore_%28programming%29) 非常相似。事实上,`互斥锁` 代表着二进制信号量。但它的实现与Linux内核中 `信号量` 实现并不同。在下一部分中,我们将继续深入研究Linux内核中的同步原语。
|
||||
|
||||
如果你有问题或者建议,请在twitter [0xAX](https://twitter.com/0xAX)上联系我,通过 [email](mailto:anotherworldofworld@gmail.com) 联系我,或者创建一个[issue](https://github.com/MintCN/linux-insides-zh/issues/new)。
|
||||
|
||||
|
||||
Reference in New Issue
Block a user