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

:Original: Documentation/PCI/acpi-info.rst

:翻译:

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

:校译:

=====================
PCI主桥的ACPI注意事项
=====================

一般的规则是，ACPI命名空间应该描述操作系统可能使用的所有东西，除非有其他方法让操作系
统找到它[1, 2]。

例如，没有标准的硬件机制来枚举PCI主桥，所以ACPI命名空间必须描述每个主桥、访问它
下面的PCI配置空间的方法、主桥转发到PCI的地址空间窗口（使用_CRS）以及传统的INTx
中断的路由（使用_PRT）。

在主桥下面的PCI设备，通常不需要通过ACPI描述。操作系统可以通过标准的PCI枚举机制来
发现它们，使用配置访问来发现和识别设备，并读取和测量它们的BAR。然而，如果ACPI为它们
提供电源管理或热插拔功能，或者如果设备有由平台中断控制器连接的INTx中断，需要一个_PRT
来描述这些连接，这种情况下ACPI可以描述PCI设备。

ACPI资源描述是通过ACPI命名空间中设备的_CRS对象完成的[2]。_CRS就像一个通用的PCI BAR：
操作系统可以读取_CRS并找出正在消耗的资源，即使它没有该设备的驱动程序[3]。这一点很重要，
因为它意味着一个旧的操作系统可以正确地工作，即使是在操作系统不知道的新设备的系统上。新设
备可能什么都不做，但操作系统至少可以确保没有资源与它们冲突。

像MCFG、HPET、ECDT等静态表，不是保留地址空间的机制。静态表是在操作系统在启动初期且在它
能够解析ACPI命名空间之前需要知道的东西。如果定义了一个新的表，即使旧的操作系统忽略了这
个表，它也需要正常运行。_CRS允许这样做，因为它是通用的，可以被旧的操作系统解析；而静态表
则不允许。

如果操作系统要管理一个通过ACPI描述的不可发现的设备，该设备将有一个特定的_HID/_CID，以
告诉操作系统与之绑定的驱动程序，并且_CRS告诉操作系统和驱动程序该设备的寄存器在哪里。

PCI主桥是PNP0A03或PNP0A08设备。它们的_CRS应该描述它们所消耗的所有地址空间。这包括它
们转发到PCI总线上的所有窗口，以及不转发到PCI的主桥本身的寄存器。主桥的寄存器包括次要/下
级总线寄存器，决定了桥下面的总线范围，窗口寄存器描述了桥洞，等等。这些都是设备相关的，非
架构相关的东西，所以PNP0A03/PNP0A08驱动可以管理它们的唯一方法是通过_PRS/_CRS/_SRS，
它包含了特定于设备的细节。主桥寄存器也包括ECAM空间，因为它是由主桥消耗的。

ACPI定义了一个Consumer/Producer位来区分桥寄存器（“Consumer”下文译作消费者）和
桥洞（“Producer”下文译作生产者）[4, 5]，但是早期的BIOS没有正确使用这个位。其结果
是，目前的ACPI规范只为扩展地址空间描述符定义了消费者/生产者；在旧的QWord/Word/Word地
址空间描述符中，该位应该被忽略。因此，操作系统必须假定所有的QWord/Word/Word描述符都是
窗口。

在增加扩展地址空间描述符之前，消费者/生产者的失败意味着没有办法描述PNP0A03/PNP0A08设
备本身的桥寄存器。解决办法是在PNP0C02捕捉器中描述桥寄存器（包括ECAM空间）[6]。
除了ECAM之外，桥寄存器空间反正是特定于设备的，所以通用的PNP0A03/PNP0A08驱动程
序(pci_root.c)没有必要了解它。

新的架构应该能够在PNP0A03设备中使用“消费者”扩展地址空间描述符，用于桥寄存器，包括
ECAM，尽管对[6]的严格解释可能禁止这样做。旧的x86和ia64内核假定所有的地址空间描述
符，包括“消费者”扩展地址空间的描述符，都是窗口，所以在这些架构上以这种方式描述桥寄
存器是不安全的。

PNP0C02“主板”设备基本上是万能的。除了“不要将这些资源用于其他用途”之外，没有其他的编
程模型。因此，PNP0C02 _CRS应该声明ACPI命名空间中(1)没有被_CRS声明的任何其他设备对
象的地址空间，(2)不应该被OS分配给其他东西。

除非有一个标准的固件接口用于配置访问，例如ia64 SAL接口[7]，否则PCIe规范要求使用增强
型配置访问方法（ECAM）。主桥消耗ECAM内存地址空间并将内存访问转换为PCI配置访问。该规范
定义了ECAM地址空间的布局和功能；只有地址空间的基础是特定于设备的。ACPI操作系统从静态
MCFG表或PNP0A03设备中的_CBA方法中了解基础地址。

MCFG表必须描述非热插拔主桥的ECAM空间[8]。由于MCFG是一个静态表，不能通过热插拔更新，
PNP0A03设备中的_CBA方法描述了可热插拔主桥的ECAM空间[9]。请注意，对于MCFG和_CBA，
基址总是对应于总线0，即使桥器下面的总线范围（通过_CRS报告）不从0开始。

[1] ACPI 6.2, sec 6.1:
    对于任何在非枚举类型的总线上的设备（例如，ISA总线），OSPM会枚举设备的标识符，ACPI
    系统固件必须为每个设备提供一个_HID对象...以使OSPM能够做到这一点。

[2] ACPI 6.2, sec 3.7:
    操作系统枚举主板设备时，只需通过读取ACPI命名空间来寻找具有硬件ID的设备。

    ACPI枚举的每个设备都包括ACPI命名空间中ACPI定义的对象，该对象报告设备可能占用的硬
    件资源[_PRS]，报告设备当前使用的资源[_CRS]的对象，以及配置这些资源的对象[_SRS]。
    这些信息被即插即用操作系统（OSPM）用来配置设备。

[3] ACPI 6.2, sec 6.2:
    OSPM使用设备配置对象来配置通过ACPI列举的设备的硬件资源。设备配置对象提供了关于当前
    和可能的资源需求的信息，共享资源之间的关系，以及配置硬件资源的方法。

    当OSPM枚举一个设备时，它调用_PRS来确定该设备的资源需求。它也可以调用_CRS来找到该设
    备的当前资源设置。利用这些信息，即插即用系统决定设备应该消耗什么资源，并通过调用设备
    的_SRS控制方法来设置这些资源。

    在ACPI中，设备可以消耗资源（例如，传统的键盘），提供资源（例如，一个专有的PCI桥），
    或者两者都做。除非另有规定，设备的资源被假定为来自设备层次结构中设备上方最近的匹配资
    源。

[4] ACPI 6.2, sec 6.4.3.5.1, 2, 3, 4:
    QWord/DWord/Word 地址空间描述符 (.1, .2, .3)
      常规标志: Bit [0] 被忽略。

    扩展地址空间描述符 (.4)
      常规标志: Bit [0] 消费者/生产者:

        * 1 – 这个设备消费这个资源
        * 0 – 该设备生产和消费该资源

[5] ACPI 6.2, sec 19.6.43:
    ResourceUsage指定内存范围是由这个设备（ResourceConsumer）消费还是传递给子设备
    （ResourceProducer）。如果没有指定，那么就假定是ResourceConsumer。

[6] PCI Firmware 3.2, sec 4.1.2:
    如果操作系统不能原生的懂得保留MMCFG区域，MMCFG区域必须由固件保留。在MCFG表中或通
    过_CBA方法（见第4.1.3节）报告的地址范围必须通过声明主板资源来保留。对于大多数系统，
    主板资源将出现在ACPI命名空间的根部（在_SB下），在一个节点的_HID为EISAID（PNP0C0
    2），在这种情况下的资源不应该要求在根PCI总线的_CRS。这些资源可以选择在Int15 E820
    或EFIGetMemoryMap中作为保留内存返回，但必须始终通过ACPI作为主板资源报告。

[7] PCI Express 4.0, sec 7.2.2:
    对于PC兼容的系统，或者没有实现允许访问配置空间的处理器架构特定固件接口标准的系统，需
    要使用本节中定义的ECAM。

[8] PCI Firmware 3.2, sec 4.1.2:
    MCFG表是一个ACPI表，用于沟通的基础地址对应的非热的可移动的PCI段组范围内的PCI段组在
    启动时提供给操作系统。这对PC兼容系统来说是必需的。

    MCFG表仅用于沟通在启动时系统可用的PCI段组对应的基址。

[9] PCI Firmware 3.2, sec 4.1.3:
    _CBA (Memory mapped Configuration Base Address) 控制方法是一个可选的ACPI对
    象，用于返回热插拔主桥的64位内存映射的配置基址。_CBA 返回的基址是与处理器相关的地址。
    _CBA 控制方法被评估为一个整数。

    这个控制方法出现在主桥对象下。当_CBA方法出现在一个活动的主桥对象下时，操作系统会评
    估这个结构，以确定内存映射的配置基址，对应于_CRS方法中指定的总线编号范围的PCI段组。
    一个包含_CBA方法的ACPI命名空间对象也必须包含一个相应的_SEG方法。


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

:Original: Documentation/PCI/index.rst

:翻译:

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

:校译:

===================
Linux PCI总线子系统
===================

.. toctree::
   :maxdepth: 2
   :numbered:

   pci
   pciebus-howto
   pci-iov-howto
   msi-howto
   sysfs-pci
   acpi-info

Todolist:

* pci-error-recovery
* pcieaer-howto
* endpoint/index
* boot-interrupts

repo_name: angery/linux


code:
.. SPDX-License-Identifier: GPL-2.0
.. include:: <isonum.txt>
.. include:: ../disclaimer-zh_CN.rst

:Original: Documentation/PCI/msi-howto.rst

:翻译:

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

:校译:

===========
MSI驱动指南
===========

:作者: Tom L Nguyen; Martine Silbermann; Matthew Wilcox

:版权: 2003, 2008 Intel Corporation

关于本指南
==========

本指南介绍了消息标记中断（MSI）的基本知识，使用MSI相对于传统中断机制的优势，如何
改变你的驱动程序以使用MSI或MSI-X，以及在设备不支持MSI时可以尝试的一些基本诊断方法。

什么是MSI?
==========

信息信号中断是指从设备写到一个特殊的地址，导致CPU接收到一个中断。

MSI能力首次在PCI 2.2中规定，后来在PCI 3.0中得到增强，允许对每个中断进行单独屏蔽。
MSI-X功能也随着PCI 3.0被引入。它比MSI支持每个设备更多的中断，并允许独立配置中断。

设备可以同时支持MSI和MSI-X，但一次只能启用一个。

为什么用MSI?
============

有三个原因可以说明为什么使用MSI比传统的基于针脚的中断有优势。

基于针脚的PCI中断通常在几个设备之间共享。为了支持这一点，内核必须调用每个与中断相
关的中断处理程序，这导致了整个系统性能的降低。MSI从不共享，所以这个问题不会出现。

当一个设备将数据写入内存，然后引发一个基于引脚的中断时，有可能在所有的数据到达内存
之前，中断就已经到达了（这在PCI-PCI桥后面的设备中变得更有可能）。为了确保所有的数
据已经到达内存中，中断处理程序必须在引发中断的设备上读取一个寄存器。PCI事务排序规
则要求所有的数据在返回寄存器的值之前到达内存。使用MSI可以避免这个问题，因为中断产
生的写入不能通过数据写入，所以当中断发生时，驱动程序知道所有的数据已经到达内存中。

PCI设备每个功能只能支持一个基于引脚的中断。通常情况下，驱动程序必须查询设备以找出
发生了什么事件，这就减慢了对常见情况的中断处理。有了MSI，设备可以支持更多的中断，
允许每个中断被专门用于不同的目的。一种可能的设计是给不经常发生的情况（如错误）提供
自己的中断，这使得驱动程序可以更有效地处理正常的中断处理路径。其他可能的设计包括给
网卡的每个数据包队列或存储控制器的每个端口提供一个中断。

如何使用MSI
===========

PCI设备被初始化为使用基于引脚的中断。设备驱动程序必须将设备设置为使用MSI或MSI-X。
并非所有的机器都能正确地支持MSI，对于这些机器，下面描述的API将简单地失败，设备将
继续使用基于引脚的中断。

加入内核对MSI的支持
-------------------

为了支持MSI或MSI-X，内核在构建时必须启用CONFIG_PCI_MSI选项。这个选项只在某些架
构上可用，而且它可能取决于其他一些选项的设置。例如，在x86上，你必须同时启用X86_UP_APIC
或SMP，才能看到CONFIG_PCI_MSI选项。

使用MSI
-------

大部分沉重的工作是在PCI层为驱动程序完成的。驱动程序只需要请求PCI层为这个设备设置
MSI功能。

要自动使用MSI或MSI-X中断向量，请使用以下函数::

  int pci_alloc_irq_vectors(struct pci_dev *dev, unsigned int min_vecs,
		unsigned int max_vecs, unsigned int flags);

它为一个PCI设备分配最多至max_vecs的中断向量。它返回分配的向量数量或一个负的错误。
如果设备对最小数量的向量有要求，驱动程序可以传递一个min_vecs参数，设置为这个限制，
如果PCI核不能满足最小数量的向量，将返回-ENOSPC。

flags参数用来指定设备和驱动程序可以使用哪种类型的中断（PCI_IRQ_LEGACY, PCI_IRQ_MSI,
PCI_IRQ_MSIX）。一个方便的短语（PCI_IRQ_ALL_TYPES）也可以用来要求任何可能的中断类型。
如果PCI_IRQ_AFFINITY标志被设置，pci_alloc_irq_vectors()将把中断分散到可用的CPU上。

要获得传递给require_irq()和free_irq()的Linux IRQ号码和向量，请使用以下函数::

  int pci_irq_vector(struct pci_dev *dev, unsigned int nr);

在删除设备之前，应使用以下功能释放任何已分配的资源::

  void pci_free_irq_vectors(struct pci_dev *dev);

如果一个设备同时支持MSI-X和MSI功能，这个API将优先使用MSI-X，而不是MSI。MSI-X支
持1到2048之间的任何数量的中断。相比之下，MSI被限制为最多32个中断（而且必须是2的幂）。
此外，MSI中断向量必须连续分配，所以系统可能无法为MSI分配像MSI-X那样多的向量。在一
些平台上，MSI中断必须全部针对同一组CPU，而MSI-X中断可以全部针对不同的CPU。

如果一个设备既不支持MSI-X，也不支持MSI，它就会退回到一个传统的IRQ向量。

MSI或MSI-X中断的典型用法是分配尽可能多的向量，可能达到设备支持的极限。如果nvec大于
设备支持的数量，它将自动被限制在支持的限度内，所以没有必要事先查询支持的向量的数量。::

	nvec = pci_alloc_irq_vectors(pdev, 1, nvec, PCI_IRQ_ALL_TYPES)
	if (nvec < 0)
		goto out_err;

如果一个驱动程序不能或不愿意处理可变数量的MSI中断，它可以要求一个特定数量的中断，将该
数量作为“min_vecs“和“max_vecs“参数传递给pci_alloc_irq_vectors()函数。::

	ret = pci_alloc_irq_vectors(pdev, nvec, nvec, PCI_IRQ_ALL_TYPES);
	if (ret < 0)
		goto out_err;

上述请求类型的最臭名昭著的例子是为一个设备启用单一的MSI模式。它可以通过传递两个1作为
'min_vecs'和'max_vecs'来实现::

	ret = pci_alloc_irq_vectors(pdev, 1, 1, PCI_IRQ_ALL_TYPES);
	if (ret < 0)
		goto out_err;

一些设备可能不支持使用传统的线路中断，在这种情况下，驱动程序可以指定只接受MSI或MSI-X。::

	nvec = pci_alloc_irq_vectors(pdev, 1, nvec, PCI_IRQ_MSI | PCI_IRQ_MSIX);
	if (nvec < 0)
		goto out_err;

传统API
-----------

以下用于启用和禁用MSI或MSI-X中断的旧API不应该在新代码中使用::

  pci_enable_msi()		/* deprecated */
  pci_disable_msi()		/* deprecated */
  pci_enable_msix_range()	/* deprecated */
  pci_enable_msix_exact()	/* deprecated */
  pci_disable_msix()		/* deprecated */

此外，还有一些API来提供支持的MSI或MSI-X向量的数量：pci_msi_vec_count()和
pci_msix_vec_count()。一般来说，应该避免使用这些方法，而是让pci_alloc_irq_vectors()
来限制向量的数量。如果你对向量的数量有合法的特殊用例，我们可能要重新审视这个决定，
并增加一个pci_nr_irq_vectors()助手，透明地处理MSI和MSI-X。

使用MSI时需要考虑的因素
-----------------------

自旋锁
~~~~~~

大多数设备驱动程序都有一个每的自旋锁，在中断处理程序中被占用。对于基于引脚的中断
或单一的MSI，没有必要禁用中断（Linux保证同一中断不会被重新输入）。如果一个设备
使用多个中断，驱动程序必须在锁被持有的时候禁用中断。如果设备发出一个不同的中断，
驱动程序将死锁，试图递归地获取自旋锁。这种死锁可以通过使用spin_lock_irqsave()
或spin_lock_irq()来避免，它们可以禁用本地中断并获取锁（见《不可靠的锁定指南》）。

如何判断一个设备上是否启用了MSI/MSI-X
-------------------------------------

使用“lspci -v“（以root身份）可能会显示一些具有“MSI“、“Message Signalled Interrupts“
或“MSI-X“功能的设备。这些功能中的每一个都有一个“启用“标志，后面是“+“（启用）
或“-“（禁用）。

MSI特性
=======

众所周知，一些PCI芯片组或设备不支持MSI。PCI协议栈提供了三种禁用MSI的方法:

1. 全局的
2. 在一个特定的桥后面的所有设备上
3. 在单一设备上

全局禁用MSI
-----------

一些主控芯片组根本无法正确支持MSI。如果我们幸运的话，制造商知道这一点，并在
ACPI FADT表中指明了它。在这种情况下，Linux会自动禁用MSI。有些板卡在表中没
有包括这一信息，因此我们必须自己检测它们。完整的列表可以在drivers/pci/quirks.c
中的quirk_disable_all_msi()函数附近找到。

如果你有一块有MSI问题的板子，你可以在内核命令行中传递pci=nomsi来禁用所有设
备上的MSI。你最好把问题报告给linux-pci@vger.kernel.org，包括完整的
“lspci -v“，这样我们就可以把这些怪癖添加到内核中。

禁用桥下的MSI
-------------

一些PCI桥接器不能在总线之间正确地路由MSI。在这种情况下，必须在桥接器后面的所
有设备上禁用MSI。

