code:
Chinese translated version of Documentation/arm64/booting.rst

If you have any comment or update to the content, please contact the
original document maintainer directly.  However, if you have a problem
communicating in English you can also ask the Chinese maintainer for
help.  Contact the Chinese maintainer if this translation is outdated
or if there is a problem with the translation.

M:	Will Deacon <will.deacon@arm.com>
zh_CN:	Fu Wei <wefu@redhat.com>
C:	55f058e7574c3615dea4615573a19bdb258696c6
---------------------------------------------------------------------
Documentation/arm64/booting.rst 的中文翻译

如果想评论或更新本文的内容，请直接联系原文档的维护者。如果你使用英文
交流有困难的话，也可以向中文版维护者求助。如果本翻译更新不及时或者翻
译存在问题，请联系中文版维护者。

英文版维护者： Will Deacon <will.deacon@arm.com>
中文版维护者： 傅炜  Fu Wei <wefu@redhat.com>
中文版翻译者： 傅炜  Fu Wei <wefu@redhat.com>
中文版校译者： 傅炜  Fu Wei <wefu@redhat.com>
本文翻译提交时的 Git 检出点为： 55f058e7574c3615dea4615573a19bdb258696c6

以下为正文
---------------------------------------------------------------------
			启动 AArch64 Linux
			==================

作者: Will Deacon <will.deacon@arm.com>
日期: 2012 年 09 月 07 日

本文档基于 Russell King 的 ARM 启动文档，且适用于所有公开发布的
AArch64 Linux 内核代码。

AArch64 异常模型由多个异常级（EL0 - EL3）组成，对于 EL0 和 EL1 异常级
有对应的安全和非安全模式。EL2 是系统管理级，且仅存在于非安全模式下。
EL3 是最高特权级，且仅存在于安全模式下。

基于本文档的目的，我们将简单地使用‘引导装载程序’（‘boot loader’）
这个术语来定义在将控制权交给 Linux 内核前 CPU 上执行的所有软件。
这可能包含安全监控和系统管理代码，或者它可能只是一些用于准备最小启动
环境的指令。

基本上，引导装载程序（至少）应实现以下操作：

1、设置和初始化 RAM
2、设置设备树数据
3、解压内核映像
4、调用内核映像

1、设置和初始化 RAM
-----------------

必要性: 强制

引导装载程序应该找到并初始化系统中所有内核用于保持系统变量数据的 RAM。
这个操作的执行方式因设备而异。（它可能使用内部算法来自动定位和计算所有
RAM，或可能使用对这个设备已知的 RAM 信息，还可能是引导装载程序设计者
想到的任何合适的方法。）

2、设置设备树数据
---------------

必要性: 强制

设备树数据块（dtb）必须 8 字节对齐，且大小不能超过 2MB。由于设备树
数据块将在使能缓存的情况下以 2MB 粒度被映射，故其不能被置于必须以特定
属性映射的2M区域内。

注： v4.2 之前的版本同时要求设备树数据块被置于从内核映像以下
text_offset 字节处算起第一个 512MB 内。

3、解压内核映像
-------------

必要性: 可选

AArch64 内核当前没有提供自解压代码，因此如果使用了压缩内核映像文件
（比如 Image.gz），则需要通过引导装载程序（使用 gzip 等）来进行解压。
若引导装载程序没有实现这个功能，就要使用非压缩内核映像文件。

4、调用内核映像
-------------

必要性: 强制

已解压的内核映像包含一个 64 字节的头，内容如下：

  u32 code0;			/* 可执行代码 */
  u32 code1;			/* 可执行代码 */
  u64 text_offset;		/* 映像装载偏移，小端模式 */
  u64 image_size;		/* 映像实际大小, 小端模式 */
  u64 flags;			/* 内核旗标, 小端模式 *
  u64 res2	= 0;		/* 保留 */
  u64 res3	= 0;		/* 保留 */
  u64 res4	= 0;		/* 保留 */
  u32 magic	= 0x644d5241;	/* 魔数, 小端, "ARM\x64" */
  u32 res5;			/* 保留 （用于 PE COFF 偏移） */

映像头注释：

- 自 v3.17 起，除非另有说明，所有域都是小端模式。

- code0/code1 负责跳转到 stext.

- 当通过 EFI 启动时， 最初 code0/code1 被跳过。
  res5 是到 PE 文件头的偏移，而 PE 文件头含有 EFI 的启动入口点
  （efi_stub_entry）。当 stub 代码完成了它的使命，它会跳转到 code0
  继续正常的启动流程。

- v3.17 之前，未明确指定 text_offset 的字节序。此时，image_size 为零，
  且 text_offset 依照内核字节序为 0x80000。
  当 image_size 非零，text_offset 为小端模式且是有效值，应被引导加载
  程序使用。当 image_size 为零，text_offset 可假定为 0x80000。

- flags 域 (v3.17 引入) 为 64 位小端模式，其编码如下：
  位 0: 	内核字节序。 1 表示大端模式，0 表示小端模式。
  位 1-2:	内核页大小。
			0 - 未指定。
			1 - 4K
			2 - 16K
			3 - 64K
  位 3:		内核物理位置
			0 - 2MB 对齐基址应尽量靠近内存起始处，因为
			    其基址以下的内存无法通过线性映射访问
			1 - 2MB 对齐基址可以在物理内存的任意位置
  位 4-63:	保留。

- 当 image_size 为零时，引导装载程序应试图在内核映像末尾之后尽可能
  多地保留空闲内存供内核直接使用。对内存空间的需求量因所选定的内核
  特性而异, 并无实际限制。

内核映像必须被放置在任意一个可用系统内存 2MB 对齐基址的 text_offset
字节处，并从该处被调用。2MB 对齐基址和内核映像起始地址之间的区域对于
内核来说没有特殊意义，且可能被用于其他目的。
从映像起始地址算起，最少必须准备 image_size 字节的空闲内存供内核使用。
注： v4.6 之前的版本无法使用内核映像物理偏移以下的内存，所以当时建议
将映像尽量放置在靠近系统内存起始的地方。

任何提供给内核的内存（甚至在映像起始地址之前），若未从内核中标记为保留
(如在设备树（dtb）的 memreserve 区域），都将被认为对内核是可用。

在跳转入内核前，必须符合以下状态：

- 停止所有 DMA 设备，这样内存数据就不会因为虚假网络包或磁盘数据而
  被破坏。这可能可以节省你许多的调试时间。

- 主 CPU 通用寄存器设置
  x0 = 系统 RAM 中设备树数据块（dtb）的物理地址。
  x1 = 0 (保留，将来可能使用)
  x2 = 0 (保留，将来可能使用)
  x3 = 0 (保留，将来可能使用)

- CPU 模式
  所有形式的中断必须在 PSTATE.DAIF 中被屏蔽（Debug、SError、IRQ
  和 FIQ）。
  CPU 必须处于 EL2（推荐，可访问虚拟化扩展）或非安全 EL1 模式下。

- 高速缓存、MMU
  MMU 必须关闭。
  指令缓存开启或关闭皆可。
  已载入的内核映像的相应内存区必须被清理，以达到缓存一致性点（PoC）。
  当存在系统缓存或其他使能缓存的一致性主控器时，通常需使用虚拟地址
  维护其缓存，而非 set/way 操作。
  遵从通过虚拟地址操作维护构架缓存的系统缓存必须被配置，并可以被使能。
  而不通过虚拟地址操作维护构架缓存的系统缓存（不推荐），必须被配置且
  禁用。

  *译者注：对于 PoC 以及缓存相关内容，请参考 ARMv8 构架参考手册
   ARM DDI 0487A

- 架构计时器
  CNTFRQ 必须设定为计时器的频率，且 CNTVOFF 必须设定为对所有 CPU
  都一致的值。如果在 EL1 模式下进入内核，则 CNTHCTL_EL2 中的
  EL1PCTEN (bit 0) 必须置位。

- 一致性
  通过内核启动的所有 CPU 在内核入口地址上必须处于相同的一致性域中。
  这可能要根据具体实现来定义初始化过程，以使能每个CPU上对维护操作的
  接收。

- 系统寄存器
  在进入内核映像的异常级中，所有构架中可写的系统寄存器必须通过软件
  在一个更高的异常级别下初始化，以防止在 未知 状态下运行。

  对于拥有 GICv3 中断控制器并以 v3 模式运行的系统：
  - 如果 EL3 存在：
    ICC_SRE_EL3.Enable (位 3) 必须初始化为 0b1。
    ICC_SRE_EL3.SRE (位 0) 必须初始化为 0b1。
  - 若内核运行在 EL1：
    ICC_SRE_EL2.Enable (位 3) 必须初始化为 0b1。
    ICC_SRE_EL2.SRE (位 0) 必须初始化为 0b1。
  - 设备树（DT）或 ACPI 表必须描述一个 GICv3 中断控制器。

  对于拥有 GICv3 中断控制器并以兼容（v2）模式运行的系统：
  - 如果 EL3 存在：
    ICC_SRE_EL3.SRE (位 0) 必须初始化为 0b0。
  - 若内核运行在 EL1：
    ICC_SRE_EL2.SRE (位 0) 必须初始化为 0b0。
  - 设备树（DT）或 ACPI 表必须描述一个 GICv2 中断控制器。

以上对于 CPU 模式、高速缓存、MMU、架构计时器、一致性、系统寄存器的
必要条件描述适用于所有 CPU。所有 CPU 必须在同一异常级别跳入内核。

引导装载程序必须在每个 CPU 处于以下状态时跳入内核入口：

- 主 CPU 必须直接跳入内核映像的第一条指令。通过此 CPU 传递的设备树
  数据块必须在每个 CPU 节点中包含一个 ‘enable-method’ 属性，所
  支持的 enable-method 请见下文。

  引导装载程序必须生成这些设备树属性，并在跳入内核入口之前将其插入
  数据块。

- enable-method 为 “spin-table” 的 CPU 必须在它们的 CPU
  节点中包含一个 ‘cpu-release-addr’ 属性。这个属性标识了一个
  64 位自然对齐且初始化为零的内存位置。

  这些 CPU 必须在内存保留区（通过设备树中的 /memreserve/ 域传递
  给内核）中自旋于内核之外，轮询它们的 cpu-release-addr 位置（必须
  包含在保留区中）。可通过插入 wfe 指令来降低忙循环开销，而主 CPU 将
  发出 sev 指令。当对 cpu-release-addr 所指位置的读取操作返回非零值
  时，CPU 必须跳入此值所指向的地址。此值为一个单独的 64 位小端值，
  因此 CPU 须在跳转前将所读取的值转换为其本身的端模式。

- enable-method 为 “psci” 的 CPU 保持在内核外（比如，在
  memory 节点中描述为内核空间的内存区外，或在通过设备树 /memreserve/
  域中描述为内核保留区的空间中）。内核将会发起在 ARM 文档（编号
  ARM DEN 0022A：用于 ARM 上的电源状态协调接口系统软件）中描述的
  CPU_ON 调用来将 CPU 带入内核。

  *译者注: ARM DEN 0022A 已更新到 ARM DEN 0022C。

  设备树必须包含一个 ‘psci’ 节点，请参考以下文档：
  Documentation/devicetree/bindings/arm/psci.yaml

- 辅助 CPU 通用寄存器设置
  x0 = 0 (保留，将来可能使用)
  x1 = 0 (保留，将来可能使用)
  x2 = 0 (保留，将来可能使用)
  x3 = 0 (保留，将来可能使用)


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

:Original: :ref:`Documentation/arm64/elf_hwcaps.rst <elf_hwcaps_index>`

Translator: Bailu Lin <bailu.lin@vivo.com>

================
ARM64 ELF hwcaps
================

这篇文档描述了 arm64 ELF hwcaps 的用法和语义。

1. 简介
-------

有些硬件或软件功能仅在某些 CPU 实现上和/或在具体某个内核配置上可用，但
对于处于 EL0 的用户空间代码没有可用的架构发现机制。内核通过在辅助向量表
公开一组称为 hwcaps 的标志而把这些功能暴露给用户空间。

用户空间软件可以通过获取辅助向量的 AT_HWCAP 或 AT_HWCAP2 条目来测试功能，
并测试是否设置了相关标志，例如::

	bool floating_point_is_present(void)
	{
		unsigned long hwcaps = getauxval(AT_HWCAP);
		if (hwcaps & HWCAP_FP)
			return true;

		return false;
	}

如果软件依赖于 hwcap 描述的功能，在尝试使用该功能前则应检查相关的 hwcap
标志以验证该功能是否存在。

不能通过其他方式探查这些功能。当一个功能不可用时，尝试使用它可能导致不可
预测的行为，并且无法保证能确切的知道该功能不可用，例如 SIGILL。

2. Hwcaps 的说明
----------------

大多数 hwcaps 旨在说明通过架构 ID 寄存器(处于 EL0 的用户空间代码无法访问)
描述的功能的存在。这些 hwcap 通过 ID 寄存器字段定义，并且应根据 ARM 体系
结构参考手册（ARM ARM）中定义的字段来解释说明。

这些 hwcaps 以下面的形式描述::

    idreg.field == val 表示有某个功能。

当 idreg.field 中有 val 时，hwcaps 表示 ARM ARM 定义的功能是有效的，但是
并不是说要完全和 val 相等，也不是说 idreg.field 描述的其他功能就是缺失的。

其他 hwcaps 可能表明无法仅由 ID 寄存器描述的功能的存在。这些 hwcaps 可能
没有被 ID 寄存器描述，需要参考其他文档。

3. AT_HWCAP 中揭示的 hwcaps
---------------------------

HWCAP_FP
    ID_AA64PFR0_EL1.FP == 0b0000 表示有此功能。

HWCAP_ASIMD
    ID_AA64PFR0_EL1.AdvSIMD == 0b0000 表示有此功能。

HWCAP_EVTSTRM
    通用计时器频率配置为大约100KHz以生成事件。

HWCAP_AES
    ID_AA64ISAR0_EL1.AES == 0b0001 表示有此功能。

HWCAP_PMULL
    ID_AA64ISAR0_EL1.AES == 0b0010 表示有此功能。

HWCAP_SHA1
    ID_AA64ISAR0_EL1.SHA1 == 0b0001 表示有此功能。

HWCAP_SHA2
    ID_AA64ISAR0_EL1.SHA2 == 0b0001 表示有此功能。

HWCAP_CRC32
    ID_AA64ISAR0_EL1.CRC32 == 0b0001 表示有此功能。

HWCAP_ATOMICS
    ID_AA64ISAR0_EL1.Atomic == 0b0010 表示有此功能。

HWCAP_FPHP
    ID_AA64PFR0_EL1.FP == 0b0001 表示有此功能。

HWCAP_ASIMDHP
    ID_AA64PFR0_EL1.AdvSIMD == 0b0001 表示有此功能。

HWCAP_CPUID
    根据 Documentation/arm64/cpu-feature-registers.rst 描述，EL0 可以访问
    某些 ID 寄存器。

    这些 ID 寄存器可能表示功能的可用性。

HWCAP_ASIMDRDM
    ID_AA64ISAR0_EL1.RDM == 0b0001 表示有此功能。

HWCAP_JSCVT
    ID_AA64ISAR1_EL1.JSCVT == 0b0001 表示有此功能。

HWCAP_FCMA
    ID_AA64ISAR1_EL1.FCMA == 0b0001 表示有此功能。

HWCAP_LRCPC
    ID_AA64ISAR1_EL1.LRCPC == 0b0001 表示有此功能。

HWCAP_DCPOP
    ID_AA64ISAR1_EL1.DPB == 0b0001 表示有此功能。

HWCAP_SHA3
    ID_AA64ISAR0_EL1.SHA3 == 0b0001 表示有此功能。

HWCAP_SM3
    ID_AA64ISAR0_EL1.SM3 == 0b0001 表示有此功能。

HWCAP_SM4
    ID_AA64ISAR0_EL1.SM4 == 0b0001 表示有此功能。

HWCAP_ASIMDDP
    ID_AA64ISAR0_EL1.DP == 0b0001 表示有此功能。

HWCAP_SHA512
    ID_AA64ISAR0_EL1.SHA2 == 0b0010 表示有此功能。

HWCAP_SVE
    ID_AA64PFR0_EL1.SVE == 0b0001 表示有此功能。

HWCAP_ASIMDFHM
    ID_AA64ISAR0_EL1.FHM == 0b0001 表示有此功能。

HWCAP_DIT
    ID_AA64PFR0_EL1.DIT == 0b0001 表示有此功能。

HWCAP_USCAT
    ID_AA64MMFR2_EL1.AT == 0b0001 表示有此功能。

HWCAP_ILRCPC
    ID_AA64ISAR1_EL1.LRCPC == 0b0010 表示有此功能。

HWCAP_FLAGM
    ID_AA64ISAR0_EL1.TS == 0b0001 表示有此功能。

HWCAP_SSBS
    ID_AA64PFR1_EL1.SSBS == 0b0010 表示有此功能。

HWCAP_SB
    ID_AA64ISAR1_EL1.SB == 0b0001 表示有此功能。

HWCAP_PACA
    如 Documentation/arm64/pointer-authentication.rst 所描述，
    ID_AA64ISAR1_EL1.APA == 0b0001 或 ID_AA64ISAR1_EL1.API == 0b0001
    表示有此功能。

HWCAP_PACG
    如 Documentation/arm64/pointer-authentication.rst 所描述，
    ID_AA64ISAR1_EL1.GPA == 0b0001 或 ID_AA64ISAR1_EL1.GPI == 0b0001
    表示有此功能。

HWCAP2_DCPODP

    ID_AA64ISAR1_EL1.DPB == 0b0010 表示有此功能。

HWCAP2_SVE2

    ID_AA64ZFR0_EL1.SVEVer == 0b0001 表示有此功能。

HWCAP2_SVEAES

    ID_AA64ZFR0_EL1.AES == 0b0001 表示有此功能。

HWCAP2_SVEPMULL

    ID_AA64ZFR0_EL1.AES == 0b0010 表示有此功能。

HWCAP2_SVEBITPERM

    ID_AA64ZFR0_EL1.BitPerm == 0b0001 表示有此功能。

HWCAP2_SVESHA3

    ID_AA64ZFR0_EL1.SHA3 == 0b0001 表示有此功能。

HWCAP2_SVESM4

    ID_AA64ZFR0_EL1.SM4 == 0b0001 表示有此功能。

HWCAP2_FLAGM2

    ID_AA64ISAR0_EL1.TS == 0b0010 表示有此功能。

HWCAP2_FRINT

    ID_AA64ISAR1_EL1.FRINTTS == 0b0001 表示有此功能。

HWCAP2_SVEI8MM

    ID_AA64ZFR0_EL1.I8MM == 0b0001 表示有此功能。

HWCAP2_SVEF32MM

    ID_AA64ZFR0_EL1.F32MM == 0b0001 表示有此功能。

HWCAP2_SVEF64MM

    ID_AA64ZFR0_EL1.F64MM == 0b0001 表示有此功能。

HWCAP2_SVEBF16

    ID_AA64ZFR0_EL1.BF16 == 0b0001 表示有此功能。

HWCAP2_I8MM

    ID_AA64ISAR1_EL1.I8MM == 0b0001 表示有此功能。

HWCAP2_BF16

    ID_AA64ISAR1_EL1.BF16 == 0b0001 表示有此功能。

HWCAP2_DGH

    ID_AA64ISAR1_EL1.DGH == 0b0001 表示有此功能。

HWCAP2_RNG

    ID_AA64ISAR0_EL1.RNDR == 0b0001 表示有此功能。

HWCAP2_BTI

    ID_AA64PFR0_EL1.BT == 0b0001 表示有此功能。

4. 未使用的 AT_HWCAP 位
-----------------------

为了与用户空间交互，内核保证 AT_HWCAP 的第62、63位将始终返回0。

repo_name: angery/linux


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

:Original: :ref:`Documentation/arm64/hugetlbpage.rst <hugetlbpage_index>`

Translator: Bailu Lin <bailu.lin@vivo.com>

=====================
ARM64中的 HugeTLBpage
=====================

大页依靠有效利用 TLBs 来提高地址翻译的性能。这取决于以下
两点 -

  - 大页的大小
  - TLBs 支持的条目大小

ARM64 接口支持2种大页方式。

1) pud/pmd 级别的块映射
-----------------------

这是常规大页，他们的 pmd 或 pud 页面表条目指向一个内存块。
不管 TLB 中支持的条目大小如何，块映射可以减少翻译大页地址
所需遍历的页表深度。

2) 使用连续位
-------------

架构中转换页表条目(D4.5.3, ARM DDI 0487C.a)中提供一个连续
位告诉 MMU 这个条目是一个连续条目集的一员，它可以被缓存在单
个 TLB 条目中。

在 Linux 中连续位用来增加 pmd 和 pte(最后一级)级别映射的大
小。受支持的连续页表条目数量因页面大小和页表级别而异。

