code:
.. SPDX-License-Identifier: GPL-2.0+
.. include:: ../disclaimer-zh_CN.rst

:Original: Documentation/core-api/xarray.rst

:翻译:

 司延腾 Yanteng Si <siyanteng@loongson.cn>
 周彬彬 Binbin Zhou <zhoubinbin@loongson.cn>

:校译:

.. _cn_core-api_xarray:

======
XArray
======

:作者: Matthew Wilcox

概览
====

XArray是一个抽象的数据类型，它的行为就像一个非常大的指针数组。它满足了许多与哈
希或传统可调整大小的数组相同的需求。与哈希不同的是，它允许你以一种高效的缓存方
式合理地转到下一个或上一个条目。与可调整大小的数组相比，不需要复制数据或改变MMU
的映射来增加数组。与双链表相比，它的内存效率更高，可并行，对缓存更友好。它利用
RCU的优势来执行查找而不需要锁定。

当使用的索引是密集聚集的时候，XArray的实现是有效的；而哈希对象并使用哈希作为索引
将不会有好的表现。XArray对小的索引进行了优化，不过对大的索引仍有良好的性能。如果
你的索引可以大于 ``ULONG_MAX`` ，那么XArray就不适合你的数据类型。XArray最重要
的用户是页面高速缓存。

普通指针可以直接存储在XArray中。它们必须是4字节对齐的，这对任何从kmalloc()和
alloc_page()返回的指针来说都是如此。这对任意的用户空间指针和函数指针来说都不是
真的。你可以存储指向静态分配对象的指针，只要这些对象的对齐方式至少是4（字节）。

你也可以在XArray中存储0到 ``LONG_MAX`` 之间的整数。你必须首先使用xa_mk_value()
将其转换为一个条目。当你从XArray中检索一个条目时，你可以通过调用xa_is_value()检
查它是否是一个值条目，并通过调用xa_to_value()将它转换回一个整数。

一些用户希望对他们存储在XArray中的指针进行标记。你可以调用xa_tag_pointer()来创建
一个带有标签的条目，xa_untag_pointer()将一个有标签的条目转回一个无标签的指针，
xa_pointer_tag()来检索一个条目的标签。标签指针使用相同的位，用于区分值条目和普通
指针，所以你必须决定他们是否要在任何特定的XArray中存储值条目或标签指针。

XArray不支持存储IS_ERR()指针，因为有些指针与值条目或内部条目冲突。

XArray的一个不寻常的特点是能够创建占据一系列索引的条目。一旦存储到其中，查询该范围
内的任何索引将返回与查询该范围内任何其他索引相同的条目。存储到任何索引都会存储所有
的索引条目。多索引条目可以明确地分割成更小的条目，或者将其存储 ``NULL`` 到任何条目中
都会使XArray忘记范围。

普通API
=======

首先初始化一个XArray，对于静态分配的XArray可以用DEFINE_XARRAY()，对于动态分配的
XArray可以用xa_init()。一个新初始化的XArray在每个索引处都包含一个 ``NULL`` 指针。

然后你可以用xa_store()来设置条目，用xa_load()来获取条目。xa_store将用新的条目覆盖任
何条目，并返回存储在该索引的上一个条目。你可以使用xa_erase()来代替调用xa_store()的
``NULL`` 条目。一个从未被存储过的条目、一个被擦除的条目和一个最近被存储过 ``NULL`` 的
条目之间没有区别。

你可以通过使用xa_cmpxchg()有条件地替换一个索引中的条目。和cmpxchg()一样，它只有在该索
引的条目有 ‘旧‘ 值时才会成功。它也会返回该索引上的条目；如果它返回与传递的 ‘旧‘ 相同的条
目，那么xa_cmpxchg()就成功了。

如果你只想在某个索引的当前条目为 ``NULL`` 时将一个新条目存储到该索引，你可以使用xa_insert()，
如果该条目不是空的，则返回 ``-EBUSY`` 。

你可以通过调用xa_extract()将条目从XArray中复制到一个普通数组中。或者你可以通过调用
xa_for_each()、xa_for_each_start()或xa_for_each_range()来遍历XArray中的现有条目。你
可能更喜欢使用xa_find()或xa_find_after()来移动到XArray中的下一个当前条目。

调用xa_store_range()可以在一个索引范围内存储同一个条目。如果你这样做，其他的一些操作将以
一种稍微奇怪的方式进行。例如，在一个索引上标记条目可能会导致该条目在一些，但不是所有其他索
引上被标记。储存到一个索引中可能会导致由一些，但不是所有其他索引检索的条目发生变化。

有时你需要确保对xa_store()的后续调用将不需要分配内存。xa_reserve()函数将在指定索引处存储
一个保留条目。普通API的用户将看到这个条目包含 ``NULL`` 。如果你不需要使用保留的条目，你可
以调用xa_release()来删除这个未使用的条目。如果在此期间有其他用户存储到该条目，xa_release()
将不做任何事情；相反，如果你想让该条目变成 ``NULL`` ，你应该使用xa_erase()。在一个保留的条
目上使用xa_insert()将会失败。

如果数组中的所有条目都是 ``NULL`` ，xa_empty()函数将返回 ``true`` 。

最后，你可以通过调用xa_destroy()删除XArray中的所有条目。如果XArray的条目是指针，你可能希望
先释放这些条目。你可以通过使用xa_for_each()迭代器遍历XArray中所有存在的条目来实现这一目的。

搜索标记
--------

数组中的每个条目都有三个与之相关的位，称为标记。每个标记可以独立于其他标记被设置或清除。你可以
通过使用xa_for_each_marked()迭代器来迭代有标记的条目。

你可以通过使用xa_get_mark()来查询某个条目是否设置了标记。如果该条目不是 ``NULL`` ，你可以通过
使用xa_set_mark()来设置一个标记，并通过调用xa_clear_mark()来删除条目上的标记。你可以通过调用
xa_marked()来询问XArray中的任何条目是否有一个特定的标记被设置。从XArray中删除一个条目会导致与
该条目相关的所有标记被清除。

在一个多索引条目的任何索引上设置或清除标记将影响该条目所涵盖的所有索引。查询任何索引上的标记将返
回相同的结果。

没有办法对没有标记的条目进行迭代；数据结构不允许有效地实现这一点。目前没有迭代器来搜索比特的逻辑
组合（例如迭代所有同时设置了 ``XA_MARK_1`` 和 ``XA_MARK_2`` 的条目，或者迭代所有设置了
``XA_MARK_0`` 或 ``XA_MARK_2`` 的条目）。如果有用户需要，可以增加这些内容。

分配XArrays
-----------

如果你使用DEFINE_XARRAY_ALLOC()来定义XArray，或者通过向xa_init_flags()传递 ``XA_FLAGS_ALLOC``
来初始化它，XArray会改变以跟踪条目是否被使用。

你可以调用xa_alloc()将条目存储在XArray中一个未使用的索引上。如果你需要从中断上下文中修改数组，你
可以使用xa_alloc_bh()或xa_alloc_irq()，在分配ID的同时禁用中断。

使用xa_store()、xa_cmpxchg()或xa_insert()也将标记该条目为正在分配。与普通的XArray不同，存储 ``NULL``
将标记该条目为正在使用中，就像xa_reserve()。要释放一个条目，请使用xa_erase()（或者xa_release()，
如果你只想释放一个 ``NULL`` 的条目）。

默认情况下，最低的空闲条目从0开始分配。如果你想从1开始分配条目，使用DEFINE_XARRAY_ALLOC1()或
``XA_FLAGS_ALLOC1`` 会更有效。如果你想分配ID到一个最大值，然后绕回最低的空闲ID，你可以使用
xa_alloc_cyclic()。

你不能在分配的XArray中使用 ``XA_MARK_0`` ，因为这个标记是用来跟踪一个条目是否是空闲的。其他的
标记可以供你使用。

内存分配
--------

xa_store(), xa_cmpxchg(), xa_alloc(), xa_reserve()和xa_insert()函数接受一个gfp_t参数，以
防XArray需要分配内存来存储这个条目。如果该条目被删除，则不需要进行内存分配，指定的GFP标志将被忽
略。

没有内存可供分配是可能的，特别是如果你传递了一组限制性的GFP标志。在这种情况下，这些函数会返回一
个特殊的值，可以用xa_err()把它变成一个错误值。如果你不需要确切地知道哪个错误发生，使用xa_is_err()
会更有效一些。

锁
--

当使用普通API时，你不必担心锁的问题。XArray使用RCU和一个内部自旋锁来同步访问:

不需要锁:
 * xa_empty()
 * xa_marked()

采取RCU读锁:
 * xa_load()
 * xa_for_each()
 * xa_for_each_start()
 * xa_for_each_range()
 * xa_find()
 * xa_find_after()
 * xa_extract()
 * xa_get_mark()

内部使用xa_lock:
 * xa_store()
 * xa_store_bh()
 * xa_store_irq()
 * xa_insert()
 * xa_insert_bh()
 * xa_insert_irq()
 * xa_erase()
 * xa_erase_bh()
 * xa_erase_irq()
 * xa_cmpxchg()
 * xa_cmpxchg_bh()
 * xa_cmpxchg_irq()
 * xa_store_range()
 * xa_alloc()
 * xa_alloc_bh()
 * xa_alloc_irq()
 * xa_reserve()
 * xa_reserve_bh()
 * xa_reserve_irq()
 * xa_destroy()
 * xa_set_mark()
 * xa_clear_mark()

假设进入时持有xa_lock:
 * __xa_store()
 * __xa_insert()
 * __xa_erase()
 * __xa_cmpxchg()
 * __xa_alloc()
 * __xa_set_mark()
 * __xa_clear_mark()

如果你想利用锁来保护你存储在XArray中的数据结构，你可以在调用xa_load()之前调用xa_lock()，然后在
调用xa_unlock()之前对你找到的对象进行一个引用计数。这将防止存储操作在查找对象和增加refcount期间
从数组中删除对象。你也可以使用RCU来避免解除对已释放内存的引用，但对这一点的解释已经超出了本文的范
围。

在修改数组时，XArray不会禁用中断或softirqs。从中断或softirq上下文中读取XArray是安全的，因为RCU锁
提供了足够的保护。

例如，如果你想在进程上下文中存储XArray中的条目，然后在softirq上下文中擦除它们，你可以这样做::

    void foo_init(struct foo *foo)
    {
        xa_init_flags(&foo->array, XA_FLAGS_LOCK_BH);
    }

    int foo_store(struct foo *foo, unsigned long index, void *entry)
    {
        int err;

        xa_lock_bh(&foo->array);
        err = xa_err(__xa_store(&foo->array, index, entry, GFP_KERNEL));
        if (!err)
            foo->count++;
        xa_unlock_bh(&foo->array);
        return err;
    }

    /* foo_erase()只在软中断上下文中调用 */
    void foo_erase(struct foo *foo, unsigned long index)
    {
        xa_lock(&foo->array);
        __xa_erase(&foo->array, index);
        foo->count--;
        xa_unlock(&foo->array);
    }

如果你要从中断或softirq上下文中修改XArray，你需要使用xa_init_flags()初始化数组，传递
``XA_FLAGS_LOCK_IRQ`` 或 ``XA_FLAGS_LOCK_BH`` （参数）。

上面的例子还显示了一个常见的模式，即希望在存储端扩展xa_lock的覆盖范围，以保护与数组相关的一些统计
数据。

与中断上下文共享XArray也是可能的，可以在中断处理程序和进程上下文中都使用xa_lock_irqsave()，或者
在进程上下文中使用xa_lock_irq()，在中断处理程序中使用xa_lock()。一些更常见的模式有一些辅助函数，
如xa_store_bh()、xa_store_irq()、xa_erase_bh()、xa_erase_irq()、xa_cmpxchg_bh() 和xa_cmpxchg_irq()。

有时你需要用一个mutex来保护对XArray的访问，因为这个锁在锁的层次结构中位于另一个mutex之上。这并不
意味着你有权使用像__xa_erase()这样的函数而不占用xa_lock；xa_lock是用来进行lockdep验证的，将来也
会用于其他用途。

__xa_set_mark() 和 __xa_clear_mark() 函数也适用于你查找一个条目并想原子化地设置或清除一个标记的
情况。在这种情况下，使用高级API可能更有效，因为它将使你免于走两次树。

高级API
=======

高级API提供了更多的灵活性和更好的性能，但代价是接口可能更难使用，保障措施更少。高级API没有为你加锁，
你需要在修改数组的时候使用xa_lock。在对数组进行只读操作时，你可以选择使用xa_lock或RCU锁。你可以在
同一个数组上混合使用高级和普通操作；事实上，普通API是以高级API的形式实现的。高级API只对具有GPL兼容
许可证的模块可用。

高级API是基于xa_state的。这是一个不透明的数据结构，你使用XA_STATE()宏在堆栈中声明。这个宏初始化了
xa_state，准备开始在XArray上移动。它被用作一个游标来保持在XArray中的位置，并让你把各种操作组合在一
起，而不必每次都从头开始。xa_state的内容受rcu_read_lock()或xas_lock()的保护。如果需要删除保护状态
和树的这些锁中的任何一个，你必须调用xas_pause()以便将来的调用不会依赖于状态中未受保护的部分。

xa_state也被用来存储错误(store errors)。你可以调用xas_error()来检索错误。所有的操作在进行之前都
会检查xa_state是否处于错误状态，所以你没有必要在每次调用之后检查错误；你可以连续进行多次调用，只在
方便的时候检查。目前XArray代码本身产生的错误只有 ``ENOMEM`` 和 ``EINVAL`` ，但它支持任意的错误，
以防你想自己调用xas_set_err()。

如果xa_state持有 ``ENOMEM`` 错误，调用xas_nomem()将尝试使用指定的gfp标志分配更多的内存，并将其缓
存在xa_state中供下一次尝试。这个想法是，你拿着xa_lock，尝试操作，然后放弃锁。该操作试图在持有锁的情
况下分配内存，但它更有可能失败。一旦你放弃了锁，xas_nomem()可以更努力地尝试分配更多内存。如果值得重
试该操作，它将返回 ``true`` （即出现了内存错误，分配了更多的内存）。如果它之前已经分配了内存，并且
该内存没有被使用，也没有错误（或者一些不是 ``ENOMEM`` 的错误），那么它将释放之前分配的内存。

内部条目
--------

XArray为它自己的目的保留了一些条目。这些条目从未通过正常的API暴露出来，但是当使用高级API时，有可能看
到它们。通常，处理它们的最好方法是把它们传递给xas_retry()，如果它返回 ``true`` ，就重试操作。

.. flat-table::
   :widths: 1 1 6

   * - 名称
     - 检测
     - 用途

   * - Node
     - xa_is_node()
     - 一个XArray节点。 在使用多索引xa_state时可能是可见的。

   * - Sibling
     - xa_is_sibling()
     - 一个多索引条目的非典型条目。该值表示该节点中的哪个槽有典型条目。

   * - Retry
     - xa_is_retry()
     - 这个条目目前正在被一个拥有xa_lock的线程修改。在这个RCU周期结束时，包含该条目的节点可能会被释放。
       你应该从数组的头部重新开始查找。

   * - Zero
     - xa_is_zero()
     - Zero条目通过普通API显示为 ``NULL`` ，但在XArray中占有一个条目，可用于保留索引供将来使用。这是
       通过为分配的条目分配XArrays来使用的，这些条目是 ``NULL`` 。

其他内部条目可能会在未来被添加。在可能的情况下，它们将由xas_retry()处理。

附加函数
--------

xas_create_range()函数分配了所有必要的内存来存储一个范围内的每一个条目。如果它不能分配内存，它将在
xa_state中设置ENOMEM。

你可以使用xas_init_marks()将一个条目上的标记重置为默认状态。这通常是清空所有标记，除非XArray被标记
为 ``XA_FLAGS_TRACK_FREE`` ，在这种情况下，标记0被设置，所有其他标记被清空。使用xas_store()将一个
条目替换为另一个条目不会重置该条目上的标记；如果你想重置标记，你应该明确地这样做。

xas_load()会尽可能地将xa_state移动到该条目附近。如果你知道xa_state已经移动到了该条目，并且需要检查
该条目是否有变化，你可以使用xas_reload()来保存一个函数调用。

如果你需要移动到XArray中的不同索引，可以调用xas_set()。这可以将光标重置到树的顶端，这通常会使下一个
操作将光标移动到树中想要的位置。如果你想移动到下一个或上一个索引，调用xas_next()或xas_prev()。设置
索引不会使光标在数组中移动，所以不需要锁，而移动到下一个或上一个索引则需要锁。

你可以使用xas_find()搜索下一个当前条目。这相当于xa_find()和xa_find_after()；如果光标已经移动到了
一个条目，那么它将找到当前引用的条目之后的下一个条目。如果没有，它将返回xa_state索引处的条目。使用
xas_next_entry()而不是xas_find()来移动到下一个当前条目，在大多数情况下会节省一个函数调用，但代价
是发出更多内联代码。

xas_find_marked()函数也是如此。如果xa_state没有被移动过，它将返回xa_state的索引处的条目，如果它
被标记了。否则，它将返回xa_state所引用的条目之后的第一个被标记的条目。xas_next_marked()函数等同
于xas_next_entry()。

当使用xas_for_each()或xas_for_each_marked()在XArray的某个范围内进行迭代时，可能需要暂时停止迭代。
xas_pause()函数的存在就是为了这个目的。在你完成了必要的工作并希望恢复后，xa_state处于适当的状态，在
你最后处理的条目后继续迭代。如果你在迭代时禁用了中断，那么暂停迭代并在每一个 ``XA_CHECK_SCHED`` 条目
中重新启用中断是很好的做法。

xas_get_mark(), xas_set_mark()和xas_clear_mark()函数要求xa_state光标已经被移动到XArray中的适当位
置；如果你在之前调用了xas_pause()或xas_set()，它们将不会有任何作用。

你可以调用xas_set_update()，让XArray每次更新一个节点时都调用一个回调函数。这被页面缓存的workingset
代码用来维护其只包含阴影项的节点列表。

多索引条目
----------

XArray有能力将多个索引联系在一起，因此对一个索引的操作会影响到所有的索引。例如，存储到任何索引将改变
从任何索引检索的条目的值。在任何索引上设置或清除一个标记，都会在每个被绑在一起的索引上设置或清除该标
记。目前的实现只允许将2的整数倍的范围绑在一起；例如指数64-127可以绑在一起，但2-6不能。这可以节省大量
的内存；例如，将512个条目绑在一起可以节省4kB以上的内存。

你可以通过使用XA_STATE_ORDER()或xas_set_order()，然后调用xas_store()来创建一个多索引条目。用一个
多索引的xa_state调用xas_load()会把xa_state移动到树中的正确位置，但是返回值没有意义，有可能是一个内
部条目或 ``NULL`` ，即使在范围内有一个条目存储。调用xas_find_conflict()将返回该范围内的第一个条目，
如果该范围内没有条目，则返回 ``NULL`` 。xas_for_each_conflict()迭代器将遍历每个与指定范围重叠的条目。

如果xas_load()遇到一个多索引条目，xa_state中的xa_index将不会被改变。当遍历一个XArray或者调用xas_find()
时，如果初始索引在一个多索引条目的中间，它将不会被改变。随后的调用或迭代将把索引移到范围内的第一个索引。
每个条目只会被返回一次，不管它占据了多少个索引。

不支持使用xas_next()或xas_prev()来处理一个多索引的xa_state。在一个多索引的条目上使用这两个函数中的任
何一个都会显示出同级的条目；这些条目应该被调用者跳过。

在一个多索引条目的任何一个索引中存储 ``NULL`` ，将把每个索引的条目设置为 ``NULL`` ，并解除绑定。通过调
用xas_split_alloc()，在没有xa_lock的情况下，可以将一个多索引条目分割成占据较小范围的条目，然后再取锁并
调用xas_split()。