一些桥接器允许你通过改变PCI配置空间的一些位来启用MSI（特别是Hypertransport
芯片组，如nVidia nForce和Serverworks HT2000）。与主机芯片组一样，Linux大
多知道它们，如果可以的话，会自动启用MSI。如果你有一个Linux不知道的网桥，你可以
用你知道的任何方法在配置空间中启用MSI，然后通过以下方式在该网桥上启用MSI::

       echo 1 > /sys/bus/pci/devices/$bridge/msi_bus

其中$bridge是你所启用的桥的PCI地址（例如0000:00:0e.0）。

要禁用MSI，请回显0而不是1。改变这个值应该谨慎进行，因为它可能会破坏这个桥下面所
有设备的中断处理。

同样，请通知 linux-pci@vger.kernel.org 任何需要特殊处理的桥。

在单一设备上关闭MSIs
--------------------

众所周知，有些设备的MSI实现是有问题的。通常情况下，这是在单个设备驱动程序中处理的，
但偶尔也有必要用一个古怪的方法来处理。一些驱动程序有一个选项可以禁用MSI的使用。虽然
这对驱动程序的作者来说是一个方便的变通办法，但这不是一个好的做法，不应该被模仿。

寻找设备上MSI被禁用的原因
-------------------------

从以上三个部分，你可以看到有许多原因导致MSI没有在某个设备上被启用。你的第一步应该是
仔细检查你的dmesg以确定你的机器是否启用了MSI。你还应该检查你的.config以确定你已经
启用了CONFIG_PCI_MSI。

然后，“lspci -t“给出一个设备上面的网列表。读取 ``/sys/bus/pci/devices/*/msi_bus``
将告诉你MSI是否被启用（1）或禁用（0）。如果在任何属于PCI根和设备之间的桥的msi_bus
文件中发现0，说明MSI被禁用。

也需要检查设备驱动程序，看它是否支持MSI。例如，它可能包含对带有PCI_IRQ_MSI或
PCI_IRQ_MSIX标志的pci_alloc_irq_vectors（）的调用。


code:
.. SPDX-License-Identifier: GPL-2.0
.. include:: <isonum.txt>
.. include:: ../disclaimer-zh_CN.rst

:Original: Documentation/PCI/pci-iov-howto.rst

:翻译:

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

:校译:

.. _cn_pci-iov-howto:

==========================
PCI Express I/O 虚拟化指南
==========================

:版权: |copy| 2009 Intel Corporation
:作者: - Yu Zhao <yu.zhao@intel.com>
          - Donald Dutile <ddutile@redhat.com>

概述
====

什么是SR-IOV
------------

单根I/O虚拟化（SR-IOV）是一种PCI Express扩展功能，它使一个物理设备显示为多个
虚拟设备。物理设备被称为物理功能（PF），而虚拟设备被称为虚拟功能（VF）。VF的分
配可以由PF通过封装在该功能中的寄存器动态控制。默认情况下，该功能未被启用，PF表
现为传统的PCIe设备。一旦开启，每个VF的PCI配置空间都可以通过自己的总线、设备和
功能编号（路由ID）来访问。而且每个VF也有PCI内存空间，用于映射其寄存器集。VF设
备驱动程序对寄存器集进行操作，这样它就可以发挥功能，并作为一个真正的现有PCI设备
出现。

使用指南
========

我怎样才能启用SR-IOV功能
------------------------

有多种方法可用于SR-IOV的启用。在第一种方法中，设备驱动（PF驱动）将通过SR-IOV
核心提供的API控制功能的启用和禁用。如果硬件具有SR-IOV能力，加载其PF驱动器将启
用它和与PF相关的所有VF。一些PF驱动需要设置一个模块参数，以确定要启用的VF的数量。
在第二种方法中，对sysfs文件sriov_numvfs的写入将启用和禁用与PCIe PF相关的VF。
这种方法实现了每个PF的VF启用/禁用值，而第一种方法则适用于同一设备的所有PF。此外，
PCI SRIOV核心支持确保启用/禁用操作是有效的，以减少同一检查在多个驱动程序中的重
复，例如，如果启用VF，检查numvfs == 0，确保numvfs <= totalvfs。
第二种方法是对新的/未来的VF设备的推荐方法。

我怎样才能使用虚拟功能
----------------------

在内核中，VF被视为热插拔的PCI设备，所以它们应该能够以与真正的PCI设备相同的方式
工作。VF需要的设备驱动与普通PCI设备的驱动相同。

开发者指南
==========

SR-IOV API
----------

用来开启SR-IOV功能:

(a) 对于第一种方法，在驱动程序中::

	int pci_enable_sriov(struct pci_dev *dev, int nr_virtfn);

nr_virtfn'是要启用的VF的编号。

(b) 对于第二种方法，从sysfs::

	echo 'nr_virtfn' > \
        /sys/bus/pci/devices/<DOMAIN:BUS:DEVICE.FUNCTION>/sriov_numvfs

用来关闭SR-IOV功能:

(a) 对于第一种方法，在驱动程序中::

	void pci_disable_sriov(struct pci_dev *dev);

(b) 对于第二种方法，从sysfs::

	echo  0 > \
        /sys/bus/pci/devices/<DOMAIN:BUS:DEVICE.FUNCTION>/sriov_numvfs

要想通过主机上的兼容驱动启用自动探测VF，在启用SR-IOV功能之前运行下面的命令。这
是默认的行为。
::

	echo 1 > \
        /sys/bus/pci/devices/<DOMAIN:BUS:DEVICE.FUNCTION>/sriov_drivers_autoprobe

要禁止主机上的兼容驱动自动探测VF，请在启用SR-IOV功能之前运行以下命令。更新这个
入口不会影响已经被探测的VF。
::

	echo  0 > \
        /sys/bus/pci/devices/<DOMAIN:BUS:DEVICE.FUNCTION>/sriov_drivers_autoprobe

用例
----

下面的代码演示了SR-IOV API的用法
::

	static int dev_probe(struct pci_dev *dev, const struct pci_device_id *id)
	{
		pci_enable_sriov(dev, NR_VIRTFN);

		...

		return 0;
	}

	static void dev_remove(struct pci_dev *dev)
	{
		pci_disable_sriov(dev);

		...
	}

	static int dev_suspend(struct device *dev)
	{
		...

		return 0;
	}

	static int dev_resume(struct device *dev)
	{
		...

		return 0;
	}

	static void dev_shutdown(struct pci_dev *dev)
	{
		...
	}

	static int dev_sriov_configure(struct pci_dev *dev, int numvfs)
	{
		if (numvfs > 0) {
			...
			pci_enable_sriov(dev, numvfs);
			...
			return numvfs;
		}
		if (numvfs == 0) {
			....
			pci_disable_sriov(dev);
			...
			return 0;
		}
	}

	static struct pci_driver dev_driver = {
		.name =		"SR-IOV Physical Function driver",
		.id_table =	dev_id_table,
		.probe =	dev_probe,
		.remove =	dev_remove,
		.driver.pm =    &dev_pm_ops
		.shutdown =	dev_shutdown,
		.sriov_configure = dev_sriov_configure,
	};


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

:Original: Documentation/PCI/pci.rst

:翻译:

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

:校译:

.. _cn_PCI_pci.rst:

===================
如何写Linux PCI驱动
===================

:作者: - Martin Mares <mj@ucw.cz>
          - Grant Grundler <grundler@parisc-linux.org>

PCI的世界是巨大的，而且充满了（大多数是不愉快的）惊喜。由于每个CPU架构实现了不同
的芯片组，并且PCI设备有不同的要求（呃，“特性”），结果是Linux内核中的PCI支持并不
像人们希望的那样简单。这篇短文试图向所有潜在的驱动程序作者介绍PCI设备驱动程序的
Linux APIs。

更完整的资源是Jonathan Corbet、Alessandro Rubini和Greg Kroah-Hartman的
《Linux设备驱动程序》第三版。LDD3可以免费获得（在知识共享许可下），网址是：
https://lwn.net/Kernel/LDD3/。

然而，请记住，所有的文档都会受到“维护不及时”的影响。如果事情没有按照这里描述的那
样进行，请参考源代码。

请将有关Linux PCI API的问题/评论/补丁发送到“Linux PCI”
<linux-pci@atrey.karlin.mff.cuni.cz> 邮件列表。

PCI驱动的结构体
===============
PCI驱动通过pci_register_driver()在系统中“发现”PCI设备。实际上，它是反过来的。
当PCI通用代码发现一个新设备时，具有匹配“描述”的驱动程序将被通知。下面是这方面的细
节。

pci_register_driver()将大部分探测设备的工作留给了PCI层，并支持设备的在线插入/移
除[从而在一个驱动中支持可热插拔的PCI、CardBus和Express-Card]。 pci_register_driver()
调用需要传入一个函数指针表，从而决定了驱动的高层结构体。

一旦驱动探测到一个PCI设备并取得了所有权，驱动通常需要执行以下初始化：

  - 启用设备
  - 请求MMIO/IOP资源
  - 设置DMA掩码大小（对于流式和一致的DMA）
  - 分配和初始化共享控制数据（pci_allocate_coherent()）
  - 访问设备配置空间(如果需要)
  - 注册IRQ处理程序(request_irq())
  - 初始化非PCI（即芯片的LAN/SCSI/等部分）
  - 启用DMA/处理引擎

当使用完设备后，也许需要卸载模块，驱动需要采取以下步骤:

  - 禁用设备产生的IRQ
  - 释放IRQ（free_irq()）
  - 停止所有DMA活动
  - 释放DMA缓冲区（包括一致性和数据流式）
  - 从其他子系统（例如scsi或netdev）上取消注册
  - 释放MMIO/IOP资源
  - 禁用设备

这些主题中的大部分都在下面的章节中有所涉及。其余的内容请参考LDD3或<linux/pci.h> 。

如果没有配置PCI子系统（没有设置 ``CONFIG_PCI`` ），下面描述的大多数PCI函数被定
义为内联函数，要么完全为空，要么只是返回一个适当的错误代码，以避免在驱动程序中出现
大量的 ``ifdef`` 。

调用pci_register_driver()
=========================

PCI设备驱动程序在初始化过程中调用 ``pci_register_driver()`` ，并提供一个指向
描述驱动程序的结构体的指针（ ``struct pci_driver`` ）：

该API在以下内核代码中:

include/linux/pci.h
pci_driver

ID表是一个由 ``struct pci_device_id`` 结构体成员组成的数组，以一个全零的成员
结束。一般来说，带有静态常数的定义是首选。

该API在以下内核代码中:

include/linux/mod_devicetable.h
pci_device_id

大多数驱动程序只需要 ``PCI_DEVICE()`` 或 ``PCI_DEVICE_CLASS()`` 来设置一个
pci_device_id表。

新的 ``PCI ID`` 可以在运行时被添加到设备驱动的 ``pci_ids`` 表中，如下所示::

  echo "vendor device subvendor subdevice class class_mask driver_data" > \
  /sys/bus/pci/drivers/{driver}/new_id

所有字段都以十六进制值传递（没有前置0x）。供应商和设备字段是强制性的，其他字段是可
选的。用户只需要传递必要的可选字段：

  - subvendor和subdevice字段默认为PCI_ANY_ID (FFFFFFF)。
  - class和classmask字段默认为0
  - driver_data默认为0UL。
  - override_only字段默认为0。

请注意， ``driver_data`` 必须与驱动程序中定义的任何一个 ``pci_device_id`` 条
目所使用的值相匹配。如果所有的 ``pci_device_id`` 成员都有一个非零的driver_data
值，这使得driver_data字段是强制性的。

一旦添加，驱动程序探测程序将被调用，以探测其（新更新的） ``pci_ids`` 列表中列出的
任何无人认领的PCI设备。

当驱动退出时，它只是调用 ``pci_unregister_driver()`` ，PCI层会自动调用驱动处理
的所有设备的移除钩子。

驱动程序功能/数据的“属性”
-------------------------

请在适当的地方标记初始化和清理函数（相应的宏在<linux/init.h>中定义）：

	======		==============================================
	__init		初始化代码。在驱动程序初始化后被抛弃。
	__exit		退出代码。对于非模块化的驱动程序来说是忽略的。
	======		==============================================

关于何时/何地使用上述属性的提示：

	- module_init()/module_exit()函数（以及所有仅由这些函数调用的初始化函数）应该被标记

	- 为__init/__exit。

	- 不要标记pci_driver结构体。

	- 如果你不确定应该使用哪种标记，请不要标记一个函数。不标记函数比标记错误的函数更好。

如何手动搜索PCI设备
===================

PCI驱动最好有一个非常好的理由不使用 ``pci_register_driver()`` 接口来搜索PCI设备。
PCI设备被多个驱动程序控制的主要原因是一个PCI设备实现了几个不同的HW服务。例如，组合的
串行/并行端口/软盘控制器。

可以使用以下结构体进行手动搜索：

通过供应商和设备ID进行搜索::

	struct pci_dev *dev = NULL;
	while (dev = pci_get_device(VENDOR_ID, DEVICE_ID, dev))
		configure_device(dev);

按类别ID搜索（以类似的方式迭代）::

	pci_get_class(CLASS_ID, dev)

通过供应商/设备和子系统供应商/设备ID进行搜索::

	pci_get_subsys(VENDOR_ID,DEVICE_ID, SUBSYS_VENDOR_ID, SUBSYS_DEVICE_ID, dev).

你可以使用常数 ``PCI_ANY_ID`` 作为 ``VENDOR_ID`` 或 ``DEVICE_ID`` 的通
配符替代。例如，这允许搜索来自一个特定供应商的任何设备。

这些函数是热拔插安全的。它们会增加它们所返回的 ``pci_dev`` 的参考计数。你最终
必须通过调用 ``pci_dev_put()`` 来减少这些设备上的参考计数（可能在模块卸载时）。

设备初始化步骤
==============

正如介绍中所指出的，大多数PCI驱动需要以下步骤进行设备初始化：

  - 启用设备
  - 请求MMIO/IOP资源
  - 设置DMA掩码大小（对于流式和一致的DMA）
  - 分配和初始化共享控制数据（pci_allocate_coherent()）
  - 访问设备配置空间(如果需要)
  - 注册IRQ处理程序（request_irq()）
  - 初始化non-PCI（即芯片的LAN/SCSI/等部分）
  - 启用DMA/处理引擎

驱动程序可以在任何时候访问PCI配置空间寄存器。（嗯，几乎如此。当运行BIST时，配置
空间可以消失......但这只会导致PCI总线主控中止，读取配置将返回垃圾值）。）

启用PCI设备
-----------
在接触任何设备寄存器之前，驱动程序需要通过调用 ``pci_enable_device()`` 启用
PCI设备。这将:

  - 唤醒处于暂停状态的设备。
  - 分配设备的I/O和内存区域（如果BIOS没有这样做）。
  - 分配一个IRQ（如果BIOS没有）。

.. note::
   pci_enable_device() 可能失败，检查返回值。

.. warning::
   OS BUG：在启用这些资源之前，我们没有检查资源分配情况。如果我们在调用
   之前调用pci_request_resources()，这个顺序会更合理。目前，当两个设备被分配
   了相同的范围时，设备驱动无法检测到这个错误。这不是一个常见的问题，不太可能很快
   得到修复。

   这个问题之前已经讨论过了，但从2.6.19开始没有改变：
   https://lore.kernel.org/r/20060302180025.GC28895@flint.arm.linux.org.uk/

pci_set_master()将通过设置PCI_COMMAND寄存器中的总线主控位来启用DMA。
``pci_clear_master()`` 将通过清除总线主控位来禁用DMA，它还修复了延迟计时器的
值，如果它被BIOS设置成假的。

如果PCI设备可以使用 ``PCI Memory-Write-Invalidate`` 事务，请调用 ``pci_set_mwi()`` 。
这将启用 ``Mem-Wr-Inval`` 的 ``PCI_COMMAND`` 位，也确保缓存行大小寄存器被正确设置。检
查 ``pci_set_mwi()`` 的返回值，因为不是所有的架构或芯片组都支持 ``Memory-Write-Invalidate`` 。
另外，如果 ``Mem-Wr-Inval`` 是好的，但不是必须的，可以调用 ``pci_try_set_mwi()`` ，让
系统尽最大努力来启用 ``Mem-Wr-Inval`` 。

请求MMIO/IOP资源
----------------
内存（MMIO）和I/O端口地址不应该直接从PCI设备配置空间中读取。使用 ``pci_dev`` 结构体
中的值，因为PCI “总线地址”可能已经被arch/chip-set特定的内核支持重新映射为“主机物理”
地址。

参见io_mapping函数，了解如何访问设备寄存器或设备内存。

设备驱动需要调用 ``pci_request_region()`` 来确认没有其他设备已经在使用相同的地址
资源。反之，驱动应该在调用 ``pci_disable_device()`` 之后调用 ``pci_release_region()`` 。
这个想法是为了防止两个设备在同一地址范围内发生冲突。

.. tip::
   见上面的操作系统BUG注释。目前(2.6.19)，驱动程序只能在调用pci_enable_device()
   后确定MMIO和IO端口资源的可用性。

``pci_request_region()`` 的通用风格是 ``request_mem_region()`` （用于MMIO
范围）和 ``request_region()`` （用于IO端口范围）。对于那些不被 "正常 "PCI BAR描
述的地址资源，使用这些方法。

也请看下面的 ``pci_request_selected_regions()`` 。

设置DMA掩码大小
---------------
.. note::
   如果下面有什么不明白的地方，请参考使用通用设备的动态DMA映射。本节只是提醒大家，
   驱动程序需要说明设备的DMA功能，并不是DMA接口的权威来源。

虽然所有的驱动程序都应该明确指出PCI总线主控的DMA功能（如32位或64位），但对于流式
数据来说，具有超过32位总线主站功能的设备需要驱动程序通过调用带有适当参数的
``dma_set_mask()`` 来“注册”这种功能。一般来说，在系统RAM高于4G物理地址的情
况下，这允许更有效的DMA。

所有PCI-X和PCIe兼容设备的驱动程序必须调用 ``dma_set_mask()`` ，因为它们
是64位DMA设备。

同样，如果设备可以通过调用 ``dma_set_coherent_mask()`` 直接寻址到
4G物理地址以上的系统RAM中的“一致性内存”，那么驱动程序也必须“注册”这种功能。同
样，这包括所有PCI-X和PCIe兼容设备的驱动程序。许多64位“PCI”设备（在PCI-X之前）
和一些PCI-X设备对有效载荷（“流式”）数据具有64位DMA功能，但对控制（“一致性”）数
据则没有。

设置共享控制数据
----------------
一旦DMA掩码设置完毕，驱动程序就可以分配“一致的”（又称共享的）内存。参见使用通
用设备的动态DMA映射，了解DMA API的完整描述。本节只是提醒大家，需要在设备上启
用DMA之前完成。

初始化设备寄存器
----------------
一些驱动程序需要对特定的“功能”字段进行编程，或对其他“供应商专用”寄存器进行初始
化或重置。例如，清除挂起的中断。

注册IRQ处理函数
---------------
虽然调用 ``request_irq()`` 是这里描述的最后一步，但这往往只是初始化设备的另
一个中间步骤。这一步通常可以推迟到设备被打开使用时进行。

所有IRQ线的中断处理程序都应该用 ``IRQF_SHARED`` 注册，并使用devid将IRQ映射
到设备（记住，所有的PCI IRQ线都可以共享）。

``request_irq()`` 将把一个中断处理程序和设备句柄与一个中断号联系起来。历史上，
中断号码代表从PCI设备到中断控制器的IRQ线。在MSI和MSI-X中（更多内容见下文），中
断号是CPU的一个“向量”。

``request_irq()`` 也启用中断。在注册中断处理程序之前，请确保设备是静止的，并且
没有任何中断等待。

MSI和MSI-X是PCI功能。两者都是“消息信号中断”，通过向本地APIC的DMA写入来向CPU发
送中断。MSI和MSI-X的根本区别在于如何分配多个“向量”。MSI需要连续的向量块，而
MSI-X可以分配几个单独的向量。

在调用 ``request_irq()`` 之前，可以通过调用 ``pci_alloc_irq_vectors()``
的PCI_IRQ_MSI和/或PCI_IRQ_MSIX标志来启用MSI功能。这将导致PCI支持将CPU向量数
据编程到PCI设备功能寄存器中。许多架构、芯片组或BIOS不支持MSI或MSI-X，调用
``pci_alloc_irq_vectors`` 时只使用PCI_IRQ_MSI和PCI_IRQ_MSIX标志会失败，
所以尽量也要指定 ``PCI_IRQ_LEGACY`` 。

对MSI/MSI-X和传统INTx有不同中断处理程序的驱动程序应该在调用
``pci_alloc_irq_vectors`` 后根据 ``pci_dev``结构体中的 ``msi_enabled``
和 ``msix_enabled`` 标志选择正确的处理程序。

使用MSI有（至少）两个真正好的理由：

1) 根据定义，MSI是一个排他性的中断向量。这意味着中断处理程序不需要验证其设备是
   否引起了中断。

2) MSI避免了DMA/IRQ竞争条件。到主机内存的DMA被保证在MSI交付时对主机CPU是可
   见的。这对数据一致性和避

3) 免控制数据过期都很重要。这个保证允许驱动程序省略MMIO读取，以刷新DMA流。

参见drivers/infiniband/hw/mthca/或drivers/net/tg3.c了解MSI/MSI-X的使
用实例。

PCI设备关闭
===========

当一个PCI设备驱动程序被卸载时，需要执行以下大部分步骤:

  - 禁用设备产生的IRQ
  - 释放IRQ（free_irq()）
  - 停止所有DMA活动
  - 释放DMA缓冲区（包括流式和一致的）
  - 从其他子系统（例如scsi或netdev）上取消注册
  - 禁用设备对MMIO/IO端口地址的响应
  - 释放MMIO/IO端口资源

停止设备上的IRQ
---------------
如何做到这一点是针对芯片/设备的。如果不这样做，如果（也只有在）IRQ与另一个设备
共享，就会出现“尖叫中断”的可能性。

当共享的IRQ处理程序被“解钩”时，使用同一IRQ线的其余设备仍然需要启用该IRQ。因此，
如果“脱钩”的设备断言IRQ线，假设它是其余设备中的一个断言IRQ线，系统将作出反应。
由于其他设备都不会处理这个IRQ，系统将“挂起”，直到它决定这个IRQ不会被处理并屏蔽
这个IRQ（100,000次之后）。一旦共享的IRQ被屏蔽，其余设备将停止正常工作。这不是
一个好事情。

这是使用MSI或MSI-X的另一个原因，如果它可用的话。MSI和MSI-X被定义为独占中断，
因此不容易受到“尖叫中断”问题的影响。

释放IRQ
-------
一旦设备被静止（不再有IRQ），就可以调用free_irq()。这个函数将在任何待处理
的IRQ被处理后返回控制，从该IRQ上“解钩”驱动程序的IRQ处理程序，最后如果没有人
使用该IRQ，则释放它。

停止所有DMA活动
---------------
在试图取消分配DMA控制数据之前，停止所有的DMA操作是非常重要的。如果不这样做，
可能会导致内存损坏、挂起，在某些芯片组上还会导致硬崩溃。

在停止IRQ后停止DMA可以避免IRQ处理程序可能重新启动DMA引擎的竞争。

虽然这个步骤听起来很明显，也很琐碎，但过去有几个“成熟”的驱动程序没有做好这个
步骤。

释放DMA缓冲区
-------------
一旦DMA被停止，首先要清理流式DMA。即取消数据缓冲区的映射，如果有的话，将缓
冲区返回给“上游”所有者。

然后清理包含控制数据的“一致的”缓冲区。

关于取消映射接口的细节，请参见Documentation/core-api/dma-api.rst。

从其他子系统取消注册
--------------------
大多数低级别的PCI设备驱动程序支持其他一些子系统，如USB、ALSA、SCSI、NetDev、
Infiniband等。请确保你的驱动程序没有从其他子系统中丢失资源。如果发生这种情况，
典型的症状是当子系统试图调用已经卸载的驱动程序时，会出现Oops（恐慌）。

禁止设备对MMIO/IO端口地址做出响应
---------------------------------
io_unmap() MMIO或IO端口资源，然后调用pci_disable_device()。
这与pci_enable_device()对称相反。
在调用pci_disable_device()后不要访问设备寄存器。

释放MMIO/IO端口资源
-------------------
调用pci_release_region()来标记MMIO或IO端口范围为可用。
如果不这样做，通常会导致无法重新加载驱动程序。

如何访问PCI配置空间
===================

你可以使用 `pci_(read|write)_config_(byte|word|dword)` 来访问由
`struct pci_dev *` 表示的设备的配置空间。所有这些函数在成功时返回0，或者返回一个
错误代码（ `PCIBIOS_...` ），这个错误代码可以通过pcibios_strerror翻译成文本字
符串。大多数驱动程序希望对有效的PCI设备的访问不会失败。

如果你没有可用的pci_dev结构体，你可以调用
`pci_bus_(read|write)_config_(byte|word|dword)` 来访问一个给定的设备和该总
线上的功能。

如果你访问配置头的标准部分的字段，请使用<linux/pci.h>中声明的位置和位的符号名称。

如果你需要访问扩展的PCI功能寄存器，只要为特定的功能调用pci_find_capability()，
它就会为你找到相应的寄存器块。

其它有趣的函数
==============

=============================	=================================================
pci_get_domain_bus_and_slot()   找到与给定的域、总线和槽以及编号相对应的pci_dev。
                                如果找到该设备，它的引用计数就会增加。
pci_set_power_state()           设置PCI电源管理状态（0=D0 ... 3=D3
pci_find_capability()           在设备的功能列表中找到指定的功能
pci_resource_start()            返回一个给定的PCI区域的总线起始地址
pci_resource_end()              返回给定PCI区域的总线末端地址
pci_resource_len()              返回一个PCI区域的字节长度
pci_set_drvdata()               为一个pci_dev设置私有驱动数据指针
pci_get_drvdata()               返回一个pci_dev的私有驱动数据指针
pci_set_mwi()                   启用设备内存写无效
pci_clear_mwi()                 关闭设备内存写无效
=============================	=================================================

杂项提示
========

当向用户显示PCI设备名称时(例如，当驱动程序想告诉用户它找到了什么卡时)，请使
用pci_name(pci_dev)。

始终通过对pci_dev结构体的指针来引用PCI设备。所有的PCI层函数都使用这个标识，
它是唯一合理的标识。除了非常特殊的目的，不要使用总线/插槽/功能号————在有多个
主总线的系统上，它们的语义可能相当复杂。

不要试图在你的驱动程序中开启快速寻址周期写入功能。总线上的所有设备都需要有这样
的功能，所以这需要由平台和通用代码来处理，而不是由单个驱动程序来处理。

供应商和设备标识
================

不要在include/linux/pci_ids.h中添加新的设备或供应商ID，除非它们是在多个驱
动程序中共享。如果有需要的话，你可以在你的驱动程序中添加私有定义，或者直接使用
普通的十六进制常量。

设备ID是任意的十六进制数字（厂商控制），通常只在一个地方使用，即pci_device_id
表。

请务必提交新的供应商/设备ID到https://pci-ids.ucw.cz/。在
https://github.com/pciutils/pciids，有一个pci.ids文件的镜像。

过时的函数
==========

当你试图将一个旧的驱动程序移植到新的PCI接口时，你可能会遇到几个函数。它们不再存
在于内核中，因为它们与热插拔或PCI域或具有健全的锁不兼容。

=================	===================================
pci_find_device()	被pci_get_device()取代
pci_find_subsys()	被pci_get_subsys()取代
pci_find_slot()		被pci_get_domain_bus_and_slot()取代
pci_get_slot()		被pci_get_domain_bus_and_slot()取代
=================	===================================

另一种方法是传统的PCI设备驱动，即走PCI设备列表。这仍然是可能的，但不鼓励这样做。

MMIO空间和“写通知”
==================

将驱动程序从使用I/O端口空间转换为使用MMIO空间，通常需要一些额外的改变。具体来说，
需要处理“写通知”。许多驱动程序（如tg3，acenic，sym53c8xx_2）已经做了这个。I/O
端口空间保证写事务在CPU继续之前到达PCI设备。对MMIO空间的写入允许CPU在事务到达PCI
设备之前继续。HW weenies称这为“写通知”，因为在事务到达目的地之前，写的完成被“通知”
给CPU。

因此，对时间敏感的代码应该添加readl()，CPU在做其他工作之前应该等待。经典的“位脉冲”
序列对I/O端口空间很有效::

       for (i = 8; --i; val >>= 1) {
               outb(val & 1, ioport_reg);      /* 置位 */
               udelay(10);
       }