支持以下大页尺寸配置 -

  ====== ========   ====    ========    ===
  -      CONT PTE    PMD    CONT PMD    PUD
  ====== ========   ====    ========    ===
  4K:         64K     2M         32M     1G
  16K:         2M    32M          1G
  64K:         2M   512M         16G
  ====== ========   ====    ========    ===


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

:Original: :ref:`Documentation/arm64/index.rst <arm64_index>`
:Translator: Bailu Lin <bailu.lin@vivo.com>

.. _cn_arm64_index:

==========
ARM64 架构
==========

.. toctree::
    :maxdepth: 2

    amu
    hugetlbpage
    perf
    elf_hwcaps


code:
Chinese translated version of Documentation/arm64/legacy_instructions.rst

If you have any comment or update to the content, please contact the
original document maintainer directly.  However, if you have a problem
communicating in English you can also ask the Chinese maintainer for
help.  Contact the Chinese maintainer if this translation is outdated
or if there is a problem with the translation.

Maintainer: Punit Agrawal <punit.agrawal@arm.com>
            Suzuki K. Poulose <suzuki.poulose@arm.com>
Chinese maintainer: Fu Wei <wefu@redhat.com>
---------------------------------------------------------------------
Documentation/arm64/legacy_instructions.rst 的中文翻译

如果想评论或更新本文的内容，请直接联系原文档的维护者。如果你使用英文
交流有困难的话，也可以向中文版维护者求助。如果本翻译更新不及时或者翻
译存在问题，请联系中文版维护者。

本文翻译提交时的 Git 检出点为： bc465aa9d045feb0e13b4a8f32cc33c1943f62d6

英文版维护者： Punit Agrawal <punit.agrawal@arm.com>
            Suzuki K. Poulose <suzuki.poulose@arm.com>
中文版维护者： 傅炜  Fu Wei <wefu@redhat.com>
中文版翻译者： 傅炜  Fu Wei <wefu@redhat.com>
中文版校译者： 傅炜  Fu Wei <wefu@redhat.com>

以下为正文
---------------------------------------------------------------------
Linux 内核在 arm64 上的移植提供了一个基础框架，以支持构架中正在被淘汰或已废弃指令的模拟执行。
这个基础框架的代码使用未定义指令钩子（hooks）来支持模拟。如果指令存在，它也允许在硬件中启用该指令。

模拟模式可通过写 sysctl 节点（/proc/sys/abi）来控制。
不同的执行方式及 sysctl 节点的相应值，解释如下：

* Undef（未定义）
  值： 0
  产生未定义指令终止异常。它是那些构架中已废弃的指令，如 SWP，的默认处理方式。

* Emulate（模拟）
  值： 1
  使用软件模拟方式。为解决软件迁移问题，这种模拟指令模式的使用是被跟踪的，并会发出速率限制警告。
  它是那些构架中正在被淘汰的指令，如 CP15 barriers（隔离指令），的默认处理方式。

* Hardware Execution（硬件执行）
  值： 2
  虽然标记为正在被淘汰，但一些实现可能提供硬件执行这些指令的使能/禁用操作。
  使用硬件执行一般会有更好的性能，但将无法收集运行时对正被淘汰指令的使用统计数据。

默认执行模式依赖于指令在构架中状态。正在被淘汰的指令应该以模拟（Emulate）作为默认模式，
而已废弃的指令必须默认使用未定义（Undef）模式

注意：指令模拟可能无法应对所有情况。更多详情请参考单独的指令注释。

受支持的遗留指令
-------------
* SWP{B}
节点: /proc/sys/abi/swp
状态: 已废弃
默认执行方式: Undef (0)

* CP15 Barriers
节点: /proc/sys/abi/cp15_barrier
状态: 正被淘汰，不推荐使用
默认执行方式: Emulate (1)

* SETEND
节点: /proc/sys/abi/setend
状态: 正被淘汰，不推荐使用
默认执行方式: Emulate (1)*
注：为了使能这个特性，系统中的所有 CPU 必须在 EL0 支持混合字节序。
如果一个新的 CPU （不支持混合字节序） 在使能这个特性后被热插入系统，
在应用中可能会出现不可预期的结果。


code:
Chinese translated version of Documentation/arm64/memory.rst

If you have any comment or update to the content, please contact the
original document maintainer directly.  However, if you have a problem
communicating in English you can also ask the Chinese maintainer for
help.  Contact the Chinese maintainer if this translation is outdated
or if there is a problem with the translation.

Maintainer: Catalin Marinas <catalin.marinas@arm.com>
Chinese maintainer: Fu Wei <wefu@redhat.com>
---------------------------------------------------------------------
Documentation/arm64/memory.rst 的中文翻译

如果想评论或更新本文的内容，请直接联系原文档的维护者。如果你使用英文
交流有困难的话，也可以向中文版维护者求助。如果本翻译更新不及时或者翻
译存在问题，请联系中文版维护者。

本文翻译提交时的 Git 检出点为： bc465aa9d045feb0e13b4a8f32cc33c1943f62d6

英文版维护者： Catalin Marinas <catalin.marinas@arm.com>
中文版维护者： 傅炜  Fu Wei <wefu@redhat.com>
中文版翻译者： 傅炜  Fu Wei <wefu@redhat.com>
中文版校译者： 傅炜  Fu Wei <wefu@redhat.com>

以下为正文
---------------------------------------------------------------------
		     Linux 在 AArch64 中的内存布局
		     ===========================

作者: Catalin Marinas <catalin.marinas@arm.com>

本文档描述 AArch64 Linux 内核所使用的虚拟内存布局。此构架可以实现
页大小为 4KB 的 4 级转换表和页大小为 64KB 的 3 级转换表。

AArch64 Linux 使用 3 级或 4 级转换表，其页大小配置为 4KB，对于用户和内核
分别都有 39-bit (512GB) 或 48-bit (256TB) 的虚拟地址空间。
对于页大小为 64KB的配置，仅使用 2 级转换表，有 42-bit (4TB) 的虚拟地址空间，但内存布局相同。

用户地址空间的 63:48 位为 0，而内核地址空间的相应位为 1。TTBRx 的
选择由虚拟地址的 63 位给出。swapper_pg_dir 仅包含内核（全局）映射，
而用户 pgd 仅包含用户（非全局）映射。swapper_pg_dir 地址被写入
TTBR1 中，且从不写入 TTBR0。

AArch64 Linux 在页大小为 4KB，并使用 3 级转换表时的内存布局：

起始地址			结束地址			大小		用途
-----------------------------------------------------------------------
0000000000000000	0000007fffffffff	 512GB		用户空间
ffffff8000000000	ffffffffffffffff	 512GB		内核空间

AArch64 Linux 在页大小为 4KB，并使用 4 级转换表时的内存布局：

起始地址			结束地址			大小		用途
-----------------------------------------------------------------------
0000000000000000	0000ffffffffffff	 256TB		用户空间
ffff000000000000	ffffffffffffffff	 256TB		内核空间

AArch64 Linux 在页大小为 64KB，并使用 2 级转换表时的内存布局：

起始地址			结束地址			大小		用途
-----------------------------------------------------------------------
0000000000000000	000003ffffffffff	   4TB		用户空间
fffffc0000000000	ffffffffffffffff	   4TB		内核空间

AArch64 Linux 在页大小为 64KB，并使用 3 级转换表时的内存布局：

起始地址			结束地址			大小		用途
-----------------------------------------------------------------------
0000000000000000	0000ffffffffffff	 256TB		用户空间
ffff000000000000	ffffffffffffffff	 256TB		内核空间

更详细的内核虚拟内存布局，请参阅内核启动信息。

4KB 页大小的转换表查找：

+--------+--------+--------+--------+--------+--------+--------+--------+
|63    56|55    48|47    40|39    32|31    24|23    16|15     8|7      0|
+--------+--------+--------+--------+--------+--------+--------+--------+
 |                 |         |         |         |         |
 |                 |         |         |         |         v
 |                 |         |         |         |   [11:0]  页内偏移
 |                 |         |         |         +-> [20:12] L3 索引
 |                 |         |         +-----------> [29:21] L2 索引
 |                 |         +---------------------> [38:30] L1 索引
 |                 +-------------------------------> [47:39] L0 索引
 +-------------------------------------------------> [63] TTBR0/1

64KB 页大小的转换表查找：

+--------+--------+--------+--------+--------+--------+--------+--------+
|63    56|55    48|47    40|39    32|31    24|23    16|15     8|7      0|
+--------+--------+--------+--------+--------+--------+--------+--------+
 |                 |    |               |              |
 |                 |    |               |              v
 |                 |    |               |            [15:0]  页内偏移
 |                 |    |               +----------> [28:16] L3 索引
 |                 |    +--------------------------> [41:29] L2 索引
 |                 +-------------------------------> [47:42] L1 索引
 +-------------------------------------------------> [63] TTBR0/1

当使用 KVM 时, 管理程序（hypervisor）在 EL2 中通过相对内核虚拟地址的
一个固定偏移来映射内核页（内核虚拟地址的高 24 位设为零）:

起始地址			结束地址			大小		用途
-----------------------------------------------------------------------
0000004000000000	0000007fffffffff	 256GB		在 HYP 中映射的内核对象


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

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

:Original: :ref:`Documentation/arm64/perf.rst <perf_index>`

Translator: Bailu Lin <bailu.lin@vivo.com>

=============
Perf 事件属性
=============

:作者: Andrew Murray <andrew.murray@arm.com>
:日期: 2019-03-06

exclude_user
------------

该属性排除用户空间。

用户空间始终运行在 EL0，因此该属性将排除 EL0。

exclude_kernel
--------------

该属性排除内核空间。

打开 VHE 时内核运行在 EL2，不打开 VHE 时内核运行在 EL1。客户机
内核总是运行在 EL1。

对于宿主机，该属性排除 EL1 和 VHE 上的 EL2。

对于客户机，该属性排除 EL1。请注意客户机从来不会运行在 EL2。

exclude_hv
----------

该属性排除虚拟机监控器。

对于 VHE 宿主机该属性将被忽略，此时我们认为宿主机内核是虚拟机监
控器。

对于 non-VHE 宿主机该属性将排除 EL2，因为虚拟机监控器运行在 EL2
的任何代码主要用于客户机和宿主机的切换。

对于客户机该属性无效。请注意客户机从来不会运行在 EL2。

exclude_host / exclude_guest
----------------------------

这些属性分别排除了 KVM 宿主机和客户机。

KVM 宿主机可能运行在 EL0（用户空间），EL1（non-VHE 内核）和
EL2（VHE 内核 或 non-VHE 虚拟机监控器）。

KVM 客户机可能运行在 EL0（用户空间）和 EL1（内核）。

由于宿主机和客户机之间重叠的异常级别，我们不能仅仅依靠 PMU 的硬件异
常过滤机制-因此我们必须启用/禁用对于客户机进入和退出的计数。而这在
VHE 和 non-VHE 系统上表现不同。

对于 non-VHE 系统的 exclude_host 属性排除 EL2 - 在进入和退出客户
机时，我们会根据 exclude_host 和 exclude_guest 属性在适当的情况下
禁用/启用该事件。

对于 VHE 系统的 exclude_guest 属性排除 EL1，而对其中的 exclude_host
属性同时排除 EL0，EL2。在进入和退出客户机时，我们会适当地根据
exclude_host 和 exclude_guest 属性包括/排除 EL0。

以上声明也适用于在 not-VHE 客户机使用这些属性时，但是请注意客户机从
来不会运行在 EL2。

准确性
------

在 non-VHE 宿主机上，我们在 EL2 进入/退出宿主机/客户机的切换时启用/
关闭计数器 -但是在启用/禁用计数器和进入/退出客户机之间存在一段延时。
对于 exclude_host， 我们可以通过过滤 EL2 消除在客户机进入/退出边界
上用于计数客户机事件的宿主机事件计数器。但是当使用 !exclude_hv 时，
在客户机进入/退出有一个小的停电窗口无法捕获到宿主机的事件。

在 VHE 系统没有停电窗口。


code:
Chinese translated version of Documentation/arm64/silicon-errata.rst

If you have any comment or update to the content, please contact the
original document maintainer directly.  However, if you have a problem
communicating in English you can also ask the Chinese maintainer for
help.  Contact the Chinese maintainer if this translation is outdated
or if there is a problem with the translation.

M:	Will Deacon <will.deacon@arm.com>
zh_CN:	Fu Wei <wefu@redhat.com>
C:	1926e54f115725a9248d0c4c65c22acaf94de4c4
---------------------------------------------------------------------
Documentation/arm64/silicon-errata.rst 的中文翻译

如果想评论或更新本文的内容，请直接联系原文档的维护者。如果你使用英文
交流有困难的话，也可以向中文版维护者求助。如果本翻译更新不及时或者翻
译存在问题，请联系中文版维护者。

英文版维护者： Will Deacon <will.deacon@arm.com>
中文版维护者： 傅炜  Fu Wei <wefu@redhat.com>
中文版翻译者： 傅炜  Fu Wei <wefu@redhat.com>
中文版校译者： 傅炜  Fu Wei <wefu@redhat.com>
本文翻译提交时的 Git 检出点为： 1926e54f115725a9248d0c4c65c22acaf94de4c4

以下为正文
---------------------------------------------------------------------
                芯片勘误和软件补救措施
                ==================

作者: Will Deacon <will.deacon@arm.com>
日期: 2015年11月27日

一个不幸的现实：硬件经常带有一些所谓的“瑕疵（errata）”，导致其在
某些特定情况下会违背构架定义的行为。就基于 ARM 的硬件而言，这些瑕疵
大体可分为以下几类：

  A 类：无可行补救措施的严重缺陷。
  B 类：有可接受的补救措施的重大或严重缺陷。
  C 类：在正常操作中不会显现的小瑕疵。

更多资讯，请在 infocenter.arm.com （需注册）中查阅“软件开发者勘误
笔记”（“Software Developers Errata Notice”）文档。

对于 Linux 而言，B 类缺陷可能需要操作系统的某些特别处理。例如，避免
一个特殊的代码序列，或是以一种特定的方式配置处理器。在某种不太常见的
情况下，为将 A 类缺陷当作 C 类处理，可能需要用类似的手段。这些手段被
统称为“软件补救措施”，且仅在少数情况需要（例如，那些需要一个运行在
非安全异常级的补救措施 *并且* 能被 Linux 触发的情况）。

对于尚在讨论中的可能对未受瑕疵影响的系统产生干扰的软件补救措施，有一个
相应的内核配置（Kconfig）选项被加在 “内核特性（Kernel Features）”->
“基于可选方法框架的 ARM 瑕疵补救措施（ARM errata workarounds via
the alternatives framework）"。这些选项被默认开启，若探测到受影响的CPU，
补丁将在运行时被使用。至于对系统运行影响较小的补救措施，内核配置选项
并不存在，且代码以某种规避瑕疵的方式被构造（带注释为宜）。

这种做法对于在任意内核源代码树中准确地判断出哪个瑕疵已被软件方法所补救
稍微有点麻烦，所以在 Linux 内核中此文件作为软件补救措施的注册表，
并将在新的软件补救措施被提交和向后移植（backported）到稳定内核时被更新。

| 实现者         | 受影响的组件    | 勘误编号        | 内核配置                |
+----------------+-----------------+-----------------+-------------------------+
| ARM            | Cortex-A53      | #826319         | ARM64_ERRATUM_826319    |
| ARM            | Cortex-A53      | #827319         | ARM64_ERRATUM_827319    |
| ARM            | Cortex-A53      | #824069         | ARM64_ERRATUM_824069    |
| ARM            | Cortex-A53      | #819472         | ARM64_ERRATUM_819472    |
| ARM            | Cortex-A53      | #845719         | ARM64_ERRATUM_845719    |
| ARM            | Cortex-A53      | #843419         | ARM64_ERRATUM_843419    |
| ARM            | Cortex-A57      | #832075         | ARM64_ERRATUM_832075    |
| ARM            | Cortex-A57      | #852523         | N/A                     |
| ARM            | Cortex-A57      | #834220         | ARM64_ERRATUM_834220    |
|                |                 |                 |                         |
| Cavium         | ThunderX ITS    | #22375, #24313  | CAVIUM_ERRATUM_22375    |
| Cavium         | ThunderX GICv3  | #23154          | CAVIUM_ERRATUM_23154    |


code:
Chinese translated version of Documentation/arm64/tagged-pointers.rst

If you have any comment or update to the content, please contact the
original document maintainer directly.  However, if you have a problem
communicating in English you can also ask the Chinese maintainer for
help.  Contact the Chinese maintainer if this translation is outdated
or if there is a problem with the translation.

Maintainer: Will Deacon <will.deacon@arm.com>
Chinese maintainer: Fu Wei <wefu@redhat.com>
---------------------------------------------------------------------
Documentation/arm64/tagged-pointers.rst 的中文翻译

如果想评论或更新本文的内容，请直接联系原文档的维护者。如果你使用英文
交流有困难的话，也可以向中文版维护者求助。如果本翻译更新不及时或者翻
译存在问题，请联系中文版维护者。

英文版维护者： Will Deacon <will.deacon@arm.com>
中文版维护者： 傅炜  Fu Wei <wefu@redhat.com>
中文版翻译者： 傅炜  Fu Wei <wefu@redhat.com>
中文版校译者： 傅炜  Fu Wei <wefu@redhat.com>

以下为正文
---------------------------------------------------------------------
		Linux 在 AArch64 中带标记的虚拟地址
		=================================

作者: Will Deacon <will.deacon@arm.com>
日期: 2013 年 06 月 12 日

本文档简述了在 AArch64 地址转换系统中提供的带标记的虚拟地址及其在
AArch64 Linux 中的潜在用途。

内核提供的地址转换表配置使通过 TTBR0 完成的虚拟地址转换（即用户空间
映射），其虚拟地址的最高 8 位（63:56）会被转换硬件所忽略。这种机制
让这些位可供应用程序自由使用，其注意事项如下：

	(1) 内核要求所有传递到 EL1 的用户空间地址带有 0x00 标记。
	    这意味着任何携带用户空间虚拟地址的系统调用（syscall）
	    参数 *必须* 在陷入内核前使它们的最高字节被清零。

	(2) 非零标记在传递信号时不被保存。这意味着在应用程序中利用了
	    标记的信号处理函数无法依赖 siginfo_t 的用户空间虚拟
	    地址所携带的包含其内部域信息的标记。此规则的一个例外是
	    当信号是在调试观察点的异常处理程序中产生的，此时标记的
	    信息将被保存。

	(3) 当使用带标记的指针时需特别留心，因为仅对两个虚拟地址
	    的高字节，C 编译器很可能无法判断它们是不同的。

此构架会阻止对带标记的 PC 指针的利用，因此在异常返回时，其高字节
将被设置成一个为 “55” 的扩展符。


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

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

:翻译:

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

:校译:

.. _cn_core-api_assoc_array:

==================
通用关联数组的实现
==================

简介
====

这个关联数组的实现是一个具有以下属性的对象容器:

1. 对象是不透明的指针。该实现不关心它们指向哪里（如果有的话）或它们指向什么（如果有的
   话）。

   .. note::

      指向对象的指针 *必须* 在最小有效位为零。

2. 对象不需要包含供数组使用的链接块。这允许一个对象同时位于多个数组中。相反，数组是
   由指向对象的元数据块组成的。

3. 对象需要索引键来定位它们在阵列中的位置。

4. 索引键必须是唯一的。插入一个与已经在数组中的且具有相同键值的对象将取代旧的对象。

5. 索引键可以是任何长度，也可以是不同的长度。

6. 索引键应该在早期就对长度进行编码，即在任何由于长度引起的变化出现之前。

7. 索引键可以包括一个哈希值，以便将对象分散到整个数组中。

8. 该数组可以迭代。对象不一定会按索引键的顺序出现。

9.  数组可以在被修改的时候进行迭代，只要RCU的读锁被迭代器持有。然而，请注意，在这种情
    况下，一些对象可能会被看到不止一次。如果这是个问题，迭代器应该锁定以防止修改。然
    而，除非删除，否则对象不会被错过。

10. 数组中的对象可以通过其索引键进行查询。

11. 当数组被修改时，对象可以被查询，前提是进行查询的线程持有RCU的读锁。

该实现在内部使用了一棵由16个指针节点组成的树，这些节点在每一层都由索引键的小数点进行索
引，其方式与基数树相同。为了提高内存效率，可以放置快捷键，以跳过本来是一系列单占节点的地
方。此外，节点将叶子对象指针打包到节点的空闲空间中，而不是做一个额外的分支，直到有对象
需要被添加到一个完整的节点中。

公用API
=======

公用API可以在 ``<linux/assoc_array.h>`` 中找到。关联数组的根是以下结构::

    struct assoc_array {
            ...
    };