函数和结构体
============

该API在以下内核代码中:

include/linux/xarray.h

lib/xarray.c


code:
.. SPDX-License-Identifier: GPL-2.0
.. include:: ../disclaimer-zh_CN.rst

:Original: Documentation/cpu-freq/core.rst

:翻译:

 司延腾 Yanteng Si <siyanteng@loongson.cn>

:校译:

 唐艺舟 Tang Yizhou <tangyeechou@gmail.com>

====================================
CPUFreq核心和CPUFreq通知器的通用说明
====================================

作者:
	- Dominik Brodowski  <linux@brodo.de>
	- David Kimdon <dwhedon@debian.org>
	- Rafael J. Wysocki <rafael.j.wysocki@intel.com>
	- Viresh Kumar <viresh.kumar@linaro.org>

.. 目录:

   1.  CPUFreq核心和接口
   2.  CPUFreq通知器
   3.  含有Operating Performance Point (OPP)的CPUFreq表的生成

1. CPUFreq核心和接口
======================

cpufreq核心代码位于drivers/cpufreq/cpufreq.c中。这些cpufreq代码为CPUFreq架构的驱
动程序（那些执行硬件频率切换的代码）以及 "通知器" 提供了一个标准化的接口。
包括设备驱动程序；需要了解策略变化（如 ACPI 热量管理），或所有频率变化（如计时代码），
甚至需要强制限制为指定频率（如 ARM 架构上的 LCD 驱动程序）的其它内核组件。
此外，内核 "常数" loops_per_jiffy 会根据频率变化而更新。

cpufreq策略的引用计数由 cpufreq_cpu_get 和 cpufreq_cpu_put 来完成，以确保 cpufreq 驱
动程序被正确地注册到核心中，并且驱动程序在 cpufreq_put_cpu 被调用之前不会被卸载。这也保证
了每个CPU核的cpufreq 策略在使用期间不会被释放。

2. CPUFreq 通知器
====================

CPUFreq通知器遵循标准的内核通知器接口。
关于通知器的细节请参阅 linux/include/linux/notifier.h。

这里有两个不同的CPUfreq通知器 - 策略通知器和转换通知器。

2.1 CPUFreq策略通知器
----------------------------

当创建或移除策略时，这些都会被通知。

阶段是在通知器的第二个参数中指定的。当第一次创建策略时，阶段是CPUFREQ_CREATE_POLICY，当
策略被移除时，阶段是CPUFREQ_REMOVE_POLICY。

第三个参数 ``void *pointer`` 指向一个结构体cpufreq_policy，其包括min，max(新策略的下限和
上限（单位为kHz）)这几个值。

2.2 CPUFreq转换通知器
--------------------------------

当CPUfreq驱动切换CPU核心频率时，策略中的每个在线CPU都会收到两次通知，这些变化没有任何外部干
预。

第二个参数指定阶段 - CPUFREQ_PRECHANGE or CPUFREQ_POSTCHANGE.

第三个参数是一个包含如下值的结构体cpufreq_freqs：

======	===============================
policy	指向struct cpufreq_policy的指针
old	旧频率
new	新频率
flags	cpufreq驱动的标志
======	===============================

3. 含有Operating Performance Point (OPP)的CPUFreq表的生成
==================================================================
关于OPP的细节请参阅 Documentation/power/opp.rst

dev_pm_opp_init_cpufreq_table -
	这个函数提供了一个随时可用的转换例程，用来将OPP层关于可用频率的内部信息翻译成一种
	cpufreq易于处理的格式。

	.. Warning::

		不要在中断上下文中使用此函数。

	例如::

	 soc_pm_init()
	 {
		/* Do things */
		r = dev_pm_opp_init_cpufreq_table(dev, &freq_table);
		if (!r)
			policy->freq_table = freq_table;
		/* Do other things */
	 }

	.. note::

		该函数只有在CONFIG_PM_OPP之外还启用了CONFIG_CPU_FREQ时才可用。

dev_pm_opp_free_cpufreq_table
	释放dev_pm_opp_init_cpufreq_table分配的表。

repo_name: angery/linux


code:
.. SPDX-License-Identifier: GPL-2.0

.. include:: ../disclaimer-zh_CN.rst

:Original: Documentation/cpu-freq/cpu-drivers.rst

:翻译:

 司延腾 Yanteng Si <siyanteng@loongson.cn>

:校译:

 唐艺舟 Tang Yizhou <tangyeechou@gmail.com>

=======================================
如何实现一个新的CPUFreq处理器驱动程序？
=======================================

作者:

	- Dominik Brodowski  <linux@brodo.de>
	- Rafael J. Wysocki <rafael.j.wysocki@intel.com>
	- Viresh Kumar <viresh.kumar@linaro.org>

.. Contents

   1.   怎么做？
   1.1  初始化
   1.2  Per-CPU 初始化
   1.3  验证
   1.4  target/target_index 或 setpolicy?
   1.5  target/target_index
   1.6  setpolicy
   1.7  get_intermediate 与 target_intermediate
   2.   频率表助手

1. 怎么做？
===========

如果，你刚刚得到了一个全新的CPU/芯片组及其数据手册，并希望为这个CPU/芯片组添加cpufreq
支持？很好，这里有一些至关重要的提示：

1.1 初始化
----------

首先，在 __initcall level 7 (module_init())或更靠后的函数中检查这个内核是否
运行在正确的CPU和正确的芯片组上。如果是，则使用cpufreq_register_driver()向
CPUfreq核心层注册一个cpufreq_driver结构体。

结构体cpufreq_driver应该包含什么成员?

 .name - 驱动的名字。

 .init - 一个指向per-policy初始化函数的指针。

 .verify - 一个指向"verification"函数的指针。

 .setpolicy 或 .fast_switch 或 .target 或 .target_index - 差异见
 下文。

其它可选成员

 .flags - 给cpufreq核心的提示。

 .driver_data - cpufreq驱动程序的特有数据。

 .get_intermediate 和 target_intermediate - 用于在改变CPU频率时切换到稳定
 的频率。

 .get - 返回CPU的当前频率。

 .bios_limit - 返回HW/BIOS对CPU的最大频率限制值。

 .exit - 一个指向per-policy清理函数的指针，该函数在CPU热插拔过程的CPU_POST_DEAD
 阶段被调用。

 .suspend - 一个指向per-policy暂停函数的指针，该函数在关中断且在该策略的调节器停止
 后被调用。

 .resume - 一个指向per-policy恢复函数的指针，该函数在关中断且在调节器再一次启动前被
 调用。

 .ready - 一个指向per-policy准备函数的指针，该函数在策略完全初始化之后被调用。

 .attr - 一个指向NULL结尾的"struct freq_attr"列表的指针，该列表允许导出值到
 sysfs。

 .boost_enabled - 如果设置，则启用提升(boost)频率。

 .set_boost - 一个指向per-policy函数的指针，该函数用来开启/关闭提升(boost)频率功能。

1.2 Per-CPU 初始化
------------------

每当一个新的CPU被注册到设备模型中，或者当cpufreq驱动注册自身之后，如果此CPU的cpufreq策
略不存在，则会调用per-policy的初始化函数cpufreq_driver.init。请注意，.init()和.exit()例程
只为某个策略调用一次，而不是对该策略管理的每个CPU调用一次。它需要一个 ``struct cpufreq_policy
*policy`` 作为参数。现在该怎么做呢？

如果有必要，请在你的CPU上激活CPUfreq功能支持。

然后，驱动程序必须填写以下值:

+-----------------------------------+--------------------------------------+
|policy->cpuinfo.min_freq和         | 该CPU支持的最低和最高频率（kHz）     |
|policy->cpuinfo.max_freq           |                                      |
|                                   |                                      |
+-----------------------------------+--------------------------------------+
|policy->cpuinfo.transition_latency | CPU在两个频率之间切换所需的时间，以  |
|                                   | 纳秒为单位（如不适用，设定为         |
|                                   | CPUFREQ_ETERNAL）                    |
|                                   |                                      |
+-----------------------------------+--------------------------------------+
|policy->cur                        | 该CPU当前的工作频率(如适用)          |
|                                   |                                      |
+-----------------------------------+--------------------------------------+
|policy->min,                       | 必须包含该CPU的"默认策略"。稍后      |
|policy->max,                       | 会用这些值调用                       |
|policy->policy and, if necessary,  | cpufreq_driver.verify和下面函数      |
|policy->governor                   | 之一：cpufreq_driver.setpolicy或     |
|                                   | cpufreq_driver.target/target_index   |
|                                   |                                      |
+-----------------------------------+--------------------------------------+
|policy->cpus                       | 该policy通过DVFS框架影响的全部CPU    |
|                                   | (即与本CPU共享"时钟/电压"对)构成     |
|                                   | 掩码(同时包含在线和离线CPU)，用掩码  |
|                                   | 更新本字段                           |
|                                   |                                      |
+-----------------------------------+--------------------------------------+

对于设置其中的一些值(cpuinfo.min[max]_freq, policy->min[max])，频率表辅助函数可能会有帮
助。关于它们的更多信息，请参见第2节。

1.3 验证
--------

当用户决定设置一个新的策略(由"policy,governor,min,max组成")时，必须对这个策略进行验证，
以便纠正不兼容的值。为了验证这些值，cpufreq_verify_within_limits(``struct cpufreq_policy
*policy``, ``unsigned int min_freq``, ``unsigned int max_freq``)函数可能会有帮助。
关于频率表辅助函数的详细内容请参见第2节。

您需要确保至少有一个有效频率（或工作范围）在 policy->min 和 policy->max 范围内。如果有必
要，先增大policy->max，只有在没有解决方案的情况下，才减小policy->min。

1.4 target 或 target_index 或 setpolicy 或 fast_switch?
-------------------------------------------------------

大多数cpufreq驱动甚至大多数CPU频率升降算法只允许将CPU频率设置为预定义的固定值。对于这些，你
可以使用->target()，->target_index()或->fast_switch()回调。

有些具有硬件调频能力的处理器可以自行依据某些限制来切换CPU频率。它们应使用->setpolicy()回调。

1.5. target/target_index
------------------------

target_index调用有两个参数： ``struct cpufreq_policy * policy`` 和 ``unsigned int``
索引(用于索引频率表项)。

当调用这里时，CPUfreq驱动必须设置新的频率。实际频率必须由freq_table[index].frequency决定。

在发生错误的情况下总是应该恢复到之前的频率(即policy->restore_freq)，即使我们已经切换到了
中间频率。

已弃用
----------
target调用有三个参数。``struct cpufreq_policy * policy``, unsigned int target_frequency,
unsigned int relation.

CPUfreq驱动在调用这里时必须设置新的频率。实际的频率必须使用以下规则来确定。

- 尽量贴近"目标频率"。
- policy->min <= new_freq <= policy->max (这必须是有效的!!!)
- 如果 relation==CPUFREQ_REL_L，尝试选择一个高于或等于 target_freq 的 new_freq。("L代表
  最低，但不能低于")
- 如果 relation==CPUFREQ_REL_H，尝试选择一个低于或等于 target_freq 的 new_freq。("H代表
  最高，但不能高于")

这里，频率表辅助函数可能会帮助你 -- 详见第2节。

1.6. fast_switch
----------------

这个函数用于从调度器的上下文进行频率切换。并非所有的驱动都要实现它，因为不允许在这个回调中睡眠。这
个回调必须经过高度优化，以尽可能快地进行切换。

这个函数有两个参数： ``struct cpufreq_policy *policy`` 和 ``unsigned int target_frequency``。

1.7 setpolicy
-------------

setpolicy调用只需要一个 ``struct cpufreq_policy * policy`` 作为参数。需要将处理器内或芯片组内动态频
率切换的下限设置为policy->min，上限设置为policy->max，如果支持的话，当policy->policy为
CPUFREQ_POLICY_PERFORMANCE时选择面向性能的设置，为CPUFREQ_POLICY_POWERSAVE时选择面向省电的设置。
也可以查看drivers/cpufreq/longrun.c中的参考实现。

1.8 get_intermediate 和 target_intermediate
--------------------------------------------

仅适用于未设置 target_index() 和 CPUFREQ_ASYNC_NOTIFICATION 的驱动。

get_intermediate应该返回一个平台想要切换到的稳定的中间频率，target_intermediate()应该将CPU设置为
该频率，然后再跳转到'index'对应的频率。cpufreq核心会负责发送通知，驱动不必在
target_intermediate()或target_index()中处理它们。

在驱动程序不想为某个目标频率切换到中间频率的情况下，它们可以让get_intermediate()返回'0'。
在这种情况下，cpufreq核心将直接调用->target_index()。

注意：->target_index()应该在发生失败的情况下将频率恢复到policy->restore_freq，
因为cpufreq核心会为此发送通知。

2. 频率表辅助函数
=================

由于大多数支持cpufreq的处理器只允许被设置为几个特定的频率，因此，"频率表"和一些相关函数可能会辅助处理器驱动
程序的一些工作。这样的"频率表"是一个由struct cpufreq_frequency_table的条目构成的数组，"driver_data"成员包
含驱动程序的专用值，"frequency"成员包含了相应的频率，此外还有标志成员。在表的最后，需要添加一个
cpufreq_frequency_table条目，频率设置为CPUFREQ_TABLE_END。如果想跳过表中的一个条目，则将频率设置为
CPUFREQ_ENTRY_INVALID。这些条目不需要按照任何特定的顺序排序，如果排序了，cpufreq核心执行DVFS会更快一点，
因为搜索最佳匹配会更快。

如果在policy->freq_table字段中包含一个有效的频率表指针，频率表就会被cpufreq核心自动验证。

cpufreq_frequency_table_verify()保证至少有一个有效的频率在policy->min和policy->max范围内，并且所有其他
准则都被满足。这对->verify调用很有帮助。

cpufreq_frequency_table_target()是对应于->target阶段的频率表辅助函数。只要把值传递给这个函数，这个函数就会返
回包含CPU要设置的频率的频率表条目。

以下宏可以作为cpufreq_frequency_table的迭代器。

cpufreq_for_each_entry(pos, table) - 遍历频率表的所有条目。

cpufreq_for_each_valid_entry(pos, table) - 该函数遍历所有条目，不包括CPUFREQ_ENTRY_INVALID频率。
使用参数"pos" -- 一个 ``cpufreq_frequency_table *`` 作为循环指针，使用参数"table" -- 作为你想迭代
的 ``cpufreq_frequency_table *`` 。

例如::

	struct cpufreq_frequency_table *pos, *driver_freq_table;

	cpufreq_for_each_entry(pos, driver_freq_table) {
		/* Do something with pos */
		pos->frequency = ...
	}

如果你需要在driver_freq_table中处理pos的位置，不要做指针减法，因为它的代价相当高。作为替代，使用宏
cpufreq_for_each_entry_idx() 和 cpufreq_for_each_valid_entry_idx() 。


code:
.. SPDX-License-Identifier: GPL-2.0

.. include:: ../disclaimer-zh_CN.rst

:Original: Documentation/cpu-freq/cpufreq-stats.rst

:翻译:

 司延腾 Yanteng Si <siyanteng@loongson.cn>

:校译:

 唐艺舟 Tang Yizhou <tangyeechou@gmail.com>

==========================================
sysfs CPUFreq Stats的一般说明
==========================================

为使用者准备的信息

作者: Venkatesh Pallipadi <venkatesh.pallipadi@intel.com>

.. Contents

   1. 简介
   2. 提供的统计数据(举例说明)
   3. 配置cpufreq-stats

1. 简介
===============

cpufreq-stats是一种为每个CPU提供CPU频率统计的驱动。
这些统计数据以/sysfs中一系列只读接口的形式呈现。cpufreq-stats接口（若已配置）将为每个CPU生成
/sysfs（<sysfs root>/devices/system/cpu/cpuX/cpufreq/stats/）中cpufreq目录下的stats目录。
各项统计数据将在stats目录下形成对应的只读文件。

此驱动是以独立于任何可能运行在你所用CPU上的特定cpufreq_driver的方式设计的。因此，它将能和任何
cpufreq_driver协同工作。

2. 已提供的统计数据(有例子)
=====================================

cpufreq stats提供了以下统计数据（在下面详细解释）。

-  time_in_state
-  total_trans
-  trans_table

所有统计数据来自以下时间范围：从统计驱动被加载的时间（或统计数据被重置的时间）开始，到某一统计数据被读取的时间为止。
显然，统计驱动不会保存它被加载之前的任何频率转换信息。

::

    <mysystem>:/sys/devices/system/cpu/cpu0/cpufreq/stats # ls -l
    total 0
    drwxr-xr-x  2 root root    0 May 14 16:06 .
    drwxr-xr-x  3 root root    0 May 14 15:58 ..
    --w-------  1 root root 4096 May 14 16:06 reset
    -r--r--r--  1 root root 4096 May 14 16:06 time_in_state
    -r--r--r--  1 root root 4096 May 14 16:06 total_trans
    -r--r--r--  1 root root 4096 May 14 16:06 trans_table

- **reset**

只写属性，可用于重置统计计数器。这对于评估不同调节器的系统行为非常有用，且无需重启。

- **time_in_state**

此文件给出了在本CPU支持的每个频率上分别花费的时间。cat输出的每一行都是一个"<frequency>
<time>"对，表示这个CPU在<frequency>上花费了<time>个usertime单位的时间。输出的每一行对应
一个CPU支持的频率。这里usertime单位是10mS（类似于/proc导出的其它时间）。

::

    <mysystem>:/sys/devices/system/cpu/cpu0/cpufreq/stats # cat time_in_state
    3600000 2089
    3400000 136
    3200000 34
    3000000 67
    2800000 172488

- **total_trans**

此文件给出了这个CPU频率转换的总次数。cat的输出是一个计数值，它就是频率转换的总次数。

::

    <mysystem>:/sys/devices/system/cpu/cpu0/cpufreq/stats # cat total_trans
    20

- **trans_table**

本文件提供所有CPU频率转换的细粒度信息。这里的cat输出是一个二维矩阵，其中一个条目<i, j>（第
i行，第j列）代表从Freq_i到Freq_j的转换次数。Freq_i行和Freq_j列遵循驱动最初提供给cpufreq
核心的频率表的排列顺序，因此可以已排序（升序或降序）或未排序。这里的输出也包含了实际
频率值，分别按行和按列显示，以便更好地阅读。

如果转换表大于PAGE_SIZE，读取时将返回一个-EFBIG错误。

::

    <mysystem>:/sys/devices/system/cpu/cpu0/cpufreq/stats # cat trans_table
    From  :    To
	    :   3600000   3400000   3200000   3000000   2800000
    3600000:         0         5         0         0         0
    3400000:         4         0         2         0         0
    3200000:         0         1         0         2         0
    3000000:         0         0         1         0         3
    2800000:         0         0         0         2         0

3. 配置cpufreq-stats
============================

按以下方式在你的内核中配置cpufreq-stats::

	Config Main Menu
		Power management options (ACPI, APM)  --->
			CPU Frequency scaling  --->
				[*] CPU Frequency scaling
				[*]   CPU frequency translation statistics

"CPU Frequency scaling" (CONFIG_CPU_FREQ) 应该被启用，以支持配置cpufreq-stats。

"CPU frequency translation statistics" (CONFIG_CPU_FREQ_STAT)提供了包括
time_in_state、total_trans和trans_table的统计数据。

一旦启用了这个选项，并且你的CPU支持cpufrequency，你就可以在/sysfs中看到CPU频率统计。


code:
.. SPDX-License-Identifier: GPL-2.0

.. include:: ../disclaimer-zh_CN.rst

:Original: Documentation/cpu-freq/index.rst

:翻译:

 司延腾 Yanteng Si <siyanteng@loongson.cn>

.. _cn_index.rst:

=======================================================
Linux CPUFreq - Linux(TM)内核中的CPU频率和电压升降代码
=======================================================

Author: Dominik Brodowski  <linux@brodo.de>

      时钟升降允许你在运行中改变CPU的时钟速度。这是一个很好的节省电池电量的方法，因为时
      钟速度越低，CPU消耗的电量越少。

.. toctree::
   :maxdepth: 1

   core
   cpu-drivers
   cpufreq-stats

邮件列表
------------
这里有一个 CPU 频率变化的 CVS 提交和通用列表，您可以在这里报告bug、问题或提交补丁。要发
布消息，请发送电子邮件到 linux-pm@vger.kernel.org。

链接
-----
FTP档案:
* ftp://ftp.linux.org.uk/pub/linux/cpufreq/

如何访问CVS仓库:
* http://cvs.arm.linux.org.uk/

CPUFreq邮件列表:
* http://vger.kernel.org/vger-lists.html#linux-pm

SA-1100的时钟和电压标度:
* http://www.lartmaker.nl/projects/scaling


code:
.. include:: ../disclaimer-zh_CN.rst

:Original: Documentation/dev-tools/gcov.rst
:Translator: 赵军奎 Bernard Zhao <bernard@vivo.com>

在Linux内核里使用gcov做代码覆盖率检查
=====================================

gcov分析核心支持在Linux内核中启用GCC的覆盖率测试工具 gcov_ ，Linux内核
运行时的代码覆盖率数据会以gcov兼容的格式导出到“gcov”debugfs目录中，可
以通过gcov的 ``-o`` 选项（如下示例）获得指定文件的代码运行覆盖率统计数据
（需要跳转到内核编译路径下并且要有root权限）::

    # cd /tmp/linux-out
    # gcov -o /sys/kernel/debug/gcov/tmp/linux-out/kernel spinlock.c

这将在当前目录中创建带有执行计数注释的源代码文件。
在获得这些统计文件后，可以使用图形化的gcov前端工具（比如 lcov_ ），来实现
自动化处理Linux内核的覆盖率运行数据，同时生成易于阅读的HTML格式文件。

可能的用途:

* 调试（用来判断每一行的代码是否已经运行过）
* 测试改进（如何修改测试代码，尽可能地覆盖到没有运行过的代码）
* 内核最小化配置（对于某一个选项配置，如果关联的代码从来没有运行过，
  是否还需要这个配置）

.. _gcov: https://gcc.gnu.org/onlinedocs/gcc/Gcov.html
.. _lcov: http://ltp.sourceforge.net/coverage/lcov.php

准备
----

内核打开如下配置::

        CONFIG_DEBUG_FS=y
        CONFIG_GCOV_KERNEL=y

获取整个内核的覆盖率数据，还需要打开::

        CONFIG_GCOV_PROFILE_ALL=y

需要注意的是，整个内核开启覆盖率统计会造成内核镜像文件尺寸的增大，
同时内核运行也会变慢一些。
另外，并不是所有的架构都支持整个内核开启覆盖率统计。

代码运行覆盖率数据只在debugfs挂载完成后才可以访问::

        mount -t debugfs none /sys/kernel/debug

定制化
------

如果要单独针对某一个路径或者文件进行代码覆盖率统计，可以在内核相应路
径的Makefile中增加如下的配置:

- 单独统计单个文件（例如main.o）::

    GCOV_PROFILE_main.o := y

- 单独统计某一个路径::

    GCOV_PROFILE := y

如果要在整个内核的覆盖率统计（开启CONFIG_GCOV_PROFILE_ALL）中单独排除
某一个文件或者路径，可以使用如下的方法::

    GCOV_PROFILE_main.o := n

和::

    GCOV_PROFILE := n

此机制仅支持链接到内核镜像或编译为内核模块的文件。

相关文件
--------

gcov功能需要在debugfs中创建如下文件:

``/sys/kernel/debug/gcov``
    gcov相关功能的根路径

``/sys/kernel/debug/gcov/reset``
    全局复位文件:向该文件写入数据后会将所有的gcov统计数据清0

``/sys/kernel/debug/gcov/path/to/compile/dir/file.gcda``
    gcov工具可以识别的覆盖率统计数据文件，向该文件写入数据后
	  会将本文件的gcov统计数据清0

``/sys/kernel/debug/gcov/path/to/compile/dir/file.gcno``
    gcov工具需要的软连接文件（指向编译时生成的信息统计文件），这个文件是
    在gcc编译时如果配置了选项 ``-ftest-coverage`` 时生成的。

针对模块的统计
--------------

内核中的模块会动态的加载和卸载，模块卸载时对应的数据会被清除掉。
gcov提供了一种机制，通过保留相关数据的副本来收集这部分卸载模块的覆盖率数据。
模块卸载后这些备份数据在debugfs中会继续存在。
一旦这个模块重新加载，模块关联的运行统计会被初始化成debugfs中备份的数据。

可以通过对内核参数gcov_persist的修改来停用gcov对模块的备份机制::

        gcov_persist = 0

在运行时，用户还可以通过写入模块的数据文件或者写入gcov复位文件来丢弃已卸
载模块的数据。

编译机和测试机分离
------------------

gcov的内核分析插桩支持内核的编译和运行是在同一台机器上，也可以编译和运
行是在不同的机器上。
如果内核编译和运行是不同的机器，那么需要额外的准备工作，这取决于gcov工具
是在哪里使用的:

.. _gcov-test_zh:

a) 若gcov运行在测试机上

    测试机上面gcov工具的版本必须要跟内核编译机器使用的gcc版本相兼容，
    同时下面的文件要从编译机拷贝到测试机上:

    从源代码中:
      - 所有的C文件和头文件

    从编译目录中:
      - 所有的C文件和头文件
      - 所有的.gcda文件和.gcno文件
      - 所有目录的链接

    特别需要注意，测试机器上面的目录结构跟编译机器上面的目录机构必须
    完全一致。
    如果文件是软链接，需要替换成真正的目录文件（这是由make的当前工作
    目录变量CURDIR引起的）。

.. _gcov-build_zh:

b) 若gcov运行在编译机上

    测试用例运行结束后，如下的文件需要从测试机中拷贝到编译机上:

    从sysfs中的gcov目录中:
      - 所有的.gcda文件
      - 所有的.gcno文件软链接

    这些文件可以拷贝到编译机的任意目录下，gcov使用-o选项指定拷贝的
    目录。

    比如一个是示例的目录结构如下::

      /tmp/linux:    内核源码目录
      /tmp/out:      内核编译文件路径（make O=指定）
      /tmp/coverage: 从测试机器上面拷贝的数据文件路径

      [user@build] cd /tmp/out
      [user@build] gcov -o /tmp/coverage/tmp/out/init main.c