对MMIO空间来说，同样的顺序应该是::

       for (i = 8; --i; val >>= 1) {
               writeb(val & 1, mmio_reg);      /* 置位 */
               readb(safe_mmio_reg);           /* 刷新写通知 */
               udelay(10);
       }

重要的是， ``safe_mmio_reg`` 不能有任何干扰设备正确操作的副作用。

另一种需要注意的情况是在重置PCI设备时。使用PCI配置空间读数来刷新writeel()。如果预期
PCI设备不响应readl()，这将在所有平台上优雅地处理PCI主控器的中止。大多数x86平台将允许
MMIO读取主控中止（又称“软失败”），并返回垃圾（例如~0）。但许多RISC平台会崩溃（又称“硬失败”）。


code:
.. SPDX-License-Identifier: GPL-2.0
.. include:: <isonum.txt>
.. include:: ../disclaimer-zh_CN.rst

:Original: Documentation/PCI/pciebus-howto.rst

:翻译:

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

:校译:

.. _cn_pciebus-howto:

===========================
PCI Express端口总线驱动指南
===========================

:作者: Tom L Nguyen tom.l.nguyen@intel.com 11/03/2004
:版权: |copy| 2004 Intel Corporation

关于本指南
==========

本指南介绍了PCI Express端口总线驱动程序的基本知识，并提供了如何使服务驱
动程序在PCI Express端口总线驱动程序中注册/取消注册的介绍。

什么是PCI Express端口总线驱动程序
=================================

一个PCI Express端口是一个逻辑的PCI-PCI桥结构。有两种类型的PCI Express端
口：根端口和交换端口。根端口从PCI Express根综合体发起一个PCI Express链接，
交换端口将PCI Express链接连接到内部逻辑PCI总线。交换机端口，其二级总线代表
交换机的内部路由逻辑，被称为交换机的上行端口。交换机的下行端口是从交换机的内部
路由总线桥接到代表来自PCI Express交换机的下游PCI Express链接的总线。

一个PCI Express端口可以提供多达四个不同的功能，在本文中被称为服务，这取决于
其端口类型。PCI Express端口的服务包括本地热拔插支持（HP）、电源管理事件支持（PME）、
高级错误报告支持（AER）和虚拟通道支持（VC）。这些服务可以由一个复杂的驱动程序
处理，也可以单独分布并由相应的服务驱动程序处理。

为什么要使用PCI Express端口总线驱动程序？
=========================================

在现有的Linux内核中，Linux设备驱动模型允许一个物理设备只由一个驱动处理。
PCI Express端口是一个具有多个不同服务的PCI-PCI桥设备。为了保持一个干净和简
单的解决方案，每个服务都可以有自己的软件服务驱动。在这种情况下，几个服务驱动将
竞争一个PCI-PCI桥设备。例如，如果PCI Express根端口的本机热拔插服务驱动程序
首先被加载，它就会要求一个PCI-PCI桥根端口。因此，内核不会为该根端口加载其他服
务驱动。换句话说，使用当前的驱动模型，不可能让多个服务驱动同时加载并运行在
PCI-PCI桥设备上。

为了使多个服务驱动程序同时运行，需要有一个PCI Express端口总线驱动程序，它管
理所有填充的PCI Express端口，并根据需要将所有提供的服务请求分配给相应的服务
驱动程序。下面列出了使用PCI Express端口总线驱动程序的一些关键优势:

  - 允许在一个PCI-PCI桥接端口设备上同时运行多个服务驱动。

  - 允许以独立的分阶段方式实施服务驱动程序。

  - 允许一个服务驱动程序在多个PCI-PCI桥接端口设备上运行。

  - 管理和分配PCI-PCI桥接端口设备的资源给要求的服务驱动程序。

配置PCI Express端口总线驱动程序与服务驱动程序
=============================================

将PCI Express端口总线驱动支持纳入内核
-------------------------------------

包括PCI Express端口总线驱动程序取决于内核配置中是否包含PCI Express支持。当内核
中的PCI Express支持被启用时，内核将自动包含PCI Express端口总线驱动程序作为内核
驱动程序。

启用服务驱动支持
----------------

PCI设备驱动是基于Linux设备驱动模型实现的。所有的服务驱动都是PCI设备驱动。如上所述，
一旦内核加载了PCI Express端口总线驱动程序，就不可能再加载任何服务驱动程序。为了满
足PCI Express端口总线驱动程序模型，需要对现有的服务驱动程序进行一些最小的改变，其
对现有的服务驱动程序的功能没有影响。

服务驱动程序需要使用下面所示的两个API，将其服务注册到PCI Express端口总线驱动程
序中（见第5.2.1和5.2.2节）。在调用这些API之前，服务驱动程序必须初始化头文件
/include/linux/pcieport_if.h中的pcie_port_service_driver数据结构。如果不这
样做，将导致身份不匹配，从而使PCI Express端口总线驱动程序无法加载服务驱动程序。

pcie_port_service_register
~~~~~~~~~~~~~~~~~~~~~~~~~~
::

  int pcie_port_service_register(struct pcie_port_service_driver *new)

这个API取代了Linux驱动模型的 pci_register_driver API。一个服务驱动应该总是在模
块启动时调用 pcie_port_service_register。请注意，在服务驱动被加载后，诸如
pci_enable_device(dev) 和 pci_set_master(dev) 的调用不再需要，因为这些调用由
PCI端口总线驱动执行。

pcie_port_service_unregister
~~~~~~~~~~~~~~~~~~~~~~~~~~~~
::

  void pcie_port_service_unregister(struct pcie_port_service_driver *new)

pcie_port_service_unregister取代了Linux驱动模型的pci_unregister_driver。当一
个模块退出时，它总是被服务驱动调用。

示例代码
~~~~~~~~

下面是服务驱动代码示例，用于初始化端口服务的驱动程序数据结构。
::

  static struct pcie_port_service_id service_id[] = { {
    .vendor = PCI_ANY_ID,
    .device = PCI_ANY_ID,
    .port_type = PCIE_RC_PORT,
    .service_type = PCIE_PORT_SERVICE_AER,
    }, { /* end: all zeroes */ }
  };

  static struct pcie_port_service_driver root_aerdrv = {
    .name		= (char *)device_name,
    .id_table	= &service_id[0],

    .probe		= aerdrv_load,
    .remove		= aerdrv_unload,

    .suspend	= aerdrv_suspend,
    .resume		= aerdrv_resume,
  };

下面是一个注册/取消注册服务驱动的示例代码。
::

  static int __init aerdrv_service_init(void)
  {
    int retval = 0;

    retval = pcie_port_service_register(&root_aerdrv);
    if (!retval) {
      /*
      * FIX ME
      */
    }
    return retval;
  }

  static void __exit aerdrv_service_exit(void)
  {
    pcie_port_service_unregister(&root_aerdrv);
  }

  module_init(aerdrv_service_init);
  module_exit(aerdrv_service_exit);

可能的资源冲突
==============

由于PCI-PCI桥接端口设备的所有服务驱动被允许同时运行，下面列出了一些可能的资源冲突和
建议的解决方案。

MSI 和 MSI-X 向量资源
---------------------

一旦设备上的MSI或MSI-X中断被启用，它就会一直保持这种模式，直到它们再次被禁用。由于同
一个PCI-PCI桥接端口的服务驱动程序共享同一个物理设备，如果一个单独的服务驱动程序启用或
禁用MSI/MSI-X模式，可能会导致不可预知的行为。

为了避免这种情况，所有的服务驱动程序都不允许在其设备上切换中断模式。PCI Express端口
总线驱动程序负责确定中断模式，这对服务驱动程序来说应该是透明的。服务驱动程序只需要知道
分配给结构体pcie_device的字段irq的向量IRQ，当PCI Express端口总线驱动程序探测每
个服务驱动程序时，它被传入。服务驱动应该使用（struct pcie_device*）dev->irq来调用
request_irq/free_irq。此外，中断模式被存储在struct pcie_device的interrupt_mode
字段中。

PCI内存/IO映射的区域
--------------------

PCI Express电源管理（PME）、高级错误报告（AER）、热插拔（HP）和虚拟通道（VC）的服务
驱动程序访问PCI Express端口的PCI配置空间。在所有情况下，访问的寄存器是相互独立的。这
个补丁假定所有的服务驱动程序都会表现良好，不会覆盖其他服务驱动程序的配置设置。

PCI配置寄存器
-------------

每个服务驱动都在自己的功能结构体上运行PCI配置操作，除了PCI Express功能结构体，其中根控制
寄存器和设备控制寄存器是在PME和AER之间共享。这个补丁假定所有的服务驱动都会表现良好，不会
覆盖其他服务驱动的配置设置。


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

:Original: Documentation/PCI/sysfs-pci.rst

:翻译:

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

:校译:

========================
通过sysfs访问PCI设备资源
========================

sysfs，通常挂载在/sys，在支持它的平台上提供对PCI资源的访问。例如，一个特定的总线可能看起
来像这样::

     /sys/devices/pci0000:17
     |-- 0000:17:00.0
     |   |-- class
     |   |-- config
     |   |-- device
     |   |-- enable
     |   |-- irq
     |   |-- local_cpus
     |   |-- remove
     |   |-- resource
     |   |-- resource0
     |   |-- resource1
     |   |-- resource2
     |   |-- revision
     |   |-- rom
     |   |-- subsystem_device
     |   |-- subsystem_vendor
     |   `-- vendor
     `-- ...