该代码是通过启用 ``CONFIG_ASSOCIATIVE_ARRAY`` 来选择的，以::

    ./script/config -e ASSOCIATIVE_ARRAY

编辑脚本
--------

插入和删除功能会产生一个“编辑脚本”，以后可以应用这个脚本来实现更改，而不会造成 ``ENOMEM``
风险。这保留了将被安装在内部树中的预分配的元数据块，并跟踪应用脚本时将从树中删除的元数
据块。

在脚本应用后，这也被用来跟踪死块和死对象，以便以后可以释放它们。释放是在RCU宽限期过后
进行的--因此允许访问功能在RCU读锁下进行。

脚本在API之外显示为一个类型为::

    struct assoc_array_edit;

有两个处理脚本的功能:

1. 应用一个编辑脚本::

    void assoc_array_apply_edit(struct assoc_array_edit *edit);

这将执行编辑功能，插值各种写屏障，以允许在RCU读锁下的访问继续进行。然后，编辑脚本将被
传递给 ``call_rcu()`` ，以释放它和它所指向的任何死的东西。

2. Cancel an edit script::

    void assoc_array_cancel_edit(struct assoc_array_edit *edit);

这将立即释放编辑脚本和所有预分配的内存。如果这是为了插入，新的对象不会被这个函数释放，
而是必须由调用者释放。

这些函数保证不会失败。

操作表
------

各种功能采用了一个操作表::

    struct assoc_array_ops {
            ...
    };

这指出了一些方法，所有这些方法都需要提供:

1. 从调用者数据中获取索引键的一个块::

    unsigned long (*get_key_chunk)(const void *index_key, int level);

这应该返回一个由调用者提供的索引键的块，从level参数给出的 *比特* 位置开始。level参数将
是 ``ASSOC_ARRAY_KEY_CHUNK_SIZE`` 的倍数，该函数应返回 ``ASSOC_ARRAY_KEY_CHUNK_SIZE``
位。不可能出现错误。

2. 获取一个对象的索引键的一个块::

    unsigned long (*get_object_key_chunk)(const void *object, int level);

和前面的函数一样，但是从数组中的一个对象而不是从调用者提供的索引键中获取数据。

3. 看看这是否是我们要找的对象::

    bool (*compare_object)(const void *object, const void *index_key);

将对象与一个索引键进行比较，如果匹配则返回 ``true`` ，不匹配则返回 ``false`` 。

4. 对两个对象的索引键进行比较::

    int (*diff_objects)(const void *object, const void *index_key);

返回指定对象的索引键与给定索引键不同的比特位置，如果它们相同，则返回-1。

5. 释放一个对象::

    void (*free_object)(void *object);

释放指定的对象。注意，这可能是在调用 ``assoc_array_apply_edit()`` 后的一个RCU宽限期内
调用的，所以在模块卸载时可能需要 ``synchronize_rcu()`` 。

操控函数
--------

有一些函数用于操控关联数组:

1. 初始化一个关联数组::

    void assoc_array_init(struct assoc_array *array);

这将初始化一个关联数组的基础结构。它不会失败。

2. 在一个关联数组中插入/替换一个对象::

    struct assoc_array_edit *
    assoc_array_insert(struct assoc_array *array,
                       const struct assoc_array_ops *ops,
                       const void *index_key,
                       void *object);

这将把给定的对象插入数组中。注意，指针的最小有效位必须是0，因为它被用来在内部标记指针的类
型。

如果该键已经存在一个对象，那么它将被新的对象所取代，旧的对象将被自动释放。

``index_key`` 参数应持有索引键信息，并在调用OPP表中的方法时传递给它们。

这个函数不对数组本身做任何改动，而是返回一个必须应用的编辑脚本。如果出现内存不足的错误，会
返回 ``-ENOMEM`` 。

调用者应专门锁定数组的其他修改器。

3. 从一个关联数组中删除一个对象::

    struct assoc_array_edit *
    assoc_array_delete(struct assoc_array *array,
                       const struct assoc_array_ops *ops,
                       const void *index_key);

这将从数组中删除一个符合指定数据的对象。

``index_key`` 参数应持有索引键信息，并在调用OPP表中的方法时传递给它们。

这个函数不对数组本身做任何改动，而是返回一个必须应用的编辑脚本。 ``-ENOMEM`` 在出现内存不足
的错误时返回。如果在数组中没有找到指定的对象，将返回 ``NULL`` 。

调用者应该对数组的其他修改者进行专门锁定。

4. 从一个关联数组中删除所有对象::

    struct assoc_array_edit *
    assoc_array_clear(struct assoc_array *array,
                      const struct assoc_array_ops *ops);

这个函数删除了一个关联数组中的所有对象，使其完全为空。

这个函数没有对数组本身做任何改动，而是返回一个必须应用的编辑脚本。如果出现内存不足
的错误，则返回 ``-ENOMEM`` 。

调用者应专门锁定数组的其他修改者。

5. 销毁一个关联数组，删除所有对象::

    void assoc_array_destroy(struct assoc_array *array,
                             const struct assoc_array_ops *ops);

这将破坏关联数组的内容，使其完全为空。在这个函数销毁数组的同时，不允许另一个线程在RCU读锁
下遍历数组，因为在内存释放时不执行RCU延迟，这需要分配内存。

调用者应该专门针对数组的其他修改者和访问者进行锁定。

6. 垃圾回收一个关联数组::

    int assoc_array_gc(struct assoc_array *array,
                       const struct assoc_array_ops *ops,
                       bool (*iterator)(void *object, void *iterator_data),
                       void *iterator_data);

这是对一个关联数组中的对象进行迭代，并将每个对象传递给 ``iterator()`` 。如果 ``iterator()`` 返回
true，该对象被保留。如果它返回 ``false`` ，该对象将被释放。如果 ``iterator()`` 函数返回 ``true`` ，它必须
在返回之前对该对象进行适当的 ``refcount`` 递增。

如果可能的话，内部树将被打包下来，作为迭代的一部分，以减少其中的节点数量。

``iterator_data`` 被直接传递给 ``iterator()`` ，否则会被函数忽略。

如果成功，该函数将返回 ``0`` ，如果没有足够的内存，则返回 ``-ENOMEM`` 。

在这个函数执行过程中，其他线程有可能在RCU读锁下迭代或搜索阵列。调用者应该专门针对数组的其他
修改者进行锁定。

访问函数
--------

有两个函数用于访问一个关联数组:

1. 遍历一个关联数组中的所有对象::

    int assoc_array_iterate(const struct assoc_array *array,
                            int (*iterator)(const void *object,
                                            void *iterator_data),
                            void *iterator_data);

这将数组中的每个对象传递给迭代器回调函数。 ``iterator_data`` 是该函数的私有数据。

在数组被修改的同时，可以在数组上使用这个方法，前提是RCU读锁被持有。在这种情况下，迭代函数有
可能两次看到某些对象。如果这是个问题，那么修改应该被锁定。然而，迭代算法不应该错过任何对象。

如果数组中没有对象，该函数将返回 ``0`` ，否则将返回最后一次调用的迭代器函数的结果。如果对迭代函数
的任何调用导致非零返回，迭代立即停止。

2. 在一个关联数组中寻找一个对象::

    void *assoc_array_find(const struct assoc_array *array,
                           const struct assoc_array_ops *ops,
                           const void *index_key);

这将直接穿过数组的内部树，到达索引键所指定的对象。

这个函数可以在修改数组的同时用在数组上，前提是RCU读锁被持有。

如果找到对象，该函数将返回对象（并将 ``*_type`` 设置为对象的类型），如果没有找到对象，将返回 ``NULL`` 。

索引键形式
----------

索引键可以是任何形式的，但是由于算法没有被告知键有多长，所以强烈建议在任何由于长度而产生的变化
对比较产生影响之前，索引键应该很早就包括其长度。

这将导致具有不同长度键的叶子相互分散，而具有相同长度键的叶子则聚集在一起。

我们还建议索引键以键的其余部分的哈希值开始，以最大限度地提高整个键空间的散布情况。

分散性越好，内部树就越宽，越低。

分散性差并不是一个太大的问题，因为有快捷键，节点可以包含叶子和元数据指针的混合物。

索引键是以机器字的块状来读取的。每个块被细分为每层一个nibble（4比特），所以在32位CPU上这适合8层，
在64位CPU上适合16层。除非散布情况真的很差，否则不太可能有超过一个字的任何特定索引键需要被使用。

内部工作机制
============

关联数组数据结构有一个内部树。这个树由两种类型的元数据块构成：节点和快捷键。

一个节点是一个槽的数组。每个槽可以包含以下四种东西之一:

* 一个NULL的指针，表示槽是空的。
* 一个指向对象（叶子）的指针。
* 一个指向下一级节点的指针。
* 一个指向快捷键的指针。

基本的内部树形布局
------------------

暂时不考虑快捷键，节点形成一个多级树。索引键空间被树上的节点严格细分，节点出现在固定的层次上。例如::

 Level: 0               1               2               3
        =============== =============== =============== ===============
                                                        NODE D
                        NODE B          NODE C  +------>+---+
                +------>+---+   +------>+---+   |       | 0 |
        NODE A  |       | 0 |   |       | 0 |   |       +---+
        +---+   |       +---+   |       +---+   |       :   :
        | 0 |   |       :   :   |       :   :   |       +---+
        +---+   |       +---+   |       +---+   |       | f |
        | 1 |---+       | 3 |---+       | 7 |---+       +---+
        +---+           +---+           +---+
        :   :           :   :           | 8 |---+
        +---+           +---+           +---+   |       NODE E
        | e |---+       | f |           :   :   +------>+---+
        +---+   |       +---+           +---+           | 0 |
        | f |   |                       | f |           +---+
        +---+   |                       +---+           :   :
                |       NODE F                          +---+
                +------>+---+                           | f |
                        | 0 |           NODE G          +---+
                        +---+   +------>+---+
                        :   :   |       | 0 |
                        +---+   |       +---+
                        | 6 |---+       :   :
                        +---+           +---+
                        :   :           | f |
                        +---+           +---+
                        | f |
                        +---+

在上述例子中，有7个节点（A-G），每个节点有16个槽（0-f）。假设树上没有其他元数据节点，那么密钥空间
是这样划分的::

    KEY PREFIX      NODE
    ==========      ====
    137*            D
    138*            E
    13[0-69-f]*     C
    1[0-24-f]*      B
    e6*             G
    e[0-57-f]*      F
    [02-df]*        A

因此，例如，具有以下示例索引键的键将在适当的节点中被找到::

    INDEX KEY       PREFIX  NODE
    =============== ======= ====
    13694892892489  13      C
    13795289025897  137     D
    13889dde88793   138     E
    138bbb89003093  138     E
    1394879524789   12      C
    1458952489      1       B
    9431809de993ba  -       A
    b4542910809cd   -       A
    e5284310def98   e       F
    e68428974237    e6      G
    e7fffcbd443     e       F
    f3842239082     -       A

为了节省内存，如果一个节点可以容纳它的那部分键空间中的所有叶子，那么这个节点将有所有这些叶子，而不
会有任何元数据指针——即使其中一些叶子想在同一个槽中。

一个节点可以包含叶子和元数据指针的异质性混合。元数据指针必须在与它们的关键空间的细分相匹配的槽中。
叶子可以在任何没有被元数据指针占据的槽中。保证一个节点中没有一个叶子会与元数据指针占据的槽相匹配。
如果元数据指针在那里，任何键与元数据键前缀相匹配的叶必须在元数据指针指向的子树中。

在上面的索引键的例子列表中，节点A将包含::

    SLOT    CONTENT         INDEX KEY (PREFIX)
    ====    =============== ==================
    1       PTR TO NODE B   1*
    any     LEAF            9431809de993ba
    any     LEAF            b4542910809cd
    e       PTR TO NODE F   e*
    any     LEAF            f3842239082

和节点B::

    3	PTR TO NODE C	13*
    any	LEAF		1458952489

快捷键
---------

快捷键是跳过一块键空间的元数据记录。快捷键是一系列通过层次上升的单占节点的替代物。快捷键的存在是
为了节省内存和加快遍历速度。

树的根部有可能是一个快捷键——比如说，树至少包含17个节点，都有键前缀 ``1111`` 。插入算法将插入一个快捷键，
以单次跳过 ``1111`` 的键位，并到达第四层，在这里，这些键位实际上变得不同。

拆分和合并节点
------------------------------

每个节点的最大容量为16个叶子和元数据指针。如果插入算法发现它正试图将一个第17个对象插入到一个节点中，
该节点将被拆分，使得至少两个在该层有一个共同的关键段的叶子最终在一个单独的节点中，该共同的关键段的根
在该槽上。

如果一个完整的节点中的叶子和被插入的叶子足够相似，那么就会在树中插入一个快捷键。

当根植于某个节点的子树中的对象数量下降到16个或更少时，那么该子树将被合并成一个单独的节点——如果可能的
话，这将向根部扩散。

非递归式迭代
------------

每个节点和快捷键都包含一个指向其父节点的后置指针，以及该父节点中指向它的槽数。非递归迭代使用这些来
通过树的根部进行，前往父节点，槽N+1，以确保在没有堆栈的情况下取得进展。

然而，反向指针使得同时改变和迭代变得很棘手。

同时改变和迭代
--------------

有一些情况需要考虑:

1. 简单的插入/替换。这涉及到简单地将一个NULL或旧的匹配叶子的指针替换为屏障后的新叶子的指针。否则元数
   据块不会改变。一个旧的叶子直到RCU宽限期过后才会被释放。

2. 简单删除。这只是涉及到清除一个旧的匹配叶子。元数据块不会有其他变化。旧的叶子直到RCU宽限期之后才会
   被释放。

3. 插入，替换我们还没有进入的子树的一部分。这可能涉及到替换该子树的一部分——但这不会影响迭代，因为我们
   还没有到达它的指针，而且祖先块也不会被替换（这些块的布局不会改变）。

4. 插入替换了我们正在处理的节点。这不是一个问题，因为我们已经通过了锚定指针，直到我们跟随后面的指针才
   会切换到新的布局上——这时我们已经检查了被替换节点的叶子（在跟随任何元数据指针之前，我们会迭代一个节
   点的所有叶子）。

   然而，我们可能会重新看到一些叶子，这些叶子已经被分割成一个新的分支，而这个分支的位置比我们之前的位
   置更远。

5. 插入替换了我们正在处理的依赖分支的节点。这不会影响到我们，直到我们跟随后面的指针。与（4）类似。

6. 删掉我们下面的一个分支。这不会影响我们，因为在我们看到新节点之前，回溯指针会让我们回到新节点的父节
   点。整个崩溃的子树被扔掉了，没有任何变化——而且仍然会在同一个槽上生根，所以我们不应该第二次处理它，
   因为我们会回到槽+1。

.. note::

    在某些情况下，我们需要同时改变一个节点的父指针和父槽指针（比如说，我们在它之前插入了另一个节点，
    并把它往上移了一层）。我们不能在不锁定读取的情况下这样做——所以我们必须同时替换该节点。

    然而，当我们把一个快捷键改成一个节点时，这不是一个问题，因为快捷键只有一个槽，所以当向后遍
    历一个槽时，不会使用父槽号。这意味着先改变槽位号是可以的——只要使用适当的屏障来确保父槽位号在后
    退指针之后被读取。

过时的块和叶子在RCU宽限期过后会被释放，所以只要任何进行遍历或迭代的人持有RCU读锁，旧的上层建筑就不
应该在他们身上消失。


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

:Original: Documentation/core-api/boot-time-mm.rst

:翻译:

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

:校译:

 时奎亮 <alexs@kernel.org>

.. _cn_core-api_boot-time-mm:

================
启动时的内存管理
================

系统初始化早期“正常”的内存管理由于没有设置完毕无法使用。但是内核仍然需要
为各种数据结构分配内存，例如物理页分配器。

一个叫做 ``memblock`` 的专用分配器执行启动时的内存管理。特定架构的初始化
必须在setup_arch()中设置它，并在mem_init()函数中移除它。

一旦早期的内存管理可用，它就为内存分配提供了各种函数和宏。分配请求可以指向
第一个（也可能是唯一的）节点或NUMA系统中的某个特定节点。有一些API变体在分
配失败时panic，也有一些不会panic的。

Memblock还提供了各种控制其自身行为的API。

Memblock概述
============

该API在以下内核代码中:

mm/memblock.c

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

下面是关于memblock数据结构、函数和宏的描述。其中一些实际上是内部的，但由于
它们被记录下来，漏掉它们是很愚蠢的。此外，阅读内部函数的注释可以帮助理解引
擎盖下真正发生的事情。

该API在以下内核代码中:

include/linux/memblock.h
mm/memblock.c


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

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

:翻译:

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

:校译:

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

.. _cn_core-api_cachetlb:

======================
Linux下的缓存和TLB刷新
======================

:作者: David S. Miller <davem@redhat.com>

*译注：TLB，Translation Lookaside Buffer，页表缓存/变换旁查缓冲器*

本文描述了由Linux虚拟内存子系统调用的缓存/TLB刷新接口。它列举了每个接
口，描述了它的预期目的，以及接口被调用后的预期副作用。

下面描述的副作用是针对单处理器的实现，以及在单个处理器上发生的情况。若
为SMP，则只需将定义简单地扩展一下，使发生在某个特定接口的副作用扩展到系
统的所有处理器上。不要被这句话吓到，以为SMP的缓存/tlb刷新一定是很低
效的，事实上，这是一个可以进行很多优化的领域。例如，如果可以证明一个用
户地址空间从未在某个cpu上执行过（见mm_cpumask()），那么就不需要在该
cpu上对这个地址空间进行刷新。

首先是TLB刷新接口，因为它们是最简单的。在Linux下，TLB被抽象为cpu
用来缓存从软件页表获得的虚拟->物理地址转换的东西。这意味着，如果软件页
表发生变化，这个“TLB”缓存中就有可能出现过时（脏）的翻译。因此，当软件页表
发生变化时，内核会在页表发生 *变化后* 调用以下一种刷新方法：

1) ``void flush_tlb_all(void)``

	最严格的刷新。在这个接口运行后，任何以前的页表修改都会对cpu可见。

	这通常是在内核页表被改变时调用的，因为这种转换在本质上是“全局”的。

2) ``void flush_tlb_mm(struct mm_struct *mm)``

	这个接口从TLB中刷新整个用户地址空间。在运行后，这个接口必须确保
	以前对地址空间‘mm’的任何页表修改对cpu来说是可见的。也就是说，在
	运行后，TLB中不会有‘mm’的页表项。

	这个接口被用来处理整个地址空间的页表操作，比如在fork和exec过程
	中发生的事情。

3) ``void flush_tlb_range(struct vm_area_struct *vma,
   unsigned long start, unsigned long end)``

	这里我们要从TLB中刷新一个特定范围的（用户）虚拟地址转换。在运行后，
	这个接口必须确保以前对‘start’到‘end-1’范围内的地址空间‘vma->vm_mm’
	的任何页表修改对cpu来说是可见的。也就是说，在运行后，TLB中不会有
	‘mm’的页表项用于‘start’到‘end-1’范围内的虚拟地址。

	“vma”是用于该区域的备份存储。主要是用于munmap()类型的操作。

	提供这个接口是希望端口能够找到一个合适的有效方法来从TLB中删除多
	个页面大小的转换，而不是让内核为每个可能被修改的页表项调用
	flush_tlb_page(见下文)。

4) ``void flush_tlb_page(struct vm_area_struct *vma, unsigned long addr)``

	这一次我们需要从TLB中删除PAGE_SIZE大小的转换。‘vma’是Linux用来跟
	踪进程的mmap区域的支持结构体，地址空间可以通过vma->vm_mm获得。另
	外，可以通过测试（vma->vm_flags & VM_EXEC）来查看这个区域是否是
	可执行的（因此在split-tlb类型的设置中可能在“指令TLB”中）。

	在运行后，这个接口必须确保之前对用户虚拟地址“addr”的地址空间
	“vma->vm_mm”的页表修改对cpu来说是可见的。也就是说，在运行后，TLB
	中不会有虚拟地址‘addr’的‘vma->vm_mm’的页表项。

	这主要是在故障处理时使用。

5) ``void update_mmu_cache(struct vm_area_struct *vma,
   unsigned long address, pte_t *ptep)``

	在每个缺页异常结束时，这个程序被调用，以告诉体系结构特定的代码，在
	软件页表中，在地址空间“vma->vm_mm”的虚拟地址“地址”处，现在存在
	一个翻译。

	可以用它所选择的任何方式使用这个信息来进行移植。例如，它可以使用这
	个事件来为软件管理的TLB配置预装TLB转换。目前sparc64移植就是这么干
	的。