关于编译器的注意事项
--------------------

GCC和LLVM gcov工具不一定兼容。
如果编译器是GCC，使用 gcov_ 来处理.gcno和.gcda文件，如果是Clang编译器，
则使用 llvm-cov_ 。

.. _gcov: https://gcc.gnu.org/onlinedocs/gcc/Gcov.html
.. _llvm-cov: https://llvm.org/docs/CommandGuide/llvm-cov.html

GCC和Clang gcov之间的版本差异由Kconfig处理的。
kconfig会根据编译工具链的检查自动选择合适的gcov格式。

问题定位
--------

可能出现的问题1
    编译到链接阶段报错终止

问题原因
    分析标志指定在了源文件但是没有链接到主内核，或者客制化了链接程序

解决方法
    通过在相应的Makefile中使用 ``GCOV_PROFILE := n``
    或者 ``GCOV_PROFILE_basename.o := n`` 来将链接报错的文件排除掉

可能出现的问题2
    从sysfs复制的文件显示为空或不完整

问题原因
    由于seq_file的工作方式，某些工具（例如cp或tar）可能无法正确地从
    sysfs复制文件。

解决方法
    使用 ``cat`` 读取 ``.gcda`` 文件，使用 ``cp -d`` 复制链接，或者使用附录B
    中所示的机制。

附录A：collect_on_build.sh
--------------------------

用于在编译机上收集覆盖率元文件的示例脚本
（见 :ref:`编译机和测试机分离 a. <gcov-test_zh>` ）

.. code-block:: sh

    #!/bin/bash

    KSRC=$1
    KOBJ=$2
    DEST=$3

    if [ -z "$KSRC" ] || [ -z "$KOBJ" ] || [ -z "$DEST" ]; then
      echo "Usage: $0 <ksrc directory> <kobj directory> <output.tar.gz>" >&2
      exit 1
    fi

    KSRC=$(cd $KSRC; printf "all:\n\t@echo \${CURDIR}\n" | make -f -)
    KOBJ=$(cd $KOBJ; printf "all:\n\t@echo \${CURDIR}\n" | make -f -)

    find $KSRC $KOBJ \( -name '*.gcno' -o -name '*.[ch]' -o -type l \) -a \
                     -perm /u+r,g+r | tar cfz $DEST -P -T -

    if [ $? -eq 0 ] ; then
      echo "$DEST successfully created, copy to test system and unpack with:"
      echo "  tar xfz $DEST -P"
    else
      echo "Could not create file $DEST"
    fi

附录B：collect_on_test.sh
-------------------------

用于在测试机上收集覆盖率数据文件的示例脚本
（见 :ref:`编译机和测试机分离 b. <gcov-build_zh>` ）

.. code-block:: sh

    #!/bin/bash -e

    DEST=$1
    GCDA=/sys/kernel/debug/gcov

    if [ -z "$DEST" ] ; then
      echo "Usage: $0 <output.tar.gz>" >&2
      exit 1
    fi

    TEMPDIR=$(mktemp -d)
    echo Collecting data..
    find $GCDA -type d -exec mkdir -p $TEMPDIR/\{\} \;
    find $GCDA -name '*.gcda' -exec sh -c 'cat < $0 > '$TEMPDIR'/$0' {} \;
    find $GCDA -name '*.gcno' -exec sh -c 'cp -d $0 '$TEMPDIR'/$0' {} \;
    tar czf $DEST -C $TEMPDIR sys
    rm -rf $TEMPDIR

    echo "$DEST successfully created, copy to build system and unpack with:"
    echo "  tar xfz $DEST"


code:
.. highlight:: none

.. include:: ../disclaimer-zh_CN.rst

:Original: Documentation/dev-tools/gdb-kernel-debugging.rst
:Translator: 高超 gao chao <gaochao49@huawei.com>

通过gdb调试内核和模块
=====================

Kgdb内核调试器、QEMU等虚拟机管理程序或基于JTAG的硬件接口，支持在运行时使用gdb
调试Linux内核及其模块。Gdb提供了一个强大的python脚本接口，内核也提供了一套
辅助脚本以简化典型的内核调试步骤。本文档为如何启用和使用这些脚本提供了一个简要的教程。
此教程基于QEMU/KVM虚拟机，但文中示例也适用于其他gdb stub。

环境配置要求
------------

- gdb 7.2+ (推荐版本: 7.4+) 且开启python支持 (通常发行版上都已支持)

设置
----

- 创建一个QEMU/KVM的linux虚拟机（详情请参考 www.linux-kvm.org 和 www.qemu.org ）。
  对于交叉开发，https://landley.net/aboriginal/bin 提供了一些镜像和工具链，
  可以帮助搭建交叉开发环境。

- 编译内核时开启CONFIG_GDB_SCRIPTS，关闭CONFIG_DEBUG_INFO_REDUCED。
  如果架构支持CONFIG_FRAME_POINTER，请保持开启。

- 在guest环境上安装该内核。如有必要，通过在内核command line中添加“nokaslr”来关闭KASLR。
  此外，QEMU允许通过-kernel、-append、-initrd这些命令行选项直接启动内核。
  但这通常仅在不依赖内核模块时才有效。有关此模式的更多详细信息，请参阅QEMU文档。
  在这种情况下，如果架构支持KASLR，应该在禁用CONFIG_RANDOMIZE_BASE的情况下构建内核。

- 启用QEMU/KVM的gdb stub，可以通过如下方式实现

    - 在VM启动时，通过在QEMU命令行中添加“-s”参数

  或

    - 在运行时通过从QEMU监视控制台发送“gdbserver”

- 切换到/path/to/linux-build(内核源码编译)目录

- 启动gdb：gdb vmlinux

  注意：某些发行版可能会将gdb脚本的自动加载限制在已知的安全目录中。
  如果gdb报告拒绝加载vmlinux-gdb.py（相关命令找不到），请将::

    add-auto-load-safe-path /path/to/linux-build

  添加到~/.gdbinit。更多详细信息，请参阅gdb帮助信息。

- 连接到已启动的guest环境::

    (gdb) target remote :1234

使用Linux提供的gdb脚本的示例
----------------------------

- 加载模块（以及主内核）符号::

    (gdb) lx-symbols
    loading vmlinux
    scanning for modules in /home/user/linux/build
    loading @0xffffffffa0020000: /home/user/linux/build/net/netfilter/xt_tcpudp.ko
    loading @0xffffffffa0016000: /home/user/linux/build/net/netfilter/xt_pkttype.ko
    loading @0xffffffffa0002000: /home/user/linux/build/net/netfilter/xt_limit.ko
    loading @0xffffffffa00ca000: /home/user/linux/build/net/packet/af_packet.ko
    loading @0xffffffffa003c000: /home/user/linux/build/fs/fuse/fuse.ko
    ...
    loading @0xffffffffa0000000: /home/user/linux/build/drivers/ata/ata_generic.ko

- 对一些尚未加载的模块中的函数函数设置断点，例如::

    (gdb) b btrfs_init_sysfs
    Function "btrfs_init_sysfs" not defined.
    Make breakpoint pending on future shared library load? (y or [n]) y
    Breakpoint 1 (btrfs_init_sysfs) pending.

- 继续执行::

    (gdb) c

- 加载模块并且能观察到正在加载的符号以及断点命中::

    loading @0xffffffffa0034000: /home/user/linux/build/lib/libcrc32c.ko
    loading @0xffffffffa0050000: /home/user/linux/build/lib/lzo/lzo_compress.ko
    loading @0xffffffffa006e000: /home/user/linux/build/lib/zlib_deflate/zlib_deflate.ko
    loading @0xffffffffa01b1000: /home/user/linux/build/fs/btrfs/btrfs.ko

    Breakpoint 1, btrfs_init_sysfs () at /home/user/linux/fs/btrfs/sysfs.c:36
    36              btrfs_kset = kset_create_and_add("btrfs", NULL, fs_kobj);