最上面的元素描述了PCI域和总线号码。在这种情况下，域号是0000，总线号是17（两个值都是十六进制）。
这个总线在0号插槽中包含一个单一功能的设备。为了方便起见，我们复制了域和总线的编号。在设备目录
下有几个文件，每个文件都有自己的功能。

       =================== =====================================================
       文件		           功能
       =================== =====================================================
       class		       PCI级别 (ascii, ro)
       config		       PCI配置空间 (binary, rw)
       device		       PCI设备 (ascii, ro)
       enable	           设备是否被启用 (ascii, rw)
       irq		           IRQ编号 (ascii, ro)
       local_cpus	       临近CPU掩码(cpumask, ro)
       remove		       从内核的列表中删除设备 (ascii, wo)
       resource		       PCI资源主机地址 (ascii, ro)
       resource0..N	       PCI资源N，如果存在的话 (binary, mmap, rw\ [1]_)
       resource0_wc..N_wc  PCI WC映射资源N，如果可预取的话 (binary, mmap)
       revision		       PCI修订版 (ascii, ro)
       rom		           PCI ROM资源，如果存在的话 (binary, ro)
       subsystem_device	   PCI子系统设备 (ascii, ro)
       subsystem_vendor	   PCI子系统供应商 (ascii, ro)
       vendor		       PCI供应商 (ascii, ro)
       =================== =====================================================

::

  ro - 只读文件
  rw - 文件是可读和可写的
  wo - 只写文件
  mmap - 文件是可移动的
  ascii - 文件包含ascii文本
  binary - 文件包含二进制数据
  cpumask - 文件包含一个cpumask类型的

.. [1] rw 仅适用于 IORESOURCE_IO（I/O 端口）区域

只读文件是信息性的，对它们的写入将被忽略，但 "rom "文件除外。可写文件可以用来在设备上执
行操作（例如，改变配置空间，分离设备）。 mmapable文件可以通过偏移量为0的文件的mmap获得，
可以用来从用户空间进行实际的设备编程。注意，有些平台不支持某些资源的mmapping，所以一定要
检查任何尝试的mmap的返回值。其中最值得注意的是I/O端口资源，它也提供读/写访问。

enable "文件提供了一个计数器，表明设备已经被启用了多少次。如果'enable'文件目前返回'4'，
而一个'1'被呼入它，它将返回'5'。向它呼入一个'0'会减少计数。不过，即使它返回到0，一些初始
化可能也不会被逆转。

rom "文件很特别，因为它提供了对设备ROM文件的只读访问，如果有的话。然而，它在默认情况下是
禁用的，所以应用程序应该在尝试读取调用之前将字符串 "1 "写入该文件以启用它，并在访问之后将
"0 "写入该文件以禁用它。请注意，设备必须被启用，才能成功返回数据。如果驱动没有被绑定到设备
上，可以使用上面提到的 "enable "文件将其启用。

remove "文件是用来移除PCI设备的，通过向该文件写入一个非零的整数。这并不涉及任何形式的热插
拔功能，例如关闭设备的电源。该设备被从内核的PCI设备列表中移除，它的sysfs目录被移除，并且该
设备将被从任何连接到它的驱动程序中移除。移除PCI根总线是不允许的。

通过sysfs访问原有资源
---------------------