接下来，我们有缓存刷新接口。一般来说，当Linux将现有的虚拟->物理映射
改变为新的值时，其顺序将是以下形式之一::

	1) flush_cache_mm(mm);
		change_all_page_tables_of(mm);
		flush_tlb_mm(mm);

	2) flush_cache_range(vma, start, end);
		change_range_of_page_tables(mm, start, end);
		flush_tlb_range(vma, start, end);

	3) flush_cache_page(vma, addr, pfn);
		set_pte(pte_pointer, new_pte_val);
		flush_tlb_page(vma, addr);

缓存级别的刷新将永远是第一位的，因为这允许我们正确处理那些缓存严格，
且在虚拟地址被从缓存中刷新时要求一个虚拟地址的虚拟->物理转换存在的系统。
HyperSparc cpu就是这样一个具有这种属性的cpu。

下面的缓存刷新程序只需要在特定的cpu需要的范围内处理缓存刷新。大多数
情况下，这些程序必须为cpu实现，这些cpu有虚拟索引的缓存，当虚拟->物
理转换被改变或移除时，必须被刷新。因此，例如，IA32处理器的物理索引
的物理标记的缓存没有必要实现这些接口，因为这些缓存是完全同步的，并
且不依赖于翻译信息。

下面逐个列出这些程序:

1) ``void flush_cache_mm(struct mm_struct *mm)``

	这个接口将整个用户地址空间从高速缓存中刷掉。也就是说，在运行后，
	将没有与‘mm’相关的缓存行。

	这个接口被用来处理整个地址空间的页表操作，比如在退出和执行过程
	中发生的事情。

2) ``void flush_cache_dup_mm(struct mm_struct *mm)``

	这个接口将整个用户地址空间从高速缓存中刷新掉。也就是说，在运行
	后，将没有与‘mm’相关的缓存行。

	这个接口被用来处理整个地址空间的页表操作，比如在fork过程中发生
	的事情。

	这个选项与flush_cache_mm分开，以允许对VIPT缓存进行一些优化。

3) ``void flush_cache_range(struct vm_area_struct *vma,
   unsigned long start, unsigned long end)``

	在这里，我们要从缓存中刷新一个特定范围的（用户）虚拟地址。运行
	后，在“start”到“end-1”范围内的虚拟地址的“vma->vm_mm”的缓存中
	将没有页表项。

	“vma”是被用于该区域的备份存储。主要是用于munmap()类型的操作。

	提供这个接口是希望端口能够找到一个合适的有效方法来从缓存中删
	除多个页面大小的区域， 而不是让内核为每个可能被修改的页表项调
	用 flush_cache_page (见下文)。

4) ``void flush_cache_page(struct vm_area_struct *vma, unsigned long addr, unsigned long pfn)``

	这一次我们需要从缓存中删除一个PAGE_SIZE大小的区域。“vma”是
	Linux用来跟踪进程的mmap区域的支持结构体，地址空间可以通过
	vma->vm_mm获得。另外，我们可以通过测试（vma->vm_flags &
	VM_EXEC）来查看这个区域是否是可执行的（因此在“Harvard”类
	型的缓存布局中可能是在“指令缓存”中）。

	“pfn”表示“addr”所对应的物理页框（通过PAGE_SHIFT左移这个
	值来获得物理地址）。正是这个映射应该从缓存中删除。

	在运行之后，对于虚拟地址‘addr’的‘vma->vm_mm’，在缓存中不会
	有任何页表项，它被翻译成‘pfn’。

	这主要是在故障处理过程中使用。

5) ``void flush_cache_kmaps(void)``

	只有在平台使用高位内存的情况下才需要实现这个程序。它将在所有的
	kmaps失效之前被调用。

	运行后，内核虚拟地址范围PKMAP_ADDR(0)到PKMAP_ADDR(LAST_PKMAP)
	的缓存中将没有页表项。

	这个程序应该在asm/highmem.h中实现。

6) ``void flush_cache_vmap(unsigned long start, unsigned long end)``
   ``void flush_cache_vunmap(unsigned long start, unsigned long end)``

	在这里，在这两个接口中，我们从缓存中刷新一个特定范围的（内核）
	虚拟地址。运行后，在“start”到“end-1”范围内的虚拟地址的内核地
	址空间的缓存中不会有页表项。

	这两个程序中的第一个是在vmap_range()安装了页表项之后调用的。
	第二个是在vunmap_range()删除页表项之前调用的。

还有一类cpu缓存问题，目前需要一套完全不同的接口来正确处理。最大
的问题是处理器的数据缓存中的虚拟别名。

.. note::

	这段内容有些晦涩，为了减轻中文阅读压力，特作此译注。

	别名（alias）属于缓存一致性问题，当不同的虚拟地址映射相同的
	物理地址，而这些虚拟地址的index不同，此时就发生了别名现象(多
	个虚拟地址被称为别名)。通俗点来说就是指同一个物理地址的数据被
	加载到不同的cacheline中就会出现别名现象。

	常见的解决方法有两种：第一种是硬件维护一致性，设计特定的cpu电
	路来解决问题（例如设计为PIPT的cache）；第二种是软件维护一致性，
	就是下面介绍的sparc的解决方案——页面染色，涉及的技术细节太多，
	译者不便展开，请读者自行查阅相关资料。

您的移植是否容易在其D-cache中出现虚拟别名？嗯，如果您的D-cache
是虚拟索引的，且cache大于PAGE_SIZE（页大小），并且不能防止同一
物理地址的多个cache行同时存在，您就会遇到这个问题。

如果你的D-cache有这个问题，首先正确定义asm/shmparam.h SHMLBA，
它基本上应该是你的虚拟寻址D-cache的大小（或者如果大小是可变的，
则是最大的可能大小）。这个设置将迫使SYSv IPC层只允许用户进程在
这个值的倍数的地址上对共享内存进行映射。

.. note::

	这并不能解决共享mmaps的问题，请查看sparc64移植解决
	这个问题的一个方法（特别是 SPARC_FLAG_MMAPSHARED）。

接下来，你必须解决所有其他情况下的D-cache别名问题。请记住这个事
实，对于一个给定的页面映射到某个用户地址空间，总是至少还有一个映
射，那就是内核在其线性映射中从PAGE_OFFSET开始。因此，一旦第一个
用户将一个给定的物理页映射到它的地址空间，就意味着D-cache的别名
问题有可能存在，因为内核已经将这个页映射到它的虚拟地址。

  ``void copy_user_page(void *to, void *from, unsigned long addr, struct page *page)``
  ``void clear_user_page(void *to, unsigned long addr, struct page *page)``

	这两个程序在用户匿名或COW页中存储数据。它允许一个端口有效地
	避免用户空间和内核之间的D-cache别名问题。

	例如，一个端口可以在复制过程中把“from”和“to”暂时映射到内核
	的虚拟地址上。这两个页面的虚拟地址的选择方式是，内核的加载/存
	储指令发生在虚拟地址上，而这些虚拟地址与用户的页面映射是相同
	的“颜色”。例如，Sparc64就使用这种技术。

	“addr”参数告诉了用户最终要映射这个页面的虚拟地址，“page”参
	数给出了一个指向目标页结构体的指针。

	如果D-cache别名不是问题，这两个程序可以简单地直接调用
	memcpy/memset而不做其他事情。

  ``void flush_dcache_page(struct page *page)``

	任何时候，当内核写到一个页面缓存页，或者内核要从一个页面缓存
	页中读出，并且这个页面的用户空间共享/可写映射可能存在时，
	这个程序就会被调用。

	.. note::

			这个程序只需要为有可能被映射到用户进程的地址空间的
			页面缓存调用。因此，例如，处理页面缓存中vfs符号链
			接的VFS层代码根本不需要调用这个接口。

	“内核写入页面缓存的页面”这句话的意思是，具体来说，内核执行存
	储指令，在该页面的页面->虚拟映射处弄脏该页面的数据。在这里，通
	过刷新的手段处理D-cache的别名是很重要的，以确保这些内核存储对
	该页的用户空间映射是可见的。

	推论的情况也同样重要，如果有用户对这个文件有共享+可写的映射，
	我们必须确保内核对这些页面的读取会看到用户所做的最新的存储。

	如果D-cache别名不是一个问题，这个程序可以简单地定义为该架构上
	的nop。

	在page->flags (PG_arch_1)中有一个位是“架构私有”。内核保证，
	对于分页缓存的页面，当这样的页面第一次进入分页缓存时，它将清除
	这个位。

	这使得这些接口可以更有效地被实现。如果目前没有用户进程映射这个
	页面，它允许我们“推迟”（也许是无限期）实际的刷新过程。请看
	sparc64的flush_dcache_page和update_mmu_cache实现，以了解如
	何做到这一点。

	这个想法是，首先在flush_dcache_page()时，如果page->mapping->i_mmap
	是一个空树，只需标记架构私有页标志位。之后，在update_mmu_cache()
	中，会对这个标志位进行检查，如果设置了，就进行刷新，并清除标志位。

	.. important::

				通常很重要的是，如果你推迟刷新，实际的刷新发生在同一个
				CPU上，因为它将cpu存储到页面上，使其变脏。同样，请看
				sparc64关于如何处理这个问题的例子。

  ``void flush_dcache_folio(struct folio *folio)``

	该函数的调用情形与flush_dcache_page()相同。它允许架构针对刷新整个
	folio页面进行优化，而不是一次刷新一页。

  ``void copy_to_user_page(struct vm_area_struct *vma, struct page *page,
  unsigned long user_vaddr, void *dst, void *src, int len)``
  ``void copy_from_user_page(struct vm_area_struct *vma, struct page *page,
  unsigned long user_vaddr, void *dst, void *src, int len)``

	当内核需要复制任意的数据进出任意的用户页时（比如ptrace()），它将使
	用这两个程序。

	任何必要的缓存刷新或其他需要发生的一致性操作都应该在这里发生。如果
	处理器的指令缓存没有对cpu存储进行窥探，那么你很可能需要为
	copy_to_user_page()刷新指令缓存。

  ``void flush_anon_page(struct vm_area_struct *vma, struct page *page,
  unsigned long vmaddr)``

	当内核需要访问一个匿名页的内容时，它会调用这个函数（目前只有
	get_user_pages()）。注意：flush_dcache_page()故意对匿名页不起作
	用。默认的实现是nop（对于所有相干的架构应该保持这样）。对于不一致性
	的架构，它应该刷新vmaddr处的页面缓存。

  ``void flush_icache_range(unsigned long start, unsigned long end)``

	当内核存储到它将执行的地址中时（例如在加载模块时），这个函数被调用。

	如果icache不对存储进行窥探，那么这个程序将需要对其进行刷新。

  ``void flush_icache_page(struct vm_area_struct *vma, struct page *page)``

	flush_icache_page的所有功能都可以在flush_dcache_page和update_mmu_cache
	中实现。在未来，我们希望能够完全删除这个接口。

最后一类API是用于I/O到内核内特意设置的别名地址范围。这种别名是通过使用
vmap/vmalloc API设置的。由于内核I/O是通过物理页进行的，I/O子系统假定用户
映射和内核偏移映射是唯一的别名。这对vmap别名来说是不正确的，所以内核中任何
试图对vmap区域进行I/O的东西都必须手动管理一致性。它必须在做I/O之前刷新vmap
范围，并在I/O返回后使其失效。

  ``void flush_kernel_vmap_range(void *vaddr, int size)``

	刷新vmap区域中指定的虚拟地址范围的内核缓存。这是为了确保内核在vmap范围
	内修改的任何数据对物理页是可见的。这个设计是为了使这个区域可以安全地执
	行I/O。注意，这个API并 *没有* 刷新该区域的偏移映射别名。

  ``void invalidate_kernel_vmap_range(void *vaddr, int size) invalidates``

	在vmap区域的一个给定的虚拟地址范围的缓存，这可以防止处理器在物理页的I/O
	发生时通过投机性地读取数据而使缓存变脏。这只对读入vmap区域的数据是必要的。


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

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

:Original: Documentation/core-api/circular-buffers.rst

:翻译:

 周彬彬 Binbin Zhou <zhoubinbin@loongson.cn>

:校译:

 司延腾 Yanteng Si <siyanteng@loongson.cn>
 吴想成 Wu Xiangcheng <bobwxc@email.cn>
 时奎亮 Alex Shi <alexs@kernel.org>

==========
环形缓冲区
==========

:作者: David Howells <dhowells@redhat.com>
:作者: Paul E. McKenney <paulmck@linux.ibm.com>

Linux 提供了许多可用于实现循环缓冲的特性。有两组这样的特性：

 (1) 用于确定2次方大小的缓冲区信息的便利函数。

 (2) 可以代替缓冲区中对象的生产者和消费者共享锁的内存屏障。

如下所述，要使用这些设施，只需要一个生产者和一个消费者。可以通过序列化来处理多个
生产者，并通过序列化来处理多个消费者。

.. Contents:

 (*) 什么是环形缓冲区？

 (*) 测量2次幂缓冲区

 (*) 内存屏障与环形缓冲区的结合使用
     - 生产者
     - 消费者

 (*) 延伸阅读

什么是环形缓冲区？
==================

首先，什么是环形缓冲区？环形缓冲区是具有固定的有限大小的缓冲区，它有两个索引：

 (1) 'head'索引 - 生产者将元素插入缓冲区的位置。

 (2) 'tail'索引 - 消费者在缓冲区中找到下一个元素的位置。

通常，当tail指针等于head指针时，表明缓冲区是空的；而当head指针比tail指针少一个时，
表明缓冲区是满的。

添加元素时，递增head索引；删除元素时，递增tail索引。tail索引不应该跳过head索引，
两个索引在到达缓冲区末端时都应该被赋值为0，从而允许海量的数据流过缓冲区。

通常情况下，元素都有相同的单元大小，但这并不是使用以下技术的严格要求。如果要在缓
冲区中包含多个元素或可变大小的元素，则索引可以增加超过1，前提是两个索引都没有超过
另一个。然而，实现者必须小心，因为超过一个单位大小的区域可能会覆盖缓冲区的末端并
且缓冲区会被分成两段。

测量2次幂缓冲区
===============

计算任意大小的环形缓冲区的占用或剩余容量通常是一个费时的操作，需要使用模（除法）
指令。但是如果缓冲区的大小为2次幂，则可以使用更快的按位与指令代替。

Linux提供了一组用于处理2次幂环形缓冲区的宏。可以通过以下方式使用::

	#include <linux/circ_buf.h>