- 查看内核的日志缓冲区::

    (gdb) lx-dmesg
    [     0.000000] Initializing cgroup subsys cpuset
    [     0.000000] Initializing cgroup subsys cpu
    [     0.000000] Linux version 3.8.0-rc4-dbg+ (...
    [     0.000000] Command line: root=/dev/sda2 resume=/dev/sda1 vga=0x314
    [     0.000000] e820: BIOS-provided physical RAM map:
    [     0.000000] BIOS-e820: [mem 0x0000000000000000-0x000000000009fbff] usable
    [     0.000000] BIOS-e820: [mem 0x000000000009fc00-0x000000000009ffff] reserved
    ....

- 查看当前task struct结构体的字段（仅x86和arm64支持）::

    (gdb) p $lx_current().pid
    $1 = 4998
    (gdb) p $lx_current().comm
    $2 = "modprobe\000\000\000\000\000\000\000"

- 对当前或指定的CPU使用per-cpu函数::

    (gdb) p $lx_per_cpu("runqueues").nr_running
    $3 = 1
    (gdb) p $lx_per_cpu("runqueues", 2).nr_running
    $4 = 0

- 使用container_of查看更多hrtimers信息::

    (gdb) set $next = $lx_per_cpu("hrtimer_bases").clock_base[0].active.next
    (gdb) p *$container_of($next, "struct hrtimer", "node")
    $5 = {
      node = {
        node = {
          __rb_parent_color = 18446612133355256072,
          rb_right = 0x0 <irq_stack_union>,
          rb_left = 0x0 <irq_stack_union>
        },
        expires = {
          tv64 = 1835268000000
        }
      },
      _softexpires = {
        tv64 = 1835268000000
      },
      function = 0xffffffff81078232 <tick_sched_timer>,
      base = 0xffff88003fd0d6f0,
      state = 1,
      start_pid = 0,
      start_site = 0xffffffff81055c1f <hrtimer_start_range_ns+20>,
      start_comm = "swapper/2\000\000\000\000\000\000"
    }

命令和辅助调试功能列表
----------------------

命令和辅助调试功能可能会随着时间的推移而改进，此文显示的是初始版本的部分示例::

 (gdb) apropos lx
 function lx_current -- Return current task
 function lx_module -- Find module by name and return the module variable
 function lx_per_cpu -- Return per-cpu variable
 function lx_task_by_pid -- Find Linux task by PID and return the task_struct variable
 function lx_thread_info -- Calculate Linux thread_info from task variable
 lx-dmesg -- Print Linux kernel log buffer
 lx-lsmod -- List currently loaded modules
 lx-symbols -- (Re-)load symbols of Linux kernel and currently loaded modules

可以通过“help <command-name>”或“help function <function-name>”命令
获取指定命令或指定调试功能的更多详细信息。


code:
.. include:: ../disclaimer-zh_CN.rst

:Original: Documentation/dev-tools/index.rst
:Translator: 赵军奎 Bernard Zhao <bernard@vivo.com>

============
内核开发工具
============

本文档是有关内核开发工具文档的合集。
目前这些文档已经整理在一起，不需要再花费额外的精力。
欢迎任何补丁。

有关测试专用工具的简要概述，参见
Documentation/translations/zh_CN/dev-tools/testing-overview.rst

.. class:: toc-title

	   目录

.. toctree::
   :maxdepth: 2

   testing-overview
   sparse
   gcov
   kasan
   gdb-kernel-debugging

Todolist:

 - coccinelle
 - kcov
 - ubsan
 - kmemleak
 - kcsan
 - kfence
 - kgdb
 - kselftest
 - kunit/index


code:
.. SPDX-License-Identifier: GPL-2.0

.. include:: ../disclaimer-zh_CN.rst

:Original: Documentation/dev-tools/kasan.rst
:Translator: 万家兵 Wan Jiabing <wanjiabing@vivo.com>

内核地址消毒剂(KASAN)
=====================

概述
----

Kernel Address SANitizer(KASAN)是一种动态内存安全错误检测工具，主要功能是
检查内存越界访问和使用已释放内存的问题。

KASAN有三种模式:

1. 通用KASAN
2. 基于软件标签的KASAN
3. 基于硬件标签的KASAN

用CONFIG_KASAN_GENERIC启用的通用KASAN，是用于调试的模式，类似于用户空
间的ASan。这种模式在许多CPU架构上都被支持，但它有明显的性能和内存开销。

基于软件标签的KASAN或SW_TAGS KASAN，通过CONFIG_KASAN_SW_TAGS启用，
可以用于调试和自我测试，类似于用户空间HWASan。这种模式只支持arm64，但其
适度的内存开销允许在内存受限的设备上用真实的工作负载进行测试。

基于硬件标签的KASAN或HW_TAGS KASAN，用CONFIG_KASAN_HW_TAGS启用，被
用作现场内存错误检测器或作为安全缓解的模式。这种模式只在支持MTE（内存标签
扩展）的arm64 CPU上工作，但它的内存和性能开销很低，因此可以在生产中使用。

关于每种KASAN模式的内存和性能影响的细节，请参见相应的Kconfig选项的描述。

通用模式和基于软件标签的模式通常被称为软件模式。基于软件标签的模式和基于
硬件标签的模式被称为基于标签的模式。

支持
----

体系架构
~~~~~~~~

在x86_64、arm、arm64、powerpc、riscv、s390和xtensa上支持通用KASAN，
而基于标签的KASAN模式只在arm64上支持。

编译器
~~~~~~

软件KASAN模式使用编译时工具在每个内存访问之前插入有效性检查，因此需要一个
提供支持的编译器版本。基于硬件标签的模式依靠硬件来执行这些检查，但仍然需要
一个支持内存标签指令的编译器版本。

通用KASAN需要GCC 8.3.0版本或更高版本，或者内核支持的任何Clang版本。

基于软件标签的KASAN需要GCC 11+或者内核支持的任何Clang版本。

基于硬件标签的KASAN需要GCC 10+或Clang 12+。

内存类型
~~~~~~~~

通用KASAN支持在所有的slab、page_alloc、vmap、vmalloc、堆栈和全局内存
中查找错误。

基于软件标签的KASAN支持slab、page_alloc、vmalloc和堆栈内存。

基于硬件标签的KASAN支持slab、page_alloc和不可执行的vmalloc内存。

对于slab，两种软件KASAN模式都支持SLUB和SLAB分配器，而基于硬件标签的
KASAN只支持SLUB。

用法
----

要启用KASAN，请使用以下命令配置内核::

	  CONFIG_KASAN=y

同时在 ``CONFIG_KASAN_GENERIC`` (启用通用KASAN模式)， ``CONFIG_KASAN_SW_TAGS``
(启用基于硬件标签的KASAN模式)，和 ``CONFIG_KASAN_HW_TAGS`` (启用基于硬件标签
的KASAN模式)之间进行选择。

对于软件模式，还可以在 ``CONFIG_KASAN_OUTLINE`` 和 ``CONFIG_KASAN_INLINE``
之间进行选择。outline和inline是编译器插桩类型。前者产生较小的二进制文件，
而后者快2倍。

要将受影响的slab对象的alloc和free堆栈跟踪包含到报告中，请启用
``CONFIG_STACKTRACE`` 。要包括受影响物理页面的分配和释放堆栈跟踪的话，
请启用 ``CONFIG_PAGE_OWNER`` 并使用 ``page_owner=on`` 进行引导。

错误报告
~~~~~~~~

典型的KASAN报告如下所示::

    ==================================================================
    BUG: KASAN: slab-out-of-bounds in kmalloc_oob_right+0xa8/0xbc [test_kasan]
    Write of size 1 at addr ffff8801f44ec37b by task insmod/2760

    CPU: 1 PID: 2760 Comm: insmod Not tainted 4.19.0-rc3+ #698
    Hardware name: QEMU Standard PC (i440FX + PIIX, 1996), BIOS 1.10.2-1 04/01/2014
    Call Trace:
     dump_stack+0x94/0xd8
     print_address_description+0x73/0x280
     kasan_report+0x144/0x187
     __asan_report_store1_noabort+0x17/0x20
     kmalloc_oob_right+0xa8/0xbc [test_kasan]
     kmalloc_tests_init+0x16/0x700 [test_kasan]
     do_one_initcall+0xa5/0x3ae
     do_init_module+0x1b6/0x547
     load_module+0x75df/0x8070
     __do_sys_init_module+0x1c6/0x200
     __x64_sys_init_module+0x6e/0xb0
     do_syscall_64+0x9f/0x2c0
     entry_SYSCALL_64_after_hwframe+0x44/0xa9
    RIP: 0033:0x7f96443109da
    RSP: 002b:00007ffcf0b51b08 EFLAGS: 00000202 ORIG_RAX: 00000000000000af
    RAX: ffffffffffffffda RBX: 000055dc3ee521a0 RCX: 00007f96443109da
    RDX: 00007f96445cff88 RSI: 0000000000057a50 RDI: 00007f9644992000
    RBP: 000055dc3ee510b0 R08: 0000000000000003 R09: 0000000000000000
    R10: 00007f964430cd0a R11: 0000000000000202 R12: 00007f96445cff88
    R13: 000055dc3ee51090 R14: 0000000000000000 R15: 0000000000000000

    Allocated by task 2760:
     save_stack+0x43/0xd0
     kasan_kmalloc+0xa7/0xd0
     kmem_cache_alloc_trace+0xe1/0x1b0
     kmalloc_oob_right+0x56/0xbc [test_kasan]
     kmalloc_tests_init+0x16/0x700 [test_kasan]
     do_one_initcall+0xa5/0x3ae
     do_init_module+0x1b6/0x547
     load_module+0x75df/0x8070
     __do_sys_init_module+0x1c6/0x200
     __x64_sys_init_module+0x6e/0xb0
     do_syscall_64+0x9f/0x2c0
     entry_SYSCALL_64_after_hwframe+0x44/0xa9

    Freed by task 815:
     save_stack+0x43/0xd0
     __kasan_slab_free+0x135/0x190
     kasan_slab_free+0xe/0x10
     kfree+0x93/0x1a0
     umh_complete+0x6a/0xa0
     call_usermodehelper_exec_async+0x4c3/0x640
     ret_from_fork+0x35/0x40

    The buggy address belongs to the object at ffff8801f44ec300
     which belongs to the cache kmalloc-128 of size 128
    The buggy address is located 123 bytes inside of
     128-byte region [ffff8801f44ec300, ffff8801f44ec380)
    The buggy address belongs to the page:
    page:ffffea0007d13b00 count:1 mapcount:0 mapping:ffff8801f7001640 index:0x0
    flags: 0x200000000000100(slab)
    raw: 0200000000000100 ffffea0007d11dc0 0000001a0000001a ffff8801f7001640
    raw: 0000000000000000 0000000080150015 00000001ffffffff 0000000000000000
    page dumped because: kasan: bad access detected

    Memory state around the buggy address:
     ffff8801f44ec200: fc fc fc fc fc fc fc fc fb fb fb fb fb fb fb fb
     ffff8801f44ec280: fb fb fb fb fb fb fb fb fc fc fc fc fc fc fc fc
    >ffff8801f44ec300: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 03
                                                                    ^
     ffff8801f44ec380: fc fc fc fc fc fc fc fc fb fb fb fb fb fb fb fb
     ffff8801f44ec400: fb fb fb fb fb fb fb fb fc fc fc fc fc fc fc fc
    ==================================================================

报告标题总结了发生的错误类型以及导致该错误的访问类型。紧随其后的是错误访问的
堆栈跟踪、所访问内存分配位置的堆栈跟踪（对于访问了slab对象的情况）以及对象
被释放的位置的堆栈跟踪（对于访问已释放内存的问题报告）。接下来是对访问的
slab对象的描述以及关于访问的内存页的信息。

最后，报告展示了访问地址周围的内存状态。在内部，KASAN单独跟踪每个内存颗粒的
内存状态，根据KASAN模式分为8或16个对齐字节。报告的内存状态部分中的每个数字
都显示了围绕访问地址的其中一个内存颗粒的状态。

对于通用KASAN，每个内存颗粒的大小为8个字节。每个颗粒的状态被编码在一个影子字节
中。这8个字节可以是可访问的，部分访问的，已释放的或成为Redzone的一部分。KASAN
对每个影子字节使用以下编码:00表示对应内存区域的所有8个字节都可以访问；数字N
(1 <= N <= 7)表示前N个字节可访问，其他(8 - N)个字节不可访问；任何负值都表示
无法访问整个8字节。KASAN使用不同的负值来区分不同类型的不可访问内存，如redzones
或已释放的内存（参见 mm/kasan/kasan.h）。

在上面的报告中，箭头指向影子字节 ``03`` ，表示访问的地址是部分可访问的。

对于基于标签的KASAN模式，报告最后的部分显示了访问地址周围的内存标签
(参考 `实施细则`_ 章节)。

请注意，KASAN错误标题（如 ``slab-out-of-bounds`` 或 ``use-after-free`` ）
是尽量接近的:KASAN根据其拥有的有限信息打印出最可能的错误类型。错误的实际类型
可能会有所不同。

通用KASAN还报告两个辅助调用堆栈跟踪。这些堆栈跟踪指向代码中与对象交互但不直接
出现在错误访问堆栈跟踪中的位置。目前，这包括 call_rcu() 和排队的工作队列。

启动参数
~~~~~~~~

KASAN受通用 ``panic_on_warn`` 命令行参数的影响。启用该功能后，KASAN在打印错误
报告后会引起内核恐慌。

默认情况下，KASAN只为第一次无效内存访问打印错误报告。使用 ``kasan_multi_shot`` ，
KASAN会针对每个无效访问打印报告。这有效地禁用了KASAN报告的 ``panic_on_warn`` 。

另外，独立于 ``panic_on_warn`` , ``kasan.fault=`` 引导参数可以用来控制恐慌和报
告行为:

- ``kasan.fault=report`` 或 ``=panic`` 控制是只打印KASAN报告还是同时使内核恐慌
  (默认: ``report`` )。即使启用了 ``kasan_multi_shot`` ，也会发生内核恐慌。

基于硬件标签的KASAN模式（请参阅下面有关各种模式的部分）旨在在生产中用作安全缓解
措施。因此，它支持允许禁用KASAN或控制其功能的附加引导参数。

- ``kasan=off`` 或 ``=on`` 控制KASAN是否启用 (默认: ``on`` )。

- ``kasan.mode=sync`` 、 ``=async`` 或 ``=asymm`` 控制KASAN是否配置
  为同步或异步执行模式(默认:``sync`` )。
  同步模式：当标签检查错误发生时，立即检测到错误访问。
  异步模式：延迟错误访问检测。当标签检查错误发生时，信息存储在硬件中（在arm64的
  TFSR_EL1寄存器中）。内核会定期检查硬件，并且仅在这些检查期间报告标签错误。
  非对称模式：读取时同步检测不良访问，写入时异步检测。

- ``kasan.vmalloc=off`` 或 ``=on`` 禁用或启用vmalloc分配的标记（默认：``on`` ）。

- ``kasan.stacktrace=off`` 或 ``=on`` 禁用或启用alloc和free堆栈跟踪收集
  (默认: ``on`` )。

实施细则
--------

通用KASAN
~~~~~~~~~

软件KASAN模式使用影子内存来记录每个内存字节是否可以安全访问，并使用编译时工具
在每次内存访问之前插入影子内存检查。

通用KASAN将1/8的内核内存专用于其影子内存（16TB以覆盖x86_64上的128TB），并使用
具有比例和偏移量的直接映射将内存地址转换为其相应的影子地址。

这是将地址转换为其相应影子地址的函数::

    static inline void *kasan_mem_to_shadow(const void *addr)
    {
	return (void *)((unsigned long)addr >> KASAN_SHADOW_SCALE_SHIFT)
		+ KASAN_SHADOW_OFFSET;
    }

在这里 ``KASAN_SHADOW_SCALE_SHIFT = 3`` 。

编译时工具用于插入内存访问检查。编译器在每次访问大小为1、2、4、8或16的内存之前
插入函数调用( ``__asan_load*(addr)`` , ``__asan_store*(addr)``)。这些函数通过
检查相应的影子内存来检查内存访问是否有效。

使用inline插桩，编译器不进行函数调用，而是直接插入代码来检查影子内存。此选项
显著地增大了内核体积，但与outline插桩内核相比，它提供了x1.1-x2的性能提升。

通用KASAN是唯一一种通过隔离延迟重新使用已释放对象的模式
（参见 mm/kasan/quarantine.c 以了解实现）。

基于软件标签的KASAN模式
~~~~~~~~~~~~~~~~~~~~~~~

基于软件标签的KASAN使用软件内存标签方法来检查访问有效性。目前仅针对arm64架构实现。

基于软件标签的KASAN使用arm64 CPU的顶部字节忽略(TBI)特性在内核指针的顶部字节中
存储一个指针标签。它使用影子内存来存储与每个16字节内存单元相关的内存标签(因此，
它将内核内存的1/16专用于影子内存)。

在每次内存分配时，基于软件标签的KASAN都会生成一个随机标签，用这个标签标记分配
的内存，并将相同的标签嵌入到返回的指针中。

基于软件标签的KASAN使用编译时工具在每次内存访问之前插入检查。这些检查确保正在
访问的内存的标签等于用于访问该内存的指针的标签。如果标签不匹配，基于软件标签
的KASAN会打印错误报告。

基于软件标签的KASAN也有两种插桩模式（outline，发出回调来检查内存访问；inline，
执行内联的影子内存检查）。使用outline插桩模式，会从执行访问检查的函数打印错误
报告。使用inline插桩，编译器会发出 ``brk`` 指令，并使用专用的 ``brk`` 处理程序
来打印错误报告。

基于软件标签的KASAN使用0xFF作为匹配所有指针标签（不检查通过带有0xFF指针标签
的指针进行的访问）。值0xFE当前保留用于标记已释放的内存区域。

基于硬件标签的KASAN模式
~~~~~~~~~~~~~~~~~~~~~~~

基于硬件标签的KASAN在概念上类似于软件模式，但它是使用硬件内存标签作为支持而
不是编译器插桩和影子内存。

基于硬件标签的KASAN目前仅针对arm64架构实现，并且基于ARMv8.5指令集架构中引入
的arm64内存标记扩展(MTE)和最高字节忽略(TBI)。

特殊的arm64指令用于为每次内存分配指定内存标签。相同的标签被指定给指向这些分配
的指针。在每次内存访问时，硬件确保正在访问的内存的标签等于用于访问该内存的指针
的标签。如果标签不匹配，则会生成故障并打印报告。

基于硬件标签的KASAN使用0xFF作为匹配所有指针标签（不检查通过带有0xFF指针标签的
指针进行的访问）。值0xFE当前保留用于标记已释放的内存区域。

如果硬件不支持MTE（ARMv8.5之前），则不会启用基于硬件标签的KASAN。在这种情况下，
所有KASAN引导参数都将被忽略。

请注意，启用CONFIG_KASAN_HW_TAGS始终会导致启用内核中的TBI。即使提供了
``kasan.mode=off`` 或硬件不支持MTE（但支持TBI）。

基于硬件标签的KASAN只报告第一个发现的错误。之后，MTE标签检查将被禁用。

影子内存
--------

本节的内容只适用于软件KASAN模式。

内核将内存映射到地址空间的几个不同部分。内核虚拟地址的范围很大：没有足够的真实
内存来支持内核可以访问的每个地址的真实影子区域。因此，KASAN只为地址空间的某些
部分映射真实的影子。

默认行为
~~~~~~~~

默认情况下，体系结构仅将实际内存映射到用于线性映射的阴影区域（以及可能的其他
小区域）。对于所有其他区域 —— 例如vmalloc和vmemmap空间 —— 一个只读页面被映射
到阴影区域上。这个只读的影子页面声明所有内存访问都是允许的。

这给模块带来了一个问题：它们不存在于线性映射中，而是存在于专用的模块空间中。
通过连接模块分配器，KASAN临时映射真实的影子内存以覆盖它们。例如，这允许检测
对模块全局变量的无效访问。

这也造成了与 ``VMAP_STACK`` 的不兼容：如果堆栈位于vmalloc空间中，它将被分配
只读页面的影子内存，并且内核在尝试为堆栈变量设置影子数据时会出错。

CONFIG_KASAN_VMALLOC
~~~~~~~~~~~~~~~~~~~~

使用 ``CONFIG_KASAN_VMALLOC`` ，KASAN可以以更大的内存使用为代价覆盖vmalloc
空间。目前，这在arm64、x86、riscv、s390和powerpc上受支持。

这通过连接到vmalloc和vmap并动态分配真实的影子内存来支持映射。

vmalloc空间中的大多数映射都很小，需要不到一整页的阴影空间。因此，为每个映射
分配一个完整的影子页面将是一种浪费。此外，为了确保不同的映射使用不同的影子
页面，映射必须与 ``KASAN_GRANULE_SIZE * PAGE_SIZE`` 对齐。

相反，KASAN跨多个映射共享后备空间。当vmalloc空间中的映射使用影子区域的特定
页面时，它会分配一个后备页面。此页面稍后可以由其他vmalloc映射共享。

KASAN连接到vmap基础架构以懒清理未使用的影子内存。

为了避免交换映射的困难，KASAN预测覆盖vmalloc空间的阴影区域部分将不会被早期
的阴影页面覆盖，但是将不会被映射。这将需要更改特定于arch的代码。

这允许在x86上支持 ``VMAP_STACK`` ，并且可以简化对没有固定模块区域的架构的支持。

对于开发者
----------

忽略访问
~~~~~~~~

软件KASAN模式使用编译器插桩来插入有效性检查。此类检测可能与内核的某些部分
不兼容，因此需要禁用。

内核的其他部分可能会访问已分配对象的元数据。通常，KASAN会检测并报告此类访问，
但在某些情况下（例如，在内存分配器中），这些访问是有效的。

对于软件KASAN模式，要禁用特定文件或目录的检测，请将 ``KASAN_SANITIZE`` 添加
到相应的内核Makefile中:

- 对于单个文件(例如，main.o)::

    KASAN_SANITIZE_main.o := n

- 对于一个目录下的所有文件::

    KASAN_SANITIZE := n

对于软件KASAN模式，要在每个函数的基础上禁用检测，请使用KASAN特定的
``__no_sanitize_address`` 函数属性或通用的 ``noinstr`` 。

请注意，禁用编译器插桩（基于每个文件或每个函数）会使KASAN忽略在软件KASAN模式
的代码中直接发生的访问。当访问是间接发生的（通过调用检测函数）或使用没有编译器
插桩的基于硬件标签的模式时，它没有帮助。

对于软件KASAN模式，要在当前任务的一部分内核代码中禁用KASAN报告，请使用
``kasan_disable_current()``/``kasan_enable_current()`` 部分注释这部分代码。
这也会禁用通过函数调用发生的间接访问的报告。

对于基于标签的KASAN模式，要禁用访问检查，请使用 ``kasan_reset_tag()`` 或
``page_kasan_tag_reset()`` 。请注意，通过 ``page_kasan_tag_reset()``
临时禁用访问检查需要通过 ``page_kasan_tag`` / ``page_kasan_tag_set`` 保
存和恢复每页KASAN标签。

测试
~~~~

有一些KASAN测试可以验证KASAN是否正常工作并可以检测某些类型的内存损坏。
测试由两部分组成:

1. 与KUnit测试框架集成的测试。使用 ``CONFIG_KASAN_KUNIT_TEST`` 启用。
这些测试可以通过几种不同的方式自动运行和部分验证；请参阅下面的说明。

2. 与KUnit不兼容的测试。使用 ``CONFIG_KASAN_MODULE_TEST`` 启用并且只能作为模块
运行。这些测试只能通过加载内核模块并检查内核日志以获取KASAN报告来手动验证。

如果检测到错误，每个KUnit兼容的KASAN测试都会打印多个KASAN报告之一，然后测试打印
其编号和状态。

当测试通过::

        ok 28 - kmalloc_double_kzfree

当由于 ``kmalloc`` 失败而导致测试失败时::

        # kmalloc_large_oob_right: ASSERTION FAILED at lib/test_kasan.c:163
        Expected ptr is not null, but is
        not ok 4 - kmalloc_large_oob_right

当由于缺少KASAN报告而导致测试失败时::

        # kmalloc_double_kzfree: EXPECTATION FAILED at lib/test_kasan.c:974
        KASAN failure expected in "kfree_sensitive(ptr)", but none occurred
        not ok 44 - kmalloc_double_kzfree

最后打印所有KASAN测试的累积状态。成功::

        ok 1 - kasan

或者，如果其中一项测试失败::

        not ok 1 - kasan

有几种方法可以运行与KUnit兼容的KASAN测试。

1. 可加载模块

   启用 ``CONFIG_KUNIT`` 后，KASAN-KUnit测试可以构建为可加载模块，并通过使用
   ``insmod`` 或 ``modprobe`` 加载 ``test_kasan.ko`` 来运行。

2. 内置

   通过内置 ``CONFIG_KUNIT`` ，也可以内置KASAN-KUnit测试。在这种情况下，
   测试将在启动时作为后期初始化调用运行。

3. 使用kunit_tool

   通过内置 ``CONFIG_KUNIT`` 和 ``CONFIG_KASAN_KUNIT_TEST`` ，还可以使用
   ``kunit_tool`` 以更易读的方式查看KUnit测试结果。这不会打印通过测试
   的KASAN报告。有关 ``kunit_tool`` 更多最新信息，请参阅
   `KUnit文档 <https://www.kernel.org/doc/html/latest/dev-tools/kunit/index.html>`_ 。

.. _KUnit: https://www.kernel.org/doc/html/latest/dev-tools/kunit/index.html


code:
Copyright 2004 Linus Torvalds
Copyright 2004 Pavel Machek <pavel@ucw.cz>
Copyright 2006 Bob Copeland <me@bobcopeland.com>

.. include:: ../disclaimer-zh_CN.rst

:Original: Documentation/dev-tools/sparse.rst

:翻译:

 Li Yang <leoyang.li@nxp.com>

:校译:

 司延腾 Yanteng Si <siyanteng@loongson.cn>

.. _cn_sparse:

Sparse
======

Sparse是一个C程序的语义检查器；它可以用来发现内核代码的一些潜在问题。 关
于sparse的概述，请参见https://lwn.net/Articles/689907/；本文档包含
一些针对内核的sparse信息。
关于sparse的更多信息，主要是关于它的内部结构，可以在它的官方网页上找到：
https://sparse.docs.kernel.org。

使用 sparse 工具做类型检查
~~~~~~~~~~~~~~~~~~~~~~~~~~

"__bitwise" 是一种类型属性，所以你应该这样使用它::

        typedef int __bitwise pm_request_t;

        enum pm_request {
                PM_SUSPEND = (__force pm_request_t) 1,
                PM_RESUME = (__force pm_request_t) 2
        };

这样会使 PM_SUSPEND 和 PM_RESUME 成为位方式(bitwise)整数（使用"__force"
是因为 sparse 会抱怨改变位方式的类型转换，但是这里我们确实需要强制进行转
换）。而且因为所有枚举值都使用了相同的类型，这里的"enum pm_request"也将
会使用那个类型做为底层实现。

而且使用 gcc 编译的时候，所有的 __bitwise/__force 都会消失，最后在 gcc
看来它们只不过是普通的整数。

坦白来说，你并不需要使用枚举类型。上面那些实际都可以浓缩成一个特殊的"int
__bitwise"类型。

所以更简单的办法只要这样做::

	typedef int __bitwise pm_request_t;

	#define PM_SUSPEND ((__force pm_request_t) 1)
	#define PM_RESUME ((__force pm_request_t) 2)

现在你就有了严格的类型检查所需要的所有基础架构。

一个小提醒：常数整数"0"是特殊的。你可以直接把常数零当作位方式整数使用而
不用担心 sparse 会抱怨。这是因为"bitwise"（恰如其名）是用来确保不同位方
式类型不会被弄混（小尾模式，大尾模式，cpu尾模式，或者其他），对他们来说
常数"0"确实 **是** 特殊的。

使用sparse进行锁检查
--------------------

下面的宏对于 gcc 来说是未定义的，在 sparse 运行时定义，以使用sparse的“上下文”
跟踪功能，应用于锁定。 这些注释告诉 sparse 什么时候有锁，以及注释的函数的进入和
退出。

__must_hold - 指定的锁在函数进入和退出时被持有。

__acquires  - 指定的锁在函数退出时被持有，但在进入时不被持有。

__releases  - 指定的锁在函数进入时被持有，但在退出时不被持有。

如果函数在不持有锁的情况下进入和退出，在函数内部以平衡的方式获取和释放锁，则不
需要注释。
上面的三个注释是针对sparse否则会报告上下文不平衡的情况。

获取 sparse 工具
~~~~~~~~~~~~~~~~

你可以从 Sparse 的主页获取最新的发布版本：

	https://www.kernel.org/pub/software/devel/sparse/dist/

或者，你也可以使用 git 克隆最新的 sparse 开发版本：

	git://git.kernel.org/pub/scm/devel/sparse/sparse.git

一旦你下载了源码，只要以普通用户身份运行：

	make
	make install

如果是标准的用户，它将会被自动安装到你的~/bin目录下。

使用 sparse 工具
~~~~~~~~~~~~~~~~

用"make C=1"命令来编译内核，会对所有重新编译的 C 文件使用 sparse 工具。
或者使用"make C=2"命令，无论文件是否被重新编译都会对其使用 sparse 工具。
如果你已经编译了内核，用后一种方式可以很快地检查整个源码树。

make 的可选变量 CHECKFLAGS 可以用来向 sparse 工具传递参数。编译系统会自
动向 sparse 工具传递 -Wbitwise 参数。

注意sparse定义了__CHECKER__预处理器符号。


code:
.. SPDX-License-Identifier: GPL-2.0

.. include:: ../disclaimer-zh_CN.rst

:Original: Documentation/dev-tools/testing-overview.rst
:Translator: 胡皓文 Hu Haowen <src.res@email.cn>

============
内核测试指南
============

有许多不同的工具可以用于测试Linux内核，因此了解什么时候使用它们可能
很困难。本文档粗略概述了它们之间的区别，并阐释了它们是怎样糅合在一起
的。

编写和运行测试
==============

大多数内核测试都是用kselftest或KUnit框架之一编写的。它们都让运行测试
更加简化，并为编写新测试提供帮助。

如果你想验证内核的行为——尤其是内核的特定部分——那你就要使用kUnit或
kselftest。

KUnit和kselftest的区别
----------------------

.. note::
     由于本文段中部分术语尚无较好的对应中文释义，可能导致与原文含义
     存在些许差异，因此建议读者结合原文
     （Documentation/dev-tools/testing-overview.rst）辅助阅读。
     如对部分翻译有异议或有更好的翻译意见，欢迎联系译者进行修订。

KUnit（Documentation/dev-tools/kunit/index.rst）是用于“白箱”测
试的一个完整的内核内部系统：因为测试代码是内核的一部分，所以它能够访
问用户空间不能访问到的内部结构和功能。

因此，KUnit测试最好针对内核中较小的、自包含的部分，以便能够独立地测
试。“单元”测试的概念亦是如此。

比如，一个KUnit测试可能测试一个单独的内核功能（甚至通过一个函数测试
一个单一的代码路径，例如一个错误处理案例），而不是整个地测试一个特性。

这也使得KUnit测试构建和运行非常地快，从而能够作为开发流程的一部分被
频繁地运行。

有关更详细的介绍，请参阅KUnit测试代码风格指南
Documentation/dev-tools/kunit/style.rst

kselftest（Documentation/dev-tools/kselftest.rst），相对来说，大量用
于用户空间，并且通常测试用户空间的脚本或程序。

这使得编写复杂的测试，或者需要操作更多全局系统状态的测试更加容易（诸
如生成进程之类）。然而，从kselftest直接调用内核函数是不行的。这也就
意味着只有通过某种方式（如系统调用、驱动设备、文件系统等）导出到了用
户空间的内核功能才能使用kselftest来测试。为此，有些测试包含了一个伴
生的内核模块用于导出更多的信息和功能。不过，对于基本上或者完全在内核
中运行的测试，KUnit可能是更佳工具。

kselftest也因此非常适合于全部功能的测试，因为这些功能会将接口暴露到
用户空间，从而能够被测试，而不是展现实现细节。“system”测试和
“end-to-end”测试亦是如此。

比如，一个新的系统调用应该伴随有新的kselftest测试。

代码覆盖率工具
==============

支持两种不同代码之间的覆盖率测量工具。它们可以用来验证一项测试执行的
确切函数或代码行。这有助于决定内核被测试了多少，或用来查找合适的测试
中没有覆盖到的极端情况。

Documentation/translations/zh_CN/dev-tools/gcov.rst 是GCC的覆盖率测试
工具，能用于获取内核的全局或每个模块的覆盖率。与KCOV不同的是，这个工具
不记录每个任务的覆盖率。覆盖率数据可以通过debugfs读取，并通过常规的
gcov工具进行解释。

Documentation/dev-tools/kcov.rst 是能够构建在内核之中，用于在每个任务
的层面捕捉覆盖率的一个功能。因此，它对于模糊测试和关于代码执行期间信
息的其它情况非常有用，比如在一个单一系统调用里使用它就很有用。

动态分析工具
============

内核也支持许多动态分析工具，用以检测正在运行的内核中出现的多种类型的
问题。这些工具通常每个去寻找一类不同的缺陷，比如非法内存访问，数据竞
争等并发问题，或整型溢出等其他未定义行为。

如下所示：

* kmemleak检测可能的内存泄漏。参阅
  Documentation/dev-tools/kmemleak.rst
* KASAN检测非法内存访问，如数组越界和释放后重用（UAF）。参阅
  Documentation/dev-tools/kasan.rst
* UBSAN检测C标准中未定义的行为，如整型溢出。参阅
  Documentation/dev-tools/ubsan.rst
* KCSAN检测数据竞争。参阅 Documentation/dev-tools/kcsan.rst
* KFENCE是一个低开销的内存问题检测器，比KASAN更快且能被用于批量构建。
  参阅 Documentation/dev-tools/kfence.rst
* lockdep是一个锁定正确性检测器。参阅
  Documentation/locking/lockdep-design.rst
* 除此以外，在内核中还有一些其它的调试工具，大多数能在
  lib/Kconfig.debug 中找到。

这些工具倾向于对内核进行整体测试，并且不像kselftest和KUnit一样“传递”。
它们可以通过在启用这些工具时运行内核测试以与kselftest或KUnit结合起来：
之后你就能确保这些错误在测试过程中都不会发生了。

一些工具与KUnit和kselftest集成，并且在检测到问题时会自动打断测试。

静态分析工具
============

除了测试运行中的内核，我们还可以使用**静态分析**工具直接分析内核的源代
码（**在编译时**）。内核中常用的工具允许人们检查整个源代码树或其中的特
定文件。它们使得在开发过程中更容易发现和修复问题。

 Sparse可以通过执行类型检查、锁检查、值范围检查来帮助测试内核，此外还
 可以在检查代码时报告各种错误和警告。关于如何使用它的细节，请参阅
 Documentation/translations/zh_CN/dev-tools/sparse.rst。

 Smatch扩展了Sparse，并提供了对编程逻辑错误的额外检查，如开关语句中
 缺少断点，错误检查中未使用的返回值，忘记在错误路径的返回中设置错误代
 码等。Smatch也有针对更严重问题的测试，如整数溢出、空指针解除引用和内
 存泄漏。见项目页面http://smatch.sourceforge.net/。

 Coccinelle是我们可以使用的另一个静态分析器。Coccinelle经常被用来
 帮助源代码的重构和并行演化，但它也可以帮助避免常见代码模式中出现的某
 些错误。可用的测试类型包括API测试、内核迭代器的正确使用测试、自由操
 作的合理性检查、锁定行为的分析，以及已知的有助于保持内核使用一致性的
 进一步测试。详情请见Documentation/dev-tools/coccinelle.rst。

 不过要注意的是，静态分析工具存在**假阳性**的问题。在试图修复错误和警
 告之前，需要仔细评估它们。


code:
.. SPDX-License-Identifier: GPL-2.0
.. include:: ../disclaimer-zh_CN.rst

:Original: Documentation/devicetree/changesets.rst

:翻译:

 司延腾 Yanteng Si <siyanteng@loongson.cn>

:校译:

============
设备树变更集
============

设备树变更集是一种方法，它允许人们以这样一种方式在实时树中使用变化，即要么使用全部的
变化，要么不使用。如果在使用变更集的过程中发生错误，那么树将被回滚到之前的状态。一个
变更集也可以在使用后被删除。

当一个变更集被使用时，所有的改变在发出OF_RECONFIG通知器之前被一次性使用到树上。这是
为了让接收者在收到通知时看到一个完整的、一致的树的状态。

一个变化集的顺序如下。

1. of_changeset_init() - 初始化一个变更集。

2. 一些DT树变化的调用，of_changeset_attach_node(), of_changeset_detach_node(),
   of_changeset_add_property(), of_changeset_remove_property,
   of_changeset_update_property()来准备一组变更。此时不会对活动树做任何变更。所有
   的变更操作都记录在of_changeset的 `entries` 列表中。

3. of_changeset_apply() - 将变更使用到树上。要么整个变更集被使用，要么如果有错误，
   树会被恢复到之前的状态。核心通过锁确保正确的顺序。如果需要的话，可以使用一个解锁的
   __of_changeset_apply版本。

如果一个成功使用的变更集需要被删除，可以用of_changeset_revert()来完成。


code:
.. SPDX-License-Identifier: GPL-2.0
.. include:: ../disclaimer-zh_CN.rst

:Original: Documentation/devicetree/dynamic-resolution-notes.rst

:翻译:

 司延腾 Yanteng Si <siyanteng@loongson.cn>

:校译:

========================
Devicetree动态解析器说明
========================

本文描述了内核内DeviceTree解析器的实现，它位于drivers/of/resolver.c中。

解析器如何工作？
----------------

解析器被赋予一个任意的树作为输入，该树用适当的dtc选项编译，并有一个/plugin/标签。这就产
生了适当的__fixups__和__local_fixups__节点。

解析器依次通过以下步骤工作:

1. 从实时树中获取最大的设备树phandle值 + 1.
2. 调整树的所有本地 phandles，以解决这个量。
3. 使用 __local__fixups__ 节点信息以相同的量调整所有本地引用。
4. 对于__fixups__节点中的每个属性，找到它在实时树中引用的节点。这是用来标记该节点的标签。
5. 检索fixup的目标的phandle。
6. 对于属性中的每个fixup，找到节点:属性:偏移的位置，并用phandle值替换它。


code:
.. SPDX-License-Identifier: GPL-2.0
.. include:: ../disclaimer-zh_CN.rst

:Original: Documentation/devicetree/index.rst

:翻译:

 司延腾 Yanteng Si <siyanteng@loongson.cn>

:校译:

=============================
Open Firmware 和 Devicetree
=============================

该文档是整个设备树文档的总目录，标题中多是业内默认的术语，初见就翻译成中文，
晦涩难懂，因此尽量保留，后面翻译其子文档时，可能会根据语境，灵活地翻译为中文。

内核Devicetree的使用
=======================
.. toctree::
   :maxdepth: 1

   usage-model
   of_unittest
   kernel-api

Devicetree Overlays
===================
.. toctree::
   :maxdepth: 1

   changesets
   dynamic-resolution-notes
   overlay-notes

Devicetree Bindings
===================
.. toctree::
   :maxdepth: 1

Todolist:

*   bindings/index


code:
.. SPDX-License-Identifier: GPL-2.0
.. include:: ../disclaimer-zh_CN.rst

:Original: Documentation/devicetree/kernel-api.rst

:翻译:

 司延腾 Yanteng Si <siyanteng@loongson.cn>

:校译:

=================
内核中的设备树API
=================

核心函数
--------

该API在以下内核代码中:

drivers/of/base.c

include/linux/of.h

drivers/of/property.c

include/linux/of_graph.h

drivers/of/address.c

drivers/of/irq.c

drivers/of/fdt.c

驱动模型函数
------------

该API在以下内核代码中:

include/linux/of_device.h

drivers/of/device.c

include/linux/of_platform.h

drivers/of/platform.c

覆盖和动态DT函数
----------------

该API在以下内核代码中:

drivers/of/resolver.c

drivers/of/dynamic.c

drivers/of/overlay.c


code:
.. SPDX-License-Identifier: GPL-2.0
.. include:: ../disclaimer-zh_CN.rst

:Original: Documentation/devicetree/of_unittest.rst

:翻译:

 司延腾 Yanteng Si <siyanteng@loongson.cn>

:校译:

=================================
Open Firmware Devicetree 单元测试
=================================

作者: Gaurav Minocha <gaurav.minocha.os@gmail.com>

1. 概述
=======

本文档解释了执行 OF 单元测试所需的测试数据是如何动态地附加到实时树上的，与机器的架构无关。

建议在继续读下去之前，先阅读以下文件。

(1) Documentation/devicetree/usage-model.rst
(2) http://www.devicetree.org/Device_Tree_Usage

OF Selftest被设计用来测试提供给设备驱动开发者的接口（include/linux/of.h），以从未扁平
化的设备树数据结构中获取设备信息等。这个接口被大多数设备驱动在各种使用情况下使用。

2. 测试数据
===========

设备树源文件（drivers/of/unittest-data/testcases.dts）包含执行drivers/of/unittest.c
中自动化单元测试所需的测试数据。目前，以下设备树源包含文件（.dtsi）被包含在testcases.dt中::

    drivers/of/unittest-data/tests-interrupts.dtsi
    drivers/of/unittest-data/tests-platform.dtsi
    drivers/of/unittest-data/tests-phandle.dtsi
    drivers/of/unittest-data/tests-match.dtsi

当内核在启用OF_SELFTEST的情况下被构建时，那么下面的make规则::

    $(obj)/%.dtb: $(src)/%.dts FORCE
	    $(call if_changed_dep, dtc)

用于将DT源文件（testcases.dts）编译成二进制blob（testcases.dtb），也被称为扁平化的DT。

之后，使用以下规则将上述二进制blob包装成一个汇编文件（testcases.dtb.S）::

    $(obj)/%.dtb.S: $(obj)/%.dtb
	    $(call cmd, dt_S_dtb)

汇编文件被编译成一个对象文件（testcases.dtb.o），并被链接到内核镜像中。

2.1. 添加测试数据
-----------------

未扁平化的设备树结构体:

未扁平化的设备树由连接的设备节点组成，其树状结构形式如下所述::

    // following struct members are used to construct the tree
    struct device_node {
	...
	struct  device_node *parent;
	struct  device_node *child;
	struct  device_node *sibling;
	...
    };

图1描述了一个机器的未扁平化设备树的通用结构，只考虑了子节点和同级指针。存在另一个指针，
``*parent`` ，用于反向遍历该树。因此，在一个特定的层次上，子节点和所有的兄弟姐妹节点将
有一个指向共同节点的父指针（例如，child1、sibling2、sibling3、sibling4的父指针指向
根节点）::

    root ('/')
    |
    child1 -> sibling2 -> sibling3 -> sibling4 -> null
    |         |           |           |
    |         |           |          null
    |         |           |
    |         |        child31 -> sibling32 -> null
    |         |           |          |
    |         |          null       null
    |         |
    |      child21 -> sibling22 -> sibling23 -> null
    |         |          |            |
    |        null       null         null
    |
    child11 -> sibling12 -> sibling13 -> sibling14 -> null
    |           |           |            |
    |           |           |           null
    |           |           |
    null        null       child131 -> null
			    |
			    null

Figure 1: 未扁平化的设备树的通用结构

在执行OF单元测试之前，需要将测试数据附加到机器的设备树上（如果存在）。因此，当调用
selftest_data_add()时，首先会读取通过以下内核符号链接到内核镜像中的扁平化设备树
数据::

    __dtb_testcases_begin - address marking the start of test data blob
    __dtb_testcases_end   - address marking the end of test data blob

其次，它调用of_fdt_unflatten_tree()来解除扁平化的blob。最后，如果机器的设备树
（即实时树）是存在的，那么它将未扁平化的测试数据树附加到实时树上，否则它将自己作为
实时设备树附加。

attach_node_and_children()使用of_attach_node()将节点附加到实时树上，如下所
述。为了解释这一点，图2中描述的测试数据树被附加到图1中描述的实时树上::

    root ('/')
	|
    testcase-data
	|
    test-child0 -> test-sibling1 -> test-sibling2 -> test-sibling3 -> null
	|               |                |                |
    test-child01      null             null             null

Figure 2: 将测试数据树附在实时树上的例子。

根据上面的方案，实时树已经存在，所以不需要附加根('/')节点。所有其他节点都是通过在
每个节点上调用of_attach_node()来附加的。

在函数of_attach_node()中，新的节点被附在实时树中给定的父节点的子节点上。但是，如
果父节点已经有了一个孩子，那么新节点就会取代当前的孩子，并将其变成其兄弟姐妹。因此，
当测试案例的数据节点被连接到上面的实时树（图1）时，最终的结构如图3所示::

    root ('/')
    |
    testcase-data -> child1 -> sibling2 -> sibling3 -> sibling4 -> null
    |               |          |           |           |
    (...)             |          |           |          null
		    |          |         child31 -> sibling32 -> null
		    |          |           |           |
		    |          |          null        null
		    |          |
		    |        child21 -> sibling22 -> sibling23 -> null
		    |          |           |            |
		    |         null        null         null
		    |
		    child11 -> sibling12 -> sibling13 -> sibling14 -> null
		    |          |            |            |
		    null       null          |           null
					    |
					    child131 -> null
					    |
					    null
    -----------------------------------------------------------------------

    root ('/')
    |
    testcase-data -> child1 -> sibling2 -> sibling3 -> sibling4 -> null
    |               |          |           |           |
    |             (...)      (...)       (...)        null
    |
    test-sibling3 -> test-sibling2 -> test-sibling1 -> test-child0 -> null
    |                |                   |                |
    null             null                null         test-child01

Figure 3: 附加测试案例数据后的实时设备树结构。

聪明的读者会注意到，与先前的结构相比，test-child0节点成为最后一个兄弟姐妹（图2）。
在连接了第一个test-child0节点之后，又连接了test-sibling1节点，该节点推动子节点
（即test-child0）成为兄弟姐妹，并使自己成为子节点，如上所述。

如果发现一个重复的节点（即如果一个具有相同full_name属性的节点已经存在于实时树中），
那么该节点不会被附加，而是通过调用函数update_node_properties()将其属性更新到活
树的节点中。

2.2. 删除测试数据
-----------------

一旦测试用例执行完，selftest_data_remove被调用，以移除最初连接的设备节点（首先是
叶子节点被分离，然后向上移动父节点被移除，最后是整个树）。selftest_data_remove()
调用detach_node_and_children()，使用of_detach_node()将节点从实时设备树上分离。

为了分离一个节点，of_detach_node()要么将给定节点的父节点的子节点指针更新为其同级节
点，要么根据情况将前一个同级节点附在给定节点的同级节点上。就这样吧。 :)