如果底层平台支持的话，传统的I/O端口和ISA内存资源也会在sysfs中提供。它们位于PCI类的层次结构
中，例如::

	/sys/class/pci_bus/0000:17/
	|-- bridge -> ../../../devices/pci0000:17
	|-- cpuaffinity
	|-- legacy_io
	`-- legacy_mem

legacy_io文件是一个读/写文件，可以被应用程序用来做传统的端口I/O。应用程序应该打开该文件，寻
找所需的端口（例如0x3e8），并进行1、2或4字节的读或写。legacy_mem文件应该被mmapped，其偏移
量与所需的内存偏移量相对应，例如0xa0000用于VGA帧缓冲器。然后，应用程序可以简单地解除引用返回
的指针（当然是在检查了错误之后）来访问遗留内存空间。

支持新平台上的PCI访问
---------------------

为了支持上述的PCI资源映射，Linux平台代码最好定义ARCH_GENERIC_PCI_MMAP_RESOURCE并使用该
功能的通用实现。为了支持通过/proc/bus/pci中的文件实现mmap()的历史接口，平台也可以设置
HAVE_PCI_MMAP。

另外，设置了 HAVE_PCI_MMAP 的平台可以提供他们自己的 pci_mmap_page_range() 实现，而不是定
义 ARCH_GENERIC_PCI_MMAP_RESOURCE。

支持PCI资源的写组合映射的平台必须定义arch_can_pci_mmap_wc()，当写组合被允许时，在运行时应
评估为非零。支持I/O资源映射的平台同样定义arch_can_pci_mmap_io()。

遗留资源由HAVE_PCI_LEGACY定义保护。希望支持遗留功能的平台应该定义它并提供 pci_legacy_read,
pci_legacy_write 和 pci_mmap_legacy_page_range 函数。


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

:Original: Documentation/accounting/delay-accounting.rst

:Translator: Yang Yang <yang.yang29@zte.com.cn>

========
延时计数
========

任务在等待某些内核资源可用时，会造成延时。例如一个可运行的任务可能会等待
一个空闲CPU来运行。

基于每任务的延时计数功能度量由以下情况造成的任务延时：

a) 等待一个CPU（任务为可运行）
b) 完成由该任务发起的块I/O同步请求
c) 页面交换
d) 内存回收
e) 页缓存抖动
f) 直接规整

并将这些统计信息通过taskstats接口提供给用户空间。

这些延时信息为适当的调整任务CPU优先级、io优先级、rss限制提供反馈。重要任务
长期延时，表示可能需要提高其相关优先级。

通过使用taskstats接口，本功能还可提供一个线程组（对应传统Unix进程）所有任务
（或线程）的总延时统计信息。此类汇总往往是需要的，由内核来完成更加高效。

用户空间的实体，特别是资源管理程序，可将延时统计信息汇总到任意组中。为实现
这一点，任务的延时统计信息在其生命周期内和退出时皆可获取，从而确保可进行
连续、完整的监控。

接口
----

延时计数使用taskstats接口，该接口由本目录另一个单独的文档详细描述。Taskstats
向用户态返回一个通用数据结构，对应每pid或每tgid的统计信息。延时计数功能填写
该数据结构的特定字段。见

     include/uapi/linux/taskstats.h

其描述了延时计数相关字段。系统通常以计数器形式返回 CPU、同步块 I/O、交换、内存
回收、页缓存抖动、直接规整等的累积延时。

取任务某计数器两个连续读数的差值，将得到任务在该时间间隔内等待对应资源的总延时。

当任务退出时，内核会将包含每任务的统计信息发送给用户空间，而无需额外的命令。
若其为线程组最后一个退出的任务，内核还会发送每tgid的统计信息。更多详细信息见
taskstats接口的描述。

tools/accounting目录中的用户空间程序getdelays.c提供了一些简单的命令，用以显示
延时统计信息。其也是使用taskstats接口的示例。

用法
----

使用以下配置编译内核::

	CONFIG_TASK_DELAY_ACCT=y
	CONFIG_TASKSTATS=y

延时计数在启动时默认关闭。
若需开启，在启动参数中增加::

   delayacct

本文后续的说明基于延时计数已开启。也可在系统运行时，使用sysctl的
kernel.task_delayacct进行开关。注意，只有在启用延时计数后启动的
任务才会有相关信息。

系统启动后，使用类似getdelays.c的工具获取任务或线程组（tgid）的延时信息。

getdelays命令的一般格式::

	getdelays [-dilv] [-t tgid] [-p pid]

获取pid为10的任务从系统启动后的延时信息::

	# ./getdelays -d -p 10
	（输出信息和下例相似）

获取所有tgid为5的任务从系统启动后的总延时信息::

	# ./getdelays -d -t 5
	print delayacct stats ON
	TGID	5

	CPU             count     real total  virtual total    delay total  delay average
	                    8        7000000        6872122        3382277          0.423ms
	IO              count    delay total  delay average
	                    0              0              0ms
	SWAP            count    delay total  delay average
	                    0              0              0ms
	RECLAIM         count    delay total  delay average
	                    0              0              0ms
	THRASHING       count    delay total  delay average
	                    0              0              0ms
	COMPACT         count    delay total  delay average
	                    0              0              0ms

获取pid为1的IO计数，它只和-p一起使用::
	# ./getdelays -i -p 1
	printing IO accounting
	linuxrc: read=65536, write=0, cancelled_write=0

上面的命令与-v一起使用，可以获取更多调试信息。


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

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

:Original: Documentation/accounting/index.rst
:Translator: Yang Yang <yang.yang29@zte.com.cn>

.. _cn_accounting_index.rst:

====
计数
====

.. toctree::
   :maxdepth: 1

   delay-accounting
   psi
   taskstats

Todolist:

   cgroupstats
   taskstats-struct


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

:Original: Documentation/accounting/psi.rst
:Translator: Yang Yang <yang.yang29@zte.com.cn>

.. _cn_psi.rst:

=================
PSI——压力阻塞信息
=================

:日期: April, 2018
:作者: Johannes Weiner <hannes@cmpxchg.org>

当CPU、memory或IO设备处于竞争状态，业务负载会遭受时延毛刺、吞吐量降低，
及面临OOM的风险。

如果没有一种准确的方法度量系统竞争程度，则有两种后果：一种是用户过于节制，
未充分利用系统资源；另一种是过度使用，经常性面临业务中断的风险。

psi特性能够识别和量化资源竞争导致的业务中断，及其对复杂负载乃至整个系统在
时间上的影响。

准确度量因资源不足造成的生产力损失，有助于用户基于硬件调整业务负载，或基
于业务负载配置硬件。

psi能够实时的提供相关信息，因此系统可基于psi实现动态的负载管理。如实施
卸载、迁移、策略性的停止或杀死低优先级或可重启的批处理任务。

psi帮助用户实现硬件资源利用率的最大化。同时无需牺牲业务负载健康度，也无需
面临OOM等造成业务中断的风险。

压力接口
========

压力信息可通过/proc/pressure/ --cpu、memory、io文件分别获取。

CPU相关信息格式如下：

        some avg10=0.00 avg60=0.00 avg300=0.00 total=0

内存和IO相关信息如下：

        some avg10=0.00 avg60=0.00 avg300=0.00 total=0
        full avg10=0.00 avg60=0.00 avg300=0.00 total=0

some行代表至少有一个任务阻塞于特定资源的时间占比。

full行代表所有非idle任务同时阻塞于特定资源的时间占比。在这种状态下CPU资源
完全被浪费，相对于正常运行，业务负载由于耗费更多时间等待而受到严重影响。

由于此情况严重影响系统性能，因此清楚的识别本情况并与some行所代表的情况区分开，
将有助于分析及提升系统性能。这就是full独立于some行的原因。

avg代表阻塞时间占比（百分比），为最近10秒、60秒、300秒内的均值。这样我们
既可观察到短期事件的影响，也可看到中等及长时间内的趋势。total代表总阻塞
时间（单位微秒），可用于观察时延毛刺，这种毛刺可能在均值中无法体现。

监控压力门限
============

用户可注册触发器，通过poll()监控资源压力是否超过门限。

触发器定义：指定时间窗口期内累积阻塞时间的最大值。比如可定义500ms内积累
100ms阻塞，即触发一次唤醒事件。

触发器注册方法：用户打开代表特定资源的psi接口文件，写入门限、时间窗口的值。
所打开的文件描述符用于等待事件，可使用select()、poll()、epoll()。
写入信息的格式如下：

        <some|full> <stall amount in us> <time window in us>

示例：向/proc/pressure/memory写入"some 150000 1000000"将新增触发器，将在
1秒内至少一个任务阻塞于内存的总时间超过150ms时触发。向/proc/pressure/io写入
"full 50000 1000000"将新增触发器，将在1秒内所有任务都阻塞于io的总时间超过50ms时触发。

触发器可针对多个psi度量值设置，同一个psi度量值可设置多个触发器。每个触发器需要
单独的文件描述符用于轮询，以区分于其他触发器。所以即使对于同一个psi接口文件，
每个触发器也需要单独的调用open()。

监控器在被监控资源进入阻塞状态时启动，在系统退出阻塞状态后停用。系统进入阻塞
状态后，监控psi增长的频率为每监控窗口刷新10次。

内核接受的窗口为500ms~10s，所以监控间隔为50ms~1s。设置窗口下限目的是为了
防止过于频繁的轮询。设置窗口上限的目的是因为窗口过长则无意义，此时查看
psi接口提供的均值即可。

监控器在激活后，至少在跟踪窗口期间将保持活动状态。以避免随着系统进入和退出
阻塞状态，监控器过于频繁的进入和退出活动状态。

用户态通知在监控窗口内会受到速率限制。当对应的文件描述符关闭，触发器会自动注销。

用户态监控器使用示例
====================

::

  #include <errno.h>
  #include <fcntl.h>
  #include <stdio.h>
  #include <poll.h>
  #include <string.h>
  #include <unistd.h>

  /* 监控内存部分阻塞，监控时间窗口为1秒、阻塞门限为150毫秒。*/
  int main() {
        const char trig[] = "some 150000 1000000";
        struct pollfd fds;
        int n;

        fds.fd = open("/proc/pressure/memory", O_RDWR | O_NONBLOCK);
        if (fds.fd < 0) {
                printf("/proc/pressure/memory open error: %s\n",
                        strerror(errno));
                return 1;
        }
        fds.events = POLLPRI;

        if (write(fds.fd, trig, strlen(trig) + 1) < 0) {
                printf("/proc/pressure/memory write error: %s\n",
                        strerror(errno));
                return 1;
        }

        printf("waiting for events...\n");
        while (1) {
                n = poll(&fds, 1, -1);
                if (n < 0) {
                        printf("poll error: %s\n", strerror(errno));
                        return 1;
                }
                if (fds.revents & POLLERR) {
                        printf("got POLLERR, event source is gone\n");
                        return 0;
                }
                if (fds.revents & POLLPRI) {
                        printf("event triggered!\n");
                } else {
                        printf("unknown event received: 0x%x\n", fds.revents);
                        return 1;
                }
        }

        return 0;
  }

Cgroup2接口
===========

对于CONFIG_CGROUP=y及挂载了cgroup2文件系统的系统，能够获取cgroups内任务的psi。
此场景下cgroupfs挂载点的子目录包含cpu.pressure、memory.pressure、io.pressure文件，
内容格式与/proc/pressure/下的文件相同。

可设置基于cgroup的psi监控器，方法与系统级psi监控器相同。


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

:Original: Documentation/accounting/taskstats.rst

:Translator: Yang Yang <yang.yang29@zte.com.cn>

================
每任务的统计接口
================

Taskstats是一个基于netlink的接口，用于从内核向用户空间发送每任务及每进程的
统计信息。

Taskstats设计目的：

- 在任务生命周期内和退出时高效的提供统计信息
- 统一不同计数子系统的接口
- 支持未来计数系统的扩展

术语
----

“pid”、“tid”、“任务”互换使用，用于描述由struct task_struct定义的标准
Linux任务。“每pid的统计数据”等价于“每任务的统计数据”。

“tgid”、“进程”、“线程组”互换使用，用于描述共享mm_struct的任务集，
也就是传统的Unix进程。尽管使用了tgid这个词，即使一个任务是线程组组长，
对它的处理也没有什么不同。只要一个进程还有任何归属它的任务，它就被认为
活着。

用法
----

为了在任务生命周期内获得统计信息，用户空间需打开一个单播的netlink套接字
（NETLINK_GENERIC族）然后发送指定pid或tgid的命令。响应消息中包含单个
任务的统计信息（若指定了pid）或进程所有任务汇总的统计信息（若指定了tgid）。

为了在任务退出时获取统计信息，用户空间的监听者发送一个指定cpu掩码的注册命令。
cpu掩码内的cpu上有任务退出时，每pid的统计信息将发送给注册成功的监听者。使用
cpu掩码可以限制一个监听者收到的数据，并有助于对netlink接口进行流量控制，后文
将进行更详细的解释。

如果正在退出的任务是线程组中最后一个退出的线程，额外一条包含每tgid统计信息的
记录也将发送给用户空间。后者包含线程组中所有线程（包括过去和现在）的每pid统计
信息总和。

getdelays.c是一个简单的示例，用以演示如何使用taskstats接口获取延迟统计信息。
用户可注册cpu掩码、发送命令和处理响应、监听每tid/tgid退出数据、将收到的数据
写入文件、通过增大接收缓冲区进行基本的流量控制。

接口
----

内核用户接口封装在include/linux/taskstats.h。

为避免本文档随着接口的演进而过期，本文仅给出当前版本的概要。当本文与taskstats.h
不一致时，以taskstats.h为准。

struct taskstats是每pid和每tgid数据共用的计数结构体。它是版本化的，可在内核新增
计数子系统时进行扩展。taskstats.h中定义了各字段及语义。

用户、内核空间的数据交换是属于NETLINK_GENERIC族的netlink消息，使用netlink属性
接口。消息格式如下::

    +----------+- - -+-------------+-------------------+
    | nlmsghdr | Pad |  genlmsghdr | taskstats payload |
    +----------+- - -+-------------+-------------------+

Taskstats载荷有三种类型：

1. 命令：由用户发送给内核。获取指定pid/tgid数据的命令包含一个类型为
TASKSTATS_CMD_ATTR_PID/TGID的属性，该属性包含u32的pid或tgid载荷。
pid/tgid指示用户空间要统计的任务/进程。

注册/注销获取指定cpu集上退出数据的命令包含一个类型为
TASKSTATS_CMD_ATTR_REGISTER/DEREGISTER_CPUMASK的属性，该属性包含cpu掩码载荷。
cpu掩码是以ascii码表示，用逗号分隔的cpu范围。例如若需监听1,2,3,5,7,8号cpu的
退出数据，cpu掩码表示为"1-3,5,7-8"。若用户空间在关闭监听套接字前忘了注销监听
的cpu集，随着时间的推移，内核会清理此监听集。但是，出于提效的目的，建议明确
执行注销。

2. 命令的应答：内核发出应答用户空间的命令。载荷有三类属性：

a) TASKSTATS_TYPE_AGGR_PID/TGID： 本属性不包含载荷，用以指示其后为被统计对象
的pig/tgid。

b) TASKSTATS_TYPE_PID/TGID：本属性的载荷为pig/tgid，其统计信息将被返回。

c) TASKSTATS_TYPE_STATS：本属性的载荷为一个struct taskstats实例。每pid和
每tgid统计信息共用该结构体。

3. 内核会在任务退出时发送新消息。其载荷包含一系列以下类型的属性：

a) TASKSTATS_TYPE_AGGR_PID：指示其后两个属性为pid+stats。
b) TASKSTATS_TYPE_PID：包含退出任务的pid。
c) TASKSTATS_TYPE_STATS：包含退出任务的每pid统计信息
d) TASKSTATS_TYPE_AGGR_TGID：指示其后两个属性为tgid+stats。
e) TASKSTATS_TYPE_TGID：包含任务所属进程的tgid
f) TASKSTATS_TYPE_STATS：包含退出任务所属进程的每tgid统计信息

每tgid的统计
------------

除了每任务的统计信息，taskstats还提供每进程的统计信息，因为资源管理通常以进程
粒度完成，并且仅在用户空间聚合任务统计信息效率低下且可能不准确（缺乏原子性）。

然而，除了每任务统计信息，在内核中维护每进程统计信息存在额外的时间和空间开销。
为解决此问题，taskstats代码将退出任务的统计信息累积到进程范围的数据结构中。
当进程最后一个任务退出时，累积的进程级数据也会发送到用户空间（与每任务数据一起）。

当用户查询每tgid数据时，内核将指定线程组中所有活动线程的统计信息相加，并添加到
该线程组的累积总数（含之前退出的线程）。

扩展taskstats
-------------

有两种方法可在未来修改内核扩展taskstats接口，以导出更多的每任务/进程统计信息：

1. 在现有struct taskstats末尾增加字段。该结构体中的版本号确保了向后兼容性。
用户空间将仅使用与其版本对应的结构体字段。

2. 定义单独的统计结构体并使用netlink属性接口返回对应的数据。由于用户空间独立
处理每个netlink属性，所以总是可以忽略其不理解类型的属性（因为使用了旧版本接口）。

在1.和2.之间进行选择，属于权衡灵活性和开销的问题。若仅需增加少数字段，那么1.是
首选方法，因为内核和用户空间无需承担处理新netlink属性的开销。但若新字段过多的
扩展现有结构体，导致不同的用户空间计数程序不必要的接收大型结构体，而对结构体
字段并不感兴趣，那么2.是值得的。

Taskstats的流量控制
-------------------

当退出任务数速率变大，监听者可能跟不上内核发送每tid/tgid退出数据的速率，而导致
数据丢失。taskstats结构体变大、cpu数量上升，都会导致这种可能性增加。

为避免统计信息丢失，用户空间应执行以下操作中至少一项：

- 增大监听者用于接收退出数据的netlink套接字接收缓存区。

- 创建更多的监听者，减少每个监听者监听的cpu数量。极端情况下可为每个cpu创建
  一个监听者。用户还可考虑将监听者的cpu亲和性设置为监听cpu的子集，特别是当他们
  仅监听一个cpu。

尽管采取了这些措施，若用户空间仍收到指示接收缓存区溢出的ENOBUFS错误消息，
则应采取其他措施处理数据丢失。


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

:Original: Documentation/admin-guide/README.rst

:译者:

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

Linux内核6.x版本 <http://kernel.org/>
=========================================

以下是Linux版本6的发行注记。仔细阅读它们，
它们会告诉你这些都是什么，解释如何安装内核，以及遇到问题时该如何做。

什么是Linux？
---------------

  Linux是Unix操作系统的克隆版本，由Linus Torvalds在一个松散的网络黑客
  （Hacker，无贬义）团队的帮助下从头开始编写。它旨在实现兼容POSIX和
  单一UNIX规范。

  它具有在现代成熟的Unix中应当具有的所有功能，包括真正的多任务处理、虚拟内存、
  共享库、按需加载、共享的写时拷贝（COW）可执行文件、恰当的内存管理以及包括
  IPv4和IPv6在内的复合网络栈。

  Linux在GNU通用公共许可证，版本2（GNU GPLv2）下分发，详见随附的COPYING文件。

它能在什么样的硬件上运行？
-----------------------------

  虽然Linux最初是为32位的x86 PC机（386或更高版本）开发的，但今天它也能运行在
  （至少）Compaq Alpha AXP、Sun SPARC与UltraSPARC、Motorola 68000、PowerPC、
  PowerPC64、ARM、Hitachi SuperH、Cell、IBM S/390、MIPS、HP PA-RISC、Intel
  IA-64、DEC VAX、AMD x86-64 Xtensa和ARC架构上。

  Linux很容易移植到大多数通用的32位或64位体系架构，只要它们有一个分页内存管理
  单元（PMMU）和一个移植的GNU C编译器（gcc；GNU Compiler Collection，GCC的一
  部分）。Linux也被移植到许多没有PMMU的体系架构中，尽管功能显然受到了一定的
  限制。
  Linux也被移植到了其自己上。现在可以将内核作为用户空间应用程序运行——这被
  称为用户模式Linux（UML）。

文档
-----
因特网上和书籍上都有大量的电子文档，既有Linux专属文档，也有与一般UNIX问题相关
的文档。我建议在任何Linux FTP站点上查找LDP（Linux文档项目）书籍的文档子目录。
本自述文件并不是关于系统的文档：有更好的可用资源。

 - 因特网上和书籍上都有大量的（电子）文档，既有Linux专属文档，也有与普通
   UNIX问题相关的文档。我建议在任何有LDP（Linux文档项目）书籍的Linux FTP
   站点上查找文档子目录。本自述文件并不是关于系统的文档：有更好的可用资源。

 - 文档/子目录中有各种自述文件：例如，这些文件通常包含一些特定驱动程序的
   内核安装说明。请阅读
   :ref:`Documentation/process/changes.rst <changes>` 文件，它包含了升级内核
   可能会导致的问题的相关信息。

安装内核源代码
---------------

 - 如果您要安装完整的源代码，请把内核tar档案包放在您有权限的目录中（例如您
   的主目录）并将其解包::

     xz -cd linux-6.x.tar.xz | tar xvf -

   将“X”替换成最新内核的版本号。

   【不要】使用 /usr/src/linux 目录！这里有一组库头文件使用的内核头文件
   （通常是不完整的）。它们应该与库匹配，而不是被内核的变化搞得一团糟。

 - 您还可以通过打补丁在6.x版本之间升级。补丁以xz格式分发。要通过打补丁进行
   安装，请获取所有较新的补丁文件，进入内核源代码（linux-6.x）的目录并
   执行::

     xz -cd ../patch-6.x.xz | patch -p1

   请【按顺序】替换所有大于当前源代码树版本的“x”，这样就可以了。您可能想要
   删除备份文件（文件名类似xxx~ 或 xxx.orig)，并确保没有失败的补丁（文件名
   类似xxx# 或 xxx.rej）。如果有，不是你就是我犯了错误。

   与6.x内核的补丁不同，6.x.y内核（也称为稳定版内核）的补丁不是增量的，而是
   直接应用于基本的6.x内核。例如，如果您的基本内核是6.0，并且希望应用6.0.3
   补丁，则不应先应用6.0.1和6.0.2的补丁。类似地，如果您运行的是6.0.2内核，
   并且希望跳转到6.0.3，那么在应用6.0.3补丁之前，必须首先撤销6.0.2补丁
   （即patch -R）。更多关于这方面的内容，请阅读
   :ref:`Documentation/process/applying-patches.rst <applying_patches>` 。

   或者，脚本 patch-kernel 可以用来自动化这个过程。它能确定当前内核版本并
   应用找到的所有补丁::

     linux/scripts/patch-kernel linux

   上面命令中的第一个参数是内核源代码的位置。补丁是在当前目录应用的，但是
   可以将另一个目录指定为第二个参数。

 - 确保没有过时的 .o 文件和依赖项::

     cd linux
     make mrproper

   现在您应该已经正确安装了源代码。

软件要求
---------

   编译和运行6.x内核需要各种软件包的最新版本。请参考
   :ref:`Documentation/process/changes.rst <changes>`
   来了解最低版本要求以及如何升级软件包。请注意，使用过旧版本的这些包可能会
   导致很难追踪的间接错误，因此不要以为在生成或操作过程中出现明显问题时可以
   只更新包。

为内核建立目录
---------------

   编译内核时，默认情况下所有输出文件都将与内核源代码放在一起。使用
   ``make O=output/dir`` 选项可以为输出文件（包括 .config）指定备用位置。
   例如::

     kernel source code: /usr/src/linux-6.x
     build directory:    /home/name/build/kernel

   要配置和构建内核，请使用::

     cd /usr/src/linux-6.x
     make O=/home/name/build/kernel menuconfig
     make O=/home/name/build/kernel
     sudo make O=/home/name/build/kernel modules_install install

   请注意：如果使用了 ``O=output/dir`` 选项，那么它必须用于make的所有调用。

配置内核
---------

   即使只升级一个小版本，也不要跳过此步骤。每个版本中都会添加新的配置选项，
   如果配置文件没有按预定设置，就会出现奇怪的问题。如果您想以最少的工作量
   将现有配置升级到新版本，请使用 ``make oldconfig`` ，它只会询问您新配置
   选项的答案。

 - 其他配置命令包括::

     "make config"      纯文本界面。

     "make menuconfig"  基于文本的彩色菜单、选项列表和对话框。

     "make nconfig"     增强的基于文本的彩色菜单。

     "make xconfig"     基于Qt的配置工具。

     "make gconfig"     基于GTK+的配置工具。

     "make oldconfig"   基于现有的 ./.config 文件选择所有选项，并询问
                        新配置选项。

     "make olddefconfig"
                        类似上一个，但不询问直接将新选项设置为默认值。

     "make defconfig"   根据体系架构，使用arch/$arch/defconfig或
                        arch/$arch/configs/${PLATFORM}_defconfig中的
                        默认选项值创建./.config文件。

     "make ${PLATFORM}_defconfig"
                        使用arch/$arch/configs/${PLATFORM}_defconfig中
                        的默认选项值创建一个./.config文件。
                        用“make help”来获取您体系架构中所有可用平台的列表。

     "make allyesconfig"
                        通过尽可能将选项值设置为“y”，创建一个
                        ./.config文件。

     "make allmodconfig"
                        通过尽可能将选项值设置为“m”，创建一个
                        ./.config文件。

     "make allnoconfig" 通过尽可能将选项值设置为“n”，创建一个
                        ./.config文件。

     "make randconfig"  通过随机设置选项值来创建./.config文件。

     "make localmodconfig" 基于当前配置和加载的模块（lsmod）创建配置。禁用
                           已加载的模块不需要的任何模块选项。

                           要为另一台计算机创建localmodconfig，请将该计算机
                           的lsmod存储到一个文件中，并将其作为lsmod参数传入。

                           此外，通过在参数LMC_KEEP中指定模块的路径，可以将
                           模块保留在某些文件夹或kconfig文件中。

                   target$ lsmod > /tmp/mylsmod
                   target$ scp /tmp/mylsmod host:/tmp

                   host$ make LSMOD=/tmp/mylsmod \
                           LMC_KEEP="drivers/usb:drivers/gpu:fs" \
                           localmodconfig

                           上述方法在交叉编译时也适用。

     "make localyesconfig" 与localmodconfig类似，只是它会将所有模块选项转换
                           为内置（=y）。你可以同时通过LMC_KEEP保留模块。

     "make kvm_guest.config"
                        为kvm客户机内核支持启用其他选项。

     "make xen.config"  为xen dom0客户机内核支持启用其他选项。

     "make tinyconfig"  配置尽可能小的内核。

   更多关于使用Linux内核配置工具的信息，见文档
   Documentation/kbuild/kconfig.rst。

 - ``make config`` 注意事项:

    - 包含不必要的驱动程序会使内核变大，并且在某些情况下会导致问题：
      探测不存在的控制器卡可能会混淆其他控制器。

    - 如果存在协处理器，则编译了数学仿真的内核仍将使用协处理器：在
      这种情况下，数学仿真永远不会被使用。内核会稍微大一点，但不管
      是否有数学协处理器，都可以在不同的机器上工作。

    - “kernel hacking”配置细节通常会导致更大或更慢的内核（或两者
      兼而有之），甚至可以通过配置一些例程来主动尝试破坏坏代码以发现
      内核问题，从而降低内核的稳定性（kmalloc()）。因此，您可能应该
      用于研究“开发”、“实验”或“调试”特性相关问题。

编译内核
---------

 - 确保您至少有gcc 5.1可用。
   有关更多信息，请参阅 :ref:`Documentation/process/changes.rst <changes>` 。

 - 执行 ``make`` 来创建压缩内核映像。如果您安装了lilo以适配内核makefile，
   那么也可以进行 ``make install`` ，但是您可能需要先检查特定的lilo设置。

   实际安装必须以root身份执行，但任何正常构建都不需要。
   无须徒然使用root身份。

 - 如果您将内核的任何部分配置为模块，那么还必须执行 ``make modules_install`` 。

 - 详细的内核编译/生成输出：

   通常，内核构建系统在相当安静的模式下运行（但不是完全安静）。但是有时您或
   其他内核开发人员需要看到编译、链接或其他命令的执行过程。为此，可使用
   “verbose（详细）”构建模式。
   向 ``make`` 命令传递 ``V=1`` 来实现，例如::

     make V=1 all

   如需构建系统也给出内个目标重建的愿意，请使用 ``V=2`` 。默认为 ``V=0`` 。

 - 准备一个备份内核以防出错。对于开发版本尤其如此，因为每个新版本都包含
   尚未调试的新代码。也要确保保留与该内核对应的模块的备份。如果要安装
   与工作内核版本号相同的新内核，请在进行 ``make modules_install`` 安装
   之前备份modules目录。

   或者，在编译之前，使用内核配置选项“LOCALVERSION”向常规内核版本附加
   一个唯一的后缀。LOCALVERSION可以在“General Setup”菜单中设置。

 - 为了引导新内核，您需要将内核映像（例如编译后的
   .../linux/arch/x86/boot/bzImage）复制到常规可引导内核的位置。

 - 不再支持在没有LILO等启动装载程序帮助的情况下直接从软盘引导内核。

   如果从硬盘引导Linux，很可能使用LILO，它使用/etc/lilo.conf文件中
   指定的内核映像文件。内核映像文件通常是/vmlinuz、/boot/vmlinuz、
   /bzImage或/boot/bzImage。使用新内核前，请保存旧映像的副本，并复制
   新映像覆盖旧映像。然后您【必须重新运行LILO】来更新加载映射！否则，
   将无法启动新的内核映像。

   重新安装LILO通常需要运行/sbin/LILO。您可能希望编辑/etc/lilo.conf
   文件为旧内核映像指定一个条目（例如/vmlinux.old)防止新的不能正常
   工作。有关更多信息，请参阅LILO文档。

   重新安装LILO之后，您应该就已经准备好了。关闭系统，重新启动，尽情
   享受吧！

   如果需要更改内核映像中的默认根设备、视频模式等，请在适当的地方使用
   启动装载程序的引导选项。无需重新编译内核即可更改这些参数。

 - 使用新内核重新启动并享受它吧。

若遇到问题
-----------

如果您发现了一些可能由于内核缺陷所导致的问题，请参阅：
Documentation/translations/zh_CN/admin-guide/reporting-issues.rst 。

想要理解内核错误报告，请参阅：
Documentation/translations/zh_CN/admin-guide/bug-hunting.rst 。

更多用GDB调试内核的信息，请参阅：
Documentation/translations/zh_CN/dev-tools/gdb-kernel-debugging.rst
和 Documentation/dev-tools/kgdb.rst 。


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

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

:Original: Documentation/admin-guide/bootconfig.rst

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

========
引导配置
========

:作者: Masami Hiramatsu <mhiramat@kernel.org>

概述
====

引导配置扩展了现有的内核命令行，以一种更有效率的方式在引导内核时进一步支持
键值数据。这允许管理员传递一份结构化关键字的配置文件。

配置文件语法
============

引导配置文件的语法采用非常简单的键值结构。每个关键字由点连接的单词组成，键
和值由 ``=`` 连接。值以分号（ ``;`` ）或换行符（ ``\n`` ）结尾。数组值中每
个元素由逗号（ ``,`` ）分隔。::

  KEY[.WORD[...]] = VALUE[, VALUE2[...]][;]

与内核命令行语法不同，逗号和 ``=`` 周围允许有空格。

关键字只允许包含字母、数字、连字符（ ``-`` ）和下划线（ ``_`` ）。值可包含
可打印字符和空格，但分号（ ``;`` ）、换行符（ ``\n`` ）、逗号（ ``,`` ）、
井号（ ``#`` ）和右大括号（ ``}`` ）等分隔符除外。

如果你需要在值中使用这些分隔符，可以用双引号（ ``"VALUE"`` ）或单引号
（ ``'VALUE'`` ）括起来。注意，引号无法转义。

键的值可以为空或不存在。这些键用于检查该键是否存在（类似布尔值）。

键值语法
--------

引导配置文件语法允许用户通过大括号合并键名部分相同的关键字。例如::

 foo.bar.baz = value1
 foo.bar.qux.quux = value2

也可以写成::

 foo.bar {
    baz = value1
    qux.quux = value2
 }

或者更紧凑一些，写成::

 foo.bar { baz = value1; qux.quux = value2 }

在这两种样式中，引导解析时相同的关键字都会自动合并。因此可以追加类似的树或
键值。

相同关键字的值
--------------

禁止两个或多个值或数组共享同一个关键字。例如::

 foo = bar, baz
 foo = qux  # !错误! 我们不可以重定义相同的关键字

如果你想要更新值，必须显式使用覆盖操作符 ``:=`` 。例如::

 foo = bar, baz
 foo := qux

这样 ``foo`` 关键字的值就变成了 ``qux`` 。这对于通过添加（部分）自定义引导
配置来覆盖默认值非常有用，免于解析默认引导配置。

如果你想对现有关键字追加值作为数组成员，可以使用 ``+=`` 操作符。例如::

 foo = bar, baz
 foo += qux

这样， ``foo`` 关键字就同时拥有了 ``bar`` ， ``baz`` 和 ``qux`` 。

此外，父关键字下可同时存在值和子关键字。
例如，下列配置是可行的。::

 foo = value1
 foo.bar = value2
 foo := value3 # 这会更新foo的值。

注意，裸值不能直接放进结构化关键字中，必须在大括号外定义它。例如::

 foo {
     bar = value1
     bar {
         baz = value2
         qux = value3
     }
 }

同时，关键字下值节点的顺序是固定的。如果值和子关键字同时存在，值永远是该关
键字的第一个子节点。因此如果用户先指定子关键字，如::

 foo.bar = value1
 foo = value2

则在程序（和/proc/bootconfig）中，它会按如下显示::

 foo = value2
 foo.bar = value1

注释
----

配置语法接受shell脚本风格的注释。注释以井号（ ``#`` ）开始，到换行符
（ ``\n`` ）结束。

::

 # comment line
 foo = value # value is set to foo.
 bar = 1, # 1st element
       2, # 2nd element
       3  # 3rd element

会被解析为::

 foo = value
 bar = 1, 2, 3

注意你不能把注释放在值和分隔符（ ``,`` 或 ``;`` ）之间。如下配置语法是错误的::

 key = 1 # comment
       ,2

/proc/bootconfig
================

/proc/bootconfig是引导配置的用户空间接口。与/proc/cmdline不同，此文件内容以
键值列表样式显示。
每个键值对一行，样式如下::

 KEY[.WORDS...] = "[VALUE]"[,"VALUE2"...]

用引导配置引导内核
==================

用引导配置引导内核有两种方法：将引导配置附加到initrd镜像或直接嵌入内核中。

*initrd: initial RAM disk，初始内存磁盘*

将引导配置附加到initrd
----------------------

由于默认情况下引导配置文件是用initrd加载的，因此它将被添加到initrd（initramfs）
镜像文件的末尾，其中包含填充、大小、校验值和12字节幻数，如下所示::

 [initrd][bootconfig][padding][size(le32)][checksum(le32)][#BOOTCONFIG\n]

大小和校验值为小端序存放的32位无符号值。

当引导配置被加到initrd镜像时，整个文件大小会对齐到4字节。空字符（ ``\0`` ）
会填补对齐空隙。因此 ``size`` 就是引导配置文件的长度+填充的字节。

Linux内核在内存中解码initrd镜像的最后部分以获取引导配置数据。由于这种“背负式”
的方法，只要引导加载器传递了正确的initrd文件大小，就无需更改或更新引导加载器
和内核镜像本身。如果引导加载器意外传递了更长的大小，内核将无法找到引导配置数
据。

Linux内核在tools/bootconfig下提供了 ``bootconfig`` 命令来完成此操作，管理员
可以用它从initrd镜像中删除或追加配置文件。你可以用以下命令来构建它::

 # make -C tools/bootconfig

要向initrd镜像添加你的引导配置文件，请按如下命令操作（旧数据会自动移除）::

 # tools/bootconfig/bootconfig -a your-config /boot/initrd.img-X.Y.Z

要从镜像中移除配置，可以使用-d选项::

 # tools/bootconfig/bootconfig -d /boot/initrd.img-X.Y.Z

然后在内核命令行上添加 ``bootconfig`` 告诉内核去initrd文件末尾寻找内核配置。

将引导配置嵌入内核
------------------

如果你不能使用initrd，也可以通过Kconfig选项将引导配置文件嵌入内核中。在此情
况下，你需要用以下选项重新编译内核::

 CONFIG_BOOT_CONFIG_EMBED=y
 CONFIG_BOOT_CONFIG_EMBED_FILE="/引导配置/文件/的/路径"

``CONFIG_BOOT_CONFIG_EMBED_FILE`` 需要从源码树或对象树开始的引导配置文件的
绝对/相对路径。内核会将其嵌入作为默认引导配置。

与将引导配置附加到initrd一样，你也需要在内核命令行上添加 ``bootconfig`` 告诉
内核去启用内嵌的引导配置。

注意，即使你已经设置了此选项，仍可用附加到initrd的其他引导配置覆盖内嵌的引导
配置。

通过引导配置传递内核参数
========================

除了内核命令行，引导配置也可以用于传递内核参数。所有 ``kernel`` 关键字下的键
值对都将直接传递给内核命令行。此外， ``init`` 下的键值对将通过命令行传递给
init进程。参数按以下顺序与用户给定的内核命令行字符串相连，因此命令行参数可以
覆盖引导配置参数（这取决于子系统如何处理参数，但通常前面的参数将被后面的参数
覆盖）::

 [bootconfig params][cmdline params] -- [bootconfig init params][cmdline init params]

如果引导配置文件给出的kernel/init参数是::

 kernel {
   root = 01234567-89ab-cdef-0123-456789abcd
 }
 init {
  splash
 }

这将被复制到内核命令行字符串中，如下所示::

 root="01234567-89ab-cdef-0123-456789abcd" -- splash

如果用户给出的其他命令行是::

 ro bootconfig -- quiet

则最后的内核命令行如下::

 root="01234567-89ab-cdef-0123-456789abcd" ro bootconfig -- splash quiet

配置文件的限制
==============

当前最大的配置大小是32KB，关键字总数（不是键值条目）必须少于1024个节点。
注意：这不是条目数而是节点数，条目必须消耗超过2个节点（一个关键字和一个值）。
所以从理论上讲最多512个键值对。如果关键字平均包含3个单词，则可有256个键值对。
在大多数情况下，配置项的数量将少于100个条目，小于8KB，因此这应该足够了。如果
节点数超过1024，解析器将返回错误，即使文件大小小于32KB。（请注意，此最大尺寸
不包括填充的空字符。）
无论如何，因为 ``bootconfig`` 命令在附加启动配置到initrd映像时会验证它，用户
可以在引导之前注意到它。

引导配置API
===========

用户可以查询或遍历键值对，也可以查找（前缀）根关键字节点，并在查找该节点下的
键值。

如果您有一个关键字字符串，则可以直接使用 xbc_find_value() 查询该键的值。如果
你想知道引导配置里有哪些关键字，可以使用 xbc_for_each_key_value() 迭代键值对。
请注意，您需要使用 xbc_array_for_each_value() 访问数组的值，例如::

 vnode = NULL;
 xbc_find_value("key.word", &vnode);
 if (vnode && xbc_node_is_array(vnode))
    xbc_array_for_each_value(vnode, value) {
      printk("%s ", value);
    }

如果您想查找具有前缀字符串的键，可以使用 xbc_find_node() 通过前缀字符串查找
节点，然后用 xbc_node_for_each_key_value() 迭代前缀节点下的键。

但最典型的用法是获取前缀下的命名值或前缀下的命名数组，例如::

 root = xbc_find_node("key.prefix");
 value = xbc_node_find_value(root, "option", &vnode);
 ...
 xbc_node_for_each_array_value(root, "array-option", value, anode) {
    ...
 }

这将访问值“key.prefix.option”的值和“key.prefix.array-option”的数组。

锁是不需要的，因为在初始化之后配置只读。如果需要修改，必须复制所有数据和关键字。

函数与结构体
============

相关定义的kernel-doc参见：

 - include/linux/bootconfig.h
 - lib/bootconfig.c


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

:Original: :doc:`../../../admin-guide/bug-bisect`

:译者:

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

二分（bisect）缺陷
+++++++++++++++++++

（英文版）最后更新：2016年10月28日

引言
=====

始终尝试由来自kernel.org的源代码构建的最新内核。如果您没有信心这样做，请将
错误报告给您的发行版供应商，而不是内核开发人员。

找到缺陷（bug）并不总是那么容易，不过仍然得去找。如果你找不到它，不要放弃。
尽可能多的向相关维护人员报告您发现的信息。请参阅MAINTAINERS文件以了解您所
关注的子系统的维护人员。

在提交错误报告之前，请阅读“Documentation/admin-guide/reporting-issues.rst”。

设备未出现（Devices not appearing）
====================================

这通常是由udev/systemd引起的。在将其归咎于内核之前先检查一下。

查找导致缺陷的补丁
===================

使用 ``git`` 提供的工具可以很容易地找到缺陷，只要缺陷是可复现的。

操作步骤：

- 从git源代码构建内核
- 以此开始二分 [#f1]_::

	$ git bisect start

- 标记损坏的变更集::

	$ git bisect bad [commit]

- 标记正常工作的变更集::

	$ git bisect good [commit]

- 重新构建内核并测试
- 使用以下任一与git bisect进行交互::

	$ git bisect good

  或::

	$ git bisect bad

  这取决于您测试的变更集上是否有缺陷
- 在一些交互之后，git bisect将给出可能导致缺陷的变更集。

- 例如，如果您知道当前版本有问题，而4.8版本是正常的，则可以执行以下操作::

	$ git bisect start
	$ git bisect bad                 # Current version is bad
	$ git bisect good v4.8

.. [#f1] 您可以（可选地）在开始git bisect的时候提供good或bad参数
         ``git bisect start [BAD] [GOOD]``

如需进一步参考，请阅读：

- ``git-bisect`` 的手册页
- `Fighting regressions with git bisect（用git bisect解决回归）
  <https://www.kernel.org/pub/software/scm/git/docs/git-bisect-lk2009.html>`_