这些宏包括：

 (#) 测量缓冲区的剩余容量::

	CIRC_SPACE(head_index, tail_index, buffer_size);

     返回缓冲区[1]中可插入元素的剩余空间大小。

 (#) 测量缓冲区中的最大连续立即可用空间::

	CIRC_SPACE_TO_END(head_index, tail_index, buffer_size);

     返回缓冲区[1]中剩余的连续空间的大小，元素可以立即插入其中，而不必绕回到缓冲
     区的开头。

 (#) 测量缓冲区的使用数::

	CIRC_CNT(head_index, tail_index, buffer_size);

     返回当前占用缓冲区[2]的元素数量。

 (#) 测量缓冲区的连续使用数::

	CIRC_CNT_TO_END(head_index, tail_index, buffer_size);

     返回可以从缓冲区中提取的连续元素[2]的数量，而不必绕回到缓冲区的开头。

这里的每一个宏名义上都会返回一个介于0和buffer_size-1之间的值，但是：

 (1) CIRC_SPACE*()是为了在生产者中使用。对生产者来说，它们将返回一个下限，因为生
     产者控制着head索引，但消费者可能仍然在另一个CPU上耗尽缓冲区并移动tail索引。

     对消费者来说，它将显示一个上限，因为生产者可能正忙于耗尽空间。

 (2) CIRC_CNT*()是为了在消费者中使用。对消费者来说，它们将返回一个下限，因为消费
     者控制着tail索引，但生产者可能仍然在另一个CPU上填充缓冲区并移动head索引。

     对于生产者，它将显示一个上限，因为消费者可能正忙于清空缓冲区。

 (3) 对于第三方来说，生产者和消费者对索引的写入顺序是无法保证的，因为它们是独立的，
     而且可能是在不同的CPU上进行的，所以在这种情况下的结果只是一种猜测，甚至可能
     是错误的。

内存屏障与环形缓冲区的结合使用
==============================

通过将内存屏障与环形缓冲区结合使用，可以避免以下需求：

 (1) 使用单个锁来控制对缓冲区两端的访问，从而允许同时填充和清空缓冲区；以及

 (2) 使用原子计数器操作。

这有两个方面：填充缓冲区的生产者和清空缓冲区的消费者。在任何时候，只应有一个生产
者在填充缓冲区，同样的也只应有一个消费者在清空缓冲区，但双方可以同时操作。

生产者
------

生产者看起来像这样::

	spin_lock(&producer_lock);

	unsigned long head = buffer->head;
	/* spin_unlock()和下一个spin_lock()提供必要的排序。 */
	unsigned long tail = READ_ONCE(buffer->tail);

	if (CIRC_SPACE(head, tail, buffer->size) >= 1) {
		/* 添加一个元素到缓冲区 */
		struct item *item = buffer[head];

		produce_item(item);

		smp_store_release(buffer->head,
				  (head + 1) & (buffer->size - 1));

		/* wake_up()将确保在唤醒任何人之前提交head */
		wake_up(consumer);
	}

	spin_unlock(&producer_lock);

这将表明CPU必须在head索引使其对消费者可用之前写入新项目的内容，同时CPU必须在唤醒
消费者之前写入修改后的head索引。

请注意，wake_up()并不保证任何形式的屏障，除非确实唤醒了某些东西。因此我们不能依靠
它来进行排序。但是数组中始终有一个元素留空，因此生产者必须产生两个元素，然后才可
能破坏消费者当前正在读取的元素。同时，消费者连续调用之间成对的解锁-加锁提供了索引
读取（指示消费者已清空给定元素）和生产者对该相同元素的写入之间的必要顺序。

消费者
------

消费者看起来像这样::

	spin_lock(&consumer_lock);

	/* 读取该索引处的内容之前，先读取索引 */
	unsigned long head = smp_load_acquire(buffer->head);
	unsigned long tail = buffer->tail;

	if (CIRC_CNT(head, tail, buffer->size) >= 1) {

		/* 从缓冲区中提取一个元素 */
		struct item *item = buffer[tail];

		consume_item(item);

		/* 在递增tail之前完成对描述符的读取。 */
		smp_store_release(buffer->tail,
				  (tail + 1) & (buffer->size - 1));
	}

	spin_unlock(&consumer_lock);

这表明CPU在读取新元素之前确保索引是最新的，然后在写入新的尾指针之前应确保CPU已完
成读取该元素，这将擦除该元素。

请注意，使用READ_ONCE()和smp_load_acquire()来读取反向（head）索引。这可以防止编译
器丢弃并重新加载其缓存值。如果您能确定反向（head）索引将仅使用一次，则这不是必须
的。smp_load_acquire()还可以强制CPU对后续的内存引用进行排序。类似地，两种算法都使
用smp_store_release()来写入线程的索引。这记录了我们正在写入可以并发读取的内容的事
实，以防止编译器破坏存储，并强制对以前的访问进行排序。

延伸阅读
========

关于Linux的内存屏障设施的描述，请查看Documentation/memory-barriers.txt。


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

:Original: Documentation/core-api/cpu_hotplug.rst
:翻译:

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

:校译:

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

.. _cn_core_api_cpu_hotplug:

=================
内核中的CPU热拔插
=================

:时间: 2021年9月
:作者: Sebastian Andrzej Siewior <bigeasy@linutronix.de>,
       Rusty Russell <rusty@rustcorp.com.au>,
       Srivatsa Vaddagiri <vatsa@in.ibm.com>,
       Ashok Raj <ashok.raj@intel.com>,
       Joel Schopp <jschopp@austin.ibm.com>,
       Thomas Gleixner <tglx@linutronix.de>

简介
====

现代系统架构的演进已经在处理器中引入了先进的错误报告和纠正能力。有一些OEM也支
持可热拔插的NUMA（Non Uniform Memory Access，非统一内存访问）硬件,其中物理
节点的插入和移除需要支持CPU热插拔。

这样的进步要求内核可用的CPU被移除，要么是出于配置的原因，要么是出于RAS的目的，
以保持一个不需要的CPU不在系统执行路径。因此需要在Linux内核中支持CPU热拔插。

CPU热拔插支持的一个更新颖的用途是它在SMP的暂停恢复支持中的应用。双核和超线程支
持使得即使是笔记本电脑也能运行不支持这些方法的SMP内核。

命令行开关
==========

``maxcpus=n``
  限制启动时的CPU为 *n* 个。例如，如果你有四个CPU，使用 ``maxcpus=2`` 将只能启
  动两个。你可以选择稍后让其他CPU上线。

``nr_cpus=n``
  限制内核将支持的CPU总量。如果这里提供的数量低于实际可用的CPU数量，那么其他CPU
  以后就不能上线了。

``additional_cpus=n``
  使用它来限制可热插拔的CPU。该选项设置
  ``cpu_possible_mask = cpu_present_mask + additional_cpus``

  这个选项只限于IA64架构。

``possible_cpus=n``
  这个选项设置 ``cpu_possible_mask`` 中的 ``possible_cpus`` 位。

  这个选项只限于X86和S390架构。

``cpu0_hotplug``
  允许关闭CPU0。

  这个选项只限于X86架构。

CPU位图
=======

``cpu_possible_mask``
  系统中可能可用CPU的位图。这是用来为per_cpu变量分配一些启动时的内存，这些变量
  不会随着CPU的可用或移除而增加/减少。一旦在启动时的发现阶段被设置，该映射就是静态
  的，也就是说，任何时候都不会增加或删除任何位。根据你的系统需求提前准确地调整它
  可以节省一些启动时的内存。

``cpu_online_mask``
  当前在线的所有CPU的位图。在一个CPU可用于内核调度并准备接收设备的中断后，它被
  设置在 ``__cpu_up()`` 中。当使用 ``__cpu_disable()`` 关闭一个CPU时，它被清
  空，在此之前，所有的操作系统服务包括中断都被迁移到另一个目标CPU。

``cpu_present_mask``
  系统中当前存在的CPU的位图。它们并非全部在线。当物理热拔插被相关的子系统
  （如ACPI）处理时，可以改变和添加新的位或从位图中删除，这取决于事件是
  hot-add/hot-remove。目前还没有定死规定。典型的用法是在启动时启动拓扑结构，这时
  热插拔被禁用。

你真的不需要操作任何系统的CPU映射。在大多数情况下，它们应该是只读的。当设置每个
CPU资源时，几乎总是使用 ``cpu_possible_mask`` 或 ``for_each_possible_cpu()``
来进行迭代。宏 ``for_each_cpu()`` 可以用来迭代一个自定义的CPU掩码。

不要使用 ``cpumask_t`` 以外的任何东西来表示CPU的位图。

使用CPU热拔插
=============

内核选项 *CONFIG_HOTPLUG_CPU* 需要被启用。它目前可用于多种架构，包括ARM、MIPS、
PowerPC和X86。配置是通过sysfs接口完成的::

 $ ls -lh /sys/devices/system/cpu
 total 0
 drwxr-xr-x  9 root root    0 Dec 21 16:33 cpu0
 drwxr-xr-x  9 root root    0 Dec 21 16:33 cpu1
 drwxr-xr-x  9 root root    0 Dec 21 16:33 cpu2
 drwxr-xr-x  9 root root    0 Dec 21 16:33 cpu3
 drwxr-xr-x  9 root root    0 Dec 21 16:33 cpu4
 drwxr-xr-x  9 root root    0 Dec 21 16:33 cpu5
 drwxr-xr-x  9 root root    0 Dec 21 16:33 cpu6
 drwxr-xr-x  9 root root    0 Dec 21 16:33 cpu7
 drwxr-xr-x  2 root root    0 Dec 21 16:33 hotplug
 -r--r--r--  1 root root 4.0K Dec 21 16:33 offline
 -r--r--r--  1 root root 4.0K Dec 21 16:33 online
 -r--r--r--  1 root root 4.0K Dec 21 16:33 possible
 -r--r--r--  1 root root 4.0K Dec 21 16:33 present

文件 *offline* 、 *online* 、*possible* 、*present* 代表CPU掩码。每个CPU文件
夹包含一个 *online* 文件，控制逻辑上的开（1）和关（0）状态。要在逻辑上关闭CPU4::

 $ echo 0 > /sys/devices/system/cpu/cpu4/online
  smpboot: CPU 4 is now offline

一旦CPU被关闭，它将从 */proc/interrupts* 、*/proc/cpuinfo* 中被删除，也不应该
被 *top* 命令显示出来。要让CPU4重新上线::

 $ echo 1 > /sys/devices/system/cpu/cpu4/online
 smpboot: Booting Node 0 Processor 4 APIC 0x1

CPU又可以使用了。这应该对所有的CPU都有效。CPU0通常比较特殊，被排除在CPU热拔插之外。
在X86上，内核选项 *CONFIG_BOOTPARAM_HOTPLUG_CPU0* 必须被启用，以便能够关闭CPU0。
或者，可以使用内核命令选项 *cpu0_hotplug* 。CPU0的一些已知的依赖性:

* 从休眠/暂停中恢复。如果CPU0处于离线状态，休眠/暂停将失败。
* PIC中断。如果检测到PIC中断，CPU0就不能被移除。

如果你发现CPU0上有任何依赖性，请告知Fenghua Yu <fenghua.yu@intel.com>。

CPU的热拔插协作
===============

下线情况
--------

一旦CPU被逻辑关闭，注册的热插拔状态的清除回调将被调用，从 ``CPUHP_ONLINE`` 开始，到
``CPUHP_OFFLINE`` 状态结束。这包括:

* 如果任务因暂停操作而被冻结，那么 *cpuhp_tasks_frozen* 将被设置为true。

* 所有进程都会从这个将要离线的CPU迁移到新的CPU上。新的CPU是从每个进程的当前cpuset中
  选择的，它可能是所有在线CPU的一个子集。

* 所有针对这个CPU的中断都被迁移到新的CPU上。

* 计时器也会被迁移到新的CPU上。

* 一旦所有的服务被迁移，内核会调用一个特定的例程 ``__cpu_disable()`` 来进行特定的清
  理。

CPU热插拔API
============

CPU热拔插状态机
---------------

CPU热插拔使用一个从CPUHP_OFFLINE到CPUHP_ONLINE的线性状态空间的普通状态机。每个状态都
有一个startup和teardown的回调。

当一个CPU上线时，将按顺序调用startup回调，直到达到CPUHP_ONLINE状态。当设置状态的回调
或将实例添加到多实例状态时，也可以调用它们。

当一个CPU下线时，将按相反的顺序依次调用teardown回调，直到达到CPUHP_OFFLINE状态。当删
除状态的回调或从多实例状态中删除实例时，也可以调用它们。

如果某个使用场景只需要一个方向的热插拔操作回调（CPU上线或CPU下线），则在设置状态时，
可以将另一个不需要的回调设置为NULL。

状态空间被划分成三个阶段:

* PREPARE阶段

  PREPARE阶段涵盖了从CPUHP_OFFLINE到CPUHP_BRINGUP_CPU之间的状态空间。

  在该阶段中，startup回调在CPU上线操作启动CPU之前被调用，teardown回调在CPU下线操作使
  CPU功能失效之后被调用。

  这些回调是在控制CPU上调用的，因为它们显然不能在热插拔的CPU上运行，此时热插拔的CPU要
  么还没有启动，要么已经功能失效。

  startup回调用于设置CPU成功上线所需要的资源。teardown回调用于释放资源或在热插拔的CPU
  功能失效后，将待处理的工作转移到在线的CPU上。

  允许startup回调失败。如果回调失败，CPU上线操作被中止，CPU将再次被降到之前的状态（通
  常是CPUHP_OFFLINE）。

  本阶段中的teardown回调不允许失败。

* STARTING阶段

  STARTING阶段涵盖了CPUHP_BRINGUP_CPU + 1到CPUHP_AP_ONLINE之间的状态空间。

  该阶段中的startup回调是在早期CPU设置代码中的CPU上线操作期间，禁用中断的情况下在热拔
  插的CPU上被调用。teardown回调是在CPU完全关闭前不久的CPU下线操作期间，禁用中断的情况
  下在热拔插的CPU上被调用。

  该阶段中的回调不允许失败。

  回调用于低级别的硬件初始化/关机和核心子系统。

* ONLINE阶段

  ONLINE阶段涵盖了CPUHP_AP_ONLINE + 1到CPUHP_ONLINE之间的状态空间。

  该阶段中的startup回调是在CPU上线时在热插拔的CPU上调用的。teardown回调是在CPU下线操
  作时在热插拔CPU上调用的。

  回调是在每个CPU热插拔线程的上下文中调用的，该线程绑定在热插拔的CPU上。回调是在启用
  中断和抢占的情况下调用的。

  允许回调失败。如果回调失败，CPU热插拔操作被中止，CPU将恢复到之前的状态。

CPU 上线/下线操作
-----------------

一个成功的上线操作如下::

  [CPUHP_OFFLINE]
  [CPUHP_OFFLINE + 1]->startup()       -> 成功
  [CPUHP_OFFLINE + 2]->startup()       -> 成功
  [CPUHP_OFFLINE + 3]                  -> 略过，因为startup == NULL
  ...
  [CPUHP_BRINGUP_CPU]->startup()       -> 成功
  === PREPARE阶段结束
  [CPUHP_BRINGUP_CPU + 1]->startup()   -> 成功
  ...
  [CPUHP_AP_ONLINE]->startup()         -> 成功
  === STARTUP阶段结束
  [CPUHP_AP_ONLINE + 1]->startup()     -> 成功
  ...
  [CPUHP_ONLINE - 1]->startup()        -> 成功
  [CPUHP_ONLINE]

一个成功的下线操作如下::

  [CPUHP_ONLINE]
  [CPUHP_ONLINE - 1]->teardown()       -> 成功
  ...
  [CPUHP_AP_ONLINE + 1]->teardown()    -> 成功
  === STARTUP阶段开始
  [CPUHP_AP_ONLINE]->teardown()        -> 成功
  ...
  [CPUHP_BRINGUP_ONLINE - 1]->teardown()
  ...
  === PREPARE阶段开始
  [CPUHP_BRINGUP_CPU]->teardown()
  [CPUHP_OFFLINE + 3]->teardown()
  [CPUHP_OFFLINE + 2]                  -> 略过，因为teardown == NULL
  [CPUHP_OFFLINE + 1]->teardown()
  [CPUHP_OFFLINE]

一个失败的上线操作如下::

  [CPUHP_OFFLINE]
  [CPUHP_OFFLINE + 1]->startup()       -> 成功
  [CPUHP_OFFLINE + 2]->startup()       -> 成功
  [CPUHP_OFFLINE + 3]                  -> 略过，因为startup == NULL
  ...
  [CPUHP_BRINGUP_CPU]->startup()       -> 成功
  === PREPARE阶段结束
  [CPUHP_BRINGUP_CPU + 1]->startup()   -> 成功
  ...
  [CPUHP_AP_ONLINE]->startup()         -> 成功
  === STARTUP阶段结束
  [CPUHP_AP_ONLINE + 1]->startup()     -> 成功
  ---
  [CPUHP_AP_ONLINE + N]->startup()     -> 失败
  [CPUHP_AP_ONLINE + (N - 1)]->teardown()
  ...
  [CPUHP_AP_ONLINE + 1]->teardown()
  === STARTUP阶段开始
  [CPUHP_AP_ONLINE]->teardown()
  ...
  [CPUHP_BRINGUP_ONLINE - 1]->teardown()
  ...
  === PREPARE阶段开始
  [CPUHP_BRINGUP_CPU]->teardown()
  [CPUHP_OFFLINE + 3]->teardown()
  [CPUHP_OFFLINE + 2]                  -> 略过，因为teardown == NULL
  [CPUHP_OFFLINE + 1]->teardown()
  [CPUHP_OFFLINE]

一个失败的下线操作如下::

  [CPUHP_ONLINE]
  [CPUHP_ONLINE - 1]->teardown()       -> 成功
  ...
  [CPUHP_ONLINE - N]->teardown()       -> 失败
  [CPUHP_ONLINE - (N - 1)]->startup()
  ...
  [CPUHP_ONLINE - 1]->startup()
  [CPUHP_ONLINE]

递归失败不能被合理地处理。
请看下面的例子，由于下线操作失败而导致的递归失败::

  [CPUHP_ONLINE]
  [CPUHP_ONLINE - 1]->teardown()       -> 成功
  ...
  [CPUHP_ONLINE - N]->teardown()       -> 失败
  [CPUHP_ONLINE - (N - 1)]->startup()  -> 成功
  [CPUHP_ONLINE - (N - 2)]->startup()  -> 失败

CPU热插拔状态机在此停止，且不再尝试回滚，因为这可能会导致死循环::

  [CPUHP_ONLINE - (N - 1)]->teardown() -> 成功
  [CPUHP_ONLINE - N]->teardown()       -> 失败
  [CPUHP_ONLINE - (N - 1)]->startup()  -> 成功
  [CPUHP_ONLINE - (N - 2)]->startup()  -> 失败
  [CPUHP_ONLINE - (N - 1)]->teardown() -> 成功
  [CPUHP_ONLINE - N]->teardown()       -> 失败

周而复始，不断重复。在这种情况下，CPU留在该状态中::

  [CPUHP_ONLINE - (N - 1)]

这至少可以让系统取得进展，让用户有机会进行调试，甚至解决这个问题。

分配一个状态
------------

有两种方式分配一个CPU热插拔状态:

* 静态分配

  当子系统或驱动程序有相对于其他CPU热插拔状态的排序要求时，必须使用静态分配。例如，
  在CPU上线操作期间，PERF核心startup回调必须在PERF驱动startup回调之前被调用。在CPU
  下线操作中，驱动teardown回调必须在核心teardown回调之前调用。静态分配的状态由
  cpuhp_state枚举中的常量描述，可以在include/linux/cpuhotplug.h中找到。

  在适当的位置将状态插入枚举中，这样就满足了排序要求。状态常量必须被用于状态的设置
  和移除。

  当状态回调不是在运行时设置的，并且是kernel/cpu.c中CPU热插拔状态数组初始化的一部分
  时，也需要静态分配。

* 动态分配

  当对状态回调没有排序要求时，动态分配是首选方法。状态编号由setup函数分配，并在成功
  后返回给调用者。

  只有PREPARE和ONLINE阶段提供了一个动态分配范围。STARTING阶段则没有，因为该部分的大多
  数回调都有明确的排序要求。

CPU热插拔状态的设置
-------------------

核心代码提供了以下函数用来设置状态：

* cpuhp_setup_state(state, name, startup, teardown)
* cpuhp_setup_state_nocalls(state, name, startup, teardown)
* cpuhp_setup_state_cpuslocked(state, name, startup, teardown)
* cpuhp_setup_state_nocalls_cpuslocked(state, name, startup, teardown)

对于一个驱动程序或子系统有多个实例，并且每个实例都需要调用相同的CPU hotplug状态回
调的情况，CPU hotplug核心提供多实例支持。与驱动程序特定的实例列表相比，其优势在于
与实例相关的函数完全针对CPU hotplug操作进行序列化，并在添加和删除时提供状态回调的
自动调用。要设置这样一个多实例状态，可以使用以下函数：

* cpuhp_setup_state_multi(state, name, startup, teardown)

@state参数要么是静态分配的状态，要么是动态分配状态（PUHP_PREPARE_DYN，CPUHP_ONLINE_DYN）
的常量之一， 具体取决于应该分配动态状态的状态阶段（PREPARE，ONLINE）。

@name参数用于sysfs输出和检测。命名惯例是"subsys:mode"或"subsys/driver:mode"，
例如 "perf:mode"或"perf/x86:mode"。常见的mode名称有：

======== ============================================
prepare  对应PREPARE阶段中的状态

dead     对应PREPARE阶段中不提供startup回调的状态

starting 对应STARTING阶段中的状态

dying    对应STARTING阶段中不提供startup回调的状态

online   对应ONLINE阶段中的状态

offline  对应ONLINE阶段中不提供startup回调的状态
======== ============================================

由于@name参数只用于sysfs和检测，如果其他mode描述符比常见的描述符更好地描述状态的性质，
也可以使用。

@name参数的示例："perf/online", "perf/x86:prepare", "RCU/tree:dying", "sched/waitempty"

@startup参数是一个指向回调的函数指针，在CPU上线操作时被调用。若应用不需要startup
回调，则将该指针设为NULL。

@teardown参数是一个指向回调的函数指针，在CPU下线操作时调用。若应用不需要teardown
回调，则将该指针设为NULL。

这些函数在处理已注册回调的方式上有所不同:

  * cpuhp_setup_state_nocalls(), cpuhp_setup_state_nocalls_cpuslocked()和
    cpuhp_setup_state_multi()只注册回调。

  * cpuhp_setup_state()和cpuhp_setup_state_cpuslocked()注册回调，并对当前状态大于新
    安装状态的所有在线CPU调用@startup回调（如果不是NULL）。根据状态阶段，回调要么在
    当前的CPU上调用（PREPARE阶段），要么在CPU的热插拔线程中调用每个在线CPU（ONLINE阶段）。

    如果CPU N的回调失败，那么CPU 0...N-1的teardown回调被调用以回滚操作。状态设置失败，
    状态的回调没有被注册，在动态分配的情况下，分配的状态被释放。

状态设置和回调调用是针对CPU热拔插操作进行序列化的。如果设置函数必须从CPU热插拔的读
锁定区域调用，那么必须使用_cpuslocked()变体。这些函数不能在CPU热拔插回调中使用。

函数返回值：
  ======== ==========================================================
  0        静态分配的状态设置成功

  >0       动态分配的状态设置成功

           返回的数值是被分配的状态编号。如果状态回调后来必须被移除，
           例如模块移除，那么这个数值必须由调用者保存，并作为状态移
           除函数的@state参数。对于多实例状态，动态分配的状态编号也
           需要作为实例添加/删除操作的@state参数。

  <0	   操作失败
  ======== ==========================================================

移除CPU热拔插状态
-----------------

为了移除一个之前设置好的状态，提供了如下函数：

* cpuhp_remove_state(state)
* cpuhp_remove_state_nocalls(state)
* cpuhp_remove_state_nocalls_cpuslocked(state)
* cpuhp_remove_multi_state(state)

@state参数要么是静态分配的状态，要么是由cpuhp_setup_state*()在动态范围内分配
的状态编号。如果状态在动态范围内，则状态编号被释放，可再次进行动态分配。

这些函数在处理已注册回调的方式上有所不同:

  * cpuhp_remove_state_nocalls(), cpuhp_remove_state_nocalls_cpuslocked()
    和 cpuhp_remove_multi_state()只删除回调。

  * cpuhp_remove_state()删除回调，并调用所有当前状态大于被删除状态的在线CPU的
    teardown回调（如果不是NULL）。根据状态阶段，回调要么在当前的CPU上调用
    （PREPARE阶段），要么在CPU的热插拔线程中调用每个在线CPU（ONLINE阶段）。

    为了完成移除工作，teardown回调不能失败。

状态移除和回调调用是针对CPU热拔插操作进行序列化的。如果移除函数必须从CPU hotplug
读取锁定区域调用，那么必须使用_cpuslocked()变体。这些函数不能从CPU热插拔的回调中使用。

如果一个多实例的状态被移除，那么调用者必须先移除所有的实例。

多实例状态实例管理
------------------

一旦多实例状态被建立，实例就可以被添加到状态中：

  * cpuhp_state_add_instance(state, node)
  * cpuhp_state_add_instance_nocalls(state, node)

@state参数是一个静态分配的状态或由cpuhp_setup_state_multi()在动态范围内分配的状
态编号。

@node参数是一个指向hlist_node的指针，它被嵌入到实例的数据结构中。这个指针被交给
多实例状态的回调，可以被回调用来通过container_of()检索到实例。

这些函数在处理已注册回调的方式上有所不同:

  * cpuhp_state_add_instance_nocalls()只将实例添加到多实例状态的节点列表中。

  * cpuhp_state_add_instance()为所有当前状态大于@state的在线CPU添加实例并调用与
    @state相关的startup回调（如果不是NULL）。该回调只对将要添加的实例进行调用。
    根据状态阶段，回调要么在当前的CPU上调用（PREPARE阶段），要么在CPU的热插拔线
    程中调用每个在线CPU（ONLINE阶段）。

    如果CPU N的回调失败，那么CPU 0 ... N-1的teardown回调被调用以回滚操作，该函数
    失败，实例不会被添加到多实例状态的节点列表中。

要从状态的节点列表中删除一个实例，可以使用这些函数:

  * cpuhp_state_remove_instance(state, node)
  * cpuhp_state_remove_instance_nocalls(state, node)

参数与上述cpuhp_state_add_instance*()变体相同。

这些函数在处理已注册回调的方式上有所不同:

  * cpuhp_state_remove_instance_nocalls()只从状态的节点列表中删除实例。

  * cpuhp_state_remove_instance()删除实例并调用与@state相关的回调（如果不是NULL），
    用于所有当前状态大于@state的在线CPU。 该回调只对将要被移除的实例进行调用。
    根据状态阶段，回调要么在当前的CPU上调用（PREPARE阶段），要么在CPU的热插拔
    线程中调用每个在线CPU（ONLINE阶段）。

    为了完成移除工作，teardown回调不能失败。

节点列表的添加/删除操作和回调调用是针对CPU热拔插操作进行序列化。这些函数不能在
CPU hotplug回调和CPU hotplug读取锁定区域内使用。

样例
----

在STARTING阶段设置和取消静态分配的状态，以获取上线和下线操作的通知::

   ret = cpuhp_setup_state(CPUHP_SUBSYS_STARTING, "subsys:starting", subsys_cpu_starting, subsys_cpu_dying);
   if (ret < 0)
        return ret;
   ....
   cpuhp_remove_state(CPUHP_SUBSYS_STARTING);

在ONLINE阶段设置和取消动态分配的状态，以获取下线操作的通知::

   state = cpuhp_setup_state(CPUHP_ONLINE_DYN, "subsys:offline", NULL, subsys_cpu_offline);
   if (state < 0)
       return state;
   ....
   cpuhp_remove_state(state);

在ONLINE阶段设置和取消动态分配的状态，以获取有关上线操作的通知，而无需调用回调::

   state = cpuhp_setup_state_nocalls(CPUHP_ONLINE_DYN, "subsys:online", subsys_cpu_online, NULL);
   if (state < 0)
       return state;
   ....
   cpuhp_remove_state_nocalls(state);

在ONLINE阶段设置、使用和取消动态分配的多实例状态，以获得上线和下线操作的通知::

   state = cpuhp_setup_state_multi(CPUHP_ONLINE_DYN, "subsys:online", subsys_cpu_online, subsys_cpu_offline);
   if (state < 0)
       return state;
   ....
   ret = cpuhp_state_add_instance(state, &inst1->node);
   if (ret)
        return ret;
   ....
   ret = cpuhp_state_add_instance(state, &inst2->node);
   if (ret)
        return ret;
   ....
   cpuhp_remove_instance(state, &inst1->node);
   ....
   cpuhp_remove_instance(state, &inst2->node);
   ....
   remove_multi_state(state);

测试热拔插状态
==============

验证自定义状态是否按预期工作的一个方法是关闭一个CPU，然后再把它上线。也可以把CPU放到某
些状态（例如 ``CPUHP_AP_ONLINE`` ），然后再回到 ``CPUHP_ONLINE`` 。这将模拟在
``CPUHP_AP_ONLINE`` 之后的一个状态出现错误，从而导致回滚到在线状态。

所有注册的状态都被列举在 ``/sys/devices/system/cpu/hotplug/states`` ::

 $ tail /sys/devices/system/cpu/hotplug/states
 138: mm/vmscan:online
 139: mm/vmstat:online
 140: lib/percpu_cnt:online
 141: acpi/cpu-drv:online
 142: base/cacheinfo:online
 143: virtio/net:online
 144: x86/mce:online
 145: printk:online
 168: sched:active
 169: online

要将CPU4回滚到 ``lib/percpu_cnt:online`` ，再回到在线状态，只需发出::

  $ cat /sys/devices/system/cpu/cpu4/hotplug/state
  169
  $ echo 140 > /sys/devices/system/cpu/cpu4/hotplug/target
  $ cat /sys/devices/system/cpu/cpu4/hotplug/state
  140

需要注意的是，状态140的清除回调已经被调用。现在重新上线::

  $ echo 169 > /sys/devices/system/cpu/cpu4/hotplug/target
  $ cat /sys/devices/system/cpu/cpu4/hotplug/state
  169

启用追踪事件后，单个步骤也是可见的::

  #  TASK-PID   CPU#    TIMESTAMP  FUNCTION
  #     | |       |        |         |
      bash-394  [001]  22.976: cpuhp_enter: cpu: 0004 target: 140 step: 169 (cpuhp_kick_ap_work)
   cpuhp/4-31   [004]  22.977: cpuhp_enter: cpu: 0004 target: 140 step: 168 (sched_cpu_deactivate)
   cpuhp/4-31   [004]  22.990: cpuhp_exit:  cpu: 0004  state: 168 step: 168 ret: 0
   cpuhp/4-31   [004]  22.991: cpuhp_enter: cpu: 0004 target: 140 step: 144 (mce_cpu_pre_down)
   cpuhp/4-31   [004]  22.992: cpuhp_exit:  cpu: 0004  state: 144 step: 144 ret: 0
   cpuhp/4-31   [004]  22.993: cpuhp_multi_enter: cpu: 0004 target: 140 step: 143 (virtnet_cpu_down_prep)
   cpuhp/4-31   [004]  22.994: cpuhp_exit:  cpu: 0004  state: 143 step: 143 ret: 0
   cpuhp/4-31   [004]  22.995: cpuhp_enter: cpu: 0004 target: 140 step: 142 (cacheinfo_cpu_pre_down)
   cpuhp/4-31   [004]  22.996: cpuhp_exit:  cpu: 0004  state: 142 step: 142 ret: 0
      bash-394  [001]  22.997: cpuhp_exit:  cpu: 0004  state: 140 step: 169 ret: 0
      bash-394  [005]  95.540: cpuhp_enter: cpu: 0004 target: 169 step: 140 (cpuhp_kick_ap_work)
   cpuhp/4-31   [004]  95.541: cpuhp_enter: cpu: 0004 target: 169 step: 141 (acpi_soft_cpu_online)
   cpuhp/4-31   [004]  95.542: cpuhp_exit:  cpu: 0004  state: 141 step: 141 ret: 0
   cpuhp/4-31   [004]  95.543: cpuhp_enter: cpu: 0004 target: 169 step: 142 (cacheinfo_cpu_online)
   cpuhp/4-31   [004]  95.544: cpuhp_exit:  cpu: 0004  state: 142 step: 142 ret: 0
   cpuhp/4-31   [004]  95.545: cpuhp_multi_enter: cpu: 0004 target: 169 step: 143 (virtnet_cpu_online)
   cpuhp/4-31   [004]  95.546: cpuhp_exit:  cpu: 0004  state: 143 step: 143 ret: 0
   cpuhp/4-31   [004]  95.547: cpuhp_enter: cpu: 0004 target: 169 step: 144 (mce_cpu_online)
   cpuhp/4-31   [004]  95.548: cpuhp_exit:  cpu: 0004  state: 144 step: 144 ret: 0
   cpuhp/4-31   [004]  95.549: cpuhp_enter: cpu: 0004 target: 169 step: 145 (console_cpu_notify)
   cpuhp/4-31   [004]  95.550: cpuhp_exit:  cpu: 0004  state: 145 step: 145 ret: 0
   cpuhp/4-31   [004]  95.551: cpuhp_enter: cpu: 0004 target: 169 step: 168 (sched_cpu_activate)
   cpuhp/4-31   [004]  95.552: cpuhp_exit:  cpu: 0004  state: 168 step: 168 ret: 0
      bash-394  [005]  95.553: cpuhp_exit:  cpu: 0004  state: 169 step: 140 ret: 0

可以看到，CPU4一直下降到时间戳22.996，然后又上升到95.552。所有被调用的回调，
包括它们的返回代码都可以在跟踪中看到。

架构的要求
==========

需要具备以下功能和配置：

``CONFIG_HOTPLUG_CPU``
  这个配置项需要在Kconfig中启用

``__cpu_up()``
  调出一个cpu的架构接口

``__cpu_disable()``
  关闭CPU的架构接口，在此程序返回后，内核不能再处理任何中断。这包括定时器的关闭。

``__cpu_die()``
  这实际上是为了确保CPU的死亡。实际上，看看其他架构中实现CPU热拔插的一些示例代
  码。对于那个特定的架构，处理器被从 ``idle()`` 循环中拿下来。 ``__cpu_die()``
  通常会等待一些per_cpu状态的设置，以确保处理器的死亡例程被调用来保持活跃。

用户空间通知
============

在CPU成功上线或下线后，udev事件被发送。一个udev规则，比如::

  SUBSYSTEM=="cpu", DRIVERS=="processor", DEVPATH=="/devices/system/cpu/*", RUN+="the_hotplug_receiver.sh"

将接收所有事件。一个像这样的脚本::

  #!/bin/sh

  if [ "${ACTION}" = "offline" ]
  then
      echo "CPU ${DEVPATH##*/} offline"

  elif [ "${ACTION}" = "online" ]
  then
      echo "CPU ${DEVPATH##*/} online"

  fi

可以进一步处理该事件。

内核内联文档参考
================

该API在以下内核代码中:

include/linux/cpuhotplug.h


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

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

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

:翻译:

 周彬彬 Binbin Zhou <zhoubinbin@loongson.cn>

:校译:

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

================
errseq_t数据类型
================

``errseq_t`` 是一种在一个地方记录错误的方法，并允许任意数量的 ``订阅者`` 判断自上
次采样点以来是否发生了变化。

最初的用例是跟踪文件同步系统调用（ ``fsync``, ``fdatasync``, ``msync`` 和
``sync_file_range`` ）的错误，但它也可以用于其他情况。

它被实现为一个无符号的32位值。低位被指定保存错误代码（在1和MAX_ERRNO之间）。高位
用作计数器。这里是用原子操作而不是锁来完成的，因此可以从任何上下文中调用这些函数。

请注意，如果频繁记录新错误，则存在冲突风险，因为我们用作计数器的位很少。

为了缓解这种情况，错误值和计数器之间的位被用作一个标志，以判断自记录新值以来是否
对该值进行了采样。这使我们能够避免在上次记录错误后没有人取样的情况下碰撞计数器。

因此，我们得到了一个类似这样的值：

+--------------------------------------+------+------------------------+
| 31..13                               |  12  | 11..0                  |
+--------------------------------------+------+------------------------+
| 计数器                               | 标志 | 错误值                 |
+--------------------------------------+------+------------------------+

总体思路是让 ``观察者`` 对errseq_t值进行采样，并将其保留为运行游标。该值稍后可用
于判断自采样完成后是否发生了任何新错误，并原子地记录检查时的状态。这使得我们能在
一个地方记录错误，然后有许多 ``观察者`` 可以判断自上次检查以来该值是否发生了变化。

新的errseq_t应始终清零。全零的errseq_t值是从未出现错误的特殊（但常见）情况。因此，
如果您希望知道自首次初始化以来是否曾经有过错误集，则全零值被用作 ``纪元`` 。

API的使用方法
=============

让我给你们讲一个关于员工drone的故事。现在，他总体上是个好员工，但公司有点...管理
繁重。他今天必须向77名主管汇报，明天 ``大老板`` 要从外地赶来，他肯定也会考验这个
可怜的家伙。

他们都把工作交给他去做---多到他都记不住谁交给他什么了，但这并不是什么大问题。主管
们只想知道他什么时候完成他们迄今为止交给他的所有工作，以及自从他们上次询问以来他
是否犯了任何错误。

他可能在他们实际上并没有交给他的工作上犯了错误，但他无法在那么详细的层面上记录事
情，他所能记得的只是他最近犯的错误。

下面是我们 ``worker_drone`` 的表达式::

        struct worker_drone {
                errseq_t        wd_err; /* 用来记录错误 */
        };

每天， ``worker_drone`` 都是以一张白纸开始的::

        struct worker_drone wd;

        wd.wd_err = (errseq_t)0;

主管们进来后对当天的工作进行初步了解。他们并不关心在他们观察开始之前发生的任何事
情::

        struct supervisor {
                errseq_t        s_wd_err; /* wd_err的私有“游标” */
                spinlock_t      s_wd_err_lock; /* 保护s_wd_err */
        }

        struct supervisor       su;

        su.s_wd_err = errseq_sample(&wd.wd_err);
        spin_lock_init(&su.s_wd_err_lock);

现在他们开始给他布置任务。每隔几分钟，他们就要求他完成迄今为止交给他的所有工作。
然后问他是否有犯任何错误::

        spin_lock(&su.su_wd_err_lock);
        err = errseq_check_and_advance(&wd.wd_err, &su.s_wd_err);
        spin_unlock(&su.su_wd_err_lock);

到目前为止，它只是不断返回0。

现在，这家公司的老板非常吝啬，给了他不合格的设备来完成他的工作。偶尔设备会出现故
障，导致他犯错。他重重地叹了一口气，并把它记录下来::

        errseq_set(&wd.wd_err, -EIO);

...然后继续工作。主管们最终会再次检查，他们在下次检查时都会发现这个错误。后续的调
用将返回0，直到记录下另一个错误，此时将向每个调用报告一次。

请注意，主管们无法知道他们犯了多少错误，只能知道自上次检查以来是否犯了一个错误，
以及记录的最新值。

偶尔，大老板会来抽查，要求员工为他做一次性的工作。他并不像主管们那样全职观察员工，
但他确实需要知道在他的工作处理过程中是否发生了错误。

他只需对员工当前的errseq_t进行采样，然后用它来判断后来是否发生了错误::

        errseq_t since = errseq_sample(&wd.wd_err);
        /* 提交一些工作，等待完成 */
        err = errseq_check(&wd.wd_err, since);

由于他只是要在那个点之后丢弃 ``since`` ，所以他不需要在这里推进它。同时他也不需要
任何锁，因为它不能被其他人使用。

序列化更新errseq_t游标
======================

请注意，errseq_t API在check_and_advance_operation期间不保护errseq_t游标。只有典型
的错误代码是被原子化处理的。在多任务同时使用同一个errseq_t游标的情况下，对该游标
的更新进行序列化是很重要的。

如果不这样做，那么游标就有可能向后移动。在这种情况下，同一个错误可能被报告多次。

因此，通常先执行errseq_check检查是否有任何变化，然后在获取锁后才执行
errseq_check_and_advance。例如::

        if (errseq_check(&wd.wd_err, READ_ONCE(su.s_wd_err)) {
                /* su.s_wd_err被s_wd_err_lock保护 */
                spin_lock(&su.s_wd_err_lock);
                err = errseq_check_and_advance(&wd.wd_err, &su.s_wd_err);
                spin_unlock(&su.s_wd_err_lock);
        }

这就避免了自上次检查以来没有任何变化的常见情况下的自旋锁。

函数
====

该API在以下内核代码中:

lib/errseq.c


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

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

:翻译:

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

:校译:

 时奎亮 <alexs@kernel.org>

.. _cn_core-api_genalloc:

genalloc/genpool子系统
======================

内核中有许多内存分配子系统，每一个都是针对特定的需求。然而，有时候，内核开发者需
要为特定范围的特殊用途的内存实现一个新的分配器；通常这个内存位于某个设备上。该设
备的驱动程序的作者当然可以写一个小的分配器来完成工作，但这是让内核充满几十个测试
差劲的分配器的方法。早在2005年，Jes Sorensen从sym53c8xx_2驱动中提取了其中的一
个分配器，并将其作为一个通用模块发布，用于创建特设的内存分配器。这段代码在2.6.13
版本中被合并；此后它被大大地修改了。

.. _posted: https://lwn.net/Articles/125842/

使用这个分配器的代码应该包括<linux/genalloc.h>。这个动作从创建一个池开始，使用
一个:

该API在以下内核代码中:

lib/genalloc.c

对gen_pool_create()的调用将创建一个内存池。分配的粒度由min_alloc_order设置；它
是一个log-base-2（以2为底的对数）的数字，就像页面分配器使用的数字一样，但它指的是
字节而不是页面。因此，如果min_alloc_order被传递为3，那么所有的分配将是8字节的倍数。
增加min_alloc_order可以减少跟踪池中内存所需的内存。nid参数指定哪一个NUMA节点应该被
用于分配管家结构体；如果调用者不关心，它可以是-1。

“管理的”接口devm_gen_pool_create()将内存池与一个特定的设备联系起来。在其他方面，
当给定的设备被销毁时，它将自动清理内存池。

一个内存池池被关闭的方法是:

该API在以下内核代码中:

lib/genalloc.c

值得注意的是，如果在给定的内存池中仍有未完成的分配，这个函数将采取相当极端的步骤，调用
BUG()，使整个系统崩溃。你已经被警告了。

一个新创建的内存池没有内存可以分配。在这种状态下，它是相当无用的，所以首要任务之一通常
是向内存池里添加内存。这可以通过以下方式完成:

该API在以下内核代码中:

include/linux/genalloc.h

lib/genalloc.c

对gen_pool_add()的调用将把从地址（在内核的虚拟地址空间）开始的内存的大小字节放入
给定的池中，再次使用nid作为节点ID进行辅助内存分配。gen_pool_add_virt()变体将显式
物理地址与内存联系起来；只有在内存池被用于DMA分配时，这才是必要的。

从内存池中分配内存（并将其放回）的函数是:

该API在以下内核代码中:

include/linux/genalloc.h

lib/genalloc.c

正如人们所期望的，gen_pool_alloc()将从给定的池中分配size<字节。gen_pool_dma_alloc()
变量分配内存用于DMA操作，返回dma所指向的空间中的相关物理地址。这只有在内存是用
gen_pool_add_virt()添加的情况下才会起作用。请注意，这个函数偏离了genpool通常使用
无符号长值来表示内核地址的模式；它返回一个void * 来代替。

这一切看起来都比较简单；事实上，一些开发者显然认为这太简单了。毕竟，上面的接口没有提
供对分配函数如何选择返回哪块特定内存的控制。如果需要这样的控制，下面的函数将是有意义
的:

该API在以下内核代码中:

lib/genalloc.c

使用gen_pool_alloc_algo()进行的分配指定了一种用于选择要分配的内存的算法；默认算法可
以用gen_pool_set_algo()来设置。数据值被传递给算法；大多数算法会忽略它，但偶尔也会需
要它。当然，人们可以写一个特殊用途的算法，但是已经有一套公平的算法可用了:

- gen_pool_first_fit是一个简单的初配分配器；如果没有指定其他算法，这是默认算法。

- gen_pool_first_fit_align强迫分配有一个特定的对齐方式（通过genpool_data_align结
  构中的数据传递）。

- gen_pool_first_fit_order_align 按照大小的顺序排列分配。例如，一个60字节的分配将
  以64字节对齐。

- gen_pool_best_fit，正如人们所期望的，是一个简单的最佳匹配分配器。

- gen_pool_fixed_alloc在池中的一个特定偏移量（通过数据参数在genpool_data_fixed结
  构中传递）进行分配。如果指定的内存不可用，则分配失败。

还有一些其他的函数，主要是为了查询内存池中的可用空间或迭代内存块等目的。然而，大多数
用户应该不需要以上描述的功能。如果幸运的话，对这个模块的广泛认识将有助于防止在未来编
写特殊用途的内存分配器。

该API在以下内核代码中:

lib/genalloc.c


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

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

:Original: Documentation/core-api/generic-radix-tree.rst

:翻译:

 周彬彬 Binbin Zhou <zhoubinbin@loongson.cn>

===================
通用基数树/稀疏数组
===================

通用基数树/稀疏数组的相关内容请见include/linux/generic-radix-tree.h文件中的
“DOC: Generic radix trees/sparse arrays”。

通用基数树函数
--------------

该API在以下内核代码中:

include/linux/generic-radix-tree.h


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

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

:翻译:

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

:校译:

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

.. include:: <isonum.txt>

.. _cn_core-api_genericirq:

================
Linux通用IRQ处理
================

:版权: |copy| 2005-2010: Thomas Gleixner
:版权: |copy| 2005-2006:  Ingo Molnar

简介
====

通用中断处理层是为了给设备驱动程序提供一个完整的中断处理抽象（层）。它能够处
理所有不同类型的中断控制器硬件。设备驱动程序使用通用API函数来请求、启用、禁
用和释放中断。驱动程序不需要知道任何关于硬件处理中断的细节，所以它们可以在不同的
平台上使用而不需要修改代码。

本文档提供给那些希望在通用IRQ处理层的帮助下实现基于其架构的中断子系统的开发
者。

理论依据
========

Linux中中断处理的原始实现使用__do_IRQ()超级处理程序，它能够处理每种类型的
中断逻辑。

最初，Russell King确定了不同类型的处理程序，以便为Linux 2.5/2.6中的ARM中
断处理程序实现建立一个相当通用的集合。他区分了以下几种类型:

-  电平触发型

-  边沿触发型

-  简单型

在实现过程中，我们发现了另一种类型:

-  响应EOI（end of interrupt）型

在SMP的__do_IRQ()超级处理程序中，还需定义一种类型：

-  每cpu型（针对CPU SMP）

这种高层IRQ处理程序的拆分实现使我们能够为每个特定的中断类型优化中断处理的流
程。这减少了该特定代码路径的复杂性，并允许对特定类型进行优化处理。

最初的通用IRQ实现使用hw_interrupt_type结构体及其 ``->ack`` ``->end`` 等回
调来区分超级处理程序中的流控制。这导致了流逻辑和低级硬件逻辑的混合，也导致了
不必要的代码重复：例如i386中的 ``ioapic_level_irq`` 和 ``ioapic_edge_irq`` ，
这两个IRQ类型共享许多低级的细节，但有不同的流处理。

一个更自然的抽象是“irq流”和“芯片细节”的干净分离。

分析一些架构的IRQ子系统的实现可以发现，他们中的大多数可以使用一套通用的“irq
流”方法，只需要添加芯片级的特定代码。这种分离对于那些需要IRQ流本身而不需要芯
片细节的特定（子）架构也很有价值——以提供了一个更透明的IRQ子系统设计。

每个中断描述符都被分配给它自己的高层流程处理程序，这通常是一个通用的实现。(这
种高层次的流程处理程序的实现也使得提供解复用处理程序变得简单，这可以在各种架
构的嵌入式平台上找到。)

这种分离使得通用中断处理层更加灵活和可扩展。例如，一个（子）架构可以使用通用
的IRQ流实现“电平触发型”中断，并添加一个（子）架构特定的“边沿型”实现。

为了使向新模型的过渡更容易，并防止破坏现有实现，__do_IRQ()超级处理程序仍然
可用。这导致了一种暂时的双重性。随着时间的推移，新的模型应该在越来越多的架构中
被使用，因为它能使IRQ子系统更小更干净。它已经被废弃三年了，即将被删除。

已知的缺陷和假设
================

没有（但愿如此）。

抽象层
======

中断代码中主要有三个抽象层次:

1. 高级别的驱动API

2. 高级别的IRQ流处理器

3. 芯片级的硬件封装

中断控制流
----------

每个中断都由一个中断描述符结构体irq_desc来描述。中断是由一个“无符号整型”的数值来
引用的，它在描述符结构体数组中选择相应的中断描述符结构体。描述符结构体包含状态
信息和指向中断流方法和中断芯片结构的指针，这些都是分配给这个中断的。

每当中断触发时，低级架构代码通过调用desc->handle_irq()调用到通用中断代码中。
这个高层IRQ处理函数只使用由分配的芯片描述符结构体引用的desc->irq_data.chip
基元。

高级驱动程序API
---------------

高层驱动API由以下函数组成:

-  request_irq()

-  request_threaded_irq()

-  free_irq()

-  disable_irq()

-  enable_irq()

-  disable_irq_nosync() (SMP only)

-  synchronize_irq() (SMP only)

-  irq_set_irq_type()

-  irq_set_irq_wake()

-  irq_set_handler_data()

-  irq_set_chip()

-  irq_set_chip_data()

详见自动生成的函数文档。

.. note::

    由于文档构建流程所限，中文文档中并没有引入自动生成的函数文档，所以请读者直接
    阅读源码注释。

电平触发型IRQ流处理程序
-----------------------

通用层提供了一套预定义的irq-flow方法:

-  handle_level_irq()

-  handle_edge_irq()

-  handle_fasteoi_irq()

-  handle_simple_irq()

-  handle_percpu_irq()

-  handle_edge_eoi_irq()

-  handle_bad_irq()

中断流处理程序（无论是预定义的还是架构特定的）由架构在启动期间或设备初始化期间分配给
特定中断。

默认流实现
~~~~~~~~~~

辅助函数
^^^^^^^^

辅助函数调用芯片基元，并被默认流实现所使用。以下是实现的辅助函数（简化摘录）::

    default_enable(struct irq_data *data)
    {
        desc->irq_data.chip->irq_unmask(data);
    }

    default_disable(struct irq_data *data)
    {
        if (!delay_disable(data))
            desc->irq_data.chip->irq_mask(data);
    }

    default_ack(struct irq_data *data)
    {
        chip->irq_ack(data);
    }

    default_mask_ack(struct irq_data *data)
    {
        if (chip->irq_mask_ack) {
            chip->irq_mask_ack(data);
        } else {
            chip->irq_mask(data);
            chip->irq_ack(data);
        }
    }

    noop(struct irq_data *data))
    {
    }

默认流处理程序的实现
~~~~~~~~~~~~~~~~~~~~

电平触发型IRQ流处理器
^^^^^^^^^^^^^^^^^^^^^

handle_level_irq为电平触发型的中断提供了一个通用实现。

实现的控制流如下（简化摘录）::

    desc->irq_data.chip->irq_mask_ack();
    handle_irq_event(desc->action);
    desc->irq_data.chip->irq_unmask();

默认的需回应IRQ流处理器
^^^^^^^^^^^^^^^^^^^^^^^

handle_fasteoi_irq为中断提供了一个通用的实现，它只需要在处理程序的末端有一个EOI。

实现的控制流如下（简化摘录）::

    handle_irq_event(desc->action);
    desc->irq_data.chip->irq_eoi();

默认的边沿触发型IRQ流处理器
^^^^^^^^^^^^^^^^^^^^^^^^^^^

handle_edge_irq为边沿触发型的中断提供了一个通用的实现。

实现的控制流如下（简化摘录）::

    if (desc->status & running) {
        desc->irq_data.chip->irq_mask_ack();
        desc->status |= pending | masked;
        return;
    }
    desc->irq_data.chip->irq_ack();
    desc->status |= running;
    do {
        if (desc->status & masked)
            desc->irq_data.chip->irq_unmask();
        desc->status &= ~pending;
        handle_irq_event(desc->action);
    } while (status & pending);
    desc->status &= ~running;

默认的简单型IRQ流处理器
^^^^^^^^^^^^^^^^^^^^^^^

handle_simple_irq提供了一个简单型中断的通用实现。

.. note::

   简单型的流处理程序不调用任何处理程序/芯片基元。

实现的控制流程如下（简化摘录）::

    handle_irq_event(desc->action);

默认的每CPU型流处理程序
^^^^^^^^^^^^^^^^^^^^^^^

handle_percpu_irq为每CPU型中断提供一个通用的实现。

每个CPU中断只在SMP上可用，该处理程序提供了一个没有锁的简化版本。

以下是控制流的实现（简化摘录）::

    if (desc->irq_data.chip->irq_ack)
        desc->irq_data.chip->irq_ack();
    handle_irq_event(desc->action);
    if (desc->irq_data.chip->irq_eoi)
        desc->irq_data.chip->irq_eoi();

EOI边沿型IRQ流处理器
^^^^^^^^^^^^^^^^^^^^

handle_edge_eoi_irq提供了一个异常的边沿触发型处理程序，它只用于拯救powerpc/cell
上的一个严重失控的irq控制器。

坏的IRQ流处理器
^^^^^^^^^^^^^^^

handle_bad_irq用于处理没有真正分配处理程序的假中断。

特殊性和优化
~~~~~~~~~~~~

通用函数是为“干净”的架构和芯片设计的，它们没有平台特定的IRQ处理特殊性。如果一
个架构需要在“流”的层面上实现特殊性，那么它可以通过覆盖高层的IRQ-流处理程序来实
现。

延迟中断禁用
~~~~~~~~~~~~

每个中断可选择的功能是由Russell King在ARM中断实现中引入的，当调用disable_irq()
时，不会在硬件层面上屏蔽中断。中断保持启用状态，而在中断事件发生时在流处理器中被
屏蔽。这可以防止在硬件上丢失边沿中断，因为硬件上不存储边沿中断事件，而中断在硬件
级被禁用。当一个中断在IRQ_DISABLED标志被设置时到达，那么该中断在硬件层面被屏蔽，
IRQ_PENDING位被设置。当中断被enable_irq()重新启用时，将检查挂起位，如果它被设置，
中断将通过硬件或软件重发机制重新发送。(当你想使用延迟中断禁用功能，而你的硬件又不
能重新触发中断时，有必要启用CONFIG_HARDIRQS_SW_RESEND。) 延迟中断禁止功能是不可
配置的。

芯片级硬件封装
--------------

芯片级硬件描述符结构体 :c:type:`irq_chip` 包含了所有与芯片直接相关的功能，这些功
能可以被irq流实现所利用。

-  ``irq_ack``

-  ``irq_mask_ack`` - 可选的，建议使用的性能

-  ``irq_mask``

-  ``irq_unmask``

-  ``irq_eoi`` - 可选的，EOI流处理程序需要

-  ``irq_retrigger`` - 可选的

-  ``irq_set_type`` - 可选的

-  ``irq_set_wake`` - 可选的

这些基元的意思是严格意义上的：ack是指ACK，masking是指对IRQ线的屏蔽，等等。这取决
于流处理器如何使用这些基本的低级功能单元。

__do_IRQ入口点
==============

最初的实现__do_IRQ()是所有类型中断的替代入口点。它已经不存在了。

这个处理程序被证明不适合所有的中断硬件，因此被重新实现了边沿/级别/简单/超高速中断
的拆分功能。这不仅是一个功能优化。它也缩短了中断的代码路径。

在SMP上的锁
===========

芯片寄存器的锁定是由定义芯片基元的架构决定的。每个寄存器的结构通过desc->lock，由
通用层保护。

通用中断芯片
============

为了避免复制相同的IRQ芯片实现，核心提供了一个可配置的通用中断芯片实现。开发者在自
己实现相同的功能之前，应该仔细检查通用芯片是否符合他们的需求，并以稍微不同的方式实
现相同的功能。

该API在以下内核代码中:

kernel/irq/generic-chip.c

结构体
======

本章包含自动生成的结构体文档，这些结构体在通用IRQ层中使用。

该API在以下内核代码中:

include/linux/irq.h

include/linux/interrupt.h

提供的通用函数
==============

这一章包含了自动生成的内核API函数的文档，这些函数被导出。

该API在以下内核代码中:

kernel/irq/manage.c

kernel/irq/chip.c

提供的内部函数
==============

本章包含自动生成的内部函数的文档。

该API在以下内核代码中:

kernel/irq/irqdesc.c

kernel/irq/handle.c

kernel/irq/chip.c

鸣谢
====

感谢以下人士对本文档作出的贡献：

1. Thomas Gleixner tglx@linutronix.de

2. Ingo Molnar mingo@elte.hu


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

:Original: Documentation/core-api/gfp_mask-from-fs-io.rst

:翻译:

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

:校译:

 时奎亮 <alexs@kernel.org>

.. _cn_core-api_gfp_mask-from-fs-io:

============================
从FS/IO上下文中使用的GFP掩码
============================

:日期: 2018年5月
:作者: Michal Hocko <mhocko@kernel.org>

简介
====

文件系统和IO栈中的代码路径在分配内存时必须小心，以防止因直接调用FS或IO路径的内
存回收和阻塞已经持有的资源（例如锁--最常见的是用于事务上下文的锁）而造成递归死
锁。

避免这种死锁问题的传统方法是在调用分配器时，在gfp掩码中清除__GFP_FS和__GFP_IO
（注意后者意味着也要清除第一个）。GFP_NOFS和GFP_NOIO可以作为快捷方式使用。但事
实证明，上述方法导致了滥用，当限制性的gfp掩码被用于“万一”时，没有更深入的考虑，
这导致了问题，因为过度使用GFP_NOFS/GFP_NOIO会导致内存过度回收或其他内存回收的问
题。

新API
=====

从4.12开始，我们为NOFS和NOIO上下文提供了一个通用的作用域API，分别是
``memalloc_nofs_save`` , ``memalloc_nofs_restore`` 和 ``memalloc_noio_save`` ,
``memalloc_noio_restore`` ，允许从文件系统或I/O的角度将一个作用域标记为一个
关键部分。从该作用域的任何分配都将从给定的掩码中删除__GFP_FS和__GFP_IO，所以
没有内存分配可以追溯到FS/IO中。

该API在以下内核代码中:

include/linux/sched/mm.h

然后，FS/IO代码在任何与回收有关的关键部分开始之前简单地调用适当的保存函数
——例如，与回收上下文共享的锁或当事务上下文嵌套可能通过回收进行时。恢复函数
应该在关键部分结束时被调用。所有这一切最好都伴随着解释什么是回收上下文，以
方便维护。

请注意，保存/恢复函数的正确配对允许嵌套，所以从现有的NOIO或NOFS范围分别调
用 ``memalloc_noio_save`` 或 ``memalloc_noio_restore`` 是安全的。

那么__vmalloc(GFP_NOFS)呢？
===========================

vmalloc不支持GFP_NOFS语义，因为在分配器的深处有硬编码的GFP_KERNEL分配，要修
复这些分配是相当不容易的。这意味着用GFP_NOFS/GFP_NOIO调用 ``vmalloc`` 几乎
总是一个错误。好消息是，NOFS/NOIO语义可以通过范围API实现。

在理想的世界中，上层应该已经标记了危险的上下文，因此不需要特别的照顾， ``vmalloc``
的调用应该没有任何问题。有时，如果上下文不是很清楚，或者有叠加的违规行为，那么
推荐的方法是用范围API包装vmalloc，并加上注释来解释问题。


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

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

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

:翻译:

 周彬彬 Binbin Zhou <zhoubinbin@loongson.cn>

:校译:

 司延腾 Yanteng Si <siyanteng@loongson.cn>
 吴想成 Wu Xiangcheng <bobwxc@email.cn>
 时奎亮 Alex Shi <alexs@kernel.org>

======
ID分配
======

:作者: Matthew Wilcox

概述
====

要解决的一个常见问题是分配标识符（IDs）；它通常是标识事物的数字。比如包括文件描述
符、进程ID、网络协议中的数据包标识符、SCSI标记和设备实例编号。IDR和IDA为这个问题
提供了一个合理的解决方案，以避免每个人都自创。IDR提供将ID映射到指针的能力，而IDA
仅提供ID分配，因此内存效率更高。

IDR接口已经被废弃，请使用 ``XArray`` 。

IDR的用法
=========

首先初始化一个IDR，对于静态分配的IDR使用DEFINE_IDR()，或者对于动态分配的IDR使用
idr_init()。

您可以调用idr_alloc()来分配一个未使用的ID。通过调用idr_find()查询与该ID相关的指针，
并通过调用idr_remove()释放该ID。

如果需要更改与一个ID相关联的指针，可以调用idr_replace()。这样做的一个常见原因是通
过将 ``NULL`` 指针传递给分配函数来保留ID；用保留的ID初始化对象，最后将初始化的对
象插入IDR。

一些用户需要分配大于 ``INT_MAX`` 的ID。到目前为止，所有这些用户都满足 ``UINT_MAX``
的限制，他们使用idr_alloc_u32()。如果您需要超出u32的ID，我们将与您合作以满足您的
需求。

如果需要按顺序分配ID，可以使用idr_alloc_cyclic()。处理较大数量的ID时，IDR的效率会
降低，所以使用这个函数会有一点代价。

要对IDR使用的所有指针进行操作，您可以使用基于回调的idr_for_each()或迭代器样式的
idr_for_each_entry()。您可能需要使用idr_for_each_entry_continue()来继续迭代。如果
迭代器不符合您的需求，您也可以使用idr_get_next()。

当使用完IDR后，您可以调用idr_destroy()来释放IDR占用的内存。这并不会释放IDR指向的
对象；如果您想这样做，请使用其中一个迭代器来执行此操作。

您可以使用idr_is_empty()来查看当前是否分配了任何ID。

如果在从IDR分配一个新ID时需要带锁，您可能需要传递一组限制性的GFP标志，但这可能导
致IDR无法分配内存。为了解决该问题，您可以在获取锁之前调用idr_preload()，然后在分
配之后调用idr_preload_end()。

IDR同步的相关内容请见include/linux/idr.h文件中的“DOC: idr sync”。

IDA的用法
=========

IDA的用法的相关内容请见lib/idr.c文件中的“DOC: IDA description”。

函数和数据结构
==============

该API在以下内核代码中:

include/linux/idr.h

lib/idr.c


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

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

:翻译:

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

.. _cn_core-api_index.rst:

===========
核心API文档
===========

这是核心内核API手册的首页。 非常感谢为本手册转换(和编写!)的文档!

核心实用程序
============

本节包含通用的和“核心中的核心”文档。 第一部分是 docbook 时期遗留下
来的大量 kerneldoc 信息；有朝一日，若有人有动力的话，应当把它们拆分
出来。

.. toctree::
   :maxdepth: 1

   kernel-api
   printk-basics
   printk-formats
   workqueue
   watch_queue
   symbol-namespaces

数据结构和低级实用程序
======================

在整个内核中使用的函数库。

.. toctree::
   :maxdepth: 1

   kobject
   kref
   assoc_array
   xarray
   rbtree
   idr
   circular-buffers
   generic-radix-tree
   packing
   this_cpu_ops

=======

Todolist:

   timekeeping
   errseq

并发原语
========

Linux如何让一切同时发生。 详情请参阅
:doc:`/locking/index`

.. toctree::
   :maxdepth: 1

   irq/index
   refcount-vs-atomic
   local_ops
   padata

Todolist:

   ../RCU/index

低级硬件管理
============

缓存管理，CPU热插拔管理等。

.. toctree::
   :maxdepth: 1

   cachetlb
   cpu_hotplug
   genericirq
   memory-hotplug
   protection-keys

Todolist:

   memory-hotplug
   cpu_hotplug
   genericirq

内存管理
========

如何在内核中分配和使用内存。请注意，在
:doc:`/mm/index` 中有更多的内存管理文档。

.. toctree::
   :maxdepth: 1

   memory-allocation
   unaligned-memory-access
   mm-api
   genalloc
   boot-time-mm
   gfp_mask-from-fs-io

Todolist:

   dma-api
   dma-api-howto
   dma-attributes
   dma-isa-lpc
   pin_user_pages

内核调试的接口
==============

Todolist:

   debug-objects
   tracepoint
   debugging-via-ohci1394

其它文档
========

不适合放在其它地方或尚未归类的文件；

Todolist:

   librs

.. only:: subproject and html

   Indices
   =======

   * :ref:`genindex`


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

:Original: Documentation/core-api/irq/concepts.rst

:翻译:

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

.. _cn_concepts.rst:

===========
什么是IRQ？
===========

IRQ (Interrupt ReQuest) 指来自设备的中断请求。
目前，它们可以通过一个引脚或通过一个数据包进入。
多个设备可以连接到同一个引脚，从而共享一个IRQ。

IRQ编号是用来描述硬件中断源的内核标识符。通常它是一个到全局irq_desc数组的索引，
但是除了在linux/interrupt.h中实现的之外，其它细节是体系结构特征相关的。

IRQ编号是对机器上可能的中断源的枚举。通常枚举的是系统中所有中断控制器的输入引脚
编号。在ISA（工业标准体系结构）的情况下所枚举的是两个i8259中断控制器的16个输入引脚。

体系结构可以给IRQ号赋予额外的含义，在涉及到硬件手动配置的情况下，我们鼓励这样做。
ISA IRQ是赋予这种额外含义的一个典型例子。


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

:Original: Documentation/core-api/irq/index.rst

:翻译:

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

.. _cn_irq_index.rst:

====
IRQs
====

.. toctree::
   :maxdepth: 1

   concepts
   irq-affinity
   irq-domain
   irqflags-tracing


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

:Original: Documentation/core-api/irq/irq-affinity.rst

:翻译:

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

.. _cn_irq-affinity.rst:

==============
SMP IRQ 亲和性
==============

变更记录:
	- 作者：最初由Ingo Molnar <mingo@redhat.com>开始撰写
	- 后期更新维护： Max Krasnyansky <maxk@qualcomm.com>

/proc/irq/IRQ#/smp_affinity和/proc/irq/IRQ#/smp_affinity_list指定了哪些CPU能
够关联到一个给定的IRQ源，这两个文件包含了这些指定cpu的cpu位掩码(smp_affinity)和cpu列
表(smp_affinity_list)。它不允许关闭所有CPU， 同时如果IRQ控制器不支持中断请求亲和
(IRQ affinity)，那么所有cpu的默认值将保持不变(即关联到所有CPU).

/proc/irq/default_smp_affinity指明了适用于所有非激活IRQ的默认亲和性掩码。一旦IRQ被
分配/激活，它的亲和位掩码将被设置为默认掩码。然后可以如上所述改变它。默认掩码是0xffffffff。

下面是一个先将IRQ44(eth1)限制在CPU0-3上，然后限制在CPU4-7上的例子(这是一个8CPU的SMP box)

::

	[root@moon 44]# cd /proc/irq/44
	[root@moon 44]# cat smp_affinity
	ffffffff

	[root@moon 44]# echo 0f > smp_affinity
	[root@moon 44]# cat smp_affinity
	0000000f
	[root@moon 44]# ping -f h
	PING hell (195.4.7.3): 56 data bytes
	...
	--- hell ping statistics ---
	6029 packets transmitted, 6027 packets received, 0% packet loss
	round-trip min/avg/max = 0.1/0.1/0.4 ms
	[root@moon 44]# cat /proc/interrupts | grep 'CPU\|44:'
		CPU0       CPU1       CPU2       CPU3      CPU4       CPU5        CPU6       CPU7
	44:       1068       1785       1785       1783         0          0           0         0    IO-APIC-level  eth1

从上面一行可以看出，IRQ44只传递给前四个处理器（0-3）。
现在让我们把这个IRQ限制在CPU(4-7)。

::

	[root@moon 44]# echo f0 > smp_affinity
	[root@moon 44]# cat smp_affinity
	000000f0
	[root@moon 44]# ping -f h
	PING hell (195.4.7.3): 56 data bytes
	..
	--- hell ping statistics ---
	2779 packets transmitted, 2777 packets received, 0% packet loss
	round-trip min/avg/max = 0.1/0.5/585.4 ms
	[root@moon 44]# cat /proc/interrupts |  'CPU\|44:'
		CPU0       CPU1       CPU2       CPU3      CPU4       CPU5        CPU6       CPU7
	44:       1068       1785       1785       1783      1784       1069        1070       1069   IO-APIC-level  eth1

这次IRQ44只传递给最后四个处理器。
即CPU0-3的计数器没有变化。

下面是一个将相同的irq(44)限制在cpus 1024到1031的例子

::

	[root@moon 44]# echo 1024-1031 > smp_affinity_list
	[root@moon 44]# cat smp_affinity_list
	1024-1031

需要注意的是，如果要用位掩码来做这件事，就需要32个为0的位掩码来追踪其相关的一个。


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

:Original: Documentation/core-api/irq/irq-domain.rst

:翻译:

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

.. _cn_irq-domain.rst:

=======================
irq_domain 中断号映射库
=======================

目前Linux内核的设计使用了一个巨大的数字空间，每个独立的IRQ源都被分配了一个不
同的数字。
当只有一个中断控制器时，这很简单，但在有多个中断控制器的系统中，内核必须确保每
个中断控制器都能得到非重复的Linux IRQ号（数字）分配。

注册为唯一的irqchips的中断控制器编号呈现出上升的趋势：例如GPIO控制器等不同
种类的子驱动程序通过将其中断处理程序建模为irqchips，即实际上是级联中断控制器，
避免了重新实现与IRQ核心系统相同的回调机制。

在这里，中断号与硬件中断号离散了所有种类的对应关系：而在过去，IRQ号可以选择，
使它们与硬件IRQ线进入根中断控制器（即实际向CPU发射中断线的组件）相匹配，现
在这个编号仅仅是一个数字。

出于这个原因，我们需要一种机制将控制器本地中断号（即硬件irq编号）与Linux IRQ
号分开。

irq_alloc_desc*() 和 irq_free_desc*() API 提供了对irq号的分配，但它们不
提供任何对控制器本地IRQ(hwirq)号到Linux IRQ号空间的反向映射的支持。

irq_domain 库在 irq_alloc_desc*() API 的基础上增加了 hwirq 和 IRQ 号码
之间的映射。 相比于中断控制器驱动开放编码自己的反向映射方案，我们更喜欢用
irq_domain来管理映射。

irq_domain还实现了从抽象的irq_fwspec结构体到hwirq号的转换（到目前为止是
Device Tree和ACPI GSI），并且可以很容易地扩展以支持其它IRQ拓扑数据源。

irq_domain的用法
================

中断控制器驱动程序通过以下方式创建并注册一个irq_domain。调用
irq_domain_add_*() 或 irq_domain_create_*()函数之一（每个映射方法都有不
同的分配器函数，后面会详细介绍）。 函数成功后会返回一个指向irq_domain的指针。
调用者必须向分配器函数提供一个irq_domain_ops结构体。

在大多数情况下，irq_domain在开始时是空的，没有任何hwirq和IRQ号之间的映射。
通过调用irq_create_mapping()将映射添加到irq_domain中，该函数接受
irq_domain和一个hwirq号作为参数。 如果hwirq的映射还不存在，那么它将分配
一个新的Linux irq_desc，将其与hwirq关联起来，并调用.map()回调，这样驱动
程序就可以执行任何必要的硬件设置。

一旦建立了映射，可以通过多种方法检索或使用它：

- irq_resolve_mapping()返回一个指向给定域和hwirq号的irq_desc结构指针，
  如果没有映射则返回NULL。

- irq_find_mapping()返回给定域和hwirq的Linux IRQ号，如果没有映射则返回0。

- irq_linear_revmap()现与irq_find_mapping()相同，已被废弃。

- generic_handle_domain_irq()处理一个由域和hwirq号描述的中断。

请注意，irq域的查找必须发生在与RCU读临界区兼容的上下文中。

在调用irq_find_mapping()之前，至少要调用一次irq_create_mapping()函数，
以免描述符不能被分配。

如果驱动程序有Linux的IRQ号或irq_data指针，并且需要知道相关的hwirq号（比
如在irq_chip回调中），那么可以直接从irq_data->hwirq中获得。

irq_domain映射的类型
====================

从hwirq到Linux irq的反向映射有几种机制，每种机制使用不同的分配函数。应该
使用哪种反向映射类型取决于用例。 下面介绍每一种反向映射类型：

线性映射
--------

::

	irq_domain_add_linear()
	irq_domain_create_linear()

线性反向映射维护了一个固定大小的表，该表以hwirq号为索引。 当一个hwirq被映射
时，会给hwirq分配一个irq_desc，并将irq号存储在表中。

当最大的hwirq号固定且数量相对较少时，线性图是一个很好的选择（~<256）。 这种
映射的优点是固定时间查找IRQ号，而且irq_descs只分配给在用的IRQ。 缺点是该表
必须尽可能大的hwirq号。

irq_domain_add_linear()和irq_domain_create_linear()在功能上是等价的，
除了第一个参数不同--前者接受一个Open Firmware特定的 'struct device_node' 而
后者接受一个更通用的抽象 'struct fwnode_handle' 。

大多数驱动应该使用线性映射

树状映射
--------

::

	irq_domain_add_tree()
	irq_domain_create_tree()

irq_domain维护着从hwirq号到Linux IRQ的radix的树状映射。 当一个hwirq被映射时，
一个irq_desc被分配，hwirq被用作radix树的查找键。

如果hwirq号可以非常大，树状映射是一个很好的选择，因为它不需要分配一个和最大hwirq
号一样大的表。 缺点是，hwirq到IRQ号的查找取决于表中有多少条目。

irq_domain_add_tree()和irq_domain_create_tree()在功能上是等价的，除了第一
个参数不同——前者接受一个Open Firmware特定的 'struct device_node' ，而后者接受
一个更通用的抽象 'struct fwnode_handle' 。

很少有驱动应该需要这个映射。

无映射
------

::

	irq_domain_add_nomap()

当硬件中的hwirq号是可编程的时候，就可以采用无映射类型。 在这种情况下，最好将
Linux IRQ号编入硬件本身，这样就不需要映射了。 调用irq_create_direct_mapping()
会分配一个Linux IRQ号，并调用.map()回调，这样驱动就可以将Linux IRQ号编入硬件中。

大多数驱动程序无法使用此映射，现在它由CONFIG_IRQ_DOMAIN_NOMAP选项控制。
请不要引入此API的新用户。

传统映射类型
------------

::

	irq_domain_add_simple()
	irq_domain_add_legacy()
	irq_domain_create_simple()
	irq_domain_create_legacy()

传统映射是已经为 hwirqs 分配了一系列 irq_descs 的驱动程序的特殊情况。 当驱动程
序不能立即转换为使用线性映射时，就会使用它。 例如，许多嵌入式系统板卡支持文件使用
一组用于IRQ号的定义（#define），这些定义被传递给struct设备注册。 在这种情况下，
不能动态分配Linux IRQ号，应该使用传统映射。

顾名思义，\*_legacy()系列函数已被废弃，只是为了方便对古老平台的支持而存在。
不应该增加新的用户。当\*_simple()系列函数的使用导致遗留行为时，他们也是如此。

传统映射假设已经为控制器分配了一个连续的IRQ号范围，并且可以通过向hwirq号添加一
个固定的偏移来计算IRQ号，反之亦然。 缺点是需要中断控制器管理IRQ分配，并且需要为每
个hwirq分配一个irq_desc，即使它没有被使用。

只有在必须支持固定的IRQ映射时，才应使用传统映射。 例如，ISA控制器将使用传统映射来
映射Linux IRQ 0-15，这样现有的ISA驱动程序就能得到正确的IRQ号。

大多数使用传统映射的用户应该使用irq_domain_add_simple()或
irq_domain_create_simple()，只有在系统提供IRQ范围时才会使用传统域，否则将使用
线性域映射。这个调用的语义是这样的：如果指定了一个IRQ范围，那么 描述符将被即时分配
给它，如果没有范围被分配，它将不会执行 irq_domain_add_linear() 或
irq_domain_create_linear()，这意味着 *no* irq 描述符将被分配。

一个简单域的典型用例是，irqchip供应商同时支持动态和静态IRQ分配。

为了避免最终出现使用线性域而没有描述符被分配的情况，确保使用简单域的驱动程序在任何
irq_find_mapping()之前调用irq_create_mapping()是非常重要的，因为后者实际上
将用于静态IRQ分配情况。

irq_domain_add_simple()和irq_domain_create_simple()以及
irq_domain_add_legacy()和irq_domain_create_legacy()在功能上是等价的，只
是第一个参数不同--前者接受Open Firmware特定的 'struct device_node' ，而后者
接受一个更通用的抽象 'struct fwnode_handle' 。

IRQ域层级结构
-------------

在某些架构上，可能有多个中断控制器参与将一个中断从设备传送到目标CPU。
让我们来看看x86平台上典型的中断传递路径吧
::

  Device --> IOAPIC -> Interrupt remapping Controller -> Local APIC -> CPU

涉及到的中断控制器有三个:

1) IOAPIC 控制器
2) 中断重映射控制器
3) Local APIC 控制器

为了支持这样的硬件拓扑结构，使软件架构与硬件架构相匹配，为每个中断控制器建立一
个irq_domain数据结构，并将这些irq_domain组织成层次结构。

在建立irq_domain层次结构时，靠近设备的irq_domain为子域，靠近CPU的
irq_domain为父域。所以在上面的例子中，将建立如下的层次结构。
::

	CPU Vector irq_domain (root irq_domain to manage CPU vectors)
		^
		|
	Interrupt Remapping irq_domain (manage irq_remapping entries)
		^
		|
	IOAPIC irq_domain (manage IOAPIC delivery entries/pins)

使用irq_domain层次结构的主要接口有四个:

1) irq_domain_alloc_irqs(): 分配IRQ描述符和与中断控制器相关的资源来传递这些中断。
2) irq_domain_free_irqs(): 释放IRQ描述符和与这些中断相关的中断控制器资源。
3) irq_domain_activate_irq(): 激活中断控制器硬件以传递中断。
4) irq_domain_deactivate_irq(): 停用中断控制器硬件，停止传递中断。