code:
.. SPDX-License-Identifier: GPL-2.0
.. include:: ../disclaimer-zh_CN.rst

:Original: Documentation/devicetree/overlay-notes.rst

:翻译:

 司延腾 Yanteng Si <siyanteng@loongson.cn>

:校译:

==============
设备树覆盖说明
==============

本文档描述了drivers/of/overlay.c中的内核内设备树覆盖功能的实现，是
Documentation/devicetree/dynamic-resolution-notes.rst[1]的配套文档。

覆盖如何工作
------------

设备树覆盖的目的是修改内核的实时树，并使修改以反映变化的方式影响内核的状态。
由于内核主要处理的是设备，任何新的设备节点如果导致一个活动的设备，就应该创建它，
而如果设备节点被禁用或被全部删除，受影响的设备应该被取消注册。

让我们举个例子，我们有一个foo板，它的基本树形图如下::

    ---- foo.dts ---------------------------------------------------------------
	/* FOO平台 */
	/dts-v1/;
	/ {
		compatible = "corp,foo";

		/* 共享的资源 */
		res: res {
		};

		/* 芯片上的外围设备 */
		ocp: ocp {
			/* 总是被实例化的外围设备 */
			peripheral1 { ... };
		};
	};
    ---- foo.dts ---------------------------------------------------------------

