linux-doc.vger.kernel.org archive mirror
 help / color / mirror / Atom feed
* [PATCH v2 1/2] docs/zh_CN: Add translation zh_CN/kernel-hacking/locking.rst
@ 2021-04-20 16:01 Wu XiangCheng
  2021-04-20 16:02 ` [PATCH v2 2/2] docs: kernel-hacking: Fix two ref links Wu XiangCheng
  0 siblings, 1 reply; 4+ messages in thread
From: Wu XiangCheng @ 2021-04-20 16:01 UTC (permalink / raw)
  To: Alex Shi; +Cc: Jonathan Corbet, Fangrui Song, Yanteng Si, linux-doc

Add new translation
  Documentation/translations/zh_CN/kernel-hacking/locking.rst
and link it to zh_CN/kernel-hacking/index.rst

Signed-off-by: Wu XiangCheng <bobwxc@email.cn>
---
YanTeng and I did not yet have enough capability to review this patch well,
if anyone can help us, I would be grateful :)

v2:
* modifid some words under YanTeng Si's advices. Thanks his review!
* Translated some notes.

v1:
<https://lore.kernel.org/linux-doc/20210419130049.GA17878@bobwxc.top/T/>

 .../zh_CN/kernel-hacking/index.rst            |    5 +-
 .../zh_CN/kernel-hacking/locking.rst          | 1249 +++++++++++++++++
 2 files changed, 1250 insertions(+), 4 deletions(-)
 create mode 100644 Documentation/translations/zh_CN/kernel-hacking/locking.rst

diff --git a/Documentation/translations/zh_CN/kernel-hacking/index.rst b/Documentation/translations/zh_CN/kernel-hacking/index.rst
index df530de2278d..230439ae21de 100644
--- a/Documentation/translations/zh_CN/kernel-hacking/index.rst
+++ b/Documentation/translations/zh_CN/kernel-hacking/index.rst
@@ -16,7 +16,4 @@
    :maxdepth: 2
 
    hacking
-
-TODO
-
-- locking
+   locking
diff --git a/Documentation/translations/zh_CN/kernel-hacking/locking.rst b/Documentation/translations/zh_CN/kernel-hacking/locking.rst
new file mode 100644
index 000000000000..6550d55acb98
--- /dev/null
+++ b/Documentation/translations/zh_CN/kernel-hacking/locking.rst
@@ -0,0 +1,1249 @@
+.. SPDX-License-Identifier: GPL-2.0
+
+.. include:: ../disclaimer-zh_CN.rst
+
+:Original: Documentation/kernel-hacking/locking.rst
+
+:译者:
+
+ 吴想成 Wu XiangCheng <bobwxc@email.cn>
+
+.. _kernel_hacking_lock_zh:
+
+==========
+锁操作指北
+==========
+
+:Author: Rusty Russell
+
+前言
+====
+
+欢迎来到Rusty的不靠谱内核锁操作问题指南。本文档描述了Linux内核2.6版本的锁系统。
+
+随着Linux内核中超线程和抢占的广泛可用,进行内核编程的每个人都需要了解SMP
+(Symmetrical Multi-Processing,对称多处理)的并发与锁的基本原理。
+
+并发问题
+========
+
+(如果您知道什么是竞态[Race Condition],请跳过。)
+
+在普通程序中,可以按如下方式递增计数器:
+
+::
+
+          very_important_count++;
+
+
+以下是他们预期会发生的:
+
+.. table:: 预期结果
+
+  +------------------------------------+------------------------------------+
+  | 实例 1                             | 实例 2                             |
+  +====================================+====================================+
+  | 读 very_important_count (5)        |                                    |
+  +------------------------------------+------------------------------------+
+  | 加 1 (6)                           |                                    |
+  +------------------------------------+------------------------------------+
+  | 写 very_important_count (6)        |                                    |
+  +------------------------------------+------------------------------------+
+  |                                    | 读 very_important_count (6)        |
+  +------------------------------------+------------------------------------+
+  |                                    | 加 1 (7)                           |
+  +------------------------------------+------------------------------------+
+  |                                    | 写 very_important_count (7)        |
+  +------------------------------------+------------------------------------+
+
+以下是可能会发生的:
+
+.. table:: 可能的结果
+
+  +------------------------------------+------------------------------------+
+  | 实例 1                             | 实例 2                             |
+  +====================================+====================================+
+  | 读 very_important_count (5)        |                                    |
+  +------------------------------------+------------------------------------+
+  |                                    | 读 very_important_count (5)        |
+  +------------------------------------+------------------------------------+
+  | 加 1 (6)                           |                                    |
+  +------------------------------------+------------------------------------+
+  |                                    | 加 1 (6)                           |
+  +------------------------------------+------------------------------------+
+  | 写 very_important_count (6)        |                                    |
+  +------------------------------------+------------------------------------+
+  |                                    | 写 very_important_count (6)        |
+  +------------------------------------+------------------------------------+
+
+
+竞争态势与临界区域
+------------------
+
+这种重叠会导致结果取决于多个任务的相对时间,称为竞争态势(Race Condition,
+竞态)。包含并发问题的代码段称为临界区域(Critical Region)。特别是从Linux
+开始在SMP机器上运行以来,它们成为内核设计和实现中的主要问题之一。
+
+抢占(Preemption)可以产生相同的效果,即使只有一个CPU:通过在临界区域抢占一个
+任务,就会出现一样的竞争条件。在这种情况下,抢占的线程可能会运行临界区域本身。
+
+解决方案是识别这些同步访问何时发生,并使用锁来确保任意时刻只有一个实例可以进
+入临界区域。Linux内核中有许多友好的原语可以帮助您做到这一点。也有一些不好使的
+原语,不过我会假装他们不存在。
+
+Linux内核的锁操作
+=================
+
+如果非要我给你关于锁的建议,那就是 **保持简单** 。
+
+不要轻易引入新锁。
+
+两种主要的内核锁:自旋锁和互斥锁
+--------------------------------
+
+内核锁主要有两种类型。
+
+最基本的类型是自旋锁(spinlock, ``include/asm/spinlock.h`` ),这是一个非常
+简单的单持有者锁:如果无法获得自旋锁,则继续尝试(自旋)直到可以。自旋锁非常
+小且快速,可以在任何地方使用。
+
+第二种类型是互斥锁(mutex, ``include/linux/mutex.h`` ):它类似于自旋锁,但
+是您可以阻塞获取互斥锁。如果无法加上互斥锁,则任务将挂起,并在释放互斥锁时被
+唤醒。这意味着CPU可以在您等待时执行其他操作。在很多情况下,你无法睡眠(见
+`从中断中调用哪些函数是安全的?`_ ),此时必须改用自旋锁。
+
+这两种锁都不是递归的:请参阅 `死锁:入门与进阶`_ 。
+
+锁和单处理器内核
+----------------
+
+对于编译时未启用 ``CONFIG_SMP`` 和 ``CONFIG_PREEMPT`` 的内核,自旋锁根本不存在。
+这是一个很好的设计决策:当没有其他进程可以同时运行时,锁就没有存在意义了。
+
+如果编译内核时未启用 ``CONFIG_SMP`` ,但是启用了 ``CONFIG_PREEMPT`` ,那么自旋
+锁只会禁用抢占,这足以防止任何竞争。在大多数情况下,我们可以将抢占权视为等同于
+SMP,而不必单独担心它。
+
+您应该始终在启用 ``CONFIG_SMP`` 和 ``CONFIG_PREEMPT`` 的情况下测试锁代码,即使
+您没有SMP测试设备,因为这仍然能捕获某些类型的锁缺陷。
+
+互斥锁仍然存在,因为它们是在用户上下文之间进行同步所必需的,我们将在下面看到。
+
+仅在用户上下文中的锁操作
+------------------------
+
+如果您有一个只能从用户上下文访问的数据结构,那么可以使用一个简单的互斥锁(
+``include/linux/mutex.h`` )来保护它。这是最简单的情形:初始化互斥锁。然后调用
+mutex_lock_interruptible()来获取互斥锁,或调用mutex_unlock()来释放它。还有一个
+应避免使用的mutex_lock(),因为当收到信号时它不会返回。
+
+示例: ``net/netfilter/nf_sockopt.c`` 允许使用nf_register_sockopt()注册新的
+setsockopt()和getsockopt() 调用。注册和取消注册仅在模块加载和卸载时(以及引导
+时,在没有并发的情况下)完成,并且注册列表仅用于未知的setsockopt()或
+getsockopt()系统调用。 ``nf_sockopt_mutex`` 非常适合用来保护此情况,尤其是因为
+setsockopt和getsockopt调用可能会睡眠。
+
+用户上下文和软中断之间的锁操作
+------------------------------
+
+如果软中断(softirq)需要与用户上下文共享数据,则会产生两个问题。首先,当前
+用户上下文可以被软中断中断;其次,临界区域可以从另一个CPU进入。此时应当使用
+spin_lock_bh()( ``include/linux/spinlock.h`` )。它禁用此CPU上的软中断,然后
+获取锁。spin_unlock_bh()执行相反的操作(“_bh”后缀是对“下半部[Bottom Halves]”
+的历史性引用,这是软中断的旧名称。它实际上应该被称为spin_lock_softirq())。
+
+请注意,您也可以在此处使用 spin_lock_irq()或spin_lock_irqsave(),它们可以停止
+硬中断,请参阅 `硬中断上下文`_ 。
+
+这对UP也非常有效:自旋锁消失,这个宏会直接变成local_bh_disable()(
+``include/linux/interrupt.h`` ),保护您不受正在运行的软中断的影响。
+
+用户上下文和子任务之间的锁操作
+------------------------------
+
+这与上面的完全相同,因为子任务(Tasklet)实际上是从软中断运行的。
+
+用户上下文和计时器之间的锁操作
+------------------------------
+
+这也与上面完全相同,因为计时器实际上也是从软中断运行的。
+从锁的角度来看,子任务和计时器是相同的。
+
+子任务/计时器之间的锁操作
+-------------------------
+
+有时,一个子任务或计时器可能希望与另一个子任务或计时器共享数据。
+
+相同的子任务/计时器
+~~~~~~~~~~~~~~~~~~~
+
+由于子任务永远不会同时在两个CPU上运行,因此您不必担心您的子任务是可重入的
+(一下运行两次),即使是在SMP上。
+
+不同的子任务/计时器
+~~~~~~~~~~~~~~~~~~~
+
+如果另一个子任务/计时器希望与您的子任务或计时器共享数据,则您需要使用
+use spin_lock()和spin_unlock()调用。这里无需使用spin_lock_bh(),因为您已经在
+一个子任务中,并且不会有另一个在同一个CPU上运行。
+
+软中断之间的锁操作
+------------------
+
+软中断常常可能希望与自己或子任务/计时器共享数据。
+
+相同的软中断
+~~~~~~~~~~~~
+
+同样的软中断可以在其他CPU上运行:您可以使用逐CPU数组(参见 `逐CPU数据`_ )来
+获得更好的性能。如果您打算使用软中断,那么您可能非常关心可伸缩性能,足以处理
+额外的复杂性。
+
+对于共享数据,您需要使用spin_lock()和spin_unlock()。
+
+不同的软中断
+~~~~~~~~~~~~
+
+对于共享数据,您需要使用spin_lock()和spin_unlock(),无论是计时器、子任务、
+相同/不同的软中断或另一个软中断:它们中的任何一个都可以在不同的CPU上运行。
+
+硬中断上下文
+============
+
+硬件中断通常与子任务或软中断通信。这常常涉及到将工作放入队列中,然后软中断会
+将其取出。
+
+硬中断和软中断/子任务之间的锁操作
+---------------------------------
+
+如果硬件中断处理程序需要与软中断共享数据,则有两个问题。首先,通过硬件中断
+可以中断软中断处理;其次,通过另一CPU上的硬件中断可以进入临界区域。此时应
+使用spin_lock_irq()。它定义为禁用该CPU上的中断,然后获取锁。spin_unlock_irq()
+执行相反的操作。
+
+中断处理程序无需使用spin_lock_irq(),因为在中断处理程序运行时软中断不能运行:
+它可以使用spin_lock(),会稍微快一点。唯一的例外是,如果不同的硬件中断处理程序
+使用相同的锁:spin_lock_irq()将阻止它中断我们。
+
+这对UP也非常有效:自旋锁消失,这个宏会直接变成local_irq_disable()(
+``include/asm/smp.h`` ),它保护您不受软中断/子任务/下半部运行的影响。
+
+spin_lock_irqsave()( ``include/linux/spinlock.h`` )是一个变体,它会在一个
+标志字中保存中断是打开还是关闭,此标志字将被传递给spin_unlock_irqrestore()。
+这意味着可以在硬中断处理程序(中断已经关闭时)和软中断(需要禁用中断)中使用
+相同的代码。
+
+请注意,软中断(以及其产生的子任务和计时器)是在硬件中断返回时运行的,因此
+spin_lock_irq()也会停止这些操作。从这个意义上说,spin_lock_irqsave()是最通用、
+最强大的锁操作函数。
+
+两个硬中断处理器之间的锁操作
+----------------------------
+
+在两个中断处理程序之间共享数据是很少见的,但是如果这样做,则应该使用
+spin_lock_irqsave() :是否在中断处理程序内部禁用所有中断取决于特定体系结构。
+
+锁操作速查表
+============
+
+Pete Zaitcev给出了以下总结:
+
+-  如果您处于进程上下文(任何系统调用)中,并且希望加锁排除其他进程,请使用
+   互斥锁。您可以持有互斥锁并休眠( ``copy_from_user*()`` 或
+   ``kmalloc(x,GFP_KERNEL)`` )。
+
+-  否则(即数据可在中断中被访问)请使用spin_lock_irqsave()和
+   spin_unlock_irqrestore()。
+
+-  避免在超过5行代码和任何函数调用中使用自旋锁(readb()之类的访问器除外)。
+
+最低要求表
+----------
+
+下表列出了不同上下文之间的 **最低** 锁操作要求。在某些情况下,同一上下文一次
+只能在一个CPU上运行,因此该上下文无需加锁(例如,特定线程一次只能在一个CPU上
+运行,但如果它需要与另一个线程共享数据,则需要加锁)。
+
+记住上面的建议:您总是可以使用spin_lock_irqsave(),它是所有其他自旋锁原语的超集。
+
+.. table:: 锁操作要求表
+
+  ============== ============= ============= ========= ========= ========= ========= ======= ======= ============== ==============
+                 中断处理程序A 中断处理程序B 软中断A   软中断B   子任务A   子任务B   计时器A 计时器B 用户上下文A    用户上下文B
+  ============== ============= ============= ========= ========= ========= ========= ======= ======= ============== ==============
+  中断处理程序A  None
+  中断处理程序B  SLIS          None
+  软中断A        SLI           SLI           SL
+  软中断B        SLI           SLI           SL        SL
+  子任务A        SLI           SLI           SL        SL        None
+  子任务B        SLI           SLI           SL        SL        SL        None
+  计时器A        SLI           SLI           SL        SL        SL        SL        None
+  计时器B        SLI           SLI           SL        SL        SL        SL        SL      None
+  用户上下文A    SLI           SLI           SLBH      SLBH      SLBH      SLBH      SLBH    SLBH    None
+  用户上下文B    SLI           SLI           SLBH      SLBH      SLBH      SLBH      SLBH    SLBH    MLI            None
+  ============== ============= ============= ========= ========= ========= ========= ======= ======= ============== ==============
+
+.. table:: 锁操作要求表 注释
+
+  +--------+----------------------------+
+  | SLIS   | spin_lock_irqsave          |
+  +--------+----------------------------+
+  | SLI    | spin_lock_irq              |
+  +--------+----------------------------+
+  | SL     | spin_lock                  |
+  +--------+----------------------------+
+  | SLBH   | spin_lock_bh               |
+  +--------+----------------------------+
+  | MLI    | mutex_lock_interruptible   |
+  +--------+----------------------------+
+
+试锁(trylock)函数
+===================
+
+这些函数只尝试获取一次锁,并立即返回一个值说明获取锁成功或失败。如果你无需
+访问受其他线程持有的锁保护的数据,可以使用它们。如果需要访问受锁保护的数据,
+则应稍后获取锁。
+
+spin_trylock()不会自旋,但若在首次尝试时获得自旋锁则返回非零,否则返回零。此
+函数可以像spin_lock()一样在所有上下文中使用:您必须禁用可能中断您并获取自旋锁
+的上下文。
+
+mutex_trylock()不会挂起任务,但若其在首次尝试时获得互斥锁,则返回非零,否则
+返回零。尽管未休眠,但此功能在硬件或软件中断上下文中无法安全使用。
+
+常见例子
+========
+
+让我们来看一个简单的例子:一个数字到名称映射的缓存(a cache of number to name
+mappings)。缓存记录每个对象的使用频率,当缓存满了时,抛出最少使用的对象。
+
+完全在用户上下文中
+------------------
+
+对于第一个示例,我们假设所有操作都在用户上下文中(即来自系统调用),因此我们
+可以休眠。这意味着我们可以使用互斥锁来保护缓存和其中的所有对象。代码如下::
+
+    #include <linux/list.h>
+    #include <linux/slab.h>
+    #include <linux/string.h>
+    #include <linux/mutex.h>
+    #include <asm/errno.h>
+
+    struct object
+    {
+            struct list_head list;
+            int id;
+            char name[32];
+            int popularity;
+    };
+
+    /* 保护cache,cache_num以及其内部的对象 */
+    static DEFINE_MUTEX(cache_lock);
+    static LIST_HEAD(cache);
+    static unsigned int cache_num = 0;
+    #define MAX_CACHE_SIZE 10
+
+    /* 必须持有cache_lock */
+    static struct object *__cache_find(int id)
+    {
+            struct object *i;
+
+            list_for_each_entry(i, &cache, list)
+                    if (i->id == id) {
+                            i->popularity++;
+                            return i;
+                    }
+            return NULL;
+    }
+
+    /* 必须持有cache_lock */
+    static void __cache_delete(struct object *obj)
+    {
+            BUG_ON(!obj);
+            list_del(&obj->list);
+            kfree(obj);
+            cache_num--;
+    }
+
+    /* 必须持有cache_lock */
+    static void __cache_add(struct object *obj)
+    {
+            list_add(&obj->list, &cache);
+            if (++cache_num > MAX_CACHE_SIZE) {
+                    struct object *i, *outcast = NULL;
+                    list_for_each_entry(i, &cache, list) {
+                            if (!outcast || i->popularity < outcast->popularity)
+                                    outcast = i;
+                    }
+                    __cache_delete(outcast);
+            }
+    }
+
+    int cache_add(int id, const char *name)
+    {
+            struct object *obj;
+
+            if ((obj = kmalloc(sizeof(*obj), GFP_KERNEL)) == NULL)
+                    return -ENOMEM;
+
+            strscpy(obj->name, name, sizeof(obj->name));
+            obj->id = id;
+            obj->popularity = 0;
+
+            mutex_lock(&cache_lock);
+            __cache_add(obj);
+            mutex_unlock(&cache_lock);
+            return 0;
+    }
+
+    void cache_delete(int id)
+    {
+            mutex_lock(&cache_lock);
+            __cache_delete(__cache_find(id));
+            mutex_unlock(&cache_lock);
+    }
+
+    int cache_find(int id, char *name)
+    {
+            struct object *obj;
+            int ret = -ENOENT;
+
+            mutex_lock(&cache_lock);
+            obj = __cache_find(id);
+            if (obj) {
+                    ret = 0;
+                    strcpy(name, obj->name);
+            }
+            mutex_unlock(&cache_lock);
+            return ret;
+    }
+
+请注意,在添加、删除或查找缓存时,我们始终确保拥有cache_lock缓存锁:缓存基础
+结构本身和对象的内容都受到锁的保护。本例中这很容易,因为我们为用户复制数据,
+从不会让他们直接访问对象。
+
+这里有一个小(也是常见的)优化:在cache_add()中,我们在获取锁之前设置对象的
+字段。这是安全的,因为在我们将它放入缓存之前,其他人无法访问它。
+
+从中断上下文访问
+----------------
+
+现在考虑可以从中断上下文调用cache_find()的情况:可以是硬中断或软中断。
+例如从缓存中删除对象的计时器。
+
+下面以标准补丁格式显示了更改: ``-`` 是删除的行,而 ``+`` 是增加的行。
+
+::
+
+    --- cache.c.usercontext 2003-12-09 13:58:54.000000000 +1100
+    +++ cache.c.interrupt   2003-12-09 14:07:49.000000000 +1100
+    @@ -12,7 +12,7 @@
+             int popularity;
+     };
+
+    -static DEFINE_MUTEX(cache_lock);
+    +static DEFINE_SPINLOCK(cache_lock);
+     static LIST_HEAD(cache);
+     static unsigned int cache_num = 0;
+     #define MAX_CACHE_SIZE 10
+    @@ -55,6 +55,7 @@
+     int cache_add(int id, const char *name)
+     {
+             struct object *obj;
+    +        unsigned long flags;
+
+             if ((obj = kmalloc(sizeof(*obj), GFP_KERNEL)) == NULL)
+                     return -ENOMEM;
+    @@ -63,30 +64,33 @@
+             obj->id = id;
+             obj->popularity = 0;
+
+    -        mutex_lock(&cache_lock);
+    +        spin_lock_irqsave(&cache_lock, flags);
+             __cache_add(obj);
+    -        mutex_unlock(&cache_lock);
+    +        spin_unlock_irqrestore(&cache_lock, flags);
+             return 0;
+     }
+
+     void cache_delete(int id)
+     {
+    -        mutex_lock(&cache_lock);
+    +        unsigned long flags;
+    +
+    +        spin_lock_irqsave(&cache_lock, flags);
+             __cache_delete(__cache_find(id));
+    -        mutex_unlock(&cache_lock);
+    +        spin_unlock_irqrestore(&cache_lock, flags);
+     }
+
+     int cache_find(int id, char *name)
+     {
+             struct object *obj;
+             int ret = -ENOENT;
+    +        unsigned long flags;
+
+    -        mutex_lock(&cache_lock);
+    +        spin_lock_irqsave(&cache_lock, flags);
+             obj = __cache_find(id);
+             if (obj) {
+                     ret = 0;
+                     strcpy(name, obj->name);
+             }
+    -        mutex_unlock(&cache_lock);
+    +        spin_unlock_irqrestore(&cache_lock, flags);
+             return ret;
+     }
+
+请注意,若中断打开,spin_lock_irqsave()会将其关闭,否则什么也不做(如果我们
+已经在中断处理程序中),因此这些函数可以从任何上下文安全调用。
+
+不幸的是,cache_add()使用 ``GFP_KERNEL`` 标志调用kmalloc(),这只在用户上下文
+中是合法的。我假设cache_add()仍然只在用户上下文中调用,否则这应该成为
+cache_add()的参数。
+
+暴露于此文件之外的对象
+----------------------
+
+如果我们的对象包含更多的信息,那么只将信息复制进复制出可能是不够的:例如,
+代码的其他部分可能希望保留指向这些对象的指针,而不是每次都查找id。这就产生了
+两个问题。
+
+第一个问题是我们使用 ``cache_lock`` 来保护对象:这需要使它成为非静态的,以便
+代码的其余部分可以使用它。这使得加锁变得更加棘手,因为它不再是全在一处。
+
+第二个问题是生命周期问题:如果另一个结构保留一个指向对象的指针,它可能希望该
+指针保持有效。但这只能在您持有锁时保证有效,否则有人可能会调用cache_delete(),
+或更糟的是添加了另一个重新使用相同地址的对象。
+
+因为只有一把锁,你不能永远持有它:这会导致别人没法完成其他工作。
+
+此问题的解决方案是使用引用计数:每个有指向对象的指针的程序在第一次得到对象时
+都会增加引用计数,而在处理完对象时会减少引用计数。当引用计数归零时,对象就是
+未使用的且可以被删除。
+
+代码如下::
+
+    --- cache.c.interrupt   2003-12-09 14:25:43.000000000 +1100
+    +++ cache.c.refcnt  2003-12-09 14:33:05.000000000 +1100
+    @@ -7,6 +7,7 @@
+     struct object
+     {
+             struct list_head list;
+    +        unsigned int refcnt;
+             int id;
+             char name[32];
+             int popularity;
+    @@ -17,6 +18,35 @@
+     static unsigned int cache_num = 0;
+     #define MAX_CACHE_SIZE 10
+
+    +static void __object_put(struct object *obj)
+    +{
+    +        if (--obj->refcnt == 0)
+    +                kfree(obj);
+    +}
+    +
+    +static void __object_get(struct object *obj)
+    +{
+    +        obj->refcnt++;
+    +}
+    +
+    +void object_put(struct object *obj)
+    +{
+    +        unsigned long flags;
+    +
+    +        spin_lock_irqsave(&cache_lock, flags);
+    +        __object_put(obj);
+    +        spin_unlock_irqrestore(&cache_lock, flags);
+    +}
+    +
+    +void object_get(struct object *obj)
+    +{
+    +        unsigned long flags;
+    +
+    +        spin_lock_irqsave(&cache_lock, flags);
+    +        __object_get(obj);
+    +        spin_unlock_irqrestore(&cache_lock, flags);
+    +}
+    +
+     /* Must be holding cache_lock */
+     static struct object *__cache_find(int id)
+     {
+    @@ -35,6 +65,7 @@
+     {
+             BUG_ON(!obj);
+             list_del(&obj->list);
+    +        __object_put(obj);
+             cache_num--;
+     }
+
+    @@ -63,6 +94,7 @@
+             strscpy(obj->name, name, sizeof(obj->name));
+             obj->id = id;
+             obj->popularity = 0;
+    +        obj->refcnt = 1; /* The cache holds a reference */
+
+             spin_lock_irqsave(&cache_lock, flags);
+             __cache_add(obj);
+    @@ -79,18 +111,15 @@
+             spin_unlock_irqrestore(&cache_lock, flags);
+     }
+
+    -int cache_find(int id, char *name)
+    +struct object *cache_find(int id)
+     {
+             struct object *obj;
+    -        int ret = -ENOENT;
+             unsigned long flags;
+
+             spin_lock_irqsave(&cache_lock, flags);
+             obj = __cache_find(id);
+    -        if (obj) {
+    -                ret = 0;
+    -                strcpy(name, obj->name);
+    -        }
+    +        if (obj)
+    +                __object_get(obj);
+             spin_unlock_irqrestore(&cache_lock, flags);
+    -        return ret;
+    +        return obj;
+     }
+
+我们将引用计数封装进标准的“get”和“put”函数中。现在我们可以从cache_find()返回
+对象本身,这样做的好处是用户现在可以在持有对象时休眠(例如用copy_to_user()将
+name复制到userspace)。
+
+需要注意的另一点是,应为指向对象的每个指针保留一个引用:因此当首次插入缓存时,
+引用计数当是1。在某些版本中,框架不占用引用计数,但它们更复杂。
+
+对引用计数使用原子操作
+~~~~~~~~~~~~~~~~~~~~~~
+
+在实践中,:c:type:`atomic_t` 通常被用于引用计数。 ``include/asm/atomic.h`` 中
+定义了许多原子操作:这些操作可以保证在系统所有CPU中的原子可见性,因此不需要锁。
+这使它比使用自旋锁更简单,尽管对于任何非琐碎的事情,使用自旋锁更清楚。用
+atomic_inc()和atomic_dec_and_test()来代替标准的递增和递减运算符,就无需使用锁
+来保护引用计数本身了。
+
+::
+
+    --- cache.c.refcnt  2003-12-09 15:00:35.000000000 +1100
+    +++ cache.c.refcnt-atomic   2003-12-11 15:49:42.000000000 +1100
+    @@ -7,7 +7,7 @@
+     struct object
+     {
+             struct list_head list;
+    -        unsigned int refcnt;
+    +        atomic_t refcnt;
+             int id;
+             char name[32];
+             int popularity;
+    @@ -18,33 +18,15 @@
+     static unsigned int cache_num = 0;
+     #define MAX_CACHE_SIZE 10
+
+    -static void __object_put(struct object *obj)
+    -{
+    -        if (--obj->refcnt == 0)
+    -                kfree(obj);
+    -}
+    -
+    -static void __object_get(struct object *obj)
+    -{
+    -        obj->refcnt++;
+    -}
+    -
+     void object_put(struct object *obj)
+     {
+    -        unsigned long flags;
+    -
+    -        spin_lock_irqsave(&cache_lock, flags);
+    -        __object_put(obj);
+    -        spin_unlock_irqrestore(&cache_lock, flags);
+    +        if (atomic_dec_and_test(&obj->refcnt))
+    +                kfree(obj);
+     }
+
+     void object_get(struct object *obj)
+     {
+    -        unsigned long flags;
+    -
+    -        spin_lock_irqsave(&cache_lock, flags);
+    -        __object_get(obj);
+    -        spin_unlock_irqrestore(&cache_lock, flags);
+    +        atomic_inc(&obj->refcnt);
+     }
+
+     /* Must be holding cache_lock */
+    @@ -65,7 +47,7 @@
+     {
+             BUG_ON(!obj);
+             list_del(&obj->list);
+    -        __object_put(obj);
+    +        object_put(obj);
+             cache_num--;
+     }
+
+    @@ -94,7 +76,7 @@
+             strscpy(obj->name, name, sizeof(obj->name));
+             obj->id = id;
+             obj->popularity = 0;
+    -        obj->refcnt = 1; /* The cache holds a reference */
+    +        atomic_set(&obj->refcnt, 1); /* The cache holds a reference */
+
+             spin_lock_irqsave(&cache_lock, flags);
+             __cache_add(obj);
+    @@ -119,7 +101,7 @@
+             spin_lock_irqsave(&cache_lock, flags);
+             obj = __cache_find(id);
+             if (obj)
+    -                __object_get(obj);
+    +                object_get(obj);
+             spin_unlock_irqrestore(&cache_lock, flags);
+             return obj;
+     }
+
+保护对象本身
+------------
+
+在这些示例中,我们假设对象(引用计数除外)在创建后从未更改。如果我们想更改
+名称,则有三种可能:
+
+-  您可以使 ``cache_lock`` 成为非静态的,并告诉人们在更改任何对象的名称之前
+   获取该锁。
+
+-  您可以提供一个cache_obj_rename()函数来获取此锁并更改调用者的名称,并告诉
+   所有人使用该函数。
+
+-  您可以使 ``cache_lock`` 仅保护缓存本身,并使用另一个锁来保护名称。
+
+理论上来说,可以使锁的粒度精细到每一个字段、每一个对象。
+实际上,最常见的变体是:
+
+-  一个锁保护基础结构(本例中的 ``cache`` 缓存列表)和所有对象。这就是我们
+   现在所做的。
+
+-  一个锁保护基础结构(包括对象中的列表指针),一个锁保护对象中的其余部分。
+
+-  多个锁来保护基础设施(例如,每个哈希链一个锁),可能每个对象都有一个单独
+   的锁。
+
+以下是“每个对象一个锁”的实现:
+
+::
+
+    --- cache.c.refcnt-atomic   2003-12-11 15:50:54.000000000 +1100
+    +++ cache.c.perobjectlock   2003-12-11 17:15:03.000000000 +1100
+    @@ -6,11 +6,17 @@
+
+     struct object
+     {
+    +        /* These two protected by cache_lock. */
+             struct list_head list;
+    +        int popularity;
+    +
+             atomic_t refcnt;
+    +
+    +        /* Doesn't change once created. */
+             int id;
+    +
+    +        spinlock_t lock; /* Protects the name */
+             char name[32];
+    -        int popularity;
+     };
+
+     static DEFINE_SPINLOCK(cache_lock);
+    @@ -77,6 +84,7 @@
+             obj->id = id;
+             obj->popularity = 0;
+             atomic_set(&obj->refcnt, 1); /* The cache holds a reference */
+    +        spin_lock_init(&obj->lock);
+
+             spin_lock_irqsave(&cache_lock, flags);
+             __cache_add(obj);
+
+注意,我使用 ``cache_lock`` 来保护活跃度计数,而不是逐对象锁:这是因为它(像
+对象中的 :c:type:`struct list_head <list_head>` )在逻辑上是基础结构的一部分。
+这样就不需要在寻找最不活跃的对象时获取__cache_add()中每个对象的锁。
+
+我还使id成员为不可更改,这样就无需一一获取__cache_find()中的对象锁来检查id:
+对象锁只由希望读取或写入名称字段的调用者使用。
+
+还要注意,我添加了一个描述哪些数据受哪些锁的保护的注释。这一点非常重要,因为
+它描述了代码运行时的行为,并且很难直接看出来。正如Alan Cox所说,“对数据加锁,
+而不是代码”。
+
+常见问题
+========
+
+死锁:入门与进阶
+----------------
+
+一段代码试图两次获取同一自旋锁会导致错误:它将永远自旋,等待锁被释放(自旋锁、
+读写锁[rwlocks]和互斥锁在Linux中不是递归的)。这诊断起来很简单琐碎:不是一个
+“我想了十天十夜……”的问题。
+
+对于稍微复杂一点的情况,假设您有一个由软中断和用户上下文共享的区域。如果您调用
+spin_lock()来保护它,那么当软中断持有锁时,用户上下文可能会被中断,然后软中断
+会一直旋转以试图获取同一锁。
+
+这两种情况都称为死锁,如上所示,即使只有一个CPU也会发生死锁(尽管在UP编译中不
+是这样,因为在 ``CONFIG_SMP = n`` 的内核编译中,自旋锁会消失。不过第二个示例中
+仍然会出现数据损坏)。
+
+这种完全锁定很容易诊断:在SMP计算机上发生这种情况时,看门狗计时器或开启
+``DEBUG_SPINLOCK`` 设置( ``include/linux/spinlock.h`` )编译会立即显示出来。
+
+更复杂的一种情况是所谓的“致命拥抱(deadly embrace)”,同时涉及两个或多个锁。
+假设您有一个哈希表:表中的每个条目都是一个自旋锁和一个哈希对象链。在软中断
+处理程序中,有时需要将对象从哈希中的一个位置更改为另一个位置:获取旧哈希链的
+自旋锁和新哈希链的自旋锁,然后从旧哈希链中删除对象,并将其插入新散列链中。
+
+这里有两个问题。首先,如果您的代码试图将对象移动到同一个链中,它将在两次尝试
+锁定对象时与自身死锁。其次,如果另一个CPU上的同一个软中断正试图反向移动另一个
+对象,则可能发生以下情况:
+
+.. table:: 结果表
+
+  +-------------------+-------------------+
+  | CPU 1             | CPU 2             |
+  +===================+===================+
+  | 获取锁A -> OK     | 获取锁B -> OK     |
+  +-------------------+-------------------+
+  | 获取锁B -> 自旋   | 获取锁A -> 自旋   |
+  +-------------------+-------------------+
+
+两个CPU将永远自旋,等待另一个放弃锁。如果这看起来像事故、闻起来像事故、感觉
+起来也像事故,那十足是个事故。
+
+防止出现死锁
+------------
+
+教科书会告诉你,如果你总是按照固定顺序加锁,那永远不会出现死锁。
+但实践会告诉您,这种方法没有扩展性:当创建一个新锁时,我对内核的理解程度尚
+无法确定它在5000级锁层次结构中的位置。
+
+最好的锁是经过封装的:它们从不暴露在头文件中,也从不被对同一文件外的复杂函数
+的调用所持有。您可以通读这段代码并看到它永远不会死锁,因为它在持有某个锁时从不
+尝试获取另一个同样的锁。使用你代码的人甚至不需要知道你在使用锁。
+
+当您提供回调或钩子时,会出现一个经典的问题:如果您在持有锁的情况下调用这些
+回调或钩子,您将面临“简单的死锁”或“致命拥抱”(谁知道回调会做什么?)。记住,
+其他程序员都是来搞你的,所以不要这样做。
+
+过分防止死锁
+~~~~~~~~~~~~
+
+死锁是个问题,但没有数据损坏那么严重。获取读锁、搜索列表、找不到所需内容、
+放弃读锁、获取写锁以及插入对象这些操作的代码是具有竞争条件的。
+
+如果你尚未清楚原因,请不要轻易改动代码。
+
+竞争计时器:内核的空闲
+----------------------
+
+计时器会出现其特有的竞争问题。试想一个对象集合(列表、散列等),其中每个对象
+都有一个计时器,该计时器将销毁它。
+
+如果要销毁整个集合(例如在删除模块时),可以执行以下操作::
+
+            /* 这段代码很糟糕:如果情况再糟一点点,
+            它将使用匈牙利符号(HUNGARIAN NOTATION)*/
+            spin_lock_bh(&list_lock);
+
+            while (list) {
+                    struct foo *next = list->next;
+                    del_timer(&list->timer);
+                    kfree(list);
+                    list = next;
+            }
+
+            spin_unlock_bh(&list_lock);
+
+
+这迟早会在SMP上崩溃,因为一个计时器可能在spin_lock_bh()之前刚刚关闭,它只会
+在我们spin_unlock_bh()之后获得锁,然后尝试释放 **已经释放了的** 元素。
+
+这可以通过检查del_timer()的结果来避免:如果它返回1,则表示计时器已被删除。
+如果为0,(在本例中)则表示它当前正在运行,因此我们可以这样做::
+
+            retry:
+                    spin_lock_bh(&list_lock);
+
+                    while (list) {
+                            struct foo *next = list->next;
+                            if (!del_timer(&list->timer)) {
+                                    /* Give timer a chance to delete this */
+                                    spin_unlock_bh(&list_lock);
+                                    goto retry;
+                            }
+                            kfree(list);
+                            list = next;
+                    }
+
+                    spin_unlock_bh(&list_lock);
+
+
+另一个常见问题是要删除会重启自己的计时器(计时器函数末尾调用了add_timer())。
+这是一种很常见的容易引发竞争的情况,应该使用del_timer_sync()
+( ``include/linux/timer.h`` )来处理。它返回我们最终阻止计时器重新添加自身
+之前必须删除的次数。
+
+锁操作速度
+==========
+
+在考虑某些执行锁操作代码的速度时,主要有三个问题值得注意。首先是并发性:当某
+函数持有锁时,有多少事情要等待。其次是实际获取和释放非竞争锁所需的时间。第三
+是使用更少或更智能的锁。这里我假设锁使用得相当频繁,否则您就不太会关心效率。
+
+并发性取决于通常锁被持有的时间:您应该根据需要持有锁,不可无端获取。在cache
+示例中,我们总是在不持有锁的情况下创建对象,只有之后准备将其插入列表时才获取锁。
+
+获取时间取决于锁操作对流水线(pipeline)造成的破坏程度(流水线暂停)以及该CPU
+最后一个获取锁的可能性(即该CPU的锁缓存是否热):在具有更多CPU的机器上,这种
+可能性下降得很快。以700MHz Intel Pentium III为例:一条指令大约需要0.7ns,一个
+原子增加大约需要58ns,一个CPU上热缓存的锁需要160ns,从另一个CPU传输缓存线需要
+额外的170到360ns(这些数据来自Paul McKenney的 `Linux Journal RCU 文章
+<http://www.linuxjournal.com/article.php?sid=6993>`_ )。
+
+这两个目标是冲突的:缩短锁的持有时间可以通过将锁分成几个部分来完成的(例如
+最后的逐对象锁示例),但是这会增加锁获取操作的数量,且结果通常比持有一个锁慢。
+这也是提倡简单锁操作的另一个原因。
+
+第三个问题如下:有一些方法可以减少需要执行的锁操作数量。
+
+读/写锁变体
+-----------
+
+自旋锁和互斥锁都有读/写变体: ``rwlock_t`` 和 :c:type:`struct rw_semaphore
+<rw_semaphore>` 。这将操作分为两类:读和写。如果只读取数据,则可以获得读锁;
+但要写入数据,则需要写锁。读锁可由多人持有,但写锁持有者必须唯一。
+
+如果您的代码读/写器清晰分离(就像我们的缓存代码那样),并且锁由读写器持有相当
+长的时间,那么使用这些锁会有所帮助。不过它们比普通锁稍慢,因此在实践中
+``rwlock_t`` 通常不值得使用。
+
+避免加锁:读-复制-更新
+----------------------
+
+有一种特殊的读/写锁定方法称为读-复制-更新(Read Copy Update,RCU)。使用RCU,
+读取程序可以完全避免使用锁:因为我们期望缓存的读取频率比更新频率高(否则缓存
+是浪费时间),所以它适用这种优化。
+
+那我们如何摆脱读锁呢?摆脱读锁意味着写程序可能会更改读程序下面的列表。这实际
+上非常简单:如果写程序非常小心地添加元素,我们可以在添加元素时读取链表。
+例如,向名为 ``list`` 的单链表添加 ``new`` ::
+
+            new->next = list->next;
+            wmb();
+            list->next = new;
+
+
+wmb()是一个写内存屏障(write memory barrier)。它确保第一个操作(设置新元素的
+``next`` 指针)是完整的,并且在第二个操作(将新元素放入列表)之前所有CPU都可
+见。这一点很重要,因为现代编译器和现代CPU都可以对指令重新排序(除非另有说明):
+我们希望读取程序要么根本看不到新元素,要么看到 ``next`` 指针正确指向列表其余
+部分的新元素。
+
+幸运的是,对于标准的 :c:type:`struct list_head <list_head>` 列表有一个函数可以
+做到这一点:list_add_rcu()( ``include/linux/list.h`` )。
+
+Fortunately, there is a function to do this for standard
+:c:type:`struct list_head <list_head>` lists:
+list_add_rcu() (``include/linux/list.h``).
+
+从列表中删除元素更简单:我们将指向旧元素的指针替换为指向其后续元素的指针,读取
+程序要么可以看到它,要么跳过它。
+
+::
+
+            list->next = old->next;
+
+list_del_rcu()( ``include/linux/list.h`` )函数可以做这个(普通版本会毒化旧
+对象,这是我们不希望的)。
+
+读取程序也必须小心:一些CPU可以查看 ``next`` 指针,以便尽早开始读取下一个元素
+的内容,但当 ``next`` 指针在其后更改时,无法发现到预读的内容是错误的。同样,
+list_for_each_entry_rcu()( ``include/linux/list.h`` )可以帮助您。当然,写入
+程序可以只使用list_for_each_entry(),因为不能同时有两个写入程序。
+
+最后一个难题是:我们什么时候才能真正销毁移除的元素?记住,读取程序可能正在列表
+中读到这个元素:如果释放这个元素并且 ``next`` 指针发生变化,读取程序就会跳进
+垃圾堆并崩溃。我们需要等到遍历列表的所有读取程序都完成了才可以删除元素。可使用
+call_rcu()注册一个回调,一旦所有预先存在的读取程序都完成,回调就会销毁该对象。
+或者,synchronize_rcu()可以用于阻止释放直到所有预先存在的读取程序完成。
+
+那么读-复制-更新如何知道读取程序何时完成?方法是:首先,读取程序总是遍历
+rcu_read_lock()/rcu_read_unlock()对中的列表:这直接禁用了抢占,以使读取程序
+在读取列表时不会休眠。
+
+然后RCU等待每个CPU至少休眠一次:由于读取程序无法休眠,这样我们就知道在删除过程
+中遍历列表的所有读取程序都已经完成,并且触发回调。真正的读-复制-更新代码比这
+更优化了一点,但基本原理相同。
+
+::
+
+    --- cache.c.perobjectlock   2003-12-11 17:15:03.000000000 +1100
+    +++ cache.c.rcupdate    2003-12-11 17:55:14.000000000 +1100
+    @@ -1,15 +1,18 @@
+     #include <linux/list.h>
+     #include <linux/slab.h>
+     #include <linux/string.h>
+    +#include <linux/rcupdate.h>
+     #include <linux/mutex.h>
+     #include <asm/errno.h>
+
+     struct object
+     {
+    -        /* These two protected by cache_lock. */
+    +        /* This is protected by RCU */
+             struct list_head list;
+             int popularity;
+
+    +        struct rcu_head rcu;
+    +
+             atomic_t refcnt;
+
+             /* Doesn't change once created. */
+    @@ -40,7 +43,7 @@
+     {
+             struct object *i;
+
+    -        list_for_each_entry(i, &cache, list) {
+    +        list_for_each_entry_rcu(i, &cache, list) {
+                     if (i->id == id) {
+                             i->popularity++;
+                             return i;
+    @@ -49,19 +52,25 @@
+             return NULL;
+     }
+
+    +/* Final discard done once we know no readers are looking. */
+    +static void cache_delete_rcu(void *arg)
+    +{
+    +        object_put(arg);
+    +}
+    +
+     /* Must be holding cache_lock */
+     static void __cache_delete(struct object *obj)
+     {
+             BUG_ON(!obj);
+    -        list_del(&obj->list);
+    -        object_put(obj);
+    +        list_del_rcu(&obj->list);
+             cache_num--;
+    +        call_rcu(&obj->rcu, cache_delete_rcu);
+     }
+
+     /* Must be holding cache_lock */
+     static void __cache_add(struct object *obj)
+     {
+    -        list_add(&obj->list, &cache);
+    +        list_add_rcu(&obj->list, &cache);
+             if (++cache_num > MAX_CACHE_SIZE) {
+                     struct object *i, *outcast = NULL;
+                     list_for_each_entry(i, &cache, list) {
+    @@ -104,12 +114,11 @@
+     struct object *cache_find(int id)
+     {
+             struct object *obj;
+    -        unsigned long flags;
+
+    -        spin_lock_irqsave(&cache_lock, flags);
+    +        rcu_read_lock();
+             obj = __cache_find(id);
+             if (obj)
+                     object_get(obj);
+    -        spin_unlock_irqrestore(&cache_lock, flags);
+    +        rcu_read_unlock();
+             return obj;
+     }
+
+请注意,读取程序将更改__cache_find()中的活跃成员,现在它不持有锁。一种解决方案
+是让其成为 ``atomic_t`` ,但对于这种用法,我们并不真正关心竞争:近似的结果就
+足够了,所以我没有更改它。
+
+结果是cache_find(不需要与任何其他函数同步,因此在SMP上的速度几乎与在UP上的速
+度一样快。
+
+还有一个可以进一步优化的点:还记得我们原来的缓存代码吗,没有引用计数,调用者
+在使用对象时直接持有锁?这仍然是可能的:如果您持有锁,没有人可以删除对象,这样
+您就不需要获取并放置引用计数。
+
+现在,由于RCU中的“读取锁”只是禁用了抢占,所以在调用cache_find()和object_put()
+之间始终禁用了抢占的调用者不需要实际获取和放置引用计数:我们可以通过使其成为
+非静态来暴露__cache_find(),这样的调用者可以直接调用。
+
+这样的好处是引用计数不会被写入:对象不会以任何方式更改,这在SMP机器上由于缓存
+而快得多。
+
+逐CPU数据
+---------
+
+另一种被广泛使用的避免锁的技术是为每个CPU复制信息。例如,如果您想保持一个常见
+条件的计数,可以使用自旋锁和单个计数器。又好又简单。
+
+如果速度太慢(通常不会,但是如果你有一台非常大的机器可以测试就会发现这种情况),
+你可以为每个CPU使用一个计数器,那么它们都不需要独占锁。请参见DEFINE_PER_CPU(),
+get_cpu_var()和put_cpu_var()( ``include/linux/percpu.h`` )。
+
+对于简单的逐CPU计数器, ``local_t`` 类型、cpu_local_inc()和相关函数特别有用,
+它们在某些体系结构( ``包括/asm/local.h`` )上比简单代码更有效。
+
+请注意,如果不引入更多锁,就没有简单、可靠的方法来获取此类计数器的精确值。
+不过对于某些用途来说,这并不是问题。
+
+主要由中断处理程序使用的数据
+----------------------------
+
+如果总是从同一个中断处理程序中访问数据,则根本不需要锁:内核已经保证中断处理
+程序不会在多个CPU上同时运行。
+
+Manfred Spraul指出您仍然可以这样做,即使数据偶尔在用户上下文或软中断/子任务中
+访问。中断处理程序不使用锁,所有其他访问都是这样进行的::
+
+        spin_lock(&lock);
+        disable_irq(irq);
+        ...
+        enable_irq(irq);
+        spin_unlock(&lock);
+
+disable_irq()会阻止中断处理程序运行(如果它当前正在其他CPU上运行,则等待它
+完成)。自旋锁会阻止任何其他访问同时发生。当然,这比单个spin_lock_irq()调用
+要慢,所以只有在这种访问很少发生时才有意义。
+
+从中断中调用哪些函数是安全的?
+==============================
+
+
+内核休眠中的许多函数(即call schedule())都是直接或间接的:在持有自旋锁或禁用
+抢占的情况下,永远不能调用它们。这也意味着您需要处于用户上下文中:从中断调用
+它们是不允许的。
+
+一些休眠的函数
+--------------
+
+下面列出了最常见的一些,但是您通常必须阅读代码才能确定其他调用是否安全。如果
+其他调用它的程序都可以休眠,你可能也可以休眠。特别是,注册和注销函数通常期望
+从用户上下文调用,并且可以休眠。
+
+-  访问用户空间:
+
+   -  copy_from_user()
+
+   -  copy_to_user()
+
+   -  get_user()
+
+   -  put_user()
+
+-  :c:func:`kmalloc(GP_KERNEL) <kmalloc>`
+
+-  mutex_lock_interruptible() 和 mutex_lock()
+
+   mutex_trylock()不休眠;但是,不能在中断上下文中使用它,因为它的实现对此不
+   安全。mutex_unlock()也不会休眠;它也不能在中断上下文中使用,因为互斥锁必须
+   由获取它的同一个任务释放。
+
+一些不休眠的函数
+----------------
+
+有些函数可以从任何上下文调用,或者持有几乎任何锁。
+
+-  printk()
+
+-  kfree()
+
+-  add_timer() 和 del_timer()
+
+互斥锁API参考
+===================
+
+.. kernel-doc:: include/linux/mutex.h
+   :internal:
+
+.. kernel-doc:: kernel/locking/mutex.c
+   :export:
+
+快速用户空间互斥锁(Futex)API参考
+==================================
+
+*译注:Futex,Fast Userspace Mutex*
+
+.. kernel-doc:: kernel/futex.c
+   :internal:
+
+进一步阅读资料
+===============
+
+-  Documentation/locking/spinlocks.rst :Linus Torvalds的自旋锁教程。
+
+-  《现代体系结构上的UNIX系统——内核程序员的SMP和Caching技术》
+
+   *Unix Systems for Modern Architectures: Symmetric Multiprocessing and
+   Caching for Kernel Programmers*
+
+   Curt Schimmel对内核级锁的上佳介绍(不是为Linux编写的,但是几乎所有的东西
+   都适用)。这本书是昂贵的,但花点钱去搞懂SMP锁定绝对不亏。
+
+   【英文原版 ISBN 0201633388】
+   【中译本 ISBN 7-155-10876-5 人民邮电出版社】
+
+致谢
+======
+
+感谢Telsa Gwynne帮助DocBooking、整理和添加格式。
+
+感谢Martin Pool,Philipp Rumpf,Stephen Rothwell,Paul Mackerras,
+Ruedi Aschwanden,Alan Cox,Manfred Spraul,Tim Waugh,Pete Zaitcev,
+JamesMorris,Robert Love,Paul McKenney,John Ashby的校对、更正、评论。
+
+感谢反对团体没有影响此文档。
+
+术语表
+======
+
+抢占 preemption
+  在2.5版本之前,或者当 ``CONFIG_PREEMPT`` 未设置时,内核里用户上下文中的进程
+  不会相互抢占(即在放弃CPU之前,除了中断之外,您一直拥有该CPU)。2.5.4版本中
+  增加的 ``CONFIG_PREEMPT`` 改变了此机制:在用户上下文中,更高优先级的任务可
+  以“切入”:自旋锁被更改为禁用抢占,即使是在UP上。
+
+下半部 bh
+  *[Bottom Half]* 由于历史原因,带有“_bh”的函数现在通常指任何软件中断,例如
+  spin_lock_bh()会阻止当前CPU上的任何软件中断。下半部已被弃用,最终将被子任务
+  取代。任何时候只有一个下半部会在运行。
+
+硬(件)中断 Hardware Interrupt / Hardware IRQ
+  硬件中断请求。in_irq()在硬件中断处理程序中返回true。
+
+中断上下文 Interrupt Context
+  非用户上下文:处理硬件中断或软件中断。由返回true的in_interrupt()宏指示。
+
+对称多处理器 SMP
+  *[Symmetric Multi-Processor]* 为多CPU机器编译的内核( ``CONFIG_SMP=y`` )。
+
+软(件)中断 Software Interrupt / softirq
+  软件中断处理程序。in_irq()返回false;in_softirq()返回true。
+  子任务和软中断都属于“软件中断”的范畴。
+
+  严格来说,软中断是最多32个枚举软件中断之一,可以同时在多个CPU上运行。
+  有时也用来指子任务(即所有软件中断)。
+
+子任务 tasklet
+  一种可动态注册的软件中断,保证一次只能在一个CPU上运行。
+
+计时器 timer
+  一种可动态注册的软件中断,在给定时间(或接近给定时间)运行。
+  运行时,它就像一个子任务(实际上,它们是从 ``TIMER_SOFTIRQ`` 调用的)。
+
+单处理器 UP
+  *[Uni-Processor]* 非SMP( ``CONFIG_SMP=n`` )。
+
+用户上下文 User Context
+  代表特定进程(即系统调用或陷阱[trap])或内核线程执行的内核。您可以用
+  ``current`` 宏来判断哪个进程)。不要与用户空间混淆。可被软件或硬件中断中断。
+
+用户空间 Userspace
+  在内核外执行自己代码的进程。
-- 
2.20.1


^ permalink raw reply related	[flat|nested] 4+ messages in thread

end of thread, other threads:[~2021-05-13 18:48 UTC | newest]

Thread overview: 4+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2021-04-20 16:01 [PATCH v2 1/2] docs/zh_CN: Add translation zh_CN/kernel-hacking/locking.rst Wu XiangCheng
2021-04-20 16:02 ` [PATCH v2 2/2] docs: kernel-hacking: Fix two ref links Wu XiangCheng
2021-05-13 15:37   ` Jonathan Corbet
2021-05-13 18:48     ` Wu X.C.

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for NNTP newsgroup(s).