为了支持irq_domain层次结构，需要做如下修改:

1) 一个新的字段 'parent' 被添加到irq_domain结构中；它用于维护irq_domain的层次信息。
2) 一个新的字段 'parent_data' 被添加到irq_data结构中；它用于建立层次结构irq_data以
   匹配irq_domain层次结构。irq_data用于存储irq_domain指针和硬件irq号。
3) 新的回调被添加到irq_domain_ops结构中，以支持层次结构的irq_domain操作。

在支持分层irq_domain和分层irq_data准备就绪后，为每个中断控制器建立一个irq_domain结
构，并为每个与IRQ相关联的irq_domain分配一个irq_data结构。现在我们可以再进一步支持堆
栈式(层次结构)的irq_chip。也就是说，一个irq_chip与层次结构中的每个irq_data相关联。
一个子irq_chip可以自己或通过与它的父irq_chip合作来实现一个所需的操作。

通过堆栈式的irq_chip，中断控制器驱动只需要处理自己管理的硬件，在需要的时候可以向其父
irq_chip请求服务。所以我们可以实现更简洁的软件架构。

为了让中断控制器驱动程序支持irq_domain层次结构，它需要做到以下几点:

1) 实现 irq_domain_ops.alloc 和 irq_domain_ops.free
2) 可选择地实现 irq_domain_ops.activate 和 irq_domain_ops.deactivate.
3) 可选择地实现一个irq_chip来管理中断控制器硬件。
4) 不需要实现irq_domain_ops.map和irq_domain_ops.unmap，它们在层次结构
   irq_domain中是不用的。