覆盖bar.dts,
::

    ---- bar.dts - 按标签覆盖目标位置 ----------------------------
	/dts-v1/;
	/插件/;
	&ocp {
		/* bar外围 */
		bar {
			compatible = "corp,bar";
			... /* 各种属性和子节点 */
		};
	};
    ---- bar.dts ---------------------------------------------------------------

当加载（并按照[1]中描述的方式解决）时，应该产生foo+bar.dts::

    ---- foo+bar.dts -----------------------------------------------------------
	/* FOO平台 + bar外围 */
	/ {
		compatible = "corp,foo";

		/* 共享资源 */
		res: res {
		};

		/* 芯片上的外围设备 */
		ocp: ocp {
			/* 总是被实例化的外围设备 */
			peripheral1 { ... };

			/* bar外围 */
			bar {
				compatible = "corp,bar";
				... /* 各种属性和子节点 */
			};
		};
	};
    ---- foo+bar.dts -----------------------------------------------------------

作为覆盖的结果，已经创建了一个新的设备节点（bar），因此将注册一个bar平台设备，
如果加载了匹配的设备驱动程序，将按预期创建设备。

如果基础DT不是用-@选项编译的，那么“&ocp”标签将不能用于将覆盖节点解析到基础
DT中的适当位置。在这种情况下，可以提供目标路径。通过标签的目标位置的语法是比
较好的，因为不管标签在DT中出现在哪里，覆盖都可以被应用到任何包含标签的基础DT上。

上面的bar.dts例子被修改为使用目标路径语法，即为::

    ---- bar.dts - 通过明确的路径覆盖目标位置 --------------------
	/dts-v1/;
	/插件/;
	&{/ocp} {
		/* bar外围 */
		bar {
			compatible = "corp,bar";
			... /* 各种外围设备和子节点 */
		}
	};
    ---- bar.dts ---------------------------------------------------------------

内核中关于覆盖的API
-------------------

该API相当容易使用。

1) 调用of_overlay_fdt_apply()来创建和应用一个覆盖的变更集。返回值是一个
   错误或一个识别这个覆盖的cookie。

2) 调用of_overlay_remove()来删除和清理先前通过调用of_overlay_fdt_apply()
   而创建的覆盖变更集。不允许删除一个被另一个覆盖的覆盖变化集。

最后，如果你需要一次性删除所有的覆盖，只需调用of_overlay_remove_all()，
它将以正确的顺序删除每一个覆盖。

你可以选择注册在覆盖操作中被调用的通知器。详见
of_overlay_notifier_register/unregister和enum of_overlay_notify_action。

OF_OVERLAY_PRE_APPLY、OF_OVERLAY_POST_APPLY或OF_OVERLAY_PRE_REMOVE
的通知器回调可以存储指向覆盖层中的设备树节点或其内容的指针，但这些指针不能持
续到OF_OVERLAY_POST_REMOVE的通知器回调。在OF_OVERLAY_POST_REMOVE通
知器被调用后，包含覆盖层的内存将被kfree()ed。请注意，即使OF_OVERLAY_POST_REMOVE
的通知器返回错误，内存也会被kfree()ed。

drivers/of/dynamic.c中的变更集通知器是第二种类型的通知器，可以通过应用或移除
覆盖层来触发。这些通知器不允许在覆盖层或其内容中存储指向设备树节点的指针。当包含
覆盖层的内存因移除覆盖层而被释放时，覆盖层代码并不能防止这类指针仍然有效。

任何其他保留指向覆盖层节点或数据的指针的代码都被认为是一个错误，因为在移除覆盖层
后，该指针将指向已释放的内存。

覆盖层的用户必须特别注意系统上发生的整体操作，以确保其他内核代码不保留任何指向覆
盖层节点或数据的指针。任何无意中使用这种指针的例子是，如果一个驱动或子系统模块在
应用了覆盖后被加载，并且该驱动或子系统扫描了整个设备树或其大部分，包括覆盖节点。


code:
.. SPDX-License-Identifier: GPL-2.0
.. include:: ../disclaimer-zh_CN.rst

:Original: Documentation/devicetree/usage-model.rst

:翻译:

 司延腾 Yanteng Si <siyanteng@loongson.cn>

:校译:

===================
Linux 和 Devicetree
===================

Linux对设备树数据的使用模型

:作者: Grant Likely <grant.likely@secretlab.ca>

这篇文章描述了Linux如何使用设备树。关于设备树数据格式的概述可以在
devicetree.org\ [1]_ 的设备树使用页面上找到。

.. [1] https://www.devicetree.org/specifications/

"Open Firmware Device Tree"，或简称为Devicetree（DT），是一种用于描述硬
件的数据结构和语言。更确切地说，它是一种操作系统可读的硬件描述，这样操作系统就不
需要对机器的细节进行硬编码。

从结构上看，DT是一棵树，或者说是带有命名节点的无环图，节点可以有任意数量的命名
属性来封装任意的数据。还存在一种机制，可以在自然的树状结构之外创建从一个节点到
另一个节点的任意链接。

从概念上讲，一套通用的使用惯例，称为 "bindings"（后文译为绑定），被定义为数据
应该如何出现在树中，以描述典型的硬件特性，包括数据总线、中断线、GPIO连接和外围
设备。

尽可能使用现有的绑定来描述硬件，以最大限度地利用现有的支持代码，但由于属性和节
点名称是简单的文本字符串，通过定义新的节点和属性来扩展现有的绑定或创建新的绑定
很容易。然而，要警惕的是，在创建一个新的绑定之前，最好先对已经存在的东西做一些
功课。目前有两种不同的、不兼容的i2c总线的绑定，这是因为在创建新的绑定时没有事先
调查i2c设备在现有系统中是如何被枚举的。

1. 历史
-------
DT最初是由Open Firmware创建的，作为将数据从Open Firmware传递给客户程序
（如传递给操作系统）的通信方法的一部分。操作系统使用设备树在运行时探测硬件的拓
扑结构，从而在没有硬编码信息的情况下支持大多数可用的硬件（假设所有设备的驱动程
序都可用）。

由于Open Firmware通常在PowerPC和SPARC平台上使用，长期以来，对这些架构的
Linux支持一直使用设备树。

2005年，当PowerPC Linux开始大规模清理并合并32位和64位支持时，决定在所有
Powerpc平台上要求DT支持，无论它们是否使用Open Firmware。为了做到这一点，
我们创建了一个叫做扁平化设备树（FDT）的DT表示法，它可以作为一个二进制的blob
传递给内核，而不需要真正的Open Firmware实现。U-Boot、kexec和其他引导程序
被修改，以支持传递设备树二进制（dtb）和在引导时修改dtb。DT也被添加到PowerPC
引导包装器（arch/powerpc/boot/\*）中，这样dtb就可以被包裹在内核镜像中，以
支持引导现有的非DT察觉的固件。

一段时间后，FDT基础架构被普及到了所有的架构中。在写这篇文章的时候，6个主线架
构（arm、microblaze、mips、powerpc、sparc和x86）和1个非主线架构（ios）
有某种程度的DT支持。

1. 数据模型
-----------
如果你还没有读过设备树用法\ [1]_页，那么现在就去读吧。没关系，我等着....

2.1 高层次视角
--------------
最重要的是要明白，DT只是一个描述硬件的数据结构。它没有什么神奇之处，也不会神
奇地让所有的硬件配置问题消失。它所做的是提供一种语言，将硬件配置与Linux内核
（或任何其他操作系统）中的板卡和设备驱动支持解耦。使用它可以使板卡和设备支持
变成数据驱动；根据传递到内核的数据做出设置决定，而不是根据每台机器的硬编码选
择。

理想情况下，数据驱动的平台设置应该导致更少的代码重复，并使其更容易用一个内核
镜像支持各种硬件。

Linux使用DT数据有三个主要目的:

1) 平台识别。
2) 运行时配置，以及
3) 设备数量。

2.2 平台识别
------------
首先，内核将使用DT中的数据来识别特定的机器。在一个理想的世界里，具体的平台对
内核来说并不重要，因为所有的平台细节都会被设备树以一致和可靠的方式完美描述。
但是，硬件并不完美，所以内核必须在早期启动时识别机器，以便有机会运行特定于机
器的修复程序。

在大多数情况下，机器的身份是不相关的，而内核将根据机器的核心CPU或SoC来选择
设置代码。例如，在ARM上，arch/arm/kernel/setup.c中的setup_arch()将调
用arch/arm/kernel/devtree.c中的setup_machine_fdt()，它通过
machine_desc表搜索并选择与设备树数据最匹配的machine_desc。它通过查看根
设备树节点中的'compatible'属性，并将其与struct machine_desc中的
dt_compat列表（如果你好奇，该列表定义在arch/arm/include/asm/mach/arch.h
中）进行比较，从而确定最佳匹配。

“compatible” 属性包含一个排序的字符串列表，以机器的确切名称开始，后面是
一个可选的与之兼容的板子列表，从最兼容到最不兼容排序。例如，TI BeagleBoard
和它的后继者BeagleBoard xM板的根兼容属性可能看起来分别为::

	compatible = "ti,omap3-beagleboard", "ti,omap3450", "ti,omap3";
	compatible = "ti,omap3-beagleboard-xm", "ti,omap3450", "ti,omap3";

其中 "ti,map3-beagleboard-xm "指定了确切的型号，它还声称它与OMAP 3450 SoC
以及一般的OMP3系列SoC兼容。你会注意到，该列表从最具体的（确切的板子）到最
不具体的（SoC系列）进行排序。

聪明的读者可能会指出，Beagle xM也可以声称与原Beagle板兼容。然而，我们应
该当心在板级上这样做，因为通常情况下，即使在同一产品系列中，每块板都有很高
的变化，而且当一块板声称与另一块板兼容时，很难确定到底是什么意思。对于高层
来说，最好是谨慎行事，不要声称一块板子与另一块板子兼容。值得注意的例外是，
当一块板子是另一块板子的载体时，例如CPU模块连接到一个载体板上。

关于兼容值还有一个注意事项。在兼容属性中使用的任何字符串都必须有文件说明它
表示什么。在Documentation/devicetree/bindings中添加兼容字符串的文档。

同样在ARM上，对于每个machine_desc，内核会查看是否有任何dt_compat列表条
目出现在兼容属性中。如果有，那么该machine_desc就是驱动该机器的候选者。在搜索
了整个machine_descs表之后，setup_machine_fdt()根据每个machine_desc
在兼容属性中匹配的条目，返回 “最兼容” 的machine_desc。如果没有找到匹配
的machine_desc，那么它将返回NULL。

这个方案背后的原因是观察到，在大多数情况下，如果它们都使用相同的SoC或相同
系列的SoC，一个machine_desc可以支持大量的电路板。然而，不可避免地会有一些例
外情况，即特定的板子需要特殊的设置代码，这在一般情况下是没有用的。特殊情况
可以通过在通用设置代码中明确检查有问题的板子来处理，但如果超过几个情况下，
这样做很快就会变得很难看和/或无法维护。

相反，兼容列表允许通用machine_desc通过在dt_compat列表中指定“不太兼容”的值
来提供对广泛的通用板的支持。在上面的例子中，通用板支持可以声称与“ti,ompa3”
或“ti,ompa3450”兼容。如果在最初的beagleboard上发现了一个bug，需要在
早期启动时使用特殊的变通代码，那么可以添加一个新的machine_desc，实现变通，
并且只在“ti,omap3-beagleboard”上匹配。

PowerPC使用了一个稍微不同的方案，它从每个machine_desc中调用.probe()钩子，
并使用第一个返回TRUE的钩子。然而，这种方法没有考虑到兼容列表的优先级，对于
新的架构支持可能应该避免。

2.3 运行时配置
--------------
在大多数情况下，DT是将数据从固件传递给内核的唯一方法，所以也被用来传递运行
时和配置数据，如内核参数字符串和initrd镜像的位置。

这些数据大部分都包含在/chosen节点中，当启动Linux时，它看起来就像这样::

	chosen {
		bootargs = "console=ttyS0,115200 loglevel=8";
		initrd-start = <0xc8000000>;
		initrd-end = <0xc8200000>;
	};

bootargs属性包含内核参数，initrd-\*属性定义initrd blob的地址和大小。注
意initrd-end是initrd映像后的第一个地址，所以这与结构体资源的通常语义不一
致。选择的节点也可以选择包含任意数量的额外属性，用于平台特定的配置数据。

在早期启动过程中，架构设置代码通过不同的辅助回调函数多次调用
of_scan_flat_dt()来解析设备树数据，然后进行分页设置。of_scan_flat_dt()
代码扫描设备树，并使用辅助函数来提取早期启动期间所需的信息。通常情况下，
early_init_dt_scan_chosen()辅助函数用于解析所选节点，包括内核参数，
early_init_dt_scan_root()用于初始化DT地址空间模型，early_init_dt_scan_memory()
用于确定可用RAM的大小和位置。

在ARM上，函数setup_machine_fdt()负责在选择支持板子的正确machine_desc
后，对设备树进行早期扫描。

2.4 设备数量
------------
在电路板被识别后，在早期配置数据被解析后，内核初始化可以以正常方式进行。在
这个过程中的某个时刻，unflatten_device_tree()被调用以将数据转换成更有
效的运行时表示。这也是调用机器特定设置钩子的时候，比如ARM上的machine_desc
.init_early()、.init_irq()和.init_machine()钩子。本节的其余部分使用
了ARM实现的例子，但所有架构在使用DT时都会做几乎相同的事情。

从名称上可以猜到，.init_early()用于在启动过程早期需要执行的任何机器特定设
置，而.init_irq()则用于设置中断处理。使用DT并不会实质性地改变这两个函数的
行为。如果提供了DT，那么.init_early()和.init_irq()都能调用任何一个DT查
询函数（of_* in include/linux/of*.h），以获得关于平台的额外数据。

DT上下文中最有趣的钩子是.init_machine()，它主要负责将平台的数据填充到
Linux设备模型中。历史上，这在嵌入式平台上是通过在板卡support .c文件中定
义一组静态时钟结构、platform_devices和其他数据，并在.init_machine()中
大量注册来实现的。当使用DT时，就不用为每个平台的静态设备进行硬编码，可以通过
解析DT获得设备列表，并动态分配设备结构体。

最简单的情况是，.init_machine()只负责注册一个platform_devices。
platform_device是Linux使用的一个概念，用于不能被硬件检测到的内存或I/O映
射的设备，以及“复合”或 “虚拟”设备（后面会详细介绍）。虽然DT没有“平台设备”的
术语，但平台设备大致对应于树根的设备节点和简单内存映射总线节点的子节点。

现在是举例说明的好时机。下面是NVIDIA Tegra板的设备树的一部分::

  /{
	compatible = "nvidia,harmony", "nvidia,tegra20";
	#address-cells = <1>;
	#size-cells = <1>;
	interrupt-parent = <&intc>;

	chosen { };
	aliases { };

	memory {
		device_type = "memory";
		reg = <0x00000000 0x40000000>;
	};

	soc {
		compatible = "nvidia,tegra20-soc", "simple-bus";
		#address-cells = <1>;
		#size-cells = <1>;
		ranges;

		intc: interrupt-controller@50041000 {
			compatible = "nvidia,tegra20-gic";
			interrupt-controller;
			#interrupt-cells = <1>;
			reg = <0x50041000 0x1000>, < 0x50040100 0x0100 >;
		};

		serial@70006300 {
			compatible = "nvidia,tegra20-uart";
			reg = <0x70006300 0x100>;
			interrupts = <122>;
		};

		i2s1: i2s@70002800 {
			compatible = "nvidia,tegra20-i2s";
			reg = <0x70002800 0x100>;
			interrupts = <77>;
			codec = <&wm8903>;
		};

		i2c@7000c000 {
			compatible = "nvidia,tegra20-i2c";
			#address-cells = <1>;
			#size-cells = <0>;
			reg = <0x7000c000 0x100>;
			interrupts = <70>;

			wm8903: codec@1a {
				compatible = "wlf,wm8903";
				reg = <0x1a>;
				interrupts = <347>;
			};
		};
	};

	sound {
		compatible = "nvidia,harmony-sound";
		i2s-controller = <&i2s1>;
		i2s-codec = <&wm8903>;
	};
  };

