diff --git a/SyncPrim/sync-1.md b/SyncPrim/sync-1.md index 8f600f0..c0f9200 100644 --- a/SyncPrim/sync-1.md +++ b/SyncPrim/sync-1.md @@ -3,9 +3,9 @@ Linux 内核中的同步原语. 第一部分. Introduction -------------------------------------------------------------------------------- -这一部分为[linux-insides](http://0xax.gitbooks.io/linux-insides/content/) 这本书开启了新的章节。定时器和时间管理相关的观念在上一个[章节](https://0xax.gitbooks.io/linux-insides/content/Timers/index.html) 已经描述过了。现在是时候继续了。正如你可能从这个部分的标题所理解的一样,这个章节将会描述 Linux 内核中的[同步](https://en.wikipedia.org/wiki/Synchronization_%28computer_science%29) 原语。 +这一部分为[linux-insides](http://0xax.gitbooks.io/linux-insides/content/) 这本书开启了新的章节。定时器和时间管理相关的概念在上一个[章节](https://0xax.gitbooks.io/linux-insides/content/Timers/index.html)已经描述过了。现在是时候继续了。就像你可能从这一部分的标题所了解的那样,本章节将会描述 Linux 内核中的[同步](https://en.wikipedia.org/wiki/Synchronization_%28computer_science%29)原语。 -像往常一样,在将要考虑一些同步相关的事情之前,我们将会尝试去概括地了解什么是`同步原语`。事实上,同步原语是一种提供两个或者多个[并行](https://en.wikipedia.org/wiki/Parallel_computing)进程或者线程不同时执行一个相同的代码段这样一种能力的软件机制。例如让我们看看下面的代码片段: +像往常一样,在考虑一些同步相关的事情之前,我们会尝试去概括地了解什么是`同步原语`。事实上,同步原语是一种软件机制,提供了两个或者多个[并行](https://en.wikipedia.org/wiki/Parallel_computing)进程或者线程在不同时刻执行一段相同的代码段的能力。例如下面的代码片段: ```C mutex_lock(&clocksource_mutex); @@ -20,9 +20,9 @@ clocksource_select(); ... mutex_unlock(&clocksource_mutex); ``` -出自 [kernel/time/clocksource.c](https://github.com/torvalds/linux/master/kernel/time/clocksource.c) 源文件。这段代码来自于 `__clocksource_register_scale` 函数,此函数添加给定的 [clocksource](https://0xax.gitbooks.io/linux-insides/content/Timers/timers-2.html) 到时钟源列表中。这个函数在注册时钟源列表中生成两个不同的操作。例如 `clocksource_enqueue` 函数就是添加给定时钟源到注册时钟源列表——`clocksource_list` 中。注意这几行代码闭两个函数所包围:`mutex_lock` 和 `mutex_unlock`,这两个函数都带有一个参数——在本例中为`clocksource_mutex`。 +出自 [kernel/time/clocksource.c](https://github.com/torvalds/linux/master/kernel/time/clocksource.c) 源文件。这段代码来自于 `__clocksource_register_scale` 函数,此函数添加给定的 [clocksource](https://0xax.gitbooks.io/linux-insides/content/Timers/timers-2.html) 到时钟源列表中。这个函数在注册时钟源列表中生成两个不同的操作。例如 `clocksource_enqueue` 函数就是添加给定时钟源到注册时钟源列表——`clocksource_list` 中。注意这几行代码闭两个函数所包围:`mutex_lock` 和 `mutex_unlock`,这两个函数都带有一个参数——在本例中为 `clocksource_mutex`。 -这些函数表达了基于[互斥锁 (mutex)](https://en.wikipedia.org/wiki/Mutual_exclusion) 同步原语的加锁和解锁。当 `mutex_lock` 被执行,则允许我们阻止两个或两个以上线程执行这段代码,而`mute_unlock`还没有被互斥锁的处理拥有者锁执行。换句话说,就是阻止在`clocksource_list`上的并行操作。为什么在这里需要使用 `互斥锁`? 如果两个并行处理尝试去注册一个时钟源会怎样。正如我们已经知道的那样,其中具有最大的等级(其具有最高的频率在系统中注册的时钟源)的列表中选择一个时钟源后,`clocksource_enqueue` 函数立即将一个给定的时钟源到`clocksource_list`列表: +这些函数展示了基于[互斥锁 (mutex)](https://en.wikipedia.org/wiki/Mutual_exclusion) 同步原语的加锁和解锁。当 `mutex_lock` 被执行,允许我们阻止两个或两个以上线程执行这段代码,而 `mute_unlock` 还没有被互斥锁的处理拥有者锁执行。换句话说,就是阻止在`clocksource_list`上的并行操作。为什么在这里需要使用 `互斥锁`? 如果两个并行处理尝试去注册一个时钟源会怎样。正如我们已经知道的那样,其中具有最大的等级(其具有最高的频率在系统中注册的时钟源)的列表中选择一个时钟源后,`clocksource_enqueue` 函数立即将一个给定的时钟源到 `clocksource_list` 列表: ```C static void clocksource_enqueue(struct clocksource *cs) @@ -36,6 +36,7 @@ static void clocksource_enqueue(struct clocksource *cs) list_add(&cs->list, entry); } ``` + 如果两个并行处理尝试同时去执行这个函数,那么这两个处理可能会找到相同的 `入口 (entry)` 可能发生[竞态条件(race condition)](https://en.wikipedia.org/wiki/Race_condition) 或者换句话说,第二个执行 `list_add` 的处理程序,将会重写第一个线程写入的时钟源。 除了这个简答的例子,同步原语在 Linux 内核无处不在。如果再翻阅之前的[章节] (https://0xax.gitbooks.io/linux-insides/content/Timers/index.html) 或者其他章节或者如果大概看看 Linux 内核源码,就会发现许多地方都使用同步原语。我们不考虑 `mutex` 在 Linux内核是如何实现的。事实上,Linux内核提供了一系列不同的同步原语: @@ -75,7 +76,7 @@ typedef struct spinlock { } spinlock_t; ``` -这段代码在 [include/linux/spinlock_types.h](https://github.com/torvalds/linux/master/include/linux/spinlock_types.h) 头文件中。可以看出,它的实现依赖于 `CONFIG_DEBUG_LOCK_ALLOC` 内核配置选项这个状态。现在我们跳过这一块,因为所有的调试相关的事情都将会在这一部分的最后。所以,如果 `CONFIG_DEBUG_LOCK_ALLOC` 内核配置选项不可用,那么`spinlock_t` 则包含 [联合体(union)](https://en.wikipedia.org/wiki/Union_type#C.2FC.2B.2B),这个联合体有一个字段——`raw_spinlock`: +这段代码在 [include/linux/spinlock_types.h](https://github.com/torvalds/linux/master/include/linux/spinlock_types.h) 头文件中。可以看出,它的实现依赖于 `CONFIG_DEBUG_LOCK_ALLOC` 内核配置选项这个状态。现在我们跳过这一块,因为所有的调试相关的事情都将会在这一部分的最后。所以,如果 `CONFIG_DEBUG_LOCK_ALLOC` 内核配置选项不可用,那么`spinlock_t` 则包含 [联合体(union)](https://en.wikipedia.org/wiki/Union_type#C.2FC.2B.2B),这个联合体有一个字段—— `raw_spinlock`: ```C typedef struct spinlock { @@ -84,8 +85,7 @@ typedef struct spinlock { }; } spinlock_t; ``` - -The `raw_spinlock` structure defined in the [same](https://github.com/torvalds/linux/master/include/linux/spinlock_types.h) header file and represents implementation of `normal` spinlock. Let's look how the `raw_spinlock` structure is defined: +`raw_spinlock` 结构的定义在[相同的](https://github.com/torvalds/linux/master/include/linux/spinlock_types.h)头文件中并且表达了`普通(normal)`自旋锁的实现。让我们看看 `raw_spinlock`结构是如何定义的: ```C typedef struct raw_spinlock { @@ -96,7 +96,7 @@ typedef struct raw_spinlock { } raw_spinlock_t; ``` -where the `arch_spinlock_t` represents architecture-specific `spinlock` implementation and the `break_lock` field which holds value - `1` in a case when one processor starts to wait while the lock is held on another processor on [SMP](https://en.wikipedia.org/wiki/Symmetric_multiprocessing) systems. This allows prevent long time locking. As consider the [x86_64](https://en.wikipedia.org/wiki/X86-64) architecture in this books, so the `arch_spinlock_t` is defined in the [arch/x86/include/asm/spinlock_types.h](https://github.com/torvalds/linux/master/arch/x86/include/asm/spinlock_types.h) header file and looks: +这里的 `arch_spinlock_t` 表示了体系结构指定的`自旋锁`实现并且 `break_lock` 字段持有值—— 为`1`,当一个处理器开始等待而锁被另一个处理器持有时,使用的[对称多处理器SMP](https://en.wikipedia.org/wiki/Symmetric_multiprocessing) 系统的例子中。这样就可以防止长时间加锁。考虑本书的 [x86_64](https://en.wikipedia.org/wiki/X86-64) 架构,因此 `arch_spinlock_t` 被定义在 [arch/x86/include/asm/spinlock_types.h](https://github.com/torvalds/linux/master/arch/x86/include/asm/spinlock_types.h) 头文件中,并且看上去是这样: ```C #ifdef CONFIG_QUEUED_SPINLOCKS @@ -112,7 +112,7 @@ typedef struct arch_spinlock { } arch_spinlock_t; ``` -As we may see, the definition of the `arch_spinlock` structure depends on the value of the `CONFIG_QUEUED_SPINLOCKS` kernel configuration option. This configuration option the Linux kernel supports `spinlocks` with queue. This special type of `spinlocks` which instead of `acquired` and `released` [atomic](https://en.wikipedia.org/wiki/Linearizability) values used `atomic` operation on a `queue`. If the `CONFIG_QUEUED_SPINLOCKS` kernel configuration option is enabled, the `arch_spinlock_t` will be represented by the following structure: +正如我们所看到的,`arch_spinlock` 结构的定义依赖于 `CONFIG_QUEUED_SPINLOCKS` 内核配置选项的值。这个 Linux内核配置选项支持使用队列的 `自旋锁`。这个`自旋锁`的特殊类型替代了 `acquired` 和 `released` [原子](https://en.wikipedia.org/wiki/Linearizability)值,在`队列`上使用`原子`操作。如果 `CONFIG_QUEUED_SPINLOCKS` 内核配置选项启动,那么 `arch_spinlock_t`将会被表示成如下的结构: ```C typedef struct qspinlock { @@ -120,20 +120,20 @@ typedef struct qspinlock { } arch_spinlock_t; ``` -from the [include/asm-generic/qspinlock_types.h](https://github.com/torvalds/linux/master/include/asm-generic/qspinlock_types.h) header file. +来自于 [include/asm-generic/qspinlock_types.h](https://github.com/torvalds/linux/master/include/asm-generic/qspinlock_types.h) 头文件。 -We will not stop on this structures for now and before we will consider both `arch_spinlock` and the `qspinlock`, let's look at the operations on a spinlock. The Linux kernel provides following main operations on a `spinlock`: +目前我们不会在这个结构上停止探索,在考虑`arch_spinlock` 和 `qspinlock` 之前,先看看自旋锁上的操作。 Linux内核在`自旋锁`上提供了一下主要的操作: -* `spin_lock_init` - produces initialization of the given `spinlock`; -* `spin_lock` - acquires given `spinlock`; -* `spin_lock_bh` - disables software [interrupts](https://en.wikipedia.org/wiki/Interrupt) and acquire given `spinlock`. -* `spin_lock_irqsave` and `spin_lock_irq` - disable interrupts on local processor and preserve/not preserve previous interrupt state in the `flags`; -* `spin_unlock` - releases given `spinlock`; -* `spin_unlock_bh` - releases given `spinlock` and enables software interrupts; -* `spin_is_locked` - returns the state of the given `spinlock`; -* and etc. +* `spin_lock_init` ——给定的`自旋锁`进行初始化; +* `spin_lock` ——获取给定的`自旋锁`; +* `spin_lock_bh` ——禁止软件[中断](https://en.wikipedia.org/wiki/Interrupt) 并且获取给定的`自旋锁`。 +* `spin_lock_irqsave` 和 `spin_lock_irq` ——禁止本地处理器上的中断,并且保存/不保存之前的中断状态的`标识(flag)`; +* `spin_unlock` ——释放给定的`自旋锁`; +* `spin_unlock_bh` ——释放给定的`自旋锁`并且启动软件中断; +* `spin_is_locked` - 返回给定的`自旋锁`的状态; +* 等等。 -Let's look on the implementation of the `spin_lock_init` macro. As I already wrote, this and other macro are defined in the [include/linux/spinlock.h](https://github.com/torvalds/linux/master/include/linux/spinlock.h) header file and the `spin_lock_init` macro looks: +来看看 `spin_lock_init` 宏的实现。就如我已经写过的一样,这个宏和其他宏定义都在 [include/linux/spinlock.h](https://github.com/torvalds/linux/master/include/linux/spinlock.h) 头文件里,并且 `spin_lock_init` 宏如下所示: ```C #define spin_lock_init(_lock) \ @@ -143,7 +143,7 @@ do { \ } while (0) ``` -As we may see, the `spin_lock_init` macro takes a `spinlock` and executes two operations: check the given `spinlock` and execute the `raw_spin_lock_init`. The implementation of the `spinlock_check` is pretty easy, this function just returns the `raw_spinlock_t` of the given `spinlock` to be sure that we got exactly `normal` raw spinlock: +正如所看到的,`spin_lock_init` 宏有一个`自旋锁`,执行两步操作:检查我们看到的给定的`自旋锁`和执行 `raw_spin_lock_init`。`spinlock_check`的实现相当简单,实现的函数仅仅返回已知的`自旋锁`的 `raw_spinlock_t`,来确保我们精确获得`正常(normal)`原生自旋锁: ```C static __always_inline raw_spinlock_t *spinlock_check(spinlock_t *lock) @@ -152,7 +152,7 @@ static __always_inline raw_spinlock_t *spinlock_check(spinlock_t *lock) } ``` -The `raw_spin_lock_init` macro: +`raw_spin_lock_init` 宏: ```C # define raw_spin_lock_init(lock) \ @@ -161,7 +161,7 @@ do { \ } while (0) \ ``` -assigns the value of the `__RAW_SPIN_LOCK_UNLOCKED` with the given `spinlock` to the given `raw_spinlock_t`. As we may understand from the name of the `__RAW_SPIN_LOCK_UNLOCKED` macro, this macro does initialization of the given `spinlock` and set it to `released` state. This macro defined in the [include/linux/spinlock_types.h](https://github.com/torvalds/linux/master/include/linux/spinlock_types.h) header file and expands to the following macros: +用 `__RAW_SPIN_LOCK_UNLOCKED` 的值和给定的`自旋锁`赋值给给定的 `raw_spinlock_t`。就像我们能从`__RAW_SPIN_LOCK_UNLOCKED` 宏的名字中了解的那样,这个宏为给定的`自旋锁`执行初始化操作,并且将锁设置为`释放(released)`状态。宏的定义在 [include/linux/spinlock_types.h](https://github.com/torvalds/linux/master/include/linux/spinlock_types.h) 头文件中,并且扩展了一下的宏: ```C #define __RAW_SPIN_LOCK_UNLOCKED(lockname) \ @@ -175,27 +175,27 @@ assigns the value of the `__RAW_SPIN_LOCK_UNLOCKED` with the given `spinlock` to } ``` -As I already wrote above, we will not consider stuff which is related to debugging of synchronization primitives. In this case we will not consider the `SPIN_DEBUG_INIT` and the `SPIN_DEP_MAP_INIT` macros. So the `__RAW_SPINLOCK_UNLOCKED` macro will be expanded to the: +正如之前所写的一样,我们不考虑同步原语调试相关的东西。在本例中也不考虑 `SPIN_DEBUG_INIT` 和 `SPIN_DEP_MAP_INIT` 宏。于是 `__RAW_SPINLOCK_UNLOCKED` 宏被扩展成: ```C *(&(_lock)->rlock) = __ARCH_SPIN_LOCK_UNLOCKED; ``` -where the `__ARCH_SPIN_LOCK_UNLOCKED` is: +而 `__ARCH_SPIN_LOCK_UNLOCKED` 宏是: ```C #define __ARCH_SPIN_LOCK_UNLOCKED { { 0 } } ``` -and: +还有: ```C #define __ARCH_SPIN_LOCK_UNLOCKED { ATOMIC_INIT(0) } ``` -for the [x86_64](https://en.wikipedia.org/wiki/X86-64) architecture. if the `CONFIG_QUEUED_SPINLOCKS` kernel configuration option is enabled. So, after the expansion of the `spin_lock_init` macro, a given `spinlock` will be initialized and its state will be - `unlocked`. +这是对于 [x86_64] 架构,如果 `CONFIG_QUEUED_SPINLOCKS` 内核配置选项启用的情况。那么,在 `spin_lock_init` 宏的扩展之后,给定的`自旋锁`将会初始化并且状态变为——`解锁(unlocked)`。 -From this moment we know how to initialize a `spinlock`, now let's consider [API](https://en.wikipedia.org/wiki/Application_programming_interface) which Linux kernel provides for manipulations of `spinlocks`. The first is: +从这一时刻起我们了解了如何去初始化一个`自旋锁`,现在考虑 Linux 内核为`自旋锁`的操作提供的 [API](https://en.wikipedia.org/wiki/Application_programming_interface)。首先是: ```C static __always_inline void spin_lock(spinlock_t *lock) @@ -204,13 +204,13 @@ static __always_inline void spin_lock(spinlock_t *lock) } ``` -function which allows us to `acquire` a spinlock. The `raw_spin_lock` macro is defined in the same header file and expands to the call of the `_raw_spin_lock` function: +此函数允许我们`获取`一个自旋锁。`raw_spin_lock` 宏定义在同一个头文件中,并且扩展了`_raw_spin_lock`函数的调用: ```C #define raw_spin_lock(lock) _raw_spin_lock(lock) ``` -As we may see in the [include/linux/spinlock.h](https://github.com/torvalds/linux/blob/master/include/linux/spinlock.h) header file, definition of the `_raw_spin_lock` macro depends on the `CONFIG_SMP` kernel configuration parameter: +就像在 [include/linux/spinlock.h](https://github.com/torvalds/linux/blob/master/include/linux/spinlock.h) 头文件所了解的那样,`_raw_spin_lock` 宏的定义依赖于 `CONFIG_SMP` 内核配置参数: ```C #if defined(CONFIG_SMP) || defined(CONFIG_DEBUG_SPINLOCK) @@ -220,13 +220,13 @@ As we may see in the [include/linux/spinlock.h](https://github.com/torvalds/linu #endif ``` -So, if the [SMP](https://en.wikipedia.org/wiki/Symmetric_multiprocessing) is enabled in the Linux kernel, the `_raw_spin_lock` macro is defined in the [arch/x86/include/asm/spinlock.h](https://github.com/torvalds/linux/blob/master/arch/x86/include/asm/spinlock.h) header file and looks like: +因此,如果在 Linux内核中 [SMP](https://en.wikipedia.org/wiki/Symmetric_multiprocessing) 启用了,那么 `_raw_spin_lock` 宏就在 [arch/x86/include/asm/spinlock.h](https://github.com/torvalds/linux/blob/master/arch/x86/include/asm/spinlock.h) 头文件中定义,并且看起来像这样: ```C #define _raw_spin_lock(lock) __raw_spin_lock(lock) ``` -The `__raw_spin_lock` function looks: +`__raw_spin_lock` 函数的定义: ```C static inline void __raw_spin_lock(raw_spinlock_t *lock) @@ -236,8 +236,7 @@ static inline void __raw_spin_lock(raw_spinlock_t *lock) LOCK_CONTENDED(lock, do_raw_spin_trylock, do_raw_spin_lock); } ``` - -As you may see, first of all we disable [preemption](https://en.wikipedia.org/wiki/Preemption_%28computing%29) by the call of the `preempt_disable` macro from the [include/linux/preempt.h](https://github.com/torvalds/linux/blob/master/include/linux/preempt.h) (more about this you may read in the ninth [part](https://0xax.gitbooks.io/linux-insides/content/Initialization/linux-initialization-9.html) of the Linux kernel initialization process chapter). When we will unlock the given `spinlock`, preemption will be enabled again: +就像你们可能了解的那样, 首先我们禁用了[抢占](https://en.wikipedia.org/wiki/Preemption_%28computing%29),通过 [include/linux/preempt.h](https://github.com/torvalds/linux/blob/master/include/linux/preempt.h) (在 Linux 内核初始化进程章节的第九[部分](https://0xax.gitbooks.io/linux-insides/content/Initialization/linux-initialization-9.html)会了解到更多关于抢占)中的 `preempt_disable` 调用实现禁用。当我们将要解开给定的`自旋锁`,抢占将会再次启用: ```C static inline void __raw_spin_unlock(raw_spinlock_t *lock) @@ -248,15 +247,14 @@ static inline void __raw_spin_unlock(raw_spinlock_t *lock) preempt_enable(); } ``` - -We need to do this while a process is spinning on a lock, other processes must be prevented to preempt the process which acquired a lock. The `spin_acquire` macro which through a chain of other macros expands to the call of the: +当程序正在自旋锁时我们需要这么做,其他处理必须被阻止抢占这个已经获取锁的程序。`spin_acquire` 宏通过其他宏宏展调用实现: ```C #define spin_acquire(l, s, t, i) lock_acquire_exclusive(l, s, t, NULL, i) #define lock_acquire_exclusive(l, s, t, n, i) lock_acquire(l, s, t, 0, 1, n, i) ``` -`lock_acquire` function: +`lock_acquire` 函数: ```C void lock_acquire(struct lockdep_map *lock, unsigned int subclass, @@ -280,22 +278,22 @@ void lock_acquire(struct lockdep_map *lock, unsigned int subclass, } ``` -As I wrote above, we will not consider stuff here which is related to debugging or tracing. The main point of the `lock_acquire` function is to disable hardware interrupts by the call of the `raw_local_irq_save` macro, because the given spinlock might be acquired with enabled hardware interrupts. In this way the process will not be preempted. Note that in the end of the `lock_acquire` function we will enable hardware interrupts again with the help of the `raw_local_irq_restore` macro. As you already may guess, the main work will be in the `__lock_acquire` function which is defined in the [kernel/locking/lockdep.c](https://github.com/torvalds/linux/blob/master/kernel/locking/lockdep.c) source code file. +就像之前所写的,我们不考虑这些调试或跟踪相关的东西。`lock_acquire` 函数的主要是通过 `raw_local_irq_save` 宏调用禁用硬件中断,因为给定的自旋锁可能被启用的硬件中断所获取。以这样的方式获取的话程序将不会被抢占。注意 `lock_acquire` 函数的最后将使用`raw_local_irq_restore`宏的帮助再次启动硬件中断。正如你们可能猜到的那样,主要工作将在`__lock_acquire` 函数中定义,这个函数在 [kernel/locking/lockdep.c](https://github.com/torvalds/linux/blob/master/kernel/locking/lockdep.c) 源代码文件中。 -The `__lock_acquire` function looks big. We will try to understand what does this function do, but not in this part. Actually this function mostly related to the Linux kernel [lock validator](https://www.kernel.org/doc/Documentation/locking/lockdep-design.txt) and it is not topic of this part. If we will return to the definition of the `__raw_spin_lock` function, we will see that it contains the following definition in the end: +`__lock_acquire` 函数看起来很大。我们将试图去理解这个函数要做什么,但不是在这一部分。事实上这个函数于 Linux内核[锁验证器 (lock validator)](https://www.kernel.org/doc/Documentation/locking/lockdep-design.txt) 密切相关,而这也不是此部分的主题。如果我们要返回 `__raw_spin_lock` 函数的定义,我们将会发现最终这个定义包含了以下的定义: ```C LOCK_CONTENDED(lock, do_raw_spin_trylock, do_raw_spin_lock); ``` -The `LOCK_CONTENDED` macro is defined in the [include/linux/lockdep.h](https://github.com/torvalds/linux/blob/master/include/linux/lockdep.h) header file and just calls the given function with the given `spinlock`: +`LOCK_CONTENDED` 宏的定义在 [include/linux/lockdep.h](https://github.com/torvalds/linux/blob/master/include/linux/lockdep.h) 头文件中,而且只是调用给定`自旋锁`的已知函数: ```C #define LOCK_CONTENDED(_lock, try, lock) \ lock(_lock) ``` -In our case, the `lock` is `do_raw_spin_lock` function from the [include/linux/spinlock.h](https://github.com/torvalds/linux/blob/master/include/linux/spnlock.h) header file and the `_lock` is the given `raw_spinlock_t`: +在本例中,`lock` 就是 [include/linux/spinlock.h](https://github.com/torvalds/linux/blob/master/include/linux/spnlock.h) 头文件中的 `do_raw_spin_lock`,而`_lock` 就是给定的 `raw_spinlock_t`: ```C static inline void do_raw_spin_lock(raw_spinlock_t *lock) __acquires(lock) @@ -304,14 +302,13 @@ static inline void do_raw_spin_lock(raw_spinlock_t *lock) __acquires(lock) arch_spin_lock(&lock->raw_lock); } ``` - -The `__acquire` here is just [sparse](https://en.wikipedia.org/wiki/Sparse) related macro and we are not interesting in it in this moment. Location of the definition of the `arch_spin_lock` function depends on two things: the first is architecture of system and the second do we use `queued spinlocks` or not. In our case we consider only `x86_64` architecture, so the definition of the `arch_spin_lock` is represented as the macro from the [include/asm-generic/qspinlock.h](https://github.com/torvalds/linux/blob/master/include/asm-generic/qspinlocks.h) header file: +这里的 `__acquire` 只是[稀疏(sparse)]相关宏,并且当前我们也对这些不感兴趣。`arch_spin_lock` 函数定义的位置依赖于两件事:第一是系统架构,第二是我们是否使用了`队列自旋锁(queued spinlocks)`。本例中我们只考虑 `x86_64` 架构,因此 `arch_spin_lock` 的定义的宏表示源自 [include/asm-generic/qspinlock.h](https://github.com/torvalds/linux/blob/master/include/asm-generic/qspinlocks.h) 头文件中: ```C #define arch_spin_lock(l) queued_spin_lock(l) ``` -if we are using `queued spinlocks`. Or in other case, the `arch_spin_lock` function is defined in the [arch/x86/include/asm/spinlock.h](https://github.com/torvalds/linux/blob/master/arch/x86/include/asm/spinlock.h) header file. Now we will consider only `normal spinlock` and information related to `queued spinlocks` we will see later. Let's look again on the definition of the `arch_spinlock` structure, to understand implementation of the `arch_spin_lock` function: +如果使用 `队列自旋锁`,或者其他例子中,`arch_spin_lock` 函数定在 [arch/x86/include/asm/spinlock.h](https://github.com/torvalds/linux/blob/master/arch/x86/include/asm/spinlock.h) 头文件中,如何处理?现在我们只考虑`普通的自旋锁`,`队列自旋锁`相关的信息将在以后了解。来再看看 `arch_spinlock` 结构的定义,理解以下 `arch_spin_lock` 函数的实现: ```C typedef struct arch_spinlock { @@ -323,8 +320,7 @@ typedef struct arch_spinlock { }; } arch_spinlock_t; ``` - -This variant of `spinlock` is called - `ticket spinlock`. As we may see, it consists from two parts. When lock is acquired, it increments a `tail` by one every time when a process wants to hold a `spinlock`. If the `tail` is not equal to `head`, the process will be locked, until values of these variables will not be equal. Let's look on the implementation of the `arch_spin_lock` function: +这个`自旋锁`的变体被称为——`标签自旋锁 (ticket spinlock)`。 就像我们锁了解的,标签自旋锁包括两个部分。当锁被获取, 它就每次处理想获取`自旋锁`时增加一个`尾部(tail)`。如果`尾部`不等于`头部`, 那么程序就会被锁住,直到这些变量的值不再相等。来看看`arch_spin_lock`函数上的实现: ```C static __always_inline void arch_spin_lock(arch_spinlock_t *lock) @@ -354,30 +350,28 @@ out: } ``` -At the beginning of the `arch_spin_lock` function we can initialization of the `__raw_tickets` structure with `tail` - `1`: +`arch_spin_lock` 函数在一开始能够使用`尾部`—— `1` 对 `__raw_tickets` 结构初始化: ```C #define __TICKET_LOCK_INC 1 ``` -In the next line we execute [xadd](http://x86.renejeschke.de/html/file_module_x86_id_327.html) operation on the `inc` and `lock->tickets`. After this operation the `inc` will store value of the `tickets` of the given `lock` and the `tickets.tail` will be increased on `inc` or `1`. The `tail` value was increased on `1` which means that one process started to try to hold a lock. In the next step we do the check that checks that `head` and `tail` have the same value. If these values are equal, this means that nobody holds lock and we go to the `out` label. In the end of the `arch_spin_lock` function we may see the `barrier` macro which represents `barrier instruction` which guarantees that compiler will not change order of operations that access memory (more about memory barriers you can read in the kernel [documentation](https://www.kernel.org/doc/Documentation/memory-barriers.txt)). - -If one process held a lock and a second process started to execute the `arch_spin_lock` function, the `head` will not be `equal` to `tail`, because the `tail` will be greater than `head` on `1`. In this way, process will occur in the loop. There will be comparison between `head` and the `tail` values at each loop iteration. If these values are not equal, the `cpu_relax` will be called which is just [NOP](https://en.wikipedia.org/wiki/NOP) instruction: +在`inc` 和 `lock->tickets` 的下一行执行 [xadd](http://x86.renejeschke.de/html/file_module_x86_id_327.html) 操作。这个操作之后 `inc`将存储给定`标签(tickets)`的值,然后 `tickets.tail`将增加 `inc` 或 `1`。`尾部`值增加 `1` 意味着一个程序开始尝试持有锁。下一步做检查,检查`头部`和`尾部`是否有相同的值。如果值相等,这意味着没有程序持有锁并且我们去了`out`标签。在 `arch_spin_lock`函数的最后,我们可能了解了 `barrier` 宏表示 `屏障指令 (barrier instruction)`,该指令保证了编译器将不更改进入内存操作的顺序(更多关于内存屏障的知识可以阅读内核[文档 (documentation)](https://www.kernel.org/doc/Documentation/memory-barriers.txt))。 +如果一个程序持有锁并且第二个程序开始执行 `arch_spin_lock` 函数,那么 `头部`将不会`等于``尾部`,因为`尾部`比`头部`大`1`。这样,程序将循环发生。在每次循坏迭代的时候`头部`和`尾部`的值进行比较。如果值不相等,`cpu_relax` ,也就是 [NOP](https://en.wikipedia.org/wiki/NOP) 指令将会被调用: ```C #define cpu_relax() asm volatile("rep; nop") ``` -and the next iteration of the loop will be started. If these values will be equal, this means that the process which held this lock, released this lock and the next process may acquire the lock. +然后将开始循环的下一次迭代。如果值相等,这意味着持有锁的程序,释放这个锁并且下一个程序获取这个锁。 -The `spin_unlock` operation goes through the all macros/function as `spin_lock`, of course with `unlock` prefix. In the end the `arch_spin_unlock` function will be called. If we will look at the implementation of the `arch_spin_lock` function, we will see that it increases `head` of the `lock tickets` list: +`spin_unlock` 操作遍布所有有 `spin_lock` 的宏或函数中,当然,使用的是`unlock`前缀。最后,`arch_spin_unlock` 函数将会被调用。如果看看 `arch_spin_lock` 函数的实现,我们将了解到这个函数增加了 `lock tickets` 列表的`头部`: ```C __add(&lock->tickets.head, TICKET_LOCK_INC, UNLOCK_LOCK_PREFIX); ``` - -In a combination of the `spin_lock` and `spin_unlock`, we get kind of queue where `head` contains an index number which maps currently executed process which holds a lock and the `tail` which contains an index number which maps last process which tried to hold the lock: +在`spin_lock` 和 `spin_unlock` 的组合使用中,我们得到一个队列,其`头部`包含了一个索引号,映射了当前执行的持有锁的程序,而`尾部`包含了一个索引号,映射了最后尝试持有锁的程序: ``` +-------+ +-------+ @@ -399,18 +393,18 @@ head | 7 | - - - | 7 | tail +-------+ ``` -That's all for now. We didn't cover `spinlock` API in full in this part, but I think that the main idea behind this concept must be clear now. +目前这就是全部。这一部分不涵盖所有的`自旋锁` API,但我认为这个概念背后的主要思想现在一定清楚了。 -Conclusion +结论 -------------------------------------------------------------------------------- -This concludes the first part covering synchronization primitives in the Linux kernel. In this part, we met first synchronization primitive `spinlock` provided by the Linux kernel. In the next part we will continue to dive into this interesting theme and will see other `synchronization` related stuff. +涵盖 Linux 内核中的同步原语的第一部分到此结束。在这一部分,我们遇见了第一个 Linux 内核提供的同步原语`自旋锁`。下一部分将会继续深入这个有趣的主题,而且将会了解到其他`同步`相关的知识。 -If you have questions or suggestions, feel free to ping me in twitter [0xAX](https://twitter.com/0xAX), drop me [email](anotherworldofworld@gmail.com) or just create [issue](https://github.com/0xAX/linux-insides/issues/new). +如果您有疑问或者建议,请在twitter [0xAX](https://twitter.com/0xAX)上联系我,通过 [email](anotherworldofworld@gmail.com)联系我,或者创建一个 [issue](https://github.com/MintCN/linux-insides-zh/issues/new)。 -**Please note that English is not my first language and I am really sorry for any inconvenience. If you found any mistakes please send me PR to [linux-insides](https://github.com/0xAX/linux-insides).** +**请注意英语不是我的母语,对于任何不便我感到抱歉。如果您发现任何错误请给我发送PR到 [linux-insides](https://github.com/0xAX/linux-insides).** -Links +链接 -------------------------------------------------------------------------------- * [Concurrent computing](https://en.wikipedia.org/wiki/Concurrent_computing)