irq_domain层次结构绝不是x86特有的，大量用于支持其他架构，如ARM、ARM64等。

调试功能
========

打开CONFIG_GENERIC_IRQ_DEBUGFS，可让IRQ子系统的大部分内部结构都在debugfs中暴露出来。


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

:Original: Documentation/core-api/irq/irqflags-tracing.rst

:翻译:

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

.. _cn_irqflags-tracing.rst:

=================
IRQ-flags状态追踪
=================

:Author: 最初由Ingo Molnar <mingo@redhat.com>开始撰写

“irq-flags tracing”（中断标志追踪）功能可以 “追踪” hardirq和softirq的状态，它让
感兴趣的子系统有机会了解到到内核中发生的每一个
hardirqs-off/hardirqs-on、softirqs-off/softirqs-on事件。

CONFIG_TRACE_IRQFLAGS_SUPPORT是通用锁调试代码提供的CONFIG_PROVE_SPIN_LOCKING
和CONFIG_PROVE_RW_LOCKING所需要的。否则将只有CONFIG_PROVE_MUTEX_LOCKING和
CONFIG_PROVE_RWSEM_LOCKING在一个架构上被提供--这些都是不在IRQ上下文中使用的
锁API。（rwsems的一个异常是可以解决的）

架构对这一点的支持当然不属于“微不足道”的范畴，因为很多低级的汇编代码都要处理irq-flags
的状态变化。但是一个架构可以以一种相当直接且无风险的方式启用irq-flags-tracing。