在.init_machine()时，Tegra板支持代码将需要查看这个DT，并决定为哪些节点
创建platform_devices。然而，看一下这个树，并不能立即看出每个节点代表什么
类型的设备，甚至不能看出一个节点是否代表一个设备。/chosen、/aliases和
/memory节点是信息节点，并不描述设备（尽管可以说内存可以被认为是一个设备）。
/soc节点的子节点是内存映射的设备，但是codec@1a是一个i2c设备，而sound节
点代表的不是一个设备，而是其他设备是如何连接在一起以创建音频子系统的。我知
道每个设备是什么，因为我熟悉电路板的设计，但是内核怎么知道每个节点该怎么做？

诀窍在于，内核从树的根部开始，寻找具有“兼容”属性的节点。首先，一般认为任何
具有“兼容”属性的节点都代表某种设备；其次，可以认为树根的任何节点要么直接连
接到处理器总线上，要么是无法用其他方式描述的杂项系统设备。对于这些节点中的
每一个，Linux都会分配和注册一个platform_device，它又可能被绑定到一个
platform_driver。

为什么为这些节点使用platform_device是一个安全的假设？嗯，就Linux对设备
的建模方式而言，几乎所有的总线类型都假定其设备是总线控制器的孩子。例如，每
个i2c_client是i2c_master的一个子节点。每个spi_device都是SPI总线的一
个子节点。类似的还有USB、PCI、MDIO等。同样的层次结构也出现在DT中，I2C设
备节点只作为I2C总线节点的子节点出现。同理，SPI、MDIO、USB等等。唯一不需
要特定类型的父设备的设备是platform_devices（和amba_devices，但后面会
详细介绍），它们将愉快地运行在Linux/sys/devices树的底部。因此，如果一个
DT节点位于树的根部，那么它真的可能最好注册为platform_device。

Linux板支持代码调用of_platform_populate(NULL, NULL, NULL, NULL)来
启动树根的设备发现。参数都是NULL，因为当从树的根部开始时，不需要提供一个起
始节点（第一个NULL），一个父结构设备（最后一个NULL），而且我们没有使用匹配
表（尚未）。对于只需要注册设备的板子，除了of_platform_populate()的调用，
.init_machine()可以完全为空。

在Tegra的例子中，这说明了/soc和/sound节点，但是SoC节点的子节点呢？它们
不应该也被注册为平台设备吗？对于Linux DT支持，一般的行为是子设备在驱动
.probe()时被父设备驱动注册。因此，一个i2c总线设备驱动程序将为每个子节点
注册一个i2c_client，一个SPI总线驱动程序将注册其spi_device子节点，其他
总线类型也是如此。根据该模型，可以编写一个与SoC节点绑定的驱动程序，并简单
地为其每个子节点注册platform_device。板卡支持代码将分配和注册一个SoC设
备，一个（理论上的）SoC设备驱动程序可以绑定到SoC设备，并在其.probe()钩
中为/soc/interruptcontroller、/soc/serial、/soc/i2s和/soc/i2c注
册platform_devices。很简单，对吗？

实际上，事实证明，将一些platform_device的子设备注册为更多的platform_device
是一种常见的模式，设备树支持代码反映了这一点，并使上述例子更简单。
of_platform_populate()的第二个参数是一个of_device_id表，任何与该表
中的条目相匹配的节点也将获得其子节点的注册。在Tegra的例子中，代码可以是
这样的::

  static void __init harmony_init_machine(void)
  {
	/* ... */
	of_platform_populate(NULL, of_default_bus_match_table, NULL, NULL);
  }

“simple-bus”在Devicetree规范中被定义为一个属性，意味着一个简单的内存映射
的总线，所以of_platform_populate()代码可以被写成只是假设简单总线兼容的节
点将总是被遍历。然而，我们把它作为一个参数传入，以便电路板支持代码可以随时覆
盖默认行为。

[需要添加关于添加i2c/spi/etc子设备的讨论] 。

附录A：AMBA设备
---------------

ARM Primecell是连接到ARM AMBA总线的某种设备，它包括对硬件检测和电源管理
的一些支持。在Linux中，amba_device和amba_bus_type结构体被用来表示
Primecell设备。然而，棘手的一点是，AMBA总线上的所有设备并非都是Primecell，
而且对于Linux来说，典型的情况是amba_device和platform_device实例都是同
一总线段的同义词。

当使用DT时，这给of_platform_populate()带来了问题，因为它必须决定是否将
每个节点注册为platform_device或amba_device。不幸的是，这使设备创建模型
变得有点复杂，但解决方案原来并不是太具有侵略性。如果一个节点与“arm,amba-primecell”
兼容，那么of_platform_populate()将把它注册为amba_device而不是
platform_device。


code:
:orphan:

.. warning::
     此文件的目的是为让中文读者更容易阅读和理解，而不是作为一个分支。 因此，
     如果您对此文件有任何意见或更新，请先尝试更新原始英文文件。

.. note::
     如果您发现本文档与原始文件有任何不同或者有翻译问题，请联系该文件的译者，
     或者请求时奎亮的帮助：<alexs@kernel.org>。


code:
.. SPDX-License-Identifier: GPL-2.0

.. include:: ../disclaimer-zh_CN.rst

:Original: Documentation/doc-guide/contributing.rst

:译者: 吴想成 Wu XiangCheng <bobwxc@email.cn>

如何帮助改进内核文档
====================

在任何软件开发项目中，文档都是重要组成部分。好的文档有助于引入新的开发人员，
并帮助已有的开发人员更有效地工作。如果缺少高质量的文档，大量的时间就会浪费在
代码的逆向工程和犯本可避免的错误上。

不幸的是，内核的文档目前远远不能满足支持如此规模和重要性的项目的需要。

本指南适用于希望帮助改善这种状况的贡献者。内核文档的改进可以由开发者在不同的
技能层级上进行；这也是一条相对简单可以帮助您了解内核过程并在社区中找到一席之
地的路径。下面的大部分内容是文档维护人员列出的最迫切需要完成的任务。

文档待办事项列表
----------------

为了使我们的文档达到应有的水平，需要完成的任务数不胜数。此列表包含许多重要的
项目，但还远远不够详尽；如果您知道改进文档的其他方法，请不要羞于启齿。

消除警告（WARNING）
~~~~~~~~~~~~~~~~~~~

文档构建目前出现了数量惊人的警告。虱子多了不痒，债多了不愁；大伙儿忽略了它们，
他们永远不会注意到他们的工作增加了新的警告。因此，消除警告是文档待办事项列表
中优先级最高的任务之一。这项任务本身相当简单，但必须以正确的方式进行，才能取
得成功。

C代码编译器发出的警告常常会被视为误报，从而导致出现了旨在让编译器闭嘴的补丁。
文档构建中的警告几乎总是指向真正的问题；要消除这些警告，需要理解问题并从源头上
解决问题。因此，修复文档警告的补丁不应在标题中直接写“修复警告”；它们应该指明
真正修复的问题。

另一个重点是，文档警告常常由C代码里kernel-doc注释中的问题引起。虽然文档维护
人员对收到这些修复补丁的副本表示感谢，但是文档树实际上通常并不适合接受这些
补丁；它们应该被交给相关子系统的维护人员。

例如，在一次文档构建中，我几乎是随意选取了一对警告::

  ./drivers/devfreq/devfreq.c:1818: warning: bad line:
  	- Resource-managed devfreq_register_notifier()
  ./drivers/devfreq/devfreq.c:1854: warning: bad line:
  	- Resource-managed devfreq_unregister_notifier()

（作了断行以便于阅读）

简单看一下上面给出的源文件，会发现几个kernel-doc注释，如下所示::

  /**
   * devm_devfreq_register_notifier()
   	- Resource-managed devfreq_register_notifier()
   * @dev:	The devfreq user device. (parent of devfreq)
   * @devfreq:	The devfreq object.
   * @nb:	The notifier block to be unregistered.
   * @list:	DEVFREQ_TRANSITION_NOTIFIER.
   */

问题在于缺了一个“*”，这不符合构建系统对C注释块的格式要求。此问题自2016年注释
被添加以来一直存在——整整四年之久。修复它只需要添加丢失的星号。看一眼该文件的
历史记录以了解主题行的常规格式是什么样，再使用 ``scripts/get_maintainer.pl``
来搞清谁应当收到此补丁。生成的补丁如下所示::

  [PATCH] PM / devfreq: Fix two malformed kerneldoc comments

  Two kerneldoc comments in devfreq.c fail to adhere to the required format,
  resulting in these doc-build warnings:

    ./drivers/devfreq/devfreq.c:1818: warning: bad line:
  	  - Resource-managed devfreq_register_notifier()
    ./drivers/devfreq/devfreq.c:1854: warning: bad line:
  	  - Resource-managed devfreq_unregister_notifier()

  Add a couple of missing asterisks and make kerneldoc a little happier.

  Signed-off-by: Jonathan Corbet <corbet@lwn.net>
  ---
   drivers/devfreq/devfreq.c | 4 ++--
   1 file changed, 2 insertions(+), 2 deletions(-)

  diff --git a/drivers/devfreq/devfreq.c b/drivers/devfreq/devfreq.c
  index 57f6944d65a6..00c9b80b3d33 100644
  --- a/drivers/devfreq/devfreq.c
  +++ b/drivers/devfreq/devfreq.c
  @@ -1814,7 +1814,7 @@ static void devm_devfreq_notifier_release(struct device *dev, void *res)

   /**
    * devm_devfreq_register_notifier()
  -	- Resource-managed devfreq_register_notifier()
  + *	- Resource-managed devfreq_register_notifier()
    * @dev:	The devfreq user device. (parent of devfreq)
    * @devfreq:	The devfreq object.
    * @nb:		The notifier block to be unregistered.
  @@ -1850,7 +1850,7 @@ EXPORT_SYMBOL(devm_devfreq_register_notifier);

   /**
    * devm_devfreq_unregister_notifier()
  -	- Resource-managed devfreq_unregister_notifier()
  + *	- Resource-managed devfreq_unregister_notifier()
    * @dev:	The devfreq user device. (parent of devfreq)
    * @devfreq:	The devfreq object.
    * @nb:		The notifier block to be unregistered.
  --
  2.24.1

整个过程只花了几分钟。当然，我后来发现有人在另一个树中修复了它，这亮出了另一
个教训：在深入研究问题之前，一定要检查linux-next树，看看问题是否已经修复。

其他修复可能需要更长的时间，尤其是那些与缺少文档的结构体成员或函数参数相关的
修复。这种情况下，需要找出这些成员或参数的作用，并正确描述它们。总之，这种
任务有时会有点乏味，但它非常重要。如果我们真的可以从文档构建中消除警告，那么
我们就可以开始期望开发人员开始注意避免添加新的警告了。

“迷失的”kernel-doc注释
~~~~~~~~~~~~~~~~~~~~~~

开发者被鼓励去为他们的代码写kernel-doc注释，但是许多注释从未被引入文档构建。
这使得这些信息更难找到，例如使Sphinx无法生成指向该文档的链接。将 ``kernel-doc``
指令添加到文档中以引入这些注释可以帮助社区获得为编写注释所做工作的全部价值。

``scripts/find-unused-docs.sh`` 工具可以用来找到这些被忽略的评论。

请注意，将导出的函数和数据结构引入文档是最有价值的。许多子系统还具有供内部
使用的kernel-doc注释；除非这些注释放在专门针对相关子系统开发人员的文档中，
否则不应将其引入文档构建中。

修正错字
~~~~~~~~

修复文档中的排版或格式错误是一种快速学习如何创建和发送修补程序的方法，也是
一项有用的服务。我总是愿意接受这样的补丁。这也意味着，一旦你修复了一些这种
错误，请考虑转移到更高级的任务，留下一些拼写错误给下一个初学者解决。

请注意，有些并 **不是** 拼写错误，不应该被“修复”：

 - 内核文档中用美式和英式英语拼写皆可，没有必要互相倒换。

 - 在内核文档中，没必要讨论句点后面应该跟一个还是两个空格的问题。其他一些有
   合理分歧的地方，比如“牛津逗号”，在这也是跑题的。

与对任何项目的任何补丁一样，请考虑您的更改是否真的让事情变得更好。

“上古”文档
~~~~~~~~~~

一些内核文档是最新的、被维护的，并且非常有用，有些文件确并非如此。尘封、陈旧
和不准确的文档可能会误导读者，并对我们的整个文档产生怀疑。任何解决这些问题的
事情都是非常受欢迎的。

无论何时处理文档，请考虑它是否是最新的，是否需要更新，或者是否应该完全删除。
您可以注意以下几个警告标志：

 - 对2.x内核的引用
 - 指向SourceForge存储库
 - 历史记录除了拼写错误啥也没有持续几年
 - 讨论Git之前时代的工作流

当然，最好的办法是更新文档，添加所需的任何信息。这样的工作通常需要与熟悉相关
子系统的开发人员合作。当有人善意地询问开发人员，并听取他们的回答然后采取
行动时，开发人员通常更愿意与这些致力于改进文档的人员合作。

有些文档已经没希望了；例如，我们偶尔会发现引用了很久以前从内核中删除的代码的
文档。删除过时的文档会碰见令人惊讶的阻力，但我们无论如何都应该这样做。文档中
多余的粗枝大叶对任何人都没有帮助。

如果一个严重过时的文档中可能有一些有用的信息，而您又无法更新它，那么最好在
开头添加一个警告。建议使用以下文本::

  .. warning ::
  	This document is outdated and in need of attention.  Please use
  	this information with caution, and please consider sending patches
  	to update it.

这样的话，至少我们长期受苦的读者会得到文件可能会把他们引入歧途的警告。

文档一致性
~~~~~~~~~~

这里的老前辈们会记得上世纪90年代出现在书架上的Linux书籍，它们只是从网上不同
位置搜来的文档文件的集合。在此之后，（大部分）这些书都得到了改进，但是内核的
文档仍然主要是建立在这种模型上。它有数千个文件，几乎每个文件都是与其他文件相
独立编写的。我们没有一个连贯的内核文档；只有数千个独立的文档。

我们一直试图通过编篡一套“书籍”来改善这种情况，以为特定读者提供成套文档。这
包括：

 - Documentation/translations/zh_CN/admin-guide/index.rst
 - Documentation/core-api/index.rst
 - Documentation/driver-api/index.rst
 - Documentation/userspace-api/index.rst

以及文档本身这本“书”。

将文档移到适当的书中是一项重要的任务，需要继续进行。不过这项工作还是有一些
挑战性。移动文档会给处理这些文档的人带来短期的痛苦；他们对这些更改无甚热情
也是可以理解的。通常情况下，可以将一个文档移动一下；不过我们真的不想一直移动
它们。

即使所有文件都在正确的位置，我们也只是把一大堆文件变成一群小堆文件。试图将
所有这些文件组合成一个整体的工作尚未开始。如果你对如何在这方面取得进展有好的
想法，我们将很高兴了解。

样式表（Stylesheet）改进
~~~~~~~~~~~~~~~~~~~~~~~~

随着Sphinx的采用，我们得到了比以前更好的HTML输出。但它仍然需要很大的改进；
Donald Knuth和Edward Tufte可能是映像平平的。这需要调整我们的样式表，以创建
更具排版效果、可访问性和可读性的输出。

请注意：如果你承担这个任务，你将进入经典的两难领域。即使是相对明显的变化，
也会有很多意见和讨论。唉，这就是我们生活的世界的本质。

无LaTeX的PDF构建
~~~~~~~~~~~~~~~~

对于拥有大量时间和Python技能的人来说，这绝对是一项不平凡的任务。Sphinx工具链
相对较小且包含良好；很容易添加到开发系统中。但是构建PDF或EPUB输出需要安装
LaTeX，它绝对称不上小或包含良好的。消除Latex将是一件很好的事情。

最初是希望使用 `rst2pdf <https://rst2pdf.org/>`_ 工具来生成PDF，但结果发现
无法胜任这项任务。不过rst2pdf的开发工作最近似乎又有了起色，这是个充满希望的
迹象。如果有开发人员愿意与该项目合作，使rst2pdf可与内核文档构建一起工作，
大家会非常感激。

编写更多文档
~~~~~~~~~~~~

当然，内核中许多部分的文档严重不足。如果您有编写一个特定内核子系统文档的相应
知识并愿意这样做，请不要犹豫，编写并向内核贡献结果吧！数不清的内核开发人员和
用户会感谢你。


code:
.. include:: ../disclaimer-zh_CN.rst

:Original: Documentation/doc-guide/index.rst

:译者: 吴想成 Wu XiangCheng <bobwxc@email.cn>

.. _doc_guide_zh:

================
如何编写内核文档
================

.. toctree::
   :maxdepth: 1

   sphinx
   kernel-doc
   parse-headers
   contributing
   maintainer-profile

.. only::  subproject and html

   目录
   ====

   * :ref:`genindex`


code:
.. include:: ../disclaimer-zh_CN.rst

:Original: Documentation/doc-guide/kernel-doc.rst

:译者: 吴想成 Wu XiangCheng <bobwxc@email.cn>

编写kernel-doc注释
==================

Linux内核源文件可以包含kernel-doc格式的结构化文档注释，用以描述代码的函数、
类型和设计。将文档嵌入源文件更容易保持文档最新。

.. note:: 内核文档格式与javadoc、gtk-doc或Doxygen看似很相似，但由于历史原因，
          实际有着明显的不同。内核源包含成千上万个kernel-doc注释。请坚持遵循
          此处描述的风格。

.. note:: kernel-doc无法包含Rust代码：请参考 Documentation/rust/general-information.rst 。

从注释中提取kernel-doc结构，并从中生成适当的 `Sphinx C 域`_ 函数和带有锚点的
类型描述。这些注释将被过滤以生成特殊kernel-doc高亮和交叉引用。详见下文。

.. _Sphinx C 域: http://www.sphinx-doc.org/en/stable/domains.html

使用 ``EXPORT_SYMBOL`` 或 ``EXPORT_SYMBOL_GPL`` 导出到可加载模块的每个函数都
应该有一个kernel-doc注释。模块使用的头文件中的函数和数据结构也应该有
kernel-doc注释。

对于其他内核文件（未标记为 ``static`` ）中外部可见的函数，提供kernel-doc格式
的文档是一个很好的实践。我们也建议为私有（文件 ``static`` ）程序提供kernel-doc
格式的文档，以确保内核源代码布局的一致性。此建议优先级较低，由内核源文件的
维护者自行决定。

如何格式化kernel-doc注释
------------------------

kernel-doc注释用 ``/**`` 作为开始标记。 ``kernel-doc`` 工具将提取以这种方式
标记的注释。注释其余部分的格式类似于一个普通的多行注释，左侧有一列星号，以
``*/`` 行结束。

函数和类型的kernel-doc注释应该放在所描述的函数或类型之前，以便最大限度地提高
更改代码的人同时更改文档的可能性。概述kernel-doc注释可以放在最顶部的任何地方。

用详细模式和不生成实际输出来运行 ``kernel-doc`` 工具，可以验证文档注释的格式
是否正确。例如::

	scripts/kernel-doc -v -none drivers/foo/bar.c

当请求执行额外的gcc检查时，内核构建将验证文档格式::

	make W=n

函数文档
--------