- `Fully automated bisecting with "git bisect run"（使用git bisect run
  来全自动二分） <https://lwn.net/Articles/317154>`_
- `Using Git bisect to figure out when brokenness was introduced
  （使用Git二分来找出何时引入了错误） <http://webchick.net/node/99>`_


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

:Original: :doc:`../../../admin-guide/bug-hunting`

:译者:

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

追踪缺陷
=========

内核错误报告通常附带如下堆栈转储::

	------------[ cut here ]------------
	WARNING: CPU: 1 PID: 28102 at kernel/module.c:1108 module_put+0x57/0x70
	Modules linked in: dvb_usb_gp8psk(-) dvb_usb dvb_core nvidia_drm(PO) nvidia_modeset(PO) snd_hda_codec_hdmi snd_hda_intel snd_hda_codec snd_hwdep snd_hda_core snd_pcm snd_timer snd soundcore nvidia(PO) [last unloaded: rc_core]
	CPU: 1 PID: 28102 Comm: rmmod Tainted: P        WC O 4.8.4-build.1 #1
	Hardware name: MSI MS-7309/MS-7309, BIOS V1.12 02/23/2009
	 00000000 c12ba080 00000000 00000000 c103ed6a c1616014 00000001 00006dc6
	 c1615862 00000454 c109e8a7 c109e8a7 00000009 ffffffff 00000000 f13f6a10
	 f5f5a600 c103ee33 00000009 00000000 00000000 c109e8a7 f80ca4d0 c109f617
	Call Trace:
	 [<c12ba080>] ? dump_stack+0x44/0x64
	 [<c103ed6a>] ? __warn+0xfa/0x120
	 [<c109e8a7>] ? module_put+0x57/0x70
	 [<c109e8a7>] ? module_put+0x57/0x70
	 [<c103ee33>] ? warn_slowpath_null+0x23/0x30
	 [<c109e8a7>] ? module_put+0x57/0x70
	 [<f80ca4d0>] ? gp8psk_fe_set_frontend+0x460/0x460 [dvb_usb_gp8psk]
	 [<c109f617>] ? symbol_put_addr+0x27/0x50
	 [<f80bc9ca>] ? dvb_usb_adapter_frontend_exit+0x3a/0x70 [dvb_usb]
	 [<f80bb3bf>] ? dvb_usb_exit+0x2f/0xd0 [dvb_usb]
	 [<c13d03bc>] ? usb_disable_endpoint+0x7c/0xb0
	 [<f80bb48a>] ? dvb_usb_device_exit+0x2a/0x50 [dvb_usb]
	 [<c13d2882>] ? usb_unbind_interface+0x62/0x250
	 [<c136b514>] ? __pm_runtime_idle+0x44/0x70
	 [<c13620d8>] ? __device_release_driver+0x78/0x120
	 [<c1362907>] ? driver_detach+0x87/0x90
	 [<c1361c48>] ? bus_remove_driver+0x38/0x90
	 [<c13d1c18>] ? usb_deregister+0x58/0xb0
	 [<c109fbb0>] ? SyS_delete_module+0x130/0x1f0
	 [<c1055654>] ? task_work_run+0x64/0x80
	 [<c1000fa5>] ? exit_to_usermode_loop+0x85/0x90
	 [<c10013f0>] ? do_fast_syscall_32+0x80/0x130
	 [<c1549f43>] ? sysenter_past_esp+0x40/0x6a
	---[ end trace 6ebc60ef3981792f ]---