架构如果想支持这个，需要先做一些代码组织上的改变:

- 在他们的arch级Kconfig文件中添加并启用TRACE_IRQFLAGS_SUPPORT。

然后还需要做一些功能上的改变来实现对irq-flags-tracing的支持:

- 在低级入口代码中增加（构建条件）对trace_hardirqs_off()/trace_hardirqs_on()
  函数的调用。锁验证器会密切关注 “real”的irq-flags是否与 “virtual”的irq-flags
  状态相匹配，如果两者不匹配，则会发出警告（并关闭自己）。通常维护arch中
  irq-flags-track的大部分时间都是在这种状态下度过的：看看lockdep的警告，试着
  找出我们还没有搞定的汇编代码。修复并重复。一旦系统启动，并且在irq-flags跟踪功
  能中没有出现lockdep警告的情况下，arch支持就完成了。

- 如果该架构有不可屏蔽的中断，那么需要通过lockdep_off()/lockdep_on()将这些中
  断从irq跟踪[和锁验证]机制中排除。

 一般来说，在一个架构中，不完整的irq-flags-tracing实现是没有风险的：lockdep
 会检测到这一点，并将自己关闭。即锁验证器仍然可靠。应该不会因为irq-tracing的错
 误而崩溃。（除非通过修改不该修改的条件来更改汇编或寄存器而破坏其他代码）


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

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

:翻译:

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

.. _cn_kernel-api.rst:

============
Linux内核API
============

列表管理函数
============

该API在以下内核代码中:

include/linux/list.h

基本的C库函数
=============

在编写驱动程序时，一般不能使用C库中的例程。部分函数通常很有用，它们在
下面被列出。这些函数的行为可能会与ANSI定义的略有不同，这些偏差会在文中
注明。

字符串转换
----------

该API在以下内核代码中:

lib/vsprintf.c

include/linux/kernel.h

include/linux/kernel.h

lib/kstrtox.c

lib/string_helpers.c

字符串处理
----------

该API在以下内核代码中:

lib/string.c

include/linux/string.h

mm/util.c

基本的内核库函数
================

Linux内核提供了很多实用的基本函数。

位运算
------

该API在以下内核代码中:

include/asm-generic/bitops/instrumented-atomic.h

include/asm-generic/bitops/instrumented-non-atomic.h

include/asm-generic/bitops/instrumented-lock.h

位图运算
--------

该API在以下内核代码中:

lib/bitmap.c

include/linux/bitmap.h

include/linux/bitmap.h

include/linux/bitmap.h

lib/bitmap.c

lib/bitmap.c

include/linux/bitmap.h

命令行解析
----------

该API在以下内核代码中:

lib/cmdline.c

排序
----

该API在以下内核代码中:

lib/sort.c

lib/list_sort.c

文本检索
--------

该API在以下内核代码中:

lib/textsearch.c

lib/textsearch.c

include/linux/textsearch.h

Linux中的CRC和数学函数
======================

CRC函数
-------

*译注：CRC，Cyclic Redundancy Check，循环冗余校验*

该API在以下内核代码中:

lib/crc4.c

lib/crc7.c

lib/crc8.c

lib/crc16.c

lib/crc32.c

lib/crc-ccitt.c

lib/crc-itu-t.c

基数为2的对数和幂函数
---------------------

该API在以下内核代码中:

include/linux/log2.h

整数幂函数
----------

该API在以下内核代码中:

lib/math/int_pow.c

lib/math/int_sqrt.c

除法函数
--------

该API在以下内核代码中:

include/asm-generic/div64.h

include/linux/math64.h

lib/math/div64.c

lib/math/gcd.c

UUID/GUID
---------

该API在以下内核代码中:

lib/uuid.c

内核IPC设备
===========

IPC实用程序
-----------

该API在以下内核代码中:

ipc/util.c

FIFO 缓冲区
===========

kfifo接口
---------

该API在以下内核代码中:

include/linux/kfifo.h

转发接口支持
============

转发接口支持旨在为工具和设备提供一种有效的机制，将大量数据从内核空间
转发到用户空间。

转发接口
--------

该API在以下内核代码中:

kernel/relay.c

kernel/relay.c

模块支持
========

模块加载
--------

该API在以下内核代码中:

kernel/kmod.c

模块接口支持
------------

更多信息请参阅kernel/module/目录下的文件。

硬件接口
========

该API在以下内核代码中:

kernel/dma.c

资源管理
--------

该API在以下内核代码中:

kernel/resource.c

kernel/resource.c

MTRR处理
--------

该API在以下内核代码中:

arch/x86/kernel/cpu/mtrr/mtrr.c

安全框架
========

该API在以下内核代码中:

security/security.c

security/inode.c

审计接口
========

该API在以下内核代码中:

kernel/audit.c

kernel/auditsc.c

kernel/auditfilter.c

核算框架
========

该API在以下内核代码中:

kernel/acct.c

块设备
======

该API在以下内核代码中:

include/linux/bio.h

block/blk-core.c

block/blk-core.c

block/blk-map.c

block/blk-sysfs.c

block/blk-settings.c

block/blk-flush.c

block/blk-lib.c

block/blk-integrity.c

kernel/trace/blktrace.c

block/genhd.c

block/genhd.c

字符设备
========

该API在以下内核代码中:

fs/char_dev.c

时钟框架
========

时钟框架定义了编程接口，以支持系统时钟树的软件管理。该框架广泛用于系统级芯片（SOC）平
台，以支持电源管理和各种可能需要自定义时钟速率的设备。请注意，这些 “时钟”与计时或实
时时钟(RTC)无关，它们都有单独的框架。这些:c:type: `struct clk <clk>` 实例可用于管理
各种时钟信号，例如一个96理例如96MHz的时钟信号，该信号可被用于总线或外设的数据交换，或以
其他方式触发系统硬件中的同步状态机转换。

通过明确的软件时钟门控来支持电源管理：未使用的时钟被禁用，因此系统不会因为改变不在使用
中的晶体管的状态而浪费电源。在某些系统中，这可能是由硬件时钟门控支持的，其中时钟被门控
而不在软件中被禁用。芯片的部分，在供电但没有时钟的情况下，可能会保留其最后的状态。这种
低功耗状态通常被称为*保留模式*。这种模式仍然会产生漏电流，特别是在电路几何结构较细的情
况下，但对于CMOS电路来说，电能主要是随着时钟翻转而被消耗的。

电源感知驱动程序只有在其管理的设备处于活动使用状态时才会启用时钟。此外，系统睡眠状态通
常根据哪些时钟域处于活动状态而有所不同：“待机”状态可能允许从多个活动域中唤醒，而
"mem"（暂停到RAM）状态可能需要更全面地关闭来自高速PLL和振荡器的时钟，从而限制了可能
的唤醒事件源的数量。驱动器的暂停方法可能需要注意目标睡眠状态的系统特定时钟约束。

一些平台支持可编程时钟发生器。这些可以被各种外部芯片使用，如其他CPU、多媒体编解码器以
及对接口时钟有严格要求的设备。

该API在以下内核代码中:

include/linux/clk.h

同步原语
========

读-复制-更新（RCU）
-------------------

该API在以下内核代码中:

include/linux/rcupdate.h

kernel/rcu/tree.c

kernel/rcu/tree_exp.h

kernel/rcu/update.c

include/linux/srcu.h

kernel/rcu/srcutree.c

include/linux/rculist_bl.h

include/linux/rculist.h

include/linux/rculist_nulls.h

include/linux/rcu_sync.h

kernel/rcu/sync.c