函数和函数式宏的kernel-doc注释的一般格式是::

  /**
   * 函数名() - 函数简要说明.
   * @参数1: 描述第一个参数.
   * @参数2: 描述第二个参数.
   *        可以为参数提供一段
   *        多行描述.
   *
   * 更详细的描述，进一步讨论函数 函数名(), 这可能对使用或修改它的人有用.
   * 以空注释行开始, 内部可以包含空注释行.
   *
   * 详细描述可以有多个段落.
   *
   * Context: 描述函数是否可以休眠, 它需要、释放或期望持有什么锁.
   *          可以写多行.
   * Return: 描述函数返回值.
   *
   * 返回值描述也可以有多个段落,
   * 并且应该放在注释块的末尾.
   */

函数名后面的简短描述可以跨多行，并以参数描述、空注释行或注释块结尾结束。

函数参数
~~~~~~~~

每个函数参数都应该按照顺序描述，紧跟在函数简要说明之后。不要在函数描述和参数
之间，也不要在参数之间留空。

每个 ``@参数:`` 描述可以跨多行。

.. note::

   如果 ``@参数`` 描述有多行，则说明的续行应该从上一行的同一列开始::

      * @参数: 较长说明
      *        的续行

   或::

      * @参数:
      *		较长说明
      *         的续行

如果函数的参数数目可变，则需用kernel-doc格式对其进行描述::

      * @...: 描述

函数上下文
~~~~~~~~~~

可调用函数的上下文应该在 ``Context`` 节中描述。此节应该包括函数是休眠的还是
可以从中断上下文调用的，以及它需要什么锁、释放什么锁和期望它的调用者持有什么
锁。

例如::

  * Context: Any context.
  * Context: Any context. Takes and releases the RCU lock.
  * Context: Any context. Expects <lock> to be held by caller.
  * Context: Process context. May sleep if @gfp flags permit.
  * Context: Process context. Takes and releases <mutex>.
  * Context: Softirq or process context. Takes and releases <lock>, BH-safe.
  * Context: Interrupt context.

返回值
~~~~~~

如有返回值，应在 ``Return`` 节中描述。

.. note::

  #) 您提供的多行描述文本 *不会* 识别换行符，因此如果您想将某些文本预格式化，
     如::

	* Return:
	* 0 - OK
	* -EINVAL - invalid argument
	* -ENOMEM - out of memory

     它们在最终文档中变成一行::

	Return: 0 - OK -EINVAL - invalid argument -ENOMEM - out of memory

     因此，为了在需要的地方换行，您需要使用ReST列表，例如::

      * Return:
      * * 0		- OK to runtime suspend the device
      * * -EBUSY	- Device should not be runtime suspended

  #) 如果您提供的描述性文本中的行以某个后跟冒号的短语开头，则每一个这种短语
     都将被视为新的节标题，可能会产生意料不到的效果。

结构体、共用体、枚举类型文档
----------------------------

结构体（struct）、共用体（union）、枚举（enum）类型kernel-doc注释的一般格式为::

  /**
   * struct 结构体名 - 简要描述.
   * @成员1: 成员1描述.
   * @成员2: 成员2描述.
   *           可以为成员提供
   *           多行描述.
   *
   * 结构体的描述.
   */

可以用 ``union`` 或 ``enum`` 替换上面示例中的 ``struct`` ，以描述共用体或枚举。
``成员`` 用于表示枚举中的元素或共用体成员。

结构体名称后面的简要说明可以跨多行，并以成员说明、空白注释行或注释块结尾结束。

成员
~~~~

结构体、共用体和枚举的成员应以与函数参数相同的方式记录；它们后紧跟简短的描述，
并且为多行。

在结构体或共用体描述中，可以使用 ``private:`` 和 ``public:`` 注释标签。
``private:`` 域内的字段不会列在生成的文档中。

``private:`` 和 ``public:`` 标签必须紧跟在 ``/*`` 注释标记之后。可以选择是否
在 ``:`` 和 ``*/`` 结束标记之间包含注释。

例子::

  /**
   * struct 张三 - 简短描述
   * @a: 第一个成员
   * @b: 第二个成员
   * @d: 第三个成员
   *
   * 详细描述
   */
  struct 张三 {
      int a;
      int b;
  /* private: 仅内部使用 */
      int c;
  /* public: 下一个是公有的 */
      int d;
  };

嵌套的结构体/共用体
~~~~~~~~~~~~~~~~~~~

嵌套的结构体/共用体可像这样记录::

      /**
       * struct nested_foobar - a struct with nested unions and structs
       * @memb1: first member of anonymous union/anonymous struct
       * @memb2: second member of anonymous union/anonymous struct
       * @memb3: third member of anonymous union/anonymous struct
       * @memb4: fourth member of anonymous union/anonymous struct
       * @bar: non-anonymous union
       * @bar.st1: struct st1 inside @bar
       * @bar.st2: struct st2 inside @bar
       * @bar.st1.memb1: first member of struct st1 on union bar
       * @bar.st1.memb2: second member of struct st1 on union bar
       * @bar.st2.memb1: first member of struct st2 on union bar
       * @bar.st2.memb2: second member of struct st2 on union bar
       */
      struct nested_foobar {
        /* Anonymous union/struct*/
        union {
          struct {
            int memb1;
            int memb2;
          };
          struct {
            void *memb3;
            int memb4;
          };
        };
        union {
          struct {
            int memb1;
            int memb2;
          } st1;
          struct {
            void *memb1;
            int memb2;
          } st2;
        } bar;
      };

.. note::

   #) 在记录嵌套结构体或共用体时，如果结构体/共用体 ``张三`` 已命名，则其中
      的成员 ``李四`` 应记录为 ``@张三.李四:``

   #) 当嵌套结构体/共用体是匿名的时，其中的成员 ``李四`` 应记录为 ``@李四:``

行间注释文档
~~~~~~~~~~~~

结构成员也可在定义时以行间注释形式记录。有两种样式，一种是单行注释，其中开始
``/**`` 和结束 ``*/`` 位于同一行；另一种是多行注释，开头结尾各自位于一行，就
像所有其他核心文档注释一样::

  /**
   * struct 张三 - 简短描述.
   * @张三: 成员张三.
   */
  struct 张三 {
        int 张三;
        /**
         * @李四: 成员李四.
         */
        int 李四;
        /**
         * @王五: 成员王五.
         *
         * 此处，成员描述可以为好几段.
         */
        int 王五;
        union {
                /** @儿子: 单行描述. */
                int 儿子;
        };
        /** @赵六: 描述@张三里面的结构体@赵六 */
        struct {
                /**
                 * @赵六.女儿: 描述@张三.赵六里面的@女儿
                 */
                int 女儿;
        } 赵六;
  };

Typedef文档
-----------

Typedef的kernel-doc文档注释的一般格式为::

  /**
   * typedef 类型名称 - 简短描述.
   *
   * 类型描述.
   */

还可以记录带有函数原型的typedef::

  /**
   * typedef 类型名称 - 简短描述.
   * @参数1: 参数1的描述
   * @参数2: 参数2的描述
   *
   * 类型描述.
   *
   * Context: 锁（Locking）上下文.
   * Return: 返回值的意义.
   */
   typedef void (*类型名称)(struct v4l2_ctrl *参数1, void *参数2);

高亮与交叉引用
--------------

在kernel-doc注释的描述文本中可以识别以下特殊模式，并将其转换为正确的
reStructuredText标记和 `Sphinx C 域`_ 引用。

.. attention:: 以下内容 **仅** 在kernel-doc注释中识别， **不会** 在普通的
               reStructuredText文档中识别。

``funcname()``
  函数引用。

``@parameter``
  函数参数的名称（未交叉引用，仅格式化）。

``%CONST``
  常量的名称（未交叉引用，仅格式化）。

````literal````
  预格式化文本块。输出将使用等距字体。

  若你需要使用在kernel-doc脚本或reStructuredText中有特殊含义的字符，则此功能
  非常有用。

  若你需要在函数描述中使用类似于 ``%ph`` 的东西，这特别有用。

``$ENVVAR``
  环境变量名称（未交叉引用，仅格式化）。

``&struct name``
  结构体引用。

``&enum name``
  枚举引用。

``&typedef name``
  Typedef引用。

``&struct_name->member`` or ``&struct_name.member``
  结构体或共用体成员引用。交叉引用将链接到结构体或共用体定义，而不是直接到成员。

``&name``
  泛类型引用。请首选上面描述的完整引用方式。此法主要是为了可能未描述的注释。

从reStructuredText交叉引用
~~~~~~~~~~~~~~~~~~~~~~~~~~

无需额外的语法来从reStructuredText文档交叉引用kernel-do注释中定义的函数和类型。
只需以 ``()`` 结束函数名，并在类型之前写上 ``struct`` ， ``union`` ， ``enum``
或 ``typedef`` 。
例如::

  See foo().
  See struct foo.
  See union bar.
  See enum baz.
  See typedef meh.

若要在交叉引用链接中使用自定义文本，可以通过以下语法进行::

  See :c:func:`my custom link text for function foo <foo>`.
  See :c:type:`my custom link text for struct bar <bar>`.

有关更多详细信息，请参阅 `Sphinx C 域`_ 文档。

总述性文档注释
--------------

为了促进源代码和注释紧密联合，可以将kernel-doc文档块作为自由形式的注释，而
不是函数、结构、联合、枚举或typedef的绑定kernel-doc。例如，这可以用于解释
驱动程序或库代码的操作理论。

这是通过使用带有节标题的 ``DOC:`` 节关键字来实现的。

总述或高层级文档注释的一般格式为::

  /**
   * DOC: Theory of Operation
   *
   * The whizbang foobar is a dilly of a gizmo. It can do whatever you
   * want it to do, at any time. It reads your mind. Here's how it works.
   *
   * foo bar splat
   *
   * The only drawback to this gizmo is that is can sometimes damage
   * hardware, software, or its subject(s).
   */

``DOC:`` 后面的标题用作源文件中的标题，但也用作提取文档注释的标识符。因此，
文件中的标题必须是唯一的。

包含kernel-doc注释
==================

文档注释可以被包含在任何使用专用kernel-doc Sphinx指令扩展的reStructuredText
文档中。

kernel-doc指令的格式如下::

  .. kernel-doc:: source
     :option:

*source* 是相对于内核源代码树的源文件路径。
支持以下指令选项：

export: *[source-pattern ...]*
  包括 *source* 中使用 ``EXPORT_SYMBOL`` 或 ``EXPORT_SYMBOL_GPL`` 导出的所有
  函数的文档，无论是在 *source* 中还是在 *source-pattern* 指定的任何文件中。

  当kernel-doc注释被放置在头文件中，而 ``EXPORT_SYMBOL`` 和 ``EXPORT_SYMBOL_GPL``
  位于函数定义旁边时， *source-pattern* 非常有用。

  例子::

    .. kernel-doc:: lib/bitmap.c
       :export:

    .. kernel-doc:: include/net/mac80211.h
       :export: net/mac80211/*.c

internal: *[source-pattern ...]*
  包括 *source* 中所有在 *source* 或 *source-pattern* 的任何文件中都没有使用
  ``EXPORT_SYMBOL`` 或 ``EXPORT_SYMBOL_GPL`` 导出的函数和类型的文档。

  例子::

    .. kernel-doc:: drivers/gpu/drm/i915/intel_audio.c
       :internal:

identifiers: *[ function/type ...]*
  在 *source* 中包含每个 *function* 和 *type* 的文档。如果没有指定 *function* ，
  则 *source* 中所有函数和类型的文档都将包含在内。

  例子::

    .. kernel-doc:: lib/bitmap.c
       :identifiers: bitmap_parselist bitmap_parselist_user

    .. kernel-doc:: lib/idr.c
       :identifiers:

no-identifiers: *[ function/type ...]*
  排除 *source* 中所有 *function* 和 *type* 的文档。

  例子::

    .. kernel-doc:: lib/bitmap.c
       :no-identifiers: bitmap_parselist

functions: *[ function/type ...]*
  这是“identifiers”指令的别名，已弃用。

doc: *title*
  包含 *source* 中由 *title*  标题标识的 ``DOC:`` 文档段落。 *title* 中允许
  空格；不要在 *title* 上加引号。 *title*  仅用作段落的标识符，不包含在输出中。
  请确保在所附的reStructuredText文档中有适当的标题。

  例子::

    .. kernel-doc:: drivers/gpu/drm/i915/intel_audio.c
       :doc: High Definition Audio over HDMI and Display Port

如果没有选项，kernel-doc指令将包含源文件中的所有文档注释。

kernel-doc扩展包含在内核源代码树中，位于 ``Documentation/sphinx/kerneldoc.py`` 。
在内部，它使用 ``scripts/kernel-doc`` 脚本从源代码中提取文档注释。

.. _kernel_doc_zh:

如何使用kernel-doc生成手册（man）页
-----------------------------------

如果您只想使用kernel-doc生成手册页，可以从内核git树这样做::

  $ scripts/kernel-doc -man \
    $(git grep -l '/\*\*' -- :^Documentation :^tools) \
    | scripts/split-man.pl /tmp/man

一些旧版本的git不支持路径排除语法的某些变体。
以下命令之一可能适用于这些版本::

  $ scripts/kernel-doc -man \
    $(git grep -l '/\*\*' -- . ':!Documentation' ':!tools') \
    | scripts/split-man.pl /tmp/man

  $ scripts/kernel-doc -man \
    $(git grep -l '/\*\*' -- . ":(exclude)Documentation" ":(exclude)tools") \
    | scripts/split-man.pl /tmp/man


code:
.. SPDX-License-Identifier: GPL-2.0

.. include:: ../disclaimer-zh_CN.rst

:Original: Documentation/doc-guide/maintainer-profile.rst

:译者: 吴想成 Wu XiangCheng <bobwxc@email.cn>

文档子系统维护人员条目概述
==========================

文档“子系统”是内核文档和相关基础设施的中心协调点。它涵盖了 Documentation/ 下
的文件层级（Documentation/devicetree 除外）、scripts/ 下的各种实用程序，并且
在某些情况下的也包括 LICENSES/ 。

不过值得注意的是，这个子系统的边界比通常更加模糊。许多其他子系统维护人员需要
保持对 Documentation/ 某些部分的控制，以便于可以更自由地做更改。除此之外，
许多内核文档都以kernel-doc注释的形式出现在源代码中；这些注释通常（但不总是）
由相关的子系统维护人员维护。

文档子系统的邮件列表是<linux-doc@vger.kernel.org>。
补丁应尽量针对docs-next树。

提交检查单补遗
--------------

在进行文档更改时，您应当构建文档以测试，并确保没有引入新的错误或警告。生成
HTML文档并查看结果将有助于避免对文档渲染结果的不必要争执。

开发周期的关键节点
------------------

补丁可以随时发送，但在合并窗口期间，响应将比通常慢。文档树往往在合并窗口打开
之前关闭得比较晚，因为文档补丁导致回归的风险很小。

审阅节奏
--------

我（译注：指Jonathan Corbet <corbet@lwn.net>）是文档子系统的唯一维护者，我在
自己的时间里完成这项工作，所以对补丁的响应有时会很慢。当补丁被合并时（或当我
决定拒绝合并补丁时），我都会发送通知。如果您在发送补丁后一周内没有收到回复，
请不要犹豫，发送提醒就好。


code:
.. SPDX-License-Identifier: GPL-2.0

.. include:: ../disclaimer-zh_CN.rst

:Original: Documentation/doc-guide/parse-headers.rst

:译者: 吴想成 Wu XiangCheng <bobwxc@email.cn>

=====================
包含用户空间API头文件
=====================

有时，为了描述用户空间API并在代码和文档之间生成交叉引用，需要包含头文件和示例
C代码。为用户空间API文件添加交叉引用还有一个好处：如果在文档中找不到相应符号，
Sphinx将生成警告。这有助于保持用户空间API文档与内核更改同步。
:ref:`parse_headers.pl <parse_headers_zh>` 提供了生成此类交叉引用的一种方法。
在构建文档时，必须通过Makefile调用它。有关如何在内核树中使用它的示例，请参阅
``Documentation/userspace-api/media/Makefile`` 。

.. _parse_headers_zh:

parse_headers.pl
----------------

脚本名称
~~~~~~~~

parse_headers.pl——解析一个C文件，识别函数、结构体、枚举、定义并对Sphinx文档
创建交叉引用。

用法概要
~~~~~~~~

\ **parse_headers.pl**\  [<选项>] <C文件> <输出文件> [<例外文件>]

<选项> 可以是： --debug, --help 或 --usage 。

选项
~~~~

\ **--debug**\

 开启脚本详细模式，在调试时很有用。

\ **--usage**\

 打印简短的帮助信息并退出。

\ **--help**\

 打印更详细的帮助信息并退出。

说明
~~~~

通过C头文件或源文件（<C文件>）中为描述API的文档编写的带交叉引用的 ..预格式化
文本 块将文件转换成重构文本（RST）。它接受一个可选的<例外文件>，其中描述了
哪些元素将被忽略或指向非默认引用。

输出被写入到<输出文件>。

它能够识别定义、函数、结构体、typedef、枚举和枚举符号，并为它们创建交叉引用。
它还能够区分用于指定Linux ioctl的 ``#define`` 。

<例外文件> 包含两种类型的语句： \ **ignore**\  或 \ **replace**\ .

ignore标记的语法为：

ignore \ **type**\  \ **name**\

The \ **ignore**\  意味着它不会为类型为 \ **type**\ 的 \ **name**\ 符号生成
交叉引用。

replace标记的语法为：

replace \ **type**\  \ **name**\  \ **new_value**\

The \ **replace**\  味着它将为 \ **type**\ 类型的 \ **name**\ 符号生成交叉引
用，但是它将使用 \ **new_value**\ 来取代默认的替换规则。

这两种语句中， \ **type**\ 可以是以下任一项：

\ **ioctl**\

 ignore 或 replace 语句应用于ioctl定义，如：

 #define	VIDIOC_DBG_S_REGISTER 	 _IOW('V', 79, struct v4l2_dbg_register)

\ **define**\

 ignore 或 replace 语句应用于在<C文件>中找到的任何其他 ``#define`` 。

\ **typedef**\

 ignore 和 replace 语句应用于<C文件>中的typedef语句。

\ **struct**\

 ignore 和 replace 语句应用于<C文件>中的结构体名称语句。

\ **enum**\

 ignore 和 replace 语句应用于<C文件>中的枚举名称语句。

\ **symbol**\

 ignore 和 replace 语句应用于<C文件>中的枚举值名称语句。

 replace语句中， \ **new_value**\  会自动使用 \ **typedef**\ , \ **enum**\
 和 \ **struct**\ 类型的 :c:type: 引用；以及 \ **ioctl**\ , \ **define**\  和
 \ **symbol**\ 类型的  :ref: 。引用的类型也可以在replace语句中显式定义。

示例
~~~~

ignore define _VIDEODEV2_H

忽略<C文件>中的 #define _VIDEODEV2_H 。

ignore symbol PRIVATE

如下结构体：

enum foo { BAR1, BAR2, PRIVATE };

不会为 \ **PRIVATE**\ 生成交叉引用。

replace symbol BAR1 :c:type:\`foo\`
replace symbol BAR2 :c:type:\`foo\`

如下结构体：

enum foo { BAR1, BAR2, PRIVATE };

它会让BAR1和BAR2枚举符号交叉引用C域中的foo符号。

缺陷
~~~~

请向Mauro Carvalho Chehab <mchehab@kernel.org>报告有关缺陷。

中文翻译问题请找中文翻译维护者。

版权
~~~~

版权所有 (c) 2016 Mauro Carvalho Chehab <mchehab+samsung@kernel.org>

许可证 GPLv2：GNU GPL version 2 <https://gnu.org/licenses/gpl.html>

这是自由软件：你可以自由地修改和重新发布它。
在法律允许的范围内，**不提供任何保证**。