这样的堆栈跟踪提供了足够的信息来识别内核源代码中发生错误的那一行。根据问题的
严重性，它还可能包含 **“Oops”** 一词，比如::

	BUG: unable to handle kernel NULL pointer dereference at   (null)
	IP: [<c06969d4>] iret_exc+0x7d0/0xa59
	*pdpt = 000000002258a001 *pde = 0000000000000000
	Oops: 0002 [#1] PREEMPT SMP
	...

尽管有 **Oops** 或其他类型的堆栈跟踪，但通常需要找到出问题的行来识别和处理缺
陷。在本章中，我们将参考“Oops”来了解需要分析的各种堆栈跟踪。

如果内核是用 ``CONFIG_DEBUG_INFO`` 编译的，那么可以使用文件：
`scripts/decode_stacktrace.sh` 。

链接的模块
-----------

受到污染或正在加载/卸载的模块用“（…）”标记，污染标志在
`Documentation/admin-guide/tainted-kernels.rst` 文件中进行了描述，“正在被加
载”用“+”标注，“正在被卸载”用“-”标注。

Oops消息在哪？
---------------

通常，Oops文本由klogd从内核缓冲区读取，然后交给 ``syslogd`` ，后者将其写入
syslog文件，通常是 ``/var/log/messages`` （取决于 ``/etc/syslog.conf`` ）。
在使用systemd的系统上，它也可以由 ``journald`` 守护进程存储，并通过运行
``journalctl`` 命令进行访问。

有时 ``klogd`` 会挂掉，这种情况下您可以运行 ``dmesg > file`` 从内核缓冲区
读取数据并保存它。或者您可以 ``cat /proc/kmsg > file`` ，但是您必须适时
中断以停止传输，因为 ``kmsg`` 是一个“永无止境的文件”。

如果机器严重崩溃，无法输入命令或磁盘不可用，那还有三个选项：

(1) 手动复制屏幕上的文本，并在机器重新启动后输入。很难受，但这是突然崩溃下
    唯一的选择。或者你可以用数码相机拍下屏幕——虽然不那么好，但总比什么都没
    有好。如果消息滚动超出控制台顶部，使用更高分辨率（例如 ``vga=791`` ）
    引导启动将允许您阅读更多文本。（警告：这需要 ``vesafb`` ，因此对“早期”
    的Oppses没有帮助）

(2) 从串口终端启动（参见
    :ref:`Documentation/admin-guide/serial-console.rst <serial_console>` ），
    在另一台机器上运行调制解调器然后用你喜欢的通信程序捕获输出。
    Minicom运行良好。

(3) 使用Kdump（参阅 Documentation/admin-guide/kdump/kdump.rst ），使用
    Documentation/admin-guide/kdump/gdbmacros.txt 中的dmesg gdbmacro从旧内存
    中提取内核环形缓冲区。

找到缺陷位置
-------------

如果你能指出缺陷在内核源代码中的位置，则报告缺陷的效果会非常好。这有两种方法。
通常来说使用 ``gdb`` 会比较容易，不过内核需要用调试信息来预编译。

gdb
^^^^

GNU 调试器（GNU debugger， ``gdb`` ）是从 ``vmlinux`` 文件中找出OOPS的确切
文件和行号的最佳方法。

在使用 ``CONFIG_DEBUG_INFO`` 编译的内核上使用gdb效果最好。可通过运行以下命令
进行设置::

  $ ./scripts/config -d COMPILE_TEST -e DEBUG_KERNEL -e DEBUG_INFO

在用 ``CONFIG_DEBUG_INFO`` 编译的内核上，你可以直接从OOPS复制EIP值::

 EIP:    0060:[<c021e50e>]    Not tainted VLI

并使用GDB来将其翻译成可读形式::

  $ gdb vmlinux
  (gdb) l *0xc021e50e

如果没有启用 ``CONFIG_DEBUG_INFO`` ，则使用OOPS的函数偏移::

 EIP is at vt_ioctl+0xda8/0x1482

并在启用 ``CONFIG_DEBUG_INFO`` 的情况下重新编译内核::

  $ ./scripts/config -d COMPILE_TEST -e DEBUG_KERNEL -e DEBUG_INFO
  $ make vmlinux
  $ gdb vmlinux
  (gdb) l *vt_ioctl+0xda8
  0x1888 is in vt_ioctl (drivers/tty/vt/vt_ioctl.c:293).
  288	{
  289		struct vc_data *vc = NULL;
  290		int ret = 0;
  291
  292		console_lock();
  293		if (VT_BUSY(vc_num))
  294			ret = -EBUSY;
  295		else if (vc_num)
  296			vc = vc_deallocate(vc_num);
  297		console_unlock();

或者若您想要更详细的显示::

  (gdb) p vt_ioctl
  $1 = {int (struct tty_struct *, unsigned int, unsigned long)} 0xae0 <vt_ioctl>
  (gdb) l *0xae0+0xda8

您也可以使用对象文件作为替代::

  $ make drivers/tty/
  $ gdb drivers/tty/vt/vt_ioctl.o
  (gdb) l *vt_ioctl+0xda8

如果你有调用跟踪，类似::

     Call Trace:
      [<ffffffff8802c8e9>] :jbd:log_wait_commit+0xa3/0xf5
      [<ffffffff810482d9>] autoremove_wake_function+0x0/0x2e
      [<ffffffff8802770b>] :jbd:journal_stop+0x1be/0x1ee
      ...

这表明问题可能在 :jbd: 模块中。您可以在gdb中加载该模块并列出相关代码::

  $ gdb fs/jbd/jbd.ko
  (gdb) l *log_wait_commit+0xa3

.. note::

     您还可以对堆栈跟踪处的任何函数调用执行相同的操作，例如::

	 [<f80bc9ca>] ? dvb_usb_adapter_frontend_exit+0x3a/0x70 [dvb_usb]

     上述调用发生的位置可以通过以下方式看到::

	$ gdb drivers/media/usb/dvb-usb/dvb-usb.o
	(gdb) l *dvb_usb_adapter_frontend_exit+0x3a

objdump
^^^^^^^^

要调试内核，请使用objdump并从崩溃输出中查找十六进制偏移，以找到有效的代码/汇
编行。如果没有调试符号，您将看到所示例程的汇编程序代码，但是如果内核有调试
符号，C代码也将可见（调试符号可以在内核配置菜单的hacking项中启用）。例如::

    $ objdump -r -S -l --disassemble net/dccp/ipv4.o

.. note::

   您需要处于内核树的顶层以便此获得您的C文件。

如果您无法访问源代码，仍然可以使用以下方法调试一些崩溃转储（如Dave Miller的
示例崩溃转储输出所示）::

     EIP is at 	+0x14/0x4c0
      ...
     Code: 44 24 04 e8 6f 05 00 00 e9 e8 fe ff ff 8d 76 00 8d bc 27 00 00
     00 00 55 57  56 53 81 ec bc 00 00 00 8b ac 24 d0 00 00 00 8b 5d 08
     <8b> 83 3c 01 00 00 89 44  24 14 8b 45 28 85 c0 89 44 24 18 0f 85

     Put the bytes into a "foo.s" file like this:

            .text
            .globl foo
     foo:
            .byte  .... /* bytes from Code: part of OOPS dump */

     Compile it with "gcc -c -o foo.o foo.s" then look at the output of
     "objdump --disassemble foo.o".

     Output:

     ip_queue_xmit:
         push       %ebp
         push       %edi
         push       %esi
         push       %ebx
         sub        $0xbc, %esp
         mov        0xd0(%esp), %ebp        ! %ebp = arg0 (skb)
         mov        0x8(%ebp), %ebx         ! %ebx = skb->sk
         mov        0x13c(%ebx), %eax       ! %eax = inet_sk(sk)->opt

`scripts/decodecode` 文件可以用来自动完成大部分工作，这取决于正在调试的CPU
体系结构。

报告缺陷
---------

一旦你通过定位缺陷找到了其发生的地方，你可以尝试自己修复它或者向上游报告它。

为了向上游报告，您应该找出用于开发受影响代码的邮件列表。这可以使用 ``get_maintainer.pl`` 。

例如，您在gspca的sonixj.c文件中发现一个缺陷，则可以通过以下方法找到它的维护者::

	$ ./scripts/get_maintainer.pl -f drivers/media/usb/gspca/sonixj.c
	Hans Verkuil <hverkuil@xs4all.nl> (odd fixer:GSPCA USB WEBCAM DRIVER,commit_signer:1/1=100%)
	Mauro Carvalho Chehab <mchehab@kernel.org> (maintainer:MEDIA INPUT INFRASTRUCTURE (V4L/DVB),commit_signer:1/1=100%)
	Tejun Heo <tj@kernel.org> (commit_signer:1/1=100%)
	Bhaktipriya Shridhar <bhaktipriya96@gmail.com> (commit_signer:1/1=100%,authored:1/1=100%,added_lines:4/4=100%,removed_lines:9/9=100%)
	linux-media@vger.kernel.org (open list:GSPCA USB WEBCAM DRIVER)
	linux-kernel@vger.kernel.org (open list)

请注意它将指出：

- 最后接触源代码的开发人员（如果这是在git树中完成的）。在上面的例子中是Tejun
  和Bhaktipriya（在这个特定的案例中，没有人真正参与这个文件的开发）；
- 驱动维护人员（Hans Verkuil）；
- 子系统维护人员（Mauro Carvalho Chehab）；
- 驱动程序和/或子系统邮件列表（linux-media@vger.kernel.org）；
- Linux内核邮件列表（linux-kernel@vger.kernel.org）。

通常，修复缺陷的最快方法是将它报告给用于开发相关代码的邮件列表（linux-media
ML），抄送驱动程序维护者（Hans）。

如果你完全不知道该把报告寄给谁，且 ``get_maintainer.pl`` 也没有提供任何有用
的信息，请发送到linux-kernel@vger.kernel.org。

感谢您的帮助，这使Linux尽可能稳定:-)

修复缺陷
---------

如果你懂得编程，你不仅可以通过报告错误来帮助我们，还可以提供一个解决方案。
毕竟，开源就是分享你的工作，你不想因为你的天才而被认可吗？

如果你决定这样做，请在制定解决方案后将其提交到上游。

请务必阅读
:ref:`Documentation/process/submitting-patches.rst <submittingpatches>` ，
以帮助您的代码被接受。

---------------------------------------------------------------------------

用 ``klogd`` 进行Oops跟踪的注意事项
------------------------------------

为了帮助Linus和其他内核开发人员， ``klogd`` 对保护故障的处理提供了大量支持。
为了完整支持地址解析，至少应该使用 ``sysklogd`` 包的1.3-pl3版本。

当发生保护故障时， ``klogd`` 守护进程会自动将内核日志消息中的重要地址转换为
它们的等效符号。然后通过 ``klogd`` 使用的任何报告机制来转发这个已翻译的内核
消息。保护错误消息可以直接从消息文件中剪切出来并转发给内核开发人员。

``klogd`` 执行两种类型的地址解析，静态翻译和动态翻译。静态翻译使用System.map
文件。为了进行静态转换， ``klogd`` 守护进程必须能够在守护进程初始化时找到系
统映射文件。有关 ``klogd`` 如何搜索映射文件的信息，请参见klogd手册页。

当使用内核可加载模块时，动态地址转换非常重要。由于内核模块的内存是从内核的
动态内存池中分配的，因此无论是模块的开头还是模块中的函数和符号都没有固定的
位置。

内核支持系统调用，允许程序确定加载哪些模块及其在内存中的位置。klogd守护进程
使用这些系统调用构建了一个符号表，可用于调试可加载内核模块中发生的保护错误。

klogd至少会提供产生保护故障的模块的名称。如果可加载模块的开发人员选择从模块
导出符号信息，则可能会有其他可用的符号信息。

由于内核模块环境可以是动态的，因此当模块环境发生变化时，必须有一种通知
``klogd`` 守护进程的机制。有一些可用的命令行选项允许klogd向当前正在执行的守
护进程发出信号示意应该刷新符号信息。有关更多信息，请参阅 ``klogd`` 手册页。

sysklogd发行版附带了一个补丁，它修改了 ``modules-2.0.0`` 包，以便在加载或
卸载模块时自动向klogd发送信号。应用此补丁基本上可无缝支持调试内核可加载模块
发生的保护故障。

以下是 ``klogd`` 处理的可加载模块中的保护故障示例::

	Aug 29 09:51:01 blizard kernel: Unable to handle kernel paging request at virtual address f15e97cc
	Aug 29 09:51:01 blizard kernel: current->tss.cr3 = 0062d000, %cr3 = 0062d000
	Aug 29 09:51:01 blizard kernel: *pde = 00000000
	Aug 29 09:51:01 blizard kernel: Oops: 0002
	Aug 29 09:51:01 blizard kernel: CPU:    0
	Aug 29 09:51:01 blizard kernel: EIP:    0010:[oops:_oops+16/3868]
	Aug 29 09:51:01 blizard kernel: EFLAGS: 00010212
	Aug 29 09:51:01 blizard kernel: eax: 315e97cc   ebx: 003a6f80   ecx: 001be77b   edx: 00237c0c
	Aug 29 09:51:01 blizard kernel: esi: 00000000   edi: bffffdb3   ebp: 00589f90   esp: 00589f8c
	Aug 29 09:51:01 blizard kernel: ds: 0018   es: 0018   fs: 002b   gs: 002b   ss: 0018
	Aug 29 09:51:01 blizard kernel: Process oops_test (pid: 3374, process nr: 21, stackpage=00589000)
	Aug 29 09:51:01 blizard kernel: Stack: 315e97cc 00589f98 0100b0b4 bffffed4 0012e38e 00240c64 003a6f80 00000001
	Aug 29 09:51:01 blizard kernel:        00000000 00237810 bfffff00 0010a7fa 00000003 00000001 00000000 bfffff00
	Aug 29 09:51:01 blizard kernel:        bffffdb3 bffffed4 ffffffda 0000002b 0007002b 0000002b 0000002b 00000036
	Aug 29 09:51:01 blizard kernel: Call Trace: [oops:_oops_ioctl+48/80] [_sys_ioctl+254/272] [_system_call+82/128]
	Aug 29 09:51:01 blizard kernel: Code: c7 00 05 00 00 00 eb 08 90 90 90 90 90 90 90 90 89 ec 5d c3

---------------------------------------------------------------------------

::

  Dr. G.W. Wettstein           Oncology Research Div. Computing Facility
  Roger Maris Cancer Center    INTERNET: greg@wind.rmcc.com
  820 4th St. N.
  Fargo, ND  58122
  Phone: 701-234-7556


code:
清除 WARN_ONCE
--------------

WARN_ONCE / WARN_ON_ONCE / printk_once 仅仅打印一次消息.

echo 1 > /sys/kernel/debug/clear_warn_once

可以清除这种状态并且再次允许打印一次告警信息，这对于运行测试集后重现问题
很有用。


code:
========
CPU 负载
========

Linux通过``/proc/stat``和``/proc/uptime``导出各种信息，用户空间工具
如top(1)使用这些信息计算系统花费在某个特定状态的平均时间。
例如：

    $ iostat
    Linux 2.6.18.3-exp (linmac)     02/20/2007

    avg-cpu:  %user   %nice %system %iowait  %steal   %idle
              10.01    0.00    2.92    5.44    0.00   81.63

    ...

这里系统认为在默认采样周期內有10.01%的时间工作在用户空间，2.92%的时
间用在系统空间，总体上有81.63%的时间是空闲的。

大多数情况下``/proc/stat``的信息几乎真实反映了系统信息，然而，由于内
核采集这些数据的方式/时间的特点，有时这些信息根本不可靠。

那么这些信息是如何被搜集的呢？每当时间中断触发时，内核查看此刻运行的
进程类型，并增加与此类型/状态进程对应的计数器的值。这种方法的问题是
在两次时间中断之间系统（进程）能够在多种状态之间切换多次，而计数器只
增加最后一种状态下的计数。

举例
---

假设系统有一个进程以如下方式周期性地占用cpu::

     两个时钟中断之间的时间线
    |-----------------------|
     ^                     ^
     |_ 开始运行           |
                           |_ 开始睡眠
                           （很快会被唤醒）

在上面的情况下，根据``/proc/stat``的信息（由于当系统处于空闲状态时，
时间中断经常会发生）系统的负载将会是0

大家能够想象内核的这种行为会发生在许多情况下，这将导致``/proc/stat``
中存在相当古怪的信息::

	/* gcc -o hog smallhog.c */
	#include <time.h>
	#include <limits.h>
	#include <signal.h>
	#include <sys/time.h>
	#define HIST 10

	static volatile sig_atomic_t stop;

	static void sighandler (int signr)
	{
	(void) signr;
	stop = 1;
	}
	static unsigned long hog (unsigned long niters)
	{
	stop = 0;
	while (!stop && --niters);
	return niters;
	}
	int main (void)
	{
	int i;
	struct itimerval it = { .it_interval = { .tv_sec = 0, .tv_usec = 1 },
				.it_value = { .tv_sec = 0, .tv_usec = 1 } };
	sigset_t set;
	unsigned long v[HIST];
	double tmp = 0.0;
	unsigned long n;
	signal (SIGALRM, &sighandler);
	setitimer (ITIMER_REAL, &it, NULL);

	hog (ULONG_MAX);
	for (i = 0; i < HIST; ++i) v[i] = ULONG_MAX - hog (ULONG_MAX);
	for (i = 0; i < HIST; ++i) tmp += v[i];
	tmp /= HIST;
	n = tmp - (tmp / 3.0);

	sigemptyset (&set);
	sigaddset (&set, SIGALRM);

	for (;;) {
		hog (n);
		sigwait (&set, &i);
	}
	return 0;
	}

参考
---

- https://lore.kernel.org/r/loom.20070212T063225-663@post.gmane.org
- Documentation/filesystems/proc.rst (1.8)

谢谢
---

Con Kolivas, Pavel Machek


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

:Original: Documentation/admin-guide/cputopology.rst

:翻译:

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

==========================
如何通过sysfs将CPU拓扑导出
==========================

CPU拓扑信息通过sysfs导出。显示的项（属性）和某些架构的/proc/cpuinfo输出相似。它们位于
/sys/devices/system/cpu/cpuX/topology/。请阅读ABI文件：
Documentation/ABI/stable/sysfs-devices-system-cpu。

drivers/base/topology.c是体系结构中性的，它导出了这些属性。然而，die、cluster、book、
draw这些层次结构相关的文件仅在体系结构提供了下文描述的宏的条件下被创建。

对于支持这个特性的体系结构，它必须在include/asm-XXX/topology.h中定义这些宏中的一部分::

	#define topology_physical_package_id(cpu)
	#define topology_die_id(cpu)
	#define topology_cluster_id(cpu)
	#define topology_core_id(cpu)
	#define topology_book_id(cpu)
	#define topology_drawer_id(cpu)
	#define topology_sibling_cpumask(cpu)
	#define topology_core_cpumask(cpu)
	#define topology_cluster_cpumask(cpu)
	#define topology_die_cpumask(cpu)
	#define topology_book_cpumask(cpu)
	#define topology_drawer_cpumask(cpu)

``**_id macros`` 的类型是int。
``**_cpumask macros`` 的类型是 ``(const) struct cpumask *`` 。后者和恰当的
``**_siblings`` sysfs属性对应（除了topology_sibling_cpumask()，它和thread_siblings
对应）。

为了在所有体系结构上保持一致，include/linux/topology.h提供了上述所有宏的默认定义，以防
它们未在include/asm-XXX/topology.h中定义:

1) topology_physical_package_id: -1
2) topology_die_id: -1
3) topology_cluster_id: -1
4) topology_core_id: 0
5) topology_book_id: -1
6) topology_drawer_id: -1
7) topology_sibling_cpumask: 仅入参CPU
8) topology_core_cpumask: 仅入参CPU
9) topology_cluster_cpumask: 仅入参CPU
10) topology_die_cpumask: 仅入参CPU
11) topology_book_cpumask:  仅入参CPU
12) topology_drawer_cpumask: 仅入参CPU

此外，CPU拓扑信息由/sys/devices/system/cpu提供，包含下述文件。输出对应的内部数据源放在
方括号（"[]"）中。

    =========== ==================================================================
    kernel_max: 内核配置允许的最大CPU下标值。[NR_CPUS-1]

    offline:    由于热插拔移除或者超过内核允许的CPU上限（上文描述的kernel_max）
                导致未上线的CPU。[~cpu_online_mask + cpus >= NR_CPUS]

    online:     在线的CPU，可供调度使用。[cpu_online_mask]

    possible:   已被分配资源的CPU，如果它们CPU实际存在，可以上线。
                [cpu_possible_mask]

    present:    被系统识别实际存在的CPU。[cpu_present_mask]
    =========== ==================================================================

上述输出的格式和cpulist_parse()兼容[参见 <linux/cpumask.h>]。下面给些例子。

在本例中，系统中有64个CPU，但是CPU 32-63超过了kernel_max值，因为NR_CPUS配置项是32，
取值范围被限制为0..31。此外注意CPU2和4-31未上线，但是可以上线，因为它们同时存在于
present和possible::

     kernel_max: 31
        offline: 2,4-31,32-63
         online: 0-1,3
       possible: 0-31
        present: 0-31

在本例中，NR_CPUS配置项是128，但内核启动时设置possible_cpus=144。系统中有4个CPU，
CPU2被手动设置下线（也是唯一一个可以上线的CPU）::

     kernel_max: 127
        offline: 2,4-127,128-143
         online: 0-1,3
       possible: 0-127
        present: 0-3

阅读Documentation/core-api/cpu_hotplug.rst可了解开机参数possible_cpus=NUM，同时还
可以了解各种cpumask的信息。


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

:Original: :doc:`../../../admin-guide/index`
:Translator: Alex Shi <alex.shi@linux.alibaba.com>

Linux 内核用户和管理员指南
==========================

下面是一组随时间添加到内核中的面向用户的文档的集合。到目前为止，还没有一个
整体的顺序或组织 - 这些材料不是一个单一的，连贯的文件！幸运的话，情况会随着
时间的推移而迅速改善。

这个初始部分包含总体信息，包括描述内核的README， 关于内核参数的文档等。

.. toctree::
   :maxdepth: 1

   README

Todolist:

*   kernel-parameters
*   devices
*   sysctl/index

本节介绍CPU漏洞及其缓解措施。

Todolist:

*   hw-vuln/index

下面的一组文档，针对的是试图跟踪问题和bug的用户。

.. toctree::
   :maxdepth: 1

   reporting-issues
   reporting-regressions
   security-bugs
   bug-hunting
   bug-bisect
   tainted-kernels
   init

Todolist:

*   ramoops
*   dynamic-debug-howto
*   kdump/index
*   perf/index

这是应用程序开发人员感兴趣的章节的开始。可以在这里找到涵盖内核ABI各个
方面的文档。

Todolist:

*   sysfs-rules

本手册的其余部分包括各种指南，介绍如何根据您的喜好配置内核的特定行为。

.. toctree::
   :maxdepth: 1

   bootconfig
   clearing-warn-once
   cpu-load
   cputopology
   lockup-watchdogs
   unicode
   sysrq
   mm/index

Todolist:

*   acpi/index
*   aoe/index
*   auxdisplay/index
*   bcache
*   binderfs
*   binfmt-misc
*   blockdev/index
*   braille-console
*   btmrvl
*   cgroup-v1/index
*   cgroup-v2
*   cifs/index
*   dell_rbu
*   device-mapper/index
*   edid
*   efi-stub
*   ext4
*   nfs/index
*   gpio/index
*   highuid
*   hw_random
*   initrd
*   iostats
*   java
*   jfs
*   kernel-per-CPU-kthreads
*   laptops/index
*   lcd-panel-cgram
*   ldm
*   LSM/index
*   md
*   media/index
*   module-signing
*   mono
*   namespaces/index
*   numastat
*   parport
*   perf-security
*   pm/index
*   pnp
*   rapidio
*   ras
*   rtc
*   serial-console
*   svga
*   thunderbolt
*   ufs
*   vga-softcursor
*   video-output
*   xfs

.. only::  subproject and html

   Indices
   =======

   * :ref:`genindex`


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

:Original: :doc:`../../../admin-guide/init`

:译者:

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

解释“No working init found.”启动挂起消息
=========================================

:作者:

 Andreas Mohr <andi at lisas period de>

 Cristian Souza <cristianmsbr at gmail period com>

本文档提供了加载初始化二进制（init binary）失败的一些高层级原因（大致按执行
顺序列出）。

1) **无法挂载根文件系统Unable to mount root FS** ：请设置“debug”内核参数（在
   引导加载程序bootloader配置文件或CONFIG_CMDLINE）以获取更详细的内核消息。

2) **初始化二进制不存在于根文件系统上init binary doesn't exist on rootfs** ：
   确保您的根文件系统类型正确（并且 ``root=`` 内核参数指向正确的分区）；拥有
   所需的驱动程序，例如SCSI或USB等存储硬件；文件系统（ext3、jffs2等）是内建的
   （或者作为模块由initrd预加载）。

3) **控制台设备损坏Broken console device** ： ``console= setup`` 中可能存在
   冲突 --> 初始控制台不可用（initial console unavailable）。例如，由于串行
   IRQ问题（如缺少基于中断的配置）导致的某些串行控制台不可靠。尝试使用不同的
   ``console= device`` 或像 ``netconsole=`` 。

4) **二进制存在但依赖项不可用Binary exists but dependencies not available** ：
   例如初始化二进制的必需库依赖项，像 ``/lib/ld-linux.so.2`` 丢失或损坏。使用
   ``readelf -d <INIT>|grep NEEDED`` 找出需要哪些库。

5) **无法加载二进制Binary cannot be loaded** ：请确保二进制的体系结构与您的
   硬件匹配。例如i386不匹配x86_64，或者尝试在ARM硬件上加载x86。如果您尝试在
   此处加载非二进制文件（shell脚本？），您应该确保脚本在其工作头（shebang
   header）行 ``#!/...`` 中指定能正常工作的解释器（包括其库依赖项）。在处理
   脚本之前，最好先测试一个简单的非脚本二进制文件，比如 ``/bin/sh`` ，并确认
   它能成功执行。要了解更多信息，请将代码添加到 ``init/main.c`` 以显示
   kernel_execve()的返回值。

当您发现新的失败原因时，请扩展本解释（毕竟加载初始化二进制是一个 **关键** 且
艰难的过渡步骤，需要尽可能无痛地进行），然后向LKML提交一个补丁。

待办事项：

- 通过一个可以存储 ``kernel_execve()`` 结果值的结构体数组实现各种
  ``run_init_process()`` 调用，并在失败时通过迭代 **所有** 结果来记录一切
  （非常重要的可用性修复）。
- 试着使实现本身在一般情况下更有帮助，例如在受影响的地方提供额外的错误消息。


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

:Original: Documentation/admin-guide/lockup-watchdogs.rst
:Translator: Hailong Liu <liu.hailong6@zte.com.cn>

.. _cn_lockup-watchdogs:

=================================================
Softlockup与hardlockup检测机制(又名:nmi_watchdog)
=================================================

Linux中内核实现了一种用以检测系统发生softlockup和hardlockup的看门狗机制。

Softlockup是一种会引发系统在内核态中一直循环超过20秒（详见下面“实现”小节）导致
其他任务没有机会得到运行的BUG。一旦检测到'softlockup'发生，默认情况下系统会打
印当前堆栈跟踪信息并进入锁定状态。也可配置使其在检测到'softlockup'后进入panic
状态；通过sysctl命令设置“kernel.softlockup_panic”、使用内核启动参数
“softlockup_panic”（详见Documentation/admin-guide/kernel-parameters.rst）以及使
能内核编译选项“BOOTPARAM_SOFTLOCKUP_PANIC”都可实现这种配置。

而'hardlockup'是一种会引发系统在内核态一直循环超过10秒钟（详见"实现"小节）导致其
他中断没有机会运行的缺陷。与'softlockup'情况类似，除了使用sysctl命令设置
'hardlockup_panic'、使能内核选项“BOOTPARAM_HARDLOCKUP_PANIC”以及使用内核参数
"nmi_watchdog"(详见:”Documentation/admin-guide/kernel-parameters.rst“)外，一旦检
测到'hardlockup'默认情况下系统打印当前堆栈跟踪信息，然后进入锁定状态。

这个panic选项也可以与panic_timeout结合使用（这个panic_timeout是通过稍具迷惑性的
sysctl命令"kernel.panic"来设置），使系统在panic指定时间后自动重启。

实现
====

Softlockup和hardlockup分别建立在hrtimer(高精度定时器)和perf两个子系统上而实现。
这也就意味着理论上任何架构只要实现了这两个子系统就支持这两种检测机制。

Hrtimer用于周期性产生中断并唤醒watchdog线程；NMI perf事件则以”watchdog_thresh“
(编译时默认初始化为10秒，也可通过”watchdog_thresh“这个sysctl接口来进行配置修改)
为间隔周期产生以检测 hardlockups。如果一个CPU在这个时间段内没有检测到hrtimer中
断发生，'hardlockup 检测器'(即NMI perf事件处理函数)将会视系统配置而选择产生内核
警告或者直接panic。

而watchdog线程本质上是一个高优先级内核线程，每调度一次就对时间戳进行一次更新。
如果时间戳在2*watchdog_thresh(这个是softlockup的触发门限)这段时间都未更新,那么
"softlocup 检测器"(内部hrtimer定时器回调函数)会将相关的调试信息打印到系统日志中，
然后如果系统配置了进入panic流程则进入panic，否则内核继续执行。

Hrtimer定时器的周期是2*watchdog_thresh/5，也就是说在hardlockup被触发前hrtimer有
2~3次机会产生时钟中断。

如上所述,内核相当于为系统管理员提供了一个可调节hrtimer定时器和perf事件周期长度
的调节旋钮。如何通过这个旋钮为特定使用场景配置一个合理的周期值要对lockups检测的
响应速度和lockups检测开销这二者之间进行权衡。

默认情况下所有在线cpu上都会运行一个watchdog线程。不过在内核配置了”NO_HZ_FULL“的
情况下watchdog线程默认只会运行在管家(housekeeping)cpu上，而”nohz_full“启动参数指
定的cpu上则不会有watchdog线程运行。试想，如果我们允许watchdog线程在”nohz_full“指
定的cpu上运行，这些cpu上必须得运行时钟定时器来激发watchdog线程调度；这样一来就会
使”nohz_full“保护用户程序免受内核干扰的功能失效。当然，副作用就是”nohz_full“指定
的cpu即使在内核产生了lockup问题我们也无法检测到。不过，至少我们可以允许watchdog
线程在管家(non-tickless)核上继续运行以便我们能继续正常的监测这些cpus上的lockups
事件。

不论哪种情况都可以通过sysctl命令kernel.watchdog_cpumask来对没有运行watchdog线程
的cpu集合进行调节。对于nohz_full而言,如果nohz_full cpu上有异常挂住的情况，通过
这种方式打开这些cpu上的watchdog进行调试可能会有所作用。


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

:Original: Documentation/admin-guide/mm/damon/index.rst

:翻译:

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

:校译:

============
监测数据访问
============

:doc:`DAMON </mm/damon/index>` 允许轻量级的数据访问监测。使用DAMON，
用户可以分析他们系统的内存访问模式，并优化它们。

.. toctree::
   :maxdepth: 2

   start
   usage
   reclaim


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

:Original: Documentation/admin-guide/mm/damon/reclaim.rst

:翻译:

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

:校译:

===============
基于DAMON的回收
===============

基于DAMON的回收（DAMON_RECLAIM）是一个静态的内核模块，旨在用于轻度内存压力下的主动和轻
量级的回收。它的目的不是取代基于LRU列表的页面回收，而是有选择地用于不同程度的内存压力和要
求。

哪些地方需要主动回收？
======================

在一般的内存超量使用（over-committed systems，虚拟化相关术语）的系统上，主动回收冷页
有助于节省内存和减少延迟高峰，这些延迟是由直接回收进程或kswapd的CPU消耗引起的，同时只产
生最小的性能下降 [1]_ [2]_ 。

基于空闲页报告 [3]_ 的内存过度承诺的虚拟化系统就是很好的例子。在这样的系统中，客户机
向主机报告他们的空闲内存，而主机则将报告的内存重新分配给其他客户。因此，系统的内存得到了充
分的利用。然而，客户可能不那么节省内存，主要是因为一些内核子系统和用户空间应用程序被设计为
使用尽可能多的内存。然后，客户机可能只向主机报告少量的内存是空闲的，导致系统的内存利用率下降。
在客户中运行主动回收可以缓解这个问题。

它是如何工作的？
================

DAMON_RECLAIM找到在特定时间内没有被访问的内存区域并分页。为了避免它在分页操作中消耗过多
的CPU，可以配置一个速度限制。在这个速度限制下，它首先分页出那些没有被访问过的内存区域。系
统管理员还可以配置在什么情况下这个方案应该自动激活和停用三个内存压力水位。

接口: 模块参数
==============

要使用这个功能，你首先要确保你的系统运行在一个以 ``CONFIG_DAMON_RECLAIM=y`` 构建的内
核上。

为了让系统管理员启用或禁用它，并为给定的系统进行调整，DAMON_RECLAIM利用了模块参数。也就
是说，你可以把 ``damon_reclaim.<parameter>=<value>`` 放在内核启动命令行上，或者把
适当的值写入 ``/sys/modules/damon_reclaim/parameters/<parameter>`` 文件。

注意，除 ``启用`` 外的参数值只在DAMON_RECLAIM启动时应用。因此，如果你想在运行时应用新
的参数值，而DAMON_RECLAIM已经被启用，你应该通过 ``启用`` 的参数文件禁用和重新启用它。
在重新启用之前，应将新的参数值写入适当的参数值中。

下面是每个参数的描述。

enabled
-------

启用或禁用DAMON_RECLAIM。

你可以通过把这个参数的值设置为 ``Y`` 来启用DAMON_RCLAIM，把它设置为 ``N`` 可以禁用
DAMON_RECLAIM。注意，由于基于水位的激活条件，DAMON_RECLAIM不能进行真正的监测和回收。
这一点请参考下面关于水位参数的描述。

min_age
-------

识别冷内存区域的时间阈值，单位是微秒。

如果一个内存区域在这个时间或更长的时间内没有被访问，DAMON_RECLAIM会将该区域识别为冷的，
并回收它。

默认为120秒。

quota_ms
--------

回收的时间限制，以毫秒为单位。

DAMON_RECLAIM 试图在一个时间窗口（quota_reset_interval_ms）内只使用到这个时间，以
尝试回收冷页。这可以用来限制DAMON_RECLAIM的CPU消耗。如果该值为零，则该限制被禁用。

默认为10ms。

quota_sz
--------

回收的内存大小限制，单位为字节。

DAMON_RECLAIM 收取在一个时间窗口（quota_reset_interval_ms）内试图回收的内存量，并
使其不超过这个限制。这可以用来限制CPU和IO的消耗。如果该值为零，则限制被禁用。

默认情况下是128 MiB。

quota_reset_interval_ms
-----------------------

时间/大小配额收取重置间隔，单位为毫秒。

时间（quota_ms）和大小（quota_sz）的配额的目标重置间隔。也就是说，DAMON_RECLAIM在
尝试回收‘不’超过quota_ms毫秒或quota_sz字节的内存。

默认为1秒。

wmarks_interval
---------------

当DAMON_RECLAIM被启用但由于其水位规则而不活跃时，在检查水位之前的最小等待时间。

wmarks_high
-----------

高水位的可用内存率（每千字节）。

如果系统的可用内存（以每千字节为单位）高于这个数值，DAMON_RECLAIM就会变得不活跃，所以
它什么也不做，只是定期检查水位。

wmarks_mid
----------

中间水位的可用内存率（每千字节）。

如果系统的空闲内存（以每千字节为单位）在这个和低水位线之间，DAMON_RECLAIM就会被激活，
因此开始监测和回收。

wmarks_low
----------

低水位的可用内存率（每千字节）。

如果系统的空闲内存（以每千字节为单位）低于这个数值，DAMON_RECLAIM就会变得不活跃，所以
它除了定期检查水位外什么都不做。在这种情况下，系统会退回到基于LRU列表的页面粒度回收逻辑。

sample_interval
---------------

监测的采样间隔，单位是微秒。

DAMON用于监测冷内存的采样间隔。更多细节请参考DAMON文档 (:doc:`usage`) 。

aggr_interval
-------------

监测的聚集间隔，单位是微秒。

DAMON对冷内存监测的聚集间隔。更多细节请参考DAMON文档 (:doc:`usage`)。

min_nr_regions
--------------

监测区域的最小数量。

DAMON用于冷内存监测的最小监测区域数。这可以用来设置监测质量的下限。但是，设
置的太高可能会导致监测开销的增加。更多细节请参考DAMON文档 (:doc:`usage`) 。

max_nr_regions
--------------

监测区域的最大数量。

DAMON用于冷内存监测的最大监测区域数。这可以用来设置监测开销的上限值。但是，
设置得太低可能会导致监测质量不好。更多细节请参考DAMON文档 (:doc:`usage`) 。

monitor_region_start
--------------------

目标内存区域的物理地址起点。

DAMON_RECLAIM将对其进行工作的内存区域的起始物理地址。也就是说，DAMON_RECLAIM
将在这个区域中找到冷的内存区域并进行回收。默认情况下，该区域使用最大系统内存区。

monitor_region_end
------------------

目标内存区域的结束物理地址。

DAMON_RECLAIM将对其进行工作的内存区域的末端物理地址。也就是说，DAMON_RECLAIM将
在这个区域内找到冷的内存区域并进行回收。默认情况下，该区域使用最大系统内存区。

kdamond_pid
-----------

DAMON线程的PID。

如果DAMON_RECLAIM被启用，这将成为工作线程的PID。否则，为-1。

nr_reclaim_tried_regions
------------------------

试图通过DAMON_RECLAIM回收的内存区域的数量。

bytes_reclaim_tried_regions
---------------------------

试图通过DAMON_RECLAIM回收的内存区域的总字节数。

nr_reclaimed_regions
--------------------

通过DAMON_RECLAIM成功回收的内存区域的数量。

bytes_reclaimed_regions
-----------------------

通过DAMON_RECLAIM成功回收的内存区域的总字节数。

nr_quota_exceeds
----------------

超过时间/空间配额限制的次数。

例子
====

下面的运行示例命令使DAMON_RECLAIM找到30秒或更长时间没有访问的内存区域并“回收”？
为了避免DAMON_RECLAIM在分页操作中消耗过多的CPU时间，回收被限制在每秒1GiB以内。
它还要求DAMON_RECLAIM在系统的可用内存率超过50%时不做任何事情，但如果它低于40%时
就开始真正的工作。如果DAMON_RECLAIM没有取得进展，因此空闲内存率低于20%，它会要求
DAMON_RECLAIM再次什么都不做，这样我们就可以退回到基于LRU列表的页面粒度回收了::

    # cd /sys/modules/damon_reclaim/parameters
    # echo 30000000 > min_age
    # echo $((1 * 1024 * 1024 * 1024)) > quota_sz
    # echo 1000 > quota_reset_interval_ms
    # echo 500 > wmarks_high
    # echo 400 > wmarks_mid
    # echo 200 > wmarks_low
    # echo Y > enabled

.. [1] https://research.google/pubs/pub48551/
.. [2] https://lwn.net/Articles/787611/
.. [3] https://www.kernel.org/doc/html/latest/mm/free_page_reporting.html


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

:Original: Documentation/admin-guide/mm/damon/start.rst

:翻译:

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

:校译:

========
入门指南
========

本文通过演示DAMON的默认用户空间工具，简要地介绍了如何使用DAMON。请注意，为了简洁
起见，本文档只描述了它的部分功能。更多细节请参考该工具的使用文档。
`doc <https://github.com/awslabs/damo/blob/next/USAGE.md>`_ .

前提条件
========

内核
----

首先，你要确保你当前系统中跑的内核构建时选定了这个功能选项 ``CONFIG_DAMON_*=y``.

用户空间工具
------------

在演示中，我们将使用DAMON的默认用户空间工具，称为DAMON Operator（DAMO）。它可以在
https://github.com/awslabs/damo找到。下面的例子假设DAMO在你的$PATH上。当然，但
这并不是强制性的。

因为DAMO使用的是DAMON的debugfs接口(详情请参考 :doc:`usage` 中的使用方法) 你应该
确保debugfs被挂载。手动挂载它，如下所示::

    # mount -t debugfs none /sys/kernel/debug/

或者在你的 ``/etc/fstab`` 文件中添加以下一行，这样你的系统就可以在启动时自动挂载
debugfs了::

    debugfs /sys/kernel/debug debugfs defaults 0 0

记录数据访问模式
================

下面的命令记录了一个程序的内存访问模式，并将监测结果保存到文件中。 ::

    $ git clone https://github.com/sjp38/masim
    $ cd masim; make; ./masim ./configs/zigzag.cfg &
    $ sudo damo record -o damon.data $(pidof masim)

命令的前两行下载了一个人工内存访问生成器程序并在后台运行。生成器将重复地逐一访问两个
100 MiB大小的内存区域。你可以用你的真实工作负载来代替它。最后一行要求 ``damo`` 将
访问模式记录在 ``damon.data`` 文件中。

将记录的模式可视化
==================

你可以在heatmap中直观地看到这种模式，显示哪个内存区域（X轴）何时被访问（Y轴）以及访
问的频率（数字）。::

    $ sudo damo report heats --heatmap stdout
    22222222222222222222222222222222222222211111111111111111111111111111111111111100
    44444444444444444444444444444444444444434444444444444444444444444444444444443200
    44444444444444444444444444444444444444433444444444444444444444444444444444444200
    33333333333333333333333333333333333333344555555555555555555555555555555555555200
    33333333333333333333333333333333333344444444444444444444444444444444444444444200
    22222222222222222222222222222222222223355555555555555555555555555555555555555200
    00000000000000000000000000000000000000288888888888888888888888888888888888888400
    00000000000000000000000000000000000000288888888888888888888888888888888888888400
    33333333333333333333333333333333333333355555555555555555555555555555555555555200
    88888888888888888888888888888888888888600000000000000000000000000000000000000000
    88888888888888888888888888888888888888600000000000000000000000000000000000000000
    33333333333333333333333333333333333333444444444444444444444444444444444444443200
    00000000000000000000000000000000000000288888888888888888888888888888888888888400
    [...]
    # access_frequency:  0  1  2  3  4  5  6  7  8  9
    # x-axis: space (139728247021568-139728453431248: 196.848 MiB)
    # y-axis: time (15256597248362-15326899978162: 1 m 10.303 s)
    # resolution: 80x40 (2.461 MiB and 1.758 s for each character)

你也可以直观地看到工作集的大小分布，按大小排序。::

    $ sudo damo report wss --range 0 101 10
    # <percentile> <wss>
    # target_id     18446632103789443072
    # avr:  107.708 MiB
      0             0 B |                                                           |
     10      95.328 MiB |****************************                               |
     20      95.332 MiB |****************************                               |
     30      95.340 MiB |****************************                               |
     40      95.387 MiB |****************************                               |
     50      95.387 MiB |****************************                               |
     60      95.398 MiB |****************************                               |
     70      95.398 MiB |****************************                               |
     80      95.504 MiB |****************************                               |
     90     190.703 MiB |*********************************************************  |
    100     196.875 MiB |***********************************************************|

在上述命令中使用 ``--sortby`` 选项，可以显示工作集的大小是如何按时间顺序变化的。::

    $ sudo damo report wss --range 0 101 10 --sortby time
    # <percentile> <wss>
    # target_id     18446632103789443072
    # avr:  107.708 MiB
      0       3.051 MiB |                                                           |
     10     190.703 MiB |***********************************************************|
     20      95.336 MiB |*****************************                              |
     30      95.328 MiB |*****************************                              |
     40      95.387 MiB |*****************************                              |
     50      95.332 MiB |*****************************                              |
     60      95.320 MiB |*****************************                              |
     70      95.398 MiB |*****************************                              |
     80      95.398 MiB |*****************************                              |
     90      95.340 MiB |*****************************                              |
    100      95.398 MiB |*****************************                              |

数据访问模式感知的内存管理
==========================

以下三个命令使每一个大小>=4K的内存区域在你的工作负载中没有被访问>=60秒，就会被换掉。 ::

    $ echo "#min-size max-size min-acc max-acc min-age max-age action" > test_scheme
    $ echo "4K        max      0       0       60s     max     pageout" >> test_scheme
    $ damo schemes -c test_scheme <pid of your workload>