diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-07 18:49:45 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-07 18:49:45 +0000 |
commit | 2c3c1048746a4622d8c89a29670120dc8fab93c4 (patch) | |
tree | 848558de17fb3008cdf4d861b01ac7781903ce39 /Documentation/translations/zh_CN | |
parent | Initial commit. (diff) | |
download | linux-2c3c1048746a4622d8c89a29670120dc8fab93c4.tar.xz linux-2c3c1048746a4622d8c89a29670120dc8fab93c4.zip |
Adding upstream version 6.1.76.upstream/6.1.76
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'Documentation/translations/zh_CN')
240 files changed, 39913 insertions, 0 deletions
diff --git a/Documentation/translations/zh_CN/PCI/acpi-info.rst b/Documentation/translations/zh_CN/PCI/acpi-info.rst new file mode 100644 index 000000000..a35f39dcd --- /dev/null +++ b/Documentation/translations/zh_CN/PCI/acpi-info.rst @@ -0,0 +1,139 @@ +.. SPDX-License-Identifier: GPL-2.0 +.. include:: ../disclaimer-zh_CN.rst + +:Original: Documentation/PCI/acpi-info.rst + +:翻译: + + 司延腾 Yanteng Si <siyanteng@loongson.cn> + +:校译: + + +===================== +PCI主桥的ACPI注意事项 +===================== + +一般的规则是,ACPI命名空间应该描述操作系统可能使用的所有东西,除非有其他方法让操作系 +统找到它[1, 2]。 + +例如,没有标准的硬件机制来枚举PCI主桥,所以ACPI命名空间必须描述每个主桥、访问它 +下面的PCI配置空间的方法、主桥转发到PCI的地址空间窗口(使用_CRS)以及传统的INTx +中断的路由(使用_PRT)。 + +在主桥下面的PCI设备,通常不需要通过ACPI描述。操作系统可以通过标准的PCI枚举机制来 +发现它们,使用配置访问来发现和识别设备,并读取和测量它们的BAR。然而,如果ACPI为它们 +提供电源管理或热插拔功能,或者如果设备有由平台中断控制器连接的INTx中断,需要一个_PRT +来描述这些连接,这种情况下ACPI可以描述PCI设备。 + +ACPI资源描述是通过ACPI命名空间中设备的_CRS对象完成的[2]。_CRS就像一个通用的PCI BAR: +操作系统可以读取_CRS并找出正在消耗的资源,即使它没有该设备的驱动程序[3]。这一点很重要, +因为它意味着一个旧的操作系统可以正确地工作,即使是在操作系统不知道的新设备的系统上。新设 +备可能什么都不做,但操作系统至少可以确保没有资源与它们冲突。 + +像MCFG、HPET、ECDT等静态表,不是保留地址空间的机制。静态表是在操作系统在启动初期且在它 +能够解析ACPI命名空间之前需要知道的东西。如果定义了一个新的表,即使旧的操作系统忽略了这 +个表,它也需要正常运行。_CRS允许这样做,因为它是通用的,可以被旧的操作系统解析;而静态表 +则不允许。 + +如果操作系统要管理一个通过ACPI描述的不可发现的设备,该设备将有一个特定的_HID/_CID,以 +告诉操作系统与之绑定的驱动程序,并且_CRS告诉操作系统和驱动程序该设备的寄存器在哪里。 + +PCI主桥是PNP0A03或PNP0A08设备。它们的_CRS应该描述它们所消耗的所有地址空间。这包括它 +们转发到PCI总线上的所有窗口,以及不转发到PCI的主桥本身的寄存器。主桥的寄存器包括次要/下 +级总线寄存器,决定了桥下面的总线范围,窗口寄存器描述了桥洞,等等。这些都是设备相关的,非 +架构相关的东西,所以PNP0A03/PNP0A08驱动可以管理它们的唯一方法是通过_PRS/_CRS/_SRS, +它包含了特定于设备的细节。主桥寄存器也包括ECAM空间,因为它是由主桥消耗的。 + +ACPI定义了一个Consumer/Producer位来区分桥寄存器(“Consumer”下文译作消费者)和 +桥洞(“Producer”下文译作生产者)[4, 5],但是早期的BIOS没有正确使用这个位。其结果 +是,目前的ACPI规范只为扩展地址空间描述符定义了消费者/生产者;在旧的QWord/Word/Word地 +址空间描述符中,该位应该被忽略。因此,操作系统必须假定所有的QWord/Word/Word描述符都是 +窗口。 + +在增加扩展地址空间描述符之前,消费者/生产者的失败意味着没有办法描述PNP0A03/PNP0A08设 +备本身的桥寄存器。解决办法是在PNP0C02捕捉器中描述桥寄存器(包括ECAM空间)[6]。 +除了ECAM之外,桥寄存器空间反正是特定于设备的,所以通用的PNP0A03/PNP0A08驱动程 +序(pci_root.c)没有必要了解它。 + +新的架构应该能够在PNP0A03设备中使用“消费者”扩展地址空间描述符,用于桥寄存器,包括 +ECAM,尽管对[6]的严格解释可能禁止这样做。旧的x86和ia64内核假定所有的地址空间描述 +符,包括“消费者”扩展地址空间的描述符,都是窗口,所以在这些架构上以这种方式描述桥寄 +存器是不安全的。 + +PNP0C02“主板”设备基本上是万能的。除了“不要将这些资源用于其他用途”之外,没有其他的编 +程模型。因此,PNP0C02 _CRS应该声明ACPI命名空间中(1)没有被_CRS声明的任何其他设备对 +象的地址空间,(2)不应该被OS分配给其他东西。 + +除非有一个标准的固件接口用于配置访问,例如ia64 SAL接口[7],否则PCIe规范要求使用增强 +型配置访问方法(ECAM)。主桥消耗ECAM内存地址空间并将内存访问转换为PCI配置访问。该规范 +定义了ECAM地址空间的布局和功能;只有地址空间的基础是特定于设备的。ACPI操作系统从静态 +MCFG表或PNP0A03设备中的_CBA方法中了解基础地址。 + +MCFG表必须描述非热插拔主桥的ECAM空间[8]。由于MCFG是一个静态表,不能通过热插拔更新, +PNP0A03设备中的_CBA方法描述了可热插拔主桥的ECAM空间[9]。请注意,对于MCFG和_CBA, +基址总是对应于总线0,即使桥器下面的总线范围(通过_CRS报告)不从0开始。 + + +[1] ACPI 6.2, sec 6.1: + 对于任何在非枚举类型的总线上的设备(例如,ISA总线),OSPM会枚举设备的标识符,ACPI + 系统固件必须为每个设备提供一个_HID对象...以使OSPM能够做到这一点。 + +[2] ACPI 6.2, sec 3.7: + 操作系统枚举主板设备时,只需通过读取ACPI命名空间来寻找具有硬件ID的设备。 + + ACPI枚举的每个设备都包括ACPI命名空间中ACPI定义的对象,该对象报告设备可能占用的硬 + 件资源[_PRS],报告设备当前使用的资源[_CRS]的对象,以及配置这些资源的对象[_SRS]。 + 这些信息被即插即用操作系统(OSPM)用来配置设备。 + +[3] ACPI 6.2, sec 6.2: + OSPM使用设备配置对象来配置通过ACPI列举的设备的硬件资源。设备配置对象提供了关于当前 + 和可能的资源需求的信息,共享资源之间的关系,以及配置硬件资源的方法。 + + 当OSPM枚举一个设备时,它调用_PRS来确定该设备的资源需求。它也可以调用_CRS来找到该设 + 备的当前资源设置。利用这些信息,即插即用系统决定设备应该消耗什么资源,并通过调用设备 + 的_SRS控制方法来设置这些资源。 + + 在ACPI中,设备可以消耗资源(例如,传统的键盘),提供资源(例如,一个专有的PCI桥), + 或者两者都做。除非另有规定,设备的资源被假定为来自设备层次结构中设备上方最近的匹配资 + 源。 + +[4] ACPI 6.2, sec 6.4.3.5.1, 2, 3, 4: + QWord/DWord/Word 地址空间描述符 (.1, .2, .3) + 常规标志: Bit [0] 被忽略。 + + 扩展地址空间描述符 (.4) + 常规标志: Bit [0] 消费者/生产者: + + * 1 – 这个设备消费这个资源 + * 0 – 该设备生产和消费该资源 + +[5] ACPI 6.2, sec 19.6.43: + ResourceUsage指定内存范围是由这个设备(ResourceConsumer)消费还是传递给子设备 + (ResourceProducer)。如果没有指定,那么就假定是ResourceConsumer。 + +[6] PCI Firmware 3.2, sec 4.1.2: + 如果操作系统不能原生的懂得保留MMCFG区域,MMCFG区域必须由固件保留。在MCFG表中或通 + 过_CBA方法(见第4.1.3节)报告的地址范围必须通过声明主板资源来保留。对于大多数系统, + 主板资源将出现在ACPI命名空间的根部(在_SB下),在一个节点的_HID为EISAID(PNP0C0 + 2),在这种情况下的资源不应该要求在根PCI总线的_CRS。这些资源可以选择在Int15 E820 + 或EFIGetMemoryMap中作为保留内存返回,但必须始终通过ACPI作为主板资源报告。 + +[7] PCI Express 4.0, sec 7.2.2: + 对于PC兼容的系统,或者没有实现允许访问配置空间的处理器架构特定固件接口标准的系统,需 + 要使用本节中定义的ECAM。 + +[8] PCI Firmware 3.2, sec 4.1.2: + MCFG表是一个ACPI表,用于沟通的基础地址对应的非热的可移动的PCI段组范围内的PCI段组在 + 启动时提供给操作系统。这对PC兼容系统来说是必需的。 + + MCFG表仅用于沟通在启动时系统可用的PCI段组对应的基址。 + +[9] PCI Firmware 3.2, sec 4.1.3: + _CBA (Memory mapped Configuration Base Address) 控制方法是一个可选的ACPI对 + 象,用于返回热插拔主桥的64位内存映射的配置基址。_CBA 返回的基址是与处理器相关的地址。 + _CBA 控制方法被评估为一个整数。 + + 这个控制方法出现在主桥对象下。当_CBA方法出现在一个活动的主桥对象下时,操作系统会评 + 估这个结构,以确定内存映射的配置基址,对应于_CRS方法中指定的总线编号范围的PCI段组。 + 一个包含_CBA方法的ACPI命名空间对象也必须包含一个相应的_SEG方法。 diff --git a/Documentation/translations/zh_CN/PCI/index.rst b/Documentation/translations/zh_CN/PCI/index.rst new file mode 100644 index 000000000..cbeb33c34 --- /dev/null +++ b/Documentation/translations/zh_CN/PCI/index.rst @@ -0,0 +1,34 @@ +.. SPDX-License-Identifier: GPL-2.0 +.. include:: ../disclaimer-zh_CN.rst + +:Original: Documentation/PCI/index.rst + +:翻译: + + 司延腾 Yanteng Si <siyanteng@loongson.cn> + +:校译: + + +=================== +Linux PCI总线子系统 +=================== + +.. toctree:: + :maxdepth: 2 + :numbered: + + pci + pciebus-howto + pci-iov-howto + msi-howto + sysfs-pci + acpi-info + + +Todolist: + +* pci-error-recovery +* pcieaer-howto +* endpoint/index +* boot-interrupts diff --git a/Documentation/translations/zh_CN/PCI/msi-howto.rst b/Documentation/translations/zh_CN/PCI/msi-howto.rst new file mode 100644 index 000000000..7ea4d50cd --- /dev/null +++ b/Documentation/translations/zh_CN/PCI/msi-howto.rst @@ -0,0 +1,233 @@ +.. SPDX-License-Identifier: GPL-2.0 +.. include:: <isonum.txt> +.. include:: ../disclaimer-zh_CN.rst + +:Original: Documentation/PCI/msi-howto.rst + +:翻译: + + 司延腾 Yanteng Si <siyanteng@loongson.cn> + +:校译: + + + +=========== +MSI驱动指南 +=========== + +:作者: Tom L Nguyen; Martine Silbermann; Matthew Wilcox + +:版权: 2003, 2008 Intel Corporation + +关于本指南 +========== + +本指南介绍了消息标记中断(MSI)的基本知识,使用MSI相对于传统中断机制的优势,如何 +改变你的驱动程序以使用MSI或MSI-X,以及在设备不支持MSI时可以尝试的一些基本诊断方法。 + + +什么是MSI? +========== + +信息信号中断是指从设备写到一个特殊的地址,导致CPU接收到一个中断。 + +MSI能力首次在PCI 2.2中规定,后来在PCI 3.0中得到增强,允许对每个中断进行单独屏蔽。 +MSI-X功能也随着PCI 3.0被引入。它比MSI支持每个设备更多的中断,并允许独立配置中断。 + +设备可以同时支持MSI和MSI-X,但一次只能启用一个。 + + +为什么用MSI? +============ + +有三个原因可以说明为什么使用MSI比传统的基于针脚的中断有优势。 + +基于针脚的PCI中断通常在几个设备之间共享。为了支持这一点,内核必须调用每个与中断相 +关的中断处理程序,这导致了整个系统性能的降低。MSI从不共享,所以这个问题不会出现。 + +当一个设备将数据写入内存,然后引发一个基于引脚的中断时,有可能在所有的数据到达内存 +之前,中断就已经到达了(这在PCI-PCI桥后面的设备中变得更有可能)。为了确保所有的数 +据已经到达内存中,中断处理程序必须在引发中断的设备上读取一个寄存器。PCI事务排序规 +则要求所有的数据在返回寄存器的值之前到达内存。使用MSI可以避免这个问题,因为中断产 +生的写入不能通过数据写入,所以当中断发生时,驱动程序知道所有的数据已经到达内存中。 + +PCI设备每个功能只能支持一个基于引脚的中断。通常情况下,驱动程序必须查询设备以找出 +发生了什么事件,这就减慢了对常见情况的中断处理。有了MSI,设备可以支持更多的中断, +允许每个中断被专门用于不同的目的。一种可能的设计是给不经常发生的情况(如错误)提供 +自己的中断,这使得驱动程序可以更有效地处理正常的中断处理路径。其他可能的设计包括给 +网卡的每个数据包队列或存储控制器的每个端口提供一个中断。 + + +如何使用MSI +=========== + +PCI设备被初始化为使用基于引脚的中断。设备驱动程序必须将设备设置为使用MSI或MSI-X。 +并非所有的机器都能正确地支持MSI,对于这些机器,下面描述的API将简单地失败,设备将 +继续使用基于引脚的中断。 + +加入内核对MSI的支持 +------------------- + +为了支持MSI或MSI-X,内核在构建时必须启用CONFIG_PCI_MSI选项。这个选项只在某些架 +构上可用,而且它可能取决于其他一些选项的设置。例如,在x86上,你必须同时启用X86_UP_APIC +或SMP,才能看到CONFIG_PCI_MSI选项。 + +使用MSI +------- + +大部分沉重的工作是在PCI层为驱动程序完成的。驱动程序只需要请求PCI层为这个设备设置 +MSI功能。 + +要自动使用MSI或MSI-X中断向量,请使用以下函数:: + + int pci_alloc_irq_vectors(struct pci_dev *dev, unsigned int min_vecs, + unsigned int max_vecs, unsigned int flags); + +它为一个PCI设备分配最多至max_vecs的中断向量。它返回分配的向量数量或一个负的错误。 +如果设备对最小数量的向量有要求,驱动程序可以传递一个min_vecs参数,设置为这个限制, +如果PCI核不能满足最小数量的向量,将返回-ENOSPC。 + +flags参数用来指定设备和驱动程序可以使用哪种类型的中断(PCI_IRQ_LEGACY, PCI_IRQ_MSI, +PCI_IRQ_MSIX)。一个方便的短语(PCI_IRQ_ALL_TYPES)也可以用来要求任何可能的中断类型。 +如果PCI_IRQ_AFFINITY标志被设置,pci_alloc_irq_vectors()将把中断分散到可用的CPU上。 + +要获得传递给require_irq()和free_irq()的Linux IRQ号码和向量,请使用以下函数:: + + int pci_irq_vector(struct pci_dev *dev, unsigned int nr); + +在删除设备之前,应使用以下功能释放任何已分配的资源:: + + void pci_free_irq_vectors(struct pci_dev *dev); + +如果一个设备同时支持MSI-X和MSI功能,这个API将优先使用MSI-X,而不是MSI。MSI-X支 +持1到2048之间的任何数量的中断。相比之下,MSI被限制为最多32个中断(而且必须是2的幂)。 +此外,MSI中断向量必须连续分配,所以系统可能无法为MSI分配像MSI-X那样多的向量。在一 +些平台上,MSI中断必须全部针对同一组CPU,而MSI-X中断可以全部针对不同的CPU。 + +如果一个设备既不支持MSI-X,也不支持MSI,它就会退回到一个传统的IRQ向量。 + +MSI或MSI-X中断的典型用法是分配尽可能多的向量,可能达到设备支持的极限。如果nvec大于 +设备支持的数量,它将自动被限制在支持的限度内,所以没有必要事先查询支持的向量的数量。:: + + nvec = pci_alloc_irq_vectors(pdev, 1, nvec, PCI_IRQ_ALL_TYPES) + if (nvec < 0) + goto out_err; + +如果一个驱动程序不能或不愿意处理可变数量的MSI中断,它可以要求一个特定数量的中断,将该 +数量作为“min_vecs“和“max_vecs“参数传递给pci_alloc_irq_vectors()函数。:: + + ret = pci_alloc_irq_vectors(pdev, nvec, nvec, PCI_IRQ_ALL_TYPES); + if (ret < 0) + goto out_err; + +上述请求类型的最臭名昭著的例子是为一个设备启用单一的MSI模式。它可以通过传递两个1作为 +'min_vecs'和'max_vecs'来实现:: + + ret = pci_alloc_irq_vectors(pdev, 1, 1, PCI_IRQ_ALL_TYPES); + if (ret < 0) + goto out_err; + +一些设备可能不支持使用传统的线路中断,在这种情况下,驱动程序可以指定只接受MSI或MSI-X。:: + + nvec = pci_alloc_irq_vectors(pdev, 1, nvec, PCI_IRQ_MSI | PCI_IRQ_MSIX); + if (nvec < 0) + goto out_err; + +传统API +----------- + +以下用于启用和禁用MSI或MSI-X中断的旧API不应该在新代码中使用:: + + pci_enable_msi() /* deprecated */ + pci_disable_msi() /* deprecated */ + pci_enable_msix_range() /* deprecated */ + pci_enable_msix_exact() /* deprecated */ + pci_disable_msix() /* deprecated */ + +此外,还有一些API来提供支持的MSI或MSI-X向量的数量:pci_msi_vec_count()和 +pci_msix_vec_count()。一般来说,应该避免使用这些方法,而是让pci_alloc_irq_vectors() +来限制向量的数量。如果你对向量的数量有合法的特殊用例,我们可能要重新审视这个决定, +并增加一个pci_nr_irq_vectors()助手,透明地处理MSI和MSI-X。 + +使用MSI时需要考虑的因素 +----------------------- + +自旋锁 +~~~~~~ + +大多数设备驱动程序都有一个每的自旋锁,在中断处理程序中被占用。对于基于引脚的中断 +或单一的MSI,没有必要禁用中断(Linux保证同一中断不会被重新输入)。如果一个设备 +使用多个中断,驱动程序必须在锁被持有的时候禁用中断。如果设备发出一个不同的中断, +驱动程序将死锁,试图递归地获取自旋锁。这种死锁可以通过使用spin_lock_irqsave() +或spin_lock_irq()来避免,它们可以禁用本地中断并获取锁(见《不可靠的锁定指南》)。 + +如何判断一个设备上是否启用了MSI/MSI-X +------------------------------------- + +使用“lspci -v“(以root身份)可能会显示一些具有“MSI“、“Message Signalled Interrupts“ +或“MSI-X“功能的设备。这些功能中的每一个都有一个“启用“标志,后面是“+“(启用) +或“-“(禁用)。 + + +MSI特性 +======= + +众所周知,一些PCI芯片组或设备不支持MSI。PCI协议栈提供了三种禁用MSI的方法: + +1. 全局的 +2. 在一个特定的桥后面的所有设备上 +3. 在单一设备上 + +全局禁用MSI +----------- + +一些主控芯片组根本无法正确支持MSI。如果我们幸运的话,制造商知道这一点,并在 +ACPI FADT表中指明了它。在这种情况下,Linux会自动禁用MSI。有些板卡在表中没 +有包括这一信息,因此我们必须自己检测它们。完整的列表可以在drivers/pci/quirks.c +中的quirk_disable_all_msi()函数附近找到。 + +如果你有一块有MSI问题的板子,你可以在内核命令行中传递pci=nomsi来禁用所有设 +备上的MSI。你最好把问题报告给linux-pci@vger.kernel.org,包括完整的 +“lspci -v“,这样我们就可以把这些怪癖添加到内核中。 + +禁用桥下的MSI +------------- + +一些PCI桥接器不能在总线之间正确地路由MSI。在这种情况下,必须在桥接器后面的所 +有设备上禁用MSI。 + +一些桥接器允许你通过改变PCI配置空间的一些位来启用MSI(特别是Hypertransport +芯片组,如nVidia nForce和Serverworks HT2000)。与主机芯片组一样,Linux大 +多知道它们,如果可以的话,会自动启用MSI。如果你有一个Linux不知道的网桥,你可以 +用你知道的任何方法在配置空间中启用MSI,然后通过以下方式在该网桥上启用MSI:: + + echo 1 > /sys/bus/pci/devices/$bridge/msi_bus + +其中$bridge是你所启用的桥的PCI地址(例如0000:00:0e.0)。 + +要禁用MSI,请回显0而不是1。改变这个值应该谨慎进行,因为它可能会破坏这个桥下面所 +有设备的中断处理。 + +同样,请通知 linux-pci@vger.kernel.org 任何需要特殊处理的桥。 + +在单一设备上关闭MSIs +-------------------- + +众所周知,有些设备的MSI实现是有问题的。通常情况下,这是在单个设备驱动程序中处理的, +但偶尔也有必要用一个古怪的方法来处理。一些驱动程序有一个选项可以禁用MSI的使用。虽然 +这对驱动程序的作者来说是一个方便的变通办法,但这不是一个好的做法,不应该被模仿。 + +寻找设备上MSI被禁用的原因 +------------------------- + +从以上三个部分,你可以看到有许多原因导致MSI没有在某个设备上被启用。你的第一步应该是 +仔细检查你的dmesg以确定你的机器是否启用了MSI。你还应该检查你的.config以确定你已经 +启用了CONFIG_PCI_MSI。 + +然后,“lspci -t“给出一个设备上面的网列表。读取 ``/sys/bus/pci/devices/*/msi_bus`` +将告诉你MSI是否被启用(1)或禁用(0)。如果在任何属于PCI根和设备之间的桥的msi_bus +文件中发现0,说明MSI被禁用。 + +也需要检查设备驱动程序,看它是否支持MSI。例如,它可能包含对带有PCI_IRQ_MSI或 +PCI_IRQ_MSIX标志的pci_alloc_irq_vectors()的调用。 diff --git a/Documentation/translations/zh_CN/PCI/pci-iov-howto.rst b/Documentation/translations/zh_CN/PCI/pci-iov-howto.rst new file mode 100644 index 000000000..fb023ea13 --- /dev/null +++ b/Documentation/translations/zh_CN/PCI/pci-iov-howto.rst @@ -0,0 +1,169 @@ +.. SPDX-License-Identifier: GPL-2.0 +.. include:: <isonum.txt> +.. include:: ../disclaimer-zh_CN.rst + +:Original: Documentation/PCI/pci-iov-howto.rst + +:翻译: + + 司延腾 Yanteng Si <siyanteng@loongson.cn> + +:校译: + + + +.. _cn_pci-iov-howto: + +========================== +PCI Express I/O 虚拟化指南 +========================== + +:版权: |copy| 2009 Intel Corporation +:作者: - Yu Zhao <yu.zhao@intel.com> + - Donald Dutile <ddutile@redhat.com> + +概述 +==== + +什么是SR-IOV +------------ + +单根I/O虚拟化(SR-IOV)是一种PCI Express扩展功能,它使一个物理设备显示为多个 +虚拟设备。物理设备被称为物理功能(PF),而虚拟设备被称为虚拟功能(VF)。VF的分 +配可以由PF通过封装在该功能中的寄存器动态控制。默认情况下,该功能未被启用,PF表 +现为传统的PCIe设备。一旦开启,每个VF的PCI配置空间都可以通过自己的总线、设备和 +功能编号(路由ID)来访问。而且每个VF也有PCI内存空间,用于映射其寄存器集。VF设 +备驱动程序对寄存器集进行操作,这样它就可以发挥功能,并作为一个真正的现有PCI设备 +出现。 + +使用指南 +======== + +我怎样才能启用SR-IOV功能 +------------------------ + +有多种方法可用于SR-IOV的启用。在第一种方法中,设备驱动(PF驱动)将通过SR-IOV +核心提供的API控制功能的启用和禁用。如果硬件具有SR-IOV能力,加载其PF驱动器将启 +用它和与PF相关的所有VF。一些PF驱动需要设置一个模块参数,以确定要启用的VF的数量。 +在第二种方法中,对sysfs文件sriov_numvfs的写入将启用和禁用与PCIe PF相关的VF。 +这种方法实现了每个PF的VF启用/禁用值,而第一种方法则适用于同一设备的所有PF。此外, +PCI SRIOV核心支持确保启用/禁用操作是有效的,以减少同一检查在多个驱动程序中的重 +复,例如,如果启用VF,检查numvfs == 0,确保numvfs <= totalvfs。 +第二种方法是对新的/未来的VF设备的推荐方法。 + +我怎样才能使用虚拟功能 +---------------------- + +在内核中,VF被视为热插拔的PCI设备,所以它们应该能够以与真正的PCI设备相同的方式 +工作。VF需要的设备驱动与普通PCI设备的驱动相同。 + +开发者指南 +========== + +SR-IOV API +---------- + +用来开启SR-IOV功能: + +(a) 对于第一种方法,在驱动程序中:: + + int pci_enable_sriov(struct pci_dev *dev, int nr_virtfn); + +nr_virtfn'是要启用的VF的编号。 + +(b) 对于第二种方法,从sysfs:: + + echo 'nr_virtfn' > \ + /sys/bus/pci/devices/<DOMAIN:BUS:DEVICE.FUNCTION>/sriov_numvfs + +用来关闭SR-IOV功能: + +(a) 对于第一种方法,在驱动程序中:: + + void pci_disable_sriov(struct pci_dev *dev); + +(b) 对于第二种方法,从sysfs:: + + echo 0 > \ + /sys/bus/pci/devices/<DOMAIN:BUS:DEVICE.FUNCTION>/sriov_numvfs + +要想通过主机上的兼容驱动启用自动探测VF,在启用SR-IOV功能之前运行下面的命令。这 +是默认的行为。 +:: + + echo 1 > \ + /sys/bus/pci/devices/<DOMAIN:BUS:DEVICE.FUNCTION>/sriov_drivers_autoprobe + +要禁止主机上的兼容驱动自动探测VF,请在启用SR-IOV功能之前运行以下命令。更新这个 +入口不会影响已经被探测的VF。 +:: + + echo 0 > \ + /sys/bus/pci/devices/<DOMAIN:BUS:DEVICE.FUNCTION>/sriov_drivers_autoprobe + +用例 +---- + +下面的代码演示了SR-IOV API的用法 +:: + + static int dev_probe(struct pci_dev *dev, const struct pci_device_id *id) + { + pci_enable_sriov(dev, NR_VIRTFN); + + ... + + return 0; + } + + static void dev_remove(struct pci_dev *dev) + { + pci_disable_sriov(dev); + + ... + } + + static int dev_suspend(struct device *dev) + { + ... + + return 0; + } + + static int dev_resume(struct device *dev) + { + ... + + return 0; + } + + static void dev_shutdown(struct pci_dev *dev) + { + ... + } + + static int dev_sriov_configure(struct pci_dev *dev, int numvfs) + { + if (numvfs > 0) { + ... + pci_enable_sriov(dev, numvfs); + ... + return numvfs; + } + if (numvfs == 0) { + .... + pci_disable_sriov(dev); + ... + return 0; + } + } + + static struct pci_driver dev_driver = { + .name = "SR-IOV Physical Function driver", + .id_table = dev_id_table, + .probe = dev_probe, + .remove = dev_remove, + .driver.pm = &dev_pm_ops + .shutdown = dev_shutdown, + .sriov_configure = dev_sriov_configure, + }; diff --git a/Documentation/translations/zh_CN/PCI/pci.rst b/Documentation/translations/zh_CN/PCI/pci.rst new file mode 100644 index 000000000..83c2a41d3 --- /dev/null +++ b/Documentation/translations/zh_CN/PCI/pci.rst @@ -0,0 +1,514 @@ +.. SPDX-License-Identifier: GPL-2.0 +.. include:: ../disclaimer-zh_CN.rst + +:Original: Documentation/PCI/pci.rst + +:翻译: + + 司延腾 Yanteng Si <siyanteng@loongson.cn> + +:校译: + + + +.. _cn_PCI_pci.rst: + +=================== +如何写Linux PCI驱动 +=================== + +:作者: - Martin Mares <mj@ucw.cz> + - Grant Grundler <grundler@parisc-linux.org> + +PCI的世界是巨大的,而且充满了(大多数是不愉快的)惊喜。由于每个CPU架构实现了不同 +的芯片组,并且PCI设备有不同的要求(呃,“特性”),结果是Linux内核中的PCI支持并不 +像人们希望的那样简单。这篇短文试图向所有潜在的驱动程序作者介绍PCI设备驱动程序的 +Linux APIs。 + +更完整的资源是Jonathan Corbet、Alessandro Rubini和Greg Kroah-Hartman的 +《Linux设备驱动程序》第三版。LDD3可以免费获得(在知识共享许可下),网址是: +https://lwn.net/Kernel/LDD3/。 + + + +然而,请记住,所有的文档都会受到“维护不及时”的影响。如果事情没有按照这里描述的那 +样进行,请参考源代码。 + +请将有关Linux PCI API的问题/评论/补丁发送到“Linux PCI” +<linux-pci@atrey.karlin.mff.cuni.cz> 邮件列表。 + + +PCI驱动的结构体 +=============== +PCI驱动通过pci_register_driver()在系统中“发现”PCI设备。实际上,它是反过来的。 +当PCI通用代码发现一个新设备时,具有匹配“描述”的驱动程序将被通知。下面是这方面的细 +节。 + +pci_register_driver()将大部分探测设备的工作留给了PCI层,并支持设备的在线插入/移 +除[从而在一个驱动中支持可热插拔的PCI、CardBus和Express-Card]。 pci_register_driver() +调用需要传入一个函数指针表,从而决定了驱动的高层结构体。 + +一旦驱动探测到一个PCI设备并取得了所有权,驱动通常需要执行以下初始化: + + - 启用设备 + - 请求MMIO/IOP资源 + - 设置DMA掩码大小(对于流式和一致的DMA) + - 分配和初始化共享控制数据(pci_allocate_coherent()) + - 访问设备配置空间(如果需要) + - 注册IRQ处理程序(request_irq()) + - 初始化非PCI(即芯片的LAN/SCSI/等部分) + - 启用DMA/处理引擎 + +当使用完设备后,也许需要卸载模块,驱动需要采取以下步骤: + + - 禁用设备产生的IRQ + - 释放IRQ(free_irq()) + - 停止所有DMA活动 + - 释放DMA缓冲区(包括一致性和数据流式) + - 从其他子系统(例如scsi或netdev)上取消注册 + - 释放MMIO/IOP资源 + - 禁用设备 + +这些主题中的大部分都在下面的章节中有所涉及。其余的内容请参考LDD3或<linux/pci.h> 。 + +如果没有配置PCI子系统(没有设置 ``CONFIG_PCI`` ),下面描述的大多数PCI函数被定 +义为内联函数,要么完全为空,要么只是返回一个适当的错误代码,以避免在驱动程序中出现 +大量的 ``ifdef`` 。 + + +调用pci_register_driver() +========================= + +PCI设备驱动程序在初始化过程中调用 ``pci_register_driver()`` ,并提供一个指向 +描述驱动程序的结构体的指针( ``struct pci_driver`` ): + +该API在以下内核代码中: + +include/linux/pci.h +pci_driver + +ID表是一个由 ``struct pci_device_id`` 结构体成员组成的数组,以一个全零的成员 +结束。一般来说,带有静态常数的定义是首选。 + +该API在以下内核代码中: + +include/linux/mod_devicetable.h +pci_device_id + +大多数驱动程序只需要 ``PCI_DEVICE()`` 或 ``PCI_DEVICE_CLASS()`` 来设置一个 +pci_device_id表。 + +新的 ``PCI ID`` 可以在运行时被添加到设备驱动的 ``pci_ids`` 表中,如下所示:: + + echo "vendor device subvendor subdevice class class_mask driver_data" > \ + /sys/bus/pci/drivers/{driver}/new_id + +所有字段都以十六进制值传递(没有前置0x)。供应商和设备字段是强制性的,其他字段是可 +选的。用户只需要传递必要的可选字段: + + - subvendor和subdevice字段默认为PCI_ANY_ID (FFFFFFF)。 + - class和classmask字段默认为0 + - driver_data默认为0UL。 + - override_only字段默认为0。 + +请注意, ``driver_data`` 必须与驱动程序中定义的任何一个 ``pci_device_id`` 条 +目所使用的值相匹配。如果所有的 ``pci_device_id`` 成员都有一个非零的driver_data +值,这使得driver_data字段是强制性的。 + +一旦添加,驱动程序探测程序将被调用,以探测其(新更新的) ``pci_ids`` 列表中列出的 +任何无人认领的PCI设备。 + +当驱动退出时,它只是调用 ``pci_unregister_driver()`` ,PCI层会自动调用驱动处理 +的所有设备的移除钩子。 + + +驱动程序功能/数据的“属性” +------------------------- + +请在适当的地方标记初始化和清理函数(相应的宏在<linux/init.h>中定义): + + ====== ============================================== + __init 初始化代码。在驱动程序初始化后被抛弃。 + __exit 退出代码。对于非模块化的驱动程序来说是忽略的。 + ====== ============================================== + +关于何时/何地使用上述属性的提示: + + - module_init()/module_exit()函数(以及所有仅由这些函数调用的初始化函数)应该被标记 + + - 为__init/__exit。 + + - 不要标记pci_driver结构体。 + + - 如果你不确定应该使用哪种标记,请不要标记一个函数。不标记函数比标记错误的函数更好。 + + +如何手动搜索PCI设备 +=================== + +PCI驱动最好有一个非常好的理由不使用 ``pci_register_driver()`` 接口来搜索PCI设备。 +PCI设备被多个驱动程序控制的主要原因是一个PCI设备实现了几个不同的HW服务。例如,组合的 +串行/并行端口/软盘控制器。 + +可以使用以下结构体进行手动搜索: + +通过供应商和设备ID进行搜索:: + + struct pci_dev *dev = NULL; + while (dev = pci_get_device(VENDOR_ID, DEVICE_ID, dev)) + configure_device(dev); + +按类别ID搜索(以类似的方式迭代):: + + pci_get_class(CLASS_ID, dev) + +通过供应商/设备和子系统供应商/设备ID进行搜索:: + + pci_get_subsys(VENDOR_ID,DEVICE_ID, SUBSYS_VENDOR_ID, SUBSYS_DEVICE_ID, dev). + +你可以使用常数 ``PCI_ANY_ID`` 作为 ``VENDOR_ID`` 或 ``DEVICE_ID`` 的通 +配符替代。例如,这允许搜索来自一个特定供应商的任何设备。 + +这些函数是热拔插安全的。它们会增加它们所返回的 ``pci_dev`` 的参考计数。你最终 +必须通过调用 ``pci_dev_put()`` 来减少这些设备上的参考计数(可能在模块卸载时)。 + + +设备初始化步骤 +============== + +正如介绍中所指出的,大多数PCI驱动需要以下步骤进行设备初始化: + + - 启用设备 + - 请求MMIO/IOP资源 + - 设置DMA掩码大小(对于流式和一致的DMA) + - 分配和初始化共享控制数据(pci_allocate_coherent()) + - 访问设备配置空间(如果需要) + - 注册IRQ处理程序(request_irq()) + - 初始化non-PCI(即芯片的LAN/SCSI/等部分) + - 启用DMA/处理引擎 + +驱动程序可以在任何时候访问PCI配置空间寄存器。(嗯,几乎如此。当运行BIST时,配置 +空间可以消失......但这只会导致PCI总线主控中止,读取配置将返回垃圾值)。) + + +启用PCI设备 +----------- +在接触任何设备寄存器之前,驱动程序需要通过调用 ``pci_enable_device()`` 启用 +PCI设备。这将: + + - 唤醒处于暂停状态的设备。 + - 分配设备的I/O和内存区域(如果BIOS没有这样做)。 + - 分配一个IRQ(如果BIOS没有)。 + +.. note:: + pci_enable_device() 可能失败,检查返回值。 + +.. warning:: + OS BUG:在启用这些资源之前,我们没有检查资源分配情况。如果我们在调用 + 之前调用pci_request_resources(),这个顺序会更合理。目前,当两个设备被分配 + 了相同的范围时,设备驱动无法检测到这个错误。这不是一个常见的问题,不太可能很快 + 得到修复。 + + 这个问题之前已经讨论过了,但从2.6.19开始没有改变: + https://lore.kernel.org/r/20060302180025.GC28895@flint.arm.linux.org.uk/ + + +pci_set_master()将通过设置PCI_COMMAND寄存器中的总线主控位来启用DMA。 +``pci_clear_master()`` 将通过清除总线主控位来禁用DMA,它还修复了延迟计时器的 +值,如果它被BIOS设置成假的。 + +如果PCI设备可以使用 ``PCI Memory-Write-Invalidate`` 事务,请调用 ``pci_set_mwi()`` 。 +这将启用 ``Mem-Wr-Inval`` 的 ``PCI_COMMAND`` 位,也确保缓存行大小寄存器被正确设置。检 +查 ``pci_set_mwi()`` 的返回值,因为不是所有的架构或芯片组都支持 ``Memory-Write-Invalidate`` 。 +另外,如果 ``Mem-Wr-Inval`` 是好的,但不是必须的,可以调用 ``pci_try_set_mwi()`` ,让 +系统尽最大努力来启用 ``Mem-Wr-Inval`` 。 + + +请求MMIO/IOP资源 +---------------- +内存(MMIO)和I/O端口地址不应该直接从PCI设备配置空间中读取。使用 ``pci_dev`` 结构体 +中的值,因为PCI “总线地址”可能已经被arch/chip-set特定的内核支持重新映射为“主机物理” +地址。 + +参见io_mapping函数,了解如何访问设备寄存器或设备内存。 + +设备驱动需要调用 ``pci_request_region()`` 来确认没有其他设备已经在使用相同的地址 +资源。反之,驱动应该在调用 ``pci_disable_device()`` 之后调用 ``pci_release_region()`` 。 +这个想法是为了防止两个设备在同一地址范围内发生冲突。 + +.. tip:: + 见上面的操作系统BUG注释。目前(2.6.19),驱动程序只能在调用pci_enable_device() + 后确定MMIO和IO端口资源的可用性。 + +``pci_request_region()`` 的通用风格是 ``request_mem_region()`` (用于MMIO +范围)和 ``request_region()`` (用于IO端口范围)。对于那些不被 "正常 "PCI BAR描 +述的地址资源,使用这些方法。 + +也请看下面的 ``pci_request_selected_regions()`` 。 + + +设置DMA掩码大小 +--------------- +.. note:: + 如果下面有什么不明白的地方,请参考使用通用设备的动态DMA映射。本节只是提醒大家, + 驱动程序需要说明设备的DMA功能,并不是DMA接口的权威来源。 + +虽然所有的驱动程序都应该明确指出PCI总线主控的DMA功能(如32位或64位),但对于流式 +数据来说,具有超过32位总线主站功能的设备需要驱动程序通过调用带有适当参数的 +``dma_set_mask()`` 来“注册”这种功能。一般来说,在系统RAM高于4G物理地址的情 +况下,这允许更有效的DMA。 + +所有PCI-X和PCIe兼容设备的驱动程序必须调用 ``dma_set_mask()`` ,因为它们 +是64位DMA设备。 + +同样,如果设备可以通过调用 ``dma_set_coherent_mask()`` 直接寻址到 +4G物理地址以上的系统RAM中的“一致性内存”,那么驱动程序也必须“注册”这种功能。同 +样,这包括所有PCI-X和PCIe兼容设备的驱动程序。许多64位“PCI”设备(在PCI-X之前) +和一些PCI-X设备对有效载荷(“流式”)数据具有64位DMA功能,但对控制(“一致性”)数 +据则没有。 + + +设置共享控制数据 +---------------- +一旦DMA掩码设置完毕,驱动程序就可以分配“一致的”(又称共享的)内存。参见使用通 +用设备的动态DMA映射,了解DMA API的完整描述。本节只是提醒大家,需要在设备上启 +用DMA之前完成。 + + +初始化设备寄存器 +---------------- +一些驱动程序需要对特定的“功能”字段进行编程,或对其他“供应商专用”寄存器进行初始 +化或重置。例如,清除挂起的中断。 + + +注册IRQ处理函数 +--------------- +虽然调用 ``request_irq()`` 是这里描述的最后一步,但这往往只是初始化设备的另 +一个中间步骤。这一步通常可以推迟到设备被打开使用时进行。 + +所有IRQ线的中断处理程序都应该用 ``IRQF_SHARED`` 注册,并使用devid将IRQ映射 +到设备(记住,所有的PCI IRQ线都可以共享)。 + +``request_irq()`` 将把一个中断处理程序和设备句柄与一个中断号联系起来。历史上, +中断号码代表从PCI设备到中断控制器的IRQ线。在MSI和MSI-X中(更多内容见下文),中 +断号是CPU的一个“向量”。 + +``request_irq()`` 也启用中断。在注册中断处理程序之前,请确保设备是静止的,并且 +没有任何中断等待。 + +MSI和MSI-X是PCI功能。两者都是“消息信号中断”,通过向本地APIC的DMA写入来向CPU发 +送中断。MSI和MSI-X的根本区别在于如何分配多个“向量”。MSI需要连续的向量块,而 +MSI-X可以分配几个单独的向量。 + +在调用 ``request_irq()`` 之前,可以通过调用 ``pci_alloc_irq_vectors()`` +的PCI_IRQ_MSI和/或PCI_IRQ_MSIX标志来启用MSI功能。这将导致PCI支持将CPU向量数 +据编程到PCI设备功能寄存器中。许多架构、芯片组或BIOS不支持MSI或MSI-X,调用 +``pci_alloc_irq_vectors`` 时只使用PCI_IRQ_MSI和PCI_IRQ_MSIX标志会失败, +所以尽量也要指定 ``PCI_IRQ_LEGACY`` 。 + +对MSI/MSI-X和传统INTx有不同中断处理程序的驱动程序应该在调用 +``pci_alloc_irq_vectors`` 后根据 ``pci_dev``结构体中的 ``msi_enabled`` +和 ``msix_enabled`` 标志选择正确的处理程序。 + +使用MSI有(至少)两个真正好的理由: + +1) 根据定义,MSI是一个排他性的中断向量。这意味着中断处理程序不需要验证其设备是 + 否引起了中断。 + +2) MSI避免了DMA/IRQ竞争条件。到主机内存的DMA被保证在MSI交付时对主机CPU是可 + 见的。这对数据一致性和避 + +3) 免控制数据过期都很重要。这个保证允许驱动程序省略MMIO读取,以刷新DMA流。 + +参见drivers/infiniband/hw/mthca/或drivers/net/tg3.c了解MSI/MSI-X的使 +用实例。 + + +PCI设备关闭 +=========== + +当一个PCI设备驱动程序被卸载时,需要执行以下大部分步骤: + + - 禁用设备产生的IRQ + - 释放IRQ(free_irq()) + - 停止所有DMA活动 + - 释放DMA缓冲区(包括流式和一致的) + - 从其他子系统(例如scsi或netdev)上取消注册 + - 禁用设备对MMIO/IO端口地址的响应 + - 释放MMIO/IO端口资源 + + +停止设备上的IRQ +--------------- +如何做到这一点是针对芯片/设备的。如果不这样做,如果(也只有在)IRQ与另一个设备 +共享,就会出现“尖叫中断”的可能性。 + +当共享的IRQ处理程序被“解钩”时,使用同一IRQ线的其余设备仍然需要启用该IRQ。因此, +如果“脱钩”的设备断言IRQ线,假设它是其余设备中的一个断言IRQ线,系统将作出反应。 +由于其他设备都不会处理这个IRQ,系统将“挂起”,直到它决定这个IRQ不会被处理并屏蔽 +这个IRQ(100,000次之后)。一旦共享的IRQ被屏蔽,其余设备将停止正常工作。这不是 +一个好事情。 + +这是使用MSI或MSI-X的另一个原因,如果它可用的话。MSI和MSI-X被定义为独占中断, +因此不容易受到“尖叫中断”问题的影响。 + +释放IRQ +------- +一旦设备被静止(不再有IRQ),就可以调用free_irq()。这个函数将在任何待处理 +的IRQ被处理后返回控制,从该IRQ上“解钩”驱动程序的IRQ处理程序,最后如果没有人 +使用该IRQ,则释放它。 + + +停止所有DMA活动 +--------------- +在试图取消分配DMA控制数据之前,停止所有的DMA操作是非常重要的。如果不这样做, +可能会导致内存损坏、挂起,在某些芯片组上还会导致硬崩溃。 + +在停止IRQ后停止DMA可以避免IRQ处理程序可能重新启动DMA引擎的竞争。 + +虽然这个步骤听起来很明显,也很琐碎,但过去有几个“成熟”的驱动程序没有做好这个 +步骤。 + + +释放DMA缓冲区 +------------- +一旦DMA被停止,首先要清理流式DMA。即取消数据缓冲区的映射,如果有的话,将缓 +冲区返回给“上游”所有者。 + +然后清理包含控制数据的“一致的”缓冲区。 + +关于取消映射接口的细节,请参见Documentation/core-api/dma-api.rst。 + + +从其他子系统取消注册 +-------------------- +大多数低级别的PCI设备驱动程序支持其他一些子系统,如USB、ALSA、SCSI、NetDev、 +Infiniband等。请确保你的驱动程序没有从其他子系统中丢失资源。如果发生这种情况, +典型的症状是当子系统试图调用已经卸载的驱动程序时,会出现Oops(恐慌)。 + + +禁止设备对MMIO/IO端口地址做出响应 +--------------------------------- +io_unmap() MMIO或IO端口资源,然后调用pci_disable_device()。 +这与pci_enable_device()对称相反。 +在调用pci_disable_device()后不要访问设备寄存器。 + + +释放MMIO/IO端口资源 +------------------- +调用pci_release_region()来标记MMIO或IO端口范围为可用。 +如果不这样做,通常会导致无法重新加载驱动程序。 + + + + +如何访问PCI配置空间 +=================== + +你可以使用 `pci_(read|write)_config_(byte|word|dword)` 来访问由 +`struct pci_dev *` 表示的设备的配置空间。所有这些函数在成功时返回0,或者返回一个 +错误代码( `PCIBIOS_...` ),这个错误代码可以通过pcibios_strerror翻译成文本字 +符串。大多数驱动程序希望对有效的PCI设备的访问不会失败。 + +如果你没有可用的pci_dev结构体,你可以调用 +`pci_bus_(read|write)_config_(byte|word|dword)` 来访问一个给定的设备和该总 +线上的功能。 + +如果你访问配置头的标准部分的字段,请使用<linux/pci.h>中声明的位置和位的符号名称。 + +如果你需要访问扩展的PCI功能寄存器,只要为特定的功能调用pci_find_capability(), +它就会为你找到相应的寄存器块。 + + +其它有趣的函数 +============== + +============================= ================================================= +pci_get_domain_bus_and_slot() 找到与给定的域、总线和槽以及编号相对应的pci_dev。 + 如果找到该设备,它的引用计数就会增加。 +pci_set_power_state() 设置PCI电源管理状态(0=D0 ... 3=D3 +pci_find_capability() 在设备的功能列表中找到指定的功能 +pci_resource_start() 返回一个给定的PCI区域的总线起始地址 +pci_resource_end() 返回给定PCI区域的总线末端地址 +pci_resource_len() 返回一个PCI区域的字节长度 +pci_set_drvdata() 为一个pci_dev设置私有驱动数据指针 +pci_get_drvdata() 返回一个pci_dev的私有驱动数据指针 +pci_set_mwi() 启用设备内存写无效 +pci_clear_mwi() 关闭设备内存写无效 +============================= ================================================= + + +杂项提示 +======== + +当向用户显示PCI设备名称时(例如,当驱动程序想告诉用户它找到了什么卡时),请使 +用pci_name(pci_dev)。 + +始终通过对pci_dev结构体的指针来引用PCI设备。所有的PCI层函数都使用这个标识, +它是唯一合理的标识。除了非常特殊的目的,不要使用总线/插槽/功能号————在有多个 +主总线的系统上,它们的语义可能相当复杂。 + +不要试图在你的驱动程序中开启快速寻址周期写入功能。总线上的所有设备都需要有这样 +的功能,所以这需要由平台和通用代码来处理,而不是由单个驱动程序来处理。 + + +供应商和设备标识 +================ + +不要在include/linux/pci_ids.h中添加新的设备或供应商ID,除非它们是在多个驱 +动程序中共享。如果有需要的话,你可以在你的驱动程序中添加私有定义,或者直接使用 +普通的十六进制常量。 + +设备ID是任意的十六进制数字(厂商控制),通常只在一个地方使用,即pci_device_id +表。 + +请务必提交新的供应商/设备ID到https://pci-ids.ucw.cz/。在 +https://github.com/pciutils/pciids,有一个pci.ids文件的镜像。 + + +过时的函数 +========== + +当你试图将一个旧的驱动程序移植到新的PCI接口时,你可能会遇到几个函数。它们不再存 +在于内核中,因为它们与热插拔或PCI域或具有健全的锁不兼容。 + +================= =================================== +pci_find_device() 被pci_get_device()取代 +pci_find_subsys() 被pci_get_subsys()取代 +pci_find_slot() 被pci_get_domain_bus_and_slot()取代 +pci_get_slot() 被pci_get_domain_bus_and_slot()取代 +================= =================================== + +另一种方法是传统的PCI设备驱动,即走PCI设备列表。这仍然是可能的,但不鼓励这样做。 + + +MMIO空间和“写通知” +================== + +将驱动程序从使用I/O端口空间转换为使用MMIO空间,通常需要一些额外的改变。具体来说, +需要处理“写通知”。许多驱动程序(如tg3,acenic,sym53c8xx_2)已经做了这个。I/O +端口空间保证写事务在CPU继续之前到达PCI设备。对MMIO空间的写入允许CPU在事务到达PCI +设备之前继续。HW weenies称这为“写通知”,因为在事务到达目的地之前,写的完成被“通知” +给CPU。 + +因此,对时间敏感的代码应该添加readl(),CPU在做其他工作之前应该等待。经典的“位脉冲” +序列对I/O端口空间很有效:: + + for (i = 8; --i; val >>= 1) { + outb(val & 1, ioport_reg); /* 置位 */ + udelay(10); + } + +对MMIO空间来说,同样的顺序应该是:: + + for (i = 8; --i; val >>= 1) { + writeb(val & 1, mmio_reg); /* 置位 */ + readb(safe_mmio_reg); /* 刷新写通知 */ + udelay(10); + } + +重要的是, ``safe_mmio_reg`` 不能有任何干扰设备正确操作的副作用。 + +另一种需要注意的情况是在重置PCI设备时。使用PCI配置空间读数来刷新writeel()。如果预期 +PCI设备不响应readl(),这将在所有平台上优雅地处理PCI主控器的中止。大多数x86平台将允许 +MMIO读取主控中止(又称“软失败”),并返回垃圾(例如~0)。但许多RISC平台会崩溃(又称“硬失败”)。 diff --git a/Documentation/translations/zh_CN/PCI/pciebus-howto.rst b/Documentation/translations/zh_CN/PCI/pciebus-howto.rst new file mode 100644 index 000000000..65c4301f1 --- /dev/null +++ b/Documentation/translations/zh_CN/PCI/pciebus-howto.rst @@ -0,0 +1,192 @@ +.. SPDX-License-Identifier: GPL-2.0 +.. include:: <isonum.txt> +.. include:: ../disclaimer-zh_CN.rst + +:Original: Documentation/PCI/pciebus-howto.rst + +:翻译: + + 司延腾 Yanteng Si <siyanteng@loongson.cn> + +:校译: + + + +.. _cn_pciebus-howto: + +=========================== +PCI Express端口总线驱动指南 +=========================== + +:作者: Tom L Nguyen tom.l.nguyen@intel.com 11/03/2004 +:版权: |copy| 2004 Intel Corporation + +关于本指南 +========== + +本指南介绍了PCI Express端口总线驱动程序的基本知识,并提供了如何使服务驱 +动程序在PCI Express端口总线驱动程序中注册/取消注册的介绍。 + + +什么是PCI Express端口总线驱动程序 +================================= + +一个PCI Express端口是一个逻辑的PCI-PCI桥结构。有两种类型的PCI Express端 +口:根端口和交换端口。根端口从PCI Express根综合体发起一个PCI Express链接, +交换端口将PCI Express链接连接到内部逻辑PCI总线。交换机端口,其二级总线代表 +交换机的内部路由逻辑,被称为交换机的上行端口。交换机的下行端口是从交换机的内部 +路由总线桥接到代表来自PCI Express交换机的下游PCI Express链接的总线。 + +一个PCI Express端口可以提供多达四个不同的功能,在本文中被称为服务,这取决于 +其端口类型。PCI Express端口的服务包括本地热拔插支持(HP)、电源管理事件支持(PME)、 +高级错误报告支持(AER)和虚拟通道支持(VC)。这些服务可以由一个复杂的驱动程序 +处理,也可以单独分布并由相应的服务驱动程序处理。 + +为什么要使用PCI Express端口总线驱动程序? +========================================= + +在现有的Linux内核中,Linux设备驱动模型允许一个物理设备只由一个驱动处理。 +PCI Express端口是一个具有多个不同服务的PCI-PCI桥设备。为了保持一个干净和简 +单的解决方案,每个服务都可以有自己的软件服务驱动。在这种情况下,几个服务驱动将 +竞争一个PCI-PCI桥设备。例如,如果PCI Express根端口的本机热拔插服务驱动程序 +首先被加载,它就会要求一个PCI-PCI桥根端口。因此,内核不会为该根端口加载其他服 +务驱动。换句话说,使用当前的驱动模型,不可能让多个服务驱动同时加载并运行在 +PCI-PCI桥设备上。 + +为了使多个服务驱动程序同时运行,需要有一个PCI Express端口总线驱动程序,它管 +理所有填充的PCI Express端口,并根据需要将所有提供的服务请求分配给相应的服务 +驱动程序。下面列出了使用PCI Express端口总线驱动程序的一些关键优势: + + - 允许在一个PCI-PCI桥接端口设备上同时运行多个服务驱动。 + + - 允许以独立的分阶段方式实施服务驱动程序。 + + - 允许一个服务驱动程序在多个PCI-PCI桥接端口设备上运行。 + + - 管理和分配PCI-PCI桥接端口设备的资源给要求的服务驱动程序。 + +配置PCI Express端口总线驱动程序与服务驱动程序 +============================================= + +将PCI Express端口总线驱动支持纳入内核 +------------------------------------- + +包括PCI Express端口总线驱动程序取决于内核配置中是否包含PCI Express支持。当内核 +中的PCI Express支持被启用时,内核将自动包含PCI Express端口总线驱动程序作为内核 +驱动程序。 + +启用服务驱动支持 +---------------- + +PCI设备驱动是基于Linux设备驱动模型实现的。所有的服务驱动都是PCI设备驱动。如上所述, +一旦内核加载了PCI Express端口总线驱动程序,就不可能再加载任何服务驱动程序。为了满 +足PCI Express端口总线驱动程序模型,需要对现有的服务驱动程序进行一些最小的改变,其 +对现有的服务驱动程序的功能没有影响。 + +服务驱动程序需要使用下面所示的两个API,将其服务注册到PCI Express端口总线驱动程 +序中(见第5.2.1和5.2.2节)。在调用这些API之前,服务驱动程序必须初始化头文件 +/include/linux/pcieport_if.h中的pcie_port_service_driver数据结构。如果不这 +样做,将导致身份不匹配,从而使PCI Express端口总线驱动程序无法加载服务驱动程序。 + +pcie_port_service_register +~~~~~~~~~~~~~~~~~~~~~~~~~~ +:: + + int pcie_port_service_register(struct pcie_port_service_driver *new) + +这个API取代了Linux驱动模型的 pci_register_driver API。一个服务驱动应该总是在模 +块启动时调用 pcie_port_service_register。请注意,在服务驱动被加载后,诸如 +pci_enable_device(dev) 和 pci_set_master(dev) 的调用不再需要,因为这些调用由 +PCI端口总线驱动执行。 + +pcie_port_service_unregister +~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +:: + + void pcie_port_service_unregister(struct pcie_port_service_driver *new) + +pcie_port_service_unregister取代了Linux驱动模型的pci_unregister_driver。当一 +个模块退出时,它总是被服务驱动调用。 + +示例代码 +~~~~~~~~ + +下面是服务驱动代码示例,用于初始化端口服务的驱动程序数据结构。 +:: + + static struct pcie_port_service_id service_id[] = { { + .vendor = PCI_ANY_ID, + .device = PCI_ANY_ID, + .port_type = PCIE_RC_PORT, + .service_type = PCIE_PORT_SERVICE_AER, + }, { /* end: all zeroes */ } + }; + + static struct pcie_port_service_driver root_aerdrv = { + .name = (char *)device_name, + .id_table = &service_id[0], + + .probe = aerdrv_load, + .remove = aerdrv_unload, + + .suspend = aerdrv_suspend, + .resume = aerdrv_resume, + }; + +下面是一个注册/取消注册服务驱动的示例代码。 +:: + + static int __init aerdrv_service_init(void) + { + int retval = 0; + + retval = pcie_port_service_register(&root_aerdrv); + if (!retval) { + /* + * FIX ME + */ + } + return retval; + } + + static void __exit aerdrv_service_exit(void) + { + pcie_port_service_unregister(&root_aerdrv); + } + + module_init(aerdrv_service_init); + module_exit(aerdrv_service_exit); + +可能的资源冲突 +============== + +由于PCI-PCI桥接端口设备的所有服务驱动被允许同时运行,下面列出了一些可能的资源冲突和 +建议的解决方案。 + +MSI 和 MSI-X 向量资源 +--------------------- + +一旦设备上的MSI或MSI-X中断被启用,它就会一直保持这种模式,直到它们再次被禁用。由于同 +一个PCI-PCI桥接端口的服务驱动程序共享同一个物理设备,如果一个单独的服务驱动程序启用或 +禁用MSI/MSI-X模式,可能会导致不可预知的行为。 + +为了避免这种情况,所有的服务驱动程序都不允许在其设备上切换中断模式。PCI Express端口 +总线驱动程序负责确定中断模式,这对服务驱动程序来说应该是透明的。服务驱动程序只需要知道 +分配给结构体pcie_device的字段irq的向量IRQ,当PCI Express端口总线驱动程序探测每 +个服务驱动程序时,它被传入。服务驱动应该使用(struct pcie_device*)dev->irq来调用 +request_irq/free_irq。此外,中断模式被存储在struct pcie_device的interrupt_mode +字段中。 + +PCI内存/IO映射的区域 +-------------------- + +PCI Express电源管理(PME)、高级错误报告(AER)、热插拔(HP)和虚拟通道(VC)的服务 +驱动程序访问PCI Express端口的PCI配置空间。在所有情况下,访问的寄存器是相互独立的。这 +个补丁假定所有的服务驱动程序都会表现良好,不会覆盖其他服务驱动程序的配置设置。 + +PCI配置寄存器 +------------- + +每个服务驱动都在自己的功能结构体上运行PCI配置操作,除了PCI Express功能结构体,其中根控制 +寄存器和设备控制寄存器是在PME和AER之间共享。这个补丁假定所有的服务驱动都会表现良好,不会 +覆盖其他服务驱动的配置设置。 diff --git a/Documentation/translations/zh_CN/PCI/sysfs-pci.rst b/Documentation/translations/zh_CN/PCI/sysfs-pci.rst new file mode 100644 index 000000000..0d75c2e99 --- /dev/null +++ b/Documentation/translations/zh_CN/PCI/sysfs-pci.rst @@ -0,0 +1,126 @@ +.. SPDX-License-Identifier: GPL-2.0 +.. include:: ../disclaimer-zh_CN.rst + +:Original: Documentation/PCI/sysfs-pci.rst + +:翻译: + + 司延腾 Yanteng Si <siyanteng@loongson.cn> + +:校译: + + + +======================== +通过sysfs访问PCI设备资源 +======================== + +sysfs,通常挂载在/sys,在支持它的平台上提供对PCI资源的访问。例如,一个特定的总线可能看起 +来像这样:: + + /sys/devices/pci0000:17 + |-- 0000:17:00.0 + | |-- class + | |-- config + | |-- device + | |-- enable + | |-- irq + | |-- local_cpus + | |-- remove + | |-- resource + | |-- resource0 + | |-- resource1 + | |-- resource2 + | |-- revision + | |-- rom + | |-- subsystem_device + | |-- subsystem_vendor + | `-- vendor + `-- ... + +最上面的元素描述了PCI域和总线号码。在这种情况下,域号是0000,总线号是17(两个值都是十六进制)。 +这个总线在0号插槽中包含一个单一功能的设备。为了方便起见,我们复制了域和总线的编号。在设备目录 +下有几个文件,每个文件都有自己的功能。 + + =================== ===================================================== + 文件 功能 + =================== ===================================================== + class PCI级别 (ascii, ro) + config PCI配置空间 (binary, rw) + device PCI设备 (ascii, ro) + enable 设备是否被启用 (ascii, rw) + irq IRQ编号 (ascii, ro) + local_cpus 临近CPU掩码(cpumask, ro) + remove 从内核的列表中删除设备 (ascii, wo) + resource PCI资源主机地址 (ascii, ro) + resource0..N PCI资源N,如果存在的话 (binary, mmap, rw\ [1]_) + resource0_wc..N_wc PCI WC映射资源N,如果可预取的话 (binary, mmap) + revision PCI修订版 (ascii, ro) + rom PCI ROM资源,如果存在的话 (binary, ro) + subsystem_device PCI子系统设备 (ascii, ro) + subsystem_vendor PCI子系统供应商 (ascii, ro) + vendor PCI供应商 (ascii, ro) + =================== ===================================================== + +:: + + ro - 只读文件 + rw - 文件是可读和可写的 + wo - 只写文件 + mmap - 文件是可移动的 + ascii - 文件包含ascii文本 + binary - 文件包含二进制数据 + cpumask - 文件包含一个cpumask类型的 + +.. [1] rw 仅适用于 IORESOURCE_IO(I/O 端口)区域 + +只读文件是信息性的,对它们的写入将被忽略,但 "rom "文件除外。可写文件可以用来在设备上执 +行操作(例如,改变配置空间,分离设备)。 mmapable文件可以通过偏移量为0的文件的mmap获得, +可以用来从用户空间进行实际的设备编程。注意,有些平台不支持某些资源的mmapping,所以一定要 +检查任何尝试的mmap的返回值。其中最值得注意的是I/O端口资源,它也提供读/写访问。 + +enable "文件提供了一个计数器,表明设备已经被启用了多少次。如果'enable'文件目前返回'4', +而一个'1'被呼入它,它将返回'5'。向它呼入一个'0'会减少计数。不过,即使它返回到0,一些初始 +化可能也不会被逆转。 + +rom "文件很特别,因为它提供了对设备ROM文件的只读访问,如果有的话。然而,它在默认情况下是 +禁用的,所以应用程序应该在尝试读取调用之前将字符串 "1 "写入该文件以启用它,并在访问之后将 +"0 "写入该文件以禁用它。请注意,设备必须被启用,才能成功返回数据。如果驱动没有被绑定到设备 +上,可以使用上面提到的 "enable "文件将其启用。 + +remove "文件是用来移除PCI设备的,通过向该文件写入一个非零的整数。这并不涉及任何形式的热插 +拔功能,例如关闭设备的电源。该设备被从内核的PCI设备列表中移除,它的sysfs目录被移除,并且该 +设备将被从任何连接到它的驱动程序中移除。移除PCI根总线是不允许的。 + +通过sysfs访问原有资源 +--------------------- + +如果底层平台支持的话,传统的I/O端口和ISA内存资源也会在sysfs中提供。它们位于PCI类的层次结构 +中,例如:: + + /sys/class/pci_bus/0000:17/ + |-- bridge -> ../../../devices/pci0000:17 + |-- cpuaffinity + |-- legacy_io + `-- legacy_mem + +legacy_io文件是一个读/写文件,可以被应用程序用来做传统的端口I/O。应用程序应该打开该文件,寻 +找所需的端口(例如0x3e8),并进行1、2或4字节的读或写。legacy_mem文件应该被mmapped,其偏移 +量与所需的内存偏移量相对应,例如0xa0000用于VGA帧缓冲器。然后,应用程序可以简单地解除引用返回 +的指针(当然是在检查了错误之后)来访问遗留内存空间。 + +支持新平台上的PCI访问 +--------------------- + +为了支持上述的PCI资源映射,Linux平台代码最好定义ARCH_GENERIC_PCI_MMAP_RESOURCE并使用该 +功能的通用实现。为了支持通过/proc/bus/pci中的文件实现mmap()的历史接口,平台也可以设置 +HAVE_PCI_MMAP。 + +另外,设置了 HAVE_PCI_MMAP 的平台可以提供他们自己的 pci_mmap_page_range() 实现,而不是定 +义 ARCH_GENERIC_PCI_MMAP_RESOURCE。 + +支持PCI资源的写组合映射的平台必须定义arch_can_pci_mmap_wc(),当写组合被允许时,在运行时应 +评估为非零。支持I/O资源映射的平台同样定义arch_can_pci_mmap_io()。 + +遗留资源由HAVE_PCI_LEGACY定义保护。希望支持遗留功能的平台应该定义它并提供 pci_legacy_read, +pci_legacy_write 和 pci_mmap_legacy_page_range 函数。 diff --git a/Documentation/translations/zh_CN/accounting/delay-accounting.rst b/Documentation/translations/zh_CN/accounting/delay-accounting.rst new file mode 100644 index 000000000..f18494110 --- /dev/null +++ b/Documentation/translations/zh_CN/accounting/delay-accounting.rst @@ -0,0 +1,109 @@ +.. include:: ../disclaimer-zh_CN.rst + +:Original: Documentation/accounting/delay-accounting.rst + +:Translator: Yang Yang <yang.yang29@zte.com.cn> + +======== +延时计数 +======== + +任务在等待某些内核资源可用时,会造成延时。例如一个可运行的任务可能会等待 +一个空闲CPU来运行。 + +基于每任务的延时计数功能度量由以下情况造成的任务延时: + +a) 等待一个CPU(任务为可运行) +b) 完成由该任务发起的块I/O同步请求 +c) 页面交换 +d) 内存回收 +e) 页缓存抖动 +f) 直接规整 + +并将这些统计信息通过taskstats接口提供给用户空间。 + +这些延时信息为适当的调整任务CPU优先级、io优先级、rss限制提供反馈。重要任务 +长期延时,表示可能需要提高其相关优先级。 + +通过使用taskstats接口,本功能还可提供一个线程组(对应传统Unix进程)所有任务 +(或线程)的总延时统计信息。此类汇总往往是需要的,由内核来完成更加高效。 + +用户空间的实体,特别是资源管理程序,可将延时统计信息汇总到任意组中。为实现 +这一点,任务的延时统计信息在其生命周期内和退出时皆可获取,从而确保可进行 +连续、完整的监控。 + +接口 +---- + +延时计数使用taskstats接口,该接口由本目录另一个单独的文档详细描述。Taskstats +向用户态返回一个通用数据结构,对应每pid或每tgid的统计信息。延时计数功能填写 +该数据结构的特定字段。见 + + include/uapi/linux/taskstats.h + +其描述了延时计数相关字段。系统通常以计数器形式返回 CPU、同步块 I/O、交换、内存 +回收、页缓存抖动、直接规整等的累积延时。 + +取任务某计数器两个连续读数的差值,将得到任务在该时间间隔内等待对应资源的总延时。 + +当任务退出时,内核会将包含每任务的统计信息发送给用户空间,而无需额外的命令。 +若其为线程组最后一个退出的任务,内核还会发送每tgid的统计信息。更多详细信息见 +taskstats接口的描述。 + +tools/accounting目录中的用户空间程序getdelays.c提供了一些简单的命令,用以显示 +延时统计信息。其也是使用taskstats接口的示例。 + +用法 +---- + +使用以下配置编译内核:: + + CONFIG_TASK_DELAY_ACCT=y + CONFIG_TASKSTATS=y + +延时计数在启动时默认关闭。 +若需开启,在启动参数中增加:: + + delayacct + +本文后续的说明基于延时计数已开启。也可在系统运行时,使用sysctl的 +kernel.task_delayacct进行开关。注意,只有在启用延时计数后启动的 +任务才会有相关信息。 + +系统启动后,使用类似getdelays.c的工具获取任务或线程组(tgid)的延时信息。 + +getdelays命令的一般格式:: + + getdelays [-dilv] [-t tgid] [-p pid] + +获取pid为10的任务从系统启动后的延时信息:: + + # ./getdelays -d -p 10 + (输出信息和下例相似) + +获取所有tgid为5的任务从系统启动后的总延时信息:: + + # ./getdelays -d -t 5 + print delayacct stats ON + TGID 5 + + + CPU count real total virtual total delay total delay average + 8 7000000 6872122 3382277 0.423ms + IO count delay total delay average + 0 0 0ms + SWAP count delay total delay average + 0 0 0ms + RECLAIM count delay total delay average + 0 0 0ms + THRASHING count delay total delay average + 0 0 0ms + COMPACT count delay total delay average + 0 0 0ms + +获取pid为1的IO计数,它只和-p一起使用:: + # ./getdelays -i -p 1 + printing IO accounting + linuxrc: read=65536, write=0, cancelled_write=0 + +上面的命令与-v一起使用,可以获取更多调试信息。 diff --git a/Documentation/translations/zh_CN/accounting/index.rst b/Documentation/translations/zh_CN/accounting/index.rst new file mode 100644 index 000000000..a34952e12 --- /dev/null +++ b/Documentation/translations/zh_CN/accounting/index.rst @@ -0,0 +1,25 @@ +.. SPDX-License-Identifier: GPL-2.0 + +.. include:: ../disclaimer-zh_CN.rst + +:Original: Documentation/accounting/index.rst +:Translator: Yang Yang <yang.yang29@zte.com.cn> + +.. _cn_accounting_index.rst: + + +==== +计数 +==== + +.. toctree:: + :maxdepth: 1 + + delay-accounting + psi + taskstats + +Todolist: + + cgroupstats + taskstats-struct diff --git a/Documentation/translations/zh_CN/accounting/psi.rst b/Documentation/translations/zh_CN/accounting/psi.rst new file mode 100644 index 000000000..a0ddb7bd2 --- /dev/null +++ b/Documentation/translations/zh_CN/accounting/psi.rst @@ -0,0 +1,155 @@ +.. include:: ../disclaimer-zh_CN.rst + +:Original: Documentation/accounting/psi.rst +:Translator: Yang Yang <yang.yang29@zte.com.cn> + +.. _cn_psi.rst: + + +================= +PSI——压力阻塞信息 +================= + +:日期: April, 2018 +:作者: Johannes Weiner <hannes@cmpxchg.org> + +当CPU、memory或IO设备处于竞争状态,业务负载会遭受时延毛刺、吞吐量降低, +及面临OOM的风险。 + +如果没有一种准确的方法度量系统竞争程度,则有两种后果:一种是用户过于节制, +未充分利用系统资源;另一种是过度使用,经常性面临业务中断的风险。 + +psi特性能够识别和量化资源竞争导致的业务中断,及其对复杂负载乃至整个系统在 +时间上的影响。 + +准确度量因资源不足造成的生产力损失,有助于用户基于硬件调整业务负载,或基 +于业务负载配置硬件。 + +psi能够实时的提供相关信息,因此系统可基于psi实现动态的负载管理。如实施 +卸载、迁移、策略性的停止或杀死低优先级或可重启的批处理任务。 + +psi帮助用户实现硬件资源利用率的最大化。同时无需牺牲业务负载健康度,也无需 +面临OOM等造成业务中断的风险。 + +压力接口 +======== + +压力信息可通过/proc/pressure/ --cpu、memory、io文件分别获取。 + +CPU相关信息格式如下: + + some avg10=0.00 avg60=0.00 avg300=0.00 total=0 + +内存和IO相关信息如下: + + some avg10=0.00 avg60=0.00 avg300=0.00 total=0 + full avg10=0.00 avg60=0.00 avg300=0.00 total=0 + +some行代表至少有一个任务阻塞于特定资源的时间占比。 + +full行代表所有非idle任务同时阻塞于特定资源的时间占比。在这种状态下CPU资源 +完全被浪费,相对于正常运行,业务负载由于耗费更多时间等待而受到严重影响。 + +由于此情况严重影响系统性能,因此清楚的识别本情况并与some行所代表的情况区分开, +将有助于分析及提升系统性能。这就是full独立于some行的原因。 + +avg代表阻塞时间占比(百分比),为最近10秒、60秒、300秒内的均值。这样我们 +既可观察到短期事件的影响,也可看到中等及长时间内的趋势。total代表总阻塞 +时间(单位微秒),可用于观察时延毛刺,这种毛刺可能在均值中无法体现。 + +监控压力门限 +============ + +用户可注册触发器,通过poll()监控资源压力是否超过门限。 + +触发器定义:指定时间窗口期内累积阻塞时间的最大值。比如可定义500ms内积累 +100ms阻塞,即触发一次唤醒事件。 + +触发器注册方法:用户打开代表特定资源的psi接口文件,写入门限、时间窗口的值。 +所打开的文件描述符用于等待事件,可使用select()、poll()、epoll()。 +写入信息的格式如下: + + <some|full> <stall amount in us> <time window in us> + +示例:向/proc/pressure/memory写入"some 150000 1000000"将新增触发器,将在 +1秒内至少一个任务阻塞于内存的总时间超过150ms时触发。向/proc/pressure/io写入 +"full 50000 1000000"将新增触发器,将在1秒内所有任务都阻塞于io的总时间超过50ms时触发。 + +触发器可针对多个psi度量值设置,同一个psi度量值可设置多个触发器。每个触发器需要 +单独的文件描述符用于轮询,以区分于其他触发器。所以即使对于同一个psi接口文件, +每个触发器也需要单独的调用open()。 + +监控器在被监控资源进入阻塞状态时启动,在系统退出阻塞状态后停用。系统进入阻塞 +状态后,监控psi增长的频率为每监控窗口刷新10次。 + +内核接受的窗口为500ms~10s,所以监控间隔为50ms~1s。设置窗口下限目的是为了 +防止过于频繁的轮询。设置窗口上限的目的是因为窗口过长则无意义,此时查看 +psi接口提供的均值即可。 + +监控器在激活后,至少在跟踪窗口期间将保持活动状态。以避免随着系统进入和退出 +阻塞状态,监控器过于频繁的进入和退出活动状态。 + +用户态通知在监控窗口内会受到速率限制。当对应的文件描述符关闭,触发器会自动注销。 + +用户态监控器使用示例 +==================== + +:: + + #include <errno.h> + #include <fcntl.h> + #include <stdio.h> + #include <poll.h> + #include <string.h> + #include <unistd.h> + + /* 监控内存部分阻塞,监控时间窗口为1秒、阻塞门限为150毫秒。*/ + int main() { + const char trig[] = "some 150000 1000000"; + struct pollfd fds; + int n; + + fds.fd = open("/proc/pressure/memory", O_RDWR | O_NONBLOCK); + if (fds.fd < 0) { + printf("/proc/pressure/memory open error: %s\n", + strerror(errno)); + return 1; + } + fds.events = POLLPRI; + + if (write(fds.fd, trig, strlen(trig) + 1) < 0) { + printf("/proc/pressure/memory write error: %s\n", + strerror(errno)); + return 1; + } + + printf("waiting for events...\n"); + while (1) { + n = poll(&fds, 1, -1); + if (n < 0) { + printf("poll error: %s\n", strerror(errno)); + return 1; + } + if (fds.revents & POLLERR) { + printf("got POLLERR, event source is gone\n"); + return 0; + } + if (fds.revents & POLLPRI) { + printf("event triggered!\n"); + } else { + printf("unknown event received: 0x%x\n", fds.revents); + return 1; + } + } + + return 0; + } + +Cgroup2接口 +=========== + +对于CONFIG_CGROUP=y及挂载了cgroup2文件系统的系统,能够获取cgroups内任务的psi。 +此场景下cgroupfs挂载点的子目录包含cpu.pressure、memory.pressure、io.pressure文件, +内容格式与/proc/pressure/下的文件相同。 + +可设置基于cgroup的psi监控器,方法与系统级psi监控器相同。 diff --git a/Documentation/translations/zh_CN/accounting/taskstats.rst b/Documentation/translations/zh_CN/accounting/taskstats.rst new file mode 100644 index 000000000..307ac5ce0 --- /dev/null +++ b/Documentation/translations/zh_CN/accounting/taskstats.rst @@ -0,0 +1,145 @@ +.. include:: ../disclaimer-zh_CN.rst + +:Original: Documentation/accounting/taskstats.rst + +:Translator: Yang Yang <yang.yang29@zte.com.cn> + +================ +每任务的统计接口 +================ + +Taskstats是一个基于netlink的接口,用于从内核向用户空间发送每任务及每进程的 +统计信息。 + +Taskstats设计目的: + +- 在任务生命周期内和退出时高效的提供统计信息 +- 统一不同计数子系统的接口 +- 支持未来计数系统的扩展 + +术语 +---- + +“pid”、“tid”、“任务”互换使用,用于描述由struct task_struct定义的标准 +Linux任务。“每pid的统计数据”等价于“每任务的统计数据”。 + +“tgid”、“进程”、“线程组”互换使用,用于描述共享mm_struct的任务集, +也就是传统的Unix进程。尽管使用了tgid这个词,即使一个任务是线程组组长, +对它的处理也没有什么不同。只要一个进程还有任何归属它的任务,它就被认为 +活着。 + +用法 +---- + +为了在任务生命周期内获得统计信息,用户空间需打开一个单播的netlink套接字 +(NETLINK_GENERIC族)然后发送指定pid或tgid的命令。响应消息中包含单个 +任务的统计信息(若指定了pid)或进程所有任务汇总的统计信息(若指定了tgid)。 + +为了在任务退出时获取统计信息,用户空间的监听者发送一个指定cpu掩码的注册命令。 +cpu掩码内的cpu上有任务退出时,每pid的统计信息将发送给注册成功的监听者。使用 +cpu掩码可以限制一个监听者收到的数据,并有助于对netlink接口进行流量控制,后文 +将进行更详细的解释。 + +如果正在退出的任务是线程组中最后一个退出的线程,额外一条包含每tgid统计信息的 +记录也将发送给用户空间。后者包含线程组中所有线程(包括过去和现在)的每pid统计 +信息总和。 + +getdelays.c是一个简单的示例,用以演示如何使用taskstats接口获取延迟统计信息。 +用户可注册cpu掩码、发送命令和处理响应、监听每tid/tgid退出数据、将收到的数据 +写入文件、通过增大接收缓冲区进行基本的流量控制。 + +接口 +---- + +内核用户接口封装在include/linux/taskstats.h。 + +为避免本文档随着接口的演进而过期,本文仅给出当前版本的概要。当本文与taskstats.h +不一致时,以taskstats.h为准。 + +struct taskstats是每pid和每tgid数据共用的计数结构体。它是版本化的,可在内核新增 +计数子系统时进行扩展。taskstats.h中定义了各字段及语义。 + +用户、内核空间的数据交换是属于NETLINK_GENERIC族的netlink消息,使用netlink属性 +接口。消息格式如下:: + + +----------+- - -+-------------+-------------------+ + | nlmsghdr | Pad | genlmsghdr | taskstats payload | + +----------+- - -+-------------+-------------------+ + +Taskstats载荷有三种类型: + +1. 命令:由用户发送给内核。获取指定pid/tgid数据的命令包含一个类型为 +TASKSTATS_CMD_ATTR_PID/TGID的属性,该属性包含u32的pid或tgid载荷。 +pid/tgid指示用户空间要统计的任务/进程。 + +注册/注销获取指定cpu集上退出数据的命令包含一个类型为 +TASKSTATS_CMD_ATTR_REGISTER/DEREGISTER_CPUMASK的属性,该属性包含cpu掩码载荷。 +cpu掩码是以ascii码表示,用逗号分隔的cpu范围。例如若需监听1,2,3,5,7,8号cpu的 +退出数据,cpu掩码表示为"1-3,5,7-8"。若用户空间在关闭监听套接字前忘了注销监听 +的cpu集,随着时间的推移,内核会清理此监听集。但是,出于提效的目的,建议明确 +执行注销。 + +2. 命令的应答:内核发出应答用户空间的命令。载荷有三类属性: + +a) TASKSTATS_TYPE_AGGR_PID/TGID: 本属性不包含载荷,用以指示其后为被统计对象 +的pig/tgid。 + +b) TASKSTATS_TYPE_PID/TGID:本属性的载荷为pig/tgid,其统计信息将被返回。 + +c) TASKSTATS_TYPE_STATS:本属性的载荷为一个struct taskstats实例。每pid和 +每tgid统计信息共用该结构体。 + +3. 内核会在任务退出时发送新消息。其载荷包含一系列以下类型的属性: + +a) TASKSTATS_TYPE_AGGR_PID:指示其后两个属性为pid+stats。 +b) TASKSTATS_TYPE_PID:包含退出任务的pid。 +c) TASKSTATS_TYPE_STATS:包含退出任务的每pid统计信息 +d) TASKSTATS_TYPE_AGGR_TGID:指示其后两个属性为tgid+stats。 +e) TASKSTATS_TYPE_TGID:包含任务所属进程的tgid +f) TASKSTATS_TYPE_STATS:包含退出任务所属进程的每tgid统计信息 + +每tgid的统计 +------------ + +除了每任务的统计信息,taskstats还提供每进程的统计信息,因为资源管理通常以进程 +粒度完成,并且仅在用户空间聚合任务统计信息效率低下且可能不准确(缺乏原子性)。 + +然而,除了每任务统计信息,在内核中维护每进程统计信息存在额外的时间和空间开销。 +为解决此问题,taskstats代码将退出任务的统计信息累积到进程范围的数据结构中。 +当进程最后一个任务退出时,累积的进程级数据也会发送到用户空间(与每任务数据一起)。 + +当用户查询每tgid数据时,内核将指定线程组中所有活动线程的统计信息相加,并添加到 +该线程组的累积总数(含之前退出的线程)。 + +扩展taskstats +------------- + +有两种方法可在未来修改内核扩展taskstats接口,以导出更多的每任务/进程统计信息: + +1. 在现有struct taskstats末尾增加字段。该结构体中的版本号确保了向后兼容性。 +用户空间将仅使用与其版本对应的结构体字段。 + +2. 定义单独的统计结构体并使用netlink属性接口返回对应的数据。由于用户空间独立 +处理每个netlink属性,所以总是可以忽略其不理解类型的属性(因为使用了旧版本接口)。 + +在1.和2.之间进行选择,属于权衡灵活性和开销的问题。若仅需增加少数字段,那么1.是 +首选方法,因为内核和用户空间无需承担处理新netlink属性的开销。但若新字段过多的 +扩展现有结构体,导致不同的用户空间计数程序不必要的接收大型结构体,而对结构体 +字段并不感兴趣,那么2.是值得的。 + +Taskstats的流量控制 +------------------- + +当退出任务数速率变大,监听者可能跟不上内核发送每tid/tgid退出数据的速率,而导致 +数据丢失。taskstats结构体变大、cpu数量上升,都会导致这种可能性增加。 + +为避免统计信息丢失,用户空间应执行以下操作中至少一项: + +- 增大监听者用于接收退出数据的netlink套接字接收缓存区。 + +- 创建更多的监听者,减少每个监听者监听的cpu数量。极端情况下可为每个cpu创建 + 一个监听者。用户还可考虑将监听者的cpu亲和性设置为监听cpu的子集,特别是当他们 + 仅监听一个cpu。 + +尽管采取了这些措施,若用户空间仍收到指示接收缓存区溢出的ENOBUFS错误消息, +则应采取其他措施处理数据丢失。 diff --git a/Documentation/translations/zh_CN/admin-guide/README.rst b/Documentation/translations/zh_CN/admin-guide/README.rst new file mode 100644 index 000000000..e679cbc3c --- /dev/null +++ b/Documentation/translations/zh_CN/admin-guide/README.rst @@ -0,0 +1,291 @@ +.. include:: ../disclaimer-zh_CN.rst + +:Original: Documentation/admin-guide/README.rst + +:译者: + + 吴想成 Wu XiangCheng <bobwxc@email.cn> + +Linux内核6.x版本 <http://kernel.org/> +========================================= + +以下是Linux版本6的发行注记。仔细阅读它们, +它们会告诉你这些都是什么,解释如何安装内核,以及遇到问题时该如何做。 + +什么是Linux? +--------------- + + Linux是Unix操作系统的克隆版本,由Linus Torvalds在一个松散的网络黑客 + (Hacker,无贬义)团队的帮助下从头开始编写。它旨在实现兼容POSIX和 + 单一UNIX规范。 + + 它具有在现代成熟的Unix中应当具有的所有功能,包括真正的多任务处理、虚拟内存、 + 共享库、按需加载、共享的写时拷贝(COW)可执行文件、恰当的内存管理以及包括 + IPv4和IPv6在内的复合网络栈。 + + Linux在GNU通用公共许可证,版本2(GNU GPLv2)下分发,详见随附的COPYING文件。 + +它能在什么样的硬件上运行? +----------------------------- + + 虽然Linux最初是为32位的x86 PC机(386或更高版本)开发的,但今天它也能运行在 + (至少)Compaq Alpha AXP、Sun SPARC与UltraSPARC、Motorola 68000、PowerPC、 + PowerPC64、ARM、Hitachi SuperH、Cell、IBM S/390、MIPS、HP PA-RISC、Intel + IA-64、DEC VAX、AMD x86-64 Xtensa和ARC架构上。 + + Linux很容易移植到大多数通用的32位或64位体系架构,只要它们有一个分页内存管理 + 单元(PMMU)和一个移植的GNU C编译器(gcc;GNU Compiler Collection,GCC的一 + 部分)。Linux也被移植到许多没有PMMU的体系架构中,尽管功能显然受到了一定的 + 限制。 + Linux也被移植到了其自己上。现在可以将内核作为用户空间应用程序运行——这被 + 称为用户模式Linux(UML)。 + +文档 +----- +因特网上和书籍上都有大量的电子文档,既有Linux专属文档,也有与一般UNIX问题相关 +的文档。我建议在任何Linux FTP站点上查找LDP(Linux文档项目)书籍的文档子目录。 +本自述文件并不是关于系统的文档:有更好的可用资源。 + + - 因特网上和书籍上都有大量的(电子)文档,既有Linux专属文档,也有与普通 + UNIX问题相关的文档。我建议在任何有LDP(Linux文档项目)书籍的Linux FTP + 站点上查找文档子目录。本自述文件并不是关于系统的文档:有更好的可用资源。 + + - 文档/子目录中有各种自述文件:例如,这些文件通常包含一些特定驱动程序的 + 内核安装说明。请阅读 + :ref:`Documentation/process/changes.rst <changes>` 文件,它包含了升级内核 + 可能会导致的问题的相关信息。 + +安装内核源代码 +--------------- + + - 如果您要安装完整的源代码,请把内核tar档案包放在您有权限的目录中(例如您 + 的主目录)并将其解包:: + + xz -cd linux-6.x.tar.xz | tar xvf - + + 将“X”替换成最新内核的版本号。 + + 【不要】使用 /usr/src/linux 目录!这里有一组库头文件使用的内核头文件 + (通常是不完整的)。它们应该与库匹配,而不是被内核的变化搞得一团糟。 + + - 您还可以通过打补丁在6.x版本之间升级。补丁以xz格式分发。要通过打补丁进行 + 安装,请获取所有较新的补丁文件,进入内核源代码(linux-6.x)的目录并 + 执行:: + + xz -cd ../patch-6.x.xz | patch -p1 + + 请【按顺序】替换所有大于当前源代码树版本的“x”,这样就可以了。您可能想要 + 删除备份文件(文件名类似xxx~ 或 xxx.orig),并确保没有失败的补丁(文件名 + 类似xxx# 或 xxx.rej)。如果有,不是你就是我犯了错误。 + + 与6.x内核的补丁不同,6.x.y内核(也称为稳定版内核)的补丁不是增量的,而是 + 直接应用于基本的6.x内核。例如,如果您的基本内核是6.0,并且希望应用6.0.3 + 补丁,则不应先应用6.0.1和6.0.2的补丁。类似地,如果您运行的是6.0.2内核, + 并且希望跳转到6.0.3,那么在应用6.0.3补丁之前,必须首先撤销6.0.2补丁 + (即patch -R)。更多关于这方面的内容,请阅读 + :ref:`Documentation/process/applying-patches.rst <applying_patches>` 。 + + 或者,脚本 patch-kernel 可以用来自动化这个过程。它能确定当前内核版本并 + 应用找到的所有补丁:: + + linux/scripts/patch-kernel linux + + 上面命令中的第一个参数是内核源代码的位置。补丁是在当前目录应用的,但是 + 可以将另一个目录指定为第二个参数。 + + - 确保没有过时的 .o 文件和依赖项:: + + cd linux + make mrproper + + 现在您应该已经正确安装了源代码。 + +软件要求 +--------- + + 编译和运行6.x内核需要各种软件包的最新版本。请参考 + :ref:`Documentation/process/changes.rst <changes>` + 来了解最低版本要求以及如何升级软件包。请注意,使用过旧版本的这些包可能会 + 导致很难追踪的间接错误,因此不要以为在生成或操作过程中出现明显问题时可以 + 只更新包。 + +为内核建立目录 +--------------- + + 编译内核时,默认情况下所有输出文件都将与内核源代码放在一起。使用 + ``make O=output/dir`` 选项可以为输出文件(包括 .config)指定备用位置。 + 例如:: + + kernel source code: /usr/src/linux-6.x + build directory: /home/name/build/kernel + + 要配置和构建内核,请使用:: + + cd /usr/src/linux-6.x + make O=/home/name/build/kernel menuconfig + make O=/home/name/build/kernel + sudo make O=/home/name/build/kernel modules_install install + + 请注意:如果使用了 ``O=output/dir`` 选项,那么它必须用于make的所有调用。 + +配置内核 +--------- + + 即使只升级一个小版本,也不要跳过此步骤。每个版本中都会添加新的配置选项, + 如果配置文件没有按预定设置,就会出现奇怪的问题。如果您想以最少的工作量 + 将现有配置升级到新版本,请使用 ``make oldconfig`` ,它只会询问您新配置 + 选项的答案。 + + - 其他配置命令包括:: + + "make config" 纯文本界面。 + + "make menuconfig" 基于文本的彩色菜单、选项列表和对话框。 + + "make nconfig" 增强的基于文本的彩色菜单。 + + "make xconfig" 基于Qt的配置工具。 + + "make gconfig" 基于GTK+的配置工具。 + + "make oldconfig" 基于现有的 ./.config 文件选择所有选项,并询问 + 新配置选项。 + + "make olddefconfig" + 类似上一个,但不询问直接将新选项设置为默认值。 + + "make defconfig" 根据体系架构,使用arch/$arch/defconfig或 + arch/$arch/configs/${PLATFORM}_defconfig中的 + 默认选项值创建./.config文件。 + + "make ${PLATFORM}_defconfig" + 使用arch/$arch/configs/${PLATFORM}_defconfig中 + 的默认选项值创建一个./.config文件。 + 用“make help”来获取您体系架构中所有可用平台的列表。 + + "make allyesconfig" + 通过尽可能将选项值设置为“y”,创建一个 + ./.config文件。 + + "make allmodconfig" + 通过尽可能将选项值设置为“m”,创建一个 + ./.config文件。 + + "make allnoconfig" 通过尽可能将选项值设置为“n”,创建一个 + ./.config文件。 + + "make randconfig" 通过随机设置选项值来创建./.config文件。 + + "make localmodconfig" 基于当前配置和加载的模块(lsmod)创建配置。禁用 + 已加载的模块不需要的任何模块选项。 + + 要为另一台计算机创建localmodconfig,请将该计算机 + 的lsmod存储到一个文件中,并将其作为lsmod参数传入。 + + 此外,通过在参数LMC_KEEP中指定模块的路径,可以将 + 模块保留在某些文件夹或kconfig文件中。 + + target$ lsmod > /tmp/mylsmod + target$ scp /tmp/mylsmod host:/tmp + + host$ make LSMOD=/tmp/mylsmod \ + LMC_KEEP="drivers/usb:drivers/gpu:fs" \ + localmodconfig + + 上述方法在交叉编译时也适用。 + + "make localyesconfig" 与localmodconfig类似,只是它会将所有模块选项转换 + 为内置(=y)。你可以同时通过LMC_KEEP保留模块。 + + "make kvm_guest.config" + 为kvm客户机内核支持启用其他选项。 + + "make xen.config" 为xen dom0客户机内核支持启用其他选项。 + + "make tinyconfig" 配置尽可能小的内核。 + + 更多关于使用Linux内核配置工具的信息,见文档 + Documentation/kbuild/kconfig.rst。 + + - ``make config`` 注意事项: + + - 包含不必要的驱动程序会使内核变大,并且在某些情况下会导致问题: + 探测不存在的控制器卡可能会混淆其他控制器。 + + - 如果存在协处理器,则编译了数学仿真的内核仍将使用协处理器:在 + 这种情况下,数学仿真永远不会被使用。内核会稍微大一点,但不管 + 是否有数学协处理器,都可以在不同的机器上工作。 + + - “kernel hacking”配置细节通常会导致更大或更慢的内核(或两者 + 兼而有之),甚至可以通过配置一些例程来主动尝试破坏坏代码以发现 + 内核问题,从而降低内核的稳定性(kmalloc())。因此,您可能应该 + 用于研究“开发”、“实验”或“调试”特性相关问题。 + +编译内核 +--------- + + - 确保您至少有gcc 5.1可用。 + 有关更多信息,请参阅 :ref:`Documentation/process/changes.rst <changes>` 。 + + - 执行 ``make`` 来创建压缩内核映像。如果您安装了lilo以适配内核makefile, + 那么也可以进行 ``make install`` ,但是您可能需要先检查特定的lilo设置。 + + 实际安装必须以root身份执行,但任何正常构建都不需要。 + 无须徒然使用root身份。 + + - 如果您将内核的任何部分配置为模块,那么还必须执行 ``make modules_install`` 。 + + - 详细的内核编译/生成输出: + + 通常,内核构建系统在相当安静的模式下运行(但不是完全安静)。但是有时您或 + 其他内核开发人员需要看到编译、链接或其他命令的执行过程。为此,可使用 + “verbose(详细)”构建模式。 + 向 ``make`` 命令传递 ``V=1`` 来实现,例如:: + + make V=1 all + + 如需构建系统也给出内个目标重建的愿意,请使用 ``V=2`` 。默认为 ``V=0`` 。 + + - 准备一个备份内核以防出错。对于开发版本尤其如此,因为每个新版本都包含 + 尚未调试的新代码。也要确保保留与该内核对应的模块的备份。如果要安装 + 与工作内核版本号相同的新内核,请在进行 ``make modules_install`` 安装 + 之前备份modules目录。 + + 或者,在编译之前,使用内核配置选项“LOCALVERSION”向常规内核版本附加 + 一个唯一的后缀。LOCALVERSION可以在“General Setup”菜单中设置。 + + - 为了引导新内核,您需要将内核映像(例如编译后的 + .../linux/arch/x86/boot/bzImage)复制到常规可引导内核的位置。 + + - 不再支持在没有LILO等启动装载程序帮助的情况下直接从软盘引导内核。 + + 如果从硬盘引导Linux,很可能使用LILO,它使用/etc/lilo.conf文件中 + 指定的内核映像文件。内核映像文件通常是/vmlinuz、/boot/vmlinuz、 + /bzImage或/boot/bzImage。使用新内核前,请保存旧映像的副本,并复制 + 新映像覆盖旧映像。然后您【必须重新运行LILO】来更新加载映射!否则, + 将无法启动新的内核映像。 + + 重新安装LILO通常需要运行/sbin/LILO。您可能希望编辑/etc/lilo.conf + 文件为旧内核映像指定一个条目(例如/vmlinux.old)防止新的不能正常 + 工作。有关更多信息,请参阅LILO文档。 + + 重新安装LILO之后,您应该就已经准备好了。关闭系统,重新启动,尽情 + 享受吧! + + 如果需要更改内核映像中的默认根设备、视频模式等,请在适当的地方使用 + 启动装载程序的引导选项。无需重新编译内核即可更改这些参数。 + + - 使用新内核重新启动并享受它吧。 + +若遇到问题 +----------- + +如果您发现了一些可能由于内核缺陷所导致的问题,请参阅: +Documentation/translations/zh_CN/admin-guide/reporting-issues.rst 。 + +想要理解内核错误报告,请参阅: +Documentation/translations/zh_CN/admin-guide/bug-hunting.rst 。 + +更多用GDB调试内核的信息,请参阅: +Documentation/translations/zh_CN/dev-tools/gdb-kernel-debugging.rst +和 Documentation/dev-tools/kgdb.rst 。 diff --git a/Documentation/translations/zh_CN/admin-guide/bootconfig.rst b/Documentation/translations/zh_CN/admin-guide/bootconfig.rst new file mode 100644 index 000000000..072d17f5f --- /dev/null +++ b/Documentation/translations/zh_CN/admin-guide/bootconfig.rst @@ -0,0 +1,293 @@ +.. SPDX-License-Identifier: GPL-2.0 + +.. include:: ../disclaimer-zh_CN.rst + +:Original: Documentation/admin-guide/bootconfig.rst + +:译者: 吴想成 Wu XiangCheng <bobwxc@email.cn> + +======== +引导配置 +======== + +:作者: Masami Hiramatsu <mhiramat@kernel.org> + +概述 +==== + +引导配置扩展了现有的内核命令行,以一种更有效率的方式在引导内核时进一步支持 +键值数据。这允许管理员传递一份结构化关键字的配置文件。 + +配置文件语法 +============ + +引导配置文件的语法采用非常简单的键值结构。每个关键字由点连接的单词组成,键 +和值由 ``=`` 连接。值以分号( ``;`` )或换行符( ``\n`` )结尾。数组值中每 +个元素由逗号( ``,`` )分隔。:: + + KEY[.WORD[...]] = VALUE[, VALUE2[...]][;] + +与内核命令行语法不同,逗号和 ``=`` 周围允许有空格。 + +关键字只允许包含字母、数字、连字符( ``-`` )和下划线( ``_`` )。值可包含 +可打印字符和空格,但分号( ``;`` )、换行符( ``\n`` )、逗号( ``,`` )、 +井号( ``#`` )和右大括号( ``}`` )等分隔符除外。 + +如果你需要在值中使用这些分隔符,可以用双引号( ``"VALUE"`` )或单引号 +( ``'VALUE'`` )括起来。注意,引号无法转义。 + +键的值可以为空或不存在。这些键用于检查该键是否存在(类似布尔值)。 + +键值语法 +-------- + +引导配置文件语法允许用户通过大括号合并键名部分相同的关键字。例如:: + + foo.bar.baz = value1 + foo.bar.qux.quux = value2 + +也可以写成:: + + foo.bar { + baz = value1 + qux.quux = value2 + } + +或者更紧凑一些,写成:: + + foo.bar { baz = value1; qux.quux = value2 } + +在这两种样式中,引导解析时相同的关键字都会自动合并。因此可以追加类似的树或 +键值。 + +相同关键字的值 +-------------- + +禁止两个或多个值或数组共享同一个关键字。例如:: + + foo = bar, baz + foo = qux # !错误! 我们不可以重定义相同的关键字 + +如果你想要更新值,必须显式使用覆盖操作符 ``:=`` 。例如:: + + foo = bar, baz + foo := qux + +这样 ``foo`` 关键字的值就变成了 ``qux`` 。这对于通过添加(部分)自定义引导 +配置来覆盖默认值非常有用,免于解析默认引导配置。 + +如果你想对现有关键字追加值作为数组成员,可以使用 ``+=`` 操作符。例如:: + + foo = bar, baz + foo += qux + +这样, ``foo`` 关键字就同时拥有了 ``bar`` , ``baz`` 和 ``qux`` 。 + +此外,父关键字下可同时存在值和子关键字。 +例如,下列配置是可行的。:: + + foo = value1 + foo.bar = value2 + foo := value3 # 这会更新foo的值。 + +注意,裸值不能直接放进结构化关键字中,必须在大括号外定义它。例如:: + + foo { + bar = value1 + bar { + baz = value2 + qux = value3 + } + } + +同时,关键字下值节点的顺序是固定的。如果值和子关键字同时存在,值永远是该关 +键字的第一个子节点。因此如果用户先指定子关键字,如:: + + foo.bar = value1 + foo = value2 + +则在程序(和/proc/bootconfig)中,它会按如下显示:: + + foo = value2 + foo.bar = value1 + +注释 +---- + +配置语法接受shell脚本风格的注释。注释以井号( ``#`` )开始,到换行符 +( ``\n`` )结束。 + +:: + + # comment line + foo = value # value is set to foo. + bar = 1, # 1st element + 2, # 2nd element + 3 # 3rd element + +会被解析为:: + + foo = value + bar = 1, 2, 3 + +注意你不能把注释放在值和分隔符( ``,`` 或 ``;`` )之间。如下配置语法是错误的:: + + key = 1 # comment + ,2 + + +/proc/bootconfig +================ + +/proc/bootconfig是引导配置的用户空间接口。与/proc/cmdline不同,此文件内容以 +键值列表样式显示。 +每个键值对一行,样式如下:: + + KEY[.WORDS...] = "[VALUE]"[,"VALUE2"...] + + +用引导配置引导内核 +================== + +用引导配置引导内核有两种方法:将引导配置附加到initrd镜像或直接嵌入内核中。 + +*initrd: initial RAM disk,初始内存磁盘* + +将引导配置附加到initrd +---------------------- + +由于默认情况下引导配置文件是用initrd加载的,因此它将被添加到initrd(initramfs) +镜像文件的末尾,其中包含填充、大小、校验值和12字节幻数,如下所示:: + + [initrd][bootconfig][padding][size(le32)][checksum(le32)][#BOOTCONFIG\n] + +大小和校验值为小端序存放的32位无符号值。 + +当引导配置被加到initrd镜像时,整个文件大小会对齐到4字节。空字符( ``\0`` ) +会填补对齐空隙。因此 ``size`` 就是引导配置文件的长度+填充的字节。 + +Linux内核在内存中解码initrd镜像的最后部分以获取引导配置数据。由于这种“背负式” +的方法,只要引导加载器传递了正确的initrd文件大小,就无需更改或更新引导加载器 +和内核镜像本身。如果引导加载器意外传递了更长的大小,内核将无法找到引导配置数 +据。 + +Linux内核在tools/bootconfig下提供了 ``bootconfig`` 命令来完成此操作,管理员 +可以用它从initrd镜像中删除或追加配置文件。你可以用以下命令来构建它:: + + # make -C tools/bootconfig + +要向initrd镜像添加你的引导配置文件,请按如下命令操作(旧数据会自动移除):: + + # tools/bootconfig/bootconfig -a your-config /boot/initrd.img-X.Y.Z + +要从镜像中移除配置,可以使用-d选项:: + + # tools/bootconfig/bootconfig -d /boot/initrd.img-X.Y.Z + +然后在内核命令行上添加 ``bootconfig`` 告诉内核去initrd文件末尾寻找内核配置。 + +将引导配置嵌入内核 +------------------ + +如果你不能使用initrd,也可以通过Kconfig选项将引导配置文件嵌入内核中。在此情 +况下,你需要用以下选项重新编译内核:: + + CONFIG_BOOT_CONFIG_EMBED=y + CONFIG_BOOT_CONFIG_EMBED_FILE="/引导配置/文件/的/路径" + +``CONFIG_BOOT_CONFIG_EMBED_FILE`` 需要从源码树或对象树开始的引导配置文件的 +绝对/相对路径。内核会将其嵌入作为默认引导配置。 + +与将引导配置附加到initrd一样,你也需要在内核命令行上添加 ``bootconfig`` 告诉 +内核去启用内嵌的引导配置。 + +注意,即使你已经设置了此选项,仍可用附加到initrd的其他引导配置覆盖内嵌的引导 +配置。 + +通过引导配置传递内核参数 +======================== + +除了内核命令行,引导配置也可以用于传递内核参数。所有 ``kernel`` 关键字下的键 +值对都将直接传递给内核命令行。此外, ``init`` 下的键值对将通过命令行传递给 +init进程。参数按以下顺序与用户给定的内核命令行字符串相连,因此命令行参数可以 +覆盖引导配置参数(这取决于子系统如何处理参数,但通常前面的参数将被后面的参数 +覆盖):: + + [bootconfig params][cmdline params] -- [bootconfig init params][cmdline init params] + +如果引导配置文件给出的kernel/init参数是:: + + kernel { + root = 01234567-89ab-cdef-0123-456789abcd + } + init { + splash + } + +这将被复制到内核命令行字符串中,如下所示:: + + root="01234567-89ab-cdef-0123-456789abcd" -- splash + +如果用户给出的其他命令行是:: + + ro bootconfig -- quiet + +则最后的内核命令行如下:: + + root="01234567-89ab-cdef-0123-456789abcd" ro bootconfig -- splash quiet + + +配置文件的限制 +============== + +当前最大的配置大小是32KB,关键字总数(不是键值条目)必须少于1024个节点。 +注意:这不是条目数而是节点数,条目必须消耗超过2个节点(一个关键字和一个值)。 +所以从理论上讲最多512个键值对。如果关键字平均包含3个单词,则可有256个键值对。 +在大多数情况下,配置项的数量将少于100个条目,小于8KB,因此这应该足够了。如果 +节点数超过1024,解析器将返回错误,即使文件大小小于32KB。(请注意,此最大尺寸 +不包括填充的空字符。) +无论如何,因为 ``bootconfig`` 命令在附加启动配置到initrd映像时会验证它,用户 +可以在引导之前注意到它。 + + +引导配置API +=========== + +用户可以查询或遍历键值对,也可以查找(前缀)根关键字节点,并在查找该节点下的 +键值。 + +如果您有一个关键字字符串,则可以直接使用 xbc_find_value() 查询该键的值。如果 +你想知道引导配置里有哪些关键字,可以使用 xbc_for_each_key_value() 迭代键值对。 +请注意,您需要使用 xbc_array_for_each_value() 访问数组的值,例如:: + + vnode = NULL; + xbc_find_value("key.word", &vnode); + if (vnode && xbc_node_is_array(vnode)) + xbc_array_for_each_value(vnode, value) { + printk("%s ", value); + } + +如果您想查找具有前缀字符串的键,可以使用 xbc_find_node() 通过前缀字符串查找 +节点,然后用 xbc_node_for_each_key_value() 迭代前缀节点下的键。 + +但最典型的用法是获取前缀下的命名值或前缀下的命名数组,例如:: + + root = xbc_find_node("key.prefix"); + value = xbc_node_find_value(root, "option", &vnode); + ... + xbc_node_for_each_array_value(root, "array-option", value, anode) { + ... + } + +这将访问值“key.prefix.option”的值和“key.prefix.array-option”的数组。 + +锁是不需要的,因为在初始化之后配置只读。如果需要修改,必须复制所有数据和关键字。 + + +函数与结构体 +============ + +相关定义的kernel-doc参见: + + - include/linux/bootconfig.h + - lib/bootconfig.c diff --git a/Documentation/translations/zh_CN/admin-guide/bug-bisect.rst b/Documentation/translations/zh_CN/admin-guide/bug-bisect.rst new file mode 100644 index 000000000..662eb5b46 --- /dev/null +++ b/Documentation/translations/zh_CN/admin-guide/bug-bisect.rst @@ -0,0 +1,81 @@ +.. include:: ../disclaimer-zh_CN.rst + +:Original: :doc:`../../../admin-guide/bug-bisect` + +:译者: + + 吴想成 Wu XiangCheng <bobwxc@email.cn> + +二分(bisect)缺陷 ++++++++++++++++++++ + +(英文版)最后更新:2016年10月28日 + +引言 +===== + +始终尝试由来自kernel.org的源代码构建的最新内核。如果您没有信心这样做,请将 +错误报告给您的发行版供应商,而不是内核开发人员。 + +找到缺陷(bug)并不总是那么容易,不过仍然得去找。如果你找不到它,不要放弃。 +尽可能多的向相关维护人员报告您发现的信息。请参阅MAINTAINERS文件以了解您所 +关注的子系统的维护人员。 + +在提交错误报告之前,请阅读“Documentation/admin-guide/reporting-issues.rst”。 + +设备未出现(Devices not appearing) +==================================== + +这通常是由udev/systemd引起的。在将其归咎于内核之前先检查一下。 + +查找导致缺陷的补丁 +=================== + +使用 ``git`` 提供的工具可以很容易地找到缺陷,只要缺陷是可复现的。 + +操作步骤: + +- 从git源代码构建内核 +- 以此开始二分 [#f1]_:: + + $ git bisect start + +- 标记损坏的变更集:: + + $ git bisect bad [commit] + +- 标记正常工作的变更集:: + + $ git bisect good [commit] + +- 重新构建内核并测试 +- 使用以下任一与git bisect进行交互:: + + $ git bisect good + + 或:: + + $ git bisect bad + + 这取决于您测试的变更集上是否有缺陷 +- 在一些交互之后,git bisect将给出可能导致缺陷的变更集。 + +- 例如,如果您知道当前版本有问题,而4.8版本是正常的,则可以执行以下操作:: + + $ git bisect start + $ git bisect bad # Current version is bad + $ git bisect good v4.8 + + +.. [#f1] 您可以(可选地)在开始git bisect的时候提供good或bad参数 + ``git bisect start [BAD] [GOOD]`` + +如需进一步参考,请阅读: + +- ``git-bisect`` 的手册页 +- `Fighting regressions with git bisect(用git bisect解决回归) + <https://www.kernel.org/pub/software/scm/git/docs/git-bisect-lk2009.html>`_ +- `Fully automated bisecting with "git bisect run"(使用git bisect run + 来全自动二分) <https://lwn.net/Articles/317154>`_ +- `Using Git bisect to figure out when brokenness was introduced + (使用Git二分来找出何时引入了错误) <http://webchick.net/node/99>`_ diff --git a/Documentation/translations/zh_CN/admin-guide/bug-hunting.rst b/Documentation/translations/zh_CN/admin-guide/bug-hunting.rst new file mode 100644 index 000000000..decb9b26d --- /dev/null +++ b/Documentation/translations/zh_CN/admin-guide/bug-hunting.rst @@ -0,0 +1,340 @@ +.. include:: ../disclaimer-zh_CN.rst + +:Original: :doc:`../../../admin-guide/bug-hunting` + +:译者: + + 吴想成 Wu XiangCheng <bobwxc@email.cn> + +追踪缺陷 +========= + +内核错误报告通常附带如下堆栈转储:: + + ------------[ cut here ]------------ + WARNING: CPU: 1 PID: 28102 at kernel/module.c:1108 module_put+0x57/0x70 + Modules linked in: dvb_usb_gp8psk(-) dvb_usb dvb_core nvidia_drm(PO) nvidia_modeset(PO) snd_hda_codec_hdmi snd_hda_intel snd_hda_codec snd_hwdep snd_hda_core snd_pcm snd_timer snd soundcore nvidia(PO) [last unloaded: rc_core] + CPU: 1 PID: 28102 Comm: rmmod Tainted: P WC O 4.8.4-build.1 #1 + Hardware name: MSI MS-7309/MS-7309, BIOS V1.12 02/23/2009 + 00000000 c12ba080 00000000 00000000 c103ed6a c1616014 00000001 00006dc6 + c1615862 00000454 c109e8a7 c109e8a7 00000009 ffffffff 00000000 f13f6a10 + f5f5a600 c103ee33 00000009 00000000 00000000 c109e8a7 f80ca4d0 c109f617 + Call Trace: + [<c12ba080>] ? dump_stack+0x44/0x64 + [<c103ed6a>] ? __warn+0xfa/0x120 + [<c109e8a7>] ? module_put+0x57/0x70 + [<c109e8a7>] ? module_put+0x57/0x70 + [<c103ee33>] ? warn_slowpath_null+0x23/0x30 + [<c109e8a7>] ? module_put+0x57/0x70 + [<f80ca4d0>] ? gp8psk_fe_set_frontend+0x460/0x460 [dvb_usb_gp8psk] + [<c109f617>] ? symbol_put_addr+0x27/0x50 + [<f80bc9ca>] ? dvb_usb_adapter_frontend_exit+0x3a/0x70 [dvb_usb] + [<f80bb3bf>] ? dvb_usb_exit+0x2f/0xd0 [dvb_usb] + [<c13d03bc>] ? usb_disable_endpoint+0x7c/0xb0 + [<f80bb48a>] ? dvb_usb_device_exit+0x2a/0x50 [dvb_usb] + [<c13d2882>] ? usb_unbind_interface+0x62/0x250 + [<c136b514>] ? __pm_runtime_idle+0x44/0x70 + [<c13620d8>] ? __device_release_driver+0x78/0x120 + [<c1362907>] ? driver_detach+0x87/0x90 + [<c1361c48>] ? bus_remove_driver+0x38/0x90 + [<c13d1c18>] ? usb_deregister+0x58/0xb0 + [<c109fbb0>] ? SyS_delete_module+0x130/0x1f0 + [<c1055654>] ? task_work_run+0x64/0x80 + [<c1000fa5>] ? exit_to_usermode_loop+0x85/0x90 + [<c10013f0>] ? do_fast_syscall_32+0x80/0x130 + [<c1549f43>] ? sysenter_past_esp+0x40/0x6a + ---[ end trace 6ebc60ef3981792f ]--- + +这样的堆栈跟踪提供了足够的信息来识别内核源代码中发生错误的那一行。根据问题的 +严重性,它还可能包含 **“Oops”** 一词,比如:: + + BUG: unable to handle kernel NULL pointer dereference at (null) + IP: [<c06969d4>] iret_exc+0x7d0/0xa59 + *pdpt = 000000002258a001 *pde = 0000000000000000 + Oops: 0002 [#1] PREEMPT SMP + ... + +尽管有 **Oops** 或其他类型的堆栈跟踪,但通常需要找到出问题的行来识别和处理缺 +陷。在本章中,我们将参考“Oops”来了解需要分析的各种堆栈跟踪。 + +如果内核是用 ``CONFIG_DEBUG_INFO`` 编译的,那么可以使用文件: +`scripts/decode_stacktrace.sh` 。 + +链接的模块 +----------- + +受到污染或正在加载/卸载的模块用“(…)”标记,污染标志在 +`Documentation/admin-guide/tainted-kernels.rst` 文件中进行了描述,“正在被加 +载”用“+”标注,“正在被卸载”用“-”标注。 + + +Oops消息在哪? +--------------- + +通常,Oops文本由klogd从内核缓冲区读取,然后交给 ``syslogd`` ,后者将其写入 +syslog文件,通常是 ``/var/log/messages`` (取决于 ``/etc/syslog.conf`` )。 +在使用systemd的系统上,它也可以由 ``journald`` 守护进程存储,并通过运行 +``journalctl`` 命令进行访问。 + +有时 ``klogd`` 会挂掉,这种情况下您可以运行 ``dmesg > file`` 从内核缓冲区 +读取数据并保存它。或者您可以 ``cat /proc/kmsg > file`` ,但是您必须适时 +中断以停止传输,因为 ``kmsg`` 是一个“永无止境的文件”。 + +如果机器严重崩溃,无法输入命令或磁盘不可用,那还有三个选项: + +(1) 手动复制屏幕上的文本,并在机器重新启动后输入。很难受,但这是突然崩溃下 + 唯一的选择。或者你可以用数码相机拍下屏幕——虽然不那么好,但总比什么都没 + 有好。如果消息滚动超出控制台顶部,使用更高分辨率(例如 ``vga=791`` ) + 引导启动将允许您阅读更多文本。(警告:这需要 ``vesafb`` ,因此对“早期” + 的Oppses没有帮助) + +(2) 从串口终端启动(参见 + :ref:`Documentation/admin-guide/serial-console.rst <serial_console>` ), + 在另一台机器上运行调制解调器然后用你喜欢的通信程序捕获输出。 + Minicom运行良好。 + +(3) 使用Kdump(参阅 Documentation/admin-guide/kdump/kdump.rst ),使用 + Documentation/admin-guide/kdump/gdbmacros.txt 中的dmesg gdbmacro从旧内存 + 中提取内核环形缓冲区。 + +找到缺陷位置 +------------- + +如果你能指出缺陷在内核源代码中的位置,则报告缺陷的效果会非常好。这有两种方法。 +通常来说使用 ``gdb`` 会比较容易,不过内核需要用调试信息来预编译。 + +gdb +^^^^ + +GNU 调试器(GNU debugger, ``gdb`` )是从 ``vmlinux`` 文件中找出OOPS的确切 +文件和行号的最佳方法。 + +在使用 ``CONFIG_DEBUG_INFO`` 编译的内核上使用gdb效果最好。可通过运行以下命令 +进行设置:: + + $ ./scripts/config -d COMPILE_TEST -e DEBUG_KERNEL -e DEBUG_INFO + +在用 ``CONFIG_DEBUG_INFO`` 编译的内核上,你可以直接从OOPS复制EIP值:: + + EIP: 0060:[<c021e50e>] Not tainted VLI + +并使用GDB来将其翻译成可读形式:: + + $ gdb vmlinux + (gdb) l *0xc021e50e + +如果没有启用 ``CONFIG_DEBUG_INFO`` ,则使用OOPS的函数偏移:: + + EIP is at vt_ioctl+0xda8/0x1482 + +并在启用 ``CONFIG_DEBUG_INFO`` 的情况下重新编译内核:: + + $ ./scripts/config -d COMPILE_TEST -e DEBUG_KERNEL -e DEBUG_INFO + $ make vmlinux + $ gdb vmlinux + (gdb) l *vt_ioctl+0xda8 + 0x1888 is in vt_ioctl (drivers/tty/vt/vt_ioctl.c:293). + 288 { + 289 struct vc_data *vc = NULL; + 290 int ret = 0; + 291 + 292 console_lock(); + 293 if (VT_BUSY(vc_num)) + 294 ret = -EBUSY; + 295 else if (vc_num) + 296 vc = vc_deallocate(vc_num); + 297 console_unlock(); + +或者若您想要更详细的显示:: + + (gdb) p vt_ioctl + $1 = {int (struct tty_struct *, unsigned int, unsigned long)} 0xae0 <vt_ioctl> + (gdb) l *0xae0+0xda8 + +您也可以使用对象文件作为替代:: + + $ make drivers/tty/ + $ gdb drivers/tty/vt/vt_ioctl.o + (gdb) l *vt_ioctl+0xda8 + +如果你有调用跟踪,类似:: + + Call Trace: + [<ffffffff8802c8e9>] :jbd:log_wait_commit+0xa3/0xf5 + [<ffffffff810482d9>] autoremove_wake_function+0x0/0x2e + [<ffffffff8802770b>] :jbd:journal_stop+0x1be/0x1ee + ... + +这表明问题可能在 :jbd: 模块中。您可以在gdb中加载该模块并列出相关代码:: + + $ gdb fs/jbd/jbd.ko + (gdb) l *log_wait_commit+0xa3 + +.. note:: + + 您还可以对堆栈跟踪处的任何函数调用执行相同的操作,例如:: + + [<f80bc9ca>] ? dvb_usb_adapter_frontend_exit+0x3a/0x70 [dvb_usb] + + 上述调用发生的位置可以通过以下方式看到:: + + $ gdb drivers/media/usb/dvb-usb/dvb-usb.o + (gdb) l *dvb_usb_adapter_frontend_exit+0x3a + +objdump +^^^^^^^^ + +要调试内核,请使用objdump并从崩溃输出中查找十六进制偏移,以找到有效的代码/汇 +编行。如果没有调试符号,您将看到所示例程的汇编程序代码,但是如果内核有调试 +符号,C代码也将可见(调试符号可以在内核配置菜单的hacking项中启用)。例如:: + + $ objdump -r -S -l --disassemble net/dccp/ipv4.o + +.. note:: + + 您需要处于内核树的顶层以便此获得您的C文件。 + +如果您无法访问源代码,仍然可以使用以下方法调试一些崩溃转储(如Dave Miller的 +示例崩溃转储输出所示):: + + EIP is at +0x14/0x4c0 + ... + Code: 44 24 04 e8 6f 05 00 00 e9 e8 fe ff ff 8d 76 00 8d bc 27 00 00 + 00 00 55 57 56 53 81 ec bc 00 00 00 8b ac 24 d0 00 00 00 8b 5d 08 + <8b> 83 3c 01 00 00 89 44 24 14 8b 45 28 85 c0 89 44 24 18 0f 85 + + Put the bytes into a "foo.s" file like this: + + .text + .globl foo + foo: + .byte .... /* bytes from Code: part of OOPS dump */ + + Compile it with "gcc -c -o foo.o foo.s" then look at the output of + "objdump --disassemble foo.o". + + Output: + + ip_queue_xmit: + push %ebp + push %edi + push %esi + push %ebx + sub $0xbc, %esp + mov 0xd0(%esp), %ebp ! %ebp = arg0 (skb) + mov 0x8(%ebp), %ebx ! %ebx = skb->sk + mov 0x13c(%ebx), %eax ! %eax = inet_sk(sk)->opt + +`scripts/decodecode` 文件可以用来自动完成大部分工作,这取决于正在调试的CPU +体系结构。 + +报告缺陷 +--------- + +一旦你通过定位缺陷找到了其发生的地方,你可以尝试自己修复它或者向上游报告它。 + +为了向上游报告,您应该找出用于开发受影响代码的邮件列表。这可以使用 ``get_maintainer.pl`` 。 + + +例如,您在gspca的sonixj.c文件中发现一个缺陷,则可以通过以下方法找到它的维护者:: + + $ ./scripts/get_maintainer.pl -f drivers/media/usb/gspca/sonixj.c + Hans Verkuil <hverkuil@xs4all.nl> (odd fixer:GSPCA USB WEBCAM DRIVER,commit_signer:1/1=100%) + Mauro Carvalho Chehab <mchehab@kernel.org> (maintainer:MEDIA INPUT INFRASTRUCTURE (V4L/DVB),commit_signer:1/1=100%) + Tejun Heo <tj@kernel.org> (commit_signer:1/1=100%) + Bhaktipriya Shridhar <bhaktipriya96@gmail.com> (commit_signer:1/1=100%,authored:1/1=100%,added_lines:4/4=100%,removed_lines:9/9=100%) + linux-media@vger.kernel.org (open list:GSPCA USB WEBCAM DRIVER) + linux-kernel@vger.kernel.org (open list) + +请注意它将指出: + +- 最后接触源代码的开发人员(如果这是在git树中完成的)。在上面的例子中是Tejun + 和Bhaktipriya(在这个特定的案例中,没有人真正参与这个文件的开发); +- 驱动维护人员(Hans Verkuil); +- 子系统维护人员(Mauro Carvalho Chehab); +- 驱动程序和/或子系统邮件列表(linux-media@vger.kernel.org); +- Linux内核邮件列表(linux-kernel@vger.kernel.org)。 + +通常,修复缺陷的最快方法是将它报告给用于开发相关代码的邮件列表(linux-media +ML),抄送驱动程序维护者(Hans)。 + +如果你完全不知道该把报告寄给谁,且 ``get_maintainer.pl`` 也没有提供任何有用 +的信息,请发送到linux-kernel@vger.kernel.org。 + +感谢您的帮助,这使Linux尽可能稳定:-) + +修复缺陷 +--------- + +如果你懂得编程,你不仅可以通过报告错误来帮助我们,还可以提供一个解决方案。 +毕竟,开源就是分享你的工作,你不想因为你的天才而被认可吗? + +如果你决定这样做,请在制定解决方案后将其提交到上游。 + +请务必阅读 +:ref:`Documentation/process/submitting-patches.rst <submittingpatches>` , +以帮助您的代码被接受。 + + +--------------------------------------------------------------------------- + +用 ``klogd`` 进行Oops跟踪的注意事项 +------------------------------------ + +为了帮助Linus和其他内核开发人员, ``klogd`` 对保护故障的处理提供了大量支持。 +为了完整支持地址解析,至少应该使用 ``sysklogd`` 包的1.3-pl3版本。 + +当发生保护故障时, ``klogd`` 守护进程会自动将内核日志消息中的重要地址转换为 +它们的等效符号。然后通过 ``klogd`` 使用的任何报告机制来转发这个已翻译的内核 +消息。保护错误消息可以直接从消息文件中剪切出来并转发给内核开发人员。 + +``klogd`` 执行两种类型的地址解析,静态翻译和动态翻译。静态翻译使用System.map +文件。为了进行静态转换, ``klogd`` 守护进程必须能够在守护进程初始化时找到系 +统映射文件。有关 ``klogd`` 如何搜索映射文件的信息,请参见klogd手册页。 + +当使用内核可加载模块时,动态地址转换非常重要。由于内核模块的内存是从内核的 +动态内存池中分配的,因此无论是模块的开头还是模块中的函数和符号都没有固定的 +位置。 + +内核支持系统调用,允许程序确定加载哪些模块及其在内存中的位置。klogd守护进程 +使用这些系统调用构建了一个符号表,可用于调试可加载内核模块中发生的保护错误。 + +klogd至少会提供产生保护故障的模块的名称。如果可加载模块的开发人员选择从模块 +导出符号信息,则可能会有其他可用的符号信息。 + +由于内核模块环境可以是动态的,因此当模块环境发生变化时,必须有一种通知 +``klogd`` 守护进程的机制。有一些可用的命令行选项允许klogd向当前正在执行的守 +护进程发出信号示意应该刷新符号信息。有关更多信息,请参阅 ``klogd`` 手册页。 + +sysklogd发行版附带了一个补丁,它修改了 ``modules-2.0.0`` 包,以便在加载或 +卸载模块时自动向klogd发送信号。应用此补丁基本上可无缝支持调试内核可加载模块 +发生的保护故障。 + +以下是 ``klogd`` 处理的可加载模块中的保护故障示例:: + + Aug 29 09:51:01 blizard kernel: Unable to handle kernel paging request at virtual address f15e97cc + Aug 29 09:51:01 blizard kernel: current->tss.cr3 = 0062d000, %cr3 = 0062d000 + Aug 29 09:51:01 blizard kernel: *pde = 00000000 + Aug 29 09:51:01 blizard kernel: Oops: 0002 + Aug 29 09:51:01 blizard kernel: CPU: 0 + Aug 29 09:51:01 blizard kernel: EIP: 0010:[oops:_oops+16/3868] + Aug 29 09:51:01 blizard kernel: EFLAGS: 00010212 + Aug 29 09:51:01 blizard kernel: eax: 315e97cc ebx: 003a6f80 ecx: 001be77b edx: 00237c0c + Aug 29 09:51:01 blizard kernel: esi: 00000000 edi: bffffdb3 ebp: 00589f90 esp: 00589f8c + Aug 29 09:51:01 blizard kernel: ds: 0018 es: 0018 fs: 002b gs: 002b ss: 0018 + Aug 29 09:51:01 blizard kernel: Process oops_test (pid: 3374, process nr: 21, stackpage=00589000) + Aug 29 09:51:01 blizard kernel: Stack: 315e97cc 00589f98 0100b0b4 bffffed4 0012e38e 00240c64 003a6f80 00000001 + Aug 29 09:51:01 blizard kernel: 00000000 00237810 bfffff00 0010a7fa 00000003 00000001 00000000 bfffff00 + Aug 29 09:51:01 blizard kernel: bffffdb3 bffffed4 ffffffda 0000002b 0007002b 0000002b 0000002b 00000036 + Aug 29 09:51:01 blizard kernel: Call Trace: [oops:_oops_ioctl+48/80] [_sys_ioctl+254/272] [_system_call+82/128] + Aug 29 09:51:01 blizard kernel: Code: c7 00 05 00 00 00 eb 08 90 90 90 90 90 90 90 90 89 ec 5d c3 + +--------------------------------------------------------------------------- + +:: + + Dr. G.W. Wettstein Oncology Research Div. Computing Facility + Roger Maris Cancer Center INTERNET: greg@wind.rmcc.com + 820 4th St. N. + Fargo, ND 58122 + Phone: 701-234-7556 diff --git a/Documentation/translations/zh_CN/admin-guide/clearing-warn-once.rst b/Documentation/translations/zh_CN/admin-guide/clearing-warn-once.rst new file mode 100644 index 000000000..659264d5f --- /dev/null +++ b/Documentation/translations/zh_CN/admin-guide/clearing-warn-once.rst @@ -0,0 +1,9 @@ +清除 WARN_ONCE +-------------- + +WARN_ONCE / WARN_ON_ONCE / printk_once 仅仅打印一次消息. + +echo 1 > /sys/kernel/debug/clear_warn_once + +可以清除这种状态并且再次允许打印一次告警信息,这对于运行测试集后重现问题 +很有用。 diff --git a/Documentation/translations/zh_CN/admin-guide/cpu-load.rst b/Documentation/translations/zh_CN/admin-guide/cpu-load.rst new file mode 100644 index 000000000..a73400a05 --- /dev/null +++ b/Documentation/translations/zh_CN/admin-guide/cpu-load.rst @@ -0,0 +1,105 @@ +======== +CPU 负载 +======== + +Linux通过``/proc/stat``和``/proc/uptime``导出各种信息,用户空间工具 +如top(1)使用这些信息计算系统花费在某个特定状态的平均时间。 +例如: + + $ iostat + Linux 2.6.18.3-exp (linmac) 02/20/2007 + + avg-cpu: %user %nice %system %iowait %steal %idle + 10.01 0.00 2.92 5.44 0.00 81.63 + + ... + +这里系统认为在默认采样周期內有10.01%的时间工作在用户空间,2.92%的时 +间用在系统空间,总体上有81.63%的时间是空闲的。 + +大多数情况下``/proc/stat``的信息几乎真实反映了系统信息,然而,由于内 +核采集这些数据的方式/时间的特点,有时这些信息根本不可靠。 + +那么这些信息是如何被搜集的呢?每当时间中断触发时,内核查看此刻运行的 +进程类型,并增加与此类型/状态进程对应的计数器的值。这种方法的问题是 +在两次时间中断之间系统(进程)能够在多种状态之间切换多次,而计数器只 +增加最后一种状态下的计数。 + +举例 +--- + +假设系统有一个进程以如下方式周期性地占用cpu:: + + 两个时钟中断之间的时间线 + |-----------------------| + ^ ^ + |_ 开始运行 | + |_ 开始睡眠 + (很快会被唤醒) + +在上面的情况下,根据``/proc/stat``的信息(由于当系统处于空闲状态时, +时间中断经常会发生)系统的负载将会是0 + +大家能够想象内核的这种行为会发生在许多情况下,这将导致``/proc/stat`` +中存在相当古怪的信息:: + + /* gcc -o hog smallhog.c */ + #include <time.h> + #include <limits.h> + #include <signal.h> + #include <sys/time.h> + #define HIST 10 + + static volatile sig_atomic_t stop; + + static void sighandler (int signr) + { + (void) signr; + stop = 1; + } + static unsigned long hog (unsigned long niters) + { + stop = 0; + while (!stop && --niters); + return niters; + } + int main (void) + { + int i; + struct itimerval it = { .it_interval = { .tv_sec = 0, .tv_usec = 1 }, + .it_value = { .tv_sec = 0, .tv_usec = 1 } }; + sigset_t set; + unsigned long v[HIST]; + double tmp = 0.0; + unsigned long n; + signal (SIGALRM, &sighandler); + setitimer (ITIMER_REAL, &it, NULL); + + hog (ULONG_MAX); + for (i = 0; i < HIST; ++i) v[i] = ULONG_MAX - hog (ULONG_MAX); + for (i = 0; i < HIST; ++i) tmp += v[i]; + tmp /= HIST; + n = tmp - (tmp / 3.0); + + sigemptyset (&set); + sigaddset (&set, SIGALRM); + + for (;;) { + hog (n); + sigwait (&set, &i); + } + return 0; + } + + +参考 +--- + +- https://lore.kernel.org/r/loom.20070212T063225-663@post.gmane.org +- Documentation/filesystems/proc.rst (1.8) + + +谢谢 +--- + +Con Kolivas, Pavel Machek diff --git a/Documentation/translations/zh_CN/admin-guide/cputopology.rst b/Documentation/translations/zh_CN/admin-guide/cputopology.rst new file mode 100644 index 000000000..544d42f8f --- /dev/null +++ b/Documentation/translations/zh_CN/admin-guide/cputopology.rst @@ -0,0 +1,96 @@ +.. SPDX-License-Identifier: GPL-2.0 +.. include:: ../disclaimer-zh_CN.rst + +:Original: Documentation/admin-guide/cputopology.rst + +:翻译: + + 唐艺舟 Tang Yizhou <tangyeechou@gmail.com> + +========================== +如何通过sysfs将CPU拓扑导出 +========================== + +CPU拓扑信息通过sysfs导出。显示的项(属性)和某些架构的/proc/cpuinfo输出相似。它们位于 +/sys/devices/system/cpu/cpuX/topology/。请阅读ABI文件: +Documentation/ABI/stable/sysfs-devices-system-cpu。 + +drivers/base/topology.c是体系结构中性的,它导出了这些属性。然而,die、cluster、book、 +draw这些层次结构相关的文件仅在体系结构提供了下文描述的宏的条件下被创建。 + +对于支持这个特性的体系结构,它必须在include/asm-XXX/topology.h中定义这些宏中的一部分:: + + #define topology_physical_package_id(cpu) + #define topology_die_id(cpu) + #define topology_cluster_id(cpu) + #define topology_core_id(cpu) + #define topology_book_id(cpu) + #define topology_drawer_id(cpu) + #define topology_sibling_cpumask(cpu) + #define topology_core_cpumask(cpu) + #define topology_cluster_cpumask(cpu) + #define topology_die_cpumask(cpu) + #define topology_book_cpumask(cpu) + #define topology_drawer_cpumask(cpu) + +``**_id macros`` 的类型是int。 +``**_cpumask macros`` 的类型是 ``(const) struct cpumask *`` 。后者和恰当的 +``**_siblings`` sysfs属性对应(除了topology_sibling_cpumask(),它和thread_siblings +对应)。 + +为了在所有体系结构上保持一致,include/linux/topology.h提供了上述所有宏的默认定义,以防 +它们未在include/asm-XXX/topology.h中定义: + +1) topology_physical_package_id: -1 +2) topology_die_id: -1 +3) topology_cluster_id: -1 +4) topology_core_id: 0 +5) topology_book_id: -1 +6) topology_drawer_id: -1 +7) topology_sibling_cpumask: 仅入参CPU +8) topology_core_cpumask: 仅入参CPU +9) topology_cluster_cpumask: 仅入参CPU +10) topology_die_cpumask: 仅入参CPU +11) topology_book_cpumask: 仅入参CPU +12) topology_drawer_cpumask: 仅入参CPU + +此外,CPU拓扑信息由/sys/devices/system/cpu提供,包含下述文件。输出对应的内部数据源放在 +方括号("[]")中。 + + =========== ================================================================== + kernel_max: 内核配置允许的最大CPU下标值。[NR_CPUS-1] + + offline: 由于热插拔移除或者超过内核允许的CPU上限(上文描述的kernel_max) + 导致未上线的CPU。[~cpu_online_mask + cpus >= NR_CPUS] + + online: 在线的CPU,可供调度使用。[cpu_online_mask] + + possible: 已被分配资源的CPU,如果它们CPU实际存在,可以上线。 + [cpu_possible_mask] + + present: 被系统识别实际存在的CPU。[cpu_present_mask] + =========== ================================================================== + +上述输出的格式和cpulist_parse()兼容[参见 <linux/cpumask.h>]。下面给些例子。 + +在本例中,系统中有64个CPU,但是CPU 32-63超过了kernel_max值,因为NR_CPUS配置项是32, +取值范围被限制为0..31。此外注意CPU2和4-31未上线,但是可以上线,因为它们同时存在于 +present和possible:: + + kernel_max: 31 + offline: 2,4-31,32-63 + online: 0-1,3 + possible: 0-31 + present: 0-31 + +在本例中,NR_CPUS配置项是128,但内核启动时设置possible_cpus=144。系统中有4个CPU, +CPU2被手动设置下线(也是唯一一个可以上线的CPU):: + + kernel_max: 127 + offline: 2,4-127,128-143 + online: 0-1,3 + possible: 0-127 + present: 0-3 + +阅读Documentation/core-api/cpu_hotplug.rst可了解开机参数possible_cpus=NUM,同时还 +可以了解各种cpumask的信息。 diff --git a/Documentation/translations/zh_CN/admin-guide/index.rst b/Documentation/translations/zh_CN/admin-guide/index.rst new file mode 100644 index 000000000..ac2960da3 --- /dev/null +++ b/Documentation/translations/zh_CN/admin-guide/index.rst @@ -0,0 +1,133 @@ +.. include:: ../disclaimer-zh_CN.rst + +:Original: :doc:`../../../admin-guide/index` +:Translator: Alex Shi <alex.shi@linux.alibaba.com> + + +Linux 内核用户和管理员指南 +========================== + +下面是一组随时间添加到内核中的面向用户的文档的集合。到目前为止,还没有一个 +整体的顺序或组织 - 这些材料不是一个单一的,连贯的文件!幸运的话,情况会随着 +时间的推移而迅速改善。 + +这个初始部分包含总体信息,包括描述内核的README, 关于内核参数的文档等。 + +.. toctree:: + :maxdepth: 1 + + README + +Todolist: + +* kernel-parameters +* devices +* sysctl/index + +本节介绍CPU漏洞及其缓解措施。 + +Todolist: + +* hw-vuln/index + +下面的一组文档,针对的是试图跟踪问题和bug的用户。 + +.. toctree:: + :maxdepth: 1 + + reporting-issues + reporting-regressions + security-bugs + bug-hunting + bug-bisect + tainted-kernels + init + +Todolist: + +* ramoops +* dynamic-debug-howto +* kdump/index +* perf/index + +这是应用程序开发人员感兴趣的章节的开始。可以在这里找到涵盖内核ABI各个 +方面的文档。 + +Todolist: + +* sysfs-rules + +本手册的其余部分包括各种指南,介绍如何根据您的喜好配置内核的特定行为。 + + +.. toctree:: + :maxdepth: 1 + + bootconfig + clearing-warn-once + cpu-load + cputopology + lockup-watchdogs + unicode + sysrq + mm/index + +Todolist: + +* acpi/index +* aoe/index +* auxdisplay/index +* bcache +* binderfs +* binfmt-misc +* blockdev/index +* braille-console +* btmrvl +* cgroup-v1/index +* cgroup-v2 +* cifs/index +* dell_rbu +* device-mapper/index +* edid +* efi-stub +* ext4 +* nfs/index +* gpio/index +* highuid +* hw_random +* initrd +* iostats +* java +* jfs +* kernel-per-CPU-kthreads +* laptops/index +* lcd-panel-cgram +* ldm +* LSM/index +* md +* media/index +* module-signing +* mono +* namespaces/index +* numastat +* parport +* perf-security +* pm/index +* pnp +* rapidio +* ras +* rtc +* serial-console +* svga +* thunderbolt +* ufs +* vga-softcursor +* video-output +* xfs + +.. only:: subproject and html + + Indices + ======= + + * :ref:`genindex` diff --git a/Documentation/translations/zh_CN/admin-guide/init.rst b/Documentation/translations/zh_CN/admin-guide/init.rst new file mode 100644 index 000000000..fbaf6d97f --- /dev/null +++ b/Documentation/translations/zh_CN/admin-guide/init.rst @@ -0,0 +1,54 @@ +.. include:: ../disclaimer-zh_CN.rst + +:Original: :doc:`../../../admin-guide/init` + +:译者: + + 吴想成 Wu XiangCheng <bobwxc@email.cn> + +解释“No working init found.”启动挂起消息 +========================================= + +:作者: + + Andreas Mohr <andi at lisas period de> + + Cristian Souza <cristianmsbr at gmail period com> + +本文档提供了加载初始化二进制(init binary)失败的一些高层级原因(大致按执行 +顺序列出)。 + +1) **无法挂载根文件系统Unable to mount root FS** :请设置“debug”内核参数(在 + 引导加载程序bootloader配置文件或CONFIG_CMDLINE)以获取更详细的内核消息。 + +2) **初始化二进制不存在于根文件系统上init binary doesn't exist on rootfs** : + 确保您的根文件系统类型正确(并且 ``root=`` 内核参数指向正确的分区);拥有 + 所需的驱动程序,例如SCSI或USB等存储硬件;文件系统(ext3、jffs2等)是内建的 + (或者作为模块由initrd预加载)。 + +3) **控制台设备损坏Broken console device** : ``console= setup`` 中可能存在 + 冲突 --> 初始控制台不可用(initial console unavailable)。例如,由于串行 + IRQ问题(如缺少基于中断的配置)导致的某些串行控制台不可靠。尝试使用不同的 + ``console= device`` 或像 ``netconsole=`` 。 + +4) **二进制存在但依赖项不可用Binary exists but dependencies not available** : + 例如初始化二进制的必需库依赖项,像 ``/lib/ld-linux.so.2`` 丢失或损坏。使用 + ``readelf -d <INIT>|grep NEEDED`` 找出需要哪些库。 + +5) **无法加载二进制Binary cannot be loaded** :请确保二进制的体系结构与您的 + 硬件匹配。例如i386不匹配x86_64,或者尝试在ARM硬件上加载x86。如果您尝试在 + 此处加载非二进制文件(shell脚本?),您应该确保脚本在其工作头(shebang + header)行 ``#!/...`` 中指定能正常工作的解释器(包括其库依赖项)。在处理 + 脚本之前,最好先测试一个简单的非脚本二进制文件,比如 ``/bin/sh`` ,并确认 + 它能成功执行。要了解更多信息,请将代码添加到 ``init/main.c`` 以显示 + kernel_execve()的返回值。 + +当您发现新的失败原因时,请扩展本解释(毕竟加载初始化二进制是一个 **关键** 且 +艰难的过渡步骤,需要尽可能无痛地进行),然后向LKML提交一个补丁。 + +待办事项: + +- 通过一个可以存储 ``kernel_execve()`` 结果值的结构体数组实现各种 + ``run_init_process()`` 调用,并在失败时通过迭代 **所有** 结果来记录一切 + (非常重要的可用性修复)。 +- 试着使实现本身在一般情况下更有帮助,例如在受影响的地方提供额外的错误消息。 diff --git a/Documentation/translations/zh_CN/admin-guide/lockup-watchdogs.rst b/Documentation/translations/zh_CN/admin-guide/lockup-watchdogs.rst new file mode 100644 index 000000000..55ed3f4af --- /dev/null +++ b/Documentation/translations/zh_CN/admin-guide/lockup-watchdogs.rst @@ -0,0 +1,66 @@ +.. include:: ../disclaimer-zh_CN.rst + +:Original: Documentation/admin-guide/lockup-watchdogs.rst +:Translator: Hailong Liu <liu.hailong6@zte.com.cn> + +.. _cn_lockup-watchdogs: + + +================================================= +Softlockup与hardlockup检测机制(又名:nmi_watchdog) +================================================= + +Linux中内核实现了一种用以检测系统发生softlockup和hardlockup的看门狗机制。 + +Softlockup是一种会引发系统在内核态中一直循环超过20秒(详见下面“实现”小节)导致 +其他任务没有机会得到运行的BUG。一旦检测到'softlockup'发生,默认情况下系统会打 +印当前堆栈跟踪信息并进入锁定状态。也可配置使其在检测到'softlockup'后进入panic +状态;通过sysctl命令设置“kernel.softlockup_panic”、使用内核启动参数 +“softlockup_panic”(详见Documentation/admin-guide/kernel-parameters.rst)以及使 +能内核编译选项“BOOTPARAM_SOFTLOCKUP_PANIC”都可实现这种配置。 + +而'hardlockup'是一种会引发系统在内核态一直循环超过10秒钟(详见"实现"小节)导致其 +他中断没有机会运行的缺陷。与'softlockup'情况类似,除了使用sysctl命令设置 +'hardlockup_panic'、使能内核选项“BOOTPARAM_HARDLOCKUP_PANIC”以及使用内核参数 +"nmi_watchdog"(详见:”Documentation/admin-guide/kernel-parameters.rst“)外,一旦检 +测到'hardlockup'默认情况下系统打印当前堆栈跟踪信息,然后进入锁定状态。 + +这个panic选项也可以与panic_timeout结合使用(这个panic_timeout是通过稍具迷惑性的 +sysctl命令"kernel.panic"来设置),使系统在panic指定时间后自动重启。 + +实现 +==== + +Softlockup和hardlockup分别建立在hrtimer(高精度定时器)和perf两个子系统上而实现。 +这也就意味着理论上任何架构只要实现了这两个子系统就支持这两种检测机制。 + +Hrtimer用于周期性产生中断并唤醒watchdog线程;NMI perf事件则以”watchdog_thresh“ +(编译时默认初始化为10秒,也可通过”watchdog_thresh“这个sysctl接口来进行配置修改) +为间隔周期产生以检测 hardlockups。如果一个CPU在这个时间段内没有检测到hrtimer中 +断发生,'hardlockup 检测器'(即NMI perf事件处理函数)将会视系统配置而选择产生内核 +警告或者直接panic。 + +而watchdog线程本质上是一个高优先级内核线程,每调度一次就对时间戳进行一次更新。 +如果时间戳在2*watchdog_thresh(这个是softlockup的触发门限)这段时间都未更新,那么 +"softlocup 检测器"(内部hrtimer定时器回调函数)会将相关的调试信息打印到系统日志中, +然后如果系统配置了进入panic流程则进入panic,否则内核继续执行。 + +Hrtimer定时器的周期是2*watchdog_thresh/5,也就是说在hardlockup被触发前hrtimer有 +2~3次机会产生时钟中断。 + +如上所述,内核相当于为系统管理员提供了一个可调节hrtimer定时器和perf事件周期长度 +的调节旋钮。如何通过这个旋钮为特定使用场景配置一个合理的周期值要对lockups检测的 +响应速度和lockups检测开销这二者之间进行权衡。 + +默认情况下所有在线cpu上都会运行一个watchdog线程。不过在内核配置了”NO_HZ_FULL“的 +情况下watchdog线程默认只会运行在管家(housekeeping)cpu上,而”nohz_full“启动参数指 +定的cpu上则不会有watchdog线程运行。试想,如果我们允许watchdog线程在”nohz_full“指 +定的cpu上运行,这些cpu上必须得运行时钟定时器来激发watchdog线程调度;这样一来就会 +使”nohz_full“保护用户程序免受内核干扰的功能失效。当然,副作用就是”nohz_full“指定 +的cpu即使在内核产生了lockup问题我们也无法检测到。不过,至少我们可以允许watchdog +线程在管家(non-tickless)核上继续运行以便我们能继续正常的监测这些cpus上的lockups +事件。 + +不论哪种情况都可以通过sysctl命令kernel.watchdog_cpumask来对没有运行watchdog线程 +的cpu集合进行调节。对于nohz_full而言,如果nohz_full cpu上有异常挂住的情况,通过 +这种方式打开这些cpu上的watchdog进行调试可能会有所作用。 diff --git a/Documentation/translations/zh_CN/admin-guide/mm/damon/index.rst b/Documentation/translations/zh_CN/admin-guide/mm/damon/index.rst new file mode 100644 index 000000000..30c69e1f4 --- /dev/null +++ b/Documentation/translations/zh_CN/admin-guide/mm/damon/index.rst @@ -0,0 +1,28 @@ +.. SPDX-License-Identifier: GPL-2.0 +.. include:: ../../../disclaimer-zh_CN.rst + +:Original: Documentation/admin-guide/mm/damon/index.rst + +:翻译: + + 司延腾 Yanteng Si <siyanteng@loongson.cn> + +:校译: + +============ +监测数据访问 +============ + +:doc:`DAMON </mm/damon/index>` 允许轻量级的数据访问监测。使用DAMON, +用户可以分析他们系统的内存访问模式,并优化它们。 + +.. toctree:: + :maxdepth: 2 + + start + usage + reclaim + + + + diff --git a/Documentation/translations/zh_CN/admin-guide/mm/damon/reclaim.rst b/Documentation/translations/zh_CN/admin-guide/mm/damon/reclaim.rst new file mode 100644 index 000000000..c976f3e33 --- /dev/null +++ b/Documentation/translations/zh_CN/admin-guide/mm/damon/reclaim.rst @@ -0,0 +1,232 @@ +.. SPDX-License-Identifier: GPL-2.0 +.. include:: ../../../disclaimer-zh_CN.rst + +:Original: Documentation/admin-guide/mm/damon/reclaim.rst + +:翻译: + + 司延腾 Yanteng Si <siyanteng@loongson.cn> + +:校译: + +=============== +基于DAMON的回收 +=============== + +基于DAMON的回收(DAMON_RECLAIM)是一个静态的内核模块,旨在用于轻度内存压力下的主动和轻 +量级的回收。它的目的不是取代基于LRU列表的页面回收,而是有选择地用于不同程度的内存压力和要 +求。 + +哪些地方需要主动回收? +====================== + +在一般的内存超量使用(over-committed systems,虚拟化相关术语)的系统上,主动回收冷页 +有助于节省内存和减少延迟高峰,这些延迟是由直接回收进程或kswapd的CPU消耗引起的,同时只产 +生最小的性能下降 [1]_ [2]_ 。 + +基于空闲页报告 [3]_ 的内存过度承诺的虚拟化系统就是很好的例子。在这样的系统中,客户机 +向主机报告他们的空闲内存,而主机则将报告的内存重新分配给其他客户。因此,系统的内存得到了充 +分的利用。然而,客户可能不那么节省内存,主要是因为一些内核子系统和用户空间应用程序被设计为 +使用尽可能多的内存。然后,客户机可能只向主机报告少量的内存是空闲的,导致系统的内存利用率下降。 +在客户中运行主动回收可以缓解这个问题。 + +它是如何工作的? +================ + +DAMON_RECLAIM找到在特定时间内没有被访问的内存区域并分页。为了避免它在分页操作中消耗过多 +的CPU,可以配置一个速度限制。在这个速度限制下,它首先分页出那些没有被访问过的内存区域。系 +统管理员还可以配置在什么情况下这个方案应该自动激活和停用三个内存压力水位。 + +接口: 模块参数 +============== + +要使用这个功能,你首先要确保你的系统运行在一个以 ``CONFIG_DAMON_RECLAIM=y`` 构建的内 +核上。 + +为了让系统管理员启用或禁用它,并为给定的系统进行调整,DAMON_RECLAIM利用了模块参数。也就 +是说,你可以把 ``damon_reclaim.<parameter>=<value>`` 放在内核启动命令行上,或者把 +适当的值写入 ``/sys/modules/damon_reclaim/parameters/<parameter>`` 文件。 + +注意,除 ``启用`` 外的参数值只在DAMON_RECLAIM启动时应用。因此,如果你想在运行时应用新 +的参数值,而DAMON_RECLAIM已经被启用,你应该通过 ``启用`` 的参数文件禁用和重新启用它。 +在重新启用之前,应将新的参数值写入适当的参数值中。 + +下面是每个参数的描述。 + +enabled +------- + +启用或禁用DAMON_RECLAIM。 + +你可以通过把这个参数的值设置为 ``Y`` 来启用DAMON_RCLAIM,把它设置为 ``N`` 可以禁用 +DAMON_RECLAIM。注意,由于基于水位的激活条件,DAMON_RECLAIM不能进行真正的监测和回收。 +这一点请参考下面关于水位参数的描述。 + +min_age +------- + +识别冷内存区域的时间阈值,单位是微秒。 + +如果一个内存区域在这个时间或更长的时间内没有被访问,DAMON_RECLAIM会将该区域识别为冷的, +并回收它。 + +默认为120秒。 + +quota_ms +-------- + +回收的时间限制,以毫秒为单位。 + +DAMON_RECLAIM 试图在一个时间窗口(quota_reset_interval_ms)内只使用到这个时间,以 +尝试回收冷页。这可以用来限制DAMON_RECLAIM的CPU消耗。如果该值为零,则该限制被禁用。 + +默认为10ms。 + +quota_sz +-------- + +回收的内存大小限制,单位为字节。 + +DAMON_RECLAIM 收取在一个时间窗口(quota_reset_interval_ms)内试图回收的内存量,并 +使其不超过这个限制。这可以用来限制CPU和IO的消耗。如果该值为零,则限制被禁用。 + +默认情况下是128 MiB。 + +quota_reset_interval_ms +----------------------- + +时间/大小配额收取重置间隔,单位为毫秒。 + +时间(quota_ms)和大小(quota_sz)的配额的目标重置间隔。也就是说,DAMON_RECLAIM在 +尝试回收‘不’超过quota_ms毫秒或quota_sz字节的内存。 + +默认为1秒。 + +wmarks_interval +--------------- + +当DAMON_RECLAIM被启用但由于其水位规则而不活跃时,在检查水位之前的最小等待时间。 + +wmarks_high +----------- + +高水位的可用内存率(每千字节)。 + +如果系统的可用内存(以每千字节为单位)高于这个数值,DAMON_RECLAIM就会变得不活跃,所以 +它什么也不做,只是定期检查水位。 + +wmarks_mid +---------- + +中间水位的可用内存率(每千字节)。 + +如果系统的空闲内存(以每千字节为单位)在这个和低水位线之间,DAMON_RECLAIM就会被激活, +因此开始监测和回收。 + +wmarks_low +---------- + +低水位的可用内存率(每千字节)。 + +如果系统的空闲内存(以每千字节为单位)低于这个数值,DAMON_RECLAIM就会变得不活跃,所以 +它除了定期检查水位外什么都不做。在这种情况下,系统会退回到基于LRU列表的页面粒度回收逻辑。 + +sample_interval +--------------- + +监测的采样间隔,单位是微秒。 + +DAMON用于监测冷内存的采样间隔。更多细节请参考DAMON文档 (:doc:`usage`) 。 + +aggr_interval +------------- + +监测的聚集间隔,单位是微秒。 + +DAMON对冷内存监测的聚集间隔。更多细节请参考DAMON文档 (:doc:`usage`)。 + +min_nr_regions +-------------- + +监测区域的最小数量。 + +DAMON用于冷内存监测的最小监测区域数。这可以用来设置监测质量的下限。但是,设 +置的太高可能会导致监测开销的增加。更多细节请参考DAMON文档 (:doc:`usage`) 。 + +max_nr_regions +-------------- + +监测区域的最大数量。 + +DAMON用于冷内存监测的最大监测区域数。这可以用来设置监测开销的上限值。但是, +设置得太低可能会导致监测质量不好。更多细节请参考DAMON文档 (:doc:`usage`) 。 + +monitor_region_start +-------------------- + +目标内存区域的物理地址起点。 + +DAMON_RECLAIM将对其进行工作的内存区域的起始物理地址。也就是说,DAMON_RECLAIM +将在这个区域中找到冷的内存区域并进行回收。默认情况下,该区域使用最大系统内存区。 + +monitor_region_end +------------------ + +目标内存区域的结束物理地址。 + +DAMON_RECLAIM将对其进行工作的内存区域的末端物理地址。也就是说,DAMON_RECLAIM将 +在这个区域内找到冷的内存区域并进行回收。默认情况下,该区域使用最大系统内存区。 + +kdamond_pid +----------- + +DAMON线程的PID。 + +如果DAMON_RECLAIM被启用,这将成为工作线程的PID。否则,为-1。 + +nr_reclaim_tried_regions +------------------------ + +试图通过DAMON_RECLAIM回收的内存区域的数量。 + +bytes_reclaim_tried_regions +--------------------------- + +试图通过DAMON_RECLAIM回收的内存区域的总字节数。 + +nr_reclaimed_regions +-------------------- + +通过DAMON_RECLAIM成功回收的内存区域的数量。 + +bytes_reclaimed_regions +----------------------- + +通过DAMON_RECLAIM成功回收的内存区域的总字节数。 + +nr_quota_exceeds +---------------- + +超过时间/空间配额限制的次数。 + +例子 +==== + +下面的运行示例命令使DAMON_RECLAIM找到30秒或更长时间没有访问的内存区域并“回收”? +为了避免DAMON_RECLAIM在分页操作中消耗过多的CPU时间,回收被限制在每秒1GiB以内。 +它还要求DAMON_RECLAIM在系统的可用内存率超过50%时不做任何事情,但如果它低于40%时 +就开始真正的工作。如果DAMON_RECLAIM没有取得进展,因此空闲内存率低于20%,它会要求 +DAMON_RECLAIM再次什么都不做,这样我们就可以退回到基于LRU列表的页面粒度回收了:: + + # cd /sys/modules/damon_reclaim/parameters + # echo 30000000 > min_age + # echo $((1 * 1024 * 1024 * 1024)) > quota_sz + # echo 1000 > quota_reset_interval_ms + # echo 500 > wmarks_high + # echo 400 > wmarks_mid + # echo 200 > wmarks_low + # echo Y > enabled + +.. [1] https://research.google/pubs/pub48551/ +.. [2] https://lwn.net/Articles/787611/ +.. [3] https://www.kernel.org/doc/html/latest/mm/free_page_reporting.html diff --git a/Documentation/translations/zh_CN/admin-guide/mm/damon/start.rst b/Documentation/translations/zh_CN/admin-guide/mm/damon/start.rst new file mode 100644 index 000000000..67d1b4948 --- /dev/null +++ b/Documentation/translations/zh_CN/admin-guide/mm/damon/start.rst @@ -0,0 +1,132 @@ +.. SPDX-License-Identifier: GPL-2.0 +.. include:: ../../../disclaimer-zh_CN.rst + +:Original: Documentation/admin-guide/mm/damon/start.rst + +:翻译: + + 司延腾 Yanteng Si <siyanteng@loongson.cn> + +:校译: + +======== +入门指南 +======== + +本文通过演示DAMON的默认用户空间工具,简要地介绍了如何使用DAMON。请注意,为了简洁 +起见,本文档只描述了它的部分功能。更多细节请参考该工具的使用文档。 +`doc <https://github.com/awslabs/damo/blob/next/USAGE.md>`_ . + + +前提条件 +======== + +内核 +---- + +首先,你要确保你当前系统中跑的内核构建时选定了这个功能选项 ``CONFIG_DAMON_*=y``. + + +用户空间工具 +------------ + +在演示中,我们将使用DAMON的默认用户空间工具,称为DAMON Operator(DAMO)。它可以在 +https://github.com/awslabs/damo找到。下面的例子假设DAMO在你的$PATH上。当然,但 +这并不是强制性的。 + +因为DAMO使用的是DAMON的debugfs接口(详情请参考 :doc:`usage` 中的使用方法) 你应该 +确保debugfs被挂载。手动挂载它,如下所示:: + + # mount -t debugfs none /sys/kernel/debug/ + +或者在你的 ``/etc/fstab`` 文件中添加以下一行,这样你的系统就可以在启动时自动挂载 +debugfs了:: + + debugfs /sys/kernel/debug debugfs defaults 0 0 + + +记录数据访问模式 +================ + +下面的命令记录了一个程序的内存访问模式,并将监测结果保存到文件中。 :: + + $ git clone https://github.com/sjp38/masim + $ cd masim; make; ./masim ./configs/zigzag.cfg & + $ sudo damo record -o damon.data $(pidof masim) + +命令的前两行下载了一个人工内存访问生成器程序并在后台运行。生成器将重复地逐一访问两个 +100 MiB大小的内存区域。你可以用你的真实工作负载来代替它。最后一行要求 ``damo`` 将 +访问模式记录在 ``damon.data`` 文件中。 + + +将记录的模式可视化 +================== + +你可以在heatmap中直观地看到这种模式,显示哪个内存区域(X轴)何时被访问(Y轴)以及访 +问的频率(数字)。:: + + $ sudo damo report heats --heatmap stdout + 22222222222222222222222222222222222222211111111111111111111111111111111111111100 + 44444444444444444444444444444444444444434444444444444444444444444444444444443200 + 44444444444444444444444444444444444444433444444444444444444444444444444444444200 + 33333333333333333333333333333333333333344555555555555555555555555555555555555200 + 33333333333333333333333333333333333344444444444444444444444444444444444444444200 + 22222222222222222222222222222222222223355555555555555555555555555555555555555200 + 00000000000000000000000000000000000000288888888888888888888888888888888888888400 + 00000000000000000000000000000000000000288888888888888888888888888888888888888400 + 33333333333333333333333333333333333333355555555555555555555555555555555555555200 + 88888888888888888888888888888888888888600000000000000000000000000000000000000000 + 88888888888888888888888888888888888888600000000000000000000000000000000000000000 + 33333333333333333333333333333333333333444444444444444444444444444444444444443200 + 00000000000000000000000000000000000000288888888888888888888888888888888888888400 + [...] + # access_frequency: 0 1 2 3 4 5 6 7 8 9 + # x-axis: space (139728247021568-139728453431248: 196.848 MiB) + # y-axis: time (15256597248362-15326899978162: 1 m 10.303 s) + # resolution: 80x40 (2.461 MiB and 1.758 s for each character) + +你也可以直观地看到工作集的大小分布,按大小排序。:: + + $ sudo damo report wss --range 0 101 10 + # <percentile> <wss> + # target_id 18446632103789443072 + # avr: 107.708 MiB + 0 0 B | | + 10 95.328 MiB |**************************** | + 20 95.332 MiB |**************************** | + 30 95.340 MiB |**************************** | + 40 95.387 MiB |**************************** | + 50 95.387 MiB |**************************** | + 60 95.398 MiB |**************************** | + 70 95.398 MiB |**************************** | + 80 95.504 MiB |**************************** | + 90 190.703 MiB |********************************************************* | + 100 196.875 MiB |***********************************************************| + +在上述命令中使用 ``--sortby`` 选项,可以显示工作集的大小是如何按时间顺序变化的。:: + + $ sudo damo report wss --range 0 101 10 --sortby time + # <percentile> <wss> + # target_id 18446632103789443072 + # avr: 107.708 MiB + 0 3.051 MiB | | + 10 190.703 MiB |***********************************************************| + 20 95.336 MiB |***************************** | + 30 95.328 MiB |***************************** | + 40 95.387 MiB |***************************** | + 50 95.332 MiB |***************************** | + 60 95.320 MiB |***************************** | + 70 95.398 MiB |***************************** | + 80 95.398 MiB |***************************** | + 90 95.340 MiB |***************************** | + 100 95.398 MiB |***************************** | + + +数据访问模式感知的内存管理 +========================== + +以下三个命令使每一个大小>=4K的内存区域在你的工作负载中没有被访问>=60秒,就会被换掉。 :: + + $ echo "#min-size max-size min-acc max-acc min-age max-age action" > test_scheme + $ echo "4K max 0 0 60s max pageout" >> test_scheme + $ damo schemes -c test_scheme <pid of your workload> diff --git a/Documentation/translations/zh_CN/admin-guide/mm/damon/usage.rst b/Documentation/translations/zh_CN/admin-guide/mm/damon/usage.rst new file mode 100644 index 000000000..aeae2ab65 --- /dev/null +++ b/Documentation/translations/zh_CN/admin-guide/mm/damon/usage.rst @@ -0,0 +1,559 @@ +.. SPDX-License-Identifier: GPL-2.0 +.. include:: ../../../disclaimer-zh_CN.rst + +:Original: Documentation/admin-guide/mm/damon/usage.rst + +:翻译: + + 司延腾 Yanteng Si <siyanteng@loongson.cn> + +:校译: + +======== +详细用法 +======== + +DAMON 为不同的用户提供了下面这些接口。 + +- *DAMON用户空间工具。* + `这 <https://github.com/awslabs/damo>`_ 为有这特权的人, 如系统管理员,希望有一个刚好 + 可以工作的人性化界面。 + 使用它,用户可以以人性化的方式使用DAMON的主要功能。不过,它可能不会为特殊情况进行高度调整。 + 它同时支持虚拟和物理地址空间的监测。更多细节,请参考它的 `使用文档 + <https://github.com/awslabs/damo/blob/next/USAGE.md>`_。 +- *sysfs接口。* + :ref:`这 <sysfs_interface>` 是为那些希望更高级的使用DAMON的特权用户空间程序员准备的。 + 使用它,用户可以通过读取和写入特殊的sysfs文件来使用DAMON的主要功能。因此,你可以编写和使 + 用你个性化的DAMON sysfs包装程序,代替你读/写sysfs文件。 `DAMON用户空间工具 + <https://github.com/awslabs/damo>`_ 就是这种程序的一个例子 它同时支持虚拟和物理地址 + 空间的监测。注意,这个界面只提供简单的监测结果 :ref:`统计 <damos_stats>`。对于详细的监测 + 结果,DAMON提供了一个:ref:`跟踪点 <tracepoint>`。 +- *debugfs interface.* + :ref:`这 <debugfs_interface>` 几乎与:ref:`sysfs interface <sysfs_interface>` 接 + 口相同。这将在下一个LTS内核发布后被移除,所以用户应该转移到 + :ref:`sysfs interface <sysfs_interface>`。 +- *内核空间编程接口。* + :doc:`这 </mm/damon/api>` 这是为内核空间程序员准备的。使用它,用户可以通过为你编写内 + 核空间的DAMON应用程序,最灵活有效地利用DAMON的每一个功能。你甚至可以为各种地址空间扩展DAMON。 + 详细情况请参考接口 :doc:`文件 </mm/damon/api>`。 + +sysfs接口 +========= +DAMON的sysfs接口是在定义 ``CONFIG_DAMON_SYSFS`` 时建立的。它在其sysfs目录下创建多 +个目录和文件, ``<sysfs>/kernel/mm/damon/`` 。你可以通过对该目录下的文件进行写入和 +读取来控制DAMON。 + +对于一个简短的例子,用户可以监测一个给定工作负载的虚拟地址空间,如下所示:: + + # cd /sys/kernel/mm/damon/admin/ + # echo 1 > kdamonds/nr && echo 1 > kdamonds/0/contexts/nr + # echo vaddr > kdamonds/0/contexts/0/operations + # echo 1 > kdamonds/0/contexts/0/targets/nr + # echo $(pidof <workload>) > kdamonds/0/contexts/0/targets/0/pid + # echo on > kdamonds/0/state + +文件层次结构 +------------ + +DAMON sysfs接口的文件层次结构如下图所示。在下图中,父子关系用缩进表示,每个目录有 +``/`` 后缀,每个目录中的文件用逗号(",")分开。 :: + + /sys/kernel/mm/damon/admin + │ kdamonds/nr_kdamonds + │ │ 0/state,pid + │ │ │ contexts/nr_contexts + │ │ │ │ 0/operations + │ │ │ │ │ monitoring_attrs/ + │ │ │ │ │ │ intervals/sample_us,aggr_us,update_us + │ │ │ │ │ │ nr_regions/min,max + │ │ │ │ │ targets/nr_targets + │ │ │ │ │ │ 0/pid_target + │ │ │ │ │ │ │ regions/nr_regions + │ │ │ │ │ │ │ │ 0/start,end + │ │ │ │ │ │ │ │ ... + │ │ │ │ │ │ ... + │ │ │ │ │ schemes/nr_schemes + │ │ │ │ │ │ 0/action + │ │ │ │ │ │ │ access_pattern/ + │ │ │ │ │ │ │ │ sz/min,max + │ │ │ │ │ │ │ │ nr_accesses/min,max + │ │ │ │ │ │ │ │ age/min,max + │ │ │ │ │ │ │ quotas/ms,bytes,reset_interval_ms + │ │ │ │ │ │ │ │ weights/sz_permil,nr_accesses_permil,age_permil + │ │ │ │ │ │ │ watermarks/metric,interval_us,high,mid,low + │ │ │ │ │ │ │ stats/nr_tried,sz_tried,nr_applied,sz_applied,qt_exceeds + │ │ │ │ │ │ ... + │ │ │ │ ... + │ │ ... + +根 +-- + +DAMON sysfs接口的根是 ``<sysfs>/kernel/mm/damon/`` ,它有一个名为 ``admin`` 的 +目录。该目录包含特权用户空间程序控制DAMON的文件。拥有根权限的用户空间工具或deamons可以 +使用这个目录。 + +kdamonds/ +--------- + +与监测相关的信息包括请求规格和结果被称为DAMON上下文。DAMON用一个叫做kdamond的内核线程 +执行每个上下文,多个kdamonds可以并行运行。 + +在 ``admin`` 目录下,有一个目录,即``kdamonds``,它有控制kdamonds的文件存在。在开始 +时,这个目录只有一个文件,``nr_kdamonds``。向该文件写入一个数字(``N``),就会创建名为 +``0`` 到 ``N-1`` 的子目录数量。每个目录代表每个kdamond。 + +kdamonds/<N>/ +------------- + +在每个kdamond目录中,存在两个文件(``state`` 和 ``pid`` )和一个目录( ``contexts`` )。 + +读取 ``state`` 时,如果kdamond当前正在运行,则返回 ``on`` ,如果没有运行则返回 ``off`` 。 +写入 ``on`` 或 ``off`` 使kdamond处于状态。向 ``state`` 文件写 ``update_schemes_stats`` , +更新kdamond的每个基于DAMON的操作方案的统计文件的内容。关于统计信息的细节,请参考 +:ref:`stats section <sysfs_schemes_stats>`. + +如果状态为 ``on``,读取 ``pid`` 显示kdamond线程的pid。 + +``contexts`` 目录包含控制这个kdamond要执行的监测上下文的文件。 + +kdamonds/<N>/contexts/ +---------------------- + +在开始时,这个目录只有一个文件,即 ``nr_contexts`` 。向该文件写入一个数字( ``N`` ),就会创 +建名为``0`` 到 ``N-1`` 的子目录数量。每个目录代表每个监测背景。目前,每个kdamond只支持 +一个上下文,所以只有 ``0`` 或 ``1`` 可以被写入文件。 + +contexts/<N>/ +------------- + +在每个上下文目录中,存在一个文件(``operations``)和三个目录(``monitoring_attrs``, +``targets``, 和 ``schemes``)。 + +DAMON支持多种类型的监测操作,包括对虚拟地址空间和物理地址空间的监测。你可以通过向文件 +中写入以下关键词之一,并从文件中读取,来设置和获取DAMON将为上下文使用何种类型的监测操作。 + + - vaddr: 监测特定进程的虚拟地址空间 + - paddr: 监视系统的物理地址空间 + +contexts/<N>/monitoring_attrs/ +------------------------------ + +用于指定监测属性的文件,包括所需的监测质量和效率,都在 ``monitoring_attrs`` 目录中。 +具体来说,这个目录下有两个目录,即 ``intervals`` 和 ``nr_regions`` 。 + +在 ``intervals`` 目录下,存在DAMON的采样间隔(``sample_us``)、聚集间隔(``aggr_us``) +和更新间隔(``update_us``)三个文件。你可以通过写入和读出这些文件来设置和获取微秒级的值。 + +在 ``nr_regions`` 目录下,有两个文件分别用于DAMON监测区域的下限和上限(``min`` 和 ``max`` ), +这两个文件控制着监测的开销。你可以通过向这些文件的写入和读出来设置和获取这些值。 + +关于间隔和监测区域范围的更多细节,请参考设计文件 (:doc:`/mm/damon/design`)。 + +contexts/<N>/targets/ +--------------------- + +在开始时,这个目录只有一个文件 ``nr_targets`` 。向该文件写入一个数字(``N``),就可以创建 +名为 ``0`` 到 ``N-1`` 的子目录的数量。每个目录代表每个监测目标。 + +targets/<N>/ +------------ + +在每个目标目录中,存在一个文件(``pid_target``)和一个目录(``regions``)。 + +如果你把 ``vaddr`` 写到 ``contexts/<N>/operations`` 中,每个目标应该是一个进程。你 +可以通过将进程的pid写到 ``pid_target`` 文件中来指定DAMON的进程。 + +targets/<N>/regions +------------------- + +当使用 ``vaddr`` 监测操作集时( ``vaddr`` 被写入 ``contexts/<N>/operations`` 文 +件),DAMON自动设置和更新监测目标区域,这样就可以覆盖目标进程的整个内存映射。然而,用户可 +能希望将初始监测区域设置为特定的地址范围。 + +相反,当使用 ``paddr`` 监测操作集时,DAMON不会自动设置和更新监测目标区域( ``paddr`` +被写入 ``contexts/<N>/operations`` 中)。因此,在这种情况下,用户应该自己设置监测目标 +区域。 + +在这种情况下,用户可以按照自己的意愿明确设置初始监测目标区域,将适当的值写入该目录下的文件。 + +开始时,这个目录只有一个文件, ``nr_regions`` 。向该文件写入一个数字(``N``),就可以创 +建名为 ``0`` 到 ``N-1`` 的子目录。每个目录代表每个初始监测目标区域。 + +regions/<N>/ +------------ + +在每个区域目录中,你会发现两个文件( ``start`` 和 ``end`` )。你可以通过向文件写入 +和从文件中读出,分别设置和获得初始监测目标区域的起始和结束地址。 + +contexts/<N>/schemes/ +--------------------- + +对于一版的基于DAMON的数据访问感知的内存管理优化,用户通常希望系统对特定访问模式的内存区 +域应用内存管理操作。DAMON从用户那里接收这种形式化的操作方案,并将这些方案应用于目标内存 +区域。用户可以通过读取和写入这个目录下的文件来获得和设置这些方案。 + +在开始时,这个目录只有一个文件,``nr_schemes``。向该文件写入一个数字(``N``),就可以 +创建名为``0``到``N-1``的子目录的数量。每个目录代表每个基于DAMON的操作方案。 + +schemes/<N>/ +------------ + +在每个方案目录中,存在四个目录(``access_pattern``, ``quotas``,``watermarks``, +和 ``stats``)和一个文件(``action``)。 + +``action`` 文件用于设置和获取你想应用于具有特定访问模式的内存区域的动作。可以写入文件 +和从文件中读取的关键词及其含义如下。 + + - ``willneed``: 对有 ``MADV_WILLNEED`` 的区域调用 ``madvise()`` 。 + - ``cold``: 对具有 ``MADV_COLD`` 的区域调用 ``madvise()`` 。 + - ``pageout``: 为具有 ``MADV_PAGEOUT`` 的区域调用 ``madvise()`` 。 + - ``hugepage``: 为带有 ``MADV_HUGEPAGE`` 的区域调用 ``madvise()`` 。 + - ``nohugepage``: 为带有 ``MADV_NOHUGEPAGE`` 的区域调用 ``madvise()``。 + - ``lru_prio``: 在其LRU列表上对区域进行优先排序。 + - ``lru_deprio``: 对区域的LRU列表进行降低优先处理。 + - ``stat``: 什么都不做,只计算统计数据 + +schemes/<N>/access_pattern/ +--------------------------- + +每个基于DAMON的操作方案的目标访问模式由三个范围构成,包括以字节为单位的区域大小、每个 +聚合区间的监测访问次数和区域年龄的聚合区间数。 + +在 ``access_pattern`` 目录下,存在三个目录( ``sz``, ``nr_accesses``, 和 ``age`` ), +每个目录有两个文件(``min`` 和 ``max`` )。你可以通过向 ``sz``, ``nr_accesses``, 和 +``age`` 目录下的 ``min`` 和 ``max`` 文件分别写入和读取来设置和获取给定方案的访问模式。 + +schemes/<N>/quotas/ +------------------- + +每个 ``动作`` 的最佳 ``目标访问模式`` 取决于工作负载,所以不容易找到。更糟糕的是,将某些动作 +的方案设置得过于激进会造成严重的开销。为了避免这种开销,用户可以为每个方案限制时间和大小配额。 +具体来说,用户可以要求DAMON尽量只使用特定的时间(``时间配额``)来应用行动,并且在给定的时间间 +隔(``重置间隔``)内,只对具有目标访问模式的内存区域应用行动,而不使用特定数量(``大小配额``)。 + +当预计超过配额限制时,DAMON会根据 ``目标访问模式`` 的大小、访问频率和年龄,对找到的内存区域 +进行优先排序。为了进行个性化的优先排序,用户可以为这三个属性设置权重。 + +在 ``quotas`` 目录下,存在三个文件(``ms``, ``bytes``, ``reset_interval_ms``)和一个 +目录(``weights``),其中有三个文件(``sz_permil``, ``nr_accesses_permil``, 和 +``age_permil``)。 + +你可以设置以毫秒为单位的 ``时间配额`` ,以字节为单位的 ``大小配额`` ,以及以毫秒为单位的 ``重 +置间隔`` ,分别向这三个文件写入数值。你还可以通过向 ``weights`` 目录下的三个文件写入数值来设 +置大小、访问频率和年龄的优先权,单位为千分之一。 + +schemes/<N>/watermarks/ +----------------------- + +为了便于根据系统状态激活和停用每个方案,DAMON提供了一个称为水位的功能。该功能接收五个值,称为 +``度量`` 、``间隔`` 、``高`` 、``中`` 、``低`` 。``度量值`` 是指可以测量的系统度量值,如 +自由内存比率。如果系统的度量值 ``高`` 于memoent的高值或 ``低`` 于低值,则该方案被停用。如果 +该值低于 ``中`` ,则该方案被激活。 + +在水位目录下,存在五个文件(``metric``, ``interval_us``,``high``, ``mid``, and ``low``) +用于设置每个值。你可以通过向这些文件的写入来分别设置和获取这五个值。 + +可以写入 ``metric`` 文件的关键词和含义如下。 + + - none: 忽略水位 + - free_mem_rate: 系统的自由内存率(千分比)。 + +``interval`` 应以微秒为单位写入。 + +schemes/<N>/stats/ +------------------ + +DAMON统计每个方案被尝试应用的区域的总数量和字节数,每个方案被成功应用的区域的两个数字,以及 +超过配额限制的总数量。这些统计数据可用于在线分析或调整方案。 + +可以通过读取 ``stats`` 目录下的文件(``nr_tried``, ``sz_tried``, ``nr_applied``, +``sz_applied``, 和 ``qt_exceeds``))分别检索这些统计数据。这些文件不是实时更新的,所以 +你应该要求DAMON sysfs接口通过在相关的 ``kdamonds/<N>/state`` 文件中写入一个特殊的关键字 +``update_schemes_stats`` 来更新统计信息的文件内容。 + +用例 +~~~~ + +下面的命令应用了一个方案:”如果一个大小为[4KiB, 8KiB]的内存区域在[10, 20]的聚合时间间隔内 +显示出每一个聚合时间间隔[0, 5]的访问量,请分页该区域。对于分页,每秒最多只能使用10ms,而且每 +秒分页不能超过1GiB。在这一限制下,首先分页出具有较长年龄的内存区域。另外,每5秒钟检查一次系统 +的可用内存率,当可用内存率低于50%时开始监测和分页,但如果可用内存率大于60%,或低于30%,则停 +止监测。“ :: + + # cd <sysfs>/kernel/mm/damon/admin + # # populate directories + # echo 1 > kdamonds/nr_kdamonds; echo 1 > kdamonds/0/contexts/nr_contexts; + # echo 1 > kdamonds/0/contexts/0/schemes/nr_schemes + # cd kdamonds/0/contexts/0/schemes/0 + # # set the basic access pattern and the action + # echo 4096 > access_patterns/sz/min + # echo 8192 > access_patterns/sz/max + # echo 0 > access_patterns/nr_accesses/min + # echo 5 > access_patterns/nr_accesses/max + # echo 10 > access_patterns/age/min + # echo 20 > access_patterns/age/max + # echo pageout > action + # # set quotas + # echo 10 > quotas/ms + # echo $((1024*1024*1024)) > quotas/bytes + # echo 1000 > quotas/reset_interval_ms + # # set watermark + # echo free_mem_rate > watermarks/metric + # echo 5000000 > watermarks/interval_us + # echo 600 > watermarks/high + # echo 500 > watermarks/mid + # echo 300 > watermarks/low + +请注意,我们强烈建议使用用户空间的工具,如 `damo <https://github.com/awslabs/damo>`_ , +而不是像上面那样手动读写文件。以上只是一个例子。 + +debugfs接口 +=========== + +DAMON导出了八个文件, ``attrs``, ``target_ids``, ``init_regions``, +``schemes``, ``monitor_on``, ``kdamond_pid``, ``mk_contexts`` 和 +``rm_contexts`` under its debugfs directory, ``<debugfs>/damon/``. + + +属性 +---- + +用户可以通过读取和写入 ``attrs`` 文件获得和设置 ``采样间隔`` 、 ``聚集间隔`` 、 ``更新间隔`` +以及监测目标区域的最小/最大数量。要详细了解监测属性,请参考 `:doc:/mm/damon/design` 。例如, +下面的命令将这些值设置为5ms、100ms、1000ms、10和1000,然后再次检查:: + + # cd <debugfs>/damon + # echo 5000 100000 1000000 10 1000 > attrs + # cat attrs + 5000 100000 1000000 10 1000 + + +目标ID +------ + +一些类型的地址空间支持多个监测目标。例如,虚拟内存地址空间的监测可以有多个进程作为监测目标。用户 +可以通过写入目标的相关id值来设置目标,并通过读取 ``target_ids`` 文件来获得当前目标的id。在监 +测虚拟地址空间的情况下,这些值应该是监测目标进程的pid。例如,下面的命令将pid为42和4242的进程设 +为监测目标,并再次检查:: + + # cd <debugfs>/damon + # echo 42 4242 > target_ids + # cat target_ids + 42 4242 + +用户还可以通过在文件中写入一个特殊的关键字 "paddr\n" 来监测系统的物理内存地址空间。因为物理地 +址空间监测不支持多个目标,读取文件会显示一个假值,即 ``42`` ,如下图所示:: + + # cd <debugfs>/damon + # echo paddr > target_ids + # cat target_ids + 42 + +请注意,设置目标ID并不启动监测。 + + +初始监测目标区域 +---------------- + +在虚拟地址空间监测的情况下,DAMON自动设置和更新监测的目标区域,这样就可以覆盖目标进程的整个 +内存映射。然而,用户可能希望将监测区域限制在特定的地址范围内,如堆、栈或特定的文件映射区域。 +或者,一些用户可以知道他们工作负载的初始访问模式,因此希望为“自适应区域调整”设置最佳初始区域。 + +相比之下,DAMON在物理内存监测的情况下不会自动设置和更新监测目标区域。因此,用户应该自己设置 +监测目标区域。 + +在这种情况下,用户可以通过在 ``init_regions`` 文件中写入适当的值,明确地设置他们想要的初 +始监测目标区域。输入的每一行应代表一个区域,形式如下:: + + <target idx> <start address> <end address> + +目标idx应该是 ``target_ids`` 文件中目标的索引,从 ``0`` 开始,区域应该按照地址顺序传递。 +例如,下面的命令将设置几个地址范围, ``1-100`` 和 ``100-200`` 作为pid 42的初始监测目标 +区域,这是 ``target_ids`` 中的第一个(索引 ``0`` ),另外几个地址范围, ``20-40`` 和 +``50-100`` 作为pid 4242的地址,这是 ``target_ids`` 中的第二个(索引 ``1`` ):: + + # cd <debugfs>/damon + # cat target_ids + 42 4242 + # echo "0 1 100 + 0 100 200 + 1 20 40 + 1 50 100" > init_regions + +请注意,这只是设置了初始的监测目标区域。在虚拟内存监测的情况下,DAMON会在一个 ``更新间隔`` +后自动更新区域的边界。因此,在这种情况下,如果用户不希望更新的话,应该把 ``更新间隔`` 设 +置得足够大。 + + +方案 +---- + +对于通常的基于DAMON的数据访问感知的内存管理优化,用户只是希望系统对特定访问模式的内存区域应用内 +存管理操作。DAMON从用户那里接收这种形式化的操作方案,并将这些方案应用到目标进程中。 + +用户可以通过读取和写入 ``scheme`` debugfs文件来获得和设置这些方案。读取该文件还可以显示每个 +方案的统计数据。在文件中,每一个方案都应该在每一行中以下列形式表示出来:: + + <target access pattern> <action> <quota> <watermarks> + +你可以通过简单地在文件中写入一个空字符串来禁用方案。 + +目标访问模式 +~~~~~~~~~~~~ + +``<目标访问模式>`` 是由三个范围构成的,形式如下:: + + min-size max-size min-acc max-acc min-age max-age + +具体来说,区域大小的字节数( `min-size` 和 `max-size` ),访问频率的每聚合区间的监测访问次 +数( `min-acc` 和 `max-acc` ),区域年龄的聚合区间数( `min-age` 和 `max-age` )都被指定。 +请注意,这些范围是封闭区间。 + +动作 +~~~~ + +``<action>`` 是一个预定义的内存管理动作的整数,DAMON将应用于具有目标访问模式的区域。支持 +的数字和它们的含义如下:: + + - 0: Call ``madvise()`` for the region with ``MADV_WILLNEED`` + - 1: Call ``madvise()`` for the region with ``MADV_COLD`` + - 2: Call ``madvise()`` for the region with ``MADV_PAGEOUT`` + - 3: Call ``madvise()`` for the region with ``MADV_HUGEPAGE`` + - 4: Call ``madvise()`` for the region with ``MADV_NOHUGEPAGE`` + - 5: Do nothing but count the statistics + +配额 +~~~~ + +每个 ``动作`` 的最佳 ``目标访问模式`` 取决于工作负载,所以不容易找到。更糟糕的是,将某个 +动作的方案设置得过于激进会导致严重的开销。为了避免这种开销,用户可以通过下面表格中的 ``<quota>`` +来限制方案的时间和大小配额:: + + <ms> <sz> <reset interval> <priority weights> + +这使得DAMON在 ``<reset interval>`` 毫秒内,尽量只用 ``<ms>`` 毫秒的时间对 ``目标访 +问模式`` 的内存区域应用动作,并在 ``<reset interval>`` 内只对最多<sz>字节的内存区域应 +用动作。将 ``<ms>`` 和 ``<sz>`` 都设置为零,可以禁用配额限制。 + +当预计超过配额限制时,DAMON会根据 ``目标访问模式`` 的大小、访问频率和年龄,对发现的内存 +区域进行优先排序。为了实现个性化的优先级,用户可以在 ``<优先级权重>`` 中设置这三个属性的 +权重,具体形式如下:: + + <size weight> <access frequency weight> <age weight> + +水位 +~~~~ + +有些方案需要根据系统特定指标的当前值来运行,如自由内存比率。对于这种情况,用户可以为该条 +件指定水位。:: + + <metric> <check interval> <high mark> <middle mark> <low mark> + +``<metric>`` 是一个预定义的整数,用于要检查的度量。支持的数字和它们的含义如下。 + + - 0: 忽视水位 + - 1: 系统空闲内存率 (千分比) + +每隔 ``<检查间隔>`` 微秒检查一次公制的值。 + +如果该值高于 ``<高标>`` 或低于 ``<低标>`` ,该方案被停用。如果该值低于 ``<中标>`` , +该方案将被激活。 + +统计数据 +~~~~~~~~ + +它还统计每个方案被尝试应用的区域的总数量和字节数,每个方案被成功应用的区域的两个数量,以 +及超过配额限制的总数量。这些统计数据可用于在线分析或调整方案。 + +统计数据可以通过读取方案文件来显示。读取该文件将显示你在每一行中输入的每个 ``方案`` , +统计的五个数字将被加在每一行的末尾。 + +例子 +~~~~ + +下面的命令应用了一个方案:”如果一个大小为[4KiB, 8KiB]的内存区域在[10, 20]的聚合时间 +间隔内显示出每一个聚合时间间隔[0, 5]的访问量,请分页出该区域。对于分页,每秒最多只能使 +用10ms,而且每秒分页不能超过1GiB。在这一限制下,首先分页出具有较长年龄的内存区域。另外, +每5秒钟检查一次系统的可用内存率,当可用内存率低于50%时开始监测和分页,但如果可用内存率 +大于60%,或低于30%,则停止监测“:: + + # cd <debugfs>/damon + # scheme="4096 8192 0 5 10 20 2" # target access pattern and action + # scheme+=" 10 $((1024*1024*1024)) 1000" # quotas + # scheme+=" 0 0 100" # prioritization weights + # scheme+=" 1 5000000 600 500 300" # watermarks + # echo "$scheme" > schemes + + +开关 +---- + +除非你明确地启动监测,否则如上所述的文件设置不会产生效果。你可以通过写入和读取 ``monitor_on`` +文件来启动、停止和检查监测的当前状态。写入 ``on`` 该文件可以启动对有属性的目标的监测。写入 +``off`` 该文件则停止这些目标。如果每个目标进程被终止,DAMON也会停止。下面的示例命令开启、关 +闭和检查DAMON的状态:: + + # cd <debugfs>/damon + # echo on > monitor_on + # echo off > monitor_on + # cat monitor_on + off + +请注意,当监测开启时,你不能写到上述的debugfs文件。如果你在DAMON运行时写到这些文件,将会返 +回一个错误代码,如 ``-EBUSY`` 。 + + +监测线程PID +----------- + +DAMON通过一个叫做kdamond的内核线程来进行请求监测。你可以通过读取 ``kdamond_pid`` 文件获 +得该线程的 ``pid`` 。当监测被 ``关闭`` 时,读取该文件不会返回任何信息:: + + # cd <debugfs>/damon + # cat monitor_on + off + # cat kdamond_pid + none + # echo on > monitor_on + # cat kdamond_pid + 18594 + + +使用多个监测线程 +---------------- + +每个监测上下文都会创建一个 ``kdamond`` 线程。你可以使用 ``mk_contexts`` 和 ``rm_contexts`` +文件为多个 ``kdamond`` 需要的用例创建和删除监测上下文。 + +将新上下文的名称写入 ``mk_contexts`` 文件,在 ``DAMON debugfs`` 目录上创建一个该名称的目录。 +该目录将有该上下文的 ``DAMON debugfs`` 文件:: + + # cd <debugfs>/damon + # ls foo + # ls: cannot access 'foo': No such file or directory + # echo foo > mk_contexts + # ls foo + # attrs init_regions kdamond_pid schemes target_ids + +如果不再需要上下文,你可以通过把上下文的名字放到 ``rm_contexts`` 文件中来删除它和相应的目录:: + + # echo foo > rm_contexts + # ls foo + # ls: cannot access 'foo': No such file or directory + +注意, ``mk_contexts`` 、 ``rm_contexts`` 和 ``monitor_on`` 文件只在根目录下。 + + +监测结果的监测点 +================ + +DAMON通过一个tracepoint ``damon:damon_aggregated`` 提供监测结果. 当监测开启时,你可 +以记录追踪点事件,并使用追踪点支持工具如perf显示结果。比如说:: + + # echo on > monitor_on + # perf record -e damon:damon_aggregated & + # sleep 5 + # kill 9 $(pidof perf) + # echo off > monitor_on + # perf script diff --git a/Documentation/translations/zh_CN/admin-guide/mm/index.rst b/Documentation/translations/zh_CN/admin-guide/mm/index.rst new file mode 100644 index 000000000..702271c5b --- /dev/null +++ b/Documentation/translations/zh_CN/admin-guide/mm/index.rst @@ -0,0 +1,49 @@ +.. include:: ../../disclaimer-zh_CN.rst + +:Original: Documentation/admin-guide/mm/index.rst + +:翻译: + + 徐鑫 xu xin <xu.xin16@zte.com.cn> + + +======== +内存管理 +======== + +Linux内存管理子系统,顾名思义,是负责系统中的内存管理。它包括了虚拟内存与请求 +分页的实现,内核内部结构和用户空间程序的内存分配、将文件映射到进程地址空间以 +及许多其他很酷的事情。 + +Linux内存管理是一个具有许多可配置设置的复杂系统, 且这些设置中的大多数都可以通 +过 ``/proc`` 文件系统获得,并且可以使用 ``sysctl`` 进行查询和调整。这些API接 +口被描述在Documentation/admin-guide/sysctl/vm.rst文件和 `man 5 proc`_ 中。 + +.. _man 5 proc: http://man7.org/linux/man-pages/man5/proc.5.html + +Linux内存管理有它自己的术语,如果你还不熟悉它,请考虑阅读下面参考: +:ref:`Documentation/admin-guide/mm/concepts.rst <mm_concepts>`. + +在此目录下,我们详细描述了如何与Linux内存管理中的各种机制交互。 + +.. toctree:: + :maxdepth: 1 + + damon/index + ksm + +Todolist: +* concepts +* cma_debugfs +* hugetlbpage +* idle_page_tracking +* memory-hotplug +* nommu-mmap +* numa_memory_policy +* numaperf +* pagemap +* soft-dirty +* swap_numa +* transhuge +* userfaultfd +* zswap diff --git a/Documentation/translations/zh_CN/admin-guide/mm/ksm.rst b/Documentation/translations/zh_CN/admin-guide/mm/ksm.rst new file mode 100644 index 000000000..4829156ef --- /dev/null +++ b/Documentation/translations/zh_CN/admin-guide/mm/ksm.rst @@ -0,0 +1,148 @@ +.. include:: ../../disclaimer-zh_CN.rst + +:Original: Documentation/admin-guide/mm/ksm.rst + +:翻译: + + 徐鑫 xu xin <xu.xin16@zte.com.cn> + + +============ +内核同页合并 +============ + + +概述 +==== + +KSM是一种能节省内存的数据去重功能,由CONFIG_KSM=y启用,并在2.6.32版本时被添 +加到Linux内核。详见 ``mm/ksm.c`` 的实现,以及http://lwn.net/Articles/306704 +和https://lwn.net/Articles/330589 + +KSM最初目的是为了与KVM(即著名的内核共享内存)一起使用而开发的,通过共享虚拟机 +之间的公共数据,将更多虚拟机放入物理内存。但它对于任何会生成多个相同数据实例的 +应用程序都是很有用的。 + +KSM的守护进程ksmd会定期扫描那些已注册的用户内存区域,查找内容相同的页面,这些 +页面可以被单个写保护页面替换(如果进程以后想要更新其内容,将自动复制)。使用: +引用:`sysfs intraface <ksm_sysfs>` 接口来配置KSM守护程序在单个过程中所扫描的页 +数以及两个过程之间的间隔时间。 + +KSM只合并匿名(私有)页面,从不合并页缓存(文件)页面。KSM的合并页面最初只能被 +锁定在内核内存中,但现在可以就像其他用户页面一样被换出(但当它们被交换回来时共 +享会被破坏: ksmd必须重新发现它们的身份并再次合并)。 + +以madvise控制KSM +================ + +KSM仅在特定的地址空间区域时运行,即应用程序通过使用如下所示的madvise(2)系统调 +用来请求某块地址成为可能的合并候选者的地址空间:: + + int madvise(addr, length, MADV_MERGEABLE) + +应用程序当然也可以通过调用:: + + int madvise(addr, length, MADV_UNMERGEABLE) + +来取消该请求,并恢复为非共享页面:此时KSM将去除合并在该范围内的任何合并页。注意: +这个去除合并的调用可能突然需要的内存量超过实际可用的内存量-那么可能会出现EAGAIN +失败,但更可能会唤醒OOM killer。 + +如果KSM未被配置到正在运行的内核中,则madvise MADV_MERGEABLE 和 MADV_UNMERGEABLE +的调用只会以EINVAL 失败。如果正在运行的内核是用CONFIG_KSM=y方式构建的,那么这些 +调用通常会成功:即使KSM守护程序当前没有运行,MADV_MERGEABLE 仍然会在KSM守护程序 +启动时注册范围,即使该范围不能包含KSM实际可以合并的任何页面,即使MADV_UNMERGEABLE +应用于从未标记为MADV_MERGEABLE的范围。 + +如果一块内存区域必须被拆分为至少一个新的MADV_MERGEABLE区域或MADV_UNMERGEABLE区域, +当该进程将超过 ``vm.max_map_count`` 的设定,则madvise可能返回ENOMEM。(请参阅文档 +Documentation/admin-guide/sysctl/vm.rst)。 + +与其他madvise调用一样,它们在用户地址空间的映射区域上使用:如果指定的范围包含未 +映射的间隙(尽管在中间的映射区域工作),它们将报告ENOMEM,如果没有足够的内存用于 +内部结构,则可能会因EAGAIN而失败。 + +KSM守护进程sysfs接口 +==================== + +KSM守护进程可以由``/sys/kernel/mm/ksm/`` 中的sysfs文件控制,所有人都可以读取,但 +只能由root用户写入。各接口解释如下: + + +pages_to_scan + ksmd进程进入睡眠前要扫描的页数。 + 例如, ``echo 100 > /sys/kernel/mm/ksm/pages_to_scan`` + + 默认值:100(该值被选择用于演示目的) + +sleep_millisecs + ksmd在下次扫描前应休眠多少毫秒 + 例如, ``echo 20 > /sys/kernel/mm/ksm/sleep_millisecs`` + + 默认值:20(该值被选择用于演示目的) + +merge_across_nodes + 指定是否可以合并来自不同NUMA节点的页面。当设置为0时,ksm仅合并在物理上位 + 于同一NUMA节点的内存区域中的页面。这降低了访问共享页面的延迟。在有明显的 + NUMA距离上,具有更多节点的系统可能受益于设置该值为0时的更低延迟。而对于 + 需要对内存使用量最小化的较小系统来说,设置该值为1(默认设置)则可能会受 + 益于更大共享页面。在决定使用哪种设置之前,您可能希望比较系统在每种设置下 + 的性能。 ``merge_across_nodes`` 仅当系统中没有ksm共享页面时,才能被更改设 + 置:首先将接口`run` 设置为2从而对页进行去合并,然后在修改 + ``merge_across_nodes`` 后再将‘run’又设置为1,以根据新设置来重新合并。 + + 默认值:1(如早期的发布版本一样合并跨站点) + +run + * 设置为0可停止ksmd运行,但保留合并页面, + * 设置为1可运行ksmd,例如, ``echo 1 > /sys/kernel/mm/ksm/run`` , + * 设置为2可停止ksmd运行,并且对所有目前已合并的页进行去合并,但保留可合并 + 区域以供下次运行。 + + 默认值:0(必须设置为1才能激活KSM,除非禁用了CONFIG_SYSFS) + +use_zero_pages + 指定是否应当特殊处理空页(即那些仅含zero的已分配页)。当该值设置为1时, + 空页与内核零页合并,而不是像通常情况下那样空页自身彼此合并。这可以根据 + 工作负载的不同,在具有着色零页的架构上可以提高性能。启用此设置时应小心, + 因为它可能会降低某些工作负载的KSM性能,比如,当待合并的候选页面的校验和 + 与空页面的校验和恰好匹配的时候。此设置可随时更改,仅对那些更改后再合并 + 的页面有效。 + + 默认值:0(如同早期版本的KSM正常表现) + +max_page_sharing + 单个KSM页面允许的最大共享站点数。这将强制执行重复数据消除限制,以避免涉 + 及遍历共享KSM页面的虚拟映射的虚拟内存操作的高延迟。最小值为2,因为新创 + 建的KSM页面将至少有两个共享者。该值越高,KSM合并内存的速度越快,去重 + 因子也越高,但是对于任何给定的KSM页面,虚拟映射的最坏情况遍历的速度也会 + 越慢。减慢了这种遍历速度就意味着在交换、压缩、NUMA平衡和页面迁移期间, + 某些虚拟内存操作将有更高的延迟,从而降低这些虚拟内存操作调用者的响应能力。 + 其他任务如果不涉及执行虚拟映射遍历的VM操作,其任务调度延迟不受此参数的影 + 响,因为这些遍历本身是调度友好的。 + +stable_node_chains_prune_millisecs + 指定KSM检查特定页面的元数据的频率(即那些达到过时信息数据去重限制标准的 + 页面)单位是毫秒。较小的毫秒值将以更低的延迟来释放KSM元数据,但它们将使 + ksmd在扫描期间使用更多CPU。如果还没有一个KSM页面达到 ``max_page_sharing`` + 标准,那就没有什么用。 + +KSM与MADV_MERGEABLE的工作有效性体现于 ``/sys/kernel/mm/ksm/`` 路径下的接口: + +pages_shared + 表示多少共享页正在被使用 +pages_sharing + 表示还有多少站点正在共享这些共享页,即节省了多少 +pages_unshared + 表示有多少页是唯一的,但被反复检查以进行合并 +pages_volatile + 表示有多少页因变化太快而无法放在tree中 +full_scans + 表示所有可合并区域已扫描多少次 +stable_node_chains + 达到 ``max_page_sharing`` 限制的KSM页数 +stable_node_dups + 重复的KSM页数 + +比值 ``pages_sharing/pages_shared`` 的最大值受限制于 ``max_page_sharing`` +的设定。要想增加该比值,则相应地要增加 ``max_page_sharing`` 的值。 diff --git a/Documentation/translations/zh_CN/admin-guide/reporting-issues.rst b/Documentation/translations/zh_CN/admin-guide/reporting-issues.rst new file mode 100644 index 000000000..59e51e353 --- /dev/null +++ b/Documentation/translations/zh_CN/admin-guide/reporting-issues.rst @@ -0,0 +1,1368 @@ +.. SPDX-License-Identifier: (GPL-2.0+ OR CC-BY-4.0) +.. See the bottom of this file for additional redistribution information. + + +.. include:: ../disclaimer-zh_CN.rst + +:Original: Documentation/admin-guide/reporting-issues.rst + +:译者: + + 吴想成 Wu XiangCheng <bobwxc@email.cn> + + +报告问题 ++++++++++ + + +简明指南(亦即 太长不看) +========================== + +您面临的是否为同系列稳定版或长期支持内核的普通内核的回归?是否仍然受支持? +请搜索 `LKML内核邮件列表 <https://lore.kernel.org/lkml/>`_ 和 +`Linux稳定版邮件列表 <https://lore.kernel.org/stable/>`_ 存档中匹配的报告并 +加入讨论。如果找不到匹配的报告,请安装该系列的最新版本。如果它仍然出现问题, +请报告给稳定版邮件列表(stable@vger.kernel.org)并抄送回归邮件列表 +(regressions@lists.linux.dev);理想情况下,还可以抄送维护者和相关子系统的 +邮件列表。 + +在所有其他情况下,请尽可能猜测是哪个内核部分导致了问题。查看MAINTAINERS文件, +了解开发人员希望如何得知问题,大多数情况下,报告问题都是通过电子邮件和抄送 +相关邮件列表进行的。检查报告目的地的存档中是否已有匹配的报告;也请搜索 +`LKML <https://lore.kernel.org/lkml/>`_ 和网络。如果找不到可加入的讨论,请 +安装 `最新的主线内核 <https://kernel.org/>`_ 。如果仍存在问题,请发送报告。 + +问题已经解决了,但是您希望看到它在一个仍然支持的稳定版或长期支持系列中得到 +解决?请安装其最新版本。如果它出现了问题,那么在主线中搜索修复它的更改,并 +检查是否正在回传(backporting)或者已放弃;如果两者都没有,那么可询问处理 +更改的人员。 + +**通用提醒** :当安装和测试上述内核时,请确保它是普通的(即:没有补丁,也没 +有使用附加模块)。还要确保它是在一个正常的环境中构建和运行,并且在问题发生 +之前没有被污染(tainted)。 + +当你同时面临Linux内核的多个问题时,请分别报告。在编写报告时,要涵盖与问题 +相关的所有信息,如使用的内核和发行版。如果碰见回归,请把报告抄送回归邮件列表 +(regressions@lists.linux.dev)。也请试试用二分法找出源头;如果成功找到,请 +在报告中写上它的提交ID并抄送sign-off-by链中的所有人。 + +一旦报告发出,请回答任何出现的问题,并尽可能地提供帮助。这包括通过不时重新 +测试新版本并发送状态更新来推动进展。 + + +如何向内核维护人员报告问题的逐步指南 +===================================== + +上面的简明指南概述了如何向Linux内核开发人员报告问题。对于已经熟悉向自由和开 +源软件(FLOSS)项目报告问题的人来说,这可能是他们所需要的全部内容。对于其他 +人,本部分更为详细,并一步一步地描述。为了便于阅读,它仍然尽量简洁,并省略 +了许多细节;这些在逐步指南后的参考章节中进行了描述,该章节更详细地解释了每 +个步骤。 + +注意:本节涉及的方面比简明指南多,顺序也稍有不同。这符合你的利益,以确保您 +尽早意识到看起来像Linux内核毛病的问题可能实际上是由其他原因引起的。这些步骤 +可以确保你最终不会觉得在这一过程中投入的时间是浪费: + + * 您是否面临硬件或软件供应商提供的Linux内核的问题?那么基本上您最好停止阅读 + 本文档,转而向您的供应商报告问题,除非您愿意自己安装最新的Linux版本。寻找 + 和解决问题往往需要后者。 + + * 使用您喜爱的网络搜索引擎对现有报告进行粗略搜索;此外,请检查 + `Linux内核邮件列表(LKML) <https://lore.kernel.org/lkml/>`_ 的存档。如果 + 找到匹配的报告,请加入讨论而不是发送新报告。 + + * 看看你正在处理的问题是否为回归问题、安全问题或非常严重的问题:这些都是需 + 要在接下来的一些步骤中特别处理的“高优先级问题”。 + + * 确保不是内核环境导致了您面临的问题。 + + * 创建一个新的备份,并将系统修复和恢复工具放在手边。 + + * 确保您的系统不会通过动态构建额外的内核模块来增强其内核,像DKMS这样的解决 + 方案可能在您不知情的情况下就在本地进行了这样的工作。 + + * 当问题发生时,检查您的内核是否被“污染”,因为使内核设置这个标志的事件可能 + 会导致您面临的问题。 + + * 粗略地写下如何重现这个问题。如果您同时处理多个问题,请为每个问题单独写注 + 释,并确保它们在新启动的系统上独立出现。这是必要的,因为每个问题都需要分 + 别报告给内核开发人员,除非它们严重纠缠在一起。 + + * 如果您正面临稳定版或长期支持版本线的回归(例如从5.10.4更新到5.10.5时出现 + 故障),请查看后文“报告稳定版和长期支持内核线的回归”小节。 + + * 定位可能引起问题的驱动程序或内核子系统。找出其开发人员期望的报告的方式和 + 位置。注意:大多数情况下不会是 bugzilla.kernel.org,因为问题通常需要通 + 过邮件发送给维护人员和公共邮件列表。 + + * 在缺陷追踪器或问题相关邮件列表的存档中彻底搜索可能与您的问题匹配的报告。 + 如果你发现了一些相关讨论,请加入讨论而不是发送新的报告。 + +在完成这些准备之后,你将进入主要部分: + + * 除非您已经在运行最新的“主线”Linux内核,否则最好在报告流程前安装它。在某些 + 情况下,使用最新的“稳定版”Linux进行测试和报告也是可以接受的替代方案;在 + 合并窗口期间,这实际上可能是最好的方法,但在开发阶段最好还是暂停几天。无论 + 你选择什么版本,最好使用“普通”构建。忽略这些建议会大大增加您的报告被拒绝 + 或忽略的风险。 + + * 确保您刚刚安装的内核在运行时不会“污染”自己。 + + * 在您刚刚安装的内核中复现这个问题。如果它没有出现,请查看下方只发生在 + 稳定版和长期支持内核的问题的说明。 + + * 优化你的笔记:试着找到并写出最直接的复现问题的方法。确保最终结果包含所有 + 重要的细节,同时让第一次听说的人容易阅读和理解。如果您在此过程中学到了一 + 些东西,请考虑再次搜索关于该问题的现有报告。 + + * 如果失败涉及“panic”、“Oops”、“warning”或“BUG”,请考虑解码内核日志以查找触 + 发错误的代码行。 + + * 如果您的问题是回归问题,请尽可能缩小引入问题时的范围。 + + * 通过详细描述问题来开始编写报告。记得包括以下条目:您为复现而安装的最新内 + 核版本、使用的Linux发行版以及关于如何复现该问题的说明。如果可能,将内核 + 构建配置(.config)和 ``dmesg`` 的输出放在网上的某个地方,并链接到它。包 + 含或上传所有其他可能相关的信息,如Oops的输出/截图或来自 ``lspci`` 的输出 + 。一旦你写完了这个主要部分,请在上方插入一个正常长度的段落快速概述问题和 + 影响。再在此之上添加一个简单描述问题的句子,以得到人们的阅读。现在给出一 + 个更短的描述性标题或主题。然后就可以像MAINTAINERS文件告诉你的那样发送或 + 提交报告了,除非你在处理一个“高优先级问题”:它们需要按照下面“高优先级问 + 题的特殊处理”所述特别关照。 + + * 等待别人的反应,继续推进事情,直到你能够接受这样或那样的结果。因此,请公 + 开和及时地回应任何询问。测试提出的修复。积极地测试:至少重新测试每个新主 + 线版本的首个候选版本(RC),并报告你的结果。如果出现拖延,就友好地提醒一 + 下。如果你没有得到任何帮助或者未能满意,请试着自己帮助自己。 + + +报告稳定版和长期支持内核线的回归 +---------------------------------- + +如果您发现了稳定版或长期支持内核版本线中的回归问题并按上述流程跳到这里,那么 +请阅读本小节。即例如您在从5.10.4更新到5.10.5时出现了问题(从5.9.15到5.10.5则 +不是)。开发人员希望尽快修复此类回归,因此有一个简化流程来报告它们: + + * 检查内核开发人员是否仍然维护你关心的Linux内核版本线:去 `kernel.org 的首页 + <https://kernel.org/>`_ ,确保此特定版本线的最新版没有“[EOL]”标记。 + + * 检查 `Linux稳定版邮件列表 <https://lore.kernel.org/stable/>`_ 中的现有报告。 + + * 从特定的版本线安装最新版本作为纯净内核。确保这个内核没有被污染,并且仍然 + 存在问题,因为问题可能已经在那里被修复了。如果您第一次发现供应商内核的问题, + 请检查已知最新版本的普通构建是否可以正常运行。 + + * 向Linux稳定版邮件列表发送一个简短的问题报告(stable@vger.kernel.org)并抄送 + Linux回归邮件列表(regressions@lists.linux.dev);如果你怀疑是由某子系统 + 引起的,请抄送其维护人员和子系统邮件列表。大致描述问题,并解释如何复现。 + 讲清楚首个出现问题的版本和最后一个工作正常的版本。然后等待进一步的指示。 + +下面的参考章节部分详细解释了这些步骤中的每一步。 + + +报告只发生在较旧内核版本线的问题 +---------------------------------- + +若您尝试了上述的最新主线内核,但未能在那里复现问题,那么本小节适用于您;以下 +流程有助于使问题在仍然支持的稳定版或长期支持版本线,或者定期基于最新稳定版或 +长期支持内核的供应商内核中得到修复。如果是这种情况,请执行以下步骤: + + * 请做好准备,接下来的几个步骤可能无法在旧版本中解决问题:修复可能太大或太 + 冒险,无法移植到那里。 + + * 执行前节“报告稳定版和长期支持内核线的回归”中的前三个步骤。 + + * 在Linux内核版本控制系统中搜索修复主线问题的更改,因为它的提交消息可能会 + 告诉你修复是否已经计划好了支持。如果你没有找到,搜索适当的邮件列表,寻找 + 讨论此类问题或同行评议可能修复的帖子;然后检查讨论是否认为修复不适合支持。 + 如果支持根本不被考虑,加入最新的讨论,询问是否有可能。 + + * 前面的步骤之一应该会给出一个解决方案。如果仍未能成功,请向可能引起问题的 + 子系统的维护人员询问建议;抄送特定子系统的邮件列表以及稳定版邮件列表 + +下面的参考章节部分详细解释了这些步骤中的每一步。 + + +参考章节:向内核维护者报告问题 +=============================== + +上面的详细指南简要地列出了所有主要步骤,这对大多数人来说应该足够了。但有时, +即使是有经验的用户也可能想知道如何实际执行这些步骤之一。这就是本节的目的, +因为它将提供关于上述每个步骤的更多细节。请将此作为参考文档:可以从头到尾 +阅读它。但它主要是为了浏览和查找如何实际执行这些步骤的详细信息。 + +在深入挖掘细节之前,我想先给你一些一般性建议: + + * Linux内核开发人员很清楚这个过程很复杂,比其他的FLOSS项目要求更多。我们很 + 希望让它更简单。但这需要在不同的地方以及一些基础设施上付诸努力,这些基础 + 设施需要持续的维护;尚未有人站出来做这些工作,所以目前情况就是这样。 + + * 与某些供应商签订的保证或支持合同并不能使您有权要求上游Linux内核社区的开 + 发人员进行修复:这样的合同完全在Linux内核、其开发社区和本文档的范围之外。 + 这就是为什么在这种情况下,你不能要求任何契约保证,即使开发人员处理的问 + 题对供应商有效。如果您想主张您的权利,使用供应商的支持渠道代替。当这样做 + 的时候,你可能想提出你希望看到这个问题在上游Linux内核中修复;可以这是确 + 保最终修复将被纳入所有Linux发行版的唯一方法来鼓励他们。 + + * 如果您从未向FLOSS项目报告过任何问题,那么您应该考虑阅读 `如何有效地报告 + 缺陷 <https://www.chiark.greenend.org.uk/~sgtatham/bugs.html>`_ , `如何 + 以明智的方式提问 <http://www.catb.org/esr/faqs/smart-questions.html>`_ , + 和 `如何提出好问题 <https://jvns.ca/blog/good-questions/>`_ 。 + +解决这些问题之后,可以在下面找到如何正确地向Linux内核报告问题的详细信息。 + + +确保您使用的是上游Linux内核 +---------------------------- + + *您是否面临硬件或软件供应商提供的Linux内核的问题?那么基本上您最好停止阅 + 读本文档,转而向您的供应商报告问题,除非您愿意自己安装最新的Linux版本。 + 寻找和解决问题往往需要后者。* + +与大多数程序员一样,Linux内核开发人员不喜欢花时间处理他们维护的源代码中根本 +不会发生的问题的报告。这只会浪费每个人的时间,尤其是你的时间。不幸的是,当 +涉及到内核时,这样的情况很容易发生,并且常常导致双方气馁。这是因为几乎所有预 +装在设备(台式机、笔记本电脑、智能手机、路由器等)上的Linux内核,以及大多数 +由Linux发行商提供的内核,都与由kernel.org发行的官方Linux内核相距甚远:从Linux +开发的角度来看,这些供应商提供的内核通常是古老的或者经过了大量修改,通常两点 +兼具。 + +大多数供应商内核都不适合用来向Linux内核开发人员报告问题:您在其中遇到的问题 +可能已经由Linux内核开发人员在数月或数年前修复;此外,供应商的修改和增强可能 +会导致您面临的问题,即使它们看起来很小或者完全不相关。这就是为什么您应该向 +供应商报告这些内核的问题。它的开发者应该查看报告,如果它是一个上游问题,直接 +于上游修复或将报告转发到那里。在实践中,这有时行不通。因此,您可能需要考虑 +通过自己安装最新的Linux内核内核来绕过供应商。如果如果您选择此方法,那么本指 +南后面的步骤将解释如何在排除了其他可能导致您的问题的原因后执行此操作。 + +注意前段使用的词语是“大多数”,因为有时候开发人员实际上愿意处理供应商内核出现 +的问题报告。他们是否这么做很大程度上取决于开发人员和相关问题。如果发行版只 +根据最近的Linux版本对内核进行了较小修改,那么机会就比较大;例如对于Debian +GNU/Linux Sid或Fedora Rawhide所提供的主线内核。一些开发人员还将接受基于最新 +稳定内核的发行版内核问题报告,只要它改动不大;例如Arch Linux、常规Fedora版本 +和openSUSE Turboweed。但是请记住,您最好使用主线Linux,并避免在此流程中使用 +稳定版内核,如“安装一个新的内核进行测试”一节中所详述。 + +当然,您可以忽略所有这些建议,并向上游Linux开发人员报告旧的或经过大量修改的 +供应商内核的问题。但是注意,这样的报告经常被拒绝或忽视,所以自行小心考虑一下。 +不过这还是比根本不报告问题要好:有时候这样的报告会直接或间接地帮助解决之后的 +问题。 + + +搜索现有报告(第一部分) +------------------------- + + *使用您喜爱的网络搜索引擎对现有报告进行粗略搜索;此外,请检查Linux内核 + 邮件列表(LKML)的存档。如果找到匹配的报告,请加入讨论而不是发送新报告。* + +报告一个别人已经提出的问题,对每个人来说都是浪费时间,尤其是作为报告人的你。 +所以彻底检查是否有人已经报告了这个问题,这对你自己是有利的。在流程中的这一步, +可以只执行一个粗略的搜索:一旦您知道您的问题需要报告到哪里,稍后的步骤将告诉 +您如何详细搜索。尽管如此,不要仓促完成这一步,它可以节省您的时间和减少麻烦。 + +只需先用你最喜欢的搜索引擎在互联网上搜索。然后再搜索Linux内核邮件列表(LKML) +存档。 + +如果搜索结果实在太多,可以考虑让你的搜索引擎将搜索时间范围限制在过去的一个 +月或一年。而且无论你在哪里搜索,一定要用恰当的搜索关键词;也要变化几次关键 +词。同时,试着从别人的角度看问题:这将帮助你想出其他的关键词。另外,一定不 +要同时使用过多的关键词。记住搜索时要同时尝试包含和不包含内核驱动程序的名称 +或受影响的硬件组件的名称等信息。但其确切的品牌名称(比如说“华硕红魔 Radeon +RX 5700 XT Gaming OC”)往往帮助不大,因为它太具体了。相反,尝试搜索术语,如 +型号(Radeon 5700 或 Radeon 5000)和核心代号(“Navi”或“Navi10”),以及包含 +和不包含其制造商(“AMD”)。 + +如果你发现了关于你的问题的现有报告,请加入讨论,因为你可能会提供有价值的额 +外信息。这一点很重要,即使是在修复程序已经准备好或处于最后阶段,因为开发人 +员可能会寻找能够提供额外信息或测试建议修复程序的人。跳到“发布报告后的责任” +一节,了解有关如何正确参与的细节。 + +注意,搜索 `bugzilla.kernel.org <https://bugzilla.kernel.org/>`_ 网站可能 +也是一个好主意,因为这可能会提供有价值的见解或找到匹配的报告。如果您发现后者, +请记住:大多数子系统都希望在不同的位置报告,如下面“你需要将问题报告到何处” +一节中所述。因此本应处理这个问题的开发人员甚至可能不知道bugzilla的工单。所以 +请检查工单中的问题是否已经按照本文档所述得到报告,如果没有,请考虑这样做。 + +高优先级的问题? +----------------- + + *看看你正在处理的问题是否是回归问题、安全问题或非常严重的问题:这些都是 + 需要在接下来的一些步骤中特别处理的“高优先级问题”。* + +Linus Torvalds和主要的Linux内核开发人员希望看到一些问题尽快得到解决,因此在 +报告过程中有一些“高优先级问题”的处理略有不同。有三种情况符合条件:回归、安全 +问题和非常严重的问题。 + +如果某个应用程序或实际用例在原先的Linux内核上运行良好,但在使用类似配置编译的 +较新版本上效果更差、或者根本不能用,那么你就需要处理回归问题。 +Documentation/admin-guide/reporting-regressions.rst 对此进行了更详细的解释。 +它还提供了很多你可能想知道的关于回归的其他信息;例如,它解释了如何将您的问题 +添加到回归跟踪列表中,以确保它不会被忽略。 + +什么是安全问题留给您自己判断。在继续之前,请考虑阅读 +Documentation/translations/zh_CN/admin-guide/security-bugs.rst , +因为它提供了如何最恰当地处理安全问题的额外细节。 + +当发生了完全无法接受的糟糕事情时,此问题就是一个“非常严重的问题”。例如, +Linux内核破坏了它处理的数据或损坏了它运行的硬件。当内核突然显示错误消息 +(“kernel panic”)并停止工作,或者根本没有任何停止信息时,您也在处理一个严重 +的问题。注意:不要混淆“panic”(内核停止自身的致命错误)和“Oops”(可恢复错误), +因为显示后者之后内核仍然在运行。 + + +确保环境健康 +-------------- + + *确保不是内核所处环境导致了你所面临的问题。* + +看起来很像内核问题的问题有时是由构建或运行时环境引起的。很难完全排除这种问 +题,但你应该尽量减少这种问题: + + * 构建内核时,请使用经过验证的工具,因为编译器或二进制文件中的错误可能会导 + 致内核出现错误行为。 + + * 确保您的计算机组件在其设计规范内运行;这对处理器、内存和主板尤为重要。因 + 此,当面临潜在的内核问题时,停止低电压或超频。 + + * 尽量确保不是硬件故障导致了你的问题。例如,内存损坏会导致大量的问题,这些 + 问题会表现为看起来像内核问题。 + + * 如果你正在处理一个文件系统问题,你可能需要用 ``fsck`` 检查一下文件系统, + 因为它可能会以某种方式被损坏,从而导致无法预期的内核行为。 + + * 在处理回归问题时,要确保没有在更新内核的同时发生了其他变化。例如,这个问 + 题可能是由同时更新的其他软件引起的。也有可能是在你第一次重启进入新内核时, + 某个硬件巧合地坏了。更新系统 BIOS 或改变 BIOS 设置中的某些内容也会导致 + 一些看起来很像内核回归的问题。 + + +为紧急情况做好准备 +------------------- + + *创建一个全新的备份,并将系统修复和还原工具放在手边* + +我得提醒您,您正在和计算机打交道,计算机有时会出现意想不到的事情,尤其是当 +您折腾其操作系统的内核等关键部件时。而这就是你在这个过程中要做的事情。因此, +一定要创建一个全新的备份;还要确保你手头有修复或重装操作系统的所有工具, +以及恢复备份所需的一切。 + + +确保你的内核不会被增强 +------------------------ + + *确保您的系统不会通过动态构建额外的内核模块来增强其内核,像DKMS这样的解 + 决方案可能在您不知情的情况下就在本地进行了这样的工作。* + +如果内核以任何方式得到增强,那么问题报告被忽略或拒绝的风险就会急剧增加。这就 +是为什么您应该删除或禁用像akmods和DKMS这样的机制:这些机制会自动构建额外内核 +模块,例如当您安装新的Linux内核或第一次引导它时。也要记得同时删除他们可能安装 +的任何模块。然后重新启动再继续。 + +注意,你可能不知道你的系统正在使用这些解决方案之一:当你安装 Nvidia 专有图 +形驱动程序、VirtualBox 或其他需要 Linux 内核以外的模块支持的软件时,它们通 +常会静默设置。这就是为什么你可能需要卸载这些软件的软件包,以摆脱任何第三方 +内核模块。 + + +检查“污染”标志 +---------------- + + *当问题发生时,检查您的内核是否被“污染”,因为使内核设置这个标志的事件可 + 能会导致您面临的问题。* + +当某些可能会导致看起来完全不相关的后续错误的事情发生时,内核会用“污染 +(taint)”标志标记自己。如果您的内核受到污染,那么您面临的可能是这样的错误。 +因此在投入更多时间到这个过程中之前,尽早排除此情况可能对你有好处。这是这个 +步骤出现在这里的唯一原因,因为这个过程稍后会告诉您安装最新的主线内核;然后 +您将需要再次检查污染标志,因为当它出问题的时候内核报告会关注它。 + +在正在运行的系统上检查内核是否污染非常容易:如果 ``cat /proc/sys/kernel/tainted`` +返回“0”,那么内核没有被污染,一切正常。在某些情况下无法检查该文件;这就是 +为什么当内核报告内部问题(“kernel bug”)、可恢复错误(“kernel Oops”)或停止 +操作前不可恢复的错误(“kernel panic”)时,它也会提到污染状态。当其中一个错 +误发生时,查看打印的错误消息的顶部,搜索以“CPU:”开头的行。如果发现问题时内 +核未被污染,那么它应该以“Not infected”结束;如果你看到“Tainted:”且后跟一些 +空格和字母,那就被污染了。 + +如果你的内核被污染了,请阅读 Documentation/translations/zh_CN/admin-guide/tainted-kernels.rst +以找出原因。设法消除污染因素。通常是由以下三种因素之一引起的: + + 1. 发生了一个可恢复的错误(“kernel Oops”),内核污染了自己,因为内核知道在 + 此之后它可能会出现奇怪的行为错乱。在这种情况下,检查您的内核或系统日志, + 并寻找以下列文字开头的部分:: + + Oops: 0000 [#1] SMP + + 如方括号中的“#1”所示,这是自启动以来的第一次Oops。每个Oops和此后发生的 + 任何其他问题都可能是首个Oops的后续问题,即使这两个问题看起来完全不相关。 + 通过消除首个Oops的原因并在之后复现该问题,可以排除这种情况。有时仅仅 + 重新启动就足够了,有时更改配置后重新启动可以消除Oops。但是在这个流程中 + 不要花费太多时间在这一点上,因为引起Oops的原因可能已经在您稍后将按流程 + 安装的新Linux内核版本中修复了。 + + 2. 您的系统使用的软件安装了自己的内核模块,例如Nvidia的专有图形驱动程序或 + VirtualBox。当内核从外部源(即使它们是开源的)加载此类模块时,它会污染 + 自己:它们有时会在不相关的内核区域导致错误,从而可能导致您面临的问题。 + 因此,当您想要向Linux内核开发人员报告问题时,您必须阻止这些模块加载。 + 大多数情况下最简单的方法是:临时卸载这些软件,包括它们可能已经安装的任 + 何模块。之后重新启动。 + + 3. 当内核加载驻留在Linux内核源代码staging树中的模块时,它也会污染自身。这 + 是一个特殊的区域,代码(主要是驱动程序)还没有达到正常Linux内核的质量 + 标准。当您报告此种模块的问题时,内核受到污染显然是没有问题的;只需确保 + 问题模块是造成污染的唯一原因。如果问题发生在一个不相关的区域,重新启动 + 并通过指定 ``foo.blacklist=1`` 作为内核参数临时阻止该模块被加载(用有 + 问题的模块名替换“foo”)。 + + +记录如何重现问题 +------------------ + + *粗略地写下如何重现这个问题。如果您同时处理多个问题,请为每个问题单独写 + 注释,并确保它们在新启动的系统上独立出现。这是必要的,因为每个问题都需 + 要分别报告给内核开发人员,除非它们严重纠缠在一起。* + +如果你同时处理多个问题,必须分别报告每个问题,因为它们可能由不同的开发人员 +处理。在一份报告中描述多种问题,也会让其他人难以将其分开。因此只有在问题严 +重纠缠的情况下,才能将问题合并在一份报告中。 + +此外,在报告过程中,你必须测试该问题是否发生在其他内核版本上。因此,如果您 +知道如何在一个新启动的系统上快速重现问题,将使您的工作更加轻松。 + +注意:报告只发生过一次的问题往往是没有结果的,因为它们可能是由于宇宙辐射导 +致的位翻转。所以你应该尝试通过重现问题来排除这种情况,然后再继续。如果你有 +足够的经验来区分由于硬件故障引起的一次性错误和难以重现的罕见内核问题,可以 +忽略这个建议。 + + +稳定版或长期支持内核的回归? +----------------------------- + + *如果您正面临稳定版或长期支持版本线的回归(例如从5.10.4更新到5.10.5时出现 + 故障),请查看后文“报告稳定版和长期支持内核线的回归”小节。* + +稳定版和长期支持内核版本线中的回归是Linux开发人员非常希望解决的问题,这样的 +问题甚至比主线开发分支中的回归更不应出现,因为它们会很快影响到很多人。开发人员 +希望尽快了解此类问题,因此有一个简化流程来报告这些问题。注意,使用更新内核版 +本线的回归(比如从5.9.15切换到5.10.5时出现故障)不符合条件。 + + +你需要将问题报告到何处 +------------------------ + + *定位可能引起问题的驱动程序或内核子系统。找出其开发人员期望的报告的方式 + 和位置。注意:大多数情况下不会是bugzilla.kernel.org,因为问题通常需要通 + 过邮件发送给维护人员和公共邮件列表。* + +将报告发送给合适的人是至关重要的,因为Linux内核是一个大项目,大多数开发人员 +只熟悉其中的一小部分。例如,相当多的程序员只关心一个驱动程序,比如一个WiFi +芯片驱动程序;它的开发人员可能对疏远的或不相关的“子系统”(如TCP堆栈、 +PCIe/PCI子系统、内存管理或文件系统)的内部知识了解很少或完全不了解。 + +问题在于:Linux内核缺少一个,可以简单地将问题归档并让需要了解它的开发人员了 +解它的,中心化缺陷跟踪器。这就是为什么你必须找到正确的途径来自己报告问题。 +您可以在脚本的帮助下做到这一点(见下文),但它主要针对的是内核开发人员和专 +家。对于其他人来说,MAINTAINERS(维护人员)文件是更好的选择。 + +如何阅读MAINTAINERS维护者文件 +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +为了说明如何使用 :ref:`MAINTAINERS <maintainers>` 文件,让我们假设您的笔记 +本电脑中的WiFi在更新内核后突然出现了错误行为。这种情况下可能是WiFi驱动的问 +题。显然,它也可能由于驱动基于的某些代码,但除非你怀疑有这样的东西会附着在 +驱动程序上。如果真的是其他的问题,驱动程序的开发人员会让合适的人参与进来。 + +遗憾的是,没有通用且简单的办法来检查哪个代码驱动了特定硬件组件。 + +在WiFi驱动出现问题的情况下,你可能想查看 ``lspci -k`` 的输出,因为它列出了 +PCI/PCIe总线上的设备和驱动它的内核模块:: + + [user@something ~]$ lspci -k + [...] + 3a:00.0 Network controller: Qualcomm Atheros QCA6174 802.11ac Wireless Network Adapter (rev 32) + Subsystem: Bigfoot Networks, Inc. Device 1535 + Kernel driver in use: ath10k_pci + Kernel modules: ath10k_pci + [...] + +但如果你的WiFi芯片通过USB或其他内部总线连接,这种方法就行不通了。在这种情况 +下,您可能需要检查您的WiFi管理器或 ``ip link`` 的输出。寻找有问题的网络接口 +的名称,它可能类似于“wlp58s0”。此名称可以用来找到驱动它的模块:: + + [user@something ~]$ realpath --relative-to=/sys/module//sys/class/net/wlp58s0/device/driver/module + ath10k_pci + +如果这些技巧不能进一步帮助您,请尝试在网上搜索如何缩小相关驱动程序或子系统 +的范围。如果你不确定是哪一个:试着猜一下,即使你猜得不好,也会有人会帮助你 +的。 + +一旦您知道了相应的驱动程序或子系统,您就希望在MAINTAINERS文件中搜索它。如果 +是“ath10k_pci”,您不会找到任何东西,因为名称太具体了。有时你需要在网上寻找 +帮助;但在此之前,请尝试使用一个稍短或修改过的名称来搜索MAINTAINERS文件,因 +为这样你可能会发现类似这样的东西:: + + QUALCOMM ATHEROS ATH10K WIRELESS DRIVER + Mail: A. Some Human <shuman@example.com> + Mailing list: ath10k@lists.infradead.org + Status: Supported + Web-page: https://wireless.wiki.kernel.org/en/users/Drivers/ath10k + SCM: git git://git.kernel.org/pub/scm/linux/kernel/git/kvalo/ath.git + Files: drivers/net/wireless/ath/ath10k/ + +注意:如果您阅读在Linux源代码树的根目录中找到的原始维护者文件,则行描述将是 +缩写。例如,“Mail:(邮件)”将是“M:”,“Mailing list:(邮件列表)”将是“L”, +“Status:(状态)”将是“S:”。此文件顶部有一段解释了这些和其他缩写。 + +首先查看“Status”状态行。理想情况下,它应该得到“Supported(支持)”或 +“Maintained(维护)”。如果状态为“Obsolete(过时的)”,那么你在使用一些过时的 +方法,需要转换到新的解决方案上。有时候,只有在感到有动力时,才会有人为代码 +提供“Odd Fixes”。如果碰见“Orphan”,你就完全不走运了,因为再也没有人关心代码 +了,只剩下这些选项:准备好与问题共存,自己修复它,或者找一个愿意修复它的程序员。 + +检查状态后,寻找以“bug:”开头的一行:它将告诉你在哪里可以找到子系统特定的缺 +陷跟踪器来提交你的问题。上面的例子没有此行。大多数部分都是这样,因为 Linux +内核的开发完全是由邮件驱动的。很少有子系统使用缺陷跟踪器,且其中只有一部分 +依赖于 bugzilla.kernel.org。 + +在这种以及其他很多情况下,你必须寻找以“Mail:”开头的行。这些行提到了特定代码 +的维护者的名字和电子邮件地址。也可以查找以“Mailing list:”开头的行,它告诉你 +开发代码的公共邮件列表。你的报告之后需要通过邮件发到这些地址。另外,对于所有 +通过电子邮件发送的问题报告,一定要抄送 Linux Kernel Mailing List(LKML) +<linux-kernel@vger.kernel.org>。在以后通过邮件发送问题报告时,不要遗漏任何 +一个邮件列表!维护者都是大忙人,可能会把一些工作留给子系统特定列表上的其他开 +发者;而 LKML 很重要,因为需要一个可以找到所有问题报告的地方。 + + +借助脚本找到维护者 +~~~~~~~~~~~~~~~~~~~~ + +对于手头有Linux源码的人来说,有第二个可以找到合适的报告地点的选择:脚本 +“scripts/get_maintainer.pl”,它尝试找到所有要联系的人。它会查询MAINTAINERS +文件,并需要用相关源代码的路径来调用。对于编译成模块的驱动程序,经常可以用 +这样的命令找到:: + + $ modinfo ath10k_pci | grep filename | sed 's!/lib/modules/.*/kernel/!!; s!filename:!!; s!\.ko\(\|\.xz\)!!' + drivers/net/wireless/ath/ath10k/ath10k_pci.ko + +将其中的部分内容传递给脚本:: + + $ ./scripts/get_maintainer.pl -f drivers/net/wireless/ath/ath10k* + Some Human <shuman@example.com> (supporter:QUALCOMM ATHEROS ATH10K WIRELESS DRIVER) + Another S. Human <asomehuman@example.com> (maintainer:NETWORKING DRIVERS) + ath10k@lists.infradead.org (open list:QUALCOMM ATHEROS ATH10K WIRELESS DRIVER) + linux-wireless@vger.kernel.org (open list:NETWORKING DRIVERS (WIRELESS)) + netdev@vger.kernel.org (open list:NETWORKING DRIVERS) + linux-kernel@vger.kernel.org (open list) + +不要把你的报告发给所有的人。发送给维护者,脚本称之为“supporter:”;另外抄送 +代码最相关的邮件列表,以及 Linux 内核邮件列表(LKML)。在此例中,你需要将报 +告发送给 “Some Human <shuman@example.com>” ,并抄送 +“ath10k@lists.infradead.org”和“linux-kernel@vger.kernel.org”。 + +注意:如果你用 git 克隆了 Linux 源代码,你可能需要用--git 再次调用 +get_maintainer.pl。脚本会查看提交历史,以找到最近哪些人参与了相关代码的编写, +因为他们可能会提供帮助。但要小心使用这些结果,因为它很容易让你误入歧途。 +例如,这种情况常常会发生在很少被修改的地方(比如老旧的或未维护的驱动程序): +有时这样的代码会在树级清理期间被根本不关心此驱动程序的开发者修改。 + + +搜索现有报告(第二部分) +-------------------------- + + *在缺陷追踪器或问题相关邮件列表的存档中彻底搜索可能与您的问题匹配的报告。 + 如果找到匹配的报告,请加入讨论而不是发送新报告。* + +如前所述:报告一个别人已经提出的问题,对每个人来说都是浪费时间,尤其是作为报告 +人的你。这就是为什么你应该再次搜索现有的报告。现在你已经知道问题需要报告到哪里。 +如果是邮件列表,那么一般在 `lore.kernel.org <https://lore.kernel.org/>`_ 可以 +找到相应存档。 + +但有些列表运行在其他地方。例如前面步骤中当例子的ath10k WiFi驱动程序就是这种 +情况。但是你通常可以在网上很容易地找到这些列表的档案。例如搜索“archive +ath10k@lists.infradead.org”,将引导您到ath10k邮件列表的信息页,该页面顶部链接 +到其 `列表存档 <https://lists.infradead.org/pipermail/ath10k/>`_ 。遗憾的是, +这个列表和其他一些列表缺乏搜索其存档的功能。在这种情况下可以使用常规的互联网 +搜索引擎,并添加类似“site:lists.infadead.org/pipermail/ath10k/”这 +样的搜索条件,这会把结果限制在该链接中的档案。 + +也请进一步搜索网络、LKML和bugzilla.kernel.org网站。如果你的报告需要发送到缺陷 +跟踪器中,那么您可能还需要检查子系统的邮件列表存档,因为可能有人只在那里报告了它。 + +有关如何搜索以及在找到匹配报告时如何操作的详细信息,请参阅上面的“搜索现有报告 +(第一部分)”。 + +不要急着完成报告过程的这一步:花30到60分钟甚至更多的时间可以为你和其他人节省 / +减少相当多的时间和麻烦。 + + +安装一个新的内核进行测试 +-------------------------- + + *除非您已经在运行最新的“主线”Linux内核,否则最好在报告流程前安装它。在 + 某些情况下,使用最新的“稳定版”Linux进行测试和报告也是可以接受的替代方案; + 在合并窗口期间,这实际上可能是最好的方法,但在开发阶段最好还是暂停几天。 + 无论你选择什么版本,最好使用“普通”构建。忽略这些建议会大大增加您的报告 + 被拒绝或忽略的风险。* + +正如第一步的详细解释中所提到的:与大多数程序员一样,与大多数程序员一样,Linux +内核开发人员不喜欢花时间处理他们维护的源代码中根本不会发生的问题的报告。这只 +会浪费每个人的时间,尤其是你的时间。这就是为什么在报告问题之前,您必须先确认 +问题仍然存在于最新的上游代码中,这符合每个人的利益。您可以忽略此建议,但如前 +所述:这样做会极大地增加问题报告被拒绝或被忽略的风险。 + +内核“最新上游”的范围通常指: + + * 安装一个主线内核;最新的稳定版内核也可以是一个选择,但大多数时候都最好避免。 + 长期支持内核(有时称为“LTS内核”)不适合此流程。下一小节将更详细地解释所有 + 这些。 + + * 下一小节描述获取和安装这样一个内核的方法。它还指出了使用预编译内核是可以的, + 但普通的内核更好,这意味着:它是直接使用从 `kernel.org <https://kernel.org/>`_ + 获得的Linux源代码构建并且没有任何方式修改或增强。 + + +选择适合测试的版本 +~~~~~~~~~~~~~~~~~~~~ + +前往 `kernel.org <https://kernel.org/>`_ 来决定使用哪个版本。忽略那个写着 +“Latest release最新版本”的巨大黄色按钮,往下看有一个表格。在表格的顶部,你会 +看到一行以“mainline”开头的字样,大多数情况下它会指向一个版本号类似“5.8-rc2” +的预发布版本。如果是这样的话,你将需要使用这个主线内核进行测试。不要让“rc” +吓到你,这些“开发版内核”实际上非常可靠——而且你已经按照上面的指示做了备份, +不是吗? + +大概每九到十周,“mainline”可能会给你指出一个版本号类似“5.7”的正式版本。如果 +碰见这种情况,请考虑暂停报告过程,直到下一个版本的第一个预发布(5.8-rc1)出 +现在 `kernel.org <https://kernel.org/>`_ 上。这是因为 Linux 的开发周期正在 +两周的“合并窗口”内。大部分的改动和所有干扰性的改动都会在这段时间内被合并到 +下一个版本中。在此期间使用主线是比较危险的。内核开发者通常也很忙,可能没有 +多余的时间来处理问题报告。这也是很有可能在合并窗口中应用了许多修改来修复你 +所面临的问题;这就是为什么你很快就得用一个新的内核版本重新测试,就像下面“发 +布报告后的责任”一节中所述的那样。 + +这就是为什么要等到合并窗口结束后才去做。但是如果你处理的是一些不应该等待的 +东西,则无需这样做。在这种情况下,可以考虑通过 git 获取最新的主线内核(见下 +文),或者使用 kernel.org 上提供的最新稳定版本。如果 mainline 因为某些原因 +不无法正常工作,那么使用它也是可以接受的。总的来说:用它来重现问题也比完全 +不报告问题要好。 + +最好避免在合并窗口外使用最新的稳定版内核,因为所有修复都必须首先应用于主线。 +这就是为什么检查最新的主线内核是如此重要:你希望看到在旧版本线修复的任何问题 +需要先在主线修复,然后才能得到回传,这可能需要几天或几周。另一个原因是:您 +希望的修复对于回传来说可能太难或太冒险;因此再次报告问题不太可能改变任何事情。 + +这些方面也部分表明了为什么长期支持内核(有时称为“LTS内核”)不适合报告流程: +它们与当前代码的距离太远。因此,先去测试主线,然后再按流程走:如果主线没有 +出现问题,流程将指导您如何在旧版本线中修复它。 + +如何获得新的 Linux 内核 +~~~~~~~~~~~~~~~~~~~~~~~~~ + +你可以使用预编译或自编译的内核进行测试;如果你选择后者,可以使用 git 获取源 +代码,或者下载其 tar 存档包。 + +**使用预编译的内核** :这往往是最快速、最简单、最安全的方法——尤其是在你不熟 +悉 Linux 内核的情况下。问题是:发行商或附加存储库提供的大多数版本都是从修改 +过的Linux源代码构建的。因此它们不是普通的,通常不适合于测试和问题报告:这些 +更改可能会导致您面临的问题或以某种方式影响问题。 + +但是如果您使用的是流行的Linux发行版,那么您就很幸运了:对于大部分的发行版, +您可以在网上找到包含最新主线或稳定版本Linux内核包的存储库。使用这些是完全可 +以的,只要从存储库的描述中确认它们是普通的或者至少接近普通。此外,请确保软件 +包包含kernel.org上提供的最新版本内核。如果这些软件包的时间超过一周,那么它们 +可能就不合适了,因为新的主线和稳定版内核通常至少每周发布一次。 + +请注意,您以后可能需要手动构建自己的内核:有时这是调试或测试修复程序所必需的, +如后文所述。还要注意,预编译的内核可能缺少在出现panic、Oops、warning或BUG时 +解码内核打印的消息所需的调试符号;如果您计划解码这些消息,最好自己编译内核 +(有关详细信息,请参阅本小节结尾和“解码失败信息”小节)。 + +**使用git** :熟悉 git 的开发者和有经验的 Linux 用户通常最好直接从 +`kernel.org 上的官方开发仓库 +<https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/tree/>`_ +中获取最新的 Linux 内核源代码。这些很可能比最新的主线预发布版本更新一些。不 +用担心:它们和正式的预发布版本一样可靠,除非内核的开发周期目前正处于合并窗 +口中。不过即便如此,它们也是相当可靠的。 + +**常规方法** :不熟悉 git 的人通常最好从 `kernel.org <https://kernel.org/>`_ +下载源码的tar 存档包。 + +如何实际构建一个内核并不在这里描述,因为许多网站已经解释了必要的步骤。如果 +你是新手,可以考虑按照那些建议使用 ``make localmodconfig`` 来做,它将尝试获 +取你当前内核的配置,然后根据你的系统进行一些调整。这样做并不能使编译出来的 +内核更好,但可以更快地编译。 + +注意:如果您正在处理来自内核的pannc、Oops、warning或BUG,请在配置内核时尝试 +启用 CONFIG_KALLSYMS 选项。此外,还可以启用 CONFIG_DEBUG_KERNEL 和 +CONFIG_DEBUG_INFO;后者是相关选项,但只有启用前者才能开启。请注意, +CONFIG_DEBUG_INFO 会需要更多储存空间来构建内核。但这是值得的,因为这些选项将 +允许您稍后精确定位触发问题的确切代码行。下面的“解码失败信息”一节对此进行了更 +详细的解释。 + +但请记住:始终记录遇到的问题,以防难以重现。发送未解码的报告总比不报告要好。 + + +检查“污染”标志 +---------------- + + *确保您刚刚安装的内核在运行时不会“污染”自己。* + +正如上面已经详细介绍过的:当发生一些可能会导致一些看起来完全不相关的后续错 +误的事情时,内核会设置一个“污染”标志。这就是为什么你需要检查你刚刚安装的内 +核是否有设置此标志。如果有的话,几乎在任何情况下你都需要在报告问题之前先消 +除它。详细的操作方法请看上面的章节。 + + +用新内核重现问题 +------------------ + + *在您刚刚安装的内核中复现这个问题。如果它没有出现,请查看下方只发生在 + 稳定版和长期支持内核的问题的说明。* + +检查这个问题是否发生在你刚刚安装的新 Linux 内核版本上。如果新内核已经修复了, +可以考虑使用此版本线,放弃报告问题。但是请记住,只要它没有在 `kernel.org +<https://kernel.org/>`_ 的稳定版和长期版(以及由这些版本衍生出来的厂商内核) +中得到修复,其他用户可能仍然会受到它的困扰。如果你喜欢使用其中的一个,或 +者只是想帮助它们的用户,请前往下面的“报告只发生在较旧内核版本线的问题”一节。 + + +优化复现问题的描述 +-------------------- + + *优化你的笔记:试着找到并写出最直接的复现问题的方法。确保最终结果包含所 + 有重要的细节,同时让第一次听说的人容易阅读和理解。如果您在此过程中学到 + 了一些东西,请考虑再次搜索关于该问题的现有报告。* + +过于复杂的报告会让别人很难理解。因此请尽量找到一个可以直接描述、易于以书面 +形式理解的再现方法。包含所有重要的细节,但同时也要尽量保持简短。 + +在这在前面的步骤中,你很可能已经了解了一些关于你所面临的问题的点。利用这些 +知识,再次搜索可以转而加入的现有报告。 + + +解码失败信息 +------------- + + *如果失败涉及“panic”、“Oops”、“warning”或“BUG”,请考虑解码内核日志以查找 + 触发错误的代码行。* + +当内核检测到内部问题时,它会记录一些有关已执行代码的信息。这使得在源代码中精 +确定位触发问题的行并显示如何调用它成为可能。但只有在配置内核时启用了 +CONFIG_DEBUG_INFO 和 CONFIG_KALLSYMS选项时,这种方法才起效。如果已启用此选项, +请考虑解码内核日志中的信息。这将使我们更容易理解是什么导致了“panic”、“Oops”、 +“warning”或“BUG”,从而增加了有人提供修复的几率。 + +解码可以通过Linux源代码树中的脚本来完成。如果您运行的内核是之前自己编译的, +这样这样调用它:: + + [user@something ~]$ sudo dmesg | ./linux-5.10.5/scripts/decode_stacktrace.sh ./linux-5.10.5/vmlinux + /usr/lib/debug/lib/modules/5.10.10-4.1.x86_64/vmlinux /usr/src/kernels/5.10.10-4.1.x86_64/ + +如果您运行的是打包好的普通内核,则可能需要安装带有调试符号的相应包。然后按以下 +方式调用脚本(如果发行版未打包,则可能需要从Linux源代码获取):: + + [user@something ~]$ sudo dmesg | ./linux-5.10.5/scripts/decode_stacktrace.sh \ + /usr/lib/debug/lib/modules/5.10.10-4.1.x86_64/vmlinux /usr/src/kernels/5.10.10-4.1.x86_64/ + +脚本将解码如下的日志行,这些日志行显示内核在发生错误时正在执行的代码的地址:: + + [ 68.387301] RIP: 0010:test_module_init+0x5/0xffa [test_module] + +解码之后,这些行将变成这样:: + + [ 68.387301] RIP: 0010:test_module_init (/home/username/linux-5.10.5/test-module/test-module.c:16) test_module + +在本例中,执行的代码是从文件“~/linux-5.10.5/test-module/test-module.c”构建的, +错误出现在第16行的指令中。 + +该脚本也会如此解码以“Call trace”开头的部分中提到的地址,该部分显示出现问题的 +函数的路径。此外,脚本还会显示内核正在执行的代码部分的汇编输出。 + +注意,如果你没法做到这一点,只需跳过这一步,并在报告中说明原因。如果你幸运的 +话,可能无需解码。如果需要的话,也许有人会帮你做这件事情。还要注意,这只是解 +码内核堆栈跟踪的几种方法之一。有时需要采取不同的步骤来检索相关的详细信息。 +别担心,如果您碰到的情况需要这样做,开发人员会告诉您该怎么做。 + + +对回归的特别关照 +----------------- + + *如果您的问题是回归问题,请尽可能缩小引入问题时的范围。* + +Linux 首席开发者 Linus Torvalds 认为 Linux 内核永远不应恶化,这就是为什么他 +认为回归是不可接受的,并希望看到它们被迅速修复。这就是为什么引入了回归的改 +动导致的问题若无法通过其他方式快速解决,通常会被迅速撤销。因此,报告回归有 +点像“王炸”,会迅速得到修复。但要做到这一点,需要知道导致回归的变化。通常情 +况下,要由报告者来追查罪魁祸首,因为维护者往往没有时间或手头设置不便来自行 +重现它。 + +有一个叫做“二分”的过程可以来寻找变化,这在 +Documentation/translations/zh_CN/admin-guide/bug-bisect.rst 文档中进行了详细 +的描述,这个过程通常需要你构建十到二十个内核镜像,每次都尝试在构建下一个镜像 +之前重现问题。是的,这需要花费一些时间,但不用担心,它比大多数人想象的要快得多。 +多亏了“binary search二分搜索”,这将引导你在源代码管理系统中找到导致回归的提交。 +一旦你找到它,就在网上搜索其主题、提交ID和缩短的提交ID(提交ID的前12个字符)。 +如果有的话,这将引导您找到关于它的现有报告。 + +需要注意的是,二分法需要一点窍门,不是每个人都懂得诀窍,也需要相当多的努力, +不是每个人都愿意投入。尽管如此,还是强烈建议自己进行一次二分。如果你真的 +不能或者不想走这条路,至少要找出是哪个主线内核引入的回归。比如说从 5.5.15 +切换到 5.8.4 的时候出现了一些问题,那么至少可以尝试一下相近的所有的主线版本 +(5.6、5.7 和 5.8)来检查它是什么时候出现的。除非你想在一个稳定版或长期支持 +内核中找到一个回归,否则要避免测试那些编号有三段的版本(5.6.12、5.7.8),因 +为那会使结果难以解释,可能会让你的测试变得无用。一旦你找到了引入回归的主要 +版本,就可以放心地继续报告了。但请记住:在不知道罪魁祸首的情况下,开发人员 +是否能够提供帮助取决于手头的问题。有时他们可能会从报告中确认是什么出现了问 +题,并能修复它;有时他们可能无法提供帮助,除非你进行二分。 + +当处理回归问题时,请确保你所面临的问题真的是由内核引起的,而不是由其他东西 +引起的,如上文所述。 + +在整个过程中,请记住:只有当旧内核和新内核的配置相似时,问题才算回归。这可以 +通过 ``make olddefconfig`` 来实现,详细解释参见 +Documentation/admin-guide/reporting-regressions.rst ;它还提供了大量其他您 +可能希望了解的有关回归的信息。 + + +撰写并发送报告 +--------------- + + *通过详细描述问题来开始编写报告。记得包括以下条目:您为复现而安装的最新 + 内核版本、使用的Linux发行版以及关于如何复现该问题的说明。如果可能,将内 + 核构建配置(.config)和 ``dmesg`` 的输出放在网上的某个地方,并链接到它。 + 包含或上传所有其他可能相关的信息,如Oops的输出/截图或来自 ``lspci`` + 的输出。一旦你写完了这个主要部分,请在上方插入一个正常长度的段落快速概 + 述问题和影响。再在此之上添加一个简单描述问题的句子,以得到人们的阅读。 + 现在给出一个更短的描述性标题或主题。然后就可以像MAINTAINERS文件告诉你的 + 那样发送或提交报告了,除非你在处理一个“高优先级问题”:它们需要按照下面 + “高优先级问题的特殊处理”所述特别关照。* + +现在你已经准备好了一切,是时候写你的报告了。上文前言中链接的三篇文档对如何 +写报告做了部分解释。这就是为什么本文将只提到一些基本的内容以及 Linux 内核特 +有的东西。 + +有一点是符合这两类的:你的报告中最关键的部分是标题/主题、第一句话和第一段。 +开发者经常会收到许多邮件。因此,他们往往只是花几秒钟的时间浏览一下邮件,然 +后再决定继续下一封或仔细查看。因此,你报告的开头越好,有人研究并帮助你的机 +会就越大。这就是为什么你应该暂时忽略他们,先写出详细的报告。;-) + +每份报告都应提及的事项 +~~~~~~~~~~~~~~~~~~~~~~~~ + +详细描述你的问题是如何发生在你安装的新纯净内核上的。试着包含你之前写的和优 +化过的分步说明,概述你和其他人如何重现这个问题;在极少数无法重现的情况下, +尽量描述你做了什么来触发它。 + +还应包括其他人为了解该问题及其环境而可能需要的所有相关信息。实际需要的东西 +在很大程度上取决于具体问题,但有些事项你总是应该包括在内: + + * ``cat /proc/version`` 的输出,其中包含 Linux 内核版本号和构建时的编译器。 + + * 机器正在运行的 Linux 发行版( ``hostnamectl | grep “Operating System“`` ) + + * CPU 和操作系统的架构( ``uname -mi`` ) + + * 如果您正在处理回归,并进行了二分,请提及导致回归的变更的主题和提交ID。 + +许多情况下,让读你报告的人多了解两件事也是明智之举: + + * 用于构建 Linux 内核的配置(“.config”文件) + + * 内核的信息,你从 ``dmesg`` 得到的信息写到一个文件里。确保它以像“Linux + version 5.8-1 (foobar@example.com) (gcc (GCC) 10.2.1, GNU ld version + 2.34) #1 SMP Mon Aug 3 14:54:37 UTC 2020”这样的行开始,如果没有,那么第 + 一次启动阶段的重要信息已经被丢弃了。在这种情况下,可以考虑使用 + ``journalctl -b 0 -k`` ;或者你也可以重启,重现这个问题,然后调用 + ``dmesg`` 。 + +这两个文件很大,所以直接把它们放到你的报告中是个坏主意。如果你是在缺陷跟踪 +器中提交问题,那么将它们附加到工单中。如果你通过邮件报告问题,不要用附件附 +上它们,因为那会使邮件变得太大,可以按下列之一做: + + * 将文件上传到某个公开的地方(你的网站,公共文件粘贴服务,在 + `bugzilla.kernel.org <https://bugzilla.kernel.org/>`_ 上创建的工单……), + 并在你的报告中放上链接。理想情况下请使用允许这些文件保存很多年的地方,因 + 为它们可能在很多年后对别人有用;例如 5 年或 10 年后,一个开发者正在修改 + 一些代码,而这些代码正是为了修复你的问题。 + + * 把文件放在一边,然后说明你会在他人回复时再单独发送。只要记得报告发出去后, + 真正做到这一点就可以了。;-) + +提供这些东西可能是明智的 +~~~~~~~~~~~~~~~~~~~~~~~~~~ + +根据问题的不同,你可能需要提供更多的背景数据。这里有一些关于提供什么比较好 +的建议: + + * 如果你处理的是内核的“warning”、“OOPS”或“panic”,请包含它。如果你不能复制 + 粘贴它,试着用netconsole网络终端远程跟踪或者至少拍一张屏幕的照片。 + + * 如果问题可能与你的电脑硬件有关,请说明你使用的是什么系统。例如,如果你的 + 显卡有问题,请提及它的制造商,显卡的型号,以及使用的芯片。如果是笔记本电 + 脑,请提及它的型号名称,但尽量确保意义明确。例如“戴尔 XPS 13”就不很明确, + 因为它可能是 2012 年的那款,那款除了看起来和现在销售的没有什么不同之外, + 两者没有任何共同之处。因此,在这种情况下,要加上准确的型号,例如 2019 + 年内推出的 XPS 13 型号为“9380”或“7390”。像“联想 Thinkpad T590”这样的名字 + 也有些含糊不清:这款笔记本有带独立显卡和不带的子型号,所以要尽量找到准确 + 的型号名称或注明主要部件。 + + * 说明正在使用的相关软件。如果你在加载模块时遇到了问题,你要说明正在使用的 + kmod、systemd 和 udev 的版本。如果其中一个 DRM 驱动出现问题,你要说明 + libdrm 和 Mesa 的版本;还要说明你的 Wayland 合成器或 X-Server 及其驱动。 + 如果你有文件系统问题,请注明相应的文件系统实用程序的版本(e2fsprogs, + btrfs-progs, xfsprogs……)。 + + * 从内核中收集可能有用的额外信息。例如, ``lspci -nn`` 的输出可以帮助别人 + 识别你使用的硬件。如果你的硬件有问题,你甚至可以给出 ``sudo lspci -vvv`` + 的结果,因为它提供了组件是如何配置的信息。对于一些问题,可能最好包含 + ``/proc/cpuinfo`` , ``/proc/ioports`` , ``/proc/iomem`` , + ``/proc/modules`` 或 ``/proc/scsi/scsi`` 等文件的内容。一些子系统还提 + 供了收集相关信息的工具。 ``alsa-info.sh`` `就是这样一个工具,它是音频/声 + 音子系统开发者提供的 <https://www.alsa-project.org/wiki/AlsaInfo>`_ 。 + +这些例子应该会给你一些知识点,让你知道附上什么数据可能是明智的,但你自己也 +要想一想,哪些数据对别人会有帮助。不要太担心忘记一些东西,因为开发人员会要 +求提供他们需要的额外细节。但从一开始就把所有重要的东西都提供出来,会增加别 +人仔细查看的机会。 + + +重要部分:报告的开头 +~~~~~~~~~~~~~~~~~~~~~~ + +现在你已经准备好了报告的详细部分,让我们进入最重要的部分:开头几句。现在到 +报告的最前面,在你刚才写的部分之前加上类似“The detailed description:”(详细 +描述)这样的内容,并在最前面插入两个新行。现在写一个正常长度的段落,大致概 +述这个问题。去掉所有枯燥的细节,把重点放在读者需要知道的关键部分,以让人了 +解这是怎么回事;如果你认为这个缺陷影响了很多用户,就提一下这点来吸引大家关 +注。 + +做好这一点后,在顶部再插入两行,写一句话的摘要,快速解释报告的内容。之后你 +要更加抽象,为报告写一个更短的主题/标题。 + +现在你已经写好了这部分,请花点时间来优化它,因为它是你的报告中最重要的部分: +很多人会先读这部分,然后才会决定是否值得花时间阅读其他部分。 + +现在就像 :ref:`MAINTAINERS <maintainers>` 维护者文件告诉你的那样发送或提交 +报告,除非它是前面概述的那些“高优先级问题”之一:在这种情况下,请先阅读下一 +小节,然后再发送报告。 + +高优先级问题的特殊处理 +~~~~~~~~~~~~~~~~~~~~~~~~ + +高优先级问题的报告需要特殊处理。 + +**非常严重的缺陷** :确保在主题或工单标题以及第一段中明显标出 severeness +(非常严重的)。 + +**回归** :报告的主题应以“[REGRESSION]”开头。 + +如果您成功用二分法定位了问题,请使用引入回归之更改的标题作为主题的第二部分。 +请在报告中写明“罪魁祸首”的提交ID。如果未能成功二分,请在报告中讲明最后一个 +正常工作的版本(例如5.7)和最先发生问题的版本(例如5.8-rc1)。 + +通过邮件发送报告时,请抄送Linux回归邮件列表(regressions@lists.linux.dev)。 +如果报告需要提交到某个web追踪器,请继续提交;并在提交后,通过邮件将报告转发 +至回归列表;抄送相关子系统的维护人员和邮件列表。请确保报告是内联转发的,不要 +把它作为附件。另外请在顶部添加一个简短的说明,在那里写上工单的网址。 + +在邮寄或转发报告时,如果成功二分,需要将“罪魁祸首”的作者添加到收件人中;同时 +抄送signed-off-by链中的每个人,您可以在提交消息的末尾找到。 + +**安全问题** :对于这种问题,你将必须评估:如果细节被公开披露,是否会对其他 +用户产生短期风险。如果不会,只需按照所述继续报告问题。如果有此风险,你需要 +稍微调整一下报告流程。 + + * 如果 MAINTAINERS 文件指示您通过邮件报告问题,请不要抄送任何公共邮件列表。 + + * 如果你应该在缺陷跟踪器中提交问题,请确保将工单标记为“私有”或“安全问题”。 + 如果缺陷跟踪器没有提供保持报告私密性的方法,那就别想了,把你的报告以私人 + 邮件的形式发送给维护者吧。 + +在这两种情况下,都一定要将报告发到 MAINTAINERS 文件中“安全联络”部分列出的 +地址。理想的情况是在发送报告的时候直接抄送他们。如果您在缺陷跟踪器中提交了 +报告,请将报告的文本转发到这些地址;但请在报告的顶部加上注释,表明您提交了 +报告,并附上工单链接。 + +更多信息请参见 Documentation/translations/zh_CN/admin-guide/security-bugs.rst 。 + + +发布报告后的责任 +------------------ + + *等待别人的反应,继续推进事情,直到你能够接受这样或那样的结果。因此,请 + 公开和及时地回应任何询问。测试提出的修复。积极地测试:至少重新测试每个 + 新主线版本的首个候选版本(RC),并报告你的结果。如果出现拖延,就友好地 + 提醒一下。如果你没有得到任何帮助或者未能满意,请试着自己帮助自己。* + +如果你的报告非常优秀,而且你真的很幸运,那么某个开发者可能会立即发现导致问 +题的原因;然后他们可能会写一个补丁来修复、测试它,并直接发送给主线集成,同 +时标记它以便以后回溯到需要它的稳定版和长期支持内核。那么你需要做的就是回复 +一句“Thank you very much”(非常感谢),然后在发布后换上修复好的版本。 + +但这种理想状况很少发生。这就是为什么你把报告拿出来之后工作才开始。你要做的 +事情要视情况而定,但通常会是下面列出的事情。但在深入研究细节之前,这里有几 +件重要的事情,你需要记住这部分的过程。 + + +关于进一步互动的一般建议 +~~~~~~~~~~~~~~~~~~~~~~~~~~ + +**总是公开回复** :当你在缺陷跟踪器中提交问题时,一定要在那里回复,不要私下 +联系任何开发者。对于邮件报告,在回复您收到的任何邮件时,总是使用“全部回复” +功能。这包括带有任何你可能想要添加到你的报告中的额外数据的邮件:进入邮件应 +用程序“已发送”文件夹,并在邮件上使用“全部回复”来回复报告。这种方法可以确保 +公共邮件列表和其他所有参与者都能及时了解情况;它还能保持邮件线程的完整性, +这对于邮件列表将所有相关邮件归为一类是非常重要的。 + +只有两种情况不适合在缺陷跟踪器或“全部回复”中发表评论: + + * 有人让你私下发东西。 + + * 你被告知要发送一些东西,但注意到其中包含需要保密的敏感信息。在这种情况下, + 可以私下发送给要求发送的开发者。但要在工单或邮件中注明你是这么做的,这 + 样其他人就知道你尊重了这个要求。 + +**在请求解释或帮助之前先研究一下** :在这部分过程中,有人可能会告诉你用尚未 +掌握的技能做一些事情。例如你可能会被要求使用一些你从未听说过的测试工具;或 +者你可能会被要求在 Linux 内核源代码上应用一个补丁来测试它是否有帮助。在某些 +情况下,发个回复询问如何做就可以了。但在走这条路之前,尽量通过在互联网上搜 +索自行找到答案;或者考虑在其他地方询问建议。比如询问朋友,或者到你平时常去 +的聊天室或论坛发帖咨询。 + +**要有耐心** :如果你真的很幸运,你可能会在几个小时内收到对你的报告的答复。 +但大多数情况下会花费更多的时间,因为维护者分散在全球各地,因此可能在不同的 +时区——在那里他们已经享受着远离键盘的夜晚。 + +一般来说,内核开发者需要一到五个工作日来回复报告。有时会花费更长的时间,因 +为他们可能正忙于合并窗口、其他工作、参加开发者会议,或者只是在享受一个漫长 +的暑假。 + +“高优先级的问题”(见上面的解释)例外:维护者应该尽快解决这些问题;这就是为 +什么你应该最多等待一个星期(如果是紧急的事情,则只需两天),然后再发送友好 +的提醒。 + +有时维护者可能没有及时回复;有时候可能会出现分歧,例如一个问题是否符合回归 +的条件。在这种情况下,在邮件列表上提出你的顾虑,并请求其他人公开或私下回复 +如何继续推进。如果失败了,可能应该让更高级别的维护者介入。如果是 WiFi 驱动, +那就是无线维护者;如果没有更高级别的维护者,或者其他一切努力都失败了,那 +这可能是一种罕见的、可以让 Linus Torvalds 参与进来的情况。 + +**主动测试** :每当一个新的主线内核版本的第一个预发布版本(rc1)发布的时候, +去检查一下这个问题是否得到了解决,或者是否有什么重要的变化。在工单中或在 +回复报告的邮件中提及结果(确保所有参与讨论的人都被抄送)。这将表明你的承诺 +和你愿意帮忙。如果问题持续存在,它也会提醒开发者确保他们不会忘记它。其他一 +些不定期的重新测试(例如用rc3、rc5 和最终版本)也是一个好主意,但只有在相关 +的东西发生变化或者你正在写什么东西的时候才报告你的结果。 + +这些些常规的事情就不说了,我们来谈谈报告后如何帮助解决问题的细节。 + +查询和测试请求 +~~~~~~~~~~~~~~~ + +如果你的报告得到了回复则需履行以下责任: + +**检查与你打交道的人** :大多数情况下,会是维护者或特定代码区域的开发人员对 +你的报告做出回应。但由于问题通常是公开报告的,所以回复的可能是任何人——包括 +那些想要帮忙的人,但最后可能会用他们的问题或请求引导你完全偏离轨道。这很少 +发生,但这是快速上网搜搜看你正在与谁互动是明智之举的许多原因之一。通过这样 +做,你也可以知道你的报告是否被正确的人听到,因为如果讨论没有导致满意的问题 +解决方案而淡出,之后可能需要提醒维护者(见下文)。 + +**查询数据** :通常你会被要求测试一些东西或提供更多细节。尽快提供所要求的信 +息,因为你已经得到了可能会帮助你的人的注意,你等待的时间越长就有越可能失去 +关注;如果你不在数个工作日内提供信息,甚至可能出现这种结果。 + +**测试请求** :当你被要求测试一个诊断补丁或可能的修复时,也要尽量及时测试。 +但要做得恰当,一定不要急于求成:混淆事情很容易发生,这会给所有人带来许多困 +惑。例如一个常见的错误是以为应用了一个带修复的建议补丁,但事实上并没有。即 +使是有经验的测试人员也会偶尔发生这样的事情,但当有修复的内核和没有修复的内 +核表现得一样时,他们大多时候会注意到。 + +当没有任何实质性进展时该怎么办 +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +有些报告不会得到负有相关责任的 Linux 内核开发者的任何反应;或者围绕这个问题 +的讨论有所发展,但渐渐淡出,没有任何实质内容产出。 + +在这种情况下,要等两个星期(最好是三个星期)后再发出友好的提醒:也许当你的 +报告到达时,维护者刚刚离开键盘一段时间,或者有更重要的事情要处理。在写提醒 +信的时候,要善意地问一下,是否还需要你这边提供什么来让事情推进下去。如果报 +告是通过邮件发出来的,那就在邮件的第一行回复你的初始邮件(见上文),其中包 +括下方的原始报告的完整引用:这是少数几种情况下,这样的“TOFU”(Text Over, +Fullquote Under文字在上,完整引用在下)是正确的做法,因为这样所有的收件人都 +会以适当的顺序立即让细节到手头上来。 + +在提醒之后,再等三周的回复。如果你仍然没有得到适当的反馈,你首先应该重新考 +虑你的方法。你是否可能尝试接触了错误的人?是不是报告也许令人反感或者太混乱, +以至于人们决定完全远离它?排除这些因素的最好方法是:把报告给一两个熟悉 +FLOSS 问题报告的人看,询问他们的意见。同时征求他们关于如何继续推进的建议。 +这可能意味着:准备一份更好的报告,让这些人在你发出去之前对它进行审查。这样 +的方法完全可以;只需说明这是关于这个问题的第二份改进的报告,并附上第一份报 +告的链接。 + +如果报告是恰当的,你可以发送第二封提醒信;在其中询问为什么报告没有得到任何 +回复。第二封提醒邮件的好时机是在新 Linux 内核版本的首个预发布版本('rc1') +发布后不久,因为无论如何你都应该在那个时候重新测试并提供状态更新(见上文)。 + +如果第二次提醒的结果又在一周内没有任何反应,可以尝试联系上级维护者询问意见: +即使再忙的维护者在这时候也至少应该发过某种确认。 + +记住要做好失望的准备:理想状况下维护者最好对每一个问题报告做出回应,但他们 +只有义务解决之前列出的“高优先级问题”。所以,如果你得到的回复是“谢谢你的报告, +我目前有更重要的问题要处理,在可预见的未来没有时间去研究这个问题”,那请不 +要太沮丧。 + +也有可能在缺陷跟踪器或列表中进行了一些讨论之后,什么都没有发生,提醒也无助 +于激励大家进行修复。这种情况可能是毁灭性的,但在 Linux 内核开发中确实会发生。 +这些和其他得不到帮助的原因在本文结尾处的“为什么有些问题在被报告后没有得到 +任何回应或者仍然没有修复”中进行了解释。 + +如果你没有得到任何帮助或问题最终没有得到解决,不要沮丧:Linux 内核是 FLOSS, +因此你仍然可以自己帮助自己。例如,你可以试着找到其他受影响的人,和他们一 +起合作来解决这个问题。这样的团队可以一起准备一份新的报告,提到团队有多少人, +为什么你们认为这是应该得到解决的事情。也许你们还可以一起缩小确切原因或引 +入回归的变化,这往往会使修复更容易。而且如果运气好的话,团队中可能会有懂点 +编程的人,也许能写出一个修复方案。 + + + +“报告稳定版和长期支持内核线的回归”的参考 +------------------------------------------ + +本小节提供了在稳定版和长期支持内核线中面对回归时需要执行的步骤的详细信息。 + +确保特定版本线仍然受支持 +~~~~~~~~~~~~~~~~~~~~~~~~~ + + *检查内核开发人员是否仍然维护你关心的Linux内核版本线:去 kernel.org 的 + 首页,确保此特定版本线的最新版没有“[EOL]”标记。* + +大多数内核版本线只支持三个月左右,因为延长维护时间会带来相当多的工作。因此, +每年只会选择一个版本来支持至少两年(通常是六年)。这就是为什么你需要检查 +内核开发者是否还支持你关心的版本线。 + +注意,如果 `kernel.org <https://kernel.org/>`_ 在首页上列出了两个“稳定”版本, +你应该考虑切换到较新的版本,而忘掉较旧的版本:对它的支持可能很快就会结束。 +然后,它将被标记为“生命周期结束”(EOL)。达到这个程度的版本线仍然会在 +`kernel.org <https://kernel.org/>`_ 首页上被显示一两周,但不适合用于测试和 +报告。 + +搜索稳定版邮件列表 +~~~~~~~~~~~~~~~~~~~ + + *检查Linux稳定版邮件列表中的现有报告。* + +也许你所面临的问题已经被发现,并且已经或即将被修复。因此,请在 `Linux 稳定 +版邮件列表的档案 <https://lore.kernel.org/stable/>`_ 中搜索类似问题的报告。 +如果你找到任何匹配的问题,可以考虑加入讨论,除非修复工作已经完成并计划很快 +得到应用。 + +用最新版本复现问题 +~~~~~~~~~~~~~~~~~~~ + + *从特定的版本线安装最新版本作为纯净内核。确保这个内核没有被污染,并且仍 + 然存在问题,因为问题可能已经在那里被修复了。* + +在投入更多时间到这个过程中之前,你要检查这个问题是否在你关注的版本线的最新 +版本中已经得到了修复。这个内核需要是纯净的,在问题发生之前不应该被污染,正 +如上面已经在测试主线的过程中详细介绍过的一样。 + +您是否是第一次注意到供应商内核的回归?供应商的更改可能会发生变化。你需要重新 +检查排除来这个问题。当您从5.10.4-vendor.42更新到5.10.5-vendor.43时,记录损坏 +的信息。然后在测试了前一段中所述的最新5.10版本之后,检查Linux 5.10.4的普通版本 +是否也可以正常工作。如果问题在那里出现,那就不符合上游回归的条件,您需要切换 +回主逐步指南来报告问题。 + +报告回归 +~~~~~~~~~~ + + *向Linux稳定版邮件列表发送一个简短的问题报告(stable@vger.kernel.org)并 + 抄送Linux回归邮件列表(regressions@lists.linux.dev);如果你怀疑是由某 + 子系统引起的,请抄送其维护人员和子系统邮件列表。大致描述问题,并解释如 + 何复现。讲清楚首个出现问题的版本和最后一个工作正常的版本。然后等待进一 + 步的指示。* + +当报告在稳定版或长期支持内核线内发生的回归(例如在从5.10.4更新到5.10.5时), +一份简短的报告足以快速报告问题。因此只需向稳定版和回归邮件列表发送粗略的描述; +不过如果你怀疑某子系统导致此问题的话,请一并抄送其维护人员和子系统邮件列表, +这会加快进程。 + +请注意,如果您能够指明引入问题的确切版本,这将对开发人员有很大帮助。因此 +如果有时间的话,请尝试使用普通内核找到该版本。让我们假设发行版发布Linux内核 +5.10.5到5.10.8的更新时发生了故障。那么按照上面的指示,去检查该版本线中的最新 +内核,比如5.10.9。如果问题出现,请尝试普通5.10.5,以确保供应商应用的补丁不会 +干扰。如果问题没有出现,那么尝试5.10.7,然后直到5.10.8或5.10.6(取决于结果) +找到第一个引入问题的版本。在报告中写明这一点,并指出5.10.9仍然存在故障。 + +前一段基本粗略地概述了“二分”方法。一旦报告出来,您可能会被要求做一个正确的 +报告,因为它允许精确地定位导致问题的确切更改(然后很容易被恢复以快速修复问题)。 +因此如果时间允许,考虑立即进行适当的二分。有关如何详细信息,请参阅“对回归的 +特别关照”部分和文档 Documentation/translations/zh_CN/admin-guide/bug-bisect.rst 。 +如果成功二分的话,请将“罪魁祸首”的作者添加到收件人中;同时抄送所有在 +signed-off-by链中的人,您可以在提交消息的末尾找到。 + + +“报告仅在旧内核版本线中发生的问题”的参考 +---------------------------------------- + +本节详细介绍了如果无法用主线内核重现问题,但希望在旧版本线(又称稳定版内核和 +长期支持内核)中修复问题时需要采取的步骤。 + +有些修复太复杂 +~~~~~~~~~~~~~~~ + + *请做好准备,接下来的几个步骤可能无法在旧版本中解决问题:修复可能太大或 + 太冒险,无法移植到那里。* + +即使是微小的、看似明显的代码变化,有时也会带来新的、完全意想不到的问题。稳 +定版和长期支持内核的维护者非常清楚这一点,因此他们只对这些内核进行符合 +Documentation/translations/zh_CN/process/stable-kernel-rules.rst 中所列出的 +规则的修改。 + +复杂或有风险的修改不符合条件,因此只能应用于主线。其他的修复很容易被回溯到 +最新的稳定版和长期支持内核,但是风险太大,无法集成到旧版内核中。所以要注意 +你所希望的修复可能是那些不会被回溯到你所关心的版本线的修复之一。在这种情况 +下,你将别无选择,要么忍受这个问题,要么切换到一个较新的 Linux 版本,除非你 +想自己把修复补丁应用到你的内核中。 + +通用准备 +~~~~~~~~~~ + + *执行上面“报告仅在旧内核版本线中发生的问题”一节中的前三个步骤。* + +您需要执行本指南另一节中已经描述的几个步骤。这些步骤将让您: + + * 检查内核开发人员是否仍然维护您关心的Linux内核版本行。 + + * 在Linux稳定邮件列表中搜索退出的报告。 + + * 检查最新版本。 + + +检查代码历史和搜索现有的讨论 +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + + *在Linux内核版本控制系统中搜索修复主线问题的更改,因为它的提交消息可能 + 会告诉你修复是否已经计划好了支持。如果你没有找到,搜索适当的邮件列表, + 寻找讨论此类问题或同行评议可能修复的帖子;然后检查讨论是否认为修复不适 + 合支持。如果支持根本不被考虑,加入最新的讨论,询问是否有可能。* + +在许多情况下,你所处理的问题会发生在主线上,但已在主线上得到了解决。修正它 +的提交也需要被回溯才能解决这个问题。这就是为什么你要搜索它或任何相关讨论。 + + * 首先尝试在存放 Linux 内核源代码的 Git 仓库中找到修复。你可以通过 + `kernel.org 上的网页 + <https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/tree/>`_ + 或 `GitHub 上的镜像 <https://github.com/torvalds/linux>`_ 来实现;如果你 + 有一个本地克隆,你也可以在命令行用 ``git log --grep=<pattern>`` 来搜索。 + + 如果你找到了修复,请查看提交消息的尾部是否包含了类似这样的“稳定版标签”: + + Cc: <stable@vger.kernel.org> # 5.4+ + + 像上面这行,开发者标记了安全修复可以回传到 5.4 及以后的版本。大多数情况 + 下,它会在两周内被应用到那里,但有时需要更长的时间。 + + * 如果提交没有告诉你任何东西,或者你找不到修复,请再找找关于这个问题的讨论。 + 用你最喜欢的搜索引擎搜索网络,以及 `Linux kernel developers mailing + list 内核开发者邮件列表 <https://lore.kernel.org/lkml/>`_ 的档案。也可以 + 阅读上面的 `定位导致问题的内核区域` 一节,然后按照说明找到导致问题的子系 + 统:它的缺陷跟踪器或邮件列表存档中可能有你要找的答案。 + + * 如果你看到了一个计划的修复,请按上所述在版本控制系统中搜索它,因为提交可 + 能会告诉你是否可以进行回溯。 + + * 检查讨论中是否有任何迹象表明,该修复程序可能风险太大,无法回溯到你关心 + 的版本线。如果是这样的话,你必须忍受这个问题,或者切换到应用了修复的内 + 核版本线。 + + * 如果修复的问题未包含稳定版标签,并且没有讨论过回溯问题,请加入讨论:如 + 果合适的话,请提及你所面对的问题的版本,以及你希望看到它被修复。 + + +请求建议 +~~~~~~~~~ + + *前面的步骤之一应该会给出一个解决方案。如果仍未能成功,请向可能引起问题 + 的子系统的维护人员询问建议;抄送特定子系统的邮件列表以及稳定版邮件列表。* + +如果前面的三个步骤都没有让你更接近解决方案,那么只剩下一个选择:请求建议。 +在你发给可能是问题根源的子系统的维护者的邮件中这样做;抄送子系统的邮件列表 +以及稳定版邮件列表(stable@vger.kernel.org)。 + + +为什么有些问题在报告后没有任何回应或仍未解决? +=============================================== + +当向 Linux 开发者报告问题时,要注意只有“高优先级的问题”(回归、安全问题、严 +重问题)才一定会得到解决。如果维护者或其他人都失败了,Linus Torvalds 他自己 +会确保这一点。他们和其他内核开发者也会解决很多其他问题。但是要知道,有时他 +们也会不能或不愿帮忙;有时甚至没有人发报告给他们。 + +最好的解释就是那些内核开发者常常是在业余时间为 Linux 内核做出贡献。内核中的 +不少驱动程序都是由这样的程序员编写的,往往只是因为他们想让自己的硬件可以在 +自己喜欢的操作系统上使用。 + +这些程序员大多数时候会很乐意修复别人报告的问题。但是没有人可以强迫他们这样 +做,因为他们是自愿贡献的。 + +还有一些情况下,这些开发者真的很想解决一个问题,但却不能解决:有时他们缺乏 +硬件编程文档来解决问题。这种情况往往由于公开的文档太简陋,或者驱动程序是通 +过逆向工程编写的。 + +业余开发者迟早也会不再关心某驱动。也许他们的测试硬件坏了,被更高级的玩意取 +代了,或者是太老了以至于只能在计算机博物馆里找到。有时开发者根本就不关心他 +们的代码和 Linux 了,因为在他们的生活中一些不同的东西变得更重要了。在某些情 +况下,没有人愿意接手维护者的工作——也没有人可以被强迫,因为对 Linux 内核的贡 +献是自愿的。然而被遗弃的驱动程序仍然存在于内核中:它们对人们仍然有用,删除 +它们可能导致回归。 + +对于那些为 Linux 内核工作而获得报酬的开发者来说,情况并没有什么不同。这些人 +现在贡献了大部分的变更。但是他们的雇主迟早也会停止关注他们的代码或者让程序 +员专注于其他事情。例如,硬件厂商主要通过销售新硬件来赚钱;因此,他们中的不 +少人并没有投入太多时间和精力来维护他们多年前就停止销售的东西的 Linux 内核驱 +动。企业级 Linux 发行商往往持续维护的时间比较长,但在新版本中往往会把对老旧 +和稀有硬件的支持放在一边,以限制范围。一旦公司抛弃了一些代码,往往由业余贡 +献者接手,但正如上面提到的:他们迟早也会放下代码。 + +优先级是一些问题没有被修复的另一个原因,因为维护者相当多的时候是被迫设置这 +些优先级的,因为在 Linux 上工作的时间是有限的。对于业余时间或者雇主给予他们 +的开发人员用于上游内核维护工作的时间也是如此。有时维护人员也会被报告淹没, +即使一个驱动程序几乎完美地工作。为了不被完全缠住,程序员可能别无选择,只能 +对问题报告进行优先级排序而拒绝其中的一些报告。 + +不过这些都不用太过担心,很多驱动都有积极的维护者,他们对尽可能多的解决问题 +相当感兴趣。 + + +结束语 +======= + +与其他免费/自由&开源软件(Free/Libre & Open Source Software,FLOSS)相比, +向 Linux 内核开发者报告问题是很难的:这个文档的长度和复杂性以及字里行间的内 +涵都说明了这一点。但目前就是这样了。这篇文字的主要作者希望通过记录现状来为 +以后改善这种状况打下一些基础。 + + +.. + end-of-content +.. + This English version of this document is maintained by Thorsten Leemhuis + <linux@leemhuis.info>. If you spot a typo or small mistake, feel free to + let him know directly and he'll fix it. For translation problems, please + contact with translators. You are free to do the same in a mostly informal + way if you want to contribute changes to the text, but for copyright + reasons please CC linux-doc@vger.kernel.org and "sign-off" your + contribution as Documentation/process/submitting-patches.rst outlines in + the section "Sign your work - the Developer's Certificate of Origin". +.. + This text is available under GPL-2.0+ or CC-BY-4.0, as stated at the top + of the file. If you want to distribute this text under CC-BY-4.0 only, + please use "The Linux kernel developers" for author attribution and link + this as source: + https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/plain/Documentation/admin-guide/reporting-issues.rst +.. + Note: Only the content of this RST file as found in the Linux kernel sources + is available under CC-BY-4.0, as versions of this text that were processed + (for example by the kernel's build system) might contain content taken from + files which use a more restrictive license. diff --git a/Documentation/translations/zh_CN/admin-guide/reporting-regressions.rst b/Documentation/translations/zh_CN/admin-guide/reporting-regressions.rst new file mode 100644 index 000000000..c0461ee85 --- /dev/null +++ b/Documentation/translations/zh_CN/admin-guide/reporting-regressions.rst @@ -0,0 +1,370 @@ +.. SPDX-License-Identifier: (GPL-2.0+ OR CC-BY-4.0) +.. 【重分发信息参见本文件结尾】 + +.. include:: ../disclaimer-zh_CN.rst + +:Original: Documentation/admin-guide/reporting-regressions.rst + +:译者: + + 吴想成 Wu XiangCheng <bobwxc@email.cn> + + +============ +报告回归问题 +============ + +“*我们拒绝出现回归*”是Linux内核开发的首要规则;Linux的发起者和领军开发者Linus +Torvalds立下了此规则并确保它被落实。 + +本文档描述了这条规则对用户的意义,以及Linux内核开发模型如何确保解决所有被报告 +的回归;关于内核开发者如何处理的方面参见 Documentation/process/handling-regressions.rst 。 + + +本文重点(亦即“太长不看”) +========================== + +#. 如果某程序在原先的Linux内核上运行良好,但在较新版本上效果更差、或者根本不 + 能用,那么你就碰见回归问题了。注意,新内核需要使用类似配置编译;更多相关细 + 节参见下方。 + +#. 按照 Documentation/translations/zh_CN/admin-guide/reporting-issues.rst 中 + 所说的报告你的问题,该文档已经包含了所有关于回归的重要方面,为了方便起见也 + 复制到了下面。两个重点:在报告主题中使用“[REGRESSION]”开头并抄送或转发到 + `回归邮件列表 <https://lore.kernel.org/regressions/>`_ + (regressions@lists.linux.dev)。 + +#. 可选但是建议:在发送或转发报告时,指明该回归发生的起点,以便Linux内核回归 + 追踪机器人“regzbot”可以追踪此问题:: + + #regzbot introduced v5.13..v5.14-rc1 + + +与用户相关的所有Linux内核回归细节 +================================= + + +基本重点 +-------- + + +什么是“回归”以及什么是“无回归规则”? +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +如果某程序/实例在原先的Linux内核上运行良好,但在较新版本上效果更差、或者根本 +不能用,那么你就碰见回归问题了。“无回归规则”不允许出现这种情况。如果偶然发 +生了,导致问题的开发者应当迅速修复问题。 + +也就是说,若Linux 5.13中的WiFi驱动程序运行良好,但是在5.14版本上却不能用、速 +度明显变慢或出现错误,那就出现了回归。如果某正常工作的应用程序突然在新内核上 +出现不稳定,这也是回归;这些问题可能是由于procfs、sysfs或Linux提供给用户空间 +软件的许多其他接口之一的变化。但请记住,前述例子中的5.14需要使用类似于5.13的 +配置构建。这可以用 ``make olddefconfig`` 实现,详细解释见下。 + +注意本节第一句话中的“实例”:即使开发者需要遵循“无回归”规则,但仍可自由地改 +变内核的任何方面,甚至是导出到用户空间的API或ABI,只要别破坏现有的应用程序或 +用例。 + +还需注意,“无回归”规则只限制内核提供给用户空间的接口。它不适用于内核内部接 +口,比如一些外部开发的驱动程序用来插入钩子到内核的模块API。 + +如何报告回归? +~~~~~~~~~~~~~~ + +只需按照 Documentation/translations/zh_CN/admin-guide/reporting-issues.rst 中 +所说的报告你的问题,该文档已经包含了要点。下面几点概述了一下只在回归中重要的 +方面: + + * 在检查可加入讨论的现有报告时,别忘了搜索 `Linux回归邮件列表 + <https://lore.kernel.org/regressions/>`_ 和 `regzbot网页界面 + <https://linux-regtracking.leemhuis.info/regzbot/>`_ 。 + + * 在报告主题的开头加上“[REGRESSION]”。 + + * 在你的报告中明确最后一个正常工作的内核版本和首个出问题的版本。如若可能, + 用二分法尝试找出导致回归的变更,更多细节见下。 + + * 记得把报告发到Linux回归邮件列表(regressions@lists.linux.dev)。 + + * 如果通过邮件报告回归,请抄送回归列表。 + + * 如果你使用某些缺陷追踪器报告回归,请通过邮件转发已提交的报告到回归列表, + 并抄送维护者以及出问题的相关子系统的邮件列表。 + + 如果是稳定版或长期支持版系列(如v5.15.3…v5.15.5)的回归,请记得抄送 + `Linux稳定版邮件列表 <https://lore.kernel.org/stable/>`_ (stable@vger.kernel.org)。 + + 如果你成功地执行了二分,请抄送肇事提交的信息中所有签了“Signed-off-by:”的人。 + +在抄送你的报告到列表时,也请记得通知前述的Linux内核回归追踪机器人。只需在邮件 +中包含如下片段:: + + #regzbot introduced: v5.13..v5.14-rc1 + +Regzbot会就将你的邮件视为在某个特定版本区间的回归报告。上例中即linux v5.13仍 +然正常,而Linux 5.14-rc1是首个您遇到问题的版本。如果你执行了二分以查找导致回 +归的提交,请使用指定肇事提交的id代替:: + + #regzbot introduced: 1f2e3d4c5d + +添加这样的“regzbot命令”对你是有好处的,它会确保报告不会被忽略。如果你省略了 +它,Linux内核的回归跟踪者会把你的回归告诉regzbot,只要你发送了一个副本到回归 +邮件列表。但是回归跟踪者只有一个人,有时不得不休息或甚至偶尔享受可以远离电脑 +的时光(听起来很疯狂)。因此,依赖此人手动将回归添加到 `已追踪且尚未解决的 +Linux内核回归列表 <https://linux-regtracking.leemhuis.info/regzbot/>`_ 和 +regzbot发送的每周回归报告,可能会出现延迟。 这样的延误会导致Linus Torvalds +在决定“继续开发还是发布新版本?”时忽略严重的回归。 + +真的修复了所有的回归吗? +~~~~~~~~~~~~~~~~~~~~~~~~ + +几乎所有都是,只要引起问题的变更(肇事提交)被可靠定位。也有些回归可以不用这 +样,但通常是必须的。 + +谁需要找出回归的根本原因? +~~~~~~~~~~~~~~~~~~~~~~~~~~ + +受影响代码区域的开发者应该自行尝试定位问题所在。但仅靠他们的努力往往是不可 +能做到的,很多问题只发生在开发者的无法接触的其他特定外部环境中——例如特定的 +硬件平台、固件、Linux发行版、系统的配置或应用程序。这就是为什么最终往往是报 +告者定位肇事提交;有时用户甚至需要再运行额外测试以查明确切的根本原因。开发 +者应该提供建议和可能的帮助,以使普通用户更容易完成该流程。 + +如何找到罪魁祸首? +~~~~~~~~~~~~~~~~~~ + +如 Documentation/translations/zh_CN/admin-guide/reporting-issues.rst (简要) +和 Documentation/translations/zh_CN/admin-guide/bug-bisect.rst (详细)中所 +述,执行二分。听起来工作量很大,但大部分情况下很快就能找到罪魁祸首。如果这很 +困难或可靠地重现问题很耗时,请考虑与其他受影响的用户合作,一起缩小搜索范围。 + +当出现回归时我可以向谁寻求建议? +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +发送邮件到回归邮件列表(regressions@lists.linux.dev)同时抄送Linux内核的回归 +跟踪者(regressions@leemhuis.info);如果问题需要保密处理,可以省略列表。 + + +关于回归的更多细节 +------------------ + + +“无回归规则”的目标是什么? +~~~~~~~~~~~~~~~~~~~~~~~~~~ + +用户应该放心升级内核版本,而不必担心有程序可能崩溃。这符合内核开发者的利益, +可以使更新有吸引力:他们不希望用户停留在停止维护或超过一年半的稳定/长期Linux +版本系列上。这也符合所有人的利益,因为 `那些系列可能含有已知的缺陷、安全问题 +或其他后续版本已经修复的问题 +<http://www.kroah.com/log/blog/2018/08/24/what-stable-kernel-should-i-use/>`_ 。 +此外,内核开发者希望使用户测试最新的预发行版或常规发行版变得简单而有吸引力。 +这同样符合所有人的利益,如果新版本出来后很快就有相关报告,会使追踪和修复问题 +更容易。 + +实际中“无回归”规则真的可行吗? +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +这不是句玩笑话,请见Linux创建者和主要开发人员Linus Torvalds在邮件列表中的许 +多发言,其中一些在 Documentation/process/handling-regressions.rst 中被引用。 + +此规则的例外情况极为罕见;之前当开发者认为某个特定的情况有必要援引例外时, +基本都被证明错了。 + +谁来确保“无回归”被落实? +~~~~~~~~~~~~~~~~~~~~~~~~ + +照看和支撑树的子系统维护者应该关心这一点——例如,Linus Torvalds之于主线, +Greg Kroah-Hartman等人之于各种稳定/长期系列。 + +他们都得到了别人的帮助,以确保回归报告不会被遗漏。其中之一是Thorsten +Leemhuis,他目前担任Linux内核的“回归跟踪者”;为了做好这项工作,他使用了 +regzbot——Linux内核回归跟踪机器人。所以这就是为什么要抄送或转发你的报告到 +回归邮件列表来通知这些人,已经最好在你的邮件中包含“regzbot命令”来立即追踪它。 + +回归通常多久能修复? +~~~~~~~~~~~~~~~~~~~~ + +开发者应该尽快修复任何被报告的回归,以提供及时为受影响的用户提供解决方案,并 +防止更多用户遇到问题;然而,开发人员需要花足够的时间和注意力确保回归修复不会 +造成额外的损害。 + +因此,答案取决于各种因素,如回归的影响、存在时长或出现于哪个Linux版本系列。 +但最终,大多数的回归应该在两周内修复。 + +当问题可以通过升级某些软件解决时,是回归吗? +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +基本都是。如果开发人员告诉您其他情况,请咨询上述回归跟踪者。 + +当新内核变慢或能耗增加,是回归吗? +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +是的,但有一些差别。在微型基准测试中变慢5%不太可能被视为回归,除非它也会对 +广泛基准测试的结果产生超过1%的影响。如果有疑问,请寻求建议。 + +当更新Linux时外部内核模块崩溃了,是回归吗? +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +不,因为“无回归”规则仅限于Linux内核提供给用户空间的接口和服务。因此,它不包括 +构建或运行外部开发的内核模块,因为它们在内核空间中运行与挂进内核使用的内部接 +口偶尔会变化。 + +如何处理安全修复引起的回归? +~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +在极为罕见的情况下,安全问题无法在不引起回归的情况下修复;这些修复都被放弃了, +因为它们终究会引起问题。幸运的是这种两难境地基本都可以避免,受影响区域的主要 +开发者以及Linus Torvalds本人通常都会努力在不引入回归的情况下解决安全问题。 + +如果你仍然面临此种情况,请查看邮件列表档案是否有人尽力避免过回归。如果没有, +请报告它;如有疑问,请如上所述寻求建议。 + +当修复回归时不可避免会引入另一个,如何处理? +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +很遗憾这种事确实会出现,但幸运的是并不经常出现;如果发生了,受影响代码区的资 +深开发者应当调查该问题以找到避免回归的解决方法,至少避免它们的影响。如果你遇 +到这样的情况,如上所述:检查之前的讨论是否有人已经尽了最大努力,如有疑问请寻 +求建议。 + +小提示:如果人们在每个开发周期中定期给出主线预发布(即v5.15-rc1或-rc3)以供 +测试,则可以避免这种情况。为了更好地解释,可以设想一个在Linux v5.14和v5.15-rc1 +之间集成的更改,该更改导致了回归,但同时是应用于5.15-rc1的其他改进的强依赖。 +如果有人在5.15发布之前就发现并报告了这个问题,那么所有更改都可以直接撤销,从 +而解决回归问题。而就在几天或几周后,此解决方案变成了不可能,因为一些软件可能 +已经开始依赖于后续更改之一:撤销所有更改将导致上述用户软件出现回归,这是不可 +接受的。 + +若我所依赖的功能在数月前被移除了,是回归吗? +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +是的,但如前节所述,通常很难修复此类回归。因此需要逐案处理。这也是定期测试主 +线预发布对所有人有好处的另一个原因。 + +如果我似乎是唯一受影响的人,是否仍适用“无回归”规则? +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +适用,但仅限于实际使用:Linux开发人员希望能够自由地取消那些只能在阁楼和博物 +馆中找到的硬件的支持。 + +请注意,有时为了取得进展,不得不出现回归——后者也是防止Linux停滞不前所必需 +的。因此如果回归所影响的用户很少,那么为了他们和其他人更大的利益,还是让事情 +过去吧。尤其是存在某种规避回归的简单方法,例如更新一些软件或者使用专门为此目 +的创建的内核参数。 + +回归规则是否也适用于staging树中的代码? +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +不,参见 `适用于所有staging代码配置选项的帮助文本 +<https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/tree/drivers/staging/Kconfig>`_ , +其早已声明:: + + 请注意:这些驱动正在积极开发中,可能无法正常工作,并可能包含会在不久的 + 将来发生变化的用户接口。 + +虽然staging开发人员通常坚持“无回归”的原则,但有时为了取得进展也会违背它。这就 +是为什么当staging树的WiFi驱动被基本推倒重来时,有些用户不得不处理回归(通常可 +以忽略)。 + +为什么较新版本必须“使用相似配置编译”? +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +因为Linux内核开发人员有时会集成已知的会导致回归的变更,但使它们成为可选的,并 +在内核的默认配置下禁用它们。这一技巧允许进步,否则“无回归”规则将导致停滞。 + +例如,试想一个新的可以阻止恶意软件滥用某个内核的接口的安全特性,同时又需要满足 +另一个很罕见的应用程序。上述的方法可使两方都满意:使用这些应用程序的人可以关闭 +新的安全功能,而其他不会遇到麻烦的人可以启用它。 + +如何创建与旧内核相似的配置? +~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +用一个已知良好的内核启动机器,并用 ``make olddefconfig`` 配置新版的Linux。这 +会让内核的构建脚本从正在运行的内核中摘录配置文件(“.config”文件),作为即将编 +译的新版本的基础配置;同时将所有新的配置选项设为默认值,以禁用可能导致回归的 +新功能。 + +如何报告在预编译的普通内核中发现的回归? +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +您需要确保新的内核是用与旧版相似的配置编译(见上文),因为那些构建它们的人可 +能启用了一些已知的与新内核不兼容的特性。如有疑问,请向内核的提供者报告问题并 +寻求建议。 + + +用“regzbot”追踪回归的更多信息 +----------------------------- + +什么是回归追踪?为啥我需要关心它? +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +像“无回归”这样的规则需要有人来确保它们被遵守,否则会被有意/无意打破。历史证 +明了这一点对于Linux内核开发也适用。这就是为什么Linux内核的回归跟踪者Thorsten +Leemhuis,,和另一些人尽力关注所有的回归直到他们解决。他们从未为此获得报酬, +因此这项工作是在尽最大努力的基础上完成的。 + +为什么/如何使用机器人追踪Linux内核回归? +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +由于Linux内核开发过程的分布式和松散结构,完全手动跟踪回归已经被证明是相当困难 +的。因此Linux内核的回归跟踪者开发了regzbot来促进这项工作,其长期目标是尽可能为 +所有相关人员自动化回归跟踪。 + +Regzbot通过监视跟踪的回归报告的回复来工作。此外,它还查找用“Link:”标签引用这 +些报告的补丁;对这些补丁的回复也会被跟踪。结合这些数据,可以很好地了解当前修 +复过程的状态。 + +如何查看regzbot当前追踪的回归? +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +参见 `regzbot在线 <https://linux-regtracking.leemhuis.info/regzbot/>`_ 。 + +何种问题可以由regzbot追踪? +~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +该机器人只为了跟踪回归,因此请不要让regzbot涉及常规问题。但是对于Linux内核的 +回归跟踪者来说,让regzbot跟踪严重问题也可以,如有关挂起、损坏数据或内部错误 +(Panic、Oops、BUG()、warning…)的报告。 + +如何修改被追踪回归的相关信息? +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +在直接或间接回复报告邮件时使用“regzbot命令”即可。最简单的方法是:在“已发送”文 +件夹或邮件列表存档中找到报告,然后使用邮件客户端的“全部回复”功能对其进行回复。 +在该邮件中的独立段落中可使用以下命令之一(即使用空行将这些命令中的一个或多个与 +其余邮件文本分隔开)。 + + * 更新回归引入起点,例如在执行二分之后:: + + #regzbot introduced: 1f2e3d4c5d + + * 设置或更新标题:: + + #regzbot title: foo + + * 监视讨论或bugzilla.kernel.org上有关讨论或修复的工单:: + + #regzbot monitor: https://lore.kernel.org/r/30th.anniversary.repost@klaava.Helsinki.FI/ + #regzbot monitor: https://bugzilla.kernel.org/show_bug.cgi?id=123456789 + + * 标记一个有更多相关细节的地方,例如有关但主题不同的邮件列表帖子或缺陷追踪器中的工单:: + + #regzbot link: https://bugzilla.kernel.org/show_bug.cgi?id=123456789 + + * 标记回归已失效:: + + #regzbot invalid: wasn't a regression, problem has always existed + +Regzbot还支持其他一些主要由开发人员或回归追踪人员使用的命令。命令的更多细节请 +参考 `入门指南 <https://gitlab.com/knurd42/regzbot/-/blob/main/docs/getting_started.md>`_ +和 `参考手册 <https://gitlab.com/knurd42/regzbot/-/blob/main/docs/reference.md>`_ 。 + +.. + 正文结束 +.. + 如本文件开头所述,本文以GPL-2.0+或CC-BY-4.0许可发行。如您想仅在CC-BY-4.0许 + 可下重分发本文,请用“Linux内核开发者”作为作者,并用如下链接作为来源: + https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/plain/Documentation/translations/zh_CN/admin-guide/reporting-regressions.rst +.. + 注意:本RST文件内容只有在来自Linux内核源代码时是使用CC-BY-4.0许可的,因为经 + 过处理的版本(如经内核的构建系统)可能包含来自使用更严格许可证的文件的内容。 diff --git a/Documentation/translations/zh_CN/admin-guide/security-bugs.rst b/Documentation/translations/zh_CN/admin-guide/security-bugs.rst new file mode 100644 index 000000000..b81203917 --- /dev/null +++ b/Documentation/translations/zh_CN/admin-guide/security-bugs.rst @@ -0,0 +1,74 @@ +.. include:: ../disclaimer-zh_CN.rst + +:Original: :doc:`../../../admin-guide/security-bugs` + +:译者: + + 吴想成 Wu XiangCheng <bobwxc@email.cn> + +安全缺陷 +========= + +Linux内核开发人员非常重视安全性。因此我们想知道何时发现了安全漏洞,以便尽快 +修复和披露。请向Linux内核安全团队报告安全漏洞。 + +联络 +----- + +可以通过电子邮件<security@kernel.org>联系Linux内核安全团队。这是一个安全人员 +的私有列表,他们将帮助验证错误报告并开发和发布修复程序。如果您已经有了一个 +修复,请将其包含在您的报告中,这样可以大大加快进程。安全团队可能会从区域维护 +人员那里获得额外的帮助,以理解和修复安全漏洞。 + +与任何缺陷一样,提供的信息越多,诊断和修复就越容易。如果您不清楚哪些信息有用, +请查看“Documentation/translations/zh_CN/admin-guide/reporting-issues.rst”中 +概述的步骤。任何利用漏洞的攻击代码都非常有用,未经报告者同意不会对外发布,除 +非已经公开。 + +请尽可能发送无附件的纯文本电子邮件。如果所有的细节都藏在附件里,那么就很难对 +一个复杂的问题进行上下文引用的讨论。把它想象成一个 +:doc:`常规的补丁提交 <../process/submitting-patches>` (即使你还没有补丁): +描述问题和影响,列出复现步骤,然后给出一个建议的解决方案,所有这些都是纯文本的。 + +披露和限制信息 +--------------- + +安全列表不是公开渠道。为此,请参见下面的协作。 + +一旦开发出了健壮的补丁,发布过程就开始了。对公开的缺陷的修复会立即发布。 + +尽管我们倾向于在未公开缺陷的修复可用时即发布补丁,但应报告者或受影响方的请求, +这可能会被推迟到发布过程开始后的7日内,如果根据缺陷的严重性需要更多的时间, +则可额外延长到14天。推迟发布修复的唯一有效原因是为了适应QA的逻辑和需要发布 +协调的大规模部署。 + +虽然可能与受信任的个人共享受限信息以开发修复,但未经报告者许可,此类信息不会 +与修复程序一起发布或发布在任何其他披露渠道上。这包括但不限于原始错误报告和 +后续讨论(如有)、漏洞、CVE信息或报告者的身份。 + +换句话说,我们唯一感兴趣的是修复缺陷。提交给安全列表的所有其他资料以及对报告 +的任何后续讨论,即使在解除限制之后,也将永久保密。 + +协调 +------ + +对敏感缺陷(例如那些可能导致权限提升的缺陷)的修复可能需要与私有邮件列表 +<linux-distros@vs.openwall.org>进行协调,以便分发供应商做好准备,在公开披露 +上游补丁时发布一个已修复的内核。发行版将需要一些时间来测试建议的补丁,通常 +会要求至少几天的限制,而供应商更新发布更倾向于周二至周四。若合适,安全团队 +可以协助这种协调,或者报告者可以从一开始就包括linux发行版。在这种情况下,请 +记住在电子邮件主题行前面加上“[vs]”,如linux发行版wiki中所述: +<http://oss-security.openwall.org/wiki/mailing-lists/distros#how-to-use-the-lists>。 + +CVE分配 +-------- + +安全团队通常不分配CVE,我们也不需要它们来进行报告或修复,因为这会使过程不必 +要的复杂化,并可能耽误缺陷处理。如果报告者希望在公开披露之前分配一个CVE编号, +他们需要联系上述的私有linux-distros列表。当在提供补丁之前已有这样的CVE编号时, +如报告者愿意,最好在提交消息中提及它。 + +保密协议 +--------- + +Linux内核安全团队不是一个正式的机构实体,因此无法签订任何保密协议。 diff --git a/Documentation/translations/zh_CN/admin-guide/sysrq.rst b/Documentation/translations/zh_CN/admin-guide/sysrq.rst new file mode 100644 index 000000000..8276d70f3 --- /dev/null +++ b/Documentation/translations/zh_CN/admin-guide/sysrq.rst @@ -0,0 +1,280 @@ +.. include:: ../disclaimer-zh_CN.rst + +:Original: Documentation/admin-guide/sysrq.rst + +:翻译: + + 黄军华 Junhua Huang <huang.junhua@zte.com.cn> + +:校译: + + 司延腾 Yanteng Si <siyanteng@loongson.cn> + +.. _cn_admin-guide_sysrq: + +Linux 魔法系统请求键骇客 +======================== + +针对 sysrq.c 的文档说明 + +什么是魔法 SysRq 键? +~~~~~~~~~~~~~~~~~~~~~ + +它是一个你可以输入的具有魔法般的组合键。 +无论内核在做什么,内核都会响应 SysRq 键的输入,除非内核完全卡死。 + +如何使能魔法 SysRq 键? +~~~~~~~~~~~~~~~~~~~~~~~ + +在配置内核时,我们需要设置 'Magic SysRq key (CONFIG_MAGIC_SYSRQ)' 为 'Y'。 +当运行一个编译进 sysrq 功能的内核时,/proc/sys/kernel/sysrq 控制着被 +SysRq 键调用的功能许可。这个文件的默认值由 CONFIG_MAGIC_SYSRQ_DEFAULT_ENABLE +配置符号设定,文件本身默认设置为 1。以下是 /proc/sys/kernel/sysrq 中可能的 +值列表: + + - 0 - 完全不使能 SysRq 键 + - 1 - 使能 SysRq 键的全部功能 + - >1 - 对于允许的 SysRq 键功能的比特掩码(参见下面更详细的功能描述):: + + 2 = 0x2 - 使能对控制台日志记录级别的控制 + 4 = 0x4 - 使能对键盘的控制 (SAK, unraw) + 8 = 0x8 - 使能对进程的调试导出等 + 16 = 0x10 - 使能同步命令 + 32 = 0x20 - 使能重新挂载只读 + 64 = 0x40 - 使能对进程的信号操作 (term, kill, oom-kill) + 128 = 0x80 - 允许重启、断电 + 256 = 0x100 - 允许让所有实时任务变普通任务 + +你可以通过如下命令把值设置到这个文件中:: + + echo "number" >/proc/sys/kernel/sysrq + +这里被写入的 number 可以是 10 进制数,或者是带着 0x 前缀的 16 进制数。 +CONFIG_MAGIC_SYSRQ_DEFAULT_ENABLE 必须是以 16 进制数写入。 + +注意,``/proc/sys/kernel/sysrq`` 的值只影响通过键盘触发 SySRq 的调用,对于 +通过 ``/proc/sysrq-trigger`` 的任何操作调用都是允许的 +(通过具有系统权限的用户)。 + +如何使用魔法 SysRq 键? +~~~~~~~~~~~~~~~~~~~~~~~ + +在 x86 架构上 + 你可以按下键盘组合键 :kbd:`ALT-SysRq-<command key>`。 + + .. note:: + 一些键盘可能没有标识 'SySRq' 键。'SySRq' 键也被当做 'Print Screen'键。 + 同时有些键盘无法处理同时按下这么多键,因此你可以先按下键盘 :kbd:`Alt` 键, + 然后按下键盘 :kbd:`SysRq` 键,再释放键盘 :kbd:`SysRq` 键,之后按下键盘上命令键 + :kbd:`<command key>`,最后释放所有键。 + +在 SPARC 架构上 + 你可以按下键盘组合键 :kbd:`ALT-STOP-<command key>` 。 + +在串行控制台(只针对 PC 类型的标准串口) + 你可以发一个 ``BREAK`` ,然后在 5 秒内发送一个命令键, + 发送 ``BREAK`` 两次将被翻译为一个正常的 BREAK 操作。 + +在 PowerPC 架构上 + 按下键盘组合键 :kbd:`ALT - Print Screen` (或者 :kbd:`F13`) - :kbd:`<命令键>` 。 + :kbd:`Print Screen` (或者 :kbd:`F13`) - :kbd:`<命令键>` 或许也能实现。 + +在其他架构上 + 如果你知道其他架构的组合键,请告诉我,我可以把它们添加到这部分。 + +在所有架构上 + 写一个字符到 /proc/sysrq-trigger 文件,例如:: + + echo t > /proc/sysrq-trigger + +这个命令键 :kbd:`<command key>` 是区分大小写的。 + +什么是命令键? +~~~~~~~~~~~~~~ + +=========== ================================================================ +命令键 功能 +=========== ================================================================ +``b`` 将立即重启系统,不会同步或者卸载磁盘。 + +``c`` 将执行系统 crash,如果配置了系统 crashdump,将执行 crashdump。 + +``d`` 显示所有持有的锁。 + +``e`` 发送 SIGTERM 信号给所有进程,除了 init 进程。 + +``f`` 将调用 oom killer 杀掉一个过度占用内存的进程,如果什么任务都没杀, + 也不会 panic。 + +``g`` kgdb 使用(内核调试器)。 + +``h`` 将会显示帮助。(实际上除了这里列举的键,其他的都将显示帮助, + 但是 ``h`` 容易记住):-) + +``i`` 发送 SIGKILL 给所有进程,除了 init 进程。 + +``j`` 强制性的 “解冻它” - 用于被 FIFREEZE ioctl 操作冻住的文件系统。 + +``k`` 安全访问秘钥(SAK)杀掉在当前虚拟控制台的所有程序,注意:参考 + 下面 SAK 节重要论述。 + +``l`` 显示所有活动 cpu 的栈回溯。 + +``m`` 将导出当前内存信息到你的控制台。 + +``n`` 用于使所有实时任务变成普通任务。 + +``o`` 将关闭系统(如果配置和支持的话)。 + +``p`` 将导出当前寄存器和标志位到控制台。 + +``q`` 将导出每个 cpu 上所有已装备的高精度定时器(不是完整的 + time_list 文件显示的 timers)和所有时钟事件设备的详细信息。 + +``r`` 关闭键盘的原始模式,设置为转换模式。 + +``s`` 将尝试同步所有的已挂载文件系统。 + +``t`` 将导出当前所有任务列表和它们的信息到控制台。 + +``u`` 将尝试重新挂载已挂载文件系统为只读。 + +``v`` 强制恢复帧缓存控制台。 +``v`` 触发 ETM 缓存导出 [ARM 架构特有] + +``w`` 导出处于不可中断状态(阻塞)的任务。 + +``x`` 在 ppc/powerpc 架构上用于 xmon 接口。 + 在 sparc64 架构上用于显示全局的 PMU(性能监控单元)寄存器。 + 在 MIPS 架构上导出所有的 tlb 条目。 + +``y`` 显示全局 cpu 寄存器 [SPARC-64 架构特有] + +``z`` 导出 ftrace 缓存信息 + +``0``-``9`` 设置控制台日志级别,该级别控制什么样的内核信息将被打印到你的 + 控制台。(比如 ``0`` ,将使得只有紧急信息,像 PANICs or OOPSes + 才能到你的控制台。) +=========== ================================================================ + +好了,我能用他们做什么呢? +~~~~~~~~~~~~~~~~~~~~~~~~~~ + +嗯,当你的 X 服务端或者 svgalib 程序崩溃,unraw(r) 非原始模式命令键是非常 +方便的。 + +sak(k)(安全访问秘钥)在你尝试登陆的同时,又想确保当前控制台没有可以获取你的 +密码的特洛伊木马程序运行时是有用的。它会杀掉给定控制台的所有程序,这样你 +就可以确认当前的登陆提示程序是实际来自 init 进程的程序,而不是某些特洛伊 +木马程序。 + +.. important:: + + 在其实际的形式中,在兼容 C2 安全标准的系统上,它不是一个真正的 SAK, + 它也不应该误认为此。 + +似乎其他人发现其可以作为(系统终端联机键)当你想退出一个程序, +同时不会让你切换控制台的方法。(比如,X 服务端或者 svgalib 程序) + +``reboot(b)`` 是个好方法,当你不能关闭机器时,它等同于按下"复位"按钮。 + +``crash(c)`` 可以用于手动触发一个 crashdump,当系统卡住时。 +注意当 crashdump 机制不可用时,这个只是触发一个内核 crash。 + +``sync(s)`` 在拔掉可移动介质之前,或者在使用不提供优雅关机的 +救援 shell 之后很方便 -- 它将确保你的数据被安全地写入磁盘。注意,在你看到 +屏幕上出现 "OK" 和 "Done" 之前,同步还没有发生。 + +``umount(u)`` 可以用来标记文件系统正常卸载,从正在运行的系统角度来看,它们将 +被重新挂载为只读。这个重新挂载动作直到你看到 "OK" 和 "Done" 信息出现在屏幕上 +才算完成。 + +日志级别 ``0`` - ``9`` 用于当你的控制台被大量的内核信息冲击,你不想看见的时候。 +选择 ``0`` 将禁止除了最紧急的内核信息外的所有的内核信息输出到控制台。(但是如果 +syslogd/klogd 进程是运行的,它们仍将被记录。) + +``term(e)`` 和 ``kill(i)`` 用于当你有些有点失控的进程,你无法通过其他方式杀掉 +它们的时候,特别是它正在创建其他进程。 + +"just thaw ``it(j)`` " 用于当你的系统由于一个 FIFREEZE ioctl 调用而产生的文件 +系统冻结,而导致的不响应时。 + +有的时候 SysRq 键在使用它之后,看起来像是“卡住”了,我能做些什么? +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +这也会发生在我这,我发现轻敲键盘两侧的 shift、alt 和 control 键,然后再次敲击 +一个无效的 SysRq 键序列可以解决问题。(比如,像键盘组合键 :kbd:`alt-sysrq-z` ) +切换到另一个虚拟控制台(键盘操作 :kbd:`ALT+Fn` ),然后再切回来应该也有帮助。 + +我敲击了 SysRq 键,但像是什么都没发生,发生了什么错误? +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +有一些键盘对于 SysRq 键设置了不同的键值,而不是提前定义的 99 +(查看在 ``include/uapi/linux/input-event-codes.h`` 文件中 ``KEY_SYSRQ`` 的定义) +或者就根本没有 SysRq 键。在这些场景下,执行 ``showkey -s`` 命令来找到一个合适 +的扫描码序列,然后使用 ``setkeycodes <sequence> 99`` 命令映射这个序列值到通用 +的 SysRq 键编码上(比如 ``setkeycodes e05b 99`` )。最好将这个命令放在启动脚本 +中。 +哦,顺便说一句,你十秒钟不输入任何东西就将退出 “showkey”。 + +我想添加一个 SysRq 键事件到一个模块中,如何去做呢? +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +为了注册一个基础函数到这个表中,首先你必须包含 ``include/linux/sysrq.h`` 头 +文件,这个头文件定义了你所需要的所有东西。然后你必须创建一个 ``sysrq_key_op`` +结构体,然后初始化它,使用如下内容,A) 你将使用的这个键的处理函数, B) 一个 +help_msg 字符串,在 SysRq 键打印帮助信息时将打印出来,C) 一个 action_msg 字 +符串,就在你的处理函数调用前打印出来。你的处理函数必须符合在 'sysrq.h' 文件中 +的函数原型。 + +在 ``sysrq_key_op`` 结构体被创建后,你可以调用内核函数 +``register_sysrq_key(int key, const struct sysrq_key_op *op_p);``, +该函数在表中的 'key' 对应位置内容是空的情况下,将通过 ``op_p`` 指针注册这个操作 +函数到表中 'key' 对应位置上。在模块卸载的时候,你必须调用 +``unregister_sysrq_key(int key, const struct sysrq_key_op *op_p)`` 函数,该函数 +只有在当前该键对应的处理函数被注册到了 'key' 对应位置时,才会移除 'op_p' 指针 +对应的键值操作函数。这是为了防止在你注册之后,该位置被改写的情况。 + +魔法 SysRq 键系统的工作原理是将键对应操作函数注册到键的操作查找表, +该表定义在 'drivers/tty/sysrq.c' 文件中。 +该键表有许多在编译时候就注册进去的操作函数,但是是可变的。 +并且有两个函数作为操作该表的接口被导出:: + + register_sysrq_key 和 unregister_sysrq_key. + +当然,永远不要在表中留下无效指针,即,当你的模块存在调用 register_sysrq_key() +函数,它一定要调用 unregister_sysrq_key() 来清除它使用过的 SysRq 键表条目。 +表中的空指针是安全的。:) + +如果对于某种原因,在 handle_sysrq 调用的处理函数中,你认为有必要调用 +handle_sysrq 函数时,你必须意识到当前你处于一个锁中(你同时也处于一个中断处理 +函数中,这意味着不能睡眠)。所以这时你必须使用 ``__handle_sysrq_nolock`` 替代。 + +当我敲击一个 SysRq 组合键时,只有标题打印出现在控制台? +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +SysRq 键的输出和所有其他控制台输出一样,受制于控制台日志级别控制。 +这意味着,如果内核以发行版内核中常见的 "quiet" 方式启动,则输出可能不会出现在实际 +的控制台上,即使它会出现在 dmesg 缓存中,也可以通过 dmesg 命令和 ``/proc/kmsg`` +文件的消费访问到。作为一个特例,来自 sysrq 命令的标题行将被传递给所有控制台 +使用者,就好像当前日志级别是最大的一样。如果只发出标题头,则几乎可以肯定内核日志 +级别太低。如果你需要控制台上的输出,那么你将需要临时提高控制台日志级别,通过使用 +键盘组合键 :kbd:`alt-sysrq-8` 或者:: + + echo 8 > /proc/sysrq-trigger + +在触发了你感兴趣的 SysRq 键命令后,记得恢复日志级别到正常情况。 + +我有很多问题时,可以请教谁? +~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +请教在内核邮件列表上的人,邮箱: + linux-kernel@vger.kernel.org + +致谢 +~~~~ + +- Mydraal <vulpyne@vulpyne.net> 撰写了该文件 +- Adam Sulmicki <adam@cfar.umd.edu> 进行了更新 +- Jeremy M. Dolan <jmd@turbogeek.org> 在 2001/01/28 10:15:59 进行了更新 +- Crutcher Dunnavant <crutcher+kernel@datastacks.com> 添加键注册部分 diff --git a/Documentation/translations/zh_CN/admin-guide/tainted-kernels.rst b/Documentation/translations/zh_CN/admin-guide/tainted-kernels.rst new file mode 100644 index 000000000..bc51d7cff --- /dev/null +++ b/Documentation/translations/zh_CN/admin-guide/tainted-kernels.rst @@ -0,0 +1,157 @@ +.. include:: ../disclaimer-zh_CN.rst + +:Original: :doc:`../../../admin-guide/tainted-kernels` + +:译者: + + 吴想成 Wu XiangCheng <bobwxc@email.cn> + +受污染的内核 +------------- + +当发生一些在稍后调查问题时可能相关的事件时,内核会将自己标记为“受污染 +(tainted)”的。不用太过担心,大多数情况下运行受污染的内核没有问题;这些信息 +主要在有人想调查某个问题时才有意义的,因为问题的真正原因可能是导致内核受污染 +的事件。这就是为什么来自受污染内核的缺陷报告常常被开发人员忽略,因此请尝试用 +未受污染的内核重现问题。 + +请注意,即使在您消除导致污染的原因(亦即卸载专有内核模块)之后,内核仍将保持 +污染状态,以表示内核仍然不可信。这也是为什么内核在注意到内部问题(“kernel +bug”)、可恢复错误(“kernel oops”)或不可恢复错误(“kernel panic”)时会打印 +受污染状态,并将有关此的调试信息写入日志 ``dmesg`` 输出。也可以通过 +``/proc/`` 中的文件在运行时检查受污染的状态。 + + +BUG、Oops或Panics消息中的污染标志 +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +在顶部以“CPU:”开头的一行中可以找到受污染的状态;内核是否受到污染和原因会显示 +在进程ID(“PID:”)和触发事件命令的缩写名称(“Comm:”)之后:: + + BUG: unable to handle kernel NULL pointer dereference at 0000000000000000 + Oops: 0002 [#1] SMP PTI + CPU: 0 PID: 4424 Comm: insmod Tainted: P W O 4.20.0-0.rc6.fc30 #1 + Hardware name: Red Hat KVM, BIOS 0.5.1 01/01/2011 + RIP: 0010:my_oops_init+0x13/0x1000 [kpanic] + [...] + +如果内核在事件发生时没有被污染,您将在那里看到“Not-tainted:”;如果被污染,那 +么它将是“Tainted:”以及字母或空格。在上面的例子中,它看起来是这样的:: + + Tainted: P W O + +下表解释了这些字符的含义。在本例中,由于加载了专有模块( ``P`` ),出现了 +警告( ``W`` ),并且加载了外部构建的模块( ``O`` ),所以内核早些时候受到 +了污染。要解码其他字符,请使用下表。 + + +解码运行时的污染状态 +~~~~~~~~~~~~~~~~~~~~~ + +在运行时,您可以通过读取 ``cat /proc/sys/kernel/tainted`` 来查询受污染状态。 +如果返回 ``0`` ,则内核没有受到污染;任何其他数字都表示受到污染的原因。解码 +这个数字的最简单方法是使用脚本 ``tools/debugging/kernel-chktaint`` ,您的 +发行版可能会将其作为名为 ``linux-tools`` 或 ``kernel-tools`` 的包的一部分提 +供;如果没有,您可以从 +`git.kernel.org <https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/plain/tools/debugging/kernel-chktaint>`_ +网站下载此脚本并用 ``sh kernel-chktaint`` 执行,它会在上面引用的日志中有类似 +语句的机器上打印这样的内容:: + + Kernel is Tainted for following reasons: + * Proprietary module was loaded (#0) + * Kernel issued warning (#9) + * Externally-built ('out-of-tree') module was loaded (#12) + See Documentation/admin-guide/tainted-kernels.rst in the Linux kernel or + https://www.kernel.org/doc/html/latest/admin-guide/tainted-kernels.html for + a more details explanation of the various taint flags. + Raw taint value as int/string: 4609/'P W O ' + +你也可以试着自己解码这个数字。如果内核被污染的原因只有一个,那么这很简单, +在本例中您可以通过下表找到数字。如果你需要解码有多个原因的数字,因为它是一 +个位域(bitfield),其中每个位表示一个特定类型的污染的存在或不存在,最好让 +前面提到的脚本来处理。但是如果您需要快速看一下,可以使用这个shell命令来检查 +设置了哪些位:: + + $ for i in $(seq 18); do echo $(($i-1)) $(($(cat /proc/sys/kernel/tainted)>>($i-1)&1));done + +污染状态代码表 +~~~~~~~~~~~~~~~ + +=== ===== ====== ======================================================== + 位 日志 数字 内核被污染的原因 +=== ===== ====== ======================================================== + 0 G/P 1 已加载专用模块 + 1 _/F 2 模块被强制加载 + 2 _/S 4 内核运行在不合规范的系统上 + 3 _/R 8 模块被强制卸载 + 4 _/M 16 处理器报告了机器检测异常(MCE) + 5 _/B 32 引用了错误的页或某些意外的页标志 + 6 _/U 64 用户空间应用程序请求的污染 + 7 _/D 128 内核最近死机了,即曾出现OOPS或BUG + 8 _/A 256 ACPI表被用户覆盖 + 9 _/W 512 内核发出警告 + 10 _/C 1024 已加载staging驱动程序 + 11 _/I 2048 已应用平台固件缺陷的解决方案 + 12 _/O 4096 已加载外部构建(“树外”)模块 + 13 _/E 8192 已加载未签名的模块 + 14 _/L 16384 发生软锁定 + 15 _/K 32768 内核已实时打补丁 + 16 _/X 65536 备用污染,为发行版定义并使用 + 17 _/T 131072 内核是用结构随机化插件构建的 +=== ===== ====== ======================================================== + +注:字符 ``_`` 表示空白,以便于阅读表。 + +污染的更详细解释 +~~~~~~~~~~~~~~~~~ + + 0) ``G`` 加载的所有模块都有GPL或兼容许可证, ``P`` 加载了任何专有模块。 + 没有MODULE_LICENSE(模块许可证)或MODULE_LICENSE未被insmod认可为GPL + 兼容的模块被认为是专有的。 + + + 1) ``F`` 任何模块被 ``insmod -f`` 强制加载, ``' '`` 所有模块正常加载。 + + 2) ``S`` 内核运行在不合规范的处理器或系统上:硬件已运行在不受支持的配置中, + 因此无法保证正确执行。内核将被污染,例如: + + - 在x86上:PAE是通过intel CPU(如Pentium M)上的forcepae强制执行的,这些 + CPU不报告PAE,但可能有功能实现,SMP内核在非官方支持的SMP Athlon CPU上 + 运行,MSR被暴露到用户空间中。 + - 在arm上:在某些CPU(如Keystone 2)上运行的内核,没有启用某些内核特性。 + - 在arm64上:CPU之间存在不匹配的硬件特性,引导加载程序以不同的模式引导CPU。 + - 某些驱动程序正在被用在不受支持的体系结构上(例如x86_64以外的其他系统 + 上的scsi/snic,非x86/x86_64/itanium上的scsi/ips,已经损坏了arm64上 + irqchip/irq-gic的固件设置…)。 + + 3) ``R`` 模块被 ``rmmod -f`` 强制卸载, ``' '`` 所有模块都正常卸载。 + + 4) ``M`` 任何处理器报告了机器检测异常, ``' '`` 未发生机器检测异常。 + + 5) ``B`` 页面释放函数发现错误的页面引用或某些意外的页面标志。这表示硬件问题 + 或内核错误;日志中应该有其他信息指示发生此污染的原因。 + + 6) ``U`` 用户或用户应用程序特意请求设置受污染标志,否则应为 ``' '`` 。 + + 7) ``D`` 内核最近死机了,即出现了OOPS或BUG。 + + 8) ``A`` ACPI表被重写。 + + 9) ``W`` 内核之前已发出过警告(尽管有些警告可能会设置更具体的污染标志)。 + + 10) ``C`` 已加载staging驱动程序。 + + 11) ``I`` 内核正在处理平台固件(BIOS或类似软件)中的严重错误。 + + 12) ``O`` 已加载外部构建(“树外”)模块。 + + 13) ``E`` 在支持模块签名的内核中加载了未签名的模块。 + + 14) ``L`` 系统上先前发生过软锁定。 + + 15) ``K`` 内核已经实时打了补丁。 + + 16) ``X`` 备用污染,由Linux发行版定义和使用。 + + 17) ``T`` 内核构建时使用了randstruct插件,它可以有意生成非常不寻常的内核结构 + 布局(甚至是性能病态的布局),这在调试时非常有用。于构建时设置。 diff --git a/Documentation/translations/zh_CN/admin-guide/unicode.rst b/Documentation/translations/zh_CN/admin-guide/unicode.rst new file mode 100644 index 000000000..b0b08d2b6 --- /dev/null +++ b/Documentation/translations/zh_CN/admin-guide/unicode.rst @@ -0,0 +1,170 @@ +.. include:: ../disclaimer-zh_CN.rst + +:Original: Documentation/admin-guide/unicode.rst + +:译者: + + 吴想成 Wu XiangCheng <bobwxc@email.cn> + +Unicode(统一码)支持 +====================== + + (英文版)上次更新:2005-01-17,版本号 1.4 + +此文档由H. Peter Anvin <unicode@lanana.org>管理,是Linux注册名称与编号管理局 +(Linux Assigned Names And Numbers Authority,LANANA)项目的一部分。 +现行版本请见: + + http://www.lanana.org/docs/unicode/admin-guide/unicode.rst + +简介 +----- + +Linux内核代码已被重写以使用Unicode来将字符映射到字体。下载一个Unicode到字体 +(Unicode-to-font)表,八位字符集与UTF-8模式都将改用此字体来显示。 + +这微妙地改变了八位字符表的语义。现在的四个字符表是: + +=============== =============================== ================ +映射代号 映射名称 Escape代码 (G0) +=============== =============================== ================ +LAT1_MAP Latin-1 (ISO 8859-1) ESC ( B +GRAF_MAP DEC VT100 pseudographics ESC ( 0 +IBMPC_MAP IBM code page 437 ESC ( U +USER_MAP User defined ESC ( K +=============== =============================== ================ + +特别是 ESC ( U 不再是“直通字体”,因为字体可能与IBM字符集完全不同。 +例如,即使加载了一个Latin-1字体,也允许使用块图形(block graphics)。 + +请注意,尽管这些代码与ISO 2022类似,但这些代码及其用途都与ISO 2022不匹配; +Linux有两个八位代码(G0和G1),而ISO 2022有四个七位代码(G0-G3)。 + +根据Unicode标准/ISO 10646,U+F000到U+F8FF被保留用于操作系统范围内的分配 +(Unicode标准将其称为“团体区域(Corporate Zone)”,因为这对于Linux是不准确 +的,所以我们称之为“Linux区域”)。选择U+F000作为起点,因为它允许直接映射 +区域以2的大倍数开始(以防需要1024或2048个字符的字体)。这就留下U+E000到 +U+EFFF作为最终用户区。 + +[v1.2]:Unicodes范围从U+F000到U+F7FF已经被硬编码为直接映射到加载的字体, +绕过了翻译表。用户定义的映射现在默认为U+F000到U+F0FF,模拟前述行为。实际上, +此范围可能较短;例如,vgacon只能处理256字符(U+F000..U+F0FF)或512字符 +(U+F000..U+F1FF)字体。 + +Linux 区域中定义的实际字符 +--------------------------- + +此外,还定义了Unicode 1.1.4中不存在的以下字符;这些字符由DEC VT图形映射使用。 +[v1.2]此用法已过时,不应再使用;请参见下文。 + +====== ====================================== +U+F800 DEC VT GRAPHICS HORIZONTAL LINE SCAN 1 +U+F801 DEC VT GRAPHICS HORIZONTAL LINE SCAN 3 +U+F803 DEC VT GRAPHICS HORIZONTAL LINE SCAN 7 +U+F804 DEC VT GRAPHICS HORIZONTAL LINE SCAN 9 +====== ====================================== + +DEC VT220使用6x10字符矩阵,这些字符在DEC VT图形字符集中形成一个平滑的过渡。 +我省略了扫描5行,因为它也被用作块图形字符,因此被编码为U+2500 FORMS LIGHT +HORIZONTAL。 + +[v1.3]:这些字符已正式添加到Unicode 3.2.0中;它们在U+23BA、U+23BB、U+23BC、 +U+23BD处添加。Linux现在使用新值。 + +[v1.2]:添加了以下字符来表示常见的键盘符号,这些符号不太可能被添加到Unicode +中,因为它们非常讨厌地取决于特定供应商。当然,这是糟糕设计的一个好例子。 + +====== ====================================== +U+F810 KEYBOARD SYMBOL FLYING FLAG +U+F811 KEYBOARD SYMBOL PULLDOWN MENU +U+F812 KEYBOARD SYMBOL OPEN APPLE +U+F813 KEYBOARD SYMBOL SOLID APPLE +====== ====================================== + +克林贡(Klingon)语支持 +------------------------ + +1996年,Linux是世界上第一个添加对人工语言克林贡支持的操作系统,克林贡是由 +Marc Okrand为《星际迷航》电视连续剧创造的。这种编码后来被征募Unicode注册表 +(ConScript Unicode Registry,CSUR)采用,并建议(但最终被拒绝)纳入Unicode +平面一。不过,它仍然是Linux区域中的Linux/CSUR私有分配。 + +这种编码已经得到克林贡语言研究所(Klingon Language Institute)的认可。 +有关更多信息,请联系他们: + + http://www.kli.org/ + +由于Linux CZ开头部分的字符大多是dingbats/symbols/forms类型,而且这是一种 +语言,因此根据标准Unicode惯例,我将它放置在16单元的边界上。 + +.. note:: + + 这个范围现在由征募Unicode注册表正式管理。规范性引用文件为: + + https://www.evertype.com/standards/csur/klingon.html + +克林贡语有一个26个字符的字母表,一个10位数的位置数字书写系统,从左到右 +,从上到下书写。 + +克林贡字母的几种字形已经被提出。但是由于这组符号看起来始终是一致的,只有实际 +的形状不同,因此按照标准Unicode惯例,这些差异被认为是字体变体。 + +====== ======================================================= +U+F8D0 KLINGON LETTER A +U+F8D1 KLINGON LETTER B +U+F8D2 KLINGON LETTER CH +U+F8D3 KLINGON LETTER D +U+F8D4 KLINGON LETTER E +U+F8D5 KLINGON LETTER GH +U+F8D6 KLINGON LETTER H +U+F8D7 KLINGON LETTER I +U+F8D8 KLINGON LETTER J +U+F8D9 KLINGON LETTER L +U+F8DA KLINGON LETTER M +U+F8DB KLINGON LETTER N +U+F8DC KLINGON LETTER NG +U+F8DD KLINGON LETTER O +U+F8DE KLINGON LETTER P +U+F8DF KLINGON LETTER Q + - Written <q> in standard Okrand Latin transliteration +U+F8E0 KLINGON LETTER QH + - Written <Q> in standard Okrand Latin transliteration +U+F8E1 KLINGON LETTER R +U+F8E2 KLINGON LETTER S +U+F8E3 KLINGON LETTER T +U+F8E4 KLINGON LETTER TLH +U+F8E5 KLINGON LETTER U +U+F8E6 KLINGON LETTER V +U+F8E7 KLINGON LETTER W +U+F8E8 KLINGON LETTER Y +U+F8E9 KLINGON LETTER GLOTTAL STOP + +U+F8F0 KLINGON DIGIT ZERO +U+F8F1 KLINGON DIGIT ONE +U+F8F2 KLINGON DIGIT TWO +U+F8F3 KLINGON DIGIT THREE +U+F8F4 KLINGON DIGIT FOUR +U+F8F5 KLINGON DIGIT FIVE +U+F8F6 KLINGON DIGIT SIX +U+F8F7 KLINGON DIGIT SEVEN +U+F8F8 KLINGON DIGIT EIGHT +U+F8F9 KLINGON DIGIT NINE + +U+F8FD KLINGON COMMA +U+F8FE KLINGON FULL STOP +U+F8FF KLINGON SYMBOL FOR EMPIRE +====== ======================================================= + +其他虚构和人工字母 +------------------- + +自从分配了克林贡Linux Unicode块之后,John Cowan <jcowan@reutershealth.com> +和 Michael Everson <everson@evertype.com> 建立了一个虚构和人工字母的注册表。 +征募Unicode注册表请访问: + + https://www.evertype.com/standards/csur/ + +所使用的范围位于最终用户区域的低端,因此无法进行规范化分配,但建议希望对虚构 +字母进行编码的人员使用这些代码,以实现互操作性。对于克林贡语,CSUR采用了Linux +编码。CSUR的人正在推动将Tengwar和Cirth添加到Unicode平面一;将克林贡添加到 +Unicode平面一被拒绝,因此上述编码仍然是官方的。 diff --git a/Documentation/translations/zh_CN/arch.rst b/Documentation/translations/zh_CN/arch.rst new file mode 100644 index 000000000..690e173d8 --- /dev/null +++ b/Documentation/translations/zh_CN/arch.rst @@ -0,0 +1,29 @@ +.. SPDX-License-Identifier: GPL-2.0 + +处理器体系结构 +============== + +以下文档提供了具体架构实现的编程细节。 + +.. toctree:: + :maxdepth: 2 + + mips/index + arm64/index + riscv/index + openrisc/index + parisc/index + loongarch/index + +TODOList: + +* arm/index +* ia64/index +* m68k/index +* nios2/index +* powerpc/index +* s390/index +* sh/index +* sparc/index +* x86/index +* xtensa/index diff --git a/Documentation/translations/zh_CN/arm/Booting b/Documentation/translations/zh_CN/arm/Booting new file mode 100644 index 000000000..5ecea0767 --- /dev/null +++ b/Documentation/translations/zh_CN/arm/Booting @@ -0,0 +1,175 @@ +Chinese translated version of Documentation/arm/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. + +Maintainer: Russell King <linux@arm.linux.org.uk> +Chinese maintainer: Fu Wei <tekkamanninja@gmail.com> +--------------------------------------------------------------------- +Documentation/arm/booting.rst 的中文翻译 + +如果想评论或更新本文的内容,请直接联系原文档的维护者。如果你使用英文 +交流有困难的话,也可以向中文版维护者求助。如果本翻译更新不及时或者翻 +译存在问题,请联系中文版维护者。 + +英文版维护者: Russell King <linux@arm.linux.org.uk> +中文版维护者: 傅炜 Fu Wei <tekkamanninja@gmail.com> +中文版翻译者: 傅炜 Fu Wei <tekkamanninja@gmail.com> +中文版校译者: 傅炜 Fu Wei <tekkamanninja@gmail.com> + +以下为正文 +--------------------------------------------------------------------- + + 启动 ARM Linux + ============== + +作者:Russell King +日期:2002年5月18日 + +以下文档适用于 2.4.18-rmk6 及以上版本。 + +为了启动 ARM Linux,你需要一个引导装载程序(boot loader), +它是一个在主内核启动前运行的一个小程序。引导装载程序需要初始化各种 +设备,并最终调用 Linux 内核,将信息传递给内核。 + +从本质上讲,引导装载程序应提供(至少)以下功能: + +1、设置和初始化 RAM。 +2、初始化一个串口。 +3、检测机器的类型(machine type)。 +4、设置内核标签列表(tagged list)。 +5、调用内核映像。 + + +1、设置和初始化 RAM +------------------- + +现有的引导加载程序: 强制 +新开发的引导加载程序: 强制 + +引导装载程序应该找到并初始化系统中所有内核用于保持系统变量数据的 RAM。 +这个操作的执行是设备依赖的。(它可能使用内部算法来自动定位和计算所有 +RAM,或可能使用对这个设备已知的 RAM 信息,还可能使用任何引导装载程序 +设计者想到的匹配方法。) + + +2、初始化一个串口 +----------------------------- + +现有的引导加载程序: 可选、建议 +新开发的引导加载程序: 可选、建议 + +引导加载程序应该初始化并使能一个目标板上的串口。这允许内核串口驱动 +自动检测哪个串口用于内核控制台。(一般用于调试或与目标板通信。) + +作为替代方案,引导加载程序也可以通过标签列表传递相关的'console=' +选项给内核以指定某个串口,而串口数据格式的选项在以下文档中描述: + + Documentation/admin-guide/kernel-parameters.rst。 + + +3、检测机器类型 +-------------------------- + +现有的引导加载程序: 可选 +新开发的引导加载程序: 强制 + +引导加载程序应该通过某些方式检测自身所处的机器类型。这是一个硬件 +代码或通过查看所连接的硬件用某些算法得到,这些超出了本文档的范围。 +引导加载程序最终必须能提供一个 MACH_TYPE_xxx 值给内核。 +(详见 linux/arch/arm/tools/mach-types )。 + +4、设置启动数据 +------------------ + +现有的引导加载程序: 可选、强烈建议 +新开发的引导加载程序: 强制 + +引导加载程序必须提供标签列表或者 dtb 映像以传递配置数据给内核。启动 +数据的物理地址通过寄存器 r2 传递给内核。 + +4a、设置内核标签列表 +-------------------------------- + +bootloader 必须创建和初始化内核标签列表。一个有效的标签列表以 +ATAG_CORE 标签开始,并以 ATAG_NONE 标签结束。ATAG_CORE 标签可以是 +空的,也可以是非空。一个空 ATAG_CORE 标签其 size 域设置为 +‘2’(0x00000002)。ATAG_NONE 标签的 size 域必须设置为零。 + +在列表中可以保存任意数量的标签。对于一个重复的标签是追加到之前标签 +所携带的信息之后,还是会覆盖原来的信息,是未定义的。某些标签的行为 +是前者,其他是后者。 + +bootloader 必须传递一个系统内存的位置和最小值,以及根文件系统位置。 +因此,最小的标签列表如下所示: + + +-----------+ +基地址 -> | ATAG_CORE | | + +-----------+ | + | ATAG_MEM | | 地址增长方向 + +-----------+ | + | ATAG_NONE | | + +-----------+ v + +标签列表应该保存在系统的 RAM 中。 + +标签列表必须置于内核自解压和 initrd'bootp' 程序都不会覆盖的内存区。 +建议放在 RAM 的头 16KiB 中。 + +4b、设置设备树 +------------------------- + +bootloader 必须以 64bit 地址对齐的形式加载一个设备树映像(dtb)到系统 +RAM 中,并用启动数据初始化它。dtb 格式在文档 +https://www.devicetree.org/specifications/ 中。内核将会在 +dtb 物理地址处查找 dtb 魔数值(0xd00dfeed),以确定 dtb 是否已经代替 +标签列表被传递进来。 + +bootloader 必须传递一个系统内存的位置和最小值,以及根文件系统位置。 +dtb 必须置于内核自解压不会覆盖的内存区。建议将其放置于 RAM 的头 16KiB +中。但是不可将其放置于“0”物理地址处,因为内核认为:r2 中为 0,意味着 +没有标签列表和 dtb 传递过来。 + +5、调用内核映像 +--------------------------- + +现有的引导加载程序: 强制 +新开发的引导加载程序: 强制 + +调用内核映像 zImage 有两个选择。如果 zImge 保存在 flash 中,且是为了 +在 flash 中直接运行而被正确链接的。这样引导加载程序就可以在 flash 中 +直接调用 zImage。 + +zImage 也可以被放在系统 RAM(任意位置)中被调用。注意:内核使用映像 +基地址的前 16KB RAM 空间来保存页表。建议将映像置于 RAM 的 32KB 处。 + +对于以上任意一种情况,都必须符合以下启动状态: + +- 停止所有 DMA 设备,这样内存数据就不会因为虚假网络包或磁盘数据而被破坏。 + 这可能可以节省你许多的调试时间。 + +- CPU 寄存器配置 + r0 = 0, + r1 = (在上面 3 中获取的)机器类型码。 + r2 = 标签列表在系统 RAM 中的物理地址,或 + 设备树块(dtb)在系统 RAM 中的物理地址 + +- CPU 模式 + 所有形式的中断必须被禁止 (IRQs 和 FIQs) + CPU 必须处于 SVC 模式。(对于 Angel 调试有特例存在) + +- 缓存,MMUs + MMU 必须关闭。 + 指令缓存开启或关闭都可以。 + 数据缓存必须关闭。 + +- 引导加载程序应该通过直接跳转到内核映像的第一条指令来调用内核映像。 + + 对于支持 ARM 指令集的 CPU,跳入内核入口时必须处在 ARM 状态,即使 + 对于 Thumb-2 内核也是如此。 + + 对于仅支持 Thumb 指令集的 CPU,比如 Cortex-M 系列的 CPU,跳入 + 内核入口时必须处于 Thumb 状态。 diff --git a/Documentation/translations/zh_CN/arm/kernel_user_helpers.txt b/Documentation/translations/zh_CN/arm/kernel_user_helpers.txt new file mode 100644 index 000000000..99af43639 --- /dev/null +++ b/Documentation/translations/zh_CN/arm/kernel_user_helpers.txt @@ -0,0 +1,284 @@ +Chinese translated version of Documentation/arm/kernel_user_helpers.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: Nicolas Pitre <nicolas.pitre@linaro.org> + Dave Martin <dave.martin@linaro.org> +Chinese maintainer: Fu Wei <tekkamanninja@gmail.com> +--------------------------------------------------------------------- +Documentation/arm/kernel_user_helpers.rst 的中文翻译 + +如果想评论或更新本文的内容,请直接联系原文档的维护者。如果你使用英文 +交流有困难的话,也可以向中文版维护者求助。如果本翻译更新不及时或者翻 +译存在问题,请联系中文版维护者。 +英文版维护者: Nicolas Pitre <nicolas.pitre@linaro.org> + Dave Martin <dave.martin@linaro.org> +中文版维护者: 傅炜 Fu Wei <tekkamanninja@gmail.com> +中文版翻译者: 傅炜 Fu Wei <tekkamanninja@gmail.com> +中文版校译者: 宋冬生 Dongsheng Song <dongshneg.song@gmail.com> + 傅炜 Fu Wei <tekkamanninja@gmail.com> + + +以下为正文 +--------------------------------------------------------------------- +内核提供的用户空间辅助代码 +========================= + +在内核内存空间的固定地址处,有一个由内核提供并可从用户空间访问的代码 +段。它用于向用户空间提供因在许多 ARM CPU 中未实现的特性和/或指令而需 +内核提供帮助的某些操作。这些代码直接在用户模式下执行的想法是为了获得 +最佳效率,但那些与内核计数器联系过于紧密的部分,则被留给了用户库实现。 +事实上,此代码甚至可能因不同的 CPU 而异,这取决于其可用的指令集或它 +是否为 SMP 系统。换句话说,内核保留在不作出警告的情况下根据需要更改 +这些代码的权利。只有本文档描述的入口及其结果是保证稳定的。 + +这与完全成熟的 VDSO 实现不同(但两者并不冲突),尽管如此,VDSO 可阻止 +某些通过常量高效跳转到那些代码段的汇编技巧。且由于那些代码段在返回用户 +代码前仅使用少量的代码周期,则一个 VDSO 间接远程调用将会在这些简单的 +操作上增加一个可测量的开销。 + +在对那些拥有原生支持的新型处理器进行代码优化时,仅在已为其他操作使用 +了类似的新增指令,而导致二进制结果已与早期 ARM 处理器不兼容的情况下, +用户空间才应绕过这些辅助代码,并在内联函数中实现这些操作(无论是通过 +编译器在代码中直接放置,还是作为库函数调用实现的一部分)。也就是说, +如果你编译的代码不会为了其他目的使用新指令,则不要仅为了避免使用这些 +内核辅助代码,导致二进制程序无法在早期处理器上运行。 + +新的辅助代码可能随着时间的推移而增加,所以新内核中的某些辅助代码在旧 +内核中可能不存在。因此,程序必须在对任何辅助代码调用假设是安全之前, +检测 __kuser_helper_version 的值(见下文)。理想情况下,这种检测应该 +只在进程启动时执行一次;如果内核版本不支持所需辅助代码,则该进程可尽早 +中止执行。 + +kuser_helper_version +-------------------- + +位置: 0xffff0ffc + +参考声明: + + extern int32_t __kuser_helper_version; + +定义: + + 这个区域包含了当前运行内核实现的辅助代码版本号。用户空间可以通过读 + 取此版本号以确定特定的辅助代码是否存在。 + +使用范例: + +#define __kuser_helper_version (*(int32_t *)0xffff0ffc) + +void check_kuser_version(void) +{ + if (__kuser_helper_version < 2) { + fprintf(stderr, "can't do atomic operations, kernel too old\n"); + abort(); + } +} + +注意: + + 用户空间可以假设这个域的值不会在任何单个进程的生存期内改变。也就 + 是说,这个域可以仅在库的初始化阶段或进程启动阶段读取一次。 + +kuser_get_tls +------------- + +位置: 0xffff0fe0 + +参考原型: + + void * __kuser_get_tls(void); + +输入: + + lr = 返回地址 + +输出: + + r0 = TLS 值 + +被篡改的寄存器: + + 无 + +定义: + + 获取之前通过 __ARM_NR_set_tls 系统调用设置的 TLS 值。 + +使用范例: + +typedef void * (__kuser_get_tls_t)(void); +#define __kuser_get_tls (*(__kuser_get_tls_t *)0xffff0fe0) + +void foo() +{ + void *tls = __kuser_get_tls(); + printf("TLS = %p\n", tls); +} + +注意: + + - 仅在 __kuser_helper_version >= 1 时,此辅助代码存在 + (从内核版本 2.6.12 开始)。 + +kuser_cmpxchg +------------- + +位置: 0xffff0fc0 + +参考原型: + + int __kuser_cmpxchg(int32_t oldval, int32_t newval, volatile int32_t *ptr); + +输入: + + r0 = oldval + r1 = newval + r2 = ptr + lr = 返回地址 + +输出: + + r0 = 成功代码 (零或非零) + C flag = 如果 r0 == 0 则置 1,如果 r0 != 0 则清零。 + +被篡改的寄存器: + + r3, ip, flags + +定义: + + 仅在 *ptr 为 oldval 时原子保存 newval 于 *ptr 中。 + 如果 *ptr 被改变,则返回值为零,否则为非零值。 + 如果 *ptr 被改变,则 C flag 也会被置 1,以实现调用代码中的汇编 + 优化。 + +使用范例: + +typedef int (__kuser_cmpxchg_t)(int oldval, int newval, volatile int *ptr); +#define __kuser_cmpxchg (*(__kuser_cmpxchg_t *)0xffff0fc0) + +int atomic_add(volatile int *ptr, int val) +{ + int old, new; + + do { + old = *ptr; + new = old + val; + } while(__kuser_cmpxchg(old, new, ptr)); + + return new; +} + +注意: + + - 这个例程已根据需要包含了内存屏障。 + + - 仅在 __kuser_helper_version >= 2 时,此辅助代码存在 + (从内核版本 2.6.12 开始)。 + +kuser_memory_barrier +-------------------- + +位置: 0xffff0fa0 + +参考原型: + + void __kuser_memory_barrier(void); + +输入: + + lr = 返回地址 + +输出: + + 无 + +被篡改的寄存器: + + 无 + +定义: + + 应用于任何需要内存屏障以防止手动数据修改带来的一致性问题,以及 + __kuser_cmpxchg 中。 + +使用范例: + +typedef void (__kuser_dmb_t)(void); +#define __kuser_dmb (*(__kuser_dmb_t *)0xffff0fa0) + +注意: + + - 仅在 __kuser_helper_version >= 3 时,此辅助代码存在 + (从内核版本 2.6.15 开始)。 + +kuser_cmpxchg64 +--------------- + +位置: 0xffff0f60 + +参考原型: + + int __kuser_cmpxchg64(const int64_t *oldval, + const int64_t *newval, + volatile int64_t *ptr); + +输入: + + r0 = 指向 oldval + r1 = 指向 newval + r2 = 指向目标值 + lr = 返回地址 + +输出: + + r0 = 成功代码 (零或非零) + C flag = 如果 r0 == 0 则置 1,如果 r0 != 0 则清零。 + +被篡改的寄存器: + + r3, lr, flags + +定义: + + 仅在 *ptr 等于 *oldval 指向的 64 位值时,原子保存 *newval + 指向的 64 位值于 *ptr 中。如果 *ptr 被改变,则返回值为零, + 否则为非零值。 + + 如果 *ptr 被改变,则 C flag 也会被置 1,以实现调用代码中的汇编 + 优化。 + +使用范例: + +typedef int (__kuser_cmpxchg64_t)(const int64_t *oldval, + const int64_t *newval, + volatile int64_t *ptr); +#define __kuser_cmpxchg64 (*(__kuser_cmpxchg64_t *)0xffff0f60) + +int64_t atomic_add64(volatile int64_t *ptr, int64_t val) +{ + int64_t old, new; + + do { + old = *ptr; + new = old + val; + } while(__kuser_cmpxchg64(&old, &new, ptr)); + + return new; +} + +注意: + + - 这个例程已根据需要包含了内存屏障。 + + - 由于这个过程的代码长度(此辅助代码跨越 2 个常规的 kuser “槽”), + 因此 0xffff0f80 不被作为有效的入口点。 + + - 仅在 __kuser_helper_version >= 5 时,此辅助代码存在 + (从内核版本 3.1 开始)。 diff --git a/Documentation/translations/zh_CN/arm64/amu.rst b/Documentation/translations/zh_CN/arm64/amu.rst new file mode 100644 index 000000000..ab7180f91 --- /dev/null +++ b/Documentation/translations/zh_CN/arm64/amu.rst @@ -0,0 +1,100 @@ +.. include:: ../disclaimer-zh_CN.rst + +:Original: :ref:`Documentation/arm64/amu.rst <amu_index>` + +Translator: Bailu Lin <bailu.lin@vivo.com> + +================================== +AArch64 Linux 中扩展的活动监控单元 +================================== + +作者: Ionela Voinescu <ionela.voinescu@arm.com> + +日期: 2019-09-10 + +本文档简要描述了 AArch64 Linux 支持的活动监控单元的规范。 + + +架构总述 +-------- + +活动监控是 ARMv8.4 CPU 架构引入的一个可选扩展特性。 + +活动监控单元(在每个 CPU 中实现)为系统管理提供了性能计数器。既可以通 +过系统寄存器的方式访问计数器,同时也支持外部内存映射的方式访问计数器。 + +AMUv1 架构实现了一个由4个固定的64位事件计数器组成的计数器组。 + + - CPU 周期计数器:同 CPU 的频率增长 + - 常量计数器:同固定的系统时钟频率增长 + - 淘汰指令计数器: 同每次架构指令执行增长 + - 内存停顿周期计数器:计算由在时钟域内的最后一级缓存中未命中而引起 + 的指令调度停顿周期数 + +当处于 WFI 或者 WFE 状态时,计数器不会增长。 + +AMU 架构提供了一个高达16位的事件计数器空间,未来新的 AMU 版本中可能 +用它来实现新增的事件计数器。 + +另外,AMUv1 实现了一个多达16个64位辅助事件计数器的计数器组。 + +冷复位时所有的计数器会清零。 + + +基本支持 +-------- + +内核可以安全地运行在支持 AMU 和不支持 AMU 的 CPU 组合中。 +因此,当配置 CONFIG_ARM64_AMU_EXTN 后我们无条件使能后续 +(secondary or hotplugged) CPU 检测和使用这个特性。 + +当在 CPU 上检测到该特性时,我们会标记为特性可用但是不能保证计数器的功能, +仅表明有扩展属性。 + +固件(代码运行在高异常级别,例如 arm-tf )需支持以下功能: + + - 提供低异常级别(EL2 和 EL1)访问 AMU 寄存器的能力。 + - 使能计数器。如果未使能,它的值应为 0。 + - 在从电源关闭状态启动 CPU 前或后保存或者恢复计数器。 + +当使用使能了该特性的内核启动但固件损坏时,访问计数器寄存器可能会遭遇 +panic 或者死锁。即使未发现这些症状,计数器寄存器返回的数据结果并不一 +定能反映真实情况。通常,计数器会返回 0,表明他们未被使能。 + +如果固件没有提供适当的支持最好关闭 CONFIG_ARM64_AMU_EXTN。 +值得注意的是,出于安全原因,不要绕过 AMUSERRENR_EL0 设置而捕获从 +EL0(用户空间) 访问 EL1(内核空间)。 因此,固件应该确保访问 AMU寄存器 +不会困在 EL2或EL3。 + +AMUv1 的固定计数器可以通过如下系统寄存器访问: + + - SYS_AMEVCNTR0_CORE_EL0 + - SYS_AMEVCNTR0_CONST_EL0 + - SYS_AMEVCNTR0_INST_RET_EL0 + - SYS_AMEVCNTR0_MEM_STALL_EL0 + +特定辅助计数器可以通过 SYS_AMEVCNTR1_EL0(n) 访问,其中n介于0到15。 + +详细信息定义在目录:arch/arm64/include/asm/sysreg.h。 + + +用户空间访问 +------------ + +由于以下原因,当前禁止从用户空间访问 AMU 的寄存器: + + - 安全因数:可能会暴露处于安全模式执行的代码信息。 + - 意愿:AMU 是用于系统管理的。 + +同样,该功能对用户空间不可见。 + + +虚拟化 +------ + +由于以下原因,当前禁止从 KVM 客户端的用户空间(EL0)和内核空间(EL1) +访问 AMU 的寄存器: + + - 安全因数:可能会暴露给其他客户端或主机端执行的代码信息。 + +任何试图访问 AMU 寄存器的行为都会触发一个注册在客户端的未定义异常。 diff --git a/Documentation/translations/zh_CN/arm64/booting.txt b/Documentation/translations/zh_CN/arm64/booting.txt new file mode 100644 index 000000000..5b0164132 --- /dev/null +++ b/Documentation/translations/zh_CN/arm64/booting.txt @@ -0,0 +1,246 @@ +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 (保留,将来可能使用) diff --git a/Documentation/translations/zh_CN/arm64/elf_hwcaps.rst b/Documentation/translations/zh_CN/arm64/elf_hwcaps.rst new file mode 100644 index 000000000..9aa4637ea --- /dev/null +++ b/Documentation/translations/zh_CN/arm64/elf_hwcaps.rst @@ -0,0 +1,240 @@ +.. 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。 diff --git a/Documentation/translations/zh_CN/arm64/hugetlbpage.rst b/Documentation/translations/zh_CN/arm64/hugetlbpage.rst new file mode 100644 index 000000000..13304d269 --- /dev/null +++ b/Documentation/translations/zh_CN/arm64/hugetlbpage.rst @@ -0,0 +1,45 @@ +.. 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 + ====== ======== ==== ======== === diff --git a/Documentation/translations/zh_CN/arm64/index.rst b/Documentation/translations/zh_CN/arm64/index.rst new file mode 100644 index 000000000..57dc5de5c --- /dev/null +++ b/Documentation/translations/zh_CN/arm64/index.rst @@ -0,0 +1,19 @@ +.. 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 diff --git a/Documentation/translations/zh_CN/arm64/legacy_instructions.txt b/Documentation/translations/zh_CN/arm64/legacy_instructions.txt new file mode 100644 index 000000000..e295cf75f --- /dev/null +++ b/Documentation/translations/zh_CN/arm64/legacy_instructions.txt @@ -0,0 +1,72 @@ +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 (不支持混合字节序) 在使能这个特性后被热插入系统, +在应用中可能会出现不可预期的结果。 diff --git a/Documentation/translations/zh_CN/arm64/memory.txt b/Documentation/translations/zh_CN/arm64/memory.txt new file mode 100644 index 000000000..be20f8228 --- /dev/null +++ b/Documentation/translations/zh_CN/arm64/memory.txt @@ -0,0 +1,114 @@ +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 中映射的内核对象 diff --git a/Documentation/translations/zh_CN/arm64/perf.rst b/Documentation/translations/zh_CN/arm64/perf.rst new file mode 100644 index 000000000..9bf21d73f --- /dev/null +++ b/Documentation/translations/zh_CN/arm64/perf.rst @@ -0,0 +1,86 @@ +.. 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 系统没有停电窗口。 diff --git a/Documentation/translations/zh_CN/arm64/silicon-errata.txt b/Documentation/translations/zh_CN/arm64/silicon-errata.txt new file mode 100644 index 000000000..440c59ac7 --- /dev/null +++ b/Documentation/translations/zh_CN/arm64/silicon-errata.txt @@ -0,0 +1,74 @@ +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 | diff --git a/Documentation/translations/zh_CN/arm64/tagged-pointers.txt b/Documentation/translations/zh_CN/arm64/tagged-pointers.txt new file mode 100644 index 000000000..77ac3548a --- /dev/null +++ b/Documentation/translations/zh_CN/arm64/tagged-pointers.txt @@ -0,0 +1,52 @@ +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” 的扩展符。 diff --git a/Documentation/translations/zh_CN/core-api/assoc_array.rst b/Documentation/translations/zh_CN/core-api/assoc_array.rst new file mode 100644 index 000000000..3649bf0d1 --- /dev/null +++ b/Documentation/translations/zh_CN/core-api/assoc_array.rst @@ -0,0 +1,473 @@ +.. 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读锁,旧的上层建筑就不 +应该在他们身上消失。 diff --git a/Documentation/translations/zh_CN/core-api/boot-time-mm.rst b/Documentation/translations/zh_CN/core-api/boot-time-mm.rst new file mode 100644 index 000000000..9e81dbec7 --- /dev/null +++ b/Documentation/translations/zh_CN/core-api/boot-time-mm.rst @@ -0,0 +1,49 @@ +.. 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 diff --git a/Documentation/translations/zh_CN/core-api/cachetlb.rst b/Documentation/translations/zh_CN/core-api/cachetlb.rst new file mode 100644 index 000000000..b4a76ec75 --- /dev/null +++ b/Documentation/translations/zh_CN/core-api/cachetlb.rst @@ -0,0 +1,333 @@ +.. 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区域的数据是必要的。 diff --git a/Documentation/translations/zh_CN/core-api/circular-buffers.rst b/Documentation/translations/zh_CN/core-api/circular-buffers.rst new file mode 100644 index 000000000..694ad8e61 --- /dev/null +++ b/Documentation/translations/zh_CN/core-api/circular-buffers.rst @@ -0,0 +1,210 @@ +.. 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。 diff --git a/Documentation/translations/zh_CN/core-api/cpu_hotplug.rst b/Documentation/translations/zh_CN/core-api/cpu_hotplug.rst new file mode 100644 index 000000000..4772a900c --- /dev/null +++ b/Documentation/translations/zh_CN/core-api/cpu_hotplug.rst @@ -0,0 +1,667 @@ +.. 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 diff --git a/Documentation/translations/zh_CN/core-api/genalloc.rst b/Documentation/translations/zh_CN/core-api/genalloc.rst new file mode 100644 index 000000000..3c78452aa --- /dev/null +++ b/Documentation/translations/zh_CN/core-api/genalloc.rst @@ -0,0 +1,109 @@ +.. 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 diff --git a/Documentation/translations/zh_CN/core-api/generic-radix-tree.rst b/Documentation/translations/zh_CN/core-api/generic-radix-tree.rst new file mode 100644 index 000000000..eacd1d2eb --- /dev/null +++ b/Documentation/translations/zh_CN/core-api/generic-radix-tree.rst @@ -0,0 +1,23 @@ +.. 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 diff --git a/Documentation/translations/zh_CN/core-api/genericirq.rst b/Documentation/translations/zh_CN/core-api/genericirq.rst new file mode 100644 index 000000000..05ccb954c --- /dev/null +++ b/Documentation/translations/zh_CN/core-api/genericirq.rst @@ -0,0 +1,409 @@ +.. 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 diff --git a/Documentation/translations/zh_CN/core-api/gfp_mask-from-fs-io.rst b/Documentation/translations/zh_CN/core-api/gfp_mask-from-fs-io.rst new file mode 100644 index 000000000..75d2997e9 --- /dev/null +++ b/Documentation/translations/zh_CN/core-api/gfp_mask-from-fs-io.rst @@ -0,0 +1,66 @@ +.. 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,并加上注释来解释问题。 diff --git a/Documentation/translations/zh_CN/core-api/idr.rst b/Documentation/translations/zh_CN/core-api/idr.rst new file mode 100644 index 000000000..97a16e76b --- /dev/null +++ b/Documentation/translations/zh_CN/core-api/idr.rst @@ -0,0 +1,80 @@ +.. 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 diff --git a/Documentation/translations/zh_CN/core-api/index.rst b/Documentation/translations/zh_CN/core-api/index.rst new file mode 100644 index 000000000..37756d240 --- /dev/null +++ b/Documentation/translations/zh_CN/core-api/index.rst @@ -0,0 +1,147 @@ +.. 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 + +Todolist: + + + + this_cpu_ops + 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` diff --git a/Documentation/translations/zh_CN/core-api/irq/concepts.rst b/Documentation/translations/zh_CN/core-api/irq/concepts.rst new file mode 100644 index 000000000..9957f0453 --- /dev/null +++ b/Documentation/translations/zh_CN/core-api/irq/concepts.rst @@ -0,0 +1,26 @@ +.. 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是赋予这种额外含义的一个典型例子。 diff --git a/Documentation/translations/zh_CN/core-api/irq/index.rst b/Documentation/translations/zh_CN/core-api/irq/index.rst new file mode 100644 index 000000000..ba6acc4b4 --- /dev/null +++ b/Documentation/translations/zh_CN/core-api/irq/index.rst @@ -0,0 +1,22 @@ +.. 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 diff --git a/Documentation/translations/zh_CN/core-api/irq/irq-affinity.rst b/Documentation/translations/zh_CN/core-api/irq/irq-affinity.rst new file mode 100644 index 000000000..36b085226 --- /dev/null +++ b/Documentation/translations/zh_CN/core-api/irq/irq-affinity.rst @@ -0,0 +1,78 @@ +.. 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的位掩码来追踪其相关的一个。 diff --git a/Documentation/translations/zh_CN/core-api/irq/irq-domain.rst b/Documentation/translations/zh_CN/core-api/irq/irq-domain.rst new file mode 100644 index 000000000..9174fce12 --- /dev/null +++ b/Documentation/translations/zh_CN/core-api/irq/irq-domain.rst @@ -0,0 +1,243 @@ +.. 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中暴露出来。 diff --git a/Documentation/translations/zh_CN/core-api/irq/irqflags-tracing.rst b/Documentation/translations/zh_CN/core-api/irq/irqflags-tracing.rst new file mode 100644 index 000000000..9af50b4b8 --- /dev/null +++ b/Documentation/translations/zh_CN/core-api/irq/irqflags-tracing.rst @@ -0,0 +1,47 @@ +.. 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的错 + 误而崩溃。(除非通过修改不该修改的条件来更改汇编或寄存器而破坏其他代码) diff --git a/Documentation/translations/zh_CN/core-api/kernel-api.rst b/Documentation/translations/zh_CN/core-api/kernel-api.rst new file mode 100644 index 000000000..c22662679 --- /dev/null +++ b/Documentation/translations/zh_CN/core-api/kernel-api.rst @@ -0,0 +1,372 @@ +.. 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 diff --git a/Documentation/translations/zh_CN/core-api/kobject.rst b/Documentation/translations/zh_CN/core-api/kobject.rst new file mode 100644 index 000000000..0747b472f --- /dev/null +++ b/Documentation/translations/zh_CN/core-api/kobject.rst @@ -0,0 +1,379 @@ +.. include:: ../disclaimer-zh_CN.rst + +:Original: Documentation/core-api/kobject.rst + +:翻译: + + 司延腾 Yanteng Si <siyanteng@loongson.cn> + +.. _cn_core_api_kobject.rst: + +======================================================= +关于kobjects、ksets和ktypes的一切你没想过需要了解的东西 +======================================================= + +:作者: Greg Kroah-Hartman <gregkh@linuxfoundation.org> +:最后一次更新: December 19, 2007 + +根据Jon Corbet于2003年10月1日为lwn.net撰写的原创文章改编,网址是: +https://lwn.net/Articles/51437/ + +理解驱动模型和建立在其上的kobject抽象的部分的困难在于,没有明显的切入点。 +处理kobjects需要理解一些不同的类型,所有这些类型都会相互引用。为了使事情 +变得更简单,我们将多路并进,从模糊的术语开始,并逐渐增加细节。那么,先来 +了解一些我们将要使用的术语的简明定义吧。 + + - 一个kobject是一个kobject结构体类型的对象。Kobjects有一个名字和一个 + 引用计数。一个kobject也有一个父指针(允许对象被排列成层次结构),一个 + 特定的类型,并且,通常在sysfs虚拟文件系统中表示。 + + Kobjects本身通常并不引人关注;相反它们常常被嵌入到其他包含真正引人注目 + 的代码的结构体中。 + + 任何结构体都 **不应该** 有一个以上的kobject嵌入其中。如果有的话,对象的引用计 + 数肯定会被打乱,而且不正确,你的代码就会出现错误。所以不要这样做。 + + - ktype是嵌入一个kobject的对象的类型。每个嵌入kobject的结构体都需要一个 + 相应的ktype。ktype控制着kobject在被创建和销毁时的行为。 + + - 一个kset是一组kobjects。这些kobjects可以是相同的ktype或者属于不同的 + ktype。kset是kobjects集合的基本容器类型。Ksets包含它们自己的kobjects, + 但你可以安全地忽略这个实现细节,因为kset的核心代码会自动处理这个kobject。 + + 当你看到一个下面全是其他目录的sysfs目录时,通常这些目录中的每一个都对应 + 于同一个kset中的一个kobject。 + + 我们将研究如何创建和操作所有这些类型。将采取一种自下而上的方法,所以我们 + 将回到kobjects。 + + +嵌入kobjects +============= + +内核代码很少创建孤立的kobject,只有一个主要的例外,下面会解释。相反, +kobjects被用来控制对一个更大的、特定领域的对象的访问。为此,kobjects会被 +嵌入到其他结构中。如果你习惯于用面向对象的术语来思考问题,那么kobjects可 +以被看作是一个顶级的抽象类,其他的类都是从它派生出来的。一个kobject实现了 +一系列的功能,这些功能本身并不特别有用,但在其他对象中却很好用。C语言不允 +许直接表达继承,所以必须使用其他技术——比如结构体嵌入。 + +(对于那些熟悉内核链表实现的人来说,这类似于“list_head”结构本身很少有用, +但总是被嵌入到感兴趣的更大的对象中)。 + +例如, ``drivers/uio/uio.c`` 中的IO代码有一个结构体,定义了与uio设备相 +关的内存区域:: + + struct uio_map { + struct kobject kobj; + struct uio_mem *mem; + }; + +如果你有一个uio_map结构体,找到其嵌入的kobject只是一个使用kobj成员的问题。 +然而,与kobjects一起工作的代码往往会遇到相反的问题:给定一个结构体kobject +的指针,指向包含结构体的指针是什么?你必须避免使用一些技巧(比如假设 +kobject在结构的开头),相反,你得使用container_of()宏,其可以在 ``<linux/kernel.h>`` +中找到:: + + container_of(ptr, type, member) + +其中: + + * ``ptr`` 是一个指向嵌入kobject的指针, + * ``type`` 是包含结构体的类型, + * ``member`` 是 ``指针`` 所指向的结构体域的名称。 + +container_of()的返回值是一个指向相应容器类型的指针。因此,例如,一个嵌入到 +uio_map结构 **中** 的kobject结构体的指针kp可以被转换为一个指向 **包含** uio_map +结构体的指针,方法是:: + + struct uio_map *u_map = container_of(kp, struct uio_map, kobj); + +为了方便起见,程序员经常定义一个简单的宏,用于将kobject指针 **反推** 到包含 +类型。在早期的 ``drivers/uio/uio.c`` 中正是如此,你可以在这里看到:: + + struct uio_map { + struct kobject kobj; + struct uio_mem *mem; + }; + + #define to_map(map) container_of(map, struct uio_map, kobj) + +其中宏的参数“map”是一个指向有关的kobject结构体的指针。该宏随后被调用:: + + struct uio_map *map = to_map(kobj); + + +kobjects的初始化 +================ + +当然,创建kobject的代码必须初始化该对象。一些内部字段是通过(强制)调用kobject_init() +来设置的:: + + void kobject_init(struct kobject *kobj, struct kobj_type *ktype); + +ktype是正确创建kobject的必要条件,因为每个kobject都必须有一个相关的kobj_type。 +在调用kobject_init()后,为了向sysfs注册kobject,必须调用函数kobject_add():: + + int kobject_add(struct kobject *kobj, struct kobject *parent, + const char *fmt, ...); + +这将正确设置kobject的父级和kobject的名称。如果该kobject要与一个特定的kset相关 +联,在调用kobject_add()之前必须分配kobj->kset。如果kset与kobject相关联,则 +kobject的父级可以在调用kobject_add()时被设置为NULL,则kobject的父级将是kset +本身。 + +由于kobject的名字是在它被添加到内核时设置的,所以kobject的名字不应该被直接操作。 +如果你必须改变kobject的名字,请调用kobject_rename():: + + int kobject_rename(struct kobject *kobj, const char *new_name); + +kobject_rename()函数不会执行任何锁定操作,也不会对name进行可靠性检查,所以调用 +者自己检查和串行化操作是明智的选择 + +有一个叫kobject_set_name()的函数,但那是历史遗产,正在被删除。如果你的代码需 +要调用这个函数,那么它是不正确的,需要被修复。 + +要正确访问kobject的名称,请使用函数kobject_name():: + + const char *kobject_name(const struct kobject * kobj); + +有一个辅助函数可以同时初始化和添加kobject到内核中,令人惊讶的是,该函数被称为 +kobject_init_and_add():: + + int kobject_init_and_add(struct kobject *kobj, struct kobj_type *ktype, + struct kobject *parent, const char *fmt, ...); + +参数与上面描述的单个kobject_init()和kobject_add()函数相同。 + + +Uevents +======= + +当一个kobject被注册到kobject核心后,你需要向全世界宣布它已经被创建了。这可以通 +过调用kobject_uevent()来实现:: + + int kobject_uevent(struct kobject *kobj, enum kobject_action action); + +当kobject第一次被添加到内核时,使用 *KOBJ_ADD* 动作。这应该在该kobject的任 +何属性或子对象被正确初始化后进行,因为当这个调用发生时,用户空间会立即开始寻 +找它们。 + +当kobject从内核中移除时(关于如何做的细节在下面), **KOBJ_REMOVE** 的uevent +将由kobject核心自动创建,所以调用者不必担心手动操作。 + + +引用计数 +======== + +kobject的关键功能之一是作为它所嵌入的对象的一个引用计数器。只要对该对象的引用 +存在,该对象(以及支持它的代码)就必须继续存在。用于操作kobject的引用计数的低 +级函数是:: + + struct kobject *kobject_get(struct kobject *kobj); + void kobject_put(struct kobject *kobj); + +对kobject_get()的成功调用将增加kobject的引用计数器值并返回kobject的指针。 + +当引用被释放时,对kobject_put()的调用将递减引用计数值,并可能释放该对象。请注 +意,kobject_init()将引用计数设置为1,所以设置kobject的代码最终需要kobject_put() +来释放该引用。 + +因为kobjects是动态的,所以它们不能以静态方式或在堆栈中声明,而总是以动态方式分 +配。未来版本的内核将包含对静态创建的kobjects的运行时检查,并将警告开发者这种不 +当的使用。 + +如果你使用struct kobject只是为了给你的结构体提供一个引用计数器,请使用struct kref +来代替;kobject是多余的。关于如何使用kref结构体的更多信息,请参见Linux内核源代 +码树中的文件Documentation/core-api/kref.rst + + +创建“简单的”kobjects +==================== + +有时,开发者想要的只是在sysfs层次结构中创建一个简单的目录,而不必去搞那些复杂 +的ksets、显示和存储函数,以及其他细节。这是一个应该创建单个kobject的例外。要 +创建这样一个条目(即简单的目录),请使用函数:: + + struct kobject *kobject_create_and_add(const char *name, struct kobject *parent); + +这个函数将创建一个kobject,并将其放在sysfs中指定的父kobject下面的位置。要创 +建与此kobject相关的简单属性,请使用:: + + int sysfs_create_file(struct kobject *kobj, const struct attribute *attr); + +或者:: + + int sysfs_create_group(struct kobject *kobj, const struct attribute_group *grp); + +这里使用的两种类型的属性,与已经用kobject_create_and_add()创建的kobject, +都可以是kobj_attribute类型,所以不需要创建特殊的自定义属性。 + +参见示例模块, ``samples/kobject/kobject-example.c`` ,以了解一个简单的 +kobject和属性的实现。 + + + +ktypes和释放方法 +================ + +以上讨论中还缺少一件重要的事情,那就是当一个kobject的引用次数达到零的时候 +会发生什么。创建kobject的代码通常不知道何时会发生这种情况;首先,如果它知 +道,那么使用kobject就没有什么意义。当sysfs被引入时,即使是可预测的对象生命 +周期也会变得更加复杂,因为内核的其他部分可以获得在系统中注册的任何kobject +的引用。 + +最终的结果是,一个由kobject保护的结构体在其引用计数归零之前不能被释放。引 +用计数不受创建kobject的代码的直接控制。因此,每当它的一个kobjects的最后一 +个引用消失时,必须异步通知该代码。 + +一旦你通过kobject_add()注册了你的kobject,你绝对不能使用kfree()来直接释 +放它。唯一安全的方法是使用kobject_put()。在kobject_init()之后总是使用 +kobject_put()以避免错误的发生是一个很好的做法。 + +这个通知是通过kobject的release()方法完成的。通常这样的方法有如下形式:: + + void my_object_release(struct kobject *kobj) + { + struct my_object *mine = container_of(kobj, struct my_object, kobj); + + /* Perform any additional cleanup on this object, then... */ + kfree(mine); + } + +有一点很重要:每个kobject都必须有一个release()方法,而且这个kobject必 +须持续存在(处于一致的状态),直到这个方法被调用。如果这些约束条件没有 +得到满足,那么代码就是有缺陷的。注意,如果你忘记提供release()方法,内 +核会警告你。不要试图通过提供一个“空”的释放函数来摆脱这个警告。 + +如果你的清理函数只需要调用kfree(),那么你必须创建一个包装函数,该函数 +使用container_of()来向上造型到正确的类型(如上面的例子所示),然后在整个 +结构体上调用kfree()。 + +注意,kobject的名字在release函数中是可用的,但它不能在这个回调中被改 +变。否则,在kobject核心中会出现内存泄漏,这让人很不爽。 + +有趣的是,release()方法并不存储在kobject本身;相反,它与ktype相关。 +因此,让我们引入结构体kobj_type:: + + struct kobj_type { + void (*release)(struct kobject *kobj); + const struct sysfs_ops *sysfs_ops; + const struct attribute_group **default_groups; + const struct kobj_ns_type_operations *(*child_ns_type)(struct kobject *kobj); + const void *(*namespace)(struct kobject *kobj); + void (*get_ownership)(struct kobject *kobj, kuid_t *uid, kgid_t *gid); + }; + +这个结构提用来描述一个特定类型的kobject(或者更正确地说,包含对象的 +类型)。每个kobject都需要有一个相关的kobj_type结构;当你调用 +kobject_init()或kobject_init_and_add()时必须指定一个指向该结构的 +指针。 + +当然,kobj_type结构中的release字段是指向这种类型的kobject的release() +方法的一个指针。另外两个字段(sysfs_ops 和 default_groups)控制这种 +类型的对象如何在 sysfs 中被表示;它们超出了本文的范围。 + +default_groups 指针是一个默认属性的列表,它将为任何用这个 ktype 注册 +的 kobject 自动创建。 + + +ksets +===== + +一个kset仅仅是一个希望相互关联的kobjects的集合。没有限制它们必须是相 +同的ktype,但是如果它们不是相同的,就要非常小心。 + +一个kset有以下功能: + + - 它像是一个包含一组对象的袋子。一个kset可以被内核用来追踪“所有块 + 设备”或“所有PCI设备驱动”。 + + - kset也是sysfs中的一个子目录,与kset相关的kobjects可以在这里显示 + 出来。每个kset都包含一个kobject,它可以被设置为其他kobject的父对象; + sysfs层次结构的顶级目录就是以这种方式构建的。 + + - Ksets可以支持kobjects的 "热插拔",并影响uevent事件如何被报告给 + 用户空间。 + + 在面向对象的术语中,“kset”是顶级的容器类;ksets包含它们自己的kobject, + 但是这个kobject是由kset代码管理的,不应该被任何其他用户所操纵。 + + kset在一个标准的内核链表中保存它的子对象。Kobjects通过其kset字段指向其 + 包含的kset。在几乎所有的情况下,属于一个kset的kobjects在它们的父 + 对象中都有那个kset(或者,严格地说,它的嵌入kobject)。 + + 由于kset中包含一个kobject,它应该总是被动态地创建,而不是静态地 + 或在堆栈中声明。要创建一个新的kset,请使用:: + + struct kset *kset_create_and_add(const char *name, + const struct kset_uevent_ops *uevent_ops, + struct kobject *parent_kobj); + +当你完成对kset的处理后,调用:: + + void kset_unregister(struct kset *k); + +来销毁它。这将从sysfs中删除该kset并递减其引用计数值。当引用计数 +为零时,该kset将被释放。因为对该kset的其他引用可能仍然存在, +释放可能发生在kset_unregister()返回之后。 + +一个使用kset的例子可以在内核树中的 ``samples/kobject/kset-example.c`` +文件中看到。 + +如果一个kset希望控制与它相关的kobjects的uevent操作,它可以使用 +结构体kset_uevent_ops来处理它:: + + struct kset_uevent_ops { + int (* const filter)(struct kobject *kobj); + const char *(* const name)(struct kobject *kobj); + int (* const uevent)(struct kobject *kobj, struct kobj_uevent_env *env); + }; + + +过滤器函数允许kset阻止一个特定kobject的uevent被发送到用户空间。 +如果该函数返回0,该uevent将不会被发射出去。 + +name函数将被调用以覆盖uevent发送到用户空间的kset的默认名称。默 +认情况下,该名称将与kset本身相同,但这个函数,如果存在,可以覆盖 +该名称。 + +当uevent即将被发送至用户空间时,uevent函数将被调用,以允许更多 +的环境变量被添加到uevent中。 + +有人可能会问,鉴于没有提出执行该功能的函数,究竟如何将一个kobject +添加到一个kset中。答案是这个任务是由kobject_add()处理的。当一个 +kobject被传递给kobject_add()时,它的kset成员应该指向这个kobject +所属的kset。 kobject_add()将处理剩下的部分。 + +如果属于一个kset的kobject没有父kobject集,它将被添加到kset的目 +录中。并非所有的kset成员都必须住在kset目录中。如果在添加kobject +之前分配了一个明确的父kobject,那么该kobject将被注册到kset中, +但是被添加到父kobject下面。 + + +移除Kobject +=========== + +当一个kobject在kobject核心注册成功后,在代码使用完它时,必须将其 +清理掉。要做到这一点,请调用kobject_put()。通过这样做,kobject核 +心会自动清理这个kobject分配的所有内存。如果为这个对象发送了 ``KOBJ_ADD`` +uevent,那么相应的 ``KOBJ_REMOVE`` uevent也将被发送,任何其他的 +sysfs内务将被正确处理。 + +如果你需要分两次对kobject进行删除(比如说在你要销毁对象时无权睡眠), +那么调用kobject_del()将从sysfs中取消kobject的注册。这使得kobject +“不可见”,但它并没有被清理掉,而且该对象的引用计数仍然是一样的。在稍 +后的时间调用kobject_put()来完成与该kobject相关的内存的清理。 + +kobject_del()可以用来放弃对父对象的引用,如果循环引用被构建的话。 +在某些情况下,一个父对象引用一个子对象是有效的。循环引用必须通过明 +确调用kobject_del()来打断,这样一个释放函数就会被调用,前一个循环 +中的对象会相互释放。 + + +示例代码出处 +============ + +关于正确使用ksets和kobjects的更完整的例子,请参见示例程序 +``samples/kobject/{kobject-example.c,kset-example.c}`` ,如果 +您选择 ``CONFIG_SAMPLE_KOBJECT`` ,它们将被构建为可加载模块。 diff --git a/Documentation/translations/zh_CN/core-api/kref.rst b/Documentation/translations/zh_CN/core-api/kref.rst new file mode 100644 index 000000000..b9902af31 --- /dev/null +++ b/Documentation/translations/zh_CN/core-api/kref.rst @@ -0,0 +1,311 @@ +.. include:: ../disclaimer-zh_CN.rst + +:Original: Documentation/core-api/kref.rst + +翻译: + +司延腾 Yanteng Si <siyanteng@loongson.cn> + +校译: + + <此处请校译员签名(自愿),我将在下一个版本添加> + +.. _cn_core_api_kref.rst: + +================================= +为内核对象添加引用计数器(krefs) +================================= + +:作者: Corey Minyard <minyard@acm.org> +:作者: Thomas Hellstrom <thellstrom@vmware.com> + +其中很多内容都是从Greg Kroah-Hartman2004年关于krefs的OLS论文和演讲中摘 +录的,可以在以下网址找到: + + - http://www.kroah.com/linux/talks/ols_2004_kref_paper/Reprint-Kroah-Hartman-OLS2004.pdf + - http://www.kroah.com/linux/talks/ols_2004_kref_talk/ + +简介 +==== + +krefs允许你为你的对象添加引用计数器。如果你有在多个地方使用和传递的对象, +而你没有refcounts,你的代码几乎肯定是坏的。如果你想要引用计数,krefs是个 +好办法。 + +要使用kref,请在你的数据结构中添加一个,如:: + + struct my_data + { + . + . + struct kref refcount; + . + . + }; + +kref可以出现在数据结构体中的任何地方。 + +初始化 +====== + +你必须在分配kref之后初始化它。 要做到这一点,可以这样调用kref_init:: + + struct my_data *data; + + data = kmalloc(sizeof(*data), GFP_KERNEL); + if (!data) + return -ENOMEM; + kref_init(&data->refcount); + +这将kref中的refcount设置为1。 + +Kref规则 +======== + +一旦你有一个初始化的kref,你必须遵循以下规则: + +1) 如果你对一个指针做了一个非临时性的拷贝,特别是如果它可以被传递给另一个执 + 行线程,你必须在传递之前用kref_get()增加refcount:: + + kref_get(&data->refcount); + + 如果你已经有了一个指向kref-ed结构体的有效指针(refcount不能为零),你 + 可以在没有锁的情况下这样做。 + +2) 当你完成对一个指针的处理时,你必须调用kref_put():: + + kref_put(&data->refcount, data_release); + + 如果这是对该指针的最后一次引用,释放程序将被调用。如果代码从来没有尝试过 + 在没有已经持有有效指针的情况下获得一个kref-ed结构体的有效指针,那么在没 + 有锁的情况下这样做是安全的。 + +3) 如果代码试图获得对一个kref-ed结构体的引用,而不持有一个有效的指针,它必 + 须按顺序访问,在kref_put()期间不能发生kref_get(),并且该结构体在kref_get() + 期间必须保持有效。 + +例如,如果你分配了一些数据,然后将其传递给另一个线程来处理:: + + void data_release(struct kref *ref) + { + struct my_data *data = container_of(ref, struct my_data, refcount); + kfree(data); + } + + void more_data_handling(void *cb_data) + { + struct my_data *data = cb_data; + . + . do stuff with data here + . + kref_put(&data->refcount, data_release); + } + + int my_data_handler(void) + { + int rv = 0; + struct my_data *data; + struct task_struct *task; + data = kmalloc(sizeof(*data), GFP_KERNEL); + if (!data) + return -ENOMEM; + kref_init(&data->refcount); + + kref_get(&data->refcount); + task = kthread_run(more_data_handling, data, "more_data_handling"); + if (task == ERR_PTR(-ENOMEM)) { + rv = -ENOMEM; + kref_put(&data->refcount, data_release); + goto out; + } + + . + . do stuff with data here + . + out: + kref_put(&data->refcount, data_release); + return rv; + } + +这样,两个线程处理数据的顺序并不重要,kref_put()处理知道数据不再被引用并释 +放它。kref_get()不需要锁,因为我们已经有了一个有效的指针,我们拥有一个 +refcount。put不需要锁,因为没有任何东西试图在没有持有指针的情况下获取数据。 + +在上面的例子中,kref_put()在成功和错误路径中都会被调用2次。这是必要的,因 +为引用计数被kref_init()和kref_get()递增了2次。 + +请注意,规则1中的 "before "是非常重要的。你不应该做类似于:: + + task = kthread_run(more_data_handling, data, "more_data_handling"); + if (task == ERR_PTR(-ENOMEM)) { + rv = -ENOMEM; + goto out; + } else + /* BAD BAD BAD - 在交接后得到 */ + kref_get(&data->refcount); + +不要以为你知道自己在做什么而使用上述构造。首先,你可能不知道自己在做什么。 +其次,你可能知道自己在做什么(有些情况下涉及到锁,上述做法可能是合法的), +但其他不知道自己在做什么的人可能会改变代码或复制代码。这是很危险的作风。请 +不要这样做。 + +在有些情况下,你可以优化get和put。例如,如果你已经完成了一个对象,并且给其 +他对象排队,或者把它传递给其他对象,那么就没有理由先做一个get,然后再做一个 +put:: + + /* 糟糕的额外获取(get)和输出(put) */ + kref_get(&obj->ref); + enqueue(obj); + kref_put(&obj->ref, obj_cleanup); + +只要做enqueue就可以了。 我们随时欢迎对这个问题的评论:: + + enqueue(obj); + /* 我们已经完成了对obj的处理,所以我们把我们的refcount传给了队列。 + 在这之后不要再碰obj了! */ + +最后一条规则(规则3)是最难处理的一条。例如,你有一个每个项目都被krefed的列表, +而你希望得到第一个项目。你不能只是从列表中抽出第一个项目,然后kref_get()它。 +这违反了规则3,因为你还没有持有一个有效的指针。你必须添加一个mutex(或其他锁)。 +比如说:: + + static DEFINE_MUTEX(mutex); + static LIST_HEAD(q); + struct my_data + { + struct kref refcount; + struct list_head link; + }; + + static struct my_data *get_entry() + { + struct my_data *entry = NULL; + mutex_lock(&mutex); + if (!list_empty(&q)) { + entry = container_of(q.next, struct my_data, link); + kref_get(&entry->refcount); + } + mutex_unlock(&mutex); + return entry; + } + + static void release_entry(struct kref *ref) + { + struct my_data *entry = container_of(ref, struct my_data, refcount); + + list_del(&entry->link); + kfree(entry); + } + + static void put_entry(struct my_data *entry) + { + mutex_lock(&mutex); + kref_put(&entry->refcount, release_entry); + mutex_unlock(&mutex); + } + +如果你不想在整个释放操作过程中持有锁,kref_put()的返回值是有用的。假设你不想在 +上面的例子中在持有锁的情况下调用kfree()(因为这样做有点无意义)。你可以使用kref_put(), +如下所示:: + + static void release_entry(struct kref *ref) + { + /* 所有的工作都是在从kref_put()返回后完成的。*/ + } + + static void put_entry(struct my_data *entry) + { + mutex_lock(&mutex); + if (kref_put(&entry->refcount, release_entry)) { + list_del(&entry->link); + mutex_unlock(&mutex); + kfree(entry); + } else + mutex_unlock(&mutex); + } + +如果你必须调用其他程序作为释放操作的一部分,而这些程序可能需要很长的时间,或者可 +能要求相同的锁,那么这真的更有用。请注意,在释放例程中做所有的事情还是比较好的, +因为它比较整洁。 + +上面的例子也可以用kref_get_unless_zero()来优化,方法如下:: + + static struct my_data *get_entry() + { + struct my_data *entry = NULL; + mutex_lock(&mutex); + if (!list_empty(&q)) { + entry = container_of(q.next, struct my_data, link); + if (!kref_get_unless_zero(&entry->refcount)) + entry = NULL; + } + mutex_unlock(&mutex); + return entry; + } + + static void release_entry(struct kref *ref) + { + struct my_data *entry = container_of(ref, struct my_data, refcount); + + mutex_lock(&mutex); + list_del(&entry->link); + mutex_unlock(&mutex); + kfree(entry); + } + + static void put_entry(struct my_data *entry) + { + kref_put(&entry->refcount, release_entry); + } + +这对于在put_entry()中移除kref_put()周围的mutex锁是很有用的,但是重要的是 +kref_get_unless_zero被封装在查找表中的同一关键部分,否则kref_get_unless_zero +可能引用已经释放的内存。注意,在不检查其返回值的情况下使用kref_get_unless_zero +是非法的。如果你确信(已经有了一个有效的指针)kref_get_unless_zero()会返回true, +那么就用kref_get()代替。 + +Krefs和RCU +========== + +函数kref_get_unless_zero也使得在上述例子中使用rcu锁进行查找成为可能:: + + struct my_data + { + struct rcu_head rhead; + . + struct kref refcount; + . + . + }; + + static struct my_data *get_entry_rcu() + { + struct my_data *entry = NULL; + rcu_read_lock(); + if (!list_empty(&q)) { + entry = container_of(q.next, struct my_data, link); + if (!kref_get_unless_zero(&entry->refcount)) + entry = NULL; + } + rcu_read_unlock(); + return entry; + } + + static void release_entry_rcu(struct kref *ref) + { + struct my_data *entry = container_of(ref, struct my_data, refcount); + + mutex_lock(&mutex); + list_del_rcu(&entry->link); + mutex_unlock(&mutex); + kfree_rcu(entry, rhead); + } + + static void put_entry(struct my_data *entry) + { + kref_put(&entry->refcount, release_entry_rcu); + } + +但要注意的是,在调用release_entry_rcu后,结构kref成员需要在有效内存中保留一个rcu +宽限期。这可以通过使用上面的kfree_rcu(entry, rhead)来实现,或者在使用kfree之前 +调用synchronize_rcu(),但注意synchronize_rcu()可能会睡眠相当长的时间。 diff --git a/Documentation/translations/zh_CN/core-api/local_ops.rst b/Documentation/translations/zh_CN/core-api/local_ops.rst new file mode 100644 index 000000000..41e452503 --- /dev/null +++ b/Documentation/translations/zh_CN/core-api/local_ops.rst @@ -0,0 +1,196 @@ +.. include:: ../disclaimer-zh_CN.rst + +:Original: Documentation/core-api/local_ops.rst + +:翻译: + + 司延腾 Yanteng Si <siyanteng@loongson.cn> + +.. _cn_local_ops: + +======================== +本地原子操作的语义和行为 +======================== + +:作者: Mathieu Desnoyers + + +本文解释了本地原子操作的目的,如何为任何给定的架构实现这些操作,并说明了 +如何正确使用这些操作。它还强调了在内存写入顺序很重要的情况下,跨CPU读取 +这些本地变量时必须采取的预防措施。 + +.. note:: + + 注意,基于 ``local_t`` 的操作不建议用于一般内核操作。请使用 ``this_cpu`` + 操作来代替使用,除非真的有特殊目的。大多数内核中使用的 ``local_t`` 已 + 经被 ``this_cpu`` 操作所取代。 ``this_cpu`` 操作在一条指令中结合了重 + 定位和类似 ``local_t`` 的语义,产生了更紧凑和更快的执行代码。 + + +本地原子操作的目的 +================== + +本地原子操作的目的是提供快速和高度可重入的每CPU计数器。它们通过移除LOCK前 +缀和通常需要在CPU间同步的内存屏障,将标准原子操作的性能成本降到最低。 + +在许多情况下,拥有快速的每CPU原子计数器是很有吸引力的:它不需要禁用中断来保护中 +断处理程序,它允许在NMI(Non Maskable Interrupt)处理程序中使用连贯的计数器。 +它对追踪目的和各种性能监测计数器特别有用。 + +本地原子操作只保证在拥有数据的CPU上的变量修改的原子性。因此,必须注意确保只 +有一个CPU写到 ``local_t`` 的数据。这是通过使用每CPU的数据来实现的,并确 +保我们在一个抢占式安全上下文中修改它。然而,从任何一个CPU读取 ``local_t`` +数据都是允许的:这样它就会显得与所有者CPU的其他内存写入顺序不一致。 + + +针对特定架构的实现 +================== + +这可以通过稍微修改标准的原子操作来实现:只有它们的UP变体必须被保留。这通常 +意味着删除LOCK前缀(在i386和x86_64上)和任何SMP同步屏障。如果架构在SMP和 +UP之间没有不同的行为,在你的架构的 ``local.h`` 中包括 ``asm-generic/local.h`` +就足够了。 + +通过在一个结构体中嵌入一个 ``atomic_long_t`` , ``local_t`` 类型被定义为 +一个不透明的 ``signed long`` 。这样做的目的是为了使从这个类型到 +``long`` 的转换失败。该定义看起来像:: + + typedef struct { atomic_long_t a; } local_t; + + +使用本地原子操作时应遵循的规则 +============================== + +* 被本地操作触及的变量必须是每cpu的变量。 + +* *只有* 这些变量的CPU所有者才可以写入这些变量。 + +* 这个CPU可以从任何上下文(进程、中断、软中断、nmi...)中使用本地操作来更新 + 它的local_t变量。 + +* 当在进程上下文中使用本地操作时,必须禁用抢占(或中断),以确保进程在获得每 + CPU变量和进行实际的本地操作之间不会被迁移到不同的CPU。 + +* 当在中断上下文中使用本地操作时,在主线内核上不需要特别注意,因为它们将在局 + 部CPU上运行,并且已经禁用了抢占。然而,我建议无论如何都要明确地禁用抢占, + 以确保它在-rt内核上仍能正确工作。 + +* 读取本地cpu变量将提供该变量的当前拷贝。 + +* 对这些变量的读取可以从任何CPU进行,因为对 “ ``long`` ”,对齐的变量的更新 + 总是原子的。由于写入程序的CPU没有进行内存同步,所以在读取 *其他* cpu的变 + 量时,可以读取该变量的过期副本。 + + +如何使用本地原子操作 +==================== + +:: + + #include <linux/percpu.h> + #include <asm/local.h> + + static DEFINE_PER_CPU(local_t, counters) = LOCAL_INIT(0); + + +计数器 +====== + +计数是在一个signed long的所有位上进行的。 + +在可抢占的上下文中,围绕本地原子操作使用 ``get_cpu_var()`` 和 +``put_cpu_var()`` :它确保在对每个cpu变量进行写访问时,抢占被禁用。比如 +说:: + + local_inc(&get_cpu_var(counters)); + put_cpu_var(counters); + +如果你已经在一个抢占安全上下文中,你可以使用 ``this_cpu_ptr()`` 代替:: + + local_inc(this_cpu_ptr(&counters)); + + + +读取计数器 +========== + +那些本地计数器可以从外部的CPU中读取,以求得计数的总和。请注意,local_read +所看到的跨CPU的数据必须被认为是相对于拥有该数据的CPU上发生的其他内存写入来 +说不符合顺序的:: + + long sum = 0; + for_each_online_cpu(cpu) + sum += local_read(&per_cpu(counters, cpu)); + +如果你想使用远程local_read来同步CPU之间对资源的访问,必须在写入者和读取者 +的CPU上分别使用显式的 ``smp_wmb()`` 和 ``smp_rmb()`` 内存屏障。如果你使 +用 ``local_t`` 变量作为写在缓冲区中的字节的计数器,就会出现这种情况:在缓 +冲区写和计数器增量之间应该有一个 ``smp_wmb()`` ,在计数器读和缓冲区读之间 +也应有一个 ``smp_rmb()`` 。 + +下面是一个使用 ``local.h`` 实现每个cpu基本计数器的示例模块:: + + /* test-local.c + * + * Sample module for local.h usage. + */ + + + #include <asm/local.h> + #include <linux/module.h> + #include <linux/timer.h> + + static DEFINE_PER_CPU(local_t, counters) = LOCAL_INIT(0); + + static struct timer_list test_timer; + + /* IPI called on each CPU. */ + static void test_each(void *info) + { + /* Increment the counter from a non preemptible context */ + printk("Increment on cpu %d\n", smp_processor_id()); + local_inc(this_cpu_ptr(&counters)); + + /* This is what incrementing the variable would look like within a + * preemptible context (it disables preemption) : + * + * local_inc(&get_cpu_var(counters)); + * put_cpu_var(counters); + */ + } + + static void do_test_timer(unsigned long data) + { + int cpu; + + /* Increment the counters */ + on_each_cpu(test_each, NULL, 1); + /* Read all the counters */ + printk("Counters read from CPU %d\n", smp_processor_id()); + for_each_online_cpu(cpu) { + printk("Read : CPU %d, count %ld\n", cpu, + local_read(&per_cpu(counters, cpu))); + } + mod_timer(&test_timer, jiffies + 1000); + } + + static int __init test_init(void) + { + /* initialize the timer that will increment the counter */ + timer_setup(&test_timer, do_test_timer, 0); + mod_timer(&test_timer, jiffies + 1); + + return 0; + } + + static void __exit test_exit(void) + { + del_timer_sync(&test_timer); + } + + module_init(test_init); + module_exit(test_exit); + + MODULE_LICENSE("GPL"); + MODULE_AUTHOR("Mathieu Desnoyers"); + MODULE_DESCRIPTION("Local Atomic Ops"); diff --git a/Documentation/translations/zh_CN/core-api/memory-allocation.rst b/Documentation/translations/zh_CN/core-api/memory-allocation.rst new file mode 100644 index 000000000..e17b87dfd --- /dev/null +++ b/Documentation/translations/zh_CN/core-api/memory-allocation.rst @@ -0,0 +1,138 @@ +.. include:: ../disclaimer-zh_CN.rst + +:Original: Documentation/core-api/memory-allocation.rst + +:翻译: + + 司延腾 Yanteng Si <siyanteng@loongson.cn> + +:校译: + + 时奎亮 <alexs@kernel.org> + +.. _cn_core-api_memory-allocation: + +============ +内存分配指南 +============ + +Linux为内存分配提供了多种API。你可以使用 `kmalloc` 或 `kmem_cache_alloc` +系列分配小块内存,使用 `vmalloc` 及其派生产品分配大的几乎连续的区域,或者 +你可以用 alloc_pages 直接向页面分配器请求页面。也可以使用更专业的分配器, +例如 `cma_alloc` 或 `zs_malloc` 。 + +大多数的内存分配API使用GFP标志来表达该内存应该如何分配。GFP的缩写代表 +“(get free pages)获取空闲页”,是底层的内存分配功能。 + +(内存)分配API的多样性与众多的GFP标志相结合,使得“我应该如何分配内存?”这个问 +题不那么容易回答,尽管很可能你应该使用 + +:: + + kzalloc(<size>, GFP_KERNEL); + +当然,有些情况下必须使用其他分配API和不同的GFP标志。 + +获取空闲页标志 +============== +GFP标志控制分配器的行为。它们告诉我们哪些内存区域可以被使用,分配器应该多努力寻 +找空闲的内存,这些内存是否可以被用户空间访问等等。内存管理API为GFP标志和它们的 +组合提供了参考文件,这里我们简要介绍一下它们的推荐用法: + + * 大多数时候, ``GFP_KERNEL`` 是你需要的。内核数据结构的内存,DMA可用内存,inode + 缓存,所有这些和其他许多分配类型都可以使用 ``GFP_KERNEL`` 。注意,使用 ``GFP_KERNEL`` + 意味着 ``GFP_RECLAIM`` ,这意味着在有内存压力的情况下可能会触发直接回收;调用上 + 下文必须允许睡眠。 + + * 如果分配是从一个原子上下文中进行的,例如中断处理程序,使用 ``GFP_NOWAIT`` 。这个 + 标志可以防止直接回收和IO或文件系统操作。因此,在内存压力下, ``GFP_NOWAIT`` 分配 + 可能会失败。有合理退路的分配应该使用 ``GFP_NOWARN`` 。 + + * 如果你认为访问保留内存区是合理的,并且除非分配成功,否则内核会有压力,你可以使用 ``GFP_ATOMIC`` 。 + + * 从用户空间触发的不可信任的分配应该是kmem核算的对象,必须设置 ``__GFP_ACCOUNT`` 位。 + 有一个方便的用于 ``GFP_KERNEL`` 分配的 ``GFP_KERNEL_ACCOUNT`` 快捷键,其应该被核 + 算。 + + * 用户空间的分配应该使用 ``GFP_USER`` 、 ``GFP_HIGHUSER`` 或 ``GFP_HIGHUSER_MOVABLE`` + 中的一个标志。标志名称越长,限制性越小。 + + ``GFP_HIGHUSER_MOVABLE`` 不要求分配的内存将被内核直接访问,并意味着数据是可迁移的。 + + ``GFP_HIGHUSER`` 意味着所分配的内存是不可迁移的,但也不要求它能被内核直接访问。举个 + 例子就是一个硬件分配内存,这些数据直接映射到用户空间,但没有寻址限制。 + + ``GFP_USER`` 意味着分配的内存是不可迁移的,它必须被内核直接访问。 + +你可能会注意到,在现有的代码中,有相当多的分配指定了 ``GFP_NOIO`` 或 ``GFP_NOFS`` 。 +从历史上看,它们被用来防止递归死锁,这种死锁是由直接内存回收调用到FS或IO路径以及对已 +经持有的资源进行阻塞引起的。从4.12开始,解决这个问题的首选方法是使用新的范围API,即 +:ref:`Documentation/core-api/gfp_mask-from-fs-io.rst <gfp_mask_from_fs_io>`. + +其他传统的GFP标志是 ``GFP_DMA`` 和 ``GFP_DMA32`` 。它们用于确保分配的内存可以被寻 +址能力有限的硬件访问。因此,除非你正在为一个有这种限制的设备编写驱动程序,否则要避免 +使用这些标志。而且,即使是有限制的硬件,也最好使用dma_alloc* APIs。 + +GFP标志和回收行为 +----------------- +内存分配可能会触发直接或后台回收,了解页面分配器将如何努力满足该请求或其他请求是非常 +有用的。 + + * ``GFP_KERNEL & ~__GFP_RECLAIM`` - 乐观分配,完全不尝试释放内存。最轻量级的模 + 式,甚至不启动后台回收。应该小心使用,因为它可能会耗尽内存,而下一个用户可能会启 + 动更积极的回收。 + + * ``GFP_KERNEL & ~__GFP_DIRECT_RECLAIM`` (or ``GFP_NOWAIT`` ) - 乐观分配,不 + 试图从当前上下文中释放内存,但如果该区域低于低水位,可以唤醒kswapd来回收内存。可 + 以从原子上下文中使用,或者当请求是一个性能优化,并且有另一个慢速路径的回退。 + + * ``(GFP_KERNEL|__GFP_HIGH) & ~__GFP_DIRECT_RECLAIM`` (aka ``GFP_ATOMIC`` ) - 非 + 睡眠分配,有一个昂贵的回退,所以它可以访问某些部分的内存储备。通常从中断/底层上下 + 文中使用,有一个昂贵的慢速路径回退。 + + * ``GFP_KERNEL`` - 允许后台和直接回收,并使用默认的页面分配器行为。这意味着廉价 + 的分配请求基本上是不会失败的,但不能保证这种行为,所以失败必须由调用者适当检查(例 + 如,目前允许OOM杀手失败)。 + + * ``GFP_KERNEL | __GFP_NORETRY`` - 覆盖默认的分配器行为,所有的分配请求都会提前 + 失败,而不是导致破坏性的回收(在这个实现中是一轮的回收)。OOM杀手不被调用。 + + * ``GFP_KERNEL | __GFP_RETRY_MAYFAIL`` - 覆盖 **默认** 的分配器行为,所有分配请求都非 + 常努力。如果回收不能取得任何进展,该请求将失败。OOM杀手不会被触发。 + + * ``GFP_KERNEL | __GFP_NOFAIL`` - 覆盖默认的分配器行为,所有分配请求将无休止地循 + 环,直到成功。这可能真的很危险,特别是对于较大的需求。 + +选择内存分配器 +============== + +分配内存的最直接的方法是使用kmalloc()系列的函数。而且,为了安全起见,最好使用将内存 +设置为零的例程,如kzalloc()。如果你需要为一个数组分配内存,有kmalloc_array()和kcalloc() +辅助程序。辅助程序struct_size()、array_size()和array3_size()可以用来安全地计算对 +象的大小而不会溢出。 + +可以用 `kmalloc` 分配的块的最大尺寸是有限的。实际的限制取决于硬件和内核配置,但是对于 +小于页面大小的对象,使用 `kmalloc` 是一个好的做法。 + +用 `kmalloc` 分配的块的地址至少要对齐到ARCH_KMALLOC_MINALIGN字节。对于2的幂的大小, +对齐方式也被保证为至少是各自的大小。 + +用kmalloc()分配的块可以用krealloc()调整大小。与kmalloc_array()类似:以krealloc_array() +的形式提供了一个用于调整数组大小的辅助工具。 + +对于大量的分配,你可以使用vmalloc()和vzalloc(),或者直接向页面分配器请求页面。由vmalloc +和相关函数分配的内存在物理上是不连续的。 + +如果你不确定分配的大小对 `kmalloc` 来说是否太大,可以使用kvmalloc()及其派生函数。它将尝 +试用kmalloc分配内存,如果分配失败,将用 `vmalloc` 重新尝试。对于哪些GFP标志可以与 `kvmalloc` +一起使用是有限制的;请看kvmalloc_node()参考文档。注意, `kvmalloc` 可能会返回物理上不连 +续的内存。 + +如果你需要分配许多相同的对象,你可以使用slab缓存分配器。在使用缓存之前,应该用 +kmem_cache_create()或kmem_cache_create_usercopy()来设置缓存。如果缓存的一部分可能被复 +制到用户空间,应该使用第二个函数。在缓存被创建后,kmem_cache_alloc()和它的封装可以从该缓 +存中分配内存。 + +当分配的内存不再需要时,它必须被释放。你可以使用kvfree()来处理用 `kmalloc` 、 `vmalloc` +和 `kvmalloc` 分配的内存。slab缓存应该用kmem_cache_free()来释放。不要忘记用 +kmem_cache_destroy()来销毁缓存。 diff --git a/Documentation/translations/zh_CN/core-api/memory-hotplug.rst b/Documentation/translations/zh_CN/core-api/memory-hotplug.rst new file mode 100644 index 000000000..9b2841fb9 --- /dev/null +++ b/Documentation/translations/zh_CN/core-api/memory-hotplug.rst @@ -0,0 +1,122 @@ +.. include:: ../disclaimer-zh_CN.rst + +:Original: Documentation/core-api/memory-hotplug.rst + +:翻译: + + 司延腾 Yanteng Si <siyanteng@loongson.cn> + +:校译: + + 吴想成 Wu XiangCheng <bobwxc@email.cn> + +.. _cn_core-api_memory-hotplug: + +========== +内存热插拔 +========== + +内存热拔插事件通知器 +==================== + +热插拔事件被发送到一个通知队列中。 + +在 ``include/linux/memory.h`` 中定义了六种类型的通知: + +MEM_GOING_ONLINE + 在新内存可用之前生成,以便能够为子系统处理内存做准备。页面分配器仍然无法从新 + 的内存中进行分配。 + +MEM_CANCEL_ONLINE + 如果MEM_GOING_ONLINE失败,则生成。 + +MEM_ONLINE + 当内存成功上线时产生。回调可以从新的内存中分配页面。 + +MEM_GOING_OFFLINE + 在开始对内存进行下线处理时生成。从内存中的分配不再可能,但是一些要下线的内存 + 仍然在使用。回调可以用来释放一个子系统在指定内存块中已知的内存。 + +MEM_CANCEL_OFFLINE + 如果MEM_GOING_OFFLINE失败,则生成。来自我们试图离线的内存块中的内存又可以使 + 用了。 + +MEM_OFFLINE + 在内存下线完成后生成。 + +可以通过调用如下函数来注册一个回调程序: + + hotplug_memory_notifier(callback_func, priority) + +优先级数值较高的回调函数在数值较低的回调函数之前被调用。 + +一个回调函数必须有以下原型:: + + int callback_func( + struct notifier_block *self, unsigned long action, void *arg); + +回调函数的第一个参数(self)是指向回调函数本身的通知器链块的一个指针。第二个参 +数(action)是上述的事件类型之一。第三个参数(arg)传递一个指向 +memory_notify结构体的指针:: + + struct memory_notify { + unsigned long start_pfn; + unsigned long nr_pages; + int status_change_nid_normal; + int status_change_nid; + } + +- start_pfn是在线/离线内存的start_pfn。 + +- nr_pages是在线/离线内存的页数。 + +- status_change_nid_normal是当nodemask的N_NORMAL_MEMORY被设置/清除时设置节 + 点id,如果是-1,则nodemask状态不改变。 + +- status_change_nid是当nodemask的N_MEMORY被(将)设置/清除时设置的节点id。这 + 意味着一个新的(没上线的)节点通过联机获得新的内存,而一个节点失去了所有的内 + 存。如果这个值为-1,那么nodemask的状态就不会改变。 + + 如果 status_changed_nid* >= 0,回调应该在必要时为节点创建/丢弃结构体。 + +回调程序应返回 ``include/linux/notifier.h`` 中定义的NOTIFY_DONE, NOTIFY_OK, +NOTIFY_BAD, NOTIFY_STOP中的一个值。 + +NOTIFY_DONE和NOTIFY_OK对进一步处理没有影响。 + +NOTIFY_BAD是作为对MEM_GOING_ONLINE、MEM_GOING_OFFLINE、MEM_ONLINE或MEM_OFFLINE +动作的回应,用于取消热插拔。它停止对通知队列的进一步处理。 + +NOTIFY_STOP停止对通知队列的进一步处理。 + +内部锁 +====== + +当添加/删除使用内存块设备(即普通RAM)的内存时,device_hotplug_lock应该被保持 +为: + +- 针对在线/离线请求进行同步(例如,通过sysfs)。这样一来,内存块设备只有在内存 + 被完全添加后才能被用户空间访问(.online/.state属性)。而在删除内存时,我们知 + 道没有人在临界区。 + +- 与CPU热拔插或类似操作同步(例如ACPI和PPC相关操作) + +特别是,在添加内存和用户空间试图以比预期更快的速度上线该内存时,有可能出现锁反转, +使用device_hotplug_lock可以避免此情况: + +- device_online()将首先接受device_lock(),然后是mem_hotplug_lock。 + +- add_memory_resource()将首先使用mem_hotplug_lock,然后是device_lock()(在创 + 建设备时,在bus_add_device()期间)。 + +由于在使用device_lock()之前,设备对用户空间是可见的,这可能导致锁的反转。 + +内存的上线/下线应该通过device_online()/device_offline()完成————确保它与通过 +sysfs进行的操作正确同步。建议持有device_hotplug_lock(例如,保护online_type)。 + +当添加/删除/上线/下线内存或者添加/删除异构或设备内存时,我们应该始终持有写模式的 +mem_hotplug_lock,以序列化内存热插拔(例如访问全局/区域变量)。 + +此外,mem_hotplug_lock(与device_hotplug_lock相反)在读取模式下允许一个相当 +有效的get_online_mems/put_online_mems实现,所以访问内存的代码可以防止该内存 +消失。 diff --git a/Documentation/translations/zh_CN/core-api/mm-api.rst b/Documentation/translations/zh_CN/core-api/mm-api.rst new file mode 100644 index 000000000..a732b0eeb --- /dev/null +++ b/Documentation/translations/zh_CN/core-api/mm-api.rst @@ -0,0 +1,131 @@ +.. include:: ../disclaimer-zh_CN.rst + +:Original: Documentation/core-api/mm-api.rst + +:翻译: + + 司延腾 Yanteng Si <siyanteng@loongson.cn> + 周彬彬 Binbin Zhou <zhoubinbin@loongson.cn> + +:校译: + + 时奎亮<alexs@kernel.org> + +.. _cn_core-api_mm-api: + +============ +内存管理APIs +============ + +API(Application Programming Interface,应用程序接口) + +用户空间内存访问 +================ + +该API在以下内核代码中: + +arch/x86/include/asm/uaccess.h + +arch/x86/lib/usercopy_32.c + +mm/gup.c + +.. _cn_mm-api-gfp-flags: + +内存分配控制 +============ + +该API在以下内核代码中: + +include/linux/gfp.h + +Slab缓存 +======== + +此缓存非cpu片上缓存,请读者自行查阅资料。 + +该API在以下内核代码中: + +include/linux/slab.h + +mm/slab.c + +mm/slab_common.c + +mm/util.c + +虚拟连续(内存页)映射 +====================== + +该API在以下内核代码中: + +mm/vmalloc.c + + +文件映射和页面缓存 +================== + +该API在以下内核代码中: + +文件映射 +-------- + +mm/filemap.c + +预读 +---- + +mm/readahead.c + +回写 +---- + +mm/page-writeback.c + +截断 +---- + +mm/truncate.c + +include/linux/pagemap.h + +内存池 +====== + +该API在以下内核代码中: + +mm/mempool.c + +DMA池 +===== + +DMA(Direct Memory Access,直接存储器访问) + +该API在以下内核代码中: + +mm/dmapool.c + +更多的内存管理函数 +================== + +该API在以下内核代码中: + +mm/memory.c + +mm/page_alloc.c + +mm/mempolicy.c + +include/linux/mm_types.h + +include/linux/mm_inline.h + +include/linux/page-flags.h + +include/linux/mm.h + +include/linux/page_ref.h + +include/linux/mmzone.h + +mm/util.c diff --git a/Documentation/translations/zh_CN/core-api/packing.rst b/Documentation/translations/zh_CN/core-api/packing.rst new file mode 100644 index 000000000..c0aab3a34 --- /dev/null +++ b/Documentation/translations/zh_CN/core-api/packing.rst @@ -0,0 +1,160 @@ +.. SPDX-License-Identifier: GPL-2.0+ + +.. include:: ../disclaimer-zh_CN.rst + +:Original: Documentation/core-api/packing.rst + +:翻译: + + 周彬彬 Binbin Zhou <zhoubinbin@loongson.cn> + +:校译: + + 司延腾 Yanteng Si <siyanteng@loongson.cn> + 吴想成 Wu Xiangcheng <bobwxc@email.cn> + 时奎亮 Alex Shi <alexs@kernel.org> + +======================== +通用的位域打包和解包函数 +======================== + +问题陈述 +-------- + +使用硬件时,必须在几种与其交互的方法之间进行选择。 + +可以将指针映射到在硬件设备的内存区上精心设计的结构体,并将其字段作为结构成员(可 +能声明为位域)访问。但是由于CPU和硬件设备之间潜在的字节顺序不匹配,以这种方式编写 +代码会降低其可移植性。 + +此外,必须密切注意将硬件文档中的寄存器定义转换为结构的位域索引。此外,一些硬件 +(通常是网络设备)倾向于以违反任何合理字边界(有时甚至是64位)的方式对其寄存器字 +段进行分组。这就造成了不得不在结构中定义寄存器字段的“高”和“低”部分的不便。 + +结构域定义的更可靠的替代方法是通过移动适当数量的位来提取所需的字段。但这仍然不能 +防止字节顺序不匹配,除非所有内存访问都是逐字节执行的。此外,代码很容易变得杂乱无 +章,同时可能会在所需的许多位移操作中丢失一些高层次的想法。 + +许多驱动程序采用了位移的方法,然后试图用定制的宏来减少杂乱无章的东西,但更多的时 +候,这些宏所采用的捷径依旧妨碍了代码真正的可移植性。 + +解决方案 +-------- + +该API涉及2个基本操作: + + - 将一个CPU可使用的数字打包到内存缓冲区中(具有硬件约束/特殊性)。 + - 将内存缓冲区(具有硬件约束/特殊性)解压缩为一个CPU可使用的数字。 + +该API提供了对所述硬件约束和特殊性以及CPU字节序的抽象,因此这两者之间可能不匹配。 + +这些API函数的基本单元是u64。从CPU的角度来看,位63总是意味着字节7的位偏移量7,尽管 +只是逻辑上的。问题是:我们将这个比特放在内存的什么位置? + +以下示例介绍了打包u64字段的内存布局。打包缓冲区中的字节偏移量始终默认为0,1...7。 +示例显示的是逻辑字节和位所在的位置。 + +1. 通常情况下(无特殊性),我们会这样做: + +:: + + 63 62 61 60 59 58 57 56 55 54 53 52 51 50 49 48 47 46 45 44 43 42 41 40 39 38 37 36 35 34 33 32 + 7 6 5 4 + 31 30 29 28 27 26 25 24 23 22 21 20 19 18 17 16 15 14 13 12 11 10 9 8 7 6 5 4 3 2 1 0 + 3 2 1 0 + +也就是说,CPU可使用的u64的MSByte(7)位于内存偏移量0处,而u64的LSByte(0)位于内存偏移量7处。 + +这对应于大多数人认为的“大端”,其中位i对应于数字2^i。这在代码注释中也称为“逻辑”符号。 + + +2. 如果设置了QUIRK_MSB_ON_THE_RIGHT,我们按如下方式操作: + +:: + + 56 57 58 59 60 61 62 63 48 49 50 51 52 53 54 55 40 41 42 43 44 45 46 47 32 33 34 35 36 37 38 39 + 7 6 5 4 + 24 25 26 27 28 29 30 31 16 17 18 19 20 21 22 23 8 9 10 11 12 13 14 15 0 1 2 3 4 5 6 7 + 3 2 1 0 + +也就是说,QUIRK_MSB_ON_THE_RIGHT不会影响字节定位,但会反转字节内的位偏移量。 + + +3. 如果设置了QUIRK_LITTLE_ENDIAN,我们按如下方式操作: + +:: + + 39 38 37 36 35 34 33 32 47 46 45 44 43 42 41 40 55 54 53 52 51 50 49 48 63 62 61 60 59 58 57 56 + 4 5 6 7 + 7 6 5 4 3 2 1 0 15 14 13 12 11 10 9 8 23 22 21 20 19 18 17 16 31 30 29 28 27 26 25 24 + 0 1 2 3 + +因此,QUIRK_LITTLE_ENDIAN意味着在内存区域内,每个4字节的字的每个字节都被放置在与 +该字的边界相比的镜像位置。 + + +4. 如果设置了QUIRK_MSB_ON_THE_RIGHT和QUIRK_LITTLE_ENDIAN,我们这样做: + +:: + + 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 + 4 5 6 7 + 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 + 0 1 2 3 + + +5. 如果只设置了QUIRK_LSW32_IS_FIRST,我们这样做: + +:: + + 31 30 29 28 27 26 25 24 23 22 21 20 19 18 17 16 15 14 13 12 11 10 9 8 7 6 5 4 3 2 1 0 + 3 2 1 0 + 63 62 61 60 59 58 57 56 55 54 53 52 51 50 49 48 47 46 45 44 43 42 41 40 39 38 37 36 35 34 33 32 + 7 6 5 4 + +在这种情况下,8字节内存区域解释如下:前4字节对应最不重要的4字节的字,后4字节对应 +更重要的4字节的字。 + +6. 如果设置了QUIRK_LSW32_IS_FIRST和QUIRK_MSB_ON_THE_RIGHT,我们这样做: + +:: + + 24 25 26 27 28 29 30 31 16 17 18 19 20 21 22 23 8 9 10 11 12 13 14 15 0 1 2 3 4 5 6 7 + 3 2 1 0 + 56 57 58 59 60 61 62 63 48 49 50 51 52 53 54 55 40 41 42 43 44 45 46 47 32 33 34 35 36 37 38 39 + 7 6 5 4 + + +7. 如果设置了QUIRK_LSW32_IS_FIRST和QUIRK_LITTLE_ENDIAN,则如下所示: + +:: + + 7 6 5 4 3 2 1 0 15 14 13 12 11 10 9 8 23 22 21 20 19 18 17 16 31 30 29 28 27 26 25 24 + 0 1 2 3 + 39 38 37 36 35 34 33 32 47 46 45 44 43 42 41 40 55 54 53 52 51 50 49 48 63 62 61 60 59 58 57 56 + 4 5 6 7 + + +8. 如果设置了QUIRK_LSW32_IS_FIRST,QUIRK_LITTLE_ENDIAN和QUIRK_MSB_ON_THE_RIGHT, + 则如下所示: + +:: + + 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 + 0 1 2 3 + 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 + 4 5 6 7 + + +我们总是认为我们的偏移量好像没有特殊性,然后在访问内存区域之前翻译它们。 + +预期用途 +-------- + +选择使用该API的驱动程序首先需要确定上述3种quirk组合(共8种)中的哪一种与硬件文档 +中描述的相匹配。然后,他们应该封装packing()函数,创建一个新的xxx_packing(),使用 +适当的QUIRK_* one-hot 位集合来调用它。 + +packing()函数返回一个int类型的错误码,以防止程序员使用不正确的API。这些错误预计不 +会在运行时发生,因此xxx_packing()返回void并简单地接受这些错误是合理的。它可以选择 +转储栈或打印错误描述。 diff --git a/Documentation/translations/zh_CN/core-api/padata.rst b/Documentation/translations/zh_CN/core-api/padata.rst new file mode 100644 index 000000000..781d30675 --- /dev/null +++ b/Documentation/translations/zh_CN/core-api/padata.rst @@ -0,0 +1,161 @@ +.. SPDX-License-Identifier: GPL-2.0 + +.. include:: ../disclaimer-zh_CN.rst + +:Original: Documentation/core-api/padata.rst + +:翻译: + + 司延腾 Yanteng Si <siyanteng@loongson.cn> + +.. _cn_core_api_padata.rst: + +================== +padata并行执行机制 +================== + +:日期: 2020年5月 + +Padata是一种机制,内核可以通过此机制将工作分散到多个CPU上并行完成,同时 +可以选择保持它们的顺序。 + +它最初是为IPsec开发的,它需要在不对这些数据包重新排序的其前提下,为大量的数 +据包进行加密和解密。这是目前padata的序列化作业支持的唯一用途。 + +Padata还支持多线程作业,将作业平均分割,同时在线程之间进行负载均衡和协调。 + +执行序列化作业 +============== + +初始化 +------ + +使用padata执行序列化作业的第一步是建立一个padata_instance结构体,以全面 +控制作业的运行方式:: + + #include <linux/padata.h> + + struct padata_instance *padata_alloc(const char *name); + +'name'即标识了这个实例。 + +然后,通过分配一个padata_shell来完成padata的初始化:: + + struct padata_shell *padata_alloc_shell(struct padata_instance *pinst); + +一个padata_shell用于向padata提交一个作业,并允许一系列这样的作业被独立地 +序列化。一个padata_instance可以有一个或多个padata_shell与之相关联,每个 +都允许一系列独立的作业。 + +修改cpumasks +------------ + +用于运行作业的CPU可以通过两种方式改变,通过padata_set_cpumask()编程或通 +过sysfs。前者的定义是:: + + int padata_set_cpumask(struct padata_instance *pinst, int cpumask_type, + cpumask_var_t cpumask); + +这里cpumask_type是PADATA_CPU_PARALLEL(并行)或PADATA_CPU_SERIAL(串行)之一,其中并 +行cpumask描述了哪些处理器将被用来并行执行提交给这个实例的作业,串行cpumask +定义了哪些处理器被允许用作串行化回调处理器。 cpumask指定了要使用的新cpumask。 + +一个实例的cpumasks可能有sysfs文件。例如,pcrypt的文件在 +/sys/kernel/pcrypt/<instance-name>。在一个实例的目录中,有两个文件,parallel_cpumask +和serial_cpumask,任何一个cpumask都可以通过在文件中回显(echo)一个bitmask +来改变,比如说:: + + echo f > /sys/kernel/pcrypt/pencrypt/parallel_cpumask + +读取其中一个文件会显示用户提供的cpumask,它可能与“可用”的cpumask不同。 + +Padata内部维护着两对cpumask,用户提供的cpumask和“可用的”cpumask(每一对由一个 +并行和一个串行cpumask组成)。用户提供的cpumasks在实例分配时默认为所有可能的CPU, +并且可以如上所述进行更改。可用的cpumasks总是用户提供的cpumasks的一个子集,只包 +含用户提供的掩码中的在线CPU;这些是padata实际使用的cpumasks。因此,向padata提 +供一个包含离线CPU的cpumask是合法的。一旦用户提供的cpumask中的一个离线CPU上线, +padata就会使用它。 + +改变CPU掩码的操作代价很高,所以不应频繁更改。 + +运行一个作业 +------------- + +实际上向padata实例提交工作需要创建一个padata_priv结构体,它代表一个作业:: + + struct padata_priv { + /* Other stuff here... */ + void (*parallel)(struct padata_priv *padata); + void (*serial)(struct padata_priv *padata); + }; + +这个结构体几乎肯定会被嵌入到一些针对要做的工作的大结构体中。它的大部分字段对 +padata来说是私有的,但是这个结构在初始化时应该被清零,并且应该提供parallel()和 +serial()函数。在完成工作的过程中,这些函数将被调用,我们马上就会遇到。 + +工作的提交是通过:: + + int padata_do_parallel(struct padata_shell *ps, + struct padata_priv *padata, int *cb_cpu); + +ps和padata结构体必须如上所述进行设置;cb_cpu指向作业完成后用于最终回调的首选CPU; +它必须在当前实例的CPU掩码中(如果不是,cb_cpu指针将被更新为指向实际选择的CPU)。 +padata_do_parallel()的返回值在成功时为0,表示工作正在进行中。-EBUSY意味着有人 +在其他地方正在搞乱实例的CPU掩码,而当cb_cpu不在串行cpumask中、并行或串行cpumasks +中无在线CPU,或实例停止时,则会出现-EINVAL反馈。 + +每个提交给padata_do_parallel()的作业将依次传递给一个CPU上的上述parallel()函数 +的一个调用,所以真正的并行是通过提交多个作业来实现的。parallel()在运行时禁用软 +件中断,因此不能睡眠。parallel()函数把获得的padata_priv结构体指针作为其唯一的参 +数;关于实际要做的工作的信息可能是通过使用container_of()找到封装结构体来获得的。 + +请注意,parallel()没有返回值;padata子系统假定parallel()将从此时开始负责这项工 +作。作业不需要在这次调用中完成,但是,如果parallel()留下了未完成的工作,它应该准 +备在前一个作业完成之前,被以新的作业再次调用 + +序列化作业 +---------- + +当一个作业完成时,parallel()(或任何实际完成该工作的函数)应该通过调用通知padata此 +事:: + + void padata_do_serial(struct padata_priv *padata); + +在未来的某个时刻,padata_do_serial()将触发对padata_priv结构体中serial()函数的调 +用。这个调用将发生在最初要求调用padata_do_parallel()的CPU上;它也是在本地软件中断 +被禁用的情况下运行的。 +请注意,这个调用可能会被推迟一段时间,因为padata代码会努力确保作业按照提交的顺序完 +成。 + +销毁 +---- + +清理一个padata实例时,可以预见的是调用两个free函数,这两个函数对应于分配的逆过程:: + + void padata_free_shell(struct padata_shell *ps); + void padata_free(struct padata_instance *pinst); + +用户有责任确保在调用上述任何一项之前,所有未完成的工作都已完成。 + +运行多线程作业 +============== + +一个多线程作业有一个主线程和零个或多个辅助线程,主线程参与作业,然后等待所有辅助线 +程完成。padata将作业分割成称为chunk的单元,其中chunk是一个线程在一次调用线程函数 +中完成的作业片段。 + +用户必须做三件事来运行一个多线程作业。首先,通过定义一个padata_mt_job结构体来描述 +作业,这在接口部分有解释。这包括一个指向线程函数的指针,padata每次将作业块分配给线 +程时都会调用这个函数。然后,定义线程函数,它接受三个参数: ``start`` 、 ``end`` 和 ``arg`` , +其中前两个参数限定了线程操作的范围,最后一个是指向作业共享状态的指针,如果有的话。 +准备好共享状态,它通常被分配在主线程的堆栈中。最后,调用padata_do_multithreaded(), +它将在作业完成后返回。 + +接口 +==== + +该API在以下内核代码中: + +include/linux/padata.h + +kernel/padata.c diff --git a/Documentation/translations/zh_CN/core-api/printk-basics.rst b/Documentation/translations/zh_CN/core-api/printk-basics.rst new file mode 100644 index 000000000..59c6efb3f --- /dev/null +++ b/Documentation/translations/zh_CN/core-api/printk-basics.rst @@ -0,0 +1,111 @@ +.. SPDX-License-Identifier: GPL-2.0 +.. include:: ../disclaimer-zh_CN.rst + +:Original: Documentation/core-api/printk-basics.rst + +:翻译: + + 司延腾 Yanteng Si <siyanteng@loongson.cn> + 周彬彬 Binbin Zhou <zhoubinbin@loongson.cn> + +.. _cn_printk-basics.rst: + +================== +使用printk记录消息 +================== + +printk()是Linux内核中最广为人知的函数之一。它是我们打印消息的标准工具,通常也是追踪和调试 +的最基本方法。如果你熟悉printf(3),你就能够知道printk()是基于它的,尽管它在功能上有一些不 +同之处: + + - printk() 消息可以指定日志级别。 + + - 格式字符串虽然与C99基本兼容,但并不遵循完全相同的规范。它有一些扩展和一些限制(没 + 有 ``%n`` 或浮点转换指定符)。参见:ref: `如何正确地获得printk格式指定符<printk-specifiers>` 。 + +所有的printk()消息都会被打印到内核日志缓冲区,这是一个通过/dev/kmsg输出到用户空间的环 +形缓冲区。读取它的通常方法是使用 ``dmesg`` 。 + +printk()的用法通常是这样的:: + + printk(KERN_INFO "Message: %s\n", arg); + +其中 ``KERN_INFO`` 是日志级别(注意,它与格式字符串连在一起,日志级别不是一个单独的参数)。 +可用的日志级别是: + + ++----------------+--------+-----------------------------------------------+ +| 名称 | 字符串 | 别名函数 | ++================+========+===============================================+ +| KERN_EMERG | "0" | pr_emerg() | ++----------------+--------+-----------------------------------------------+ +| KERN_ALERT | "1" | pr_alert() | ++----------------+--------+-----------------------------------------------+ +| KERN_CRIT | "2" | pr_crit() | ++----------------+--------+-----------------------------------------------+ +| KERN_ERR | "3" | pr_err() | ++----------------+--------+-----------------------------------------------+ +| KERN_WARNING | "4" | pr_warn() | ++----------------+--------+-----------------------------------------------+ +| KERN_NOTICE | "5" | pr_notice() | ++----------------+--------+-----------------------------------------------+ +| KERN_INFO | "6" | pr_info() | ++----------------+--------+-----------------------------------------------+ +| KERN_DEBUG | "7" | pr_debug() and pr_devel() 若定义了DEBUG | ++----------------+--------+-----------------------------------------------+ +| KERN_DEFAULT | "" | | ++----------------+--------+-----------------------------------------------+ +| KERN_CONT | "c" | pr_cont() | ++----------------+--------+-----------------------------------------------+ + + +日志级别指定了一条消息的重要性。内核根据日志级别和当前 *console_loglevel* (一个内核变量)决 +定是否立即显示消息(将其打印到当前控制台)。如果消息的优先级比 *console_loglevel* 高(日志级 +别值较低),消息将被打印到控制台。 + +如果省略了日志级别,则以 ``KERN_DEFAULT`` 级别打印消息。 + +你可以用以下方法检查当前的 *console_loglevel* :: + + $ cat /proc/sys/kernel/printk + 4 4 1 7 + +结果显示了 *current*, *default*, *minimum* 和 *boot-time-default* 日志级别 + +要改变当前的 console_loglevel,只需在 ``/proc/sys/kernel/printk`` 中写入所需的 +级别。例如,要打印所有的消息到控制台上:: + + # echo 8 > /proc/sys/kernel/printk + +另一种方式,使用 ``dmesg``:: + + # dmesg -n 5 + +设置 console_loglevel 打印 KERN_WARNING (4) 或更严重的消息到控制台。更多消息参 +见 ``dmesg(1)`` 。 + +作为printk()的替代方案,你可以使用 ``pr_*()`` 别名来记录日志。这个系列的宏在宏名中 +嵌入了日志级别。例如:: + + pr_info("Info message no. %d\n", msg_num); + +打印 ``KERN_INFO`` 消息。 + +除了比等效的printk()调用更简洁之外,它们还可以通过pr_fmt()宏为格式字符串使用一个通用 +的定义。例如,在源文件的顶部(在任何 ``#include`` 指令之前)定义这样的内容。:: + + #define pr_fmt(fmt) "%s:%s: " fmt, KBUILD_MODNAME, __func__ + +会在该文件中的每一条 pr_*() 消息前加上发起该消息的模块和函数名称。 + +为了调试,还有两个有条件编译的宏: +pr_debug()和pr_devel(),除非定义了 ``DEBUG`` (或者在pr_debug()的情况下定义了 +``CONFIG_DYNAMIC_DEBUG`` ),否则它们会被编译。 + + +函数接口 +======== + +该API在以下内核代码中: + +include/linux/printk.h diff --git a/Documentation/translations/zh_CN/core-api/printk-formats.rst b/Documentation/translations/zh_CN/core-api/printk-formats.rst new file mode 100644 index 000000000..bd36d35eb --- /dev/null +++ b/Documentation/translations/zh_CN/core-api/printk-formats.rst @@ -0,0 +1,598 @@ +.. include:: ../disclaimer-zh_CN.rst + +:Original: Documentation/core-api/printk-formats.rst + +:翻译: + + 司延腾 Yanteng Si <siyanteng@loongson.cn> + 周彬彬 Binbin Zhou <zhoubinbin@loongson.cn> + +.. _cn_printk-formats.rst: + +============================== +如何获得正确的printk格式占位符 +============================== + + + +:作者: Randy Dunlap <rdunlap@infradead.org> +:作者: Andrew Murray <amurray@mpc-data.co.uk> + + +整数类型 +======== + +:: + + 若变量类型是Type,则使用printk格式占位符。 + ------------------------------------------- + char %d 或 %x + unsigned char %u 或 %x + short int %d 或 %x + unsigned short int %u 或 %x + int %d 或 %x + unsigned int %u 或 %x + long %ld 或 %lx + unsigned long %lu 或 %lx + long long %lld 或 %llx + unsigned long long %llu 或 %llx + size_t %zu 或 %zx + ssize_t %zd 或 %zx + s8 %d 或 %x + u8 %u 或 %x + s16 %d 或 %x + u16 %u 或 %x + s32 %d 或 %x + u32 %u 或 %x + s64 %lld 或 %llx + u64 %llu 或 %llx + + +如果 <type> 的大小依赖于配置选项 (例如 sector_t, blkcnt_t) 或其大小依赖于架构 +(例如 tcflag_t),则使用其可能的最大类型的格式占位符并显式强制转换为它。 + +例如 + +:: + + printk("test: sector number/total blocks: %llu/%llu\n", + (unsigned long long)sector, (unsigned long long)blockcount); + +提醒:sizeof()返回类型为size_t。 + +内核的printf不支持%n。显而易见,浮点格式(%e, %f, %g, %a)也不被识别。使用任何不 +支持的占位符或长度限定符都会导致一个WARN并且终止vsnprintf()执行。 + +指针类型 +======== + +一个原始指针值可以用%p打印,它将在打印前对地址进行哈希处理。内核也支持扩展占位符来打印 +不同类型的指针。 + +一些扩展占位符会打印给定地址上的数据,而不是打印地址本身。在这种情况下,以下错误消息可能 +会被打印出来,而不是无法访问的消息:: + + (null) data on plain NULL address + (efault) data on invalid address + (einval) invalid data on a valid address + +普通指针 +---------- + +:: + + %p abcdef12 or 00000000abcdef12 + +没有指定扩展名的指针(即没有修饰符的%p)被哈希(hash),以防止内核内存布局消息的泄露。这 +样还有一个额外的好处,就是提供一个唯一的标识符。在64位机器上,前32位被清零。当没有足够的 +熵进行散列处理时,内核将打印(ptrval)代替 + +如果可能的话,使用专门的修饰符,如%pS或%pB(如下所述),以避免打印一个必须事后解释的非哈 +希地址。如果不可能,而且打印地址的目的是为调试提供更多的消息,使用%p,并在调试过程中 +用 ``no_hash_pointers`` 参数启动内核,这将打印所有未修改的%p地址。如果你 *真的* 想知 +道未修改的地址,请看下面的%px。 + +如果(也只有在)你将地址作为虚拟文件的内容打印出来,例如在procfs或sysfs中(使用 +seq_printf(),而不是printk())由用户空间进程读取,使用下面描述的%pK修饰符,不 +要用%p或%px。 + + +错误指针 +-------- + +:: + + %pe -ENOSPC + +用于打印错误指针(即IS_ERR()为真的指针)的符号错误名。不知道符号名的错误值会以十进制打印, +而作为%pe参数传递的非ERR_PTR会被视为普通的%p。 + +符号/函数指针 +------------- + +:: + + %pS versatile_init+0x0/0x110 + %ps versatile_init + %pSR versatile_init+0x9/0x110 + (with __builtin_extract_return_addr() translation) + %pB prev_fn_of_versatile_init+0x88/0x88 + + +``S`` 和 ``s`` 占位符用于打印符号格式的指针。它们的结果是符号名称带有(S)或不带有(s)偏移 +量。如果禁用KALLSYMS,则打印符号地址。 + +``B`` 占位符的结果是带有偏移量的符号名,在打印堆栈回溯时应该使用。占位符将考虑编译器优化 +的影响,当使用尾部调用并使用noreturn GCC属性标记时,可能会发生这种优化。 + +如果指针在一个模块内,模块名称和可选的构建ID将被打印在符号名称之后,并在说明符的末尾添加 +一个额外的 ``b`` 。 + +:: + + %pS versatile_init+0x0/0x110 [module_name] + %pSb versatile_init+0x0/0x110 [module_name ed5019fdf5e53be37cb1ba7899292d7e143b259e] + %pSRb versatile_init+0x9/0x110 [module_name ed5019fdf5e53be37cb1ba7899292d7e143b259e] + (with __builtin_extract_return_addr() translation) + %pBb prev_fn_of_versatile_init+0x88/0x88 [module_name ed5019fdf5e53be37cb1ba7899292d7e143b259e] + +来自BPF / tracing追踪的探查指针 +---------------------------------- + +:: + + %pks kernel string + %pus user string + +``k`` 和 ``u`` 指定符用于打印来自内核内存(k)或用户内存(u)的先前探测的内存。后面的 ``s`` 指 +定符的结果是打印一个字符串。对于直接在常规的vsnprintf()中使用时,(k)和(u)注释被忽略,但是,当 +在BPF的bpf_trace_printk()之外使用时,它会读取它所指向的内存,不会出现错误。 + +内核指针 +-------- + +:: + + %pK 01234567 or 0123456789abcdef + +用于打印应该对非特权用户隐藏的内核指针。%pK的行为取决于kptr_restrict sysctl——详见 +Documentation/admin-guide/sysctl/kernel.rst。 + +未经修改的地址 +-------------- + +:: + + %px 01234567 or 0123456789abcdef + +对于打印指针,当你 *真的* 想打印地址时。在用%px打印指针之前,请考虑你是否泄露了内核内 +存布局的敏感消息。%px在功能上等同于%lx(或%lu)。%px是首选,因为它在grep查找时更唯一。 +如果将来我们需要修改内核处理打印指针的方式,我们将能更好地找到调用点。 + +在使用%px之前,请考虑使用%p并在调试过程中启用' ' no_hash_pointer ' '内核参数是否足 +够(参见上面的%p描述)。%px的一个有效场景可能是在panic发生之前立即打印消息,这样无论如何 +都可以防止任何敏感消息被利用,使用%px就不需要用no_hash_pointer来重现panic。 + +指针差异 +-------- + +:: + + %td 2560 + %tx a00 + +为了打印指针的差异,使用ptrdiff_t的%t修饰符。 + +例如:: + + printk("test: difference between pointers: %td\n", ptr2 - ptr1); + +结构体资源(Resources) +----------------------- + +:: + + %pr [mem 0x60000000-0x6fffffff flags 0x2200] or + [mem 0x0000000060000000-0x000000006fffffff flags 0x2200] + %pR [mem 0x60000000-0x6fffffff pref] or + [mem 0x0000000060000000-0x000000006fffffff pref] + +用于打印结构体资源。 ``R`` 和 ``r`` 占位符的结果是打印出的资源带有(R)或不带有(r)解码标志 +成员。 + +通过引用传递。 + +物理地址类型 phys_addr_t +------------------------ + +:: + + %pa[p] 0x01234567 or 0x0123456789abcdef + +用于打印phys_addr_t类型(以及它的衍生物,如resource_size_t),该类型可以根据构建选项而 +变化,无论CPU数据真实物理地址宽度如何。 + +通过引用传递。 + +DMA地址类型dma_addr_t +--------------------- + +:: + + %pad 0x01234567 or 0x0123456789abcdef + +用于打印dma_addr_t类型,该类型可以根据构建选项而变化,而不考虑CPU数据路径的宽度。 + +通过引用传递。 + +原始缓冲区为转义字符串 +---------------------- + +:: + + %*pE[achnops] + +用于将原始缓冲区打印成转义字符串。对于以下缓冲区:: + + 1b 62 20 5c 43 07 22 90 0d 5d + +几个例子展示了如何进行转换(不包括两端的引号)。:: + + %*pE "\eb \C\a"\220\r]" + %*pEhp "\x1bb \C\x07"\x90\x0d]" + %*pEa "\e\142\040\\\103\a\042\220\r\135" + +转换规则是根据可选的标志组合来应用的(详见:c:func:`string_escape_mem` 内核文档): + + - a - ESCAPE_ANY + - c - ESCAPE_SPECIAL + - h - ESCAPE_HEX + - n - ESCAPE_NULL + - o - ESCAPE_OCTAL + - p - ESCAPE_NP + - s - ESCAPE_SPACE + +默认情况下,使用 ESCAPE_ANY_NP。 + +ESCAPE_ANY_NP是许多情况下的明智选择,特别是对于打印SSID。 + +如果字段宽度被省略,那么将只转义1个字节。 + +原始缓冲区为十六进制字符串 +-------------------------- + +:: + + %*ph 00 01 02 ... 3f + %*phC 00:01:02: ... :3f + %*phD 00-01-02- ... -3f + %*phN 000102 ... 3f + +对于打印小的缓冲区(最长64个字节),可以用一定的分隔符作为一个 +十六进制字符串。对于较大的缓冲区,可以考虑使用 +:c:func:`print_hex_dump` 。 + +MAC/FDDI地址 +------------ + +:: + + %pM 00:01:02:03:04:05 + %pMR 05:04:03:02:01:00 + %pMF 00-01-02-03-04-05 + %pm 000102030405 + %pmR 050403020100 + +用于打印以十六进制表示的6字节MAC/FDDI地址。 ``M`` 和 ``m`` 占位符导致打印的 +地址有(M)或没有(m)字节分隔符。默认的字节分隔符是冒号(:)。 + +对于FDDI地址,可以在 ``M`` 占位符之后使用 ``F`` 说明,以使用破折号(——)分隔符 +代替默认的分隔符。 + +对于蓝牙地址, ``R`` 占位符应使用在 ``M`` 占位符之后,以使用反转的字节顺序,适 +合于以小尾端顺序的蓝牙地址的肉眼可见的解析。 + +通过引用传递。 + +IPv4地址 +-------- + +:: + + %pI4 1.2.3.4 + %pi4 001.002.003.004 + %p[Ii]4[hnbl] + +用于打印IPv4点分隔的十进制地址。 ``I4`` 和 ``i4`` 占位符的结果是打印的地址 +有(i4)或没有(I4)前导零。 + +附加的 ``h`` 、 ``n`` 、 ``b`` 和 ``l`` 占位符分别用于指定主机、网络、大 +尾端或小尾端地址。如果没有提供占位符,则使用默认的网络/大尾端顺序。 + +通过引用传递。 + +IPv6 地址 +--------- + +:: + + %pI6 0001:0002:0003:0004:0005:0006:0007:0008 + %pi6 00010002000300040005000600070008 + %pI6c 1:2:3:4:5:6:7:8 + +用于打印IPv6网络顺序的16位十六进制地址。 ``I6`` 和 ``i6`` 占位符的结果是 +打印的地址有(I6)或没有(i6)分号。始终使用前导零。 + +额外的 ``c`` 占位符可与 ``I`` 占位符一起使用,以打印压缩的IPv6地址,如 +https://tools.ietf.org/html/rfc5952 所述 + +通过引用传递。 + +IPv4/IPv6地址(generic, with port, flowinfo, scope) +-------------------------------------------------- + +:: + + %pIS 1.2.3.4 or 0001:0002:0003:0004:0005:0006:0007:0008 + %piS 001.002.003.004 or 00010002000300040005000600070008 + %pISc 1.2.3.4 or 1:2:3:4:5:6:7:8 + %pISpc 1.2.3.4:12345 or [1:2:3:4:5:6:7:8]:12345 + %p[Ii]S[pfschnbl] + +用于打印一个IP地址,不需要区分它的类型是AF_INET还是AF_INET6。一个指向有效结构 +体sockaddr的指针,通过 ``IS`` 或 ``IS`` 指定,可以传递给这个格式占位符。 + +附加的 ``p`` 、 ``f`` 和 ``s`` 占位符用于指定port(IPv4, IPv6)、 +flowinfo (IPv6)和sope(IPv6)。port有一个 ``:`` 前缀,flowinfo是 ``/`` 和 +范围是 ``%`` ,每个后面都跟着实际的值。 + +对于IPv6地址,如果指定了额外的指定符 ``c`` ,则使用 +https://tools.ietf.org/html/rfc5952 描述的压缩IPv6地址。 +如https://tools.ietf.org/html/draft-ietf-6man-text-addr-representation-07 +所建议的,IPv6地址由'[',']'包围,以防止出现额外的占位符 ``p`` , ``f`` 或 ``s`` 。 + +对于IPv4地址,也可以使用额外的 ``h`` , ``n`` , ``b`` 和 ``l`` 说 +明符,但对于IPv6地址则忽略。 + +通过引用传递。 + +更多例子:: + + %pISfc 1.2.3.4 or [1:2:3:4:5:6:7:8]/123456789 + %pISsc 1.2.3.4 or [1:2:3:4:5:6:7:8]%1234567890 + %pISpfc 1.2.3.4:12345 or [1:2:3:4:5:6:7:8]:12345/123456789 + +UUID/GUID地址 +------------- + +:: + + %pUb 00010203-0405-0607-0809-0a0b0c0d0e0f + %pUB 00010203-0405-0607-0809-0A0B0C0D0E0F + %pUl 03020100-0504-0706-0809-0a0b0c0e0e0f + %pUL 03020100-0504-0706-0809-0A0B0C0E0E0F + +用于打印16字节的UUID/GUIDs地址。附加的 ``l`` , ``L`` , ``b`` 和 ``B`` 占位符用 +于指定小写(l)或大写(L)十六进制表示法中的小尾端顺序,以及小写(b)或大写(B)十六进制表 +示法中的大尾端顺序。 + +如果没有使用额外的占位符,则将打印带有小写十六进制表示法的默认大端顺序。 + +通过引用传递。 + +目录项(dentry)的名称 +---------------------- + +:: + + %pd{,2,3,4} + %pD{,2,3,4} + +用于打印dentry名称;如果我们用 :c:func:`d_move` 和它比较,名称可能是新旧混合的,但 +不会oops。 %pd dentry比较安全,其相当于我们以前用的%s dentry->d_name.name,%pd<n>打 +印 ``n`` 最后的组件。 %pD对结构文件做同样的事情。 + + +通过引用传递。 + +块设备(block_device)名称 +-------------------------- + +:: + + %pg sda, sda1 or loop0p1 + +用于打印block_device指针的名称。 + +va_format结构体 +--------------- + +:: + + %pV + +用于打印结构体va_format。这些结构包含一个格式字符串 +和va_list如下 + +:: + + struct va_format { + const char *fmt; + va_list *va; + }; + +实现 "递归vsnprintf"。 + +如果没有一些机制来验证格式字符串和va_list参数的正确性,请不要使用这个功能。 + +通过引用传递。 + +设备树节点 +---------- + +:: + + %pOF[fnpPcCF] + + +用于打印设备树节点结构。默认行为相当于%pOFf。 + + - f - 设备节点全称 + - n - 设备节点名 + - p - 设备节点句柄 + - P - 设备节点路径规范(名称+@单位) + - F - 设备节点标志 + - c - 主要兼容字符串 + - C - 全兼容字符串 + +当使用多个参数时,分隔符是':'。 + +例如 + +:: + + %pOF /foo/bar@0 - Node full name + %pOFf /foo/bar@0 - Same as above + %pOFfp /foo/bar@0:10 - Node full name + phandle + %pOFfcF /foo/bar@0:foo,device:--P- - Node full name + + major compatible string + + node flags + D - dynamic + d - detached + P - Populated + B - Populated bus + +通过引用传递。 + +Fwnode handles +-------------- + +:: + + %pfw[fP] + +用于打印fwnode_handles的消息。默认情况下是打印完整的节点名称,包括路径。 +这些修饰符在功能上等同于上面的%pOF。 + + - f - 节点的全名,包括路径。 + - P - 节点名称,包括地址(如果有的话)。 + +例如 (ACPI) + +:: + + %pfwf \_SB.PCI0.CIO2.port@1.endpoint@0 - Full node name + %pfwP endpoint@0 - Node name + +例如 (OF) + +:: + + %pfwf /ocp@68000000/i2c@48072000/camera@10/port/endpoint - Full name + %pfwP endpoint - Node name + +时间和日期 +---------- + +:: + + %pt[RT] YYYY-mm-ddTHH:MM:SS + %pt[RT]s YYYY-mm-dd HH:MM:SS + %pt[RT]d YYYY-mm-dd + %pt[RT]t HH:MM:SS + %pt[RT][dt][r][s] + +用于打印日期和时间:: + + R struct rtc_time structure + T time64_t type + +以我们(人类)可读的格式。 + +默认情况下,年将以1900为单位递增,月将以1为单位递增。 使用%pt[RT]r (raw) +来抑制这种行为。 + +%pt[RT]s(空格)将覆盖ISO 8601的分隔符,在日期和时间之间使用''(空格)而 +不是'T'(大写T)。当日期或时间被省略时,它不会有任何影响。 + +通过引用传递。 + +clk结构体 +--------- + +:: + + %pC pll1 + %pCn pll1 + +用于打印clk结构。%pC 和 %pCn 打印时钟的名称(通用时钟框架)或唯一的32位 +ID(传统时钟框架)。 + +通过引用传递。 + +位图及其衍生物,如cpumask和nodemask +----------------------------------- + +:: + + %*pb 0779 + %*pbl 0,3-6,8-10 + +对于打印位图(bitmap)及其派生的cpumask和nodemask,%*pb输出以字段宽度为位数的位图, +%*pbl输出以字段宽度为位数的范围列表。 + +字段宽度用值传递,位图用引用传递。可以使用辅助宏cpumask_pr_args()和 +nodemask_pr_args()来方便打印cpumask和nodemask。 + +标志位字段,如页标志、gfp_flags +------------------------------- + +:: + + %pGp 0x17ffffc0002036(referenced|uptodate|lru|active|private|node=0|zone=2|lastcpupid=0x1fffff) + %pGg GFP_USER|GFP_DMA32|GFP_NOWARN + %pGv read|exec|mayread|maywrite|mayexec|denywrite + +将flags位字段打印为构造值的符号常量集合。标志的类型由第三个字符给出。目前支持的 +是[p]age flags, [v]ma_flags(都期望 ``unsigned long *`` )和 +[g]fp_flags(期望 ``gfp_t *`` )。标志名称和打印顺序取决于特定的类型。 + +注意,这种格式不应该直接用于跟踪点的:c:func:`TP_printk()` 部分。相反,应使 +用 <trace/events/mmflags.h>中的show_*_flags()函数。 + +通过引用传递。 + +网络设备特性 +------------ + +:: + + %pNF 0x000000000000c000 + +用于打印netdev_features_t。 + +通过引用传递。 + +V4L2和DRM FourCC代码(像素格式) +------------------------------ + +:: + + %p4cc + +打印V4L2或DRM使用的FourCC代码,包括格式端序及其十六进制的数值。 + +通过引用传递。 + +例如:: + + %p4cc BG12 little-endian (0x32314742) + %p4cc Y10 little-endian (0x20303159) + %p4cc NV12 big-endian (0xb231564e) + +谢谢 +==== + +如果您添加了其他%p扩展,请在可行的情况下,用一个或多个测试用例扩展<lib/test_printf.c>。 + +谢谢你的合作和关注。 diff --git a/Documentation/translations/zh_CN/core-api/protection-keys.rst b/Documentation/translations/zh_CN/core-api/protection-keys.rst new file mode 100644 index 000000000..d07830050 --- /dev/null +++ b/Documentation/translations/zh_CN/core-api/protection-keys.rst @@ -0,0 +1,99 @@ +.. SPDX-License-Identifier: GPL-2.0 +.. include:: ../disclaimer-zh_CN.rst + +:Original: Documentation/core-api/protection-keys.rst + +:翻译: + + 司延腾 Yanteng Si <siyanteng@loongson.cn> + +:校译: + + 吴想成 Wu XiangCheng <bobwxc@email.cn> + +.. _cn_core-api_protection-keys: + +============ +内存保护密钥 +============ + +用户空间的内存保护密钥(Memory Protection Keys for Userspace,PKU,亦 +即PKEYs)是英特尔Skylake(及以后)“可扩展处理器”服务器CPU上的一项功能。 +它将在未来的非服务器英特尔处理器和未来的AMD处理器中可用。 + +对于任何希望测试或使用该功能的人来说,它在亚马逊的EC2 C5实例中是可用的, +并且已知可以在那里使用Ubuntu 17.04镜像运行。 + +内存保护密钥提供了一种机制来执行基于页面的保护,但在应用程序改变保护域 +时不需要修改页表。它的工作原理是在每个页表项中为“保护密钥”分配4个以 +前被忽略的位,从而提供16个可能的密钥。 + +还有一个新的用户可访问寄存器(PKRU),为每个密钥提供两个单独的位(访 +问禁止和写入禁止)。作为一个CPU寄存器,PKRU在本质上是线程本地的,可能 +会给每个线程提供一套不同于其他线程的保护措施。 + +有两条新指令(RDPKRU/WRPKRU)用于读取和写入新的寄存器。该功能仅在64位 +模式下可用,尽管物理地址扩展页表中理论上有空间。这些权限只在数据访问上 +强制执行,对指令获取没有影响。 + + +系统调用 +======== + +有3个系统调用可以直接与pkeys进行交互:: + + int pkey_alloc(unsigned long flags, unsigned long init_access_rights) + int pkey_free(int pkey); + int pkey_mprotect(unsigned long start, size_t len, + unsigned long prot, int pkey); + +在使用一个pkey之前,必须先用pkey_alloc()分配它。一个应用程序直接调用 +WRPKRU指令,以改变一个密钥覆盖的内存的访问权限。在这个例子中,WRPKRU +被一个叫做pkey_set()的C函数所封装:: + + int real_prot = PROT_READ|PROT_WRITE; + pkey = pkey_alloc(0, PKEY_DISABLE_WRITE); + ptr = mmap(NULL, PAGE_SIZE, PROT_NONE, MAP_ANONYMOUS|MAP_PRIVATE, -1, 0); + ret = pkey_mprotect(ptr, PAGE_SIZE, real_prot, pkey); + ... application runs here + +现在,如果应用程序需要更新'ptr'处的数据,它可以获得访问权,进行更新, +然后取消其写访问权:: + + pkey_set(pkey, 0); // clear PKEY_DISABLE_WRITE + *ptr = foo; // assign something + pkey_set(pkey, PKEY_DISABLE_WRITE); // set PKEY_DISABLE_WRITE again + +现在,当它释放内存时,它也将释放pkey,因为它不再被使用了:: + + munmap(ptr, PAGE_SIZE); + pkey_free(pkey); + +.. note:: pkey_set()是RDPKRU和WRPKRU指令的一个封装器。在tools/testing/selftests/x86/protection_keys.c中可以找到一个实现实例。 + tools/testing/selftests/x86/protection_keys.c. + +行为 +==== + +内核试图使保护密钥与普通的mprotect()的行为一致。例如,如果你这样做:: + + mprotect(ptr, size, PROT_NONE); + something(ptr); + +这样做的时候,你可以期待保护密钥的相同效果:: + + pkey = pkey_alloc(0, PKEY_DISABLE_WRITE | PKEY_DISABLE_READ); + pkey_mprotect(ptr, size, PROT_READ|PROT_WRITE, pkey); + something(ptr); + +无论something()是否是对'ptr'的直接访问,这都应该为真。 +如:: + + *ptr = foo; + +或者当内核代表应用程序进行访问时,比如read():: + + read(fd, ptr, 1); + +在这两种情况下,内核都会发送一个SIGSEGV,但当违反保护密钥时,si_code +将被设置为SEGV_PKERR,而当违反普通的mprotect()权限时,则是SEGV_ACCERR。 diff --git a/Documentation/translations/zh_CN/core-api/rbtree.rst b/Documentation/translations/zh_CN/core-api/rbtree.rst new file mode 100644 index 000000000..a3e1555cb --- /dev/null +++ b/Documentation/translations/zh_CN/core-api/rbtree.rst @@ -0,0 +1,391 @@ +.. SPDX-License-Identifier: GPL-2.0 +.. include:: ../disclaimer-zh_CN.rst + +:Original: Documentation/core-api/rbtree.rst + +:翻译: + + 唐艺舟 Tang Yizhou <tangyeechou@gmail.com> + +========================= +Linux中的红黑树(rbtree) +========================= + + +:日期: 2007年1月18日 +:作者: Rob Landley <rob@landley.net> + +何为红黑树,它们有什么用? +-------------------------- + +红黑树是一种自平衡二叉搜索树,被用来存储可排序的键/值数据对。这与基数树(被用来高效 +存储稀疏数组,因此使用长整型下标来插入/访问/删除结点)和哈希表(没有保持排序因而无法 +容易地按序遍历,同时必须调节其大小和哈希函数,然而红黑树可以优雅地伸缩以便存储任意 +数量的键)不同。 + +红黑树和AVL树类似,但在插入和删除时提供了更快的实时有界的最坏情况性能(分别最多两次 +旋转和三次旋转,来平衡树),查询时间轻微变慢(但时间复杂度仍然是O(log n))。 + +引用Linux每周新闻(Linux Weekly News): + + 内核中有多处红黑树的使用案例。最后期限调度器和完全公平排队(CFQ)I/O调度器利用 + 红黑树跟踪请求;数据包CD/DVD驱动程序也是如此。高精度时钟代码使用一颗红黑树组织 + 未完成的定时器请求。ext3文件系统用红黑树跟踪目录项。虚拟内存区域(VMAs)、epoll + 文件描述符、密码学密钥和在“分层令牌桶”调度器中的网络数据包都由红黑树跟踪。 + +本文档涵盖了对Linux红黑树实现的使用方法。更多关于红黑树的性质和实现的信息,参见: + + Linux每周新闻关于红黑树的文章 + https://lwn.net/Articles/184495/ + + 维基百科红黑树词条 + https://en.wikipedia.org/wiki/Red-black_tree + +红黑树的Linux实现 +----------------- + +Linux的红黑树实现在文件“lib/rbtree.c”中。要使用它,需要“#include <linux/rbtree.h>”。 + +Linux的红黑树实现对速度进行了优化,因此比传统的实现少一个间接层(有更好的缓存局部性)。 +每个rb_node结构体的实例嵌入在它管理的数据结构中,因此不需要靠指针来分离rb_node和它 +管理的数据结构。用户应该编写他们自己的树搜索和插入函数,来调用已提供的红黑树函数, +而不是使用一个比较回调函数指针。加锁代码也留给红黑树的用户编写。 + +创建一颗红黑树 +-------------- + +红黑树中的数据结点是包含rb_node结构体成员的结构体:: + + struct mytype { + struct rb_node node; + char *keystring; + }; + +当处理一个指向内嵌rb_node结构体的指针时,包住rb_node的结构体可用标准的container_of() +宏访问。此外,个体成员可直接用rb_entry(node, type, member)访问。 + +每颗红黑树的根是一个rb_root数据结构,它由以下方式初始化为空: + + struct rb_root mytree = RB_ROOT; + +在一颗红黑树中搜索值 +-------------------- + +为你的树写一个搜索函数是相当简单的:从树根开始,比较每个值,然后根据需要继续前往左边或 +右边的分支。 + +示例:: + + struct mytype *my_search(struct rb_root *root, char *string) + { + struct rb_node *node = root->rb_node; + + while (node) { + struct mytype *data = container_of(node, struct mytype, node); + int result; + + result = strcmp(string, data->keystring); + + if (result < 0) + node = node->rb_left; + else if (result > 0) + node = node->rb_right; + else + return data; + } + return NULL; + } + +在一颗红黑树中插入数据 +---------------------- + +在树中插入数据的步骤包括:首先搜索插入新结点的位置,然后插入结点并对树再平衡 +("recoloring")。 + +插入的搜索和上文的搜索不同,它要找到嫁接新结点的位置。新结点也需要一个指向它的父节点 +的链接,以达到再平衡的目的。 + +示例:: + + int my_insert(struct rb_root *root, struct mytype *data) + { + struct rb_node **new = &(root->rb_node), *parent = NULL; + + /* Figure out where to put new node */ + while (*new) { + struct mytype *this = container_of(*new, struct mytype, node); + int result = strcmp(data->keystring, this->keystring); + + parent = *new; + if (result < 0) + new = &((*new)->rb_left); + else if (result > 0) + new = &((*new)->rb_right); + else + return FALSE; + } + + /* Add new node and rebalance tree. */ + rb_link_node(&data->node, parent, new); + rb_insert_color(&data->node, root); + + return TRUE; + } + +在一颗红黑树中删除或替换已经存在的数据 +-------------------------------------- + +若要从树中删除一个已经存在的结点,调用:: + + void rb_erase(struct rb_node *victim, struct rb_root *tree); + +示例:: + + struct mytype *data = mysearch(&mytree, "walrus"); + + if (data) { + rb_erase(&data->node, &mytree); + myfree(data); + } + +若要用一个新结点替换树中一个已经存在的键值相同的结点,调用:: + + void rb_replace_node(struct rb_node *old, struct rb_node *new, + struct rb_root *tree); + +通过这种方式替换结点不会对树做重排序:如果新结点的键值和旧结点不同,红黑树可能被 +破坏。 + +(按排序的顺序)遍历存储在红黑树中的元素 +---------------------------------------- + +我们提供了四个函数,用于以排序的方式遍历一颗红黑树的内容。这些函数可以在任意红黑树 +上工作,并且不需要被修改或包装(除非加锁的目的):: + + struct rb_node *rb_first(struct rb_root *tree); + struct rb_node *rb_last(struct rb_root *tree); + struct rb_node *rb_next(struct rb_node *node); + struct rb_node *rb_prev(struct rb_node *node); + +要开始迭代,需要使用一个指向树根的指针调用rb_first()或rb_last(),它将返回一个指向 +树中第一个或最后一个元素所包含的节点结构的指针。要继续的话,可以在当前结点上调用 +rb_next()或rb_prev()来获取下一个或上一个结点。当没有剩余的结点时,将返回NULL。 + +迭代器函数返回一个指向被嵌入的rb_node结构体的指针,由此,包住rb_node的结构体可用 +标准的container_of()宏访问。此外,个体成员可直接用rb_entry(node, type, member) +访问。 + +示例:: + + struct rb_node *node; + for (node = rb_first(&mytree); node; node = rb_next(node)) + printk("key=%s\n", rb_entry(node, struct mytype, node)->keystring); + +带缓存的红黑树 +-------------- + +计算最左边(最小的)结点是二叉搜索树的一个相当常见的任务,例如用于遍历,或用户根据 +他们自己的逻辑依赖一个特定的顺序。为此,用户可以使用'struct rb_root_cached'来优化 +时间复杂度为O(logN)的rb_first()的调用,以简单地获取指针,避免了潜在的昂贵的树迭代。 +维护操作的额外运行时间开销可忽略,不过内存占用较大。 + +和rb_root结构体类似,带缓存的红黑树由以下方式初始化为空:: + + struct rb_root_cached mytree = RB_ROOT_CACHED; + +带缓存的红黑树只是一个常规的rb_root,加上一个额外的指针来缓存最左边的节点。这使得 +rb_root_cached可以存在于rb_root存在的任何地方,并且只需增加几个接口来支持带缓存的 +树:: + + struct rb_node *rb_first_cached(struct rb_root_cached *tree); + void rb_insert_color_cached(struct rb_node *, struct rb_root_cached *, bool); + void rb_erase_cached(struct rb_node *node, struct rb_root_cached *); + +操作和删除也有对应的带缓存的树的调用:: + + void rb_insert_augmented_cached(struct rb_node *node, struct rb_root_cached *, + bool, struct rb_augment_callbacks *); + void rb_erase_augmented_cached(struct rb_node *, struct rb_root_cached *, + struct rb_augment_callbacks *); + + +对增强型红黑树的支持 +-------------------- + +增强型红黑树是一种在每个结点里存储了“一些”附加数据的红黑树,其中结点N的附加数据 +必须是以N为根的子树中所有结点的内容的函数。它是建立在红黑树基础设施之上的可选特性。 +想要使用这个特性的红黑树用户,插入和删除结点时必须调用增强型接口并提供增强型回调函数。 + +实现增强型红黑树操作的C文件必须包含<linux/rbtree_augmented.h>而不是<linux/rbtree.h>。 +注意,linux/rbtree_augmented.h暴露了一些红黑树实现的细节而你不应依赖它们,请坚持 +使用文档记录的API,并且不要在头文件中包含<linux/rbtree_augmented.h>,以最小化你的 +用户意外地依赖这些实现细节的可能。 + +插入时,用户必须更新通往被插入节点的路径上的增强信息,然后像往常一样调用rb_link_node(), +然后是rb_augment_inserted()而不是平时的rb_insert_color()调用。如果 +rb_augment_inserted()再平衡了红黑树,它将回调至一个用户提供的函数来更新受影响的 +子树上的增强信息。 + +删除一个结点时,用户必须调用rb_erase_augmented()而不是rb_erase()。 +rb_erase_augmented()回调至一个用户提供的函数来更新受影响的子树上的增强信息。 + +在两种情况下,回调都是通过rb_augment_callbacks结构体提供的。必须定义3个回调: + +- 一个传播回调,它更新一个给定结点和它的祖先们的增强数据,直到一个给定的停止点 + (如果是NULL,将更新一路更新到树根)。 + +- 一个复制回调,它将一颗给定子树的增强数据复制到一个新指定的子树树根。 + +- 一个树旋转回调,它将一颗给定的子树的增强值复制到新指定的子树树根上,并重新计算 + 先前的子树树根的增强值。 + +rb_erase_augmented()编译后的代码可能会内联传播、复制回调,这将导致函数体积更大, +因此每个增强型红黑树的用户应该只有一个rb_erase_augmented()的调用点,以限制编译后 +的代码大小。 + + +使用示例 +^^^^^^^^ + +区间树是增强型红黑树的一个例子。参考Cormen,Leiserson,Rivest和Stein写的 +《算法导论》。区间树的更多细节: + +经典的红黑树只有一个键,它不能直接用来存储像[lo:hi]这样的区间范围,也不能快速查找 +与新的lo:hi重叠的部分,或者查找是否有与新的lo:hi完全匹配的部分。 + +然而,红黑树可以被增强,以一种结构化的方式来存储这种区间范围,从而使高效的查找和 +精确匹配成为可能。 + +这个存储在每个节点中的“额外信息”是其所有后代结点中的最大hi(max_hi)值。这个信息 +可以保持在每个结点上,只需查看一下该结点和它的直系子结点们。这将被用于时间复杂度 +为O(log n)的最低匹配查找(所有可能的匹配中最低的起始地址),就像这样:: + + struct interval_tree_node * + interval_tree_first_match(struct rb_root *root, + unsigned long start, unsigned long last) + { + struct interval_tree_node *node; + + if (!root->rb_node) + return NULL; + node = rb_entry(root->rb_node, struct interval_tree_node, rb); + + while (true) { + if (node->rb.rb_left) { + struct interval_tree_node *left = + rb_entry(node->rb.rb_left, + struct interval_tree_node, rb); + if (left->__subtree_last >= start) { + /* + * Some nodes in left subtree satisfy Cond2. + * Iterate to find the leftmost such node N. + * If it also satisfies Cond1, that's the match + * we are looking for. Otherwise, there is no + * matching interval as nodes to the right of N + * can't satisfy Cond1 either. + */ + node = left; + continue; + } + } + if (node->start <= last) { /* Cond1 */ + if (node->last >= start) /* Cond2 */ + return node; /* node is leftmost match */ + if (node->rb.rb_right) { + node = rb_entry(node->rb.rb_right, + struct interval_tree_node, rb); + if (node->__subtree_last >= start) + continue; + } + } + return NULL; /* No match */ + } + } + +插入/删除是通过以下增强型回调来定义的:: + + static inline unsigned long + compute_subtree_last(struct interval_tree_node *node) + { + unsigned long max = node->last, subtree_last; + if (node->rb.rb_left) { + subtree_last = rb_entry(node->rb.rb_left, + struct interval_tree_node, rb)->__subtree_last; + if (max < subtree_last) + max = subtree_last; + } + if (node->rb.rb_right) { + subtree_last = rb_entry(node->rb.rb_right, + struct interval_tree_node, rb)->__subtree_last; + if (max < subtree_last) + max = subtree_last; + } + return max; + } + + static void augment_propagate(struct rb_node *rb, struct rb_node *stop) + { + while (rb != stop) { + struct interval_tree_node *node = + rb_entry(rb, struct interval_tree_node, rb); + unsigned long subtree_last = compute_subtree_last(node); + if (node->__subtree_last == subtree_last) + break; + node->__subtree_last = subtree_last; + rb = rb_parent(&node->rb); + } + } + + static void augment_copy(struct rb_node *rb_old, struct rb_node *rb_new) + { + struct interval_tree_node *old = + rb_entry(rb_old, struct interval_tree_node, rb); + struct interval_tree_node *new = + rb_entry(rb_new, struct interval_tree_node, rb); + + new->__subtree_last = old->__subtree_last; + } + + static void augment_rotate(struct rb_node *rb_old, struct rb_node *rb_new) + { + struct interval_tree_node *old = + rb_entry(rb_old, struct interval_tree_node, rb); + struct interval_tree_node *new = + rb_entry(rb_new, struct interval_tree_node, rb); + + new->__subtree_last = old->__subtree_last; + old->__subtree_last = compute_subtree_last(old); + } + + static const struct rb_augment_callbacks augment_callbacks = { + augment_propagate, augment_copy, augment_rotate + }; + + void interval_tree_insert(struct interval_tree_node *node, + struct rb_root *root) + { + struct rb_node **link = &root->rb_node, *rb_parent = NULL; + unsigned long start = node->start, last = node->last; + struct interval_tree_node *parent; + + while (*link) { + rb_parent = *link; + parent = rb_entry(rb_parent, struct interval_tree_node, rb); + if (parent->__subtree_last < last) + parent->__subtree_last = last; + if (start < parent->start) + link = &parent->rb.rb_left; + else + link = &parent->rb.rb_right; + } + + node->__subtree_last = last; + rb_link_node(&node->rb, rb_parent, link); + rb_insert_augmented(&node->rb, root, &augment_callbacks); + } + + void interval_tree_remove(struct interval_tree_node *node, + struct rb_root *root) + { + rb_erase_augmented(&node->rb, root, &augment_callbacks); + } diff --git a/Documentation/translations/zh_CN/core-api/refcount-vs-atomic.rst b/Documentation/translations/zh_CN/core-api/refcount-vs-atomic.rst new file mode 100644 index 000000000..e2467fd26 --- /dev/null +++ b/Documentation/translations/zh_CN/core-api/refcount-vs-atomic.rst @@ -0,0 +1,156 @@ +.. include:: ../disclaimer-zh_CN.rst + +:Original: Documentation/core-api/refcount-vs-atomic.rst + +:翻译: + + 司延腾 Yanteng Si <siyanteng@loongson.cn> + +.. _cn_refcount-vs-atomic: + +======================================= +与atomic_t相比,refcount_t的API是这样的 +======================================= + +.. contents:: :local: + +简介 +==== + +refcount_t API的目标是为实现对象的引用计数器提供一个最小的API。虽然来自 +lib/refcount.c的独立于架构的通用实现在下面使用了原子操作,但一些 ``refcount_*()`` +和 ``atomic_*()`` 函数在内存顺序保证方面有很多不同。本文档概述了这些差异,并 +提供了相应的例子,以帮助开发者根据这些内存顺序保证的变化来验证他们的代码。 + +本文档中使用的术语尽量遵循tools/memory-model/Documentation/explanation.txt +中定义的正式LKMM。 + +memory-barriers.txt和atomic_t.txt提供了更多关于内存顺序的背景,包括通用的 +和针对原子操作的。 + +内存顺序的相关类型 +================== + +.. note:: 下面的部分只涵盖了本文使用的与原子操作和引用计数器有关的一些内存顺 + 序类型。如果想了解更广泛的情况,请查阅memory-barriers.txt文件。 + +在没有任何内存顺序保证的情况下(即完全无序),atomics和refcounters只提供原 +子性和程序顺序(program order, po)关系(在同一个CPU上)。它保证每个 +``atomic_* ()`` 和 ``refcount_*()`` 操作都是原子性的,指令在单个CPU上按程序 +顺序执行。这是用READ_ONCE()/WRITE_ONCE()和比较并交换原语实现的。 + +强(完全)内存顺序保证在同一CPU上的所有较早加载和存储的指令(所有程序顺序较早 +[po-earlier]指令)在执行任何程序顺序较后指令(po-later)之前完成。它还保证 +同一CPU上储存的程序优先较早的指令和来自其他CPU传播的指令必须在该CPU执行任何 +程序顺序较后指令之前传播到其他CPU(A-累积属性)。这是用smp_mb()实现的。 + +RELEASE内存顺序保证了在同一CPU上所有较早加载和存储的指令(所有程序顺序较早 +指令)在此操作前完成。它还保证同一CPU上储存的程序优先较早的指令和来自其他CPU +传播的指令必须在释放(release)操作之前传播到所有其他CPU(A-累积属性)。这是用 +smp_store_release()实现的。 + +ACQUIRE内存顺序保证了同一CPU上的所有后加载和存储的指令(所有程序顺序较后 +指令)在获取(acquire)操作之后完成。它还保证在获取操作执行后,同一CPU上 +储存的所有程序顺序较后指令必须传播到所有其他CPU。这是用 +smp_acquire__after_ctrl_dep()实现的。 + +对Refcounters的控制依赖(取决于成功)保证了如果一个对象的引用被成功获得(引用计数 +器的增量或增加行为发生了,函数返回true),那么进一步的存储是针对这个操作的命令。对存 +储的控制依赖没有使用任何明确的屏障来实现,而是依赖于CPU不对存储进行猜测。这只是 +一个单一的CPU关系,对其他CPU不提供任何保证。 + + +函数的比较 +========== + +情况1) - 非 “读/修改/写”(RMW)操作 +------------------------------------ + +函数变化: + + * atomic_set() --> refcount_set() + * atomic_read() --> refcount_read() + +内存顺序保证变化: + + * none (两者都是完全无序的) + + +情况2) - 基于增量的操作,不返回任何值 +-------------------------------------- + +函数变化: + + * atomic_inc() --> refcount_inc() + * atomic_add() --> refcount_add() + +内存顺序保证变化: + + * none (两者都是完全无序的) + +情况3) - 基于递减的RMW操作,没有返回值 +--------------------------------------- + +函数变化: + + * atomic_dec() --> refcount_dec() + +内存顺序保证变化: + + * 完全无序的 --> RELEASE顺序 + + +情况4) - 基于增量的RMW操作,返回一个值 +--------------------------------------- + +函数变化: + + * atomic_inc_not_zero() --> refcount_inc_not_zero() + * 无原子性对应函数 --> refcount_add_not_zero() + +内存顺序保证变化: + + * 完全有序的 --> 控制依赖于存储的成功 + +.. note:: 此处 **假设** 了,必要的顺序是作为获得对象指针的结果而提供的。 + + +情况 5) - 基于Dec/Sub递减的通用RMW操作,返回一个值 +--------------------------------------------------- + +函数变化: + + * atomic_dec_and_test() --> refcount_dec_and_test() + * atomic_sub_and_test() --> refcount_sub_and_test() + +内存顺序保证变化: + + * 完全有序的 --> RELEASE顺序 + 成功后ACQUIRE顺序 + + +情况6)其他基于递减的RMW操作,返回一个值 +---------------------------------------- + +函数变化: + + * 无原子性对应函数 --> refcount_dec_if_one() + * ``atomic_add_unless(&var, -1, 1)`` --> ``refcount_dec_not_one(&var)`` + +内存顺序保证变化: + + * 完全有序的 --> RELEASE顺序 + 控制依赖 + +.. note:: atomic_add_unless()只在执行成功时提供完整的顺序。 + + +情况7)--基于锁的RMW +-------------------- + +函数变化: + + * atomic_dec_and_lock() --> refcount_dec_and_lock() + * atomic_dec_and_mutex_lock() --> refcount_dec_and_mutex_lock() + +内存顺序保证变化: + + * 完全有序 --> RELEASE顺序 + 控制依赖 + 持有 diff --git a/Documentation/translations/zh_CN/core-api/symbol-namespaces.rst b/Documentation/translations/zh_CN/core-api/symbol-namespaces.rst new file mode 100644 index 000000000..bb16f0611 --- /dev/null +++ b/Documentation/translations/zh_CN/core-api/symbol-namespaces.rst @@ -0,0 +1,144 @@ +.. include:: ../disclaimer-zh_CN.rst + +:Original: Documentation/core-api/symbol-namespaces.rst + +:翻译: + + 司延腾 Yanteng Si <siyanteng@loongson.cn> + +.. _cn_symbol-namespaces.rst: + +================================= +符号命名空间(Symbol Namespaces) +================================= + +本文档描述了如何使用符号命名空间来构造通过EXPORT_SYMBOL()系列宏导出的内核内符号的导出面。 + +.. 目录 + + === 1 简介 + === 2 如何定义符号命名空间 + --- 2.1 使用EXPORT_SYMBOL宏 + --- 2.2 使用DEFAULT_SYMBOL_NAMESPACE定义 + === 3 如何使用命名空间中导出的符号 + === 4 加载使用命名空间符号的模块 + === 5 自动创建MODULE_IMPORT_NS声明 + +1. 简介 +======= + +符号命名空间已经被引入,作为构造内核内API的导出面的一种手段。它允许子系统维护者将 +他们导出的符号划分进独立的命名空间。这对于文档的编写非常有用(想想SUBSYSTEM_DEBUG +命名空间),也可以限制一组符号在内核其他部分的使用。今后,使用导出到命名空间的符号 +的模块必须导入命名空间。否则,内核将根据其配置,拒绝加载该模块或警告说缺少 +导入。 + +2. 如何定义符号命名空间 +======================= + +符号可以用不同的方法导出到命名空间。所有这些都在改变 EXPORT_SYMBOL 和与之类似的那些宏 +被检测到的方式,以创建 ksymtab 条目。 + +2.1 使用EXPORT_SYMBOL宏 +======================= + +除了允许将内核符号导出到内核符号表的宏EXPORT_SYMBOL()和EXPORT_SYMBOL_GPL()之外, +这些宏的变体还可以将符号导出到某个命名空间:EXPORT_SYMBOL_NS() 和 EXPORT_SYMBOL_NS_GPL()。 +它们需要一个额外的参数:命名空间(the namespace)。请注意,由于宏扩展,该参数需 +要是一个预处理器符号。例如,要把符号 ``usb_stor_suspend`` 导出到命名空间 ``USB_STORAGE``, +请使用:: + + EXPORT_SYMBOL_NS(usb_stor_suspend, USB_STORAGE); + +相应的 ksymtab 条目结构体 ``kernel_symbol`` 将有相应的成员 ``命名空间`` 集。 +导出时未指明命名空间的符号将指向 ``NULL`` 。如果没有定义命名空间,则默认没有。 +``modpost`` 和kernel/module/main.c分别在构建时或模块加载时使用名称空间。 + +2.2 使用DEFAULT_SYMBOL_NAMESPACE定义 +==================================== + +为一个子系统的所有符号定义命名空间可能会非常冗长,并可能变得难以维护。因此,我 +们提供了一个默认定义(DEFAULT_SYMBOL_NAMESPACE),如果设置了这个定义, 它将成 +为所有没有指定命名空间的 EXPORT_SYMBOL() 和 EXPORT_SYMBOL_GPL() 宏扩展的默认 +定义。 + +有多种方法来指定这个定义,使用哪种方法取决于子系统和维护者的喜好。第一种方法是在 +子系统的 ``Makefile`` 中定义默认命名空间。例如,如果要将usb-common中定义的所有符号导 +出到USB_COMMON命名空间,可以在drivers/usb/common/Makefile中添加这样一行:: + + ccflags-y += -DDEFAULT_SYMBOL_NAMESPACE=USB_COMMON + +这将影响所有 EXPORT_SYMBOL() 和 EXPORT_SYMBOL_GPL() 语句。当这个定义存在时, +用EXPORT_SYMBOL_NS()导出的符号仍然会被导出到作为命名空间参数传递的命名空间中, +因为这个参数优先于默认的符号命名空间。 + +定义默认命名空间的第二个选项是直接在编译单元中作为预处理声明。上面的例子就会变 +成:: + + #undef DEFAULT_SYMBOL_NAMESPACE + #define DEFAULT_SYMBOL_NAMESPACE USB_COMMON + +应置于相关编译单元中任何 EXPORT_SYMBOL 宏之前 + +3. 如何使用命名空间中导出的符号 +=============================== + +为了使用被导出到命名空间的符号,内核模块需要明确地导入这些命名空间。 +否则内核可能会拒绝加载该模块。模块代码需要使用宏MODULE_IMPORT_NS来 +表示它所使用的命名空间的符号。例如,一个使用usb_stor_suspend符号的 +模块,需要使用如下语句导入命名空间USB_STORAGE:: + + MODULE_IMPORT_NS(USB_STORAGE); + +这将在模块中为每个导入的命名空间创建一个 ``modinfo`` 标签。这也顺带 +使得可以用modinfo检查模块已导入的命名空间:: + + $ modinfo drivers/usb/storage/ums-karma.ko + [...] + import_ns: USB_STORAGE + [...] + + +建议将 MODULE_IMPORT_NS() 语句添加到靠近其他模块元数据定义的地方, +如 MODULE_AUTHOR() 或 MODULE_LICENSE() 。关于自动创建缺失的导入 +语句的方法,请参考第5节。 + +4. 加载使用命名空间符号的模块 +============================= + +在模块加载时(比如 ``insmod`` ),内核将检查每个从模块中引用的符号是否可 +用,以及它可能被导出到的名字空间是否被模块导入。内核的默认行为是拒绝 +加载那些没有指明足以导入的模块。此错误会被记录下来,并且加载将以 +EINVAL方式失败。要允许加载不满足这个前提条件的模块,可以使用此配置选项: +设置 MODULE_ALLOW_MISSING_NAMESPACE_IMPORTS=y 将使加载不受影响,但会 +发出警告。 + +5. 自动创建MODULE_IMPORT_NS声明 +=============================== + +缺少命名空间的导入可以在构建时很容易被检测到。事实上,如果一个模块 +使用了一个命名空间的符号而没有导入它,modpost会发出警告。 +MODULE_IMPORT_NS()语句通常会被添加到一个明确的位置(和其他模块元 +数据一起)。为了使模块作者(和子系统维护者)的生活更加轻松,我们提 +供了一个脚本和make目标来修复丢失的导入。修复丢失的导入可以用:: + + $ make nsdeps + +对模块作者来说,以下情况可能很典型:: + + - 编写依赖未导入命名空间的符号的代码 + - ``make`` + - 注意 ``modpost`` 的警告,提醒你有一个丢失的导入。 + - 运行 ``make nsdeps``将导入添加到正确的代码位置。 + +对于引入命名空间的子系统维护者来说,其步骤非常相似。同样,make nsdeps最终将 +为树内模块添加缺失的命名空间导入:: + + - 向命名空间转移或添加符号(例如,使用EXPORT_SYMBOL_NS())。 + - `make e`(最好是用allmodconfig来覆盖所有的内核模块)。 + - 注意 ``modpost`` 的警告,提醒你有一个丢失的导入。 + - 运行 ``maknsdeps``将导入添加到正确的代码位置。 + +你也可以为外部模块的构建运行nsdeps。典型的用法是:: + + $ make -C <path_to_kernel_src> M=$PWD nsdeps diff --git a/Documentation/translations/zh_CN/core-api/unaligned-memory-access.rst b/Documentation/translations/zh_CN/core-api/unaligned-memory-access.rst new file mode 100644 index 000000000..29c33e7e0 --- /dev/null +++ b/Documentation/translations/zh_CN/core-api/unaligned-memory-access.rst @@ -0,0 +1,229 @@ +.. include:: ../disclaimer-zh_CN.rst + +:Original: Documentation/core-api/unaligned-memory-access.rst + +:翻译: + + 司延腾 Yanteng Si <siyanteng@loongson.cn> + +:校译: + + 时奎亮 <alexs@kernel.org> + +.. _cn_core-api_unaligned-memory-access: + +============== +非对齐内存访问 +============== + +:作者: Daniel Drake <dsd@gentoo.org>, +:作者: Johannes Berg <johannes@sipsolutions.net> + +:感谢他们的帮助: Alan Cox, Avuton Olrich, Heikki Orsila, Jan Engelhardt, + Kyle McMartin, Kyle Moffett, Randy Dunlap, Robert Hancock, Uli Kunitz, + Vadim Lobanov + + +Linux运行在各种各样的架构上,这些架构在内存访问方面有不同的表现。本文介绍了一些 +关于不对齐访问的细节,为什么你需要编写不引起不对齐访问的代码,以及如何编写这样的 +代码 + + +非对齐访问的定义 +================ + +当你试图从一个不被N偶数整除的地址(即addr % N != 0)开始读取N字节的数据时,就 +会发生无对齐内存访问。例如,从地址0x10004读取4个字节的数据是可以的,但从地址 +0x10005读取4个字节的数据将是一个不对齐的内存访问。 + +上述内容可能看起来有点模糊,因为内存访问可以以不同的方式发生。这里的背景是在机器 +码层面上:某些指令在内存中读取或写入一些字节(例如x86汇编中的movb、movw、movl)。 +正如将变得清晰的那样,相对容易发现那些将编译为多字节内存访问指令的C语句,即在处理 +u16、u32和u64等类型时。 + + +自然对齐 +======== + +上面提到的规则构成了我们所说的自然对齐。当访问N个字节的内存时,基础内存地址必须被 +N平均分割,即addr % N == 0。 + +在编写代码时,假设目标架构有自然对齐的要求。 + +在现实中,只有少数架构在所有大小的内存访问上都要求自然对齐。然而,我们必须考虑所 +有支持的架构;编写满足自然对齐要求的代码是实现完全可移植性的最简单方法。 + + +为什么非对齐访问时坏事 +====================== + +执行非对齐内存访问的效果因架构不同而不同。在这里写一整篇关于这些差异的文档是很容 +易的;下面是对常见情况的总结: + + - 一些架构能够透明地执行非对齐内存访问,但通常会有很大的性能代价。 + - 当不对齐的访问发生时,一些架构会引发处理器异常。异常处理程序能够纠正不对齐的 + 访问,但要付出很大的性能代价。 + - 一些架构在发生不对齐访问时,会引发处理器异常,但异常中并没有包含足够的信息来 + 纠正不对齐访问。 + - 有些架构不能进行无对齐内存访问,但会默默地执行与请求不同的内存访问,从而导致 + 难以发现的微妙的代码错误! + +从上文可以看出,如果你的代码导致不对齐的内存访问发生,那么你的代码在某些平台上将无 +法正常工作,在其他平台上将导致性能问题。 + +不会导致非对齐访问的代码 +======================== + +起初,上面的概念似乎有点难以与实际编码实践联系起来。毕竟,你对某些变量的内存地址没 +有很大的控制权,等等。 + +幸运的是事情并不复杂,因为在大多数情况下,编译器会确保代码工作正常。例如,以下面的 +结构体为例:: + + struct foo { + u16 field1; + u32 field2; + u8 field3; + }; + +让我们假设上述结构体的一个实例驻留在从地址0x10000开始的内存中。根据基本的理解,访问 +field2会导致非对齐访问,这并不是不合理的。你会期望field2位于该结构体的2个字节的偏移 +量,即地址0x10002,但该地址不能被4平均整除(注意,我们在这里读一个4字节的值)。 + +幸运的是,编译器理解对齐约束,所以在上述情况下,它会在field1和field2之间插入2个字节 +的填充。因此,对于标准的结构体类型,你总是可以依靠编译器来填充结构体,以便对字段的访 +问可以适当地对齐(假设你没有将字段定义不同长度的类型)。 + +同样,你也可以依靠编译器根据变量类型的大小,将变量和函数参数对齐到一个自然对齐的方案。 + +在这一点上,应该很清楚,访问单个字节(u8或char)永远不会导致无对齐访问,因为所有的内 +存地址都可以被1均匀地整除。 + +在一个相关的话题上,考虑到上述因素,你可以观察到,你可以对结构体中的字段进行重新排序, +以便将字段放在不重排就会插入填充物的地方,从而减少结构体实例的整体常驻内存大小。上述 +例子的最佳布局是:: + + struct foo { + u32 field2; + u16 field1; + u8 field3; + }; + +对于一个自然对齐方案,编译器只需要在结构的末尾添加一个字节的填充。添加这种填充是为了满 +足这些结构的数组的对齐约束。 + +另一点值得一提的是在结构体类型上使用__attribute__((packed))。这个GCC特有的属性告诉编 +译器永远不要在结构体中插入任何填充,当你想用C结构体来表示一些“off the wire”的固定排列 +的数据时,这个属性很有用。 + +你可能会倾向于认为,在访问不满足架构对齐要求的字段时,使用这个属性很容易导致不对齐的访 +问。然而,编译器也意识到了对齐的限制,并且会产生额外的指令来执行内存访问,以避免造成不 +对齐的访问。当然,与non-packed的情况相比,额外的指令显然会造成性能上的损失,所以packed +属性应该只在避免结构填充很重要的时候使用。 + + +导致非对齐访问的代码 +==================== + +考虑到上述情况,让我们来看看一个现实生活中可能导致非对齐内存访问的函数的例子。下面这个 +函数取自include/linux/etherdevice.h,是一个优化的例程,用于比较两个以太网MAC地址是否 +相等:: + + bool ether_addr_equal(const u8 *addr1, const u8 *addr2) + { + #ifdef CONFIG_HAVE_EFFICIENT_UNALIGNED_ACCESS + u32 fold = ((*(const u32 *)addr1) ^ (*(const u32 *)addr2)) | + ((*(const u16 *)(addr1 + 4)) ^ (*(const u16 *)(addr2 + 4))); + + return fold == 0; + #else + const u16 *a = (const u16 *)addr1; + const u16 *b = (const u16 *)addr2; + return ((a[0] ^ b[0]) | (a[1] ^ b[1]) | (a[2] ^ b[2])) == 0; + #endif + } + +在上述函数中,当硬件具有高效的非对齐访问能力时,这段代码没有问题。但是当硬件不能在任意 +边界上访问内存时,对a[0]的引用导致从地址addr1开始的2个字节(16位)被读取。 + +想一想,如果addr1是一个奇怪的地址,如0x10003,会发生什么?(提示:这将是一个非对齐访 +问。) + +尽管上述函数存在潜在的非对齐访问问题,但它还是被包含在内核中,但被理解为只在16位对齐 +的地址上正常工作。调用者应该确保这种对齐方式或者根本不使用这个函数。这个不对齐的函数 +仍然是有用的,因为它是在你能确保对齐的情况下的一个很好的优化,这在以太网网络环境中几 +乎是一直如此。 + + +下面是另一个可能导致非对齐访问的代码的例子:: + + void myfunc(u8 *data, u32 value) + { + [...] + *((u32 *) data) = cpu_to_le32(value); + [...] + } + +每当数据参数指向的地址不被4均匀整除时,这段代码就会导致非对齐访问。 + +综上所述,你可能遇到非对齐访问问题的两种主要情况包括: + + 1. 将变量定义不同长度的类型 + 2. 指针运算后访问至少2个字节的数据 + + +避免非对齐访问 +============== + +避免非对齐访问的最简单方法是使用<asm/unaligned.h>头文件提供的get_unaligned()和 +put_unaligned()宏。 + +回到前面的一个可能导致非对齐访问的代码例子:: + + void myfunc(u8 *data, u32 value) + { + [...] + *((u32 *) data) = cpu_to_le32(value); + [...] + } + +为了避免非对齐的内存访问,你可以将其改写如下:: + + void myfunc(u8 *data, u32 value) + { + [...] + value = cpu_to_le32(value); + put_unaligned(value, (u32 *) data); + [...] + } + +get_unaligned()宏的工作原理与此类似。假设'data'是一个指向内存的指针,并且你希望避免 +非对齐访问,其用法如下:: + + u32 value = get_unaligned((u32 *) data); + +这些宏适用于任何长度的内存访问(不仅仅是上面例子中的32位)。请注意,与标准的对齐内存 +访问相比,使用这些宏来访问非对齐内存可能会在性能上付出代价。 + +如果使用这些宏不方便,另一个选择是使用memcpy(),其中源或目标(或两者)的类型为u8*或 +非对齐char*。由于这种操作的字节性质,避免了非对齐访问。 + + +对齐 vs. 网络 +============= + +在需要对齐负载的架构上,网络要求IP头在四字节边界上对齐,以优化IP栈。对于普通的以太网 +硬件,常数NET_IP_ALIGN被使用。在大多数架构上,这个常数的值是2,因为正常的以太网头是 +14个字节,所以为了获得适当的对齐,需要DMA到一个可以表示为4*n+2的地址。一个值得注意的 +例外是powerpc,它将NET_IP_ALIGN定义为0,因为DMA到未对齐的地址可能非常昂贵,与未对齐 +的负载的成本相比相形见绌。 + +对于一些不能DMA到未对齐地址的以太网硬件,如4*n+2或非以太网硬件,这可能是一个问题,这 +时需要将传入的帧复制到一个对齐的缓冲区。因为这在可以进行非对齐访问的架构上是不必要的, +所以可以使代码依赖于CONFIG_HAVE_EFFICIENT_UNALIGNED_ACCESS,像这样:: + + #ifdef CONFIG_HAVE_EFFICIENT_UNALIGNED_ACCESS + skb = original skb + #else + skb = copy skb + #endif diff --git a/Documentation/translations/zh_CN/core-api/watch_queue.rst b/Documentation/translations/zh_CN/core-api/watch_queue.rst new file mode 100644 index 000000000..23b17ae2e --- /dev/null +++ b/Documentation/translations/zh_CN/core-api/watch_queue.rst @@ -0,0 +1,313 @@ +.. SPDX-License-Identifier: GPL-2.0+ + +.. include:: ../disclaimer-zh_CN.rst + +:Original: Documentation/core-api/watch_queue.rst + +:翻译: + +周彬彬 Binbin Zhou <zhoubinbin@loongson.cn> + +:校译: + +司延腾 Yanteng Si <siyanteng@loongson.cn> +吴想成 Wu Xiangcheng <bobwxc@email.cn> + + +============ +通用通知机制 +============ + +通用通知机制是建立在标准管道驱动之上的,它可以有效地将来自内核的通知消息拼接到用 +户空间打开的管道中。这可以与以下方面结合使用:: + + * Key/keyring 通知 + +通知缓冲区可以通过以下方式启用: + + “General setup”/“General notification queue” + (CONFIG_WATCH_QUEUE) + +文档包含以下章节: + +.. contents:: :local: + + +概述 +==== + +该设施以一种特殊模式打开的管道形式出现,管道的内部环形缓冲区用于保存内核生成的消 +息。然后通过read()读出这些消息。在此类管道上禁用拼接以及类似的操作,因为它们希望 +在某些情况下将其添加的内容还原到环中-这可能最终会与通知消息重叠。 + +管道的所有者必须告诉内核它想通过该管道观察哪些源。只有连接到该管道上的源才会将消 +息插入其中。请注意,一个源可能绑定到多个管道,并同时将消息插入到所有管道中。 + +还可以将过滤器放置在管道上,以便在不感兴趣时可以忽略某些源类型和子事件。 + +如果环中没有可用的插槽,或者没有预分配的消息缓冲区可用,则将丢弃消息。在这两种情 +况下,read()都会在读取缓冲区中当前的最后一条消息后,将WATCH_META_LOSS_NOTIFICATION +插入到输出缓冲区中。 + +请注意,当生成一个通知时,内核不会等待消费者收集它,而是继续执行。这意味着可以在 +持有自旋锁的同时生成通知,并且还可以保护内核不被用户空间故障无限期地阻碍。 + + +消息结构 +======== + +通知消息由一个简短的头部开始:: + + struct watch_notification { + __u32 type:24; + __u32 subtype:8; + __u32 info; + }; + +“type”表示通知记录的来源,“subtype”表示该来源的记录类型(见下文观测源章节)。该类 +型也可以是“WATCH_TYPE_META”。这是一个由观测队列本身在内部生成的特殊记录类型。有两 +个子类型: + + * WATCH_META_REMOVAL_NOTIFICATION + * WATCH_META_LOSS_NOTIFICATION + +第一个表示安装了观察的对象已被删除或销毁,第二个表示某些消息已丢失。 + +“info”表示一系列东西,包括: + + * 消息的长度,以字节为单位,包括头(带有WATCH_INFO_LENGTH的掩码,并按 + WATCH_INFO_LENGTH__SHIFT移位)。这表示记录的大小,可能在8到127字节之间。 + + * 观测ID(带有WATCH_INFO_ID掩码,并按WATCH_INFO_ID__SHIFT移位)。这表示观测的主 + 叫ID,可能在0到255之间。多个观测组可以共享一个队列,这提供了一种区分它们的方法。 + + * 特定类型的字段(WATCH_INFO_TYPE_INFO)。这是由通知生产者设置的,以指示类型和 + 子类型的某些特定含义。 + +除长度外,信息中的所有内容都可以用于过滤。 + +头部后面可以有补充信息。此格式是由类型和子类型决定的。 + + +观测列表(通知源)API +===================== + +“观测列表“是订阅通知源的观测者的列表。列表可以附加到对象(比如键或超级块),也可 +以是全局的(比如对于设备事件)。从用户空间的角度来看,一个非全局的观测列表通常是 +通过引用它所属的对象来引用的(比如使用KEYCTL_NOTIFY并给它一个密钥序列号来观测特定 +的密钥)。 + +为了管理观测列表,提供了以下函数: + + * :: + + void init_watch_list(struct watch_list *wlist, + void (*release_watch)(struct watch *wlist)); + + 初始化一个观测列表。 如果 ``release_watch`` 不是NULL,那么这表示当watch_list对 + 象被销毁时,应该调用函数来丢弃观测列表对被观测对象的任何引用。 + + * ``void remove_watch_list(struct watch_list *wlist);`` + + 这将删除订阅watch_list的所有观测,并释放它们,然后销毁watch_list对象本身。 + + +观测队列(通知输出)API +======================= + +“观测队列”是由应用程序分配的用以记录通知的缓冲区,其工作原理完全隐藏在管道设备驱 +动中,但必须获得对它的引用才能设置观测。可以通过以下方式进行管理: + + * ``struct watch_queue *get_watch_queue(int fd);`` + + 由于观测队列在内核中通过实现缓冲区的管道的文件描述符表示,用户空间必须通过系 + 统调用传递该文件描述符,这可以用于从系统调用中查找指向观测队列的不透明指针。 + + * ``void put_watch_queue(struct watch_queue *wqueue);`` + + 该函数用以丢弃从 ``get_watch_queue()`` 获得的引用。 + + +观测订阅API +=========== + +“观测”是观测列表上的订阅,表示观测队列,从而表示应写入通知记录的缓冲区。观测队列 +对象还可以携带该对象的过滤规则,由用户空间设置。watch结构体的某些部分可以由驱动程 +序设置:: + + struct watch { + union { + u32 info_id; /* 在info字段中进行OR运算的ID */ + ... + }; + void *private; /* 被观测对象的私有数据 */ + u64 id; /* 内部标识符 */ + ... + }; + +``info_id`` 值是从用户空间获得并按WATCH_INFO_ID__SHIFT移位的8位数字。当通知写入关 +联的观测队列缓冲区时,这将与struct watch_notification::info的WATCH_INFO_ID字段进 +行或运算。 + +``private`` 字段是与watch_list相关联的驱动程序数据,并由 ``watch_list::release_watch()`` +函数清除。 + +``id`` 字段是源的ID。使用不同ID发布的通知将被忽略。 + +提供以下函数来管理观测: + + * ``void init_watch(struct watch *watch, struct watch_queue *wqueue);`` + + 初始化一个观测对象,把它的指针设置到观察队列中,使用适当的限制来避免死锁。 + + * ``int add_watch_to_object(struct watch *watch, struct watch_list *wlist);`` + + 将观测订阅到观测列表(通知源)。watch结构体中的driver-settable字段必须在调用 + 它之前设置。 + + * :: + + int remove_watch_from_object(struct watch_list *wlist, + struct watch_queue *wqueue, + u64 id, false); + + 从观测列表中删除一个观测,该观测必须与指定的观测队列(``wqueue``)和对象标识 + 符(``id``)匹配。通知(``WATCH_META_REMOVAL_NOTIFICATION``)被发送到观测队列 + 表示该观测已被删除。 + + * ``int remove_watch_from_object(struct watch_list *wlist, NULL, 0, true);`` + + 从观测列表中删除所有观测。预计这将被称为销毁前的准备工作,届时新的观测将无法 + 访问观测列表。通知(``WATCH_META_REMOVAL_NOTIFICATION``)被发送到每个订阅观测 + 的观测队列,以表明该观测已被删除。 + + +通知发布API +=========== + +要将通知发布到观测列表以便订阅的观测可以看到,应使用以下函数:: + + void post_watch_notification(struct watch_list *wlist, + struct watch_notification *n, + const struct cred *cred, + u64 id); + +应预先设置通知格式,并应传入一个指向头部(``n``)的指针。通知可能大于此值,并且缓 +冲槽为单位的大小在 ``n->info & WATCH_INFO_LENGTH`` 中注明。 + +``cred`` 结构体表示源(对象)的证书,并传递给LSM,例如SELinux,以允许或禁止根据该队 +列(对象)的证书在每个单独队列中记录注释。 + +``id`` 是源对象ID(如密钥上的序列号)。只有设置相同ID的观测才能看到这个通知。 + + +观测源 +====== + +任何特定的缓冲区都可以从多个源获取信息。 这些源包括: + + * WATCH_TYPE_KEY_NOTIFY + + 这种类型的通知表示密钥和密钥环的变化,包括密钥环内容或密钥属性的变化。 + + 更多信息请参见Documentation/security/keys/core.rst。 + + +事件过滤 +======== + +当创建观测队列后,我们可以应用一组过滤器以限制接收的事件:: + + struct watch_notification_filter filter = { + ... + }; + ioctl(fd, IOC_WATCH_QUEUE_SET_FILTER, &filter) + +过滤器的描述的类型变量是:: + + struct watch_notification_filter { + __u32 nr_filters; + __u32 __reserved; + struct watch_notification_type_filter filters[]; + }; + +其中“nr_filters”表示filters[]数组中过滤器的数量,而“__reserved”应为0。 +“filter”数组有以下类型的元素:: + + struct watch_notification_type_filter { + __u32 type; + __u32 info_filter; + __u32 info_mask; + __u32 subtype_filter[8]; + }; + +其中: + + * ``type`` 是过滤的事件类型,应类似于“WATCH_TYPE_KEY_NOTIFY”。 + + * ``info_filter`` 与 ``info_mask`` 充当通知记录的信息字段的过滤器,只有在以下情 + 况,通知才会写入缓冲区:: + + (watch.info & info_mask) == info_filter + + 例如,这可以用于忽略不在一个挂载树上的观测点的事件。 + + * ``subtype_filter`` 是一个位掩码,表示感兴趣的子类型。subtype_filter[0]的 + bit[0]对应子类型0,bit[1]对应子类型1,以此类推。 + +若ioctl()的参数为NULL,则过滤器将被移除,并且来自观测源的所有事件都将通过。 + + +用户空间代码示例 +================ + +缓冲区的创建如下所示:: + + pipe2(fds, O_TMPFILE); + ioctl(fds[1], IOC_WATCH_QUEUE_SET_SIZE, 256); + +它可以被设置成接收密钥环变化的通知:: + + keyctl(KEYCTL_WATCH_KEY, KEY_SPEC_SESSION_KEYRING, fds[1], 0x01); + +然后,这些通知可以被如下方式所使用:: + + static void consumer(int rfd, struct watch_queue_buffer *buf) + { + unsigned char buffer[128]; + ssize_t buf_len; + + while (buf_len = read(rfd, buffer, sizeof(buffer)), + buf_len > 0 + ) { + void *p = buffer; + void *end = buffer + buf_len; + while (p < end) { + union { + struct watch_notification n; + unsigned char buf1[128]; + } n; + size_t largest, len; + + largest = end - p; + if (largest > 128) + largest = 128; + memcpy(&n, p, largest); + + len = (n->info & WATCH_INFO_LENGTH) >> + WATCH_INFO_LENGTH__SHIFT; + if (len == 0 || len > largest) + return; + + switch (n.n.type) { + case WATCH_TYPE_META: + got_meta(&n.n); + case WATCH_TYPE_KEY_NOTIFY: + saw_key_change(&n.n); + break; + } + + p += len; + } + } + } diff --git a/Documentation/translations/zh_CN/core-api/workqueue.rst b/Documentation/translations/zh_CN/core-api/workqueue.rst new file mode 100644 index 000000000..f6567cf9d --- /dev/null +++ b/Documentation/translations/zh_CN/core-api/workqueue.rst @@ -0,0 +1,352 @@ +.. SPDX-License-Identifier: GPL-2.0 +.. include:: ../disclaimer-zh_CN.rst + +:Original: Documentation/core-api/workqueue.rst + +:翻译: + + 司延腾 Yanteng Si <siyanteng@loongson.cn> + 周彬彬 Binbin Zhou <zhoubinbin@loongson.cn> + +.. _cn_workqueue.rst: + +========================= +并发管理的工作队列 (cmwq) +========================= + +:日期: September, 2010 +:作者: Tejun Heo <tj@kernel.org> +:作者: Florian Mickler <florian@mickler.org> + + +简介 +==== + +在很多情况下,需要一个异步进程的执行环境,工作队列(wq)API是这种情况下 +最常用的机制。 + +当需要这样一个异步执行上下文时,一个描述将要执行的函数的工作项(work, +即一个待执行的任务)被放在队列中。一个独立的线程作为异步执行环境。该队 +列被称为workqueue,线程被称为工作者(worker,即执行这一队列的线程)。 + +当工作队列上有工作项时,工作者会一个接一个地执行与工作项相关的函数。当 +工作队列中没有任何工作项时,工作者就会变得空闲。当一个新的工作项被排入 +队列时,工作者又开始执行。 + + +为什么要cmwq? +============= + +在最初的wq实现中,多线程(MT)wq在每个CPU上有一个工作者线程,而单线程 +(ST)wq在全系统有一个工作者线程。一个MT wq需要保持与CPU数量相同的工 +作者数量。这些年来,内核增加了很多MT wq的用户,随着CPU核心数量的不断 +增加,一些系统刚启动就达到了默认的32k PID的饱和空间。 + +尽管MT wq浪费了大量的资源,但所提供的并发性水平却不能令人满意。这个限 +制在ST和MT wq中都有,只是在MT中没有那么严重。每个wq都保持着自己独立的 +工作者池。一个MT wq只能为每个CPU提供一个执行环境,而一个ST wq则为整个 +系统提供一个。工作项必须竞争这些非常有限的执行上下文,从而导致各种问题, +包括在单一执行上下文周围容易发生死锁。 + +(MT wq)所提供的并发性水平和资源使用之间的矛盾也迫使其用户做出不必要的权衡,比 +如libata选择使用ST wq来轮询PIO,并接受一个不必要的限制,即没有两个轮 +询PIO可以同时进行。由于MT wq并没有提供更好的并发性,需要更高层次的并 +发性的用户,如async或fscache,不得不实现他们自己的线程池。 + +并发管理工作队列(cmwq)是对wq的重新实现,重点是以下目标。 + +* 保持与原始工作队列API的兼容性。 + +* 使用由所有wq共享的每CPU统一的工作者池,在不浪费大量资源的情况下按 +* 需提供灵活的并发水平。 + +* 自动调节工作者池和并发水平,使API用户不需要担心这些细节。 + + +设计 +==== + +为了简化函数的异步执行,引入了一个新的抽象概念,即工作项。 + +一个工作项是一个简单的结构,它持有一个指向将被异步执行的函数的指针。 +每当一个驱动程序或子系统希望一个函数被异步执行时,它必须建立一个指 +向该函数的工作项,并在工作队列中排队等待该工作项。(就是挂到workqueue +队列里面去) + +特定目的线程,称为工作线程(工作者),一个接一个地执行队列中的功能。 +如果没有工作项排队,工作者线程就会闲置。这些工作者线程被管理在所谓 +的工作者池中。 + +cmwq设计区分了面向用户的工作队列,子系统和驱动程序在上面排队工作, +以及管理工作者池和处理排队工作项的后端机制。 + +每个可能的CPU都有两个工作者池,一个用于正常的工作项,另一个用于高 +优先级的工作项,还有一些额外的工作者池,用于服务未绑定工作队列的工 +作项目——这些后备池的数量是动态的。 + +当他们认为合适的时候,子系统和驱动程序可以通过特殊的 +``workqueue API`` 函数创建和排队工作项。他们可以通过在工作队列上 +设置标志来影响工作项执行方式的某些方面,他们把工作项放在那里。这些 +标志包括诸如CPU定位、并发限制、优先级等等。要获得详细的概述,请参 +考下面的 ``alloc_workqueue()`` 的 API 描述。 + +当一个工作项被排入一个工作队列时,目标工作池将根据队列参数和工作队 +列属性确定,并被附加到工作池的共享工作列表上。例如,除非特别重写, +否则一个绑定的工作队列的工作项将被排在与发起线程运行的CPU相关的普 +通或高级工作工作者池的工作项列表中。 + +对于任何工作者池的实施,管理并发水平(有多少执行上下文处于活动状 +态)是一个重要问题。最低水平是为了节省资源,而饱和水平是指系统被 +充分使用。 + +每个与实际CPU绑定的worker-pool通过钩住调度器来实现并发管理。每当 +一个活动的工作者被唤醒或睡眠时,工作者池就会得到通知,并跟踪当前可 +运行的工作者的数量。一般来说,工作项不会占用CPU并消耗很多周期。这 +意味着保持足够的并发性以防止工作处理停滞应该是最优的。只要CPU上有 +一个或多个可运行的工作者,工作者池就不会开始执行新的工作,但是,当 +最后一个运行的工作者进入睡眠状态时,它会立即安排一个新的工作者,这 +样CPU就不会在有待处理的工作项目时闲置。这允许在不损失执行带宽的情 +况下使用最少的工作者。 + +除了kthreads的内存空间外,保留空闲的工作者并没有其他成本,所以cmwq +在杀死它们之前会保留一段时间的空闲。 + +对于非绑定的工作队列,后备池的数量是动态的。可以使用 +``apply_workqueue_attrs()`` 为非绑定工作队列分配自定义属性, +workqueue将自动创建与属性相匹配的后备工作者池。调节并发水平的责任在 +用户身上。也有一个标志可以将绑定的wq标记为忽略并发管理。 +详情请参考API部分。 + +前进进度的保证依赖于当需要更多的执行上下文时可以创建工作者,这也是 +通过使用救援工作者来保证的。所有可能在处理内存回收的代码路径上使用 +的工作项都需要在wq上排队,wq上保留了一个救援工作者,以便在内存有压 +力的情况下下执行。否则,工作者池就有可能出现死锁,等待执行上下文释 +放出来。 + + +应用程序编程接口 (API) +====================== + +``alloc_workqueue()`` 分配了一个wq。原来的 ``create_*workqueue()`` +函数已被废弃,并计划删除。 ``alloc_workqueue()`` 需要三个 +参数 - ``@name`` , ``@flags`` 和 ``@max_active`` 。 +``@name`` 是wq的名称,如果有的话,也用作救援线程的名称。 + +一个wq不再管理执行资源,而是作为前进进度保证、刷新(flush)和 +工作项属性的域。 ``@flags`` 和 ``@max_active`` 控制着工作 +项如何被分配执行资源、安排和执行。 + + +``flags`` +--------- + +``WQ_UNBOUND`` + 排队到非绑定wq的工作项由特殊的工作者池提供服务,这些工作者不 + 绑定在任何特定的CPU上。这使得wq表现得像一个简单的执行环境提 + 供者,没有并发管理。非绑定工作者池试图尽快开始执行工作项。非 + 绑定的wq牺牲了局部性,但在以下情况下是有用的。 + + * 预计并发水平要求会有很大的波动,使用绑定的wq最终可能会在不 + 同的CPU上产生大量大部分未使用的工作者,因为发起线程在不同 + 的CPU上跳转。 + + * 长期运行的CPU密集型工作负载,可以由系统调度器更好地管理。 + +``WQ_FREEZABLE`` + 一个可冻结的wq参与了系统暂停操作的冻结阶段。wq上的工作项被 + 排空,在解冻之前没有新的工作项开始执行。 + +``WQ_MEM_RECLAIM`` + 所有可能在内存回收路径中使用的wq都必须设置这个标志。无论内 + 存压力如何,wq都能保证至少有一个执行上下文。 + +``WQ_HIGHPRI`` + 高优先级wq的工作项目被排到目标cpu的高优先级工作者池中。高 + 优先级的工作者池由具有较高级别的工作者线程提供服务。 + + 请注意,普通工作者池和高优先级工作者池之间并不相互影响。他 + 们各自维护其独立的工作者池,并在其工作者之间实现并发管理。 + +``WQ_CPU_INTENSIVE`` + CPU密集型wq的工作项对并发水平没有贡献。换句话说,可运行的 + CPU密集型工作项不会阻止同一工作者池中的其他工作项开始执行。 + 这对于那些预计会占用CPU周期的绑定工作项很有用,这样它们的 + 执行就会受到系统调度器的监管。 + + 尽管CPU密集型工作项不会对并发水平做出贡献,但它们的执行开 + 始仍然受到并发管理的管制,可运行的非CPU密集型工作项会延迟 + CPU密集型工作项的执行。 + + 这个标志对于未绑定的wq来说是没有意义的。 + + +``max_active`` +-------------- + +``@max_active`` 决定了每个CPU可以分配给wq的工作项的最大执行上 +下文数量。例如,如果 ``@max_active为16`` ,每个CPU最多可以同 +时执行16个wq的工作项。 + +目前,对于一个绑定的wq, ``@max_active`` 的最大限制是512,当指 +定为0时使用的默认值是256。对于非绑定的wq,其限制是512和 +4 * ``num_possible_cpus()`` 中的较高值。这些值被选得足够高,所 +以它们不是限制性因素,同时会在失控情况下提供保护。 + +一个wq的活动工作项的数量通常由wq的用户来调节,更具体地说,是由用 +户在同一时间可以排列多少个工作项来调节。除非有特定的需求来控制活动 +工作项的数量,否则建议指定 为"0"。 + +一些用户依赖于ST wq的严格执行顺序。 ``@max_active`` 为1和 ``WQ_UNBOUND`` +的组合用来实现这种行为。这种wq上的工作项目总是被排到未绑定的工作池 +中,并且在任何时候都只有一个工作项目处于活动状态,从而实现与ST wq相 +同的排序属性。 + +在目前的实现中,上述配置只保证了特定NUMA节点内的ST行为。相反, +``alloc_ordered_queue()`` 应该被用来实现全系统的ST行为。 + + +执行场景示例 +============ + +下面的示例执行场景试图说明cmwq在不同配置下的行为。 + + 工作项w0、w1、w2被排到同一个CPU上的一个绑定的wq q0上。w0 + 消耗CPU 5ms,然后睡眠10ms,然后在完成之前再次消耗CPU 5ms。 + +忽略所有其他的任务、工作和处理开销,并假设简单的FIFO调度, +下面是一个高度简化的原始wq的可能事件序列的版本。:: + + TIME IN MSECS EVENT + 0 w0 starts and burns CPU + 5 w0 sleeps + 15 w0 wakes up and burns CPU + 20 w0 finishes + 20 w1 starts and burns CPU + 25 w1 sleeps + 35 w1 wakes up and finishes + 35 w2 starts and burns CPU + 40 w2 sleeps + 50 w2 wakes up and finishes + +And with cmwq with ``@max_active`` >= 3, :: + + TIME IN MSECS EVENT + 0 w0 starts and burns CPU + 5 w0 sleeps + 5 w1 starts and burns CPU + 10 w1 sleeps + 10 w2 starts and burns CPU + 15 w2 sleeps + 15 w0 wakes up and burns CPU + 20 w0 finishes + 20 w1 wakes up and finishes + 25 w2 wakes up and finishes + +如果 ``@max_active`` == 2, :: + + TIME IN MSECS EVENT + 0 w0 starts and burns CPU + 5 w0 sleeps + 5 w1 starts and burns CPU + 10 w1 sleeps + 15 w0 wakes up and burns CPU + 20 w0 finishes + 20 w1 wakes up and finishes + 20 w2 starts and burns CPU + 25 w2 sleeps + 35 w2 wakes up and finishes + +现在,我们假设w1和w2被排到了不同的wq q1上,这个wq q1 +有 ``WQ_CPU_INTENSIVE`` 设置:: + + TIME IN MSECS EVENT + 0 w0 starts and burns CPU + 5 w0 sleeps + 5 w1 and w2 start and burn CPU + 10 w1 sleeps + 15 w2 sleeps + 15 w0 wakes up and burns CPU + 20 w0 finishes + 20 w1 wakes up and finishes + 25 w2 wakes up and finishes + + +指南 +==== + +* 如果一个wq可能处理在内存回收期间使用的工作项目,请不 + 要忘记使用 ``WQ_MEM_RECLAIM`` 。每个设置了 + ``WQ_MEM_RECLAIM`` 的wq都有一个为其保留的执行环境。 + 如果在内存回收过程中使用的多个工作项之间存在依赖关系, + 它们应该被排在不同的wq中,每个wq都有 ``WQ_MEM_RECLAIM`` 。 + +* 除非需要严格排序,否则没有必要使用ST wq。 + +* 除非有特殊需要,建议使用0作为@max_active。在大多数使用情 + 况下,并发水平通常保持在默认限制之下。 + +* 一个wq作为前进进度保证(WQ_MEM_RECLAIM,冲洗(flush)和工 + 作项属性的域。不涉及内存回收的工作项,不需要作为工作项组的一 + 部分被刷新,也不需要任何特殊属性,可以使用系统中的一个wq。使 + 用专用wq和系统wq在执行特性上没有区别。 + +* 除非工作项预计会消耗大量的CPU周期,否则使用绑定的wq通常是有 + 益的,因为wq操作和工作项执行中的定位水平提高了。 + + +调试 +==== + +因为工作函数是由通用的工作者线程执行的,所以需要一些手段来揭示一些行为不端的工作队列用户。 + +工作者线程在进程列表中显示为: :: + + root 5671 0.0 0.0 0 0 ? S 12:07 0:00 [kworker/0:1] + root 5672 0.0 0.0 0 0 ? S 12:07 0:00 [kworker/1:2] + root 5673 0.0 0.0 0 0 ? S 12:12 0:00 [kworker/0:0] + root 5674 0.0 0.0 0 0 ? S 12:13 0:00 [kworker/1:0] + +如果kworkers失控了(使用了太多的cpu),有两类可能的问题: + + 1. 正在迅速调度的事情 + 2. 一个消耗大量cpu周期的工作项。 + +第一个可以用追踪的方式进行跟踪: :: + + $ echo workqueue:workqueue_queue_work > /sys/kernel/debug/tracing/set_event + $ cat /sys/kernel/debug/tracing/trace_pipe > out.txt + (wait a few secs) + +如果有什么东西在工作队列上忙着做循环,它就会主导输出,可以用工作项函数确定违规者。 + +对于第二类问题,应该可以只检查违规工作者线程的堆栈跟踪。 :: + + $ cat /proc/THE_OFFENDING_KWORKER/stack + +工作项函数在堆栈追踪中应该是微不足道的。 + +不可重入条件 +============ + +工作队列保证,如果在工作项排队后满足以下条件,则工作项不能重入: + + + 1. 工作函数没有被改变。 + 2. 没有人将该工作项排到另一个工作队列中。 + 3. 该工作项尚未被重新启动。 + +换言之,如果上述条件成立,则保证在任何给定时间最多由一个系统范围内的工作程序执行 +该工作项。 + +请注意,在self函数中将工作项重新排队(到同一队列)不会破坏这些条件,因此可以安全 +地执行此操作。否则在破坏工作函数内部的条件时需要小心。 + + +内核内联文档参考 +================ + +该API在以下内核代码中: + +include/linux/workqueue.h + +kernel/workqueue.c diff --git a/Documentation/translations/zh_CN/core-api/xarray.rst b/Documentation/translations/zh_CN/core-api/xarray.rst new file mode 100644 index 000000000..fb1932496 --- /dev/null +++ b/Documentation/translations/zh_CN/core-api/xarray.rst @@ -0,0 +1,373 @@ +.. SPDX-License-Identifier: GPL-2.0+ +.. include:: ../disclaimer-zh_CN.rst + +:Original: Documentation/core-api/xarray.rst + +:翻译: + + 司延腾 Yanteng Si <siyanteng@loongson.cn> + 周彬彬 Binbin Zhou <zhoubinbin@loongson.cn> + +:校译: + + + +.. _cn_core-api_xarray: + +====== +XArray +====== + +:作者: Matthew Wilcox + +概览 +==== + +XArray是一个抽象的数据类型,它的行为就像一个非常大的指针数组。它满足了许多与哈 +希或传统可调整大小的数组相同的需求。与哈希不同的是,它允许你以一种高效的缓存方 +式合理地转到下一个或上一个条目。与可调整大小的数组相比,不需要复制数据或改变MMU +的映射来增加数组。与双链表相比,它的内存效率更高,可并行,对缓存更友好。它利用 +RCU的优势来执行查找而不需要锁定。 + +当使用的索引是密集聚集的时候,XArray的实现是有效的;而哈希对象并使用哈希作为索引 +将不会有好的表现。XArray对小的索引进行了优化,不过对大的索引仍有良好的性能。如果 +你的索引可以大于 ``ULONG_MAX`` ,那么XArray就不适合你的数据类型。XArray最重要 +的用户是页面高速缓存。 + +普通指针可以直接存储在XArray中。它们必须是4字节对齐的,这对任何从kmalloc()和 +alloc_page()返回的指针来说都是如此。这对任意的用户空间指针和函数指针来说都不是 +真的。你可以存储指向静态分配对象的指针,只要这些对象的对齐方式至少是4(字节)。 + +你也可以在XArray中存储0到 ``LONG_MAX`` 之间的整数。你必须首先使用xa_mk_value() +将其转换为一个条目。当你从XArray中检索一个条目时,你可以通过调用xa_is_value()检 +查它是否是一个值条目,并通过调用xa_to_value()将它转换回一个整数。 + +一些用户希望对他们存储在XArray中的指针进行标记。你可以调用xa_tag_pointer()来创建 +一个带有标签的条目,xa_untag_pointer()将一个有标签的条目转回一个无标签的指针, +xa_pointer_tag()来检索一个条目的标签。标签指针使用相同的位,用于区分值条目和普通 +指针,所以你必须决定他们是否要在任何特定的XArray中存储值条目或标签指针。 + +XArray不支持存储IS_ERR()指针,因为有些指针与值条目或内部条目冲突。 + +XArray的一个不寻常的特点是能够创建占据一系列索引的条目。一旦存储到其中,查询该范围 +内的任何索引将返回与查询该范围内任何其他索引相同的条目。存储到任何索引都会存储所有 +的索引条目。多索引条目可以明确地分割成更小的条目,或者将其存储 ``NULL`` 到任何条目中 +都会使XArray忘记范围。 + +普通API +======= + +首先初始化一个XArray,对于静态分配的XArray可以用DEFINE_XARRAY(),对于动态分配的 +XArray可以用xa_init()。一个新初始化的XArray在每个索引处都包含一个 ``NULL`` 指针。 + +然后你可以用xa_store()来设置条目,用xa_load()来获取条目。xa_store将用新的条目覆盖任 +何条目,并返回存储在该索引的上一个条目。你可以使用xa_erase()来代替调用xa_store()的 +``NULL`` 条目。一个从未被存储过的条目、一个被擦除的条目和一个最近被存储过 ``NULL`` 的 +条目之间没有区别。 + +你可以通过使用xa_cmpxchg()有条件地替换一个索引中的条目。和cmpxchg()一样,它只有在该索 +引的条目有 ‘旧‘ 值时才会成功。它也会返回该索引上的条目;如果它返回与传递的 ‘旧‘ 相同的条 +目,那么xa_cmpxchg()就成功了。 + +如果你只想在某个索引的当前条目为 ``NULL`` 时将一个新条目存储到该索引,你可以使用xa_insert(), +如果该条目不是空的,则返回 ``-EBUSY`` 。 + +你可以通过调用xa_extract()将条目从XArray中复制到一个普通数组中。或者你可以通过调用 +xa_for_each()、xa_for_each_start()或xa_for_each_range()来遍历XArray中的现有条目。你 +可能更喜欢使用xa_find()或xa_find_after()来移动到XArray中的下一个当前条目。 + +调用xa_store_range()可以在一个索引范围内存储同一个条目。如果你这样做,其他的一些操作将以 +一种稍微奇怪的方式进行。例如,在一个索引上标记条目可能会导致该条目在一些,但不是所有其他索 +引上被标记。储存到一个索引中可能会导致由一些,但不是所有其他索引检索的条目发生变化。 + +有时你需要确保对xa_store()的后续调用将不需要分配内存。xa_reserve()函数将在指定索引处存储 +一个保留条目。普通API的用户将看到这个条目包含 ``NULL`` 。如果你不需要使用保留的条目,你可 +以调用xa_release()来删除这个未使用的条目。如果在此期间有其他用户存储到该条目,xa_release() +将不做任何事情;相反,如果你想让该条目变成 ``NULL`` ,你应该使用xa_erase()。在一个保留的条 +目上使用xa_insert()将会失败。 + +如果数组中的所有条目都是 ``NULL`` ,xa_empty()函数将返回 ``true`` 。 + +最后,你可以通过调用xa_destroy()删除XArray中的所有条目。如果XArray的条目是指针,你可能希望 +先释放这些条目。你可以通过使用xa_for_each()迭代器遍历XArray中所有存在的条目来实现这一目的。 + +搜索标记 +-------- + +数组中的每个条目都有三个与之相关的位,称为标记。每个标记可以独立于其他标记被设置或清除。你可以 +通过使用xa_for_each_marked()迭代器来迭代有标记的条目。 + +你可以通过使用xa_get_mark()来查询某个条目是否设置了标记。如果该条目不是 ``NULL`` ,你可以通过 +使用xa_set_mark()来设置一个标记,并通过调用xa_clear_mark()来删除条目上的标记。你可以通过调用 +xa_marked()来询问XArray中的任何条目是否有一个特定的标记被设置。从XArray中删除一个条目会导致与 +该条目相关的所有标记被清除。 + +在一个多索引条目的任何索引上设置或清除标记将影响该条目所涵盖的所有索引。查询任何索引上的标记将返 +回相同的结果。 + +没有办法对没有标记的条目进行迭代;数据结构不允许有效地实现这一点。目前没有迭代器来搜索比特的逻辑 +组合(例如迭代所有同时设置了 ``XA_MARK_1`` 和 ``XA_MARK_2`` 的条目,或者迭代所有设置了 +``XA_MARK_0`` 或 ``XA_MARK_2`` 的条目)。如果有用户需要,可以增加这些内容。 + +分配XArrays +----------- + +如果你使用DEFINE_XARRAY_ALLOC()来定义XArray,或者通过向xa_init_flags()传递 ``XA_FLAGS_ALLOC`` +来初始化它,XArray会改变以跟踪条目是否被使用。 + +你可以调用xa_alloc()将条目存储在XArray中一个未使用的索引上。如果你需要从中断上下文中修改数组,你 +可以使用xa_alloc_bh()或xa_alloc_irq(),在分配ID的同时禁用中断。 + +使用xa_store()、xa_cmpxchg()或xa_insert()也将标记该条目为正在分配。与普通的XArray不同,存储 ``NULL`` +将标记该条目为正在使用中,就像xa_reserve()。要释放一个条目,请使用xa_erase()(或者xa_release(), +如果你只想释放一个 ``NULL`` 的条目)。 + +默认情况下,最低的空闲条目从0开始分配。如果你想从1开始分配条目,使用DEFINE_XARRAY_ALLOC1()或 +``XA_FLAGS_ALLOC1`` 会更有效。如果你想分配ID到一个最大值,然后绕回最低的空闲ID,你可以使用 +xa_alloc_cyclic()。 + +你不能在分配的XArray中使用 ``XA_MARK_0`` ,因为这个标记是用来跟踪一个条目是否是空闲的。其他的 +标记可以供你使用。 + +内存分配 +-------- + +xa_store(), xa_cmpxchg(), xa_alloc(), xa_reserve()和xa_insert()函数接受一个gfp_t参数,以 +防XArray需要分配内存来存储这个条目。如果该条目被删除,则不需要进行内存分配,指定的GFP标志将被忽 +略。 + +没有内存可供分配是可能的,特别是如果你传递了一组限制性的GFP标志。在这种情况下,这些函数会返回一 +个特殊的值,可以用xa_err()把它变成一个错误值。如果你不需要确切地知道哪个错误发生,使用xa_is_err() +会更有效一些。 + +锁 +-- + +当使用普通API时,你不必担心锁的问题。XArray使用RCU和一个内部自旋锁来同步访问: + +不需要锁: + * xa_empty() + * xa_marked() + +采取RCU读锁: + * xa_load() + * xa_for_each() + * xa_for_each_start() + * xa_for_each_range() + * xa_find() + * xa_find_after() + * xa_extract() + * xa_get_mark() + +内部使用xa_lock: + * xa_store() + * xa_store_bh() + * xa_store_irq() + * xa_insert() + * xa_insert_bh() + * xa_insert_irq() + * xa_erase() + * xa_erase_bh() + * xa_erase_irq() + * xa_cmpxchg() + * xa_cmpxchg_bh() + * xa_cmpxchg_irq() + * xa_store_range() + * xa_alloc() + * xa_alloc_bh() + * xa_alloc_irq() + * xa_reserve() + * xa_reserve_bh() + * xa_reserve_irq() + * xa_destroy() + * xa_set_mark() + * xa_clear_mark() + +假设进入时持有xa_lock: + * __xa_store() + * __xa_insert() + * __xa_erase() + * __xa_cmpxchg() + * __xa_alloc() + * __xa_set_mark() + * __xa_clear_mark() + +如果你想利用锁来保护你存储在XArray中的数据结构,你可以在调用xa_load()之前调用xa_lock(),然后在 +调用xa_unlock()之前对你找到的对象进行一个引用计数。这将防止存储操作在查找对象和增加refcount期间 +从数组中删除对象。你也可以使用RCU来避免解除对已释放内存的引用,但对这一点的解释已经超出了本文的范 +围。 + +在修改数组时,XArray不会禁用中断或softirqs。从中断或softirq上下文中读取XArray是安全的,因为RCU锁 +提供了足够的保护。 + +例如,如果你想在进程上下文中存储XArray中的条目,然后在softirq上下文中擦除它们,你可以这样做:: + + void foo_init(struct foo *foo) + { + xa_init_flags(&foo->array, XA_FLAGS_LOCK_BH); + } + + int foo_store(struct foo *foo, unsigned long index, void *entry) + { + int err; + + xa_lock_bh(&foo->array); + err = xa_err(__xa_store(&foo->array, index, entry, GFP_KERNEL)); + if (!err) + foo->count++; + xa_unlock_bh(&foo->array); + return err; + } + + /* foo_erase()只在软中断上下文中调用 */ + void foo_erase(struct foo *foo, unsigned long index) + { + xa_lock(&foo->array); + __xa_erase(&foo->array, index); + foo->count--; + xa_unlock(&foo->array); + } + +如果你要从中断或softirq上下文中修改XArray,你需要使用xa_init_flags()初始化数组,传递 +``XA_FLAGS_LOCK_IRQ`` 或 ``XA_FLAGS_LOCK_BH`` (参数)。 + +上面的例子还显示了一个常见的模式,即希望在存储端扩展xa_lock的覆盖范围,以保护与数组相关的一些统计 +数据。 + +与中断上下文共享XArray也是可能的,可以在中断处理程序和进程上下文中都使用xa_lock_irqsave(),或者 +在进程上下文中使用xa_lock_irq(),在中断处理程序中使用xa_lock()。一些更常见的模式有一些辅助函数, +如xa_store_bh()、xa_store_irq()、xa_erase_bh()、xa_erase_irq()、xa_cmpxchg_bh() 和xa_cmpxchg_irq()。 + +有时你需要用一个mutex来保护对XArray的访问,因为这个锁在锁的层次结构中位于另一个mutex之上。这并不 +意味着你有权使用像__xa_erase()这样的函数而不占用xa_lock;xa_lock是用来进行lockdep验证的,将来也 +会用于其他用途。 + +__xa_set_mark() 和 __xa_clear_mark() 函数也适用于你查找一个条目并想原子化地设置或清除一个标记的 +情况。在这种情况下,使用高级API可能更有效,因为它将使你免于走两次树。 + +高级API +======= + +高级API提供了更多的灵活性和更好的性能,但代价是接口可能更难使用,保障措施更少。高级API没有为你加锁, +你需要在修改数组的时候使用xa_lock。在对数组进行只读操作时,你可以选择使用xa_lock或RCU锁。你可以在 +同一个数组上混合使用高级和普通操作;事实上,普通API是以高级API的形式实现的。高级API只对具有GPL兼容 +许可证的模块可用。 + +高级API是基于xa_state的。这是一个不透明的数据结构,你使用XA_STATE()宏在堆栈中声明。这个宏初始化了 +xa_state,准备开始在XArray上移动。它被用作一个游标来保持在XArray中的位置,并让你把各种操作组合在一 +起,而不必每次都从头开始。xa_state的内容受rcu_read_lock()或xas_lock()的保护。如果需要删除保护状态 +和树的这些锁中的任何一个,你必须调用xas_pause()以便将来的调用不会依赖于状态中未受保护的部分。 + +xa_state也被用来存储错误(store errors)。你可以调用xas_error()来检索错误。所有的操作在进行之前都 +会检查xa_state是否处于错误状态,所以你没有必要在每次调用之后检查错误;你可以连续进行多次调用,只在 +方便的时候检查。目前XArray代码本身产生的错误只有 ``ENOMEM`` 和 ``EINVAL`` ,但它支持任意的错误, +以防你想自己调用xas_set_err()。 + +如果xa_state持有 ``ENOMEM`` 错误,调用xas_nomem()将尝试使用指定的gfp标志分配更多的内存,并将其缓 +存在xa_state中供下一次尝试。这个想法是,你拿着xa_lock,尝试操作,然后放弃锁。该操作试图在持有锁的情 +况下分配内存,但它更有可能失败。一旦你放弃了锁,xas_nomem()可以更努力地尝试分配更多内存。如果值得重 +试该操作,它将返回 ``true`` (即出现了内存错误,分配了更多的内存)。如果它之前已经分配了内存,并且 +该内存没有被使用,也没有错误(或者一些不是 ``ENOMEM`` 的错误),那么它将释放之前分配的内存。 + +内部条目 +-------- + +XArray为它自己的目的保留了一些条目。这些条目从未通过正常的API暴露出来,但是当使用高级API时,有可能看 +到它们。通常,处理它们的最好方法是把它们传递给xas_retry(),如果它返回 ``true`` ,就重试操作。 + +.. flat-table:: + :widths: 1 1 6 + + * - 名称 + - 检测 + - 用途 + + * - Node + - xa_is_node() + - 一个XArray节点。 在使用多索引xa_state时可能是可见的。 + + * - Sibling + - xa_is_sibling() + - 一个多索引条目的非典型条目。该值表示该节点中的哪个槽有典型条目。 + + * - Retry + - xa_is_retry() + - 这个条目目前正在被一个拥有xa_lock的线程修改。在这个RCU周期结束时,包含该条目的节点可能会被释放。 + 你应该从数组的头部重新开始查找。 + + * - Zero + - xa_is_zero() + - Zero条目通过普通API显示为 ``NULL`` ,但在XArray中占有一个条目,可用于保留索引供将来使用。这是 + 通过为分配的条目分配XArrays来使用的,这些条目是 ``NULL`` 。 + +其他内部条目可能会在未来被添加。在可能的情况下,它们将由xas_retry()处理。 + +附加函数 +-------- + +xas_create_range()函数分配了所有必要的内存来存储一个范围内的每一个条目。如果它不能分配内存,它将在 +xa_state中设置ENOMEM。 + +你可以使用xas_init_marks()将一个条目上的标记重置为默认状态。这通常是清空所有标记,除非XArray被标记 +为 ``XA_FLAGS_TRACK_FREE`` ,在这种情况下,标记0被设置,所有其他标记被清空。使用xas_store()将一个 +条目替换为另一个条目不会重置该条目上的标记;如果你想重置标记,你应该明确地这样做。 + +xas_load()会尽可能地将xa_state移动到该条目附近。如果你知道xa_state已经移动到了该条目,并且需要检查 +该条目是否有变化,你可以使用xas_reload()来保存一个函数调用。 + +如果你需要移动到XArray中的不同索引,可以调用xas_set()。这可以将光标重置到树的顶端,这通常会使下一个 +操作将光标移动到树中想要的位置。如果你想移动到下一个或上一个索引,调用xas_next()或xas_prev()。设置 +索引不会使光标在数组中移动,所以不需要锁,而移动到下一个或上一个索引则需要锁。 + +你可以使用xas_find()搜索下一个当前条目。这相当于xa_find()和xa_find_after();如果光标已经移动到了 +一个条目,那么它将找到当前引用的条目之后的下一个条目。如果没有,它将返回xa_state索引处的条目。使用 +xas_next_entry()而不是xas_find()来移动到下一个当前条目,在大多数情况下会节省一个函数调用,但代价 +是发出更多内联代码。 + +xas_find_marked()函数也是如此。如果xa_state没有被移动过,它将返回xa_state的索引处的条目,如果它 +被标记了。否则,它将返回xa_state所引用的条目之后的第一个被标记的条目。xas_next_marked()函数等同 +于xas_next_entry()。 + +当使用xas_for_each()或xas_for_each_marked()在XArray的某个范围内进行迭代时,可能需要暂时停止迭代。 +xas_pause()函数的存在就是为了这个目的。在你完成了必要的工作并希望恢复后,xa_state处于适当的状态,在 +你最后处理的条目后继续迭代。如果你在迭代时禁用了中断,那么暂停迭代并在每一个 ``XA_CHECK_SCHED`` 条目 +中重新启用中断是很好的做法。 + +xas_get_mark(), xas_set_mark()和xas_clear_mark()函数要求xa_state光标已经被移动到XArray中的适当位 +置;如果你在之前调用了xas_pause()或xas_set(),它们将不会有任何作用。 + +你可以调用xas_set_update(),让XArray每次更新一个节点时都调用一个回调函数。这被页面缓存的workingset +代码用来维护其只包含阴影项的节点列表。 + +多索引条目 +---------- + +XArray有能力将多个索引联系在一起,因此对一个索引的操作会影响到所有的索引。例如,存储到任何索引将改变 +从任何索引检索的条目的值。在任何索引上设置或清除一个标记,都会在每个被绑在一起的索引上设置或清除该标 +记。目前的实现只允许将2的整数倍的范围绑在一起;例如指数64-127可以绑在一起,但2-6不能。这可以节省大量 +的内存;例如,将512个条目绑在一起可以节省4kB以上的内存。 + +你可以通过使用XA_STATE_ORDER()或xas_set_order(),然后调用xas_store()来创建一个多索引条目。用一个 +多索引的xa_state调用xas_load()会把xa_state移动到树中的正确位置,但是返回值没有意义,有可能是一个内 +部条目或 ``NULL`` ,即使在范围内有一个条目存储。调用xas_find_conflict()将返回该范围内的第一个条目, +如果该范围内没有条目,则返回 ``NULL`` 。xas_for_each_conflict()迭代器将遍历每个与指定范围重叠的条目。 + +如果xas_load()遇到一个多索引条目,xa_state中的xa_index将不会被改变。当遍历一个XArray或者调用xas_find() +时,如果初始索引在一个多索引条目的中间,它将不会被改变。随后的调用或迭代将把索引移到范围内的第一个索引。 +每个条目只会被返回一次,不管它占据了多少个索引。 + +不支持使用xas_next()或xas_prev()来处理一个多索引的xa_state。在一个多索引的条目上使用这两个函数中的任 +何一个都会显示出同级的条目;这些条目应该被调用者跳过。 + +在一个多索引条目的任何一个索引中存储 ``NULL`` ,将把每个索引的条目设置为 ``NULL`` ,并解除绑定。通过调 +用xas_split_alloc(),在没有xa_lock的情况下,可以将一个多索引条目分割成占据较小范围的条目,然后再取锁并 +调用xas_split()。 + +函数和结构体 +============ + +该API在以下内核代码中: + +include/linux/xarray.h + +lib/xarray.c diff --git a/Documentation/translations/zh_CN/cpu-freq/core.rst b/Documentation/translations/zh_CN/cpu-freq/core.rst new file mode 100644 index 000000000..b3c095306 --- /dev/null +++ b/Documentation/translations/zh_CN/cpu-freq/core.rst @@ -0,0 +1,109 @@ +.. SPDX-License-Identifier: GPL-2.0 +.. include:: ../disclaimer-zh_CN.rst + +:Original: Documentation/cpu-freq/core.rst + +:翻译: + + 司延腾 Yanteng Si <siyanteng@loongson.cn> + +:校译: + + 唐艺舟 Tang Yizhou <tangyeechou@gmail.com> + +==================================== +CPUFreq核心和CPUFreq通知器的通用说明 +==================================== + +作者: + - Dominik Brodowski <linux@brodo.de> + - David Kimdon <dwhedon@debian.org> + - Rafael J. Wysocki <rafael.j.wysocki@intel.com> + - Viresh Kumar <viresh.kumar@linaro.org> + +.. 目录: + + 1. CPUFreq核心和接口 + 2. CPUFreq通知器 + 3. 含有Operating Performance Point (OPP)的CPUFreq表的生成 + +1. CPUFreq核心和接口 +====================== + +cpufreq核心代码位于drivers/cpufreq/cpufreq.c中。这些cpufreq代码为CPUFreq架构的驱 +动程序(那些执行硬件频率切换的代码)以及 "通知器" 提供了一个标准化的接口。 +包括设备驱动程序;需要了解策略变化(如 ACPI 热量管理),或所有频率变化(如计时代码), +甚至需要强制限制为指定频率(如 ARM 架构上的 LCD 驱动程序)的其它内核组件。 +此外,内核 "常数" loops_per_jiffy 会根据频率变化而更新。 + +cpufreq策略的引用计数由 cpufreq_cpu_get 和 cpufreq_cpu_put 来完成,以确保 cpufreq 驱 +动程序被正确地注册到核心中,并且驱动程序在 cpufreq_put_cpu 被调用之前不会被卸载。这也保证 +了每个CPU核的cpufreq 策略在使用期间不会被释放。 + +2. CPUFreq 通知器 +==================== + +CPUFreq通知器遵循标准的内核通知器接口。 +关于通知器的细节请参阅 linux/include/linux/notifier.h。 + +这里有两个不同的CPUfreq通知器 - 策略通知器和转换通知器。 + + +2.1 CPUFreq策略通知器 +---------------------------- + +当创建或移除策略时,这些都会被通知。 + +阶段是在通知器的第二个参数中指定的。当第一次创建策略时,阶段是CPUFREQ_CREATE_POLICY,当 +策略被移除时,阶段是CPUFREQ_REMOVE_POLICY。 + +第三个参数 ``void *pointer`` 指向一个结构体cpufreq_policy,其包括min,max(新策略的下限和 +上限(单位为kHz))这几个值。 + + +2.2 CPUFreq转换通知器 +-------------------------------- + +当CPUfreq驱动切换CPU核心频率时,策略中的每个在线CPU都会收到两次通知,这些变化没有任何外部干 +预。 + +第二个参数指定阶段 - CPUFREQ_PRECHANGE or CPUFREQ_POSTCHANGE. + +第三个参数是一个包含如下值的结构体cpufreq_freqs: + +====== =============================== +policy 指向struct cpufreq_policy的指针 +old 旧频率 +new 新频率 +flags cpufreq驱动的标志 +====== =============================== + +3. 含有Operating Performance Point (OPP)的CPUFreq表的生成 +================================================================== +关于OPP的细节请参阅 Documentation/power/opp.rst + +dev_pm_opp_init_cpufreq_table - + 这个函数提供了一个随时可用的转换例程,用来将OPP层关于可用频率的内部信息翻译成一种 + cpufreq易于处理的格式。 + + .. Warning:: + + 不要在中断上下文中使用此函数。 + + 例如:: + + soc_pm_init() + { + /* Do things */ + r = dev_pm_opp_init_cpufreq_table(dev, &freq_table); + if (!r) + policy->freq_table = freq_table; + /* Do other things */ + } + + .. note:: + + 该函数只有在CONFIG_PM_OPP之外还启用了CONFIG_CPU_FREQ时才可用。 + +dev_pm_opp_free_cpufreq_table + 释放dev_pm_opp_init_cpufreq_table分配的表。 diff --git a/Documentation/translations/zh_CN/cpu-freq/cpu-drivers.rst b/Documentation/translations/zh_CN/cpu-freq/cpu-drivers.rst new file mode 100644 index 000000000..2ca920427 --- /dev/null +++ b/Documentation/translations/zh_CN/cpu-freq/cpu-drivers.rst @@ -0,0 +1,257 @@ +.. SPDX-License-Identifier: GPL-2.0 + +.. include:: ../disclaimer-zh_CN.rst + +:Original: Documentation/cpu-freq/cpu-drivers.rst + +:翻译: + + 司延腾 Yanteng Si <siyanteng@loongson.cn> + +:校译: + + 唐艺舟 Tang Yizhou <tangyeechou@gmail.com> + +======================================= +如何实现一个新的CPUFreq处理器驱动程序? +======================================= + +作者: + + + - Dominik Brodowski <linux@brodo.de> + - Rafael J. Wysocki <rafael.j.wysocki@intel.com> + - Viresh Kumar <viresh.kumar@linaro.org> + +.. Contents + + 1. 怎么做? + 1.1 初始化 + 1.2 Per-CPU 初始化 + 1.3 验证 + 1.4 target/target_index 或 setpolicy? + 1.5 target/target_index + 1.6 setpolicy + 1.7 get_intermediate 与 target_intermediate + 2. 频率表助手 + + + +1. 怎么做? +=========== + +如果,你刚刚得到了一个全新的CPU/芯片组及其数据手册,并希望为这个CPU/芯片组添加cpufreq +支持?很好,这里有一些至关重要的提示: + + +1.1 初始化 +---------- + +首先,在 __initcall level 7 (module_init())或更靠后的函数中检查这个内核是否 +运行在正确的CPU和正确的芯片组上。如果是,则使用cpufreq_register_driver()向 +CPUfreq核心层注册一个cpufreq_driver结构体。 + +结构体cpufreq_driver应该包含什么成员? + + .name - 驱动的名字。 + + .init - 一个指向per-policy初始化函数的指针。 + + .verify - 一个指向"verification"函数的指针。 + + .setpolicy 或 .fast_switch 或 .target 或 .target_index - 差异见 + 下文。 + +其它可选成员 + + .flags - 给cpufreq核心的提示。 + + .driver_data - cpufreq驱动程序的特有数据。 + + .get_intermediate 和 target_intermediate - 用于在改变CPU频率时切换到稳定 + 的频率。 + + .get - 返回CPU的当前频率。 + + .bios_limit - 返回HW/BIOS对CPU的最大频率限制值。 + + .exit - 一个指向per-policy清理函数的指针,该函数在CPU热插拔过程的CPU_POST_DEAD + 阶段被调用。 + + .suspend - 一个指向per-policy暂停函数的指针,该函数在关中断且在该策略的调节器停止 + 后被调用。 + + .resume - 一个指向per-policy恢复函数的指针,该函数在关中断且在调节器再一次启动前被 + 调用。 + + .ready - 一个指向per-policy准备函数的指针,该函数在策略完全初始化之后被调用。 + + .attr - 一个指向NULL结尾的"struct freq_attr"列表的指针,该列表允许导出值到 + sysfs。 + + .boost_enabled - 如果设置,则启用提升(boost)频率。 + + .set_boost - 一个指向per-policy函数的指针,该函数用来开启/关闭提升(boost)频率功能。 + + +1.2 Per-CPU 初始化 +------------------ + +每当一个新的CPU被注册到设备模型中,或者当cpufreq驱动注册自身之后,如果此CPU的cpufreq策 +略不存在,则会调用per-policy的初始化函数cpufreq_driver.init。请注意,.init()和.exit()例程 +只为某个策略调用一次,而不是对该策略管理的每个CPU调用一次。它需要一个 ``struct cpufreq_policy +*policy`` 作为参数。现在该怎么做呢? + +如果有必要,请在你的CPU上激活CPUfreq功能支持。 + +然后,驱动程序必须填写以下值: + ++-----------------------------------+--------------------------------------+ +|policy->cpuinfo.min_freq和 | 该CPU支持的最低和最高频率(kHz) | +|policy->cpuinfo.max_freq | | +| | | ++-----------------------------------+--------------------------------------+ +|policy->cpuinfo.transition_latency | CPU在两个频率之间切换所需的时间,以 | +| | 纳秒为单位(如不适用,设定为 | +| | CPUFREQ_ETERNAL) | +| | | ++-----------------------------------+--------------------------------------+ +|policy->cur | 该CPU当前的工作频率(如适用) | +| | | ++-----------------------------------+--------------------------------------+ +|policy->min, | 必须包含该CPU的"默认策略"。稍后 | +|policy->max, | 会用这些值调用 | +|policy->policy and, if necessary, | cpufreq_driver.verify和下面函数 | +|policy->governor | 之一:cpufreq_driver.setpolicy或 | +| | cpufreq_driver.target/target_index | +| | | ++-----------------------------------+--------------------------------------+ +|policy->cpus | 该policy通过DVFS框架影响的全部CPU | +| | (即与本CPU共享"时钟/电压"对)构成 | +| | 掩码(同时包含在线和离线CPU),用掩码 | +| | 更新本字段 | +| | | ++-----------------------------------+--------------------------------------+ + +对于设置其中的一些值(cpuinfo.min[max]_freq, policy->min[max]),频率表辅助函数可能会有帮 +助。关于它们的更多信息,请参见第2节。 + + +1.3 验证 +-------- + +当用户决定设置一个新的策略(由"policy,governor,min,max组成")时,必须对这个策略进行验证, +以便纠正不兼容的值。为了验证这些值,cpufreq_verify_within_limits(``struct cpufreq_policy +*policy``, ``unsigned int min_freq``, ``unsigned int max_freq``)函数可能会有帮助。 +关于频率表辅助函数的详细内容请参见第2节。 + +您需要确保至少有一个有效频率(或工作范围)在 policy->min 和 policy->max 范围内。如果有必 +要,先增大policy->max,只有在没有解决方案的情况下,才减小policy->min。 + + +1.4 target 或 target_index 或 setpolicy 或 fast_switch? +------------------------------------------------------- + +大多数cpufreq驱动甚至大多数CPU频率升降算法只允许将CPU频率设置为预定义的固定值。对于这些,你 +可以使用->target(),->target_index()或->fast_switch()回调。 + +有些具有硬件调频能力的处理器可以自行依据某些限制来切换CPU频率。它们应使用->setpolicy()回调。 + + +1.5. target/target_index +------------------------ + +target_index调用有两个参数: ``struct cpufreq_policy * policy`` 和 ``unsigned int`` +索引(用于索引频率表项)。 + +当调用这里时,CPUfreq驱动必须设置新的频率。实际频率必须由freq_table[index].frequency决定。 + +在发生错误的情况下总是应该恢复到之前的频率(即policy->restore_freq),即使我们已经切换到了 +中间频率。 + +已弃用 +---------- +target调用有三个参数。``struct cpufreq_policy * policy``, unsigned int target_frequency, +unsigned int relation. + +CPUfreq驱动在调用这里时必须设置新的频率。实际的频率必须使用以下规则来确定。 + +- 尽量贴近"目标频率"。 +- policy->min <= new_freq <= policy->max (这必须是有效的!!!) +- 如果 relation==CPUFREQ_REL_L,尝试选择一个高于或等于 target_freq 的 new_freq。("L代表 + 最低,但不能低于") +- 如果 relation==CPUFREQ_REL_H,尝试选择一个低于或等于 target_freq 的 new_freq。("H代表 + 最高,但不能高于") + +这里,频率表辅助函数可能会帮助你 -- 详见第2节。 + +1.6. fast_switch +---------------- + +这个函数用于从调度器的上下文进行频率切换。并非所有的驱动都要实现它,因为不允许在这个回调中睡眠。这 +个回调必须经过高度优化,以尽可能快地进行切换。 + +这个函数有两个参数: ``struct cpufreq_policy *policy`` 和 ``unsigned int target_frequency``。 + + +1.7 setpolicy +------------- + +setpolicy调用只需要一个 ``struct cpufreq_policy * policy`` 作为参数。需要将处理器内或芯片组内动态频 +率切换的下限设置为policy->min,上限设置为policy->max,如果支持的话,当policy->policy为 +CPUFREQ_POLICY_PERFORMANCE时选择面向性能的设置,为CPUFREQ_POLICY_POWERSAVE时选择面向省电的设置。 +也可以查看drivers/cpufreq/longrun.c中的参考实现。 + +1.8 get_intermediate 和 target_intermediate +-------------------------------------------- + +仅适用于未设置 target_index() 和 CPUFREQ_ASYNC_NOTIFICATION 的驱动。 + +get_intermediate应该返回一个平台想要切换到的稳定的中间频率,target_intermediate()应该将CPU设置为 +该频率,然后再跳转到'index'对应的频率。cpufreq核心会负责发送通知,驱动不必在 +target_intermediate()或target_index()中处理它们。 + +在驱动程序不想为某个目标频率切换到中间频率的情况下,它们可以让get_intermediate()返回'0'。 +在这种情况下,cpufreq核心将直接调用->target_index()。 + +注意:->target_index()应该在发生失败的情况下将频率恢复到policy->restore_freq, +因为cpufreq核心会为此发送通知。 + + +2. 频率表辅助函数 +================= + +由于大多数支持cpufreq的处理器只允许被设置为几个特定的频率,因此,"频率表"和一些相关函数可能会辅助处理器驱动 +程序的一些工作。这样的"频率表"是一个由struct cpufreq_frequency_table的条目构成的数组,"driver_data"成员包 +含驱动程序的专用值,"frequency"成员包含了相应的频率,此外还有标志成员。在表的最后,需要添加一个 +cpufreq_frequency_table条目,频率设置为CPUFREQ_TABLE_END。如果想跳过表中的一个条目,则将频率设置为 +CPUFREQ_ENTRY_INVALID。这些条目不需要按照任何特定的顺序排序,如果排序了,cpufreq核心执行DVFS会更快一点, +因为搜索最佳匹配会更快。 + +如果在policy->freq_table字段中包含一个有效的频率表指针,频率表就会被cpufreq核心自动验证。 + +cpufreq_frequency_table_verify()保证至少有一个有效的频率在policy->min和policy->max范围内,并且所有其他 +准则都被满足。这对->verify调用很有帮助。 + +cpufreq_frequency_table_target()是对应于->target阶段的频率表辅助函数。只要把值传递给这个函数,这个函数就会返 +回包含CPU要设置的频率的频率表条目。 + +以下宏可以作为cpufreq_frequency_table的迭代器。 + +cpufreq_for_each_entry(pos, table) - 遍历频率表的所有条目。 + +cpufreq_for_each_valid_entry(pos, table) - 该函数遍历所有条目,不包括CPUFREQ_ENTRY_INVALID频率。 +使用参数"pos" -- 一个 ``cpufreq_frequency_table *`` 作为循环指针,使用参数"table" -- 作为你想迭代 +的 ``cpufreq_frequency_table *`` 。 + +例如:: + + struct cpufreq_frequency_table *pos, *driver_freq_table; + + cpufreq_for_each_entry(pos, driver_freq_table) { + /* Do something with pos */ + pos->frequency = ... + } + +如果你需要在driver_freq_table中处理pos的位置,不要做指针减法,因为它的代价相当高。作为替代,使用宏 +cpufreq_for_each_entry_idx() 和 cpufreq_for_each_valid_entry_idx() 。 diff --git a/Documentation/translations/zh_CN/cpu-freq/cpufreq-stats.rst b/Documentation/translations/zh_CN/cpu-freq/cpufreq-stats.rst new file mode 100644 index 000000000..e8fdba923 --- /dev/null +++ b/Documentation/translations/zh_CN/cpu-freq/cpufreq-stats.rst @@ -0,0 +1,133 @@ +.. SPDX-License-Identifier: GPL-2.0 + +.. include:: ../disclaimer-zh_CN.rst + +:Original: Documentation/cpu-freq/cpufreq-stats.rst + +:翻译: + + 司延腾 Yanteng Si <siyanteng@loongson.cn> + +:校译: + + 唐艺舟 Tang Yizhou <tangyeechou@gmail.com> + +========================================== +sysfs CPUFreq Stats的一般说明 +========================================== + +为使用者准备的信息 + + +作者: Venkatesh Pallipadi <venkatesh.pallipadi@intel.com> + +.. Contents + + 1. 简介 + 2. 提供的统计数据(举例说明) + 3. 配置cpufreq-stats + + +1. 简介 +=============== + +cpufreq-stats是一种为每个CPU提供CPU频率统计的驱动。 +这些统计数据以/sysfs中一系列只读接口的形式呈现。cpufreq-stats接口(若已配置)将为每个CPU生成 +/sysfs(<sysfs root>/devices/system/cpu/cpuX/cpufreq/stats/)中cpufreq目录下的stats目录。 +各项统计数据将在stats目录下形成对应的只读文件。 + +此驱动是以独立于任何可能运行在你所用CPU上的特定cpufreq_driver的方式设计的。因此,它将能和任何 +cpufreq_driver协同工作。 + + +2. 已提供的统计数据(有例子) +===================================== + +cpufreq stats提供了以下统计数据(在下面详细解释)。 + +- time_in_state +- total_trans +- trans_table + +所有统计数据来自以下时间范围:从统计驱动被加载的时间(或统计数据被重置的时间)开始,到某一统计数据被读取的时间为止。 +显然,统计驱动不会保存它被加载之前的任何频率转换信息。 + +:: + + <mysystem>:/sys/devices/system/cpu/cpu0/cpufreq/stats # ls -l + total 0 + drwxr-xr-x 2 root root 0 May 14 16:06 . + drwxr-xr-x 3 root root 0 May 14 15:58 .. + --w------- 1 root root 4096 May 14 16:06 reset + -r--r--r-- 1 root root 4096 May 14 16:06 time_in_state + -r--r--r-- 1 root root 4096 May 14 16:06 total_trans + -r--r--r-- 1 root root 4096 May 14 16:06 trans_table + +- **reset** + +只写属性,可用于重置统计计数器。这对于评估不同调节器的系统行为非常有用,且无需重启。 + + +- **time_in_state** + +此文件给出了在本CPU支持的每个频率上分别花费的时间。cat输出的每一行都是一个"<frequency> +<time>"对,表示这个CPU在<frequency>上花费了<time>个usertime单位的时间。输出的每一行对应 +一个CPU支持的频率。这里usertime单位是10mS(类似于/proc导出的其它时间)。 + +:: + + <mysystem>:/sys/devices/system/cpu/cpu0/cpufreq/stats # cat time_in_state + 3600000 2089 + 3400000 136 + 3200000 34 + 3000000 67 + 2800000 172488 + + +- **total_trans** + +此文件给出了这个CPU频率转换的总次数。cat的输出是一个计数值,它就是频率转换的总次数。 + +:: + + <mysystem>:/sys/devices/system/cpu/cpu0/cpufreq/stats # cat total_trans + 20 + +- **trans_table** + +本文件提供所有CPU频率转换的细粒度信息。这里的cat输出是一个二维矩阵,其中一个条目<i, j>(第 +i行,第j列)代表从Freq_i到Freq_j的转换次数。Freq_i行和Freq_j列遵循驱动最初提供给cpufreq +核心的频率表的排列顺序,因此可以已排序(升序或降序)或未排序。这里的输出也包含了实际 +频率值,分别按行和按列显示,以便更好地阅读。 + +如果转换表大于PAGE_SIZE,读取时将返回一个-EFBIG错误。 + +:: + + <mysystem>:/sys/devices/system/cpu/cpu0/cpufreq/stats # cat trans_table + From : To + : 3600000 3400000 3200000 3000000 2800000 + 3600000: 0 5 0 0 0 + 3400000: 4 0 2 0 0 + 3200000: 0 1 0 2 0 + 3000000: 0 0 1 0 3 + 2800000: 0 0 0 2 0 + +3. 配置cpufreq-stats +============================ + +按以下方式在你的内核中配置cpufreq-stats:: + + Config Main Menu + Power management options (ACPI, APM) ---> + CPU Frequency scaling ---> + [*] CPU Frequency scaling + [*] CPU frequency translation statistics + + +"CPU Frequency scaling" (CONFIG_CPU_FREQ) 应该被启用,以支持配置cpufreq-stats。 + +"CPU frequency translation statistics" (CONFIG_CPU_FREQ_STAT)提供了包括 +time_in_state、total_trans和trans_table的统计数据。 + +一旦启用了这个选项,并且你的CPU支持cpufrequency,你就可以在/sysfs中看到CPU频率统计。 diff --git a/Documentation/translations/zh_CN/cpu-freq/index.rst b/Documentation/translations/zh_CN/cpu-freq/index.rst new file mode 100644 index 000000000..c6e50963c --- /dev/null +++ b/Documentation/translations/zh_CN/cpu-freq/index.rst @@ -0,0 +1,47 @@ +.. SPDX-License-Identifier: GPL-2.0 + +.. include:: ../disclaimer-zh_CN.rst + +:Original: Documentation/cpu-freq/index.rst + +:翻译: + + 司延腾 Yanteng Si <siyanteng@loongson.cn> + +.. _cn_index.rst: + +======================================================= +Linux CPUFreq - Linux(TM)内核中的CPU频率和电压升降代码 +======================================================= + +Author: Dominik Brodowski <linux@brodo.de> + + 时钟升降允许你在运行中改变CPU的时钟速度。这是一个很好的节省电池电量的方法,因为时 + 钟速度越低,CPU消耗的电量越少。 + + +.. toctree:: + :maxdepth: 1 + + core + cpu-drivers + cpufreq-stats + +邮件列表 +------------ +这里有一个 CPU 频率变化的 CVS 提交和通用列表,您可以在这里报告bug、问题或提交补丁。要发 +布消息,请发送电子邮件到 linux-pm@vger.kernel.org。 + +链接 +----- +FTP档案: +* ftp://ftp.linux.org.uk/pub/linux/cpufreq/ + +如何访问CVS仓库: +* http://cvs.arm.linux.org.uk/ + +CPUFreq邮件列表: +* http://vger.kernel.org/vger-lists.html#linux-pm + +SA-1100的时钟和电压标度: +* http://www.lartmaker.nl/projects/scaling diff --git a/Documentation/translations/zh_CN/dev-tools/gcov.rst b/Documentation/translations/zh_CN/dev-tools/gcov.rst new file mode 100644 index 000000000..3158c9da1 --- /dev/null +++ b/Documentation/translations/zh_CN/dev-tools/gcov.rst @@ -0,0 +1,264 @@ +.. include:: ../disclaimer-zh_CN.rst + +:Original: Documentation/dev-tools/gcov.rst +:Translator: 赵军奎 Bernard Zhao <bernard@vivo.com> + +在Linux内核里使用gcov做代码覆盖率检查 +===================================== + +gcov分析核心支持在Linux内核中启用GCC的覆盖率测试工具 gcov_ ,Linux内核 +运行时的代码覆盖率数据会以gcov兼容的格式导出到“gcov”debugfs目录中,可 +以通过gcov的 ``-o`` 选项(如下示例)获得指定文件的代码运行覆盖率统计数据 +(需要跳转到内核编译路径下并且要有root权限):: + + # cd /tmp/linux-out + # gcov -o /sys/kernel/debug/gcov/tmp/linux-out/kernel spinlock.c + +这将在当前目录中创建带有执行计数注释的源代码文件。 +在获得这些统计文件后,可以使用图形化的gcov前端工具(比如 lcov_ ),来实现 +自动化处理Linux内核的覆盖率运行数据,同时生成易于阅读的HTML格式文件。 + +可能的用途: + +* 调试(用来判断每一行的代码是否已经运行过) +* 测试改进(如何修改测试代码,尽可能地覆盖到没有运行过的代码) +* 内核最小化配置(对于某一个选项配置,如果关联的代码从来没有运行过, + 是否还需要这个配置) + +.. _gcov: https://gcc.gnu.org/onlinedocs/gcc/Gcov.html +.. _lcov: http://ltp.sourceforge.net/coverage/lcov.php + + +准备 +---- + +内核打开如下配置:: + + CONFIG_DEBUG_FS=y + CONFIG_GCOV_KERNEL=y + +获取整个内核的覆盖率数据,还需要打开:: + + CONFIG_GCOV_PROFILE_ALL=y + +需要注意的是,整个内核开启覆盖率统计会造成内核镜像文件尺寸的增大, +同时内核运行也会变慢一些。 +另外,并不是所有的架构都支持整个内核开启覆盖率统计。 + +代码运行覆盖率数据只在debugfs挂载完成后才可以访问:: + + mount -t debugfs none /sys/kernel/debug + + +定制化 +------ + +如果要单独针对某一个路径或者文件进行代码覆盖率统计,可以在内核相应路 +径的Makefile中增加如下的配置: + +- 单独统计单个文件(例如main.o):: + + GCOV_PROFILE_main.o := y + +- 单独统计某一个路径:: + + GCOV_PROFILE := y + +如果要在整个内核的覆盖率统计(开启CONFIG_GCOV_PROFILE_ALL)中单独排除 +某一个文件或者路径,可以使用如下的方法:: + + GCOV_PROFILE_main.o := n + +和:: + + GCOV_PROFILE := n + +此机制仅支持链接到内核镜像或编译为内核模块的文件。 + + +相关文件 +-------- + +gcov功能需要在debugfs中创建如下文件: + +``/sys/kernel/debug/gcov`` + gcov相关功能的根路径 + +``/sys/kernel/debug/gcov/reset`` + 全局复位文件:向该文件写入数据后会将所有的gcov统计数据清0 + +``/sys/kernel/debug/gcov/path/to/compile/dir/file.gcda`` + gcov工具可以识别的覆盖率统计数据文件,向该文件写入数据后 + 会将本文件的gcov统计数据清0 + +``/sys/kernel/debug/gcov/path/to/compile/dir/file.gcno`` + gcov工具需要的软连接文件(指向编译时生成的信息统计文件),这个文件是 + 在gcc编译时如果配置了选项 ``-ftest-coverage`` 时生成的。 + + +针对模块的统计 +-------------- + +内核中的模块会动态的加载和卸载,模块卸载时对应的数据会被清除掉。 +gcov提供了一种机制,通过保留相关数据的副本来收集这部分卸载模块的覆盖率数据。 +模块卸载后这些备份数据在debugfs中会继续存在。 +一旦这个模块重新加载,模块关联的运行统计会被初始化成debugfs中备份的数据。 + +可以通过对内核参数gcov_persist的修改来停用gcov对模块的备份机制:: + + gcov_persist = 0 + +在运行时,用户还可以通过写入模块的数据文件或者写入gcov复位文件来丢弃已卸 +载模块的数据。 + + +编译机和测试机分离 +------------------ + +gcov的内核分析插桩支持内核的编译和运行是在同一台机器上,也可以编译和运 +行是在不同的机器上。 +如果内核编译和运行是不同的机器,那么需要额外的准备工作,这取决于gcov工具 +是在哪里使用的: + +.. _gcov-test_zh: + +a) 若gcov运行在测试机上 + + 测试机上面gcov工具的版本必须要跟内核编译机器使用的gcc版本相兼容, + 同时下面的文件要从编译机拷贝到测试机上: + + 从源代码中: + - 所有的C文件和头文件 + + 从编译目录中: + - 所有的C文件和头文件 + - 所有的.gcda文件和.gcno文件 + - 所有目录的链接 + + 特别需要注意,测试机器上面的目录结构跟编译机器上面的目录机构必须 + 完全一致。 + 如果文件是软链接,需要替换成真正的目录文件(这是由make的当前工作 + 目录变量CURDIR引起的)。 + +.. _gcov-build_zh: + +b) 若gcov运行在编译机上 + + 测试用例运行结束后,如下的文件需要从测试机中拷贝到编译机上: + + 从sysfs中的gcov目录中: + - 所有的.gcda文件 + - 所有的.gcno文件软链接 + + 这些文件可以拷贝到编译机的任意目录下,gcov使用-o选项指定拷贝的 + 目录。 + + 比如一个是示例的目录结构如下:: + + /tmp/linux: 内核源码目录 + /tmp/out: 内核编译文件路径(make O=指定) + /tmp/coverage: 从测试机器上面拷贝的数据文件路径 + + [user@build] cd /tmp/out + [user@build] gcov -o /tmp/coverage/tmp/out/init main.c + + +关于编译器的注意事项 +-------------------- + +GCC和LLVM gcov工具不一定兼容。 +如果编译器是GCC,使用 gcov_ 来处理.gcno和.gcda文件,如果是Clang编译器, +则使用 llvm-cov_ 。 + +.. _gcov: https://gcc.gnu.org/onlinedocs/gcc/Gcov.html +.. _llvm-cov: https://llvm.org/docs/CommandGuide/llvm-cov.html + +GCC和Clang gcov之间的版本差异由Kconfig处理的。 +kconfig会根据编译工具链的检查自动选择合适的gcov格式。 + +问题定位 +-------- + +可能出现的问题1 + 编译到链接阶段报错终止 + +问题原因 + 分析标志指定在了源文件但是没有链接到主内核,或者客制化了链接程序 + +解决方法 + 通过在相应的Makefile中使用 ``GCOV_PROFILE := n`` + 或者 ``GCOV_PROFILE_basename.o := n`` 来将链接报错的文件排除掉 + +可能出现的问题2 + 从sysfs复制的文件显示为空或不完整 + +问题原因 + 由于seq_file的工作方式,某些工具(例如cp或tar)可能无法正确地从 + sysfs复制文件。 + +解决方法 + 使用 ``cat`` 读取 ``.gcda`` 文件,使用 ``cp -d`` 复制链接,或者使用附录B + 中所示的机制。 + + +附录A:collect_on_build.sh +-------------------------- + +用于在编译机上收集覆盖率元文件的示例脚本 +(见 :ref:`编译机和测试机分离 a. <gcov-test_zh>` ) + +.. code-block:: sh + + #!/bin/bash + + KSRC=$1 + KOBJ=$2 + DEST=$3 + + if [ -z "$KSRC" ] || [ -z "$KOBJ" ] || [ -z "$DEST" ]; then + echo "Usage: $0 <ksrc directory> <kobj directory> <output.tar.gz>" >&2 + exit 1 + fi + + KSRC=$(cd $KSRC; printf "all:\n\t@echo \${CURDIR}\n" | make -f -) + KOBJ=$(cd $KOBJ; printf "all:\n\t@echo \${CURDIR}\n" | make -f -) + + find $KSRC $KOBJ \( -name '*.gcno' -o -name '*.[ch]' -o -type l \) -a \ + -perm /u+r,g+r | tar cfz $DEST -P -T - + + if [ $? -eq 0 ] ; then + echo "$DEST successfully created, copy to test system and unpack with:" + echo " tar xfz $DEST -P" + else + echo "Could not create file $DEST" + fi + + +附录B:collect_on_test.sh +------------------------- + +用于在测试机上收集覆盖率数据文件的示例脚本 +(见 :ref:`编译机和测试机分离 b. <gcov-build_zh>` ) + +.. code-block:: sh + + #!/bin/bash -e + + DEST=$1 + GCDA=/sys/kernel/debug/gcov + + if [ -z "$DEST" ] ; then + echo "Usage: $0 <output.tar.gz>" >&2 + exit 1 + fi + + TEMPDIR=$(mktemp -d) + echo Collecting data.. + find $GCDA -type d -exec mkdir -p $TEMPDIR/\{\} \; + find $GCDA -name '*.gcda' -exec sh -c 'cat < $0 > '$TEMPDIR'/$0' {} \; + find $GCDA -name '*.gcno' -exec sh -c 'cp -d $0 '$TEMPDIR'/$0' {} \; + tar czf $DEST -C $TEMPDIR sys + rm -rf $TEMPDIR + + echo "$DEST successfully created, copy to build system and unpack with:" + echo " tar xfz $DEST" diff --git a/Documentation/translations/zh_CN/dev-tools/gdb-kernel-debugging.rst b/Documentation/translations/zh_CN/dev-tools/gdb-kernel-debugging.rst new file mode 100644 index 000000000..17b5ce85a --- /dev/null +++ b/Documentation/translations/zh_CN/dev-tools/gdb-kernel-debugging.rst @@ -0,0 +1,167 @@ +.. highlight:: none + +.. include:: ../disclaimer-zh_CN.rst + +:Original: Documentation/dev-tools/gdb-kernel-debugging.rst +:Translator: 高超 gao chao <gaochao49@huawei.com> + +通过gdb调试内核和模块 +===================== + +Kgdb内核调试器、QEMU等虚拟机管理程序或基于JTAG的硬件接口,支持在运行时使用gdb +调试Linux内核及其模块。Gdb提供了一个强大的python脚本接口,内核也提供了一套 +辅助脚本以简化典型的内核调试步骤。本文档为如何启用和使用这些脚本提供了一个简要的教程。 +此教程基于QEMU/KVM虚拟机,但文中示例也适用于其他gdb stub。 + + +环境配置要求 +------------ + +- gdb 7.2+ (推荐版本: 7.4+) 且开启python支持 (通常发行版上都已支持) + +设置 +---- + +- 创建一个QEMU/KVM的linux虚拟机(详情请参考 www.linux-kvm.org 和 www.qemu.org )。 + 对于交叉开发,https://landley.net/aboriginal/bin 提供了一些镜像和工具链, + 可以帮助搭建交叉开发环境。 + +- 编译内核时开启CONFIG_GDB_SCRIPTS,关闭CONFIG_DEBUG_INFO_REDUCED。 + 如果架构支持CONFIG_FRAME_POINTER,请保持开启。 + +- 在guest环境上安装该内核。如有必要,通过在内核command line中添加“nokaslr”来关闭KASLR。 + 此外,QEMU允许通过-kernel、-append、-initrd这些命令行选项直接启动内核。 + 但这通常仅在不依赖内核模块时才有效。有关此模式的更多详细信息,请参阅QEMU文档。 + 在这种情况下,如果架构支持KASLR,应该在禁用CONFIG_RANDOMIZE_BASE的情况下构建内核。 + +- 启用QEMU/KVM的gdb stub,可以通过如下方式实现 + + - 在VM启动时,通过在QEMU命令行中添加“-s”参数 + + 或 + + - 在运行时通过从QEMU监视控制台发送“gdbserver” + +- 切换到/path/to/linux-build(内核源码编译)目录 + +- 启动gdb:gdb vmlinux + + 注意:某些发行版可能会将gdb脚本的自动加载限制在已知的安全目录中。 + 如果gdb报告拒绝加载vmlinux-gdb.py(相关命令找不到),请将:: + + add-auto-load-safe-path /path/to/linux-build + + 添加到~/.gdbinit。更多详细信息,请参阅gdb帮助信息。 + +- 连接到已启动的guest环境:: + + (gdb) target remote :1234 + + +使用Linux提供的gdb脚本的示例 +---------------------------- + +- 加载模块(以及主内核)符号:: + + (gdb) lx-symbols + loading vmlinux + scanning for modules in /home/user/linux/build + loading @0xffffffffa0020000: /home/user/linux/build/net/netfilter/xt_tcpudp.ko + loading @0xffffffffa0016000: /home/user/linux/build/net/netfilter/xt_pkttype.ko + loading @0xffffffffa0002000: /home/user/linux/build/net/netfilter/xt_limit.ko + loading @0xffffffffa00ca000: /home/user/linux/build/net/packet/af_packet.ko + loading @0xffffffffa003c000: /home/user/linux/build/fs/fuse/fuse.ko + ... + loading @0xffffffffa0000000: /home/user/linux/build/drivers/ata/ata_generic.ko + +- 对一些尚未加载的模块中的函数函数设置断点,例如:: + + (gdb) b btrfs_init_sysfs + Function "btrfs_init_sysfs" not defined. + Make breakpoint pending on future shared library load? (y or [n]) y + Breakpoint 1 (btrfs_init_sysfs) pending. + +- 继续执行:: + + (gdb) c + +- 加载模块并且能观察到正在加载的符号以及断点命中:: + + loading @0xffffffffa0034000: /home/user/linux/build/lib/libcrc32c.ko + loading @0xffffffffa0050000: /home/user/linux/build/lib/lzo/lzo_compress.ko + loading @0xffffffffa006e000: /home/user/linux/build/lib/zlib_deflate/zlib_deflate.ko + loading @0xffffffffa01b1000: /home/user/linux/build/fs/btrfs/btrfs.ko + + Breakpoint 1, btrfs_init_sysfs () at /home/user/linux/fs/btrfs/sysfs.c:36 + 36 btrfs_kset = kset_create_and_add("btrfs", NULL, fs_kobj); + +- 查看内核的日志缓冲区:: + + (gdb) lx-dmesg + [ 0.000000] Initializing cgroup subsys cpuset + [ 0.000000] Initializing cgroup subsys cpu + [ 0.000000] Linux version 3.8.0-rc4-dbg+ (... + [ 0.000000] Command line: root=/dev/sda2 resume=/dev/sda1 vga=0x314 + [ 0.000000] e820: BIOS-provided physical RAM map: + [ 0.000000] BIOS-e820: [mem 0x0000000000000000-0x000000000009fbff] usable + [ 0.000000] BIOS-e820: [mem 0x000000000009fc00-0x000000000009ffff] reserved + .... + +- 查看当前task struct结构体的字段(仅x86和arm64支持):: + + (gdb) p $lx_current().pid + $1 = 4998 + (gdb) p $lx_current().comm + $2 = "modprobe\000\000\000\000\000\000\000" + +- 对当前或指定的CPU使用per-cpu函数:: + + (gdb) p $lx_per_cpu("runqueues").nr_running + $3 = 1 + (gdb) p $lx_per_cpu("runqueues", 2).nr_running + $4 = 0 + +- 使用container_of查看更多hrtimers信息:: + + (gdb) set $next = $lx_per_cpu("hrtimer_bases").clock_base[0].active.next + (gdb) p *$container_of($next, "struct hrtimer", "node") + $5 = { + node = { + node = { + __rb_parent_color = 18446612133355256072, + rb_right = 0x0 <irq_stack_union>, + rb_left = 0x0 <irq_stack_union> + }, + expires = { + tv64 = 1835268000000 + } + }, + _softexpires = { + tv64 = 1835268000000 + }, + function = 0xffffffff81078232 <tick_sched_timer>, + base = 0xffff88003fd0d6f0, + state = 1, + start_pid = 0, + start_site = 0xffffffff81055c1f <hrtimer_start_range_ns+20>, + start_comm = "swapper/2\000\000\000\000\000\000" + } + + +命令和辅助调试功能列表 +---------------------- + +命令和辅助调试功能可能会随着时间的推移而改进,此文显示的是初始版本的部分示例:: + + (gdb) apropos lx + function lx_current -- Return current task + function lx_module -- Find module by name and return the module variable + function lx_per_cpu -- Return per-cpu variable + function lx_task_by_pid -- Find Linux task by PID and return the task_struct variable + function lx_thread_info -- Calculate Linux thread_info from task variable + lx-dmesg -- Print Linux kernel log buffer + lx-lsmod -- List currently loaded modules + lx-symbols -- (Re-)load symbols of Linux kernel and currently loaded modules + +可以通过“help <command-name>”或“help function <function-name>”命令 +获取指定命令或指定调试功能的更多详细信息。 diff --git a/Documentation/translations/zh_CN/dev-tools/index.rst b/Documentation/translations/zh_CN/dev-tools/index.rst new file mode 100644 index 000000000..02577c379 --- /dev/null +++ b/Documentation/translations/zh_CN/dev-tools/index.rst @@ -0,0 +1,40 @@ +.. include:: ../disclaimer-zh_CN.rst + +:Original: Documentation/dev-tools/index.rst +:Translator: 赵军奎 Bernard Zhao <bernard@vivo.com> + +============ +内核开发工具 +============ + +本文档是有关内核开发工具文档的合集。 +目前这些文档已经整理在一起,不需要再花费额外的精力。 +欢迎任何补丁。 + +有关测试专用工具的简要概述,参见 +Documentation/translations/zh_CN/dev-tools/testing-overview.rst + +.. class:: toc-title + + 目录 + +.. toctree:: + :maxdepth: 2 + + testing-overview + sparse + gcov + kasan + gdb-kernel-debugging + +Todolist: + + - coccinelle + - kcov + - ubsan + - kmemleak + - kcsan + - kfence + - kgdb + - kselftest + - kunit/index diff --git a/Documentation/translations/zh_CN/dev-tools/kasan.rst b/Documentation/translations/zh_CN/dev-tools/kasan.rst new file mode 100644 index 000000000..fe76cbe77 --- /dev/null +++ b/Documentation/translations/zh_CN/dev-tools/kasan.rst @@ -0,0 +1,454 @@ +.. SPDX-License-Identifier: GPL-2.0 + +.. include:: ../disclaimer-zh_CN.rst + +:Original: Documentation/dev-tools/kasan.rst +:Translator: 万家兵 Wan Jiabing <wanjiabing@vivo.com> + +内核地址消毒剂(KASAN) +===================== + +概述 +---- + +Kernel Address SANitizer(KASAN)是一种动态内存安全错误检测工具,主要功能是 +检查内存越界访问和使用已释放内存的问题。 + +KASAN有三种模式: + +1. 通用KASAN +2. 基于软件标签的KASAN +3. 基于硬件标签的KASAN + +用CONFIG_KASAN_GENERIC启用的通用KASAN,是用于调试的模式,类似于用户空 +间的ASan。这种模式在许多CPU架构上都被支持,但它有明显的性能和内存开销。 + +基于软件标签的KASAN或SW_TAGS KASAN,通过CONFIG_KASAN_SW_TAGS启用, +可以用于调试和自我测试,类似于用户空间HWASan。这种模式只支持arm64,但其 +适度的内存开销允许在内存受限的设备上用真实的工作负载进行测试。 + +基于硬件标签的KASAN或HW_TAGS KASAN,用CONFIG_KASAN_HW_TAGS启用,被 +用作现场内存错误检测器或作为安全缓解的模式。这种模式只在支持MTE(内存标签 +扩展)的arm64 CPU上工作,但它的内存和性能开销很低,因此可以在生产中使用。 + +关于每种KASAN模式的内存和性能影响的细节,请参见相应的Kconfig选项的描述。 + +通用模式和基于软件标签的模式通常被称为软件模式。基于软件标签的模式和基于 +硬件标签的模式被称为基于标签的模式。 + +支持 +---- + +体系架构 +~~~~~~~~ + +在x86_64、arm、arm64、powerpc、riscv、s390和xtensa上支持通用KASAN, +而基于标签的KASAN模式只在arm64上支持。 + +编译器 +~~~~~~ + +软件KASAN模式使用编译时工具在每个内存访问之前插入有效性检查,因此需要一个 +提供支持的编译器版本。基于硬件标签的模式依靠硬件来执行这些检查,但仍然需要 +一个支持内存标签指令的编译器版本。 + +通用KASAN需要GCC 8.3.0版本或更高版本,或者内核支持的任何Clang版本。 + +基于软件标签的KASAN需要GCC 11+或者内核支持的任何Clang版本。 + +基于硬件标签的KASAN需要GCC 10+或Clang 12+。 + +内存类型 +~~~~~~~~ + +通用KASAN支持在所有的slab、page_alloc、vmap、vmalloc、堆栈和全局内存 +中查找错误。 + +基于软件标签的KASAN支持slab、page_alloc、vmalloc和堆栈内存。 + +基于硬件标签的KASAN支持slab、page_alloc和不可执行的vmalloc内存。 + +对于slab,两种软件KASAN模式都支持SLUB和SLAB分配器,而基于硬件标签的 +KASAN只支持SLUB。 + +用法 +---- + +要启用KASAN,请使用以下命令配置内核:: + + CONFIG_KASAN=y + +同时在 ``CONFIG_KASAN_GENERIC`` (启用通用KASAN模式), ``CONFIG_KASAN_SW_TAGS`` +(启用基于硬件标签的KASAN模式),和 ``CONFIG_KASAN_HW_TAGS`` (启用基于硬件标签 +的KASAN模式)之间进行选择。 + +对于软件模式,还可以在 ``CONFIG_KASAN_OUTLINE`` 和 ``CONFIG_KASAN_INLINE`` +之间进行选择。outline和inline是编译器插桩类型。前者产生较小的二进制文件, +而后者快2倍。 + +要将受影响的slab对象的alloc和free堆栈跟踪包含到报告中,请启用 +``CONFIG_STACKTRACE`` 。要包括受影响物理页面的分配和释放堆栈跟踪的话, +请启用 ``CONFIG_PAGE_OWNER`` 并使用 ``page_owner=on`` 进行引导。 + +错误报告 +~~~~~~~~ + +典型的KASAN报告如下所示:: + + ================================================================== + BUG: KASAN: slab-out-of-bounds in kmalloc_oob_right+0xa8/0xbc [test_kasan] + Write of size 1 at addr ffff8801f44ec37b by task insmod/2760 + + CPU: 1 PID: 2760 Comm: insmod Not tainted 4.19.0-rc3+ #698 + Hardware name: QEMU Standard PC (i440FX + PIIX, 1996), BIOS 1.10.2-1 04/01/2014 + Call Trace: + dump_stack+0x94/0xd8 + print_address_description+0x73/0x280 + kasan_report+0x144/0x187 + __asan_report_store1_noabort+0x17/0x20 + kmalloc_oob_right+0xa8/0xbc [test_kasan] + kmalloc_tests_init+0x16/0x700 [test_kasan] + do_one_initcall+0xa5/0x3ae + do_init_module+0x1b6/0x547 + load_module+0x75df/0x8070 + __do_sys_init_module+0x1c6/0x200 + __x64_sys_init_module+0x6e/0xb0 + do_syscall_64+0x9f/0x2c0 + entry_SYSCALL_64_after_hwframe+0x44/0xa9 + RIP: 0033:0x7f96443109da + RSP: 002b:00007ffcf0b51b08 EFLAGS: 00000202 ORIG_RAX: 00000000000000af + RAX: ffffffffffffffda RBX: 000055dc3ee521a0 RCX: 00007f96443109da + RDX: 00007f96445cff88 RSI: 0000000000057a50 RDI: 00007f9644992000 + RBP: 000055dc3ee510b0 R08: 0000000000000003 R09: 0000000000000000 + R10: 00007f964430cd0a R11: 0000000000000202 R12: 00007f96445cff88 + R13: 000055dc3ee51090 R14: 0000000000000000 R15: 0000000000000000 + + Allocated by task 2760: + save_stack+0x43/0xd0 + kasan_kmalloc+0xa7/0xd0 + kmem_cache_alloc_trace+0xe1/0x1b0 + kmalloc_oob_right+0x56/0xbc [test_kasan] + kmalloc_tests_init+0x16/0x700 [test_kasan] + do_one_initcall+0xa5/0x3ae + do_init_module+0x1b6/0x547 + load_module+0x75df/0x8070 + __do_sys_init_module+0x1c6/0x200 + __x64_sys_init_module+0x6e/0xb0 + do_syscall_64+0x9f/0x2c0 + entry_SYSCALL_64_after_hwframe+0x44/0xa9 + + Freed by task 815: + save_stack+0x43/0xd0 + __kasan_slab_free+0x135/0x190 + kasan_slab_free+0xe/0x10 + kfree+0x93/0x1a0 + umh_complete+0x6a/0xa0 + call_usermodehelper_exec_async+0x4c3/0x640 + ret_from_fork+0x35/0x40 + + The buggy address belongs to the object at ffff8801f44ec300 + which belongs to the cache kmalloc-128 of size 128 + The buggy address is located 123 bytes inside of + 128-byte region [ffff8801f44ec300, ffff8801f44ec380) + The buggy address belongs to the page: + page:ffffea0007d13b00 count:1 mapcount:0 mapping:ffff8801f7001640 index:0x0 + flags: 0x200000000000100(slab) + raw: 0200000000000100 ffffea0007d11dc0 0000001a0000001a ffff8801f7001640 + raw: 0000000000000000 0000000080150015 00000001ffffffff 0000000000000000 + page dumped because: kasan: bad access detected + + Memory state around the buggy address: + ffff8801f44ec200: fc fc fc fc fc fc fc fc fb fb fb fb fb fb fb fb + ffff8801f44ec280: fb fb fb fb fb fb fb fb fc fc fc fc fc fc fc fc + >ffff8801f44ec300: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 03 + ^ + ffff8801f44ec380: fc fc fc fc fc fc fc fc fb fb fb fb fb fb fb fb + ffff8801f44ec400: fb fb fb fb fb fb fb fb fc fc fc fc fc fc fc fc + ================================================================== + +报告标题总结了发生的错误类型以及导致该错误的访问类型。紧随其后的是错误访问的 +堆栈跟踪、所访问内存分配位置的堆栈跟踪(对于访问了slab对象的情况)以及对象 +被释放的位置的堆栈跟踪(对于访问已释放内存的问题报告)。接下来是对访问的 +slab对象的描述以及关于访问的内存页的信息。 + +最后,报告展示了访问地址周围的内存状态。在内部,KASAN单独跟踪每个内存颗粒的 +内存状态,根据KASAN模式分为8或16个对齐字节。报告的内存状态部分中的每个数字 +都显示了围绕访问地址的其中一个内存颗粒的状态。 + +对于通用KASAN,每个内存颗粒的大小为8个字节。每个颗粒的状态被编码在一个影子字节 +中。这8个字节可以是可访问的,部分访问的,已释放的或成为Redzone的一部分。KASAN +对每个影子字节使用以下编码:00表示对应内存区域的所有8个字节都可以访问;数字N +(1 <= N <= 7)表示前N个字节可访问,其他(8 - N)个字节不可访问;任何负值都表示 +无法访问整个8字节。KASAN使用不同的负值来区分不同类型的不可访问内存,如redzones +或已释放的内存(参见 mm/kasan/kasan.h)。 + +在上面的报告中,箭头指向影子字节 ``03`` ,表示访问的地址是部分可访问的。 + +对于基于标签的KASAN模式,报告最后的部分显示了访问地址周围的内存标签 +(参考 `实施细则`_ 章节)。 + +请注意,KASAN错误标题(如 ``slab-out-of-bounds`` 或 ``use-after-free`` ) +是尽量接近的:KASAN根据其拥有的有限信息打印出最可能的错误类型。错误的实际类型 +可能会有所不同。 + +通用KASAN还报告两个辅助调用堆栈跟踪。这些堆栈跟踪指向代码中与对象交互但不直接 +出现在错误访问堆栈跟踪中的位置。目前,这包括 call_rcu() 和排队的工作队列。 + +启动参数 +~~~~~~~~ + +KASAN受通用 ``panic_on_warn`` 命令行参数的影响。启用该功能后,KASAN在打印错误 +报告后会引起内核恐慌。 + +默认情况下,KASAN只为第一次无效内存访问打印错误报告。使用 ``kasan_multi_shot`` , +KASAN会针对每个无效访问打印报告。这有效地禁用了KASAN报告的 ``panic_on_warn`` 。 + +另外,独立于 ``panic_on_warn`` , ``kasan.fault=`` 引导参数可以用来控制恐慌和报 +告行为: + +- ``kasan.fault=report`` 或 ``=panic`` 控制是只打印KASAN报告还是同时使内核恐慌 + (默认: ``report`` )。即使启用了 ``kasan_multi_shot`` ,也会发生内核恐慌。 + +基于硬件标签的KASAN模式(请参阅下面有关各种模式的部分)旨在在生产中用作安全缓解 +措施。因此,它支持允许禁用KASAN或控制其功能的附加引导参数。 + +- ``kasan=off`` 或 ``=on`` 控制KASAN是否启用 (默认: ``on`` )。 + +- ``kasan.mode=sync`` 、 ``=async`` 或 ``=asymm`` 控制KASAN是否配置 + 为同步或异步执行模式(默认:``sync`` )。 + 同步模式:当标签检查错误发生时,立即检测到错误访问。 + 异步模式:延迟错误访问检测。当标签检查错误发生时,信息存储在硬件中(在arm64的 + TFSR_EL1寄存器中)。内核会定期检查硬件,并且仅在这些检查期间报告标签错误。 + 非对称模式:读取时同步检测不良访问,写入时异步检测。 + +- ``kasan.vmalloc=off`` 或 ``=on`` 禁用或启用vmalloc分配的标记(默认:``on`` )。 + +- ``kasan.stacktrace=off`` 或 ``=on`` 禁用或启用alloc和free堆栈跟踪收集 + (默认: ``on`` )。 + + +实施细则 +-------- + +通用KASAN +~~~~~~~~~ + +软件KASAN模式使用影子内存来记录每个内存字节是否可以安全访问,并使用编译时工具 +在每次内存访问之前插入影子内存检查。 + +通用KASAN将1/8的内核内存专用于其影子内存(16TB以覆盖x86_64上的128TB),并使用 +具有比例和偏移量的直接映射将内存地址转换为其相应的影子地址。 + +这是将地址转换为其相应影子地址的函数:: + + static inline void *kasan_mem_to_shadow(const void *addr) + { + return (void *)((unsigned long)addr >> KASAN_SHADOW_SCALE_SHIFT) + + KASAN_SHADOW_OFFSET; + } + +在这里 ``KASAN_SHADOW_SCALE_SHIFT = 3`` 。 + +编译时工具用于插入内存访问检查。编译器在每次访问大小为1、2、4、8或16的内存之前 +插入函数调用( ``__asan_load*(addr)`` , ``__asan_store*(addr)``)。这些函数通过 +检查相应的影子内存来检查内存访问是否有效。 + +使用inline插桩,编译器不进行函数调用,而是直接插入代码来检查影子内存。此选项 +显著地增大了内核体积,但与outline插桩内核相比,它提供了x1.1-x2的性能提升。 + +通用KASAN是唯一一种通过隔离延迟重新使用已释放对象的模式 +(参见 mm/kasan/quarantine.c 以了解实现)。 + +基于软件标签的KASAN模式 +~~~~~~~~~~~~~~~~~~~~~~~ + +基于软件标签的KASAN使用软件内存标签方法来检查访问有效性。目前仅针对arm64架构实现。 + +基于软件标签的KASAN使用arm64 CPU的顶部字节忽略(TBI)特性在内核指针的顶部字节中 +存储一个指针标签。它使用影子内存来存储与每个16字节内存单元相关的内存标签(因此, +它将内核内存的1/16专用于影子内存)。 + +在每次内存分配时,基于软件标签的KASAN都会生成一个随机标签,用这个标签标记分配 +的内存,并将相同的标签嵌入到返回的指针中。 + +基于软件标签的KASAN使用编译时工具在每次内存访问之前插入检查。这些检查确保正在 +访问的内存的标签等于用于访问该内存的指针的标签。如果标签不匹配,基于软件标签 +的KASAN会打印错误报告。 + +基于软件标签的KASAN也有两种插桩模式(outline,发出回调来检查内存访问;inline, +执行内联的影子内存检查)。使用outline插桩模式,会从执行访问检查的函数打印错误 +报告。使用inline插桩,编译器会发出 ``brk`` 指令,并使用专用的 ``brk`` 处理程序 +来打印错误报告。 + +基于软件标签的KASAN使用0xFF作为匹配所有指针标签(不检查通过带有0xFF指针标签 +的指针进行的访问)。值0xFE当前保留用于标记已释放的内存区域。 + + +基于硬件标签的KASAN模式 +~~~~~~~~~~~~~~~~~~~~~~~ + +基于硬件标签的KASAN在概念上类似于软件模式,但它是使用硬件内存标签作为支持而 +不是编译器插桩和影子内存。 + +基于硬件标签的KASAN目前仅针对arm64架构实现,并且基于ARMv8.5指令集架构中引入 +的arm64内存标记扩展(MTE)和最高字节忽略(TBI)。 + +特殊的arm64指令用于为每次内存分配指定内存标签。相同的标签被指定给指向这些分配 +的指针。在每次内存访问时,硬件确保正在访问的内存的标签等于用于访问该内存的指针 +的标签。如果标签不匹配,则会生成故障并打印报告。 + +基于硬件标签的KASAN使用0xFF作为匹配所有指针标签(不检查通过带有0xFF指针标签的 +指针进行的访问)。值0xFE当前保留用于标记已释放的内存区域。 + +如果硬件不支持MTE(ARMv8.5之前),则不会启用基于硬件标签的KASAN。在这种情况下, +所有KASAN引导参数都将被忽略。 + +请注意,启用CONFIG_KASAN_HW_TAGS始终会导致启用内核中的TBI。即使提供了 +``kasan.mode=off`` 或硬件不支持MTE(但支持TBI)。 + +基于硬件标签的KASAN只报告第一个发现的错误。之后,MTE标签检查将被禁用。 + +影子内存 +-------- + +本节的内容只适用于软件KASAN模式。 + +内核将内存映射到地址空间的几个不同部分。内核虚拟地址的范围很大:没有足够的真实 +内存来支持内核可以访问的每个地址的真实影子区域。因此,KASAN只为地址空间的某些 +部分映射真实的影子。 + +默认行为 +~~~~~~~~ + +默认情况下,体系结构仅将实际内存映射到用于线性映射的阴影区域(以及可能的其他 +小区域)。对于所有其他区域 —— 例如vmalloc和vmemmap空间 —— 一个只读页面被映射 +到阴影区域上。这个只读的影子页面声明所有内存访问都是允许的。 + +这给模块带来了一个问题:它们不存在于线性映射中,而是存在于专用的模块空间中。 +通过连接模块分配器,KASAN临时映射真实的影子内存以覆盖它们。例如,这允许检测 +对模块全局变量的无效访问。 + +这也造成了与 ``VMAP_STACK`` 的不兼容:如果堆栈位于vmalloc空间中,它将被分配 +只读页面的影子内存,并且内核在尝试为堆栈变量设置影子数据时会出错。 + +CONFIG_KASAN_VMALLOC +~~~~~~~~~~~~~~~~~~~~ + +使用 ``CONFIG_KASAN_VMALLOC`` ,KASAN可以以更大的内存使用为代价覆盖vmalloc +空间。目前,这在arm64、x86、riscv、s390和powerpc上受支持。 + +这通过连接到vmalloc和vmap并动态分配真实的影子内存来支持映射。 + +vmalloc空间中的大多数映射都很小,需要不到一整页的阴影空间。因此,为每个映射 +分配一个完整的影子页面将是一种浪费。此外,为了确保不同的映射使用不同的影子 +页面,映射必须与 ``KASAN_GRANULE_SIZE * PAGE_SIZE`` 对齐。 + +相反,KASAN跨多个映射共享后备空间。当vmalloc空间中的映射使用影子区域的特定 +页面时,它会分配一个后备页面。此页面稍后可以由其他vmalloc映射共享。 + +KASAN连接到vmap基础架构以懒清理未使用的影子内存。 + +为了避免交换映射的困难,KASAN预测覆盖vmalloc空间的阴影区域部分将不会被早期 +的阴影页面覆盖,但是将不会被映射。这将需要更改特定于arch的代码。 + +这允许在x86上支持 ``VMAP_STACK`` ,并且可以简化对没有固定模块区域的架构的支持。 + +对于开发者 +---------- + +忽略访问 +~~~~~~~~ + +软件KASAN模式使用编译器插桩来插入有效性检查。此类检测可能与内核的某些部分 +不兼容,因此需要禁用。 + +内核的其他部分可能会访问已分配对象的元数据。通常,KASAN会检测并报告此类访问, +但在某些情况下(例如,在内存分配器中),这些访问是有效的。 + +对于软件KASAN模式,要禁用特定文件或目录的检测,请将 ``KASAN_SANITIZE`` 添加 +到相应的内核Makefile中: + +- 对于单个文件(例如,main.o):: + + KASAN_SANITIZE_main.o := n + +- 对于一个目录下的所有文件:: + + KASAN_SANITIZE := n + +对于软件KASAN模式,要在每个函数的基础上禁用检测,请使用KASAN特定的 +``__no_sanitize_address`` 函数属性或通用的 ``noinstr`` 。 + +请注意,禁用编译器插桩(基于每个文件或每个函数)会使KASAN忽略在软件KASAN模式 +的代码中直接发生的访问。当访问是间接发生的(通过调用检测函数)或使用没有编译器 +插桩的基于硬件标签的模式时,它没有帮助。 + +对于软件KASAN模式,要在当前任务的一部分内核代码中禁用KASAN报告,请使用 +``kasan_disable_current()``/``kasan_enable_current()`` 部分注释这部分代码。 +这也会禁用通过函数调用发生的间接访问的报告。 + +对于基于标签的KASAN模式,要禁用访问检查,请使用 ``kasan_reset_tag()`` 或 +``page_kasan_tag_reset()`` 。请注意,通过 ``page_kasan_tag_reset()`` +临时禁用访问检查需要通过 ``page_kasan_tag`` / ``page_kasan_tag_set`` 保 +存和恢复每页KASAN标签。 + +测试 +~~~~ + +有一些KASAN测试可以验证KASAN是否正常工作并可以检测某些类型的内存损坏。 +测试由两部分组成: + +1. 与KUnit测试框架集成的测试。使用 ``CONFIG_KASAN_KUNIT_TEST`` 启用。 +这些测试可以通过几种不同的方式自动运行和部分验证;请参阅下面的说明。 + +2. 与KUnit不兼容的测试。使用 ``CONFIG_KASAN_MODULE_TEST`` 启用并且只能作为模块 +运行。这些测试只能通过加载内核模块并检查内核日志以获取KASAN报告来手动验证。 + +如果检测到错误,每个KUnit兼容的KASAN测试都会打印多个KASAN报告之一,然后测试打印 +其编号和状态。 + +当测试通过:: + + ok 28 - kmalloc_double_kzfree + +当由于 ``kmalloc`` 失败而导致测试失败时:: + + # kmalloc_large_oob_right: ASSERTION FAILED at lib/test_kasan.c:163 + Expected ptr is not null, but is + not ok 4 - kmalloc_large_oob_right + +当由于缺少KASAN报告而导致测试失败时:: + + # kmalloc_double_kzfree: EXPECTATION FAILED at lib/test_kasan.c:974 + KASAN failure expected in "kfree_sensitive(ptr)", but none occurred + not ok 44 - kmalloc_double_kzfree + + +最后打印所有KASAN测试的累积状态。成功:: + + ok 1 - kasan + +或者,如果其中一项测试失败:: + + not ok 1 - kasan + +有几种方法可以运行与KUnit兼容的KASAN测试。 + +1. 可加载模块 + + 启用 ``CONFIG_KUNIT`` 后,KASAN-KUnit测试可以构建为可加载模块,并通过使用 + ``insmod`` 或 ``modprobe`` 加载 ``test_kasan.ko`` 来运行。 + +2. 内置 + + 通过内置 ``CONFIG_KUNIT`` ,也可以内置KASAN-KUnit测试。在这种情况下, + 测试将在启动时作为后期初始化调用运行。 + +3. 使用kunit_tool + + 通过内置 ``CONFIG_KUNIT`` 和 ``CONFIG_KASAN_KUNIT_TEST`` ,还可以使用 + ``kunit_tool`` 以更易读的方式查看KUnit测试结果。这不会打印通过测试 + 的KASAN报告。有关 ``kunit_tool`` 更多最新信息,请参阅 + `KUnit文档 <https://www.kernel.org/doc/html/latest/dev-tools/kunit/index.html>`_ 。 + +.. _KUnit: https://www.kernel.org/doc/html/latest/dev-tools/kunit/index.html diff --git a/Documentation/translations/zh_CN/dev-tools/sparse.rst b/Documentation/translations/zh_CN/dev-tools/sparse.rst new file mode 100644 index 000000000..0664c634b --- /dev/null +++ b/Documentation/translations/zh_CN/dev-tools/sparse.rst @@ -0,0 +1,110 @@ +Copyright 2004 Linus Torvalds +Copyright 2004 Pavel Machek <pavel@ucw.cz> +Copyright 2006 Bob Copeland <me@bobcopeland.com> + +.. include:: ../disclaimer-zh_CN.rst + +:Original: Documentation/dev-tools/sparse.rst + +:翻译: + + Li Yang <leoyang.li@nxp.com> + +:校译: + + 司延腾 Yanteng Si <siyanteng@loongson.cn> + +.. _cn_sparse: + +Sparse +====== + +Sparse是一个C程序的语义检查器;它可以用来发现内核代码的一些潜在问题。 关 +于sparse的概述,请参见https://lwn.net/Articles/689907/;本文档包含 +一些针对内核的sparse信息。 +关于sparse的更多信息,主要是关于它的内部结构,可以在它的官方网页上找到: +https://sparse.docs.kernel.org。 + +使用 sparse 工具做类型检查 +~~~~~~~~~~~~~~~~~~~~~~~~~~ + +"__bitwise" 是一种类型属性,所以你应该这样使用它:: + + typedef int __bitwise pm_request_t; + + enum pm_request { + PM_SUSPEND = (__force pm_request_t) 1, + PM_RESUME = (__force pm_request_t) 2 + }; + +这样会使 PM_SUSPEND 和 PM_RESUME 成为位方式(bitwise)整数(使用"__force" +是因为 sparse 会抱怨改变位方式的类型转换,但是这里我们确实需要强制进行转 +换)。而且因为所有枚举值都使用了相同的类型,这里的"enum pm_request"也将 +会使用那个类型做为底层实现。 + +而且使用 gcc 编译的时候,所有的 __bitwise/__force 都会消失,最后在 gcc +看来它们只不过是普通的整数。 + +坦白来说,你并不需要使用枚举类型。上面那些实际都可以浓缩成一个特殊的"int +__bitwise"类型。 + +所以更简单的办法只要这样做:: + + typedef int __bitwise pm_request_t; + + #define PM_SUSPEND ((__force pm_request_t) 1) + #define PM_RESUME ((__force pm_request_t) 2) + +现在你就有了严格的类型检查所需要的所有基础架构。 + +一个小提醒:常数整数"0"是特殊的。你可以直接把常数零当作位方式整数使用而 +不用担心 sparse 会抱怨。这是因为"bitwise"(恰如其名)是用来确保不同位方 +式类型不会被弄混(小尾模式,大尾模式,cpu尾模式,或者其他),对他们来说 +常数"0"确实 **是** 特殊的。 + +使用sparse进行锁检查 +-------------------- + +下面的宏对于 gcc 来说是未定义的,在 sparse 运行时定义,以使用sparse的“上下文” +跟踪功能,应用于锁定。 这些注释告诉 sparse 什么时候有锁,以及注释的函数的进入和 +退出。 + +__must_hold - 指定的锁在函数进入和退出时被持有。 + +__acquires - 指定的锁在函数退出时被持有,但在进入时不被持有。 + +__releases - 指定的锁在函数进入时被持有,但在退出时不被持有。 + +如果函数在不持有锁的情况下进入和退出,在函数内部以平衡的方式获取和释放锁,则不 +需要注释。 +上面的三个注释是针对sparse否则会报告上下文不平衡的情况。 + +获取 sparse 工具 +~~~~~~~~~~~~~~~~ + +你可以从 Sparse 的主页获取最新的发布版本: + + https://www.kernel.org/pub/software/devel/sparse/dist/ + +或者,你也可以使用 git 克隆最新的 sparse 开发版本: + + git://git.kernel.org/pub/scm/devel/sparse/sparse.git + +一旦你下载了源码,只要以普通用户身份运行: + + make + make install + +如果是标准的用户,它将会被自动安装到你的~/bin目录下。 + +使用 sparse 工具 +~~~~~~~~~~~~~~~~ + +用"make C=1"命令来编译内核,会对所有重新编译的 C 文件使用 sparse 工具。 +或者使用"make C=2"命令,无论文件是否被重新编译都会对其使用 sparse 工具。 +如果你已经编译了内核,用后一种方式可以很快地检查整个源码树。 + +make 的可选变量 CHECKFLAGS 可以用来向 sparse 工具传递参数。编译系统会自 +动向 sparse 工具传递 -Wbitwise 参数。 + +注意sparse定义了__CHECKER__预处理器符号。
\ No newline at end of file diff --git a/Documentation/translations/zh_CN/dev-tools/testing-overview.rst b/Documentation/translations/zh_CN/dev-tools/testing-overview.rst new file mode 100644 index 000000000..d6f2c65ed --- /dev/null +++ b/Documentation/translations/zh_CN/dev-tools/testing-overview.rst @@ -0,0 +1,134 @@ +.. SPDX-License-Identifier: GPL-2.0 + +.. include:: ../disclaimer-zh_CN.rst + +:Original: Documentation/dev-tools/testing-overview.rst +:Translator: 胡皓文 Hu Haowen <src.res@email.cn> + +============ +内核测试指南 +============ + +有许多不同的工具可以用于测试Linux内核,因此了解什么时候使用它们可能 +很困难。本文档粗略概述了它们之间的区别,并阐释了它们是怎样糅合在一起 +的。 + +编写和运行测试 +============== + +大多数内核测试都是用kselftest或KUnit框架之一编写的。它们都让运行测试 +更加简化,并为编写新测试提供帮助。 + +如果你想验证内核的行为——尤其是内核的特定部分——那你就要使用kUnit或 +kselftest。 + +KUnit和kselftest的区别 +---------------------- + +.. note:: + 由于本文段中部分术语尚无较好的对应中文释义,可能导致与原文含义 + 存在些许差异,因此建议读者结合原文 + (Documentation/dev-tools/testing-overview.rst)辅助阅读。 + 如对部分翻译有异议或有更好的翻译意见,欢迎联系译者进行修订。 + +KUnit(Documentation/dev-tools/kunit/index.rst)是用于“白箱”测 +试的一个完整的内核内部系统:因为测试代码是内核的一部分,所以它能够访 +问用户空间不能访问到的内部结构和功能。 + +因此,KUnit测试最好针对内核中较小的、自包含的部分,以便能够独立地测 +试。“单元”测试的概念亦是如此。 + +比如,一个KUnit测试可能测试一个单独的内核功能(甚至通过一个函数测试 +一个单一的代码路径,例如一个错误处理案例),而不是整个地测试一个特性。 + +这也使得KUnit测试构建和运行非常地快,从而能够作为开发流程的一部分被 +频繁地运行。 + +有关更详细的介绍,请参阅KUnit测试代码风格指南 +Documentation/dev-tools/kunit/style.rst + +kselftest(Documentation/dev-tools/kselftest.rst),相对来说,大量用 +于用户空间,并且通常测试用户空间的脚本或程序。 + +这使得编写复杂的测试,或者需要操作更多全局系统状态的测试更加容易(诸 +如生成进程之类)。然而,从kselftest直接调用内核函数是不行的。这也就 +意味着只有通过某种方式(如系统调用、驱动设备、文件系统等)导出到了用 +户空间的内核功能才能使用kselftest来测试。为此,有些测试包含了一个伴 +生的内核模块用于导出更多的信息和功能。不过,对于基本上或者完全在内核 +中运行的测试,KUnit可能是更佳工具。 + +kselftest也因此非常适合于全部功能的测试,因为这些功能会将接口暴露到 +用户空间,从而能够被测试,而不是展现实现细节。“system”测试和 +“end-to-end”测试亦是如此。 + +比如,一个新的系统调用应该伴随有新的kselftest测试。 + +代码覆盖率工具 +============== + +支持两种不同代码之间的覆盖率测量工具。它们可以用来验证一项测试执行的 +确切函数或代码行。这有助于决定内核被测试了多少,或用来查找合适的测试 +中没有覆盖到的极端情况。 + +Documentation/translations/zh_CN/dev-tools/gcov.rst 是GCC的覆盖率测试 +工具,能用于获取内核的全局或每个模块的覆盖率。与KCOV不同的是,这个工具 +不记录每个任务的覆盖率。覆盖率数据可以通过debugfs读取,并通过常规的 +gcov工具进行解释。 + +Documentation/dev-tools/kcov.rst 是能够构建在内核之中,用于在每个任务 +的层面捕捉覆盖率的一个功能。因此,它对于模糊测试和关于代码执行期间信 +息的其它情况非常有用,比如在一个单一系统调用里使用它就很有用。 + +动态分析工具 +============ + +内核也支持许多动态分析工具,用以检测正在运行的内核中出现的多种类型的 +问题。这些工具通常每个去寻找一类不同的缺陷,比如非法内存访问,数据竞 +争等并发问题,或整型溢出等其他未定义行为。 + +如下所示: + +* kmemleak检测可能的内存泄漏。参阅 + Documentation/dev-tools/kmemleak.rst +* KASAN检测非法内存访问,如数组越界和释放后重用(UAF)。参阅 + Documentation/dev-tools/kasan.rst +* UBSAN检测C标准中未定义的行为,如整型溢出。参阅 + Documentation/dev-tools/ubsan.rst +* KCSAN检测数据竞争。参阅 Documentation/dev-tools/kcsan.rst +* KFENCE是一个低开销的内存问题检测器,比KASAN更快且能被用于批量构建。 + 参阅 Documentation/dev-tools/kfence.rst +* lockdep是一个锁定正确性检测器。参阅 + Documentation/locking/lockdep-design.rst +* 除此以外,在内核中还有一些其它的调试工具,大多数能在 + lib/Kconfig.debug 中找到。 + +这些工具倾向于对内核进行整体测试,并且不像kselftest和KUnit一样“传递”。 +它们可以通过在启用这些工具时运行内核测试以与kselftest或KUnit结合起来: +之后你就能确保这些错误在测试过程中都不会发生了。 + +一些工具与KUnit和kselftest集成,并且在检测到问题时会自动打断测试。 + +静态分析工具 +============ + +除了测试运行中的内核,我们还可以使用**静态分析**工具直接分析内核的源代 +码(**在编译时**)。内核中常用的工具允许人们检查整个源代码树或其中的特 +定文件。它们使得在开发过程中更容易发现和修复问题。 + + Sparse可以通过执行类型检查、锁检查、值范围检查来帮助测试内核,此外还 + 可以在检查代码时报告各种错误和警告。关于如何使用它的细节,请参阅 + Documentation/translations/zh_CN/dev-tools/sparse.rst。 + + Smatch扩展了Sparse,并提供了对编程逻辑错误的额外检查,如开关语句中 + 缺少断点,错误检查中未使用的返回值,忘记在错误路径的返回中设置错误代 + 码等。Smatch也有针对更严重问题的测试,如整数溢出、空指针解除引用和内 + 存泄漏。见项目页面http://smatch.sourceforge.net/。 + + Coccinelle是我们可以使用的另一个静态分析器。Coccinelle经常被用来 + 帮助源代码的重构和并行演化,但它也可以帮助避免常见代码模式中出现的某 + 些错误。可用的测试类型包括API测试、内核迭代器的正确使用测试、自由操 + 作的合理性检查、锁定行为的分析,以及已知的有助于保持内核使用一致性的 + 进一步测试。详情请见Documentation/dev-tools/coccinelle.rst。 + + 不过要注意的是,静态分析工具存在**假阳性**的问题。在试图修复错误和警 + 告之前,需要仔细评估它们。 diff --git a/Documentation/translations/zh_CN/devicetree/changesets.rst b/Documentation/translations/zh_CN/devicetree/changesets.rst new file mode 100644 index 000000000..3df1b03c5 --- /dev/null +++ b/Documentation/translations/zh_CN/devicetree/changesets.rst @@ -0,0 +1,37 @@ +.. SPDX-License-Identifier: GPL-2.0 +.. include:: ../disclaimer-zh_CN.rst + +:Original: Documentation/devicetree/changesets.rst + +:翻译: + + 司延腾 Yanteng Si <siyanteng@loongson.cn> + +:校译: + + +============ +设备树变更集 +============ + +设备树变更集是一种方法,它允许人们以这样一种方式在实时树中使用变化,即要么使用全部的 +变化,要么不使用。如果在使用变更集的过程中发生错误,那么树将被回滚到之前的状态。一个 +变更集也可以在使用后被删除。 + +当一个变更集被使用时,所有的改变在发出OF_RECONFIG通知器之前被一次性使用到树上。这是 +为了让接收者在收到通知时看到一个完整的、一致的树的状态。 + +一个变化集的顺序如下。 + +1. of_changeset_init() - 初始化一个变更集。 + +2. 一些DT树变化的调用,of_changeset_attach_node(), of_changeset_detach_node(), + of_changeset_add_property(), of_changeset_remove_property, + of_changeset_update_property()来准备一组变更。此时不会对活动树做任何变更。所有 + 的变更操作都记录在of_changeset的 `entries` 列表中。 + +3. of_changeset_apply() - 将变更使用到树上。要么整个变更集被使用,要么如果有错误, + 树会被恢复到之前的状态。核心通过锁确保正确的顺序。如果需要的话,可以使用一个解锁的 + __of_changeset_apply版本。 + +如果一个成功使用的变更集需要被删除,可以用of_changeset_revert()来完成。 diff --git a/Documentation/translations/zh_CN/devicetree/dynamic-resolution-notes.rst b/Documentation/translations/zh_CN/devicetree/dynamic-resolution-notes.rst new file mode 100644 index 000000000..6dfd946d7 --- /dev/null +++ b/Documentation/translations/zh_CN/devicetree/dynamic-resolution-notes.rst @@ -0,0 +1,31 @@ +.. SPDX-License-Identifier: GPL-2.0 +.. include:: ../disclaimer-zh_CN.rst + +:Original: Documentation/devicetree/dynamic-resolution-notes.rst + +:翻译: + + 司延腾 Yanteng Si <siyanteng@loongson.cn> + +:校译: + +======================== +Devicetree动态解析器说明 +======================== + +本文描述了内核内DeviceTree解析器的实现,它位于drivers/of/resolver.c中。 + +解析器如何工作? +---------------- + +解析器被赋予一个任意的树作为输入,该树用适当的dtc选项编译,并有一个/plugin/标签。这就产 +生了适当的__fixups__和__local_fixups__节点。 + +解析器依次通过以下步骤工作: + +1. 从实时树中获取最大的设备树phandle值 + 1. +2. 调整树的所有本地 phandles,以解决这个量。 +3. 使用 __local__fixups__ 节点信息以相同的量调整所有本地引用。 +4. 对于__fixups__节点中的每个属性,找到它在实时树中引用的节点。这是用来标记该节点的标签。 +5. 检索fixup的目标的phandle。 +6. 对于属性中的每个fixup,找到节点:属性:偏移的位置,并用phandle值替换它。 diff --git a/Documentation/translations/zh_CN/devicetree/index.rst b/Documentation/translations/zh_CN/devicetree/index.rst new file mode 100644 index 000000000..7451dbfdd --- /dev/null +++ b/Documentation/translations/zh_CN/devicetree/index.rst @@ -0,0 +1,45 @@ +.. SPDX-License-Identifier: GPL-2.0 +.. include:: ../disclaimer-zh_CN.rst + +:Original: Documentation/devicetree/index.rst + +:翻译: + + 司延腾 Yanteng Si <siyanteng@loongson.cn> + +:校译: + + +============================= +Open Firmware 和 Devicetree +============================= + +该文档是整个设备树文档的总目录,标题中多是业内默认的术语,初见就翻译成中文, +晦涩难懂,因此尽量保留,后面翻译其子文档时,可能会根据语境,灵活地翻译为中文。 + +内核Devicetree的使用 +======================= +.. toctree:: + :maxdepth: 1 + + usage-model + of_unittest + kernel-api + +Devicetree Overlays +=================== +.. toctree:: + :maxdepth: 1 + + changesets + dynamic-resolution-notes + overlay-notes + +Devicetree Bindings +=================== +.. toctree:: + :maxdepth: 1 + +Todolist: + +* bindings/index diff --git a/Documentation/translations/zh_CN/devicetree/kernel-api.rst b/Documentation/translations/zh_CN/devicetree/kernel-api.rst new file mode 100644 index 000000000..2fb729368 --- /dev/null +++ b/Documentation/translations/zh_CN/devicetree/kernel-api.rst @@ -0,0 +1,58 @@ +.. SPDX-License-Identifier: GPL-2.0 +.. include:: ../disclaimer-zh_CN.rst + +:Original: Documentation/devicetree/kernel-api.rst + +:翻译: + + 司延腾 Yanteng Si <siyanteng@loongson.cn> + +:校译: + + +================= +内核中的设备树API +================= + +核心函数 +-------- + +该API在以下内核代码中: + +drivers/of/base.c + +include/linux/of.h + +drivers/of/property.c + +include/linux/of_graph.h + +drivers/of/address.c + +drivers/of/irq.c + +drivers/of/fdt.c + +驱动模型函数 +------------ + +该API在以下内核代码中: + +include/linux/of_device.h + +drivers/of/device.c + +include/linux/of_platform.h + +drivers/of/platform.c + +覆盖和动态DT函数 +---------------- + +该API在以下内核代码中: + +drivers/of/resolver.c + +drivers/of/dynamic.c + +drivers/of/overlay.c diff --git a/Documentation/translations/zh_CN/devicetree/of_unittest.rst b/Documentation/translations/zh_CN/devicetree/of_unittest.rst new file mode 100644 index 000000000..11eb08ca8 --- /dev/null +++ b/Documentation/translations/zh_CN/devicetree/of_unittest.rst @@ -0,0 +1,189 @@ +.. SPDX-License-Identifier: GPL-2.0 +.. include:: ../disclaimer-zh_CN.rst + +:Original: Documentation/devicetree/of_unittest.rst + +:翻译: + + 司延腾 Yanteng Si <siyanteng@loongson.cn> + +:校译: + +================================= +Open Firmware Devicetree 单元测试 +================================= + +作者: Gaurav Minocha <gaurav.minocha.os@gmail.com> + +1. 概述 +======= + +本文档解释了执行 OF 单元测试所需的测试数据是如何动态地附加到实时树上的,与机器的架构无关。 + +建议在继续读下去之前,先阅读以下文件。 + +(1) Documentation/devicetree/usage-model.rst +(2) http://www.devicetree.org/Device_Tree_Usage + +OF Selftest被设计用来测试提供给设备驱动开发者的接口(include/linux/of.h),以从未扁平 +化的设备树数据结构中获取设备信息等。这个接口被大多数设备驱动在各种使用情况下使用。 + + +2. 测试数据 +=========== + +设备树源文件(drivers/of/unittest-data/testcases.dts)包含执行drivers/of/unittest.c +中自动化单元测试所需的测试数据。目前,以下设备树源包含文件(.dtsi)被包含在testcases.dt中:: + + drivers/of/unittest-data/tests-interrupts.dtsi + drivers/of/unittest-data/tests-platform.dtsi + drivers/of/unittest-data/tests-phandle.dtsi + drivers/of/unittest-data/tests-match.dtsi + +当内核在启用OF_SELFTEST的情况下被构建时,那么下面的make规则:: + + $(obj)/%.dtb: $(src)/%.dts FORCE + $(call if_changed_dep, dtc) + +用于将DT源文件(testcases.dts)编译成二进制blob(testcases.dtb),也被称为扁平化的DT。 + +之后,使用以下规则将上述二进制blob包装成一个汇编文件(testcases.dtb.S):: + + $(obj)/%.dtb.S: $(obj)/%.dtb + $(call cmd, dt_S_dtb) + +汇编文件被编译成一个对象文件(testcases.dtb.o),并被链接到内核镜像中。 + + +2.1. 添加测试数据 +----------------- + +未扁平化的设备树结构体: + +未扁平化的设备树由连接的设备节点组成,其树状结构形式如下所述:: + + // following struct members are used to construct the tree + struct device_node { + ... + struct device_node *parent; + struct device_node *child; + struct device_node *sibling; + ... + }; + +图1描述了一个机器的未扁平化设备树的通用结构,只考虑了子节点和同级指针。存在另一个指针, +``*parent`` ,用于反向遍历该树。因此,在一个特定的层次上,子节点和所有的兄弟姐妹节点将 +有一个指向共同节点的父指针(例如,child1、sibling2、sibling3、sibling4的父指针指向 +根节点):: + + root ('/') + | + child1 -> sibling2 -> sibling3 -> sibling4 -> null + | | | | + | | | null + | | | + | | child31 -> sibling32 -> null + | | | | + | | null null + | | + | child21 -> sibling22 -> sibling23 -> null + | | | | + | null null null + | + child11 -> sibling12 -> sibling13 -> sibling14 -> null + | | | | + | | | null + | | | + null null child131 -> null + | + null + +Figure 1: 未扁平化的设备树的通用结构 + + +在执行OF单元测试之前,需要将测试数据附加到机器的设备树上(如果存在)。因此,当调用 +selftest_data_add()时,首先会读取通过以下内核符号链接到内核镜像中的扁平化设备树 +数据:: + + __dtb_testcases_begin - address marking the start of test data blob + __dtb_testcases_end - address marking the end of test data blob + +其次,它调用of_fdt_unflatten_tree()来解除扁平化的blob。最后,如果机器的设备树 +(即实时树)是存在的,那么它将未扁平化的测试数据树附加到实时树上,否则它将自己作为 +实时设备树附加。 + +attach_node_and_children()使用of_attach_node()将节点附加到实时树上,如下所 +述。为了解释这一点,图2中描述的测试数据树被附加到图1中描述的实时树上:: + + root ('/') + | + testcase-data + | + test-child0 -> test-sibling1 -> test-sibling2 -> test-sibling3 -> null + | | | | + test-child01 null null null + + +Figure 2: 将测试数据树附在实时树上的例子。 + +根据上面的方案,实时树已经存在,所以不需要附加根('/')节点。所有其他节点都是通过在 +每个节点上调用of_attach_node()来附加的。 + +在函数of_attach_node()中,新的节点被附在实时树中给定的父节点的子节点上。但是,如 +果父节点已经有了一个孩子,那么新节点就会取代当前的孩子,并将其变成其兄弟姐妹。因此, +当测试案例的数据节点被连接到上面的实时树(图1)时,最终的结构如图3所示:: + + root ('/') + | + testcase-data -> child1 -> sibling2 -> sibling3 -> sibling4 -> null + | | | | | + (...) | | | null + | | child31 -> sibling32 -> null + | | | | + | | null null + | | + | child21 -> sibling22 -> sibling23 -> null + | | | | + | null null null + | + child11 -> sibling12 -> sibling13 -> sibling14 -> null + | | | | + null null | null + | + child131 -> null + | + null + ----------------------------------------------------------------------- + + root ('/') + | + testcase-data -> child1 -> sibling2 -> sibling3 -> sibling4 -> null + | | | | | + | (...) (...) (...) null + | + test-sibling3 -> test-sibling2 -> test-sibling1 -> test-child0 -> null + | | | | + null null null test-child01 + + +Figure 3: 附加测试案例数据后的实时设备树结构。 + + +聪明的读者会注意到,与先前的结构相比,test-child0节点成为最后一个兄弟姐妹(图2)。 +在连接了第一个test-child0节点之后,又连接了test-sibling1节点,该节点推动子节点 +(即test-child0)成为兄弟姐妹,并使自己成为子节点,如上所述。 + +如果发现一个重复的节点(即如果一个具有相同full_name属性的节点已经存在于实时树中), +那么该节点不会被附加,而是通过调用函数update_node_properties()将其属性更新到活 +树的节点中。 + + +2.2. 删除测试数据 +----------------- + +一旦测试用例执行完,selftest_data_remove被调用,以移除最初连接的设备节点(首先是 +叶子节点被分离,然后向上移动父节点被移除,最后是整个树)。selftest_data_remove() +调用detach_node_and_children(),使用of_detach_node()将节点从实时设备树上分离。 + +为了分离一个节点,of_detach_node()要么将给定节点的父节点的子节点指针更新为其同级节 +点,要么根据情况将前一个同级节点附在给定节点的同级节点上。就这样吧。 :) diff --git a/Documentation/translations/zh_CN/devicetree/overlay-notes.rst b/Documentation/translations/zh_CN/devicetree/overlay-notes.rst new file mode 100644 index 000000000..43e3c0bc5 --- /dev/null +++ b/Documentation/translations/zh_CN/devicetree/overlay-notes.rst @@ -0,0 +1,140 @@ +.. SPDX-License-Identifier: GPL-2.0 +.. include:: ../disclaimer-zh_CN.rst + +:Original: Documentation/devicetree/overlay-notes.rst + +:翻译: + + 司延腾 Yanteng Si <siyanteng@loongson.cn> + +:校译: + +============== +设备树覆盖说明 +============== + +本文档描述了drivers/of/overlay.c中的内核内设备树覆盖功能的实现,是 +Documentation/devicetree/dynamic-resolution-notes.rst[1]的配套文档。 + +覆盖如何工作 +------------ + +设备树覆盖的目的是修改内核的实时树,并使修改以反映变化的方式影响内核的状态。 +由于内核主要处理的是设备,任何新的设备节点如果导致一个活动的设备,就应该创建它, +而如果设备节点被禁用或被全部删除,受影响的设备应该被取消注册。 + +让我们举个例子,我们有一个foo板,它的基本树形图如下:: + + ---- foo.dts --------------------------------------------------------------- + /* FOO平台 */ + /dts-v1/; + / { + compatible = "corp,foo"; + + /* 共享的资源 */ + res: res { + }; + + /* 芯片上的外围设备 */ + ocp: ocp { + /* 总是被实例化的外围设备 */ + peripheral1 { ... }; + }; + }; + ---- foo.dts --------------------------------------------------------------- + +覆盖bar.dts, +:: + + ---- bar.dts - 按标签覆盖目标位置 ---------------------------- + /dts-v1/; + /插件/; + &ocp { + /* bar外围 */ + bar { + compatible = "corp,bar"; + ... /* 各种属性和子节点 */ + }; + }; + ---- bar.dts --------------------------------------------------------------- + +当加载(并按照[1]中描述的方式解决)时,应该产生foo+bar.dts:: + + ---- foo+bar.dts ----------------------------------------------------------- + /* FOO平台 + bar外围 */ + / { + compatible = "corp,foo"; + + /* 共享资源 */ + res: res { + }; + + /* 芯片上的外围设备 */ + ocp: ocp { + /* 总是被实例化的外围设备 */ + peripheral1 { ... }; + + /* bar外围 */ + bar { + compatible = "corp,bar"; + ... /* 各种属性和子节点 */ + }; + }; + }; + ---- foo+bar.dts ----------------------------------------------------------- + +作为覆盖的结果,已经创建了一个新的设备节点(bar),因此将注册一个bar平台设备, +如果加载了匹配的设备驱动程序,将按预期创建设备。 + +如果基础DT不是用-@选项编译的,那么“&ocp”标签将不能用于将覆盖节点解析到基础 +DT中的适当位置。在这种情况下,可以提供目标路径。通过标签的目标位置的语法是比 +较好的,因为不管标签在DT中出现在哪里,覆盖都可以被应用到任何包含标签的基础DT上。 + +上面的bar.dts例子被修改为使用目标路径语法,即为:: + + ---- bar.dts - 通过明确的路径覆盖目标位置 -------------------- + /dts-v1/; + /插件/; + &{/ocp} { + /* bar外围 */ + bar { + compatible = "corp,bar"; + ... /* 各种外围设备和子节点 */ + } + }; + ---- bar.dts --------------------------------------------------------------- + + +内核中关于覆盖的API +------------------- + +该API相当容易使用。 + +1) 调用of_overlay_fdt_apply()来创建和应用一个覆盖的变更集。返回值是一个 + 错误或一个识别这个覆盖的cookie。 + +2) 调用of_overlay_remove()来删除和清理先前通过调用of_overlay_fdt_apply() + 而创建的覆盖变更集。不允许删除一个被另一个覆盖的覆盖变化集。 + +最后,如果你需要一次性删除所有的覆盖,只需调用of_overlay_remove_all(), +它将以正确的顺序删除每一个覆盖。 + +你可以选择注册在覆盖操作中被调用的通知器。详见 +of_overlay_notifier_register/unregister和enum of_overlay_notify_action。 + +OF_OVERLAY_PRE_APPLY、OF_OVERLAY_POST_APPLY或OF_OVERLAY_PRE_REMOVE +的通知器回调可以存储指向覆盖层中的设备树节点或其内容的指针,但这些指针不能持 +续到OF_OVERLAY_POST_REMOVE的通知器回调。在OF_OVERLAY_POST_REMOVE通 +知器被调用后,包含覆盖层的内存将被kfree()ed。请注意,即使OF_OVERLAY_POST_REMOVE +的通知器返回错误,内存也会被kfree()ed。 + +drivers/of/dynamic.c中的变更集通知器是第二种类型的通知器,可以通过应用或移除 +覆盖层来触发。这些通知器不允许在覆盖层或其内容中存储指向设备树节点的指针。当包含 +覆盖层的内存因移除覆盖层而被释放时,覆盖层代码并不能防止这类指针仍然有效。 + +任何其他保留指向覆盖层节点或数据的指针的代码都被认为是一个错误,因为在移除覆盖层 +后,该指针将指向已释放的内存。 + +覆盖层的用户必须特别注意系统上发生的整体操作,以确保其他内核代码不保留任何指向覆 +盖层节点或数据的指针。任何无意中使用这种指针的例子是,如果一个驱动或子系统模块在 +应用了覆盖后被加载,并且该驱动或子系统扫描了整个设备树或其大部分,包括覆盖节点。 diff --git a/Documentation/translations/zh_CN/devicetree/usage-model.rst b/Documentation/translations/zh_CN/devicetree/usage-model.rst new file mode 100644 index 000000000..c6aee82c7 --- /dev/null +++ b/Documentation/translations/zh_CN/devicetree/usage-model.rst @@ -0,0 +1,330 @@ +.. SPDX-License-Identifier: GPL-2.0 +.. include:: ../disclaimer-zh_CN.rst + +:Original: Documentation/devicetree/usage-model.rst + +:翻译: + + 司延腾 Yanteng Si <siyanteng@loongson.cn> + +:校译: + + +=================== +Linux 和 Devicetree +=================== + +Linux对设备树数据的使用模型 + +:作者: Grant Likely <grant.likely@secretlab.ca> + +这篇文章描述了Linux如何使用设备树。关于设备树数据格式的概述可以在 +devicetree.org\ [1]_ 的设备树使用页面上找到。 + +.. [1] https://www.devicetree.org/specifications/ + +"Open Firmware Device Tree",或简称为Devicetree(DT),是一种用于描述硬 +件的数据结构和语言。更确切地说,它是一种操作系统可读的硬件描述,这样操作系统就不 +需要对机器的细节进行硬编码。 + +从结构上看,DT是一棵树,或者说是带有命名节点的无环图,节点可以有任意数量的命名 +属性来封装任意的数据。还存在一种机制,可以在自然的树状结构之外创建从一个节点到 +另一个节点的任意链接。 + +从概念上讲,一套通用的使用惯例,称为 "bindings"(后文译为绑定),被定义为数据 +应该如何出现在树中,以描述典型的硬件特性,包括数据总线、中断线、GPIO连接和外围 +设备。 + +尽可能使用现有的绑定来描述硬件,以最大限度地利用现有的支持代码,但由于属性和节 +点名称是简单的文本字符串,通过定义新的节点和属性来扩展现有的绑定或创建新的绑定 +很容易。然而,要警惕的是,在创建一个新的绑定之前,最好先对已经存在的东西做一些 +功课。目前有两种不同的、不兼容的i2c总线的绑定,这是因为在创建新的绑定时没有事先 +调查i2c设备在现有系统中是如何被枚举的。 + +1. 历史 +------- +DT最初是由Open Firmware创建的,作为将数据从Open Firmware传递给客户程序 +(如传递给操作系统)的通信方法的一部分。操作系统使用设备树在运行时探测硬件的拓 +扑结构,从而在没有硬编码信息的情况下支持大多数可用的硬件(假设所有设备的驱动程 +序都可用)。 + +由于Open Firmware通常在PowerPC和SPARC平台上使用,长期以来,对这些架构的 +Linux支持一直使用设备树。 + +2005年,当PowerPC Linux开始大规模清理并合并32位和64位支持时,决定在所有 +Powerpc平台上要求DT支持,无论它们是否使用Open Firmware。为了做到这一点, +我们创建了一个叫做扁平化设备树(FDT)的DT表示法,它可以作为一个二进制的blob +传递给内核,而不需要真正的Open Firmware实现。U-Boot、kexec和其他引导程序 +被修改,以支持传递设备树二进制(dtb)和在引导时修改dtb。DT也被添加到PowerPC +引导包装器(arch/powerpc/boot/\*)中,这样dtb就可以被包裹在内核镜像中,以 +支持引导现有的非DT察觉的固件。 + +一段时间后,FDT基础架构被普及到了所有的架构中。在写这篇文章的时候,6个主线架 +构(arm、microblaze、mips、powerpc、sparc和x86)和1个非主线架构(ios) +有某种程度的DT支持。 + +1. 数据模型 +----------- +如果你还没有读过设备树用法\ [1]_页,那么现在就去读吧。没关系,我等着.... + +2.1 高层次视角 +-------------- +最重要的是要明白,DT只是一个描述硬件的数据结构。它没有什么神奇之处,也不会神 +奇地让所有的硬件配置问题消失。它所做的是提供一种语言,将硬件配置与Linux内核 +(或任何其他操作系统)中的板卡和设备驱动支持解耦。使用它可以使板卡和设备支持 +变成数据驱动;根据传递到内核的数据做出设置决定,而不是根据每台机器的硬编码选 +择。 + +理想情况下,数据驱动的平台设置应该导致更少的代码重复,并使其更容易用一个内核 +镜像支持各种硬件。 + +Linux使用DT数据有三个主要目的: + +1) 平台识别。 +2) 运行时配置,以及 +3) 设备数量。 + +2.2 平台识别 +------------ +首先,内核将使用DT中的数据来识别特定的机器。在一个理想的世界里,具体的平台对 +内核来说并不重要,因为所有的平台细节都会被设备树以一致和可靠的方式完美描述。 +但是,硬件并不完美,所以内核必须在早期启动时识别机器,以便有机会运行特定于机 +器的修复程序。 + +在大多数情况下,机器的身份是不相关的,而内核将根据机器的核心CPU或SoC来选择 +设置代码。例如,在ARM上,arch/arm/kernel/setup.c中的setup_arch()将调 +用arch/arm/kernel/devtree.c中的setup_machine_fdt(),它通过 +machine_desc表搜索并选择与设备树数据最匹配的machine_desc。它通过查看根 +设备树节点中的'compatible'属性,并将其与struct machine_desc中的 +dt_compat列表(如果你好奇,该列表定义在arch/arm/include/asm/mach/arch.h +中)进行比较,从而确定最佳匹配。 + +“compatible” 属性包含一个排序的字符串列表,以机器的确切名称开始,后面是 +一个可选的与之兼容的板子列表,从最兼容到最不兼容排序。例如,TI BeagleBoard +和它的后继者BeagleBoard xM板的根兼容属性可能看起来分别为:: + + compatible = "ti,omap3-beagleboard", "ti,omap3450", "ti,omap3"; + compatible = "ti,omap3-beagleboard-xm", "ti,omap3450", "ti,omap3"; + +其中 "ti,map3-beagleboard-xm "指定了确切的型号,它还声称它与OMAP 3450 SoC +以及一般的OMP3系列SoC兼容。你会注意到,该列表从最具体的(确切的板子)到最 +不具体的(SoC系列)进行排序。 + +聪明的读者可能会指出,Beagle xM也可以声称与原Beagle板兼容。然而,我们应 +该当心在板级上这样做,因为通常情况下,即使在同一产品系列中,每块板都有很高 +的变化,而且当一块板声称与另一块板兼容时,很难确定到底是什么意思。对于高层 +来说,最好是谨慎行事,不要声称一块板子与另一块板子兼容。值得注意的例外是, +当一块板子是另一块板子的载体时,例如CPU模块连接到一个载体板上。 + +关于兼容值还有一个注意事项。在兼容属性中使用的任何字符串都必须有文件说明它 +表示什么。在Documentation/devicetree/bindings中添加兼容字符串的文档。 + +同样在ARM上,对于每个machine_desc,内核会查看是否有任何dt_compat列表条 +目出现在兼容属性中。如果有,那么该machine_desc就是驱动该机器的候选者。在搜索 +了整个machine_descs表之后,setup_machine_fdt()根据每个machine_desc +在兼容属性中匹配的条目,返回 “最兼容” 的machine_desc。如果没有找到匹配 +的machine_desc,那么它将返回NULL。 + +这个方案背后的原因是观察到,在大多数情况下,如果它们都使用相同的SoC或相同 +系列的SoC,一个machine_desc可以支持大量的电路板。然而,不可避免地会有一些例 +外情况,即特定的板子需要特殊的设置代码,这在一般情况下是没有用的。特殊情况 +可以通过在通用设置代码中明确检查有问题的板子来处理,但如果超过几个情况下, +这样做很快就会变得很难看和/或无法维护。 + +相反,兼容列表允许通用machine_desc通过在dt_compat列表中指定“不太兼容”的值 +来提供对广泛的通用板的支持。在上面的例子中,通用板支持可以声称与“ti,ompa3” +或“ti,ompa3450”兼容。如果在最初的beagleboard上发现了一个bug,需要在 +早期启动时使用特殊的变通代码,那么可以添加一个新的machine_desc,实现变通, +并且只在“ti,omap3-beagleboard”上匹配。 + +PowerPC使用了一个稍微不同的方案,它从每个machine_desc中调用.probe()钩子, +并使用第一个返回TRUE的钩子。然而,这种方法没有考虑到兼容列表的优先级,对于 +新的架构支持可能应该避免。 + +2.3 运行时配置 +-------------- +在大多数情况下,DT是将数据从固件传递给内核的唯一方法,所以也被用来传递运行 +时和配置数据,如内核参数字符串和initrd镜像的位置。 + +这些数据大部分都包含在/chosen节点中,当启动Linux时,它看起来就像这样:: + + chosen { + bootargs = "console=ttyS0,115200 loglevel=8"; + initrd-start = <0xc8000000>; + initrd-end = <0xc8200000>; + }; + +bootargs属性包含内核参数,initrd-\*属性定义initrd blob的地址和大小。注 +意initrd-end是initrd映像后的第一个地址,所以这与结构体资源的通常语义不一 +致。选择的节点也可以选择包含任意数量的额外属性,用于平台特定的配置数据。 + +在早期启动过程中,架构设置代码通过不同的辅助回调函数多次调用 +of_scan_flat_dt()来解析设备树数据,然后进行分页设置。of_scan_flat_dt() +代码扫描设备树,并使用辅助函数来提取早期启动期间所需的信息。通常情况下, +early_init_dt_scan_chosen()辅助函数用于解析所选节点,包括内核参数, +early_init_dt_scan_root()用于初始化DT地址空间模型,early_init_dt_scan_memory() +用于确定可用RAM的大小和位置。 + +在ARM上,函数setup_machine_fdt()负责在选择支持板子的正确machine_desc +后,对设备树进行早期扫描。 + +2.4 设备数量 +------------ +在电路板被识别后,在早期配置数据被解析后,内核初始化可以以正常方式进行。在 +这个过程中的某个时刻,unflatten_device_tree()被调用以将数据转换成更有 +效的运行时表示。这也是调用机器特定设置钩子的时候,比如ARM上的machine_desc +.init_early()、.init_irq()和.init_machine()钩子。本节的其余部分使用 +了ARM实现的例子,但所有架构在使用DT时都会做几乎相同的事情。 + +从名称上可以猜到,.init_early()用于在启动过程早期需要执行的任何机器特定设 +置,而.init_irq()则用于设置中断处理。使用DT并不会实质性地改变这两个函数的 +行为。如果提供了DT,那么.init_early()和.init_irq()都能调用任何一个DT查 +询函数(of_* in include/linux/of*.h),以获得关于平台的额外数据。 + +DT上下文中最有趣的钩子是.init_machine(),它主要负责将平台的数据填充到 +Linux设备模型中。历史上,这在嵌入式平台上是通过在板卡support .c文件中定 +义一组静态时钟结构、platform_devices和其他数据,并在.init_machine()中 +大量注册来实现的。当使用DT时,就不用为每个平台的静态设备进行硬编码,可以通过 +解析DT获得设备列表,并动态分配设备结构体。 + +最简单的情况是,.init_machine()只负责注册一个platform_devices。 +platform_device是Linux使用的一个概念,用于不能被硬件检测到的内存或I/O映 +射的设备,以及“复合”或 “虚拟”设备(后面会详细介绍)。虽然DT没有“平台设备”的 +术语,但平台设备大致对应于树根的设备节点和简单内存映射总线节点的子节点。 + +现在是举例说明的好时机。下面是NVIDIA Tegra板的设备树的一部分:: + + /{ + compatible = "nvidia,harmony", "nvidia,tegra20"; + #address-cells = <1>; + #size-cells = <1>; + interrupt-parent = <&intc>; + + chosen { }; + aliases { }; + + memory { + device_type = "memory"; + reg = <0x00000000 0x40000000>; + }; + + soc { + compatible = "nvidia,tegra20-soc", "simple-bus"; + #address-cells = <1>; + #size-cells = <1>; + ranges; + + intc: interrupt-controller@50041000 { + compatible = "nvidia,tegra20-gic"; + interrupt-controller; + #interrupt-cells = <1>; + reg = <0x50041000 0x1000>, < 0x50040100 0x0100 >; + }; + + serial@70006300 { + compatible = "nvidia,tegra20-uart"; + reg = <0x70006300 0x100>; + interrupts = <122>; + }; + + i2s1: i2s@70002800 { + compatible = "nvidia,tegra20-i2s"; + reg = <0x70002800 0x100>; + interrupts = <77>; + codec = <&wm8903>; + }; + + i2c@7000c000 { + compatible = "nvidia,tegra20-i2c"; + #address-cells = <1>; + #size-cells = <0>; + reg = <0x7000c000 0x100>; + interrupts = <70>; + + wm8903: codec@1a { + compatible = "wlf,wm8903"; + reg = <0x1a>; + interrupts = <347>; + }; + }; + }; + + sound { + compatible = "nvidia,harmony-sound"; + i2s-controller = <&i2s1>; + i2s-codec = <&wm8903>; + }; + }; + +在.init_machine()时,Tegra板支持代码将需要查看这个DT,并决定为哪些节点 +创建platform_devices。然而,看一下这个树,并不能立即看出每个节点代表什么 +类型的设备,甚至不能看出一个节点是否代表一个设备。/chosen、/aliases和 +/memory节点是信息节点,并不描述设备(尽管可以说内存可以被认为是一个设备)。 +/soc节点的子节点是内存映射的设备,但是codec@1a是一个i2c设备,而sound节 +点代表的不是一个设备,而是其他设备是如何连接在一起以创建音频子系统的。我知 +道每个设备是什么,因为我熟悉电路板的设计,但是内核怎么知道每个节点该怎么做? + +诀窍在于,内核从树的根部开始,寻找具有“兼容”属性的节点。首先,一般认为任何 +具有“兼容”属性的节点都代表某种设备;其次,可以认为树根的任何节点要么直接连 +接到处理器总线上,要么是无法用其他方式描述的杂项系统设备。对于这些节点中的 +每一个,Linux都会分配和注册一个platform_device,它又可能被绑定到一个 +platform_driver。 + +为什么为这些节点使用platform_device是一个安全的假设?嗯,就Linux对设备 +的建模方式而言,几乎所有的总线类型都假定其设备是总线控制器的孩子。例如,每 +个i2c_client是i2c_master的一个子节点。每个spi_device都是SPI总线的一 +个子节点。类似的还有USB、PCI、MDIO等。同样的层次结构也出现在DT中,I2C设 +备节点只作为I2C总线节点的子节点出现。同理,SPI、MDIO、USB等等。唯一不需 +要特定类型的父设备的设备是platform_devices(和amba_devices,但后面会 +详细介绍),它们将愉快地运行在Linux/sys/devices树的底部。因此,如果一个 +DT节点位于树的根部,那么它真的可能最好注册为platform_device。 + +Linux板支持代码调用of_platform_populate(NULL, NULL, NULL, NULL)来 +启动树根的设备发现。参数都是NULL,因为当从树的根部开始时,不需要提供一个起 +始节点(第一个NULL),一个父结构设备(最后一个NULL),而且我们没有使用匹配 +表(尚未)。对于只需要注册设备的板子,除了of_platform_populate()的调用, +.init_machine()可以完全为空。 + +在Tegra的例子中,这说明了/soc和/sound节点,但是SoC节点的子节点呢?它们 +不应该也被注册为平台设备吗?对于Linux DT支持,一般的行为是子设备在驱动 +.probe()时被父设备驱动注册。因此,一个i2c总线设备驱动程序将为每个子节点 +注册一个i2c_client,一个SPI总线驱动程序将注册其spi_device子节点,其他 +总线类型也是如此。根据该模型,可以编写一个与SoC节点绑定的驱动程序,并简单 +地为其每个子节点注册platform_device。板卡支持代码将分配和注册一个SoC设 +备,一个(理论上的)SoC设备驱动程序可以绑定到SoC设备,并在其.probe()钩 +中为/soc/interruptcontroller、/soc/serial、/soc/i2s和/soc/i2c注 +册platform_devices。很简单,对吗? + +实际上,事实证明,将一些platform_device的子设备注册为更多的platform_device +是一种常见的模式,设备树支持代码反映了这一点,并使上述例子更简单。 +of_platform_populate()的第二个参数是一个of_device_id表,任何与该表 +中的条目相匹配的节点也将获得其子节点的注册。在Tegra的例子中,代码可以是 +这样的:: + + static void __init harmony_init_machine(void) + { + /* ... */ + of_platform_populate(NULL, of_default_bus_match_table, NULL, NULL); + } + +“simple-bus”在Devicetree规范中被定义为一个属性,意味着一个简单的内存映射 +的总线,所以of_platform_populate()代码可以被写成只是假设简单总线兼容的节 +点将总是被遍历。然而,我们把它作为一个参数传入,以便电路板支持代码可以随时覆 +盖默认行为。 + +[需要添加关于添加i2c/spi/etc子设备的讨论] 。 + +附录A:AMBA设备 +--------------- + +ARM Primecell是连接到ARM AMBA总线的某种设备,它包括对硬件检测和电源管理 +的一些支持。在Linux中,amba_device和amba_bus_type结构体被用来表示 +Primecell设备。然而,棘手的一点是,AMBA总线上的所有设备并非都是Primecell, +而且对于Linux来说,典型的情况是amba_device和platform_device实例都是同 +一总线段的同义词。 + +当使用DT时,这给of_platform_populate()带来了问题,因为它必须决定是否将 +每个节点注册为platform_device或amba_device。不幸的是,这使设备创建模型 +变得有点复杂,但解决方案原来并不是太具有侵略性。如果一个节点与“arm,amba-primecell” +兼容,那么of_platform_populate()将把它注册为amba_device而不是 +platform_device。 diff --git a/Documentation/translations/zh_CN/disclaimer-zh_CN.rst b/Documentation/translations/zh_CN/disclaimer-zh_CN.rst new file mode 100644 index 000000000..3c6db094a --- /dev/null +++ b/Documentation/translations/zh_CN/disclaimer-zh_CN.rst @@ -0,0 +1,9 @@ +:orphan: + +.. warning:: + 此文件的目的是为让中文读者更容易阅读和理解,而不是作为一个分支。 因此, + 如果您对此文件有任何意见或更新,请先尝试更新原始英文文件。 + +.. note:: + 如果您发现本文档与原始文件有任何不同或者有翻译问题,请联系该文件的译者, + 或者请求时奎亮的帮助:<alexs@kernel.org>。 diff --git a/Documentation/translations/zh_CN/doc-guide/contributing.rst b/Documentation/translations/zh_CN/doc-guide/contributing.rst new file mode 100644 index 000000000..394a13b43 --- /dev/null +++ b/Documentation/translations/zh_CN/doc-guide/contributing.rst @@ -0,0 +1,238 @@ +.. SPDX-License-Identifier: GPL-2.0 + +.. include:: ../disclaimer-zh_CN.rst + +:Original: Documentation/doc-guide/contributing.rst + +:译者: 吴想成 Wu XiangCheng <bobwxc@email.cn> + +如何帮助改进内核文档 +==================== + +在任何软件开发项目中,文档都是重要组成部分。好的文档有助于引入新的开发人员, +并帮助已有的开发人员更有效地工作。如果缺少高质量的文档,大量的时间就会浪费在 +代码的逆向工程和犯本可避免的错误上。 + +不幸的是,内核的文档目前远远不能满足支持如此规模和重要性的项目的需要。 + +本指南适用于希望帮助改善这种状况的贡献者。内核文档的改进可以由开发者在不同的 +技能层级上进行;这也是一条相对简单可以帮助您了解内核过程并在社区中找到一席之 +地的路径。下面的大部分内容是文档维护人员列出的最迫切需要完成的任务。 + +文档待办事项列表 +---------------- + +为了使我们的文档达到应有的水平,需要完成的任务数不胜数。此列表包含许多重要的 +项目,但还远远不够详尽;如果您知道改进文档的其他方法,请不要羞于启齿。 + +消除警告(WARNING) +~~~~~~~~~~~~~~~~~~~ + +文档构建目前出现了数量惊人的警告。虱子多了不痒,债多了不愁;大伙儿忽略了它们, +他们永远不会注意到他们的工作增加了新的警告。因此,消除警告是文档待办事项列表 +中优先级最高的任务之一。这项任务本身相当简单,但必须以正确的方式进行,才能取 +得成功。 + +C代码编译器发出的警告常常会被视为误报,从而导致出现了旨在让编译器闭嘴的补丁。 +文档构建中的警告几乎总是指向真正的问题;要消除这些警告,需要理解问题并从源头上 +解决问题。因此,修复文档警告的补丁不应在标题中直接写“修复警告”;它们应该指明 +真正修复的问题。 + +另一个重点是,文档警告常常由C代码里kernel-doc注释中的问题引起。虽然文档维护 +人员对收到这些修复补丁的副本表示感谢,但是文档树实际上通常并不适合接受这些 +补丁;它们应该被交给相关子系统的维护人员。 + +例如,在一次文档构建中,我几乎是随意选取了一对警告:: + + ./drivers/devfreq/devfreq.c:1818: warning: bad line: + - Resource-managed devfreq_register_notifier() + ./drivers/devfreq/devfreq.c:1854: warning: bad line: + - Resource-managed devfreq_unregister_notifier() + +(作了断行以便于阅读) + +简单看一下上面给出的源文件,会发现几个kernel-doc注释,如下所示:: + + /** + * devm_devfreq_register_notifier() + - Resource-managed devfreq_register_notifier() + * @dev: The devfreq user device. (parent of devfreq) + * @devfreq: The devfreq object. + * @nb: The notifier block to be unregistered. + * @list: DEVFREQ_TRANSITION_NOTIFIER. + */ + +问题在于缺了一个“*”,这不符合构建系统对C注释块的格式要求。此问题自2016年注释 +被添加以来一直存在——整整四年之久。修复它只需要添加丢失的星号。看一眼该文件的 +历史记录以了解主题行的常规格式是什么样,再使用 ``scripts/get_maintainer.pl`` +来搞清谁应当收到此补丁。生成的补丁如下所示:: + + [PATCH] PM / devfreq: Fix two malformed kerneldoc comments + + Two kerneldoc comments in devfreq.c fail to adhere to the required format, + resulting in these doc-build warnings: + + ./drivers/devfreq/devfreq.c:1818: warning: bad line: + - Resource-managed devfreq_register_notifier() + ./drivers/devfreq/devfreq.c:1854: warning: bad line: + - Resource-managed devfreq_unregister_notifier() + + Add a couple of missing asterisks and make kerneldoc a little happier. + + Signed-off-by: Jonathan Corbet <corbet@lwn.net> + --- + drivers/devfreq/devfreq.c | 4 ++-- + 1 file changed, 2 insertions(+), 2 deletions(-) + + diff --git a/drivers/devfreq/devfreq.c b/drivers/devfreq/devfreq.c + index 57f6944d65a6..00c9b80b3d33 100644 + --- a/drivers/devfreq/devfreq.c + +++ b/drivers/devfreq/devfreq.c + @@ -1814,7 +1814,7 @@ static void devm_devfreq_notifier_release(struct device *dev, void *res) + + /** + * devm_devfreq_register_notifier() + - - Resource-managed devfreq_register_notifier() + + * - Resource-managed devfreq_register_notifier() + * @dev: The devfreq user device. (parent of devfreq) + * @devfreq: The devfreq object. + * @nb: The notifier block to be unregistered. + @@ -1850,7 +1850,7 @@ EXPORT_SYMBOL(devm_devfreq_register_notifier); + + /** + * devm_devfreq_unregister_notifier() + - - Resource-managed devfreq_unregister_notifier() + + * - Resource-managed devfreq_unregister_notifier() + * @dev: The devfreq user device. (parent of devfreq) + * @devfreq: The devfreq object. + * @nb: The notifier block to be unregistered. + -- + 2.24.1 + +整个过程只花了几分钟。当然,我后来发现有人在另一个树中修复了它,这亮出了另一 +个教训:在深入研究问题之前,一定要检查linux-next树,看看问题是否已经修复。 + +其他修复可能需要更长的时间,尤其是那些与缺少文档的结构体成员或函数参数相关的 +修复。这种情况下,需要找出这些成员或参数的作用,并正确描述它们。总之,这种 +任务有时会有点乏味,但它非常重要。如果我们真的可以从文档构建中消除警告,那么 +我们就可以开始期望开发人员开始注意避免添加新的警告了。 + +“迷失的”kernel-doc注释 +~~~~~~~~~~~~~~~~~~~~~~ + +开发者被鼓励去为他们的代码写kernel-doc注释,但是许多注释从未被引入文档构建。 +这使得这些信息更难找到,例如使Sphinx无法生成指向该文档的链接。将 ``kernel-doc`` +指令添加到文档中以引入这些注释可以帮助社区获得为编写注释所做工作的全部价值。 + +``scripts/find-unused-docs.sh`` 工具可以用来找到这些被忽略的评论。 + +请注意,将导出的函数和数据结构引入文档是最有价值的。许多子系统还具有供内部 +使用的kernel-doc注释;除非这些注释放在专门针对相关子系统开发人员的文档中, +否则不应将其引入文档构建中。 + + +修正错字 +~~~~~~~~ + + +修复文档中的排版或格式错误是一种快速学习如何创建和发送修补程序的方法,也是 +一项有用的服务。我总是愿意接受这样的补丁。这也意味着,一旦你修复了一些这种 +错误,请考虑转移到更高级的任务,留下一些拼写错误给下一个初学者解决。 + +请注意,有些并 **不是** 拼写错误,不应该被“修复”: + + - 内核文档中用美式和英式英语拼写皆可,没有必要互相倒换。 + + - 在内核文档中,没必要讨论句点后面应该跟一个还是两个空格的问题。其他一些有 + 合理分歧的地方,比如“牛津逗号”,在这也是跑题的。 + +与对任何项目的任何补丁一样,请考虑您的更改是否真的让事情变得更好。 + +“上古”文档 +~~~~~~~~~~ + +一些内核文档是最新的、被维护的,并且非常有用,有些文件确并非如此。尘封、陈旧 +和不准确的文档可能会误导读者,并对我们的整个文档产生怀疑。任何解决这些问题的 +事情都是非常受欢迎的。 + +无论何时处理文档,请考虑它是否是最新的,是否需要更新,或者是否应该完全删除。 +您可以注意以下几个警告标志: + + - 对2.x内核的引用 + - 指向SourceForge存储库 + - 历史记录除了拼写错误啥也没有持续几年 + - 讨论Git之前时代的工作流 + +当然,最好的办法是更新文档,添加所需的任何信息。这样的工作通常需要与熟悉相关 +子系统的开发人员合作。当有人善意地询问开发人员,并听取他们的回答然后采取 +行动时,开发人员通常更愿意与这些致力于改进文档的人员合作。 + +有些文档已经没希望了;例如,我们偶尔会发现引用了很久以前从内核中删除的代码的 +文档。删除过时的文档会碰见令人惊讶的阻力,但我们无论如何都应该这样做。文档中 +多余的粗枝大叶对任何人都没有帮助。 + +如果一个严重过时的文档中可能有一些有用的信息,而您又无法更新它,那么最好在 +开头添加一个警告。建议使用以下文本:: + + .. warning :: + This document is outdated and in need of attention. Please use + this information with caution, and please consider sending patches + to update it. + +这样的话,至少我们长期受苦的读者会得到文件可能会把他们引入歧途的警告。 + +文档一致性 +~~~~~~~~~~ + +这里的老前辈们会记得上世纪90年代出现在书架上的Linux书籍,它们只是从网上不同 +位置搜来的文档文件的集合。在此之后,(大部分)这些书都得到了改进,但是内核的 +文档仍然主要是建立在这种模型上。它有数千个文件,几乎每个文件都是与其他文件相 +独立编写的。我们没有一个连贯的内核文档;只有数千个独立的文档。 + +我们一直试图通过编篡一套“书籍”来改善这种情况,以为特定读者提供成套文档。这 +包括: + + - Documentation/translations/zh_CN/admin-guide/index.rst + - Documentation/core-api/index.rst + - Documentation/driver-api/index.rst + - Documentation/userspace-api/index.rst + +以及文档本身这本“书”。 + +将文档移到适当的书中是一项重要的任务,需要继续进行。不过这项工作还是有一些 +挑战性。移动文档会给处理这些文档的人带来短期的痛苦;他们对这些更改无甚热情 +也是可以理解的。通常情况下,可以将一个文档移动一下;不过我们真的不想一直移动 +它们。 + +即使所有文件都在正确的位置,我们也只是把一大堆文件变成一群小堆文件。试图将 +所有这些文件组合成一个整体的工作尚未开始。如果你对如何在这方面取得进展有好的 +想法,我们将很高兴了解。 + +样式表(Stylesheet)改进 +~~~~~~~~~~~~~~~~~~~~~~~~ + +随着Sphinx的采用,我们得到了比以前更好的HTML输出。但它仍然需要很大的改进; +Donald Knuth和Edward Tufte可能是映像平平的。这需要调整我们的样式表,以创建 +更具排版效果、可访问性和可读性的输出。 + +请注意:如果你承担这个任务,你将进入经典的两难领域。即使是相对明显的变化, +也会有很多意见和讨论。唉,这就是我们生活的世界的本质。 + +无LaTeX的PDF构建 +~~~~~~~~~~~~~~~~ + +对于拥有大量时间和Python技能的人来说,这绝对是一项不平凡的任务。Sphinx工具链 +相对较小且包含良好;很容易添加到开发系统中。但是构建PDF或EPUB输出需要安装 +LaTeX,它绝对称不上小或包含良好的。消除Latex将是一件很好的事情。 + +最初是希望使用 `rst2pdf <https://rst2pdf.org/>`_ 工具来生成PDF,但结果发现 +无法胜任这项任务。不过rst2pdf的开发工作最近似乎又有了起色,这是个充满希望的 +迹象。如果有开发人员愿意与该项目合作,使rst2pdf可与内核文档构建一起工作, +大家会非常感激。 + +编写更多文档 +~~~~~~~~~~~~ + +当然,内核中许多部分的文档严重不足。如果您有编写一个特定内核子系统文档的相应 +知识并愿意这样做,请不要犹豫,编写并向内核贡献结果吧!数不清的内核开发人员和 +用户会感谢你。 diff --git a/Documentation/translations/zh_CN/doc-guide/index.rst b/Documentation/translations/zh_CN/doc-guide/index.rst new file mode 100644 index 000000000..5151953c1 --- /dev/null +++ b/Documentation/translations/zh_CN/doc-guide/index.rst @@ -0,0 +1,27 @@ +.. include:: ../disclaimer-zh_CN.rst + +:Original: Documentation/doc-guide/index.rst + +:译者: 吴想成 Wu XiangCheng <bobwxc@email.cn> + +.. _doc_guide_zh: + +================ +如何编写内核文档 +================ + +.. toctree:: + :maxdepth: 1 + + sphinx + kernel-doc + parse-headers + contributing + maintainer-profile + +.. only:: 子项目与HTML + + 目录 + ==== + + * :ref:`genindex` diff --git a/Documentation/translations/zh_CN/doc-guide/kernel-doc.rst b/Documentation/translations/zh_CN/doc-guide/kernel-doc.rst new file mode 100644 index 000000000..ccfb9b832 --- /dev/null +++ b/Documentation/translations/zh_CN/doc-guide/kernel-doc.rst @@ -0,0 +1,499 @@ +.. include:: ../disclaimer-zh_CN.rst + +:Original: Documentation/doc-guide/kernel-doc.rst + +:译者: 吴想成 Wu XiangCheng <bobwxc@email.cn> + +编写kernel-doc注释 +================== + +Linux内核源文件可以包含kernel-doc格式的结构化文档注释,用以描述代码的函数、 +类型和设计。将文档嵌入源文件更容易保持文档最新。 + +.. note:: 内核文档格式与javadoc、gtk-doc或Doxygen看似很相似,但由于历史原因, + 实际有着明显的不同。内核源包含成千上万个kernel-doc注释。请坚持遵循 + 此处描述的风格。 + +.. note:: kernel-doc无法包含Rust代码:请参考 Documentation/rust/general-information.rst 。 + +从注释中提取kernel-doc结构,并从中生成适当的 `Sphinx C 域`_ 函数和带有锚点的 +类型描述。这些注释将被过滤以生成特殊kernel-doc高亮和交叉引用。详见下文。 + +.. _Sphinx C 域: http://www.sphinx-doc.org/en/stable/domains.html + +使用 ``EXPORT_SYMBOL`` 或 ``EXPORT_SYMBOL_GPL`` 导出到可加载模块的每个函数都 +应该有一个kernel-doc注释。模块使用的头文件中的函数和数据结构也应该有 +kernel-doc注释。 + +对于其他内核文件(未标记为 ``static`` )中外部可见的函数,提供kernel-doc格式 +的文档是一个很好的实践。我们也建议为私有(文件 ``static`` )程序提供kernel-doc +格式的文档,以确保内核源代码布局的一致性。此建议优先级较低,由内核源文件的 +维护者自行决定。 + +如何格式化kernel-doc注释 +------------------------ + +kernel-doc注释用 ``/**`` 作为开始标记。 ``kernel-doc`` 工具将提取以这种方式 +标记的注释。注释其余部分的格式类似于一个普通的多行注释,左侧有一列星号,以 +``*/`` 行结束。 + +函数和类型的kernel-doc注释应该放在所描述的函数或类型之前,以便最大限度地提高 +更改代码的人同时更改文档的可能性。概述kernel-doc注释可以放在最顶部的任何地方。 + +用详细模式和不生成实际输出来运行 ``kernel-doc`` 工具,可以验证文档注释的格式 +是否正确。例如:: + + scripts/kernel-doc -v -none drivers/foo/bar.c + +当请求执行额外的gcc检查时,内核构建将验证文档格式:: + + make W=n + +函数文档 +-------- + +函数和函数式宏的kernel-doc注释的一般格式是:: + + /** + * 函数名() - 函数简要说明. + * @参数1: 描述第一个参数. + * @参数2: 描述第二个参数. + * 可以为参数提供一段 + * 多行描述. + * + * 更详细的描述,进一步讨论函数 函数名(), 这可能对使用或修改它的人有用. + * 以空注释行开始, 内部可以包含空注释行. + * + * 详细描述可以有多个段落. + * + * Context: 描述函数是否可以休眠, 它需要、释放或期望持有什么锁. + * 可以写多行. + * Return: 描述函数返回值. + * + * 返回值描述也可以有多个段落, + * 并且应该放在注释块的末尾. + */ + +函数名后面的简短描述可以跨多行,并以参数描述、空注释行或注释块结尾结束。 + +函数参数 +~~~~~~~~ + +每个函数参数都应该按照顺序描述,紧跟在函数简要说明之后。不要在函数描述和参数 +之间,也不要在参数之间留空。 + +每个 ``@参数:`` 描述可以跨多行。 + +.. note:: + + 如果 ``@参数`` 描述有多行,则说明的续行应该从上一行的同一列开始:: + + * @参数: 较长说明 + * 的续行 + + 或:: + + * @参数: + * 较长说明 + * 的续行 + +如果函数的参数数目可变,则需用kernel-doc格式对其进行描述:: + + * @...: 描述 + +函数上下文 +~~~~~~~~~~ + +可调用函数的上下文应该在 ``Context`` 节中描述。此节应该包括函数是休眠的还是 +可以从中断上下文调用的,以及它需要什么锁、释放什么锁和期望它的调用者持有什么 +锁。 + +例如:: + + * Context: Any context. + * Context: Any context. Takes and releases the RCU lock. + * Context: Any context. Expects <lock> to be held by caller. + * Context: Process context. May sleep if @gfp flags permit. + * Context: Process context. Takes and releases <mutex>. + * Context: Softirq or process context. Takes and releases <lock>, BH-safe. + * Context: Interrupt context. + +返回值 +~~~~~~ + +如有返回值,应在 ``Return`` 节中描述。 + +.. note:: + + #) 您提供的多行描述文本 *不会* 识别换行符,因此如果您想将某些文本预格式化, + 如:: + + * Return: + * 0 - OK + * -EINVAL - invalid argument + * -ENOMEM - out of memory + + 它们在最终文档中变成一行:: + + Return: 0 - OK -EINVAL - invalid argument -ENOMEM - out of memory + + 因此,为了在需要的地方换行,您需要使用ReST列表,例如:: + + * Return: + * * 0 - OK to runtime suspend the device + * * -EBUSY - Device should not be runtime suspended + + #) 如果您提供的描述性文本中的行以某个后跟冒号的短语开头,则每一个这种短语 + 都将被视为新的节标题,可能会产生意料不到的效果。 + +结构体、共用体、枚举类型文档 +---------------------------- + +结构体(struct)、共用体(union)、枚举(enum)类型kernel-doc注释的一般格式为:: + + /** + * struct 结构体名 - 简要描述. + * @成员1: 成员1描述. + * @成员2: 成员2描述. + * 可以为成员提供 + * 多行描述. + * + * 结构体的描述. + */ + +可以用 ``union`` 或 ``enum`` 替换上面示例中的 ``struct`` ,以描述共用体或枚举。 +``成员`` 用于表示枚举中的元素或共用体成员。 + +结构体名称后面的简要说明可以跨多行,并以成员说明、空白注释行或注释块结尾结束。 + +成员 +~~~~ + +结构体、共用体和枚举的成员应以与函数参数相同的方式记录;它们后紧跟简短的描述, +并且为多行。 + +在结构体或共用体描述中,可以使用 ``private:`` 和 ``public:`` 注释标签。 +``private:`` 域内的字段不会列在生成的文档中。 + +``private:`` 和 ``public:`` 标签必须紧跟在 ``/*`` 注释标记之后。可以选择是否 +在 ``:`` 和 ``*/`` 结束标记之间包含注释。 + +例子:: + + /** + * struct 张三 - 简短描述 + * @a: 第一个成员 + * @b: 第二个成员 + * @d: 第三个成员 + * + * 详细描述 + */ + struct 张三 { + int a; + int b; + /* private: 仅内部使用 */ + int c; + /* public: 下一个是公有的 */ + int d; + }; + +嵌套的结构体/共用体 +~~~~~~~~~~~~~~~~~~~ + +嵌套的结构体/共用体可像这样记录:: + + /** + * struct nested_foobar - a struct with nested unions and structs + * @memb1: first member of anonymous union/anonymous struct + * @memb2: second member of anonymous union/anonymous struct + * @memb3: third member of anonymous union/anonymous struct + * @memb4: fourth member of anonymous union/anonymous struct + * @bar: non-anonymous union + * @bar.st1: struct st1 inside @bar + * @bar.st2: struct st2 inside @bar + * @bar.st1.memb1: first member of struct st1 on union bar + * @bar.st1.memb2: second member of struct st1 on union bar + * @bar.st2.memb1: first member of struct st2 on union bar + * @bar.st2.memb2: second member of struct st2 on union bar + */ + struct nested_foobar { + /* Anonymous union/struct*/ + union { + struct { + int memb1; + int memb2; + }; + struct { + void *memb3; + int memb4; + }; + }; + union { + struct { + int memb1; + int memb2; + } st1; + struct { + void *memb1; + int memb2; + } st2; + } bar; + }; + +.. note:: + + #) 在记录嵌套结构体或共用体时,如果结构体/共用体 ``张三`` 已命名,则其中 + 的成员 ``李四`` 应记录为 ``@张三.李四:`` + + #) 当嵌套结构体/共用体是匿名的时,其中的成员 ``李四`` 应记录为 ``@李四:`` + +行间注释文档 +~~~~~~~~~~~~ + +结构成员也可在定义时以行间注释形式记录。有两种样式,一种是单行注释,其中开始 +``/**`` 和结束 ``*/`` 位于同一行;另一种是多行注释,开头结尾各自位于一行,就 +像所有其他核心文档注释一样:: + + /** + * struct 张三 - 简短描述. + * @张三: 成员张三. + */ + struct 张三 { + int 张三; + /** + * @李四: 成员李四. + */ + int 李四; + /** + * @王五: 成员王五. + * + * 此处,成员描述可以为好几段. + */ + int 王五; + union { + /** @儿子: 单行描述. */ + int 儿子; + }; + /** @赵六: 描述@张三里面的结构体@赵六 */ + struct { + /** + * @赵六.女儿: 描述@张三.赵六里面的@女儿 + */ + int 女儿; + } 赵六; + }; + +Typedef文档 +----------- + +Typedef的kernel-doc文档注释的一般格式为:: + + /** + * typedef 类型名称 - 简短描述. + * + * 类型描述. + */ + +还可以记录带有函数原型的typedef:: + + /** + * typedef 类型名称 - 简短描述. + * @参数1: 参数1的描述 + * @参数2: 参数2的描述 + * + * 类型描述. + * + * Context: 锁(Locking)上下文. + * Return: 返回值的意义. + */ + typedef void (*类型名称)(struct v4l2_ctrl *参数1, void *参数2); + +高亮与交叉引用 +-------------- + +在kernel-doc注释的描述文本中可以识别以下特殊模式,并将其转换为正确的 +reStructuredText标记和 `Sphinx C 域`_ 引用。 + +.. attention:: 以下内容 **仅** 在kernel-doc注释中识别, **不会** 在普通的 + reStructuredText文档中识别。 + +``funcname()`` + 函数引用。 + +``@parameter`` + 函数参数的名称(未交叉引用,仅格式化)。 + +``%CONST`` + 常量的名称(未交叉引用,仅格式化)。 + +````literal```` + 预格式化文本块。输出将使用等距字体。 + + 若你需要使用在kernel-doc脚本或reStructuredText中有特殊含义的字符,则此功能 + 非常有用。 + + 若你需要在函数描述中使用类似于 ``%ph`` 的东西,这特别有用。 + +``$ENVVAR`` + 环境变量名称(未交叉引用,仅格式化)。 + +``&struct name`` + 结构体引用。 + +``&enum name`` + 枚举引用。 + +``&typedef name`` + Typedef引用。 + +``&struct_name->member`` or ``&struct_name.member`` + 结构体或共用体成员引用。交叉引用将链接到结构体或共用体定义,而不是直接到成员。 + +``&name`` + 泛类型引用。请首选上面描述的完整引用方式。此法主要是为了可能未描述的注释。 + +从reStructuredText交叉引用 +~~~~~~~~~~~~~~~~~~~~~~~~~~ + +无需额外的语法来从reStructuredText文档交叉引用kernel-do注释中定义的函数和类型。 +只需以 ``()`` 结束函数名,并在类型之前写上 ``struct`` , ``union`` , ``enum`` +或 ``typedef`` 。 +例如:: + + See foo(). + See struct foo. + See union bar. + See enum baz. + See typedef meh. + +若要在交叉引用链接中使用自定义文本,可以通过以下语法进行:: + + See :c:func:`my custom link text for function foo <foo>`. + See :c:type:`my custom link text for struct bar <bar>`. + +有关更多详细信息,请参阅 `Sphinx C 域`_ 文档。 + +总述性文档注释 +-------------- + +为了促进源代码和注释紧密联合,可以将kernel-doc文档块作为自由形式的注释,而 +不是函数、结构、联合、枚举或typedef的绑定kernel-doc。例如,这可以用于解释 +驱动程序或库代码的操作理论。 + +这是通过使用带有节标题的 ``DOC:`` 节关键字来实现的。 + +总述或高层级文档注释的一般格式为:: + + /** + * DOC: Theory of Operation + * + * The whizbang foobar is a dilly of a gizmo. It can do whatever you + * want it to do, at any time. It reads your mind. Here's how it works. + * + * foo bar splat + * + * The only drawback to this gizmo is that is can sometimes damage + * hardware, software, or its subject(s). + */ + +``DOC:`` 后面的标题用作源文件中的标题,但也用作提取文档注释的标识符。因此, +文件中的标题必须是唯一的。 + +包含kernel-doc注释 +================== + +文档注释可以被包含在任何使用专用kernel-doc Sphinx指令扩展的reStructuredText +文档中。 + +kernel-doc指令的格式如下:: + + .. kernel-doc:: source + :option: + +*source* 是相对于内核源代码树的源文件路径。 +支持以下指令选项: + +export: *[source-pattern ...]* + 包括 *source* 中使用 ``EXPORT_SYMBOL`` 或 ``EXPORT_SYMBOL_GPL`` 导出的所有 + 函数的文档,无论是在 *source* 中还是在 *source-pattern* 指定的任何文件中。 + + 当kernel-doc注释被放置在头文件中,而 ``EXPORT_SYMBOL`` 和 ``EXPORT_SYMBOL_GPL`` + 位于函数定义旁边时, *source-pattern* 非常有用。 + + 例子:: + + .. kernel-doc:: lib/bitmap.c + :export: + + .. kernel-doc:: include/net/mac80211.h + :export: net/mac80211/*.c + +internal: *[source-pattern ...]* + 包括 *source* 中所有在 *source* 或 *source-pattern* 的任何文件中都没有使用 + ``EXPORT_SYMBOL`` 或 ``EXPORT_SYMBOL_GPL`` 导出的函数和类型的文档。 + + 例子:: + + .. kernel-doc:: drivers/gpu/drm/i915/intel_audio.c + :internal: + +identifiers: *[ function/type ...]* + 在 *source* 中包含每个 *function* 和 *type* 的文档。如果没有指定 *function* , + 则 *source* 中所有函数和类型的文档都将包含在内。 + + 例子:: + + .. kernel-doc:: lib/bitmap.c + :identifiers: bitmap_parselist bitmap_parselist_user + + .. kernel-doc:: lib/idr.c + :identifiers: + +no-identifiers: *[ function/type ...]* + 排除 *source* 中所有 *function* 和 *type* 的文档。 + + 例子:: + + .. kernel-doc:: lib/bitmap.c + :no-identifiers: bitmap_parselist + +functions: *[ function/type ...]* + 这是“identifiers”指令的别名,已弃用。 + +doc: *title* + 包含 *source* 中由 *title* 标题标识的 ``DOC:`` 文档段落。 *title* 中允许 + 空格;不要在 *title* 上加引号。 *title* 仅用作段落的标识符,不包含在输出中。 + 请确保在所附的reStructuredText文档中有适当的标题。 + + 例子:: + + .. kernel-doc:: drivers/gpu/drm/i915/intel_audio.c + :doc: High Definition Audio over HDMI and Display Port + +如果没有选项,kernel-doc指令将包含源文件中的所有文档注释。 + +kernel-doc扩展包含在内核源代码树中,位于 ``Documentation/sphinx/kerneldoc.py`` 。 +在内部,它使用 ``scripts/kernel-doc`` 脚本从源代码中提取文档注释。 + +.. _kernel_doc_zh: + +如何使用kernel-doc生成手册(man)页 +----------------------------------- + +如果您只想使用kernel-doc生成手册页,可以从内核git树这样做:: + + $ scripts/kernel-doc -man \ + $(git grep -l '/\*\*' -- :^Documentation :^tools) \ + | scripts/split-man.pl /tmp/man + +一些旧版本的git不支持路径排除语法的某些变体。 +以下命令之一可能适用于这些版本:: + + $ scripts/kernel-doc -man \ + $(git grep -l '/\*\*' -- . ':!Documentation' ':!tools') \ + | scripts/split-man.pl /tmp/man + + $ scripts/kernel-doc -man \ + $(git grep -l '/\*\*' -- . ":(exclude)Documentation" ":(exclude)tools") \ + | scripts/split-man.pl /tmp/man + diff --git a/Documentation/translations/zh_CN/doc-guide/maintainer-profile.rst b/Documentation/translations/zh_CN/doc-guide/maintainer-profile.rst new file mode 100644 index 000000000..35c88e5b3 --- /dev/null +++ b/Documentation/translations/zh_CN/doc-guide/maintainer-profile.rst @@ -0,0 +1,43 @@ +.. SPDX-License-Identifier: GPL-2.0 + +.. include:: ../disclaimer-zh_CN.rst + +:Original: Documentation/doc-guide/maintainer-profile.rst + +:译者: 吴想成 Wu XiangCheng <bobwxc@email.cn> + +文档子系统维护人员条目概述 +========================== + +文档“子系统”是内核文档和相关基础设施的中心协调点。它涵盖了 Documentation/ 下 +的文件层级(Documentation/devicetree 除外)、scripts/ 下的各种实用程序,并且 +在某些情况下的也包括 LICENSES/ 。 + +不过值得注意的是,这个子系统的边界比通常更加模糊。许多其他子系统维护人员需要 +保持对 Documentation/ 某些部分的控制,以便于可以更自由地做更改。除此之外, +许多内核文档都以kernel-doc注释的形式出现在源代码中;这些注释通常(但不总是) +由相关的子系统维护人员维护。 + +文档子系统的邮件列表是<linux-doc@vger.kernel.org>。 +补丁应尽量针对docs-next树。 + +提交检查单补遗 +-------------- + +在进行文档更改时,您应当构建文档以测试,并确保没有引入新的错误或警告。生成 +HTML文档并查看结果将有助于避免对文档渲染结果的不必要争执。 + +开发周期的关键节点 +------------------ + +补丁可以随时发送,但在合并窗口期间,响应将比通常慢。文档树往往在合并窗口打开 +之前关闭得比较晚,因为文档补丁导致回归的风险很小。 + +审阅节奏 +-------- + +我(译注:指Jonathan Corbet <corbet@lwn.net>)是文档子系统的唯一维护者,我在 +自己的时间里完成这项工作,所以对补丁的响应有时会很慢。当补丁被合并时(或当我 +决定拒绝合并补丁时),我都会发送通知。如果您在发送补丁后一周内没有收到回复, +请不要犹豫,发送提醒就好。 + diff --git a/Documentation/translations/zh_CN/doc-guide/parse-headers.rst b/Documentation/translations/zh_CN/doc-guide/parse-headers.rst new file mode 100644 index 000000000..a08819e90 --- /dev/null +++ b/Documentation/translations/zh_CN/doc-guide/parse-headers.rst @@ -0,0 +1,187 @@ +.. SPDX-License-Identifier: GPL-2.0 + +.. include:: ../disclaimer-zh_CN.rst + +:Original: Documentation/doc-guide/parse-headers.rst + +:译者: 吴想成 Wu XiangCheng <bobwxc@email.cn> + +===================== +包含用户空间API头文件 +===================== + +有时,为了描述用户空间API并在代码和文档之间生成交叉引用,需要包含头文件和示例 +C代码。为用户空间API文件添加交叉引用还有一个好处:如果在文档中找不到相应符号, +Sphinx将生成警告。这有助于保持用户空间API文档与内核更改同步。 +:ref:`parse_headers.pl <parse_headers_zh>` 提供了生成此类交叉引用的一种方法。 +在构建文档时,必须通过Makefile调用它。有关如何在内核树中使用它的示例,请参阅 +``Documentation/userspace-api/media/Makefile`` 。 + +.. _parse_headers_zh: + +parse_headers.pl +---------------- + +脚本名称 +~~~~~~~~ + + +parse_headers.pl——解析一个C文件,识别函数、结构体、枚举、定义并对Sphinx文档 +创建交叉引用。 + + +用法概要 +~~~~~~~~ + + +\ **parse_headers.pl**\ [<选项>] <C文件> <输出文件> [<例外文件>] + +<选项> 可以是: --debug, --help 或 --usage 。 + + +选项 +~~~~ + + + +\ **--debug**\ + + 开启脚本详细模式,在调试时很有用。 + + +\ **--usage**\ + + 打印简短的帮助信息并退出。 + + + +\ **--help**\ + + 打印更详细的帮助信息并退出。 + + +说明 +~~~~ + +通过C头文件或源文件(<C文件>)中为描述API的文档编写的带交叉引用的 ..预格式化 +文本 块将文件转换成重构文本(RST)。它接受一个可选的<例外文件>,其中描述了 +哪些元素将被忽略或指向非默认引用。 + +输出被写入到<输出文件>。 + +它能够识别定义、函数、结构体、typedef、枚举和枚举符号,并为它们创建交叉引用。 +它还能够区分用于指定Linux ioctl的 ``#define`` 。 + +<例外文件> 包含两种类型的语句: \ **ignore**\ 或 \ **replace**\ . + +ignore标记的语法为: + + +ignore \ **type**\ \ **name**\ + +The \ **ignore**\ 意味着它不会为类型为 \ **type**\ 的 \ **name**\ 符号生成 +交叉引用。 + + +replace标记的语法为: + + +replace \ **type**\ \ **name**\ \ **new_value**\ + +The \ **replace**\ 味着它将为 \ **type**\ 类型的 \ **name**\ 符号生成交叉引 +用,但是它将使用 \ **new_value**\ 来取代默认的替换规则。 + + +这两种语句中, \ **type**\ 可以是以下任一项: + + +\ **ioctl**\ + + ignore 或 replace 语句应用于ioctl定义,如: + + #define VIDIOC_DBG_S_REGISTER _IOW('V', 79, struct v4l2_dbg_register) + + + +\ **define**\ + + ignore 或 replace 语句应用于在<C文件>中找到的任何其他 ``#define`` 。 + + + +\ **typedef**\ + + ignore 和 replace 语句应用于<C文件>中的typedef语句。 + + + +\ **struct**\ + + ignore 和 replace 语句应用于<C文件>中的结构体名称语句。 + + + +\ **enum**\ + + ignore 和 replace 语句应用于<C文件>中的枚举名称语句。 + + + +\ **symbol**\ + + ignore 和 replace 语句应用于<C文件>中的枚举值名称语句。 + + replace语句中, \ **new_value**\ 会自动使用 \ **typedef**\ , \ **enum**\ + 和 \ **struct**\ 类型的 :c:type: 引用;以及 \ **ioctl**\ , \ **define**\ 和 + \ **symbol**\ 类型的 :ref: 。引用的类型也可以在replace语句中显式定义。 + + +示例 +~~~~ + + +ignore define _VIDEODEV2_H + + +忽略<C文件>中的 #define _VIDEODEV2_H 。 + +ignore symbol PRIVATE + + +如下结构体: + +enum foo { BAR1, BAR2, PRIVATE }; + +不会为 \ **PRIVATE**\ 生成交叉引用。 + +replace symbol BAR1 :c:type:\`foo\` +replace symbol BAR2 :c:type:\`foo\` + + +如下结构体: + +enum foo { BAR1, BAR2, PRIVATE }; + +它会让BAR1和BAR2枚举符号交叉引用C域中的foo符号。 + + + +缺陷 +~~~~ + + +请向Mauro Carvalho Chehab <mchehab@kernel.org>报告有关缺陷。 + +中文翻译问题请找中文翻译维护者。 + + +版权 +~~~~ + + +版权所有 (c) 2016 Mauro Carvalho Chehab <mchehab+samsung@kernel.org> + +许可证 GPLv2:GNU GPL version 2 <https://gnu.org/licenses/gpl.html> + +这是自由软件:你可以自由地修改和重新发布它。 +在法律允许的范围内,**不提供任何保证**。 diff --git a/Documentation/translations/zh_CN/doc-guide/sphinx.rst b/Documentation/translations/zh_CN/doc-guide/sphinx.rst new file mode 100644 index 000000000..23eac67fb --- /dev/null +++ b/Documentation/translations/zh_CN/doc-guide/sphinx.rst @@ -0,0 +1,412 @@ +.. include:: ../disclaimer-zh_CN.rst + +:Original: Documentation/doc-guide/sphinx.rst + +:译者: 吴想成 Wu XiangCheng <bobwxc@email.cn> + +.. _sphinxdoc_zh: + +简介 +==== + +Linux内核使用 `Sphinx <http://www.sphinx-doc.org/>`_ 来把 ``Documentation`` +下的 `reStructuredText <http://docutils.sourceforge.net/rst.html>`_ 文件转 +换成漂亮的文档。使用 ``make htmldocs`` 或 ``make pdfdocs`` 命令即可构建HTML +或PDF格式的文档。生成的文档放在 ``Documentation/output`` 文件夹中。 + +reStructuredText文件可能包含包含来自源文件的结构化文档注释或kernel-doc注释。 +通常它们用于描述代码的功能、类型和设计。kernel-doc注释有一些特殊的结构和 +格式,但除此之外,它们还被作为reStructuredText处理。 + +最后,有成千上万的纯文本文档文件散布在 ``Documentation`` 里。随着时间推移, +其中一些可能会转换为reStructuredText,但其中大部分仍保持纯文本。 + +.. _sphinx_install_zh: + +安装Sphinx +========== + +Documentation/ 下的ReST文件现在使用sphinx1.7或更高版本构建。 + +这有一个脚本可以检查Sphinx的依赖项。更多详细信息见 +:ref:`sphinx-pre-install_zh` 。 + +大多数发行版都附带了Sphinx,但是它的工具链比较脆弱,而且在您的机器上升级它 +或其他一些Python包导致文档构建中断的情况并不少见。 + +避免此情况的一种方法是使用与发行版附带的不同的版本。因此,建议使用 +``virtualenv-3`` 或 ``virtualenv`` 在虚拟环境中安装Sphinx,具体取决于发行版 +如何打包Python3。 + +.. note:: + + #) html输出建议使用RTD主题。根据Sphinx版本的不同,它应该用 + ``pip install sphinx_rtd_theme`` 单独安装。 + + #) 一些ReST页面包含数学表达式。由于Sphinx的工作方式,这些表达式是使用 LaTeX + 编写的。它需要安装amsfonts和amsmath宏包,以便显示。 + +总之,如您要安装Sphinx 2.4.4版本,应执行:: + + $ virtualenv sphinx_2.4.4 + $ . sphinx_2.4.4/bin/activate + (sphinx_2.4.4) $ pip install -r Documentation/sphinx/requirements.txt + +在运行 ``. sphinx_2.4.4/bin/activate`` 之后,提示符将变化,以指示您正在使用新 +环境。如果您打开了一个新的shell,那么在构建文档之前,您需要重新运行此命令以再 +次进入虚拟环境中。 + +图片输出 +-------- + +内核文档构建系统包含一个扩展,可以处理GraphViz和SVG格式的图像(参见 +:ref:`sphinx_kfigure_zh` )。 + +为了让它工作,您需要同时安装GraphViz和ImageMagick包。如果没有安装这些软件包, +构建系统仍将构建文档,但不会在输出中包含任何图像。 + +PDF和LaTeX构建 +-------------- + +目前只有Sphinx 2.4及更高版本才支持这种构建。 + +对于PDF和LaTeX输出,还需要 ``XeLaTeX`` 3.14159265版本。(译注:此版本号真实 +存在) + +根据发行版的不同,您可能还需要安装一系列 ``texlive`` 软件包,这些软件包提供了 +``XeLaTeX`` 工作所需的最小功能集。 + +.. _sphinx-pre-install_zh: + +检查Sphinx依赖项 +---------------- + +这有一个脚本可以自动检查Sphinx依赖项。如果它认得您的发行版,还会提示您所用发行 +版的安装命令:: + + $ ./scripts/sphinx-pre-install + Checking if the needed tools for Fedora release 26 (Twenty Six) are available + Warning: better to also install "texlive-luatex85". + You should run: + + sudo dnf install -y texlive-luatex85 + /usr/bin/virtualenv sphinx_2.4.4 + . sphinx_2.4.4/bin/activate + pip install -r Documentation/sphinx/requirements.txt + + Can't build as 1 mandatory dependency is missing at ./scripts/sphinx-pre-install line 468. + +默认情况下,它会检查html和PDF的所有依赖项,包括图像、数学表达式和LaTeX构建的 +需求,并假设将使用虚拟Python环境。html构建所需的依赖项被认为是必需的,其他依 +赖项则是可选的。 + +它支持两个可选参数: + +``--no-pdf`` + + 禁用PDF检查; + +``--no-virtualenv`` + + 使用Sphinx的系统打包,而不是Python虚拟环境。 + +Sphinx构建 +========== + +生成文档的常用方法是运行 ``make htmldocs`` 或 ``make pdfdocs`` 。还有其它可用 +的格式:请参阅 ``make help`` 的文档部分。生成的文档放在 ``Documentation/output`` +下相应格式的子目录中。 + +要生成文档,显然必须安装Sphinx( ``sphinx-build`` )。要让HTML输出更漂亮,可以 +使用Read the Docs Sphinx主题( ``sphinx_rtd_theme`` )。对于PDF输出,您还需要 +``XeLaTeX`` 和来自ImageMagick(https://www.imagemagick.org)的 ``convert(1)`` 。 +所有这些软件在大多发行版中都可用或已打包。 + +要传递额外的选项给Sphinx,可以使用make变量 ``SPHINXOPTS`` 。例如,使用 +``make SPHINXOPTS=-v htmldocs`` 获得更详细的输出。 + + +要删除生成的文档,请运行 ``make cleandocs`` 。 + +编写文档 +======== + +添加新文档很容易,只需: + +1. 在 ``Documentation`` 下某处添加一个新的 ``.rst`` 文件。 +2. 从 ``Documentation/index.rst`` 中的Sphinx `主目录树`_ 链接到它。 + +.. _主目录树: http://www.sphinx-doc.org/en/stable/markup/toctree.html + +对于简单的文档(比如您现在正在阅读的文档),这通常已经足够好了,但是对于较大 +的文档,最好创建一个子目录(或者使用现有的子目录)。例如,图形子系统文档位于 +``Documentation/gpu`` 下,拆分为多个 ``.rst`` 文件,并具有从主目录链接来的单 +独索引 ``index.rst`` (有自己的目录树 ``toctree`` )。 + +请参阅 `Sphinx <http://www.sphinx-doc.org/>`_ 和 `reStructuredText +<http://docutils.sourceforge.net/rst.html>`_ 的文档,以了解如何使用它们。 +特别是Sphinx `reStructuredText 基础`_ 这是开始学习使用reStructuredText的 +好地方。还有一些 `Sphinx 特殊标记结构`_ 。 + +.. _reStructuredText 基础: http://www.sphinx-doc.org/en/stable/rest.html +.. _Sphinx 特殊标记结构: http://www.sphinx-doc.org/en/stable/markup/index.html + +内核文档的具体指南 +------------------ + +这是一些内核文档的具体指南: + +* 请不要过于痴迷转换格式到reStructuredText。保持简单。在大多数情况下,文档 + 应该是纯文本,格式应足够一致,以便可以转换为其他格式。 + +* 将现有文档转换为reStructuredText时,请尽量减少格式更改。 + +* 在转换文档时,还要更新内容,而不仅仅是格式。 + +* 请遵循标题修饰符的顺序: + + 1. ``=`` 文档标题,要有上线:: + + ======== + 文档标题 + ======== + + 2. ``=`` 章:: + + 章标题 + ====== + + 3. ``-`` 节:: + + 节标题 + ------ + + 4. ``~`` 小节:: + + 小节标题 + ~~~~~~~~ + + 尽管RST没有规定具体的顺序(“没有强加一个固定数量和顺序的节标题装饰风格,最终 + 按照的顺序将是实际遇到的顺序。”),但是拥有一个通用级别的文档更容易遵循。 + +* 对于插入固定宽度的文本块(用于代码样例、用例等): ``::`` 用于语法高亮意义不 + 大的内容,尤其是短代码段; ``.. code-block:: <language>`` 用于需要语法高亮的 + 较长代码块。对于嵌入到文本中的简短代码片段,请使用 \`\` 。 + + +C域 +--- + +**Sphinx C域(Domain)** (name c)适用于C API文档。例如,函数原型: + +.. code-block:: rst + + .. c:function:: int ioctl( int fd, int request ) + +内核文档的C域有一些附加特性。例如,您可以使用诸如 ``open`` 或 ``ioctl`` 这样的 +通用名称重命名函数的引用名称: + +.. code-block:: rst + + .. c:function:: int ioctl( int fd, int request ) + :name: VIDIOC_LOG_STATUS + +函数名称(例如ioctl)仍保留在输出中,但引用名称从 ``ioctl`` 变为 +``VIDIOC_LOG_STATUS`` 。此函数的索引项也变为 ``VIDIOC_LOG_STATUS`` 。 + +请注意,不需要使用 ``c:func:`` 生成函数文档的交叉引用。由于一些Sphinx扩展的 +神奇力量,如果给定函数名的索引项存在,文档构建系统会自动将对 ``function()`` +的引用转换为交叉引用。如果在内核文档中看到 ``c:func:`` 的用法,请删除它。 + + +列表 +---- + +我们建议使用 *列式表* 格式。 *列式表* 格式是二级列表。与ASCII艺术相比,它们对 +文本文件的读者来说可能没有那么舒适。但其优点是易于创建或修改,而且修改的差异 +(diff)更有意义,因为差异仅限于修改的内容。 + +*平铺表* 也是一个二级列表,类似于 *列式表* ,但具有一些额外特性: + +* 列范围:使用 ``cspan`` 修饰,可以通过其他列扩展单元格 + +* 行范围:使用 ``rspan`` 修饰,可以通过其他行扩展单元格 + +* 自动将表格行最右边的单元格扩展到该行右侧空缺的单元格上。若使用 + ``:fill-cells:`` 选项,此行为可以从 *自动合并* 更改为 *自动插入* ,自动 + 插入(空)单元格,而不是扩展合并到最后一个单元格。 + +选项: + +* ``:header-rows:`` [int] 标题行计数 +* ``:stub-columns:`` [int] 标题列计数 +* ``:widths:`` [[int] [int] ... ] 列宽 +* ``:fill-cells:`` 插入缺少的单元格,而不是自动合并缺少的单元格 + +修饰: + +* ``:cspan:`` [int] 扩展列 +* ``:rspan:`` [int] 扩展行 + +下面的例子演示了如何使用这些标记。分级列表的第一级是 *表格行* 。 *表格行* 中 +只允许一个标记,即该 *表格行* 中的单元格列表。 *comments* ( ``..`` )和 +*targets* 例外(例如引用 ``:ref:`最后一行 <last row_zh>``` / :ref:`最后一行 +<last row_zh>` )。 + +.. code-block:: rst + + .. flat-table:: 表格标题 + :widths: 2 1 1 3 + + * - 表头 列1 + - 表头 列2 + - 表头 列3 + - 表头 列4 + + * - 行1 + - 字段1.1 + - 字段1.2(自动扩展) + + * - 行2 + - 字段2.1 + - :rspan:`1` :cspan:`1` 字段2.2~3.3 + + * .. _`last row_zh`: + + - 行3 + +渲染效果: + + .. flat-table:: 表格标题 + :widths: 2 1 1 3 + + * - 表头 列1 + - 表头 列2 + - 表头 列3 + - 表头 列4 + + * - 行1 + - 字段1.1 + - 字段1.2(自动扩展) + + * - 行2 + - 字段2.1 + - :rspan:`1` :cspan:`1` 字段2.2~3.3 + + * .. _`last row_zh`: + + - 行3 + +交叉引用 +-------- + +从一页文档到另一页文档的交叉引用可以通过简单地写出文件路径来完成,无特殊格式 +要求。路径可以是绝对路径或相对路径。绝对路径从“Documentation/”开始。例如,要 +交叉引用此页,以下写法皆可,取决于具体的文档目录(注意 ``.rst`` 扩展名是可选 +的):: + + 参见 Documentation/doc-guide/sphinx.rst 。此法始终可用。 + 请查看 sphinx.rst ,仅在同级目录中有效。 + 请阅读 ../sphinx.rst ,上级目录中的文件。 + +如果要使用相对路径,则需要使用Sphinx的 ``doc`` 修饰。例如,从同一目录引用此页 +的操作如下:: + + 参见 :doc:`sphinx文档的自定义链接文本 <sphinx>`. + +对于大多数用例,前者是首选,因为它更干净,更适合阅读源文件的人。如果您遇到一 +个没有任何特殊作用的 ``:doc:`` 用法,请将其转换为文档路径。 + +有关交叉引用kernel-doc函数或类型的信息,请参阅 +Documentation/doc-guide/kernel-doc.rst 。 + +.. _sphinx_kfigure_zh: + +图形图片 +======== + +如果要添加图片,应该使用 ``kernel-figure`` 和 ``kernel-image`` 指令。例如, +要插入具有可缩放图像格式的图形,请使用SVG(:ref:`svg_image_example_zh` ):: + + .. kernel-figure:: ../../../doc-guide/svg_image.svg + :alt: 简易 SVG 图片 + + SVG 图片示例 + +.. _svg_image_example_zh: + +.. kernel-figure:: ../../../doc-guide/svg_image.svg + :alt: 简易 SVG 图片 + + SVG 图片示例 + +内核figure(和image)指令支持 DOT 格式文件,请参阅 + +* DOT:http://graphviz.org/pdf/dotguide.pdf +* Graphviz:http://www.graphviz.org/content/dot-language + +一个简单的例子(:ref:`hello_dot_file_zh` ):: + + .. kernel-figure:: ../../../doc-guide/hello.dot + :alt: 你好,世界 + + DOT 示例 + +.. _hello_dot_file_zh: + +.. kernel-figure:: ../../../doc-guide/hello.dot + :alt: 你好,世界 + + DOT 示例 + +嵌入的渲染标记(或语言),如Graphviz的 **DOT** 由 ``kernel-render`` 指令提供:: + + .. kernel-render:: DOT + :alt: 有向图 + :caption: 嵌入式 **DOT** (Graphviz) 代码 + + digraph foo { + "五棵松" -> "国贸"; + } + +如何渲染取决于安装的工具。如果安装了Graphviz,您将看到一个矢量图像。否则,原始 +标记将作为 *文字块* 插入(:ref:`hello_dot_render_zh` )。 + +.. _hello_dot_render_zh: + +.. kernel-render:: DOT + :alt: 有向图 + :caption: 嵌入式 **DOT** (Graphviz) 代码 + + digraph foo { + "五棵松" -> "国贸"; + } + +*render* 指令包含 *figure* 指令中已知的所有选项,以及选项 ``caption`` 。如果 +``caption`` 有值,则插入一个 *figure* 节点,若无,则插入一个 *image* 节点。 +如果您想引用它,还需要一个 ``caption`` (:ref:`hello_svg_render_zh` )。 + +嵌入式 **SVG**:: + + .. kernel-render:: SVG + :caption: 嵌入式 **SVG** 标记 + :alt: 右上箭头 + + <?xml version="1.0" encoding="UTF-8"?> + <svg xmlns="http://www.w3.org/2000/svg" version="1.1" ...> + ... + </svg> + +.. _hello_svg_render_zh: + +.. kernel-render:: SVG + :caption: 嵌入式 **SVG** 标记 + :alt: 右上箭头 + + <?xml version="1.0" encoding="UTF-8"?> + <svg xmlns="http://www.w3.org/2000/svg" + version="1.1" baseProfile="full" width="70px" height="40px" viewBox="0 0 700 400"> + <line x1="180" y1="370" x2="500" y2="50" stroke="black" stroke-width="15px"/> + <polygon points="585 0 525 25 585 50" transform="rotate(135 525 25)"/> + </svg> + diff --git a/Documentation/translations/zh_CN/driver-api/gpio/index.rst b/Documentation/translations/zh_CN/driver-api/gpio/index.rst new file mode 100644 index 000000000..9ab64e94a --- /dev/null +++ b/Documentation/translations/zh_CN/driver-api/gpio/index.rst @@ -0,0 +1,69 @@ +.. SPDX-License-Identifier: GPL-2.0 + +.. include:: ../../disclaimer-zh_CN.rst + +:Original: Documentation/driver-api/gpio/index.rst + +:翻译: + + 司延腾 Yanteng Si <siyanteng@loongson.cn> + +:校译: + +======================= +通用型输入/输出(GPIO) +======================= + +目录: + +.. toctree:: + :maxdepth: 2 + + legacy + +Todolist: + +* intro +* using-gpio +* driver +* consumer +* board +* drivers-on-gpio +* bt8xxgpio + +核心 +==== + +该API在以下内核代码中: + +include/linux/gpio/driver.h + +drivers/gpio/gpiolib.c + +ACPI支持 +======== + +该API在以下内核代码中: + +drivers/gpio/gpiolib-acpi.c + +设备树支持 +========== + +该API在以下内核代码中: + +drivers/gpio/gpiolib-of.c + +设备管理支持 +============ + +该API在以下内核代码中: + +drivers/gpio/gpiolib-devres.c + +sysfs帮助(函数) +================= + +该API在以下内核代码中: + +drivers/gpio/gpiolib-sysfs.c diff --git a/Documentation/translations/zh_CN/driver-api/gpio/legacy.rst b/Documentation/translations/zh_CN/driver-api/gpio/legacy.rst new file mode 100644 index 000000000..6399521d0 --- /dev/null +++ b/Documentation/translations/zh_CN/driver-api/gpio/legacy.rst @@ -0,0 +1,694 @@ +.. SPDX-License-Identifier: GPL-2.0 + +.. include:: ../../disclaimer-zh_CN.rst + +:Original: Documentation/driver-api/gpio/legacy.rst + +:翻译: + + 傅炜 Fu Wei <tekkamanninja@gmail.com> + 司延腾 Yanteng Si <siyanteng@loongson.cn> + +:校译: + + +传统GPIO接口 +============ + +本文档概述了Linux下的GPIO访问公约。 + +这些函数以 gpio_* 作为前缀。其他的函数不允许使用这样的前缀或相关的 +__gpio_* 前缀。 + + +什么是GPIO? +============ +"通用输入/输出口"(GPIO)是一个灵活的由软件控制的数字信号。他们可 +由多种芯片提供,且对于从事嵌入式和定制硬件的 Linux 开发者来说是 +比较熟悉。每个GPIO 都代表一个连接到特定引脚或球栅阵列(BGA)封装中 +“球珠”的一个位。电路板原理图显示了 GPIO 与外部硬件的连接关系。 +驱动可以编写成通用代码,以使板级启动代码可传递引脚配置数据给驱动。 + +片上系统 (SOC) 处理器对 GPIO 有很大的依赖。在某些情况下,每个 +非专用引脚都可配置为 GPIO,且大多数芯片都最少有一些 GPIO。 +可编程逻辑器件(类似 FPGA) 可以方便地提供 GPIO。像电源管理和 +音频编解码器这样的多功能芯片经常留有一些这样的引脚来帮助那些引脚 +匮乏的 SOC。同时还有通过 I2C 或 SPI 串行总线连接的“GPIO扩展器” +芯片。大多数 PC 的南桥有一些拥有 GPIO 能力的引脚 (只有BIOS +固件才知道如何使用他们)。 + +GPIO 的实际功能因系统而异。通常用法有: + + - 输出值可写 (高电平=1,低电平=0)。一些芯片也有如何驱动这些值的选项, + 例如只允许输出一个值、支持“线与”及其他取值类似的模式(值得注意的是 + “开漏”信号) + + - 输入值可读(1、0)。一些芯片支持引脚在配置为“输出”时回读,这对于类似 + “线与”的情况(以支持双向信号)是非常有用的。GPIO 控制器可能有输入 + 去毛刺/消抖逻辑,这有时需要软件控制。 + + - 输入通常可作为 IRQ 信号,一般是沿触发,但有时是电平触发。这样的 IRQ + 可能配置为系统唤醒事件,以将系统从低功耗状态下唤醒。 + + - 通常一个 GPIO 根据不同产品电路板的需求,可以配置为输入或输出,也有仅 + 支持单向的。 + + - 大部分 GPIO 可以在持有自旋锁时访问,但是通常由串行总线扩展的 GPIO + 不允许持有自旋锁。但某些系统也支持这种类型。 + +对于给定的电路板,每个 GPIO 都用于某个特定的目的,如监控 MMC/SD 卡的 +插入/移除、检测卡的写保护状态、驱动 LED、配置收发器、模拟串行总线、 +复位硬件看门狗、感知开关状态等等。 + + +GPIO 公约 +========= +注意,这个叫做“公约”,因为这不是强制性的,不遵循这个公约是无伤大雅的, +因为此时可移植性并不重要。GPIO 常用于板级特定的电路逻辑,甚至可能 +随着电路板的版本而改变,且不可能在不同走线的电路板上使用。仅有在少数 +功能上才具有可移植性,其他功能是平台特定。这也是由于“胶合”的逻辑造成的。 + +此外,这不需要任何的执行框架,只是一个接口。某个平台可能通过一个简单地 +访问芯片寄存器的内联函数来实现它,其他平台可能通过委托一系列不同的GPIO +控制器的抽象函数来实现它。(有一些可选的代码能支持这种策略的实现,本文档 +后面会介绍,但作为 GPIO 接口的客户端驱动程序必须与它的实现无关。) + +也就是说,如果在他们的平台上支持这个公约,驱动应尽可能的使用它。同时,平台 +必须在 Kconfig 中选择 ARCH_REQUIRE_GPIOLIB 或者 ARCH_WANT_OPTIONAL_GPIOLIB +选项。那些调用标准 GPIO 函数的驱动应该在 Kconfig 入口中声明依赖GENERIC_GPIO。 +当驱动包含文件: + + #include <linux/gpio.h> + +则 GPIO 函数是可用,无论是“真实代码”还是经优化过的语句。如果你遵守 +这个公约,当你的代码完成后,对其他的开发者来说会更容易看懂和维护。 + +注意,这些操作包含所用平台的 I/O 屏障代码,驱动无须显式地调用他们。 + + +标识 GPIO +--------- + +GPIO 是通过无符号整型来标识的,范围是 0 到 MAX_INT。保留“负”数 +用于其他目的,例如标识信号“在这个板子上不可用”或指示错误。未接触底层 +硬件的代码会忽略这些整数。 + +平台会定义这些整数的用法,且通常使用 #define 来定义 GPIO,这样 +板级特定的启动代码可以直接关联相应的原理图。相对来说,驱动应该仅使用 +启动代码传递过来的 GPIO 编号,使用 platform_data 保存板级特定 +引脚配置数据 (同时还有其他须要的板级特定数据),避免可能出现的问题。 + +例如一个平台使用编号 32-159 来标识 GPIO,而在另一个平台使用编号0-63 +标识一组 GPIO 控制器,64-79标识另一类 GPIO 控制器,且在一个含有 +FPGA 的特定板子上使用 80-95。编号不一定要连续,那些平台中,也可以 +使用编号2000-2063来标识一个 I2C 接口的 GPIO 扩展器中的 GPIO。 + +如果你要初始化一个带有无效 GPIO 编号的结构体,可以使用一些负编码 +(如"-EINVAL"),那将使其永远不会是有效。来测试这样一个结构体中的编号 +是否关联一个 GPIO,你可使用以下断言:: + + int gpio_is_valid(int number); + +如果编号不存在,则请求和释放 GPIO 的函数将拒绝执行相关操作(见下文)。 +其他编号也可能被拒绝,比如一个编号可能存在,但暂时在给定的电路上不可用。 + +一个平台是否支持多个 GPIO 控制器为平台特定的实现问题,就像是否可以 +在 GPIO 编号空间中有“空洞”和是否可以在运行时添加新的控制器一样。 +这些问题会影响其他事情,包括相邻的 GPIO 编号是否存在等。 + +使用 GPIO +--------- + +对于一个 GPIO,系统应该做的第一件事情就是通过 gpio_request() +函数分配它,见下文。 + +接下来是设置I/O方向,这通常是在板级启动代码中为所使用的 GPIO 设置 +platform_device 时完成:: + + /* 设置为输入或输出, 返回 0 或负的错误代码 */ + int gpio_direction_input(unsigned gpio); + int gpio_direction_output(unsigned gpio, int value); + +返回值为零代表成功,否则返回一个负的错误代码。这个返回值需要检查,因为 +get/set(获取/设置)函数调用没法返回错误,且有可能是配置错误。通常, +你应该在进程上下文中调用这些函数。然而,对于自旋锁安全的 GPIO,在板子 +启动的早期、进程启动前使用他们也是可以的。 + +对于作为输出的 GPIO,为其提供初始输出值,对于避免在系统启动期间出现 +信号毛刺是很有帮助的。 + +为了与传统的 GPIO 接口兼容, 在设置一个 GPIO 方向时,如果它还未被申请, +则隐含了申请那个 GPIO 的操作(见下文)。这种兼容性正在从可选的 gpiolib +框架中移除。 + +如果这个 GPIO 编码不存在,或者特定的 GPIO 不能用于那种模式,则方向 +设置可能失败。依赖启动固件来正确地设置方向通常是一个坏主意,因为它可能 +除了启动Linux,并没有做更多的验证工作。(同理, 板子的启动代码可能需要 +将这个复用的引脚设置为 GPIO,并正确地配置上拉/下拉电阻。) + + +访问自旋锁安全的 GPIO +--------------------- + +大多数 GPIO 控制器可以通过内存读/写指令来访问。这些指令不会休眠,可以 +安全地在硬(非线程)中断例程和类似的上下文中完成。 + +对于那些用 gpio_cansleep()测试总是返回失败的 GPIO(见下文),使用 +以下的函数访问:: + + /* GPIO 输入:返回零或非零 */ + int gpio_get_value(unsigned gpio); + + /* GPIO 输出 */ + void gpio_set_value(unsigned gpio, int value); + +GPIO值是布尔值,零表示低电平,非零表示高电平。当读取一个输出引脚的值时, +返回值应该是引脚上的值。这个值不总是和输出值相符,因为存在开漏输出信号和 +输出延迟问题。 + +以上的 get/set 函数无错误返回值,因为之前 gpio_direction_*()应已检查过 +其是否为“无效GPIO”。此外,还需要注意的是并不是所有平台都可以从输出引脚 +中读取数据,对于不能读取的引脚应总返回零。另外,对那些在原子上下文中无法 +安全访问的 GPIO (译者注:因为访问可能导致休眠)使用这些函数是不合适的 +(见下文)。 + +在 GPIO 编号(还有输出、值)为常数的情况下,鼓励通过平台特定的实现来优化 +这两个函数来访问 GPIO 值。这种情况(读写一个硬件寄存器)下只需要几条指令 +是很正常的,且无须自旋锁。这种优化函数比起那些在子程序上花费许多指令的 +函数可以使得模拟接口(译者注:例如 GPIO 模拟 I2C、1-wire 或 SPI)的 +应用(在空间和时间上都)更具效率。 + + +访问可能休眠的 GPIO +------------------- + +某些 GPIO 控制器必须通过基于总线(如 I2C 或 SPI)的消息访问。读或写这些 +GPIO 值的命令需要等待其信息排到队首才发送命令,再获得其反馈。期间需要 +休眠,这不能在 IRQ 例程(中断上下文)中执行。 + +支持此类 GPIO 的平台通过以下函数返回非零值来区分出这种 GPIO。(此函数需要 +一个之前通过 gpio_request 分配到的有效 GPIO 编号):: + + int gpio_cansleep(unsigned gpio); + +为了访问这种 GPIO,内核定义了一套不同的函数:: + + /* GPIO 输入:返回零或非零 ,可能会休眠 */ + int gpio_get_value_cansleep(unsigned gpio); + + /* GPIO 输出,可能会休眠 */ + void gpio_set_value_cansleep(unsigned gpio, int value); + + +访问这样的 GPIO 需要一个允许休眠的上下文,例如线程 IRQ 处理例程,并用以上的 +访问函数替换那些没有 cansleep()后缀的自旋锁安全访问函数。 + +除了这些访问函数可能休眠,且它们操作的 GPIO 不能在硬件 IRQ 处理例程中访问的 +事实,这些处理例程实际上和自旋锁安全的函数是一样的。 + +** 除此之外 ** 调用设置和配置此类 GPIO 的函数也必须在允许休眠的上下文中, +因为它们可能也需要访问 GPIO 控制器芯片 (这些设置函数通常在板级启动代码或者 +驱动探测/断开代码中,所以这是一个容易满足的约束条件。) :: + + gpio_direction_input() + gpio_direction_output() + gpio_request() + + ## gpio_request_one() + ## gpio_request_array() + ## gpio_free_array() + + gpio_free() + gpio_set_debounce() + + + +声明和释放 GPIO +---------------- + +为了有助于捕获系统配置错误,定义了两个函数:: + + /* 申请 GPIO, 返回 0 或负的错误代码. + * 非空标签可能有助于诊断. + */ + int gpio_request(unsigned gpio, const char *label); + + /* 释放之前声明的 GPIO */ + void gpio_free(unsigned gpio); + +将无效的 GPIO 编码传递给 gpio_request()会导致失败,申请一个已使用这个 +函数声明过的 GPIO 也会失败。gpio_request()的返回值必须检查。你应该在 +进程上下文中调用这些函数。然而,对于自旋锁安全的 GPIO,在板子启动的早期、 +进入进程之前是可以申请的。 + +这个函数完成两个基本的目标。一是标识那些实际上已作为 GPIO 使用的信号线, +这样便于更好地诊断;系统可能需要服务几百个可用的 GPIO,但是对于任何一个 +给定的电路板通常只有一些被使用。另一个目的是捕获冲突,查明错误:如两个或 +更多驱动错误地认为他们已经独占了某个信号线,或是错误地认为移除一个管理着 +某个已激活信号的驱动是安全的。也就是说,申请 GPIO 的作用类似一种锁机制。 + +某些平台可能也使用 GPIO 作为电源管理激活信号(例如通过关闭未使用芯片区和 +简单地关闭未使用时钟)。 + +对于 GPIO 使用引脚控制子系统已知的引脚,子系统应该被告知其使用情况; +一个 gpiolib 驱动的 .request()操作应调用 pinctrl_gpio_request(), +而 gpiolib 驱动的 .free()操作应调用 pinctrl_gpio_free()。引脚控制 +子系统允许 pinctrl_gpio_request()在某个引脚或引脚组以复用形式“属于” +一个设备时都成功返回。 + +任何须将 GPIO 信号导向适当引脚的引脚复用硬件的编程应该发生在 GPIO +驱动的 .direction_input()或 .direction_output()函数中,以及 +任何输出 GPIO 值的设置之后。这样可使从引脚特殊功能到 GPIO 的转换 +不会在引脚产生毛刺波形。有时当用一个 GPIO 实现其信号驱动一个非 GPIO +硬件模块的解决方案时,就需要这种机制。 + +某些平台允许部分或所有 GPIO 信号使用不同的引脚。类似的,GPIO 或引脚的 +其他方面也需要配置,如上拉/下拉。平台软件应该在对这些 GPIO 调用 +gpio_request()前将这类细节配置好,例如使用引脚控制子系统的映射表, +使得 GPIO 的用户无须关注这些细节。 + +还有一个值得注意的是在释放 GPIO 前,你必须停止使用它。 + + +注意:申请一个 GPIO 并没有以任何方式配置它,只不过标识那个 GPIO 处于使用 +状态。必须有另外的代码来处理引脚配置(如控制 GPIO 使用的引脚、上拉/下拉)。 +考虑到大多数情况下声明 GPIO 之后就会立即配置它们,所以定义了以下三个辅助函数:: + + /* 申请一个 GPIO 信号, 同时通过特定的'flags'初始化配置, + * 其他和 gpio_request()的参数和返回值相同 + * + */ + int gpio_request_one(unsigned gpio, unsigned long flags, const char *label); + + /* 在单个函数中申请多个 GPIO + */ + int gpio_request_array(struct gpio *array, size_t num); + + /* 在单个函数中释放多个 GPIO + */ + void gpio_free_array(struct gpio *array, size_t num); + +这里 'flags' 当前定义可指定以下属性: + + * GPIOF_DIR_IN - 配置方向为输入 + * GPIOF_DIR_OUT - 配置方向为输出 + + * GPIOF_INIT_LOW - 在作为输出时,初始值为低电平 + * GPIOF_INIT_HIGH - 在作为输出时,初始值为高电平 + * GPIOF_OPEN_DRAIN - gpio引脚为开漏信号 + * GPIOF_OPEN_SOURCE - gpio引脚为源极开路信号 + + * GPIOF_EXPORT_DIR_FIXED - 将 gpio 导出到 sysfs,并保持方向 + * GPIOF_EXPORT_DIR_CHANGEABLE - 同样是导出, 但允许改变方向 + +因为 GPIOF_INIT_* 仅有在配置为输出的时候才存在,所以有效的组合为: + + * GPIOF_IN - 配置为输入 + * GPIOF_OUT_INIT_LOW - 配置为输出,并初始化为低电平 + * GPIOF_OUT_INIT_HIGH - 配置为输出,并初始化为高电平 + +当设置 flag 为 GPIOF_OPEN_DRAIN 时,则假设引脚是开漏信号。这样的引脚 +将不会在输出模式下置1。这样的引脚需要连接上拉电阻。通过使能这个标志,gpio库 +将会在被要求输出模式下置1时将引脚变为输入状态来使引脚置高。引脚在输出模式下 +通过置0使其输出低电平。 + +当设置 flag 为 GPIOF_OPEN_SOURCE 时,则假设引脚为源极开路信号。这样的引脚 +将不会在输出模式下置0。这样的引脚需要连接下拉电阻。通过使能这个标志,gpio库 +将会在被要求输出模式下置0时将引脚变为输入状态来使引脚置低。引脚在输出模式下 +通过置1使其输出高电平。 + +将来这些标志可能扩展到支持更多的属性。 + +更进一步,为了更简单地声明/释放多个 GPIO,'struct gpio'被引进来封装所有 +这三个领域:: + + struct gpio { + unsigned gpio; + unsigned long flags; + const char *label; + }; + +一个典型的用例:: + + static struct gpio leds_gpios[] = { + { 32, GPIOF_OUT_INIT_HIGH, "Power LED" }, /* 默认开启 */ + { 33, GPIOF_OUT_INIT_LOW, "Green LED" }, /* 默认关闭 */ + { 34, GPIOF_OUT_INIT_LOW, "Red LED" }, /* 默认关闭 */ + { 35, GPIOF_OUT_INIT_LOW, "Blue LED" }, /* 默认关闭 */ + { ... }, + }; + + err = gpio_request_one(31, GPIOF_IN, "Reset Button"); + if (err) + ... + + err = gpio_request_array(leds_gpios, ARRAY_SIZE(leds_gpios)); + if (err) + ... + + gpio_free_array(leds_gpios, ARRAY_SIZE(leds_gpios)); + + +GPIO 映射到 IRQ +---------------- + +GPIO 编号是无符号整数;IRQ 编号也是。这些构成了两个逻辑上不同的命名空间 +(GPIO 0 不一定使用 IRQ 0)。你可以通过以下函数在它们之间实现映射:: + + /* 映射 GPIO 编号到 IRQ 编号 */ + int gpio_to_irq(unsigned gpio); + + /* 映射 IRQ 编号到 GPIO 编号 (尽量避免使用) */ + int irq_to_gpio(unsigned irq); + +它们的返回值为对应命名空间的相关编号,或是负的错误代码(如果无法映射)。 +(例如,某些 GPIO 无法做为 IRQ 使用。)以下的编号错误是未经检测的:使用一个 +未通过 gpio_direction_input()配置为输入的 GPIO 编号,或者使用一个 +并非来源于gpio_to_irq()的 IRQ 编号。 + +这两个映射函数可能会在信号编号的加减计算过程上花些时间。它们不可休眠。 + +gpio_to_irq()返回的非错误值可以传递给 request_irq()或者 free_irq()。 +它们通常通过板级特定的初始化代码存放到平台设备的 IRQ 资源中。注意:IRQ +触发选项是 IRQ 接口的一部分,如 IRQF_TRIGGER_FALLING,系统唤醒能力 +也是如此。 + +irq_to_gpio()返回的非错误值大多数通常可以被 gpio_get_value()所使用, +比如在 IRQ 是沿触发时初始化或更新驱动状态。注意某些平台不支持反映射,所以 +你应该尽量避免使用它。 + + +模拟开漏信号 +------------ + +有时在只有低电平信号作为实际驱动结果(译者注:多个输出连接于一点,逻辑电平 +结果为所有输出的逻辑与)的时候,共享的信号线需要使用“开漏”信号。(该术语 +适用于 CMOS 管;而 TTL 用“集电极开路”。)一个上拉电阻使信号为高电平。这 +有时被称为“线与”。实际上,从负逻辑(低电平为真)的角度来看,这是一个“线或”。 + +一个开漏信号的常见例子是共享的低电平使能 IRQ 信号线。此外,有时双向数据总线 +信号也使用漏极开路信号。 + +某些 GPIO 控制器直接支持开漏输出,还有许多不支持。当你需要开漏信号,但 +硬件又不直接支持的时候,一个常用的方法是用任何即可作输入也可作输出的 GPIO +引脚来模拟: + + LOW: gpio_direction_output(gpio, 0) ... 这代码驱动信号并覆盖 + 上拉配置。 + + HIGH: gpio_direction_input(gpio) ... 这代码关闭输出,所以上拉电阻 + (或其他的一些器件)控制了信号。 + +如果你将信号线“驱动”为高电平,但是 gpio_get_value(gpio)报告了一个 +低电平(在适当的上升时间后),你就可以知道是其他的一些组件将共享信号线拉低了。 +这不一定是错误的。一个常见的例子就是 I2C 时钟的延长:一个需要较慢时钟的 +从设备延迟 SCK 的上升沿,而 I2C 主设备相应地调整其信号传输速率。 + +GPIO控制器和引脚控制子系统 +-------------------------- + +SOC上的GPIO控制器可能与引脚控制子系统紧密结合,即引脚可以与可选的gpio功 +能一起被其他功能使用。我们已经涵盖了这样的情况,例如一个GPIO控制器需要保 +留一个引脚或通过调用以下任何一个引脚来设置其方向:: + + pinctrl_gpio_request() + pinctrl_gpio_free() + pinctrl_gpio_direction_input() + pinctrl_gpio_direction_output() + +但是,引脚控制子系统是如何将GPIO号码(这是一个全局事项)与某个引脚控制器 +上的某个引脚交叉关联的? + +这是通过注册引脚的“范围”来实现的,这基本上是交叉参考表。这些描述是在 +Documentation/driver-api/pin-control.rst + +虽然引脚分配完全由引脚控制子系统管理,但gpio(在gpiolib下)仍由gpio驱动 +维护。可能发生的情况是,SoC中的不同引脚范围由不同的gpio驱动器管理。 + +这使得在调用 "pinctrl_gpio_request" 之前,让gpio驱动向pin ctrl子系 +统宣布它们的引脚范围是合理的,以便在使用任何gpio之前要求引脚控制子系统准 +备相应的引脚。 + +为此,gpio控制器可以用引脚控制子系统注册其引脚范围。目前有两种方法:有或 +无DT。 + +关于对DT的支持,请参考 Documentation/devicetree/bindings/gpio/gpio.txt. + +对于非DT支持,用户可以用适当的参数调用gpiochip_add_pin_range(),将一 +系列的gpio引脚注册到引脚控制驱动上。为此,必须将引脚控制设备的名称字符串 +作为参数之一传给这个程序。 + + +这些公约忽略了什么? +==================== + +这些公约忽略的最大一件事就是引脚复用,因为这属于高度芯片特定的属性且 +没有可移植性。某个平台可能不需要明确的复用信息;有的对于任意给定的引脚 +可能只有两个功能选项;有的可能每个引脚有八个功能选项;有的可能可以将 +几个引脚中的任何一个作为给定的 GPIO。(是的,这些例子都来自于当前运行 +Linux 的系统。) + +在某些系统中,与引脚复用相关的是配置和使能集成的上、下拉模式。并不是所有 +平台都支持这种模式,或者不会以相同的方式来支持这种模式;且任何给定的电路板 +可能使用外置的上拉(或下拉)电阻,这时芯片上的就不应该使用。(当一个电路需要 +5kOhm 的拉动电阻,芯片上的 100 kOhm 电阻就不能做到。)同样的,驱动能力 +(2 mA vs 20 mA)和电压(1.8V vs 3.3V)是平台特定问题,就像模型一样在 +可配置引脚和 GPIO 之间(没)有一一对应的关系。 + +还有其他一些系统特定的机制没有在这里指出,例如上述的输入去毛刺和线与输出 +选项。硬件可能支持批量读或写 GPIO,但是那一般是配置相关的:对于处于同一 +块区(bank)的GPIO。(GPIO 通常以 16 或 32 个组成一个区块,一个给定的 +片上系统一般有几个这样的区块。)某些系统可以通过输出 GPIO 触发 IRQ, +或者从并非以 GPIO 管理的引脚取值。这些机制的相关代码没有必要具有可移植性。 + +当前,动态定义 GPIO 并不是标准的,例如作为配置一个带有某些 GPIO 扩展器的 +附加电路板的副作用。 + +GPIO 实现者的框架(可选) +========================= + +前面提到了,有一个可选的实现框架,让平台使用相同的编程接口,更加简单地支持 +不同种类的 GPIO 控制器。这个框架称为"gpiolib"。 + +作为一个辅助调试功能,如果 debugfs 可用,就会有一个 /sys/kernel/debug/gpio +文件。通过这个框架,它可以列出所有注册的控制器,以及当前正在使用中的 GPIO +的状态。 + + +控制器驱动: gpio_chip +--------------------- + +在框架中每个 GPIO 控制器都包装为一个 "struct gpio_chip",他包含了 +该类型的每个控制器的常用信息: + + - 设置 GPIO 方向的方法 + - 用于访问 GPIO 值的方法 + - 告知调用其方法是否可能休眠的标志 + - 可选的 debugfs 信息导出方法 (显示类似上拉配置一样的额外状态) + - 诊断标签 + +也包含了来自 device.platform_data 的每个实例的数据:它第一个 GPIO 的 +编号和它可用的 GPIO 的数量。 + +实现 gpio_chip 的代码应支持多控制器实例,这可能使用驱动模型。那些代码要 +配置每个 gpio_chip,并发起gpiochip_add()。卸载一个 GPIO 控制器很少见, +但在必要的时候可以使用 gpiochip_remove()。 + +大部分 gpio_chip 是一个实例特定结构体的一部分,而并不将 GPIO 接口单独 +暴露出来,比如编址、电源管理等。类似编解码器这样的芯片会有复杂的非 GPIO +状态。 + +任何一个 debugfs 信息导出方法通常应该忽略还未申请作为 GPIO 的信号线。 +他们可以使用 gpiochip_is_requested()测试,当这个 GPIO 已经申请过了 +就返回相关的标签,否则返回 NULL。 + + +平台支持 +-------- + +为了支持这个框架,一个平台的 Kconfig 文件将会 "select"(选择) +ARCH_REQUIRE_GPIOLIB 或 ARCH_WANT_OPTIONAL_GPIOLIB,并让它的 +<asm/gpio.h> 包含 <asm-generic/gpio.h>,同时定义三个方法: +gpio_get_value()、gpio_set_value()和 gpio_cansleep()。 + +它也应提供一个 ARCH_NR_GPIOS 的定义值,这样可以更好地反映该平台 GPIO +的实际数量,节省静态表的空间。(这个定义值应该包含片上系统内建 GPIO 和 +GPIO 扩展器中的数据。) + +ARCH_REQUIRE_GPIOLIB 意味着 gpiolib 核心在这个构架中将总是编译进内核。 + +ARCH_WANT_OPTIONAL_GPIOLIB 意味着 gpiolib 核心默认关闭,且用户可以 +使能它,并将其编译进内核(可选)。 + +如果这些选项都没被选择,该平台就不通过 GPIO-lib 支持 GPIO,且代码不可以 +被用户使能。 + +以下这些方法的实现可以直接使用框架代码,并总是通过 gpio_chip 调度:: + + #define gpio_get_value __gpio_get_value + #define gpio_set_value __gpio_set_value + #define gpio_cansleep __gpio_cansleep + +这些定义可以用更理想的实现方法替代,那就是使用经过逻辑优化的内联函数来访问 +基于特定片上系统的 GPIO。例如,若引用的 GPIO (寄存器位偏移)是常量“12”, +读取或设置它可能只需少则两或三个指令,且不会休眠。当这样的优化无法实现时, +那些函数必须使用框架提供的代码,那就至少要几十条指令才可以实现。对于用 GPIO +模拟的 I/O 接口, 如此精简指令是很有意义的。 + +对于片上系统,平台特定代码为片上 GPIO 每个区(bank)定义并注册 gpio_chip +实例。那些 GPIO 应该根据芯片厂商的文档进行编码/标签,并直接和电路板原理图 +对应。他们应该开始于零并终止于平台特定的限制。这些 GPIO(代码)通常从 +arch_initcall()或者更早的地方集成进平台初始化代码,使这些 GPIO 总是可用, +且他们通常可以作为 IRQ 使用。 + +板级支持 +-------- + +对于外部 GPIO 控制器(例如 I2C 或 SPI 扩展器、专用芯片、多功能器件、FPGA +或 CPLD),大多数常用板级特定代码都可以注册控制器设备,并保证他们的驱动知道 +gpiochip_add()所使用的 GPIO 编号。他们的起始编号通常跟在平台特定的 GPIO +编号之后。 + +例如板级启动代码应该创建结构体指明芯片公开的 GPIO 范围,并使用 platform_data +将其传递给每个 GPIO 扩展器芯片。然后芯片驱动中的 probe()例程可以将这个 +数据传递给 gpiochip_add()。 + +初始化顺序很重要。例如,如果一个设备依赖基于 I2C 的(扩展)GPIO,那么它的 +probe()例程就应该在那个 GPIO 有效以后才可以被调用。这意味着设备应该在 +GPIO 可以工作之后才可被注册。解决这类依赖的的一种方法是让这种 gpio_chip +控制器向板级特定代码提供 setup()和 teardown()回调函数。一旦所有必须的 +资源可用之后,这些板级特定的回调函数将会注册设备,并可以在这些 GPIO 控制器 +设备变成无效时移除它们。 + + +用户空间的 Sysfs 接口(可选) +============================= + +使用“gpiolib”实现框架的平台可以选择配置一个 GPIO 的 sysfs 用户接口。 +这不同于 debugfs 接口,因为它提供的是对 GPIO方向和值的控制,而不只显示 +一个GPIO 的状态摘要。此外,它可以出现在没有调试支持的产品级系统中。 + +例如,通过适当的系统硬件文档,用户空间可以知道 GIOP #23 控制 Flash +存储器的写保护(用于保护其中 Bootloader 分区)。产品的系统升级可能需要 +临时解除这个保护:首先导入一个 GPIO,改变其输出状态,然后在重新使能写保护 +前升级代码。通常情况下,GPIO #23 是不会被触及的,并且内核也不需要知道他。 + +根据适当的硬件文档,某些系统的用户空间 GPIO 可以用于确定系统配置数据, +这些数据是标准内核不知道的。在某些任务中,简单的用户空间 GPIO 驱动可能是 +系统真正需要的。 + +注意:标准内核驱动中已经存在通用的“LED 和按键”GPIO 任务,分别是: +"leds-gpio" 和 "gpio_keys"。请使用这些来替代直接访问 GPIO,因为集成在 +内核框架中的这类驱动比你在用户空间的代码更好。 + + +Sysfs 中的路径 +-------------- + +在/sys/class/gpio 中有 3 类入口: + + - 用于在用户空间控制 GPIO 的控制接口; + + - GPIOs 本身;以及 + + - GPIO 控制器 ("gpio_chip" 实例)。 + +除了这些标准的文件,还包含“device”符号链接。 + +控制接口是只写的: + + /sys/class/gpio/ + + "export" ... 用户空间可以通过写其编号到这个文件,要求内核导出 + 一个 GPIO 的控制到用户空间。 + + 例如: 如果内核代码没有申请 GPIO #19,"echo 19 > export" + 将会为 GPIO #19 创建一个 "gpio19" 节点。 + + "unexport" ... 导出到用户空间的逆操作。 + + 例如: "echo 19 > unexport" 将会移除使用"export"文件导出的 + "gpio19" 节点。 + +GPIO 信号的路径类似 /sys/class/gpio/gpio42/ (对于 GPIO #42 来说), +并有如下的读/写属性: + + /sys/class/gpio/gpioN/ + + "direction" ... 读取得到 "in" 或 "out"。这个值通常运行写入。 + 写入"out" 时,其引脚的默认输出为低电平。为了确保无故障运行, + "low" 或 "high" 的电平值应该写入 GPIO 的配置,作为初始输出值。 + + 注意:如果内核不支持改变 GPIO 的方向,或者在导出时内核代码没有 + 明确允许用户空间可以重新配置 GPIO 方向,那么这个属性将不存在。 + + "value" ... 读取得到 0 (低电平) 或 1 (高电平)。如果 GPIO 配置为 + 输出,这个值允许写操作。任何非零值都以高电平看待。 + + 如果引脚可以配置为中断信号,且如果已经配置了产生中断的模式 + (见"edge"的描述),你可以对这个文件使用轮询操作(poll(2)), + 且轮询操作会在任何中断触发时返回。如果你使用轮询操作(poll(2)), + 请在 events 中设置 POLLPRI 和 POLLERR。如果你使用轮询操作 + (select(2)),请在 exceptfds 设置你期望的文件描述符。在 + 轮询操作(poll(2))返回之后,既可以通过 lseek(2)操作读取 + sysfs 文件的开始部分,也可以关闭这个文件并重新打开它来读取数据。 + + "edge" ... 读取得到“none”、“rising”、“falling”或者“both”。 + 将这些字符串写入这个文件可以选择沿触发模式,会使得轮询操作 + (select(2))在"value"文件中返回。 + + 这个文件仅有在这个引脚可以配置为可产生中断输入引脚时,才存在。 + + "active_low" ... 读取得到 0 (假) 或 1 (真)。写入任何非零值可以 + 翻转这个属性的(读写)值。已存在或之后通过"edge"属性设置了"rising" + 和 "falling" 沿触发模式的轮询操作(poll(2))将会遵循这个设置。 + +GPIO 控制器的路径类似 /sys/class/gpio/gpiochip42/ (对于从#42 GPIO +开始实现控制的控制器),并有着以下只读属性: + + /sys/class/gpio/gpiochipN/ + + "base" ... 与以上的 N 相同,代表此芯片管理的第一个 GPIO 的编号 + + "label" ... 用于诊断 (并不总是只有唯一值) + + "ngpio" ... 此控制器所管理的 GPIO 数量(而 GPIO 编号从 N 到 + N + ngpio - 1) + +大多数情况下,电路板的文档应当标明每个 GPIO 的使用目的。但是那些编号并不总是 +固定的,例如在扩展卡上的 GPIO会根据所使用的主板或所在堆叠架构中其他的板子而 +有所不同。在这种情况下,你可能需要使用 gpiochip 节点(尽可能地结合电路图)来 +确定给定信号所用的 GPIO 编号。 + + +从内核代码中导出 +---------------- + +内核代码可以明确地管理那些已通过 gpio_request()申请的 GPIO 的导出:: + + /* 导出 GPIO 到用户空间 */ + int gpio_export(unsigned gpio, bool direction_may_change); + + /* gpio_export()的逆操作 */ + void gpio_unexport(); + + /* 创建一个 sysfs 连接到已导出的 GPIO 节点 */ + int gpio_export_link(struct device *dev, const char *name, + unsigned gpio) + +在一个内核驱动申请一个 GPIO 之后,它可以通过 gpio_export()使其在 sysfs +接口中可见。该驱动可以控制信号方向是否可修改。这有助于防止用户空间代码无意间 +破坏重要的系统状态。 + +这个明确的导出有助于(通过使某些实验更容易来)调试,也可以提供一个始终存在的接口, +与文档配合作为板级支持包的一部分。 + +在 GPIO 被导出之后,gpio_export_link()允许在 sysfs 文件系统的任何地方 +创建一个到这个 GPIO sysfs 节点的符号链接。这样驱动就可以通过一个描述性的 +名字,在 sysfs 中他们所拥有的设备下提供一个(到这个 GPIO sysfs 节点的)接口。 + + +API参考 +======= + +本节中列出的函数已被废弃。在新的代码中应该使用基于GPIO描述符的API。 diff --git a/Documentation/translations/zh_CN/driver-api/index.rst b/Documentation/translations/zh_CN/driver-api/index.rst new file mode 100644 index 000000000..ba354e1f4 --- /dev/null +++ b/Documentation/translations/zh_CN/driver-api/index.rst @@ -0,0 +1,132 @@ +.. SPDX-License-Identifier: GPL-2.0 + +.. include:: ../disclaimer-zh_CN.rst + +:Original: Documentation/driver-api/index.rst + +:翻译: + + 司延腾 Yanteng Si <siyanteng@loongson.cn> + +:校译: + +======================== +Linux驱动实现者的API指南 +======================== + +内核提供了各种各样的接口来支持设备驱动的开发。这份文档只是对其中一些接口进行了 +一定程度的整理——希望随着时间的推移,它能变得更好!可用的小节可以在下面看到。 + +.. class:: toc-title + + 目录列表: + +.. toctree:: + :maxdepth: 2 + + gpio/index + io_ordering + +Todolist: + +* driver-model/index +* basics +* infrastructure +* ioctl +* early-userspace/index +* pm/index +* clk +* device-io +* dma-buf +* device_link +* component +* message-based +* infiniband +* aperture +* frame-buffer +* regulator +* reset +* iio/index +* input +* usb/index +* firewire +* pci/index +* cxl/index +* spi +* i2c +* ipmb +* ipmi +* i3c/index +* interconnect +* devfreq +* hsi +* edac +* scsi +* libata +* target +* mailbox +* mtdnand +* miscellaneous +* mei/index +* mtd/index +* mmc/index +* nvdimm/index +* w1 +* rapidio/index +* s390-drivers +* vme +* 80211/index +* uio-howto +* firmware/index +* pin-control +* md/index +* media/index +* misc_devices +* nfc/index +* dmaengine/index +* slimbus +* soundwire/index +* thermal/index +* fpga/index +* acpi/index +* auxiliary_bus +* backlight/lp855x-driver.rst +* connector +* console +* dcdbas +* eisa +* isa +* isapnp +* io-mapping +* generic-counter +* memory-devices/index +* men-chameleon-bus +* ntb +* nvmem +* parport-lowlevel +* pps +* ptp +* phy/index +* pwm +* pldmfw/index +* rfkill +* serial/index +* sm501 +* surface_aggregator/index +* switchtec +* sync_file +* tty/index +* vfio-mediated-device +* vfio +* vfio-pci-device-specific-driver-acceptance +* xilinx/index +* xillybus +* zorro +* hte/index + +.. only:: subproject and html + + Indices + ======= + + * :ref:`genindex` diff --git a/Documentation/translations/zh_CN/driver-api/io_ordering.rst b/Documentation/translations/zh_CN/driver-api/io_ordering.rst new file mode 100644 index 000000000..4dbfa4ce9 --- /dev/null +++ b/Documentation/translations/zh_CN/driver-api/io_ordering.rst @@ -0,0 +1,60 @@ +.. SPDX-License-Identifier: GPL-2.0 + +.. include:: ../disclaimer-zh_CN.rst + +:Original: Documentation/driver-api/io_ordering.rst + +:翻译: + + 林永听 Lin Yongting <linyongting@gmail.com> + 司延腾 Yanteng Si <siyanteng@loongson.cn> + +:校译: + +=========================== +对内存映射地址的I/O写入排序 +=========================== + +在某些平台上,所谓的内存映射I/O是弱顺序。在这些平台上,驱动开发者有责任 +保证I/O内存映射地址的写操作按程序图意的顺序达到设备。通常读取一个“安全” +设备寄存器或桥寄存器,触发IO芯片清刷未处理的写操作到达设备后才处理读操作, +而达到保证目的。驱动程序通常在spinlock保护的临界区退出之前使用这种技术。 +这也可以保证后面的写操作只在前面的写操作之后到达设备(这非常类似于内存 +屏障操作,mb(),不过仅适用于I/O)。 + +假设一个设备驱动程的具体例子:: + + ... + CPU A: spin_lock_irqsave(&dev_lock, flags) + CPU A: val = readl(my_status); + CPU A: ... + CPU A: writel(newval, ring_ptr); + CPU A: spin_unlock_irqrestore(&dev_lock, flags) + ... + CPU B: spin_lock_irqsave(&dev_lock, flags) + CPU B: val = readl(my_status); + CPU B: ... + CPU B: writel(newval2, ring_ptr); + CPU B: spin_unlock_irqrestore(&dev_lock, flags) + ... + +上述例子中,设备可能会先接收到newval2的值,然后接收到newval的值,问题就 +发生了。不过很容易通过下面方法来修复:: + + ... + CPU A: spin_lock_irqsave(&dev_lock, flags) + CPU A: val = readl(my_status); + CPU A: ... + CPU A: writel(newval, ring_ptr); + CPU A: (void)readl(safe_register); /* 配置寄存器?*/ + CPU A: spin_unlock_irqrestore(&dev_lock, flags) + ... + CPU B: spin_lock_irqsave(&dev_lock, flags) + CPU B: val = readl(my_status); + CPU B: ... + CPU B: writel(newval2, ring_ptr); + CPU B: (void)readl(safe_register); /* 配置寄存器?*/ + CPU B: spin_unlock_irqrestore(&dev_lock, flags) + +在解决方案中,读取safe_register寄存器,触发IO芯片清刷未处理的写操作, +再处理后面的读操作,防止引发数据不一致问题。 diff --git a/Documentation/translations/zh_CN/filesystems/debugfs.rst b/Documentation/translations/zh_CN/filesystems/debugfs.rst new file mode 100644 index 000000000..4981a82dd --- /dev/null +++ b/Documentation/translations/zh_CN/filesystems/debugfs.rst @@ -0,0 +1,221 @@ +.. SPDX-License-Identifier: GPL-2.0 + +.. include:: ../disclaimer-zh_CN.rst + +:Original: Documentation/filesystems/debugfs.rst + +======= +Debugfs +======= + +译者 +:: + + 中文版维护者: 罗楚成 Chucheng Luo <luochucheng@vivo.com> + 中文版翻译者: 罗楚成 Chucheng Luo <luochucheng@vivo.com> + 中文版校译者: 罗楚成 Chucheng Luo <luochucheng@vivo.com> + + + +版权所有2020 罗楚成 <luochucheng@vivo.com> + + +Debugfs是内核开发人员在用户空间获取信息的简单方法。与/proc不同,proc只提供进程 +信息。也不像sysfs,具有严格的“每个文件一个值“的规则。debugfs根本没有规则,开发 +人员可以在这里放置他们想要的任何信息。debugfs文件系统也不能用作稳定的ABI接口。 +从理论上讲,debugfs导出文件的时候没有任何约束。但是[1]实际情况并不总是那么 +简单。即使是debugfs接口,也最好根据需要进行设计,并尽量保持接口不变。 + + +Debugfs通常使用以下命令安装:: + + mount -t debugfs none /sys/kernel/debug + +(或等效的/etc/fstab行)。 +debugfs根目录默认仅可由root用户访问。要更改对文件树的访问,请使用“ uid”,“ gid” +和“ mode”挂载选项。请注意,debugfs API仅按照GPL协议导出到模块。 + +使用debugfs的代码应包含<linux/debugfs.h>。然后,首先是创建至少一个目录来保存 +一组debugfs文件:: + + struct dentry *debugfs_create_dir(const char *name, struct dentry *parent); + +如果成功,此调用将在指定的父目录下创建一个名为name的目录。如果parent参数为空, +则会在debugfs根目录中创建。创建目录成功时,返回值是一个指向dentry结构体的指针。 +该dentry结构体的指针可用于在目录中创建文件(以及最后将其清理干净)。ERR_PTR +(-ERROR)返回值表明出错。如果返回ERR_PTR(-ENODEV),则表明内核是在没有debugfs +支持的情况下构建的,并且下述函数都不会起作用。 + +在debugfs目录中创建文件的最通用方法是:: + + struct dentry *debugfs_create_file(const char *name, umode_t mode, + struct dentry *parent, void *data, + const struct file_operations *fops); + +在这里,name是要创建的文件的名称,mode描述了访问文件应具有的权限,parent指向 +应该保存文件的目录,data将存储在产生的inode结构体的i_private字段中,而fops是 +一组文件操作函数,这些函数中实现文件操作的具体行为。至少,read()和/或 +write()操作应提供;其他可以根据需要包括在内。同样的,返回值将是指向创建文件 +的dentry指针,错误时返回ERR_PTR(-ERROR),系统不支持debugfs时返回值为ERR_PTR +(-ENODEV)。创建一个初始大小的文件,可以使用以下函数代替:: + + struct dentry *debugfs_create_file_size(const char *name, umode_t mode, + struct dentry *parent, void *data, + const struct file_operations *fops, + loff_t file_size); + +file_size是初始文件大小。其他参数跟函数debugfs_create_file的相同。 + +在许多情况下,没必要自己去创建一组文件操作;对于一些简单的情况,debugfs代码提供 +了许多帮助函数。包含单个整数值的文件可以使用以下任何一项创建:: + + void debugfs_create_u8(const char *name, umode_t mode, + struct dentry *parent, u8 *value); + void debugfs_create_u16(const char *name, umode_t mode, + struct dentry *parent, u16 *value); + struct dentry *debugfs_create_u32(const char *name, umode_t mode, + struct dentry *parent, u32 *value); + void debugfs_create_u64(const char *name, umode_t mode, + struct dentry *parent, u64 *value); + +这些文件支持读取和写入给定值。如果某个文件不支持写入,只需根据需要设置mode +参数位。这些文件中的值以十进制表示;如果需要使用十六进制,可以使用以下函数 +替代:: + + void debugfs_create_x8(const char *name, umode_t mode, + struct dentry *parent, u8 *value); + void debugfs_create_x16(const char *name, umode_t mode, + struct dentry *parent, u16 *value); + void debugfs_create_x32(const char *name, umode_t mode, + struct dentry *parent, u32 *value); + void debugfs_create_x64(const char *name, umode_t mode, + struct dentry *parent, u64 *value); + +这些功能只有在开发人员知道导出值的大小的时候才有用。某些数据类型在不同的架构上 +有不同的宽度,这样会使情况变得有些复杂。在这种特殊情况下可以使用以下函数:: + + void debugfs_create_size_t(const char *name, umode_t mode, + struct dentry *parent, size_t *value); + +不出所料,此函数将创建一个debugfs文件来表示类型为size_t的变量。 + +同样地,也有导出无符号长整型变量的函数,分别以十进制和十六进制表示如下:: + + struct dentry *debugfs_create_ulong(const char *name, umode_t mode, + struct dentry *parent, + unsigned long *value); + void debugfs_create_xul(const char *name, umode_t mode, + struct dentry *parent, unsigned long *value); + +布尔值可以通过以下方式放置在debugfs中:: + + struct dentry *debugfs_create_bool(const char *name, umode_t mode, + struct dentry *parent, bool *value); + + +读取结果文件将产生Y(对于非零值)或N,后跟换行符写入的时候,它只接受大写或小写 +值或1或0。任何其他输入将被忽略。 + +同样,atomic_t类型的值也可以放置在debugfs中:: + + void debugfs_create_atomic_t(const char *name, umode_t mode, + struct dentry *parent, atomic_t *value) + +读取此文件将获得atomic_t值,写入此文件将设置atomic_t值。 + +另一个选择是通过以下结构体和函数导出一个任意二进制数据块:: + + struct debugfs_blob_wrapper { + void *data; + unsigned long size; + }; + + struct dentry *debugfs_create_blob(const char *name, umode_t mode, + struct dentry *parent, + struct debugfs_blob_wrapper *blob); + +读取此文件将返回由指针指向debugfs_blob_wrapper结构体的数据。一些驱动使用“blobs” +作为一种返回几行(静态)格式化文本的简单方法。这个函数可用于导出二进制信息,但 +似乎在主线中没有任何代码这样做。请注意,使用debugfs_create_blob()命令创建的 +所有文件是只读的。 + +如果您要转储一个寄存器块(在开发过程中经常会这么做,但是这样的调试代码很少上传 +到主线中。Debugfs提供两个函数:一个用于创建仅寄存器文件,另一个把一个寄存器块 +插入一个顺序文件中:: + + struct debugfs_reg32 { + char *name; + unsigned long offset; + }; + + struct debugfs_regset32 { + struct debugfs_reg32 *regs; + int nregs; + void __iomem *base; + }; + + struct dentry *debugfs_create_regset32(const char *name, umode_t mode, + struct dentry *parent, + struct debugfs_regset32 *regset); + + void debugfs_print_regs32(struct seq_file *s, struct debugfs_reg32 *regs, + int nregs, void __iomem *base, char *prefix); + +“base”参数可能为0,但您可能需要使用__stringify构建reg32数组,实际上有许多寄存器 +名称(宏)是寄存器块在基址上的字节偏移量。 + +如果要在debugfs中转储u32数组,可以使用以下函数创建文件:: + + void debugfs_create_u32_array(const char *name, umode_t mode, + struct dentry *parent, + u32 *array, u32 elements); + +“array”参数提供数据,而“elements”参数为数组中元素的数量。注意:数组创建后,数组 +大小无法更改。 + +有一个函数来创建与设备相关的seq_file:: + + struct dentry *debugfs_create_devm_seqfile(struct device *dev, + const char *name, + struct dentry *parent, + int (*read_fn)(struct seq_file *s, + void *data)); + +“dev”参数是与此debugfs文件相关的设备,并且“read_fn”是一个函数指针,这个函数在 +打印seq_file内容的时候被回调。 + +还有一些其他的面向目录的函数:: + + struct dentry *debugfs_rename(struct dentry *old_dir, + struct dentry *old_dentry, + struct dentry *new_dir, + const char *new_name); + + struct dentry *debugfs_create_symlink(const char *name, + struct dentry *parent, + const char *target); + +调用debugfs_rename()将为现有的debugfs文件重命名,可能同时切换目录。 new_name +函数调用之前不能存在;返回值为old_dentry,其中包含更新的信息。可以使用 +debugfs_create_symlink()创建符号链接。 + +所有debugfs用户必须考虑的一件事是: + +debugfs不会自动清除在其中创建的任何目录。如果一个模块在不显式删除debugfs目录的 +情况下卸载模块,结果将会遗留很多野指针,从而导致系统不稳定。因此,所有debugfs +用户-至少是那些可以作为模块构建的用户-必须做模块卸载的时候准备删除在此创建的 +所有文件和目录。一份文件可以通过以下方式删除:: + + void debugfs_remove(struct dentry *dentry); + +dentry值可以为NULL或错误值,在这种情况下,不会有任何文件被删除。 + +很久以前,内核开发者使用debugfs时需要记录他们创建的每个dentry指针,以便最后所有 +文件都可以被清理掉。但是,现在debugfs用户能调用以下函数递归清除之前创建的文件:: + + void debugfs_remove_recursive(struct dentry *dentry); + +如果将对应顶层目录的dentry传递给以上函数,则该目录下的整个层次结构将会被删除。 + +注释: +[1] http://lwn.net/Articles/309298/ diff --git a/Documentation/translations/zh_CN/filesystems/index.rst b/Documentation/translations/zh_CN/filesystems/index.rst new file mode 100644 index 000000000..9f2a8b003 --- /dev/null +++ b/Documentation/translations/zh_CN/filesystems/index.rst @@ -0,0 +1,29 @@ +.. SPDX-License-Identifier: GPL-2.0 + +.. include:: ../disclaimer-zh_CN.rst + +:Original: :ref:`Documentation/filesystems/index.rst <filesystems_index>` +:Translator: Wang Wenhu <wenhu.wang@vivo.com> + +.. _cn_filesystems_index: + +======================== +Linux Kernel中的文件系统 +======================== + +这份正在开发的手册或许在未来某个辉煌的日子里以易懂的形式将Linux虚拟\ +文件系统(VFS)层以及基于其上的各种文件系统如何工作呈现给大家。当前\ +可以看到下面的内容。 + +文件系统 +======== + +文件系统实现文档。 + +.. toctree:: + :maxdepth: 2 + + virtiofs + debugfs + tmpfs + diff --git a/Documentation/translations/zh_CN/filesystems/sysfs.txt b/Documentation/translations/zh_CN/filesystems/sysfs.txt new file mode 100644 index 000000000..046cc1d52 --- /dev/null +++ b/Documentation/translations/zh_CN/filesystems/sysfs.txt @@ -0,0 +1,373 @@ +Chinese translated version of Documentation/filesystems/sysfs.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: Patrick Mochel <mochel@osdl.org> + Mike Murphy <mamurph@cs.clemson.edu> +Chinese maintainer: Fu Wei <tekkamanninja@gmail.com> +--------------------------------------------------------------------- +Documentation/filesystems/sysfs.rst 的中文翻译 + +如果想评论或更新本文的内容,请直接联系原文档的维护者。如果你使用英文 +交流有困难的话,也可以向中文版维护者求助。如果本翻译更新不及时或者翻 +译存在问题,请联系中文版维护者。 +英文版维护者: Patrick Mochel <mochel@osdl.org> + Mike Murphy <mamurph@cs.clemson.edu> +中文版维护者: 傅炜 Fu Wei <tekkamanninja@gmail.com> +中文版翻译者: 傅炜 Fu Wei <tekkamanninja@gmail.com> +中文版校译者: 傅炜 Fu Wei <tekkamanninja@gmail.com> + + +以下为正文 +--------------------------------------------------------------------- +sysfs - 用于导出内核对象(kobject)的文件系统 + +Patrick Mochel <mochel@osdl.org> +Mike Murphy <mamurph@cs.clemson.edu> + +修订: 16 August 2011 +原始版本: 10 January 2003 + + +sysfs 简介: +~~~~~~~~~~ + +sysfs 是一个最初基于 ramfs 且位于内存的文件系统。它提供导出内核 +数据结构及其属性,以及它们之间的关联到用户空间的方法。 + +sysfs 始终与 kobject 的底层结构紧密相关。请阅读 +Documentation/core-api/kobject.rst 文档以获得更多关于 kobject 接口的 +信息。 + + +使用 sysfs +~~~~~~~~~~~ + +只要内核配置中定义了 CONFIG_SYSFS ,sysfs 总是被编译进内核。你可 +通过以下命令挂载它: + + mount -t sysfs sysfs /sys + + +创建目录 +~~~~~~~~ + +任何 kobject 在系统中注册,就会有一个目录在 sysfs 中被创建。这个 +目录是作为该 kobject 的父对象所在目录的子目录创建的,以准确地传递 +内核的对象层次到用户空间。sysfs 中的顶层目录代表着内核对象层次的 +共同祖先;例如:某些对象属于某个子系统。 + +Sysfs 在与其目录关联的 kernfs_node 对象中内部保存一个指向实现 +目录的 kobject 的指针。以前,这个 kobject 指针被 sysfs 直接用于 +kobject 文件打开和关闭的引用计数。而现在的 sysfs 实现中,kobject +引用计数只能通过 sysfs_schedule_callback() 函数直接修改。 + + +属性 +~~~~ + +kobject 的属性可在文件系统中以普通文件的形式导出。Sysfs 为属性定义 +了面向文件 I/O 操作的方法,以提供对内核属性的读写。 + + +属性应为 ASCII 码文本文件。以一个文件只存储一个属性值为宜。但一个 +文件只包含一个属性值可能影响效率,所以一个包含相同数据类型的属性值 +数组也被广泛地接受。 + +混合类型、表达多行数据以及一些怪异的数据格式会遭到强烈反对。这样做是 +很丢脸的,而且其代码会在未通知作者的情况下被重写。 + + +一个简单的属性结构定义如下: + +struct attribute { + char * name; + struct module *owner; + umode_t mode; +}; + + +int sysfs_create_file(struct kobject * kobj, const struct attribute * attr); +void sysfs_remove_file(struct kobject * kobj, const struct attribute * attr); + + +一个单独的属性结构并不包含读写其属性值的方法。子系统最好为增删特定 +对象类型的属性定义自己的属性结构体和封装函数。 + +例如:驱动程序模型定义的 device_attribute 结构体如下: + +struct device_attribute { + struct attribute attr; + ssize_t (*show)(struct device *dev, struct device_attribute *attr, + char *buf); + ssize_t (*store)(struct device *dev, struct device_attribute *attr, + const char *buf, size_t count); +}; + +int device_create_file(struct device *, const struct device_attribute *); +void device_remove_file(struct device *, const struct device_attribute *); + +为了定义设备属性,同时定义了一下辅助宏: + +#define DEVICE_ATTR(_name, _mode, _show, _store) \ +struct device_attribute dev_attr_##_name = __ATTR(_name, _mode, _show, _store) + +例如:声明 + +static DEVICE_ATTR(foo, S_IWUSR | S_IRUGO, show_foo, store_foo); + +等同于如下代码: + +static struct device_attribute dev_attr_foo = { + .attr = { + .name = "foo", + .mode = S_IWUSR | S_IRUGO, + .show = show_foo, + .store = store_foo, + }, +}; + + +子系统特有的回调函数 +~~~~~~~~~~~~~~~~~~~ + +当一个子系统定义一个新的属性类型时,必须实现一系列的 sysfs 操作, +以帮助读写调用实现属性所有者的显示和储存方法。 + +struct sysfs_ops { + ssize_t (*show)(struct kobject *, struct attribute *, char *); + ssize_t (*store)(struct kobject *, struct attribute *, const char *, size_t); +}; + +[子系统应已经定义了一个 struct kobj_type 结构体作为这个类型的 +描述符,并在此保存 sysfs_ops 的指针。更多的信息参见 kobject 的 +文档] + +sysfs 会为这个类型调用适当的方法。当一个文件被读写时,这个方法会 +将一般的kobject 和 attribute 结构体指针转换为适当的指针类型后 +调用相关联的函数。 + + +示例: + +#define to_dev_attr(_attr) container_of(_attr, struct device_attribute, attr) + +static ssize_t dev_attr_show(struct kobject *kobj, struct attribute *attr, + char *buf) +{ + struct device_attribute *dev_attr = to_dev_attr(attr); + struct device *dev = kobj_to_dev(kobj); + ssize_t ret = -EIO; + + if (dev_attr->show) + ret = dev_attr->show(dev, dev_attr, buf); + if (ret >= (ssize_t)PAGE_SIZE) { + printk("dev_attr_show: %pS returned bad count\n", + dev_attr->show); + } + return ret; +} + + + +读写属性数据 +~~~~~~~~~~~~ + +在声明属性时,必须指定 show() 或 store() 方法,以实现属性的 +读或写。这些方法的类型应该和以下的设备属性定义一样简单。 + +ssize_t (*show)(struct device *dev, struct device_attribute *attr, char *buf); +ssize_t (*store)(struct device *dev, struct device_attribute *attr, + const char *buf, size_t count); + +也就是说,他们应只以一个处理对象、一个属性和一个缓冲指针作为参数。 + +sysfs 会分配一个大小为 (PAGE_SIZE) 的缓冲区并传递给这个方法。 +Sysfs 将会为每次读写操作调用一次这个方法。这使得这些方法在执行时 +会出现以下的行为: + +- 在读方面(read(2)),show() 方法应该填充整个缓冲区。回想属性 + 应只导出了一个属性值或是一个同类型属性值的数组,所以这个代价将 + 不会不太高。 + + 这使得用户空间可以局部地读和任意的向前搜索整个文件。如果用户空间 + 向后搜索到零或使用‘0’偏移执行一个pread(2)操作,show()方法将 + 再次被调用,以重新填充缓存。 + +- 在写方面(write(2)),sysfs 希望在第一次写操作时得到整个缓冲区。 + 之后 Sysfs 传递整个缓冲区给 store() 方法。 + + 当要写 sysfs 文件时,用户空间进程应首先读取整个文件,修该想要 + 改变的值,然后回写整个缓冲区。 + + 在读写属性值时,属性方法的执行应操作相同的缓冲区。 + +注记: + +- 写操作导致的 show() 方法重载,会忽略当前文件位置。 + +- 缓冲区应总是 PAGE_SIZE 大小。对于i386,这个值为4096。 + +- show() 方法应该返回写入缓冲区的字节数,也就是 scnprintf()的 + 返回值。 + +- show() 方法在将格式化返回值返回用户空间的时候,禁止使用snprintf()。 + 如果可以保证不会发生缓冲区溢出,可以使用sprintf(),否则必须使用 + scnprintf()。 + +- store() 应返回缓冲区的已用字节数。如果整个缓存都已填满,只需返回 + count 参数。 + +- show() 或 store() 可以返回错误值。当得到一个非法值,必须返回一个 + 错误值。 + +- 一个传递给方法的对象将会通过 sysfs 调用对象内嵌的引用计数固定在 + 内存中。尽管如此,对象代表的物理实体(如设备)可能已不存在。如有必要, + 应该实现一个检测机制。 + +一个简单的(未经实验证实的)设备属性实现如下: + +static ssize_t show_name(struct device *dev, struct device_attribute *attr, + char *buf) +{ + return scnprintf(buf, PAGE_SIZE, "%s\n", dev->name); +} + +static ssize_t store_name(struct device *dev, struct device_attribute *attr, + const char *buf, size_t count) +{ + snprintf(dev->name, sizeof(dev->name), "%.*s", + (int)min(count, sizeof(dev->name) - 1), buf); + return count; +} + +static DEVICE_ATTR(name, S_IRUGO, show_name, store_name); + + +(注意:真正的实现不允许用户空间设置设备名。) + +顶层目录布局 +~~~~~~~~~~~~ + +sysfs 目录的安排显示了内核数据结构之间的关系。 + +顶层 sysfs 目录如下: + +block/ +bus/ +class/ +dev/ +devices/ +firmware/ +net/ +fs/ + +devices/ 包含了一个设备树的文件系统表示。他直接映射了内部的内核 +设备树,反映了设备的层次结构。 + +bus/ 包含了内核中各种总线类型的平面目录布局。每个总线目录包含两个 +子目录: + + devices/ + drivers/ + +devices/ 包含了系统中出现的每个设备的符号链接,他们指向 root/ 下的 +设备目录。 + +drivers/ 包含了每个已为特定总线上的设备而挂载的驱动程序的目录(这里 +假定驱动没有跨越多个总线类型)。 + +fs/ 包含了一个为文件系统设立的目录。现在每个想要导出属性的文件系统必须 +在 fs/ 下创建自己的层次结构(参见Documentation/filesystems/fuse.rst)。 + +dev/ 包含两个子目录: char/ 和 block/。在这两个子目录中,有以 +<major>:<minor> 格式命名的符号链接。这些符号链接指向 sysfs 目录 +中相应的设备。/sys/dev 提供一个通过一个 stat(2) 操作结果,查找 +设备 sysfs 接口快捷的方法。 + +更多有关 driver-model 的特性信息可以在 Documentation/driver-api/driver-model/ +中找到。 + + +TODO: 完成这一节。 + + +当前接口 +~~~~~~~~ + +以下的接口层普遍存在于当前的sysfs中: + +- 设备 (include/linux/device.h) +---------------------------------- +结构体: + +struct device_attribute { + struct attribute attr; + ssize_t (*show)(struct device *dev, struct device_attribute *attr, + char *buf); + ssize_t (*store)(struct device *dev, struct device_attribute *attr, + const char *buf, size_t count); +}; + +声明: + +DEVICE_ATTR(_name, _mode, _show, _store); + +增/删属性: + +int device_create_file(struct device *dev, const struct device_attribute * attr); +void device_remove_file(struct device *dev, const struct device_attribute * attr); + + +- 总线驱动程序 (include/linux/device.h) +-------------------------------------- +结构体: + +struct bus_attribute { + struct attribute attr; + ssize_t (*show)(struct bus_type *, char * buf); + ssize_t (*store)(struct bus_type *, const char * buf, size_t count); +}; + +声明: + +BUS_ATTR(_name, _mode, _show, _store) + +增/删属性: + +int bus_create_file(struct bus_type *, struct bus_attribute *); +void bus_remove_file(struct bus_type *, struct bus_attribute *); + + +- 设备驱动程序 (include/linux/device.h) +----------------------------------------- + +结构体: + +struct driver_attribute { + struct attribute attr; + ssize_t (*show)(struct device_driver *, char * buf); + ssize_t (*store)(struct device_driver *, const char * buf, + size_t count); +}; + +声明: + +DRIVER_ATTR(_name, _mode, _show, _store) + +增/删属性: + +int driver_create_file(struct device_driver *, const struct driver_attribute *); +void driver_remove_file(struct device_driver *, const struct driver_attribute *); + + +文档 +~~~~ + +sysfs 目录结构以及其中包含的属性定义了一个内核与用户空间之间的 ABI。 +对于任何 ABI,其自身的稳定和适当的文档是非常重要的。所有新的 sysfs +属性必须在 Documentation/ABI 中有文档。详见 Documentation/ABI/README。 diff --git a/Documentation/translations/zh_CN/filesystems/tmpfs.rst b/Documentation/translations/zh_CN/filesystems/tmpfs.rst new file mode 100644 index 000000000..6fd9d83b2 --- /dev/null +++ b/Documentation/translations/zh_CN/filesystems/tmpfs.rst @@ -0,0 +1,146 @@ +.. SPDX-License-Identifier: GPL-2.0 + +.. include:: ../disclaimer-zh_CN.rst + +:Original: Documentation/filesystems/tmpfs.rst + +translated by Wang Qing<wangqing@vivo.com> + +===== +Tmpfs +===== + +Tmpfs是一个将所有文件都保存在虚拟内存中的文件系统。 + +tmpfs中的所有内容都是临时的,也就是说没有任何文件会在硬盘上创建。 +如果卸载tmpfs实例,所有保存在其中的文件都会丢失。 + +tmpfs将所有文件保存在内核缓存中,随着文件内容增长或缩小可以将不需要的 +页面swap出去。它具有最大限制,可以通过“mount -o remount ...”调整。 + +和ramfs(创建tmpfs的模板)相比,tmpfs包含交换和限制检查。和tmpfs相似的另 +一个东西是RAM磁盘(/dev/ram*),可以在物理RAM中模拟固定大小的硬盘,并在 +此之上创建一个普通的文件系统。Ramdisks无法swap,因此无法调整它们的大小。 + +由于tmpfs完全保存于页面缓存和swap中,因此所有tmpfs页面将在/proc/meminfo +中显示为“Shmem”,而在free(1)中显示为“Shared”。请注意,这些计数还包括 +共享内存(shmem,请参阅ipcs(1))。获得计数的最可靠方法是使用df(1)和du(1)。 + +tmpfs具有以下用途: + +1) 内核总有一个无法看到的内部挂载,用于共享匿名映射和SYSV共享内存。 + + 挂载不依赖于CONFIG_TMPFS。如果CONFIG_TMPFS未设置,tmpfs对用户不可见。 + 但是内部机制始终存在。 + +2) glibc 2.2及更高版本期望将tmpfs挂载在/dev/shm上以用于POSIX共享内存 + (shm_open,shm_unlink)。添加内容到/etc/fstab应注意如下: + + tmpfs /dev/shm tmpfs defaults 0 0 + + 使用时需要记住创建挂载tmpfs的目录。 + + SYSV共享内存无需挂载,内部已默认支持。(在2.3内核版本中,必须挂载 + tmpfs的前身(shm fs)才能使用SYSV共享内存) + +3) 很多人(包括我)都觉的在/tmp和/var/tmp上挂载非常方便,并具有较大的 + swap分区。目前循环挂载tmpfs可以正常工作,所以大多数发布都应当可以 + 使用mkinitrd通过/tmp访问/tmp。 + +4) 也许还有更多我不知道的地方:-) + + +tmpfs有三个用于调整大小的挂载选项: + +========= =========================================================== +size tmpfs实例分配的字节数限制。默认值是不swap时物理RAM的一半。 + 如果tmpfs实例过大,机器将死锁,因为OOM处理将无法释放该内存。 +nr_blocks 与size相同,但以PAGE_SIZE为单位。 +nr_inodes tmpfs实例的最大inode个数。默认值是物理内存页数的一半,或者 + (有高端内存的机器)低端内存RAM的页数,二者以较低者为准。 +========= =========================================================== + +这些参数接受后缀k,m或g表示千,兆和千兆字节,可以在remount时更改。 +size参数也接受后缀%用来限制tmpfs实例占用物理RAM的百分比: +未指定size或nr_blocks时,默认值为size=50% + +如果nr_blocks=0(或size=0),block个数将不受限制;如果nr_inodes=0, +inode个数将不受限制。这样挂载通常是不明智的,因为它允许任何具有写权限的 +用户通过访问tmpfs耗尽机器上的所有内存;但同时这样做也会增强在多个CPU的 +场景下的访问。 + +tmpfs具有为所有文件设置NUMA内存分配策略挂载选项(如果启用了CONFIG_NUMA), +可以通过“mount -o remount ...”调整 + +======================== ========================= +mpol=default 采用进程分配策略 + (请参阅 set_mempolicy(2)) +mpol=prefer:Node 倾向从给定的节点分配 +mpol=bind:NodeList 只允许从指定的链表分配 +mpol=interleave 倾向于依次从每个节点分配 +mpol=interleave:NodeList 依次从每个节点分配 +mpol=local 优先本地节点分配内存 +======================== ========================= + +NodeList格式是以逗号分隔的十进制数字表示大小和范围,最大和最小范围是用- +分隔符的十进制数来表示。例如,mpol=bind0-3,5,7,9-15 + +带有有效NodeList的内存策略将按指定格式保存,在创建文件时使用。当任务在该 +文件系统上创建文件时,会使用到挂载时的内存策略NodeList选项,如果设置的话, +由调用任务的cpuset[请参见Documentation/admin-guide/cgroup-v1/cpusets.rst] +以及下面列出的可选标志约束。如果NodeLists为设置为空集,则文件的内存策略将 +恢复为“默认”策略。 + +NUMA内存分配策略有可选标志,可以用于模式结合。在挂载tmpfs时指定这些可选 +标志可以在NodeList之前生效。 +Documentation/admin-guide/mm/numa_memory_policy.rst列出所有可用的内存 +分配策略模式标志及其对内存策略。 + +:: + + =static 相当于 MPOL_F_STATIC_NODES + =relative 相当于 MPOL_F_RELATIVE_NODES + +例如,mpol=bind=staticNodeList相当于MPOL_BIND|MPOL_F_STATIC_NODES的分配策略 + +请注意,如果内核不支持NUMA,那么使用mpol选项挂载tmpfs将会失败;nodelist指定不 +在线的节点也会失败。如果您的系统依赖于此,但内核会运行不带NUMA功能(也许是安全 +revocery内核),或者具有较少的节点在线,建议从自动模式中省略mpol选项挂载选项。 +可以在以后通过“mount -o remount,mpol=Policy:NodeList MountPoint”添加到挂载点。 + +要指定初始根目录,可以使用如下挂载选项: + +==== ==================== +模式 权限用八进制数字表示 +uid 用户ID +gid 组ID +==== ==================== + +这些选项对remount没有任何影响。您可以通过chmod(1),chown(1)和chgrp(1)的更改 +已经挂载的参数。 + +tmpfs具有选择32位还是64位inode的挂载选项: + +======= ============= +inode64 使用64位inode +inode32 使用32位inode +======= ============= + +在32位内核上,默认是inode32,挂载时指定inode64会被拒绝。 +在64位内核上,默认配置是CONFIG_TMPFS_INODE64。inode64避免了单个设备上可能有多个 +具有相同inode编号的文件;比如32位应用程序使用glibc如果长期访问tmpfs,一旦达到33 +位inode编号,就有EOVERFLOW失败的危险,无法打开大于2GiB的文件,并返回EINVAL。 + +所以'mount -t tmpfs -o size=10G,nr_inodes=10k,mode=700 tmpfs /mytmpfs'将在 +/mytmpfs上挂载tmpfs实例,分配只能由root用户访问的10GB RAM/SWAP,可以有10240个 +inode的实例。 + + +:作者: + Christoph Rohland <cr@sap.com>, 1.12.01 +:更新: + Hugh Dickins, 4 June 2007 +:更新: + KOSAKI Motohiro, 16 Mar 2010 +:更新: + Chris Down, 13 July 2020 diff --git a/Documentation/translations/zh_CN/filesystems/virtiofs.rst b/Documentation/translations/zh_CN/filesystems/virtiofs.rst new file mode 100644 index 000000000..09bc9e012 --- /dev/null +++ b/Documentation/translations/zh_CN/filesystems/virtiofs.rst @@ -0,0 +1,58 @@ +.. SPDX-License-Identifier: GPL-2.0 + +.. include:: ../disclaimer-zh_CN.rst + +:Original: :ref:`Documentation/filesystems/virtiofs.rst <virtiofs_index>` + +译者 +:: + + 中文版维护者: 王文虎 Wang Wenhu <wenhu.wang@vivo.com> + 中文版翻译者: 王文虎 Wang Wenhu <wenhu.wang@vivo.com> + 中文版校译者: 王文虎 Wang Wenhu <wenhu.wang@vivo.com> + +=========================================== +virtiofs: virtio-fs 主机<->客机共享文件系统 +=========================================== + +- Copyright (C) 2020 Vivo Communication Technology Co. Ltd. + +介绍 +==== +Linux的virtiofs文件系统实现了一个半虚拟化VIRTIO类型“virtio-fs”设备的驱动,通过该\ +类型设备实现客机<->主机文件系统共享。它允许客机挂载一个已经导出到主机的目录。 + +客机通常需要访问主机或者远程系统上的文件。使用场景包括:在新客机安装时让文件对其\ +可见;从主机上的根文件系统启动;对无状态或临时客机提供持久存储和在客机之间共享目录。 + +尽管在某些任务可能通过使用已有的网络文件系统完成,但是却需要非常难以自动化的配置\ +步骤,且将存储网络暴露给客机。而virtio-fs设备通过提供不经过网络的文件系统访问文件\ +的设计方式解决了这些问题。 + +另外,virto-fs设备发挥了主客机共存的优点提高了性能,并且提供了网络文件系统所不具备 +的一些语义功能。 + +用法 +==== +以``myfs``标签将文件系统挂载到``/mnt``: + +.. code-block:: sh + + guest# mount -t virtiofs myfs /mnt + +请查阅 https://virtio-fs.gitlab.io/ 了解配置QEMU和virtiofsd守护程序的详细信息。 + +内幕 +==== +由于virtio-fs设备将FUSE协议用于文件系统请求,因此Linux的virtiofs文件系统与FUSE文\ +件系统客户端紧密集成在一起。客机充当FUSE客户端而主机充当FUSE服务器,内核与用户空\ +间之间的/dev/fuse接口由virtio-fs设备接口代替。 + +FUSE请求被置于虚拟队列中由主机处理。主机填充缓冲区中的响应部分,而客机处理请求的完成部分。 + +将/dev/fuse映射到虚拟队列需要解决/dev/fuse和虚拟队列之间语义上的差异。每次读取\ +/dev/fuse设备时,FUSE客户端都可以选择要传输的请求,从而可以使某些请求优先于其他\ +请求。虚拟队列有其队列语义,无法更改已入队请求的顺序。在虚拟队列已满的情况下尤 +其关键,因为此时不可能加入高优先级的请求。为了解决此差异,virtio-fs设备采用“hiprio”\ +(高优先级)虚拟队列,专门用于有别于普通请求的高优先级请求。 + diff --git a/Documentation/translations/zh_CN/iio/ep93xx_adc.rst b/Documentation/translations/zh_CN/iio/ep93xx_adc.rst new file mode 100644 index 000000000..64f3f3508 --- /dev/null +++ b/Documentation/translations/zh_CN/iio/ep93xx_adc.rst @@ -0,0 +1,48 @@ +.. include:: ../disclaimer-zh_CN.rst + +:Original: Documentation/iio/ep93xx_adc.rst + +:翻译: + + 司延腾 Yanteng Si <siyanteng@loongson.cn> + +.. _cn_iio_ep93xx_adc: + +================================== +思睿逻辑 EP93xx 模拟数字转换器驱动 +================================== + +1. 概述 +======= + +该驱动同时适用于具有5通道模拟数字转换器的低端 (EP9301, Ep9302) 设备和10通道 +触摸屏/模拟数字转换器的高端设备(EP9307, EP9312, EP9315)。 + +2. 通道编号 +=========== + +EP9301和EP9302数据表定义了通道0..4的编号方案。虽然EP9307, EP9312和EP9315多 +了3个通道(一共8个),但是编号并没有定义。所以说最后三个通道是随机编号的。 + +如果ep93xx_adc是IIO设备0,您将在以下位置找到条目 +/sys/bus/iio/devices/iio:device0/: + + +-----------------+---------------+ + | sysfs 入口 | ball/pin 名称 | + +=================+===============+ + | in_voltage0_raw | YM | + +-----------------+---------------+ + | in_voltage1_raw | SXP | + +-----------------+---------------+ + | in_voltage2_raw | SXM | + +-----------------+---------------+ + | in_voltage3_raw | SYP | + +-----------------+---------------+ + | in_voltage4_raw | SYM | + +-----------------+---------------+ + | in_voltage5_raw | XP | + +-----------------+---------------+ + | in_voltage6_raw | XM | + +-----------------+---------------+ + | in_voltage7_raw | YP | + +-----------------+---------------+ diff --git a/Documentation/translations/zh_CN/iio/iio_configfs.rst b/Documentation/translations/zh_CN/iio/iio_configfs.rst new file mode 100644 index 000000000..eccaf1c64 --- /dev/null +++ b/Documentation/translations/zh_CN/iio/iio_configfs.rst @@ -0,0 +1,106 @@ +.. include:: ../disclaimer-zh_CN.rst + +:Original: Documentation/iio/iio_configfs.rst + +:翻译: + + 司延腾 Yanteng Si <siyanteng@loongson.cn> + +.. _cn_iio_configfs: + +===================== +工业 IIO configfs支持 +===================== + +1. 概述 +======= + +Configfs是一种内核对象的基于文件系统的管理系统,IIO使用一些可以通过 +configfs轻松配置的对象(例如:设备,触发器)。 + +关于configfs是如何运行的,请查阅Documentation/filesystems/configfs.rst +了解更多信息。 + +2. 用法 +======= +为了使configfs支持IIO,我们需要在编译时选中config的CONFIG_IIO_CONFIGFS +选项。 + +然后,挂载configfs文件系统(通常在 /config directory目录下):: + + $ mkdir/config + $ mount -t configfs none/config + +此时,将创建所有默认IIO组,并可以在/ config / iio下对其进行访问。 下一章 +将介绍可用的IIO配置对象。 + +3. 软件触发器 +============= + +IIO默认configfs组之一是“触发器”组。挂载configfs后可以自动访问它,并且可 +以在/config/iio/triggers下找到。 + +IIO软件触发器为创建多种触发器类型提供了支持。通常在include/linux/iio +/sw_trigger.h:中的接口下将新的触发器类型实现为单独的内核模块: +:: + + /* + * drivers/iio/trigger/iio-trig-sample.c + * 一种新触发器类型的内核模块实例 + */ + #include <linux/iio/sw_trigger.h> + + + static struct iio_sw_trigger *iio_trig_sample_probe(const char *name) + { + /* + * 这将分配并注册一个IIO触发器以及其他触发器类型特性的初始化。 + */ + } + + static int iio_trig_sample_remove(struct iio_sw_trigger *swt) + { + /* + * 这会废弃iio_trig_sample_probe中的操作 + */ + } + + static const struct iio_sw_trigger_ops iio_trig_sample_ops = { + .probe = iio_trig_sample_probe, + .remove = iio_trig_sample_remove, + }; + + static struct iio_sw_trigger_type iio_trig_sample = { + .name = "trig-sample", + .owner = THIS_MODULE, + .ops = &iio_trig_sample_ops, + }; + + module_iio_sw_trigger_driver(iio_trig_sample); + +每种触发器类型在/config/iio/triggers下都有其自己的目录。加载iio-trig-sample +模块将创建“trig-sample”触发器类型目录/config/iio/triggers/trig-sample. + +我们支持以下中断源(触发器类型) + + * hrtimer,使用高分辨率定时器作为中断源 + +3.1 Hrtimer触发器创建与销毁 +--------------------------- + +加载iio-trig-hrtimer模块将注册hrtimer触发器类型,从而允许用户在 +/config/iio/triggers/hrtimer下创建hrtimer触发器。 + +例如:: + + $ mkdir /config/iio/triggers/hrtimer/instance1 + $ rmdir /config/iio/triggers/hrtimer/instance1 + +每个触发器可以具有一个或多个独特的触发器类型的属性。 + +3.2 "hrtimer" 触发器类型属性 +---------------------------- + +"hrtimer”触发器类型没有来自/config dir的任何可配置属性。 +它确实引入了触发目录的sampling_frequency属性。 +该属性以Hz为单位设置轮询频率,精度为mHz。
\ No newline at end of file diff --git a/Documentation/translations/zh_CN/iio/index.rst b/Documentation/translations/zh_CN/iio/index.rst new file mode 100644 index 000000000..32d69047b --- /dev/null +++ b/Documentation/translations/zh_CN/iio/index.rst @@ -0,0 +1,22 @@ +.. SPDX-License-Identifier: GPL-2.0 + +.. include:: ../disclaimer-zh_CN.rst + +:Original: Documentation/iio/index.rst + +:翻译: + + 司延腾 Yanteng Si <siyanteng@loongson.cn> + +.. _cn_iio_index: + +======== +工业 I/O +======== + +.. toctree:: + :maxdepth: 1 + + iio_configfs + + ep93xx_adc diff --git a/Documentation/translations/zh_CN/index.rst b/Documentation/translations/zh_CN/index.rst new file mode 100644 index 000000000..ec99ef5fe --- /dev/null +++ b/Documentation/translations/zh_CN/index.rst @@ -0,0 +1,142 @@ +.. SPDX-License-Identifier: GPL-2.0 + +.. raw:: latex + + \renewcommand\thesection* + \renewcommand\thesubsection* + \kerneldocCJKon + \kerneldocBeginSC{ + +.. _linux_doc_zh: + +中文翻译 +======== + + +.. note:: + + **翻译计划:** + 内核中文文档欢迎任何翻译投稿,特别是关于内核用户和管理员指南部分。 + +这是中文内核文档树的顶级目录。内核文档,就像内核本身一样,在很大程度上是一 +项正在进行的工作;当我们努力将许多分散的文件整合成一个连贯的整体时尤其如此。 +另外,随时欢迎您对内核文档进行改进;如果您想提供帮助,请加入vger.kernel.org +上的linux-doc邮件列表。 + +顺便说下,中文文档也需要遵守内核编码风格,风格中中文和英文的主要不同就是中文 +的字符标点占用两个英文字符宽度, 所以,当英文要求不要超过每行100个字符时, +中文就不要超过50个字符。另外,也要注意'-','=' 等符号与相关标题的对齐。在将 +补丁提交到社区之前,一定要进行必要的 ``checkpatch.pl`` 检查和编译测试。 + +与Linux 内核社区一起工作 +------------------------ + +与内核开发社区进行协作并将工作推向上游的基本指南。 + +.. toctree:: + :maxdepth: 1 + + process/development-process + process/submitting-patches + 行为准则 <process/code-of-conduct> + maintainer/index + 完整开发流程文档 <process/index> + +内部API文档 +----------- + +开发人员使用的内核内部交互接口手册。 + +.. toctree:: + :maxdepth: 1 + + core-api/index + driver-api/index + 内核中的锁 <locking/index> + +TODOList: + +* subsystem-apis + +开发工具和流程 +-------------- + +为所有内核开发人员提供有用信息的各种其他手册。 + +.. toctree:: + :maxdepth: 1 + + process/license-rules + doc-guide/index + dev-tools/index + dev-tools/testing-overview + kernel-hacking/index + +TODOList: + +* trace/index +* fault-injection/index +* livepatch/index + +面向用户的文档 +-------------- + +下列手册针对 +希望内核在给定系统上以最佳方式工作的*用户*, +和查找内核用户空间API信息的程序开发人员。 + +.. toctree:: + :maxdepth: 1 + + admin-guide/index + admin-guide/reporting-issues.rst + +TODOList: + +* 内核构建系统 <kbuild/index> +* 用户空间工具 <tools/index> +* userspace-api/index + +也可参考独立于内核文档的 `Linux 手册页 <https://www.kernel.org/doc/man-pages/>`_ 。 + +固件相关文档 +------------ + +下列文档描述了内核需要的平台固件相关信息。 + +.. toctree:: + :maxdepth: 2 + + devicetree/index + +TODOList: + +* firmware-guide/index + +体系结构文档 +------------ + +.. toctree:: + :maxdepth: 2 + + arch + +其他文档 +-------- + +有几份未排序的文档似乎不适合放在文档的其他部分,或者可能需要进行一些调整和/或 +转换为reStructureText格式,也有可能太旧。 + +TODOList: + +* staging/index + + +索引和表格 +---------- + +* :ref:`genindex` + +.. raw:: latex + + }\kerneldocEndSC diff --git a/Documentation/translations/zh_CN/infiniband/core_locking.rst b/Documentation/translations/zh_CN/infiniband/core_locking.rst new file mode 100644 index 000000000..42f08038d --- /dev/null +++ b/Documentation/translations/zh_CN/infiniband/core_locking.rst @@ -0,0 +1,115 @@ + +.. include:: ../disclaimer-zh_CN.rst + +:Original: Documentation/infiniband/core_locking.rst + +:翻译: + + 司延腾 Yanteng Si <siyanteng@loongson.cn> + +:校译: + + 王普宇 Puyu Wang <realpuyuwang@gmail.com> + 时奎亮 Alex Shi <alexs@kernel.org> + +.. _cn_infiniband_core_locking: + +================== +infiniband中间层锁 +================== + + 本指南试图明确infiniband中间层的锁假设。它描述了对位于中间层以下的低 + 级驱动程序和使用中间层的上层协议的要求。 + +睡眠和中断环境 +============== + + 除了以下异常情况,ib_device结构体中所有方法的低级驱动实现都可以睡眠。 + 这些异常情况是列表中的任意的方法: + + - create_ah + - modify_ah + - query_ah + - destroy_ah + - post_send + - post_recv + - poll_cq + - req_notify_cq + + 他们可能不可以睡眠,而且必须可以从任何上下文中调用。 + + 向上层协议使用者输出的相应函数: + + - rdma_create_ah + - rdma_modify_ah + - rdma_query_ah + - rdma_destroy_ah + - ib_post_send + - ib_post_recv + - ib_req_notify_cq + + 因此,在任何情况下都可以安全调用(它们)。 + + 此外,该函数 + + - ib_dispatch_event + + 被底层驱动用来通过中间层调度异步事件的“A”,也可以从任何上下文中安全调 + 用。 + +可重入性 +-------- + + 由低级驱动程序导出的ib_device结构体中的所有方法必须是完全可重入的。 + 即使使用同一对象的多个函数调用被同时运行,低级驱动程序也需要执行所有 + 必要的同步以保持一致性。 + + IB中间层不执行任何函数调用的序列化。 + + 因为低级驱动程序是可重入的,所以不要求上层协议使用者任何顺序执行。然 + 而,为了得到合理的结果,可能需要一些顺序。例如,一个使用者可以在多个 + CPU上同时安全地调用ib_poll_cq()。然而,不同的ib_poll_cq()调用之间 + 的工作完成信息的顺序没有被定义。 + +回调 +---- + + 低级驱动程序不得直接从与ib_device方法调用相同的调用链中执行回调。例 + 如,低级驱动程序不允许从post_send方法直接调用使用者的完成事件处理程 + 序。相反,低级驱动程序应该推迟这个回调,例如,调度一个tasklet来执行 + 这个回调。 + + 低层驱动负责确保同一CQ的多个完成事件处理程序不被同时调用。驱动程序必 + 须保证一个给定的CQ的事件处理程序在同一时间只有一个在运行。换句话说, + 以下情况是不允许的:: + + CPU1 CPU2 + + low-level driver -> + consumer CQ event callback: + /* ... */ + ib_req_notify_cq(cq, ...); + low-level driver -> + /* ... */ consumer CQ event callback: + /* ... */ + return from CQ event handler + + 完成事件和异步事件回调的运行环境没有被定义。 根据低级别的驱动程序,它可能是 + 进程上下文、softirq上下文或中断上下文。上层协议使用者可能不会在回调中睡眠。 + +热插拔 +------ + + 当一个低级驱动程序调用ib_register_device()时,它宣布一个设备已经 + 准备好供使用者使用,所有的初始化必须在这个调用之前完成。设备必须保 + 持可用,直到驱动对ib_unregister_device()的调用返回。 + + 低级驱动程序必须从进程上下文调用ib_register_device()和 + ib_unregister_device()。如果使用者在这些调用中回调到驱动程序,它 + 不能持有任何可能导致死锁的semaphores。 + + 一旦其结构体ib_client的add方法被调用,上层协议使用者就可以开始使用 + 一个IB设备。使用者必须在从移除方法返回之前完成所有的清理工作并释放 + 与设备相关的所有资源。 + + 使用者被允许在其添加和删除方法中睡眠。 diff --git a/Documentation/translations/zh_CN/infiniband/index.rst b/Documentation/translations/zh_CN/infiniband/index.rst new file mode 100644 index 000000000..5634cc483 --- /dev/null +++ b/Documentation/translations/zh_CN/infiniband/index.rst @@ -0,0 +1,40 @@ +.. SPDX-License-Identifier: GPL-2.0 + +.. include:: ../disclaimer-zh_CN.rst + +:Original: Documentation/infiniband/index.rst + +:翻译: + + 司延腾 Yanteng Si <siyanteng@loongson.cn> + +:校译: + + 王普宇 Puyu Wang <realpuyuwang@gmail.com> + 时奎亮 Alex Shi <alexs@kernel.org> + +.. _cn_infiniband_index: + +========== +infiniband +========== + +.. toctree:: + :maxdepth: 1 + + core_locking + ipoib + opa_vnic + sysfs + tag_matching + user_mad + user_verbs + + + +.. only:: subproject and html + + Indices + ======= + + * :ref:`genindex` diff --git a/Documentation/translations/zh_CN/infiniband/ipoib.rst b/Documentation/translations/zh_CN/infiniband/ipoib.rst new file mode 100644 index 000000000..56517ea5f --- /dev/null +++ b/Documentation/translations/zh_CN/infiniband/ipoib.rst @@ -0,0 +1,111 @@ +.. include:: ../disclaimer-zh_CN.rst + +:Original: Documentation/infiniband/ipoib.rst + +:翻译: + + 司延腾 Yanteng Si <siyanteng@loongson.cn> + +:校译: + + 王普宇 Puyu Wang <realpuyuwang@gmail.com> + 时奎亮 Alex Shi <alexs@kernel.org> + +.. _cn_infiniband_ipoib: + +========================= +infiniband上的IP(IPoIB) +========================= + + ib_ipoib驱动是IETF ipoib工作组发布的RFC 4391和4392所规定的 + infiniband上IP协议的一个实现。它是一个“本地”实现,即把接口类型设置为 + ARPHRD_INFINIBAND,硬件地址长度为20(早期的专有实现向内核伪装为以太网 + 接口)。 + +分区和P_Keys +============ + + 当IPoIB驱动被加载时,它会使用索引为0的P_Key给每个端口创建一个接口。要用 + 不同的P_Key创建一个接口,将所需的P_Key写入主接口的 + /sys/class/net/<intf name>/create_child文件里面。比如说:: + + echo 0x8001 > /sys/class/net/ib0/create_child + + 这将用P_Key 0x8001创建一个名为ib0.8001的接口。要删除一个子接口,使用 + ``delete_child`` 文件:: + + echo 0x8001 > /sys/class/net/ib0/delete_child + + 任何接口的P_Key都由“pkey”文件给出,而子接口的主接口在“parent”中。 + + 子接口的创建/删除也可以使用IPoIB的rtnl_link_ops来完成,使用两种 + 方式创建的子接口的行为是一样的。 + +数据报与连接模式 +================ + + IPoIB驱动支持两种操作模式:数据报和连接。模式是通过接口的 + /sys/class/net/<intf name>/mode文件设置和读取的。 + + 在数据报模式下,使用IB UD(不可靠数据报)传输,因此接口MTU等于IB L2 MTU + 减去IPoIB封装头(4字节)。例如,在一个典型的具有2K MTU的IB结构中,IPoIB + MTU将是2048 - 4 = 2044字节。 + + 在连接模式下,使用IB RC(可靠的连接)传输。连接模式利用IB传输的连接特性, + 允许MTU达到最大的IP包大小64K,这减少了处理大型UDP数据包、TCP段等所需的 + IP包数量,提高了大型信息的性能。 + + 在连接模式下,接口的UD QP仍被用于组播和与不支持连接模式的对等体的通信。 + 在这种情况下,ICMP PMTU数据包的RX仿真被用来使网络堆栈对这些邻居使用较 + 小的UD MTU。 + +无状态卸载 +========== + + 如果IB HW支持IPoIB无状态卸载,IPoIB会向网络堆栈广播TCP/IP校验和/或大量 + 传送(LSO)负载转移能力。 + + 大量传送(LSO)负载转移也已实现,可以使用ethtool调用打开/关闭。目前,LRO + 只支持具有校验和卸载能力的设备。 + + 无状态卸载只在数据报模式下支持。 + +中断管理 +======== + + 如果底层IB设备支持CQ事件管理,可以使用ethtool来设置中断缓解参数,从而减少 + 处理中断产生的开销。IPoIB的主要代码路径不使用TX完成信号的事件,所以只支持 + RX管理。 + +调试信息 +======== + + 通过将CONFIG_INFINIBAND_IPOIB_DEBUG设置为“y”来编译IPoIB驱动,跟踪信 + 息被编译到驱动中。通过将模块参数debug_level和mcast_debug_level设置为1来 + 打开它们。这些参数可以在运行时通过/sys/module/ib_ipoib/的文件来控制。 + + CONFIG_INFINIBAND_IPOIB_DEBUG也启用debugfs虚拟文件系统中的文件。通过挂 + 载这个文件系统,例如用:: + + mount -t debugfs none /sys/kernel/debug + + 可以从/sys/kernel/debug/ipoib/ib0_mcg等文件中获得关于多播组的统计数据。 + + 这个选项对性能的影响可以忽略不计,所以在正常运行时,在debug_level设置为 + 0的情况下启用这个选项是安全的。 + + CONFIG_INFINIBAND_IPOIB_DEBUG_DATA当data_debug_level设置为1时,可以 + 在数据路径中启用更多的调试输出。 然而,即使禁用输出,启用这个配置选项也 + 会影响性能,因为它在快速路径中增加了测试。 + +引用 +==== + + 在InfiniBand上传输IP(IPoIB)(RFC 4391)。 + http://ietf.org/rfc/rfc4391.txt + + infiniband上的IP:上的IP架构(RFC 4392)。 + http://ietf.org/rfc/rfc4392.txt + + infiniband上的IP: 连接模式 (RFC 4755) + http://ietf.org/rfc/rfc4755.txt diff --git a/Documentation/translations/zh_CN/infiniband/opa_vnic.rst b/Documentation/translations/zh_CN/infiniband/opa_vnic.rst new file mode 100644 index 000000000..12b147fbf --- /dev/null +++ b/Documentation/translations/zh_CN/infiniband/opa_vnic.rst @@ -0,0 +1,156 @@ +.. include:: ../disclaimer-zh_CN.rst + +:Original: Documentation/infiniband/opa_vnic.rst + +:翻译: + + 司延腾 Yanteng Si <siyanteng@loongson.cn> + +:校译: + + 王普宇 Puyu Wang <realpuyuwang@gmail.com> + 时奎亮 Alex Shi <alexs@kernel.org> + +.. _cn_infiniband_opa_vnic: + +============================================= +英特尔全路径(OPA)虚拟网络接口控制器(VNIC) +============================================= + +英特尔全路径(OPA)虚拟网络接口控制器(VNIC)功能通过封装HFI节点之间的以 +太网数据包,支持Omni-Path结构上的以太网功能。 + +体系结构 +======== + +Omni-Path封装的以太网数据包的交换模式涉及Omni-Path结构拓扑上覆盖的一个或 +多个虚拟以太网交换机。Omni-Path结构上的HFI节点的一个子集被允许在特定的虚 +拟以太网交换机上交换封装的以太网数据包。虚拟以太网交换机是通过配置结构上的 +HFI节点实现的逻辑抽象,用于生成和处理报头。在最简单的配置中,整个结构的所有 +HFI节点通过一个虚拟以太网交换机交换封装的以太网数据包。一个虚拟以太网交换机, +实际上是一个独立的以太网网络。该配置由以太网管理器(EM)执行,它是可信的结 +构管理器(FM)应用程序的一部分。HFI节点可以有多个VNIC,每个连接到不同的虚 +拟以太网交换机。下图介绍了两个虚拟以太网交换机与两个HFI节点的情况:: + + +-------------------+ + | 子网/ | + | 以太网 | + | 管理 | + +-------------------+ + / / + / / + / / + / / + +-----------------------------+ +------------------------------+ + | 虚拟以太网切换 | | 虚拟以太网切换 | + | +---------+ +---------+ | | +---------+ +---------+ | + | | VPORT | | VPORT | | | | VPORT | | VPORT | | + +--+---------+----+---------+-+ +-+---------+----+---------+---+ + | \ / | + | \ / | + | \/ | + | / \ | + | / \ | + +-----------+------------+ +-----------+------------+ + | VNIC | VNIC | | VNIC | VNIC | + +-----------+------------+ +-----------+------------+ + | HFI | | HFI | + +------------------------+ +------------------------+ + + +Omni-Path封装的以太网数据包格式如下所述。 + +==================== ================================ +位 域 +==================== ================================ +Quad Word 0: +0-19 SLID (低20位) +20-30 长度 (以四字为单位) +31 BECN 位 +32-51 DLID (低20位) +52-56 SC (服务级别) +57-59 RC (路由控制) +60 FECN 位 +61-62 L2 (=10, 16B 格式) +63 LT (=1, 链路传输头 Flit) + +Quad Word 1: +0-7 L4 type (=0x78 ETHERNET) +8-11 SLID[23:20] +12-15 DLID[23:20] +16-31 PKEY +32-47 熵 +48-63 保留 + +Quad Word 2: +0-15 保留 +16-31 L4 头 +32-63 以太网数据包 + +Quad Words 3 to N-1: +0-63 以太网数据包 (pad拓展) + +Quad Word N (last): +0-23 以太网数据包 (pad拓展) +24-55 ICRC +56-61 尾 +62-63 LT (=01, 链路传输尾 Flit) +==================== ================================ + +以太网数据包在传输端被填充,以确保VNIC OPA数据包是四字对齐的。“尾”字段 +包含填充的字节数。在接收端,“尾”字段被读取,在将数据包向上传递到网络堆 +栈之前,填充物被移除(与ICRC、尾和OPA头一起)。 + +L4头字段包含VNIC端口所属的虚拟以太网交换机ID。在接收端,该字段用于将收 +到的VNIC数据包去多路复用到不同的VNIC端口。 + +驱动设计 +======== + +英特尔OPA VNIC的软件设计如下图所示。OPA VNIC功能有一个依赖于硬件的部分 +和一个独立于硬件的部分。 + +对IB设备分配和释放RDMA netdev设备的支持已经被加入。RDMA netdev支持与 +网络堆栈的对接,从而创建标准的网络接口。OPA_VNIC是一个RDMA netdev设备 +类型。 + +依赖于HW的VNIC功能是HFI1驱动的一部分。它实现了分配和释放OPA_VNIC RDMA +netdev的动作。它涉及VNIC功能的HW资源分配/管理。它与网络堆栈接口并实现所 +需的net_device_ops功能。它在传输路径中期待Omni-Path封装的以太网数据包, +并提供对它们的HW访问。在将数据包向上传递到网络堆栈之前,它把Omni-Path头 +从接收的数据包中剥离。它还实现了RDMA netdev控制操作。 + +OPA VNIC模块实现了独立于硬件的VNIC功能。它由两部分组成。VNIC以太网管理 +代理(VEMA)作为一个IB客户端向IB核心注册,并与IB MAD栈接口。它与以太网 +管理器(EM)和VNIC netdev交换管理信息。VNIC netdev部分分配和释放OPA_VNIC +RDMA netdev设备。它在需要时覆盖由依赖HW的VNIC驱动设置的net_device_ops函数, +以适应任何控制操作。它还处理以太网数据包的封装,在传输路径中使用Omni-Path头。 +对于每个VNIC接口,封装所需的信息是由EM通过VEMA MAD接口配置的。它还通过调用 +RDMA netdev控制操作将任何控制信息传递给依赖于HW的驱动程序:: + + +-------------------+ +----------------------+ + | | | Linux | + | IB MAD | | 网络 | + | | | 栈 | + +-------------------+ +----------------------+ + | | | + | | | + +----------------------------+ | + | | | + | OPA VNIC 模块 | | + | (OPA VNIC RDMA Netdev | | + | & EMA 函数) | | + | | | + +----------------------------+ | + | | + | | + +------------------+ | + | IB 核心 | | + +------------------+ | + | | + | | + +--------------------------------------------+ + | | + | HFI1 驱动和 VNIC 支持 | + | | + +--------------------------------------------+ diff --git a/Documentation/translations/zh_CN/infiniband/sysfs.rst b/Documentation/translations/zh_CN/infiniband/sysfs.rst new file mode 100644 index 000000000..e9a48b0b2 --- /dev/null +++ b/Documentation/translations/zh_CN/infiniband/sysfs.rst @@ -0,0 +1,21 @@ +.. include:: ../disclaimer-zh_CN.rst + +:Original: Documentation/infiniband/sysfs.rst + +:翻译: + + 司延腾 Yanteng Si <siyanteng@loongson.cn> + +:校译: + + 王普宇 Puyu Wang <realpuyuwang@gmail.com> + 时奎亮 Alex Shi <alexs@kernel.org> + +.. _cn_infiniband_sysfs: + +========= +Sysfs文件 +========= + +sysfs接口已移至 +Documentation/ABI/stable/sysfs-class-infiniband. diff --git a/Documentation/translations/zh_CN/infiniband/tag_matching.rst b/Documentation/translations/zh_CN/infiniband/tag_matching.rst new file mode 100644 index 000000000..19b99587b --- /dev/null +++ b/Documentation/translations/zh_CN/infiniband/tag_matching.rst @@ -0,0 +1,63 @@ +.. include:: ../disclaimer-zh_CN.rst + +:Original: Documentation/infiniband/tag_matching.rst + +:翻译: + + 司延腾 Yanteng Si <siyanteng@loongson.cn> + +:校译: + + 王普宇 Puyu Wang <realpuyuwang@gmail.com> + 时奎亮 Alex Shi <alexs@kernel.org> + +.. _cn_infiniband_tag_matching: + +============ +标签匹配逻辑 +============ + +MPI标准定义了一套规则,称为标签匹配,用于将源发送操作与目的接收匹配。以下参数必 +须与以下源和目的参数相匹配: + +* 沟通者 +* 用户标签--通配符(wild card)可由接收方指定 +* 来源等级--通配符可由接收方指定 +* 目的地等级 – wild + +排序规则要求,当一对以上的发送和接收消息信封可能匹配时,包括最早发布-发送和最早 +发布-接收的一对是必须用来满足匹配操作的一对。然而,这并不意味着标签是按照它们被 +创建的顺序消耗的,例如,如果早期的标签不能用来满足匹配规则,那么后来生成的标签 +可能被消耗。 + +当消息从发送方发送到接收方时,通信库可能试图在相应的匹配接收被发布之后或之前处 +理该操作。如果匹配的接收被发布,这就是一个预期的消息,否则就被称为一个意外的消 +息。实现时经常为这两种不同的匹配实例使用不同的匹配方案。 + +为了减少MPI库的内存占用,MPI实现通常使用两种不同的协议来实现这一目的: + +1. Eager协议--当发送方处理完发送时,完整的信息就会被发送。在send_cq中会收到 +一个完成发送的通知,通知缓冲区可以被重新使用。 + +2. Rendezvous协议--发送方在第一次通知接收方时发送标签匹配头,也许还有一部分 +数据。当相应的缓冲区被发布时,响应者将使用头中的信息,直接向匹配的缓冲区发起 +RDMA读取操作。为了使缓冲区得到重用,需要收到一个fin消息。 + +标签匹配的实现 +============== + +使用的匹配对象有两种类型,即发布的接收列表和意外消息列表。应用程序通过调用发布 +的接收列表中的MPI接收例程发布接收缓冲区,并使用MPI发送例程发布发送消息。发布的 +接收列表的头部可以由硬件来维护,而软件则要对这个列表进行跟踪。 + +当发送开始并到达接收端时,如果没有为这个到达的消息预先发布接收,它将被传递给软 +件并被放在意外(unexpect)消息列表中。否则,将对该匹配进行处理,包括交会处理, +如果合适的话,将数据传送到指定的接收缓冲区。这允许接收方MPI标签匹配与计算重叠。 + +当一个接收信息被发布时,通信库将首先检查软件的意外信息列表,以寻找一个匹配的接 +收信息。如果找到一个匹配的,数据就会被送到用户缓冲区,使用一个软件控制的协议。 +UCX的实现根据数据大小,使用急切或交会协议。如果没有找到匹配,整个预置的接收列 +表由硬件维护,并且有空间在这个列表中增加一个预置的接收,这个接收被传递给硬件。 +软件要对这个列表进行跟踪,以帮助处理MPI取消操作。此外,由于硬件和软件在标签匹 +配操作方面预计不会紧密同步,这个影子列表被用来检测预先发布的接收被传递到硬件的 +情况,因为匹配的意外消息正在从硬件传递到软件。 diff --git a/Documentation/translations/zh_CN/infiniband/user_mad.rst b/Documentation/translations/zh_CN/infiniband/user_mad.rst new file mode 100644 index 000000000..d9ab2edfb --- /dev/null +++ b/Documentation/translations/zh_CN/infiniband/user_mad.rst @@ -0,0 +1,164 @@ +.. include:: ../disclaimer-zh_CN.rst + +:Original: Documentation/infiniband/user_mad.rst + +:翻译: + + 司延腾 Yanteng Si <siyanteng@loongson.cn> + +:校译: + + 王普宇 Puyu Wang <realpuyuwang@gmail.com> + 时奎亮 Alex Shi <alexs@kernel.org> + +.. _cn_infiniband_user_mad: + +=============== +用户空间MAD访问 +=============== + +设备文件 +======== + + 每个InfiniBand设备的每个端口都有一个“umad”设备和一个“issm”设备连接。 + 例如,一个双端口的HCA将有两个umad设备和两个issm设备,而一个交换机将 + 有每个类型的一个设备(对于交换机端口0)。 + +创建MAD代理 +=========== + + 一个MAD代理可以通过填写一个结构体ib_user_mad_reg_req来创建,然后在 + 适当的设备文件的文件描述符上调用IB_USER_MAD_REGISTER_AGENT ioctl。 + 如果注册请求成功,结构体中会返回一个32位的ID。比如说:: + + struct ib_user_mad_reg_req req = { /* ... */ }; + ret = ioctl(fd, IB_USER_MAD_REGISTER_AGENT, (char *) &req); + if (!ret) + my_agent = req.id; + else + perror("agent register"); + + 代理可以通过IB_USER_MAD_UNREGISTER_AGENT ioctl取消注册。另外,所有 + 通过文件描述符注册的代理在描述符关闭时将被取消注册。 + + 2014 + 现在提供了一个新的注册IOctl,允许在注册时提供额外的字段。这个注册 + 调用的用户隐含了对pkey_index的使用(见下文)。现在提供了一个新的 + 注册IOctl,允许在注册时提供额外的字段。这个注册调用的用户隐含了对 + pkey_index的使用(见下文)。 + +接收MADs +======== + + 使用read()接收MAD。现在接收端支持RMPP。传给read()的缓冲区必须至少是 + 一个struct ib_user_mad + 256字节。比如说: + + 如果传递的缓冲区不足以容纳收到的MAD(RMPP),errno被设置为ENOSPC,需 + 要的缓冲区长度被设置在mad.length中。 + + 正常MAD(非RMPP)的读取示例:: + + struct ib_user_mad *mad; + mad = malloc(sizeof *mad + 256); + ret = read(fd, mad, sizeof *mad + 256); + if (ret != sizeof mad + 256) { + perror("read"); + free(mad); + } + + RMPP读取示例:: + + struct ib_user_mad *mad; + mad = malloc(sizeof *mad + 256); + ret = read(fd, mad, sizeof *mad + 256); + if (ret == -ENOSPC)) { + length = mad.length; + free(mad); + mad = malloc(sizeof *mad + length); + ret = read(fd, mad, sizeof *mad + length); + } + if (ret < 0) { + perror("read"); + free(mad); + } + + 除了实际的MAD内容外,其他结构体ib_user_mad字段将被填入收到的MAD的信 + 息。例如,远程LID将在mad.lid中。 + + 如果发送超时,将产生一个接收,mad.status设置为ETIMEDOUT。否则,当一个 + MAD被成功接收后,mad.status将是0。 + + poll()/select()可以用来等待一个MAD可以被读取。 + + poll()/select()可以用来等待,直到可以读取一个MAD。 + +发送MADs +======== + + MADs是用write()发送的。发送的代理ID应该填入MAD的id字段,目的地LID应该 + 填入lid字段,以此类推。发送端确实支持RMPP,所以可以发送任意长度的MAD。 + 比如说:: + + struct ib_user_mad *mad; + + mad = malloc(sizeof *mad + mad_length); + + /* fill in mad->data */ + + mad->hdr.id = my_agent; /* req.id from agent registration */ + mad->hdr.lid = my_dest; /* in network byte order... */ + /* etc. */ + + ret = write(fd, &mad, sizeof *mad + mad_length); + if (ret != sizeof *mad + mad_length) + perror("write"); + +交换IDs +======= + + umad设备的用户可以在发送的MAD中使用交换ID字段的低32位(也就是网络字节顺序中 + 最小有效的一半字段)来匹配请求/响应对。上面的32位是保留给内核使用的,在发送 + MAD之前会被改写。 + +P_Key索引处理 +============= + + 旧的ib_umad接口不允许为发送的MAD设置P_Key索引,也没有提供获取接收的MAD的 + P_Key索引的方法。一个带有pkey_index成员的struct ib_user_mad_hdr的新布局已 + 经被定义;然而,为了保持与旧的应用程序的二进制兼容性,除非在文件描述符被用于 + 其他用途之前调用IB_USER_MAD_ENABLE_PKEY或IB_USER_MAD_REGISTER_AGENT2 ioctl + 之一,否则不会使用这种新布局。 + + 在2008年9月,IB_USER_MAD_ABI_VERSION将被增加到6,默认使用新的ib_user_mad_hdr + 结构布局,并且IB_USER_MAD_ENABLE_PKEY ioctl将被删除。 + +设置IsSM功能位 +============== + + 要为一个端口设置IsSM功能位,只需打开相应的issm设备文件。如果IsSM位已经被设置,那 + 么打开调用将阻塞,直到该位被清除(或者如果O_NONBLOCK标志被传递给open(),则立即返 + 回,errno设置为EAGAIN)。当issm文件被关闭时,IsSM位将被清除。在issm文件上不能进 + 行任何读、写或其他操作。 + +/dev文件 +======== + +为了用 udev自动创建相应的字符设备文件,一个类似:: + + KERNEL=="umad*", NAME="infiniband/%k" + KERNEL=="issm*", NAME="infiniband/%k" + + 的规则可以被使用。它将创建节点的名字:: + + /dev/infiniband/umad0 + /dev/infiniband/issm0 + + 为第一个端口,以此类推。与这些设备相关的infiniband设备和端口可以从以下文件中确定:: + + /sys/class/infiniband_mad/umad0/ibdev + /sys/class/infiniband_mad/umad0/port + + 和:: + + /sys/class/infiniband_mad/issm0/ibdev + /sys/class/infiniband_mad/issm0/port diff --git a/Documentation/translations/zh_CN/infiniband/user_verbs.rst b/Documentation/translations/zh_CN/infiniband/user_verbs.rst new file mode 100644 index 000000000..970bc1a4e --- /dev/null +++ b/Documentation/translations/zh_CN/infiniband/user_verbs.rst @@ -0,0 +1,72 @@ +.. include:: ../disclaimer-zh_CN.rst + +:Original: Documentation/infiniband/user_verbs.rst + +:翻译: + + 司延腾 Yanteng Si <siyanteng@loongson.cn> + +:校译: + + 王普宇 Puyu Wang <realpuyuwang@gmail.com> + 时奎亮 Alex Shi <alexs@kernel.org> + +.. _cn_infiniband_user_verbs: + +================= +用户空间verbs访问 +================= + + ib_uverbs模块,通过启用CONFIG_INFINIBAND_USER_VERBS构建,使用户空间 + 通过“verbs”直接访问IB硬件,如InfiniBand架构规范第11章所述。 + + 要使用verbs,需要libibverbs库,可从https://github.com/linux-rdma/rdma-core。 + libibverbs包含一个独立于设备的API,用于使用ib_uverbs接口。libibverbs + 还需要为你的InfiniBand硬件提供适当的独立于设备的内核和用户空间驱动。例如, + 要使用Mellanox HCA,你需要安装ib_mthca内核模块和libmthca用户空间驱动。 + +用户-内核通信 +============= + + 用户空间通过/dev/infiniband/uverbsN字符设备与内核进行慢速路径、资源管理 + 操作的通信。快速路径操作通常是通过直接写入硬件寄存器mmap()到用户空间来完成 + 的,没有系统调用或上下文切换到内核。 + + 命令是通过在这些设备文件上的write()s发送给内核的。ABI在 + drivers/infiniband/include/ib_user_verbs.h中定义。需要内核响应的命令的结 + 构包含一个64位字段,用来传递一个指向输出缓冲区的指针。状态作为write()系统调 + 用的返回值被返回到用户空间。 + +资源管理 +======== + + 由于所有IB资源的创建和销毁都是通过文件描述符传递的命令完成的,所以内核可以跟 + 踪那些被附加到给定用户空间上下文的资源。ib_uverbs模块维护着idr表,用来在 + 内核指针和不透明的用户空间句柄之间进行转换,这样内核指针就不会暴露给用户空间, + 而用户空间也无法欺骗内核去跟踪一个假的指针。 + + 这也允许内核在一个进程退出时进行清理,并防止一个进程触及另一个进程的资源。 + +内存固定 +======== + + 直接的用户空间I/O要求与作为潜在I/O目标的内存区域保持在同一物理地址上。ib_uverbs + 模块通过get_user_pages()和put_page()调用来管理内存区域的固定和解除固定。它还核 + 算进程的pinned_vm中被固定的内存量,并检查非特权进程是否超过其RLIMIT_MEMLOCK限制。 + + 被多次固定的页面在每次被固定时都会被计数,所以pinned_vm的值可能会高估一个进程所 + 固定的页面数量。 + +/dev文件 +======== + + 要想用udev自动创建适当的字符设备文件,可以采用如下规则:: + + KERNEL=="uverbs*", NAME="infiniband/%k" + + 可以使用。 这将创建设备节点,名为:: + + /dev/infiniband/uverbs0 + + 等等。由于InfiniBand的用户空间verbs对于非特权进程来说应该是安全的,因此在udev规 + 则中加入适当的MODE或GROUP可能是有用的。 diff --git a/Documentation/translations/zh_CN/kernel-hacking/hacking.rst b/Documentation/translations/zh_CN/kernel-hacking/hacking.rst new file mode 100644 index 000000000..9112949b9 --- /dev/null +++ b/Documentation/translations/zh_CN/kernel-hacking/hacking.rst @@ -0,0 +1,707 @@ +.. include:: ../disclaimer-zh_CN.rst + +:Original: Documentation/kernel-hacking/hacking.rst + +:译者: + + 吴想成 Wu XiangCheng <bobwxc@email.cn> + +============== +内核骇客指北 +============== + +:作者: Rusty Russell + +引言 +===== + +欢迎咱优雅的读者们来阅读Rusty的非常不靠谱的Linux内核骇客(Hacking)指南。本文 +描述了内核代码的常见例程和一般要求:其目标是引导有经验的C程序员入门Linux内核 +开发。我回避了实现细节:这是代码要做的,也忽略了很多有用的例程。 + +在你读这篇文章之前,请理解我从来没有想过要写这篇文章,因为我的资历太低了; +但我一直想读这样的文章,自己写是唯一的方法。我希望它能成长为一个最佳实践、 +通用起点和其他信息的汇编。 + +玩家 +======= + +在任何时候,系统中的每个CPU都可以: + +- 与任何进程无关,服务于硬件中断; + +- 与任何进程无关,服务于软件中断(softirq)或子任务(tasklet); + +- 运行于内核空间中,与进程(用户上下文)相关联; + +- 在用户空间中运行进程。 + +它们之间有优先级顺序。最下面的两个可以互相抢占,但上面为严格的层次结构: +每个层级只能被上方的抢占。例如,当一个软中断在CPU上运行时,没有其他软中断 +会抢占它,但是硬件中断可以抢占它。不过,系统中的任何其他CPU都是独立执行的。 + +我们将会看到许多方法,用户上下文可以阻止中断,从而成为真正的不可抢占。 + +用户上下文 +------------ + +用户上下文是指当您从系统调用或其他陷阱进入时:就像用户空间一样,您可以被更 +重要的任务和中断抢占。您可以通过调用 :c:func:`schedule()` 进行睡眠。 + +.. note:: + + 在模块加载和卸载以及块设备层上的操作时,你始终处于用户上下文中。 + +在用户上下文中,当前 ``current`` 指针(指示我们当前正在执行的任务)是有效的, +且 :c:func:`in_interrupt()` ( ``include/linux/preempt.h`` )值为非(false)。 + +.. warning:: + + 请注意,如果您禁用了抢占或软中断(见下文),:c:func:`in_interrupt()` 会 + 返回假阳性。 + +硬件中断(Hard IRQs) +---------------------- + +像定时器、网卡和键盘等都是可能在任意时刻产生中断的真实硬件。内核运行中断 +处理程序,为硬件提供服务。内核确保处理程序永远不会重入:如果相同的中断到达, +它将被排队(或丢弃)。因为它会关闭中断,所以处理程序必须很快:通常它只是 +确认中断,标记一个“软件中断”以执行并退出。 + +您可以通过 in_hardirq() 返回真来判断您处于硬件中断状态。 + +.. warning:: + + 请注意,如果中断被禁用,这将返回假阳性(见下文)。 + +软件中断上下文:软中断(Softirqs)与子任务(Tasklets) +------------------------------------------------------- + +当系统调用即将返回用户空间或硬件中断处理程序退出时,任何标记为挂起(通常通 +过硬件中断)的“软件中断”将运行( ``kernel/softirq.c`` )。 + +此处完成了许多真正的中断处理工作。在向SMP过渡的早期,只有“bottom halves下半 +部”(BHs)机制,无法利用多个CPU的优势。在从那些一团糟的旧电脑切换过来后不久, +我们放弃了这个限制,转而使用“软中断”。 + +``include/linux/interrupt.h`` 列出了不同的软中断。定时器软中断是一个非常重要 +的软中断( ``include/linux/timer.h`` ):您可以注册它以在给定时间后为您调用 +函数。 + +软中断通常是一个很难处理的问题,因为同一个软中断将同时在多个CPU上运行。因此, +子任务( ``include/linux/interrupt.h`` )更常用:它们是动态可注册的(意味着 +您可以拥有任意数量),并且它们还保证任何子任务都只能在一个CPU上运行,不同的 +子任务也可以同时运行。 + +.. warning:: + + “tasklet”这个名字是误导性的:它们与“任务”无关。 + +你可以使用 :c:func:`in_softirq()` 宏( ``include/linux/preempt.h`` )来确认 +是否处于软中断(或子任务)中。 + +.. warning:: + + 注意,如果持有 :ref:`bottom half lock <local_bh_disable_zh>` 锁,这将返回 + 假阳性。 + +一些基本规则 +================ + +缺少内存保护 + 如果你损坏了内存,无论是在用户上下文还是中断上下文中,整个机器都会崩溃。 + 你确定你不能在用户空间里做你想做的事吗? + +缺少浮点或MMX + FPU上下文不会被保存;即使在用户上下文中,FPU状态也可能与当前进程不一致: + 您会弄乱某些用户进程的FPU状态。如果真的要这样做,就必须显式地保存/恢复 + 完整的FPU状态(并避免上下文切换)。这通常不是个好主意;请优先用定点算法。 + +严格的堆栈限制 + 对于大多数32位体系结构,根据配置选项的不同内核堆栈大约为3K到6K;对于大 + 多数64位机器,内核堆栈大约为14K,并且经常与中断共享,因此你无法使用全部。 + 应避免深度递归和栈上的巨型本地数组(用动态分配它们来代替)。 + +Linux内核是可移植的 + 就这样吧。您的代码应该是纯64位的,并且不依赖于字节序(endian)。您还应该 + 尽量减少CPU特定的东西,例如内联汇编(inline assembly)应该被干净地封装和 + 最小化以便于移植。一般来说,它应该局限于内核树中有体系结构依赖的部分。 + +输入输出控制(ioctls):避免编写新的系统调用 +============================================== + +系统调用(system call)通常看起来像这样:: + + asmlinkage long sys_mycall(int arg) + { + return 0; + } + + +首先,在大多数情况下,您无需创建新的系统调用。创建一个字符设备并为其实现适当 +的输入输出控制(ioctls)。这比系统调用灵活得多,不必写进每个体系结构的 +``include/asm/unistd.h`` 和 ``arch/kernel/entry.S`` 文件里,而且更容易被Linus +接受。 + +如果您的程序所做的只是读取或写入一些参数,请考虑实现 :c:func:`sysfs()` 接口。 + +在输入输出控制中,您处于进程的用户上下文。出现错误时,返回一个负的错误参数 +(errno,请参阅 ``include/uapi/asm-generic/errno-base.h`` 、 +``include/uapi/asm-generic/errno.h`` 和 ``include/linux/errno.h`` ),否则返 +回0。 + +在睡眠之后,您应该检查是否出现了信号:Unix/Linux处理信号的方法是暂时退出系统 +调用,并返回 ``-ERESTARTSYS`` 错误。系统调用入口代码将切换回用户上下文,处理 +信号处理程序,然后系统调用将重新启动(除非用户禁用了该功能)。因此,您应该准 +备好处理重新启动,例如若您处理某些数据结构到一半。 + +:: + + if (signal_pending(current)) + return -ERESTARTSYS; + + +如果你要做更长时间的计算:优先考虑用户空间。如果你真的想在内核中做这件事,你 +应该定期检查你是否需要让出CPU(请记得每个CPU都有协作多任务)。 +习惯用法:: + + cond_resched(); /* Will sleep */ + + +接口设计的小注释:UNIX系统调用的格言是“提供机制而不是策略 +Provide mechanism not policy”。 + +死锁的“配方” +==================== + +您不能调用任何可能睡眠的程序,除非: + +- 您处于用户上下文中。 + +- 你未拥有任何自旋锁。 + +- 您已经启用中断(实际上,Andi Kleen说调度代码将为您启用它们,但这可能不是 + 您想要的)。 + +注意,有些函数可能隐式地睡眠:常见的是用户空间访问函数(\*_user)和没有 +``GFP_ATOMIC`` 的内存分配函数。 + +您应该始终打开 ``CONFIG_DEBUG_ATOMIC_SLEEP`` 项来编译内核,如果您违反这些 +规则,它将警告您。如果你 **真的** 违反了规则,你最终会锁住你的电脑。 + +真的会这样。 + + +常用函数/程序 +=============== + +:c:func:`printk()` +------------------ + +定义于 ``include/linux/printk.h`` + +:c:func:`printk()` 将内核消息提供给控制台、dmesg和syslog守护进程。它对于调 +试和报告错误很有用,并且可以在中断上下文中使用,但是使用时要小心:如果机器 +的控制台中充斥着printk消息则会无法使用。它使用与ANSI C printf基本兼容的格式 +字符串,并通过C字符串串联为其提供第一个“优先”参数:: + + printk(KERN_INFO "i = %u\n", i); + + +参见 ``include/linux/kern_levels.h`` ;了解其他 ``KERN_`` 值;syslog将这些值 +解释为级别。特殊用法:打印IP地址使用:: + + __be32 ipaddress; + printk(KERN_INFO "my ip: %pI4\n", &ipaddress); + + +:c:func:`printk()` 内部使用的1K缓冲区,不捕获溢出。请确保足够使用。 + +.. note:: + + 当您开始在用户程序中将printf打成printk时,就知道自己是真正的内核程序员了 + :) + +.. note:: + + 另一个注释:最初的unix第六版源代码在其printf函数的顶部有一个注释:“printf + 不应该用于叽叽喳喳”。你也应该遵循此建议。 + +:c:func:`copy_to_user()` / :c:func:`copy_from_user()` / :c:func:`get_user()` / :c:func:`put_user()` +--------------------------------------------------------------------------------------------------- + +定义于 ``include/linux/uaccess.h`` / ``asm/uaccess.h`` + +**[睡眠]** + +:c:func:`put_user()` 和 :c:func:`get_user()` 用于从用户空间中获取和向用户空 +间中传出单个值(如int、char或long)。指向用户空间的指针永远不应该直接取消 +引用:应该使用这些程序复制数据。两者都返回 ``-EFAULT`` 或 0。 + +:c:func:`copy_to_user()` 和 :c:func:`copy_from_user()` 更通用:它们从/向用户 +空间复制任意数量的数据。 + +.. warning:: + + 与 :c:func:`put_user()` 和 :c:func:`get_user()` 不同,它们返回未复制的 + 数据量(即0仍然意味着成功)。 + +【是的,这个讨厌的接口真心让我尴尬。火爆的口水仗大概每年都会发生。 +—— Rusty Russell】 + +这些函数可以隐式睡眠。它不应该在用户上下文之外调用(没有意义)、调用时禁用中断 +或获得自旋锁。 + +:c:func:`kmalloc()`/:c:func:`kfree()` +------------------------------------- + +定义于 ``include/linux/slab.h`` + +**[可能睡眠:见下]** + +这些函数用于动态请求指针对齐的内存块,类似用户空间中的malloc和free,但 +:c:func:`kmalloc()` 需要额外的标志词。重要的值: + +``GFP_KERNEL`` + 可以睡眠和交换以释放内存。只允许在用户上下文中使用,但这是分配内存最可靠 + 的方法。 + +``GFP_ATOMIC`` + 不会睡眠。较 ``GFP_KERNEL`` 更不可靠,但可以从中断上下文调用。你 **应该** + 有一个很好的内存不足错误处理策略。 + +``GFP_DMA`` + 分配低于16MB的ISA DMA。如果你不知道那是什么,那你就不需要了。非常不可靠。 + +如果您看到一个从无效上下文警告消息调用的睡眠的函数,那么您可能在没有 +``GFP_ATOMIC`` 的情况下从中断上下文调用了一个睡眠的分配函数。你必须立即修复, +快点! + +如果你要分配至少 ``PAGE_SIZE`` ( ``asm/page.h`` 或 ``asm/page_types.h`` ) +字节,请考虑使用 :c:func:`__get_free_pages()` ( ``include/linux/gfp.h`` )。 +它采用顺序参数(0表示页面大小,1表示双页,2表示四页……)和与上述相同的内存 +优先级标志字。 + +如果分配的字节数超过一页,可以使用 :c:func:`vmalloc()` 。它将在内核映射中分 +配虚拟内存。此块在物理内存中不是连续的,但是MMU(内存管理单元)使它看起来像 +是为您准备好的连续空间(因此它只是看起来对cpu连续,对外部设备驱动程序则不然)。 +如果您真的需要为一些奇怪的设备提供大量物理上连续的内存,那么您就会遇到问题: +Linux对此支持很差,因为正在运行的内核中的内存碎片化会使它变得很困难。最好的 +方法是在引导过程的早期通过 :c:func:`alloc_bootmem()` 函数分配。 + +在创建自己的常用对象缓存之前,请考虑使用 ``include/linux/slab.h`` 中的slab +缓存。 + +:c:macro:`current` +------------------ + +定义于 ``include/asm/current.h`` + +此全局变量(其实是宏)包含指向当前任务结构(task structure)的指针,因此仅在 +用户上下文中有效。例如,当进程进行系统调用时,这将指向调用进程的任务结构。 +在中断上下文中不为空(**not NULL**)。 + +:c:func:`mdelay()`/:c:func:`udelay()` +------------------------------------- + +定义于 ``include/asm/delay.h`` / ``include/linux/delay.h`` + +:c:func:`udelay()` 和 :c:func:`ndelay()` 函数可被用于小暂停。不要对它们使用 +大的值,因为这样会导致溢出——帮助函数 :c:func:`mdelay()` 在这里很有用,或者 +考虑 :c:func:`msleep()`。 + +:c:func:`cpu_to_be32()`/:c:func:`be32_to_cpu()`/:c:func:`cpu_to_le32()`/:c:func:`le32_to_cpu()` +----------------------------------------------------------------------------------------------- + +定义于 ``include/asm/byteorder.h`` + +:c:func:`cpu_to_be32()` 系列函数(其中“32”可以替换为64或16,“be”可以替换为 +“le”)是在内核中进行字节序转换的常用方法:它们返回转换后的值。所有的变体也 +提供反向转换函数: +:c:func:`be32_to_cpu()` 等。 + +这些函数有两个主要的变体:指针变体,例如 :c:func:`cpu_to_be32p()` ,它获取 +指向给定类型的指针,并返回转换后的值。另一个变体是“in-situ”系列,例如 +:c:func:`cpu_to_be32s()` ,它转换指针引用的值,并返回void。 + +:c:func:`local_irq_save()`/:c:func:`local_irq_restore()` +-------------------------------------------------------- + +定义于 ``include/linux/irqflags.h`` + + +这些程序禁用本地CPU上的硬中断,并还原它们。它们是可重入的;在其一个 +``unsigned long flags`` 参数中保存以前的状态。如果您知道中断已启用,那么可 +直接使用 :c:func:`local_irq_disable()` 和 :c:func:`local_irq_enable()`。 + +.. _local_bh_disable_zh: + +:c:func:`local_bh_disable()`/:c:func:`local_bh_enable()` +-------------------------------------------------------- + +定义于 ``include/linux/bottom_half.h`` + + +这些程序禁用本地CPU上的软中断,并还原它们。它们是可重入的;如果之前禁用了 +软中断,那么在调用这对函数之后仍然会禁用它们。它们阻止软中断和子任务在当前 +CPU上运行。 + +:c:func:`smp_processor_id()` +---------------------------- + +定义于 ``include/linux/smp.h`` + +:c:func:`get_cpu()` 禁用抢占(这样您就不会突然移动到另一个cpu)并返回当前 +处理器号,介于0和 ``NR_CPUS`` 之间。请注意,CPU编号不一定是连续的。完成后, +使用 :c:func:`put_cpu()` 再次返回。 + +如果您知道您不能被另一个任务抢占(即您处于中断上下文中,或已禁用抢占),您 +可以使用 :c:func:`smp_processor_id()`。 + +``__init``/``__exit``/``__initdata`` +------------------------------------ + +定义于 ``include/linux/init.h`` + +引导之后,内核释放一个特殊的部分;用 ``__init`` 标记的函数和用 ``__initdata`` +标记的数据结构在引导完成后被丢弃:同样地,模块在初始化后丢弃此内存。 +``__exit`` 用于声明只在退出时需要的函数:如果此文件未编译为模块,则该函数将 +被删除。请参阅头文件以使用。请注意,使用 :c:func:`EXPORT_SYMBOL()` 或 +:c:func:`EXPORT_SYMBOL_GPL()` 将标记为 ``__init`` 的函数导出到模块是没有意义 +的——这将出问题。 + + +:c:func:`__initcall()`/:c:func:`module_init()` +---------------------------------------------- + +定义于 ``include/linux/init.h`` / ``include/linux/module.h`` + +内核的许多部分都作为模块(内核的可动态加载部分)良好服务。使用 +:c:func:`module_init()` 和 :c:func:`module_exit()` 宏可以简化代码编写,无需 +``#ifdef`` ,即可以作为模块运行或内置在内核中。 + +:c:func:`module_init()` 宏定义在模块插入时(如果文件编译为模块)或在引导时 +调用哪个函数:如果文件未编译为模块,:c:func:`module_init()` 宏将等效于 +:c:func:`__initcall()` ,它通过链接器的魔力确保在引导时调用该函数。 + +该函数可以返回一个错误值,以导致模块加载失败(不幸的是,如果将模块编译到内核 +中,则此操作无效)。此函数在启用中断的用户上下文中调用,因此可以睡眠。 + +:c:func:`module_exit()` +----------------------- + + +定义于 ``include/linux/module.h`` + +这个宏定义了在模块删除时要调用的函数(如果是编译到内核中的文件,则无用武之地)。 +只有在模块使用计数到零时才会调用它。这个函数也可以睡眠,但不能失败:当它返回 +时,所有的东西都必须清理干净。 + +注意,这个宏是可选的:如果它不存在,您的模块将不可移除(除非 ``rmmod -f`` )。 + +:c:func:`try_module_get()`/:c:func:`module_put()` +------------------------------------------------- + +定义于 ``include/linux/module.h`` + +这些函数会操作模块使用计数,以防止删除(如果另一个模块使用其导出的符号之一, +则无法删除模块,参见下文)。在调用模块代码之前,您应该在该模块上调用 +:c:func:`try_module_get()` :若失败,那么该模块将被删除,您应该将其视为不存在。 +若成功,您就可以安全地进入模块,并在完成后调用模块 :c:func:`module_put()` 。 + +大多数可注册结构体都有所有者字段,例如在 +:c:type:`struct file_operations <file_operations>` 结构体中,此字段应设置为 +宏 ``THIS_MODULE`` 。 + +等待队列 ``include/linux/wait.h`` +==================================== + +**[睡眠]** + +等待队列用于等待某程序在条件为真时唤醒另一程序。必须小心使用,以确保没有竞争 +条件。先声明一个 :c:type:`wait_queue_head_t` ,然后对希望等待该条件的进程声明 +一个关于它们自己的 :c:type:`wait_queue_entry_t` ,并将其放入队列中。 + +声明 +----- + +使用 :c:func:`DECLARE_WAIT_QUEUE_HEAD()` 宏声明一个 ``wait_queue_head_t`` , +或者在初始化代码中使用 :c:func:`init_waitqueue_head()` 程序。 + +排队 +----- + +将自己放在等待队列中相当复杂,因为你必须在检查条件之前将自己放入队列中。有一 +个宏可以来执行此操作: :c:func:`wait_event_interruptible()` +( ``include/linux/wait.h`` )第一个参数是等待队列头,第二个参数是计算的表达 +式;当该表达式为true时宏返回0,或者在接收到信号时返回 ``-ERESTARTSYS`` 。 +:c:func:`wait_event()` 版本会忽略信号。 + +唤醒排队任务 +------------- + +调用 :c:func:`wake_up()` ( ``include/linux/wait.h`` ),它将唤醒队列中的所有 +进程。例外情况:如果有一个进程设置了 ``TASK_EXCLUSIVE`` ,队列的其余部分将不 +会被唤醒。这个基本函数的其他变体也可以在同一个头文件中使用。 + +原子操作 +========= + +某些操作在所有平台上都有保证。第一类为操作 :c:type:`atomic_t` +( ``include/asm/atomic.h`` )的函数;它包含一个有符号整数(至少32位长), +您必须使用这些函数来操作或读取 :c:type:`atomic_t` 变量。 +:c:func:`atomic_read()` 和 :c:func:`atomic_set()` 获取并设置计数器,还有 +:c:func:`atomic_add()` ,:c:func:`atomic_sub()` ,:c:func:`atomic_inc()` , +:c:func:`atomic_dec()` 和 :c:func:`atomic_dec_and_test()` (如果递减为零, +则返回true)。 + +是的。它在原子变量为零时返回true(即!=0)。 + +请注意,这些函数比普通的算术运算速度慢,因此不应过度使用。 + +第二类原子操作是在 ``unsigned long`` ( ``include/linux/bitops.h`` )上的 +原子位操作。这些操作通常采用指向位模式(bit pattern)的指针,第0位是最低有效 +位。:c:func:`set_bit()`,:c:func:`clear_bit()` 和 :c:func:`change_bit()` 设置、 +清除和更改给定位。:c:func:`test_and_set_bit()` ,:c:func:`test_and_clear_bit()` +和 :c:func:`test_and_change_bit()` 执行相同的操作,但如果之前设置了位,则返回 +true;这些对于原子设置标志特别有用。 + +可以使用大于 ``BITS_PER_LONG`` 位的位索引调用这些操作。但结果在大端序平台上 +不太正常,所以最好不要这样做。 + +符号 +===== + +在内核内部,正常的链接规则仍然适用(即除非用static关键字将符号声明为文件范围, +否则它可以在内核中的任何位置使用)。但是对于模块,会保留一个特殊可导出符号表, +该表将入口点限制为内核内部。模块也可以导出符号。 + +:c:func:`EXPORT_SYMBOL()` +------------------------- + +定义于 ``include/linux/export.h`` + +这是导出符号的经典方法:动态加载的模块将能够正常使用符号。 + +:c:func:`EXPORT_SYMBOL_GPL()` +----------------------------- + +定义于 ``include/linux/export.h`` + + +类似于 :c:func:`EXPORT_SYMBOL()`,只是 :c:func:`EXPORT_SYMBOL_GPL()` 导出的 +符号只能由具有由 :c:func:`MODULE_LICENSE()` 指定GPL兼容许可证的模块看到。这 +意味着此函数被认为是一个内部实现问题,而不是一个真正的接口。一些维护人员和 +开发人员在添加一些新的API或功能时可能却需要导出 EXPORT_SYMBOL_GPL()。 + +:c:func:`EXPORT_SYMBOL_NS()` +---------------------------- + +定义于 ``include/linux/export.h`` + +这是 ``EXPORT_SYMBOL()`` 的变体,允许指定符号命名空间。符号名称空间记录于 +Documentation/core-api/symbol-namespaces.rst 。 + +:c:func:`EXPORT_SYMBOL_NS_GPL()` +-------------------------------- + +定义于 ``include/linux/export.h`` + +这是 ``EXPORT_SYMBOL_GPL()`` 的变体,允许指定符号命名空间。符号名称空间记录于 +Documentation/core-api/symbol-namespaces.rst 。 + +程序与惯例 +=========== + +双向链表 ``include/linux/list.h`` +----------------------------------- + +内核头文件中曾经有三组链表程序,但这一组是赢家。如果你对一个单链表没有特别迫切的 +需求,那么这是一个不错的选择。 + +通常 :c:func:`list_for_each_entry()` 很有用。 + +返回值惯例 +------------ + +对于在用户上下文中调用的代码,违背C语言惯例是很常见的,即返回0表示成功,返回 +负错误值(例如 ``-EFAULT`` )表示失败。这在一开始可能是不直观的,但在内核中 +相当普遍。 + +使用 :c:func:`ERR_PTR()` ( ``include/linux/err.h`` )将负错误值编码到指针中, +然后使用 :c:func:`IS_ERR()` 和 :c:func:`PTR_ERR()` 将其再取出:避免为错误值 +使用单独的指针参数。挺讨厌的,但的确是个好方式。 + +破坏编译 +---------- + +Linus和其他开发人员有时会更改开发内核中的函数或结构体名称;这样做不仅是为了 +让每个人都保持警惕,还反映了一个重大的更改(例如,不能再在打开中断的情况下 +调用,或者执行额外的检查,或者不执行以前捕获的检查)。通常这会附带发送一个 +相当全面的注释到相应的内核邮件列表中;请搜索存档以查看。简单地对文件进行全局 +替换通常只会让事情变得 **更糟** 。 + +初始化结构体成员 +------------------ + +初始化结构体的首选方法是使用指定的初始化器,如ISO C99所述。 +例如:: + + static struct block_device_operations opt_fops = { + .open = opt_open, + .release = opt_release, + .ioctl = opt_ioctl, + .check_media_change = opt_media_change, + }; + + +这使得很容易查找(grep),并且可以清楚地看到设置了哪些结构字段。你应该这样做, +因为它看起来很酷。 + +GNU 扩展 +---------- + +Linux内核中明确允许GNU扩展。请注意,由于缺乏通用性,一些更复杂的版本并没有 +得到很好的支持,但以下内容被认为是标准的(有关更多详细信息,请参阅GCC info页 +的“C 扩展”部分——是的,实际上是info页,手册页只是info中内容的简短摘要)。 + +- 内联函数 + +- 语句表达式(Statement expressions)(即({ 和 })结构)。 + + +- 声明函数/变量/类型的属性(__attribute__) + +- typeof + +- 零长度数组 + +- 宏变量 + +- 空指针运算 + +- 非常量(Non-Constant)初始化程序 + +- 汇编程序指令(在 arch/ 和 include/asm/ 之内) + +- 字符串函数名(__func__)。 + +- __builtin_constant_p() + +在内核中使用long long时要小心,gcc为其生成的代码非常糟糕:除法和乘法在i386上 +不能工作,因为内核环境中缺少用于它的gcc运行时函数。 + +C++ +--- + +在内核中使用C++通常是个坏主意,因为内核不提供必要的运行时环境,并且不为其 +测试包含文件。不过这仍然是可能的,但不建议。如果你真的想这么做,至少别用 +异常处理(exceptions)。 + +#if +--- + +通常认为,在头文件(或.c文件顶部)中使用宏来抽象函数比在源代码中使用“if”预 +处理器语句更干净。 + +把你的东西放进内核里 +====================== + +为了让你的东西更正式、补丁更整洁,还有一些工作要做: + +- 搞清楚你修改的代码属于谁。查看源文件的根目录、 ``MAINTAINERS`` 文件以及 + ``CREDITS`` 文件的最后一部分。你应该和此人协调,确保你没有重新发明轮子, + 或者尝试一些已经被拒绝的东西。 + + 确保你把你的名字和电子邮件地址放在你创建或修改的任何文件的顶部。当人们发 + 现一个缺陷,或者想要做出修改时,这是他们首先会看的地方。 + +- 通常你需要一个配置选项来支持你的内核编程。在适当的目录中编辑 ``Kconfig`` 。 + 配置语言很容易通过剪切和粘贴来使用,在 + Documentation/kbuild/kconfig-language.rst 中有完整的文档。 + + 在您对选项的描述中,请确保同时照顾到了专家用户和对此功能一无所知的用户。 + 在此说明任何不兼容和问题。结尾一定要写上“如有疑问,就选N”(或者是“Y”); + 这是针对那些看不懂你在说什么的人的。 + +- 编辑 ``Makefile`` :配置变量在这里导出,因此通常你只需添加一行 + “obj-$(CONFIG_xxx) += xxx.o”。语法记录在 + Documentation/kbuild/makefiles.rst 。 + +- 如果你认为自己做了一些有意义的事情,可以把自己放进 ``CREDITS`` ,通常不 + 止一个文件(无论如何你的名字都应该在源文件的顶部)。 ``MAINTAINERS`` + 意味着您希望在对子系统进行更改时得到询问,并了解缺陷;这意味着对某部分 + 代码做出更多承诺。 + +- 最后,别忘记去阅读 Documentation/process/submitting-patches.rst。 + +Kernel 仙女棒 +=============== + +浏览源代码时的一些收藏。请随意添加到此列表。 + +``arch/x86/include/asm/delay.h``:: + + #define ndelay(n) (__builtin_constant_p(n) ? \ + ((n) > 20000 ? __bad_ndelay() : __const_udelay((n) * 5ul)) : \ + __ndelay(n)) + + +``include/linux/fs.h``:: + + /* + * Kernel pointers have redundant information, so we can use a + * scheme where we can return either an error code or a dentry + * pointer with the same return value. + * + * This should be a per-architecture thing, to allow different + * error and pointer decisions. + */ + #define ERR_PTR(err) ((void *)((long)(err))) + #define PTR_ERR(ptr) ((long)(ptr)) + #define IS_ERR(ptr) ((unsigned long)(ptr) > (unsigned long)(-1000)) + +``arch/x86/include/asm/uaccess_32.h:``:: + + #define copy_to_user(to,from,n) \ + (__builtin_constant_p(n) ? \ + __constant_copy_to_user((to),(from),(n)) : \ + __generic_copy_to_user((to),(from),(n))) + + +``arch/sparc/kernel/head.S:``:: + + /* + * Sun people can't spell worth damn. "compatibility" indeed. + * At least we *know* we can't spell, and use a spell-checker. + */ + + /* Uh, actually Linus it is I who cannot spell. Too much murky + * Sparc assembly will do this to ya. + */ + C_LABEL(cputypvar): + .asciz "compatibility" + + /* Tested on SS-5, SS-10. Probably someone at Sun applied a spell-checker. */ + .align 4 + C_LABEL(cputypvar_sun4m): + .asciz "compatible" + + +``arch/sparc/lib/checksum.S:``:: + + /* Sun, you just can't beat me, you just can't. Stop trying, + * give up. I'm serious, I am going to kick the living shit + * out of you, game over, lights out. + */ + + +致谢 +===== + +感谢Andi Kleen提出点子,回答我的问题,纠正我的错误,充实内容等帮助。 +感谢Philipp Rumpf做了许多拼写和清晰度修复,以及一些优秀的不明显的点。 +感谢Werner Almesberger对 :c:func:`disable_irq()` 做了一个很好的总结, +Jes Sorensen和Andrea Arcangeli补充了一些注意事项。 +感谢Michael Elizabeth Chastain检查并补充了配置部分。 +感谢Telsa Gwynne教我DocBook。 diff --git a/Documentation/translations/zh_CN/kernel-hacking/index.rst b/Documentation/translations/zh_CN/kernel-hacking/index.rst new file mode 100644 index 000000000..df530de22 --- /dev/null +++ b/Documentation/translations/zh_CN/kernel-hacking/index.rst @@ -0,0 +1,22 @@ +.. _kernel_hacking_zh: + +.. include:: ../disclaimer-zh_CN.rst + +:Original: Documentation/kernel-hacking/index.rst + +:译者: + + 吴想成 Wu XiangCheng <bobwxc@email.cn> + +============= +内核骇客指南 +============= + +.. toctree:: + :maxdepth: 2 + + hacking + +TODO + +- locking diff --git a/Documentation/translations/zh_CN/locking/index.rst b/Documentation/translations/zh_CN/locking/index.rst new file mode 100644 index 000000000..f0b107076 --- /dev/null +++ b/Documentation/translations/zh_CN/locking/index.rst @@ -0,0 +1,43 @@ +.. SPDX-License-Identifier: GPL-2.0 +.. include:: ../disclaimer-zh_CN.rst + +:Original: Documentation/locking/index.rst + +:翻译: + + 唐艺舟 Tang Yizhou <tangyeechou@gmail.com> + +== +锁 +== + +.. toctree:: + :maxdepth: 1 + + mutex-design + spinlocks + +TODOList: + + * locktypes + * lockdep-design + * lockstat + * locktorture + * rt-mutex-design + * rt-mutex + * seqlock + * ww-mutex-design + * preempt-locking + * pi-futex + * futex-requeue-pi + * hwspinlock + * percpu-rw-semaphore + * robust-futexes + * robust-futex-ABI + +.. only:: subproject and html + + Indices + ======= + + * :ref:`genindex` diff --git a/Documentation/translations/zh_CN/locking/mutex-design.rst b/Documentation/translations/zh_CN/locking/mutex-design.rst new file mode 100644 index 000000000..6aad54372 --- /dev/null +++ b/Documentation/translations/zh_CN/locking/mutex-design.rst @@ -0,0 +1,145 @@ +.. SPDX-License-Identifier: GPL-2.0 +.. include:: ../disclaimer-zh_CN.rst + +:Original: Documentation/locking/mutex-design.rst + +:翻译: + + 唐艺舟 Tang Yizhou <tangyeechou@gmail.com> + +================ +通用互斥锁子系统 +================ + +:初稿: + + Ingo Molnar <mingo@redhat.com> + +:更新: + + Davidlohr Bueso <davidlohr@hp.com> + +什么是互斥锁? +-------------- + +在Linux内核中,互斥锁(mutex)指的是一个特殊的加锁原语,它在共享内存系统上 +强制保证序列化,而不仅仅是指在学术界或类似的理论教科书中出现的通用术语“相互 +排斥”。互斥锁是一种睡眠锁,它的行为类似于二进制信号量(semaphores),在 +2006年被引入时[1],作为后者的替代品。这种新的数据结构提供了许多优点,包括更 +简单的接口,以及在当时更少的代码量(见缺陷)。 + +[1] https://lwn.net/Articles/164802/ + +实现 +---- + +互斥锁由“struct mutex”表示,在include/linux/mutex.h中定义,并在 +kernel/locking/mutex.c中实现。这些锁使用一个原子变量(->owner)来跟踪 +它们生命周期内的锁状态。字段owner实际上包含的是指向当前锁所有者的 +`struct task_struct *` 指针,因此如果无人持有锁,则它的值为空(NULL)。 +由于task_struct的指针至少按L1_CACHE_BYTES对齐,低位(3)被用来存储额外 +的状态(例如,等待者列表非空)。在其最基本的形式中,它还包括一个等待队列和 +一个确保对其序列化访问的自旋锁。此外,CONFIG_MUTEX_SPIN_ON_OWNER=y的 +系统使用一个自旋MCS锁(->osq,译注:MCS是两个人名的合并缩写),在下文的 +(ii)中描述。 + +准备获得一把自旋锁时,有三种可能经过的路径,取决于锁的状态: + +(i) 快速路径:试图通过调用cmpxchg()修改锁的所有者为当前任务,以此原子化地 + 获取锁。这只在无竞争的情况下有效(cmpxchg()检查值是否为0,所以3个状态 + 比特必须为0)。如果锁处在竞争状态,代码进入下一个可能的路径。 + +(ii) 中速路径:也就是乐观自旋,当锁的所有者正在运行并且没有其它优先级更高的 + 任务(need_resched,需要重新调度)准备运行时,当前任务试图自旋来获得 + 锁。原理是,如果锁的所有者正在运行,它很可能不久就会释放锁。互斥锁自旋体 + 使用MCS锁排队,这样只有一个自旋体可以竞争互斥锁。 + + MCS锁(由Mellor-Crummey和Scott提出)是一个简单的自旋锁,它具有一些 + 理想的特性,比如公平,以及每个CPU在试图获得锁时在一个本地变量上自旋。 + 它避免了常见的“检测-设置”自旋锁实现导致的(CPU核间)缓存行回弹 + (cacheline bouncing)这种昂贵的开销。一个类MCS锁是为实现睡眠锁的 + 乐观自旋而专门定制的。这种定制MCS锁的一个重要特性是,它有一个额外的属性, + 当自旋体需要重新调度时,它们能够退出MCS自旋锁队列。这进一步有助于避免 + 以下场景:需要重新调度的MCS自旋体将继续自旋等待自旋体所有者,即将获得 + MCS锁时却直接进入慢速路径。 + +(iii) 慢速路径:最后的手段,如果仍然无法获得锁,该任务会被添加到等待队列中, + 休眠直到被解锁路径唤醒。在通常情况下,它以TASK_UNINTERRUPTIBLE状态 + 阻塞。 + +虽然从形式上看,内核互斥锁是可睡眠的锁,路径(ii)使它实际上成为混合类型。通过 +简单地不中断一个任务并忙着等待几个周期,而不是立即睡眠,这种锁已经被认为显著 +改善一些工作负载的性能。注意,这种技术也被用于读写信号量(rw-semaphores)。 + +语义 +---- + +互斥锁子系统检查并强制执行以下规则: + + - 每次只有一个任务可以持有该互斥锁。 + - 只有锁的所有者可以解锁该互斥锁。 + - 不允许多次解锁。 + - 不允许递归加锁/解锁。 + - 互斥锁只能通过API进行初始化(见下文)。 + - 一个任务不能在持有互斥锁的情况下退出。 + - 持有锁的内存区域不得被释放。 + - 被持有的锁不能被重新初始化。 + - 互斥锁不能用于硬件或软件中断上下文,如小任务(tasklet)和定时器。 + +当CONFIG DEBUG_MUTEXES被启用时,这些语义将被完全强制执行。此外,互斥锁 +调试代码还实现了一些其它特性,使锁的调试更容易、更快速: + + - 当打印到调试输出时,总是使用互斥锁的符号名称。 + - 加锁点跟踪,函数名符号化查找,系统持有的全部锁的列表,打印出它们。 + - 所有者跟踪。 + - 检测自我递归的锁并打印所有相关信息。 + - 检测多任务环形依赖死锁,并打印所有受影响的锁和任务(并且只限于这些任务)。 + + +接口 +---- +静态定义互斥锁:: + + DEFINE_MUTEX(name); + +动态初始化互斥锁:: + + mutex_init(mutex); + +以不可中断方式(uninterruptible)获取互斥锁:: + + void mutex_lock(struct mutex *lock); + void mutex_lock_nested(struct mutex *lock, unsigned int subclass); + int mutex_trylock(struct mutex *lock); + +以可中断方式(interruptible)获取互斥锁:: + + int mutex_lock_interruptible_nested(struct mutex *lock, + unsigned int subclass); + int mutex_lock_interruptible(struct mutex *lock); + +当原子变量减为0时,以可中断方式(interruptible)获取互斥锁:: + + int atomic_dec_and_mutex_lock(atomic_t *cnt, struct mutex *lock); + +释放互斥锁:: + + void mutex_unlock(struct mutex *lock); + +检测是否已经获取互斥锁:: + + int mutex_is_locked(struct mutex *lock); + +缺陷 +---- + +与它最初的设计和目的不同,'struct mutex' 是内核中最大的锁之一。例如:在 +x86-64上它是32字节,而 'struct semaphore' 是24字节,rw_semaphore是 +40字节。更大的结构体大小意味着更多的CPU缓存和内存占用。 + + +何时使用互斥锁 +-------------- + +总是优先选择互斥锁而不是任何其它锁原语,除非互斥锁的严格语义不合适,和/或临界区 +阻止锁被共享。 diff --git a/Documentation/translations/zh_CN/locking/spinlocks.rst b/Documentation/translations/zh_CN/locking/spinlocks.rst new file mode 100644 index 000000000..2017c01f0 --- /dev/null +++ b/Documentation/translations/zh_CN/locking/spinlocks.rst @@ -0,0 +1,149 @@ +.. SPDX-License-Identifier: GPL-2.0 +.. include:: ../disclaimer-zh_CN.rst + +:Original: Documentation/locking/spinlocks.rst + +:翻译: + + 唐艺舟 Tang Yizhou <tangyeechou@gmail.com> + +========== +加锁的教训 +========== + +教训 1:自旋锁 +============== + +加锁最基本的原语是自旋锁(spinlock):: + + static DEFINE_SPINLOCK(xxx_lock); + + unsigned long flags; + + spin_lock_irqsave(&xxx_lock, flags); + ... 这里是临界区 .. + spin_unlock_irqrestore(&xxx_lock, flags); + +上述代码总是安全的。自旋锁将在 _本地_ 禁用中断,但它本身将保证全局锁定。所以它 +将保证在该锁保护的区域内只有一个控制线程。即使在单处理器(UP)下也能很好的工作, +所以代码 _不_ 需要担心UP还是SMP的问题:自旋锁在两种情况下都能正常工作。 + + 注意!自旋锁对内存的潜在影响由下述文档进一步描述: + + Documentation/memory-barriers.txt + + (5) ACQUIRE operations. + + (6) RELEASE operations. + +上述代码通常非常简单(对大部分情况,你通常需要并且只希望有一个自旋锁——使用多个 +自旋锁会使事情变得更复杂,甚至更慢,而且通常仅仅在你 **理解的** 序列有被拆分的 +需求时才值得这么做:如果你不确定的话,请不惜一切代价避免这样做)。 + +这是关于自旋锁的唯一真正困难的部分:一旦你开始使用自旋锁,它们往往会扩展到你以前 +可能没有注意到的领域,因为你必须确保自旋锁正确地保护共享数据结构 **每一处** 被 +使用的地方。自旋锁是最容易被添加到完全独立于其它代码的地方(例如,没有人访问的 +内部驱动数据结构)的。 + + 注意!仅当你在跨CPU核访问时使用 **同一把** 自旋锁,对它的使用才是安全的。 + 这意味着所有访问共享变量的代码必须对它们想使用的自旋锁达成一致。 + +---- + +教训 2:读-写自旋锁 +=================== + +如果你的数据访问有一个非常自然的模式,倾向于从共享变量中读取数据,读-写自旋锁 +(rw_lock)有时是有用的。它们允许多个读者同时出现在同一个临界区,但是如果有人想 +改变变量,它必须获得一个独占的写锁。 + + 注意!读-写自旋锁比原始自旋锁需要更多的原子内存操作。除非读者的临界区很长, + 否则你最好只使用原始自旋锁。 + +例程看起来和上面一样:: + + rwlock_t xxx_lock = __RW_LOCK_UNLOCKED(xxx_lock); + + unsigned long flags; + + read_lock_irqsave(&xxx_lock, flags); + .. 仅读取信息的临界区 ... + read_unlock_irqrestore(&xxx_lock, flags); + + write_lock_irqsave(&xxx_lock, flags); + .. 读取和独占写信息 ... + write_unlock_irqrestore(&xxx_lock, flags); + +上面这种锁对于复杂的数据结构如链表可能会有用,特别是在不改变链表的情况下搜索其中 +的条目。读锁允许许多并发的读者。任何希望 **修改** 链表的代码将必须先获取写锁。 + + 注意!RCU锁更适合遍历链表,但需要仔细注意设计细节(见Documentation/RCU/listRCU.rst)。 + +另外,你不能把读锁“升级”为写锁,所以如果你在 _任何_ 时候需要做任何修改 +(即使你不是每次都这样做),你必须在一开始就获得写锁。 + + 注意!我们正在努力消除大多数情况下的读-写自旋锁的使用,所以请不要在没有达成 + 共识的情况下增加一个新的(相反,请参阅Documentation/RCU/rcu.rst以获得完整 + 信息)。 + +---- + +教训 3:重新审视自旋锁 +====================== + +上述的自旋锁原语绝不是唯一的。它们是最安全的,在所有情况下都能正常工作,但部分 +**因为** 它们是安全的,它们也是相当慢的。它们比原本需要的更慢,因为它们必须要 +禁用中断(在X86上只是一条指令,但却是一条昂贵的指令——而在其他体系结构上,情况 +可能更糟)。 + +如果你有必须保护跨CPU访问的数据结构且你想使用自旋锁的场景,你有可能使用代价小的 +自旋锁版本。当且仅当你知道某自旋锁永远不会在中断处理程序中使用,你可以使用非中断 +的版本:: + + spin_lock(&lock); + ... + spin_unlock(&lock); + +(当然,也可以使用相应的读-写锁版本)。这种自旋锁将同样可以保证独占访问,而且 +速度会快很多。如果你知道有关的数据只在“进程上下文”中被存取,即,不涉及中断, +这种自旋锁就有用了。 + +当这些版本的自旋锁涉及中断时,你不能使用的原因是会陷入死锁:: + + spin_lock(&lock); + ... + <- 中断来临: + spin_lock(&lock); + +一个中断试图对一个已经锁定的变量上锁。如果中断发生在另一个CPU上,不会有问题; +但如果中断发生在已经持有自旋锁的同一个CPU上,将 _会_ 有问题,因为该锁显然永远 +不会被释放(因为中断正在等待该锁,而锁的持有者被中断打断,并且无法继续执行, +直到中断处理结束)。 + +(这也是自旋锁的中断版本只需要禁用 _本地_ 中断的原因——在发生于其它CPU的中断中 +使用同一把自旋锁是没问题的,因为发生于其它CPU的中断不会打断已经持锁的CPU,所以 +锁的持有者可以继续执行并最终释放锁)。 + + Linus + +---- + +参考信息 +======== + +对于动态初始化,使用spin_lock_init()或rwlock_init()是合适的:: + + spinlock_t xxx_lock; + rwlock_t xxx_rw_lock; + + static int __init xxx_init(void) + { + spin_lock_init(&xxx_lock); + rwlock_init(&xxx_rw_lock); + ... + } + + module_init(xxx_init); + +对于静态初始化,使用DEFINE_SPINLOCK() / DEFINE_RWLOCK()或 +__SPIN_LOCK_UNLOCKED() / __RW_LOCK_UNLOCKED()是合适的。 diff --git a/Documentation/translations/zh_CN/loongarch/booting.rst b/Documentation/translations/zh_CN/loongarch/booting.rst new file mode 100644 index 000000000..fb6440c43 --- /dev/null +++ b/Documentation/translations/zh_CN/loongarch/booting.rst @@ -0,0 +1,48 @@ +.. SPDX-License-Identifier: GPL-2.0 + +.. include:: ../disclaimer-zh_CN.rst + +:Original: Documentation/loongarch/booting.rst + +:翻译: + + 司延腾 Yanteng Si <siyanteng@loongson.cn> + +==================== +启动 Linux/LoongArch +==================== + +:作者: 司延腾 <siyanteng@loongson.cn> +:日期: 2022年11月18日 + +BootLoader传递给内核的信息 +========================== + +LoongArch支持ACPI和FDT启动,需要传递给内核的信息包括memmap、initrd、cmdline、可 +选的ACPI/FDT表等。 + +内核在 `kernel_entry` 入口处被传递以下参数: + + - a0 = efi_boot: `efi_boot` 是一个标志,表示这个启动环境是否完全符合UEFI + 的要求。 + + - a1 = cmdline: `cmdline` 是一个指向内核命令行的指针。 + + - a2 = systemtable: `systemtable` 指向EFI的系统表,在这个阶段涉及的所有 + 指针都是物理地址。 + +Linux/LoongArch内核镜像文件头 +============================= + +内核镜像是EFI镜像。作为PE文件,它们有一个64字节的头部结构体,如下所示:: + + u32 MZ_MAGIC /* "MZ", MS-DOS 头 */ + u32 res0 = 0 /* 保留 */ + u64 kernel_entry /* 内核入口点 */ + u64 _end - _text /* 内核镜像有效大小 */ + u64 load_offset /* 加载内核镜像相对内存起始地址的偏移量 */ + u64 res1 = 0 /* 保留 */ + u64 res2 = 0 /* 保留 */ + u64 res3 = 0 /* 保留 */ + u32 LINUX_PE_MAGIC /* 魔术数 */ + u32 pe_header - _head /* 到PE头的偏移量 */ diff --git a/Documentation/translations/zh_CN/loongarch/features.rst b/Documentation/translations/zh_CN/loongarch/features.rst new file mode 100644 index 000000000..3886e635e --- /dev/null +++ b/Documentation/translations/zh_CN/loongarch/features.rst @@ -0,0 +1,8 @@ +.. SPDX-License-Identifier: GPL-2.0 + +.. include:: ../disclaimer-zh_CN.rst + +:Original: Documentation/loongarch/features.rst +:Translator: Huacai Chen <chenhuacai@loongson.cn> + +.. kernel-feat:: $srctree/Documentation/features loongarch diff --git a/Documentation/translations/zh_CN/loongarch/index.rst b/Documentation/translations/zh_CN/loongarch/index.rst new file mode 100644 index 000000000..0273a0834 --- /dev/null +++ b/Documentation/translations/zh_CN/loongarch/index.rst @@ -0,0 +1,27 @@ +.. SPDX-License-Identifier: GPL-2.0 + +.. include:: ../disclaimer-zh_CN.rst + +:Original: Documentation/loongarch/index.rst +:Translator: Huacai Chen <chenhuacai@loongson.cn> + +================= +LoongArch体系结构 +================= + +.. toctree:: + :maxdepth: 2 + :numbered: + + introduction + booting + irq-chip-model + + features + +.. only:: subproject and html + + Indices + ======= + + * :ref:`genindex` diff --git a/Documentation/translations/zh_CN/loongarch/introduction.rst b/Documentation/translations/zh_CN/loongarch/introduction.rst new file mode 100644 index 000000000..f3ec25b16 --- /dev/null +++ b/Documentation/translations/zh_CN/loongarch/introduction.rst @@ -0,0 +1,353 @@ +.. SPDX-License-Identifier: GPL-2.0 + +.. include:: ../disclaimer-zh_CN.rst + +:Original: Documentation/loongarch/introduction.rst +:Translator: Huacai Chen <chenhuacai@loongson.cn> + +============= +LoongArch介绍 +============= + +LoongArch是一种新的RISC ISA,在一定程度上类似于MIPS和RISC-V。LoongArch指令集 +包括一个精简32位版(LA32R)、一个标准32位版(LA32S)、一个64位版(LA64)。 +LoongArch定义了四个特权级(PLV0~PLV3),其中PLV0是最高特权级,用于内核;而PLV3 +是最低特权级,用于应用程序。本文档介绍了LoongArch的寄存器、基础指令集、虚拟内 +存以及其他一些主题。 + +寄存器 +====== + +LoongArch的寄存器包括通用寄存器(GPRs)、浮点寄存器(FPRs)、向量寄存器(VRs) +和用于特权模式(PLV0)的控制状态寄存器(CSRs)。 + +通用寄存器 +---------- + +LoongArch包括32个通用寄存器( ``$r0`` ~ ``$r31`` ),LA32中每个寄存器为32位宽, +LA64中每个寄存器为64位宽。 ``$r0`` 的内容总是固定为0,而其他寄存器在体系结构层面 +没有特殊功能。( ``$r1`` 算是一个例外,在BL指令中固定用作链接返回寄存器。) + +内核使用了一套LoongArch寄存器约定,定义在LoongArch ELF psABI规范中,详细描述参见 +:ref:`参考文献 <loongarch-references-zh_CN>`: + +================= =============== =================== ========== +寄存器名 别名 用途 跨调用保持 +================= =============== =================== ========== +``$r0`` ``$zero`` 常量0 不使用 +``$r1`` ``$ra`` 返回地址 否 +``$r2`` ``$tp`` TLS/线程信息指针 不使用 +``$r3`` ``$sp`` 栈指针 是 +``$r4``-``$r11`` ``$a0``-``$a7`` 参数寄存器 否 +``$r4``-``$r5`` ``$v0``-``$v1`` 返回值 否 +``$r12``-``$r20`` ``$t0``-``$t8`` 临时寄存器 否 +``$r21`` ``$u0`` 每CPU变量基地址 不使用 +``$r22`` ``$fp`` 帧指针 是 +``$r23``-``$r31`` ``$s0``-``$s8`` 静态寄存器 是 +================= =============== =================== ========== + +.. note:: + 注意: ``$r21`` 寄存器在ELF psABI中保留未使用,但是在Linux内核用于保 + 存每CPU变量基地址。该寄存器没有ABI命名,不过在内核中称为 ``$u0`` 。在 + 一些遗留代码中有时可能见到 ``$v0`` 和 ``$v1`` ,它们是 ``$a0`` 和 + ``$a1`` 的别名,属于已经废弃的用法。 + +浮点寄存器 +---------- + +当系统中存在FPU时,LoongArch有32个浮点寄存器( ``$f0`` ~ ``$f31`` )。在LA64 +的CPU核上,每个寄存器均为64位宽。 + +浮点寄存器的使用约定与LoongArch ELF psABI规范的描述相同: + +================= ================== =================== ========== +寄存器名 别名 用途 跨调用保持 +================= ================== =================== ========== +``$f0``-``$f7`` ``$fa0``-``$fa7`` 参数寄存器 否 +``$f0``-``$f1`` ``$fv0``-``$fv1`` 返回值 否 +``$f8``-``$f23`` ``$ft0``-``$ft15`` 临时寄存器 否 +``$f24``-``$f31`` ``$fs0``-``$fs7`` 静态寄存器 是 +================= ================== =================== ========== + +.. note:: + 注意:在一些遗留代码中有时可能见到 ``$fv0`` 和 ``$fv1`` ,它们是 + ``$fa0`` 和 ``$fa1`` 的别名,属于已经废弃的用法。 + + +向量寄存器 +---------- + +LoongArch现有两种向量扩展: + +- 128位向量扩展LSX(全称Loongson SIMD eXtention), +- 256位向量扩展LASX(全称Loongson Advanced SIMD eXtention)。 + +LSX使用 ``$v0`` ~ ``$v31`` 向量寄存器,而LASX则使用 ``$x0`` ~ ``$x31`` 。 + +浮点寄存器和向量寄存器是复用的,比如:在一个实现了LSX和LASX的核上, ``$x0`` 的 +低128位与 ``$v0`` 共用, ``$v0`` 的低64位与 ``$f0`` 共用,其他寄存器依此类推。 + +控制状态寄存器 +-------------- + +控制状态寄存器只能在特权模式(PLV0)下访问: + +================= ==================================== ========== +地址 全称描述 简称 +================= ==================================== ========== +0x0 当前模式信息 CRMD +0x1 异常前模式信息 PRMD +0x2 扩展部件使能 EUEN +0x3 杂项控制 MISC +0x4 异常配置 ECFG +0x5 异常状态 ESTAT +0x6 异常返回地址 ERA +0x7 出错(Faulting)虚拟地址 BADV +0x8 出错(Faulting)指令字 BADI +0xC 异常入口地址 EENTRY +0x10 TLB索引 TLBIDX +0x11 TLB表项高位 TLBEHI +0x12 TLB表项低位0 TLBELO0 +0x13 TLB表项低位1 TLBELO1 +0x18 地址空间标识符 ASID +0x19 低半地址空间页全局目录基址 PGDL +0x1A 高半地址空间页全局目录基址 PGDH +0x1B 页全局目录基址 PGD +0x1C 页表遍历控制低半部分 PWCL +0x1D 页表遍历控制高半部分 PWCH +0x1E STLB页大小 STLBPS +0x1F 缩减虚地址配置 RVACFG +0x20 CPU编号 CPUID +0x21 特权资源配置信息1 PRCFG1 +0x22 特权资源配置信息2 PRCFG2 +0x23 特权资源配置信息3 PRCFG3 +0x30+n (0≤n≤15) 数据保存寄存器 SAVEn +0x40 定时器编号 TID +0x41 定时器配置 TCFG +0x42 定时器值 TVAL +0x43 计时器补偿 CNTC +0x44 定时器中断清除 TICLR +0x60 LLBit相关控制 LLBCTL +0x80 实现相关控制1 IMPCTL1 +0x81 实现相关控制2 IMPCTL2 +0x88 TLB重填异常入口地址 TLBRENTRY +0x89 TLB重填异常出错(Faulting)虚地址 TLBRBADV +0x8A TLB重填异常返回地址 TLBRERA +0x8B TLB重填异常数据保存 TLBRSAVE +0x8C TLB重填异常表项低位0 TLBRELO0 +0x8D TLB重填异常表项低位1 TLBRELO1 +0x8E TLB重填异常表项高位 TLBEHI +0x8F TLB重填异常前模式信息 TLBRPRMD +0x90 机器错误控制 MERRCTL +0x91 机器错误信息1 MERRINFO1 +0x92 机器错误信息2 MERRINFO2 +0x93 机器错误异常入口地址 MERRENTRY +0x94 机器错误异常返回地址 MERRERA +0x95 机器错误异常数据保存 MERRSAVE +0x98 高速缓存标签 CTAG +0x180+n (0≤n≤3) 直接映射配置窗口n DMWn +0x200+2n (0≤n≤31) 性能监测配置n PMCFGn +0x201+2n (0≤n≤31) 性能监测计数器n PMCNTn +0x300 内存读写监视点整体控制 MWPC +0x301 内存读写监视点整体状态 MWPS +0x310+8n (0≤n≤7) 内存读写监视点n配置1 MWPnCFG1 +0x311+8n (0≤n≤7) 内存读写监视点n配置2 MWPnCFG2 +0x312+8n (0≤n≤7) 内存读写监视点n配置3 MWPnCFG3 +0x313+8n (0≤n≤7) 内存读写监视点n配置4 MWPnCFG4 +0x380 取指监视点整体控制 FWPC +0x381 取指监视点整体状态 FWPS +0x390+8n (0≤n≤7) 取指监视点n配置1 FWPnCFG1 +0x391+8n (0≤n≤7) 取指监视点n配置2 FWPnCFG2 +0x392+8n (0≤n≤7) 取指监视点n配置3 FWPnCFG3 +0x393+8n (0≤n≤7) 取指监视点n配置4 FWPnCFG4 +0x500 调试寄存器 DBG +0x501 调试异常返回地址 DERA +0x502 调试数据保存 DSAVE +================= ==================================== ========== + +ERA,TLBRERA,MERRERA和DERA有时也分别称为EPC,TLBREPC,MERREPC和DEPC。 + +基础指令集 +========== + +指令格式 +-------- + +LoongArch的指令字长为32位,一共有9种基本指令格式(以及一些变体): + +=========== ========================== +格式名称 指令构成 +=========== ========================== +2R Opcode + Rj + Rd +3R Opcode + Rk + Rj + Rd +4R Opcode + Ra + Rk + Rj + Rd +2RI8 Opcode + I8 + Rj + Rd +2RI12 Opcode + I12 + Rj + Rd +2RI14 Opcode + I14 + Rj + Rd +2RI16 Opcode + I16 + Rj + Rd +1RI21 Opcode + I21L + Rj + I21H +I26 Opcode + I26L + I26H +=========== ========================== + +Opcode是指令操作码,Rj和Rk是源操作数(寄存器),Rd是目标操作数(寄存器),Ra是 +4R-type格式特有的附加操作数(寄存器)。I8/I12/I14/I16/I21/I26分别是8位/12位/14位/ +16位/21位/26位的立即数。其中较长的21位和26位立即数在指令字中被分割为高位部分与低位 +部分,所以你们在这里的格式描述中能够看到I21L/I21H和I26L/I26H这样带后缀的表述。 + +指令列表 +-------- + +为了简便起见,我们在此只罗列一下指令名称(助记符),需要详细信息请阅读 +:ref:`参考文献 <loongarch-references-zh_CN>` 中的文档。 + +1. 算术运算指令:: + + ADD.W SUB.W ADDI.W ADD.D SUB.D ADDI.D + SLT SLTU SLTI SLTUI + AND OR NOR XOR ANDN ORN ANDI ORI XORI + MUL.W MULH.W MULH.WU DIV.W DIV.WU MOD.W MOD.WU + MUL.D MULH.D MULH.DU DIV.D DIV.DU MOD.D MOD.DU + PCADDI PCADDU12I PCADDU18I + LU12I.W LU32I.D LU52I.D ADDU16I.D + +2. 移位运算指令:: + + SLL.W SRL.W SRA.W ROTR.W SLLI.W SRLI.W SRAI.W ROTRI.W + SLL.D SRL.D SRA.D ROTR.D SLLI.D SRLI.D SRAI.D ROTRI.D + +3. 位域操作指令:: + + EXT.W.B EXT.W.H CLO.W CLO.D SLZ.W CLZ.D CTO.W CTO.D CTZ.W CTZ.D + BYTEPICK.W BYTEPICK.D BSTRINS.W BSTRINS.D BSTRPICK.W BSTRPICK.D + REVB.2H REVB.4H REVB.2W REVB.D REVH.2W REVH.D BITREV.4B BITREV.8B BITREV.W BITREV.D + MASKEQZ MASKNEZ + +4. 分支转移指令:: + + BEQ BNE BLT BGE BLTU BGEU BEQZ BNEZ B BL JIRL + +5. 访存读写指令:: + + LD.B LD.BU LD.H LD.HU LD.W LD.WU LD.D ST.B ST.H ST.W ST.D + LDX.B LDX.BU LDX.H LDX.HU LDX.W LDX.WU LDX.D STX.B STX.H STX.W STX.D + LDPTR.W LDPTR.D STPTR.W STPTR.D + PRELD PRELDX + +6. 原子操作指令:: + + LL.W SC.W LL.D SC.D + AMSWAP.W AMSWAP.D AMADD.W AMADD.D AMAND.W AMAND.D AMOR.W AMOR.D AMXOR.W AMXOR.D + AMMAX.W AMMAX.D AMMIN.W AMMIN.D + +7. 栅障指令:: + + IBAR DBAR + +8. 特殊指令:: + + SYSCALL BREAK CPUCFG NOP IDLE ERTN(ERET) DBCL(DBGCALL) RDTIMEL.W RDTIMEH.W RDTIME.D + ASRTLE.D ASRTGT.D + +9. 特权指令:: + + CSRRD CSRWR CSRXCHG + IOCSRRD.B IOCSRRD.H IOCSRRD.W IOCSRRD.D IOCSRWR.B IOCSRWR.H IOCSRWR.W IOCSRWR.D + CACOP TLBP(TLBSRCH) TLBRD TLBWR TLBFILL TLBCLR TLBFLUSH INVTLB LDDIR LDPTE + +虚拟内存 +======== + +LoongArch可以使用直接映射虚拟内存和分页映射虚拟内存。 + +直接映射虚拟内存通过CSR.DMWn(n=0~3)来进行配置,虚拟地址(VA)和物理地址(PA) +之间有简单的映射关系:: + + VA = PA + 固定偏移 + +分页映射的虚拟地址(VA)和物理地址(PA)有任意的映射关系,这种关系记录在TLB和页 +表中。LoongArch的TLB包括一个全相联的MTLB(Multiple Page Size TLB,多样页大小TLB) +和一个组相联的STLB(Single Page Size TLB,单一页大小TLB)。 + +缺省状态下,LA32的整个虚拟地址空间配置如下: + +============ =========================== =========================== +区段名 地址范围 属性 +============ =========================== =========================== +``UVRANGE`` ``0x00000000 - 0x7FFFFFFF`` 分页映射, 可缓存, PLV0~3 +``KPRANGE0`` ``0x80000000 - 0x9FFFFFFF`` 直接映射, 非缓存, PLV0 +``KPRANGE1`` ``0xA0000000 - 0xBFFFFFFF`` 直接映射, 可缓存, PLV0 +``KVRANGE`` ``0xC0000000 - 0xFFFFFFFF`` 分页映射, 可缓存, PLV0 +============ =========================== =========================== + +用户态(PLV3)只能访问UVRANGE,对于直接映射的KPRANGE0和KPRANGE1,将虚拟地址的第 +30~31位清零就等于物理地址。例如:物理地址0x00001000对应的非缓存直接映射虚拟地址 +是0x80001000,而其可缓存直接映射虚拟地址是0xA0001000。 + +缺省状态下,LA64的整个虚拟地址空间配置如下: + +============ ====================== ================================== +区段名 地址范围 属性 +============ ====================== ================================== +``XUVRANGE`` ``0x0000000000000000 - 分页映射, 可缓存, PLV0~3 + 0x3FFFFFFFFFFFFFFF`` +``XSPRANGE`` ``0x4000000000000000 - 直接映射, 可缓存 / 非缓存, PLV0 + 0x7FFFFFFFFFFFFFFF`` +``XKPRANGE`` ``0x8000000000000000 - 直接映射, 可缓存 / 非缓存, PLV0 + 0xBFFFFFFFFFFFFFFF`` +``XKVRANGE`` ``0xC000000000000000 - 分页映射, 可缓存, PLV0 + 0xFFFFFFFFFFFFFFFF`` +============ ====================== ================================== + +用户态(PLV3)只能访问XUVRANGE,对于直接映射的XSPRANGE和XKPRANGE,将虚拟地址的第 +60~63位清零就等于物理地址,而其缓存属性是通过虚拟地址的第60~61位配置的(0表示强序 +非缓存,1表示一致可缓存,2表示弱序非缓存)。 + +目前,我们仅用XKPRANGE来进行直接映射,XSPRANGE保留给以后用。 + +此处给出一个直接映射的例子:物理地址0x00000000_00001000的强序非缓存直接映射虚拟地址 +(在XKPRANGE中)是0x80000000_00001000,其一致可缓存直接映射虚拟地址(在XKPRANGE中) +是0x90000000_00001000,而其弱序非缓存直接映射虚拟地址(在XKPRANGE中)是0xA0000000_ +00001000。 + +Loongson与LoongArch的关系 +========================= + +LoongArch是一种RISC指令集架构(ISA),不同于现存的任何一种ISA,而Loongson(即龙 +芯)是一个处理器家族。龙芯包括三个系列:Loongson-1(龙芯1号)是32位处理器系列, +Loongson-2(龙芯2号)是低端64位处理器系列,而Loongson-3(龙芯3号)是高端64位处理 +器系列。旧的龙芯处理器基于MIPS架构,而新的龙芯处理器基于LoongArch架构。以龙芯3号 +为例:龙芯3A1000/3B1500/3A2000/3A3000/3A4000都是兼容MIPS的,而龙芯3A5000(以及将 +来的型号)都是基于LoongArch的。 + +.. _loongarch-references-zh_CN: + +参考文献 +======== + +Loongson官方网站(龙芯中科技术股份有限公司): + + http://www.loongson.cn/ + +Loongson与LoongArch的开发者网站(软件与文档资源): + + http://www.loongnix.cn/ + + https://github.com/loongson/ + + https://loongson.github.io/LoongArch-Documentation/ + +LoongArch指令集架构的文档: + + https://github.com/loongson/LoongArch-Documentation/releases/latest/download/LoongArch-Vol1-v1.00-CN.pdf (中文版) + + https://github.com/loongson/LoongArch-Documentation/releases/latest/download/LoongArch-Vol1-v1.00-EN.pdf (英文版) + +LoongArch的ELF psABI文档: + + https://github.com/loongson/LoongArch-Documentation/releases/latest/download/LoongArch-ELF-ABI-v1.00-CN.pdf (中文版) + + https://github.com/loongson/LoongArch-Documentation/releases/latest/download/LoongArch-ELF-ABI-v1.00-EN.pdf (英文版) + +Loongson与LoongArch的Linux内核源码仓库: + + https://git.kernel.org/pub/scm/linux/kernel/git/chenhuacai/linux-loongson.git diff --git a/Documentation/translations/zh_CN/loongarch/irq-chip-model.rst b/Documentation/translations/zh_CN/loongarch/irq-chip-model.rst new file mode 100644 index 000000000..fb5d23b49 --- /dev/null +++ b/Documentation/translations/zh_CN/loongarch/irq-chip-model.rst @@ -0,0 +1,157 @@ +.. SPDX-License-Identifier: GPL-2.0 + +.. include:: ../disclaimer-zh_CN.rst + +:Original: Documentation/loongarch/irq-chip-model.rst +:Translator: Huacai Chen <chenhuacai@loongson.cn> + +================================== +LoongArch的IRQ芯片模型(层级关系) +================================== + +目前,基于LoongArch的处理器(如龙芯3A5000)只能与LS7A芯片组配合工作。LoongArch计算机 +中的中断控制器(即IRQ芯片)包括CPUINTC(CPU Core Interrupt Controller)、LIOINTC( +Legacy I/O Interrupt Controller)、EIOINTC(Extended I/O Interrupt Controller)、 +HTVECINTC(Hyper-Transport Vector Interrupt Controller)、PCH-PIC(LS7A芯片组的主中 +断控制器)、PCH-LPC(LS7A芯片组的LPC中断控制器)和PCH-MSI(MSI中断控制器)。 + +CPUINTC是一种CPU内部的每个核本地的中断控制器,LIOINTC/EIOINTC/HTVECINTC是CPU内部的 +全局中断控制器(每个芯片一个,所有核共享),而PCH-PIC/PCH-LPC/PCH-MSI是CPU外部的中 +断控制器(在配套芯片组里面)。这些中断控制器(或者说IRQ芯片)以一种层次树的组织形式 +级联在一起,一共有两种层级关系模型(传统IRQ模型和扩展IRQ模型)。 + +传统IRQ模型 +=========== + +在这种模型里面,IPI(Inter-Processor Interrupt)和CPU本地时钟中断直接发送到CPUINTC, +CPU串口(UARTs)中断发送到LIOINTC,而其他所有设备的中断则分别发送到所连接的PCH-PIC/ +PCH-LPC/PCH-MSI,然后被HTVECINTC统一收集,再发送到LIOINTC,最后到达CPUINTC:: + + +-----+ +---------+ +-------+ + | IPI | --> | CPUINTC | <-- | Timer | + +-----+ +---------+ +-------+ + ^ + | + +---------+ +-------+ + | LIOINTC | <-- | UARTs | + +---------+ +-------+ + ^ + | + +-----------+ + | HTVECINTC | + +-----------+ + ^ ^ + | | + +---------+ +---------+ + | PCH-PIC | | PCH-MSI | + +---------+ +---------+ + ^ ^ ^ + | | | + +---------+ +---------+ +---------+ + | PCH-LPC | | Devices | | Devices | + +---------+ +---------+ +---------+ + ^ + | + +---------+ + | Devices | + +---------+ + +扩展IRQ模型 +=========== + +在这种模型里面,IPI(Inter-Processor Interrupt)和CPU本地时钟中断直接发送到CPUINTC, +CPU串口(UARTs)中断发送到LIOINTC,而其他所有设备的中断则分别发送到所连接的PCH-PIC/ +PCH-LPC/PCH-MSI,然后被EIOINTC统一收集,再直接到达CPUINTC:: + + +-----+ +---------+ +-------+ + | IPI | --> | CPUINTC | <-- | Timer | + +-----+ +---------+ +-------+ + ^ ^ + | | + +---------+ +---------+ +-------+ + | EIOINTC | | LIOINTC | <-- | UARTs | + +---------+ +---------+ +-------+ + ^ ^ + | | + +---------+ +---------+ + | PCH-PIC | | PCH-MSI | + +---------+ +---------+ + ^ ^ ^ + | | | + +---------+ +---------+ +---------+ + | PCH-LPC | | Devices | | Devices | + +---------+ +---------+ +---------+ + ^ + | + +---------+ + | Devices | + +---------+ + +ACPI相关的定义 +============== + +CPUINTC:: + + ACPI_MADT_TYPE_CORE_PIC; + struct acpi_madt_core_pic; + enum acpi_madt_core_pic_version; + +LIOINTC:: + + ACPI_MADT_TYPE_LIO_PIC; + struct acpi_madt_lio_pic; + enum acpi_madt_lio_pic_version; + +EIOINTC:: + + ACPI_MADT_TYPE_EIO_PIC; + struct acpi_madt_eio_pic; + enum acpi_madt_eio_pic_version; + +HTVECINTC:: + + ACPI_MADT_TYPE_HT_PIC; + struct acpi_madt_ht_pic; + enum acpi_madt_ht_pic_version; + +PCH-PIC:: + + ACPI_MADT_TYPE_BIO_PIC; + struct acpi_madt_bio_pic; + enum acpi_madt_bio_pic_version; + +PCH-MSI:: + + ACPI_MADT_TYPE_MSI_PIC; + struct acpi_madt_msi_pic; + enum acpi_madt_msi_pic_version; + +PCH-LPC:: + + ACPI_MADT_TYPE_LPC_PIC; + struct acpi_madt_lpc_pic; + enum acpi_madt_lpc_pic_version; + +参考文献 +======== + +龙芯3A5000的文档: + + https://github.com/loongson/LoongArch-Documentation/releases/latest/download/Loongson-3A5000-usermanual-1.02-CN.pdf (中文版) + + https://github.com/loongson/LoongArch-Documentation/releases/latest/download/Loongson-3A5000-usermanual-1.02-EN.pdf (英文版) + +龙芯LS7A芯片组的文档: + + https://github.com/loongson/LoongArch-Documentation/releases/latest/download/Loongson-7A1000-usermanual-2.00-CN.pdf (中文版) + + https://github.com/loongson/LoongArch-Documentation/releases/latest/download/Loongson-7A1000-usermanual-2.00-EN.pdf (英文版) + +.. note:: + - CPUINTC:即《龙芯架构参考手册卷一》第7.4节所描述的CSR.ECFG/CSR.ESTAT寄存器及其 + 中断控制逻辑; + - LIOINTC:即《龙芯3A5000处理器使用手册》第11.1节所描述的“传统I/O中断”; + - EIOINTC:即《龙芯3A5000处理器使用手册》第11.2节所描述的“扩展I/O中断”; + - HTVECINTC:即《龙芯3A5000处理器使用手册》第14.3节所描述的“HyperTransport中断”; + - PCH-PIC/PCH-MSI:即《龙芯7A1000桥片用户手册》第5章所描述的“中断控制器”; + - PCH-LPC:即《龙芯7A1000桥片用户手册》第24.3节所描述的“LPC中断”。 diff --git a/Documentation/translations/zh_CN/maintainer/configure-git.rst b/Documentation/translations/zh_CN/maintainer/configure-git.rst new file mode 100644 index 000000000..a45ea736f --- /dev/null +++ b/Documentation/translations/zh_CN/maintainer/configure-git.rst @@ -0,0 +1,62 @@ +.. include:: ../disclaimer-zh_CN.rst + +:Original: Documentation/maintainer/configure-git.rst + +:译者: + + 吴想成 Wu XiangCheng <bobwxc@email.cn> + +.. _configuregit_zh: + +Git配置 +======= + +本章讲述了维护者级别的git配置。 + +Documentation/maintainer/pull-requests.rst 中使用的标记分支应使用开发人员的 +GPG公钥进行签名。可以通过将 ``-u`` 标志传递给 ``git tag`` 来创建签名标记。 +但是,由于 *通常* 对同一项目使用同一个密钥,因此可以设置:: + + git config user.signingkey "keyname" + +或者手动编辑你的 ``.git/config`` 或 ``~/.gitconfig`` 文件:: + + [user] + name = Jane Developer + email = jd@domain.org + signingkey = jd@domain.org + +你可能需要告诉 ``git`` 去使用 ``gpg2``:: + + [gpg] + program = /path/to/gpg2 + +你可能也需要告诉 ``gpg`` 去使用哪个 ``tty`` (添加到你的shell rc文件中):: + + export GPG_TTY=$(tty) + + +创建链接到lore.kernel.org的提交 +------------------------------- + +http://lore.kernel.org 网站是所有涉及或影响内核开发的邮件列表的总存档。在这里 +存储补丁存档是推荐的做法,当维护人员将补丁应用到子系统树时,最好提供一个指向 +lore存档链接的标签,以便浏览提交历史的人可以找到某个更改背后的相关讨论和基本 +原理。链接标签如下所示: + + Link: https://lore.kernel.org/r/<message-id> + +通过在git中添加以下钩子,可以将此配置为在发布 ``git am`` 时自动执行: + +.. code-block:: none + + $ git config am.messageid true + $ cat >.git/hooks/applypatch-msg <<'EOF' + #!/bin/sh + . git-sh-setup + perl -pi -e 's|^Message-Id:\s*<?([^>]+)>?$|Link: https://lore.kernel.org/r/$1|g;' "$1" + test -x "$GIT_DIR/hooks/commit-msg" && + exec "$GIT_DIR/hooks/commit-msg" ${1+"$@"} + : + EOF + $ chmod a+x .git/hooks/applypatch-msg diff --git a/Documentation/translations/zh_CN/maintainer/index.rst b/Documentation/translations/zh_CN/maintainer/index.rst new file mode 100644 index 000000000..eb75ccea9 --- /dev/null +++ b/Documentation/translations/zh_CN/maintainer/index.rst @@ -0,0 +1,21 @@ +.. include:: ../disclaimer-zh_CN.rst + +:Original: Documentation/maintainer/index.rst + +============== +内核维护者手册 +============== + +本文档本是内核维护者手册的首页。 +本手册还需要大量完善!请自由提出(和编写)本手册的补充内容。 +*译注:指英文原版* + +.. toctree:: + :maxdepth: 2 + + configure-git + rebasing-and-merging + pull-requests + maintainer-entry-profile + modifying-patches + diff --git a/Documentation/translations/zh_CN/maintainer/maintainer-entry-profile.rst b/Documentation/translations/zh_CN/maintainer/maintainer-entry-profile.rst new file mode 100644 index 000000000..a1ee99c47 --- /dev/null +++ b/Documentation/translations/zh_CN/maintainer/maintainer-entry-profile.rst @@ -0,0 +1,92 @@ +.. include:: ../disclaimer-zh_CN.rst + +:Original: Documentation/maintainer/maintainer-entry-profile.rst + +:译者: + + 吴想成 Wu XiangCheng <bobwxc@email.cn> + +.. _maintainerentryprofile_zh: + +维护者条目概要 +============== + +维护人员条目概要补充了顶层过程文档(提交补丁,提交驱动程序……),增加了子系 +统/设备驱动程序本地习惯以及有关补丁提交生命周期的相关内容。贡献者使用此文档 +来调整他们的期望和避免常见错误;维护人员可以使用这些信息超越子系统层面查看 +是否有机会汇聚到通用实践中。 + + +总览 +---- + +提供了子系统如何操作的介绍。MAINTAINERS文件告诉了贡献者应发送某文件的补丁到哪, +但它没有传达其他子系统的本地基础设施和机制以协助开发。 + +请考虑以下问题: + +- 当补丁被本地树接纳或合并到上游时是否有通知? +- 子系统是否使用patchwork实例?Patchwork状态变更是否有通知? +- 是否有任何机器人或CI基础设施监视列表,或子系统是否使用自动测试反馈以便把 + 控接纳补丁? +- 被拉入-next的Git分支是哪个? +- 贡献者应针对哪个分支提交? +- 是否链接到其他维护者条目概要?例如一个设备驱动可能指向其父子系统的条目。 + 这使得贡献者意识到某维护者可能对提交链中其他维护者负有的义务。 + + +提交检查单补遗 +-------------- + +列出强制性和咨询性标准,超出通用标准“提交检查表,以便维护者检查一个补丁是否 +足够健康。例如:“通过checkpatch.pl,没有错误、没有警告。通过单元测试详见某处”。 + +提交检查单补遗还可以包括有关硬件规格状态的详细信息。例如,子系统接受补丁之前 +是否需要考虑在某个修订版上发布的规范。 + + +开发周期的关键日期 +------------------ + +提交者常常会误以为补丁可以在合并窗口关闭之前的任何时间发送,且下一个-rc1时仍 +可以。事实上,大多数补丁都需要在下一个合并窗口打开之前提前进入linux-next中。 +向提交者澄清关键日期(以-rc发布周为标志)以明确什么时候补丁会被考虑合并以及 +何时需要等待下一个-rc。 + +至少需要讲明: + +- 最后一个可以提交新功能的-rc: + 针对下一个合并窗口的新功能提交应该在此点之前首次发布以供考虑。在此时间点 + 之后提交的补丁应该明确他们的目标为下下个合并窗口,或者给出应加快进度被接受 + 的充足理由。通常新特性贡献者的提交应出现在-rc5之前。 + +- 最后合并-rc:合并决策的最后期限。 + 向贡献者指出尚未接受的补丁集需要等待下下个合并窗口。当然,维护者没有义务 + 接受所有给定的补丁集,但是如果审阅在此时间点尚未结束,那么希望贡献者应该 + 等待并在下一个合并窗口重新提交。 + +可选项: + +- 开发基线分支的首个-rc,列在概述部分,视为已为新提交做好准备。 + + +审阅节奏 +-------- + +贡献者最担心的问题之一是:补丁集已发布却未收到反馈,应在多久后发送提醒。除了 +指定在重新提交之前要等待多长时间,还可以指示更新的首选样式;例如,重新发送 +整个系列,或私下发送提醒邮件。本节也可以列出本区域的代码审阅方式,以及获取 +不能直接从维护者那里得到的反馈的方法。 + + +现有概要 +-------- + +这里列出了现有的维护人员条目概要;我们可能会想要在不久的将来做一些不同的事情。 + +.. toctree:: + :maxdepth: 1 + + ../doc-guide/maintainer-profile + ../../../nvdimm/maintainer-entry-profile + ../../../riscv/patch-acceptance diff --git a/Documentation/translations/zh_CN/maintainer/modifying-patches.rst b/Documentation/translations/zh_CN/maintainer/modifying-patches.rst new file mode 100644 index 000000000..7f6326137 --- /dev/null +++ b/Documentation/translations/zh_CN/maintainer/modifying-patches.rst @@ -0,0 +1,51 @@ +.. include:: ../disclaimer-zh_CN.rst + +:Original: Documentation/maintainer/modifying-patches.rst + +:译者: + + 吴想成 Wu XiangCheng <bobwxc@email.cn> + +.. _modifyingpatches_zh: + +修改补丁 +======== + +如果你是子系统或者分支的维护者,由于代码在你的和提交者的树中并不完全相同, +有时你需要稍微修改一下收到的补丁以合并它们。 + +如果你严格遵守开发者来源证书的规则(c),你应该要求提交者重做,但这完全是会 +适得其反的时间、精力浪费。规则(b)允许你调整代码,但这样修改提交者的代码并 +让他背书你的错误是非常不礼貌的。为解决此问题,建议在你之前最后一个 +Signed-off-by标签和你的之间添加一行,以指示更改的性质。这没有强制性要求,最 +好在描述前面加上你的邮件和/或姓名,用方括号括住整行,以明显指出你对最后一刻 +的更改负责。例如:: + + Signed-off-by: Random J Developer <random@developer.example.org> + [lucky@maintainer.example.org: struct foo moved from foo.c to foo.h] + Signed-off-by: Lucky K Maintainer <lucky@maintainer.example.org> + +如果您维护着一个稳定的分支,并希望同时明确贡献、跟踪更改、合并修复,并保护 +提交者免受责难,这种做法尤其有用。请注意,在任何情况下都不得更改作者的身份 +(From头),因为它会在变更日志中显示。 + +向后移植(back-port)人员特别要注意:为了便于跟踪,请在提交消息的顶部(即主题行 +之后)插入补丁的来源,这是一种常见而有用的做法。例如,我们可以在3.x稳定版本 +中看到以下内容:: + + Date: Tue Oct 7 07:26:38 2014 -0400 + + libata: Un-break ATA blacklist + + commit 1c40279960bcd7d52dbdf1d466b20d24b99176c8 upstream. + +下面是一个旧的内核在某补丁被向后移植后会出现的:: + + Date: Tue May 13 22:12:27 2008 +0200 + + wireless, airo: waitbusy() won't delay + + [backport of 2.6 commit b7acbdfbd1f277c1eb23f344f899cfa4cd0bf36a] + +不管什么格式,这些信息都为人们跟踪你的树,以及试图解决你树中的错误的人提供了 +有价值的帮助。 diff --git a/Documentation/translations/zh_CN/maintainer/pull-requests.rst b/Documentation/translations/zh_CN/maintainer/pull-requests.rst new file mode 100644 index 000000000..ce9725f46 --- /dev/null +++ b/Documentation/translations/zh_CN/maintainer/pull-requests.rst @@ -0,0 +1,148 @@ +.. include:: ../disclaimer-zh_CN.rst + +:Original: Documentation/maintainer/pull-requests.rst + +:译者: + + 吴想成 Wu XiangCheng <bobwxc@email.cn> + +.. _pullrequests_zh: + +如何创建拉取请求 +================ + +本章描述维护人员如何创建并向其他维护人员提交拉取请求。这对将更改从一个维护者 +树转移到另一个维护者树非常有用。 + +本文档由Tobin C. Harding(当时他尚不是一名经验丰富的维护人员)编写,内容主要 +来自Greg Kroah Hartman和Linus Torvalds在LKML上的评论。Jonathan Corbet和Mauro +Carvalho Chehab提出了一些建议和修改。错误不可避免,如有问题,请找Tobin C. +Harding <me@tobin.cc>。 + +原始邮件线程:: + + https://lore.kernel.org/r/20171114110500.GA21175@kroah.com + + +创建分支 +-------- + +首先,您需要将希望包含拉取请求里的所有更改都放在单独分支中。通常您将基于某开发 +人员树的一个分支,一般是打算向其发送拉取请求的开发人员。 + +为了创建拉取请求,您必须首先标记刚刚创建的分支。建议您选择一个有意义的标记名, +以即使过了一段时间您和他人仍能理解的方式。在名称中包含源子系统和目标内核版本 +的指示也是一个好的做法。 + +Greg提供了以下内容。对于一个含有drivers/char中混杂事项、将应用于4.15-rc1内核的 +拉取请求,可以命名为 ``char-misc-4.15-rc1`` 。如果要在 ``char-misc-next`` 分支 +上打上此标记,您可以使用以下命令:: + + git tag -s char-misc-4.15-rc1 char-misc-next + +这将在 ``char-misc-next`` 分支的最后一个提交上创建一个名为 ``char-misc-4.15-rc1`` +的标记,并用您的gpg密钥签名(参见 Documentation/maintainer/configure-git.rst )。 + +Linus只接受基于签名过的标记的拉取请求。其他维护者可能会有所不同。 + +当您运行上述命令时 ``git`` 会打开编辑器要求你描述一下这个标记。在本例中您需要 +描述拉取请求,所以请概述一下包含的内容,为什么要合并,是否完成任何测试。所有 +这些信息都将留在标记中,然后在维护者合并拉取请求时保留在合并提交中。所以把它 +写好,它将永远留在内核中。 + +正如Linus所说:: + + 不管怎么样,至少对我来说,重要的是 *信息* 。我需要知道我在拉取什么、 + 为什么我要拉取。我也希望将此消息用于合并消息,因此它不仅应该对我有 + 意义,也应该可以成为一个有意义的历史记录。 + + 注意,如果拉取请求有一些不寻常的地方,请详细说明。如果你修改了并非 + 由你维护的文件,请解释 **为什么** 。我总会在差异中看到的,如果你不 + 提的话,我只会觉得分外可疑。当你在合并窗口后给我发新东西的时候, + (甚至是比较重大的错误修复),不仅需要解释做了什么、为什么这么做, + 还请解释一下 **时间问题** 。为什么错过了合并窗口…… + + 我会看你写在拉取请求邮件和签名标记里面的内容,所以根据你的工作流, + 你可以在签名标记里面描述工作内容(也会自动放进拉取请求邮件),也 + 可以只在标记里面放个占位符,稍后在你实际发给我拉取请求时描述工作内容。 + + 是的,我会编辑这些消息。部分因为我需要做一些琐碎的格式调整(整体缩进、 + 括号等),也因为此消息可能对我有意义(描述了冲突或一些个人问题)而对 + 合并提交信息上下文没啥意义,因此我需要尽力让它有意义起来。我也会 + 修复一些拼写和语法错误,特别是非母语者(母语者也是;^)。但我也会删掉 + 或增加一些内容。 + + Linus + +Greg给出了一个拉取请求的例子:: + + Char/Misc patches for 4.15-rc1 + + Here is the big char/misc patch set for the 4.15-rc1 merge window. + Contained in here is the normal set of new functions added to all + of these crazy drivers, as well as the following brand new + subsystems: + - time_travel_controller: Finally a set of drivers for the + latest time travel bus architecture that provides i/o to + the CPU before it asked for it, allowing uninterrupted + processing + - relativity_shifters: due to the affect that the + time_travel_controllers have on the overall system, there + was a need for a new set of relativity shifter drivers to + accommodate the newly formed black holes that would + threaten to suck CPUs into them. This subsystem handles + this in a way to successfully neutralize the problems. + There is a Kconfig option to force these to be enabled + when needed, so problems should not occur. + + All of these patches have been successfully tested in the latest + linux-next releases, and the original problems that it found have + all been resolved (apologies to anyone living near Canberra for the + lack of the Kconfig options in the earlier versions of the + linux-next tree creations.) + + Signed-off-by: Your-name-here <your_email@domain> + + +此标记消息格式就像一个git提交。顶部有一行“总结标题”, 一定要在下面sign-off。 + +现在您已经有了一个本地签名标记,您需要将它推送到可以被拉取的位置:: + + git push origin char-misc-4.15-rc1 + + +创建拉取请求 +------------ + +最后要做的是创建拉取请求消息。可以使用 ``git request-pull`` 命令让 ``git`` +为你做这件事,但它需要确定你想拉取什么,以及拉取针对的基础(显示正确的拉取 +更改和变更状态)。以下命令将生成一个拉取请求:: + + git request-pull master git://git.kernel.org/pub/scm/linux/kernel/git/gregkh/char-misc.git/ char-misc-4.15-rc1 + +引用Greg的话:: + + 此命令要求git比较从“char-misc-4.15-rc1”标记位置到“master”分支头(上述 + 例子中指向了我从Linus的树分叉的地方,通常是-rc发布)的差异,并去使用 + git:// 协议拉取。如果你希望使用 https:// 协议,也可以用在这里(但是请 + 注意,部分人由于防火墙问题没法用https协议拉取)。 + + 如果char-misc-4.15-rc1标记没有出现在我要求拉取的仓库中,git会提醒 + 它不在那里,所以记得推送到公开地方。 + + “git request-pull”会包含git树的地址和需要拉取的特定标记,以及标记 + 描述全文(详尽描述标记)。同时它也会创建此拉取请求的差异状态和单个 + 提交的缩短日志。 + +Linus回复说他倾向于 ``git://`` 协议。其他维护者可能有不同的偏好。另外,请注意 +如果你创建的拉取请求没有签名标记, ``https://`` 可能是更好的选择。完整的讨论 +请看原邮件。 + + +提交拉取请求 +------------ + +拉取请求的提交方式与普通补丁相同。向维护人员发送内联电子邮件并抄送LKML以及 +任何必要特定子系统的列表。对Linus的拉取请求通常有如下主题行:: + + [GIT PULL] <subsystem> changes for v4.15-rc1 diff --git a/Documentation/translations/zh_CN/maintainer/rebasing-and-merging.rst b/Documentation/translations/zh_CN/maintainer/rebasing-and-merging.rst new file mode 100644 index 000000000..83b7dabfe --- /dev/null +++ b/Documentation/translations/zh_CN/maintainer/rebasing-and-merging.rst @@ -0,0 +1,165 @@ +.. SPDX-License-Identifier: GPL-2.0 + +.. include:: ../disclaimer-zh_CN.rst + +:Original: Documentation/maintainer/rebasing-and-merging.rst + +:译者: + + 吴想成 Wu XiangCheng <bobwxc@email.cn> + +========== +变基与合并 +========== + +一般来说,维护子系统需要熟悉Git源代码管理系统。Git是一个功能强大的工具,有 +很多功能;就像这类工具常出现的情况一样,使用这些功能的方法有对有错。本文档 +特别介绍了变基与合并的用法。维护者经常在错误使用这些工具时遇到麻烦,但避免 +问题实际上并不那么困难。 + +总的来说,需要注意的一点是:与许多其他项目不同,内核社区并不害怕在其开发历史 +中看到合并提交。事实上,考虑到该项目的规模,避免合并几乎是不可能的。维护者会 +在希望避免合并时遇到一些问题,而过于频繁的合并也会带来另一些问题。 + +变基 +==== + +“变基(Rebase)”是更改存储库中一系列提交的历史记录的过程。有两种不同型的操作 +都被称为变基,因为这两种操作都使用 ``git rebase`` 命令,但它们之间存在显著 +差异: + + - 更改一系列补丁的父提交(起始提交)。例如,变基操作可以将基于上一内核版本 + 的一个补丁集重建到当前版本上。在下面的讨论中,我们将此操作称为“变根”。 + + - 通过修复(或删除)损坏的提交、添加补丁、添加标记以更改一系列补丁的历史, + 来提交变更日志或更改已应用提交的顺序。在下文中,这种类型的操作称为“历史 + 修改” + +术语“变基”将用于指代上述两种操作。如果使用得当,变基可以产生更清晰、更整洁的 +开发历史;如果使用不当,它可能会模糊历史并引入错误。 + +以下一些经验法则可以帮助开发者避免最糟糕的变基风险: + + - 已经发布到你私人系统之外世界的历史通常不应更改。其他人可能会拉取你的树 + 的副本,然后基于它进行工作;修改你的树会给他们带来麻烦。如果工作需要变基, + 这通常是表明它还没有准备好提交到公共存储库的信号。 + + 但是,总有例外。有些树(linux-next是一个典型的例子)由于它们的需要经常 + 变基,开发人员知道不要基于它们来工作。开发人员有时会公开一个不稳定的分支, + 供其他人或自动测试服务进行测试。如果您确实以这种方式公开了一个可能不稳定 + 的分支,请确保潜在使用者知道不要基于它来工作。 + + - 不要在包含由他人创建的历史的分支上变基。如果你从别的开发者的仓库拉取了变更, + 那你现在就成了他们历史记录的保管人。你不应该改变它,除了少数例外情况。例如 + 树中有问题的提交必须显式恢复(即通过另一个补丁修复),而不是通过修改历史而 + 消失。 + + - 没有合理理由,不要对树变根。仅为了切换到更新的基或避免与上游储存库的合并 + 通常不是合理理由。 + + - 如果你必须对储存库进行变根,请不要随机选取一个提交作为新基。在发布节点之间 + 内核通常处于一个相对不稳定的状态;基于其中某点进行开发会增加遇到意外错误的 + 几率。当一系列补丁必须移动到新基时,请选择移动到一个稳定节点(例如-rc版本 + 节点)。 + + - 请知悉对补丁系列进行变根(或做明显的历史修改)会改变它们的开发环境,且很 + 可能使做过的大部分测试失效。一般来说,变基后的补丁系列应当像新代码一样对 + 待,并重新测试。 + +合并窗口麻烦的一个常见原因是,Linus收到了一个明显在拉取请求发送之前不久才变根 +(通常是变根到随机的提交上)的补丁系列。这样一个系列被充分测试的可能性相对较 +低,拉取请求被接受的几率也同样较低。 + +相反,如果变基仅限于私有树、提交基于一个通用的起点、且经过充分测试,则引起 +麻烦的可能性就很低。 + +合并 +==== + +内核开发过程中,合并是一个很常见的操作;5.1版本开发周期中有超过1126个合并 +——差不多占了整体的9%。内核开发工作积累在100多个不同的子系统树中,每个 +子系统树都可能包含多个主题分支;每个分支通常独立于其他分支进行开发。因此 +在任何给定分支进入上游储存库之前,至少需要一次合并。 + +许多项目要求拉取请求中的分支基于当前主干,这样历史记录中就不会出现合并提交。 +内核并不是这样;任何为了避免合并而重新对分支变基都很可能导致麻烦。 + +子系统维护人员发现他们必须进行两种类型的合并:从较低层级的子系统树和从其他 +子系统树(同级树或主线)进行合并。这两种情况下要遵循的最佳实践是不同的。 + +合并较低层级树 +-------------- + +较大的子系统往往有多个级别的维护人员,较低级别的维护人员向较高级别发送拉取 +请求。合并这样的请求执行几乎肯定会生成一个合并提交;这也是应该的。实际上, +子系统维护人员可能希望在极少数快进合并情况下使用 ``-–no-ff`` 标志来强制添加 +合并提交,以便记录合并的原因。 **任何** 类型的合并的变更日志必须说明 +*为什么* 合并。对于较低级别的树,“为什么”通常是对该取所带来的变化的总结。 + +各级维护人员都应在他们的拉取请求上使用经签名的标签,上游维护人员应在拉取分支 +时验证标签。不这样做会威胁整个开发过程的安全。 + +根据上面列出的规则,一旦您将其他人的历史记录合并到树中,您就不得对该分支进行 +变基,即使您能够这样做。 + +合并同级树或上游树 +------------------ + +虽然来自下游的合并是常见且不起眼的,但当需要将一个分支推向上游时,其中来自 +其他树的合并往往是一个危险信号。这种合并需要仔细考虑并加以充分证明,否则后续 +的拉取请求很可能会被拒绝。 + +想要将主分支合并到存储库中是很自然的;这种类型的合并通常被称为“反向合并” +。反向合并有助于确保与并行的开发没有冲突,并且通常会给人一种温暖、舒服的 +感觉,即处于最新。但这种诱惑几乎总是应该避免的。 + +为什么呢?反向合并将搅乱你自己分支的开发历史。它们会大大增加你遇到来自社区 +其他地方的错误的机会,且使你很难确保你所管理的工作稳定并准备好合入上游。 +频繁的合并还可以掩盖树中开发过程中的问题;它们会隐藏与其他树的交互,而这些 +交互不应该(经常)发生在管理良好的分支中。 + +也就是说,偶尔需要进行反向合并;当这种情况发生时,一定要在提交信息中记录 +*为什么* 。同样,在一个众所周知的稳定点进行合并,而不是随机提交。即使这样, +你也不应该反向合并一棵比你的直接上游树更高层级的树;如果确实需要更高级别的 +反向合并,应首先在上游树进行。 + +导致合并相关问题最常见的原因之一是:在发送拉取请求之前维护者合并上游以解决 +合并冲突。同样,这种诱惑很容易理解,但绝对应该避免。对于最终拉取请求来说 +尤其如此:Linus坚信他更愿意看到合并冲突,而不是不必要的反向合并。看到冲突 +可以让他了解潜在的问题所在。他做过很多合并(在5.1版本开发周期中是382次), +而且在解决冲突方面也很在行——通常比参与的开发人员要强。 + +那么,当他们的子系统分支和主线之间发生冲突时,维护人员应该怎么做呢?最重要 +的一步是在拉取请求中提示Linus会发生冲突;如果啥都没说则表明您的分支可以正常 +合入。对于特别困难的冲突,创建并推送一个 *独立* 分支来展示你将如何解决问题。 +在拉取请求中提到该分支,但是请求本身应该针对未合并的分支。 + +即使不存在已知冲突,在发送拉取请求之前进行合并测试也是个好主意。它可能会提醒 +您一些在linux-next树中没有发现的问题,并帮助您准确地理解您正在要求上游做什么。 + +合并上游树或另一个子系统树的另一个原因是解决依赖关系。这些依赖性问题有时确实 +会发生,而且有时与另一棵树交叉合并是解决这些问题的最佳方法;同样,在这种情况 +下,合并提交应该解释为什么要进行合并。花点时间把它做好;会有人阅读这些变更 +日志。 + +然而依赖性问题通常表明需要改变方法。合并另一个子系统树以解决依赖性风险会带来 +其他缺陷,几乎永远不应这样做。如果该子系统树无法被合到上游,那么它的任何问题 +也都会阻碍你的树合并。更可取的选择包括与维护人员达成一致意见,在其中一个树中 +同时进行两组更改;或者创建一个主题分支专门处理可以合并到两个树中的先决条件提交。 +如果依赖关系与主要的基础结构更改相关,正确的解决方案可能是将依赖提交保留一个 +开发周期,以便这些更改有时间在主线上稳定。 + +最后 +==== + +在开发周期的开头合并主线是比较常见的,可以获取树中其他地方的更改和修复。同样, +这样的合并应该选择一个众所周知的发布点,而不是一些随机点。如果在合并窗口期间 +上游分支已完全清空到主线中,则可以使用以下命令向前拉取它:: + + git merge v5.2-rc1^0 + +“^0”使Git执行快进合并(在这种情况下这应该可以),从而避免多余的虚假合并提交。 + +上面列出的就是指导方针了。总是会有一些情况需要不同的解决方案,这些指导原则 +不应阻止开发人员在需要时做正确的事情。但是,我们应该时刻考虑是否真的出现了 +这样的需求,并准备好解释为什么需要做一些不寻常的事情。 diff --git a/Documentation/translations/zh_CN/mips/booting.rst b/Documentation/translations/zh_CN/mips/booting.rst new file mode 100644 index 000000000..e0bbd3f20 --- /dev/null +++ b/Documentation/translations/zh_CN/mips/booting.rst @@ -0,0 +1,34 @@ +.. SPDX-License-Identifier: GPL-2.0 + +.. include:: ../disclaimer-zh_CN.rst + +:Original: Documentation/mips/booting.rst + +:翻译: + + 司延腾 Yanteng Si <siyanteng@loongson.cn> + +.. _cn_booting: + +BMIPS设备树引导 +------------------------ + + 一些bootloaders只支持在内核镜像开始地址处的单一入口点。而其它 + bootloaders将跳转到ELF的开始地址处。两种方案都支持的;因为 + CONFIG_BOOT_RAW=y and CONFIG_NO_EXCEPT_FILL=y, 所以第一条指令 + 会立即跳转到kernel_entry()入口处执行。 + + 与arch/arm情况(b)类似,dt感知的引导加载程序需要设置以下寄存器: + + a0 : 0 + + a1 : 0xffffffff + + a2 : RAM中指向设备树块的物理指针(在chapterII中定义)。 + 设备树可以位于前512MB物理地址空间(0x00000000 - + 0x1fffffff)的任何位置,以64位边界对齐。 + + 传统bootloaders不会使用这样的约定,并且它们不传入DT块。 + 在这种情况下,Linux将通过选中CONFIG_DT_*查找DTB。 + + 以上约定只在32位系统中定义,因为目前没有任何64位的BMIPS实现。 diff --git a/Documentation/translations/zh_CN/mips/features.rst b/Documentation/translations/zh_CN/mips/features.rst new file mode 100644 index 000000000..b61dab06c --- /dev/null +++ b/Documentation/translations/zh_CN/mips/features.rst @@ -0,0 +1,13 @@ +.. SPDX-License-Identifier: GPL-2.0 + +.. include:: ../disclaimer-zh_CN.rst + +:Original: Documentation/mips/features.rst + +:翻译: + + 司延腾 Yanteng Si <siyanteng@loongson.cn> + +.. _cn_features: + +.. kernel-feat:: $srctree/Documentation/features mips diff --git a/Documentation/translations/zh_CN/mips/index.rst b/Documentation/translations/zh_CN/mips/index.rst new file mode 100644 index 000000000..192c6adbb --- /dev/null +++ b/Documentation/translations/zh_CN/mips/index.rst @@ -0,0 +1,29 @@ +.. SPDX-License-Identifier: GPL-2.0 + +.. include:: ../disclaimer-zh_CN.rst + +:Original: Documentation/mips/index.rst + +:翻译: + + 司延腾 Yanteng Si <siyanteng@loongson.cn> + +=========================== +MIPS特性文档 +=========================== + +.. toctree:: + :maxdepth: 2 + :numbered: + + booting + ingenic-tcu + + features + +.. only:: subproject and html + + Indices + ======= + + * :ref:`genindex` diff --git a/Documentation/translations/zh_CN/mips/ingenic-tcu.rst b/Documentation/translations/zh_CN/mips/ingenic-tcu.rst new file mode 100644 index 000000000..ddbe149c5 --- /dev/null +++ b/Documentation/translations/zh_CN/mips/ingenic-tcu.rst @@ -0,0 +1,72 @@ +.. SPDX-License-Identifier: GPL-2.0 + +.. include:: ../disclaimer-zh_CN.rst + +:Original: Documentation/mips/ingenic-tcu.rst + +:翻译: + + 司延腾 Yanteng Si <siyanteng@loongson.cn> + +.. _cn_ingenic-tcu: + +=============================================== +君正 JZ47xx SoC定时器/计数器硬件单元 +=============================================== + +君正 JZ47xx SoC中的定时器/计数器单元(TCU)是一个多功能硬件块。它有多达 +8个通道,可以用作计数器,计时器,或脉冲宽度调制器。 + +- JZ4725B, JZ4750, JZ4755 只有6个TCU通道。其它SoC都有8个通道。 + +- JZ4725B引入了一个独立的通道,称为操作系统计时器(OST)。这是一个32位可 + 编程定时器。在JZ4760B及以上型号上,它是64位的。 + +- 每个TCU通道都有自己的时钟源,可以通过 TCSR 寄存器设置通道的父级时钟 + 源(pclk、ext、rtc)、开关以及分频。 + + - 看门狗和OST硬件模块在它们的寄存器空间中也有相同形式的TCSR寄存器。 + - 用于关闭/开启的 TCU 寄存器也可以关闭/开启看门狗和 OST 时钟。 + +- 每个TCU通道在两种模式的其中一种模式下运行: + + - 模式 TCU1:通道无法在睡眠模式下运行,但更易于操作。 + - 模式 TCU2:通道可以在睡眠模式下运行,但操作比 TCU1 通道复杂一些。 + +- 每个 TCU 通道的模式取决于使用的SoC: + + - 在最老的SoC(高于JZ4740),八个通道都运行在TCU1模式。 + - 在 JZ4725B,通道5运行在TCU2,其它通道则运行在TCU1。 + - 在最新的SoC(JZ4750及之后),通道1-2运行在TCU2,其它通道则运行 + 在TCU1。 + +- 每个通道都可以生成中断。有些通道共享一条中断线,而有些没有,其在SoC型 + 号之间的变更: + + - 在很老的SoC(JZ4740及更低),通道0和通道1有它们自己的中断线;通 + 道2-7共享最后一条中断线。 + - 在 JZ4725B,通道0有它自己的中断线;通道1-5共享一条中断线;OST + 使用最后一条中断线。 + - 在比较新的SoC(JZ4750及以后),通道5有它自己的中断线;通 + 道0-4和(如果是8通道)6-7全部共享一条中断线;OST使用最后一条中 + 断线。 + +实现 +==== + +TCU硬件的功能分布在多个驱动程序: + +============== =================================== +时钟 drivers/clk/ingenic/tcu.c +中断 drivers/irqchip/irq-ingenic-tcu.c +定时器 drivers/clocksource/ingenic-timer.c +OST drivers/clocksource/ingenic-ost.c +脉冲宽度调制器 drivers/pwm/pwm-jz4740.c +看门狗 drivers/watchdog/jz4740_wdt.c +============== =================================== + +因为可以从相同的寄存器控制属于不同驱动程序和框架的TCU的各种功能,所以 +所有这些驱动程序都通过相同的控制总线通用接口访问它们的寄存器。 + +有关TCU驱动程序的设备树绑定的更多信息,请参阅: +Documentation/devicetree/bindings/timer/ingenic,tcu.yaml. diff --git a/Documentation/translations/zh_CN/mm/active_mm.rst b/Documentation/translations/zh_CN/mm/active_mm.rst new file mode 100644 index 000000000..c2816f523 --- /dev/null +++ b/Documentation/translations/zh_CN/mm/active_mm.rst @@ -0,0 +1,85 @@ +.. include:: ../disclaimer-zh_CN.rst + +:Original: Documentation/mm/active_mm.rst + +:翻译: + + 司延腾 Yanteng Si <siyanteng@loongson.cn> + +:校译: + + +========= +Active MM +========= + +这是一封linux之父回复开发者的一封邮件,所以翻译时我尽量保持邮件格式的完整。 + +:: + + List: linux-kernel + Subject: Re: active_mm + From: Linus Torvalds <torvalds () transmeta ! com> + Date: 1999-07-30 21:36:24 + + 因为我并不经常写解释,所以已经抄送到linux-kernel邮件列表,而当我做这些, + 且更多的人在阅读它们时,我觉得棒极了。 + + 1999年7月30日 星期五, David Mosberger 写道: + > + > 是否有一个简短的描述,说明task_struct中的 + > "mm" 和 "active_mm"应该如何使用? (如果 + > 这个问题在邮件列表中讨论过,我表示歉意--我刚 + > 刚度假回来,有一段时间没能关注linux-kernel了)。 + + 基本上,新的设定是: + + - 我们有“真实地址空间”和“匿名地址空间”。区别在于,匿名地址空间根本不关心用 + 户级页表,所以当我们做上下文切换到匿名地址空间时,我们只是让以前的地址空间 + 处于活动状态。 + + 一个“匿名地址空间”的明显用途是任何不需要任何用户映射的线程--所有的内核线 + 程基本上都属于这一类,但即使是“真正的”线程也可以暂时说在一定时间内它们不 + 会对用户空间感兴趣,调度器不妨试着避免在切换VM状态上浪费时间。目前只有老 + 式的bdflush sync能做到这一点。 + + - “tsk->mm” 指向 “真实地址空间”。对于一个匿名进程来说,tsk->mm将是NULL, + 其逻辑原因是匿名进程实际上根本就 “没有” 真正的地址空间。 + + - 然而,我们显然需要跟踪我们为这样的匿名用户“偷用”了哪个地址空间。为此,我们 + 有 “tsk->active_mm”,它显示了当前活动的地址空间是什么。 + + 规则是,对于一个有真实地址空间的进程(即tsk->mm是 non-NULL),active_mm + 显然必须与真实的mm相同。 + + 对于一个匿名进程,tsk->mm == NULL,而tsk->active_mm是匿名进程运行时 + “借用”的mm。当匿名进程被调度走时,借用的地址空间被返回并清除。 + + 为了支持所有这些,“struct mm_struct”现在有两个计数器:一个是 “mm_users” + 计数器,即有多少 “真正的地址空间用户”,另一个是 “mm_count”计数器,即 “lazy” + 用户(即匿名用户)的数量,如果有任何真正的用户,则加1。 + + 通常情况下,至少有一个真正的用户,但也可能是真正的用户在另一个CPU上退出,而 + 一个lazy的用户仍在活动,所以你实际上得到的情况是,你有一个地址空间 **只** + 被lazy的用户使用。这通常是一个短暂的生命周期状态,因为一旦这个线程被安排给一 + 个真正的线程,这个 “僵尸” mm就会被释放,因为 “mm_count”变成了零。 + + 另外,一个新的规则是,**没有人** 再把 “init_mm” 作为一个真正的MM了。 + “init_mm”应该被认为只是一个 “没有其他上下文时的lazy上下文”,事实上,它主 + 要是在启动时使用,当时还没有真正的VM被创建。因此,用来检查的代码 + + if (current->mm == &init_mm) + + 一般来说,应该用 + + if (!current->mm) + + 取代上面的写法(这更有意义--测试基本上是 “我们是否有一个用户环境”,并且通常 + 由缺页异常处理程序和类似的东西来完成)。 + + 总之,我刚才在ftp.kernel.org上放了一个pre-patch-2.3.13-1,因为它稍微改 + 变了接口以适配alpha(谁会想到呢,但alpha体系结构上下文切换代码实际上最终是 + 最丑陋的之一--不像其他架构的MM和寄存器状态是分开的,alpha的PALcode将两者 + 连接起来,你需要同时切换两者)。 + + (文档来源 http://marc.info/?l=linux-kernel&m=93337278602211&w=2) diff --git a/Documentation/translations/zh_CN/mm/balance.rst b/Documentation/translations/zh_CN/mm/balance.rst new file mode 100644 index 000000000..6fd79209c --- /dev/null +++ b/Documentation/translations/zh_CN/mm/balance.rst @@ -0,0 +1,81 @@ +.. include:: ../disclaimer-zh_CN.rst + +:Original: Documentation/mm/balance.rst + +:翻译: + + 司延腾 Yanteng Si <siyanteng@loongson.cn> + +:校译: + + +======== +内存平衡 +======== + +2000年1月开始,作者:Kanoj Sarcar <kanoj@sgi.com> + +对于 !__GFP_HIGH 和 !__GFP_KSWAPD_RECLAIM 以及非 __GFP_IO 的分配,需要进行 +内存平衡。 + +调用者避免回收的第一个原因是调用者由于持有自旋锁或处于中断环境中而无法睡眠。第二个 +原因可能是,调用者愿意在不产生页面回收开销的情况下分配失败。这可能发生在有0阶回退 +选项的机会主义高阶分配请求中。在这种情况下,调用者可能也希望避免唤醒kswapd。 + +__GFP_IO分配请求是为了防止文件系统死锁。 + +在没有非睡眠分配请求的情况下,做平衡似乎是有害的。页面回收可以被懒散地启动,也就是 +说,只有在需要的时候(也就是区域的空闲内存为0),而不是让它成为一个主动的过程。 + +也就是说,内核应该尝试从直接映射池中满足对直接映射页的请求,而不是回退到dma池中, +这样就可以保持dma池为dma请求(不管是不是原子的)所填充。类似的争论也适用于高内存 +和直接映射的页面。相反,如果有很多空闲的dma页,最好是通过从dma池中分配一个来满足 +常规的内存请求,而不是产生常规区域平衡的开销。 + +在2.2中,只有当空闲页总数低于总内存的1/64时,才会启动内存平衡/页面回收。如果dma +和常规内存的比例合适,即使dma区完全空了,也很可能不会进行平衡。2.2已经在不同内存 +大小的生产机器上运行,即使有这个问题存在,似乎也做得不错。在2.3中,由于HIGHMEM的 +存在,这个问题变得更加严重。 + +在2.3中,区域平衡可以用两种方式之一来完成:根据区域的大小(可能是低级区域的大小), +我们可以在初始化阶段决定在平衡任何区域时应该争取多少空闲页。好的方面是,在平衡的时 +候,我们不需要看低级区的大小,坏的方面是,我们可能会因为忽略低级区可能较低的使用率 +而做过于频繁的平衡。另外,只要对分配程序稍作修改,就有可能将memclass()宏简化为一 +个简单的等式。 + +另一个可能的解决方案是,我们只在一个区 **和** 其所有低级区的空闲内存低于该区及其 +低级区总内存的1/64时进行平衡。这就解决了2.2的平衡问题,并尽可能地保持了与2.2行为 +的接近。另外,平衡算法在各种架构上的工作方式也是一样的,这些架构有不同数量和类型的 +内存区。如果我们想变得更花哨一点,我们可以在未来为不同区域的自由页面分配不同的权重。 + +请注意,如果普通区的大小与dma区相比是巨大的,那么在决定是否平衡普通区的时候,考虑 +空闲的dma页就变得不那么重要了。那么第一个解决方案就变得更有吸引力。 + +所附的补丁实现了第二个解决方案。它还 “修复”了两个问题:首先,在低内存条件下,kswapd +被唤醒,就像2.2中的非睡眠分配。第二,HIGHMEM区也被平衡了,以便给replace_with_highmem() +一个争取获得HIGHMEM页的机会,同时确保HIGHMEM分配不会落回普通区。这也确保了HIGHMEM +页不会被泄露(例如,在一个HIGHMEM页在交换缓存中但没有被任何人使用的情况下)。 + +kswapd还需要知道它应该平衡哪些区。kswapd主要是在无法进行平衡的情况下需要的,可能 +是因为所有的分配请求都来自中断上下文,而所有的进程上下文都在睡眠。对于2.3, +kswapd并不真正需要平衡高内存区,因为中断上下文并不请求高内存页。kswapd看zone +结构体中的zone_wake_kswapd字段来决定一个区是否需要平衡。 + +如果从进程内存和shm中偷取页面可以减轻该页面节点中任何区的内存压力,而该区的内存压力 +已经低于其水位,则会进行偷取。 + +watemark[WMARK_MIN/WMARK_LOW/WMARK_HIGH]/low_on_memory/zone_wake_kswapd: +这些是每个区的字段,用于确定一个区何时需要平衡。当页面数低于水位[WMARK_MIN]时, +hysteric 的字段low_on_memory被设置。这个字段会一直被设置,直到空闲页数变成水位 +[WMARK_HIGH]。当low_on_memory被设置时,页面分配请求将尝试释放该区域的一些页面(如果 +请求中设置了GFP_WAIT)。与此相反的是,决定唤醒kswapd以释放一些区的页。这个决定不是基于 +hysteresis 的,而是当空闲页的数量低于watermark[WMARK_LOW]时就会进行;在这种情况下, +zone_wake_kswapd也被设置。 + + +我所听到的(超棒的)想法: + +1. 动态经历应该影响平衡:可以跟踪一个区的失败请求的数量,并反馈到平衡方案中(jalvo@mbay.net)。 + +2. 实现一个类似于replace_with_highmem()的replace_with_regular(),以保留dma页面。 + (lkd@tantalophile.demon.co.uk) diff --git a/Documentation/translations/zh_CN/mm/damon/api.rst b/Documentation/translations/zh_CN/mm/damon/api.rst new file mode 100644 index 000000000..5593a83c8 --- /dev/null +++ b/Documentation/translations/zh_CN/mm/damon/api.rst @@ -0,0 +1,32 @@ +.. SPDX-License-Identifier: GPL-2.0 + +:Original: Documentation/mm/damon/api.rst + +:翻译: + + 司延腾 Yanteng Si <siyanteng@loongson.cn> + +:校译: + + +======= +API参考 +======= + +内核空间的程序可以使用下面的API来使用DAMON的每个功能。你所需要做的就是引用 ``damon.h`` , +它位于源代码树的include/linux/。 + +结构体 +====== + +该API在以下内核代码中: + +include/linux/damon.h + + +函数 +==== + +该API在以下内核代码中: + +mm/damon/core.c diff --git a/Documentation/translations/zh_CN/mm/damon/design.rst b/Documentation/translations/zh_CN/mm/damon/design.rst new file mode 100644 index 000000000..16e3db34a --- /dev/null +++ b/Documentation/translations/zh_CN/mm/damon/design.rst @@ -0,0 +1,140 @@ +.. SPDX-License-Identifier: GPL-2.0 + +:Original: Documentation/mm/damon/design.rst + +:翻译: + + 司延腾 Yanteng Si <siyanteng@loongson.cn> + +:校译: + + +==== +设计 +==== + +可配置的层 +========== + +DAMON提供了数据访问监控功能,同时使其准确性和开销可控。基本的访问监控需要依赖于目标地址空间 +并为之优化的基元。另一方面,作为DAMON的核心,准确性和开销的权衡机制是在纯逻辑空间中。DAMON +将这两部分分离在不同的层中,并定义了它的接口,以允许各种低层次的基元实现与核心逻辑的配置。 + +由于这种分离的设计和可配置的接口,用户可以通过配置核心逻辑和适当的低级基元实现来扩展DAMON的 +任何地址空间。如果没有提供合适的,用户可以自己实现基元。 + +例如,物理内存、虚拟内存、交换空间、那些特定的进程、NUMA节点、文件和支持的内存设备将被支持。 +另外,如果某些架构或设备支持特殊的优化访问检查基元,这些基元将很容易被配置。 + + +特定地址空间基元的参考实现 +========================== + +基本访问监测的低级基元被定义为两部分。: + +1. 确定地址空间的监测目标地址范围 +2. 目标空间中特定地址范围的访问检查。 + +DAMON目前为物理和虚拟地址空间提供了基元的实现。下面两个小节描述了这些工作的方式。 + + +基于VMA的目标地址范围构造 +------------------------- + +这仅仅是针对虚拟地址空间基元的实现。对于物理地址空间,只是要求用户手动设置监控目标地址范围。 + +在进程的超级巨大的虚拟地址空间中,只有小部分被映射到物理内存并被访问。因此,跟踪未映射的地 +址区域只是一种浪费。然而,由于DAMON可以使用自适应区域调整机制来处理一定程度的噪声,所以严 +格来说,跟踪每一个映射并不是必须的,但在某些情况下甚至会产生很高的开销。也就是说,监测目标 +内部过于巨大的未映射区域应该被移除,以不占用自适应机制的时间。 + +出于这个原因,这个实现将复杂的映射转换为三个不同的区域,覆盖地址空间的每个映射区域。这三个 +区域之间的两个空隙是给定地址空间中两个最大的未映射区域。这两个最大的未映射区域是堆和最上面 +的mmap()区域之间的间隙,以及在大多数情况下最下面的mmap()区域和堆之间的间隙。因为这些间隙 +在通常的地址空间中是异常巨大的,排除这些间隙就足以做出合理的权衡。下面详细说明了这一点:: + + <heap> + <BIG UNMAPPED REGION 1> + <uppermost mmap()-ed region> + (small mmap()-ed regions and munmap()-ed regions) + <lowermost mmap()-ed region> + <BIG UNMAPPED REGION 2> + <stack> + + +基于PTE访问位的访问检查 +----------------------- + +物理和虚拟地址空间的实现都使用PTE Accessed-bit进行基本访问检查。唯一的区别在于从地址中 +找到相关的PTE访问位的方式。虚拟地址的实现是为该地址的目标任务查找页表,而物理地址的实现则 +是查找与该地址有映射关系的每一个页表。通过这种方式,实现者找到并清除下一个采样目标地址的位, +并检查该位是否在一个采样周期后再次设置。这可能会干扰其他使用访问位的内核子系统,即空闲页跟 +踪和回收逻辑。为了避免这种干扰,DAMON使其与空闲页面跟踪相互排斥,并使用 ``PG_idle`` 和 +``PG_young`` 页面标志来解决与回收逻辑的冲突,就像空闲页面跟踪那样。 + + +独立于地址空间的核心机制 +======================== + +下面四个部分分别描述了DAMON的核心机制和五个监测属性,即 ``采样间隔`` 、 ``聚集间隔`` 、 +``更新间隔`` 、 ``最小区域数`` 和 ``最大区域数`` 。 + + +访问频率监测 +------------ + +DAMON的输出显示了在给定的时间内哪些页面的访问频率是多少。访问频率的分辨率是通过设置 +``采样间隔`` 和 ``聚集间隔`` 来控制的。详细地说,DAMON检查每个 ``采样间隔`` 对每 +个页面的访问,并将结果汇总。换句话说,计算每个页面的访问次数。在每个 ``聚合间隔`` 过 +去后,DAMON调用先前由用户注册的回调函数,以便用户可以阅读聚合的结果,然后再清除这些结 +果。这可以用以下简单的伪代码来描述:: + + while monitoring_on: + for page in monitoring_target: + if accessed(page): + nr_accesses[page] += 1 + if time() % aggregation_interval == 0: + for callback in user_registered_callbacks: + callback(monitoring_target, nr_accesses) + for page in monitoring_target: + nr_accesses[page] = 0 + sleep(sampling interval) + +这种机制的监测开销将随着目标工作负载规模的增长而任意增加。 + + +基于区域的抽样调查 +------------------ + +为了避免开销的无限制增加,DAMON将假定具有相同访问频率的相邻页面归入一个区域。只要保持 +这个假设(一个区域内的页面具有相同的访问频率),该区域内就只需要检查一个页面。因此,对 +于每个 ``采样间隔`` ,DAMON在每个区域中随机挑选一个页面,等待一个 ``采样间隔`` ,检 +查该页面是否同时被访问,如果被访问则增加该区域的访问频率。因此,监测开销是可以通过设置 +区域的数量来控制的。DAMON允许用户设置最小和最大的区域数量来进行权衡。 + +然而,如果假设没有得到保证,这个方案就不能保持输出的质量。 + + +适应性区域调整 +-------------- + +即使最初的监测目标区域被很好地构建以满足假设(同一区域内的页面具有相似的访问频率),数 +据访问模式也会被动态地改变。这将导致监测质量下降。为了尽可能地保持假设,DAMON根据每个 +区域的访问频率自适应地进行合并和拆分。 + +对于每个 ``聚集区间`` ,它比较相邻区域的访问频率,如果频率差异较小,就合并这些区域。 +然后,在它报告并清除每个区域的聚合接入频率后,如果区域总数不超过用户指定的最大区域数, +它将每个区域拆分为两个或三个区域。 + +通过这种方式,DAMON提供了其最佳的质量和最小的开销,同时保持了用户为其权衡设定的界限。 + + +动态目标空间更新处理 +-------------------- + +监测目标地址范围可以动态改变。例如,虚拟内存可以动态地被映射和解映射。物理内存可以被 +热插拔。 + +由于在某些情况下变化可能相当频繁,DAMON允许监控操作检查动态变化,包括内存映射变化, +并仅在用户指定的时间间隔( ``更新间隔`` )中的每个时间段,将其应用于监控操作相关的 +数据结构,如抽象的监控目标内存区。
\ No newline at end of file diff --git a/Documentation/translations/zh_CN/mm/damon/faq.rst b/Documentation/translations/zh_CN/mm/damon/faq.rst new file mode 100644 index 000000000..de4be4174 --- /dev/null +++ b/Documentation/translations/zh_CN/mm/damon/faq.rst @@ -0,0 +1,48 @@ +.. SPDX-License-Identifier: GPL-2.0 + +:Original: Documentation/mm/damon/faq.rst + +:翻译: + + 司延腾 Yanteng Si <siyanteng@loongson.cn> + +:校译: + + +======== +常见问题 +======== + +为什么是一个新的子系统,而不是扩展perf或其他用户空间工具? +========================================================== + +首先,因为它需要尽可能的轻量级,以便可以在线使用,所以应该避免任何不必要的开销,如内核-用户 +空间的上下文切换成本。第二,DAMON的目标是被包括内核在内的其他程序所使用。因此,对特定工具 +(如perf)的依赖性是不可取的。这就是DAMON在内核空间实现的两个最大的原因。 + + +“闲置页面跟踪” 或 “perf mem” 可以替代DAMON吗? +============================================== + +闲置页跟踪是物理地址空间访问检查的一个低层次的原始方法。“perf mem”也是类似的,尽管它可以 +使用采样来减少开销。另一方面,DAMON是一个更高层次的框架,用于监控各种地址空间。它专注于内 +存管理优化,并提供复杂的精度/开销处理机制。因此,“空闲页面跟踪” 和 “perf mem” 可以提供 +DAMON输出的一个子集,但不能替代DAMON。 + + +DAMON是否只支持虚拟内存? +========================= + +不,DAMON的核心是独立于地址空间的。用户可以在DAMON核心上实现和配置特定地址空间的低级原始 +部分,包括监测目标区域的构造和实际的访问检查。通过这种方式,DAMON用户可以用任何访问检查技 +术来监测任何地址空间。 + +尽管如此,DAMON默认为虚拟内存和物理内存提供了基于vma/rmap跟踪和PTE访问位检查的地址空间 +相关功能的实现,以供参考和方便使用。 + + +我可以简单地监测页面的粒度吗? +============================== + +是的,你可以通过设置 ``min_nr_regions`` 属性高于工作集大小除以页面大小的值来实现。 +因为监视目标区域的大小被强制为 ``>=page size`` ,所以区域分割不会产生任何影响。 diff --git a/Documentation/translations/zh_CN/mm/damon/index.rst b/Documentation/translations/zh_CN/mm/damon/index.rst new file mode 100644 index 000000000..b03bf3072 --- /dev/null +++ b/Documentation/translations/zh_CN/mm/damon/index.rst @@ -0,0 +1,32 @@ +.. SPDX-License-Identifier: GPL-2.0 + +:Original: Documentation/mm/damon/index.rst + +:翻译: + + 司延腾 Yanteng Si <siyanteng@loongson.cn> + +:校译: + + +========================== +DAMON:数据访问监视器 +========================== + +DAMON是Linux内核的一个数据访问监控框架子系统。DAMON的核心机制使其成为 +(该核心机制详见(Documentation/translations/zh_CN/mm/damon/design.rst)) + + - *准确度* (监测输出对DRAM级别的内存管理足够有用;但可能不适合CPU Cache级别), + - *轻量级* (监控开销低到可以在线应用),以及 + - *可扩展* (无论目标工作负载的大小,开销的上限值都在恒定范围内)。 + +因此,利用这个框架,内核的内存管理机制可以做出高级决策。会导致高数据访问监控开销的实 +验性内存管理优化工作可以再次进行。同时,在用户空间,有一些特殊工作负载的用户可以编写 +个性化的应用程序,以便更好地了解和优化他们的工作负载和系统。 + +.. toctree:: + :maxdepth: 2 + + faq + design + api diff --git a/Documentation/translations/zh_CN/mm/free_page_reporting.rst b/Documentation/translations/zh_CN/mm/free_page_reporting.rst new file mode 100644 index 000000000..5bfd58014 --- /dev/null +++ b/Documentation/translations/zh_CN/mm/free_page_reporting.rst @@ -0,0 +1,38 @@ +.. include:: ../disclaimer-zh_CN.rst + +:Original: Documentation/mm/free_page_reporting.rst + +:翻译: + + 司延腾 Yanteng Si <siyanteng@loongson.cn> + +:校译: + +========== +空闲页报告 +========== + +空闲页报告是一个API,设备可以通过它来注册接收系统当前未使用的页面列表。这在虚拟 +化的情况下是很有用的,客户机能够使用这些数据来通知管理器它不再使用内存中的某些页 +面。 + +对于驱动,通常是气球驱动要使用这个功能,它将分配和初始化一个page_reporting_dev_info +结构体。它要填充的结构体中的字段是用于处理散点列表的 "report" 函数指针。它还必 +须保证每次调用该函数时能处理至少相当于PAGE_REPORTING_CAPACITY的散点列表条目。 +假设没有其他页面报告设备已经注册, 对page_reporting_register的调用将向报告框 +架注册页面报告接口。 + +一旦注册,页面报告API将开始向驱动报告成批的页面。API将在接口被注册后2秒开始报告 +页面,并在任何足够高的页面被释放之后2秒继续报告。 + +报告的页面将被存储在传递给报告函数的散列表中,最后一个条目的结束位被设置在条目 +nent-1中。 当页面被报告函数处理时,分配器将无法访问它们。一旦报告函数完成,这些 +页将被返回到它们所获得的自由区域。 + +在移除使用空闲页报告的驱动之前,有必要调用page_reporting_unregister,以移除 +目前被空闲页报告使用的page_reporting_dev_info结构体。这样做将阻止进一步的报 +告通过该接口发出。如果另一个驱动或同一驱动被注册,它就有可能恢复前一个驱动在报告 +空闲页方面的工作。 + + +Alexander Duyck, 2019年12月04日 diff --git a/Documentation/translations/zh_CN/mm/frontswap.rst b/Documentation/translations/zh_CN/mm/frontswap.rst new file mode 100644 index 000000000..434975390 --- /dev/null +++ b/Documentation/translations/zh_CN/mm/frontswap.rst @@ -0,0 +1,196 @@ +:Original: Documentation/mm/frontswap.rst + +:翻译: + + 司延腾 Yanteng Si <siyanteng@loongson.cn> + +:校译: + +========= +Frontswap +========= + +Frontswap为交换页提供了一个 “transcendent memory” 的接口。在一些环境中,由 +于交换页被保存在RAM(或类似RAM的设备)中,而不是交换磁盘,因此可以获得巨大的性能 +节省(提高)。 + +.. _Transcendent memory in a nutshell: https://lwn.net/Articles/454795/ + +Frontswap之所以这么命名,是因为它可以被认为是与swap设备的“back”存储相反。存 +储器被认为是一个同步并发安全的面向页面的“伪RAM设备”,符合transcendent memory +(如Xen的“tmem”,或内核内压缩内存,又称“zcache”,或未来的类似RAM的设备)的要 +求;这个伪RAM设备不能被内核直接访问或寻址,其大小未知且可能随时间变化。驱动程序通过 +调用frontswap_register_ops将自己与frontswap链接起来,以适当地设置frontswap_ops +的功能,它提供的功能必须符合某些策略,如下所示: + +一个 “init” 将设备准备好接收与指定的交换设备编号(又称“类型”)相关的frontswap +交换页。一个 “store” 将把该页复制到transcendent memory,并与该页的类型和偏移 +量相关联。一个 “load” 将把该页,如果找到的话,从transcendent memory复制到内核 +内存,但不会从transcendent memory中删除该页。一个 “invalidate_page” 将从 +transcendent memory中删除该页,一个 “invalidate_area” 将删除所有与交换类型 +相关的页(例如,像swapoff)并通知 “device” 拒绝进一步存储该交换类型。 + +一旦一个页面被成功存储,在该页面上的匹配加载通常会成功。因此,当内核发现自己处于需 +要交换页面的情况时,它首先尝试使用frontswap。如果存储的结果是成功的,那么数据就已 +经成功的保存到了transcendent memory中,并且避免了磁盘写入,如果后来再读回数据, +也避免了磁盘读取。如果存储返回失败,transcendent memory已经拒绝了该数据,且该页 +可以像往常一样被写入交换空间。 + +请注意,如果一个页面被存储,而该页面已经存在于transcendent memory中(一个 “重复” +的存储),要么存储成功,数据被覆盖,要么存储失败,该页面被废止。这确保了旧的数据永远 +不会从frontswap中获得。 + +如果配置正确,对frontswap的监控是通过 `/sys/kernel/debug/frontswap` 目录下的 +debugfs完成的。frontswap的有效性可以通过以下方式测量(在所有交换设备中): + +``failed_stores`` + 有多少次存储的尝试是失败的 + +``loads`` + 尝试了多少次加载(应该全部成功) + +``succ_stores`` + 有多少次存储的尝试是成功的 + +``invalidates`` + 尝试了多少次作废 + +后台实现可以提供额外的指标。 + +经常问到的问题 +============== + +* 价值在哪里? + +当一个工作负载开始交换时,性能就会下降。Frontswap通过提供一个干净的、动态的接口来 +读取和写入交换页到 “transcendent memory”,从而大大增加了许多这样的工作负载的性 +能,否则内核是无法直接寻址的。当数据被转换为不同的形式和大小(比如压缩)或者被秘密 +移动(对于一些类似RAM的设备来说,这可能对写平衡很有用)时,这个接口是理想的。交换 +页(和被驱逐的页面缓存页)是这种比RAM慢但比磁盘快得多的“伪RAM设备”的一大用途。 + +Frontswap对内核的影响相当小,为各种系统配置中更动态、更灵活的RAM利用提供了巨大的 +灵活性: + +在单一内核的情况下,又称“zcache”,页面被压缩并存储在本地内存中,从而增加了可以安 +全保存在RAM中的匿名页面总数。Zcache本质上是用压缩/解压缩的CPU周期换取更好的内存利 +用率。Benchmarks测试显示,当内存压力较低时,几乎没有影响,而在高内存压力下的一些 +工作负载上,则有明显的性能改善(25%以上)。 + +“RAMster” 在zcache的基础上增加了对集群系统的 “peer-to-peer” transcendent memory +的支持。Frontswap页面像zcache一样被本地压缩,但随后被“remotified” 到另一个系 +统的RAM。这使得RAM可以根据需要动态地来回负载平衡,也就是说,当系统A超载时,它可以 +交换到系统B,反之亦然。RAMster也可以被配置成一个内存服务器,因此集群中的许多服务器 +可以根据需要动态地交换到配置有大量内存的单一服务器上......而不需要预先配置每个客户 +有多少内存可用 + +在虚拟情况下,虚拟化的全部意义在于统计地将物理资源在多个虚拟机的不同需求之间进行复 +用。对于RAM来说,这真的很难做到,而且在不改变内核的情况下,要做好这一点的努力基本上 +是失败的(除了一些广为人知的特殊情况下的工作负载)。具体来说,Xen Transcendent Memory +后端允许管理器拥有的RAM “fallow”,不仅可以在多个虚拟机之间进行“time-shared”, +而且页面可以被压缩和重复利用,以优化RAM的利用率。当客户操作系统被诱导交出未充分利用 +的RAM时(如 “selfballooning”),突然出现的意外内存压力可能会导致交换;frontswap +允许这些页面被交换到管理器RAM中或从管理器RAM中交换(如果整体主机系统内存条件允许), +从而减轻计划外交换可能带来的可怕的性能影响。 + +一个KVM的实现正在进行中,并且已经被RFC'ed到lkml。而且,利用frontswap,对NVM作为 +内存扩展技术的调查也在进行中。 + +* 当然,在某些情况下可能有性能上的优势,但frontswap的空间/时间开销是多少? + +如果 CONFIG_FRONTSWAP 被禁用,每个 frontswap 钩子都会编译成空,唯一的开销是每 +个 swapon'ed swap 设备的几个额外字节。如果 CONFIG_FRONTSWAP 被启用,但没有 +frontswap的 “backend” 寄存器,每读或写一个交换页就会有一个额外的全局变量,而不 +是零。如果 CONFIG_FRONTSWAP 被启用,并且有一个frontswap的backend寄存器,并且 +后端每次 “store” 请求都失败(即尽管声称可能,但没有提供内存),CPU 的开销仍然可以 +忽略不计 - 因为每次frontswap失败都是在交换页写到磁盘之前,系统很可能是 I/O 绑定 +的,无论如何使用一小部分的 CPU 都是不相关的。 + +至于空间,如果CONFIG_FRONTSWAP被启用,并且有一个frontswap的backend注册,那么 +每个交换设备的每个交换页都会被分配一个比特。这是在内核已经为每个交换设备的每个交换 +页分配的8位(在2.6.34之前是16位)上增加的。(Hugh Dickins观察到,frontswap可能 +会偷取现有的8个比特,但是我们以后再来担心这个小的优化问题)。对于标准的4K页面大小的 +非常大的交换盘(这很罕见),这是每32GB交换盘1MB开销。 + +当交换页存储在transcendent memory中而不是写到磁盘上时,有一个副作用,即这可能会 +产生更多的内存压力,有可能超过其他的优点。一个backend,比如zcache,必须实现策略 +来仔细(但动态地)管理内存限制,以确保这种情况不会发生。 + +* 好吧,那就用内核骇客能理解的术语来快速概述一下这个frontswap补丁的作用如何? + +我们假设在内核初始化过程中,一个frontswap 的 “backend” 已经注册了;这个注册表 +明这个frontswap 的 “backend” 可以访问一些不被内核直接访问的“内存”。它到底提 +供了多少内存是完全动态和随机的。 + +每当一个交换设备被交换时,就会调用frontswap_init(),把交换设备的编号(又称“类 +型”)作为一个参数传给它。这就通知了frontswap,以期待 “store” 与该号码相关的交 +换页的尝试。 + +每当交换子系统准备将一个页面写入交换设备时(参见swap_writepage()),就会调用 +frontswap_store。Frontswap与frontswap backend协商,如果backend说它没有空 +间,frontswap_store返回-1,内核就会照常把页换到交换设备上。注意,来自frontswap +backend的响应对内核来说是不可预测的;它可能选择从不接受一个页面,可能接受每九个 +页面,也可能接受每一个页面。但是如果backend确实接受了一个页面,那么这个页面的数 +据已经被复制并与类型和偏移量相关联了,而且backend保证了数据的持久性。在这种情况 +下,frontswap在交换设备的“frontswap_map” 中设置了一个位,对应于交换设备上的 +页面偏移量,否则它就会将数据写入该设备。 + +当交换子系统需要交换一个页面时(swap_readpage()),它首先调用frontswap_load(), +检查frontswap_map,看这个页面是否早先被frontswap backend接受。如果是,该页 +的数据就会从frontswap后端填充,换入就完成了。如果不是,正常的交换代码将被执行, +以便从真正的交换设备上获得这一页的数据。 + +所以每次frontswap backend接受一个页面时,交换设备的读取和(可能)交换设备的写 +入都被 “frontswap backend store” 和(可能)“frontswap backend loads” +所取代,这可能会快得多。 + +* frontswap不能被配置为一个 “特殊的” 交换设备,它的优先级要高于任何真正的交换 + 设备(例如像zswap,或者可能是swap-over-nbd/NFS)? + +首先,现有的交换子系统不允许有任何种类的交换层次结构。也许它可以被重写以适应层次 +结构,但这将需要相当大的改变。即使它被重写,现有的交换子系统也使用了块I/O层,它 +假定交换设备是固定大小的,其中的任何页面都是可线性寻址的。Frontswap几乎没有触 +及现有的交换子系统,而是围绕着块I/O子系统的限制,提供了大量的灵活性和动态性。 + +例如,frontswap backend对任何交换页的接受是完全不可预测的。这对frontswap backend +的定义至关重要,因为它赋予了backend完全动态的决定权。在zcache中,人们无法预 +先知道一个页面的可压缩性如何。可压缩性 “差” 的页面会被拒绝,而 “差” 本身也可 +以根据当前的内存限制动态地定义。 + +此外,frontswap是完全同步的,而真正的交换设备,根据定义,是异步的,并且使用 +块I/O。块I/O层不仅是不必要的,而且可能进行 “优化”,这对面向RAM的设备来说是 +不合适的,包括将一些页面的写入延迟相当长的时间。同步是必须的,以确保后端的动 +态性,并避免棘手的竞争条件,这将不必要地大大增加frontswap和/或块I/O子系统的 +复杂性。也就是说,只有最初的 “store” 和 “load” 操作是需要同步的。一个独立 +的异步线程可以自由地操作由frontswap存储的页面。例如,RAMster中的 “remotification” +线程使用标准的异步内核套接字,将压缩的frontswap页面移动到远程机器。同样, +KVM的客户方实现可以进行客户内压缩,并使用 “batched” hypercalls。 + +在虚拟化环境中,动态性允许管理程序(或主机操作系统)做“intelligent overcommit”。 +例如,它可以选择只接受页面,直到主机交换可能即将发生,然后强迫客户机做他们 +自己的交换。 + +transcendent memory规格的frontswap有一个坏处。因为任何 “store” 都可 +能失败,所以必须在一个真正的交换设备上有一个真正的插槽来交换页面。因此, +frontswap必须作为每个交换设备的 “影子” 来实现,它有可能容纳交换设备可能 +容纳的每一个页面,也有可能根本不容纳任何页面。这意味着frontswap不能包含比 +swap设备总数更多的页面。例如,如果在某些安装上没有配置交换设备,frontswap +就没有用。无交换设备的便携式设备仍然可以使用frontswap,但是这种设备的 +backend必须配置某种 “ghost” 交换设备,并确保它永远不会被使用。 + + +* 为什么会有这种关于 “重复存储” 的奇怪定义?如果一个页面以前被成功地存储过, + 难道它不能总是被成功地覆盖吗? + +几乎总是可以的,不,有时不能。考虑一个例子,数据被压缩了,原来的4K页面被压 +缩到了1K。现在,有人试图用不可压缩的数据覆盖该页,因此会占用整个4K。但是 +backend没有更多的空间了。在这种情况下,这个存储必须被拒绝。每当frontswap +拒绝一个会覆盖的存储时,它也必须使旧的数据作废,并确保它不再被访问。因为交 +换子系统会把新的数据写到读交换设备上,这是确保一致性的正确做法。 + +* 为什么frontswap补丁会创建新的头文件swapfile.h? + +frontswap代码依赖于一些swap子系统内部的数据结构,这些数据结构多年来一直 +在静态和全局之间来回移动。这似乎是一个合理的妥协:将它们定义为全局,但在一 +个新的包含文件中声明它们,该文件不被包含swap.h的大量源文件所包含。 + +Dan Magenheimer,最后更新于2012年4月9日 diff --git a/Documentation/translations/zh_CN/mm/highmem.rst b/Documentation/translations/zh_CN/mm/highmem.rst new file mode 100644 index 000000000..f74800a6d --- /dev/null +++ b/Documentation/translations/zh_CN/mm/highmem.rst @@ -0,0 +1,137 @@ +.. include:: ../disclaimer-zh_CN.rst + +:Original: Documentation/mm/highmem.rst + +:翻译: + + 司延腾 Yanteng Si <siyanteng@loongson.cn> + +:校译: + +========== +高内存处理 +========== + +作者: Peter Zijlstra <a.p.zijlstra@chello.nl> + +.. contents:: :local: + +高内存是什么? +============== + +当物理内存的大小接近或超过虚拟内存的最大大小时,就会使用高内存(highmem)。在这一点上,内 +核不可能在任何时候都保持所有可用的物理内存的映射。这意味着内核需要开始使用它想访问的物理内 +存的临时映射。 + +没有被永久映射覆盖的那部分(物理)内存就是我们所说的 "高内存"。对于这个边界的确切位置,有 +各种架构上的限制。 + +例如,在i386架构中,我们选择将内核映射到每个进程的虚拟空间,这样我们就不必为内核的进入/退 +出付出全部的TLB作废代价。这意味着可用的虚拟内存空间(i386上为4GiB)必须在用户和内核空间之 +间进行划分。 + +使用这种方法的架构的传统分配方式是3:1,3GiB用于用户空间,顶部的1GiB用于内核空间。:: + + +--------+ 0xffffffff + | Kernel | + +--------+ 0xc0000000 + | | + | User | + | | + +--------+ 0x00000000 + +这意味着内核在任何时候最多可以映射1GiB的物理内存,但是由于我们需要虚拟地址空间来做其他事 +情--包括访问其余物理内存的临时映射--实际的直接映射通常会更少(通常在~896MiB左右)。 + +其他有mm上下文标签的TLB的架构可以有独立的内核和用户映射。然而,一些硬件(如一些ARM)在使 +用mm上下文标签时,其虚拟空间有限。 + + +临时虚拟映射 +============ + +内核包含几种创建临时映射的方法。下面的列表按照使用的优先顺序显示了它们。 + +* kmap_local_page()。这个函数是用来要求短期映射的。它可以从任何上下文(包括中断)中调用, + 但是映射只能在获取它们的上下文中使用。 + + 在可行的情况下,这个函数应该比其他所有的函数优先使用。 + + 这些映射是线程本地和CPU本地的,这意味着映射只能从这个线程中访问,并且当映射处于活动状 + 态时,该线程与CPU绑定。即使线程被抢占了(因为抢占永远不会被函数禁用),CPU也不能通过 + CPU-hotplug从系统中拔出,直到映射被处理掉。 + + 在本地的kmap区域中采取pagefaults是有效的,除非获取本地映射的上下文由于其他原因不允许 + 这样做。 + + kmap_local_page()总是返回一个有效的虚拟地址,并且假定kunmap_local()不会失败。 + + 嵌套kmap_local_page()和kmap_atomic()映射在一定程度上是允许的(最多到KMAP_TYPE_NR), + 但是它们的调用必须严格排序,因为映射的实现是基于堆栈的。关于如何管理嵌套映射的细节, + 请参见kmap_local_page() kdocs(包含在 "函数 "部分)。 + +* kmap_atomic(). 这允许对单个页面进行非常短的时间映射。由于映射被限制在发布它的CPU上, + 它表现得很好,但发布的任务因此被要求留在该CPU上直到它完成,以免其他任务取代它的映射。 + + kmap_atomic()也可以被中断上下文使用,因为它不睡眠,调用者也可能在调用kunmap_atomic() + 后才睡眠。 + + 内核中对kmap_atomic()的每次调用都会创建一个不可抢占的段,并禁用缺页异常。这可能是 + 未预期延迟的来源之一。因此用户应该选择kmap_local_page()而不是kmap_atomic()。 + + 假设k[un]map_atomic()不会失败。 + +* kmap()。这应该被用来对单个页面进行短时间的映射,对抢占或迁移没有限制。它会带来开销, + 因为映射空间是受限制的,并且受到全局锁的保护,以实现同步。当不再需要映射时,必须用 + kunmap()释放该页被映射的地址。 + + 映射变化必须广播到所有CPU(核)上,kmap()还需要在kmap的池被回绕(TLB项用光了,需要从第 + 一项复用)时进行全局TLB无效化,当映射空间被完全利用时,它可能会阻塞,直到有一个可用的 + 槽出现。因此,kmap()只能从可抢占的上下文中调用。 + + 如果一个映射必须持续相对较长的时间,上述所有的工作都是必要的,但是内核中大部分的 + 高内存映射都是短暂的,而且只在一个地方使用。这意味着在这种情况下,kmap()的成本大 + 多被浪费了。kmap()并不是为长期映射而设计的,但是它已经朝着这个方向发展了,在较新 + 的代码中强烈不鼓励使用它,前面的函数集应该是首选。 + + 在64位系统中,调用kmap_local_page()、kmap_atomic()和kmap()没有实际作用,因为64位 + 地址空间足以永久映射所有物理内存页面。 + +* vmap()。这可以用来将多个物理页长期映射到一个连续的虚拟空间。它需要全局同步来解除 + 映射。 + +临时映射的成本 +============== + +创建临时映射的代价可能相当高。体系架构必须操作内核的页表、数据TLB和/或MMU的寄存器。 + +如果CONFIG_HIGHMEM没有被设置,那么内核会尝试用一点计算来创建映射,将页面结构地址转换成 +指向页面内容的指针,而不是去捣鼓映射。在这种情况下,解映射操作可能是一个空操作。 + +如果CONFIG_MMU没有被设置,那么就不可能有临时映射和高内存。在这种情况下,也将使用计算方法。 + + +i386 PAE +======== + +在某些情况下,i386 架构将允许你在 32 位机器上安装多达 64GiB 的内存。但这有一些后果: + +* Linux需要为系统中的每个页面建立一个页帧结构,而且页帧需要驻在永久映射中,这意味着: + +* 你最多可以有896M/sizeof(struct page)页帧;由于页结构体是32字节的,所以最终会有 + 112G的页;然而,内核需要在内存中存储更多的页帧...... + +* PAE使你的页表变大--这使系统变慢,因为更多的数据需要在TLB填充等方面被访问。一个好处 + 是,PAE有更多的PTE位,可以提供像NX和PAT这样的高级功能。 + +一般的建议是,你不要在32位机器上使用超过8GiB的空间--尽管更多的空间可能对你和你的工作 +量有用,但你几乎是靠你自己--不要指望内核开发者真的会很关心事情的进展情况。 + +函数 +==== + +该API在以下内核代码中: + +include/linux/highmem.h + +include/linux/highmem-internal.h diff --git a/Documentation/translations/zh_CN/mm/hmm.rst b/Documentation/translations/zh_CN/mm/hmm.rst new file mode 100644 index 000000000..5024a8a15 --- /dev/null +++ b/Documentation/translations/zh_CN/mm/hmm.rst @@ -0,0 +1,361 @@ +.. include:: ../disclaimer-zh_CN.rst + +:Original: Documentation/mm/hmm.rst + +:翻译: + + 司延腾 Yanteng Si <siyanteng@loongson.cn> + +:校译: + +================== +异构内存管理 (HMM) +================== + +提供基础设施和帮助程序以将非常规内存(设备内存,如板上 GPU 内存)集成到常规内核路径中,其 +基石是此类内存的专用struct page(请参阅本文档的第 5 至 7 节)。 + +HMM 还为 SVM(共享虚拟内存)提供了可选的帮助程序,即允许设备透明地访问与 CPU 一致的程序 +地址,这意味着 CPU 上的任何有效指针也是该设备的有效指针。这对于简化高级异构计算的使用变得 +必不可少,其中 GPU、DSP 或 FPGA 用于代表进程执行各种计算。 + +本文档分为以下部分:在第一部分中,我揭示了与使用特定于设备的内存分配器相关的问题。在第二 +部分中,我揭示了许多平台固有的硬件限制。第三部分概述了 HMM 设计。第四部分解释了 CPU 页 +表镜像的工作原理以及 HMM 在这种情况下的目的。第五部分处理内核中如何表示设备内存。最后, +最后一节介绍了一个新的迁移助手,它允许利用设备 DMA 引擎。 + +.. contents:: :local: + +使用特定于设备的内存分配器的问题 +================================ + +具有大量板载内存(几 GB)的设备(如 GPU)历来通过专用驱动程序特定 API 管理其内存。这会 +造成设备驱动程序分配和管理的内存与常规应用程序内存(私有匿名、共享内存或常规文件支持内存) +之间的隔断。从这里开始,我将把这个方面称为分割的地址空间。我使用共享地址空间来指代相反的情况: +即,设备可以透明地使用任何应用程序内存区域。 + +分割的地址空间的发生是因为设备只能访问通过设备特定 API 分配的内存。这意味着从设备的角度来 +看,程序中的所有内存对象并不平等,这使得依赖于广泛的库的大型程序变得复杂。 + +具体来说,这意味着想要利用像 GPU 这样的设备的代码需要在通用分配的内存(malloc、mmap +私有、mmap 共享)和通过设备驱动程序 API 分配的内存之间复制对象(这仍然以 mmap 结束, +但是是设备文件)。 + +对于平面数据集(数组、网格、图像……),这并不难实现,但对于复杂数据集(列表、树……), +很难做到正确。复制一个复杂的数据集需要重新映射其每个元素之间的所有指针关系。这很容易出错, +而且由于数据集和地址的重复,程序更难调试。 + +分割地址空间也意味着库不能透明地使用它们从核心程序或另一个库中获得的数据,因此每个库可能 +不得不使用设备特定的内存分配器来重复其输入数据集。大型项目会因此受到影响,并因为各种内存 +拷贝而浪费资源。 + +复制每个库的API以接受每个设备特定分配器分配的内存作为输入或输出,并不是一个可行的选择。 +这将导致库入口点的组合爆炸。 + +最后,随着高级语言结构(在 C++ 中,当然也在其他语言中)的进步,编译器现在有可能在没有程 +序员干预的情况下利用 GPU 和其他设备。某些编译器识别的模式仅适用于共享地址空间。对所有 +其他模式,使用共享地址空间也更合理。 + + +I/O 总线、设备内存特性 +====================== + +由于一些限制,I/O 总线削弱了共享地址空间。大多数 I/O 总线只允许从设备到主内存的基本 +内存访问;甚至缓存一致性通常是可选的。从 CPU 访问设备内存甚至更加有限。通常情况下,它 +不是缓存一致的。 + +如果我们只考虑 PCIE 总线,那么设备可以访问主内存(通常通过 IOMMU)并与 CPU 缓存一 +致。但是,它只允许设备对主存储器进行一组有限的原子操作。这在另一个方向上更糟:CPU +只能访问有限范围的设备内存,而不能对其执行原子操作。因此,从内核的角度来看,设备内存不 +能被视为与常规内存等同。 + +另一个严重的因素是带宽有限(约 32GBytes/s,PCIE 4.0 和 16 通道)。这比最快的 GPU +内存 (1 TBytes/s) 慢 33 倍。最后一个限制是延迟。从设备访问主内存的延迟比设备访问自 +己的内存时高一个数量级。 + +一些平台正在开发新的 I/O 总线或对 PCIE 的添加/修改以解决其中一些限制 +(OpenCAPI、CCIX)。它们主要允许 CPU 和设备之间的双向缓存一致性,并允许架构支持的所 +有原子操作。遗憾的是,并非所有平台都遵循这一趋势,并且一些主要架构没有针对这些问题的硬 +件解决方案。 + +因此,为了使共享地址空间有意义,我们不仅必须允许设备访问任何内存,而且还必须允许任何内 +存在设备使用时迁移到设备内存(在迁移时阻止 CPU 访问)。 + + +共享地址空间和迁移 +================== + +HMM 打算提供两个主要功能。第一个是通过复制cpu页表到设备页表中来共享地址空间,因此对 +于进程地址空间中的任何有效主内存地址,相同的地址指向相同的物理内存。 + +为了实现这一点,HMM 提供了一组帮助程序来填充设备页表,同时跟踪 CPU 页表更新。设备页表 +更新不像 CPU 页表更新那么容易。要更新设备页表,您必须分配一个缓冲区(或使用预先分配的 +缓冲区池)并在其中写入 GPU 特定命令以执行更新(取消映射、缓存失效和刷新等)。这不能通 +过所有设备的通用代码来完成。因此,为什么HMM提供了帮助器,在把硬件的具体细节留给设备驱 +动程序的同时,把一切可以考虑的因素都考虑进去了。 + +HMM 提供的第二种机制是一种新的 ZONE_DEVICE 内存,它允许为设备内存的每个页面分配一个 +struct page。这些页面很特殊,因为 CPU 无法映射它们。然而,它们允许使用现有的迁移机 +制将主内存迁移到设备内存,从 CPU 的角度来看,一切看起来都像是换出到磁盘的页面。使用 +struct page可以与现有的 mm 机制进行最简单、最干净的集成。再次,HMM 仅提供帮助程序, +首先为设备内存热插拔新的 ZONE_DEVICE 内存,然后执行迁移。迁移内容和时间的策略决定留 +给设备驱动程序。 + +请注意,任何 CPU 对设备页面的访问都会触发缺页异常并迁移回主内存。例如,当支持给定CPU +地址 A 的页面从主内存页面迁移到设备页面时,对地址 A 的任何 CPU 访问都会触发缺页异常 +并启动向主内存的迁移。 + +凭借这两个特性,HMM 不仅允许设备镜像进程地址空间并保持 CPU 和设备页表同步,而且还通 +过迁移设备正在使用的数据集部分来利用设备内存。 + + +地址空间镜像实现和API +===================== + +地址空间镜像的主要目标是允许将一定范围的 CPU 页表复制到一个设备页表中;HMM 有助于 +保持两者同步。想要镜像进程地址空间的设备驱动程序必须从注册 mmu_interval_notifier +开始:: + + int mmu_interval_notifier_insert(struct mmu_interval_notifier *interval_sub, + struct mm_struct *mm, unsigned long start, + unsigned long length, + const struct mmu_interval_notifier_ops *ops); + +在 ops->invalidate() 回调期间,设备驱动程序必须对范围执行更新操作(将范围标记为只 +读,或完全取消映射等)。设备必须在驱动程序回调返回之前完成更新。 + +当设备驱动程序想要填充一个虚拟地址范围时,它可以使用:: + + int hmm_range_fault(struct hmm_range *range); + +如果请求写访问,它将在丢失或只读条目上触发缺页异常(见下文)。缺页异常使用通用的 mm 缺 +页异常代码路径,就像 CPU 缺页异常一样。 + +这两个函数都将 CPU 页表条目复制到它们的 pfns 数组参数中。该数组中的每个条目对应于虚拟 +范围中的一个地址。HMM 提供了一组标志来帮助驱动程序识别特殊的 CPU 页表项。 + +在 sync_cpu_device_pagetables() 回调中锁定是驱动程序必须尊重的最重要的方面,以保 +持事物正确同步。使用模式是:: + + int driver_populate_range(...) + { + struct hmm_range range; + ... + + range.notifier = &interval_sub; + range.start = ...; + range.end = ...; + range.hmm_pfns = ...; + + if (!mmget_not_zero(interval_sub->notifier.mm)) + return -EFAULT; + + again: + range.notifier_seq = mmu_interval_read_begin(&interval_sub); + mmap_read_lock(mm); + ret = hmm_range_fault(&range); + if (ret) { + mmap_read_unlock(mm); + if (ret == -EBUSY) + goto again; + return ret; + } + mmap_read_unlock(mm); + + take_lock(driver->update); + if (mmu_interval_read_retry(&ni, range.notifier_seq) { + release_lock(driver->update); + goto again; + } + + /* Use pfns array content to update device page table, + * under the update lock */ + + release_lock(driver->update); + return 0; + } + +driver->update 锁与驱动程序在其 invalidate() 回调中使用的锁相同。该锁必须在调用 +mmu_interval_read_retry() 之前保持,以避免与并发 CPU 页表更新发生任何竞争。 + +利用 default_flags 和 pfn_flags_mask +==================================== + +hmm_range 结构有 2 个字段,default_flags 和 pfn_flags_mask,它们指定整个范围 +的故障或快照策略,而不必为 pfns 数组中的每个条目设置它们。 + +例如,如果设备驱动程序需要至少具有读取权限的范围的页面,它会设置:: + + range->default_flags = HMM_PFN_REQ_FAULT; + range->pfn_flags_mask = 0; + +并如上所述调用 hmm_range_fault()。这将填充至少具有读取权限的范围内的所有页面。 + +现在假设驱动程序想要做同样的事情,除了它想要拥有写权限的范围内的一页。现在驱动程序设 +置:: + + range->default_flags = HMM_PFN_REQ_FAULT; + range->pfn_flags_mask = HMM_PFN_REQ_WRITE; + range->pfns[index_of_write] = HMM_PFN_REQ_WRITE; + +有了这个,HMM 将在至少读取(即有效)的所有页面中异常,并且对于地址 +== range->start + (index_of_write << PAGE_SHIFT) 它将异常写入权限,即,如果 +CPU pte 没有设置写权限,那么HMM将调用handle_mm_fault()。 + +hmm_range_fault 完成后,标志位被设置为页表的当前状态,即 HMM_PFN_VALID | 如果页 +面可写,将设置 HMM_PFN_WRITE。 + + +从核心内核的角度表示和管理设备内存 +================================== + +尝试了几种不同的设计来支持设备内存。第一个使用特定于设备的数据结构来保存有关迁移内存 +的信息,HMM 将自身挂接到 mm 代码的各个位置,以处理对设备内存支持的地址的任何访问。 +事实证明,这最终复制了 struct page 的大部分字段,并且还需要更新许多内核代码路径才 +能理解这种新的内存类型。 + +大多数内核代码路径从不尝试访问页面后面的内存,而只关心struct page的内容。正因为如此, +HMM 切换到直接使用 struct page 用于设备内存,这使得大多数内核代码路径不知道差异。 +我们只需要确保没有人试图从 CPU 端映射这些页面。 + +移入和移出设备内存 +================== + +由于 CPU 无法直接访问设备内存,因此设备驱动程序必须使用硬件 DMA 或设备特定的加载/存 +储指令来迁移数据。migrate_vma_setup()、migrate_vma_pages() 和 +migrate_vma_finalize() 函数旨在使驱动程序更易于编写并集中跨驱动程序的通用代码。 + +在将页面迁移到设备私有内存之前,需要创建特殊的设备私有 ``struct page`` 。这些将用 +作特殊的“交换”页表条目,以便 CPU 进程在尝试访问已迁移到设备专用内存的页面时会发生异常。 + +这些可以通过以下方式分配和释放:: + + struct resource *res; + struct dev_pagemap pagemap; + + res = request_free_mem_region(&iomem_resource, /* number of bytes */, + "name of driver resource"); + pagemap.type = MEMORY_DEVICE_PRIVATE; + pagemap.range.start = res->start; + pagemap.range.end = res->end; + pagemap.nr_range = 1; + pagemap.ops = &device_devmem_ops; + memremap_pages(&pagemap, numa_node_id()); + + memunmap_pages(&pagemap); + release_mem_region(pagemap.range.start, range_len(&pagemap.range)); + +还有devm_request_free_mem_region(), devm_memremap_pages(), +devm_memunmap_pages() 和 devm_release_mem_region() 当资源可以绑定到 ``struct device``. + +整体迁移步骤类似于在系统内存中迁移 NUMA 页面(see :ref:`Page migration <page_migration>`) , +但这些步骤分为设备驱动程序特定代码和共享公共代码: + +1. ``mmap_read_lock()`` + + 设备驱动程序必须将 ``struct vm_area_struct`` 传递给migrate_vma_setup(), + 因此需要在迁移期间保留 mmap_read_lock() 或 mmap_write_lock()。 + +2. ``migrate_vma_setup(struct migrate_vma *args)`` + + 设备驱动初始化了 ``struct migrate_vma`` 的字段,并将该指针传递给 + migrate_vma_setup()。``args->flags`` 字段是用来过滤哪些源页面应该被迁移。 + 例如,设置 ``MIGRATE_VMA_SELECT_SYSTEM`` 将只迁移系统内存,设置 + ``MIGRATE_VMA_SELECT_DEVICE_PRIVATE`` 将只迁移驻留在设备私有内存中的页 + 面。如果后者被设置, ``args->pgmap_owner`` 字段被用来识别驱动所拥有的设备 + 私有页。这就避免了试图迁移驻留在其他设备中的设备私有页。目前,只有匿名的私有VMA + 范围可以被迁移到系统内存和设备私有内存。 + + migrate_vma_setup()所做的第一步是用 ``mmu_notifier_invalidate_range_start()`` + 和 ``mmu_notifier_invalidate_range_end()`` 调用来遍历设备周围的页表,使 + 其他设备的MMU无效,以便在 ``args->src`` 数组中填写要迁移的PFN。 + ``invalidate_range_start()`` 回调传递给一个``struct mmu_notifier_range`` , + 其 ``event`` 字段设置为MMU_NOTIFY_MIGRATE, ``owner`` 字段设置为传递给 + migrate_vma_setup()的 ``args->pgmap_owner`` 字段。这允许设备驱动跳过无 + 效化回调,只无效化那些实际正在迁移的设备私有MMU映射。这一点将在下一节详细解释。 + + + 在遍历页表时,一个 ``pte_none()`` 或 ``is_zero_pfn()`` 条目导致一个有效 + 的 “zero” PFN 存储在 ``args->src`` 阵列中。这让驱动分配设备私有内存并清 + 除它,而不是复制一个零页。到系统内存或设备私有结构页的有效PTE条目将被 + ``lock_page()``锁定,与LRU隔离(如果系统内存和设备私有页不在LRU上),从进 + 程中取消映射,并插入一个特殊的迁移PTE来代替原来的PTE。 migrate_vma_setup() + 还清除了 ``args->dst`` 数组。 + +3. 设备驱动程序分配目标页面并将源页面复制到目标页面。 + + 驱动程序检查每个 ``src`` 条目以查看该 ``MIGRATE_PFN_MIGRATE`` 位是否已 + 设置并跳过未迁移的条目。设备驱动程序还可以通过不填充页面的 ``dst`` 数组来选 + 择跳过页面迁移。 + + 然后,驱动程序分配一个设备私有 struct page 或一个系统内存页,用 ``lock_page()`` + 锁定该页,并将 ``dst`` 数组条目填入:: + + dst[i] = migrate_pfn(page_to_pfn(dpage)); + + 现在驱动程序知道这个页面正在被迁移,它可以使设备私有 MMU 映射无效并将设备私有 + 内存复制到系统内存或另一个设备私有页面。由于核心 Linux 内核会处理 CPU 页表失 + 效,因此设备驱动程序只需使其自己的 MMU 映射失效。 + + 驱动程序可以使用 ``migrate_pfn_to_page(src[i])`` 来获取源设备的 + ``struct page`` 面,并将源页面复制到目标设备上,如果指针为 ``NULL`` ,意 + 味着源页面没有被填充到系统内存中,则清除目标设备的私有内存。 + +4. ``migrate_vma_pages()`` + + 这一步是实际“提交”迁移的地方。 + + 如果源页是 ``pte_none()`` 或 ``is_zero_pfn()`` 页,这时新分配的页会被插 + 入到CPU的页表中。如果一个CPU线程在同一页面上发生异常,这可能会失败。然而,页 + 表被锁定,只有一个新页会被插入。如果它失去了竞争,设备驱动将看到 + ``MIGRATE_PFN_MIGRATE`` 位被清除。 + + 如果源页被锁定、隔离等,源 ``struct page`` 信息现在被复制到目标 + ``struct page`` ,最终完成CPU端的迁移。 + +5. 设备驱动为仍在迁移的页面更新设备MMU页表,回滚未迁移的页面。 + + 如果 ``src`` 条目仍然有 ``MIGRATE_PFN_MIGRATE`` 位被设置,设备驱动可以 + 更新设备MMU,如果 ``MIGRATE_PFN_WRITE`` 位被设置,则设置写启用位。 + +6. ``migrate_vma_finalize()`` + + 这一步用新页的页表项替换特殊的迁移页表项,并释放对源和目的 ``struct page`` + 的引用。 + +7. ``mmap_read_unlock()`` + + 现在可以释放锁了。 + +独占访问存储器 +============== + +一些设备具有诸如原子PTE位的功能,可以用来实现对系统内存的原子访问。为了支持对一 +个共享的虚拟内存页的原子操作,这样的设备需要对该页的访问是排他的,而不是来自CPU +的任何用户空间访问。 ``make_device_exclusive_range()`` 函数可以用来使一 +个内存范围不能从用户空间访问。 + +这将用特殊的交换条目替换给定范围内的所有页的映射。任何试图访问交换条目的行为都会 +导致一个异常,该异常会通过用原始映射替换该条目而得到恢复。驱动程序会被通知映射已 +经被MMU通知器改变,之后它将不再有对该页的独占访问。独占访问被保证持续到驱动程序 +放弃页面锁和页面引用为止,这时页面上的任何CPU异常都可以按所述进行。 + +内存 cgroup (memcg) 和 rss 统计 +=============================== + +目前,设备内存被视为 rss 计数器中的任何常规页面(如果设备页面用于匿名,则为匿名, +如果设备页面用于文件支持页面,则为文件,如果设备页面用于共享内存,则为 shmem)。 +这是为了保持现有应用程序的故意选择,这些应用程序可能在不知情的情况下开始使用设备 +内存,运行不受影响。 + +一个缺点是 OOM 杀手可能会杀死使用大量设备内存而不是大量常规系统内存的应用程序, +因此不会释放太多系统内存。在决定以不同方式计算设备内存之前,我们希望收集更多关 +于应用程序和系统在存在设备内存的情况下在内存压力下如何反应的实际经验。 + +对内存 cgroup 做出了相同的决定。设备内存页面根据相同的内存 cgroup 计算,常规 +页面将被计算在内。这确实简化了进出设备内存的迁移。这也意味着从设备内存迁移回常规 +内存不会失败,因为它会超过内存 cgroup 限制。一旦我们对设备内存的使用方式及其对 +内存资源控制的影响有了更多的了解,我们可能会在后面重新考虑这个选择。 + +请注意,设备内存永远不能由设备驱动程序或通过 GUP 固定,因此此类内存在进程退出时 +总是被释放的。或者在共享内存或文件支持内存的情况下,当删除最后一个引用时。 diff --git a/Documentation/translations/zh_CN/mm/hugetlbfs_reserv.rst b/Documentation/translations/zh_CN/mm/hugetlbfs_reserv.rst new file mode 100644 index 000000000..752e5696c --- /dev/null +++ b/Documentation/translations/zh_CN/mm/hugetlbfs_reserv.rst @@ -0,0 +1,436 @@ +.. include:: ../disclaimer-zh_CN.rst + +:Original: Documentation/mm/hugetlbfs_reserv.rst + +:翻译: + + 司延腾 Yanteng Si <siyanteng@loongson.cn> + +:校译: + +============== +Hugetlbfs 预留 +============== + +概述 +==== + +:ref:`hugetlbpage` 中描述的巨页通常是预先分配给应用程序使用的。如果VMA指 +示要使用巨页,这些巨页会在缺页异常时被实例化到任务的地址空间。如果在缺页异常 +时没有巨页存在,任务就会被发送一个SIGBUS,并经常不高兴地死去。在加入巨页支 +持后不久,人们决定,在mmap()时检测巨页的短缺情况会更好。这个想法是,如果 +没有足够的巨页来覆盖映射,mmap()将失败。这首先是在mmap()时在代码中做一个 +简单的检查,以确定是否有足够的空闲巨页来覆盖映射。就像内核中的大多数东西一 +样,代码随着时间的推移而不断发展。然而,基本的想法是在mmap()时 “预留” +巨页,以确保巨页可以用于该映射中的缺页异常。下面的描述试图描述在v4.10内核 +中是如何进行巨页预留处理的。 + + +读者 +==== +这个描述主要是针对正在修改hugetlbfs代码的内核开发者。 + + +数据结构 +======== + +resv_huge_pages + 这是一个全局的(per-hstate)预留的巨页的计数。预留的巨页只对预留它们的任 + 务可用。因此,一般可用的巨页的数量被计算为(``free_huge_pages - resv_huge_pages``)。 +Reserve Map + 预留映射由以下结构体描述:: + + struct resv_map { + struct kref refs; + spinlock_t lock; + struct list_head regions; + long adds_in_progress; + struct list_head region_cache; + long region_cache_count; + }; + + 系统中每个巨页映射都有一个预留映射。resv_map中的regions列表描述了映射中的 + 区域。一个区域被描述为:: + + struct file_region { + struct list_head link; + long from; + long to; + }; + + file_region结构体的 ‘from’ 和 ‘to’ 字段是进入映射的巨页索引。根据映射的类型,在 + reserv_map 中的一个区域可能表示该范围存在预留,或预留不存在。 +Flags for MAP_PRIVATE Reservations + 这些被存储在预留的映射指针的底部。 + + ``#define HPAGE_RESV_OWNER (1UL << 0)`` + 表示该任务是与该映射相关的预留的所有者。 + ``#define HPAGE_RESV_UNMAPPED (1UL << 1)`` + 表示最初映射此范围(并创建储备)的任务由于COW失败而从该任务(子任务)中取消映 + 射了一个页面。 +Page Flags + PagePrivate页面标志是用来指示在释放巨页时必须恢复巨页的预留。更多细节将在 + “释放巨页” 一节中讨论。 + + +预留映射位置(私有或共享) +========================== + +一个巨页映射或段要么是私有的,要么是共享的。如果是私有的,它通常只对一个地址空间 +(任务)可用。如果是共享的,它可以被映射到多个地址空间(任务)。对于这两种类型的映射, +预留映射的位置和语义是明显不同的。位置的差异是: + +- 对于私有映射,预留映射挂在VMA结构体上。具体来说,就是vma->vm_private_data。这个保 + 留映射是在创建映射(mmap(MAP_PRIVATE))时创建的。 +- 对于共享映射,预留映射挂在inode上。具体来说,就是inode->i_mapping->private_data。 + 由于共享映射总是由hugetlbfs文件系统中的文件支持,hugetlbfs代码确保每个节点包含一个预 + 留映射。因此,预留映射在创建节点时被分配。 + + +创建预留 +======== +当创建一个巨大的有页面支持的共享内存段(shmget(SHM_HUGETLB))或通过mmap(MAP_HUGETLB) +创建一个映射时,就会创建预留。这些操作会导致对函数hugetlb_reserve_pages()的调用:: + + int hugetlb_reserve_pages(struct inode *inode, + long from, long to, + struct vm_area_struct *vma, + vm_flags_t vm_flags) + +hugetlb_reserve_pages()做的第一件事是检查在调用shmget()或mmap()时是否指定了NORESERVE +标志。如果指定了NORESERVE,那么这个函数立即返回,因为不需要预留。 + +参数'from'和'to'是映射或基础文件的巨页索引。对于shmget(),'from'总是0,'to'对应于段/映射 +的长度。对于mmap(),offset参数可以用来指定进入底层文件的偏移量。在这种情况下,'from'和'to' +参数已经被这个偏移量所调整。 + +PRIVATE和SHARED映射之间的一个很大的区别是预留在预留映射中的表示方式。 + +- 对于共享映射,预留映射中的条目表示对应页面的预留存在或曾经存在。当预留被消耗时,预留映射不被 + 修改。 +- 对于私有映射,预留映射中没有条目表示相应页面存在预留。随着预留被消耗,条目被添加到预留映射中。 + 因此,预留映射也可用于确定哪些预留已被消耗。 + +对于私有映射,hugetlb_reserve_pages()创建预留映射并将其挂在VMA结构体上。此外, +HPAGE_RESV_OWNER标志被设置,以表明该VMA拥有预留。 + +预留映射被查阅以确定当前映射/段需要多少巨页预留。对于私有映射,这始终是一个值(to - from)。 +然而,对于共享映射来说,一些预留可能已经存在于(to - from)的范围内。关于如何实现这一点的细节, +请参见 :ref:`预留映射的修改 <resv_map_modifications>` 一节。 + +该映射可能与一个子池(subpool)相关联。如果是这样,将查询子池以确保有足够的空间用于映射。子池 +有可能已经预留了可用于映射的预留空间。更多细节请参见 :ref: `子池预留 <sub_pool_resv>` +一节。 + +在咨询了预留映射和子池之后,就知道了需要的新预留数量。hugetlb_acct_memory()函数被调用以检查 +并获取所要求的预留数量。hugetlb_acct_memory()调用到可能分配和调整剩余页数的函数。然而,在这 +些函数中,代码只是检查以确保有足够的空闲的巨页来容纳预留。如果有的话,全局预留计数resv_huge_pages +会被调整,如下所示:: + + if (resv_needed <= (resv_huge_pages - free_huge_pages)) + resv_huge_pages += resv_needed; + +注意,在检查和调整这些计数器时,全局锁hugetlb_lock会被预留。 + +如果有足够的空闲的巨页,并且全局计数resv_huge_pages被调整,那么与映射相关的预留映射被修改以 +反映预留。在共享映射的情况下,将存在一个file_region,包括'from'-'to'范围。对于私有映射, +不对预留映射进行修改,因为没有条目表示存在预留。 + +如果hugetlb_reserve_pages()成功,全局预留数和与映射相关的预留映射将根据需要被修改,以确保 +在'from'-'to'范围内存在预留。 + +消耗预留/分配一个巨页 +=========================== + +当与预留相关的巨页在相应的映射中被分配和实例化时,预留就被消耗了。该分配是在函数alloc_huge_page() +中进行的:: + + struct page *alloc_huge_page(struct vm_area_struct *vma, + unsigned long addr, int avoid_reserve) + +alloc_huge_page被传递给一个VMA指针和一个虚拟地址,因此它可以查阅预留映射以确定是否存在预留。 +此外,alloc_huge_page需要一个参数avoid_reserve,该参数表示即使看起来已经为指定的地址预留了 +预留,也不应该使用预留。avoid_reserve参数最常被用于写时拷贝和页面迁移的情况下,即现有页面的额 +外拷贝被分配。 + + +调用辅助函数vma_needs_reservation()来确定是否存在对映射(vma)中地址的预留。关于这个函数的详 +细内容,请参见 :ref:`预留映射帮助函数 <resv_map_helpers>` 一节。从 +vma_needs_reservation()返回的值通常为0或1。如果该地址存在预留,则为0,如果不存在预留,则为1。 +如果不存在预留,并且有一个与映射相关联的子池,则查询子池以确定它是否包含预留。如果子池包含预留, +则可将其中一个用于该分配。然而,在任何情况下,avoid_reserve参数都会优先考虑为分配使用预留。在 +确定预留是否存在并可用于分配后,调用dequeue_huge_page_vma()函数。这个函数需要两个与预留有关 +的参数: + +- avoid_reserve,这是传递给alloc_huge_page()的同一个值/参数。 +- chg,尽管这个参数的类型是long,但只有0或1的值被传递给dequeue_huge_page_vma。如果该值为0, + 则表明存在预留(关于可能的问题,请参见 “预留和内存策略” 一节)。如果值 + 为1,则表示不存在预留,如果可能的话,必须从全局空闲池中取出该页。 + +与VMA的内存策略相关的空闲列表被搜索到一个空闲页。如果找到了一个页面,当该页面从空闲列表中移除时, +free_huge_pages的值被递减。如果有一个与该页相关的预留,将进行以下调整:: + + SetPagePrivate(page); /* 表示分配这个页面消耗了一个预留, + * 如果遇到错误,以至于必须释放这个页面,预留将被 + * 恢复。 */ + resv_huge_pages--; /* 减少全局预留计数 */ + +注意,如果找不到满足VMA内存策略的巨页,将尝试使用伙伴分配器分配一个。这就带来了超出预留范围 +的剩余巨页和超额分配的问题。即使分配了一个多余的页面,也会进行与上面一样的基于预留的调整: +SetPagePrivate(page) 和 resv_huge_pages--. + +在获得一个新的巨页后,(page)->private被设置为与该页面相关的子池的值,如果它存在的话。当页 +面被释放时,这将被用于子池的计数。 + +然后调用函数vma_commit_reservation(),根据预留的消耗情况调整预留映射。一般来说,这涉及 +到确保页面在区域映射的file_region结构体中被表示。对于预留存在的共享映射,预留映射中的条目 +已经存在,所以不做任何改变。然而,如果共享映射中没有预留,或者这是一个私有映射,则必须创建一 +个新的条目。 + +注意,如果找不到满足VMA内存策略的巨页,将尝试使用伙伴分配器分配一个。这就带来了超出预留范围 +的剩余巨页和过度分配的问题。即使分配了一个多余的页面,也会进行与上面一样的基于预留的调整。 +SetPagePrivate(page)和resv_huge_pages-。 + +在获得一个新的巨页后,(page)->private被设置为与该页面相关的子池的值,如果它存在的话。当页 +面被释放时,这将被用于子池的计数。 + +然后调用函数vma_commit_reservation(),根据预留的消耗情况调整预留映射。一般来说,这涉及 +到确保页面在区域映射的file_region结构体中被表示。对于预留存在的共享映射,预留映射中的条目 +已经存在,所以不做任何改变。然而,如果共享映射中没有预留,或者这是一个私有映射,则必须创建 +一个新的条目。 + +在alloc_huge_page()开始调用vma_needs_reservation()和页面分配后调用 +vma_commit_reservation()之间,预留映射有可能被改变。如果hugetlb_reserve_pages在共 +享映射中为同一页面被调用,这将是可能的。在这种情况下,预留计数和子池空闲页计数会有一个偏差。 +这种罕见的情况可以通过比较vma_needs_reservation和vma_commit_reservation的返回值来 +识别。如果检测到这种竞争,子池和全局预留计数将被调整以进行补偿。关于这些函数的更多信息,请 +参见 :ref:`预留映射帮助函数 <resv_map_helpers>` 一节。 + + +实例化巨页 +========== + +在巨页分配之后,页面通常被添加到分配任务的页表中。在此之前,共享映射中的页面被添加到页面缓 +存中,私有映射中的页面被添加到匿名反向映射中。在这两种情况下,PagePrivate标志被清除。因此, +当一个已经实例化的巨页被释放时,不会对全局预留计数(resv_huge_pages)进行调整。 + + +释放巨页 +======== + +巨页释放是由函数free_huge_page()执行的。这个函数是hugetlbfs复合页的析构器。因此,它只传 +递一个指向页面结构体的指针。当一个巨页被释放时,可能需要进行预留计算。如果该页与包含保 +留的子池相关联,或者该页在错误路径上被释放,必须恢复全局预留计数,就会出现这种情况。 + +page->private字段指向与该页相关的任何子池。如果PagePrivate标志被设置,它表明全局预留计数 +应该被调整(关于如何设置这些标志的信息,请参见 +:ref: `消耗预留/分配一个巨页 <consume_resv>` )。 + + +该函数首先调用hugepage_subpool_put_pages()来处理该页。如果这个函数返回一个0的值(不等于 +传递的1的值),它表明预留与子池相关联,这个新释放的页面必须被用来保持子池预留的数量超过最小值。 +因此,在这种情况下,全局resv_huge_pages计数器被递增。 + +如果页面中设置了PagePrivate标志,那么全局resv_huge_pages计数器将永远被递增。 + +子池预留 +======== + +有一个结构体hstate与每个巨页尺寸相关联。hstate跟踪所有指定大小的巨页。一个子池代表一 +个hstate中的页面子集,它与一个已挂载的hugetlbfs文件系统相关 + +当一个hugetlbfs文件系统被挂载时,可以指定min_size选项,它表示文件系统所需的最小的巨页数量。 +如果指定了这个选项,与min_size相对应的巨页的数量将被预留给文件系统使用。这个数字在结构体 +hugepage_subpool的min_hpages字段中被跟踪。在挂载时,hugetlb_acct_memory(min_hpages) +被调用以预留指定数量的巨页。如果它们不能被预留,挂载就会失败。 + +当从子池中获取或释放页面时,会调用hugepage_subpool_get/put_pages()函数。 +hugepage_subpool_get/put_pages被传递给巨页数量,以此来调整子池的 “已用页面” 计数 +(get为下降,put为上升)。通常情况下,如果子池中没有足够的页面,它们会返回与传递的相同的值或 +一个错误。 + +然而,如果预留与子池相关联,可能会返回一个小于传递值的返回值。这个返回值表示必须进行的额外全局 +池调整的数量。例如,假设一个子池包含3个预留的巨页,有人要求5个。与子池相关的3个预留页可以用来 +满足部分请求。但是,必须从全局池中获得2个页面。为了向调用者转达这一信息,将返回值2。然后,调用 +者要负责从全局池中获取另外两个页面。 + + +COW和预留 +========== + +由于共享映射都指向并使用相同的底层页面,COW最大的预留问题是私有映射。在这种情况下,两个任务可 +以指向同一个先前分配的页面。一个任务试图写到该页,所以必须分配一个新的页,以便每个任务都指向它 +自己的页。 + +当该页最初被分配时,该页的预留被消耗了。当由于COW而试图分配一个新的页面时,有可能没有空闲的巨 +页,分配会失败。 + +当最初创建私有映射时,通过设置所有者的预留映射指针中的HPAGE_RESV_OWNER位来标记映射的所有者。 +由于所有者创建了映射,所有者拥有与映射相关的所有预留。因此,当一个写异常发生并且没有可用的页面 +时,对预留的所有者和非所有者采取不同的行动。 + +在发生异常的任务不是所有者的情况下,异常将失败,该任务通常会收到一个SIGBUS。 + +如果所有者是发生异常的任务,我们希望它能够成功,因为它拥有原始的预留。为了达到这个目的,该页被 +从非所有者任务中解映射出来。这样一来,唯一的引用就是来自拥有者的任务。此外,HPAGE_RESV_UNMAPPED +位被设置在非拥有任务的预留映射指针中。如果非拥有者任务后来在一个不存在的页面上发生异常,它可能 +会收到一个SIGBUS。但是,映射/预留的原始拥有者的行为将与预期一致。 + +预留映射的修改 +============== + +以下低级函数用于对预留映射进行修改。通常情况下,这些函数不会被直接调用。而是调用一个预留映射辅 +助函数,该函数调用这些低级函数中的一个。这些低级函数在源代码(mm/hugetlb.c)中得到了相当好的 +记录。这些函数是:: + + long region_chg(struct resv_map *resv, long f, long t); + long region_add(struct resv_map *resv, long f, long t); + void region_abort(struct resv_map *resv, long f, long t); + long region_count(struct resv_map *resv, long f, long t); + +在预留映射上的操作通常涉及两个操作: + +1) region_chg()被调用来检查预留映射,并确定在指定的范围[f, t]内有多少页目前没有被代表。 + + 调用代码执行全局检查和分配,以确定是否有足够的巨页使操作成功。 + +2) + a) 如果操作能够成功,regi_add()将被调用,以实际修改先前传递给regi_chg()的相同范围 + [f, t]的预留映射。 + b) 如果操作不能成功,region_abort被调用,在相同的范围[f, t]内中止操作。 + +注意,这是一个两步的过程, region_add()和 region_abort()在事先调用 region_chg()后保证 +成功。 region_chg()负责预先分配任何必要的数据结构以确保后续操作(特别是 region_add())的 +成功。 + +如上所述,region_chg()确定该范围内当前没有在映射中表示的页面的数量。region_add()返回添加 +到映射中的范围内的页数。在大多数情况下, region_add() 的返回值与 region_chg() 的返回值相 +同。然而,在共享映射的情况下,有可能在调用 region_chg() 和 region_add() 之间对预留映射进 +行更改。在这种情况下,regi_add()的返回值将与regi_chg()的返回值不符。在这种情况下,全局计数 +和子池计数很可能是不正确的,需要调整。检查这种情况并进行适当的调整是调用者的责任。 + +函数region_del()被调用以从预留映射中移除区域。 +它通常在以下情况下被调用: + +- 当hugetlbfs文件系统中的一个文件被删除时,该节点将被释放,预留映射也被释放。在释放预留映射 + 之前,所有单独的file_region结构体必须被释放。在这种情况下,region_del的范围是[0, LONG_MAX]。 +- 当一个hugetlbfs文件正在被截断时。在这种情况下,所有在新文件大小之后分配的页面必须被释放。 + 此外,预留映射中任何超过新文件大小的file_region条目必须被删除。在这种情况下,region_del + 的范围是[new_end_of_file, LONG_MAX]。 +- 当在一个hugetlbfs文件中打洞时。在这种情况下,巨页被一次次从文件的中间移除。当这些页被移除 + 时,region_del()被调用以从预留映射中移除相应的条目。在这种情况下,region_del被传递的范 + 围是[page_idx, page_idx + 1]。 + +在任何情况下,region_del()都会返回从预留映射中删除的页面数量。在非常罕见的情况下,region_del() +会失败。这只能发生在打洞的情况下,即它必须分割一个现有的file_region条目,而不能分配一个新的 +结构体。在这种错误情况下,region_del()将返回-ENOMEM。这里的问题是,预留映射将显示对该页有 +预留。然而,子池和全局预留计数将不反映该预留。为了处理这种情况,调用函数hugetlb_fix_reserve_counts() +来调整计数器,使其与不能被删除的预留映射条目相对应。 + +region_count()在解除私有巨页映射时被调用。在私有映射中,预留映射中没有条目表明存在一个预留。 +因此,通过计算预留映射中的条目数,我们知道有多少预留被消耗了,有多少预留是未完成的 +(Outstanding = (end - start) - region_count(resv, start, end))。由于映射正在消 +失,子池和全局预留计数被未完成的预留数量所减去。 + +预留映射帮助函数 +================ + +有几个辅助函数可以查询和修改预留映射。这些函数只对特定的巨页的预留感兴趣,所以它们只是传入一个 +地址而不是一个范围。此外,它们还传入相关的VMA。从VMA中,可以确定映射的类型(私有或共享)和预留 +映射的位置(inode或VMA)。这些函数只是调用 “预留映射的修改” 一节中描述的基础函数。然而, +它们确实考虑到了私有和共享映射的预留映射条目的 “相反” 含义,并向调用者隐藏了这个细节:: + + long vma_needs_reservation(struct hstate *h, + struct vm_area_struct *vma, + unsigned long addr) + +该函数为指定的页面调用 region_chg()。如果不存在预留,则返回1。如果存在预留,则返回0:: + + long vma_commit_reservation(struct hstate *h, + struct vm_area_struct *vma, + unsigned long addr) + +这将调用 region_add(),用于指定的页面。与region_chg和region_add的情况一样,该函数应在 +先前调用的vma_needs_reservation后调用。它将为该页添加一个预留条目。如果预留被添加,它将 +返回1,如果没有则返回0。返回值应与之前调用vma_needs_reservation的返回值进行比较。如果出 +现意外的差异,说明在两次调用之间修改了预留映射:: + + void vma_end_reservation(struct hstate *h, + struct vm_area_struct *vma, + unsigned long addr) + +这将调用指定页面的 region_abort()。与region_chg和region_abort的情况一样,该函数应在 +先前调用的vma_needs_reservation后被调用。它将中止/结束正在进行的预留添加操作:: + + long vma_add_reservation(struct hstate *h, + struct vm_area_struct *vma, + unsigned long addr) + +这是一个特殊的包装函数,有助于在错误路径上清理预留。它只从repare_reserve_on_error()函数 +中调用。该函数与vma_needs_reservation一起使用,试图将一个预留添加到预留映射中。它考虑到 +了私有和共享映射的不同预留映射语义。因此,region_add被调用用于共享映射(因为映射中的条目表 +示预留),而region_del被调用用于私有映射(因为映射中没有条目表示预留)。关于在错误路径上需 +要做什么的更多信息,请参见 “错误路径中的预留清理” 。 + + +错误路径中的预留清理 +==================== + +正如在:ref:`预留映射帮助函数<resv_map_helpers>` 一节中提到的,预留的修改分两步进行。首 +先,在分配页面之前调用vma_needs_reservation。如果分配成功,则调用vma_commit_reservation。 +如果不是,则调用vma_end_reservation。全局和子池的预留计数根据操作的成功或失败进行调整, +一切都很好。 + +此外,在一个巨页被实例化后,PagePrivate标志被清空,这样,当页面最终被释放时,计数是 +正确的。 + +然而,有几种情况是,在一个巨页被分配后,但在它被实例化之前,就遇到了错误。在这种情况下, +页面分配已经消耗了预留,并进行了适当的子池、预留映射和全局计数调整。如果页面在这个时候被释放 +(在实例化和清除PagePrivate之前),那么free_huge_page将增加全局预留计数。然而,预留映射 +显示报留被消耗了。这种不一致的状态将导致预留的巨页的 “泄漏” 。全局预留计数将比它原本的要高, +并阻止分配一个预先分配的页面。 + +函数 restore_reserve_on_error() 试图处理这种情况。它有相当完善的文档。这个函数的目的 +是将预留映射恢复到页面分配前的状态。通过这种方式,预留映射的状态将与页面释放后的全局预留计 +数相对应。 + +函数restore_reserve_on_error本身在试图恢复预留映射条目时可能会遇到错误。在这种情况下, +它将简单地清除该页的PagePrivate标志。这样一来,当页面被释放时,全局预留计数将不会被递增。 +然而,预留映射将继续看起来像预留被消耗了一样。一个页面仍然可以被分配到该地址,但它不会像最 +初设想的那样使用一个预留页。 + +有一些代码(最明显的是userfaultfd)不能调用restore_reserve_on_error。在这种情况下, +它简单地修改了PagePrivate,以便在释放巨页时不会泄露预留。 + + +预留和内存策略 +============== +当git第一次被用来管理Linux代码时,每个节点的巨页列表就存在于hstate结构中。预留的概念是 +在一段时间后加入的。当预留被添加时,没有尝试将内存策略考虑在内。虽然cpusets与内存策略不 +完全相同,但hugetlb_acct_memory中的这个注释总结了预留和cpusets/内存策略之间的相互作 +用:: + + + /* + * 当cpuset被配置时,它打破了严格的hugetlb页面预留,因为计数是在一个全局变量上完 + * 成的。在有cpuset的情况下,这样的预留完全是垃圾,因为预留没有根据当前cpuset的 + * 页面可用性来检查。在任务所在的cpuset中缺乏空闲的htlb页面时,应用程序仍然有可能 + * 被内核OOM'ed。试图用cpuset来执行严格的计数几乎是不可能的(或者说太难看了),因 + * 为cpuset太不稳定了,任务或内存节点可以在cpuset之间动态移动。与cpuset共享 + * hugetlb映射的语义变化是不可取的。然而,为了预留一些语义,我们退回到检查当前空闲 + * 页的可用性,作为一种最好的尝试,希望能将cpuset改变语义的影响降到最低。 + */ + +添加巨页预留是为了防止在缺页异常时出现意外的页面分配失败(OOM)。然而,如果一个应用 +程序使用cpusets或内存策略,就不能保证在所需的节点上有巨页可用。即使有足够数量的全局 +预留,也是如此。 + +Hugetlbfs回归测试 +================= + +最完整的hugetlb测试集在libhugetlbfs仓库。如果你修改了任何hugetlb相关的代码,请使用 +libhugetlbfs测试套件来检查回归情况。此外,如果你添加了任何新的hugetlb功能,请在 +libhugetlbfs中添加适当的测试。 + +-- +Mike Kravetz,2017年4月7日 diff --git a/Documentation/translations/zh_CN/mm/hwpoison.rst b/Documentation/translations/zh_CN/mm/hwpoison.rst new file mode 100644 index 000000000..310862edc --- /dev/null +++ b/Documentation/translations/zh_CN/mm/hwpoison.rst @@ -0,0 +1,166 @@ + +:Original: Documentation/mm/hwpoison.rst + +:翻译: + + 司延腾 Yanteng Si <siyanteng@loongson.cn> + +:校译: + + +======== +hwpoison +======== + +什么是hwpoison? +=============== + + +即将推出的英特尔CPU支持从一些内存错误中恢复( ``MCA恢复`` )。这需要操作系统宣布 +一个页面"poisoned",杀死与之相关的进程,并避免在未来使用它。 + +这个补丁包在虚拟机中实现了必要的(编程)框架。 + +引用概述中的评论:: + + 高级机器的检查与处理。处理方法是损坏的页面被硬件报告,通常是由于2位ECC内 + 存或高速缓存故障。 + + 这主要是针对在后台检测到的损坏的页面。当当前的CPU试图访问它时,当前运行的进程 + 可以直接被杀死。因为还没有访问损坏的页面, 如果错误由于某种原因不能被处理,就可 + 以安全地忽略它. 而不是用另外一个机器检查去处理它。 + + 处理不同状态的页面缓存页。这里棘手的部分是,相对于其他虚拟内存用户, 我们可以异 + 步访问任何页面。因为内存故障可能随时随地发生,可能违反了他们的一些假设。这就是 + 为什么这段代码必须非常小心。一般来说,它试图使用正常的锁规则,如获得标准锁,即使 + 这意味着错误处理可能需要很长的时间。 + + 这里的一些操作有点低效,并且具有非线性的算法复杂性,因为数据结构没有针对这种情 + 况进行优化。特别是从vma到进程的映射就是这种情况。由于这种情况大概率是罕见的,所 + 以我们希望我们可以摆脱这种情况。 + +该代码由mm/memory-failure.c中的高级处理程序、一个新的页面poison位和虚拟机中的 +各种检查组成,用来处理poison的页面。 + +现在主要目标是KVM客户机,但它适用于所有类型的应用程序。支持KVM需要最近的qemu-kvm +版本。 + +对于KVM的使用,需要一个新的信号类型,这样KVM就可以用适当的地址将机器检查注入到客户 +机中。这在理论上也允许其他应用程序处理内存故障。我们的期望是,所有的应用程序都不要这 +样做,但一些非常专业的应用程序可能会这样做。 + +故障恢复模式 +============ + +有两种(实际上是三种)模式的内存故障恢复可以在。 + +vm.memory_failure_recovery sysctl 置零: + 所有的内存故障都会导致panic。请不要尝试恢复。 + +早期处理 + (可以在全局和每个进程中控制) 一旦检测到错误,立即向应用程序发送SIGBUS这允许 + 应用程序以温和的方式处理内存错误(例如,放弃受影响的对象) 这是KVM qemu使用的 + 模式。 + +推迟处理 + 当应用程序运行到损坏的页面时,发送SIGBUS。这对不知道内存错误的应用程序来说是 + 最好的,默认情况下注意一些页面总是被当作late kill处理。 + +用户控制 +======== + +vm.memory_failure_recovery + 参阅 sysctl.txt + +vm.memory_failure_early_kill + 全局启用early kill + +PR_MCE_KILL + 设置early/late kill mode/revert 到系统默认值。 + + arg1: PR_MCE_KILL_CLEAR: + 恢复到系统默认值 + arg1: PR_MCE_KILL_SET: + arg2定义了线程特定模式 + + PR_MCE_KILL_EARLY: + Early kill + PR_MCE_KILL_LATE: + Late kill + PR_MCE_KILL_DEFAULT + 使用系统全局默认值 + + 注意,如果你想有一个专门的线程代表进程处理SIGBUS(BUS_MCEERR_AO),你应该在 + 指定线程上调用prctl(PR_MCE_KILL_EARLY)。否则,SIGBUS将被发送到主线程。 + +PR_MCE_KILL_GET + 返回当前模式 + +测试 +==== + +* madvise(MADV_HWPOISON, ....) (as root) - 在测试过程中Poison一个页面 + +* 通过debugfs ``/sys/kernel/debug/hwpoison/`` hwpoison-inject模块 + + corrupt-pfn + 在PFN处注入hwpoison故障,并echoed到这个文件。这做了一些早期过滤,以避 + 免在测试套件中损坏非预期页面。 + unpoison-pfn + 在PFN的Software-unpoison页面对应到这个文件。这样,一个页面可以再次被 + 复用。这只对Linux注入的故障起作用,对真正的内存故障不起作用。 + + 注意这些注入接口并不稳定,可能会在不同的内核版本中发生变化 + + corrupt-filter-dev-major, corrupt-filter-dev-minor + 只处理与块设备major/minor定义的文件系统相关的页面的内存故障。-1U是通 + 配符值。这应该只用于人工注入的测试。 + + corrupt-filter-memcg + 限制注入到memgroup拥有的页面。由memcg的inode号指定。 + + Example:: + + mkdir /sys/fs/cgroup/mem/hwpoison + + usemem -m 100 -s 1000 & + echo `jobs -p` > /sys/fs/cgroup/mem/hwpoison/tasks + + memcg_ino=$(ls -id /sys/fs/cgroup/mem/hwpoison | cut -f1 -d' ') + echo $memcg_ino > /debug/hwpoison/corrupt-filter-memcg + + page-types -p `pidof init` --hwpoison # shall do nothing + page-types -p `pidof usemem` --hwpoison # poison its pages + + corrupt-filter-flags-mask, corrupt-filter-flags-value + 当指定时,只有在((page_flags & mask) == value)的情况下才会poison页面。 + 这允许对许多种类的页面进行压力测试。page_flags与/proc/kpageflags中的相 + 同。这些标志位在include/linux/kernel-page-flags.h中定义,并在 + Documentation/admin-guide/mm/pagemap.rst中记录。 + +* 架构特定的MCE注入器 + + x86 有 mce-inject, mce-test + + 在mce-test中的一些便携式hwpoison测试程序,见下文。 + +引用 +==== + +http://halobates.de/mce-lc09-2.pdf + 09年LinuxCon的概述演讲 + +git://git.kernel.org/pub/scm/utils/cpu/mce/mce-test.git + 测试套件(在tsrc中的hwpoison特定可移植测试)。 + +git://git.kernel.org/pub/scm/utils/cpu/mce/mce-inject.git + x86特定的注入器 + + +限制 +==== +- 不是所有的页面类型都被支持,而且永远不会。大多数内核内部对象不能被恢 + 复,目前只有LRU页。 + +--- +Andi Kleen, 2009年10月 diff --git a/Documentation/translations/zh_CN/mm/index.rst b/Documentation/translations/zh_CN/mm/index.rst new file mode 100644 index 000000000..2f53e37b8 --- /dev/null +++ b/Documentation/translations/zh_CN/mm/index.rst @@ -0,0 +1,69 @@ +.. include:: ../disclaimer-zh_CN.rst + +:Original: Documentation/mm/index.rst + +:翻译: + + 司延腾 Yanteng Si <siyanteng@loongson.cn> + +:校译: + +================= +Linux内存管理文档 +================= + +这是一份关于了解Linux的内存管理子系统的指南。如果你正在寻找关于简单分配内存的 +建议,请参阅内存分配指南 +(Documentation/translations/zh_CN/core-api/memory-allocation.rst)。 +关于控制和调整的指南,请看管理指南 +(Documentation/translations/zh_CN/admin-guide/mm/index.rst)。 + + +.. toctree:: + :maxdepth: 1 + + highmem + +该处剩余文档待原始文档有内容后翻译。 + + +遗留文档 +======== + +这是一个关于Linux内存管理(MM)子系统内部的旧文档的集合,其中有不同层次的细节, +包括注释和邮件列表的回复,用于阐述数据结构和算法的描述。它应该被很好地整合到上述 +结构化的文档中,如果它已经完成了它的使命,可以删除。 + +.. toctree:: + :maxdepth: 1 + + active_mm + balance + damon/index + free_page_reporting + ksm + frontswap + hmm + hwpoison + hugetlbfs_reserv + memory-model + mmu_notifier + numa + overcommit-accounting + page_frags + page_migration + page_owner + page_table_check + remap_file_pages + split_page_table_lock + vmalloced-kernel-stacks + z3fold + zsmalloc + +TODOLIST: +* arch_pgtable_helpers +* free_page_reporting +* hugetlbfs_reserv +* slub +* transhuge +* unevictable-lru diff --git a/Documentation/translations/zh_CN/mm/ksm.rst b/Documentation/translations/zh_CN/mm/ksm.rst new file mode 100644 index 000000000..f0f458753 --- /dev/null +++ b/Documentation/translations/zh_CN/mm/ksm.rst @@ -0,0 +1,70 @@ +.. include:: ../disclaimer-zh_CN.rst + +:Original: Documentation/mm/ksm.rst + +:翻译: + + 徐鑫 xu xin <xu.xin16@zte.com.cn> + +============ +内核同页合并 +============ + +KSM 是一种节省内存的数据去重功能,由CONFIG_KSM=y启用,并在2.6.32版本时被添加 +到Linux内核。详见 ``mm/ksm.c`` 的实现,以及http://lwn.net/Articles/306704和 +https://lwn.net/Articles/330589 + +KSM的用户空间的接口在Documentation/translations/zh_CN/admin-guide/mm/ksm.rst +文档中有描述。 + +设计 +==== + +概述 +---- + +概述内容请见mm/ksm.c文档中的“DOC: Overview” + +逆映射 +------ +KSM维护着稳定树中的KSM页的逆映射信息。 + +当KSM页面的共享数小于 ``max_page_sharing`` 的虚拟内存区域(VMAs)时,则代表了 +KSM页的稳定树其中的节点指向了一个ksm_rmap_item结构体类型的列表。同时,这个KSM页 +的 ``page->mapping`` 指向了该稳定树节点。 + +如果共享数超过了阈值,KSM将给稳定树添加第二个维度。稳定树就变成链接一个或多 +个稳定树"副本"的"链"。每个副本都保留KSM页的逆映射信息,其中 ``page->mapping`` +指向该"副本"。 + +每个链以及链接到该链中的所有"副本"强制不变的是,它们代表了相同的写保护内存 +内容,尽管任中一个"副本"是由同一片内存区的不同的KSM复制页所指向的。 + +这样一来,相比与无限的逆映射链表,稳定树的查找计算复杂性不受影响。但在稳定树 +本身中不能有重复的KSM页面内容仍然是强制要求。 + +由 ``max_page_sharing`` 强制决定的数据去重限制是必要的,以此来避免虚拟内存 +rmap链表变得过大。rmap的遍历具有O(N)的复杂度,其中N是共享页面的rmap_项(即 +虚拟映射)的数量,而这个共享页面的节点数量又被 ``max_page_sharing`` 所限制。 +因此,这有效地将线性O(N)计算复杂度从rmap遍历中分散到不同的KSM页面上。ksmd进 +程在稳定节点"链"上的遍历也是O(N),但这个N是稳定树"副本"的数量,而不是rmap项 +的数量,因此它对ksmd性能没有显著影响。实际上,最佳稳定树"副本"的候选节点将 +保留在"副本"列表的开头。 + +``max_page_sharing`` 的值设置得高了会促使更快的内存合并(因为将有更少的稳定 +树副本排队进入稳定节点chain->hlist)和更高的数据去重系数,但代价是在交换、压 +缩、NUMA平衡和页面迁移过程中可能导致KSM页的最大rmap遍历速度较慢。 + +``stable_node_dups/stable_node_chains`` 的比值还受 ``max_page_sharing`` 调控 +的影响,高比值可能意味着稳定节点dup中存在碎片,这可以通过在ksmd中引入碎片算 +法来解决,该算法将rmap项从一个稳定节点dup重定位到另一个稳定节点dup,以便释放 +那些仅包含极少rmap项的稳定节点"dup",但这可能会增加ksmd进程的CPU使用率,并可 +能会减慢应用程序在KSM页面上的只读计算。 + +KSM会定期扫描稳定节点"链"中链接的所有稳定树"副本",以便删减过时了的稳定节点。 +这种扫描的频率由 ``stable_node_chains_prune_millisecs`` 这个sysfs 接口定义。 + +参考 +==== +内核代码请见mm/ksm.c。 +涉及的函数(mm_slot ksm_scan stable_node rmap_item)。 diff --git a/Documentation/translations/zh_CN/mm/memory-model.rst b/Documentation/translations/zh_CN/mm/memory-model.rst new file mode 100644 index 000000000..77ec149a9 --- /dev/null +++ b/Documentation/translations/zh_CN/mm/memory-model.rst @@ -0,0 +1,135 @@ +.. SPDX-License-Identifier: GPL-2.0 + +:Original: Documentation/mm/memory-model.rst + +:翻译: + + 司延腾 Yanteng Si <siyanteng@loongson.cn> + +:校译: + + +============ +物理内存模型 +============ + +系统中的物理内存可以用不同的方式进行寻址。最简单的情况是,物理内存从地址0开 +始,跨越一个连续的范围,直到最大的地址。然而,这个范围可能包含CPU无法访问的 +小孔隙。那么,在完全不同的地址可能有几个连续的范围。而且,别忘了NUMA,即不 +同的内存库连接到不同的CPU。 + +Linux使用两种内存模型中的一种对这种多样性进行抽象。FLATMEM和SPARSEM。每 +个架构都定义了它所支持的内存模型,默认的内存模型是什么,以及是否有可能手动 +覆盖该默认值。 + +所有的内存模型都使用排列在一个或多个数组中的 `struct page` 来跟踪物理页 +帧的状态。 + +无论选择哪种内存模型,物理页框号(PFN)和相应的 `struct page` 之间都存 +在一对一的映射关系。 + +每个内存模型都定义了 :c:func:`pfn_to_page` 和 :c:func:`page_to_pfn` +帮助函数,允许从PFN到 `struct page` 的转换,反之亦然。 + +FLATMEM +======= + +最简单的内存模型是FLATMEM。这个模型适用于非NUMA系统的连续或大部分连续的 +物理内存。 + +在FLATMEM内存模型中,有一个全局的 `mem_map` 数组来映射整个物理内存。对 +于大多数架构,孔隙在 `mem_map` 数组中都有条目。与孔洞相对应的 `struct page` +对象从未被完全初始化。 + +为了分配 `mem_map` 数组,架构特定的设置代码应该调用free_area_init()函数。 +然而,在调用memblock_free_all()函数之前,映射数组是不能使用的,该函数 +将所有的内存交给页分配器。 + +一个架构可能会释放 `mem_map` 数组中不包括实际物理页的部分。在这种情况下,特 +定架构的 :c:func:`pfn_valid` 实现应该考虑到 `mem_map` 中的孔隙。 + +使用FLATMEM,PFN和 `struct page` 之间的转换是直接的。 `PFN - ARCH_PFN_OFFSET` +是 `mem_map` 数组的一个索引。 + +`ARCH_PFN_OFFSET` 定义了物理内存起始地址不同于0的系统的第一个页框号。 + +SPARSEMEM +========= + +SPARSEMEM是Linux中最通用的内存模型,它是唯一支持若干高级功能的内存模型, +如物理内存的热插拔、非易失性内存设备的替代内存图和较大系统的内存图的延迟 +初始化。 + +SPARSEMEM模型将物理内存显示为一个部分的集合。一个区段用mem_section结构 +体表示,它包含 `section_mem_map` ,从逻辑上讲,它是一个指向 `struct page` +阵列的指针。然而,它被存储在一些其他的magic中,以帮助分区管理。区段的大小 +和最大区段数是使用 `SECTION_SIZE_BITS` 和 `MAX_PHYSMEM_BITS` 常量 +来指定的,这两个常量是由每个支持SPARSEMEM的架构定义的。 `MAX_PHYSMEM_BITS` +是一个架构所支持的物理地址的实际宽度,而 `SECTION_SIZE_BITS` 是一个任 +意的值。 + +最大的段数表示为 `NR_MEM_SECTIONS` ,定义为 + +.. math:: + + NR\_MEM\_SECTIONS = 2 ^ {(MAX\_PHYSMEM\_BITS - SECTION\_SIZE\_BITS)} + +`mem_section` 对象被安排在一个叫做 `mem_sections` 的二维数组中。这个数组的 +大小和位置取决于 `CONFIG_SPARSEM_EXTREME` 和可能的最大段数: + +* 当 `CONFIG_SPARSEMEM_EXTREME` 被禁用时, `mem_sections` 数组是静态的,有 + `NR_MEM_SECTIONS` 行。每一行持有一个 `mem_section` 对象。 +* 当 `CONFIG_SPARSEMEM_EXTREME` 被启用时, `mem_sections` 数组被动态分配。 + 每一行包含价值 `PAGE_SIZE` 的 `mem_section` 对象,行数的计算是为了适应所有的 + 内存区。 + +架构设置代码应该调用sparse_init()来初始化内存区和内存映射。 + +通过SPARSEMEM,有两种可能的方式将PFN转换为相应的 `struct page` --"classic sparse"和 + "sparse vmemmap"。选择是在构建时进行的,它由 `CONFIG_SPARSEMEM_VMEMMAP` 的 + 值决定。 + +Classic sparse在page->flags中编码了一个页面的段号,并使用PFN的高位来访问映射该页 +框的段。在一个区段内,PFN是指向页数组的索引。 + +Sparse vmemmapvmemmap使用虚拟映射的内存映射来优化pfn_to_page和page_to_pfn操 +作。有一个全局的 `struct page *vmemmap` 指针,指向一个虚拟连续的 `struct page` +对象阵列。PFN是该数组的一个索引,`struct page` 从 `vmemmap` 的偏移量是该页的PFN。 + +为了使用vmemmap,一个架构必须保留一个虚拟地址的范围,以映射包含内存映射的物理页,并 +确保 `vmemmap`指向该范围。此外,架构应该实现 :c:func:`vmemmap_populate` 方法, +它将分配物理内存并为虚拟内存映射创建页表。如果一个架构对vmemmap映射没有任何特殊要求, +它可以使用通用内存管理提供的默认 :c:func:`vmemmap_populate_basepages`。 + +虚拟映射的内存映射允许将持久性内存设备的 `struct page` 对象存储在这些设备上预先分 +配的存储中。这种存储用vmem_altmap结构表示,最终通过一长串的函数调用传递给 +vmemmap_populate()。vmemmap_populate()实现可以使用 `vmem_altmap` 和 +:c:func:`vmemmap_alloc_block_buf` 助手来分配持久性内存设备上的内存映射。 + +ZONE_DEVICE +=========== +`ZONE_DEVICE` 设施建立在 `SPARSEM_VMEMMAP` 之上,为设备驱动识别的物理地址范 +围提供 `struct page` `mem_map` 服务。 `ZONE_DEVICE` 的 "设备" 方面与以下 +事实有关:这些地址范围的页面对象从未被在线标记过,而且必须对设备进行引用,而不仅仅 +是页面,以保持内存被“锁定”以便使用。 `ZONE_DEVICE` ,通过 :c:func:`devm_memremap_pages` , +为给定的pfns范围执行足够的内存热插拔来开启 :c:func:`pfn_to_page`, +:c:func:`page_to_pfn`, ,和 :c:func:`get_user_pages` 服务。由于页面引 +用计数永远不会低于1,所以页面永远不会被追踪为空闲内存,页面的 `struct list_head lru` +空间被重新利用,用于向映射该内存的主机设备/驱动程序进行反向引用。 + +虽然 `SPARSEMEM` 将内存作为一个区段的集合,可以选择收集并合成内存块,但 +`ZONE_DEVICE` 用户需要更小的颗粒度来填充 `mem_map` 。鉴于 `ZONE_DEVICE` +内存从未被在线标记,因此它的内存范围从未通过sysfs内存热插拔api暴露在内存块边界 +上。这个实现依赖于这种缺乏用户接口的约束,允许子段大小的内存范围被指定给 +:c:func:`arch_add_memory` ,即内存热插拔的上半部分。子段支持允许2MB作为 +:c:func:`devm_memremap_pages` 的跨架构通用对齐颗粒度。 + +`ZONE_DEVICE` 的用户是: + +* pmem: 通过DAX映射将平台持久性内存作为直接I/O目标使用。 + +* hmm: 用 `->page_fault()` 和 `->page_free()` 事件回调扩展 `ZONE_DEVICE` , + 以允许设备驱动程序协调与设备内存相关的内存管理事件,通常是GPU内存。参见Documentation/mm/hmm.rst。 + +* p2pdma: 创建 `struct page` 对象,允许PCI/E拓扑结构中的peer设备协调它们之间的 + 直接DMA操作,即绕过主机内存。 diff --git a/Documentation/translations/zh_CN/mm/mmu_notifier.rst b/Documentation/translations/zh_CN/mm/mmu_notifier.rst new file mode 100644 index 000000000..ce3664d1a --- /dev/null +++ b/Documentation/translations/zh_CN/mm/mmu_notifier.rst @@ -0,0 +1,97 @@ +:Original: Documentation/mm/mmu_notifier.rst + +:翻译: + + 司延腾 Yanteng Si <siyanteng@loongson.cn> + +:校译: + + + +什么时候需要页表锁内通知? +========================== + +当清除一个pte/pmd时,我们可以选择通过在页表锁下(通知版的\*_clear_flush调用 +mmu_notifier_invalidate_range)通知事件。但这种通知并不是在所有情况下都需要的。 + +对于二级TLB(非CPU TLB),如IOMMU TLB或设备TLB(当设备使用类似ATS/PASID的东西让 +IOMMU走CPU页表来访问进程的虚拟地址空间)。只有两种情况需要在清除pte/pmd时在持有页 +表锁的同时通知这些二级TLB: + + A) 在mmu_notifier_invalidate_range_end()之前,支持页的地址被释放。 + B) 一个页表项被更新以指向一个新的页面(COW,零页上的写异常,__replace_page(),...)。 + +情况A很明显,你不想冒风险让设备写到一个现在可能被一些完全不同的任务使用的页面。 + +情况B更加微妙。为了正确起见,它需要按照以下序列发生: + + - 上页表锁 + - 清除页表项并通知 ([pmd/pte]p_huge_clear_flush_notify()) + - 设置页表项以指向新页 + +如果在设置新的pte/pmd值之前,清除页表项之后没有进行通知,那么你就会破坏设备的C11或 +C++11等内存模型。 + +考虑以下情况(设备使用类似于ATS/PASID的功能)。 + +两个地址addrA和addrB,这样|addrA - addrB| >= PAGE_SIZE,我们假设它们是COW的 +写保护(B的其他情况也适用)。 + +:: + + [Time N] -------------------------------------------------------------------- + CPU-thread-0 {尝试写到addrA} + CPU-thread-1 {尝试写到addrB} + CPU-thread-2 {} + CPU-thread-3 {} + DEV-thread-0 {读取addrA并填充设备TLB} + DEV-thread-2 {读取addrB并填充设备TLB} + [Time N+1] ------------------------------------------------------------------ + CPU-thread-0 {COW_step0: {mmu_notifier_invalidate_range_start(addrA)}} + CPU-thread-1 {COW_step0: {mmu_notifier_invalidate_range_start(addrB)}} + CPU-thread-2 {} + CPU-thread-3 {} + DEV-thread-0 {} + DEV-thread-2 {} + [Time N+2] ------------------------------------------------------------------ + CPU-thread-0 {COW_step1: {更新页表以指向addrA的新页}} + CPU-thread-1 {COW_step1: {更新页表以指向addrB的新页}} + CPU-thread-2 {} + CPU-thread-3 {} + DEV-thread-0 {} + DEV-thread-2 {} + [Time N+3] ------------------------------------------------------------------ + CPU-thread-0 {preempted} + CPU-thread-1 {preempted} + CPU-thread-2 {写入addrA,这是对新页面的写入} + CPU-thread-3 {} + DEV-thread-0 {} + DEV-thread-2 {} + [Time N+3] ------------------------------------------------------------------ + CPU-thread-0 {preempted} + CPU-thread-1 {preempted} + CPU-thread-2 {} + CPU-thread-3 {写入addrB,这是一个写入新页的过程} + DEV-thread-0 {} + DEV-thread-2 {} + [Time N+4] ------------------------------------------------------------------ + CPU-thread-0 {preempted} + CPU-thread-1 {COW_step3: {mmu_notifier_invalidate_range_end(addrB)}} + CPU-thread-2 {} + CPU-thread-3 {} + DEV-thread-0 {} + DEV-thread-2 {} + [Time N+5] ------------------------------------------------------------------ + CPU-thread-0 {preempted} + CPU-thread-1 {} + CPU-thread-2 {} + CPU-thread-3 {} + DEV-thread-0 {从旧页中读取addrA} + DEV-thread-2 {从新页面读取addrB} + +所以在这里,因为在N+2的时候,清空页表项没有和通知一起作废二级TLB,设备在看到addrA的新值之前 +就看到了addrB的新值。这就破坏了设备的总内存序。 + +当改变一个pte的写保护或指向一个新的具有相同内容的写保护页(KSM)时,将mmu_notifier_invalidate_range +调用延迟到页表锁外的mmu_notifier_invalidate_range_end()是可以的。即使做页表更新的线程 +在释放页表锁后但在调用mmu_notifier_invalidate_range_end()前被抢占,也是如此。 diff --git a/Documentation/translations/zh_CN/mm/numa.rst b/Documentation/translations/zh_CN/mm/numa.rst new file mode 100644 index 000000000..b15cfeeb6 --- /dev/null +++ b/Documentation/translations/zh_CN/mm/numa.rst @@ -0,0 +1,101 @@ +:Original: Documentation/mm/numa.rst + +:翻译: + + 司延腾 Yanteng Si <siyanteng@loongson.cn> + +:校译: + + +始于1999年11月,作者: <kanoj@sgi.com> + +========================== +何为非统一内存访问(NUMA)? +========================== + +这个问题可以从几个视角来回答:硬件观点和Linux软件视角。 + +从硬件角度看,NUMA系统是一个由多个组件或装配组成的计算机平台,每个组件可能包含0个或更多的CPU、 +本地内存和/或IO总线。为了简洁起见,并将这些物理组件/装配的硬件视角与软件抽象区分开来,我们在 +本文中称这些组件/装配为“单元”。 + +每个“单元”都可以看作是系统的一个SMP[对称多处理器]子集——尽管独立的SMP系统所需的一些组件可能 +不会在任何给定的单元上填充。NUMA系统的单元通过某种系统互连连接在一起——例如,交叉开关或点对点 +链接是NUMA系统互连的常见类型。这两种类型的互连都可以聚合起来,以创建NUMA平台,其中的单元与其 +他单元有多个距离。 + +对于Linux,感兴趣的NUMA平台主要是所谓的缓存相干NUMA--简称ccNUMA系统系统。在ccNUMA系统中, +所有的内存都是可见的,并且可以从连接到任何单元的任何CPU中访问,缓存一致性是由处理器缓存和/或 +系统互连在硬件中处理。 + +内存访问时间和有效的内存带宽取决于包含CPU的单元或进行内存访问的IO总线距离包含目标内存的单元 +有多远。例如,连接到同一单元的CPU对内存的访问将比访问其他远程单元的内存经历更快的访问时间和 +更高的带宽。 NUMA平台可以在任何给定单元上访问多种远程距离的(其他)单元。 + +平台供应商建立NUMA系统并不只是为了让软件开发人员的生活变得有趣。相反,这种架构是提供可扩展 +内存带宽的一种手段。然而,为了实现可扩展的内存带宽,系统和应用软件必须安排大部分的内存引用 +[cache misses]到“本地”内存——同一单元的内存,如果有的话——或者到最近的有内存的单元。 + +这就自然而然有了Linux软件对NUMA系统的视角: + +Linux将系统的硬件资源划分为多个软件抽象,称为“节点”。Linux将节点映射到硬件平台的物理单元 +上,对一些架构的细节进行了抽象。与物理单元一样,软件节点可能包含0或更多的CPU、内存和/或IO +总线。同样,对“较近”节点的内存访问——映射到较近单元的节点——通常会比对较远单元的访问经历更快 +的访问时间和更高的有效带宽。 + +对于一些架构,如x86,Linux将“隐藏”任何代表没有内存连接的物理单元的节点,并将连接到该单元 +的任何CPU重新分配到代表有内存的单元的节点上。因此,在这些架构上,我们不能假设Linux将所有 +的CPU与一个给定的节点相关联,会看到相同的本地内存访问时间和带宽。 + +此外,对于某些架构,同样以x86为例,Linux支持对额外节点的仿真。对于NUMA仿真,Linux会将现 +有的节点或者非NUMA平台的系统内存分割成多个节点。每个模拟的节点将管理底层单元物理内存的一部 +分。NUMA仿真对于在非NUMA平台上测试NUMA内核和应用功能是非常有用的,当与cpusets一起使用时, +可以作为一种内存资源管理机制。[见 Documentation/admin-guide/cgroup-v1/cpusets.rst] + +对于每个有内存的节点,Linux构建了一个独立的内存管理子系统,有自己的空闲页列表、使用中页列表、 +使用统计和锁来调解访问。此外,Linux为每个内存区[DMA、DMA32、NORMAL、HIGH_MEMORY、MOVABLE +中的一个或多个]构建了一个有序的“区列表”。zonelist指定了当一个选定的区/节点不能满足分配请求 +时要访问的区/节点。当一个区没有可用的内存来满足请求时,这种情况被称为“overflow 溢出”或 +“fallback 回退”。 + +由于一些节点包含多个包含不同类型内存的区,Linux必须决定是否对区列表进行排序,使分配回退到不同 +节点上的相同区类型,或同一节点上的不同区类型。这是一个重要的考虑因素,因为有些区,如DMA或DMA32, +代表了相对稀缺的资源。Linux选择了一个默认的Node ordered zonelist。这意味着在使用按NUMA距 +离排序的远程节点之前,它会尝试回退到同一节点的其他分区。 + +默认情况下,Linux会尝试从执行请求的CPU被分配到的节点中满足内存分配请求。具体来说,Linux将试 +图从请求来源的节点的适当分区列表中的第一个节点进行分配。这被称为“本地分配”。如果“本地”节点不能 +满足请求,内核将检查所选分区列表中其他节点的区域,寻找列表中第一个能满足请求的区域。 + +本地分配将倾向于保持对分配的内存的后续访问 “本地”的底层物理资源和系统互连——只要内核代表其分配 +一些内存的任务后来不从该内存迁移。Linux调度器知道平台的NUMA拓扑结构——体现在“调度域”数据结构 +中[见 Documentation/scheduler/sched-domains.rst]——并且调度器试图尽量减少任务迁移到遥 +远的调度域中。然而,调度器并没有直接考虑到任务的NUMA足迹。因此,在充分不平衡的情况下,任务可 +以在节点之间迁移,远离其初始节点和内核数据结构。 + +系统管理员和应用程序设计者可以使用各种CPU亲和命令行接口,如taskset(1)和numactl(1),以及程 +序接口,如sched_setaffinity(2),来限制任务的迁移,以改善NUMA定位。此外,人们可以使用 +Linux NUMA内存策略修改内核的默认本地分配行为。 [见 +:ref:`Documentation/admin-guide/mm/numa_memory_policy.rst <numa_memory_policy>`]. + +系统管理员可以使用控制组和CPUsets限制非特权用户在调度或NUMA命令和功能中可以指定的CPU和节点 +的内存。 [见 Documentation/admin-guide/cgroup-v1/cpusets.rst] + +在不隐藏无内存节点的架构上,Linux会在分区列表中只包括有内存的区域[节点]。这意味着对于一个无 +内存的节点,“本地内存节点”——CPU节点的分区列表中的第一个区域的节点——将不是节点本身。相反,它 +将是内核在建立分区列表时选择的离它最近的有内存的节点。所以,默认情况下,本地分配将由内核提供 +最近的可用内存来完成。这是同一机制的结果,该机制允许这种分配在一个包含内存的节点溢出时回退到 +其他附近的节点。 + +一些内核分配不希望或不能容忍这种分配回退行为。相反,他们想确保他们从指定的节点获得内存,或者 +得到通知说该节点没有空闲内存。例如,当一个子系统分配每个CPU的内存资源时,通常是这种情况。 + +一个典型的分配模式是使用内核的numa_node_id()或CPU_to_node()函数获得“当前CPU”所在节点的 +节点ID,然后只从返回的节点ID请求内存。当这样的分配失败时,请求的子系统可以恢复到它自己的回退 +路径。板块内核内存分配器就是这样的一个例子。或者,子系统可以选择在分配失败时禁用或不启用自己。 +内核分析子系统就是这样的一个例子。 + +如果架构支持——不隐藏无内存节点,那么连接到无内存节点的CPU将总是产生回退路径的开销,或者一些 +子系统如果试图完全从无内存的节点分配内存,将无法初始化。为了透明地支持这种架构,内核子系统可 +以使用numa_mem_id()或cpu_to_mem()函数来定位调用或指定CPU的“本地内存节点”。同样,这是同 +一个节点,默认的本地页分配将从这个节点开始尝试。 diff --git a/Documentation/translations/zh_CN/mm/overcommit-accounting.rst b/Documentation/translations/zh_CN/mm/overcommit-accounting.rst new file mode 100644 index 000000000..d8452d8b7 --- /dev/null +++ b/Documentation/translations/zh_CN/mm/overcommit-accounting.rst @@ -0,0 +1,86 @@ +:Original: Documentation/mm/overcommit-accounting.rst + +:翻译: + + 司延腾 Yanteng Si <siyanteng@loongson.cn> + +:校译: + + + +============== +超量使用审计 +============== + +Linux内核支持下列超量使用处理模式 + +0 + 启发式超量使用处理。拒绝明显的地址空间超量使用。用于一个典型的系统。 + 它确保严重的疯狂分配失败,同时允许超量使用以减少swap的使用。在这种模式下, + 允许root分配稍多的内存。这是默认的。 +1 + 总是超量使用。适用于一些科学应用。经典的例子是使用稀疏数组的代码,只是依赖 + 几乎完全由零页组成的虚拟内存 + +2 + 不超量使用。系统提交的总地址空间不允许超过swap+一个可配置的物理RAM的数量 + (默认为50%)。根据你使用的数量,在大多数情况下,这意味着一个进程在访问页面时 + 不会被杀死,但会在内存分配上收到相应的错误。 + + 对于那些想保证他们的内存分配在未来可用而又不需要初始化每一个页面的应用程序来说 + 是很有用的。 + +超量使用策略是通过sysctl `vm.overcommit_memory` 设置的。 + +可以通过 `vm.overcommit_ratio` (百分比)或 `vm.overcommit_kbytes` (绝对值) +来设置超限数量。这些只有在 `vm.overcommit_memory` 被设置为2时才有效果。 + +在 ``/proc/meminfo`` 中可以分别以CommitLimit和Committed_AS的形式查看当前 +的超量使用和提交量。 + +陷阱 +==== + +C语言的堆栈增长是一个隐含的mremap。如果你想得到绝对的保证,并在接近边缘的地方运行, +你 **必须** 为你认为你需要的最大尺寸的堆栈进行mmap。对于典型的堆栈使用来说,这并 +不重要,但如果你真的非常关心的话,这就是一个值得关注的案例。 + + +在模式2中,MAP_NORESERVE标志被忽略。 + + +它是如何工作的 +============== + +超量使用是基于以下规则 + +对于文件映射 + | SHARED or READ-only - 0 cost (该文件是映射而不是交换) + | PRIVATE WRITABLE - 每个实例的映射大小 + +对于匿名或者 ``/dev/zero`` 映射 + | SHARED - 映射的大小 + | PRIVATE READ-only - 0 cost (但作用不大) + | PRIVATE WRITABLE - 每个实例的映射大小 + +额外的计数 + | 通过mmap制作可写副本的页面 + | 从同一池中提取的shmfs内存 + +状态 +==== + +* 我们核算mmap内存映射 +* 我们核算mprotect在提交中的变化 +* 我们核算mremap的大小变化 +* 我们的审计 brk +* 审计munmap +* 我们在/proc中报告commit 状态 +* 核对并检查分叉的情况 +* 审查堆栈处理/执行中的构建 +* 叙述SHMfs的情况 +* 实现实际限制的执行 + +待续 +==== +* ptrace 页计数(这很难)。 diff --git a/Documentation/translations/zh_CN/mm/page_frags.rst b/Documentation/translations/zh_CN/mm/page_frags.rst new file mode 100644 index 000000000..20bd3fafd --- /dev/null +++ b/Documentation/translations/zh_CN/mm/page_frags.rst @@ -0,0 +1,38 @@ +:Original: Documentation/mm/page_frags.rst + +:翻译: + + 司延腾 Yanteng Si <siyanteng@loongson.cn> + +:校译: + + +======== +页面片段 +======== + +一个页面片段是一个任意长度的任意偏移的内存区域,它位于一个0或更高阶的复合页面中。 +该页中的多个碎片在该页的引用计数器中被单独计算。 + +page_frag函数,page_frag_alloc和page_frag_free,为页面片段提供了一个简单 +的分配框架。这被网络堆栈和网络设备驱动使用,以提供一个内存的支持区域,作为 +sk_buff->head使用,或者用于skb_shared_info的 “frags” 部分。 + +为了使用页面片段API,需要一个支持页面片段的缓冲区。这为碎片分配提供了一个中心点, +并允许多个调用使用一个缓存的页面。这样做的好处是可以避免对get_page的多次调用, +这在分配时开销可能会很大。然而,由于这种缓存的性质,要求任何对缓存的调用都要受到每 +个CPU的限制,或者每个CPU的限制,并在执行碎片分配时强制禁止中断。 + +网络堆栈在每个CPU使用两个独立的缓存来处理碎片分配。netdev_alloc_cache被使用 +netdev_alloc_frag和__netdev_alloc_skb调用的调用者使用。napi_alloc_cache +被调用__napi_alloc_frag和__napi_alloc_skb的调用者使用。这两个调用的主要区别是 +它们可能被调用的环境。“netdev” 前缀的函数可以在任何上下文中使用,因为这些函数 +将禁用中断,而 ”napi“ 前缀的函数只可以在softirq上下文中使用。 + +许多网络设备驱动程序使用类似的方法来分配页面片段,但页面片段是在环或描述符级别上 +缓存的。为了实现这些情况,有必要提供一种拆解页面缓存的通用方法。出于这个原因, +__page_frag_cache_drain被实现了。它允许通过一次调用从一个页面释放多个引用。 +这样做的好处是,它允许清理被添加到一个页面的多个引用,以避免每次分配都调用 +get_page。 + +Alexander Duyck,2016年11月29日。 diff --git a/Documentation/translations/zh_CN/mm/page_migration.rst b/Documentation/translations/zh_CN/mm/page_migration.rst new file mode 100644 index 000000000..076081dc1 --- /dev/null +++ b/Documentation/translations/zh_CN/mm/page_migration.rst @@ -0,0 +1,228 @@ +.. include:: ../disclaimer-zh_CN.rst + +:Original: Documentation/mm/page_migration.rst + +:翻译: + + 司延腾 Yanteng Si <siyanteng@loongson.cn> + +:校译: + +======== +页面迁移 +======== + +页面迁移允许在进程运行时在NUMA系统的节点之间移动页面的物理位置。这意味着进程所看到的虚拟地 +址并没有改变。然而,系统会重新安排这些页面的物理位置。 + +也可以参见 :ref: `<异构内存管理 (HMM)>` 以了解将页面迁移到设备私有内存或从设备私有内存中迁移。 + +页面迁移的主要目的是通过将页面移到访问该内存的进程所运行的处理器附近来减少内存访问的延迟。 + +页面迁移允许进程通过MF_MOVE和MF_MOVE_ALL选项手动重新定位其页面所在的节点,同时通过 +mbind()设置一个新的内存策略。一个进程的页面也可以通过sys_migrate_pages()函数调用从另 +一个进程重新定位。migrate_pages()函数调用接收两组节点,并将一个进程位于旧节点上的页面移 +动到目标节点上。页面迁移功能由Andi Kleen的numactl包提供(需要0.9.3以上的版本,其仓库 +地址https://github.com/numactl/numactl.git)。numactl提供了libnuma,它为页面迁移 +提供了与其他NUMA功能类似的接口。执行 cat ``/proc/<pid>/numa_maps`` 允许轻松查看进 +程的页面位置。参见proc(5)手册中的numa_maps文档。 + +如果调度程序将一个进程重新安置到一个遥远的节点上的处理器,手动迁移是很有用的。批量调度程序 +或管理员可以检测到这种情况,并将进程的页面移到新处理器附近。内核本身只提供手动的页迁移支持。 +自动的页面迁移可以通过用户空间的进程移动页面来实现。一个特殊的函数调用 "move_pages" 允许 +在一个进程中移动单个页面。例如,NUMA分析器可以获得一个显示频繁的节点外访问的日志,并可以使 +用这个结果将页面移动到更有利的位置。 + +较大型的设备通常使用cpusets将系统分割成若干个节点。Paul Jackson为cpusets配备了当任务被 +转移到另一个cpuset时移动页面的能力(见:ref:`CPUSETS <cpusets>`)。Cpusets允许进程定 +位的自动化。如果一个任务被移到一个新的cpuset上,那么它的所有页面也会随之移动,这样进程的 +性能就不会急剧下降。如果cpuset允许的内存节点发生变化,cpuset中的进程页也会被移动。 + +页面迁移允许为所有迁移技术保留一组节点中页面的相对位置,这将保留生成的特定内存分配模式即使 +进程已被迁移。为了保留内存延迟,这一点是必要的。迁移后的进程将以类似的性能运行。 + +页面迁移分几个步骤进行。首先为那些试图从内核中使用migrate_pages()的进程做一个高层次的 +描述(对于用户空间的使用,可以参考上面提到的Andi Kleen的numactl包),然后对低水平的细 +节工作做一个低水平描述。 + +在内核中使用 migrate_pages() +============================ + +1. 从LRU中移除页面。 + + 要迁移的页面列表是通过扫描页面并把它们移到列表中来生成的。这是通过调用 isolate_lru_page() + 来完成的。调用isolate_lru_page()增加了对该页的引用,这样在页面迁移发生时它就不会 + 消失。它还可以防止交换器或其他扫描器遇到该页。 + + +2. 我们需要有一个new_page_t类型的函数,可以传递给migrate_pages()。这个函数应该计算 + 出如何在给定的旧页面中分配正确的新页面。 + +3. migrate_pages()函数被调用,它试图进行迁移。它将调用该函数为每个被考虑迁移的页面分 + 配新的页面。 + +migrate_pages()如何工作 +======================= + +migrate_pages()对它的页面列表进行了多次处理。如果当时对一个页面的所有引用都可以被移除, +那么这个页面就会被移动。该页已经通过isolate_lru_page()从LRU中移除,并且refcount被 +增加,以便在页面迁移发生时不释放该页。 + +步骤: + +1. 锁定要迁移的页面。 + +2. 确保回写已经完成。 + +3. 锁定我们要迁移到的新页面。锁定它是为了在迁移过程中立即阻止对这个(尚未更新的)页面的 + 访问。 + +4. 所有对该页的页表引用都被转换为迁移条目。这就减少了一个页面的mapcount。如果产生的 + mapcount不是零,那么我们就不迁移该页。所有试图访问该页的用户空间进程现在将等待页 + 面锁或者等待迁移页表项被移除。 + +5. i_pages的锁被持有。这将导致所有试图通过映射访问该页的进程在自旋锁上阻塞。 + +6. 检查该页的Refcount,如果还有引用,我们就退出。否则,我们知道我们是唯一引用这个页 + 面的人。 + +7. 检查基数树,如果它不包含指向这个页面的指针,那么我们就退出,因为其他人修改了基数树。 + +8. 新的页面要用旧的页面的一些设置进行预处理,这样访问新的页面就会发现一个具有正确设置 + 的页面。 + +9. 基数树被改变以指向新的页面。 + +10. 旧页的引用计数被删除,因为地址空间的引用已经消失。对新页的引用被建立,因为新页被 + 地址空间引用。 + +11. i_pages锁被放弃。这样一来,在映射中的查找又变得可能了。进程将从在锁上自旋到在 + 被锁的新页上睡眠。 + +12. 页面内容被复制到新的页面上。 + +13. 剩余的页面标志被复制到新的页面上。 + +14. 旧的页面标志被清除,以表明该页面不再提供任何信息。 + +15. 新页面上的回写队列被触发了。 + +16. 如果迁移条目被插入到页表中,那么就用真正的ptes替换它们。这样做将使那些尚未等待页 + 锁的用户空间进程能够访问。 + +17. 页面锁从新旧页面上被撤销。等待页锁的进程将重做他们的缺页异常,并将到达新的页面。 + +18. 新的页面被移到LRU中,可以被交换器等再次扫描。 + +非LRU页面迁移 +============= + +尽管迁移最初的目的是为了减少NUMA的内存访问延迟,但压缩也使用迁移来创建高阶页面。 + +目前实现的问题是,它被设计为只迁移*LRU*页。然而,有一些潜在的非LRU页面可以在驱动中 +被迁移,例如,zsmalloc,virtio-balloon页面。 + +对于virtio-balloon页面,迁移代码路径的某些部分已经被钩住,并添加了virtio-balloon +的特定函数来拦截迁移逻辑。这对一个驱动来说太特殊了,所以其他想让自己的页面可移动的驱 +动就必须在迁移路径中添加自己的特定钩子。 + +为了克服这个问题,VM支持非LRU页面迁移,它为非LRU可移动页面提供了通用函数,而在迁移 +路径中没有特定的驱动程序钩子。 + +如果一个驱动程序想让它的页面可移动,它应该定义三个函数,这些函数是 +struct address_space_operations的函数指针。 + +1. ``bool (*isolate_page) (struct page *page, isolate_mode_t mode);`` + + VM对驱动的isolate_page()函数的期望是,如果驱动成功隔离了该页,则返回*true*。 + 返回true后,VM会将该页标记为PG_isolated,这样多个CPU的并发隔离就会跳过该 + 页进行隔离。如果驱动程序不能隔离该页,它应该返回*false*。 + + 一旦页面被成功隔离,VM就会使用page.lru字段,因此驱动程序不应期望保留这些字段的值。 + +2. ``int (*migratepage) (struct address_space *mapping,`` +| ``struct page *newpage, struct page *oldpage, enum migrate_mode);`` + + 隔离后,虚拟机用隔离的页面调用驱动的migratepage()。migratepage()的功能是将旧页 + 的内容移动到新页,并设置struct page newpage的字段。请记住,如果你成功迁移了旧页 + 并返回MIGRATEPAGE_SUCCESS,你应该通过page_lock下的__ClearPageMovable()向虚 + 拟机表明旧页不再可移动。如果驱动暂时不能迁移该页,驱动可以返回-EAGAIN。在-EAGAIN + 时,VM会在短时间内重试页面迁移,因为VM将-EAGAIN理解为 "临时迁移失败"。在返回除 + -EAGAIN以外的任何错误时,VM将放弃页面迁移而不重试。 + + 在migratepage()函数中,驱动程序不应该接触page.lru字段。 + +3. ``void (*putback_page)(struct page *);`` + + 如果在隔离页上迁移失败,VM应该将隔离页返回给驱动,因此VM用隔离页调用驱动的 + putback_page()。在这个函数中,驱动应该把隔离页放回自己的数据结构中。 + +非LRU可移动页标志 + + 有两个页面标志用于支持非LRU可移动页面。 + + * PG_movable + + 驱动应该使用下面的函数来使页面在page_lock下可移动。:: + + void __SetPageMovable(struct page *page, struct address_space *mapping) + + 它需要address_space的参数来注册将被VM调用的migration family函数。确切地说, + PG_movable不是struct page的一个真正的标志。相反,VM复用了page->mapping的低 + 位来表示它:: + + #define PAGE_MAPPING_MOVABLE 0x2 + page->mapping = page->mapping | PAGE_MAPPING_MOVABLE; + + 所以驱动不应该直接访问page->mapping。相反,驱动应该使用page_mapping(),它可 + 以在页面锁下屏蔽掉page->mapping的低2位,从而获得正确的struct address_space。 + + 对于非LRU可移动页面的测试,VM支持__PageMovable()函数。然而,它并不能保证识别 + 非LRU可移动页面,因为page->mapping字段与struct page中的其他变量是统一的。如 + 果驱动程序在被虚拟机隔离后释放了页面,尽管page->mapping设置了PAGE_MAPPING_MOVABLE, + 但它并没有一个稳定的值(看看__ClearPageMovable)。但是__PageMovable()在页 + 面被隔离后,无论页面是LRU还是非LRU可移动的,调用它开销都很低,因为LRU页面在 + page->mapping中不可能有PAGE_MAPPING_MOVABLE设置。在用pfn扫描中的lock_page() + 进行更大开销的检查来选择受害者之前,它也很适合只是瞥一眼来测试非LRU可移动的页面。 + + 为了保证非LRU的可移动页面,VM提供了PageMovable()函数。与__PageMovable()不 + 同,PageMovable()在lock_page()下验证page->mapping和 + mapping->a_ops->isolate_page。lock_page()可以防止突然破坏page->mapping。 + + 使用__SetPageMovable()的驱动应该在释放页面之前通过page_lock()下的 + __ClearMovablePage()清除该标志。 + + * PG_isolated + + 为了防止几个CPU同时进行隔离,VM在lock_page()下将隔离的页面标记为PG_isolated。 + 因此,如果一个CPU遇到PG_isolated非LRU可移动页面,它可以跳过它。驱动程序不需要 + 操作这个标志,因为VM会自动设置/清除它。请记住,如果驱动程序看到PG_isolated页, + 这意味着该页已经被VM隔离,所以它不应该碰page.lru字段。PG_isolated标志与 + PG_reclaim标志是同义的,所以驱动程序不应该为自己的目的使用PG_isolated。 + +监测迁移 +======== + +以下事件(计数器)可用于监控页面迁移。 + +1. PGMIGRATE_SUCCESS: 正常的页面迁移成功。每个计数器意味着一个页面被迁移了。如果该 + 页是一个非THP和非hugetlb页,那么这个计数器会增加1。如果该页面是一个THP或hugetlb + 页面,那么这个计数器会随着THP或hugetlb子页面的数量而增加。例如,迁移一个有4KB大小 + 的基础页(子页)的2MB THP,将导致这个计数器增加512。 + +2. PGMIGRATE_FAIL: 正常的页面迁移失败。与上面PGMIGRATE_SUCCESS的计数规则相同:如 + 果是THP或hugetlb,这个计数将被子页的数量增加。 + +3. THP_MIGRATION_SUCCESS: 一个THP被迁移而没有被分割。 + +4. THP_MIGRATION_FAIL: 一个THP不能被迁移,也不能被分割。 + +5. THP_MIGRATION_SPLIT: 一个THP被迁移了,但不是这样的:首先,这个THP必须被分割。 + 在拆分之后,对它的子页面进行了迁移重试。 + +THP_MIGRATION_* 事件也会更新相应的PGMIGRATE_SUCCESS或PGMIGRATE_FAIL事件。 +例如,一个THP迁移失败将导致THP_MIGRATION_FAIL和PGMIGRATE_FAIL增加。 + +Christoph Lameter,2006年5月8日。 + +Minchan Kim,2016年3月28日。 diff --git a/Documentation/translations/zh_CN/mm/page_owner.rst b/Documentation/translations/zh_CN/mm/page_owner.rst new file mode 100644 index 000000000..21a6a0837 --- /dev/null +++ b/Documentation/translations/zh_CN/mm/page_owner.rst @@ -0,0 +1,181 @@ +:Original: Documentation/mm/page_owner.rst + +:翻译: + + 司延腾 Yanteng Si <siyanteng@loongson.cn> + +:校译: + + +================================ +page owner: 跟踪谁分配的每个页面 +================================ + +概述 +==== + +page owner是用来追踪谁分配的每一个页面。它可以用来调试内存泄漏或找到内存占用者。 +当分配发生时,有关分配的信息,如调用堆栈和页面的顺序被存储到每个页面的特定存储中。 +当我们需要了解所有页面的状态时,我们可以获得并分析这些信息。 + +尽管我们已经有了追踪页面分配/释放的tracepoint,但用它来分析谁分配的每个页面是 +相当复杂的。我们需要扩大跟踪缓冲区,以防止在用户空间程序启动前出现重叠。而且,启 +动的程序会不断地将跟踪缓冲区转出,供以后分析,这将会改变系统的行为,会产生更多的 +可能性,而不是仅仅保留在内存中,所以不利于调试。 + +页面所有者也可以用于各种目的。例如,可以通过每个页面的gfp标志信息获得精确的碎片 +统计。如果启用了page owner,它就已经实现并激活了。我们非常欢迎其他用途。 + +page owner在默认情况下是禁用的。所以,如果你想使用它,你需要在你的启动cmdline +中加入"page_owner=on"。如果内核是用page owner构建的,并且由于没有启用启动 +选项而在运行时禁用page owner,那么运行时的开销是很小的。如果在运行时禁用,它不 +需要内存来存储所有者信息,所以没有运行时内存开销。而且,页面所有者在页面分配器的 +热路径中只插入了两个不可能的分支,如果不启用,那么分配就会像没有页面所有者的内核 +一样进行。这两个不可能的分支应该不会影响到分配的性能,特别是在静态键跳转标签修补 +功能可用的情况下。以下是由于这个功能而导致的内核代码大小的变化。 + +- 没有page owner:: + + text data bss dec hex filename + 48392 2333 644 51369 c8a9 mm/page_alloc.o + +- 有page owner:: + + text data bss dec hex filename + 48800 2445 644 51889 cab1 mm/page_alloc.o + 6662 108 29 6799 1a8f mm/page_owner.o + 1025 8 8 1041 411 mm/page_ext.o + +虽然总共增加了8KB的代码,但page_alloc.o增加了520字节,其中不到一半是在hotpath +中。构建带有page owner的内核,并在需要时打开它,将是调试内核内存问题的最佳选择。 + +有一个问题是由实现细节引起的。页所有者将信息存储到struct page扩展的内存中。这 +个内存的初始化时间比稀疏内存系统中的页面分配器启动的时间要晚一些,所以,在初始化 +之前,许多页面可以被分配,但它们没有所有者信息。为了解决这个问题,这些早期分配的 +页面在初始化阶段被调查并标记为分配。虽然这并不意味着它们有正确的所有者信息,但至 +少,我们可以更准确地判断该页是否被分配。在2GB内存的x86-64虚拟机上,有13343 +个早期分配的页面被捕捉和标记,尽管它们大部分是由结构页扩展功能分配的。总之,在这 +之后,没有任何页面处于未追踪状态。 + +使用方法 +======== + +1) 构建用户空间的帮助:: + + cd tools/vm + make page_owner_sort + +2) 启用page owner: 添加 "page_owner=on" 到 boot cmdline. + +3) 做你想调试的工作。 + +4) 分析来自页面所有者的信息:: + + cat /sys/kernel/debug/page_owner > page_owner_full.txt + ./page_owner_sort page_owner_full.txt sorted_page_owner.txt + + ``page_owner_full.txt`` 的一般输出情况如下:: + + Page allocated via order XXX, ... + PFN XXX ... + // 栈详情 + + Page allocated via order XXX, ... + PFN XXX ... + // 栈详情 + 默认情况下,它将以一个给定的pfn开始,做完整的pfn转储,且page_owner支持fseek。 + + FILE *fp = fopen("/sys/kernel/debug/page_owner", "r"); + fseek(fp, pfn_start, SEEK_SET); + + ``page_owner_sort`` 工具忽略了 ``PFN`` 行,将剩余的行放在buf中,使用regexp提 + 取页序值,计算buf的次数和页数,最后根据参数进行排序。 + + 在 ``sorted_page_owner.txt`` 中可以看到关于谁分配了每个页面的结果。一般输出:: + + XXX times, XXX pages: + Page allocated via order XXX, ... + // Detailed stack + + 默认情况下, ``page_owner_sort`` 是根据buf的时间来排序的。如果你想 + 按buf的页数排序,请使用-m参数。详细的参数是: + + 基本函数:: + + 排序: + -a 按内存分配时间排序 + -m 按总内存排序 + -p 按pid排序。 + -P 按tgid排序。 + -n 按任务命令名称排序。 + -r 按内存释放时间排序。 + -s 按堆栈跟踪排序。 + -t 按时间排序(默认)。 + --sort <order> 指定排序顺序。排序的语法是[+|-]key[,[+|-]key[,...]]。从 + **标准格式指定器**那一节选择一个键。"+"是可选的,因为默认的方向是数字或 + 词法的增加。允许混合使用缩写和完整格式的键。 + + 例子: + ./page_owner_sort <input> <output> --sort=n,+pid,-tgid + ./page_owner_sort <input> <output> --sort=at + + 其它函数:: + + 剔除: + --cull <rules> + 指定剔除规则。剔除的语法是key[,key[,...]]。从**标准格式指定器** + 部分选择一个多字母键。 + <rules>是一个以逗号分隔的列表形式的单一参数,它提供了一种指定单个剔除规则的 + 方法。 识别的关键字在下面的**标准格式指定器**部分有描述。<规则>可以通过键的 + 序列k1,k2,...来指定,在下面的标准排序键部分有描述。允许混合使用简写和完整形 + 式的键。 + + Examples: + ./page_owner_sort <input> <output> --cull=stacktrace + ./page_owner_sort <input> <output> --cull=st,pid,name + ./page_owner_sort <input> <output> --cull=n,f + + 过滤: + -f 过滤掉内存已被释放的块的信息。 + + 选择: + --pid <pidlist> 按pid选择。这将选择进程ID号出现在<pidlist>中的块。 + --tgid <tgidlist> 按tgid选择。这将选择其线程组ID号出现在<tgidlist> + 中的块。 + --name <cmdlist> 按任务命令名称选择。这将选择其任务命令名称出现在 + <cmdlist>中的区块。 + + <pidlist>, <tgidlist>, <cmdlist>是以逗号分隔的列表形式的单个参数, + 它提供了一种指定单个选择规则的方法。 + + + 例子: + ./page_owner_sort <input> <output> --pid=1 + ./page_owner_sort <input> <output> --tgid=1,2,3 + ./page_owner_sort <input> <output> --name name1,name2 + +标准格式指定器 +============== +:: + + --sort的选项: + + 短键 长键 描述 + p pid 进程ID + tg tgid 线程组ID + n name 任务命令名称 + st stacktrace 页面分配的堆栈跟踪 + T txt 块的全文 + ft free_ts 页面释放时的时间戳 + at alloc_ts 页面被分配时的时间戳 + ator allocator 页面的内存分配器 + + --curl的选项: + + 短键 长键 描述 + p pid 进程ID + tg tgid 线程组ID + n name 任务命令名称 + f free 该页是否已经释放 + st stacktrace 页面分配的堆栈跟踪 + ator allocator 页面的内存分配器 diff --git a/Documentation/translations/zh_CN/mm/page_table_check.rst b/Documentation/translations/zh_CN/mm/page_table_check.rst new file mode 100644 index 000000000..e8077310a --- /dev/null +++ b/Documentation/translations/zh_CN/mm/page_table_check.rst @@ -0,0 +1,56 @@ +.. SPDX-License-Identifier: GPL-2.0 + +:Original: Documentation/mm/page_table_check.rst + +:翻译: + + 司延腾 Yanteng Si <siyanteng@loongson.cn> + +:校译: + + +======== +页表检查 +======== + +概述 +==== + +页表检查允许通过确保防止某些类型的内存损坏来强化内核。 + +当新的页面可以从用户空间访问时,页表检查通过将它们的页表项(PTEs PMD等)添加到页表中来执行额外 +的验证。 + +在检测到损坏的情况下,内核会被崩溃。页表检查有一个小的性能和内存开销。因此,它在默认情况下是禁用 +的,但是在额外的加固超过性能成本的系统上,可以选择启用。另外,由于页表检查是同步的,它可以帮助调 +试双映射内存损坏问题,在错误的映射发生时崩溃内核,而不是在内存损坏错误发生后内核崩溃。 + +双重映射检测逻辑 +================ + ++-------------------+-------------------+-------------------+------------------+ +| Current Mapping | New mapping | Permissions | Rule | ++===================+===================+===================+==================+ +| Anonymous | Anonymous | Read | Allow | ++-------------------+-------------------+-------------------+------------------+ +| Anonymous | Anonymous | Read / Write | Prohibit | ++-------------------+-------------------+-------------------+------------------+ +| Anonymous | Named | Any | Prohibit | ++-------------------+-------------------+-------------------+------------------+ +| Named | Anonymous | Any | Prohibit | ++-------------------+-------------------+-------------------+------------------+ +| Named | Named | Any | Allow | ++-------------------+-------------------+-------------------+------------------+ + +启用页表检查 +============ + +用以下方法构建内核: + +- PAGE_TABLE_CHECK=y + 注意,它只能在ARCH_SUPPORTS_PAGE_TABLE_CHECK可用的平台上启用。 + +- 使用 "page_table_check=on" 内核参数启动。 + +可以选择用PAGE_TABLE_CHECK_ENFORCED来构建内核,以便在没有额外的内核参数的情况下获得页表 +支持。 diff --git a/Documentation/translations/zh_CN/mm/remap_file_pages.rst b/Documentation/translations/zh_CN/mm/remap_file_pages.rst new file mode 100644 index 000000000..31e0c54dc --- /dev/null +++ b/Documentation/translations/zh_CN/mm/remap_file_pages.rst @@ -0,0 +1,32 @@ +:Original: Documentation/mm/remap_file_pages.rst + +:翻译: + + 司延腾 Yanteng Si <siyanteng@loongson.cn> + +:校译: + + +============================== +remap_file_pages()系统调用 +============================== + +remap_file_pages()系统调用被用来创建一个非线性映射,也就是说,在这个映射中, +文件的页面被无序映射到内存中。使用remap_file_pages()比重复调用mmap(2)的好 +处是,前者不需要内核创建额外的VMA(虚拟内存区)数据结构。 + +支持非线性映射需要在内核虚拟内存子系统中编写大量的non-trivial的代码,包括热 +路径。另外,为了使非线性映射工作,内核需要一种方法来区分正常的页表项和带有文件 +偏移的项(pte_file)。内核为达到这个目的在PTE中保留了标志。PTE标志是稀缺资 +源,特别是在某些CPU架构上。如果能腾出这个标志用于其他用途就更好了。 + +幸运的是,在生活中并没有很多remap_file_pages()的用户。只知道有一个企业的RDBMS +实现在32位系统上使用这个系统调用来映射比32位虚拟地址空间线性尺寸更大的文件。 +由于64位系统的广泛使用,这种使用情况已经不重要了。 + +syscall被废弃了,现在用一个模拟来代替它。仿真会创建新的VMA,而不是非线性映射。 +对于remap_file_pages()的少数用户来说,它的工作速度会变慢,但ABI被保留了。 + +仿真的一个副作用(除了性能之外)是,由于额外的VMA,用户可以更容易达到 +vm.max_map_count的限制。关于限制的更多细节,请参见DEFAULT_MAX_MAP_COUNT +的注释。 diff --git a/Documentation/translations/zh_CN/mm/split_page_table_lock.rst b/Documentation/translations/zh_CN/mm/split_page_table_lock.rst new file mode 100644 index 000000000..4fb7aa666 --- /dev/null +++ b/Documentation/translations/zh_CN/mm/split_page_table_lock.rst @@ -0,0 +1,96 @@ +:Original: Documentation/mm/split_page_table_lock.rst + +:翻译: + + 司延腾 Yanteng Si <siyanteng@loongson.cn> + +:校译: + + +================================= +分页表锁(split page table lock) +================================= + +最初,mm->page_table_lock spinlock保护了mm_struct的所有页表。但是这种方 +法导致了多线程应用程序的缺页异常可扩展性差,因为对锁的争夺很激烈。为了提高可扩 +展性,我们引入了分页表锁。 + +有了分页表锁,我们就有了单独的每张表锁来顺序化对表的访问。目前,我们对PTE和 +PMD表使用分页锁。对高层表的访问由mm->page_table_lock保护。 + +有一些辅助工具来锁定/解锁一个表和其他访问器函数: + + - pte_offset_map_lock() + 映射pte并获取PTE表锁,返回所取锁的指针; + - pte_unmap_unlock() + 解锁和解映射PTE表; + - pte_alloc_map_lock() + 如果需要的话,分配PTE表并获取锁,如果分配失败,返回已获取的锁的指针 + 或NULL; + - pte_lockptr() + 返回指向PTE表锁的指针; + - pmd_lock() + 取得PMD表锁,返回所取锁的指针。 + - pmd_lockptr() + 返回指向PMD表锁的指针; + +如果CONFIG_SPLIT_PTLOCK_CPUS(通常为4)小于或等于NR_CPUS,则在编译 +时启用PTE表的分页表锁。如果分页锁被禁用,所有的表都由mm->page_table_lock +来保护。 + +如果PMD表启用了分页锁,并且架构支持它,那么PMD表的分页锁就会被启用(见 +下文)。 + +Hugetlb 和分页表锁 +================== + +Hugetlb可以支持多种页面大小。我们只对PMD级别使用分页锁,但不对PUD使用。 + +Hugetlb特定的辅助函数: + + - huge_pte_lock() + 对PMD_SIZE页面采取pmd分割锁,否则mm->page_table_lock; + - huge_pte_lockptr() + 返回指向表锁的指针。 + +架构对分页表锁的支持 +==================== + +没有必要特别启用PTE分页表锁:所有需要的东西都由pgtable_pte_page_ctor() +和pgtable_pte_page_dtor()完成,它们必须在PTE表分配/释放时被调用。 + +确保架构不使用slab分配器来分配页表:slab使用page->slab_cache来分配其页 +面。这个区域与page->ptl共享存储。 + +PMD分页锁只有在你有两个以上的页表级别时才有意义。 + +启用PMD分页锁需要在PMD表分配时调用pgtable_pmd_page_ctor(),在释放时调 +用pgtable_pmd_page_dtor()。 + +分配通常发生在pmd_alloc_one()中,释放发生在pmd_free()和pmd_free_tlb() +中,但要确保覆盖所有的PMD表分配/释放路径:即X86_PAE在pgd_alloc()中预先 +分配一些PMD。 + +一切就绪后,你可以设置CONFIG_ARCH_ENABLE_SPLIT_PMD_PTLOCK。 + +注意:pgtable_pte_page_ctor()和pgtable_pmd_page_ctor()可能失败--必 +须正确处理。 + +page->ptl +========= + +page->ptl用于访问分割页表锁,其中'page'是包含该表的页面struct page。它 +与page->private(以及union中的其他几个字段)共享存储。 + +为了避免增加struct page的大小并获得最佳性能,我们使用了一个技巧: + + - 如果spinlock_t适合于long,我们使用page->ptr作为spinlock,这样我们 + 就可以避免间接访问并节省一个缓存行。 + - 如果spinlock_t的大小大于long的大小,我们使用page->ptl作为spinlock_t + 的指针并动态分配它。这允许在启用DEBUG_SPINLOCK或DEBUG_LOCK_ALLOC的 + 情况下使用分页锁,但由于间接访问而多花了一个缓存行。 + +PTE表的spinlock_t分配在pgtable_pte_page_ctor()中,PMD表的spinlock_t +分配在pgtable_pmd_page_ctor()中。 + +请不要直接访问page->ptl - -使用适当的辅助函数。 diff --git a/Documentation/translations/zh_CN/mm/vmalloced-kernel-stacks.rst b/Documentation/translations/zh_CN/mm/vmalloced-kernel-stacks.rst new file mode 100644 index 000000000..d02a23f7f --- /dev/null +++ b/Documentation/translations/zh_CN/mm/vmalloced-kernel-stacks.rst @@ -0,0 +1,133 @@ +.. SPDX-License-Identifier: GPL-2.0 +.. include:: ../disclaimer-zh_CN.rst + +:Original: Documentation/mm/vmalloced-kernel-stacks.rst + +:翻译: + + 司延腾 Yanteng Si <siyanteng@loongson.cn> + +:校译: + +==================== +支持虚拟映射的内核栈 +==================== + +:作者: Shuah Khan <skhan@linuxfoundation.org> + +.. contents:: :local: + +概览 +---- + +这是介绍 `虚拟映射内核栈功能 <https://lwn.net/Articles/694348/>` 的代码 +和原始补丁系列的信息汇总。 + +简介 +---- + +内核堆栈溢出通常难以调试,并使内核容易被(恶意)利用。问题可能在稍后的时间出现,使其难以 +隔离和究其根本原因。 + +带有保护页的虚拟映射内核堆栈如果溢出,会被立即捕获,而不会放任其导致难以诊断的损 +坏。 + +HAVE_ARCH_VMAP_STACK和VMAP_STACK配置选项能够支持带有保护页的虚拟映射堆栈。 +当堆栈溢出时,这个特性会引发可靠的异常。溢出后堆栈跟踪的可用性以及对溢出本身的 +响应取决于架构。 + +.. note:: + 截至本文撰写时, arm64, powerpc, riscv, s390, um, 和 x86 支持VMAP_STACK。 + +HAVE_ARCH_VMAP_STACK +-------------------- + +能够支持虚拟映射内核栈的架构应该启用这个bool配置选项。要求是: + +- vmalloc空间必须大到足以容纳许多内核堆栈。这可能排除了许多32位架构。 +- vmalloc空间的堆栈需要可靠地工作。例如,如果vmap页表是按需创建的,当堆栈指向 + 具有未填充页表的虚拟地址时,这种机制需要工作,或者架构代码(switch_to()和 + switch_mm(),很可能)需要确保堆栈的页表项在可能未填充的堆栈上运行之前已经填 + 充。 +- 如果堆栈溢出到一个保护页,就应该发生一些合理的事情。“合理”的定义是灵活的,但 + 在没有记录任何东西的情况下立即重启是不友好的。 + +VMAP_STACK +---------- + +VMAP_STACK bool配置选项在启用时分配虚拟映射的任务栈。这个选项依赖于 +HAVE_ARCH_VMAP_STACK。 + +- 如果你想使用带有保护页的虚拟映射的内核堆栈,请启用该选项。这将导致内核栈溢出 + 被立即捕获,而不是难以诊断的损坏。 + +.. note:: + + 使用KASAN的这个功能需要架构支持用真实的影子内存来支持虚拟映射,并且 + 必须启用KASAN_VMALLOC。 + +.. note:: + + 启用VMAP_STACK时,无法在堆栈分配的数据上运行DMA。 + +内核配置选项和依赖性不断变化。请参考最新的代码库: + +`Kconfig <https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/tree/arch/Kconfig>` + +分配方法 +-------- + +当一个新的内核线程被创建时,线程堆栈是由页级分配器分配的虚拟连续的内存页组成。这 +些页面被映射到有PAGE_KERNEL保护的连续的内核虚拟空间。 + +alloc_thread_stack_node()调用__vmalloc_node_range()来分配带有PAGE_KERNEL +保护的栈。 + +- 分配的堆栈被缓存起来,以后会被新的线程重用,所以在分配/释放堆栈给任务时,要手动 + 进行memcg核算。因此,__vmalloc_node_range被调用时没有__GFP_ACCOUNT。 +- vm_struct被缓存起来,以便能够找到在中断上下文中启动的空闲线程。 free_thread_stack() + 可以在中断上下文中调用。 +- 在arm64上,所有VMAP的堆栈都需要有相同的对齐方式,以确保VMAP的堆栈溢出检测正常 + 工作。架构特定的vmap堆栈分配器照顾到了这个细节。 +- 这并不涉及中断堆栈--参考原始补丁 + +线程栈分配是由clone()、fork()、vfork()、kernel_thread()通过kernel_clone() +启动的。留点提示在这,以便搜索代码库,了解线程栈何时以及如何分配。 + +大量的代码是在: +`kernel/fork.c <https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/tree/kernel/fork.c>`. + +task_struct中的stack_vm_area指针可以跟踪虚拟分配的堆栈,一个非空的stack_vm_area +指针可以表明虚拟映射的内核堆栈已经启用。 + +:: + + struct vm_struct *stack_vm_area; + +堆栈溢出处理 +------------ + +前守护页和后守护页有助于检测堆栈溢出。当堆栈溢出到守护页时,处理程序必须小心不要再 +次溢出堆栈。当处理程序被调用时,很可能只留下很少的堆栈空间。 + +在x86上,这是通过处理表明内核堆栈溢出的双异常堆栈的缺页异常来实现的。 + +用守护页测试VMAP分配 +-------------------- + +我们如何确保VMAP_STACK在分配时确实有前守护页和后守护页的保护?下面的 lkdtm 测试 +可以帮助检测任何回归。 + +:: + + void lkdtm_STACK_GUARD_PAGE_LEADING() + void lkdtm_STACK_GUARD_PAGE_TRAILING() + +结论 +---- + +- vmalloced堆栈的percpu缓存似乎比高阶堆栈分配要快一些,至少在缓存命中时是这样。 +- THREAD_INFO_IN_TASK完全摆脱了arch-specific thread_info,并简单地将 + thread_info(仅包含标志)和'int cpu'嵌入task_struct中。 +- 一旦任务死亡,线程栈就可以被释放(无需等待RCU),然后,如果使用vmapped栈,就 + 可以将整个栈缓存起来,以便在同一cpu上重复使用。 diff --git a/Documentation/translations/zh_CN/mm/z3fold.rst b/Documentation/translations/zh_CN/mm/z3fold.rst new file mode 100644 index 000000000..9569a6d88 --- /dev/null +++ b/Documentation/translations/zh_CN/mm/z3fold.rst @@ -0,0 +1,31 @@ +:Original: Documentation/mm/z3fold.rst + +:翻译: + + 司延腾 Yanteng Si <siyanteng@loongson.cn> + +:校译: + + +====== +z3fold +====== + +z3fold是一个专门用于存储压缩页的分配器。它被设计为每个物理页最多可以存储三个压缩页。 +它是zbud的衍生物,允许更高的压缩率,保持其前辈的简单性和确定性。 + +z3fold和zbud的主要区别是: + +* 与zbud不同的是,z3fold允许最大的PAGE_SIZE分配。 +* z3fold在其页面中最多可以容纳3个压缩页面 +* z3fold本身没有输出任何API,因此打算通过zpool的API来使用 + +为了保持确定性和简单性,z3fold,就像zbud一样,总是在每页存储一个整数的压缩页,但是 +它最多可以存储3页,不像zbud最多可以存储2页。因此压缩率达到2.7倍左右,而zbud的压缩 +率是1.7倍左右。 + +不像zbud(但也像zsmalloc),z3fold_alloc()那样不返回一个可重复引用的指针。相反,它 +返回一个无符号长句柄,它编码了被分配对象的实际位置。 + +保持有效的压缩率接近于zsmalloc,z3fold不依赖于MMU的启用,并提供更可预测的回收行 +为,这使得它更适合于小型和反应迅速的系统。 diff --git a/Documentation/translations/zh_CN/mm/zsmalloc.rst b/Documentation/translations/zh_CN/mm/zsmalloc.rst new file mode 100644 index 000000000..4c8c9b100 --- /dev/null +++ b/Documentation/translations/zh_CN/mm/zsmalloc.rst @@ -0,0 +1,78 @@ +:Original: Documentation/mm/zsmalloc.rst + +:翻译: + + 司延腾 Yanteng Si <siyanteng@loongson.cn> + +:校译: + +======== +zsmalloc +======== + +这个分配器是为与zram一起使用而设计的。因此,该分配器应该在低内存条件下工作良好。特别是, +它从未尝试过higher order页面的分配,这在内存压力下很可能会失败。另一方面,如果我们只 +是使用单(0-order)页,它将遭受非常高的碎片化 - 任何大小为PAGE_SIZE/2或更大的对象将 +占据整个页面。这是其前身(xvmalloc)的主要问题之一。 + +为了克服这些问题,zsmalloc分配了一堆0-order页面,并使用各种"struct page"字段将它 +们链接起来。这些链接的页面作为一个单一的higher order页面,即一个对象可以跨越0-order +页面的边界。代码将这些链接的页面作为一个实体,称为zspage。 + +为了简单起见,zsmalloc只能分配大小不超过PAGE_SIZE的对象,因为这满足了所有当前用户的 +要求(在最坏的情况下,页面是不可压缩的,因此以"原样"即未压缩的形式存储)。对于大于这 +个大小的分配请求,会返回失败(见zs_malloc)。 + +此外,zs_malloc()并不返回一个可重复引用的指针。相反,它返回一个不透明的句柄(无符号 +长),它编码了被分配对象的实际位置。这种间接性的原因是zsmalloc并不保持zspages的永久 +映射,因为这在32位系统上会导致问题,因为内核空间映射的VA区域非常小。因此,在使用分配 +的内存之前,对象必须使用zs_map_object()进行映射以获得一个可用的指针,随后使用 +zs_unmap_object()解除映射。 + +stat +==== + +通过CONFIG_ZSMALLOC_STAT,我们可以通过 ``/sys/kernel/debug/zsmalloc/<user name>`` +看到zsmalloc内部信息。下面是一个统计输出的例子。:: + + # cat /sys/kernel/debug/zsmalloc/zram0/classes + + class size almost_full almost_empty obj_allocated obj_used pages_used pages_per_zspage + ... + ... + 9 176 0 1 186 129 8 4 + 10 192 1 0 2880 2872 135 3 + 11 208 0 1 819 795 42 2 + 12 224 0 1 219 159 12 4 + ... + ... + + +class + 索引 +size + zspage存储对象大小 +almost_empty + ZS_ALMOST_EMPTY zspage的数量(见下文)。 +almost_full + ZS_ALMOST_FULL zspage的数量(见下图) +obj_allocated + 已分配对象的数量 +obj_used + 分配给用户的对象的数量 +pages_used + 为该类分配的页数 +pages_per_zspage + 组成一个zspage的0-order页面的数量 + +当n <= N / f时,我们将一个zspage分配给ZS_ALMOST_EMPTYfullness组,其中 + +* n = 已分配对象的数量 +* N = zspage可以存储的对象总数 +* f = fullness_threshold_frac(即,目前是4个) + +同样地,我们将zspage分配给: + +* ZS_ALMOST_FULL when n > N / f +* ZS_EMPTY when n == 0 +* ZS_FULL when n == N diff --git a/Documentation/translations/zh_CN/openrisc/index.rst b/Documentation/translations/zh_CN/openrisc/index.rst new file mode 100644 index 000000000..9ad6cc600 --- /dev/null +++ b/Documentation/translations/zh_CN/openrisc/index.rst @@ -0,0 +1,32 @@ +.. SPDX-License-Identifier: GPL-2.0 + +.. include:: ../disclaimer-zh_CN.rst + +:Original: Documentation/openrisc/index.rst + +:翻译: + + 司延腾 Yanteng Si <siyanteng@loongson.cn> + +.. _cn_openrisc_index: + +================= +OpenRISC 体系架构 +================= + +.. toctree:: + :maxdepth: 2 + + openrisc_port + todo + +Todolist: + features + + +.. only:: subproject and html + + Indices + ======= + + * :ref:`genindex` diff --git a/Documentation/translations/zh_CN/openrisc/openrisc_port.rst b/Documentation/translations/zh_CN/openrisc/openrisc_port.rst new file mode 100644 index 000000000..b8a676704 --- /dev/null +++ b/Documentation/translations/zh_CN/openrisc/openrisc_port.rst @@ -0,0 +1,127 @@ +.. include:: ../disclaimer-zh_CN.rst + +:Original: Documentation/openrisc/openrisc_port.rst + +:翻译: + + 司延腾 Yanteng Si <siyanteng@loongson.cn> + +.. _cn_openrisc_port: + +============== +OpenRISC Linux +============== + +这是Linux对OpenRISC类微处理器的移植;具体来说,最早移植目标是32位 +OpenRISC 1000系列(或1k)。 + +关于OpenRISC处理器和正在进行中的开发的信息: + + ======= ============================= + 网站 https://openrisc.io + 邮箱 openrisc@lists.librecores.org + ======= ============================= + +--------------------------------------------------------------------- + +OpenRISC工具链和Linux的构建指南 +=============================== + +为了构建和运行Linux for OpenRISC,你至少需要一个基本的工具链,或许 +还需要架构模拟器。 这里概述了准备就位这些部分的步骤。 + +1) 工具链 + +工具链二进制文件可以从openrisc.io或我们的github发布页面获得。不同 +工具链的构建指南可以在openrisc.io或Stafford的工具链构建和发布脚本 +中找到。 + + ====== ================================================= + 二进制 https://github.com/openrisc/or1k-gcc/releases + 工具链 https://openrisc.io/software + 构建 https://github.com/stffrdhrn/or1k-toolchain-build + ====== ================================================= + +2) 构建 + +像往常一样构建Linux内核:: + + make ARCH=openrisc CROSS_COMPILE="or1k-linux-" defconfig + make ARCH=openrisc CROSS_COMPILE="or1k-linux-" + +3) 在FPGA上运行(可选) + +OpenRISC社区通常使用FuseSoC来管理构建和编程SoC到FPGA中。 下面是用 +OpenRISC SoC对De0 Nano开发板进行编程的一个例子。 在构建过程中, +FPGA RTL是从FuseSoC IP核库中下载的代码,并使用FPGA供应商工具构建。 +二进制文件用openocd加载到电路板上。 + +:: + + git clone https://github.com/olofk/fusesoc + cd fusesoc + sudo pip install -e . + + fusesoc init + fusesoc build de0_nano + fusesoc pgm de0_nano + + openocd -f interface/altera-usb-blaster.cfg \ + -f board/or1k_generic.cfg + + telnet localhost 4444 + > init + > halt; load_image vmlinux ; reset + +4) 在模拟器上运行(可选) + +QEMU是一个处理器仿真器,我们推荐它来模拟OpenRISC平台。 请按照QEMU网 +站上的OpenRISC说明,让Linux在QEMU上运行。 你可以自己构建QEMU,但你的 +Linux发行版可能提供了支持OpenRISC的二进制包。 + + ============= ====================================================== + qemu openrisc https://wiki.qemu.org/Documentation/Platforms/OpenRISC + ============= ====================================================== + +--------------------------------------------------------------------- + +术语表 +====== + +代码中使用了以下符号约定以将范围限制在几个特定处理器实现上: + +========= ======================= +openrisc: OpenRISC类型处理器 +or1k: OpenRISC 1000系列处理器 +or1200: OpenRISC 1200处理器 +========= ======================= + +--------------------------------------------------------------------- + +历史 +==== + +2003-11-18 Matjaz Breskvar (phoenix@bsemi.com) + 将linux初步移植到OpenRISC或32架构。 + 所有的核心功能都实现了,并且可以使用。 + +2003-12-08 Matjaz Breskvar (phoenix@bsemi.com) + 彻底改变TLB失误处理。 + 重写异常处理。 + 在默认的initrd中实现了sash-3.6的所有功能。 + 大幅改进的版本。 + +2004-04-10 Matjaz Breskvar (phoenix@bsemi.com) + 大量的bug修复。 + 支持以太网,http和telnet服务器功能。 + 可以运行许多标准的linux应用程序。 + +2004-06-26 Matjaz Breskvar (phoenix@bsemi.com) + 移植到2.6.x。 + +2004-11-30 Matjaz Breskvar (phoenix@bsemi.com) + 大量的bug修复和增强功能。 + 增加了opencores framebuffer驱动。 + +2010-10-09 Jonas Bonn (jonas@southpole.se) + 重大重写,使其与上游的Linux 2.6.36看齐。 diff --git a/Documentation/translations/zh_CN/openrisc/todo.rst b/Documentation/translations/zh_CN/openrisc/todo.rst new file mode 100644 index 000000000..63c38717e --- /dev/null +++ b/Documentation/translations/zh_CN/openrisc/todo.rst @@ -0,0 +1,23 @@ +.. include:: ../disclaimer-zh_CN.rst + +:Original: Documentation/openrisc/todo.rst + +:翻译: + + 司延腾 Yanteng Si <siyanteng@loongson.cn> + +.. _cn_openrisc_todo.rst: + +======== +待办事项 +======== + +OpenRISC Linux的移植已经完全投入使用,并且从 2.6.35 开始就一直在上游同步。 +然而,还有一些剩余的项目需要在未来几个月内完成。 下面是一个即将进行调查的已知 +不尽完美的项目列表,即我们的待办事项列表。 + +- 实现其余的DMA API……dma_map_sg等。 + +- 完成重命名清理工作……代码中提到了or32,这是架构的一个老名字。 我们 + 已经确定的名字是or1k,这个改变正在以缓慢积累的方式进行。 目前,or32相当 + 于or1k。 diff --git a/Documentation/translations/zh_CN/parisc/debugging.rst b/Documentation/translations/zh_CN/parisc/debugging.rst new file mode 100644 index 000000000..68b73eb57 --- /dev/null +++ b/Documentation/translations/zh_CN/parisc/debugging.rst @@ -0,0 +1,45 @@ +.. include:: ../disclaimer-zh_CN.rst + +:Original: Documentation/parisc/debugging.rst + +:翻译: + + 司延腾 Yanteng Si <siyanteng@loongson.cn> + +.. _cn_parisc_debugging: + +================= +调试PA-RISC +================= + +好吧,这里有一些关于调试linux/parisc的较底层部分的信息。 + + +1. 绝对地址 +===================== + +很多汇编代码目前运行在实模式下,这意味着会使用绝对地址,而不是像内核其他 +部分那样使用虚拟地址。要将绝对地址转换为虚拟地址,你可以在System.map中查 +找,添加__PAGE_OFFSET(目前是0x10000000)。 + + +2. HPMCs +======== + +当实模式的代码试图访问不存在的内存时,会出现HPMC(high priority machine +check)而不是内核oops。若要调试HPMC,请尝试找到系统响应程序/请求程序地址。 +系统请求程序地址应该与(某)处理器的HPA(I/O范围内的高地址)相匹配;系统响应程 +序地址是实模式代码试图访问的地址。 + +系统响应程序地址的典型值是大于__PAGE_OFFSET (0x10000000)的地址,这意味着 +在实模式试图访问它之前,虚拟地址没有被翻译成物理地址。 + + +3. 有趣的Q位 +============ + +某些非常关键的代码必须清除PSW中的Q位。当Q位被清除时,CPU不会更新中断处理 +程序所读取的寄存器,以找出机器被中断的位置——所以如果你在清除Q位的指令和再 +次设置Q位的RFI之间遇到中断,你不知道它到底发生在哪里。如果你幸运的话,IAOQ +会指向清除Q位的指令,如果你不幸运的话,它会指向任何地方。通常Q位的问题会 +表现为无法解释的系统挂起或物理内存越界。 diff --git a/Documentation/translations/zh_CN/parisc/index.rst b/Documentation/translations/zh_CN/parisc/index.rst new file mode 100644 index 000000000..0cc553fc8 --- /dev/null +++ b/Documentation/translations/zh_CN/parisc/index.rst @@ -0,0 +1,31 @@ +.. SPDX-License-Identifier: GPL-2.0 +.. include:: ../disclaimer-zh_CN.rst + +:Original: Documentation/parisc/index.rst + +:翻译: + + 司延腾 Yanteng Si <siyanteng@loongson.cn> + +.. _cn_parisc_index: + +==================== +PA-RISC体系架构 +==================== + +.. toctree:: + :maxdepth: 2 + + debugging + registers + +Todolist: + + features + +.. only:: subproject and html + + Indices + ======= + + * :ref:`genindex` diff --git a/Documentation/translations/zh_CN/parisc/registers.rst b/Documentation/translations/zh_CN/parisc/registers.rst new file mode 100644 index 000000000..d2ab1874a --- /dev/null +++ b/Documentation/translations/zh_CN/parisc/registers.rst @@ -0,0 +1,156 @@ +.. include:: ../disclaimer-zh_CN.rst + +:Original: Documentation/parisc/registers.rst + +:翻译: + + 司延腾 Yanteng Si <siyanteng@loongson.cn> + +.. _cn_parisc_registers: + +========================= +Linux/PA-RISC的寄存器用法 +========================= + +[ 用星号表示目前尚未实现的计划用途。 ] + +ABI约定的通用寄存器 +=================== + +控制寄存器 +---------- + +============================ ================================= +CR 0 (恢复计数器) 用于ptrace +CR 1-CR 7(无定义) 未使用 +CR 8 (Protection ID) 每进程值* +CR 9, 12, 13 (PIDS) 未使用 +CR10 (CCR) FPU延迟保存* +CR11 按照ABI的规定(SAR) +CR14 (中断向量) 初始化为 fault_vector +CR15 (EIEM) 所有位初始化为1* +CR16 (间隔计时器) 读取周期数/写入开始时间间隔计时器 +CR17-CR22 中断参数 +CR19 中断指令寄存器 +CR20 中断空间寄存器 +CR21 中断偏移量寄存器 +CR22 中断 PSW +CR23 (EIRR) 读取未决中断/写入清除位 +CR24 (TR 0) 内核空间页目录指针 +CR25 (TR 1) 用户空间页目录指针 +CR26 (TR 2) 不使用 +CR27 (TR 3) 线程描述符指针 +CR28 (TR 4) 不使用 +CR29 (TR 5) 不使用 +CR30 (TR 6) 当前 / 0 +CR31 (TR 7) 临时寄存器,在不同地方使用 +============================ ================================= + +空间寄存器(内核模式) +---------------------- + +======== ============================== +SR0 临时空间寄存器 +SR4-SR7 设置为0 +SR1 临时空间寄存器 +SR2 内核不应该破坏它 +SR3 用于用户空间访问(当前进程) +======== ============================== + +空间寄存器(用户模式) +---------------------- + +======== ============================ +SR0 临时空间寄存器 +SR1 临时空间寄存器 +SR2 保存Linux gateway page的空间 +SR3 在内核中保存用户地址空间的值 +SR4-SR7 定义了用户/内核的短地址空间 +======== ============================ + + +处理器状态字 +------------ + +====================== ================================================ +W (64位地址) 0 +E (小尾端) 0 +S (安全间隔计时器) 0 +T (产生分支陷阱) 0 +H (高特权级陷阱) 0 +L (低特权级陷阱) 0 +N (撤销下一条指令) 被C代码使用 +X (数据存储中断禁用) 0 +B (产生分支) 被C代码使用 +C (代码地址转译) 1, 在执行实模式代码时为0 +V (除法步长校正) 被C代码使用 +M (HPMC 掩码) 0, 在执行HPMC操作*时为1 +C/B (进/借 位) 被C代码使用 +O (有序引用) 1* +F (性能监视器) 0 +R (回收计数器陷阱) 0 +Q (收集中断状态) 1 (在rfi之前的代码中为0) +P (保护标识符) 1* +D (数据地址转译) 1, 在执行实模式代码时为0 +I (外部中断掩码) 由cli()/sti()宏使用。 +====================== ================================================ + +“隐形”寄存器(影子寄存器) +--------------------------- + +============= =================== +PSW W 默认值 0 +PSW E 默认值 0 +影子寄存器 被中断处理代码使用 +TOC启用位 1 +============= =================== + +---------------------------------------------------------- + +PA-RISC架构定义了7个寄存器作为“影子寄存器”。这些寄存器在 +RETURN FROM INTERRUPTION AND RESTORE指令中使用,通过消 +除中断处理程序中对一般寄存器(GR)的保存和恢复的需要来减 +少状态保存和恢复时间。影子寄存器是GRs 1, 8, 9, 16, 17, +24和25。 + +------------------------------------------------------------------------- + +寄存器使用说明,最初由John Marvin提供,并由Randolph Chung提供一些补充说明。 + +对于通用寄存器: + +r1,r2,r19-r26,r28,r29 & r31可以在不保存它们的情况下被使用。当然,如果你 +关心它们,在调用另一个程序之前,你也需要保存它们。上面的一些寄存器确实 +有特殊的含义,你应该注意一下: + + r1: + addil指令是硬性规定将其结果放在r1中,所以如果你使用这条指令要 + 注意这点。 + + r2: + 这就是返回指针。一般来说,你不想使用它,因为你需要这个指针来返 + 回给你的调用者。然而,它与这组寄存器组合在一起,因为调用者不能 + 依赖你返回时的值是相同的,也就是说,你可以将r2复制到另一个寄存 + 器,并在作废r2后通过该寄存器返回,这应该不会给调用程序带来问题。 + + r19-r22: + 这些通常被认为是临时寄存器。 + 请注意,在64位中它们是arg7-arg4。 + + r23-r26: + 这些是arg3-arg0,也就是说,如果你不再关心传入的值,你可以使用 + 它们。 + + r28,r29: + 这俩是ret0和ret1。它们是你传入返回值的地方。r28是主返回值。当返回 + 小结构体时,r29也可以用来将数据传回给调用程序。 + + r30: + 栈指针 + + r31: + ble指令将返回指针放在这里。 + + + r3-r18,r27,r30需要被保存和恢复。r3-r18只是一般用途的寄存器。 + r27是数据指针,用来使对全局变量的引用更容易。r30是栈指针。 diff --git a/Documentation/translations/zh_CN/peci/index.rst b/Documentation/translations/zh_CN/peci/index.rst new file mode 100644 index 000000000..4f6694c82 --- /dev/null +++ b/Documentation/translations/zh_CN/peci/index.rst @@ -0,0 +1,26 @@ +.. SPDX-License-Identifier: GPL-2.0-only +.. include:: ../disclaimer-zh_CN.rst + +:Original: Documentation/peci/index.rst + +:翻译: + + 司延腾 Yanteng Si <siyanteng@loongson.cn> + +:校译: + +================= +Linux PECI 子系统 +================= + +.. toctree:: + + + peci + +.. only:: subproject and html + + Indices + ======= + + * :ref:`genindex` diff --git a/Documentation/translations/zh_CN/peci/peci.rst b/Documentation/translations/zh_CN/peci/peci.rst new file mode 100644 index 000000000..a3b4f99b9 --- /dev/null +++ b/Documentation/translations/zh_CN/peci/peci.rst @@ -0,0 +1,54 @@ +.. SPDX-License-Identifier: GPL-2.0-only +.. include:: ../disclaimer-zh_CN.rst + +:Original: Documentation/peci/peci.rst + +:翻译: + + 司延腾 Yanteng Si <siyanteng@loongson.cn> + +:校译: + +==== +概述 +==== + +平台环境控制接口(PECI)是英特尔处理器和管理控制器(如底板管理控制器,BMC) +之间的一个通信接口。PECI提供的服务允许管理控制器通过访问各种寄存器来配置、监 +控和调试平台。它定义了一个专门的命令协议,管理控制器作为PECI的发起者,处理器 +作为PECI的响应者。PECI可以用于基于单处理器和多处理器的系统中。 + +注意:英特尔PECI规范没有作为专门的文件发布,而是作为英特尔CPU的外部设计规范 +(EDS)的一部分。外部设计规范通常是不公开的。 + +PECI 线 +--------- + +PECI线接口使用单线进行自锁和数据传输。它不需要任何额外的控制线--物理层是一个 +自锁的单线总线信号,每一个比特都从接近零伏的空闲状态开始驱动、上升边缘。驱动高 +电平信号的持续时间可以确定位值是逻辑 “0” 还是逻辑 “1”。PECI线还包括与每个信 +息建立的可变数据速率。 + +对于PECI线,每个处理器包将在一个定义的范围内利用唯一的、固定的地址,该地址应 +该与处理器插座ID有固定的关系--如果其中一个处理器被移除,它不会影响其余处理器 +的地址。 + +PECI子系统代码内嵌文档 +------------------------ + +该API在以下内核代码中: + +include/linux/peci.h + +drivers/peci/internal.h + +drivers/peci/core.c + +drivers/peci/request.c + +PECI CPU 驱动 API +------------------- + +该API在以下内核代码中: + +drivers/peci/cpu.c diff --git a/Documentation/translations/zh_CN/power/energy-model.rst b/Documentation/translations/zh_CN/power/energy-model.rst new file mode 100644 index 000000000..c7da1b6ae --- /dev/null +++ b/Documentation/translations/zh_CN/power/energy-model.rst @@ -0,0 +1,190 @@ +.. SPDX-License-Identifier: GPL-2.0 +.. include:: ../disclaimer-zh_CN.rst + +:Original: Documentation/power/energy-model.rst + +:翻译: + + 唐艺舟 Tang Yizhou <tangyeechou@gmail.com> + +============ +设备能量模型 +============ + +1. 概述 +------- + +能量模型(EM)框架是一种驱动程序与内核子系统之间的接口。其中驱动程序了解不同 +性能层级的设备所消耗的功率,而内核子系统愿意使用该信息做出能量感知决策。 + +设备所消耗的功率的信息来源在不同的平台上可能有很大的不同。这些功率成本在某些 +情况下可以使用设备树数据来估算。在其它情况下,固件会更清楚。或者,用户空间可能 +是最清楚的。以此类推。为了避免每一个客户端子系统对每一种可能的信息源自己重新 +实现支持,EM框架作为一个抽象层介入,它在内核中对功率成本表的格式进行标准化, +因此能够避免多余的工作。 + +功率值可以用毫瓦或“抽象刻度”表示。多个子系统可能使用EM,由系统集成商来检查 +功率值刻度类型的要求是否满足。可以在能量感知调度器的文档中找到一个例子 +Documentation/scheduler/sched-energy.rst。对于一些子系统,比如热能或 +powercap,用“抽象刻度”描述功率值可能会导致问题。这些子系统对过去使用的功率的 +估算值更感兴趣,因此可能需要真实的毫瓦。这些要求的一个例子可以在智能功率分配 +Documentation/driver-api/thermal/power_allocator.rst文档中找到。 + +内核子系统可能(基于EM内部标志位)实现了对EM注册设备是否具有不一致刻度的自动 +检查。要记住的重要事情是,当功率值以“抽象刻度”表示时,从中推导以毫焦耳为单位 +的真实能量消耗是不可能的。 + +下图描述了一个驱动的例子(这里是针对Arm的,但该方法适用于任何体系结构),它 +向EM框架提供了功率成本,感兴趣的客户端可从中读取数据:: + + +---------------+ +-----------------+ +---------------+ + | Thermal (IPA) | | Scheduler (EAS) | | Other | + +---------------+ +-----------------+ +---------------+ + | | em_cpu_energy() | + | | em_cpu_get() | + +---------+ | +---------+ + | | | + v v v + +---------------------+ + | Energy Model | + | Framework | + +---------------------+ + ^ ^ ^ + | | | em_dev_register_perf_domain() + +----------+ | +---------+ + | | | + +---------------+ +---------------+ +--------------+ + | cpufreq-dt | | arm_scmi | | Other | + +---------------+ +---------------+ +--------------+ + ^ ^ ^ + | | | + +--------------+ +---------------+ +--------------+ + | Device Tree | | Firmware | | ? | + +--------------+ +---------------+ +--------------+ + +对于CPU设备,EM框架管理着系统中每个“性能域”的功率成本表。一个性能域是一组 +性能一起伸缩的CPU。性能域通常与CPUFreq策略具有1对1映射。一个性能域中的 +所有CPU要求具有相同的微架构。不同性能域中的CPU可以有不同的微架构。 + + +2. 核心API +---------- + +2.1 配置选项 +^^^^^^^^^^^^ + +必须使能CONFIG_ENERGY_MODEL才能使用EM框架。 + + +2.2 性能域的注册 +^^^^^^^^^^^^^^^^ + +“高级”EM的注册 +~~~~~~~~~~~~~~~~ + +“高级”EM因它允许驱动提供更精确的功率模型而得名。它并不受限于框架中的一些已 +实现的数学公式(就像“简单”EM那样)。它可以更好地反映每个性能状态的实际功率 +测量。因此,在EM静态功率(漏电流功率)是重要的情况下,应该首选这种注册方式。 + +驱动程序应通过以下API将性能域注册到EM框架中:: + + int em_dev_register_perf_domain(struct device *dev, unsigned int nr_states, + struct em_data_callback *cb, cpumask_t *cpus, bool milliwatts); + +驱动程序必须提供一个回调函数,为每个性能状态返回<频率,功率>元组。驱动程序 +提供的回调函数可以自由地从任何相关位置(DT、固件......)以及以任何被认为是 +必要的方式获取数据。只有对于CPU设备,驱动程序必须使用cpumask指定性能域的CPU。 +对于CPU以外的其他设备,最后一个参数必须被设置为NULL。 + +最后一个参数“milliwatts”(毫瓦)设置成正确的值是很重要的,使用EM的内核 +子系统可能会依赖这个标志来检查所有的EM设备是否使用相同的刻度。如果有不同的 +刻度,这些子系统可能决定:返回警告/错误,停止工作或崩溃(panic)。 + +关于实现这个回调函数的驱动程序的例子,参见第3节。或者在第2.4节阅读这个API +的更多文档。 + + +“简单”EM的注册 +~~~~~~~~~~~~~~~~ + +“简单”EM是用框架的辅助函数cpufreq_register_em_with_opp()注册的。它实现了 +一个和以下数学公式紧密相关的功率模型:: + + Power = C * V^2 * f + +使用这种方法注册的EM可能无法正确反映真实设备的物理特性,例如当静态功率 +(漏电流功率)很重要时。 + + +2.3 访问性能域 +^^^^^^^^^^^^^^ + +有两个API函数提供对能量模型的访问。em_cpu_get()以CPU id为参数,em_pd_get() +以设备指针为参数。使用哪个接口取决于子系统,但对于CPU设备来说,这两个函数都返 +回相同的性能域。 + +对CPU的能量模型感兴趣的子系统可以通过em_cpu_get() API检索它。在创建性能域时 +分配一次能量模型表,它保存在内存中不被修改。 + +一个性能域所消耗的能量可以使用em_cpu_energy() API来估算。该估算假定CPU设备 +使用的CPUfreq监管器是schedutil。当前该计算不能提供给其它类型的设备。 + +关于上述API的更多细节可以在 ``<linux/energy_model.h>`` 或第2.4节中找到。 + + +2.4 API的细节描述 +^^^^^^^^^^^^^^^^^ +参见 include/linux/energy_model.h 和 kernel/power/energy_model.c 的kernel doc。 + +3. 驱动示例 +----------- + +CPUFreq框架支持专用的回调函数,用于为指定的CPU(们)注册EM: +cpufreq_driver::register_em()。这个回调必须为每个特定的驱动程序正确实现, +因为框架会在设置过程中适时地调用它。本节提供了一个简单的例子,展示CPUFreq驱动 +在能量模型框架中使用(假的)“foo”协议注册性能域。该驱动实现了一个est_power() +函数提供给EM框架:: + + -> drivers/cpufreq/foo_cpufreq.c + + 01 static int est_power(unsigned long *mW, unsigned long *KHz, + 02 struct device *dev) + 03 { + 04 long freq, power; + 05 + 06 /* 使用“foo”协议设置频率上限 */ + 07 freq = foo_get_freq_ceil(dev, *KHz); + 08 if (freq < 0); + 09 return freq; + 10 + 11 /* 估算相关频率下设备的功率成本 */ + 12 power = foo_estimate_power(dev, freq); + 13 if (power < 0); + 14 return power; + 15 + 16 /* 将这些值返回给EM框架 */ + 17 *mW = power; + 18 *KHz = freq; + 19 + 20 return 0; + 21 } + 22 + 23 static void foo_cpufreq_register_em(struct cpufreq_policy *policy) + 24 { + 25 struct em_data_callback em_cb = EM_DATA_CB(est_power); + 26 struct device *cpu_dev; + 27 int nr_opp; + 28 + 29 cpu_dev = get_cpu_device(cpumask_first(policy->cpus)); + 30 + 31 /* 查找该策略支持的OPP数量 */ + 32 nr_opp = foo_get_nr_opp(policy); + 33 + 34 /* 并注册新的性能域 */ + 35 em_dev_register_perf_domain(cpu_dev, nr_opp, &em_cb, policy->cpus, + 36 true); + 37 } + 38 + 39 static struct cpufreq_driver foo_cpufreq_driver = { + 40 .register_em = foo_cpufreq_register_em, + 41 }; diff --git a/Documentation/translations/zh_CN/power/index.rst b/Documentation/translations/zh_CN/power/index.rst new file mode 100644 index 000000000..bc54983ba --- /dev/null +++ b/Documentation/translations/zh_CN/power/index.rst @@ -0,0 +1,56 @@ +.. SPDX-License-Identifier: GPL-2.0 +.. include:: ../disclaimer-zh_CN.rst + +:Original: Documentation/power/index.rst + +:翻译: + + 唐艺舟 Tang Yizhou <tangyeechou@gmail.com> + +======== +电源管理 +======== + +.. toctree:: + :maxdepth: 1 + + energy-model + opp + +TODOList: + + * apm-acpi + * basic-pm-debugging + * charger-manager + * drivers-testing + * freezing-of-tasks + * pci + * pm_qos_interface + * power_supply_class + * runtime_pm + * s2ram + * suspend-and-cpuhotplug + * suspend-and-interrupts + * swsusp-and-swap-files + * swsusp-dmcrypt + * swsusp + * video + * tricks + + * userland-swsusp + + * powercap/powercap + * powercap/dtpm + + * regulator/consumer + * regulator/design + * regulator/machine + * regulator/overview + * regulator/regulator + +.. only:: subproject and html + + Indices + ======= + + * :ref:`genindex` diff --git a/Documentation/translations/zh_CN/power/opp.rst b/Documentation/translations/zh_CN/power/opp.rst new file mode 100644 index 000000000..8d6e3f6f6 --- /dev/null +++ b/Documentation/translations/zh_CN/power/opp.rst @@ -0,0 +1,341 @@ +.. SPDX-License-Identifier: GPL-2.0 +.. include:: ../disclaimer-zh_CN.rst + +:Original: Documentation/power/opp.rst + +:翻译: + + 唐艺舟 Tang Yizhou <tangyeechou@gmail.com> + +====================== +操作性能值(OPP)库 +====================== + +(C) 2009-2010 Nishanth Menon <nm@ti.com>, 德州仪器公司 + +.. 目录 + + 1. 简介 + 2. OPP链表初始注册 + 3. OPP搜索函数 + 4. OPP可用性控制函数 + 5. OPP数据检索函数 + 6. 数据结构 + +1. 简介 +======= + +1.1 何为操作性能值(OPP)? +------------------------------ + +当今复杂的单片系统(SoC)由多个子模块组成,这些子模块会联合工作。在一个执行不同用例 +的操作系统中,并不是SoC中的所有模块都需要一直以最高频率工作。为了促成这一点,SoC中 +的子模块被分组为不同域,允许一些域以较低的电压和频率运行,而其它域则以较高的“电压/ +频率对”运行。 + +设备按域支持的由频率电压对组成的离散的元组的集合,被称为操作性能值(组),或OPPs。 + +举例来说: + +让我们考虑一个支持下述频率、电压值的内存保护单元(MPU)设备: +{300MHz,最低电压为1V}, {800MHz,最低电压为1.2V}, {1GHz,最低电压为1.3V} + +我们能将它们表示为3个OPP,如下述{Hz, uV}元组(译注:频率的单位是赫兹,电压的单位是 +微伏)。 + +- {300000000, 1000000} +- {800000000, 1200000} +- {1000000000, 1300000} + +1.2 操作性能值库 +---------------- + +OPP库提供了一组辅助函数来组织和查询OPP信息。该库位于drivers/opp/目录下,其头文件 +位于include/linux/pm_opp.h中。OPP库可以通过开启CONFIG_PM_OPP来启用。某些SoC, +如德州仪器的OMAP框架允许在不需要cpufreq的情况下可选地在某一OPP下启动。 + +OPP库的典型用法如下:: + + (用户) -> 注册一个默认的OPP集合 -> (库) + (SoC框架) -> 在必要的情况下,对某些OPP进行修改 -> OPP layer + -> 搜索/检索信息的查询 -> + +OPP层期望每个域由一个唯一的设备指针来表示。SoC框架在OPP层为每个设备注册了一组初始 +OPP。这个链表的长度被期望是一个最优化的小数字,通常每个设备大约5个。初始链表包含了 +一个OPP集合,这个集合被期望能在系统中安全使能。 + +关于OPP可用性的说明 +^^^^^^^^^^^^^^^^^^^ + +随着系统的运行,SoC框架可能会基于各种外部因素选择让某些OPP在每个设备上可用或不可用, +示例:温度管理或其它异常场景中,SoC框架可能会选择禁用一个较高频率的OPP以安全地继续 +运行,直到该OPP被重新启用(如果可能)。 + +OPP库在它的实现中达成了这个概念。以下操作函数只能对可用的OPP使用: +dev_pm_opp_find_freq_{ceil, floor}, dev_pm_opp_get_voltage, +dev_pm_opp_get_freq, dev_pm_opp_get_opp_count。 + +dev_pm_opp_find_freq_exact是用来查找OPP指针的,该指针可被用在dev_pm_opp_enable/ +disable函数,使一个OPP在被需要时变为可用。 + +警告:如果对一个设备调用dev_pm_opp_enable/disable函数,OPP库的用户应该使用 +dev_pm_opp_get_opp_count来刷新OPP的可用性计数。触发这些的具体机制,或者对有依赖的 +子系统(比如cpufreq)的通知机制,都是由使用OPP库的SoC特定框架酌情处理的。在这些操作 +中,同样需要注意刷新cpufreq表。 + +2. OPP链表初始注册 +================== +SoC的实现会迭代调用dev_pm_opp_add函数来增加每个设备的OPP。预期SoC框架将以最优的 +方式注册OPP条目 - 典型的数字范围小于5。通过注册OPP生成的OPP链表,在整个设备运行过程 +中由OPP库维护。SoC框架随后可以使用dev_pm_opp_enable / disable函数动态地 +控制OPP的可用性。 + +dev_pm_opp_add + 为设备指针所指向的特定域添加一个新的OPP。OPP是用频率和电压定义的。一旦完成 + 添加,OPP被认为是可用的,可以用dev_pm_opp_enable/disable函数来控制其可用性。 + OPP库内部用dev_pm_opp结构体存储并管理这些信息。这个函数可以被SoC框架根据SoC + 的使用环境的需求来定义一个最优链表。 + + 警告: + 不要在中断上下文使用这个函数。 + + 示例:: + + soc_pm_init() + { + /* 做一些事情 */ + r = dev_pm_opp_add(mpu_dev, 1000000, 900000); + if (!r) { + pr_err("%s: unable to register mpu opp(%d)\n", r); + goto no_cpufreq; + } + /* 做一些和cpufreq相关的事情 */ + no_cpufreq: + /* 做剩余的事情 */ + } + +3. OPP搜索函数 +============== +cpufreq等高层框架对频率进行操作,为了将频率映射到相应的OPP,OPP库提供了便利的函数 +来搜索OPP库内部管理的OPP链表。这些搜索函数如果找到匹配的OPP,将返回指向该OPP的指针, +否则返回错误。这些错误预计由标准的错误检查,如IS_ERR()来处理,并由调用者采取适当的 +行动。 + +这些函数的调用者应在使用完OPP后调用dev_pm_opp_put()。否则,OPP的内存将永远不会 +被释放,并导致内存泄露。 + +dev_pm_opp_find_freq_exact + 根据 *精确的* 频率和可用性来搜索OPP。这个函数对默认不可用的OPP特别有用。 + 例子:在SoC框架检测到更高频率可用的情况下,它可以使用这个函数在调用 + dev_pm_opp_enable之前找到OPP:: + + opp = dev_pm_opp_find_freq_exact(dev, 1000000000, false); + dev_pm_opp_put(opp); + /* 不要操作指针.. 只是做有效性检查.. */ + if (IS_ERR(opp)) { + pr_err("frequency not disabled!\n"); + /* 触发合适的操作.. */ + } else { + dev_pm_opp_enable(dev,1000000000); + } + + 注意: + 这是唯一一个可以搜索不可用OPP的函数。 + +dev_pm_opp_find_freq_floor + 搜索一个 *最多* 提供指定频率的可用OPP。这个函数在搜索较小的匹配或按频率 + 递减的顺序操作OPP信息时很有用。 + 例子:要找的一个设备的最高OPP:: + + freq = ULONG_MAX; + opp = dev_pm_opp_find_freq_floor(dev, &freq); + dev_pm_opp_put(opp); + +dev_pm_opp_find_freq_ceil + 搜索一个 *最少* 提供指定频率的可用OPP。这个函数在搜索较大的匹配或按频率 + 递增的顺序操作OPP信息时很有用。 + 例1:找到一个设备最小的OPP:: + + freq = 0; + opp = dev_pm_opp_find_freq_ceil(dev, &freq); + dev_pm_opp_put(opp); + + 例: 一个SoC的cpufreq_driver->target的简易实现:: + + soc_cpufreq_target(..) + { + /* 做策略检查等操作 */ + /* 找到和请求最接近的频率 */ + opp = dev_pm_opp_find_freq_ceil(dev, &freq); + dev_pm_opp_put(opp); + if (!IS_ERR(opp)) + soc_switch_to_freq_voltage(freq); + else + /* 当不能满足请求时,要做的事 */ + /* 做其它事 */ + } + +4. OPP可用性控制函数 +==================== +在OPP库中注册的默认OPP链表也许无法满足所有可能的场景。OPP库提供了一套函数来修改 +OPP链表中的某个OPP的可用性。这使得SoC框架能够精细地动态控制哪一组OPP是可用于操作 +的。设计这些函数的目的是在诸如考虑温度时 *暂时地* 删除某个OPP(例如,在温度下降 +之前不要使用某OPP)。 + +警告: + 不要在中断上下文使用这些函数。 + +dev_pm_opp_enable + 使一个OPP可用于操作。 + 例子:假设1GHz的OPP只有在SoC温度低于某个阈值时才可用。SoC框架的实现可能 + 会选择做以下事情:: + + if (cur_temp < temp_low_thresh) { + /* 若1GHz未使能,则使能 */ + opp = dev_pm_opp_find_freq_exact(dev, 1000000000, false); + dev_pm_opp_put(opp); + /* 仅仅是错误检查 */ + if (!IS_ERR(opp)) + ret = dev_pm_opp_enable(dev, 1000000000); + else + goto try_something_else; + } + +dev_pm_opp_disable + 使一个OPP不可用于操作。 + 例子:假设1GHz的OPP只有在SoC温度高于某个阈值时才可用。SoC框架的实现可能 + 会选择做以下事情:: + + if (cur_temp > temp_high_thresh) { + /* 若1GHz已使能,则关闭 */ + opp = dev_pm_opp_find_freq_exact(dev, 1000000000, true); + dev_pm_opp_put(opp); + /* 仅仅是错误检查 */ + if (!IS_ERR(opp)) + ret = dev_pm_opp_disable(dev, 1000000000); + else + goto try_something_else; + } + +5. OPP数据检索函数 +================== +由于OPP库对OPP信息进行了抽象化处理,因此需要一组函数来从dev_pm_opp结构体中提取 +信息。一旦使用搜索函数检索到一个OPP指针,以下函数就可以被SoC框架用来检索OPP层 +内部描述的信息。 + +dev_pm_opp_get_voltage + 检索OPP指针描述的电压。 + 例子: 当cpufreq切换到到不同频率时,SoC框架需要用稳压器框架将OPP描述 + 的电压设置到提供电压的电源管理芯片中:: + + soc_switch_to_freq_voltage(freq) + { + /* 做一些事情 */ + opp = dev_pm_opp_find_freq_ceil(dev, &freq); + v = dev_pm_opp_get_voltage(opp); + dev_pm_opp_put(opp); + if (v) + regulator_set_voltage(.., v); + /* 做其它事 */ + } + +dev_pm_opp_get_freq + 检索OPP指针描述的频率。 + 例子:比方说,SoC框架使用了几个辅助函数,通过这些函数,我们可以将OPP + 指针传入,而不是传入额外的参数,用来处理一系列数据参数:: + + soc_cpufreq_target(..) + { + /* 做一些事情.. */ + max_freq = ULONG_MAX; + max_opp = dev_pm_opp_find_freq_floor(dev,&max_freq); + requested_opp = dev_pm_opp_find_freq_ceil(dev,&freq); + if (!IS_ERR(max_opp) && !IS_ERR(requested_opp)) + r = soc_test_validity(max_opp, requested_opp); + dev_pm_opp_put(max_opp); + dev_pm_opp_put(requested_opp); + /* 做其它事 */ + } + soc_test_validity(..) + { + if(dev_pm_opp_get_voltage(max_opp) < dev_pm_opp_get_voltage(requested_opp)) + return -EINVAL; + if(dev_pm_opp_get_freq(max_opp) < dev_pm_opp_get_freq(requested_opp)) + return -EINVAL; + /* 做一些事情.. */ + } + +dev_pm_opp_get_opp_count + 检索某个设备可用的OPP数量。 + 例子:假设SoC中的一个协处理器需要知道某个表中的可用频率,主处理器可以 + 按如下方式发出通知:: + + soc_notify_coproc_available_frequencies() + { + /* 做一些事情 */ + num_available = dev_pm_opp_get_opp_count(dev); + speeds = kzalloc(sizeof(u32) * num_available, GFP_KERNEL); + /* 按升序填充表 */ + freq = 0; + while (!IS_ERR(opp = dev_pm_opp_find_freq_ceil(dev, &freq))) { + speeds[i] = freq; + freq++; + i++; + dev_pm_opp_put(opp); + } + + soc_notify_coproc(AVAILABLE_FREQs, speeds, num_available); + /* 做其它事 */ + } + +6. 数据结构 +=========== +通常,一个SoC包含多个可变电压域。每个域由一个设备指针描述。和OPP之间的关系可以 +按以下方式描述:: + + SoC + |- device 1 + | |- opp 1 (availability, freq, voltage) + | |- opp 2 .. + ... ... + | `- opp n .. + |- device 2 + ... + `- device m + +OPP库维护着一个内部链表,SoC框架使用上文描述的各个函数来填充和访问。然而,描述 +真实OPP和域的结构体是OPP库自身的内部组成,以允许合适的抽象在不同系统中得到复用。 + +struct dev_pm_opp + OPP库的内部数据结构,用于表示一个OPP。除了频率、电压、可用性信息外, + 它还包含OPP库运行所需的内部统计信息。指向这个结构体的指针被提供给 + 用户(比如SoC框架)使用,在与OPP层的交互中作为OPP的标识符。 + + 警告: + 结构体dev_pm_opp的指针不应该由用户解析或修改。一个实例的默认值由 + dev_pm_opp_add填充,但OPP的可用性由dev_pm_opp_enable/disable函数 + 修改。 + +struct device + 这用于向OPP层标识一个域。设备的性质和它的实现是由OPP库的用户决定的, + 如SoC框架。 + +总体来说,以一个简化的视角看,对数据结构的操作可以描述为下面各图:: + + 初始化 / 修改: + +-----+ /- dev_pm_opp_enable + dev_pm_opp_add --> | opp | <------- + | +-----+ \- dev_pm_opp_disable + \-------> domain_info(device) + + 搜索函数: + /-- dev_pm_opp_find_freq_ceil ---\ +-----+ + domain_info<---- dev_pm_opp_find_freq_exact -----> | opp | + \-- dev_pm_opp_find_freq_floor ---/ +-----+ + + 检索函数: + +-----+ /- dev_pm_opp_get_voltage + | opp | <--- + +-----+ \- dev_pm_opp_get_freq + + domain_info <- dev_pm_opp_get_opp_count diff --git a/Documentation/translations/zh_CN/process/1.Intro.rst b/Documentation/translations/zh_CN/process/1.Intro.rst new file mode 100644 index 000000000..4f9284cbe --- /dev/null +++ b/Documentation/translations/zh_CN/process/1.Intro.rst @@ -0,0 +1,195 @@ +.. include:: ../disclaimer-zh_CN.rst + +:Original: :ref:`Documentation/process/1.Intro.rst <development_process_intro>` + +:Translator: + + 时奎亮 Alex Shi <alex.shi@linux.alibaba.com> + +:校译: + + 吴想成 Wu XiangCheng <bobwxc@email.cn> + +.. _cn_development_process_intro: + +引言 +==== + +内容提要 +-------- + +本节的其余部分涵盖了内核开发的过程,以及开发人员及其雇主在这方面可能遇到的 +各种问题。有很多原因使内核代码应被合并到正式的(“主线”)内核中,包括对用户 +的自动可用性、多种形式的社区支持以及影响内核开发方向的能力。提供给Linux内核 +的代码必须在与GPL兼容的许可证下可用。 + +:ref:`cn_development_process` 介绍了开发过程、内核发布周期和合并窗口的机制。 +涵盖了补丁开发、审查和合并周期中的各个阶段。还有一些关于工具和邮件列表的讨论? +鼓励希望开始内核开发的开发人员跟踪并修复缺陷以作为初步练习。 + + +:ref:`cn_development_early_stage` 包括项目的早期规划,重点是尽快让开发社区 +参与进来。 + +:ref:`cn_development_coding` 是关于编程过程的;介绍了其他开发人员遇到的几个 +陷阱。也涵盖了对补丁的一些要求,并且介绍了一些工具,这些工具有助于确保内核 +补丁是正确的。 + +:ref:`cn_development_posting` 描述发布补丁以供评审的过程。为了让开发社区能 +认真对待,补丁必须被正确格式化和描述,并且必须发送到正确的地方。遵循本节中的 +建议有助于确保您的工作能被较好地接纳。 + +:ref:`cn_development_followthrough` 介绍了发布补丁之后发生的事情;工作在这时 +还远远没有完成。与审阅者一起工作是开发过程中的一个重要部分;本节提供了一些 +关于如何在这个重要阶段避免问题的提示。当补丁被合并到主线中时,开发人员要注意 +不要假定任务已经完成。 + +:ref:`cn_development_advancedtopics` 介绍了两个“高级”主题:使用Git管理补丁 +和查看其他人发布的补丁。 + +:ref:`cn_development_conclusion` 总结了有关内核开发的更多信息,附带有相关资源 +链接。 + +这个文档是关于什么的 +-------------------- + +Linux内核有超过800万行代码,每个版本的贡献者超过1000人,是现存最大、最活跃的 +免费软件项目之一。从1991年开始,这个内核已经发展成为一个最好的操作系统组件, +运行在袖珍数字音乐播放器、台式电脑、现存最大的超级计算机以及所有类型的系统上。 +它是一种适用于几乎任何情况的健壮、高效和可扩展的解决方案。 + +随着Linux的发展,希望参与其开发的开发人员(和公司)的数量也在增加。硬件供应商 +希望确保Linux能够很好地支持他们的产品,使这些产品对Linux用户具有吸引力。嵌入 +式系统供应商使用Linux作为集成产品的组件,希望Linux能够尽可能地胜任手头的任务。 +分销商和其他基于Linux的软件供应商切实关心Linux内核的功能、性能和可靠性。最终 +用户也常常希望修改Linux,使之能更好地满足他们的需求。 + +Linux最引人注目的特性之一是这些开发人员可以访问它;任何具备必要技能的人都可以 +改进Linux并影响其开发方向。专有产品不能提供这种开放性,这是自由软件的一个特点。 +如果有什么不同的话,那就是内核比大多数其他自由软件项目更开放。一个典型的三个 +月内核开发周期可以涉及1000多个开发人员,他们为100多个不同的公司(或者根本不 +隶属公司)工作。 + +与内核开发社区合作并不是特别困难。但尽管如此,仍有许多潜在的贡献者在尝试做 +内核工作时遇到了困难。内核社区已经发展出自己独特的操作方式,使其能够在每天 +都要更改数千行代码的环境中顺利运行(并生成高质量的产品)。因此,Linux内核开发 +过程与专有的开发模式有很大的不同也就不足为奇了。 + +对于新开发人员来说,内核的开发过程可能会让人感到奇怪和恐惧,但这背后有充分的 +理由和坚实的经验。一个不了解内核社区工作方式的开发人员(或者更糟的是,他们 +试图抛弃或规避之)会得到令人沮丧的体验。开发社区在帮助那些试图学习的人的同时, +没有时间帮助那些不愿意倾听或不关心开发过程的人。 + +希望阅读本文的人能够避免这种令人沮丧的经历。这些材料很长,但阅读它们时所做的 +努力会在短时间内得到回报。开发社区总是需要能让内核变更好的开发人员;下面的 +文字应该帮助您或为您工作的人员加入我们的社区。 + +致谢 +---- + +本文档由Jonathan Corbet <corbet@lwn.net> 撰写。以下人员的建议使之更为完善: +Johannes Berg, James Berry, Alex Chiang, Roland Dreier, Randy Dunlap, +Jake Edge, Jiri Kosina, Matt Mackall, Arthur Marsh, Amanda McPherson, +Andrew Morton, Andrew Price, Tsugikazu Shibata 和 Jochen Voß 。 + +这项工作得到了Linux基金会的支持,特别感谢Amanda McPherson,他看到了这项工作 +的价值并将其变成现实。 + +代码进入主线的重要性 +-------------------- + +有些公司和开发人员偶尔会想,为什么他们要费心学习如何与内核社区合作,并将代码 +放入主线内核(“主线”是由Linus Torvalds维护的内核,Linux发行商将其用作基础)。 +在短期内,贡献代码看起来像是一种可以避免的开销;维护独立代码并直接支持用户 +似乎更容易。事实上,保持代码独立(“树外”)是在经济上是错误的。 + +为了说明树外代码成本,下面给出内核开发过程的一些相关方面;本文稍后将更详细地 +讨论其中的大部分内容。请考虑: + +- 所有Linux用户都可以使用合并到主线内核中的代码。它将自动出现在所有启用它的 + 发行版上。无需驱动程序磁盘、额外下载,也不需要为多个发行版的多个版本提供 + 支持;这一切将方便所有开发人员和用户。并入主线解决了大量的分发和支持问题。 + +- 当内核开发人员努力维护一个稳定的用户空间接口时,内核内部API处于不断变化之中。 + 不维持稳定的内部接口是一个慎重的设计决策;它允许在任何时候进行基本的改进, + 并产出更高质量的代码。但该策略导致结果是,若要使用新的内核,任何树外代码都 + 需要持续的维护。维护树外代码会需要大量的工作才能使代码保持正常运行。 + + 相反,位于主线中的代码不需要这样做,因为基本规则要求进行API更改的任何开发 + 人员也必须修复由于该更改而破坏的任何代码。因此,合并到主线中的代码大大降低 + 了维护成本。 + +- 除此之外,内核中的代码通常会被其他开发人员改进。您授权的用户社区和客户对您 + 产品的改进可能会令人惊喜。 + +- 内核代码在合并到主线之前和之后都要经过审查。无论原始开发人员的技能有多强, + 这个审查过程总是能找到改进代码的方法。审查经常发现严重的错误和安全问题。 + 对于在封闭环境中开发的代码尤其如此;这种代码从外部开发人员的审查中获益匪浅。 + 树外代码是低质量代码。 + +- 参与开发过程是您影响内核开发方向的方式。旁观者的抱怨会被听到,但是活跃的 + 开发人员有更强的声音——并且能够实现使内核更好地满足其需求的更改。 + +- 当单独维护代码时,总是存在第三方为类似功能提供不同实现的可能性。如果发生 + 这种情况,合并代码将变得更加困难——甚至成为不可能。之后,您将面临以下令人 + 不快的选择:(1)无限期地维护树外的非标准特性,或(2)放弃代码并将用户迁移 + 到树内版本。 + +- 代码的贡献是使整个流程工作的根本。通过贡献代码,您可以向内核添加新功能,并 + 提供其他内核开发人员使用的功能和示例。如果您已经为Linux开发了代码(或者正在 + 考虑这样做),那么您显然对这个平台的持续成功感兴趣;贡献代码是确保成功的 + 最好方法之一。 + +上述所有理由都适用于任何树外内核代码,包括以专有的、仅二进制形式分发的代码。 +然而,在考虑任何类型的纯二进制内核代码分布之前,还需要考虑其他因素。包括: + +- 围绕专有内核模块分发的法律问题其实较为模糊;相当多的内核版权所有者认为, + 大多数仅二进制的模块是内核的派生产品,因此,它们的分发违反了GNU通用公共 + 许可证(下面将详细介绍)。本文作者不是律师,本文档中的任何内容都不可能被 + 视为法律建议。封闭源代码模块的真实法律地位只能由法院决定。但不管怎样,困扰 + 这些模块的不确定性仍然存在。 + +- 二进制模块大大增加了调试内核问题的难度,以至于大多数内核开发人员甚至都不会 + 尝试。因此,只分发二进制模块将使您的用户更难从社区获得支持。 + +- 对于仅二进制的模块的发行者来说,支持也更加困难,他们必须为他们希望支持的 + 每个发行版和每个内核版本提供不同版本的模块。为了提供较为全面的覆盖范围, + 可能需要一个模块的几十个构建,并且每次升级内核时,您的用户都必须单独升级 + 这些模块。 + +- 上面提到的关于代码评审的所有问题都更加存在于封闭源代码中。由于该代码根本 + 不可得,因此社区无法对其进行审查,毫无疑问,它将存在严重问题。 + +尤其是嵌入式系统的制造商,可能会倾向于忽视本节中所说的大部分内容;因为他们 +相信自己正在商用一种使用冻结内核版本的独立产品,在发布后不需要再进行开发。 +这个论点忽略了广泛的代码审查的价值以及允许用户向产品添加功能的价值。但这些 +产品的商业寿命有限,之后必须发布新版本的产品。在这一点上,代码在主线上并得到 +良好维护的供应商将能够更好地占位,以使新产品快速上市。 + +许可 +---- + +代码是根据一些许可证提供给Linux内核的,但是所有代码都必须与GNU通用公共许可 +证(GPLV2)的版本2兼容,该版本是覆盖整个内核分发的许可证。在实践中,这意味 +着所有代码贡献都由GPLv2(可选地,语言允许在更高版本的GPL下分发)或3子句BSD +许可(New BSD License,译者注)覆盖。任何不包含在兼容许可证中的贡献都不会 +被接受到内核中。 + +贡献给内核的代码不需要(或请求)版权分配。合并到主线内核中的所有代码都保留 +其原始所有权;因此,内核现在拥有数千个所有者。 + +这种所有权结构也暗示着,任何改变内核许可的尝试都注定会失败。很少有实际情况 +可以获得所有版权所有者的同意(或者从内核中删除他们的代码)。因此,尤其是在 +可预见的将来,许可证不大可能迁移到GPL的版本3。 + +所有贡献给内核的代码都必须是合法的免费软件。因此,不接受匿名(或化名)贡献 +者的代码。所有贡献者都需要在他们的代码上“sign off(签发)”,声明代码可以 +在GPL下与内核一起分发。无法提供未被其所有者许可为免费软件的代码,或可能为 +内核造成版权相关问题的代码(例如,由缺乏适当保护的反向工程工作派生的代码) +不能被接受。 + +有关版权问题的提问在Linux开发邮件列表中很常见。这样的问题通常会得到不少答案, +但请记住,回答这些问题的人不是律师,不能提供法律咨询。如果您有关于Linux源代码 +的法律问题,没有什么可以代替咨询了解这一领域的律师。依赖从技术邮件列表中获得 +的答案是一件冒险的事情。 + diff --git a/Documentation/translations/zh_CN/process/2.Process.rst b/Documentation/translations/zh_CN/process/2.Process.rst new file mode 100644 index 000000000..4a6ed0219 --- /dev/null +++ b/Documentation/translations/zh_CN/process/2.Process.rst @@ -0,0 +1,365 @@ +.. include:: ../disclaimer-zh_CN.rst + +:Original: :ref:`Documentation/process/2.Process.rst <development_process>` + +:Translator: + + 时奎亮 Alex Shi <alex.shi@linux.alibaba.com> + +:校译: + + 吴想成 Wu XiangCheng <bobwxc@email.cn> + +.. _cn_development_process: + +开发流程如何进行 +================ + +90年代早期的Linux内核开发是一件相当松散的事情,涉及的用户和开发人员相对较少。 +由于拥有数以百万计的用户群,且每年有大约2000名开发人员参与进来,内核因此必须 +发展出许多既定流程来保证开发的顺利进行。要参与到流程中来,需要对此流程的进行 +方式有一个扎实的理解。 + +总览 +---- + +内核开发人员使用一个松散的基于时间的发布过程,每两到三个月发布一次新的主要 +内核版本。最近的发布历史记录如下: + + ====== ================= + 5.0 2019年3月3日 + 5.1 2019年5月5日 + 5.2 2019年7月7日 + 5.3 2019年9月15日 + 5.4 2019年11月24日 + 5.5 2020年1月6日 + ====== ================= + +每个5.x版本都是一个主要的内核版本,具有新特性、内部API更改等等。一个典型的5.x +版本包含大约13000个变更集,变更了几十万行代码。因此,5.x是Linux内核开发的前 +沿;内核使用滚动开发模型,不断集成重大变化。 + +对于每个版本的补丁合并,遵循一个相对简单的规则。在每个开发周期的开头,“合并 +窗口”被打开。这时,被认为足够稳定(并且被开发社区接受)的代码被合并到主线内 +核中。在这段时间内,新开发周期的大部分变更(以及所有主要变更)将以接近每天 +1000次变更(“补丁”或“变更集”)的速度合并。 + +(顺便说一句,值得注意的是,合并窗口期间集成的更改并不是凭空产生的;它们是经 +提前收集、测试和分级的。稍后将详细描述该过程的工作方式。) + +合并窗口持续大约两周。在这段时间结束时,Linus Torvalds将声明窗口已关闭,并 +释放第一个“rc”内核。例如,对于目标为5.6的内核,在合并窗口结束时发生的释放 +将被称为5.6-rc1。-rc1 版本是一个信号,表示合并新特性的时间已经过去,稳定下一 +个内核的时间已经到来。 + +在接下来的6到10周内,只有修复问题的补丁才应该提交给主线。有时会允许更大的 +更改,但这种情况很少发生;试图在合并窗口外合并新功能的开发人员往往受不到 +友好的接待。一般来说,如果您错过了给定特性的合并窗口,最好的做法是等待下一 +个开发周期。(偶尔会对未支持硬件的驱动程序进行例外;如果它们不改变已有代码, +则不会导致回归,应该可以随时被安全地加入)。 + +随着修复程序进入主线,补丁速度将随着时间的推移而变慢。Linus大约每周发布一次 +新的-rc内核;在内核被认为足够稳定并最终发布前,一般会达到-rc6到-rc9之间。 +然后,整个过程又重新开始了。 + +例如,这里是5.4的开发周期进行情况(2019年): + + ============== ============================== + 九月 15 5.3 稳定版发布 + 九月 30 5.4-rc1 合并窗口关闭 + 十月 6 5.4-rc2 + 十月 13 5.4-rc3 + 十月 20 5.4-rc4 + 十月 27 5.4-rc5 + 十一月 3 5.4-rc6 + 十一月 10 5.4-rc7 + 十一月 17 5.4-rc8 + 十一月 24 5.4 稳定版发布 + ============== ============================== + +开发人员如何决定何时结束开发周期并创建稳定版本?最重要的指标是以前版本的 +回归列表。不欢迎出现任何错误,但是那些破坏了以前能工作的系统的错误被认为是 +特别严重的。因此,导致回归的补丁是不受欢迎的,很可能在稳定期内删除。 + +开发人员的目标是在稳定发布之前修复所有已知的回归。在现实世界中,这种完美是 +很难实现的;在这种规模的项目中,变数太多了。需要说明的是,延迟最终版本只会 +使问题变得更糟;等待下一个合并窗口的更改将变多,导致下次出现更多的回归错误。 +因此,大多数5.x内核都有一些已知的回归错误,不过,希望没有一个是严重的。 + +一旦一个稳定的版本发布,它的持续维护工作就被移交给“稳定团队”,目前由 +Greg Kroah-Hartman领导。稳定团队将使用5.x.y编号方案不定期地发布稳定版本的 +更新。要合入更新版本,补丁必须(1)修复一个重要的缺陷,且(2)已经合并到 +下一个开发版本主线中。内核通常会在其初始版本后的一个以上的开发周期内收到 +稳定版更新。例如,5.2内核的历史如下(2019年): + + ============== =============================== + 七月 7 5.2 稳定版发布 + 七月 13 5.2.1 + 七月 21 5.2.2 + 七月 26 5.2.3 + 七月 28 5.2.4 + 七月 31 5.2.5 + ... ... + 十月 11 5.2.21 + ============== =============================== + +5.2.21是5.2版本的最终稳定更新。 + +有些内核被指定为“长期”内核;它们将得到更长时间的支持。在本文中,当前的长期 +内核及其维护者是: + + ====== ================================ ================ + 3.16 Ben Hutchings (长期稳定内核) + 4.4 Greg Kroah-Hartman & Sasha Levin (长期稳定内核) + 4.9 Greg Kroah-Hartman & Sasha Levin + 4.14 Greg Kroah-Hartman & Sasha Levin + 4.19 Greg Kroah-Hartman & Sasha Levin + 5.4 Greg Kroah-Hartman & Sasha Levin + ====== ================================ ================ + +长期支持内核的选择纯粹是维护人员是否有需求和时间来维护该版本的问题。 +目前还没有为即将发布的任何特定版本提供长期支持的已知计划。 + +补丁的生命周期 +-------------- + +补丁不会直接从开发人员的键盘进入主线内核。相反,有一个稍微复杂(如果有些非 +正式)的过程,旨在确保对每个补丁进行质量审查,并确保每个补丁实现了一个在主线 +中需要的更改。对于小的修复,这个过程可能会很快完成,,而对于较大或有争议的 +变更,可能会持续数年。许多开发人员的沮丧来自于对这个过程缺乏理解或者试图绕过它。 + +为了减少这种挫败,本文将描述补丁如何进入内核。下面的介绍以一种较为理想化的 +方式描述了这个过程。更详细的过程将在后面的章节中介绍。 + +补丁通常要经历以下阶段: + +- 设计。这就是补丁的真正需求——以及满足这些需求的方式——所在。设计工作通常 + 是在不涉及社区的情况下完成的,但是如果可能的话,最好是在公开的情况下完成 + 这项工作;这样可以节省很多稍后再重新设计的时间。 + +- 早期评审。补丁被发布到相关的邮件列表中,列表中的开发人员会回复他们可能有 + 的任何评论。如果一切顺利的话,这个过程应该会发现补丁的任何主要问题。 + +- 更广泛的评审。当补丁接近准备好纳入主线时,它应该被相关的子系统维护人员 + 接受——尽管这种接受并不能保证补丁会一直延伸到主线。补丁将出现在维护人员的 + 子系统树中,并进入 -next 树(如下所述)。当流程进行时,此步骤将会对补丁 + 进行更广泛的审查,并发现由于将此补丁与其他人所做的工作合并而导致的任何 + 问题。 + +- 请注意,大多数维护人员也有日常工作,因此合并补丁可能不是他们的最优先工作。 + 如果您的补丁得到了需要更改的反馈,那么您应该进行这些更改,或者解释为何 + 不应该进行这些更改。如果您的补丁没有评审意见,也没有被其相应的子系统或 + 驱动程序维护者接受,那么您应该坚持不懈地将补丁更新到当前内核使其可被正常 + 应用,并不断地发送它以供审查和合并。 + +- 合并到主线。最终,一个成功的补丁将被合并到由LinusTorvalds管理的主线存储库 + 中。此时可能会出现更多的评论和/或问题;对开发人员来说应对这些问题并解决 + 出现的任何问题仍很重要。 + +- 稳定版发布。大量用户可能受此补丁影响,因此可能再次出现新的问题。 + +- 长期维护。虽然开发人员在合并代码后可能会忘记代码,但这种行为往往会给开发 + 社区留下不良印象。合并代码消除了一些维护负担,因为其他人将修复由API更改 + 引起的问题。但是,如果代码要长期保持可用,原始开发人员应该继续为代码负责。 + +内核开发人员(或他们的雇主)犯的最大错误之一是试图将流程简化为一个“合并到 +主线”步骤。这种方法总是会让所有相关人员感到沮丧。 + +补丁如何进入内核 +---------------- + +只有一个人可以将补丁合并到主线内核存储库中:Linus Torvalds。但是,在进入 +2.6.38内核的9500多个补丁中,只有112个(大约1.3%)是由Linus自己直接选择的。 +内核项目已经发展到一个没有一个开发人员可以在没有支持的情况下检查和选择每个 +补丁的规模。内核开发人员处理这种增长的方式是使用围绕信任链构建的助理系统。 + +内核代码库在逻辑上被分解为一组子系统:网络、特定体系结构支持、内存管理、视 +频设备等。大多数子系统都有一个指定的维护人员,其总体负责该子系统中的代码。 +这些子系统维护者(松散地)是他们所管理的内核部分的“守门员”;他们(通常) +会接受一个补丁以包含到主线内核中。 + +子系统维护人员每个人都管理着自己版本的内核源代码树,通常(并非总是)使用Git。 +Git等工具(以及Quilt或Mercurial等相关工具)允许维护人员跟踪补丁列表,包括作者 +信息和其他元数据。在任何给定的时间,维护人员都可以确定他或她的存储库中的哪 +些补丁在主线中找不到。 + +当合并窗口打开时,顶级维护人员将要求Linus从存储库中“拉出”他们为合并选择 +的补丁。如果Linus同意,补丁流将流向他的存储库,成为主线内核的一部分。 +Linus对拉取中接收到的特定补丁的关注程度各不相同。很明显,有时他看起来很 +关注。但是一般来说,Linus相信子系统维护人员不会向上游发送坏补丁。 + +子系统维护人员反过来也可以从其他维护人员那里获取补丁。例如,网络树是由首先 +在专用于网络设备驱动程序、无线网络等的树中积累的补丁构建的。此存储链可以 +任意长,但很少超过两个或三个链接。由于链中的每个维护者都信任那些管理较低 +级别树的维护者,所以这个过程称为“信任链”。 + +显然,在这样的系统中,获取内核补丁取决于找到正确的维护者。直接向Linus发送 +补丁通常不是正确的方法。 + +Next 树 +------- + +子系统树链引导补丁流到内核,但它也提出了一个有趣的问题:如果有人想查看为 +下一个合并窗口准备的所有补丁怎么办?开发人员将感兴趣的是,还有什么其他的 +更改有待解决,以了解是否存在需要担心的冲突;例如,更改核心内核函数原型的 +修补程序将与使用该函数旧形式的任何其他修补程序冲突。审查人员和测试人员希望 +在所有这些变更到达主线内核之前,能够访问它们的集成形式的变更。您可以从所有 +相关的子系统树中提取更改,但这将是一项复杂且容易出错的工作。 + +解决方案以-next树的形式出现,在这里子系统树被收集以供测试和审查。这些树中 +由Andrew Morton维护的较老的一个,被称为“-mm”(用于内存管理,创建时为此)。 +-mm 树集成了一长串子系统树中的补丁;它还包含一些旨在帮助调试的补丁。 + +除此之外,-mm 还包含大量由Andrew直接选择的补丁。这些补丁可能已经发布在邮件 +列表上,或者它们可能应用于内核中未指定子系统树的部分。同时,-mm 作为最后 +手段的子系统树;如果没有其他明显的路径可以让补丁进入主线,那么它很可能最 +终选择-mm 树。累积在-mm 中的各种补丁最终将被转发到适当的子系统树,或者直接 +发送到Linus。在典型的开发周期中,大约5-10%的补丁通过-mm 进入主线。 + +当前-mm 补丁可在“mmotm”(-mm of the moment)目录中找到: + + https://www.ozlabs.org/~akpm/mmotm/ + +然而,使用MMOTM树可能会十分令人头疼;它甚至可能无法编译。 + +下一个周期补丁合并的主要树是linux-next,由Stephen Rothwell 维护。根据设计 +linux-next 是下一个合并窗口关闭后主线的快照。linux-next树在Linux-kernel 和 +Linux-next 邮件列表中发布,可从以下位置下载: + + https://www.kernel.org/pub/linux/kernel/next/ + +Linux-next 已经成为内核开发过程中不可或缺的一部分;在一个给定的合并窗口中合并 +的所有补丁都应该在合并窗口打开之前的一段时间内找到进入Linux-next 的方法。 + +Staging 树 +---------- + +内核源代码树包含drivers/staging/目录,其中有许多驱动程序或文件系统的子目录 +正在被添加到内核树中。它们在仍然需要更多的修正的时候可以保留在driver/staging/ +目录中;一旦完成,就可以将它们移到内核中。这是一种跟踪不符合Linux内核编码或 +质量标准的驱动程序的方法,人们可能希望使用它们并跟踪开发。 + +Greg Kroah Hartman 目前负责维护staging 树。仍需要修正的驱动程序将发送给他, +每个驱动程序在drivers/staging/中都有自己的子目录。除了驱动程序源文件之外, +目录中还应该有一个TODO文件。TODO文件列出了驱动程序需要接受的暂停的工作, +以及驱动程序的任何补丁都应该抄送的人员列表。当前的规则要求,staging的驱动 +程序必须至少正确编译。 + +Staging 是一种让新的驱动程序进入主线的相对容易的方法,它们会幸运地引起其他 +开发人员的注意,并迅速改进。然而,进入staging并不是故事的结尾;staging中 +没有看到常规进展的代码最终将被删除。经销商也倾向于相对不愿意使用staging驱动 +程序。因此,在成为一个合适的主线驱动的路上,staging 仅是一个中转站。 + +工具 +---- + +从上面的文本可以看出,内核开发过程在很大程度上依赖于在不同方向上聚集补丁的 +能力。如果没有适当强大的工具,整个系统将无法在任何地方正常工作。关于如何使用 +这些工具的教程远远超出了本文档的范围,但还是用一点篇幅介绍一些关键点。 + +到目前为止,内核社区使用的主要源代码管理系统是git。Git是在自由软件社区中开发 +的许多分布式版本控制系统之一。它非常适合内核开发,因为它在处理大型存储库和 +大量补丁时性能非常好。它也以难以学习和使用而著称,尽管随着时间的推移它变得 +更好了。对于内核开发人员来说,对Git的某种熟悉几乎是一种要求;即使他们不将它 +用于自己的工作,他们也需要Git来跟上其他开发人员(以及主线)正在做的事情。 + +现在几乎所有的Linux发行版都打包了Git。Git主页位于: + + https://git-scm.com/ + +此页面包含了文档和教程的链接。 + +在不使用git的内核开发人员中,最流行的选择几乎肯定是Mercurial: + + http://www.seleric.com/mercurial/ + +Mercurial与Git共享许多特性,但它提供了一个界面,许多人觉得它更易于使用。 + +另一个值得了解的工具是Quilt: + + https://savannah.nongnu.org/projects/quilt + +Quilt 是一个补丁管理系统,而不是源代码管理系统。它不会随着时间的推移跟踪历史; +相反,它面向根据不断发展的代码库跟踪一组特定的更改。一些主要的子系统维护人员 +使用Quilt来管理打算向上游移动的补丁。对于某些树的管理(例如-mm),quilt 是 +最好的工具。 + +邮件列表 +-------- + +大量的Linux内核开发工作是通过邮件列表完成的。如果不加入至少一个某个列表, +就很难成为社区中的一个“全功能”成员。但是,Linux邮件列表对开发人员来说也是 +一个潜在的危险,他们可能会被一堆电子邮件淹没、违反Linux列表上使用的约定, +或者两者兼而有之。 + +大多数内核邮件列表都在vger.kernel.org上运行;主列表位于: + + http://vger.kernel.org/vger-lists.html + +不过,也有一些列表托管在别处;其中一些列表位于 +redhat.com/mailman/listinfo。 + +当然,内核开发的核心邮件列表是linux-kernel。这个列表是一个令人生畏的地方: +每天的信息量可以达到500条,噪音很高,谈话技术性很强,且参与者并不总是表现出 +高度的礼貌。但是,没有其他地方可以让内核开发社区作为一个整体聚集在一起; +不使用此列表的开发人员将错过重要信息。 + +以下一些提示可以帮助在linux-kernel生存: + +- 将邮件转移到单独的文件夹,而不是主邮箱文件夹。我们必须能够持续地忽略洪流。 + +- 不要试图跟上每一次谈话——没人会这样。重要的是要筛选感兴趣的主题(但请注意 + 长时间的对话可能会偏离原来的主题,尽管未改变电子邮件的主题)和参与的人。 + +- 不要回复挑事的人。如果有人试图激起愤怒,请忽略他们。 + +- 当回复Linux内核电子邮件(或其他列表上的电子邮件)时,请为所有相关人员保留 + Cc: 抄送头。如果没有确实的理由(如明确的请求),则不应删除收件人。一定要 + 确保你要回复的人在抄送列表中。这个惯例也使你不必在回复邮件时明确要求被抄送。 + +- 在提出问题之前,搜索列表存档(和整个网络)。有些开发人员可能会对那些显然 + 没有完成家庭作业的人感到不耐烦。 + +- 避免顶部回复(把你的答案放在你要回复的引文上面的做法)。这会让你的回答更难 + 理解,印象也很差。 + +- 在正确的邮件列表发问。linux-kernel 可能是通用的讨论场所,但它不是寻找所有 + 子系统开发人员的最佳场所。 + +最后一点——找到正确的邮件列表——是开发人员常出错的地方。在linux-kernel上 +提出与网络相关的问题的人几乎肯定会收到一个礼貌的建议,转到netdev列表上提出, +因为这是大多数网络开发人员经常出现的列表。还有其他列表可用于scsi、video4linux、 +ide、filesystem等子系统。查找邮件列表的最佳位置是与内核源代码一起打包的 +MAINTAINERS文件。 + +开始内核开发 +------------ + +关于如何开始内核开发过程的问题很常见——个人和公司皆然。同样常见的是失误,这 +使得关系的开始比本应的更困难。 + +公司通常希望聘请知名的开发人员来启动开发团队。实际上,这是一种有效的技术。 +但它也往往是昂贵的,而且对增加有经验的内核开发人员的数量没有多大帮助。考 +虑到时间投入,可以让内部开发人员加快Linux内核的开发速度。利用这段时间可以 +让雇主拥有一批既了解内核又了解公司的开发人员,还可以帮助培训其他人。从中期 +来看,这通常是更有利可图的方法。 + +可以理解的是,单个开发人员往往对起步感到茫然。从一个大型项目开始可能会很 +吓人;人们往往想先用一些较小的东西来试试水。由此,一些开发人员开始创建修补 +拼写错误或轻微编码风格问题的补丁。不幸的是,这样的补丁会产生一定程度的噪音, +这会分散整个开发社区的注意力,因此,它们越来越被人不看重。希望向社区介绍 +自己的新开发人员将无法通过这些方式获得他们期待的反响。 + +Andrew Morton 为有抱负的内核开发人员提供了如下建议 + +:: + + 所有内核开发者的第一个项目肯定应该是“确保内核在您可以操作的所有 + 机器上始终完美运行”。通常的方法是和其他人一起解决问题(这可能需 + 要坚持!),但就是如此——这是内核开发的一部分。 + +(http://lwn.net/articles/283982/) + +在没有明显问题需要解决的情况下,通常建议开发人员查看当前的回归和开放缺陷 +列表。从来都不缺少需要解决的问题;通过解决这些问题,开发人员将从该过程获得 +经验,同时与开发社区的其他成员建立相互尊重。 diff --git a/Documentation/translations/zh_CN/process/3.Early-stage.rst b/Documentation/translations/zh_CN/process/3.Early-stage.rst new file mode 100644 index 000000000..de53dd12e --- /dev/null +++ b/Documentation/translations/zh_CN/process/3.Early-stage.rst @@ -0,0 +1,168 @@ +.. include:: ../disclaimer-zh_CN.rst + +:Original: :ref:`Documentation/process/3.Early-stage.rst <development_early_stage>` + +:Translator: + + 时奎亮 Alex Shi <alex.shi@linux.alibaba.com> + +:校译: + + 吴想成 Wu XiangCheng <bobwxc@email.cn> + +.. _cn_development_early_stage: + +早期规划 +======== + +当考虑一个Linux内核开发项目时,很可能会直接跳进去开始编码。然而,与任何重要 +的项目一样,许多成功的基础最好是在第一行代码编写之前就打下。在早期计划和 +沟通中花费一些时间可以在之后节省更多的时间。 + +搞清问题 +-------- + +与任何工程项目一样,成功的内核改善从清晰描述要解决的问题开始。在某些情况 +下,这个步骤很容易:例如当某个特定硬件需要驱动程序时。不过,在其他情况下, +很容易将实际问题与建议的解决方案混在一起,这可能会导致麻烦。 + +举个例子:几年前,Linux音频的开发人员寻求一种方法来运行应用程序,而不会因 +系统延迟过大而导致退出或其他问题。他们得到的解决方案是一个连接到Linux安全 +模块(LSM)框架中的内核模块;这个模块可以配置为允许特定的应用程序访问实时 +调度程序。这个模块被实现并发到linux-kernel邮件列表,在那里它立即遇到了麻烦。 + +对于音频开发人员来说,这个安全模块足以解决他们当前的问题。但是,对于更广泛的 +内核社区来说,这被视为对LSM框架的滥用(LSM框架并不打算授予他们原本不具备的 +进程特权),并对系统稳定性造成风险。他们首选的解决方案包括短期的通过rlimit +机制进行实时调度访问,以及长期的减少延迟的工作。 + +然而,音频社区无法超越他们实施的特定解决方案来看问题;他们不愿意接受替代方案。 +由此产生的分歧使这些开发人员对整个内核开发过程感到失望;其中一个开发人员返回 +到audio列表并发布了以下内容: + + 有很多非常好的Linux内核开发人员,但他们往往会被一群傲慢的傻瓜所压倒。 + 试图向这些人传达用户需求是浪费时间。他们太“聪明”了,根本听不到少数 + 人的话。 + +(http://lwn.net/articles/131776/) + +实际情况却是不同的;与特定模块相比,内核开发人员更关心系统稳定性、长期维护 +以及找到问题的正确解决方案。这个故事的寓意是把重点放在问题上——而不是具体的 +解决方案上——并在开始编写代码之前与开发社区讨论这个问题。 + +因此,在考虑一个内核开发项目时,我们应该得到一组简短问题的答案: + + - 需要解决的问题究竟是什么? + + - 受此问题影响的用户有哪些?解决方案应该解决哪些使用案例? + + - 内核现在为何没能解决这个问题? + +只有这样,才能开始考虑可能的解决方案。 + + +早期讨论 +-------- + +在计划内核开发项目时,在开始实施之前与社区进行讨论是很有意义的。早期沟通可以 +通过多种方式节省时间和麻烦: + + - 很可能问题是由内核以您不理解的方式解决的。Linux内核很大,具有许多不明显 + 的特性和功能。并不是所有的内核功能都像人们所希望的那样有文档记录,而且很 + 容易遗漏一些东西。某作者发布了一个完整的驱动程序,重复了一个其不 + 知道的现有驱动程序。重新发明现有轮子的代码不仅浪费,而且不会被接受到主线 + 内核中。 + + - 建议的解决方案中可能有一些要素不适合并入主线。在编写代码之前,最好先了解 + 这样的问题。 + + - 其他开发人员完全有可能考虑过这个问题;他们可能有更好的解决方案的想法,并且 + 可能愿意帮助创建这个解决方案。 + +在内核开发社区的多年经验给了我们一个明确的教训:闭门设计和开发的内核代码总是 +有一些问题,这些问题只有在代码发布到社区中时才会被发现。有时这些问题很严重, +需要数月或数年的努力才能使代码达到内核社区的标准。例如: + + - 设计并实现了单处理器系统的DeviceScape网络栈。只有使其适合于多处理器系统, + 才能将其合并到主线中。在代码中修改锁等等是一项困难的任务;因此,这段代码 + (现在称为mac80211)的合并被推迟了一年多。 + + - Reiser4文件系统包含许多功能,核心内核开发人员认为这些功能应该在虚拟文件 + 系统层中实现。它还包括一些特性,这些特性在不将系统暴露于用户引起的死锁的 + 情况下是不容易实现的。这些问题过迟发现——以及拒绝处理其中一些问题——已经 + 导致Reiser4置身主线内核之外。 + + - Apparmor安全模块以被认为不安全和不可靠的方式使用内部虚拟文件系统数据结构。 + 这种担心(包括其他)使Apparmor多年来无法进入主线。 + +在这些情况下,与内核开发人员的早期讨论,可以避免大量的痛苦和额外的工作。 + +找谁交流? +---------- + +当开发人员决定公开他们的计划时,下一个问题是:我们从哪里开始?答案是找到正确 +的邮件列表和正确的维护者。对于邮件列表,最好的方法是在维护者(MAINTAINERS)文件 +中查找要发布的相关位置。如果有一个合适的子系统列表,那么其上发布通常比在 +linux-kernel上发布更可取;您更有可能接触到在相关子系统中具有专业知识的开发 +人员,并且环境可能具支持性。 + +找到维护人员可能会有点困难。同样,维护者文件是开始的地方。但是,该文件往往不 +是最新的,并且并非所有子系统都在那里显示。实际上,维护者文件中列出的人员可能 +不是当前实际担任该角色的人员。因此,当对联系谁有疑问时,一个有用的技巧是使用 +git(尤其是“git-log”)查看感兴趣的子系统中当前活动的用户。看看谁在写补丁、 +谁会在这些补丁上加上Signed-off-by行签名(如有)。这些人将是帮助新开发项目的 +最佳人选。 + +找到合适的维护者有时是非常具有挑战性的,以至于内核开发人员添加了一个脚本来 +简化这个过程: + +:: + + .../scripts/get_maintainer.pl + +当给定“-f”选项时,此脚本将返回指定文件或目录的当前维护者。如果在命令行上 +给出了一个补丁,它将列出可能接收补丁副本的维护人员。有许多选项可以调节 +get_maintainer.pl搜索维护者的严格程度;请小心使用更激进的选项,因为最终结果 +可能会包括对您正在修改的代码没有真正兴趣的开发人员。 + +如果所有其他方法都失败了,那么与Andrew Morton交流是跟踪特定代码段维护人员 +的一种有效方法。 + +何时邮寄? +---------- + +如果可能的话,在早期阶段发布你的计划只会更有帮助。描述正在解决的问题以及已经 +制定的关于如何实施的任何计划。您可以提供的任何信息都可以帮助开发社区为项目 +提供有用的输入。 + +在这个阶段可能发生的一件令人沮丧的事情不是得到反对意见,而是很少或根本没有 +反馈。令人伤心的事实是:(1)内核开发人员往往很忙;(2)不缺少有宏伟计划但 +代码(甚至代码设想)很少的人去支持他们;(3)没有人有义务审查或评论别人发表 +的想法。除此之外,高层级的设计常常隐藏着一些问题,这些问题只有在有人真正尝试 +实现这些设计时才会被发现;因此,内核开发人员宁愿看到代码。 + +如果发布请求评论(RFC)并没得到什么有用的评论,不要以为这意味着无人对此项目 +有兴趣,同时你也不能假设你的想法没有问题。在这种情况下,最好的做法是继续进 +行,把你的进展随时通知社区。 + +获得官方认可 +----------------------- + +如果您的工作是在公司环境中完成的,就像大多数Linux内核工作一样;显然,在您将 +公司的计划或代码发布到公共邮件列表之前,必须获得有适当权利经理的许可。发布 +不确定是否兼容GPL的代码尤其会带来问题;公司的管理层和法律人员越早能够就发布 +内核开发项目达成一致,对参与的每个人都越好。 + +一些读者可能会认为他们的核心工作是为了支持还没有正式承认存在的产品。将雇主 +的计划公布在公共邮件列表上可能不是一个可行的选择。在这种情况下,有必要考虑 +保密是否真的是必要的;通常不需要把开发计划关在门内。 + +的确,有些情况下一家公司在开发过程的早期无法合法地披露其计划。拥有经验丰富 +的内核开发人员的公司可能选择以开环的方式进行开发,前提是他们以后能够避免 +严重的集成问题。对于没有这种内部专业知识的公司,最好的选择往往是聘请外部 +开发者根据保密协议审查计划。Linux基金会运行了一个NDA程序,旨在帮助解决这种 +情况;更多信息参见: + + http://www.linuxfoundation.org/nda/ + +这种审查通常足以避免以后出现严重问题,而无需公开披露项目。 diff --git a/Documentation/translations/zh_CN/process/4.Coding.rst b/Documentation/translations/zh_CN/process/4.Coding.rst new file mode 100644 index 000000000..94f7f866f --- /dev/null +++ b/Documentation/translations/zh_CN/process/4.Coding.rst @@ -0,0 +1,293 @@ +.. include:: ../disclaimer-zh_CN.rst + +:Original: :ref:`Documentation/process/4.Coding.rst <development_coding>` + +:Translator: + + 时奎亮 Alex Shi <alex.shi@linux.alibaba.com> + +:校译: + + 吴想成 Wu XiangCheng <bobwxc@email.cn> + +.. _cn_development_coding: + +使代码正确 +====================== + +虽然一个坚实的、面向社区的设计过程有很多值得说道的,但是任何内核开发项目工作 +的证明都反映在代码中。它是将由其他开发人员检查并合并(或不合并)到主线树中 +的代码。所以这段代码的质量决定了项目的最终成功。 + +本节将检查编码过程。我们将从内核开发人员常犯的几种错误开始。然后重点将转移 +到正确的做法和相关有用的工具上。 + +陷阱 +---- + +代码风格 +******** + +内核长期以来都有其标准的代码风格,如 +:ref:`Documentation/translations/zh_CN/process/coding-style.rst <cn_codingstyle>` +中所述。在多数时候,该文档中描述的准则至多被认为是建议性的。因此,内核中存在 +大量不符合代码风格准则的代码。这种代码的存在会给内核开发人员带来两方面的危害。 + +首先,相信内核代码标准并不重要,也不强制执行。但事实上,如果没有按照标准 +编写代码,那么新代码将很难加入到内核中;许多开发人员甚至会在审查代码之前要求 +对代码进行重新格式化。一个像内核这么大的代码库需要一些统一格式的代码,以使 +开发人员能够快速理解其中的任何部分。所以再也经不起奇怪格式的代码的折腾了。 + +内核的代码风格偶尔会与雇主的强制风格发生冲突。在这种情况下,必须在代码合并 +之前遵从内核代码风格。将代码放入内核意味着以多种方式放弃一定程度的控制权—— +包括控制代码样式。 + +另一个危害是认为已经在内核中的代码迫切需要修复代码样式。开发者可能会开始编写 +重新格式化补丁,作为熟悉开发过程的一种方式,或者作为将其名字写入内核变更日志 +的一种方式,或者两者兼而有之。但是纯代码风格的修复被开发社区视为噪音,它们往 +往受到冷遇。因此,最好避免编写这种类型的补丁。在由于其他原因处理一段代码的 +同时顺带修复其样式是很自然的,但是不应该仅为了更改代码样式而更改之。 + +代码风格文档也不应该被视为绝对不可违反的规则。如果有一个足够的理由反对这种 +样式(例如为了80列限制拆分行会导致可读性大大降低),那么就这样做吧。 + +注意您还可以使用 ``clang-format`` 工具来帮助您处理这些规则,快速自动重新格式 +化部分代码,和审阅完整的文件以发现代码样式错误、拼写错误和可能的改进。它还 +可以方便地排序 ``#includes`` 、对齐变量/宏、重排文本和其他类似任务。有关详细 +信息,请参阅文档 :ref:`Documentation/process/clang-format.rst <clangformat>` + +抽象层 +****** + +计算机科学教授教学生以灵活性和信息隐藏的名义广泛使用抽象层。当然,内核广泛 +地使用了抽象;任何涉及数百万行代码的项目都必须做到这一点以存续下来。但经验 +表明,过度或过早的抽象可能和过早的优化一样有害。抽象应用在适当层级, +不要过度。 + +简单点,先考虑一个调用时始终只有一个参数且总为零的函数。我们可以保留这个参数, +以在需要使用它时提供的额外灵活性。不过,在那时实现了这个额外参数的代码很有 +可能以某种从未被注意到的微妙方式被破坏——因为它从未被使用过。或者当需要额外 +的灵活性时,它并未以符合程序员当初期望的方式来实现。内核开发人员通常会提交 +补丁来删除未使用的参数;一般来说,一开始就不应该添加这些参数。 + +隐藏硬件访问的抽象层——通常为了允许大量的驱动程序兼容多个操作系统——尤其不受 +欢迎。这样的层使代码变得模糊,可能会造成性能损失;它们不属于Linux内核。 + +另一方面,如果您发现自己从另一个内核子系统复制了大量的代码,那么是时候 +了解一下:是否需要将这些代码中的部分提取到单独的库中,或者在更高的层次上 +实现这些功能。在整个内核中复制相同的代码没有价值。 + +#ifdef 和预处理 +*************** + +C预处理器似乎给一些C程序员带来了强大的诱惑,他们认为它是一种将大量灵活性加入 +源代码中的方法。但是预处理器不是C,大量使用它会导致代码对其他人来说更难阅读, +对编译器来说更难检查正确性。使用了大量预处理器几乎总是代码需要一些 +清理工作的标志。 + +使用#ifdef的条件编译实际上是一个强大的功能,它在内核中使用。但是很少有人希望 +看到代码被铺满#ifdef块。一般规定,ifdef的使用应尽可能限制在头文件中。条件 +编译代码可以限制函数,如果代码不存在,这些函数就直接变成空的。然后编译器将 +悄悄地优化对空函数的调用。使得代码更加清晰,更容易理解。 + +C预处理器宏存在许多危险性,包括可能对具有副作用且没有类型安全的表达式进行多 +重评估。如果您试图定义宏,请考虑创建一个内联函数替代。结果相同的代码,内联 +函数更容易阅读,不会多次计算其参数,并且允许编译器对参数和返回值执行类型检查。 + +内联函数 +******** + +不过,内联函数本身也存在风险。程序员可以倾心于避免函数调用和用内联函数填充源 +文件所固有的效率。然而,这些功能实际上会降低性能。因为它们的代码在每个调用站 +点都被复制一遍,所以最终会增加编译内核的大小。此外,这也对处理器的内存缓存 +造成压力,从而大大降低执行速度。通常内联函数应该非常小,而且相对较少。毕竟 +函数调用的成本并不高;大量创建内联函数是过早优化的典型例子。 + +一般来说,内核程序员会自冒风险忽略缓存效果。在数据结构课程开头中的经典 +时间/空间权衡通常不适用于当代硬件。空间 *就是* 时间,因为一个大的程序比一个 +更紧凑的程序运行得慢。 + +较新的编译器越来越激进地决定一个给定函数是否应该内联。因此,随意放置使用 +“inline”关键字可能不仅仅是过度的,也可能是无用的。 + +锁 +** + +2006年5月,“deviceescape”网络堆栈在前呼后拥下以GPL发布,并被纳入主线内核。 +这是一个受欢迎的消息;Linux中对无线网络的支持充其量被认为是不合格的,而 +Deviceescape堆栈承诺修复这种情况。然而直到2007年6月(2.6.22),这段代码才真 +正进入主线。发生了什么? + +这段代码出现了许多闭门造车的迹象。但一个大麻烦是,它并不是为多处理器系统而 +设计。在合并这个网络堆栈(现在称为mac80211)之前,需要对其进行一个锁方案的 +改造。 + +曾经,Linux内核代码可以在不考虑多处理器系统所带来的并发性问题的情况下进行 +开发。然而现在,这个文档就是在双核笔记本电脑上写的。即使在单处理器系统上, +为提高响应能力所做的工作也会提高内核内的并发性水平。编写内核代码而不考虑锁 +的日子早已远去。 + +可以由多个线程并发访问的任何资源(数据结构、硬件寄存器等)必须由锁保护。新 +的代码应该谨记这一要求;事后修改锁是一项相当困难的任务。内核开发人员应该花 +时间充分了解可用的锁原语,以便为工作选择正确的工具。对并发性缺乏关注的代码 +很难进入主线。 + +回归 +**** + +最后一个值得一提的危险是回归:它可能会引起导致现有用户的某些东西中断的改变 +(这也可能会带来很大的改进)。这种变化被称为“回归”,回归已经成为主线内核 +最不受欢迎的问题。除了少数例外情况,如果回归不能及时修正,会导致回归的修改 +将被取消。最好首先避免回归发生。 + +人们常常争论,如果回归带来的功能远超过产生的问题,那么回归是否为可接受的。 +如果它破坏了一个系统却为十个系统带来新的功能,为何不改改态度呢?2007年7月, +Linus对这个问题给出了最佳答案: + +:: + + 所以我们不会通过引入新问题来修复错误。这种方式是靠不住的,没人知道 + 是否真的有进展。是前进两步、后退一步,还是前进一步、后退两步? + +(http://lwn.net/articles/243460/) + +特别不受欢迎的一种回归类型是用户空间ABI的任何变化。一旦接口被导出到用户空间, +就必须无限期地支持它。这一事实使得用户空间接口的创建特别具有挑战性:因为它们 +不能以不兼容的方式进行更改,所以必须一次就对。因此,用户空间接口总是需要大量 +的思考、清晰的文档和广泛的审查。 + + +代码检查工具 +------------ + +至少目前,编写无错误代码仍然是我们中很少人能达到的理想状态。不过,我们希望做 +的是,在代码进入主线内核之前,尽可能多地捕获并修复这些错误。为此,内核开发人 +员已经提供了一系列令人印象深刻的工具,可以自动捕获各种各样的隐藏问题。计算机 +发现的任何问题都是一个以后不会困扰用户的问题,因此,只要有可能,就应该使用 +自动化工具。 + +第一步是注意编译器产生的警告。当前版本的GCC可以检测(并警告)大量潜在错误。 +通常,这些警告都指向真正的问题。提交以供审阅的代码一般不会产生任何编译器警告。 +在消除警告时,注意了解真正的原因,并尽量避免仅“修复”使警告消失而不解决其原因。 + +请注意,并非所有编译器警告都默认启用。使用“make KCFLAGS=-W”构建内核以 +获得完整集合。 + +内核提供了几个配置选项,可以打开调试功能;大多数配置选项位于“kernel hacking” +子菜单中。对于任何用于开发或测试目的的内核,都应该启用其中几个选项。特别是, +您应该打开: + + - FRAME_WARN 获取大于给定数量的堆栈帧的警告。 + 这些警告生成的输出可能比较冗长,但您不必担心来自内核其他部分的警告。 + + - DEBUG_OBJECTS 将添加代码以跟踪内核创建的各种对象的生命周期,并在出现问题 + 时发出警告。如果你要添加创建(和导出)关于其自己的复杂对象的子系统,请 + 考虑打开对象调试基础结构的支持。 + + - DEBUG_SLAB 可以发现各种内存分配和使用错误;它应该用于大多数开发内核。 + + - DEBUG_SPINLOCK, DEBUG_ATOMIC_SLEEP 和 DEBUG_MUTEXES 会发现许多常见的 + 锁错误。 + +还有很多其他调试选项,其中一些将在下面讨论。其中一些有显著的性能影响,不应 +一直使用。在学习可用选项上花费一些时间,可能会在短期内得到许多回报。 + +其中一个较重的调试工具是锁检查器或“lockdep”。该工具将跟踪系统中每个锁 +(spinlock或mutex)的获取和释放、获取锁的相对顺序、当前中断环境等等。然后, +它可以确保总是以相同的顺序获取锁,相同的中断假设适用于所有情况等等。换句话 +说,lockdep可以找到许多导致系统死锁的场景。在部署的系统中,这种问题可能会 +很痛苦(对于开发人员和用户而言);LockDep允许提前以自动方式发现问题。具有 +任何类型的非普通锁的代码在提交合并前应在启用lockdep的情况下运行测试。 + +作为一个勤奋的内核程序员,毫无疑问,您将检查任何可能失败的操作(如内存分配) +的返回状态。然而,事实上,最终的故障复现路径可能完全没有经过测试。未测试的 +代码往往会出问题;如果所有这些错误处理路径都被执行了几次,那么您可能对代码 +更有信心。 + +内核提供了一个可以做到这一点的错误注入框架,特别是在涉及内存分配的情况下。 +启用故障注入后,内存分配的可配置失败的百分比;这些失败可以限定在特定的代码 +范围内。在启用了故障注入的情况下运行,程序员可以看到当情况恶化时代码如何响 +应。有关如何使用此工具的详细信息,请参阅 +Documentation/fault-injection/fault-injection.rst。 + +“sparse”静态分析工具可以发现其他类型的错误。sparse可以警告程序员用户空间 +和内核空间地址之间的混淆、大端序与小端序的混淆、在需要一组位标志的地方传递 +整数值等等。sparse必须单独安装(如果您的分发服务器没有将其打包, +可以在 https://sparse.wiki.kernel.org/index.php/Main_page 找到), +然后可以通过在make命令中添加“C=1”在代码上运行它。 + +“Coccinelle”工具 :ref:`http://coccinelle.lip6.fr/ <devtools_coccinelle>` +能够发现各种潜在的编码问题;它还可以为这些问题提出修复方案。在 +scripts/coccinelle目录下已经打包了相当多的内核“语义补丁”;运行 +“make coccicheck”将运行这些语义补丁并报告发现的任何问题。有关详细信息,请参阅 +:ref:`Documentation/dev-tools/coccinelle.rst <devtools_coccinelle>` + + +其他类型的可移植性错误最好通过为其他体系结构编译代码来发现。如果没有S/390系统 +或Blackfin开发板,您仍然可以执行编译步骤。可以在以下位置找到一大堆用于x86系统的 +交叉编译器: + + https://www.kernel.org/pub/tools/crosstool/ + +花一些时间安装和使用这些编译器将有助于避免以后的尴尬。 + +文档 +---- + +文档通常比内核开发规则更为例外。即便如此,足够的文档将有助于简化将新代码合并 +到内核中的过程,使其他开发人员的生活更轻松,并对您的用户有所帮助。在许多情况 +下,添加文档已基本上是强制性的。 + +任何补丁的第一个文档是其关联的变更日志。日志条目应该描述正在解决的问题、解决 +方案的形式、处理补丁的人员、对性能的任何相关影响,以及理解补丁可能需要的任何 +其他内容。确保变更日志说明了*为什么*补丁值得应用;大量开发者未能提供这些信息。 + +任何添加新用户空间接口的代码——包括新的sysfs或/proc文件——都应该包含该接口 +的文档,该文档使用户空间开发人员能够知道他们在使用什么。请参阅 +Documentation/ABI/README,了解如何此文档格式以及需要提供哪些信息。 + +文档 :ref:`Documentation/admin-guide/kernel-parameters.rst <kernelparameters>` +描述了内核的所有引导时间参数。任何添加新参数的补丁都应该向该文档添加适当的 +条目。 + +任何新的配置选项都必须附有帮助文本,帮助文本需清楚地解释这些选项以及用户可能 +希望何时使用它们。 + +许多子系统的内部API信息通过专门格式化的注释进行记录;这些注释可以通过 +“kernel-doc”脚本以多种方式提取和格式化。如果您在具有kerneldoc注释的子系统中 +工作,则应该维护它们,并根据需要为外部可用的功能添加它们。即使在没有如此记录 +的领域中,为将来添加kerneldoc注释也没有坏处;实际上,这对于刚开始开发内核的人 +来说是一个有用的活动。这些注释的格式以及如何创建kerneldoc模板的一些信息可以在 +:ref:`Documentation/doc-guide/ <doc_guide>` 上找到。 + +任何阅读大量现有内核代码的人都会注意到,注释的缺失往往是最值得注意的。同时, +对新代码的要求比过去更高;合并未注释的代码将更加困难。这就是说,人们并不期望 +详细注释的代码。代码本身应该是自解释的,注释阐释了更微妙的方面。 + +某些事情应该总是被注释。使用内存屏障时,应附上一行文字,解释为什么需要设置内存 +屏障。数据结构的锁规则通常需要在某个地方解释。一般来说,主要数据结构需要全面 +的文档。应该指出代码中分立的位之间不明显的依赖性。任何可能诱使代码管理人进行 +错误的“清理”的事情都需要一个注释来说明为什么要这样做。等等。 + + +内部API更改 +----------- + +内核提供给用户空间的二进制接口不能被破坏,除非逼不得已。而内核的内部编程接口 +是高度流动的,当需要时可以更改。如果你发现自己不得不处理一个内核API,或者仅 +仅因为它不满足你的需求导致无法使用特定的功能,这可能是API需要改变的一个标志。 +作为内核开发人员,您有权进行此类更改。 + +的确可以进行API更改,但更改必须是合理的。因此任何进行内部API更改的补丁都应该 +附带关于更改内容和必要原因的描述。这种变化也应该拆分成一个单独的补丁,而不是 +埋在一个更大的补丁中。 + +另一个要点是,更改内部API的开发人员通常要负责修复内核树中被更改破坏的任何代码。 +对于一个广泛使用的函数,这个责任可以导致成百上千的变化,其中许多变化可能与其他 +开发人员正在做的工作相冲突。不用说,这可能是一项大工程,所以最好确保理由是 +可靠的。请注意,coccinelle工具可以帮助进行广泛的API更改。 + +在进行不兼容的API更改时,应尽可能确保编译器捕获未更新的代码。这将帮助您确保找 +到该接口的树内用处。它还将警告开发人员树外代码存在他们需要响应的更改。支持树外 +代码不是内核开发人员需要担心的事情,但是我们也不必使树外开发人员的生活有不必要 +的困难。 diff --git a/Documentation/translations/zh_CN/process/5.Posting.rst b/Documentation/translations/zh_CN/process/5.Posting.rst new file mode 100644 index 000000000..6a469e1c7 --- /dev/null +++ b/Documentation/translations/zh_CN/process/5.Posting.rst @@ -0,0 +1,246 @@ +.. include:: ../disclaimer-zh_CN.rst + +:Original: :ref:`Documentation/process/5.Posting.rst <development_posting>` + +:Translator: + + 时奎亮 Alex Shi <alex.shi@linux.alibaba.com> + +:校译: + + 吴想成 Wu XiangCheng <bobwxc@email.cn> + +.. _cn_development_posting: + +发布补丁 +======== + +您的工作迟早会准备好提交给社区进行审查,并最终包含到主线内核中。毫不稀奇, +内核开发社区已经发展出一套用于发布补丁的约定和过程;遵循这些约定和过程将使 +参与其中的每个人的生活更加轻松。本文档试图描述这些约定的部分细节;更多信息 +也可在以下文档中找到 +:ref:`Documentation/translations/zh_CN/process/submitting-patches.rst <cn_submittingpatches>` +和 :ref:`Documentation/translations/zh_CN/process/submit-checklist.rst <cn_submitchecklist>`。 + +何时寄送 +-------- + +在补丁完全“准备好”之前,避免发布补丁是一种持续的诱惑。对于简单的补丁,这 +不是问题。但是如果正在完成的工作很复杂,那么在工作完成之前从社区获得反馈就 +可以获得很多好处。因此,您应该考虑发布正在进行的工作,甚至维护一个可用的Git +树,以便感兴趣的开发人员可以随时赶上您的工作。 + +当发布中有尚未准备好被包含的代码,最好在发布中说明。还应提及任何有待完成的 +主要工作和任何已知问题。很少有人会愿意看那些被认为是半生不熟的补丁,但是 +那些愿意的人会带着他们的点子来一起帮助你把工作推向正确的方向。 + +创建补丁之前 +------------ + +在考虑将补丁发送到开发社区之前,有许多事情应该做。包括: + + - 尽可能地测试代码。利用内核的调试工具,确保内核使用了所有可能的配置选项组合 + 进行构建,使用交叉编译器为不同的体系结构进行构建等。 + + - 确保您的代码符合内核代码风格指南。 + + - 您的更改是否具有性能影响?如果是这样,您应该运行基准测试来显示您的变更的 + 影响(或好处);结果的摘要应该包含在补丁中。 + + - 确保您有权发布代码。如果这项工作是为雇主完成的,雇主对这项工作具有所有权, + 并且必须同意根据GPL对其进行发布。 + +一般来说,在发布代码之前进行一些额外的思考,几乎总是能在短时间内得到回报。 + +补丁准备 +-------- + +准备补丁发布的工作量可能很惊人,但在此尝试节省时间通常是不明智的,即使在短期 +内亦然。 + +必须针对内核的特定版本准备补丁。一般来说,补丁应该基于Linus的Git树中的当前 +主线。当以主线为基础时,请从一个众所周知的发布点开始——如稳定版本或 -rc +版本发布点——而不是在一个任意的主线分支点。 + +也可能需要针对-mm、linux-next或子系统树生成版本,以便于更广泛的测试和审查。 +根据补丁的区域以及其他地方的情况,针对其他树建立的补丁可能需要大量的工作来 +解决冲突和处理API更改。 + +只有最简单的更改才应格式化为单个补丁;其他所有更改都应作为一系列逻辑更改进行。 +分割补丁是一门艺术;一些开发人员花了很长时间来弄清楚如何按照社区期望的方式来 +分割。不过,这些经验法则也许有帮助: + + - 您发布的补丁系列几乎肯定不会是开发过程中版本控制系统中的一系列更改。相反, + 需要对您所做更改的最终形式加以考虑,然后以有意义的方式进行拆分。开发人员对 + 离散的、自包含的更改感兴趣,而不是您创造这些更改的原始路径。 + + - 每个逻辑上独立的变更都应该格式化为单独的补丁。这些更改可以是小的(如“向 + 此结构体添加字段”)或大的(如添加一个重要的新驱动程序),但它们在概念上 + 应该是小的,并且可以在一行内简述。每个补丁都应该做一个特定的、可以单独 + 检查并验证它所做的事情的更改。 + + - 换种方式重申上述准则,也就是说:不要在同一补丁中混合不同类型的更改。如果 + 一个补丁修复了一个关键的安全漏洞,又重新排列了一些结构,还重新格式化了代 + 码,那么它很有可能会被忽略,从而导致重要的修复丢失。 + + - 每个补丁都应该能创建一个可以正确地构建和运行的内核;如果补丁系列在中间被 + 断开,那么结果仍应是一个正常工作的内核。部分应用一系列补丁是使用 + “git bisct”工具查找回归的一个常见场景;如果结果是一个损坏的内核,那么将使 + 那些从事追踪问题的高尚工作的开发人员和用户的生活更加艰难。 + + - 不要过分分割。一位开发人员曾经将一组针对单个文件的编辑分成500个单独的补丁 + 发布,这并没有使他成为内核邮件列表中最受欢迎的人。一个补丁可以相当大, + 只要它仍然包含一个单一的 *逻辑* 变更。 + + - 用一系列补丁添加一个全新的基础设施,但是该设施在系列中的最后一个补丁启用 + 整个变更之前不能使用,这看起来很诱人。如果可能的话,应该避免这种诱惑; + 如果这个系列增加了回归,那么二分法将指出最后一个补丁是导致问题的补丁, + 即使真正的bug在其他地方。只要有可能,添加新代码的补丁程序应该立即激活该 + 代码。 + +创建完美补丁系列的工作可能是一个令人沮丧的过程,在完成“真正的工作”之后需要 +花费大量的时间和思考。但是如果做得好,花费的时间就是值得的。 + +补丁格式和更改日志 +------------------ + +所以现在你有了一系列完美的补丁可以发布,但是这项工作还没有完成。每个补丁都 +需要被格式化成一条消息,以快速而清晰地将其目的传达到世界其他地方。为此, +每个补丁将由以下部分组成: + + - 可选的“From”行,表明补丁作者。只有当你通过电子邮件发送别人的补丁时,这一行 + 才是必须的,但是为防止疑问加上它也不会有什么坏处。 + + - 一行描述,说明补丁的作用。对于在没有其他上下文的情况下看到该消息的读者来说, + 该消息应足以确定修补程序的范围;此行将显示在“short form(简短格式)”变更 + 日志中。此消息通常需要先加上子系统名称前缀,然后是补丁的目的。例如: + + :: + + gpio: fix build on CONFIG_GPIO_SYSFS=n + + - 一行空白,后接补丁内容的详细描述。此描述可以是任意需要的长度;它应该说明补丁 + 的作用以及为什么它应该应用于内核。 + + - 一个或多个标记行,至少有一个由补丁作者的 Signed-off-by 签名。标记将在下面 + 详细描述。 + +上面的项目一起构成补丁的变更日志。写一则好的变更日志是一门至关重要但常常被 +忽视的艺术;值得花一点时间来讨论这个问题。当你编写变更日志时,你应该记住有 +很多不同的人会读你的话。其中包括子系统维护人员和审查人员,他们需要决定是否 +应该合并补丁,分销商和其他维护人员试图决定是否应该将补丁反向移植到其他内核, +缺陷搜寻人员想知道补丁是否导致他们正在追查的问题,以及想知道内核如何变化的 +用户等等。一个好的变更日志以最直接和最简洁的方式向所有这些人传达所需的信息。 + +在结尾,总结行应该描述变更的影响和动机,以及在一行约束条件下可能发生的变化。 +然后,详细的描述可以详述这些主题,并提供任何需要的附加信息。如果补丁修复了 +一个缺陷,请引用引入该缺陷的提交(如果可能,请在引用提交时同时提供其 id 和 +标题)。如果某个问题与特定的日志或编译器输出相关联,请包含该输出以帮助其他 +人搜索同一问题的解决方案。如果更改是为了支持以后补丁中的其他更改,那么应当 +说明。如果更改了内部API,请详细说明这些更改以及其他开发人员应该如何响应。 +一般来说,你越把自己放在每个阅读你变更日志的人的位置上,变更日志(和内核 +作为一个整体)就越好。 + +不需要说,变更日志是将变更提交到版本控制系统时使用的文本。接下来将是: + + - 补丁本身,采用统一的(“-u”)补丁格式。使用“-p”选项来diff将使函数名与 + 更改相关联,从而使结果补丁更容易被其他人读取。 + +您应该避免在补丁中包括与更改不相关文件(例如,构建过程生成的文件或编辑器 +备份文件)。文档目录中的“dontdiff”文件在这方面有帮助;使用“-X”选项将 +其传递给diff。 + +上面提到的标签(tag)用于描述各种开发人员如何与这个补丁的开发相关联。 +:ref:`Documentation/translations/zh_CN/process/submitting-patches.rst <cn_submittingpatches>` +文档中对它们进行了详细描述;下面是一个简短的总结。每一行的格式如下: + +:: + + tag: Full Name <email address> optional-other-stuff + +常用的标签有: + + - Signed-off-by: 这是一个开发人员的证明,证明他或她有权提交补丁以包含到内核 + 中。这表明同意开发者来源认证协议,其全文见 + :ref:`Documentation/translations/zh_CN/process/submitting-patches.rst <cn_submittingpatches>` + 如果没有合适的签字,则不能合并到主线中。 + + - Co-developed-by: 声明补丁是由多个开发人员共同创建的;当几个人在一个补丁上 + 工作时,它用于给出共同作者(除了 From: 所给出的作者之外)。由于 + Co-developed-by: 表示作者身份,所以每个共同开发人,必须紧跟在相关合作作者 + 的Signed-off-by之后。具体内容和示例见以下文件 + :ref:`Documentation/translations/zh_CN/process/submitting-patches.rst <cn_submittingpatches>` + + - Acked-by: 表示另一个开发人员(通常是相关代码的维护人员)同意补丁适合包含 + 在内核中。 + + - Tested-by: 声明某人已经测试了补丁并确认它可以工作。 + + - Reviewed-by: 表示某开发人员已经审查了补丁的正确性;有关详细信息,请参阅 + :ref:`Documentation/translations/zh_CN/process/submitting-patches.rst <cn_submittingpatches>` + + - Reported-by: 指定报告此补丁修复的问题的用户;此标记用于表示感谢。 + + - Cc:指定某人收到了补丁的副本,并有机会对此发表评论。 + +在补丁中添加标签时要小心:只有Cc:才适合在没有指定人员明确许可的情况下添加。 + +寄送补丁 +-------- + +在寄送补丁之前,您还需要注意以下几点: + + - 您确定您的邮件发送程序不会损坏补丁吗?被邮件客户端更改空白或修饰了行的补丁 + 无法被另一端接受,并且通常不会进行任何详细检查。如果有任何疑问,先把补丁寄 + 给你自己,让你自己确定它是完好无损的。 + + :ref:`Documentation/translations/zh_CN/process/email-clients.rst <cn_email_clients>` + 提供了一些有用的提示,可以让特定的邮件客户端正常发送补丁。 + + - 你确定你的补丁没有荒唐的错误吗?您应该始终通过scripts/checkpatch.pl检查 + 补丁程序,并解决它提出的问题。请记住,checkpatch.pl,虽然体现了对内核补丁 + 应该是什么样的大量思考,但它并不比您聪明。如果修复checkpatch.pl给的问题会 + 使代码变得更糟,请不要这样做。 + +补丁应始终以纯文本形式发送。请不要将它们作为附件发送;这使得审阅者在答复中更难 +引用补丁的部分。相反,只需将补丁直接放到您的消息中。 + +寄出补丁时,重要的是将副本发送给任何可能感兴趣的人。与其他一些项目不同,内核 +鼓励人们甚至错误地发送过多的副本;不要假定相关人员会看到您在邮件列表中的发布。 +尤其是,副本应发送至: + + - 受影响子系统的维护人员。如前所述,维护人员文件是查找这些人员的首选地方。 + + - 其他在同一领域工作的开发人员,尤其是那些现在可能在那里工作的开发人员。使用 + git查看还有谁修改了您正在处理的文件,这很有帮助。 + + - 如果您对某错误报告或功能请求做出响应,也可以抄送原始发送人。 + + - 将副本发送到相关邮件列表,或者若无相关列表,则发送到linux-kernel列表。 + + - 如果您正在修复一个缺陷,请考虑该修复是否应进入下一个稳定更新。如果是这样, + 补丁副本也应发到stable@vger.kernel.org 。另外,在补丁本身的标签中添加一个 + “Cc: stable@vger.kernel.org”;这将使稳定版团队在修复进入主线时收到通知。 + +当为一个补丁选择接收者时,最好清楚你认为谁最终会接受这个补丁并将其合并。虽然 +可以将补丁直接发给Linus Torvalds并让他合并,但通常情况下不会这样做。Linus很 +忙,并且有子系统维护人员负责监视内核的特定部分。通常您会希望维护人员合并您的 +补丁。如果没有明显的维护人员,Andrew Morton通常是最后的补丁接收者。 + +补丁需要好的主题行。补丁主题行的规范格式如下: + +:: + + [PATCH nn/mm] subsys: one-line description of the patch + +其中“nn”是补丁的序号,“mm”是系列中补丁的总数,“subsys”是受影响子系统的 +名称。当然,一个单独的补丁可以省略nn/mm。 + +如果您有一系列重要的补丁,那么通常发送一个简介作为第〇部分。不过,这个约定 +并没有得到普遍遵循;如果您使用它,请记住简介中的信息不会进入内核变更日志。 +因此,请确保补丁本身具有完整的变更日志信息。 + +一般来说,多部分补丁的第二部分和后续部分应作为对第一部分的回复发送,以便它们 +在接收端都连接在一起。像git和coilt这样的工具有命令,可以通过适当的线程发送 +一组补丁。但是,如果您有一长串补丁,并正使用git,请不要使用–-chain-reply-to +选项,以避免创建过深的嵌套。 diff --git a/Documentation/translations/zh_CN/process/6.Followthrough.rst b/Documentation/translations/zh_CN/process/6.Followthrough.rst new file mode 100644 index 000000000..2a127e737 --- /dev/null +++ b/Documentation/translations/zh_CN/process/6.Followthrough.rst @@ -0,0 +1,152 @@ +.. include:: ../disclaimer-zh_CN.rst + +:Original: :ref:`Documentation/process/6.Followthrough.rst <development_followthrough>` + +:Translator: + + 时奎亮 Alex Shi <alex.shi@linux.alibaba.com> + +:校译: + + 吴想成 Wu XiangCheng <bobwxc@email.cn> + +.. _cn_development_followthrough: + +跟进 +==== + +此时,您已经遵循了到目前为止给出的指导方针,并且,随着您自己的工程技能的增加, +已经发布了一系列完美的补丁。即使是经验丰富的内核开发人员也能犯的最大错误之一 +是,认为他们的工作现在已经完成了。事实上,发布补丁意味着进入流程的下一个阶段, +可能还需要做很多工作。 + +一个补丁在首次发布时就非常出色、没有改进的余地,这是很罕见的。内核开发流程已 +认识到这一事实,因此它非常注重对已发布代码的改进。作为代码的作者,您应该与 +内核社区合作,以确保您的代码符合内核的质量标准。如果不参与这个过程,很可能会 +无法将补丁合并到主线中。 + +与审阅者合作 +------------ + +任何意义上的补丁都会导致其他开发人员在审查代码时发表大量评论。对于许多开发 +人员来说,与审阅人员合作可能是内核开发过程中最令人生畏的部分。但是如果你 +记住一些事情,生活会变得容易得多: + + - 如果你已经很好地解释了你的补丁,审阅人员会理解它的价值,以及为什么你会 + 费尽心思去写它。但是这个并不能阻止他们提出一个基本的问题:在五年或十年后 + 维护含有此代码的内核会怎么样?你可能被要求做出的许多改变——从编码风格的 + 调整到大量的重写——都来自于对Linux的理解,即从现在起十年后,Linux仍将 + 在开发中。 + + - 代码审查是一项艰苦的工作,这是一项相对吃力不讨好的工作;人们记得谁编写了 + 内核代码,但对于那些审查它的人来说,几乎没有什么长久的名声。因此,审阅 + 人员可能会变得暴躁,尤其是当他们看到同样的错误被一遍又一遍地犯下时。如果 + 你得到了一个看起来愤怒、侮辱或完全冒犯你的评论,请抑制以同样方式回应的冲动。 + 代码审查是关于代码的,而不是关于人的,代码审阅人员不会亲自攻击您。 + + - 同样,代码审阅人员也不想以牺牲你雇主的利益为代价来宣传他们雇主的议程。 + 内核开发人员通常希望今后几年能在内核上工作,但他们明白他们的雇主可能会改 + 变。他们真的,几乎毫无例外地,致力于创造他们所能做到的最好的内核;他们并 + 没有试图给雇主的竞争对手造成不适。 + +所有这些归根结底就是,当审阅者向您发送评论时,您需要注意他们正在进行的技术 +评论。不要让他们的表达方式或你自己的骄傲阻止此事。当你在一个补丁上得到评论 +时,花点时间去理解评论人想说什么。如果可能的话,请修复审阅者要求您修复的内 +容。然后回复审阅者:谢谢他们,并描述你将如何回答他们的问题。 + +请注意,您不必同意审阅者建议的每个更改。如果您认为审阅者误解了您的代码,请 +解释到底发生了什么。如果您对建议的更改有技术上的异议,请描述它并证明您对该 +问题的解决方案是正确的。如果你的解释有道理,审阅者会接受的。不过,如果你的 +解释证明缺乏说服力,尤其是当其他人开始同意审稿人的观点时,请花些时间重新考虑 +一下。你很容易对自己解决问题的方法视而不见,以至于你没有意识到某些东西完全 +是错误的,或者你甚至没有解决正确的问题。 + +Andrew Morton建议,每一个不会导致代码更改的审阅评论都应该产生一个额外的代码 +注释;这可以帮助未来的审阅人员避免第一次出现的问题。 + +一个致命的错误是忽视评论,希望它们会消失。它们不会走的。如果您在没有对之前 +收到的评论做出响应的情况下重新发布代码,那么很可能会发现补丁毫无用处。 + +说到重新发布代码:请记住,审阅者不会记住您上次发布的代码的所有细节。因此, +提醒审阅人员以前提出的问题以及您如何处理这些问题总是一个好主意;补丁变更 +日志是提供此类信息的好地方。审阅者不必搜索列表档案来熟悉上次所说的内容; +如果您帮助他们直接开始,当他们重新查看您的代码时,心情会更好。 + +如果你已经试着做正确的事情,但事情仍然没有进展呢?大多数技术上的分歧都可以 +通过讨论来解决,但有时人们仍需要做出决定。如果你真的认为这个决定对你不利, +你可以试着向有更高权力的人上诉。对于本文,更高权力的人是 Andrew Morton 。 +Andrew 在内核开发社区中非常受尊敬;他经常为似乎被绝望阻塞的事情清障。尽管 +如此,不应轻易就直接找 Andrew ,也不应在所有其他替代方案都被尝试之前找他。 +当然,记住,他也可能不同意你的意见。 + +接下来会发生什么 +---------------- + +如果一个补丁被认为适合添加到内核中,并且大多数审查问题得到解决,下一步通常 +是进入子系统维护人员的树中。工作方式因子系统而异;每个维护人员都有自己的 +工作方式。特别是可能有不止一棵树——也许一棵树专门用于计划下一个合并窗口的 +补丁,另一棵树用于长期工作。 + +对于应用到不属于明显子系统树(例如内存管理修补程序)的区域的修补程序,默认树 +通常上溯到-mm。影响多个子系统的补丁也可以最终进入-mm树。 + +包含在子系统树中可以提高补丁的可见性。现在,使用该树的其他开发人员将默认获 +得补丁。子系统树通常也为Linux提供支持,使其内容对整个开发社区可见。在这一点 +上,您很可能会从一组新的审阅者那里得到更多的评论;这些评论需要像上一轮那样 +得到回应。 + +在这时也会发生点什么,这取决于你的补丁的性质,是否与其他人正在做的工作发生 +冲突。在最坏的情况下,严重的补丁冲突可能会导致一些工作被搁置,以便剩余的补丁 +可以成形并合并。另一些时候,冲突解决将涉及到与其他开发人员合作,可能还会 +在树之间移动一些补丁,以确保所有的应用都是干净的。这项工作可能是一件痛苦的 +事情,但也需庆幸现在的幸福:在linux-next树出现之前,这些冲突通常只在合并窗口 +中出现,必须迅速解决。现在可以在合并窗口打开之前的空闲时间解决这些问题。 + +有朝一日,如果一切顺利,您将登录并看到您的补丁已经合并到主线内核中。祝贺你! +然而,一旦庆祝完了(并且您已经将自己添加到维护人员文件中),就一定要记住 +一个重要的小事实:工作仍然没有完成。并入主线也带来了它的挑战。 + +首先,补丁的可见性再次提高。可能会有以前不知道这个补丁的开发者的新一轮评论。 +忽略它们可能很有诱惑力,因为您的代码不再存在任何被合并的问题。但是,要抵制 +这种诱惑,您仍然需要对有问题或建议的开发人员作出响应。 + +不过,更重要的是:将代码包含在主线中会将代码交给更多的一些测试人员。即使您 +为尚未可用的硬件提供了驱动程序,您也会惊讶于有多少人会将您的代码构建到内核 +中。当然,如果有测试人员,也可能会有错误报告。 + +最糟糕的错误报告是回归。如果你的补丁导致回归,你会发现多到让你不舒服的眼睛盯 +着你;回归需要尽快修复。如果您不愿意或无法修复回归(其他人都不会为您修复), +那么在稳定期内,您的补丁几乎肯定会被移除。除了否定您为使补丁进入主线所做的 +所有工作之外,如果由于未能修复回归而取消补丁,很可能会使将来的工作更难被合并。 + +在处理完任何回归之后,可能还有其他普通缺陷需要处理。稳定期是修复这些错误并 +确保代码在主线内核版本中的首次发布尽可能可靠的最好机会。所以,请回应错误 +报告,并尽可能解决问题。这就是稳定期的目的;一旦解决了旧补丁的任何问题,就 +可以开始尽情创建新补丁。 + +别忘了,还有其他节点也可能会创建缺陷报告:下一个主线稳定版本,当著名的发行 +商选择包含您补丁的内核版本时等等。继续响应这些报告是您工作的基本素养。但是 +如果这不能提供足够的动机,那么也需要考虑:开发社区会记住那些在合并后对代码 +失去兴趣的开发人员。下一次你发布补丁时,他们会以你以后不会持续维护它为前提 +来评估它。 + +其他可能发生的事情 +------------------ + +某天,当你打开你的邮件客户端时,看到有人给你寄了一个代码补丁。毕竟,这是 +让您的代码公开存在的好处之一。如果您同意这个补丁,您可以将它转发给子系统 +维护人员(确保包含一个正确的From:行,这样属性是正确的,并添加一个您自己的 +signoff ),或者回复一个 Acked-by: 让原始发送者向上发送它。 + +如果您不同意补丁,请礼貌地回复,解释原因。如果可能的话,告诉作者需要做哪些 +更改才能让您接受补丁。合并代码的编写者和维护者所反对的补丁的确存在着一定的 +阻力,但仅此而已。如果你被认为不必要的阻碍了好的工作,那么这些补丁最终会 +绕过你并进入主线。在Linux内核中,没有人对任何代码拥有绝对的否决权。可能除 +了Linus。 + +在非常罕见的情况下,您可能会看到完全不同的东西:另一个开发人员发布了针对您 +的问题的不同解决方案。在这时,两个补丁之一可能不会被合并,“我的补丁首先 +发布”不被认为是一个令人信服的技术论据。如果有别人的补丁取代了你的补丁而进 +入了主线,那么只有一种方法可以回应你:很高兴你的问题解决了,请继续工作吧。 +以这种方式把某人的工作推到一边可能导致伤心和气馁,但是社区会记住你的反应, +即使很久以后他们已经忘记了谁的补丁真正被合并。 diff --git a/Documentation/translations/zh_CN/process/7.AdvancedTopics.rst b/Documentation/translations/zh_CN/process/7.AdvancedTopics.rst new file mode 100644 index 000000000..6d0dadae1 --- /dev/null +++ b/Documentation/translations/zh_CN/process/7.AdvancedTopics.rst @@ -0,0 +1,133 @@ +.. include:: ../disclaimer-zh_CN.rst + +:Original: :ref:`Documentation/process/7.AdvancedTopics.rst <development_advancedtopics>` + +:Translator: + + 时奎亮 Alex Shi <alex.shi@linux.alibaba.com> + +:校译: + + 吴想成 Wu XiangCheng <bobwxc@email.cn> + +.. _cn_development_advancedtopics: + +高级主题 +======== + +现在,希望您能够掌握开发流程的工作方式。然而,还有更多的东西要学!本节将介绍 +一些主题,这些主题对希望成为Linux内核开发过程常规部分的开发人员有帮助。 + +使用Git管理补丁 +--------------- + +内核使用分布式版本控制始于2002年初,当时Linus首次开始使用专有的Bitkeeper应用 +程序。虽然BitKeeper存在争议,但它所体现的软件版本管理方法却肯定不是。分布式 +版本控制可以立即加速内核开发项目。现在有好几种免费的BitKeeper替代品。 +但无论好坏,内核项目都已经选择了Git作为其工具。 + +使用Git管理补丁可以使开发人员的生活更加轻松,尤其是随着补丁数量的增长。Git也 +有其粗糙的边角和一定的危险性,它是一个年轻和强大的工具,仍然在其开发人员完善 +中。本文档不会试图教会读者如何使用git;这会是个巨长的文档。相反,这里的重点 +将是Git如何特别适合内核开发过程。想要加快用Git速度的开发人员可以在以下网站上 +找到更多信息: + + https://git-scm.com/ + + https://www.kernel.org/pub/software/scm/git/docs/user-manual.html + +同时网上也能找到各种各样的教程。 + +在尝试使用它生成补丁供他人使用之前,第一要务是阅读上述网页,对Git的工作方式 +有一个扎实的了解。使用Git的开发人员应能进行拉取主线存储库的副本,查询修订 +历史,提交对树的更改,使用分支等操作。了解Git用于重写历史的工具(如rebase) +也很有用。Git有自己的术语和概念;Git的新用户应该了解引用、远程分支、索引、 +快进合并、推拉、游离头等。一开始可能有点吓人,但这些概念不难通过一点学习来 +理解。 + +使用git生成通过电子邮件提交的补丁是提高速度的一个很好的练习。 + +当您准备好开始建立Git树供其他人查看时,无疑需要一个可以从中拉取的服务器。 +如果您有一个可以访问因特网的系统,那么使用git-daemon设置这样的服务器相对 +简单。同时,免费的公共托管网站(例如github)也开始出现在网络上。成熟的开发 +人员可以在kernel.org上获得一个帐户,但这些帐户并不容易得到;更多有关信息, +请参阅 https://kernel.org/faq/ 。 + +正常的Git工作流程涉及到许多分支的使用。每一条开发线都可以分为单独的“主题 +分支”,并独立维护。Git的分支很容易使用,没有理由不使用它们。而且,在任何 +情况下,您都不应该在任何您打算让其他人从中拉取的分支中进行开发。应该小心地 +创建公开可用的分支;当开发分支处于完整状态并已准备好时(而不是之前)才合并 +开发分支的补丁。 + +Git提供了一些强大的工具,可以让您重写开发历史。一个不方便的补丁(比如说, +一个打破二分法的补丁,或者有其他一些明显的缺陷)可以在适当的位置修复,或者 +完全从历史中消失。一个补丁系列可以被重写,就好像它是在今天的主线上写的一样, +即使你已经花了几个月的时间在写它。可以透明地将更改从一个分支转移到另一个 +分支。等等。明智地使用git修改历史的能力可以帮助创建问题更少的干净补丁集。 + +然而,过度使用这种功能可能会导致其他问题,而不仅仅是对创建完美项目历史的 +简单痴迷。重写历史将重写该历史中包含的更改,将经过测试(希望如此)的内核树 +变为未经测试的内核树。除此之外,如果开发人员没有共享项目历史,他们就无法 +轻松地协作;如果您重写了其他开发人员拉入他们存储库的历史,您将使这些开发 +人员的生活更加困难。因此,这里有一个简单的经验法则:被导出到其他地方的历史 +在此后通常被认为是不可变的。 + +因此,一旦将一组更改推送到公开可用的服务器上,就不应该重写这些更改。如果您 +尝试强制进行无法快进合并的更改(即不共享同一历史记录的更改),Git将尝试强制 +执行此规则。这可能覆盖检查,有时甚至需要重写导出的树。在树之间移动变更集以 +避免linux-next中的冲突就是一个例子。但这种行为应该是罕见的。这就是为什么 +开发应该在私有分支中进行(必要时可以重写)并且只有在公共分支处于合理的较新 +状态时才转移到公共分支中的原因之一。 + +当主线(或其他一组变更所基于的树)前进时,很容易与该树合并以保持领先地位。 +对于一个私有的分支,rebasing 可能是一个很容易跟上另一棵树的方法,但是一旦 +一棵树被导出到外界,rebasing就不可取了。一旦发生这种情况,就必须进行完全 +合并(merge)。合并有时是很有意义的,但是过于频繁的合并会不必要地扰乱历史。 +在这种情况下建议的做法是不要频繁合并,通常只在特定的发布点(如主线-rc发布) +合并。如果您对特定的更改感到紧张,则可以始终在私有分支中执行测试合并。在 +这种情况下,git“rerere”工具很有用;它能记住合并冲突是如何解决的,这样您 +就不必重复相同的工作。 + +关于Git这样的工具的一个最大的反复抱怨是:补丁从一个存储库到另一个存储库的 +大量移动使得很容易陷入错误建议的变更中,这些变更避开审查雷达进入主线。当内 +核开发人员看到这种情况发生时,他们往往会感到不高兴;在Git树上放置未审阅或 +主题外的补丁可能会影响您将来让树被拉取的能力。引用Linus的话: + +:: + + 你可以给我发补丁,但当我从你那里拉取一个Git补丁时,我需要知道你清楚 + 自己在做什么,我需要能够相信事情而 *无需* 手动检查每个单独的更改。 + +(http://lwn.net/articles/224135/)。 + +为了避免这种情况,请确保给定分支中的所有补丁都与相关主题紧密相关;“驱动程序 +修复”分支不应更改核心内存管理代码。而且,最重要的是,不要使用Git树来绕过 +审查过程。不时的将树的摘要发布到相关的列表中,在合适时候请求linux-next中 +包含该树。 + +如果其他人开始发送补丁以包含到您的树中,不要忘记审阅它们。还要确保您维护正确 +的作者信息; git “am”工具在这方面做得最好,但是如果补丁通过第三方转发给您, +您可能需要在补丁中添加“From:”行。 + +请求拉取时,请务必提供所有相关信息:树的位置、要拉取的分支以及拉取将导致的 +更改。在这方面 git request-pull 命令非常有用;它将按照其他开发人员所期望的 +格式化请求,并检查以确保您已记得将这些更改推送到公共服务器。 + +审阅补丁 +-------- + +一些读者显然会反对将本节与“高级主题”放在一起,因为即使是刚开始的内核开发人员 +也应该审阅补丁。当然,没有比查看其他人发布的代码更好的方法来学习如何在内核环境 +中编程了。此外,审阅者永远供不应求;通过审阅代码,您可以对整个流程做出重大贡献。 + +审查代码可能是一副令人生畏的图景,特别是对一个新的内核开发人员来说,他们 +可能会对公开询问代码感到紧张,而这些代码是由那些有更多经验的人发布的。不过, +即使是最有经验的开发人员编写的代码也可以得到改进。也许对(所有)审阅者最好 +的建议是:把审阅评论当成问题而不是批评。询问“在这条路径中如何释放锁?” +总是比说“这里的锁是错误的”更好。 + +不同的开发人员将从不同的角度审查代码。部分人会主要关注代码风格以及代码行是 +否有尾随空格。其他人会主要关注补丁作为一个整体实现的变更是否对内核有好处。 +同时也有人会检查是否存在锁问题、堆栈使用过度、可能的安全问题、在其他地方 +发现的代码重复、足够的文档、对性能的不利影响、用户空间ABI更改等。所有类型 +的检查,只要它们能引导更好的代码进入内核,都是受欢迎和值得的。 diff --git a/Documentation/translations/zh_CN/process/8.Conclusion.rst b/Documentation/translations/zh_CN/process/8.Conclusion.rst new file mode 100644 index 000000000..643b88af9 --- /dev/null +++ b/Documentation/translations/zh_CN/process/8.Conclusion.rst @@ -0,0 +1,69 @@ +.. include:: ../disclaimer-zh_CN.rst + +:Original: :ref:`Documentation/process/8.Conclusion.rst <development_conclusion>` +:Translator: + + 时奎亮 Alex Shi <alex.shi@linux.alibaba.com> + +:校译: + + 吴想成 Wu XiangCheng <bobwxc@email.cn> + +.. _cn_development_conclusion: + +更多信息 +======== + +关于Linux内核开发和相关主题的信息来源很多。首先是在内核源代码分发中找到的 +文档目录。顶级 +:ref:`Documentation/translations/zh_CN/process/howto.rst <cn_process_howto>` +文件是一个重要的起点; +:ref:`Documentation/translations/zh_CN/process/submitting-patches.rst <cn_submittingpatches>` +也是所有内核开发人员都应该阅读的内容。许多内部内核API都是使用kerneldoc机制 +记录的;“make htmldocs”或“make pdfdocs”可用于以HTML或PDF格式生成这些文档 +(尽管某些发行版提供的tex版本会遇到内部限制,无法正确处理文档)。 + +不同的网站在各个细节层次上讨论内核开发。本文作者想谦虚地建议用 https://lwn.net/ +作为来源;有关许多特定内核主题的信息可以通过以下网址的 LWN 内核索引找到: + + http://lwn.net/kernel/index/ + +除此之外,内核开发人员的一个宝贵资源是: + + https://kernelnewbies.org/ + +当然,也不应该忘记 https://kernel.org/ ,这是内核发布信息的最终位置。 + +关于内核开发有很多书: + + 《Linux设备驱动程序》第三版(Jonathan Corbet、Alessandro Rubini和Greg Kroah Hartman) + 线上版本在 http://lwn.net/kernel/ldd3/ + + 《Linux内核设计与实现》(Robert Love) + + 《深入理解Linux内核》(Daniel Bovet和Marco Cesati) + +然而,所有这些书都有一个共同的缺点:它们上架时就往往有些过时,而且已经上架 +一段时间了。不过,在那里还是可以找到相当多的好信息。 + +有关git的文档,请访问: + + https://www.kernel.org/pub/software/scm/git/docs/ + + https://www.kernel.org/pub/software/scm/git/docs/user-manual.html + +结论 +==== + +祝贺所有通过这篇冗长的文档的人。希望它能够帮助您理解Linux内核是如何开发的, +以及您如何参与这个过程。 + +最后,重要的是参与。任何开源软件项目都不会超过其贡献者投入其中的总和。Linux +内核的发展速度和以前一样快,因为它得到了大量开发人员的帮助,他们都在努力使它 +变得更好。内核是一个最成功的例子,说明了当成千上万的人为了一个共同的目标一起 +工作时,可以做出什么。 + +不过,内核总是可以从更大的开发人员基础中获益。总有更多的工作要做。但是同样 +重要的是,Linux生态系统中的大多数其他参与者可以通过为内核做出贡献而受益。使 +代码进入主线是提高代码质量、降低维护和分发成本、提高对内核开发方向的影响程度 +等的关键。这是一种共赢的局面。启动你的编辑器,来加入我们吧;你会非常受欢迎的。 diff --git a/Documentation/translations/zh_CN/process/code-of-conduct-interpretation.rst b/Documentation/translations/zh_CN/process/code-of-conduct-interpretation.rst new file mode 100644 index 000000000..c323ce76e --- /dev/null +++ b/Documentation/translations/zh_CN/process/code-of-conduct-interpretation.rst @@ -0,0 +1,108 @@ +.. include:: ../disclaimer-zh_CN.rst + +:Original: :ref:`Documentation/process/code-of-conduct-interpretation.rst <code_of_conduct_interpretation>` +:Translator: Alex Shi <alex.shi@linux.alibaba.com> + +.. _cn_code_of_conduct_interpretation: + +Linux内核贡献者契约行为准则解释 +=============================== + +:ref:`cn_code_of_conduct` 准则是一个通用文档,旨在为几乎所有开源社区提供一套规则。 +每个开源社区都是独一无二的,Linux内核也不例外。因此,本文描述了Linux内核社区中 +如何解释它。我们也不希望这种解释随着时间的推移是静态的,并将根据需要进行调整。 + +与开发软件的“传统”方法相比,Linux内核开发工作是一个非常个人化的过程。你的贡献 +和背后的想法将被仔细审查,往往导致批判和批评。审查将几乎总是需要改进,材料才 +能包括在内核中。要知道这是因为所有相关人员都希望看到Linux整体成功的最佳解决方 +案。这个开发过程已经被证明可以创建有史以来最健壮的操作系统内核,我们不想做任何 +事情来导致提交质量和最终结果的下降。 + +维护者 +------ + +行为准则多次使用“维护者”一词。在内核社区中,“维护者”是负责子系统、驱动程序或 +文件的任何人,并在内核源代码树的维护者文件中列出。 + +责任 +---- + +《行为准则》提到了维护人员的权利和责任,这需要进一步澄清。 + +首先,最重要的是,有一个合理的期望是由维护人员通过实例来领导。 + +也就是说,我们的社区是广阔的,对维护者没有新的要求,他们单方面处理其他人在 +他们活跃的社区的行为。这一责任由我们所有人承担,最终《行为准则》记录了最终的 +上诉路径,以防有关行为问题的问题悬而未决。 + +维护人员应该愿意在出现问题时提供帮助,并在需要时与社区中的其他人合作。如果您 +不确定如何处理出现的情况,请不要害怕联系技术咨询委员会(TAB)或其他维护人员。 +除非您愿意,否则不会将其视为违规报告。如果您不确定是否该联系TAB 或任何其他维 +护人员,请联系我们的冲突调解人 Mishi Choudhary <mishi@linux.com>。 + +最后,“善待对方”才是每个人的最终目标。我们知道每个人都是人,有时我们都会失败, +但我们所有人的首要目标应该是努力友好地解决问题。执行行为准则将是最后的选择。 + +我们的目标是创建一个强大的、技术先进的操作系统,以及所涉及的技术复杂性,这自 +然需要专业知识和决策。 + +所需的专业知识因贡献领域而异。它主要由上下文和技术复杂性决定,其次由贡献者和 +维护者的期望决定。 + +专家的期望和决策都要经过讨论,但在最后,为了取得进展,必须能够做出决策。这一 +特权掌握在维护人员和项目领导的手中,预计将善意使用。 + +因此,设定专业知识期望、作出决定和拒绝不适当的贡献不被视为违反行为准则。 + +虽然维护人员一般都欢迎新来者,但他们帮助(新)贡献者克服障碍的能力有限,因此 +他们必须确定优先事项。这也不应被视为违反了行为准则。内核社区意识到这一点,并 +以各种形式提供入门级节目,如 kernelnewbies.org 。 + +范围 +---- + +Linux内核社区主要在一组公共电子邮件列表上进行交互,这些列表分布在由多个不同 +公司或个人控制的多个不同服务器上。所有这些列表都在内核源代码树中的 +MAINTAINERS 文件中定义。发送到这些邮件列表的任何电子邮件都被视为包含在行为 +准则中。 + +使用 kernel.org bugzilla和其他子系统bugzilla 或bug跟踪工具的开发人员应该遵循 +行为准则的指导原则。Linux内核社区没有“官方”项目电子邮件地址或“官方”社交媒体 +地址。使用kernel.org电子邮件帐户执行的任何活动必须遵循为kernel.org发布的行为 +准则,就像任何使用公司电子邮件帐户的个人必须遵循该公司的特定规则一样。 + +行为准则并不禁止在邮件列表消息、内核更改日志消息或代码注释中继续包含名称、 +电子邮件地址和相关注释。 + +其他论坛中的互动包括在适用于上述论坛的任何规则中,通常不包括在行为准则中。 +除了在极端情况下可考虑的例外情况。 + +提交给内核的贡献应该使用适当的语言。在行为准则之前已经存在的内容现在不会被 +视为违反。然而,不适当的语言可以被视为一个bug;如果任何相关方提交补丁, +这样的bug将被更快地修复。当前属于用户/内核API的一部分的表达式,或者反映已 +发布标准或规范中使用的术语的表达式,不被视为bug。 + +执行 +---- + +行为准则中列出的地址属于行为准则委员会。https://kernel.org/code-of-conduct.html +列出了在任何给定时间接收这些电子邮件的确切成员。成员不能访问在加入委员会之前 +或离开委员会之后所做的报告。 + +最初的行为准则委员会由TAB的志愿者以及作为中立第三方的专业调解人组成。委员会 +的首要任务是建立文件化的流程,并将其公开。 + +如果报告人不希望将整个委员会纳入投诉或关切,可直接联系委员会的任何成员,包括 +调解人。 + +行为准则委员会根据流程审查案例(见上文),并根据需要和适当与TAB协商,例如请求 +和接收有关内核社区的信息。 + +委员会做出的任何决定都将提交到表中,以便在必要时与相关维护人员一起执行。行为 +准则委员会的决定可以通过三分之二的投票推翻。 + +每季度,行为准则委员会和标签将提供一份报告,概述行为准则委员会收到的匿名报告 +及其状态,以及任何否决决定的细节,包括完整和可识别的投票细节。 + +我们希望在启动期之后为行为准则委员会人员配备建立一个不同的流程。发生此情况时, +将使用该信息更新此文档。 diff --git a/Documentation/translations/zh_CN/process/code-of-conduct.rst b/Documentation/translations/zh_CN/process/code-of-conduct.rst new file mode 100644 index 000000000..99024df05 --- /dev/null +++ b/Documentation/translations/zh_CN/process/code-of-conduct.rst @@ -0,0 +1,72 @@ +.. include:: ../disclaimer-zh_CN.rst + +:Original: :ref:`Documentation/process/code-of-conduct.rst <code_of_conduct>` +:Translator: Alex Shi <alex.shi@linux.alibaba.com> + +.. _cn_code_of_conduct: + +贡献者契约行为准则 +++++++++++++++++++ + +我们的誓言 +========== + +为了营造一个开放、友好的环境,我们作为贡献者和维护人承诺,让我们的社区和参 +与者,拥有一个无骚扰的体验,无论年龄、体型、残疾、种族、性别特征、性别认同 +和表达、经验水平、教育程度、社会状况,经济地位、国籍、个人外貌、种族、宗教 +或性身份和取向。 + +我们的标准 +========== + +有助于创造积极环境的行为包括: + +* 使用欢迎和包容的语言 +* 尊重不同的观点和经验 +* 优雅地接受建设性的批评 +* 关注什么对社区最有利 +* 对其他社区成员表示同情 + +参与者的不可接受行为包括: + +* 使用性意味的语言或意象以及不受欢迎的性注意或者更过分的行为 +* 煽动、侮辱/贬损评论以及个人或政治攻击 +* 公开或私下骚扰 +* 未经明确许可,发布他人的私人信息,如物理或电子地址。 +* 在专业场合被合理认为不适当的其他行为 + +我们的责任 +========== + +维护人员负责澄清可接受行为的标准,并应针对任何不可接受行为采取适当和公平的 +纠正措施。 + +维护人员有权和责任删除、编辑或拒绝与本行为准则不一致的评论、承诺、代码、 +wiki编辑、问题和其他贡献,或暂时或永久禁止任何贡献者从事他们认为不适当、 +威胁、冒犯或有害的其他行为。 + +范围 +==== + +当个人代表项目或其社区时,本行为准则既适用于项目空间,也适用于公共空间。 +代表一个项目或社区的例子包括使用一个正式的项目电子邮件地址,通过一个正式 +的社交媒体帐户发布,或者在在线或离线事件中担任指定的代表。项目维护人员可以 +进一步定义和澄清项目的表示。 + +执行 +==== + +如有滥用、骚扰或其他不可接受的行为,可联系行为准则委员会<conduct@kernel.org>。 +所有投诉都将接受审查和调查,并将得到必要和适当的答复。行为准则委员会有义务 +对事件报告人保密。具体执行政策的进一步细节可单独公布。 + +归属 +==== + +本行为准则改编自《贡献者契约》,版本1.4,可从 +https://www.contributor-covenant.org/version/1/4/code-of-conduct.html 获取。 + +解释 +==== + +有关Linux内核社区如何解释此文档,请参阅 :ref:`cn_code_of_conduct_interpretation` diff --git a/Documentation/translations/zh_CN/process/coding-style.rst b/Documentation/translations/zh_CN/process/coding-style.rst new file mode 100644 index 000000000..fa28ef0a7 --- /dev/null +++ b/Documentation/translations/zh_CN/process/coding-style.rst @@ -0,0 +1,1083 @@ +.. include:: ../disclaimer-zh_CN.rst + +:Original: Documentation/process/coding-style.rst + +.. _cn_codingstyle: + +:译者: + - 张乐 Zhang Le <r0bertz@gentoo.org> + - Andy Deng <theandy.deng@gmail.com> + - 吴想成 <bobwxc@email.cn> + +:校译: + - 王聪 Wang Cong <xiyou.wangcong@gmail.com> + - wheelz <kernel.zeng@gmail.com> + - 管旭东 Xudong Guan <xudong.guan@gmail.com> + - Li Zefan <lizf@cn.fujitsu.com> + - Wang Chen <wangchen@cn.fujitsu.com> + +Linux 内核代码风格 +================== + +这是一个简短的文档,描述了 linux 内核的首选代码风格。代码风格是因人而异的, +而且我不愿意把自己的观点强加给任何人,但这就像我去做任何事情都必须遵循的原则 +那样,我也希望在绝大多数事上保持这种的态度。请 (在写代码时) 至少考虑一下这里 +的代码风格。 + +首先,我建议你打印一份 GNU 代码规范,然后不要读。烧了它,这是一个具有重大象征 +性意义的动作。 + +不管怎样,现在我们开始: + + +1) 缩进 +------- + +制表符是 8 个字符,所以缩进也是 8 个字符。有些异端运动试图将缩进变为 4 (甚至 +2!) 字符深,这几乎相当于尝试将圆周率的值定义为 3。 + +理由:缩进的全部意义就在于清楚的定义一个控制块起止于何处。尤其是当你盯着你的 +屏幕连续看了 20 小时之后,你将会发现大一点的缩进会使你更容易分辨缩进。 + +现在,有些人会抱怨 8 个字符的缩进会使代码向右边移动的太远,在 80 个字符的终端 +屏幕上就很难读这样的代码。这个问题的答案是,如果你需要 3 级以上的缩进,不管用 +何种方式你的代码已经有问题了,应该修正你的程序。 + +简而言之,8 个字符的缩进可以让代码更容易阅读,还有一个好处是当你的函数嵌套太 +深的时候可以给你警告。留心这个警告。 + +在 switch 语句中消除多级缩进的首选的方式是让 ``switch`` 和从属于它的 ``case`` +标签对齐于同一列,而不要 ``两次缩进`` ``case`` 标签。比如: + +.. code-block:: c + + switch (suffix) { + case 'G': + case 'g': + mem <<= 30; + break; + case 'M': + case 'm': + mem <<= 20; + break; + case 'K': + case 'k': + mem <<= 10; + fallthrough; + default: + break; + } + +不要把多个语句放在一行里,除非你有什么东西要隐藏: + +.. code-block:: c + + if (condition) do_this; + do_something_everytime; + +不要使用逗号来避免使用大括号: + +.. code-block:: c + + if (condition) + do_this(), do_that(); + +使用大括号包裹多语句: + +.. code-block:: c + + if (condition) { + do_this(); + do_that(); + } + +也不要在一行里放多个赋值语句。内核代码风格超级简单。就是避免可能导致别人误读 +的表达式。 + +除了注释、文档和 Kconfig 之外,不要使用空格来缩进,前面的例子是例外,是有意为 +之。 + +选用一个好的编辑器,不要在行尾留空格。 + + +2) 把长的行和字符串打散 +----------------------- + +代码风格的意义就在于使用平常使用的工具来维持代码的可读性和可维护性。 + +每一行的长度的限制是 80 列,我们强烈建议您遵守这个惯例。 + +长于 80 列的语句要打散成有意义的片段。除非超过 80 列能显著增加可读性,并且不 +会隐藏信息。 + +子片段要明显短于母片段,并明显靠右。一种非常常用的样式是将子体与函数左括号对齐。 + +这同样适用于有着很长参数列表的函数头。 + +然而,绝对不要打散对用户可见的字符串,例如 printk 信息,因为这样就 +很难对它们 grep。 + + +3) 大括号和空格的放置 +--------------------- + +C 语言风格中另外一个常见问题是大括号的放置。和缩进大小不同,选择或弃用某种放 +置策略并没有多少技术上的原因,不过首选的方式,就像 Kernighan 和 Ritchie 展示 +给我们的,是把起始大括号放在行尾,而把结束大括号放在行首,所以: + +.. code-block:: c + + if (x is true) { + we do y + } + +这适用于所有的非函数语句块 (if, switch, for, while, do)。比如: + +.. code-block:: c + + switch (action) { + case KOBJ_ADD: + return "add"; + case KOBJ_REMOVE: + return "remove"; + case KOBJ_CHANGE: + return "change"; + default: + return NULL; + } + +不过,有一个例外,那就是函数:函数的起始大括号放置于下一行的开头,所以: + +.. code-block:: c + + int function(int x) + { + body of function + } + +全世界的异端可能会抱怨这个不一致性是……呃……不一致,不过所有思维健全的人 +都知道 (a) K&R 是 **正确的** 并且 (b) K&R 是正确的。此外,不管怎样函数都是特 +殊的 (C 函数是不能嵌套的)。 + +注意结束大括号独自占据一行,除非它后面跟着同一个语句的剩余部分,也就是 do 语 +句中的 ``while`` 或者 if 语句中的 ``else`` ,像这样: + +.. code-block:: c + + do { + body of do-loop + } while (condition); + +和 + +.. code-block:: c + + if (x == y) { + .. + } else if (x > y) { + ... + } else { + .... + } + +理由:K&R。 + +也请注意这种大括号的放置方式也能使空 (或者差不多空的) 行的数量最小化,同时不 +失可读性。因此,由于你的屏幕上的新行是不可再生资源 (想想 25 行的终端屏幕),你 +将会有更多的空行来放置注释。 + +当只有一个单独的语句的时候,不用加不必要的大括号。 + +.. code-block:: c + + if (condition) + action(); + +和 + +.. code-block:: c + + if (condition) + do_this(); + else + do_that(); + +这并不适用于只有一个条件分支是单语句的情况;这时所有分支都要使用大括号: + +.. code-block:: c + + if (condition) { + do_this(); + do_that(); + } else { + otherwise(); + } + +3.1) 空格 +********* + +Linux 内核的空格使用方式 (主要) 取决于它是用于函数还是关键字。(大多数) 关键字 +后要加一个空格。值得注意的例外是 sizeof, typeof, alignof 和 __attribute__,这 +些关键字某些程度上看起来更像函数 (它们在 Linux 里也常常伴随小括号而使用,尽管 +在 C 里这样的小括号不是必需的,就像 ``struct fileinfo info;`` 声明过后的 +``sizeof info``)。 + +所以在这些关键字之后放一个空格:: + + if, switch, case, for, do, while + +但是不要在 sizeof, typeof, alignof 或者 __attribute__ 这些关键字之后放空格。 +例如, + +.. code-block:: c + + s = sizeof(struct file); + +不要在小括号里的表达式两侧加空格。这是一个 **反例** : + +.. code-block:: c + + s = sizeof( struct file ); + +当声明指针类型或者返回指针类型的函数时, ``*`` 的首选使用方式是使之靠近变量名 +或者函数名,而不是靠近类型名。例子: + +.. code-block:: c + + char *linux_banner; + unsigned long long memparse(char *ptr, char **retptr); + char *match_strdup(substring_t *s); + +在大多数二元和三元操作符两侧使用一个空格,例如下面所有这些操作符:: + + = + - < > * / % | & ^ <= >= == != ? : + +但是一元操作符后不要加空格:: + + & * + - ~ ! sizeof typeof alignof __attribute__ defined + +后缀自加和自减一元操作符前不加空格:: + + ++ -- + +前缀自加和自减一元操作符后不加空格:: + + ++ -- + +``.`` 和 ``->`` 结构体成员操作符前后不加空格。 + +不要在行尾留空白。有些可以自动缩进的编辑器会在新行的行首加入适量的空白,然后 +你就可以直接在那一行输入代码。不过假如你最后没有在那一行输入代码,有些编辑器 +就不会移除已经加入的空白,就像你故意留下一个只有空白的行。包含行尾空白的行就 +这样产生了。 + +当 git 发现补丁包含了行尾空白的时候会警告你,并且可以应你的要求去掉行尾空白; +不过如果你是正在打一系列补丁,这样做会导致后面的补丁失败,因为你改变了补丁的 +上下文。 + + +4) 命名 +------- + +C 是一个简朴的语言,你的命名也应该这样。和 Modula-2 和 Pascal 程序员不同, +C 程序员不使用类似 ThisVariableIsATemporaryCounter 这样华丽的名字。C 程序员会 +称那个变量为 ``tmp`` ,这样写起来会更容易,而且至少不会令其难于理解。 + +不过,虽然混用大小写的名字是不提倡使用的,但是全局变量还是需要一个具描述性的 +名字。称一个全局函数为 ``foo`` 是一个难以饶恕的错误。 + +全局变量 (只有当你 **真正** 需要它们的时候再用它) 需要有一个具描述性的名字,就 +像全局函数。如果你有一个可以计算活动用户数量的函数,你应该叫它 +``count_active_users()`` 或者类似的名字,你不应该叫它 ``cntuser()`` 。 + +在函数名中包含函数类型 (所谓的匈牙利命名法) 是脑子出了问题——编译器知道那些类 +型而且能够检查那些类型,这样做只能把程序员弄糊涂了。 + +本地变量名应该简短,而且能够表达相关的含义。如果你有一些随机的整数型的循环计 +数器,它应该被称为 ``i`` 。叫它 ``loop_counter`` 并无益处,如果它没有被误解的 +可能的话。类似的, ``tmp`` 可以用来称呼任意类型的临时变量。 + +如果你怕混淆了你的本地变量名,你就遇到另一个问题了,叫做函数增长荷尔蒙失衡综 +合征。请看第六章 (函数)。 + +对于符号名称和文档,避免引入新的“master/slave”(或独立于“master”的“slave”) +和“blacklist/whitelist”。 + +“master/slave”推荐替换为: + '{primary,main} / {secondary,replica,subordinate}' + '{initiator,requester} / {target,responder}' + '{controller,host} / {device,worker,proxy}' + 'leader/follower' + 'director/performer' + +“blacklist/whitelist”推荐替换为: + 'denylist/allowlist' + 'blocklist/passlist' + +引入新用法的例外情况是:维护用户空间ABI/API,或更新现有(截至2020年)硬件或 +协议规范的代码时要求这些术语。对于新规范,尽可能将术语的规范用法转换为内核 +编码标准。 + +.. warning:: + 以上主从、黑白名单规则不适用于中文文档,请勿更改中文术语! + +5) Typedef +---------- + +不要使用类似 ``vps_t`` 之类的东西。 + +对结构体和指针使用 typedef 是一个 **错误** 。当你在代码里看到: + +.. code-block:: c + + vps_t a; + +这代表什么意思呢? + +相反,如果是这样 + +.. code-block:: c + + struct virtual_container *a; + +你就知道 ``a`` 是什么了。 + +很多人认为 typedef ``能提高可读性`` 。实际不是这样的。它们只在下列情况下有用: + + (a) 完全不透明的对象 (这种情况下要主动使用 typedef 来 **隐藏** 这个对象实际上 + 是什么)。 + + 例如: ``pte_t`` 等不透明对象,你只能用合适的访问函数来访问它们。 + + .. note:: + + 不透明性和“访问函数”本身是不好的。我们使用 pte_t 等类型的原因在于真 + 的是完全没有任何共用的可访问信息。 + + (b) 清楚的整数类型,如此,这层抽象就可以 **帮助** 消除到底是 ``int`` 还是 + ``long`` 的混淆。 + + u8/u16/u32 是完全没有问题的 typedef,不过它们更符合类别 (d) 而不是这里。 + + .. note:: + + 要这样做,必须事出有因。如果某个变量是 ``unsigned long`` ,那么没有必要 + + typedef unsigned long myflags_t; + + 不过如果有一个明确的原因,比如它在某种情况下可能会是一个 ``unsigned int`` + 而在其他情况下可能为 ``unsigned long`` ,那么就不要犹豫,请务必使用 + typedef。 + + (c) 当你使用 sparse 按字面的创建一个 **新** 类型来做类型检查的时候。 + + (d) 和标准 C99 类型相同的类型,在某些例外的情况下。 + + 虽然让眼睛和脑筋来适应新的标准类型比如 ``uint32_t`` 不需要花很多时间,可 + 是有些人仍然拒绝使用它们。 + + 因此,Linux 特有的等同于标准类型的 ``u8/u16/u32/u64`` 类型和它们的有符号 + 类型是被允许的——尽管在你自己的新代码中,它们不是强制要求要使用的。 + + 当编辑已经使用了某个类型集的已有代码时,你应该遵循那些代码中已经做出的选 + 择。 + + (e) 可以在用户空间安全使用的类型。 + + 在某些用户空间可见的结构体里,我们不能要求 C99 类型而且不能用上面提到的 + ``u32`` 类型。因此,我们在与用户空间共享的所有结构体中使用 __u32 和类似 + 的类型。 + +可能还有其他的情况,不过基本的规则是 **永远不要** 使用 typedef,除非你可以明 +确的应用上述某个规则中的一个。 + +总的来说,如果一个指针或者一个结构体里的元素可以合理的被直接访问到,那么它们 +就不应该是一个 typedef。 + + +6) 函数 +------- + +函数应该简短而漂亮,并且只完成一件事情。函数应该可以一屏或者两屏显示完 (我们 +都知道 ISO/ANSI 屏幕大小是 80x24),只做一件事情,而且把它做好。 + +一个函数的最大长度是和该函数的复杂度和缩进级数成反比的。所以,如果你有一个理 +论上很简单的只有一个很长 (但是简单) 的 case 语句的函数,而且你需要在每个 case +里做很多很小的事情,这样的函数尽管很长,但也是可以的。 + +不过,如果你有一个复杂的函数,而且你怀疑一个天分不是很高的高中一年级学生可能 +甚至搞不清楚这个函数的目的,你应该严格遵守前面提到的长度限制。使用辅助函数, +并为之取个具描述性的名字 (如果你觉得它们的性能很重要的话,可以让编译器内联它 +们,这样的效果往往会比你写一个复杂函数的效果要好。) + +函数的另外一个衡量标准是本地变量的数量。此数量不应超过 5-10 个,否则你的函数 +就有问题了。重新考虑一下你的函数,把它分拆成更小的函数。人的大脑一般可以轻松 +的同时跟踪 7 个不同的事物,如果再增多的话,就会糊涂了。即便你聪颖过人,你也可 +能会记不清你 2 个星期前做过的事情。 + +在源文件里,使用空行隔开不同的函数。如果该函数需要被导出,它的 **EXPORT** 宏 +应该紧贴在它的结束大括号之下。比如: + +.. code-block:: c + + int system_is_up(void) + { + return system_state == SYSTEM_RUNNING; + } + EXPORT_SYMBOL(system_is_up); + +6.1) 函数原型 +************* + +在函数原型中包含参数名和它们的数据类型。虽然 C 语言里没有这样的要求,但在 +Linux 里这是提倡的做法,因为这样可以很简单的给读者提供更多的有价值的信息。 + +不要在函数声明里使用 ``extern`` 关键字,因为这会导致代码行变长,并且不是严格 +必需的。 + +写函数原型时,请保持 `元素顺序规则 <https://lore.kernel.org/mm-commits/CAHk-=wiOCLRny5aifWNhr621kYrJwhfURsa0vFPeUEm8mF0ufg@mail.gmail.com/>`_ 。 +例如下列函数声明:: + + __init void * __must_check action(enum magic value, size_t size, u8 count, + char *fmt, ...) __printf(4, 5) __malloc; + +推荐的函数原型元素顺序是: + +- 储存类型(下方的 ``static __always_inline`` ,注意 ``__always_inline`` + 技术上来讲是个属性但被当做 ``inline`` ) +- 储存类型属性(上方的 ``__init`` ——即节声明,但也像 ``__cold`` ) +- 返回类型(上方的 ``void *`` ) +- 返回类型属性(上方的 ``__must_check`` ) +- 函数名(上方的 ``action`` ) +- 函数参数(上方的 ``(enum magic value, size_t size, u8 count, char *fmt, ...)`` , + 注意必须写上参数名) +- 函数参数属性(上方的 ``__printf(4, 5)`` ) +- 函数行为属性(上方的 ``__malloc`` ) + +请注意,对于函数 **定义** (即实际函数体),编译器不允许在函数参数之后添加函 +数参数属性。在这种情况下,它们应该跟随存储类型属性(例如,与上面的 **声明** +示例相比,请注意下面的 ``__printf(4, 5)`` 的位置发生了变化):: + + static __always_inline __init __printf(4, 5) void * __must_check action(enum magic value, + size_t size, u8 count, char *fmt, ...) __malloc + { + ... + } + +7) 集中的函数退出途径 +--------------------- + +虽然被某些人声称已经过时,但是 goto 语句的等价物还是经常被编译器所使用,具体 +形式是无条件跳转指令。 + +当一个函数从多个位置退出,并且需要做一些类似清理的常见操作时,goto 语句就很方 +便了。如果并不需要清理操作,那么直接 return 即可。 + +选择一个能够说明 goto 行为或它为何存在的标签名。如果 goto 要释放 ``buffer``, +一个不错的名字可以是 ``out_free_buffer:`` 。别去使用像 ``err1:`` 和 ``err2:`` +这样的GW_BASIC 名称,因为一旦你添加或删除了 (函数的) 退出路径,你就必须对它们 +重新编号,这样会难以去检验正确性。 + +使用 goto 的理由是: + +- 无条件语句容易理解和跟踪 +- 嵌套程度减小 +- 可以避免由于修改时忘记更新个别的退出点而导致错误 +- 让编译器省去删除冗余代码的工作 ;) + +.. code-block:: c + + int fun(int a) + { + int result = 0; + char *buffer; + + buffer = kmalloc(SIZE, GFP_KERNEL); + if (!buffer) + return -ENOMEM; + + if (condition1) { + while (loop1) { + ... + } + result = 1; + goto out_free_buffer; + } + ... + out_free_buffer: + kfree(buffer); + return result; + } + +一个需要注意的常见错误是 ``单 err 错误`` ,就像这样: + +.. code-block:: c + + err: + kfree(foo->bar); + kfree(foo); + return ret; + +这段代码的错误是,在某些退出路径上 ``foo`` 是 NULL。通常情况下,通过把它分离 +成两个错误标签 ``err_free_bar:`` 和 ``err_free_foo:`` 来修复这个错误: + +.. code-block:: c + + err_free_bar: + kfree(foo->bar); + err_free_foo: + kfree(foo); + return ret; + +理想情况下,你应该模拟错误来测试所有退出路径。 + + +8) 注释 +------- + +注释是好的,不过有过度注释的危险。永远不要在注释里解释你的代码是如何运作的: +更好的做法是让别人一看你的代码就可以明白,解释写的很差的代码是浪费时间。 + +一般来说你用注释告诉别人你的代码做了什么,而不是怎么做的。也请你不要把 +注释放在一个函数体内部:如果函数复杂到你需要独立的注释其中的一部分,你很可能 +需要回到第六章看一看。你可以做一些小注释来注明或警告某些很聪明 (或者槽糕) 的 +做法,但不要加太多。你应该做的,是把注释放在函数的头部,告诉人们它做了什么, +也可以加上它做这些事情的原因。 + +当注释内核 API 函数时,请使用 kernel-doc 格式。详见 +Documentation/translations/zh_CN/doc-guide/index.rst 和 scripts/kernel-doc 。 + +长 (多行) 注释的首选风格是: + +.. code-block:: c + + /* + * This is the preferred style for multi-line + * comments in the Linux kernel source code. + * Please use it consistently. + * + * Description: A column of asterisks on the left side, + * with beginning and ending almost-blank lines. + */ + +对于在 net/ 和 drivers/net/ 的文件,首选的长 (多行) 注释风格有些不同。 + +.. code-block:: c + + /* The preferred comment style for files in net/ and drivers/net + * looks like this. + * + * It is nearly the same as the generally preferred comment style, + * but there is no initial almost-blank line. + */ + +注释数据也是很重要的,不管是基本类型还是衍生类型。为了方便实现这一点,每一行 +应只声明一个数据 (不要使用逗号来一次声明多个数据)。这样你就有空间来为每个数据 +写一段小注释来解释它们的用途了。 + + +9) 你已经把事情弄糟了 +--------------------- + +这没什么,我们都是这样。可能你长期使用 Unix 的朋友已经告诉你 +``GNU emacs`` 能自动帮你格式化 C 源代码,而且你也注意到了,确实是这样,不过它 +所使用的默认值和我们想要的相去甚远 (实际上,甚至比随机打的还要差——无数个猴子 +在 GNU emacs 里打字永远不会创造出一个好程序) +*(译注:Infinite Monkey Theorem)* + +所以你要么放弃 GNU emacs,要么改变它让它使用更合理的设定。要采用后一个方案, +你可以把下面这段粘贴到你的 .emacs 文件里。 + +.. code-block:: elisp + + (defun c-lineup-arglist-tabs-only (ignored) + "Line up argument lists by tabs, not spaces" + (let* ((anchor (c-langelem-pos c-syntactic-element)) + (column (c-langelem-2nd-pos c-syntactic-element)) + (offset (- (1+ column) anchor)) + (steps (floor offset c-basic-offset))) + (* (max steps 1) + c-basic-offset))) + + (dir-locals-set-class-variables + 'linux-kernel + '((c-mode . ( + (c-basic-offset . 8) + (c-label-minimum-indentation . 0) + (c-offsets-alist . ( + (arglist-close . c-lineup-arglist-tabs-only) + (arglist-cont-nonempty . + (c-lineup-gcc-asm-reg c-lineup-arglist-tabs-only)) + (arglist-intro . +) + (brace-list-intro . +) + (c . c-lineup-C-comments) + (case-label . 0) + (comment-intro . c-lineup-comment) + (cpp-define-intro . +) + (cpp-macro . -1000) + (cpp-macro-cont . +) + (defun-block-intro . +) + (else-clause . 0) + (func-decl-cont . +) + (inclass . +) + (inher-cont . c-lineup-multi-inher) + (knr-argdecl-intro . 0) + (label . -1000) + (statement . 0) + (statement-block-intro . +) + (statement-case-intro . +) + (statement-cont . +) + (substatement . +) + )) + (indent-tabs-mode . t) + (show-trailing-whitespace . t) + )))) + + (dir-locals-set-directory-class + (expand-file-name "~/src/linux-trees") + 'linux-kernel) + +这会让 emacs 在 ``~/src/linux-trees`` 下的 C 源文件获得更好的内核代码风格。 + +不过就算你尝试让 emacs 正确的格式化代码失败了,也并不意味着你失去了一切:还可 +以用 ``indent`` 。 + +不过,GNU indent 也有和 GNU emacs 一样有问题的设定,所以你需要给它一些命令选 +项。不过,这还不算太糟糕,因为就算是 GNU indent 的作者也认同 K&R 的权威性 +(GNU 的人并不是坏人,他们只是在这个问题上被严重的误导了),所以你只要给 indent +指定选项 ``-kr -i8`` (代表 ``K&R,8 字符缩进``),或使用 ``scripts/Lindent`` +这样就可以以最时髦的方式缩进源代码。 + +``indent`` 有很多选项,特别是重新格式化注释的时候,你可能需要看一下它的手册。 +不过记住: ``indent`` 不能修正坏的编程习惯。 + +请注意,您还可以使用 ``clang-format`` 工具帮助您处理这些规则,快速自动重新格 +式化部分代码,并审阅整个文件以发现代码风格错误、打字错误和可能的改进。它还可 +以方便地排序 ``#include`` ,对齐变量/宏,重排文本和其他类似任务。 +详见 Documentation/process/clang-format.rst 。 + + +10) Kconfig 配置文件 +-------------------- + +对于遍布源码树的所有 Kconfig* 配置文件来说,它们缩进方式有所不同。紧挨着 +``config`` 定义的行,用一个制表符缩进,然而 help 信息的缩进则额外增加 2 个空 +格。举个例子:: + + config AUDIT + bool "Auditing support" + depends on NET + help + Enable auditing infrastructure that can be used with another + kernel subsystem, such as SELinux (which requires this for + logging of avc messages output). Does not do system-call + auditing without CONFIG_AUDITSYSCALL. + +而那些危险的功能 (比如某些文件系统的写支持) 应该在它们的提示字符串里显著的声 +明这一点:: + + config ADFS_FS_RW + bool "ADFS write support (DANGEROUS)" + depends on ADFS_FS + ... + +要查看配置文件的完整文档,请看 Documentation/kbuild/kconfig-language.rst 。 + + +11) 数据结构 +------------ + +如果一个数据结构,在创建和销毁它的单线执行环境之外可见,那么它必须要有一个引 +用计数器。内核里没有垃圾收集 (并且内核之外的垃圾收集慢且效率低下),这意味着你 +绝对需要记录你对这种数据结构的使用情况。 + +引用计数意味着你能够避免上锁,并且允许多个用户并行访问这个数据结构——而不需要 +担心这个数据结构仅仅因为暂时不被使用就消失了,那些用户可能不过是沉睡了一阵或 +者做了一些其他事情而已。 + +注意上锁 **不能** 取代引用计数。上锁是为了保持数据结构的一致性,而引用计数是一 +个内存管理技巧。通常二者都需要,不要把两个搞混了。 + +很多数据结构实际上有 2 级引用计数,它们通常有不同 ``类`` 的用户。子类计数器统 +计子类用户的数量,每当子类计数器减至零时,全局计数器减一。 + +这种 ``多级引用计数`` 的例子可以在内存管理 (``struct mm_struct``: mm_users 和 +mm_count),和文件系统 (``struct super_block``: s_count 和 s_active) 中找到。 + +记住:如果另一个执行线索可以找到你的数据结构,但这个数据结构没有引用计数器, +这里几乎肯定是一个 bug。 + + +12) 宏,枚举和RTL +----------------- + +用于定义常量的宏的名字及枚举里的标签需要大写。 + +.. code-block:: c + + #define CONSTANT 0x12345 + +在定义几个相关的常量时,最好用枚举。 + +宏的名字请用大写字母,不过形如函数的宏的名字可以用小写字母。 + +通常如果能写成内联函数就不要写成像函数的宏。 + +含有多个语句的宏应该被包含在一个 do-while 代码块里: + +.. code-block:: c + + #define macrofun(a, b, c) \ + do { \ + if (a == 5) \ + do_this(b, c); \ + } while (0) + +使用宏的时候应避免的事情: + +1) 影响控制流程的宏: + +.. code-block:: c + + #define FOO(x) \ + do { \ + if (blah(x) < 0) \ + return -EBUGGERED; \ + } while (0) + +**非常** 不好。它看起来像一个函数,不过却能导致 ``调用`` 它的函数退出;不要打 +乱读者大脑里的语法分析器。 + +2) 依赖于一个固定名字的本地变量的宏: + +.. code-block:: c + + #define FOO(val) bar(index, val) + +可能看起来像是个不错的东西,不过它非常容易把读代码的人搞糊涂,而且容易导致看起 +来不相关的改动带来错误。 + +3) 作为左值的带参数的宏: FOO(x) = y;如果有人把 FOO 变成一个内联函数的话,这 + 种用法就会出错了。 + +4) 忘记了优先级:使用表达式定义常量的宏必须将表达式置于一对小括号之内。带参数 + 的宏也要注意此类问题。 + +.. code-block:: c + + #define CONSTANT 0x4000 + #define CONSTEXP (CONSTANT | 3) + +5) 在宏里定义类似函数的本地变量时命名冲突: + +.. code-block:: c + + #define FOO(x) \ + ({ \ + typeof(x) ret; \ + ret = calc_ret(x); \ + (ret); \ + }) + +ret 是本地变量的通用名字—— __foo_ret 更不容易与一个已存在的变量冲突。 + +cpp 手册对宏的讲解很详细。gcc internals 手册也详细讲解了 RTL,内核里的汇编语 +言经常用到它。 + + +13) 打印内核消息 +---------------- + +内核开发者应该看起来有文化。请一定注意内核信息的拼写,以给人良好的印象。 +不要用不规范的单词比如 ``dont``,而要用 ``do not`` 或者 ``don't`` 。保证这些信 +息简单明了、无歧义。 + +内核信息不必以英文句号结束。 + +在小括号里打印数字 (%d) 没有任何价值,应该避免这样做。 + +<linux/device.h> 里有一些驱动模型诊断宏,你应该使用它们,以确保信息对应于正确 +的设备和驱动,并且被标记了正确的消息级别。这些宏有:dev_err(), dev_warn(), +dev_info() 等等。对于那些不和某个特定设备相关连的信息,<linux/printk.h> 定义 +了 pr_notice(), pr_info(), pr_warn(), pr_err() 和其他。 + +写出好的调试信息可以是一个很大的挑战;一旦你写出后,这些信息在远程除错时能提 +供极大的帮助。然而打印调试信息的处理方式同打印非调试信息不同。其他 pr_XXX() +函数能无条件地打印,pr_debug() 却不;默认情况下它不会被编译,除非定义了 DEBUG +或设定了 CONFIG_DYNAMIC_DEBUG。实际这同样是为了 dev_dbg(),一个相关约定是在一 +个已经开启了 DEBUG 时,使用 VERBOSE_DEBUG 来添加 dev_vdbg()。 + +许多子系统拥有 Kconfig 调试选项来开启对应 Makefile 里面的 -DDEBUG;在其他 +情况下,特殊文件使用 #define DEBUG。当一条调试信息需要被无条件打印时,例如, +如果已经包含一个调试相关的 #ifdef 条件,printk(KERN_DEBUG ...) 就可被使用。 + + +14) 分配内存 +------------ + +内核提供了下面的一般用途的内存分配函数: +kmalloc(), kzalloc(), kmalloc_array(), kcalloc(), vmalloc() 和 vzalloc()。 +请参考 API 文档以获取有关它们的详细信息: +Documentation/translations/zh_CN/core-api/memory-allocation.rst 。 + +传递结构体大小的首选形式是这样的: + +.. code-block:: c + + p = kmalloc(sizeof(*p), ...); + +另外一种传递方式中,sizeof 的操作数是结构体的名字,这样会降低可读性,并且可能 +会引入 bug。有可能指针变量类型被改变时,而对应的传递给内存分配函数的 sizeof +的结果不变。 + +强制转换一个 void 指针返回值是多余的。C 语言本身保证了从 void 指针到其他任何 +指针类型的转换是没有问题的。 + +分配一个数组的首选形式是这样的: + +.. code-block:: c + + p = kmalloc_array(n, sizeof(...), ...); + +分配一个零长数组的首选形式是这样的: + +.. code-block:: c + + p = kcalloc(n, sizeof(...), ...); + +两种形式都会检查分配 n * sizeof(...) 大小时内存的溢出,如果溢出返回 NULL。 + +在没有 __GFP_NOWARN 的情况下使用时,这些通用分配函数都会在失败时发起堆栈转储, +因此当返回NULL时,没有必要发出额外的失败消息。 + +15) 内联弊病 +------------ + +有一个常见的误解是 ``内联`` 是 gcc 提供的可以让代码运行更快的一个选项。虽然使 +用内联函数有时候是恰当的 (比如作为一种替代宏的方式,请看第十二章),不过很多情 +况下不是这样。inline 的过度使用会使内核变大,从而使整个系统运行速度变慢。 +因为体积大内核会占用更多的指令高速缓存,而且会导致 pagecache 的可用内存减少。 +想象一下,一次 pagecache 未命中就会导致一次磁盘寻址,将耗时 5 毫秒。5 毫秒的 +时间内 CPU 能执行很多很多指令。 + +一个基本的原则是如果一个函数有 3 行以上,就不要把它变成内联函数。这个原则的一 +个例外是,如果你知道某个参数是一个编译时常量,而且因为这个常量你确定编译器在 +编译时能优化掉你的函数的大部分代码,那仍然可以给它加上 inline 关键字。 +kmalloc() 内联函数就是一个很好的例子。 + +人们经常主张给 static 的而且只用了一次的函数加上 inline,如此不会有任何损失, +因为没有什么好权衡的。虽然从技术上说这是正确的,但是实际上这种情况下即使不加 +inline gcc 也可以自动使其内联。而且其他用户可能会要求移除 inline,由此而来的 +争论会抵消 inline 自身的潜在价值,得不偿失。 + + +16) 函数返回值及命名 +-------------------- + +函数可以返回多种不同类型的值,最常见的一种是表明函数执行成功或者失败的值。这样 +的一个值可以表示为一个错误代码整数 (-Exxx=失败,0=成功) 或者一个 ``成功`` +布尔值 (0=失败,非0=成功)。 + +混合使用这两种表达方式是难于发现的 bug 的来源。如果 C 语言本身严格区分整形和 +布尔型变量,那么编译器就能够帮我们发现这些错误... 不过 C 语言不区分。为了避免 +产生这种 bug,请遵循下面的惯例:: + + 如果函数的名字是一个动作或者强制性的命令,那么这个函数应该返回错误代 + 码整数。如果是一个判断,那么函数应该返回一个“成功”布尔值。 + +比如, ``add work`` 是一个命令,所以 add_work() 在成功时返回 0,在失败时返回 +-EBUSY。类似的,因为 ``PCI device present`` 是一个判断,所以 pci_dev_present() +在成功找到一个匹配的设备时应该返回 1,如果找不到时应该返回 0。 + +所有 EXPORTed 函数都必须遵守这个惯例,所有的公共函数也都应该如此。私有 +(static) 函数不需要如此,但是我们也推荐这样做。 + +返回值是实际计算结果而不是计算是否成功的标志的函数不受此惯例的限制。通常 +他们通过返回一些正常值范围之外的结果来表示出错。典型的例子是返回指针的函数, +他们使用 NULL 或者 ERR_PTR 机制来报告错误。 + +17) 使用布尔类型 +---------------- + +Linux内核布尔(bool)类型是C99 _Bool类型的别名。布尔值只能为0或1,而对布尔的 +隐式或显式转换将自动将值转换为true或false。在使用布尔类型时 **不需要** 构造, +它会消除一类错误。 + +使用布尔值时,应使用true和false定义,而不是1和0。 + +布尔函数返回类型和堆栈变量总是可以在适当的时候使用。鼓励使用布尔来提高可读性, +并且布尔值在存储时通常比“int”更好。 + +如果缓存行布局或值的大小很重要,请不要使用布尔,因为其大小和对齐方式根据编译 +的体系结构而不同。针对对齐和大小进行优化的结构体不应使用布尔。 + +如果一个结构体有多个true/false值,请考虑将它们合并为具有1比特成员的位域,或使 +用适当的固定宽度类型,如u8。 + +类似地,对于函数参数,多个true/false值可以合并为单个按位的“标志”参数,如果调 +用点具有裸true/false常量,“标志”参数通常是更具可读性的替代方法。 + +总之,在结构体和参数中有限地使用布尔可以提高可读性。 + +18) 不要重新发明内核宏 +---------------------- + +头文件 include/linux/kernel.h 包含了一些宏,你应该使用它们,而不要自己写一些 +它们的变种。比如,如果你需要计算一个数组的长度,使用这个宏 + +.. code-block:: c + + #define ARRAY_SIZE(x) (sizeof(x) / sizeof((x)[0])) + +类似的,如果你要计算某结构体成员的大小,使用 + +.. code-block:: c + + #define sizeof_field(t, f) (sizeof(((t*)0)->f)) + +还有可以做严格的类型检查的 min() 和 max() 宏,如果你需要可以使用它们。你可以 +自己看看那个头文件里还定义了什么你可以拿来用的东西,如果有定义的话,你就不应 +在你的代码里自己重新定义。 + + +19) 编辑器模式行和其他需要罗嗦的事情 +------------------------------------ + +有一些编辑器可以解释嵌入在源文件里的由一些特殊标记标明的配置信息。比如,emacs +能够解析被标记成这样的行: + +.. code-block:: c + + -*- mode: c -*- + +或者这样的: + +.. code-block:: c + + /* + Local Variables: + compile-command: "gcc -DMAGIC_DEBUG_FLAG foo.c" + End: + */ + +Vim 能够解析这样的标记: + +.. code-block:: c + + /* vim:set sw=8 noet */ + +不要在源代码中包含任何这样的内容。每个人都有他自己的编辑器配置,你的源文件不 +应该覆盖别人的配置。这包括有关缩进和模式配置的标记。人们可以使用他们自己定制 +的模式,或者使用其他可以产生正确的缩进的巧妙方法。 + + +20) 内联汇编 +------------ + +在特定架构的代码中,你可能需要内联汇编与 CPU 和平台相关功能连接。需要这么做时 +就不要犹豫。然而,当 C 可以完成工作时,不要平白无故地使用内联汇编。在可能的情 +况下,你可以并且应该用 C 和硬件沟通。 + +请考虑去写捆绑通用位元 (wrap common bits) 的内联汇编的简单辅助函数,别去重复 +地写下只有细微差异内联汇编。记住内联汇编可以使用 C 参数。 + +大型,有一定复杂度的汇编函数应该放在 .S 文件内,用相应的 C 原型定义在 C 头文 +件中。汇编函数的 C 原型应该使用 ``asmlinkage`` 。 + +你可能需要把汇编语句标记为 volatile,用来阻止 GCC 在没发现任何副作用后就把它 +移除了。你不必总是这样做,尽管,这不必要的举动会限制优化。 + +在写一个包含多条指令的单个内联汇编语句时,把每条指令用引号分割而且各占一行, +除了最后一条指令外,在每个指令结尾加上 ``\n\t`` ,让汇编输出时可以正确地缩进 +下一条指令: + +.. code-block:: c + + asm ("magic %reg1, #42\n\t" + "more_magic %reg2, %reg3" + : /* outputs */ : /* inputs */ : /* clobbers */); + + +21) 条件编译 +------------ + +只要可能,就不要在 .c 文件里面使用预处理条件 (#if, #ifdef);这样做会让代码更难 +阅读并且更难去跟踪逻辑。替代方案是,在头文件中用预处理条件提供给那些 .c 文件 +使用,再给 #else 提供一个空桩 (no-op stub) 版本,然后在 .c 文件内无条件地调用 +那些 (定义在头文件内的) 函数。这样做,编译器会避免为桩函数 (stub) 的调用生成 +任何代码,产生的结果是相同的,但逻辑将更加清晰。 + +最好倾向于编译整个函数,而不是函数的一部分或表达式的一部分。与其放一个 ifdef +在表达式内,不如分解出部分或全部表达式,放进一个单独的辅助函数,并应用预处理 +条件到这个辅助函数内。 + +如果你有一个在特定配置中,可能变成未使用的函数或变量,编译器会警告它定义了但 +未使用,请把它标记为 __maybe_unused 而不是将它包含在一个预处理条件中。(然而, +如果一个函数或变量总是未使用,就直接删除它。) + +在代码中,尽可能地使用 IS_ENABLED 宏来转化某个 Kconfig 标记为 C 的布尔 +表达式,并在一般的 C 条件中使用它: + +.. code-block:: c + + if (IS_ENABLED(CONFIG_SOMETHING)) { + ... + } + +编译器会做常量折叠,然后就像使用 #ifdef 那样去包含或排除代码块,所以这不会带 +来任何运行时开销。然而,这种方法依旧允许 C 编译器查看块内的代码,并检查它的正 +确性 (语法,类型,符号引用,等等)。因此,如果条件不满足,代码块内的引用符号就 +不存在时,你还是必须去用 #ifdef。 + +在任何有意义的 #if 或 #ifdef 块的末尾 (超过几行的),在 #endif 同一行的后面写下 +注解,注释这个条件表达式。例如: + +.. code-block:: c + + #ifdef CONFIG_SOMETHING + ... + #endif /* CONFIG_SOMETHING */ + + +附录 I) 参考资料 +---------------- + +The C Programming Language, 2nd Edition +作者:Brian W. Kernighan 和 Denni M. Ritchie. +Prentice Hall, Inc., 1988. +ISBN 0-13-110362-8 (平装), 0-13-110370-9 (精装). + +.. note:: + + 《C程序设计语言(第2版)》 + 作者:[美] Brian W. Kernighan / [美] Dennis M. Ritchie + 译者:徐宝文 / 李志 / 尤晋元(审校) + 出版社:机械工业出版社,2019 + ISBN:9787111617945 + +The Practice of Programming +作者:Brian W. Kernighan 和 Rob Pike. +Addison-Wesley, Inc., 1999. +ISBN 0-201-61586-X. + +.. note:: + + 《程序设计实践》 + 作者:[美] Brian W. Kernighan / [美] Rob Pike + 出版社:机械工业出版社,2005 + ISBN:9787111091578 + + 《程序设计实践》 + 作者:[美] Brian W. Kernighan / Rob Pike + 译者:裘宗燕 + 出版社:机械工业出版社,2000 + ISBN:9787111075738 + +GNU 手册 - 遵循 K&R 标准和此文本 - cpp, gcc, gcc internals and indent, +都可以从 https://www.gnu.org/manual/ 找到 + +WG14 是 C 语言的国际标准化工作组,URL: http://www.open-std.org/JTC1/SC22/WG14/ + +内核文档 Documentation/process/coding-style.rst, +作者 greg@kroah.com 发表于 OLS 2002: +http://www.kroah.com/linux/talks/ols_2002_kernel_codingstyle_talk/html/ diff --git a/Documentation/translations/zh_CN/process/development-process.rst b/Documentation/translations/zh_CN/process/development-process.rst new file mode 100644 index 000000000..30cffe66c --- /dev/null +++ b/Documentation/translations/zh_CN/process/development-process.rst @@ -0,0 +1,26 @@ +.. include:: ../disclaimer-zh_CN.rst + +:Original: :ref:`Documentation/process/development-process.rst <development_process_main>` +:Translator: Alex Shi <alex.shi@linux.alibaba.com> + +.. _cn_development_process_main: + +内核开发过程指南 +================ + +内容: + +.. toctree:: + :numbered: + :maxdepth: 2 + + 1.Intro + 2.Process + 3.Early-stage + 4.Coding + 5.Posting + 6.Followthrough + 7.AdvancedTopics + 8.Conclusion + +本文档的目的是帮助开发人员(及其经理)以最小的挫折感与开发社区合作。它试图记录这个社区如何以一种不熟悉Linux内核开发(或者实际上是自由软件开发)的人可以访问的方式工作。虽然这里有一些技术资料,但这是一个面向过程的讨论,不需要深入了解内核编程就可以理解。 diff --git a/Documentation/translations/zh_CN/process/email-clients.rst b/Documentation/translations/zh_CN/process/email-clients.rst new file mode 100644 index 000000000..34d51cdad --- /dev/null +++ b/Documentation/translations/zh_CN/process/email-clients.rst @@ -0,0 +1,327 @@ +.. SPDX-License-Identifier: GPL-2.0-or-later + +.. include:: ../disclaimer-zh_CN.rst + +.. _cn_email_clients: + +:Original: Documentation/process/email-clients.rst + +:译者: + - 贾威威 Harry Wei <harryxiyou@gmail.com> + - 时奎亮 Alex Shi <alexs@kernel.org> + - 吴想成 Wu XiangCheng <bobwxc@email.cn> + +:校译: + - Yinglin Luan <synmyth@gmail.com> + - Xiaochen Wang <wangxiaochen0@gmail.com> + - yaxinsn <yaxinsn@163.com> + +Linux邮件客户端配置信息 +======================= + +Git +--- + +现在大多数开发人员使用 ``git send-email`` 而不是常规的电子邮件客户端。这方面 +的手册非常好。在接收端,维护人员使用 ``git am`` 加载补丁。 + +如果你是 ``git`` 新手,那么把你的第一个补丁发送给你自己。将其保存为包含所有 +标题的原始文本。运行 ``git am raw_email.txt`` ,然后使用 ``git log`` 查看更 +改日志。如果工作正常,再将补丁发送到相应的邮件列表。 + + +通用配置 +-------- + +Linux内核补丁是通过邮件被提交的,最好把补丁作为邮件体的内嵌文本。有些维护者 +接收附件,但是附件的内容格式应该是"text/plain"。然而,附件一般是不赞成的, +因为这会使补丁的引用部分在评论过程中变的很困难。 + +同时也强烈建议在补丁或其他邮件的正文中使用纯文本格式。https://useplaintext.email +有助于了解如何配置你喜欢的邮件客户端,并在您还没有首选的情况下列出一些推荐的 +客户端。 + +用来发送Linux内核补丁的邮件客户端在发送补丁时应该处于文本的原始状态。例如, +他们不能改变或者删除制表符或者空格,甚至是在每一行的开头或者结尾。 + +不要通过"format=flowed"模式发送补丁。这样会引起不可预期以及有害的断行。 + +不要让你的邮件客户端进行自动换行。这样也会破坏你的补丁。 + +邮件客户端不能改变文本的字符集编码方式。要发送的补丁只能是ASCII或者UTF-8编码 +方式,如果你使用UTF-8编码方式发送邮件,那么你将会避免一些可能发生的字符集问题。 + +邮件客户端应该生成并且保持“References:”或者“In-Reply-To:”邮件头,这样邮件会话 +就不会中断。 + +复制粘帖(或者剪贴粘帖)通常不能用于补丁,因为制表符会转换为空格。使用xclipboard, +xclip或者xcutsel也许可以,但是最好测试一下或者避免使用复制粘帖。 + +不要在使用PGP/GPG签名的邮件中包含补丁。这样会使得很多脚本不能读取和适用于你的 +补丁。(这个问题应该是可以修复的) + +在给内核邮件列表发送补丁之前,给自己发送一个补丁是个不错的主意,保存接收到的 +邮件,将补丁用'patch'命令打上,如果成功了,再给内核邮件列表发送。 + + +一些邮件客户端提示 +------------------ + +这里给出一些详细的MUA配置提示,可以用于给Linux内核发送补丁。这些并不意味是 +所有的软件包配置总结。 + +说明: + +- TUI = 以文本为基础的用户接口 +- GUI = 图形界面用户接口 + +Alpine (TUI) +************ + +配置选项: + +在 :menuselection:`Sending Preferences` 菜单: + +- :menuselection:`Do Not Send Flowed Text` 必须开启 +- :menuselection:`Strip Whitespace Before Sending` 必须关闭 + +当写邮件时,光标应该放在补丁会出现的地方,然后按下 :kbd:`CTRL-R` 组合键,使指 +定的补丁文件嵌入到邮件中。 + +Claws Mail (GUI) +**************** + +可以用,有人用它成功地发过补丁。 + +用 :menuselection:`Message-->Insert File` (:kbd:`CTRL-I`) 或外置编辑器插入补丁。 + +若要在Claws编辑窗口重修改插入的补丁,需关闭 +:menuselection:`Configuration-->Preferences-->Compose-->Wrapping` +的 `Auto wrapping` 。 + +Evolution (GUI) +*************** + +一些开发者成功的使用它发送补丁。 + +撰写邮件时: +从 :menuselection:`格式-->段落样式-->预格式化` (:kbd:`CTRL-7`) +或工具栏选择 :menuselection:`预格式化` ; + +然后使用: +:menuselection:`插入-->文本文件...` (:kbd:`ALT-N x`) 插入补丁文件。 + +你还可以 ``diff -Nru old.c new.c | xclip`` ,选择 :menuselection:`预格式化` , +然后使用鼠标中键进行粘帖。 + +Kmail (GUI) +*********** + +一些开发者成功的使用它发送补丁。 + +默认撰写设置禁用HTML格式是合适的;不要启用它。 + +当书写一封邮件的时候,在选项下面不要选择自动换行。唯一的缺点就是你在邮件中输 +入的任何文本都不会被自动换行,因此你必须在发送补丁之前手动换行。最简单的方法 +就是启用自动换行来书写邮件,然后把它保存为草稿。一旦你在草稿中再次打开它,它 +已经全部自动换行了,那么你的邮件虽然没有选择自动换行,但是还不会失去已有的自 +动换行。 + +在邮件的底部,插入补丁之前,放上常用的补丁定界符:三个连字符(``---``)。 + +然后在 :menuselection:`信件` 菜单,选择 :menuselection:`插入文本文件` ,接 +着选取你的补丁文件。还有一个额外的选项,你可以通过它配置你的创建新邮件工具栏, +加上 :menuselection:`插入文本文件` 图标。 + +将编辑器窗口拉到足够宽避免折行。对于KMail 1.13.5 (KDE 4.5.4),它会在发送邮件 +时对编辑器窗口中显示折行的地方自动换行。在选项菜单中取消自动换行仍不能解决。 +因此,如果你的补丁中有非常长的行,必须在发送之前把编辑器窗口拉得非常宽。 +参见:https://bugs.kde.org/show_bug.cgi?id=174034 + +你可以安全地用GPG签名附件,但是内嵌补丁最好不要使用GPG签名它们。作为内嵌文本 +插入的签名补丁将使其难以从7-bit编码中提取。 + +如果你非要以附件的形式发送补丁,那么就右键点击附件,然后选择 +:menuselection:`属性` ,打开 :menuselection:`建议自动显示` ,使附件内联更容 +易让读者看到。 + +当你要保存将要发送的内嵌文本补丁,你可以从消息列表窗格选择包含补丁的邮件,然 +后右键选择 :menuselection:`另存为` 。如果整个电子邮件的组成正确,您可直接将 +其作为补丁使用。电子邮件以当前用户可读写权限保存,因此您必须 ``chmod`` ,以 +使其在复制到别处时用户组和其他人可读。 + +Lotus Notes (GUI) +***************** + +不要使用它。 + +IBM Verse (Web GUI) +******************* + +同上条。 + +Mutt (TUI) +********** + +很多Linux开发人员使用mutt客户端,这证明它肯定工作得非常漂亮。 + +Mutt不自带编辑器,所以不管你使用什么编辑器,不自动断行就行。大多数编辑器都有 +:menuselection:`插入文件` 选项,它可以在不改变文件内容的情况下插入文件。 + +用 ``vim`` 作为mutt的编辑器:: + + set editor="vi" + +如果使用xclip,敲入以下命令:: + + :set paste + +然后再按中键或者shift-insert或者使用:: + + :r filename + +把补丁插入为内嵌文本。 +在未设置 ``set paste`` 时(a)ttach工作的很好。 + +你可以通过 ``git format-patch`` 生成补丁,然后用 Mutt发送它们:: + + $ mutt -H 0001-some-bug-fix.patch + +配置选项: + +它应该以默认设置的形式工作。 +然而,把 ``send_charset`` 设置一下也是一个不错的主意:: + + set send_charset="us-ascii:utf-8" + +Mutt 是高度可配置的。 这里是个使用mutt通过 Gmail 发送的补丁的最小配置:: + + # .muttrc + # ================ IMAP ==================== + set imap_user = 'yourusername@gmail.com' + set imap_pass = 'yourpassword' + set spoolfile = imaps://imap.gmail.com/INBOX + set folder = imaps://imap.gmail.com/ + set record="imaps://imap.gmail.com/[Gmail]/Sent Mail" + set postponed="imaps://imap.gmail.com/[Gmail]/Drafts" + set mbox="imaps://imap.gmail.com/[Gmail]/All Mail" + + # ================ SMTP ==================== + set smtp_url = "smtp://username@smtp.gmail.com:587/" + set smtp_pass = $imap_pass + set ssl_force_tls = yes # Require encrypted connection + + # ================ Composition ==================== + set editor = `echo \$EDITOR` + set edit_headers = yes # See the headers when editing + set charset = UTF-8 # value of $LANG; also fallback for send_charset + # Sender, email address, and sign-off line must match + unset use_domain # because joe@localhost is just embarrassing + set realname = "YOUR NAME" + set from = "username@gmail.com" + set use_from = yes + +Mutt文档含有更多信息: + + https://gitlab.com/muttmua/mutt/-/wikis/UseCases/Gmail + + http://www.mutt.org/doc/manual/ + +Pine (TUI) +********** + +Pine过去有一些空格删减问题,但是这些现在应该都被修复了。 + +如果可以,请使用alpine(pine的继承者)。 + +配置选项: + +- 最近的版本需要 ``quell-flowed-text`` +- ``no-strip-whitespace-before-send`` 选项也是需要的。 + + +Sylpheed (GUI) +************** + +- 内嵌文本可以很好的工作(或者使用附件)。 +- 允许使用外部的编辑器。 +- 收件箱较多时非常慢。 +- 如果通过non-SSL连接,无法使用TLS SMTP授权。 +- 撰写窗口的标尺很有用。 +- 将地址添加到通讯簿时无法正确理解显示的名称。 + +Thunderbird (GUI) +***************** + +Thunderbird是Outlook的克隆版本,它很容易损坏文本,但也有一些方法强制修正。 + +在完成修改后(包括安装扩展),您需要重新启动Thunderbird。 + +- 允许使用外部编辑器: + + 使用Thunderbird发补丁最简单的方法是使用扩展来打开您最喜欢的外部编辑器。 + + 下面是一些能够做到这一点的扩展样例。 + + - “External Editor Revived” + + https://github.com/Frederick888/external-editor-revived + + https://addons.thunderbird.net/en-GB/thunderbird/addon/external-editor-revived/ + + 它需要安装“本地消息主机(native messaging host)”。 + 参见以下文档: + https://github.com/Frederick888/external-editor-revived/wiki + + - “External Editor” + + https://github.com/exteditor/exteditor + + 下载并安装此扩展,然后打开 :menuselection:`新建消息` 窗口, 用 + :menuselection:`查看-->工具栏-->自定义...` 给它增加一个按钮,直接点击此 + 按钮即可使用外置编辑器。 + + 请注意,“External Editor”要求你的编辑器不能fork,换句话说,编辑器必须在 + 关闭前不返回。你可能需要传递额外的参数或修改编辑器设置。最值得注意的是, + 如果您使用的是gvim,那么您必须将 :menuselection:`external editor` 设置的 + 编辑器字段设置为 ``/usr/bin/gvim --nofork"`` (假设可执行文件在 + ``/usr/bin`` ),以传递 ``-f`` 参数。如果您正在使用其他编辑器,请阅读其 + 手册了解如何处理。 + +若要修正内部编辑器,请执行以下操作: + +- 修改你的Thunderbird设置,不要使用 ``format=flowed`` ! + 回到主窗口,按照 + :menuselection:`主菜单-->首选项-->常规-->配置编辑器...` + 打开Thunderbird的配置编辑器。 + + - 将 ``mailnews.send_plaintext_flowed`` 设为 ``false`` + + - 将 ``mailnews.wraplength`` 从 ``72`` 改为 ``0`` + +- 不要写HTML邮件! + 回到主窗口,打开 + :menuselection:`主菜单-->账户设置-->你的@邮件.地址-->通讯录/编写&地址簿` , + 关掉 ``以HTML格式编写消息`` 。 + +- 只用纯文本格式查看邮件! + 回到主窗口, :menuselection:`主菜单-->查看-->消息体为-->纯文本` ! + +TkRat (GUI) +*********** + +可以使用它。使用"Insert file..."或者外部的编辑器。 + +Gmail (Web GUI) +*************** + +不要使用它发送补丁。 + +Gmail网页客户端自动地把制表符转换为空格。 + +虽然制表符转换为空格问题可以被外部编辑器解决,但它同时还会使用回车换行把每行 +拆分为78个字符。 + +另一个问题是Gmail还会把任何含有非ASCII的字符的消息改用base64编码,如欧洲人的 +名字。 + diff --git a/Documentation/translations/zh_CN/process/embargoed-hardware-issues.rst b/Documentation/translations/zh_CN/process/embargoed-hardware-issues.rst new file mode 100644 index 000000000..cf5f1fca3 --- /dev/null +++ b/Documentation/translations/zh_CN/process/embargoed-hardware-issues.rst @@ -0,0 +1,228 @@ +.. include:: ../disclaimer-zh_CN.rst + +:Original: :ref:`Documentation/process/embargoed-hardware-issues.rst <embargoed_hardware_issues>` +:Translator: Alex Shi <alex.shi@linux.alibaba.com> + +被限制的硬件问题 +================ + +范围 +---- + +导致安全问题的硬件问题与只影响Linux内核的纯软件错误是不同的安全错误类别。 + +必须区别对待诸如熔毁(Meltdown)、Spectre、L1TF等硬件问题,因为它们通常会影响 +所有操作系统(“OS”),因此需要在不同的OS供应商、发行版、硬件供应商和其他各方 +之间进行协调。对于某些问题,软件缓解可能依赖于微码或固件更新,这需要进一步的 +协调。 + +.. _zh_Contact: + +接触 +---- + +Linux内核硬件安全小组独立于普通的Linux内核安全小组。 + +该小组只负责协调被限制的硬件安全问题。Linux内核中纯软件安全漏洞的报告不由该 +小组处理,报告者将被引导至常规Linux内核安全小组(:ref:`Documentation/admin-guide/ +<securitybugs>`)联系。 + +可以通过电子邮件 <hardware-security@kernel.org> 与小组联系。这是一份私密的安全 +官名单,他们将帮助您根据我们的文档化流程协调问题。 + +邮件列表是加密的,发送到列表的电子邮件可以通过PGP或S/MIME加密,并且必须使用报告 +者的PGP密钥或S/MIME证书签名。该列表的PGP密钥和S/MIME证书可从 +https://www.kernel.org/.... 获得。 + +虽然硬件安全问题通常由受影响的硬件供应商处理,但我们欢迎发现潜在硬件缺陷的研究 +人员或个人与我们联系。 + +硬件安全官 +^^^^^^^^^^ + +目前的硬件安全官小组: + + - Linus Torvalds(Linux基金会院士) + - Greg Kroah Hartman(Linux基金会院士) + - Thomas Gleixner(Linux基金会院士) + +邮件列表的操作 +^^^^^^^^^^^^^^ + +处理流程中使用的加密邮件列表托管在Linux Foundation的IT基础设施上。通过提供这项 +服务,Linux基金会的IT基础设施安全总监在技术上有能力访问被限制的信息,但根据他 +的雇佣合同,他必须保密。Linux基金会的IT基础设施安全总监还负责 kernel.org 基础 +设施。 + +Linux基金会目前的IT基础设施安全总监是 Konstantin Ryabitsev。 + +保密协议 +-------- + +Linux内核硬件安全小组不是正式的机构,因此无法签订任何保密协议。核心社区意识到 +这些问题的敏感性,并提供了一份谅解备忘录。 + +谅解备忘录 +---------- + +Linux内核社区深刻理解在不同操作系统供应商、发行商、硬件供应商和其他各方之间 +进行协调时,保持硬件安全问题处于限制状态的要求。 + +Linux内核社区在过去已经成功地处理了硬件安全问题,并且有必要的机制允许在限制 +限制下进行符合社区的开发。 + +Linux内核社区有一个专门的硬件安全小组负责初始联系,并监督在限制规则下处理 +此类问题的过程。 + +硬件安全小组确定开发人员(领域专家),他们将组成特定问题的初始响应小组。最初 +的响应小组可以引入更多的开发人员(领域专家)以最佳的技术方式解决这个问题。 + +所有相关开发商承诺遵守限制规定,并对收到的信息保密。违反承诺将导致立即从当前 +问题中排除,并从所有相关邮件列表中删除。此外,硬件安全小组还将把违反者排除在 +未来的问题之外。这一后果的影响在我们社区是一种非常有效的威慑。如果发生违规 +情况,硬件安全小组将立即通知相关方。如果您或任何人发现潜在的违规行为,请立即 +向硬件安全人员报告。 + +流程 +^^^^ + +由于Linux内核开发的全球分布式特性,面对面的会议几乎不可能解决硬件安全问题。 +由于时区和其他因素,电话会议很难协调,只能在绝对必要时使用。加密电子邮件已被 +证明是解决此类问题的最有效和最安全的通信方法。 + +开始披露 +"""""""" + +披露内容首先通过电子邮件联系Linux内核硬件安全小组。此初始联系人应包含问题的 +描述和任何已知受影响硬件的列表。如果您的组织制造或分发受影响的硬件,我们建议 +您也考虑哪些其他硬件可能会受到影响。 + +硬件安全小组将提供一个特定于事件的加密邮件列表,用于与报告者进行初步讨论、 +进一步披露和协调。 + +硬件安全小组将向披露方提供一份开发人员(领域专家)名单,在与开发人员确认他们 +将遵守本谅解备忘录和文件化流程后,应首先告知开发人员有关该问题的信息。这些开发 +人员组成初始响应小组,并在初始接触后负责处理问题。硬件安全小组支持响应小组, +但不一定参与缓解开发过程。 + +虽然个别开发人员可能通过其雇主受到保密协议的保护,但他们不能以Linux内核开发 +人员的身份签订个别保密协议。但是,他们将同意遵守这一书面程序和谅解备忘录。 + +披露方应提供已经或应该被告知该问题的所有其他实体的联系人名单。这有几个目的: + + - 披露的实体列表允许跨行业通信,例如其他操作系统供应商、硬件供应商等。 + + - 可联系已披露的实体,指定应参与缓解措施开发的专家。 + + - 如果需要处理某一问题的专家受雇于某一上市实体或某一上市实体的成员,则响应 + 小组可要求该实体披露该专家。这确保专家也是实体反应小组的一部分。 + +披露 +"""" + +披露方通过特定的加密邮件列表向初始响应小组提供详细信息。 + +根据我们的经验,这些问题的技术文档通常是一个足够的起点,最好通过电子邮件进行 +进一步的技术澄清。 + +缓解开发 +"""""""" + +初始响应小组设置加密邮件列表,或在适当的情况下重新修改现有邮件列表。 + +使用邮件列表接近于正常的Linux开发过程,并且在过去已经成功地用于为各种硬件安全 +问题开发缓解措施。 + +邮件列表的操作方式与正常的Linux开发相同。发布、讨论和审查修补程序,如果同意, +则应用于非公共git存储库,参与开发人员只能通过安全连接访问该存储库。存储库包含 +针对主线内核的主开发分支,并根据需要为稳定的内核版本提供向后移植分支。 + +最初的响应小组将根据需要从Linux内核开发人员社区中确定更多的专家。引进专家可以 +在开发过程中的任何时候发生,需要及时处理。 + +如果专家受雇于披露方提供的披露清单上的实体或其成员,则相关实体将要求其参与。 + +否则,披露方将被告知专家参与的情况。谅解备忘录涵盖了专家,要求披露方确认参与。 +如果披露方有令人信服的理由提出异议,则必须在五个工作日内提出异议,并立即与事件 +小组解决。如果披露方在五个工作日内未作出回应,则视为默许。 + +在确认或解决异议后,专家由事件小组披露,并进入开发过程。 + +协调发布 +"""""""" + +有关各方将协商限制结束的日期和时间。此时,准备好的缓解措施集成到相关的内核树中 +并发布。 + +虽然我们理解硬件安全问题需要协调限制时间,但限制时间应限制在所有有关各方制定、 +测试和准备缓解措施所需的最短时间内。人为地延长限制时间以满足会议讨论日期或其他 +非技术原因,会给相关的开发人员和响应小组带来了更多的工作和负担,因为补丁需要 +保持最新,以便跟踪正在进行的上游内核开发,这可能会造成冲突的更改。 + +CVE分配 +""""""" + +硬件安全小组和初始响应小组都不分配CVE,开发过程也不需要CVE。如果CVE是由披露方 +提供的,则可用于文档中。 + +流程专使 +-------- + +为了协助这一进程,我们在各组织设立了专使,他们可以回答有关报告流程和进一步处理 +的问题或提供指导。专使不参与特定问题的披露,除非响应小组或相关披露方提出要求。 +现任专使名单: + + ============= ======================================================== + ARM + AMD Tom Lendacky <thomas.lendacky@amd.com> + IBM + Intel Tony Luck <tony.luck@intel.com> + Qualcomm Trilok Soni <tsoni@codeaurora.org> + + Microsoft Sasha Levin <sashal@kernel.org> + VMware + Xen Andrew Cooper <andrew.cooper3@citrix.com> + + Canonical John Johansen <john.johansen@canonical.com> + Debian Ben Hutchings <ben@decadent.org.uk> + Oracle Konrad Rzeszutek Wilk <konrad.wilk@oracle.com> + Red Hat Josh Poimboeuf <jpoimboe@redhat.com> + SUSE Jiri Kosina <jkosina@suse.cz> + + Amazon + Google Kees Cook <keescook@chromium.org> + ============= ======================================================== + +如果要将您的组织添加到专使名单中,请与硬件安全小组联系。被提名的专使必须完全 +理解和支持我们的过程,并且在Linux内核社区中很容易联系。 + +加密邮件列表 +------------ + +我们使用加密邮件列表进行通信。这些列表的工作原理是,发送到列表的电子邮件使用 +列表的PGP密钥或列表的/MIME证书进行加密。邮件列表软件对电子邮件进行解密,并 +使用订阅者的PGP密钥或S/MIME证书为每个订阅者分别对其进行重新加密。有关邮件列表 +软件和用于确保列表安全和数据保护的设置的详细信息,请访问: +https://www.kernel.org/.... + +关键点 +^^^^^^ + +初次接触见 :ref:`zh_Contact`. 对于特定于事件的邮件列表,密钥和S/MIME证书通过 +特定列表发送的电子邮件传递给订阅者。 + +订阅事件特定列表 +^^^^^^^^^^^^^^^^ + +订阅由响应小组处理。希望参与通信的披露方将潜在订户的列表发送给响应组,以便 +响应组可以验证订阅请求。 + +每个订户都需要通过电子邮件向响应小组发送订阅请求。电子邮件必须使用订阅服务器 +的PGP密钥或S/MIME证书签名。如果使用PGP密钥,则必须从公钥服务器获得该密钥, +并且理想情况下该密钥连接到Linux内核的PGP信任网。另请参见: +https://www.kernel.org/signature.html. + +响应小组验证订阅者,并将订阅者添加到列表中。订阅后,订阅者将收到来自邮件列表 +的电子邮件,该邮件列表使用列表的PGP密钥或列表的/MIME证书签名。订阅者的电子邮件 +客户端可以从签名中提取PGP密钥或S/MIME证书,以便订阅者可以向列表发送加密电子 +邮件。 diff --git a/Documentation/translations/zh_CN/process/howto.rst b/Documentation/translations/zh_CN/process/howto.rst new file mode 100644 index 000000000..888978a62 --- /dev/null +++ b/Documentation/translations/zh_CN/process/howto.rst @@ -0,0 +1,495 @@ +.. _cn_process_howto: + +.. include:: ../disclaimer-zh_CN.rst + +:Original: :ref:`Documentation/process/howto.rst <process_howto>` + +译者:: + + 英文版维护者: Greg Kroah-Hartman <greg@kroah.com> + 中文版维护者: 李阳 Li Yang <leoyang.li@nxp.com> + 中文版翻译者: 李阳 Li Yang <leoyang.li@nxp.com> + 时奎亮 Alex Shi <alex.shi@linux.alibaba.com> + 中文版校译者: + 钟宇 TripleX Chung <xxx.phy@gmail.com> + 陈琦 Maggie Chen <chenqi@beyondsoft.com> + 王聪 Wang Cong <xiyou.wangcong@gmail.com> + +如何参与Linux内核开发 +===================== + +这是一篇将如何参与Linux内核开发的相关问题一网打尽的终极秘笈。它将指导你 +成为一名Linux内核开发者,并且学会如何同Linux内核开发社区合作。它尽可能不 +包括任何关于内核编程的技术细节,但会给你指引一条获得这些知识的正确途径。 + +如果这篇文章中的任何内容不再适用,请给文末列出的文件维护者发送补丁。 + + +入门 +---- + +你想了解如何成为一名Linux内核开发者?或者老板吩咐你“给这个设备写个Linux +驱动程序”?这篇文章的目的就是教会你达成这些目标的全部诀窍,它将描述你需 +要经过的流程以及给出如何同内核社区合作的一些提示。它还将试图解释内核社区 +为何这样运作。 + +Linux内核大部分是由C语言写成的,一些体系结构相关的代码用到了汇编语言。要 +参与内核开发,你必须精通C语言。除非你想为某个架构开发底层代码,否则你并 +不需要了解(任何体系结构的)汇编语言。下面列举的书籍虽然不能替代扎实的C +语言教育和多年的开发经验,但如果需要的话,做为参考还是不错的: + + - "The C Programming Language" by Kernighan and Ritchie [Prentice Hall] + 《C程序设计语言(第2版·新版)》(徐宝文 李志 译)[机械工业出版社] + - "Practical C Programming" by Steve Oualline [O'Reilly] + 《实用C语言编程(第三版)》(郭大海 译)[中国电力出版社] + - "C: A Reference Manual" by Harbison and Steele [Prentice Hall] + 《C语言参考手册(原书第5版)》(邱仲潘 等译)[机械工业出版社] + +Linux内核使用GNU C和GNU工具链开发。虽然它遵循ISO C11标准,但也用到了一些 +标准中没有定义的扩展。内核是自给自足的C环境,不依赖于标准C库的支持,所以 +并不支持C标准中的部分定义。比如long long类型的大数除法和浮点运算就不允许 +使用。有时候确实很难弄清楚内核对工具链的要求和它所使用的扩展,不幸的是目 +前还没有明确的参考资料可以解释它们。请查阅gcc信息页(使用“info gcc”命令 +显示)获得一些这方面信息。 + +请记住你是在学习怎么和已经存在的开发社区打交道。它由一群形形色色的人组成, +他们对代码、风格和过程有着很高的标准。这些标准是在长期实践中总结出来的, +适应于地理上分散的大型开发团队。它们已经被很好得整理成档,建议你在开发 +之前尽可能多的学习这些标准,而不要期望别人来适应你或者你公司的行为方式。 + + +法律问题 +-------- + +Linux内核源代码都是在GPL(通用公共许可证)的保护下发布的。要了解这种许可 +的细节请查看源代码主目录下的COPYING文件。Linux内核许可准则和如何使用 +`SPDX <https://spdx.org/>` 标志符说明在这个文件中 +:ref:`Documentation/translations/zh_CN/process/license-rules.rst <cn_kernel_licensing>` +如果你对它还有更深入问题请联系律师,而不要在Linux内核邮件组上提问。因为 +邮件组里的人并不是律师,不要期望他们的话有法律效力。 + +对于GPL的常见问题和解答,请访问以下链接: + https://www.gnu.org/licenses/gpl-faq.html + + +文档 +---- + +Linux内核代码中包含有大量的文档。这些文档对于学习如何与内核社区互动有着 +不可估量的价值。当一个新的功能被加入内核,最好把解释如何使用这个功能的文 +档也放进内核。当内核的改动导致面向用户空间的接口发生变化时,最好将相关信 +息或手册页(manpages)的补丁发到mtk.manpages@gmail.com,以向手册页(manpages) +的维护者解释这些变化。 + +以下是内核代码中需要阅读的文档: + :ref:`Documentation/admin-guide/README.rst <readme>` + 文件简要介绍了Linux内核的背景,并且描述了如何配置和编译内核。内核的 + 新用户应该从这里开始。 + + + :ref:`Documentation/process/changes.rst <changes>` + 文件给出了用来编译和使用内核所需要的最小软件包列表。 + + :ref:`Documentation/translations/zh_CN/process/coding-style.rst <cn_codingstyle>` + 描述Linux内核的代码风格和理由。所有新代码需要遵守这篇文档中定义的规 + 范。大多数维护者只会接收符合规定的补丁,很多人也只会帮忙检查符合风格 + 的代码。 + + :ref:`Documentation/translations/zh_CN/process/submitting-patches.rst <cn_submittingpatches>` + + 这两份文档明确描述如何创建和发送补丁,其中包括(但不仅限于): + - 邮件内容 + - 邮件格式 + - 选择收件人 + + 遵守这些规定并不能保证提交成功(因为所有补丁需要通过严格的内容和风格 + 审查),但是忽视他们几乎就意味着失败。 + + 其他关于如何正确地生成补丁的优秀文档包括: + "The Perfect Patch" + + https://www.ozlabs.org/~akpm/stuff/tpp.txt + + "Linux kernel patch submission format" + + https://web.archive.org/web/20180829112450/http://linux.yyz.us/patch-format.html + + :ref:`Documentation/translations/zh_CN/process/stable-api-nonsense.rst <cn_stable_api_nonsense>` + 论证内核为什么特意不包括稳定的内核内部API,也就是说不包括像这样的特 + 性: + + - 子系统中间层(为了兼容性?) + - 在不同操作系统间易于移植的驱动程序 + - 减缓(甚至阻止)内核代码的快速变化 + + 这篇文档对于理解Linux的开发哲学至关重要。对于将开发平台从其他操作系 + 统转移到Linux的人来说也很重要。 + + :ref:`Documentation/admin-guide/security-bugs.rst <securitybugs>` + 如果你认为自己发现了Linux内核的安全性问题,请根据这篇文档中的步骤来 + 提醒其他内核开发者并帮助解决这个问题。 + + :ref:`Documentation/translations/zh_CN/process/management-style.rst <cn_managementstyle>` + + 描述内核维护者的工作方法及其共有特点。这对于刚刚接触内核开发(或者对 + 它感到好奇)的人来说很重要,因为它解释了很多对于内核维护者独特行为的 + 普遍误解与迷惑。 + + :ref:`Documentation/process/stable-kernel-rules.rst <stable_kernel_rules>` + 解释了稳定版内核发布的规则,以及如何将改动放入这些版本的步骤。 + + :ref:`Documentation/process/kernel-docs.rst <kernel_docs>` + 有助于内核开发的外部文档列表。如果你在内核自带的文档中没有找到你想找 + 的内容,可以查看这些文档。 + + :ref:`Documentation/process/applying-patches.rst <applying_patches>` + 关于补丁是什么以及如何将它打在不同内核开发分支上的好介绍 + +内核还拥有大量从代码自动生成或者从 ReStructuredText(ReST) 标记生成的文档, +比如这个文档,它包含内核内部API的全面介绍以及如何妥善处理加锁的规则。所有 +这些文档都可以通过运行以下命令从内核代码中生成为PDF或HTML文档:: + + make pdfdocs + make htmldocs + +ReST格式的文档会生成在 Documentation/output. 目录中。 +它们也可以用下列命令生成 LaTeX 和 ePub 格式文档:: + + make latexdocs + make epubdocs + +如何成为内核开发者 +------------------ +如果你对Linux内核开发一无所知,你应该访问“Linux内核新手”计划: + + https://kernelnewbies.org + +它拥有一个可以问各种最基本的内核开发问题的邮件列表(在提问之前一定要记得 +查找已往的邮件,确认是否有人已经回答过相同的问题)。它还拥有一个可以获得 +实时反馈的IRC聊天频道,以及大量对于学习Linux内核开发相当有帮助的文档。 + +网站简要介绍了源代码组织结构、子系统划分以及目前正在进行的项目(包括内核 +中的和单独维护的)。它还提供了一些基本的帮助信息,比如如何编译内核和打补 +丁。 + +如果你想加入内核开发社区并协助完成一些任务,却找不到从哪里开始,可以访问 +“Linux内核房管员”计划: + + https://kernelnewbies.org/KernelJanitors + +这是极佳的起点。它提供一个相对简单的任务列表,列出内核代码中需要被重新 +整理或者改正的地方。通过和负责这个计划的开发者们一同工作,你会学到将补丁 +集成进内核的基本原理。如果还没有决定下一步要做什么的话,你还可能会得到方 +向性的指点。 + +在真正动手修改内核代码之前,理解要修改的代码如何运作是必需的。要达到这个 +目的,没什么办法比直接读代码更有效了(大多数花招都会有相应的注释),而且 +一些特制的工具还可以提供帮助。例如,“Linux代码交叉引用”项目就是一个值得 +特别推荐的帮助工具,它将源代码显示在有编目和索引的网页上。其中一个更新及 +时的内核源码库,可以通过以下地址访问: + + https://elixir.bootlin.com/ + + +开发流程 +-------- + +目前Linux内核开发流程包括几个“主内核分支”和很多子系统相关的内核分支。这 +些分支包括: + + - Linus 的内核源码树 + - 多个主要版本的稳定版内核树 + - 子系统相关的内核树 + - linux-next 集成测试树 + + +主线树 +------ +主线树是由Linus Torvalds 维护的。你可以在https://kernel.org 网站或者代码 +库中下找到它。它的开发遵循以下步骤: + + - 每当一个新版本的内核被发布,为期两周的集成窗口将被打开。在这段时间里 + 维护者可以向Linus提交大段的修改,通常这些修改已经被放到-mm内核中几个 + 星期了。提交大量修改的首选方式是使用git工具(内核的代码版本管理工具 + ,更多的信息可以在 https://git-scm.com/ 获取),不过使用普通补丁也是 + 可以的。 + - 两个星期以后-rc1版本内核发布。之后只有不包含可能影响整个内核稳定性的 + 新功能的补丁才可能被接受。请注意一个全新的驱动程序(或者文件系统)有 + 可能在-rc1后被接受是因为这样的修改完全独立,不会影响其他的代码,所以 + 没有造成内核退步的风险。在-rc1以后也可以用git向Linus提交补丁,不过所 + 有的补丁需要同时被发送到相应的公众邮件列表以征询意见。 + - 当Linus认为当前的git源码树已经达到一个合理健全的状态足以发布供人测试 + 时,一个新的-rc版本就会被发布。计划是每周都发布新的-rc版本。 + - 这个过程一直持续下去直到内核被认为达到足够稳定的状态,持续时间大概是 + 6个星期。 + +关于内核发布,值得一提的是Andrew Morton在linux-kernel邮件列表中如是说: + “没有人知道新内核何时会被发布,因为发布是根据已知bug的情况来决定 + 的,而不是根据一个事先制定好的时间表。” + +子系统特定树 +------------ + +各种内核子系统的维护者——以及许多内核子系统开发人员——在源代码库中公开了他们 +当前的开发状态。这样,其他人就可以看到内核的不同区域发生了什么。在开发速度 +很快的领域,可能会要求开发人员将提交的内容建立在这样的子系统内核树上,这样 +就避免了提交与其他已经进行的工作之间的冲突。 + +这些存储库中的大多数都是Git树,但是也有其他的scm在使用,或者补丁队列被发布 +为Quilt系列。这些子系统存储库的地址列在MAINTAINERS文件中。其中许多可以在 +https://git.kernel.org/上浏览。 + +在将一个建议的补丁提交到这样的子系统树之前,需要对它进行审查,审查主要发生 +在邮件列表上(请参见下面相应的部分)。对于几个内核子系统,这个审查过程是通 +过工具补丁跟踪的。Patchwork提供了一个Web界面,显示补丁发布、对补丁的任何评 +论或修订,维护人员可以将补丁标记为正在审查、接受或拒绝。大多数补丁网站都列 +在 https://patchwork.kernel.org/ + +Linux-next 集成测试树 +--------------------- + +在将子系统树的更新合并到主线树之前,需要对它们进行集成测试。为此,存在一个 +特殊的测试存储库,其中几乎每天都会提取所有子系统树: + + https://git.kernel.org/?p=linux/kernel/git/next/linux-next.git + +通过这种方式,Linux-next 对下一个合并阶段将进入主线内核的内容给出了一个概要 +展望。非常欢冒险的测试者运行测试Linux-next。 + +多个主要版本的稳定版内核树 +----------------------------------- +由3个数字组成的内核版本号说明此内核是-stable版本。它们包含内核的相对较小且 +至关重要的修补,这些修补针对安全性问题或者严重的内核退步。 + +这种版本的内核适用于那些期望获得最新的稳定版内核并且不想参与测试开发版或 +者实验版的用户。 + +稳定版内核树版本由“稳定版”小组(邮件地址<stable@vger.kernel.org>)维护,一般 +隔周发布新版本。 + +内核源码中的 :ref:`Documentation/process/stable-kernel-rules.rst <stable_kernel_rules>` +文件具体描述了可被稳定版内核接受的修改类型以及发布的流程。 + + +报告bug +------- + +bugzilla.kernel.org是Linux内核开发者们用来跟踪内核Bug的网站。我们鼓励用 +户在这个工具中报告找到的所有bug。如何使用内核bugzilla的细节请访问: + + http://test.kernel.org/bugzilla/faq.html + +内核源码主目录中的:ref:`admin-guide/reporting-bugs.rst <reportingbugs>` +文件里有一个很好的模板。它指导用户如何报告可能的内核bug以及需要提供哪些信息 +来帮助内核开发者们找到问题的根源。 + + +利用bug报告 +----------- + +练习内核开发技能的最好办法就是修改其他人报告的bug。你不光可以帮助内核变 +得更加稳定,还可以学会如何解决实际问题从而提高自己的技能,并且让其他开发 +者感受到你的存在。修改bug是赢得其他开发者赞誉的最好办法,因为并不是很多 +人都喜欢浪费时间去修改别人报告的bug。 + +要尝试修改已知的bug,请访问 http://bugzilla.kernel.org 网址。 + + +邮件列表 +-------- + +正如上面的文档所描述,大多数的骨干内核开发者都加入了Linux Kernel邮件列 +表。如何订阅和退订列表的细节可以在这里找到: + + http://vger.kernel.org/vger-lists.html#linux-kernel + +网上很多地方都有这个邮件列表的存档(archive)。可以使用搜索引擎来找到这些 +存档。比如: + + https://lore.kernel.org/lkml/ + +在发信之前,我们强烈建议你先在存档中搜索你想要讨论的问题。很多已经被详细 +讨论过的问题只在邮件列表的存档中可以找到。 + +大多数内核子系统也有自己独立的邮件列表来协调各自的开发工作。从 +MAINTAINERS文件中可以找到不同话题对应的邮件列表。 + +很多邮件列表架设在kernel.org服务器上。这些列表的信息可以在这里找到: + + http://vger.kernel.org/vger-lists.html + +在使用这些邮件列表时,请记住保持良好的行为习惯。下面的链接提供了与这些列 +表(或任何其它邮件列表)交流的一些简单规则,虽然内容有点滥竽充数。 + + http://www.albion.com/netiquette/ + +当有很多人回复你的邮件时,邮件的抄送列表会变得很长。请不要将任何人从抄送 +列表中删除,除非你有足够的理由这么做。也不要只回复到邮件列表。请习惯于同 +一封邮件接收两次(一封来自发送者一封来自邮件列表),而不要试图通过添加一 +些奇特的邮件头来解决这个问题,人们不会喜欢的。 + +记住保留你所回复内容的上下文和源头。在你回复邮件的顶部保留“某某某说到……” +这几行。将你的评论加在被引用的段落之间而不要放在邮件的顶部。 + +如果你在邮件中附带补丁,请确认它们是可以直接阅读的纯文本(如 +:ref:`Documentation/translations/zh_CN/process/submitting-patches.rst <cn_submittingpatches>` +文档中所述)。内核开发者们不希望遇到附件或者被压缩了的补丁。只有这样才能 +保证他们可以直接评论你的每行代码。请确保你使用的邮件发送程序不会修改空格 +和制表符。一个防范性的测试方法是先将邮件发送给自己,然后自己尝试是否可以 +顺利地打上收到的补丁。如果测试不成功,请调整或者更换你的邮件发送程序直到 +它正确工作为止。 + +总而言之,请尊重其他的邮件列表订阅者。 + + +同内核社区合作 +---------------- + +内核社区的目标就是提供尽善尽美的内核。所以当你提交补丁期望被接受进内核的 +时候,它的技术价值以及其他方面都将被评审。那么你可能会得到什么呢? + + - 批评 + - 评论 + - 要求修改 + - 要求证明修改的必要性 + - 沉默 + +要记住,这些是把补丁放进内核的正常情况。你必须学会听取对补丁的批评和评论, +从技术层面评估它们,然后要么重写你的补丁要么简明扼要地论证修改是不必要 +的。如果你发的邮件没有得到任何回应,请过几天后再试一次,因为有时信件会湮 +没在茫茫信海中。 + +你不应该做的事情: + + - 期望自己的补丁不受任何质疑就直接被接受 + - 翻脸 + - 忽略别人的评论 + - 没有按照别人的要求做任何修改就重新提交 + +在一个努力追寻最好技术方案的社区里,对于一个补丁有多少好处总会有不同的见 +解。你必须要抱着合作的态度,愿意改变自己的观点来适应内核的风格。或者至少 +愿意去证明你的想法是有价值的。记住,犯错误是允许的,只要你愿意朝着正确的 +方案去努力。 + +如果你的第一个补丁换来的是一堆修改建议,这是很正常的。这并不代表你的补丁 +不会被接受,也不意味着有人和你作对。你只需要改正所有提出的问题然后重新发 +送你的补丁。 + +内核社区和公司文化的差异 +------------------------ + +内核社区的工作模式同大多数传统公司开发队伍的工作模式并不相同。下面这些例 +子,可以帮助你避免某些可能发生问题: +用这些话介绍你的修改提案会有好处:(在任何时候你都不应该用中文写提案) + + - 它同时解决了多个问题 + - 它删除了2000行代码 + - 这是补丁,它已经解释了我想要说明的 + - 我在5种不同的体系结构上测试过它…… + - 这是一系列小补丁用来…… + - 这个修改提高了普通机器的性能…… + +应该避免如下的说法: + + - 我们在AIX/ptx/Solaris就是这么做的,所以这么做肯定是好的…… + - 我做这行已经20年了,所以…… + - 为了我们公司赚钱考虑必须这么做 + - 这是我们的企业产品线所需要的 + - 这里是描述我观点的1000页设计文档 + - 这是一个5000行的补丁用来…… + - 我重写了现在乱七八糟的代码,这就是…… + - 我被规定了最后期限,所以这个补丁需要立刻被接受 + +另外一个内核社区与大部分传统公司的软件开发队伍不同的地方是无法面对面地交 +流。使用电子邮件和IRC聊天工具做为主要沟通工具的一个好处是性别和种族歧视 +将会更少。Linux内核的工作环境更能接受妇女和少数族群,因为每个人在别人眼 +里只是一个邮件地址。国际化也帮助了公平的实现,因为你无法通过姓名来判断人 +的性别。男人有可能叫李丽,女人也有可能叫王刚。大多数在Linux内核上工作过 +并表达过看法的女性对在linux上工作的经历都给出了正面的评价。 + +对于一些不习惯使用英语的人来说,语言可能是一个引起问题的障碍。在邮件列表 +中要正确地表达想法必需良好地掌握语言,所以建议你在发送邮件之前最好检查一 +下英文写得是否正确。 + + +拆分修改 +-------- + +Linux内核社区并不喜欢一下接收大段的代码。修改需要被恰当地介绍、讨论并且 +拆分成独立的小段。这几乎完全和公司中的习惯背道而驰。你的想法应该在开发最 +开始的阶段就让大家知道,这样你就可以及时获得对你正在进行的开发的反馈。这 +样也会让社区觉得你是在和他们协作,而不是仅仅把他们当作倾销新功能的对象。 +无论如何,你不要一次性地向邮件列表发送50封信,你的补丁序列应该永远用不到 +这么多。 + +将补丁拆开的原因如下: + +1) 小的补丁更有可能被接受,因为它们不需要太多的时间和精力去验证其正确性。 + 一个5行的补丁,可能在维护者看了一眼以后就会被接受。而500行的补丁则 + 需要数个小时来审查其正确性(所需时间随补丁大小增加大约呈指数级增长)。 + + 当出了问题的时候,小的补丁也会让调试变得非常容易。一个一个补丁地回溯 + 将会比仔细剖析一个被打上的大补丁(这个补丁破坏了其他东西)容易得多。 + +2)不光发送小的补丁很重要,在提交之前重新编排、化简(或者仅仅重新排列) + 补丁也是很重要的。 + +这里有内核开发者Al Viro打的一个比方: + “想象一个老师正在给学生批改数学作业。老师并不希望看到学生为了得 + 到正确解法所进行的尝试和产生的错误。他希望看到的是最干净最优雅的 + 解答。好学生了解这点,绝不会把最终解决之前的中间方案提交上去。” + + 内核开发也是这样。维护者和评审者不希望看到一个人在解决问题时的思 + 考过程。他们只希望看到简单和优雅的解决方案。 + +直接给出一流的解决方案,和社区一起协作讨论尚未完成的工作,这两者之间似乎 +很难找到一个平衡点。所以最好尽早开始收集有利于你进行改进的反馈;同时也要 +保证修改分成很多小块,这样在整个项目都准备好被包含进内核之前,其中的一部 +分可能会先被接收。 + +你必须明白这么做是无法令人接受的:试图将不完整的代码提交进内核,然后再找 +时间修复。 + + +证明修改的必要性 +---------------- +除了将补丁拆成小块,很重要的一点是让Linux社区了解他们为什么需要这样修改。 +你必须证明新功能是有人需要的并且是有用的。 + + +记录修改 +-------- + +当你发送补丁的时候,需要特别留意邮件正文的内容。因为这里的信息将会做为补 +丁的修改记录(ChangeLog),会被一直保留以备大家查阅。它需要完全地描述补丁, +包括: + + - 为什么需要这个修改 + - 补丁的总体设计 + - 实现细节 + - 测试结果 + +想了解它具体应该看起来像什么,请查阅以下文档中的“ChangeLog”章节: + “The Perfect Patch” + https://www.ozlabs.org/~akpm/stuff/tpp.txt + + +这些事情有时候做起来很难。想要在任何方面都做到完美可能需要好几年时间。这 +是一个持续提高的过程,它需要大量的耐心和决心。只要不放弃,你一定可以做到。 +很多人已经做到了,而他们都曾经和现在的你站在同样的起点上。 + + +感谢 +---- +感谢Paolo Ciarrocchi允许“开发流程”部分基于他所写的文章 +(http://www.kerneltravel.net/newbie/2.6-development_process),感谢Randy +Dunlap和Gerrit Huizenga完善了应该说和不该说的列表。感谢Pat Mochel, Hanna +Linder, Randy Dunlap, Kay Sievers, Vojtech Pavlik, Jan Kara, Josh Boyer, +Kees Cook, Andrew Morton, Andi Kleen, Vadim Lobanov, Jesper Juhl, Adrian +Bunk, Keri Harris, Frans Pop, David A. Wheeler, Junio Hamano, Michael +Kerrisk和Alex Shepard的评审、建议和贡献。没有他们的帮助,这篇文档是不可 +能完成的。 + + + +英文版维护者: Greg Kroah-Hartman <greg@kroah.com> diff --git a/Documentation/translations/zh_CN/process/index.rst b/Documentation/translations/zh_CN/process/index.rst new file mode 100644 index 000000000..a1a35f88f --- /dev/null +++ b/Documentation/translations/zh_CN/process/index.rst @@ -0,0 +1,63 @@ +.. raw:: latex + + \renewcommand\thesection* + \renewcommand\thesubsection* + +.. include:: ../disclaimer-zh_CN.rst + +:Original: :ref:`Documentation/process/index.rst <process_index>` +:Translator: Alex Shi <alex.shi@linux.alibaba.com> + +.. _cn_process_index: + +======================== +与Linux 内核社区一起工作 +======================== + +你想成为Linux内核开发人员吗?欢迎之至!在学习许多关于内核的技术知识的同时, +了解我们社区的工作方式也很重要。阅读这些文档可以让您以更轻松的、麻烦更少的 +方式将更改合并到内核。 + +以下是每位开发人员都应阅读的基本指南: + +.. toctree:: + :maxdepth: 1 + + howto + code-of-conduct + code-of-conduct-interpretation + submitting-patches + programming-language + coding-style + development-process + email-clients + license-rules + kernel-enforcement-statement + kernel-driver-statement + +其它大多数开发人员感兴趣的社区指南: + + +.. toctree:: + :maxdepth: 1 + + submit-checklist + stable-api-nonsense + stable-kernel-rules + management-style + embargoed-hardware-issues + +这些是一些总体性技术指南,由于不大好分类而放在这里: + +.. toctree:: + :maxdepth: 1 + + magic-number + volatile-considered-harmful + +.. only:: subproject and html + + 目录 + ==== + + * :ref:`genindex` diff --git a/Documentation/translations/zh_CN/process/kernel-driver-statement.rst b/Documentation/translations/zh_CN/process/kernel-driver-statement.rst new file mode 100644 index 000000000..2b3375bcc --- /dev/null +++ b/Documentation/translations/zh_CN/process/kernel-driver-statement.rst @@ -0,0 +1,199 @@ +.. _cn_process_statement_driver: + +.. include:: ../disclaimer-zh_CN.rst + +:Original: :ref:`Documentation/process/kernel-driver-statement.rst <process_statement_driver>` +:Translator: Alex Shi <alex.shi@linux.alibaba.com> + +内核驱动声明 +------------ + +关于Linux内核模块的立场声明 +=========================== + +我们,以下署名的Linux内核开发人员,认为任何封闭源Linux内核模块或驱动程序都是 +有害的和不可取的。我们已经一再发现它们对Linux用户,企业和更大的Linux生态系统 +有害。这样的模块否定了Linux开发模型的开放性,稳定性,灵活性和可维护性,并使 +他们的用户无法使用Linux社区的专业知识。提供闭源内核模块的供应商迫使其客户 +放弃Linux的主要优势或选择新的供应商。因此,为了充分利用开源所提供的成本节省和 +共享支持优势,我们敦促供应商采取措施,以开源内核代码在Linux上为其客户提供支持。 + +我们只为自己说话,而不是我们今天可能会为之工作,过去或将来会为之工作的任何公司。 + + - Dave Airlie + - Nick Andrew + - Jens Axboe + - Ralf Baechle + - Felipe Balbi + - Ohad Ben-Cohen + - Muli Ben-Yehuda + - Jiri Benc + - Arnd Bergmann + - Thomas Bogendoerfer + - Vitaly Bordug + - James Bottomley + - Josh Boyer + - Neil Brown + - Mark Brown + - David Brownell + - Michael Buesch + - Franck Bui-Huu + - Adrian Bunk + - François Cami + - Ralph Campbell + - Luiz Fernando N. Capitulino + - Mauro Carvalho Chehab + - Denis Cheng + - Jonathan Corbet + - Glauber Costa + - Alan Cox + - Magnus Damm + - Ahmed S. Darwish + - Robert P. J. Day + - Hans de Goede + - Arnaldo Carvalho de Melo + - Helge Deller + - Jean Delvare + - Mathieu Desnoyers + - Sven-Thorsten Dietrich + - Alexey Dobriyan + - Daniel Drake + - Alex Dubov + - Randy Dunlap + - Michael Ellerman + - Pekka Enberg + - Jan Engelhardt + - Mark Fasheh + - J. Bruce Fields + - Larry Finger + - Jeremy Fitzhardinge + - Mike Frysinger + - Kumar Gala + - Robin Getz + - Liam Girdwood + - Jan-Benedict Glaw + - Thomas Gleixner + - Brice Goglin + - Cyrill Gorcunov + - Andy Gospodarek + - Thomas Graf + - Krzysztof Halasa + - Harvey Harrison + - Stephen Hemminger + - Michael Hennerich + - Tejun Heo + - Benjamin Herrenschmidt + - Kristian Høgsberg + - Henrique de Moraes Holschuh + - Marcel Holtmann + - Mike Isely + - Takashi Iwai + - Olof Johansson + - Dave Jones + - Jesper Juhl + - Matthias Kaehlcke + - Kenji Kaneshige + - Jan Kara + - Jeremy Kerr + - Russell King + - Olaf Kirch + - Roel Kluin + - Hans-Jürgen Koch + - Auke Kok + - Peter Korsgaard + - Jiri Kosina + - Aaro Koskinen + - Mariusz Kozlowski + - Greg Kroah-Hartman + - Michael Krufky + - Aneesh Kumar + - Clemens Ladisch + - Christoph Lameter + - Gunnar Larisch + - Anders Larsen + - Grant Likely + - John W. Linville + - Yinghai Lu + - Tony Luck + - Pavel Machek + - Matt Mackall + - Paul Mackerras + - Roland McGrath + - Patrick McHardy + - Kyle McMartin + - Paul Menage + - Thierry Merle + - Eric Miao + - Akinobu Mita + - Ingo Molnar + - James Morris + - Andrew Morton + - Paul Mundt + - Oleg Nesterov + - Luca Olivetti + - S.Çağlar Onur + - Pierre Ossman + - Keith Owens + - Venkatesh Pallipadi + - Nick Piggin + - Nicolas Pitre + - Evgeniy Polyakov + - Richard Purdie + - Mike Rapoport + - Sam Ravnborg + - Gerrit Renker + - Stefan Richter + - David Rientjes + - Luis R. Rodriguez + - Stefan Roese + - Francois Romieu + - Rami Rosen + - Stephen Rothwell + - Maciej W. Rozycki + - Mark Salyzyn + - Yoshinori Sato + - Deepak Saxena + - Holger Schurig + - Amit Shah + - Yoshihiro Shimoda + - Sergei Shtylyov + - Kay Sievers + - Sebastian Siewior + - Rik Snel + - Jes Sorensen + - Alexey Starikovskiy + - Alan Stern + - Timur Tabi + - Hirokazu Takata + - Eliezer Tamir + - Eugene Teo + - Doug Thompson + - FUJITA Tomonori + - Dmitry Torokhov + - Marcelo Tosatti + - Steven Toth + - Theodore Tso + - Matthias Urlichs + - Geert Uytterhoeven + - Arjan van de Ven + - Ivo van Doorn + - Rik van Riel + - Wim Van Sebroeck + - Hans Verkuil + - Horst H. von Brand + - Dmitri Vorobiev + - Anton Vorontsov + - Daniel Walker + - Johannes Weiner + - Harald Welte + - Matthew Wilcox + - Dan J. Williams + - Darrick J. Wong + - David Woodhouse + - Chris Wright + - Bryan Wu + - Rafael J. Wysocki + - Herbert Xu + - Vlad Yasevich + - Peter Zijlstra + - Bartlomiej Zolnierkiewicz diff --git a/Documentation/translations/zh_CN/process/kernel-enforcement-statement.rst b/Documentation/translations/zh_CN/process/kernel-enforcement-statement.rst new file mode 100644 index 000000000..75f7b7b91 --- /dev/null +++ b/Documentation/translations/zh_CN/process/kernel-enforcement-statement.rst @@ -0,0 +1,151 @@ +.. _cn_process_statement_kernel: + +.. include:: ../disclaimer-zh_CN.rst + +:Original: :ref:`Documentation/process/kernel-enforcement-statement.rst <process_statement_kernel>` +:Translator: Alex Shi <alex.shi@linux.alibaba.com> + +Linux 内核执行声明 +------------------ + +作为Linux内核的开发人员,我们对如何使用我们的软件以及如何实施软件许可证有着 +浓厚的兴趣。遵守GPL-2.0的互惠共享义务对我们软件和社区的长期可持续性至关重要。 + +虽然有权强制执行对我们社区的贡献中的单独版权权益,但我们有共同的利益,即确保 +个人强制执行行动的方式有利于我们的社区,不会对我们软件生态系统的健康和增长 +产生意外的负面影响。为了阻止无益的执法行动,我们同意代表我们自己和我们版权 +利益的任何继承人对Linux内核用户作出以下符合我们开发社区最大利益的承诺: + + 尽管有GPL-2.0的终止条款,我们同意,采用以下GPL-3.0条款作为我们许可证下的 + 附加许可,作为任何对许可证下权利的非防御性主张,这符合我们开发社区的最佳 + 利益。 + + 但是,如果您停止所有违反本许可证的行为,则您从特定版权持有人处获得的 + 许可证将被恢复:(a)暂时恢复,除非版权持有人明确并最终终止您的许可证; + 以及(b)永久恢复, 如果版权持有人未能在你终止违反后60天内以合理方式 + 通知您违反本许可证的行为,则永久恢复您的许可证。 + + 此外,如果版权所有者以某种合理的方式通知您违反了本许可,这是您第一次 + 从该版权所有者处收到违反本许可的通知(对于任何作品),并且您在收到通知 + 后的30天内纠正违规行为。则您从特定版权所有者处获得的许可将永久恢复. + +我们提供这些保证的目的是鼓励更多地使用该软件。我们希望公司和个人使用、修改和 +分发此软件。我们希望以公开和透明的方式与用户合作,以消除我们对法规遵从性或强制 +执行的任何不确定性,这些不确定性可能会限制我们软件的采用。我们将法律行动视为 +最后手段,只有在其他社区努力未能解决这一问题时才采取行动。 + +最后,一旦一个不合规问题得到解决,我们希望用户会感到欢迎,加入我们为之努力的 +这个项目。共同努力,我们会更强大。 + +除了下面提到的以外,我们只为自己说话,而不是为今天、过去或将来可能为之工作的 +任何公司说话。 + + - Laura Abbott + - Bjorn Andersson (Linaro) + - Andrea Arcangeli + - Neil Armstrong + - Jens Axboe + - Pablo Neira Ayuso + - Khalid Aziz + - Ralf Baechle + - Felipe Balbi + - Arnd Bergmann + - Ard Biesheuvel + - Tim Bird + - Paolo Bonzini + - Christian Borntraeger + - Mark Brown (Linaro) + - Paul Burton + - Javier Martinez Canillas + - Rob Clark + - Kees Cook (Google) + - Jonathan Corbet + - Dennis Dalessandro + - Vivien Didelot (Savoir-faire Linux) + - Hans de Goede + - Mel Gorman (SUSE) + - Sven Eckelmann + - Alex Elder (Linaro) + - Fabio Estevam + - Larry Finger + - Bhumika Goyal + - Andy Gross + - Juergen Gross + - Shawn Guo + - Ulf Hansson + - Stephen Hemminger (Microsoft) + - Tejun Heo + - Rob Herring + - Masami Hiramatsu + - Michal Hocko + - Simon Horman + - Johan Hovold (Hovold Consulting AB) + - Christophe JAILLET + - Olof Johansson + - Lee Jones (Linaro) + - Heiner Kallweit + - Srinivas Kandagatla + - Jan Kara + - Shuah Khan (Samsung) + - David Kershner + - Jaegeuk Kim + - Namhyung Kim + - Colin Ian King + - Jeff Kirsher + - Greg Kroah-Hartman (Linux Foundation) + - Christian König + - Vinod Koul + - Krzysztof Kozlowski + - Viresh Kumar + - Aneesh Kumar K.V + - Julia Lawall + - Doug Ledford + - Chuck Lever (Oracle) + - Daniel Lezcano + - Shaohua Li + - Xin Long + - Tony Luck + - Catalin Marinas (Arm Ltd) + - Mike Marshall + - Chris Mason + - Paul E. McKenney + - Arnaldo Carvalho de Melo + - David S. Miller + - Ingo Molnar + - Kuninori Morimoto + - Trond Myklebust + - Martin K. Petersen (Oracle) + - Borislav Petkov + - Jiri Pirko + - Josh Poimboeuf + - Sebastian Reichel (Collabora) + - Guenter Roeck + - Joerg Roedel + - Leon Romanovsky + - Steven Rostedt (VMware) + - Frank Rowand + - Ivan Safonov + - Anna Schumaker + - Jes Sorensen + - K.Y. Srinivasan + - David Sterba (SUSE) + - Heiko Stuebner + - Jiri Kosina (SUSE) + - Willy Tarreau + - Dmitry Torokhov + - Linus Torvalds + - Thierry Reding + - Rik van Riel + - Luis R. Rodriguez + - Geert Uytterhoeven (Glider bvba) + - Eduardo Valentin (Amazon.com) + - Daniel Vetter + - Linus Walleij + - Richard Weinberger + - Dan Williams + - Rafael J. Wysocki + - Arvind Yadav + - Masahiro Yamada + - Wei Yongjun + - Lv Zheng + - Marc Zyngier (Arm Ltd) diff --git a/Documentation/translations/zh_CN/process/license-rules.rst b/Documentation/translations/zh_CN/process/license-rules.rst new file mode 100644 index 000000000..30c272b2a --- /dev/null +++ b/Documentation/translations/zh_CN/process/license-rules.rst @@ -0,0 +1,370 @@ +.. SPDX-License-Identifier: GPL-2.0 + +.. include:: ../disclaimer-zh_CN.rst + +:Original: :ref:`Documentation/process/license-rules.rst <kernel_licensing>` +:Translator: Alex Shi <alex.shi@linux.alibaba.com> + +.. _cn_kernel_licensing: + +Linux内核许可规则 +================= + +Linux内核根据LICENSES/preferred/GPL-2.0中提供的GNU通用公共许可证版本2 +(GPL-2.0)的条款提供,并在LICENSES/exceptions/Linux-syscall-note中显式 +描述了例外的系统调用,如COPYING文件中所述。 + +此文档文件提供了如何对每个源文件进行注释以使其许可证清晰明确的说明。 +它不会取代内核的许可证。 + +内核源代码作为一个整体适用于COPYING文件中描述的许可证,但是单个源文件可以 +具有不同的与GPL-20兼容的许可证:: + + GPL-1.0+ : GNU通用公共许可证v1.0或更高版本 + GPL-2.0+ : GNU通用公共许可证v2.0或更高版本 + LGPL-2.0 : 仅限GNU库通用公共许可证v2 + LGPL-2.0+: GNU 库通用公共许可证v2或更高版本 + LGPL-2.1 : 仅限GNU宽通用公共许可证v2.1 + LGPL-2.1+: GNU宽通用公共许可证v2.1或更高版本 + +除此之外,个人文件可以在双重许可下提供,例如一个兼容的GPL变体,或者BSD, +MIT等许可。 + +用户空间API(UAPI)头文件描述了用户空间程序与内核的接口,这是一种特殊情况。 +根据内核COPYING文件中的注释,syscall接口是一个明确的边界,它不会将GPL要求 +扩展到任何使用它与内核通信的软件。由于UAPI头文件必须包含在创建在Linux内核 +上运行的可执行文件的任何源文件中,因此此例外必须记录在特别的许可证表述中。 + +表达源文件许可证的常用方法是将匹配的样板文本添加到文件的顶部注释中。由于 +格式,拼写错误等,这些“样板”很难通过那些在上下文中使用的验证许可证合规性 +的工具。 + +样板文本的替代方法是在每个源文件中使用软件包数据交换(SPDX)许可证标识符。 +SPDX许可证标识符是机器可解析的,并且是用于提供文件内容的许可证的精确缩写。 +SPDX许可证标识符由Linux 基金会的SPDX 工作组管理,并得到了整个行业,工具 +供应商和法律团队的合作伙伴的一致同意。有关详细信息,请参阅 +https://spdx.org/ + +Linux内核需要所有源文件中的精确SPDX标识符。内核中使用的有效标识符在 +`许可标识符`_ 一节中进行了解释,并且已可以在 +https://spdx.org/licenses/ 上的官方SPDX许可证列表中检索,并附带许可证 +文本。 + +许可标识符语法 +-------------- + +1.安置: + + 内核文件中的SPDX许可证标识符应添加到可包含注释的文件中的第一行。对于大多 + 数文件,这是第一行,除了那些在第一行中需要'#!PATH_TO_INTERPRETER'的脚本。 + 对于这些脚本,SPDX标识符进入第二行。 + +| + +2. 风格: + + SPDX许可证标识符以注释的形式添加。注释样式取决于文件类型:: + + C source: // SPDX-License-Identifier: <SPDX License Expression> + C header: /* SPDX-License-Identifier: <SPDX License Expression> */ + ASM: /* SPDX-License-Identifier: <SPDX License Expression> */ + scripts: # SPDX-License-Identifier: <SPDX License Expression> + .rst: .. SPDX-License-Identifier: <SPDX License Expression> + .dts{i}: // SPDX-License-Identifier: <SPDX License Expression> + + 如果特定工具无法处理标准注释样式,则应使用工具接受的相应注释机制。这是在 + C 头文件中使用“/\*\*/”样式注释的原因。过去在使用生成的.lds文件中观察到 + 构建被破坏,其中'ld'无法解析C++注释。现在已经解决了这个问题,但仍然有较 + 旧的汇编程序工具无法处理C++样式的注释。 + +| + +3. 句法: + + <SPDX许可证表达式>是SPDX许可证列表中的SPDX短格式许可证标识符,或者在许可 + 证例外适用时由“WITH”分隔的两个SPDX短格式许可证标识符的组合。当应用多个许 + 可证时,表达式由分隔子表达式的关键字“AND”,“OR”组成,并由“(”,“)”包围。 + + 带有“或更高”选项的[L]GPL等许可证的许可证标识符通过使用“+”来表示“或更高” + 选项来构建。:: + + // SPDX-License-Identifier: GPL-2.0+ + // SPDX-License-Identifier: LGPL-2.1+ + + 当需要修正的许可证时,应使用WITH。 例如,linux内核UAPI文件使用表达式:: + + // SPDX-License-Identifier: GPL-2.0 WITH Linux-syscall-note + // SPDX-License-Identifier: GPL-2.0+ WITH Linux-syscall-note + + 其它在内核中使用WITH例外的事例如下:: + + // SPDX-License-Identifier: GPL-2.0 WITH mif-exception + // SPDX-License-Identifier: GPL-2.0+ WITH GCC-exception-2.0 + + 例外只能与特定的许可证标识符一起使用。有效的许可证标识符列在异常文本文件 + 的标记中。有关详细信息,请参阅 `许可标识符`_ 一章中的 `例外`_ 。 + + 如果文件是双重许可且只选择一个许可证,则应使用OR。例如,一些dtsi文件在双 + 许可下可用:: + + // SPDX-License-Identifier: GPL-2.0 OR BSD-3-Clause + + 内核中双许可文件中许可表达式的示例:: + + // SPDX-License-Identifier: GPL-2.0 OR MIT + // SPDX-License-Identifier: GPL-2.0 OR BSD-2-Clause + // SPDX-License-Identifier: GPL-2.0 OR Apache-2.0 + // SPDX-License-Identifier: GPL-2.0 OR MPL-1.1 + // SPDX-License-Identifier: (GPL-2.0 WITH Linux-syscall-note) OR MIT + // SPDX-License-Identifier: GPL-1.0+ OR BSD-3-Clause OR OpenSSL + + 如果文件具有多个许可证,其条款全部适用于使用该文件,则应使用AND。例如, + 如果代码是从另一个项目继承的,并且已经授予了将其放入内核的权限,但原始 + 许可条款需要保持有效:: + + // SPDX-License-Identifier: (GPL-2.0 WITH Linux-syscall-note) AND MIT + + 另一个需要遵守两套许可条款的例子是:: + + // SPDX-License-Identifier: GPL-1.0+ AND LGPL-2.1+ + +许可标识符 +---------- + +当前使用的许可证以及添加到内核的代码许可证可以分解为: + +1. _`优先许可`: + + 应尽可能使用这些许可证,因为它们已知完全兼容并广泛使用。这些许可证在内核 + 目录:: + + LICENSES/preferred/ + + 此目录中的文件包含完整的许可证文本和 `元标记`_ 。文件名与SPDX许可证标识 + 符相同,后者应用于源文件中的许可证。 + + 例如:: + + LICENSES/preferred/GPL-2.0 + + 包含GPLv2许可证文本和所需的元标签:: + + LICENSES/preferred/MIT + + 包含MIT许可证文本和所需的元标记 + + _`元标记`: + + 许可证文件中必须包含以下元标记: + + - Valid-License-Identifier: + + 一行或多行, 声明那些许可标识符在项目内有效, 以引用此特定许可的文本。通 + 常这是一个有效的标识符,但是例如对于带有'或更高'选项的许可证,两个标识 + 符都有效。 + + - SPDX-URL: + + SPDX页面的URL,其中包含与许可证相关的其他信息. + + - Usage-Guidance: + + 使用建议的自由格式文本。该文本必须包含SPDX许可证标识符的正确示例,因为 + 它们应根据 `许可标识符语法`_ 指南放入源文件中。 + + - License-Text: + + 此标记之后的所有文本都被视为原始许可文本 + + 文件格式示例:: + + Valid-License-Identifier: GPL-2.0 + Valid-License-Identifier: GPL-2.0+ + SPDX-URL: https://spdx.org/licenses/GPL-2.0.html + Usage-Guide: + To use this license in source code, put one of the following SPDX + tag/value pairs into a comment according to the placement + guidelines in the licensing rules documentation. + For 'GNU General Public License (GPL) version 2 only' use: + SPDX-License-Identifier: GPL-2.0 + For 'GNU General Public License (GPL) version 2 or any later version' use: + SPDX-License-Identifier: GPL-2.0+ + License-Text: + Full license text + + :: + + SPDX-License-Identifier: MIT + SPDX-URL: https://spdx.org/licenses/MIT.html + Usage-Guide: + To use this license in source code, put the following SPDX + tag/value pair into a comment according to the placement + guidelines in the licensing rules documentation. + SPDX-License-Identifier: MIT + License-Text: + Full license text + +| + +2. 不推荐的许可证: + + 这些许可证只应用于现有代码或从其他项目导入代码。这些许可证在内核目录:: + + LICENSES/other/ + + 此目录中的文件包含完整的许可证文本和 `元标记`_ 。文件名与SPDX许可证标识 + 符相同,后者应用于源文件中的许可证。 + + 例如:: + + LICENSES/other/ISC + + 包含国际系统联合许可文本和所需的元标签:: + + LICENSES/other/ZLib + + 包含ZLIB许可文本和所需的元标签. + + 元标签: + + “其他”许可证的元标签要求与 `优先许可`_ 的要求相同。 + + 文件格式示例:: + + Valid-License-Identifier: ISC + SPDX-URL: https://spdx.org/licenses/ISC.html + Usage-Guide: + Usage of this license in the kernel for new code is discouraged + and it should solely be used for importing code from an already + existing project. + To use this license in source code, put the following SPDX + tag/value pair into a comment according to the placement + guidelines in the licensing rules documentation. + SPDX-License-Identifier: ISC + License-Text: + Full license text + +| + +3. _`例外`: + + 某些许可证可以修改,并允许原始许可证不具有的某些例外权利。这些例外在 + 内核目录:: + + LICENSES/exceptions/ + + 此目录中的文件包含完整的例外文本和所需的 `例外元标记`_ 。 + + 例如:: + + LICENSES/exceptions/Linux-syscall-note + + 包含Linux内核的COPYING文件中记录的Linux系统调用例外,该文件用于UAPI + 头文件。例如:: + + LICENSES/exceptions/GCC-exception-2.0 + + 包含GCC'链接例外',它允许独立于其许可证的任何二进制文件与标记有此例外的 + 文件的编译版本链接。这是从GPL不兼容源代码创建可运行的可执行文件所必需的。 + + _`例外元标记`: + + 以下元标记必须在例外文件中可用: + + - SPDX-Exception-Identifier: + + 一个可与SPDX许可证标识符一起使用的例外标识符。 + + - SPDX-URL: + + SPDX页面的URL,其中包含与例外相关的其他信息。 + + - SPDX-Licenses: + + 以逗号分隔的例外可用的SPDX许可证标识符列表。 + + - Usage-Guidance: + + 使用建议的自由格式文本。必须在文本后面加上SPDX许可证标识符的正确示例, + 因为它们应根据 `许可标识符语法`_ 指南放入源文件中。 + + - Exception-Text: + + 此标记之后的所有文本都被视为原始异常文本 + + 文件格式示例:: + + SPDX-Exception-Identifier: Linux-syscall-note + SPDX-URL: https://spdx.org/licenses/Linux-syscall-note.html + SPDX-Licenses: GPL-2.0, GPL-2.0+, GPL-1.0+, LGPL-2.0, LGPL-2.0+, LGPL-2.1, LGPL-2.1+ + Usage-Guidance: + This exception is used together with one of the above SPDX-Licenses + to mark user-space API (uapi) header files so they can be included + into non GPL compliant user-space application code. + To use this exception add it with the keyword WITH to one of the + identifiers in the SPDX-Licenses tag: + SPDX-License-Identifier: <SPDX-License> WITH Linux-syscall-note + Exception-Text: + Full exception text + + :: + + SPDX-Exception-Identifier: GCC-exception-2.0 + SPDX-URL: https://spdx.org/licenses/GCC-exception-2.0.html + SPDX-Licenses: GPL-2.0, GPL-2.0+ + Usage-Guidance: + The "GCC Runtime Library exception 2.0" is used together with one + of the above SPDX-Licenses for code imported from the GCC runtime + library. + To use this exception add it with the keyword WITH to one of the + identifiers in the SPDX-Licenses tag: + SPDX-License-Identifier: <SPDX-License> WITH GCC-exception-2.0 + Exception-Text: + Full exception text + + +所有SPDX许可证标识符和例外都必须在LICENSES子目录中具有相应的文件。这是允许 +工具验证(例如checkpatch.pl)以及准备好从源读取和提取许可证所必需的, 这是 +各种FOSS组织推荐的,例如 `FSFE REUSE initiative <https://reuse.software/>`_. + +_`模块许可` +----------------- + + 可加载内核模块还需要MODULE_LICENSE()标记。此标记既不替代正确的源代码 + 许可证信息(SPDX-License-Identifier),也不以任何方式表示或确定提供模块 + 源代码的确切许可证。 + + 此标记的唯一目的是提供足够的信息,该模块是否是自由软件或者是内核模块加 + 载器和用户空间工具的专有模块。 + + MODULE_LICENSE()的有效许可证字符串是: + + ============================= ============================================= + "GPL" 模块是根据GPL版本2许可的。这并不表示仅限于 + GPL-2.0或GPL-2.0或更高版本之间的任何区别。 + 最正确许可证信息只能通过相应源文件中的许可证 + 信息来确定 + + "GPL v2" 和"GPL"相同,它的存在是因为历史原因。 + + "GPL and additional rights" 表示模块源在GPL v2变体和MIT许可下双重许可的 + 历史变体。请不要在新代码中使用。 + + "Dual MIT/GPL" 表达该模块在GPL v2变体或MIT许可证选择下双重 + 许可的正确方式。 + + "Dual BSD/GPL" 该模块根据GPL v2变体或BSD许可证选择进行双重 + 许可。 BSD许可证的确切变体只能通过相应源文件 + 中的许可证信息来确定。 + + "Dual MPL/GPL" 该模块根据GPL v2变体或Mozilla Public License + (MPL)选项进行双重许可。 MPL许可证的确切变体 + 只能通过相应的源文件中的许可证信息来确定。 + + "Proprietary" 该模块属于专有许可。此字符串仅用于专有的第三 + 方模块,不能用于在内核树中具有源代码的模块。 + 以这种方式标记的模块在加载时会使用'P'标记污 + 染内核,并且内核模块加载器拒绝将这些模块链接 + 到使用EXPORT_SYMBOL_GPL()导出的符号。 + ============================= ============================================= + diff --git a/Documentation/translations/zh_CN/process/magic-number.rst b/Documentation/translations/zh_CN/process/magic-number.rst new file mode 100644 index 000000000..0617ce125 --- /dev/null +++ b/Documentation/translations/zh_CN/process/magic-number.rst @@ -0,0 +1,74 @@ +.. _cn_magicnumbers: + +.. include:: ../disclaimer-zh_CN.rst + +:Original: :ref:`Documentation/process/magic-number.rst <magicnumbers>` + +如果想评论或更新本文的内容,请直接发信到LKML。如果你使用英文交流有困难的话,也可 +以向中文版维护者求助。如果本翻译更新不及时或者翻译存在问题,请联系中文版维护者:: + + 中文版维护者: 贾威威 Jia Wei Wei <harryxiyou@gmail.com> + 中文版翻译者: 贾威威 Jia Wei Wei <harryxiyou@gmail.com> + 中文版校译者: 贾威威 Jia Wei Wei <harryxiyou@gmail.com> + +Linux 魔术数 +============ + +这个文件是有关当前使用的魔术值注册表。当你给一个结构添加了一个魔术值,你也应该把这个魔术值添加到这个文件,因为我们最好把用于各种结构的魔术值统一起来。 + +使用魔术值来保护内核数据结构是一个非常好的主意。这就允许你在运行期检查(a)一个结构是否已经被攻击,或者(b)你已经给一个例行程序通过了一个错误的结构。后一种情况特别地有用---特别是当你通过一个空指针指向结构体的时候。tty源码,例如,经常通过特定驱动使用这种方法并且反复地排列特定方面的结构。 + +使用魔术值的方法是在结构的开始处声明的,如下:: + + struct tty_ldisc { + int magic; + ... + }; + +当你以后给内核添加增强功能的时候,请遵守这条规则!这样就会节省数不清的调试时间,特别是一些古怪的情况,例如,数组超出范围并且重新写了超出部分。遵守这个规则,这些情况可以被快速地,安全地避免。 + + Theodore Ts'o + 31 Mar 94 + +给当前的Linux 2.1.55添加魔术表。 + + Michael Chastain + <mailto:mec@shout.net> + 22 Sep 1997 + +现在应该最新的Linux 2.1.112.因为在特性冻结期间,不能在2.2.x前改变任何东西。这些条目被数域所排序。 + + Krzysztof G.Baranowski + <mailto: kgb@knm.org.pl> + 29 Jul 1998 + +更新魔术表到Linux 2.5.45。刚好越过特性冻结,但是有可能还会有一些新的魔术值在2.6.x之前融入到内核中。 + + Petr Baudis + <pasky@ucw.cz> + 03 Nov 2002 + +更新魔术表到Linux 2.5.74。 + + Fabian Frederick + <ffrederick@users.sourceforge.net> + 09 Jul 2003 + +===================== ================ ======================== ========================================== +魔术数名 数字 结构 文件 +===================== ================ ======================== ========================================== +PG_MAGIC 'P' pg_{read,write}_hdr ``include/linux/pg.h`` +APM_BIOS_MAGIC 0x4101 apm_user ``arch/x86/kernel/apm_32.c`` +FASYNC_MAGIC 0x4601 fasync_struct ``include/linux/fs.h`` +SLIP_MAGIC 0x5302 slip ``drivers/net/slip.h`` +MGSLPC_MAGIC 0x5402 mgslpc_info ``drivers/char/pcmcia/synclink_cs.c`` +BAYCOM_MAGIC 0x19730510 baycom_state ``drivers/net/baycom_epp.c`` +HDLCDRV_MAGIC 0x5ac6e778 hdlcdrv_state ``include/linux/hdlcdrv.h`` +KV_MAGIC 0x5f4b565f kernel_vars_s ``arch/mips/include/asm/sn/klkernvars.h`` +CODA_MAGIC 0xC0DAC0DA coda_file_info ``fs/coda/coda_fs_i.h`` +YAM_MAGIC 0xF10A7654 yam_port ``drivers/net/hamradio/yam.c`` +CCB_MAGIC 0xf2691ad2 ccb ``drivers/scsi/ncr53c8xx.c`` +QUEUE_MAGIC_FREE 0xf7e1c9a3 queue_entry ``drivers/scsi/arm/queue.c`` +QUEUE_MAGIC_USED 0xf7e1cc33 queue_entry ``drivers/scsi/arm/queue.c`` +NMI_MAGIC 0x48414d4d455201 nmi_s ``arch/mips/include/asm/sn/nmi.h`` +===================== ================ ======================== ========================================== diff --git a/Documentation/translations/zh_CN/process/management-style.rst b/Documentation/translations/zh_CN/process/management-style.rst new file mode 100644 index 000000000..8053ae474 --- /dev/null +++ b/Documentation/translations/zh_CN/process/management-style.rst @@ -0,0 +1,207 @@ +.. include:: ../disclaimer-zh_CN.rst + +:Original: :ref:`Documentation/process/management-style.rst <managementstyle>` +:Translator: Alex Shi <alex.shi@linux.alibaba.com> + +.. _cn_managementstyle: + +Linux内核管理风格 +================= + +这是一个简短的文档,描述了Linux内核首选的(或胡编的,取决于您问谁)管理风格。 +它的目的是在某种程度上参照 :ref:`process/coding-style.rst <codingstyle>` +主要是为了避免反复回答 [#cnf1]_ 相同(或类似)的问题。 + +管理风格是非常个人化的,比简单的编码风格规则更难以量化,因此本文档可能与实 +际情况有关,也可能与实际情况无关。起初它是一个玩笑,但这并不意味着它可能不 +是真的。你得自己决定。 + +顺便说一句,在谈到“核心管理者”时,主要是技术负责人,而不是在公司内部进行传 +统管理的人。如果你签署了采购订单或者对你的团队的预算有任何了解,你几乎肯定 +不是一个核心管理者。这些建议可能适用于您,也可能不适用于您。 + +首先,我建议你购买“高效人的七个习惯”,而不是阅读它。烧了它,这是一个伟大的 +象征性姿态。 + +.. [#cnf1] 本文件并不是通过回答问题,而是通过让提问者痛苦地明白,我们不知道 + 答案是什么。 + +不管怎样,这里是: + +.. _cn_decisions: + +1)决策 +------- + +每个人都认为管理者做决定,而且决策很重要。决定越大越痛苦,管理者就必须越高级。 +这很明显,但事实并非如此。 + +最重要的是 **避免** 做出决定。尤其是,如果有人告诉你“选择(a)或(b), +我们真的需要你来做决定”,你就是陷入麻烦的管理者。你管理的人比你更了解细节, +所以如果他们来找你做技术决策,你完蛋了。你显然没有能力为他们做这个决定。 + +(推论:如果你管理的人不比你更了解细节,你也会被搞砸,尽管原因完全不同。 +也就是说,你的工作是错的,他们应该管理你的才智) + +所以最重要的是 **避免** 做出决定,至少是那些大而痛苦的决定。做一些小的 +和非结果性的决定是很好的,并且使您看起来好像知道自己在做什么,所以内核管理者 +需要做的是将那些大的和痛苦的决定变成那些没有人真正关心的小事情。 + +这有助于认识到一个大的决定和一个小的决定之间的关键区别是你是否可以在事后修正 +你的决定。任何决定都可以通过始终确保如果你错了(而且你一定会错),你以后总是 +可以通过回溯来弥补损失。突然间,你就要做两个无关紧要的决定,一个是错误的,另 +一个是正确的。 + +人们甚至会认为这是真正的领导能力(咳,胡说,咳)。 + +因此,避免重大决策的关键在于避免做那些无法挽回的事情。不要被引导到一个你无法 +逃离的角落。走投无路的老鼠可能很危险——走投无路的管理者真可怜。 + +事实证明,由于没有人会愚蠢到让内核管理者承担巨大的财政责任,所以通常很容易 +回溯。既然你不可能浪费掉你无法偿还的巨额资金,你唯一可以回溯的就是技术决策, +而回溯很容易:只要告诉大家你是个不称职的傻瓜,说对不起,然后撤销你去年让别 +人所做的毫无价值的工作。突然间,你一年前做的决定不在是一个重大的决定,因为 +它很容易被推翻。 + +事实证明,有些人对接受这种方法有困难,原因有两个: + + - 承认你是个白痴比看起来更难。我们都喜欢保持形象,在公共场合说你错了有时 + 确实很难。 + - 如果有人告诉你,你去年所做的工作终究是不值得的,那么对那些可怜的低级工 + 程师来说也是很困难的,虽然实际的 **工作** 很容易删除,但你可能已经不可 + 挽回地失去了工程师的信任。记住:“不可撤销”是我们一开始就试图避免的, + 而你的决定终究是一个重大的决定。 + +令人欣慰的是,这两个原因都可以通过预先承认你没有任何线索,提前告诉人们你的 +决定完全是初步的,而且可能是错误的事情来有效地缓解。你应该始终保留改变主意 +的权利,并让人们 **意识** 到这一点。当你 **还没有** 做过真正愚蠢的事情的时 +候,承认自己是愚蠢的要容易得多。 + +然后,当它真的被证明是愚蠢的时候,人们就转动他们的眼珠说“哎呀,下次不要了”。 + +这种对不称职的先发制人的承认,也可能使真正做这项工作的人也会三思是否值得做。 +毕竟,如果他们不确定这是否是一个好主意,你肯定不应该通过向他们保证他们所做 +的工作将会进入(内核)鼓励他们。在他们开始一项巨大的努力之前,至少让他们三 +思而后行。 + +记住:他们最好比你更了解细节,而且他们通常认为他们对每件事都有答案。作为一 +个管理者,你能做的最好的事情不是灌输自信,而是对他们所做的事情进行健康的批 +判性思考。 + +顺便说一句,另一种避免做出决定的方法是看起来很可怜的抱怨 “我们不能两者兼 +得吗?” 相信我,它是有效的。如果不清楚哪种方法更好,他们最终会弄清楚的。 +最终的答案可能是两个团队都会因为这种情况而感到沮丧,以至于他们放弃了。 + +这听起来像是一个失败,但这通常是一个迹象,表明两个项目都有问题,而参与其中 +的人不能做决定的原因是他们都是错误的。你最终会闻到玫瑰的味道,你避免了另一 +个你本可以搞砸的决定。 + +2)人 +----- + +大多数人都是白痴,做一名管理者意味着你必须处理好这件事,也许更重要的是, +**他们** 必须处理好你。 + +事实证明,虽然很容易纠正技术错误,但不容易纠正人格障碍。你只能和他们的和 +你的(人格障碍)共处。 + +但是,为了做好作为内核管理者的准备,最好记住不要烧掉任何桥梁,不要轰炸任何 +无辜的村民,也不要疏远太多的内核开发人员。事实证明,疏远人是相当容易的,而 +亲近一个疏远的人是很难的。因此,“疏远”立即属于“不可逆”的范畴,并根据 +:ref:`cn_decisions` 成为绝不可以做的事情。 + +这里只有几个简单的规则: + + (1) 不要叫人笨蛋(至少不要在公共场合) + (2) 学习如何在忘记规则(1)时道歉 + +问题在于 #1 很容易去做,因为你可以用数百万种不同的方式说“你是一个笨蛋” [#cnf2]_ +有时甚至没有意识到,而且几乎总是带着一种白热化的信念,认为你是对的。 + +你越确信自己是对的(让我们面对现实吧,你可以把几乎所有人都称为坏人,而且你 +经常是对的),事后道歉就越难。 + +要解决此问题,您实际上只有两个选项: + + - 非常擅长道歉 + - 把“爱”均匀地散开,没有人会真正感觉到自己被不公平地瞄准了。让它有足够的 + 创造性,他们甚至可能会觉得好笑。 + +选择永远保持礼貌是不存在的。没有人会相信一个如此明显地隐藏了他们真实性格的人。 + +.. [#cnf2] 保罗·西蒙演唱了“离开爱人的50种方法”,因为坦率地说,“告诉开发者 + 他们是D*CKHEAD" 的100万种方法都无法确认。但我确信他已经这么想了。 + +3)人2 - 好人 +------------- + +虽然大多数人都是白痴,但不幸的是,据此推论你也是白痴,尽管我们都自我感觉良 +好,我们比普通人更好(让我们面对现实吧,没有人相信他们是普通人或低于普通人), +我们也应该承认我们不是最锋利的刀,而且会有其他人比你更不像白痴。 + +有些人对聪明人反应不好。其他人利用它们。 + +作为内核维护人员,确保您在第二组中。接受他们,因为他们会让你的工作更容易。 +特别是,他们能够为你做决定,这就是游戏的全部内容。 + +所以当你发现一个比你聪明的人时,就顺其自然吧。你的管理职责在很大程度上变成 +了“听起来像是个好主意——去尝试吧”,或者“听起来不错,但是XXX呢?”“。第二个版 +本尤其是一个很好的方法,要么学习一些关于“XXX”的新东西,要么通过指出一些聪明 +人没有想到的东西来显得更具管理性。无论哪种情况,你都会赢。 + +要注意的一件事是认识到一个领域的伟大不一定会转化为其他领域。所以你可能会向 +特定的方向刺激人们,但让我们面对现实吧,他们可能擅长他们所做的事情,而且对 +其他事情都很差劲。好消息是,人们往往会自然而然地重拾他们擅长的东西,所以当 +你向某个方向刺激他们时,你并不是在做不可逆转的事情,只是不要用力推。 + +4)责备 +------- + +事情会出问题的,人们希望去责备人。贴标签,你就是受责备的人。 + +事实上,接受责备并不难,尤其是当人们意识到这不 **全是** 你的过错时。这让我 +们找到了承担责任的最佳方式:为别人承担这件事。你会感觉很好,他们会感觉很好, +没有受到指责. 那谁,失去了他们的全部36GB色情收藏的人,因为你的无能将勉强承 +认,你至少没有试图逃避责任。 + +然后让真正搞砸了的开发人员(如果你能找到他们)私下知道他们搞砸了。不仅是为 +了将来可以避免,而且为了让他们知道他们欠你一个人情。而且,也许更重要的是, +他们也可能是能够解决问题的人。因为,让我们面对现实吧,肯定不是你。 + +承担责任也是你首先成为管理者的原因。这是让人们信任你,让你获得潜在的荣耀的 +一部分,因为你就是那个会说“我搞砸了”的人。如果你已经遵循了以前的规则,你现 +在已经很擅长说了。 + +5)应避免的事情 +--------------- + +有一件事人们甚至比被称为“笨蛋”更讨厌,那就是在一个神圣的声音中被称为“笨蛋”。 +第一个你可以道歉,第二个你不会真正得到机会。即使你做得很好,他们也可能不再 +倾听。 + +我们都认为自己比别人强,这意味着当别人装腔作势时,这会让我们很恼火。你也许 +在道德和智力上比你周围的每个人都优越,但不要试图太明显,除非你真的打算激怒 +某人 [#cnf3]_ + +同样,不要对事情太客气或太微妙。礼貌很容易落得落花流水,把问题隐藏起来, +正如他们所说,“在互联网上,没人能听到你的含蓄。”用一个钝器把这一点锤进去, +因为你不能真的依靠别人来获得你的观点。 + +一些幽默可以帮助缓和直率和道德化。过度到荒谬的地步,可以灌输一个观点,而不 +会让接受者感到痛苦,他们只是认为你是愚蠢的。因此,它可以帮助我们摆脱对批评 +的个人心理障碍。 + +.. [#cnf3] 提示:与你的工作没有直接关系的网络新闻组是消除你对他人不满的好 + 方法。偶尔写些侮辱性的帖子,打个喷嚏,让你的情绪得到净化。别把牢骚带回家 + +6)为什么是我? +--------------- + +既然你的主要责任似乎是为别人的错误承担责任,并且让别人痛苦地明白你是不称职 +的,那么显而易见的问题之一就变成了为什么首先要这样做。 + +首先,虽然你可能会或可能不会听到十几岁女孩(或男孩,让我们不要在这里评判或 +性别歧视)敲你的更衣室门,你会得到一个巨大的个人成就感为“负责”。别介意你真 +的在领导别人,你要跟上别人,尽可能快地追赶他们。每个人都会认为你是负责人。 + +如果你可以做到这个, 这是个伟大的工作! diff --git a/Documentation/translations/zh_CN/process/programming-language.rst b/Documentation/translations/zh_CN/process/programming-language.rst new file mode 100644 index 000000000..fabdc338d --- /dev/null +++ b/Documentation/translations/zh_CN/process/programming-language.rst @@ -0,0 +1,71 @@ +.. include:: ../disclaimer-zh_CN.rst + +:Original: :ref:`Documentation/process/programming-language.rst <programming_language>` +:Translator: Alex Shi <alex.shi@linux.alibaba.com> + +.. _cn_programming_language: + +程序设计语言 +============ + +内核是用C语言 :ref:`c-language <cn_c-language>` 编写的。更准确地说,内核通常是用 :ref:`gcc <cn_gcc>` +在 ``-std=gnu11`` :ref:`gcc-c-dialect-options <cn_gcc-c-dialect-options>` 下编译的:ISO C11的 GNU 方言 + +这种方言包含对语言 :ref:`gnu-extensions <cn_gnu-extensions>` 的许多扩展,当然,它们许多都在内核中使用。 + +对于一些体系结构,有一些使用 :ref:`clang <cn_clang>` 和 :ref:`icc <cn_icc>` 编译内核 +的支持,尽管在编写此文档时还没有完成,仍需要第三方补丁。 + +属性 +---- + +在整个内核中使用的一个常见扩展是属性(attributes) :ref:`gcc-attribute-syntax <cn_gcc-attribute-syntax>` +属性允许将实现定义的语义引入语言实体(如变量、函数或类型),而无需对语言进行 +重大的语法更改(例如添加新关键字) :ref:`n2049 <cn_n2049>` + +在某些情况下,属性是可选的(即不支持这些属性的编译器仍然应该生成正确的代码, +即使其速度较慢或执行的编译时检查/诊断次数不够) + +内核定义了伪关键字(例如, ``pure`` ),而不是直接使用GNU属性语法(例如, +``__attribute__((__pure__))`` ),以检测可以使用哪些关键字和/或缩短代码, 具体 +请参阅 ``include/linux/compiler_attributes.h`` + +.. _cn_c-language: + +c-language + http://www.open-std.org/jtc1/sc22/wg14/www/standards + +.. _cn_gcc: + +gcc + https://gcc.gnu.org + +.. _cn_clang: + +clang + https://clang.llvm.org + +.. _cn_icc: + +icc + https://software.intel.com/en-us/c-compilers + +.. _cn_gcc-c-dialect-options: + +c-dialect-options + https://gcc.gnu.org/onlinedocs/gcc/C-Dialect-Options.html + +.. _cn_gnu-extensions: + +gnu-extensions + https://gcc.gnu.org/onlinedocs/gcc/C-Extensions.html + +.. _cn_gcc-attribute-syntax: + +gcc-attribute-syntax + https://gcc.gnu.org/onlinedocs/gcc/Attribute-Syntax.html + +.. _cn_n2049: + +n2049 + http://www.open-std.org/jtc1/sc22/wg14/www/docs/n2049.pdf diff --git a/Documentation/translations/zh_CN/process/stable-api-nonsense.rst b/Documentation/translations/zh_CN/process/stable-api-nonsense.rst new file mode 100644 index 000000000..b4ddb6e88 --- /dev/null +++ b/Documentation/translations/zh_CN/process/stable-api-nonsense.rst @@ -0,0 +1,155 @@ +.. _cn_stable_api_nonsense: + +.. include:: ../disclaimer-zh_CN.rst + +:Original: :ref:`Documentation/process/stable-api-nonsense.rst + <stable_api_nonsense>` + +译者:: + + 中文版维护者: 钟宇 TripleX Chung <xxx.phy@gmail.com> + 中文版翻译者: 钟宇 TripleX Chung <xxx.phy@gmail.com> + 中文版校译者: 李阳 Li Yang <leoyang.li@nxp.com> + +Linux 内核驱动接口 +================== + +写作本文档的目的,是为了解释为什么Linux既没有二进制内核接口,也没有稳定 +的内核接口。这里所说的内核接口,是指内核里的接口,而不是内核和用户空间 +的接口。内核到用户空间的接口,是提供给应用程序使用的系统调用,系统调用 +在历史上几乎没有过变化,将来也不会有变化。我有一些老应用程序是在0.9版本 +或者更早版本的内核上编译的,在使用2.6版本内核的Linux发布上依然用得很好 +。用户和应用程序作者可以将这个接口看成是稳定的。 + + +执行纲要 +-------- + +你也许以为自己想要稳定的内核接口,但是你不清楚你要的实际上不是它。你需 +要的其实是稳定的驱动程序,而你只有将驱动程序放到公版内核的源代码树里, +才有可能达到这个目的。而且这样做还有很多其它好处,正是因为这些好处使得 +Linux能成为强壮,稳定,成熟的操作系统,这也是你最开始选择Linux的原因。 + + +入门 +----- + +只有那些写驱动程序的“怪人”才会担心内核接口的改变,对广大用户来说,既 +看不到内核接口,也不需要去关心它。 + +首先,我不打算讨论关于任何非GPL许可的内核驱动的法律问题,这些非GPL许可 +的驱动程序包括不公开源代码,隐藏源代码,二进制或者是用源代码包装,或者 +是其它任何形式的不能以GPL许可公开源代码的驱动程序。如果有法律问题,请咨 +询律师,我只是一个程序员,所以我只打算探讨技术问题(不是小看法律问题, +法律问题很实际,并且需要一直关注)。 + +既然只谈技术问题,我们就有了下面两个主题:二进制内核接口和稳定的内核源 +代码接口。这两个问题是互相关联的,让我们先解决掉二进制接口的问题。 + + +二进制内核接口 +-------------- +假如我们有一个稳定的内核源代码接口,那么自然而然的,我们就拥有了稳定的 +二进制接口,是这样的吗?错。让我们看看关于Linux内核的几点事实: + + - 取决于所用的C编译器的版本,不同的内核数据结构里的结构体的对齐方 + 式会有差别,代码中不同函数的表现形式也不一样(函数是不是被inline + 编译取决于编译器行为)。不同的函数的表现形式并不重要,但是数据 + 结构内部的对齐方式很关键。 + + - 取决于内核的配置选项,不同的选项会让内核的很多东西发生改变: + + - 同一个结构体可能包含不同的成员变量 + - 有的函数可能根本不会被实现(比如编译的时候没有选择SMP支持 + 一些锁函数就会被定义成空函数)。 + - 内核使用的内存会以不同的方式对齐,这取决于不同的内核配置选 + 项。 + + - Linux可以在很多的不同体系结构的处理器上运行。在某个体系结构上编 + 译好的二进制驱动程序,不可能在另外一个体系结构上正确的运行。 + +对于一个特定的内核,满足这些条件并不难,使用同一个C编译器和同样的内核配 +置选项来编译驱动程序模块就可以了。这对于给一个特定Linux发布的特定版本提 +供驱动程序,是完全可以满足需求的。但是如果你要给不同发布的不同版本都发 +布一个驱动程序,就需要在每个发布上用不同的内核设置参数都编译一次内核, +这简直跟噩梦一样。而且还要注意到,每个Linux发布还提供不同的Linux内核, +这些内核都针对不同的硬件类型进行了优化(有很多种不同的处理器,还有不同 +的内核设置选项)。所以每发布一次驱动程序,都需要提供很多不同版本的内核 +模块。 + +相信我,如果你真的要采取这种发布方式,一定会慢慢疯掉,我很久以前就有过 +深刻的教训... + + +稳定的内核源代码接口 +-------------------- + +如果有人不将他的内核驱动程序,放入公版内核的源代码树,而又想让驱动程序 +一直保持在最新的内核中可用,那么这个话题将会变得没完没了。 +内核开发是持续而且快节奏的,从来都不会慢下来。内核开发人员在当前接口中 +找到bug,或者找到更好的实现方式。一旦发现这些,他们就很快会去修改当前的 +接口。修改接口意味着,函数名可能会改变,结构体可能被扩充或者删减,函数 +的参数也可能发生改变。一旦接口被修改,内核中使用这些接口的地方需要同时 +修正,这样才能保证所有的东西继续工作。 + +举一个例子,内核的USB驱动程序接口在USB子系统的整个生命周期中,至少经历 +了三次重写。这些重写解决以下问题: + + - 把数据流从同步模式改成非同步模式,这个改动减少了一些驱动程序的 + 复杂度,提高了所有USB驱动程序的吞吐率,这样几乎所有的USB设备都 + 能以最大速率工作了。 + - 修改了USB核心代码中为USB驱动分配数据包内存的方式,所有的驱动都 + 需要提供更多的参数给USB核心,以修正了很多已经被记录在案的死锁。 + +这和一些封闭源代码的操作系统形成鲜明的对比,在那些操作系统上,不得不额 +外的维护旧的USB接口。这导致了一个可能性,新的开发者依然会不小心使用旧的 +接口,以不恰当的方式编写代码,进而影响到操作系统的稳定性。 +在上面的例子中,所有的开发者都同意这些重要的改动,在这样的情况下修改代 +价很低。如果Linux保持一个稳定的内核源代码接口,那么就得创建一个新的接口 +;旧的,有问题的接口必须一直维护,给Linux USB开发者带来额外的工作。既然 +所有的Linux USB驱动的作者都是利用自己的时间工作,那么要求他们去做毫无意 +义的免费额外工作,是不可能的。 +安全问题对Linux来说十分重要。一个安全问题被发现,就会在短时间内得到修 +正。在很多情况下,这将导致Linux内核中的一些接口被重写,以从根本上避免安 +全问题。一旦接口被重写,所有使用这些接口的驱动程序,必须同时得到修正, +以确定安全问题已经得到修复并且不可能在未来还有同样的安全问题。如果内核 +内部接口不允许改变,那么就不可能修复这样的安全问题,也不可能确认这样的 +安全问题以后不会发生。 +开发者一直在清理内核接口。如果一个接口没有人在使用了,它就会被删除。这 +样可以确保内核尽可能的小,而且所有潜在的接口都会得到尽可能完整的测试 +(没有人使用的接口是不可能得到良好的测试的)。 + + +要做什么 +-------- + +如果你写了一个Linux内核驱动,但是它还不在Linux源代码树里,作为一个开发 +者,你应该怎么做?为每个发布的每个版本提供一个二进制驱动,那简直是一个 +噩梦,要跟上永远处于变化之中的内核接口,也是一件辛苦活。 +很简单,让你的驱动进入内核源代码树(要记得我们在谈论的是以GPL许可发行 +的驱动,如果你的代码不符合GPL,那么祝你好运,你只能自己解决这个问题了, +你这个吸血鬼<把Andrew和Linus对吸血鬼的定义链接到这里>)。当你的代码加入 +公版内核源代码树之后,如果一个内核接口改变,你的驱动会直接被修改接口的 +那个人修改。保证你的驱动永远都可以编译通过,并且一直工作,你几乎不需要 +做什么事情。 + +把驱动放到内核源代码树里会有很多的好处: + + - 驱动的质量会提升,而维护成本(对原始作者来说)会下降。 + - 其他人会给驱动添加新特性。 + - 其他人会找到驱动中的bug并修复。 + - 其他人会在驱动中找到性能优化的机会。 + - 当外部的接口的改变需要修改驱动程序的时候,其他人会修改驱动程序 + - 不需要联系任何发行商,这个驱动会自动的随着所有的Linux发布一起发 + 布。 + +和别的操作系统相比,Linux为更多不同的设备提供现成的驱动,而且能在更多不 +同体系结构的处理器上支持这些设备。这个经过考验的开发模式,必然是错不了 +的 :) + +感谢 +---- +感谢 Randy Dunlap, Andrew Morton, David Brownell, Hanna Linder, +Robert Love, and Nishanth Aravamudan 对于本文档早期版本的评审和建议。 + +英文版维护者: Greg Kroah-Hartman <greg@kroah.com> diff --git a/Documentation/translations/zh_CN/process/stable-kernel-rules.rst b/Documentation/translations/zh_CN/process/stable-kernel-rules.rst new file mode 100644 index 000000000..fba361f2d --- /dev/null +++ b/Documentation/translations/zh_CN/process/stable-kernel-rules.rst @@ -0,0 +1,64 @@ +.. _cn_stable_kernel_rules: + +.. include:: ../disclaimer-zh_CN.rst + +:Original: :ref:`Documentation/process/stable-kernel-rules.rst <stable_kernel_rules>` + +如果想评论或更新本文的内容,请直接联系原文档的维护者。如果你使用英文 +交流有困难的话,也可以向中文版维护者求助。如果本翻译更新不及时或者翻 +译存在问题,请联系中文版维护者:: + + 中文版维护者: 钟宇 TripleX Chung <xxx.phy@gmail.com> + 中文版翻译者: 钟宇 TripleX Chung <xxx.phy@gmail.com> + 中文版校译者: + - 李阳 Li Yang <leoyang.li@nxp.com> + - Kangkai Yin <e12051@motorola.com> + +所有你想知道的事情 - 关于linux稳定版发布 +======================================== + +关于Linux 2.6稳定版发布,所有你想知道的事情。 + +关于哪些类型的补丁可以被接收进入稳定版代码树,哪些不可以的规则: +---------------------------------------------------------------- + + - 必须是显而易见的正确,并且经过测试的。 + - 连同上下文,不能大于100行。 + - 必须只修正一件事情。 + - 必须修正了一个给大家带来麻烦的真正的bug(不是“这也许是一个问题...” + 那样的东西)。 + - 必须修正带来如下后果的问题:编译错误(对被标记为CONFIG_BROKEN的例外), + 内核崩溃,挂起,数据损坏,真正的安全问题,或者一些类似“哦,这不 + 好”的问题。简短的说,就是一些致命的问题。 + - 没有“理论上的竞争条件”,除非能给出竞争条件如何被利用的解释。 + - 不能存在任何的“琐碎的”修正(拼写修正,去掉多余空格之类的)。 + - 必须被相关子系统的维护者接受。 + - 必须遵循Documentation/translations/zh_CN/process/submitting-patches.rst里的规则。 + +向稳定版代码树提交补丁的过程: +------------------------------ + + - 在确认了补丁符合以上的规则后,将补丁发送到stable@vger.kernel.org。 + - 如果补丁被接受到队列里,发送者会收到一个ACK回复,如果没有被接受,收 + 到的是NAK回复。回复需要几天的时间,这取决于开发者的时间安排。 + - 被接受的补丁会被加到稳定版本队列里,等待其他开发者的审查。 + - 安全方面的补丁不要发到这个列表,应该发送到security@kernel.org。 + +审查周期: +---------- + + - 当稳定版的维护者决定开始一个审查周期,补丁将被发送到审查委员会,以 + 及被补丁影响的领域的维护者(除非提交者就是该领域的维护者)并且抄送 + 到linux-kernel邮件列表。 + - 审查委员会有48小时的时间,用来决定给该补丁回复ACK还是NAK。 + - 如果委员会中有成员拒绝这个补丁,或者linux-kernel列表上有人反对这个 + 补丁,并提出维护者和审查委员会之前没有意识到的问题,补丁会从队列中 + 丢弃。 + - 在审查周期结束的时候,那些得到ACK回应的补丁将会被加入到最新的稳定版 + 发布中,一个新的稳定版发布就此产生。 + - 安全性补丁将从内核安全小组那里直接接收到稳定版代码树中,而不是通过 + 通常的审查周期。请联系内核安全小组以获得关于这个过程的更多细节。 + +审查委员会: +------------ + - 由一些自愿承担这项任务的内核开发者,和几个非志愿的组成。 diff --git a/Documentation/translations/zh_CN/process/submit-checklist.rst b/Documentation/translations/zh_CN/process/submit-checklist.rst new file mode 100644 index 000000000..3d6ee21c7 --- /dev/null +++ b/Documentation/translations/zh_CN/process/submit-checklist.rst @@ -0,0 +1,111 @@ +.. include:: ../disclaimer-zh_CN.rst + +:Original: Documentation/process/submit-checklist.rst +:Translator: + - Alex Shi <alexs@kernel.org> + - Wu XiangCheng <bobwxc@email.cn> + +.. _cn_submitchecklist: + +Linux内核补丁提交检查单 +~~~~~~~~~~~~~~~~~~~~~~~ + +如果开发人员希望看到他们的内核补丁提交更快地被接受,那么他们应该做一些基本 +的事情。 + +这些都是在 Documentation/translations/zh_CN/process/submitting-patches.rst +和其他有关提交Linux内核补丁的文档中提供的。 + +1) 如果使用工具,则包括定义/声明该工具的文件。不要依赖其他头文件来引入您使用 + 的头文件。 + +2) 干净的编译: + + a) 使用合适的 ``CONFIG`` 选项 ``=y``、``=m`` 和 ``=n`` 。没有 ``gcc`` + 警告/错误,没有链接器警告/错误。 + + b) 通过 ``allnoconfig`` 、 ``allmodconfig`` + + c) 使用 ``O=builddir`` 时可以成功编译 + + d) 任何 Doucmentation/ 下的变更都能成功构建且不引入新警告/错误。 + 用 ``make htmldocs`` 或 ``make pdfdocs`` 检验构建情况并修复问题。 + +3) 通过使用本地交叉编译工具或其他一些构建设施在多个CPU体系结构上构建。 + +4) PPC64是一种很好的交叉编译检查体系结构,因为它倾向于对64位的数使用无符号 + 长整型。 + +5) 按 Documentation/translations/zh_CN/process/coding-style.rst 所述检查您的 + 补丁是否为常规样式。在提交之前使用补丁样式检查器 ``scripts/checkpatch.pl`` + 检查是否有轻微的冲突。您应该能够处理您的补丁中存在的所有 + 违规行为。 + +6) 任何新的或修改过的 ``CONFIG`` 选项都不应搞乱配置菜单,并默认为关闭,除非 + 它们符合 ``Documentation/kbuild/kconfig-language.rst`` 菜单属性:默认值中 + 记录的例外条件。 + +7) 所有新的 ``kconfig`` 选项都有帮助文本。 + +8) 已仔细审查了相关的 ``Kconfig`` 组合。这很难用测试来纠正——脑力在这里是有 + 回报的。 + +9) 通过 sparse 清查。 + (参见 Documentation/translations/zh_CN/dev-tools/sparse.rst ) + +10) 使用 ``make checkstack`` 和 ``make namespacecheck`` 并修复他们发现的任何 + 问题。 + + .. note:: + + ``checkstack`` 并不会明确指出问题,但是任何一个在堆栈上使用超过512 + 字节的函数都可以进行更改。 + +11) 包括 :ref:`kernel-doc <kernel_doc_zh>` 内核文档以记录全局内核API。(静态 + 函数不需要,但也可以。)使用 ``make htmldocs`` 或 ``make pdfdocs`` 检查 + :ref:`kernel-doc <kernel_doc_zh>` 并修复任何问题。 + +12) 通过以下选项同时启用的测试: ``CONFIG_PREEMPT``, ``CONFIG_DEBUG_PREEMPT``, + ``CONFIG_DEBUG_SLAB``, ``CONFIG_DEBUG_PAGEALLOC``, ``CONFIG_DEBUG_MUTEXES``, + ``CONFIG_DEBUG_SPINLOCK``, ``CONFIG_DEBUG_ATOMIC_SLEEP``, + ``CONFIG_PROVE_RCU`` 和 ``CONFIG_DEBUG_OBJECTS_RCU_HEAD`` 。 + +13) 在 ``CONFIG_SMP``, ``CONFIG_PREEMPT`` 开启和关闭的情况下都进行构建和运行 + 时测试。 + +14) 所有代码路径都已在启用所有死锁检测(lockdep)功能的情况下运行。 + +15) 所有新的 ``/proc`` 条目都记录在 ``Documentation/`` + +16) 所有新的内核引导参数都记录在 + Documentation/admin-guide/kernel-parameters.rst 中。 + +17) 所有新的模块参数都记录在 ``MODULE_PARM_DESC()`` + +18) 所有新的用户空间接口都记录在 ``Documentation/ABI/`` 中。有关详细信息, + 请参阅 ``Documentation/ABI/README`` 。更改用户空间接口的补丁应该抄送 + linux-api@vger.kernel.org。 + +19) 已通过至少注入slab和page分配失败进行检查。请参阅 ``Documentation/fault-injection/`` 。 + 如果新代码是实质性的,那么添加子系统特定的故障注入可能是合适的。 + +20) 新添加的代码已经用 ``gcc -W`` 编译(使用 ``make EXTRA-CFLAGS=-W`` )。这 + 将产生大量噪声,但对于查找诸如“警告:有符号和无符号之间的比较”之类的错误 + 很有用。 + +21) 在它被合并到-mm补丁集中之后进行测试,以确保它仍然与所有其他排队的补丁以 + 及VM、VFS和其他子系统中的各种更改一起工作。 + +22) 所有内存屏障(例如 ``barrier()``, ``rmb()``, ``wmb()`` )都需要源代码注 + 释来解释它们正在执行的操作及其原因的逻辑。 + +23) 如果补丁添加了任何ioctl,那么也要更新 + ``Documentation/userspace-api/ioctl/ioctl-number.rst`` 。 + +24) 如果修改后的源代码依赖或使用与以下 ``Kconfig`` 符号相关的任何内核API或 + 功能,则在禁用相关 ``Kconfig`` 符号和/或 ``=m`` (如果该选项可用)的情况 + 下测试以下多个构建[并非所有这些都同时存在,只是它们的各种/随机组合]: + + ``CONFIG_SMP``, ``CONFIG_SYSFS``, ``CONFIG_PROC_FS``, ``CONFIG_INPUT``, + ``CONFIG_PCI``, ``CONFIG_BLOCK``, ``CONFIG_PM``, ``CONFIG_MAGIC_SYSRQ``, + ``CONFIG_NET``, ``CONFIG_INET=n`` (但是最后一个需要 ``CONFIG_NET=y`` )。 diff --git a/Documentation/translations/zh_CN/process/submitting-patches.rst b/Documentation/translations/zh_CN/process/submitting-patches.rst new file mode 100644 index 000000000..f8978f020 --- /dev/null +++ b/Documentation/translations/zh_CN/process/submitting-patches.rst @@ -0,0 +1,657 @@ +.. SPDX-License-Identifier: GPL-2.0-or-later + +.. include:: ../disclaimer-zh_CN.rst + +.. _cn_submittingpatches: + +:Original: Documentation/process/submitting-patches.rst + +:译者: + - 钟宇 TripleX Chung <xxx.phy@gmail.com> + - 时奎亮 Alex Shi <alexs@kernel.org> + - 吴想成 Wu XiangCheng <bobwxc@email.cn> + +:校译: + - 李阳 Li Yang <leoyang.li@nxp.com> + - 王聪 Wang Cong <xiyou.wangcong@gmail.com> + + +提交补丁:如何让你的改动进入内核 +================================ + +对于想要将改动提交到 Linux 内核的个人或者公司来说,如果不熟悉“规矩”, +提交的流程会让人畏惧。本文档包含了一系列建议,可以大大提高你 +的改动被接受的机会. + +本文档以较为简洁的行文给出了大量建议。关于内核开发流程如何进行的详细信息, +参见: Documentation/translations/zh_CN/process/development-process.rst 。 +Documentation/translations/zh_CN/process/submit-checklist.rst 给出了一系列 +提交补丁之前要检查的事项。设备树相关的补丁,请参阅 +Documentation/devicetree/bindings/submitting-patches.rst 。 + +本文档假设您正在使用 ``git`` 准备你的补丁。如果您不熟悉 ``git`` ,最好学习 +如何使用它,这将使您作为内核开发人员的生活变得更加轻松。 + +部分子系统和维护人员的树有一些关于其工作流程和要求的额外信息,请参阅 +Documentation/process/maintainer-handbooks.rst 。 + +获取当前源码树 +-------------- + +如果您手头没有当前内核源代码的存储库,请使用 ``git`` 获取一份。您需要先获取 +主线存储库,它可以通过以下命令拉取:: + + git clone git://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git + +但是,请注意,您可能不想直接针对主线树进行开发。大多数子系统维护人员运 +行自己的树,并希望看到针对这些树准备的补丁。请参见MAINTAINERS文件中子系 +统的 **T:** 项以查找该树,或者直接询问维护者该树是否未在其中列出。 + +.. _zh_describe_changes: + +描述你的改动 +------------ + +描述你的问题。无论您的补丁是一行错误修复还是5000行新功能,都必须有一个潜在 +的问题激励您完成这项工作。说服审阅者相信有一个问题值得解决,让他们读完第一段 +后就能明白这一点。 + +描述用户可见的影响。直接崩溃和锁定是相当有说服力的,但并不是所有的错误都那么 +明目张胆。即使在代码审阅期间发现了这个问题,也要描述一下您认为它可能对用户产 +生的影响。请记住,大多数Linux安装运行的内核来自二级稳定树或特定于供应商/产品 +的树,只从上游精选特定的补丁,因此请包含任何可以帮助您将更改定位到下游的内容: +触发的场景、DMESG的摘录、崩溃描述、性能回归、延迟尖峰、锁定等。 + +质量优化和权衡。如果您声称在性能、内存消耗、堆栈占用空间或二进制大小方面有所 +改进,请包括支持它们的数据。但也要描述不明显的成本。优化通常不是零成本的,而是 +在CPU、内存和可读性之间进行权衡;或者,做探索性的工作,在不同的工作负载之间进 +行权衡。请描述优化的预期缺点,以便审阅者可以权衡成本和收益。 + +提出问题之后,就要详细地描述一下您实际在做的技术细节。对于审阅者来说,用简练的 +英语描述代码的变化是很重要的,以验证代码的行为是否符合您的意图。 + +如果您将补丁描述写成“标准格式”,可以很容易地作为“提交日志”放入Linux的源代 +码管理系统 ``git`` 中,那么维护人员将非常感谢您。 +参见 :ref:`zh_the_canonical_patch_format` 。 + +每个补丁只解决一个问题。如果你的描述开始变长,这就表明你可能需要拆分你的补丁。 +请见 :ref:`zh_split_changes` 。 + +提交或重新提交补丁或补丁系列时,请包括完整的补丁说明和理由。不要 +只说这是补丁(系列)的第几版。不要期望子系统维护人员引用更早的补丁版本或引用 +URL来查找补丁描述并将其放入补丁中。也就是说,补丁(系列)及其描述应该是独立的。 +这对维护人员和审阅者都有好处。一些审阅者可能甚至没有收到补丁的早期版本。 + +用祈使句描述你的变更,例如“make xyzzy do frotz”而不是“[This patch]make +xyzzy do frotz”或“[I]changed xyzzy to do frotz”,就好像你在命令代码库改变 +它的行为一样。 + +如果您想要引用一个特定的提交,不要只引用提交的SHA-1 ID。还请包括提交的一行 +摘要,以便于审阅者了解它是关于什么的。例如:: + + Commit e21d2170f36602ae2708 ("video: remove unnecessary + platform_set_drvdata()") removed the unnecessary + platform_set_drvdata(), but left the variable "dev" unused, + delete it. + +您还应该确保至少使用前12位SHA-1 ID。内核存储库包含 *许多* 对象,使较短的ID +发生冲突的可能性很大。记住,即使现在不会与您的六个字符ID发生冲突,这种情况 +也可能在五年后改变。 + +如果该变更的相关讨论或背景信息可以在网上查阅,请加上“Link:”标签指向它。例如 +你的补丁修复了一个缺陷,需要添加一个带有URL的标签指向邮件列表存档或缺陷跟踪器 +的相关报告;如果该补丁是由一些早先邮件列表讨论或网络上的记录引起的,请指向它。 + +当链接到邮件列表存档时,请首选lore.kernel.org邮件存档服务。用邮件中的 +``Message-ID`` 头(去掉尖括号)可以创建链接URL。例如:: + + Link: https://lore.kernel.org/r/30th.anniversary.repost@klaava.Helsinki.FI/ + +请检查该链接以确保可用且指向正确的邮件。 + +不过,在没有外部资源的情况下,也要尽量让你的解释可理解。除了提供邮件列表存档或 +缺陷的URL之外,还要需要总结该补丁的相关讨论要点。 + +如果补丁修复了特定提交中的错误,例如使用 ``git bisct`` 发现了一个问题,请使用 +带有前12个字符SHA-1 ID的“Fixes:”标签和单行摘要。为了简化解析脚本,不要将该 +标签拆分为多行,标签不受“75列换行”规则的限制。例如:: + + Fixes: 54a4f0239f2e ("KVM: MMU: make kvm_mmu_zap_page() return the number of pages it actually freed") + +下列 ``git config`` 设置可以让 ``git log``, ``git show`` 增加上述风格的显示格式:: + + [core] + abbrev = 12 + [pretty] + fixes = Fixes: %h (\"%s\") + +使用示例:: + + $ git log -1 --pretty=fixes 54a4f0239f2e + Fixes: 54a4f0239f2e ("KVM: MMU: make kvm_mmu_zap_page() return the number of pages it actually freed") + +.. _zh_split_changes: + +拆分你的改动 +------------ + +将每个 **逻辑更改** 拆分成一个单独的补丁。 + +例如,如果你的改动里同时有bug修正和性能优化,那么把这些改动拆分到两个或 +者更多的补丁文件中。如果你的改动包含对API的修改,并且增加了一个使用该新API +的驱动,那么把这些修改分成两个补丁。 + +另一方面,如果你将一个单独的改动做成多个补丁文件,那么将它们合并成一个 +单独的补丁文件。这样一个逻辑上单独的改动只被包含在一个补丁文件里。 + +需要记住的一点是,每个补丁的更改都应易于理解,以便审阅者验证。每个补丁都应该 +对其价值进行阐述。 + +如果有一个补丁依赖另外一个补丁来完成它的改动,那没问题。直接在你的补 +丁描述里指出 **“这个补丁依赖某补丁”** 就好了。 + +在将您的更改划分为一系列补丁时,要特别注意确保内核在应用系列中的每个补丁之后 +都能正常构建和运行。使用 ``git bisect`` 来追踪问题的开发者可能会在任何地方分 +割你的补丁系列;如果你在中间引入错误,他们不会感谢你。 + +如果你不能将补丁系列浓缩得更小,那么每次大约发送出15个补丁,然后等待审阅 +和集成。 + +检查你的更改风格 +---------------- + +检查您的补丁是否违反了基本样式规定,详细信息参见 +Documentation/translations/zh_CN/process/coding-style.rst +中找到。如果不这样做,只会浪费审阅者的时间,并且会导致你的补丁被拒绝,甚至 +可能没有被阅读。 + +一个重要的例外是在将代码从一个文件移动到另一个文件时——在这种情况下,您不应 +该在移动代码的同一个补丁中修改移动的代码。这清楚地描述了移动代码和您的更改 +的行为。这大大有助于审阅实际差异,并允许工具更好地跟踪代码本身的历史。 + +在提交之前,使用补丁样式检查程序检查补丁(scripts/check patch.pl)。不过, +请注意,样式检查程序应该被视为一个指南,而不是作为人类判断的替代品。如果您 +的代码看起来更好,但有违规行为,那么最好别管它。 + +检查者报告三个级别: + + - ERROR:很可能出错的事情 + - WARNING:需要仔细审阅的事项 + - CHECK:需要思考的事情 + +您应该能够判断您的补丁中存在的所有违规行为。 + +选择补丁收件人 +-------------- + +您应该总是知会任何补丁相应代码的子系统维护人员;查看 +维护人员文件和源代码修订历史记录,以了解这些维护人员是谁。脚本 +scripts/get_maintainer.pl在这个步骤中非常有用。如果您找不到正在工作的子系统 +的维护人员,那么Andrew Morton(akpm@linux-foundation.org)将充当最后的维护 +人员。 + +您通常还应该选择至少一个邮件列表来接收补丁集的副本。linux-kernel@vger.kernel.org +是所有补丁的默认列表,但是这个列表的流量已经导致了许多开发人员不再看它。 +在MAINTAINERS文件中查找子系统特定的列表;您的补丁可能会在那里得到更多的关注。 +不过,请不要发送垃圾邮件到无关的列表。 + +许多与内核相关的列表托管在vger.kernel.org上;您可以在 +http://vger.kernel.org/vger-lists.html 上找到它们的列表。不过,也有与内核相关 +的列表托管在其他地方。 + +不要一次发送超过15个补丁到vger邮件列表!!!! + +Linus Torvalds是决定改动能否进入 Linux 内核的最终裁决者。他的邮件地址是 +torvalds@linux-foundation.org 。他收到的邮件很多,所以一般来说最好 **别** +给他发邮件。 + +如果您有修复可利用安全漏洞的补丁,请将该补丁发送到 security@kernel.org 。对于 +严重的bug,可以考虑短期禁令以允许分销商(有时间)向用户发布补丁;在这种情况下, +显然不应将补丁发送到任何公共列表。 +参见 Documentation/translations/zh_CN/admin-guide/security-bugs.rst 。 + +修复已发布内核中严重错误的补丁程序应该抄送给稳定版维护人员,方法是把以下列行 +放进补丁的签准区(注意,不是电子邮件收件人):: + + Cc: stable@vger.kernel.org + +除了本文件之外,您还应该阅读 +Documentation/translations/zh_CN/process/stable-kernel-rules.rst 。 + +如果更改影响到用户侧内核接口,请向手册页维护人员(如维护人员文件中所列)发送 +手册页补丁,或至少发送更改通知,以便一些信息进入手册页。还应将用户空间API +更改抄送到 linux-api@vger.kernel.org 。 + + +不要MIME编码,不要链接,不要压缩,不要附件,只要纯文本 +------------------------------------------------------ + +Linus 和其他的内核开发者需要阅读和评论你提交的改动。对于内核开发者来说 +,可以“引用”你的改动很重要,使用一般的邮件工具,他们就可以在你的 +代码的任何位置添加评论。 + +因为这个原因,所有的提交的补丁都是邮件中“内嵌”的。最简单(和推荐)的方法就 +是使用 ``git send-email`` 。https://git-send-email.io 有 ``git send-email`` +的交互式教程。 + +如果你选择不用 ``git send-email`` : + +.. warning:: + + 如果你使用剪切-粘贴你的补丁,小心你的编辑器的自动换行功能破坏你的补丁 + +不要将补丁作为MIME编码的附件,不管是否压缩。很多流行的邮件软件不 +是任何时候都将MIME编码的附件当作纯文本发送的,这会使得别人无法在你的 +代码中加评论。另外,MIME编码的附件会让Linus多花一点时间来处理,这就 +降低了你的改动被接受的可能性。 + +例外:如果你的邮路损坏了补丁,那么有人可能会要求你使用MIME重新发送补丁。 + +请参阅 Documentation/translations/zh_CN/process/email-clients.rst +以获取有关配置电子邮件客户端以使其不受影响地发送补丁的提示。 + +回复审阅意见 +------------ + +你的补丁几乎肯定会得到审阅者对补丁改进方法的评论(以回复邮件的形式)。您必须 +对这些评论作出回应;让补丁被忽略的一个好办法就是忽略审阅者的意见。直接回复邮 +件来回应意见即可。不会导致代码更改的意见或问题几乎肯定会带来注释或变更日志的 +改变,以便下一个审阅者更好地了解正在发生的事情。 + +一定要告诉审阅者你在做什么改变,并感谢他们的时间。代码审阅是一个累人且耗时的 +过程,审阅者有时会变得暴躁。即使在这种情况下,也要礼貌地回应并解决他们指出的 +问题。当发送下一版时,在封面邮件或独立补丁里加上 ``patch changelog`` 说明与 +前一版本的不同之处(参见 :ref:`zh_the_canonical_patch_format` )。 + +.. _zh_resend_reminders: + +不要泄气或不耐烦 +---------------- + +提交更改后,请耐心等待。审阅者是大忙人,可能无法立即审阅您的补丁。 + +曾几何时,补丁曾在没收到评论的情况下消失在虚空中,但现在开发过程应该更加顺利了。 +您应该在一周左右的时间内收到评论;如果没有收到评论,请确保您已将补丁发送 +到正确的位置。在重新提交或联系审阅者之前至少等待一周——在诸如合并窗口之类的 +繁忙时间可能更长。 + +在等了几个星期后,用带RESEND的主题重发补丁也是可以的:: + + [PATCH Vx RESEND] sub/sys: Condensed patch summary + +当你发布补丁(系列)修改版的时候,不要加上“RESEND”——“RESEND”只适用于重 +新提交之前未经修改的补丁(系列)。 + +主题中包含 PATCH +---------------- + +由于到Linus和linux-kernel的电子邮件流量很高,通常会在主题行前面加上[PATCH] +前缀。这使Linus和其他内核开发人员更容易将补丁与其他电子邮件讨论区分开。 + +``git send-email`` 会自动为你加上。 + +签署你的作品——开发者来源认证 +------------------------------ + +为了加强对谁做了何事的追踪,尤其是对那些透过好几层维护者才最终到达的补丁,我 +们在通过邮件发送的补丁上引入了“签署(sign-off)”流程。 + +“签署”是在补丁注释最后的一行简单文字,认证你编写了它或者其他 +人有权力将它作为开放源代码的补丁传递。规则很简单:如果你能认证如下信息: + +开发者来源认证 1.1 +^^^^^^^^^^^^^^^^^^ + +对于本项目的贡献,我认证如下信息: + + (a) 这些贡献是完全或者部分的由我创建,我有权利以文件中指出 + 的开放源代码许可证提交它;或者 + + (b) 这些贡献基于以前的工作,据我所知,这些以前的工作受恰当的开放 + 源代码许可证保护,而且,根据文件中指出的许可证,我有权提交修改后的贡献, + 无论是完全还是部分由我创造,这些贡献都使用同一个开放源代码许可证 + (除非我被允许用其它的许可证);或者 + + (c) 这些贡献由认证(a),(b)或者(c)的人直接提供给我,而 + 且我没有修改它。 + + (d) 我理解并同意这个项目和贡献是公开的,贡献的记录(包括我 + 一起提交的个人记录,包括sign-off)被永久维护并且可以和这个项目 + 或者开放源代码的许可证同步地再发行。 + +那么加入这样一行:: + + Signed-off-by: Random J Developer <random@developer.example.org> + +使用你的真名(抱歉,不能使用假名或者匿名。)如果使用 ``git commit -s`` 的话 +将会自动完成。撤销也应当包含“Signed-off-by”, ``git revert -s`` 会帮你搞定。 + +有些人会在最后加上额外的标签。现在这些东西会被忽略,但是你可以这样做,来标记 +公司内部的过程,或者只是指出关于签署的一些特殊细节。 + +作者签署之后的任何其他签署(Signed-off-by:'s)均来自处理和传递补丁的人员,但 +未参与其开发。签署链应当反映补丁传播到维护者并最终传播到Linus所经过的 **真实** +路径,首个签署指明单个作者的主要作者身份。 + +何时使用Acked-by:,CC:,和Co-Developed by: +------------------------------------------ + +Singed-off-by: 标签表示签名者参与了补丁的开发,或者他/她在补丁的传递路径中。 + +如果一个人没有直接参与补丁的准备或处理,但希望表示并记录他们对补丁的批准/赞成, +那么他们可以要求在补丁的变更日志中添加一个Acked-by:。 + +Acked-by: 通常由受影响代码的维护者使用,当该维护者既没有贡献也没有转发补丁时。 + +Acked-by: 不像签署那样正式。这是一个记录,确认人至少审阅了补丁,并表示接受。 +因此,补丁合并有时会手动将Acker的“Yep,looks good to me”转换为 Acked-By:(但 +请注意,通常最好要求一个明确的Ack)。 + +Acked-by:不一定表示对整个补丁的确认。例如,如果一个补丁影响多个子系统,并且 +有一个来自某个子系统维护者的Acked-By:,那么这通常表示只确认影响维护者代码的部 +分。这里应该仔细判断。如有疑问,应参考邮件列表存档中的原始讨论。 + +如果某人本应有机会对补丁进行评论,但没有提供此类评论,您可以选择在补丁中添加 +``Cc:`` 这是唯一可以在没有被该人明确同意的情况下添加的标签——但它应该表明 +这个人是在补丁上抄送的。此标签记录了讨论中包含的潜在利益相关方。 + +Co-developed-by: 声明补丁是由多个开发人员共同创建的;当几个人在一个补丁上工 +作时,它用于给出共同作者(除了From:所给出的作者之外)。因为Co-developed-by: +表示作者身份,所以每个Co-developed-by:必须紧跟在相关合作作者的签署之后。标准 +签署程序要求Singed-off-by:标签的顺序应尽可能反映补丁的时间历史,无论作者是通 +过From:还是Co-developed-by:表明。值得注意的是,最后一个Singed-off-by:必须是 +提交补丁的开发人员。 + +注意,如果From:作者也是电子邮件标题的From:行中列出的人,则From:标签是可选的。 + +被From:作者提交的补丁示例:: + + <changelog> + + Co-developed-by: First Co-Author <first@coauthor.example.org> + Signed-off-by: First Co-Author <first@coauthor.example.org> + Co-developed-by: Second Co-Author <second@coauthor.example.org> + Signed-off-by: Second Co-Author <second@coauthor.example.org> + Signed-off-by: From Author <from@author.example.org> + +被合作开发者提交的补丁示例:: + + From: From Author <from@author.example.org> + + <changelog> + + Co-developed-by: Random Co-Author <random@coauthor.example.org> + Signed-off-by: Random Co-Author <random@coauthor.example.org> + Signed-off-by: From Author <from@author.example.org> + Co-developed-by: Submitting Co-Author <sub@coauthor.example.org> + Signed-off-by: Submitting Co-Author <sub@coauthor.example.org> + + +使用Reported-by:、Tested-by:、Reviewed-by:、Suggested-by:和Fixes: +----------------------------------------------------------------- + +Reported-by: 给那些发现错误并报告错误的人致谢,它希望激励他们在将来再次帮助 +我们。请注意,如果bug是以私有方式报告的,那么在使用Reported-by标签之前,请 +先请求许可。此标签是为Bug设计的;请不要将其用于感谢功能请求。 + +Tested-by: 标签表示补丁已由指定的人(在某些环境中)成功测试。这个标签通知 +维护人员已经执行了一些测试,为将来的补丁提供了一种定位测试人员的方法,并彰显测试人员的功劳。 + +Reviewed-by:根据审阅者的监督声明,表明该补丁已被审阅并被认为是可接受的: + + +审阅者的监督声明 +^^^^^^^^^^^^^^^^ + +通过提供我的Reviewed-by:标签,我声明: + + (a) 我已经对这个补丁进行了一次技术审阅,以评估它是否适合被包含到 + 主线内核中。 + + (b) 与补丁相关的任何问题、顾虑或问题都已反馈给提交者。我对提交者对 + 我的评论的回应感到满意。 + + (c) 虽然这一提交可能仍可被改进,但我相信,此时,(1)对内核 + 进行了有价值的修改,(2)没有包含争论中涉及的已知问题。 + + (d) 虽然我已经审阅了补丁并认为它是健全的,但我不会(除非另有明确 + 说明)作出任何保证或担保它会在任何给定情况下实现其规定的目的 + 或正常运行。 + +Reviewed-by是一种观点声明,即补丁是对内核的适当修改,没有任何遗留的严重技术 +问题。任何感兴趣的审阅者(完成工作的人)都可以为一个补丁提供一个Reviewed-by +标签。此标签用于向审阅者提供致谢,并通知维护者补丁的审阅进度。 +当Reviewed-by:标签由已知了解主题区域并执行彻底检查的审阅者提供时,通常会增加 +补丁进入内核的可能性。 + +一旦从测试人员或审阅者的“Tested-by”和“Reviewed-by”标签出现在邮件列表中, +作者应在发送下一个版本时将其添加到适用的补丁中。但是,如果补丁在以下版本中发 +生了实质性更改,这些标签可能不再适用,因此应该删除。通常,在补丁更改日志中 +(在 ``---`` 分隔符之后)应该提到删除某人的测试者或审阅者标签。 + +Suggested-by: 表示补丁的想法是由指定的人提出的,并确保将此想法归功于指定的 +人。请注意,未经许可,不得添加此标签,特别是如果该想法未在公共论坛上发布。 +也就是说,如果我们勤快地致谢创意提供者,他们将受到鼓舞,很有希望在未来再次 +帮助我们。 + +Fixes: 指示补丁修复了之前提交的一个问题。它可以便于确定错误的来源,这有助于 +检查错误修复。这个标签还帮助稳定内核团队确定应该接收修复的稳定内核版本。这是 +指示补丁修复的错误的首选方法。请参阅 :ref:`zh_describe_changes` 了解更多信息。 + +.. note:: + + 附加Fixes:标签不会改变稳定内核规则流程,也不改变所有稳定版补丁抄送 + stable@vger.kernel.org的要求。有关更多信息,请阅读 + Documentation/translations/zh_CN/process/stable-kernel-rules.rst 。 + +.. _zh_the_canonical_patch_format: + +标准补丁格式 +------------ + +本节描述如何格式化补丁本身。请注意,如果您的补丁存储在 ``Git`` 存储库中,则 +可以使用 ``git format-patch`` 进行正确的补丁格式化。但是,这些工具无法创建 +必要的文本,因此请务必阅读下面的说明。 + +标准的补丁标题行是:: + + Subject: [PATCH 001/123] 子系统:一句话概述 + +标准补丁的信体包含如下部分: + + - 一个 ``from`` 行指出补丁作者。后跟空行(仅当发送补丁的人不是作者时才需要)。 + + - 说明文字,每行最长75列,这将被复制到永久变更日志来描述这个补丁。 + + - 一个空行 + + - 上述的 ``Signed-off-by:`` 行,也将出现在更改日志中。 + + - 只包含 ``---`` 的标记线。 + + - 任何其他不适合放在变更日志的注释。 + + - 实际补丁( ``diff`` 输出)。 + +标题行的格式,使得对标题行按字母序排序非常的容易——很多邮件客户端都 +可以支持——因为序列号是用零填充的,所以按数字排序和按字母排序是一样的。 + +邮件标题中的“子系统”标识哪个内核子系统将被打补丁。 + +邮件标题中的“一句话概述”扼要的描述邮件中的补丁。“一句话概述” +不应该是一个文件名。对于一个补丁系列(“补丁系列”指一系列的多个相关补 +丁),不要对每个补丁都使用同样的“一句话概述”。 + +记住邮件的“一句话概述”会成为该补丁的全局唯一标识。它会进入 ``git`` +的改动记录里。然后“一句话概述”会被用在开发者的讨论里,用来指代这个补 +丁。用户将希望通过搜索引擎搜索“一句话概述”来找到那些讨论这个补丁的文 +章。当人们在两三个月后使用诸如 ``gitk`` 或 ``git log --oneline`` 之类 +的工具查看数千个补丁时,也会很快看到它。 + +出于这些原因,概述必须不超过70-75个字符,并且必须描述补丁的更改以及为 +什么需要补丁。既要简洁又要描述性很有挑战性,但写得好的概述应该这样。 + +概述的前缀可以用方括号括起来:“Subject: [PATCH <tag>...] <概述>”。标记 +不被视为概述的一部分,而是描述应该如何处理补丁。如果补丁的多个版本已发 +送出来以响应评审(即“v1,v2,v3”)则必须包含版本号,或包含“RFC”以指示 +评审请求。如果一个补丁系列中有四个补丁,那么各个补丁可以这样编号:1/4、2/4、 +3/4、4/4。这可以确保开发人员了解补丁应用的顺序,且 +已经查看或应用了补丁系列中的所有补丁。 + +一些标题的例子:: + + Subject: [patch 2/5] ext2: improve scalability of bitmap searching + Subject: [PATCHv2 001/207] x86: fix eflags tracking + +``From`` 行是信体里的最上面一行,具有如下格式:: + + From: Patch Author <author@example.com> + +``From`` 行指明在永久改动日志里,谁会被确认为作者。如果没有 ``From`` 行,那 +么邮件头里的 ``From:`` 行会被用来决定改动日志中的作者。 + +说明文字将会被提交到永久的源代码改动日志里,因此应针对那些早已经不记得和这 +个补丁相关的讨论细节的读者。包括补丁处理的故障症状(内核日志消息、oops消息 +等),这对于可能正在搜索提交日志以查找适用补丁的人特别有用。文本应该写得如 +此详细,以便在数周、数月甚至数年后阅读时,能够为读者提供所需的细节信息,以 +掌握创建补丁的 **原因** 。 + +如果一个补丁修复了一个编译失败,那么可能不需要包含 *所有* 编译失败; +只要足够让搜索补丁的人能够找到它就行了。与概述一样,既要简洁又要描述性。 + +``---`` 标记行对于补丁处理工具要找到哪里是改动日志信息的结束,是不可缺少 +的。 + +对于 ``---`` 标记之后的额外注解,一个好的用途就是用来写 ``diffstat`` ,用来显 +示修改了什么文件和每个文件都增加和删除了多少行。 ``diffstat`` 对于比较大的补 +丁特别有用。 +使用 ``diffstat`` 的选项 ``-p 1 -w 70`` 这样文件名就会从内核源代码树的目录开始 +,不会占用太宽的空间(很容易适合80列的宽度,也许会有一些缩进。) +( ``git`` 默认会生成合适的diffstat。) + +其余那些只适用于当时或者与维护者相关的注解,不合适放到永久的改动日志里的,也 +应该放这里。较好的例子就是 ``补丁更改记录`` ,记录了v1和v2版本补丁之间的差异。 + +请将此信息放在将变更日志与补丁的其余部分分隔开的 ``---`` 行 **之后** 。版本 +信息不是提交到git树的变更日志的一部分。只是供审阅人员使用的附加信息。如果将 +其放置在提交标记上方,则需要手动交互才能将其删除。如果它位于分隔线以下,则在 +应用补丁时会自动剥离:: + + <commit message> + ... + Signed-off-by: Author <author@mail> + --- + V2 -> V3: Removed redundant helper function + V1 -> V2: Cleaned up coding style and addressed review comments + + path/to/file | 5+++-- + ... + +在后面的参考资料中能看到正确补丁格式的更多细节。 + +.. _zh_backtraces: + +提交消息中的回溯(Backtraces) +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +回溯有助于记录导致问题的调用链。然而,并非所有回溯都有帮助。例如,早期引导调 +用链是独特而明显的。而逐字复制完整的dmesg输出则会增加时间戳、模块列表、寄存 +器和堆栈转储等分散注意力的信息。 + +因此,最有用的回溯应该从转储中提取相关信息,以更容易集中在真实问题上。下面是 +一个剪裁良好的回溯示例:: + + unchecked MSR access error: WRMSR to 0xd51 (tried to write 0x0000000000000064) + at rIP: 0xffffffffae059994 (native_write_msr+0x4/0x20) + Call Trace: + mba_wrmsr + update_domains + rdtgroup_mkdir + +.. _zh_explicit_in_reply_to: + +明确回复邮件头(In-Reply-To) +----------------------------- + +手动添加回复补丁的的邮件头(In-Reply_To:)是有用的(例如,使用 ``git send-email`` ), +可以将补丁与以前的相关讨论关联起来,例如,将bug补丁链接到电子邮件和bug报告。 +但是,对于多补丁系列,最好避免在回复时使用链接到该系列的旧版本。这样, +补丁的多个版本就不会成为电子邮件客户端中无法管理的引用树。如果链接有用, +可以使用 https://lore.kernel.org/ 重定向器(例如,在封面电子邮件文本中) +链接到补丁系列的早期版本。 + +给出基础树信息 +-------------- + +当其他开发人员收到您的补丁并开始审阅时,知道应该将您的工作放到代码树历史记录 +中的什么位置通常很有用。这对于自动化持续集成流水(CI)特别有用,这些流水线试 +图运行一系列测试,以便在维护人员开始审阅之前确定提交的质量。 + +如果您使用 ``git format-patch`` 生成补丁,则可以通过 ``--base`` 标志在提交中 +自动包含基础树信息。使用此选项最简单、最方便的方法是配合主题分支:: + + $ git checkout -t -b my-topical-branch master + Branch 'my-topical-branch' set up to track local branch 'master'. + Switched to a new branch 'my-topical-branch' + + [perform your edits and commits] + + $ git format-patch --base=auto --cover-letter -o outgoing/ master + outgoing/0000-cover-letter.patch + outgoing/0001-First-Commit.patch + outgoing/... + +当你编辑 ``outgoing/0000-cover-letter.patch`` 时,您会注意到在它的最底部有一 +行 ``base-commit:`` 尾注,它为审阅者和CI工具提供了足够的信息以正确执行 +``git am`` 而不必担心冲突:: + + $ git checkout -b patch-review [base-commit-id] + Switched to a new branch 'patch-review' + $ git am patches.mbox + Applying: First Commit + Applying: ... + +有关此选项的更多信息,请参阅 ``man git-format-patch`` 。 + +.. note:: + + ``--base`` 功能是在2.9.0版git中引入的。 + +如果您不使用git格式化补丁,仍然可以包含相同的 ``base-commit`` 尾注,以指示您 +的工作所基于的树的提交哈希。你应该在封面邮件或系列的第一个补丁中添加它,它应 +该放在 ``---`` 行的下面或所有其他内容之后,即只在你的电子邮件签名之前。 + +参考文献 +-------- + +Andrew Morton,“完美的补丁”(tpp) + <https://www.ozlabs.org/~akpm/stuff/tpp.txt> + +Jeff Garzik,“Linux内核补丁提交格式” + <https://web.archive.org/web/20180829112450/http://linux.yyz.us/patch-format.html> + +Greg Kroah-Hartman,“如何惹恼内核子系统维护人员” + <http://www.kroah.com/log/linux/maintainer.html> + + <http://www.kroah.com/log/linux/maintainer-02.html> + + <http://www.kroah.com/log/linux/maintainer-03.html> + + <http://www.kroah.com/log/linux/maintainer-04.html> + + <http://www.kroah.com/log/linux/maintainer-05.html> + + <http://www.kroah.com/log/linux/maintainer-06.html> + +不!!!别再发巨型补丁炸弹给linux-kernel@vger.kernel.org的人们了! + <https://lore.kernel.org/r/20050711.125305.08322243.davem@davemloft.net> + +内核 Documentation/translations/zh_CN/process/coding-style.rst + +Linus Torvalds关于标准补丁格式的邮件 + <https://lore.kernel.org/r/Pine.LNX.4.58.0504071023190.28951@ppc970.osdl.org> + +Andi Kleen,“提交补丁之路” + 一些帮助合入困难或有争议的变更的策略。 + + http://halobates.de/on-submitting-patches.pdf diff --git a/Documentation/translations/zh_CN/process/volatile-considered-harmful.rst b/Documentation/translations/zh_CN/process/volatile-considered-harmful.rst new file mode 100644 index 000000000..ded3b5d0c --- /dev/null +++ b/Documentation/translations/zh_CN/process/volatile-considered-harmful.rst @@ -0,0 +1,106 @@ +.. _cn_volatile_considered_harmful: + +.. include:: ../disclaimer-zh_CN.rst + +:Original: :ref:`Documentation/process/volatile-considered-harmful.rst + <volatile_considered_harmful>` + +如果想评论或更新本文的内容,请直接联系原文档的维护者。如果你使用英文 +交流有困难的话,也可以向中文版维护者求助。如果本翻译更新不及时或者翻 +译存在问题,请联系中文版维护者:: + + 英文版维护者: Jonathan Corbet <corbet@lwn.net> + 中文版维护者: 伍鹏 Bryan Wu <bryan.wu@analog.com> + 中文版翻译者: 伍鹏 Bryan Wu <bryan.wu@analog.com> + 中文版校译者: 张汉辉 Eugene Teo <eugeneteo@kernel.sg> + 杨瑞 Dave Young <hidave.darkstar@gmail.com> + 时奎亮 Alex Shi <alex.shi@linux.alibaba.com> + +为什么不应该使用“volatile”类型 +============================== + +C程序员通常认为volatile表示某个变量可以在当前执行的线程之外被改变;因此,在内核 +中用到共享数据结构时,常常会有C程序员喜欢使用volatile这类变量。换句话说,他们经 +常会把volatile类型看成某种简易的原子变量,当然它们不是。在内核中使用volatile几 +乎总是错误的;本文档将解释为什么这样。 + +理解volatile的关键是知道它的目的是用来消除优化,实际上很少有人真正需要这样的应 +用。在内核中,程序员必须防止意外的并发访问破坏共享的数据结构,这其实是一个完全 +不同的任务。用来防止意外并发访问的保护措施,可以更加高效的避免大多数优化相关的 +问题。 + +像volatile一样,内核提供了很多原语来保证并发访问时的数据安全(自旋锁, 互斥量,内 +存屏障等等),同样可以防止意外的优化。如果可以正确使用这些内核原语,那么就没有 +必要再使用volatile。如果仍然必须使用volatile,那么几乎可以肯定在代码的某处有一 +个bug。在正确设计的内核代码中,volatile能带来的仅仅是使事情变慢。 + +思考一下这段典型的内核代码:: + + spin_lock(&the_lock); + do_something_on(&shared_data); + do_something_else_with(&shared_data); + spin_unlock(&the_lock); + +如果所有的代码都遵循加锁规则,当持有the_lock的时候,不可能意外的改变shared_data的 +值。任何可能访问该数据的其他代码都会在这个锁上等待。自旋锁原语跟内存屏障一样—— 它 +们显式的用来书写成这样 —— 意味着数据访问不会跨越它们而被优化。所以本来编译器认为 +它知道在shared_data里面将有什么,但是因为spin_lock()调用跟内存屏障一样,会强制编 +译器忘记它所知道的一切。那么在访问这些数据时不会有优化的问题。 + +如果shared_data被声名为volatile,锁操作将仍然是必须的。就算我们知道没有其他人正在 +使用它,编译器也将被阻止优化对临界区内shared_data的访问。在锁有效的同时, +shared_data不是volatile的。在处理共享数据的时候,适当的锁操作可以不再需要 +volatile —— 并且是有潜在危害的。 + +volatile的存储类型最初是为那些内存映射的I/O寄存器而定义。在内核里,寄存器访问也应 +该被锁保护,但是人们也不希望编译器“优化”临界区内的寄存器访问。内核里I/O的内存访问 +是通过访问函数完成的;不赞成通过指针对I/O内存的直接访问,并且不是在所有体系架构上 +都能工作。那些访问函数正是为了防止意外优化而写的,因此,再说一次,volatile类型不 +是必需的。 + +另一种引起用户可能使用volatile的情况是当处理器正忙着等待一个变量的值。正确执行一 +个忙等待的方法是:: + + while (my_variable != what_i_want) + cpu_relax(); + +cpu_relax()调用会降低CPU的能量消耗或者让位于超线程双处理器;它也作为内存屏障一样出 +现,所以,再一次,volatile不是必需的。当然,忙等待一开始就是一种反常规的做法。 + +在内核中,一些稀少的情况下volatile仍然是有意义的: + + - 在一些体系架构的系统上,允许直接的I/0内存访问,那么前面提到的访问函数可以使用 + volatile。基本上,每一个访问函数调用它自己都是一个小的临界区域并且保证了按照 + 程序员期望的那样发生访问操作。 + + - 某些会改变内存的内联汇编代码虽然没有什么其他明显的附作用,但是有被GCC删除的可 + 能性。在汇编声明中加上volatile关键字可以防止这种删除操作。 + + - Jiffies变量是一种特殊情况,虽然每次引用它的时候都可以有不同的值,但读jiffies + 变量时不需要任何特殊的加锁保护。所以jiffies变量可以使用volatile,但是不赞成 + 其他跟jiffies相同类型变量使用volatile。Jiffies被认为是一种“愚蠢的遗留物" + (Linus的话)因为解决这个问题比保持现状要麻烦的多。 + + - 由于某些I/0设备可能会修改连续一致的内存,所以有时,指向连续一致内存的数据结构 + 的指针需要正确的使用volatile。网络适配器使用的环状缓存区正是这类情形的一个例 + 子,其中适配器用改变指针来表示哪些描述符已经处理过了。 + +对于大多代码,上述几种可以使用volatile的情况都不适用。所以,使用volatile是一种 +bug并且需要对这样的代码额外仔细检查。那些试图使用volatile的开发人员需要退一步想想 +他们真正想实现的是什么。 + +非常欢迎删除volatile变量的补丁 - 只要证明这些补丁完整的考虑了并发问题。 + +注释 +---- + +[1] https://lwn.net/Articles/233481/ +[2] https://lwn.net/Articles/233482/ + +致谢 +---- + +最初由Randy Dunlap推动并作初步研究 +由Jonathan Corbet撰写 +参考Satyam Sharma,Johannes Stezenbach,Jesper Juhl,Heikki Orsila, +H. Peter Anvin,Philipp Hahn和Stefan Richter的意见改善了本档。 diff --git a/Documentation/translations/zh_CN/riscv/boot-image-header.rst b/Documentation/translations/zh_CN/riscv/boot-image-header.rst new file mode 100644 index 000000000..0234c28a7 --- /dev/null +++ b/Documentation/translations/zh_CN/riscv/boot-image-header.rst @@ -0,0 +1,69 @@ +.. include:: ../disclaimer-zh_CN.rst + +:Original: Documentation/riscv/boot-image-header.rst + +:翻译: + + 司延腾 Yanteng Si <siyanteng@loongson.cn> + +.. _cn_boot-image-header.rst: + +========================== +RISC-V Linux启动镜像文件头 +========================== + +:Author: Atish Patra <atish.patra@wdc.com> +:Date: 20 May 2019 + +此文档仅描述RISC-V Linux 启动文件头的详情。 + +TODO: + 写一个完整的启动指南。 + +在解压后的Linux内核镜像中存在以下64字节的文件头:: + + u32 code0; /* Executable code */ + u32 code1; /* Executable code */ + u64 text_offset; /* Image load offset, little endian */ + u64 image_size; /* Effective Image size, little endian */ + u64 flags; /* kernel flags, little endian */ + u32 version; /* Version of this header */ + u32 res1 = 0; /* Reserved */ + u64 res2 = 0; /* Reserved */ + u64 magic = 0x5643534952; /* Magic number, little endian, "RISCV" */ + u32 magic2 = 0x05435352; /* Magic number 2, little endian, "RSC\x05" */ + u32 res3; /* Reserved for PE COFF offset */ + +这种头格式与PE/COFF文件头兼容,并在很大程度上受到ARM64文件头的启发。因此,ARM64 +和RISC-V文件头可以在未来合并为一个共同的头。 + +注意 +==== + +- 将来也可以复用这个文件头,用来对RISC-V的EFI桩提供支持。为了使内核镜像如同一个 + EFI应用程序一样加载,EFI规范中规定在内核镜像的开始需要PE/COFF镜像文件头。为了 + 支持EFI桩,应该用“MZ”魔术字符替换掉code0,并且res3(偏移量未0x3c)应指向PE/COFF + 文件头的其余部分. + +- 表示文件头版本号的Drop-bit位域 + + ========== ========== + Bits 0:15 次要 版本 + Bits 16:31 主要 版本 + ========== ========== + + 这保持了新旧版本之间的兼容性。 + 当前版本被定义为0.2。 + +- 从版本0.2开始,结构体成员“magic”就已经被弃用,在之后的版本中,可能会移除掉它。 + 最初,该成员应该与ARM64头的“magic”成员匹配,但遗憾的是并没有。 + “magic2”成员代替“magic”成员与ARM64头相匹配。 + +- 在当前的文件头,标志位域只剩下了一个位。 + + ===== ============================== + Bit 0 内核字节序。1 if BE, 0 if LE. + ===== ============================== + +- 对于引导加载程序加载内核映像来说,image_size成员对引导加载程序而言是必须的,否 + 则将引导失败。 diff --git a/Documentation/translations/zh_CN/riscv/index.rst b/Documentation/translations/zh_CN/riscv/index.rst new file mode 100644 index 000000000..131e405aa --- /dev/null +++ b/Documentation/translations/zh_CN/riscv/index.rst @@ -0,0 +1,30 @@ +.. SPDX-License-Identifier: GPL-2.0 + +.. include:: ../disclaimer-zh_CN.rst + +:Original: Documentation/riscv/index.rst + +:翻译: + + 司延腾 Yanteng Si <siyanteng@loongson.cn> + +.. _cn_riscv_index: + +=============== +RISC-V 体系结构 +=============== + +.. toctree:: + :maxdepth: 1 + + boot-image-header + vm-layout + patch-acceptance + + +.. only:: subproject and html + + 目录 + ==== + + * :ref:`genindex` diff --git a/Documentation/translations/zh_CN/riscv/patch-acceptance.rst b/Documentation/translations/zh_CN/riscv/patch-acceptance.rst new file mode 100644 index 000000000..d180d2471 --- /dev/null +++ b/Documentation/translations/zh_CN/riscv/patch-acceptance.rst @@ -0,0 +1,33 @@ +.. SPDX-License-Identifier: GPL-2.0 + +.. include:: ../disclaimer-zh_CN.rst + +:Original: Documentation/riscv/patch-acceptance.rst + +:翻译: + + 司延腾 Yanteng Si <siyanteng@loongson.cn> + +.. _cn_riscv_patch-acceptance: + +arch/riscv 开发者维护指南 +========================= + +概述 +---- +RISC-V指令集体系结构是公开开发的: +正在进行的草案可供所有人查看和测试实现。新模块或者扩展草案可能会在开发过程中发 +生更改---有时以不兼容的方式对以前的草案进行更改。这种灵活性可能会给RISC-V Linux +维护者带来挑战。Linux开发过程更喜欢经过良好检查和测试的代码,而不是试验代码。我 +们希望推广同样的规则到即将被内核合并的RISC-V相关代码。 + +附加的提交检查单 +---------------- +我们仅接受相关标准已经被RISC-V基金会标准为“已批准”或“已冻结”的扩展或模块的补丁。 +(开发者当然可以维护自己的Linux内核树,其中包含所需代码扩展草案的代码。) + +此外,RISC-V规范允许爱好者创建自己的自定义扩展。这些自定义拓展不需要通过RISC-V +基金会的任何审核或批准。为了避免将爱好者一些特别的RISC-V拓展添加进内核代码带来 +的维护复杂性和对性能的潜在影响,我们将只接受RISC-V基金会正式冻结或批准的的扩展 +补丁。(开发者当然可以维护自己的Linux内核树,其中包含他们想要的任何自定义扩展 +的代码。) diff --git a/Documentation/translations/zh_CN/riscv/vm-layout.rst b/Documentation/translations/zh_CN/riscv/vm-layout.rst new file mode 100644 index 000000000..91884e2df --- /dev/null +++ b/Documentation/translations/zh_CN/riscv/vm-layout.rst @@ -0,0 +1,104 @@ +.. SPDX-License-Identifier: GPL-2.0 +.. include:: ../disclaimer-zh_CN.rst + +:Original: Documentation/riscv/vm-layout.rst + +:翻译: + + 司延腾 Yanteng Si <siyanteng@loongson.cn> + Binbin Zhou <zhoubinbin@loongson.cn> + +============================ +RISC-V Linux上的虚拟内存布局 +============================ + +:作者: Alexandre Ghiti <alex@ghiti.fr> +:日期: 12 February 2021 + +这份文件描述了RISC-V Linux内核使用的虚拟内存布局。 + +32位 RISC-V Linux 内核 +====================== + +RISC-V Linux Kernel SV32 +------------------------ + +TODO + +64位 RISC-V Linux 内核 +====================== + +RISC-V特权架构文档指出,64位地址 "必须使第63-48位值都等于第47位,否则将发生缺页异常。":这将虚 +拟地址空间分成两半,中间有一个非常大的洞,下半部分是用户空间所在的地方,上半部分是RISC-V Linux +内核所在的地方。 + +RISC-V Linux Kernel SV39 +------------------------ + +:: + + ======================================================================================================================== + 开始地址 | 偏移 | 结束地址 | 大小 | 虚拟内存区域描述 + ======================================================================================================================== + | | | | + 0000000000000000 | 0 | 0000003fffffffff | 256 GB | 用户空间虚拟内存,每个内存管理器不同 + __________________|____________|__________________|_________|___________________________________________________________ + | | | | + 0000004000000000 | +256 GB | ffffffbfffffffff | ~16M TB | ... 巨大的、几乎64位宽的直到内核映射的-256GB地方 + | | | | 开始偏移的非经典虚拟内存地址空洞。 + | | | | + __________________|____________|__________________|_________|___________________________________________________________ + | + | 内核空间的虚拟内存,在所有进程之间共享: + ____________________________________________________________|___________________________________________________________ + | | | | + ffffffc6fee00000 | -228 GB | ffffffc6feffffff | 2 MB | fixmap + ffffffc6ff000000 | -228 GB | ffffffc6ffffffff | 16 MB | PCI io + ffffffc700000000 | -228 GB | ffffffc7ffffffff | 4 GB | vmemmap + ffffffc800000000 | -224 GB | ffffffd7ffffffff | 64 GB | vmalloc/ioremap space + ffffffd800000000 | -160 GB | fffffff6ffffffff | 124 GB | 直接映射所有物理内存 + fffffff700000000 | -36 GB | fffffffeffffffff | 32 GB | kasan + __________________|____________|__________________|_________|____________________________________________________________ + | + | + ____________________________________________________________|____________________________________________________________ + | | | | + ffffffff00000000 | -4 GB | ffffffff7fffffff | 2 GB | modules, BPF + ffffffff80000000 | -2 GB | ffffffffffffffff | 2 GB | kernel + __________________|____________|__________________|_________|____________________________________________________________ + + +RISC-V Linux Kernel SV48 +------------------------ + +:: + + ======================================================================================================================== + 开始地址 | 偏移 | 结束地址 | 大小 | 虚拟内存区域描述 + ======================================================================================================================== + | | | | + 0000000000000000 | 0 | 00007fffffffffff | 128 TB | 用户空间虚拟内存,每个内存管理器不同 + __________________|____________|__________________|_________|___________________________________________________________ + | | | | + 0000800000000000 | +128 TB | ffff7fffffffffff | ~16M TB | ... 巨大的、几乎64位宽的直到内核映射的-128TB地方 + | | | | 开始偏移的非经典虚拟内存地址空洞。 + | | | | + __________________|____________|__________________|_________|___________________________________________________________ + | + | 内核空间的虚拟内存,在所有进程之间共享: + ____________________________________________________________|___________________________________________________________ + | | | | + ffff8d7ffee00000 | -114.5 TB | ffff8d7ffeffffff | 2 MB | fixmap + ffff8d7fff000000 | -114.5 TB | ffff8d7fffffffff | 16 MB | PCI io + ffff8d8000000000 | -114.5 TB | ffff8f7fffffffff | 2 TB | vmemmap + ffff8f8000000000 | -112.5 TB | ffffaf7fffffffff | 32 TB | vmalloc/ioremap space + ffffaf8000000000 | -80.5 TB | ffffef7fffffffff | 64 TB | 直接映射所有物理内存 + ffffef8000000000 | -16.5 TB | fffffffeffffffff | 16.5 TB | kasan + __________________|____________|__________________|_________|____________________________________________________________ + | + | 从此处开始,与39-bit布局相同: + ____________________________________________________________|____________________________________________________________ + | | | | + ffffffff00000000 | -4 GB | ffffffff7fffffff | 2 GB | modules, BPF + ffffffff80000000 | -2 GB | ffffffffffffffff | 2 GB | kernel + __________________|____________|__________________|_________|____________________________________________________________ diff --git a/Documentation/translations/zh_CN/scheduler/completion.rst b/Documentation/translations/zh_CN/scheduler/completion.rst new file mode 100644 index 000000000..bc8218514 --- /dev/null +++ b/Documentation/translations/zh_CN/scheduler/completion.rst @@ -0,0 +1,256 @@ +.. include:: ../disclaimer-zh_CN.rst + +:Original: Documentation/scheduler/completion.rst + +:翻译: + + 司延腾 Yanteng Si <siyanteng@loongson.cn> + +:校译: + + 唐艺舟 Tang Yizhou <tangyeechou@gmail.com> + +======================================= +完成 - "等待完成" 屏障应用程序接口(API) +======================================= + +简介: +----- + +如果你有一个或多个线程必须等待某些内核活动达到某个点或某个特定的状态,完成可以为这 +个问题提供一个无竞争的解决方案。从语义上讲,它们有点像pthread_barrier(),并且使 +用的案例类似 + +完成是一种代码同步机制,它比任何滥用锁/信号量和忙等待循环的行为都要好。当你想用yield() +或一些古怪的msleep(1)循环来允许其它代码继续运行时,你可能想用wait_for_completion*() +调用和completion()来代替。 + +使用“完成”的好处是,它们有一个良好定义、聚焦的目标,这不仅使得我们很容易理解代码的意图, +而且它们也会生成更高效的代码,因为所有线程都可以继续执行,直到真正需要结果的时刻。而且等 +待和信号都高效的使用了低层调度器的睡眠/唤醒设施。 + +完成是建立在Linux调度器的等待队列和唤醒基础设施之上的。等待队列中的线程所等待的 +事件被简化为 ``struct completion`` 中的一个简单标志,被恰如其名地称为‘done’。 + +由于完成与调度有关,代码可以在kernel/sched/completion.c中找到。 + + +用法: +----- + +使用完成需要三个主要部分: + + - 'struct completion' 同步对象的初始化 + - 通过调用wait_for_completion()的一个变体来实现等待部分。 + - 通过调用complete()或complete_all()实现发信端。 + +也有一些辅助函数用于检查完成的状态。请注意,虽然必须先做初始化,但等待和信号部分可以 +按任何时间顺序出现。也就是说,一个线程在另一个线程检查是否需要等待它之前,已经将一个 +完成标记为 "done",这是完全正常的。 + +要使用完成API,你需要#include <linux/completion.h>并创建一个静态或动态的 +``struct completion`` 类型的变量,它只有两个字段:: + + struct completion { + unsigned int done; + wait_queue_head_t wait; + }; + +结构体提供了->wait等待队列来放置任务进行等待(如果有的话),以及->done完成标志来表明它 +是否完成。 + +完成的命名应当与正在被同步的事件名一致。一个好的例子是:: + + wait_for_completion(&early_console_added); + + complete(&early_console_added); + +好的、直观的命名(一如既往地)有助于代码的可读性。将一个完成命名为 ``complete`` +是没有帮助的,除非其目的是超级明显的... + + +初始化完成: +----------- + +动态分配的完成对象最好被嵌入到数据结构中,以确保在函数/驱动的生命周期内存活,以防 +止与异步complete()调用发生竞争。 + +在使用wait_for_completion()的_timeout()或_killable()/_interruptible()变体 +时应特别小心,因为必须保证在所有相关活动(complete()或reinit_completion())发生 +之前不会发生内存解除分配,即使这些等待函数由于超时或信号触发而过早返回。 + +动态分配的完成对象的初始化是通过调用init_completion()来完成的:: + + init_completion(&dynamic_object->done); + +在这个调用中,我们初始化 waitqueue 并将 ->done 设置为 0,即“not completed”或 +“not done”。 + +重新初始化函数reinit_completion(),只是将->done字段重置为0(“not done”),而 +不触及等待队列。这个函数的调用者必须确保没有任何令人讨厌的wait_for_completion() +调用在并行进行。 + +在同一个完成对象上调用init_completion()两次很可能是一个bug,因为它将队列重新初始 +化为一个空队列,已排队的任务可能会“丢失”--在这种情况下使用reinit_completion(),但 +要注意其他竞争。 + +对于静态声明和初始化,可以使用宏。 + +对于文件范围内的静态(或全局)声明,你可以使用 DECLARE_COMPLETION():: + + static DECLARE_COMPLETION(setup_done); + DECLARE_COMPLETION(setup_done); + +注意,在这种情况下,完成在启动时(或模块加载时)被初始化为“not done”,不需要调用 +init_completion()。 + +当完成被声明为一个函数中的局部变量时,那么应该总是明确地使用 +DECLARE_COMPLETION_ONSTACK()来初始化,这不仅仅是为了让lockdep正确运行,也是明确表 +名它有限的使用范围是有意为之并被仔细考虑的:: + + DECLARE_COMPLETION_ONSTACK(setup_done) + +请注意,当使用完成对象作为局部变量时,你必须敏锐地意识到函数堆栈的短暂生命期:在所有 +活动(如等待的线程)停止并且完成对象完全未被使用之前,函数不得返回到调用上下文。 + +再次强调这一点:特别是在使用一些具有更复杂结果的等待API变体时,比如超时或信号 +(_timeout(), _killable()和_interruptible())变体,等待可能会提前完成,而对象可 +能仍在被其他线程使用 - 从wait_on_completion*()调用者函数的返回会取消分配函数栈,如 +果complete()在其它某线程中完成调用,会引起微小的数据损坏。简单的测试可能不会触发这 +些类型的竞争。 + +如果不确定的话,使用动态分配的完成对象, 最好是嵌入到其它一些生命周期长的对象中,长到 +超过使用完成对象的任何辅助线程的生命周期,或者有一个锁或其他同步机制来确保complete() +不会在一个被释放的对象中调用。 + +在堆栈上单纯地调用DECLARE_COMPLETION()会触发一个lockdep警告。 + +等待完成: +--------- + +对于一个线程来说,要等待一些并发活动的完成,它要在初始化的完成结构体上调用 +wait_for_completion():: + + void wait_for_completion(struct completion *done) + +一个典型的使用场景是:: + + CPU#1 CPU#2 + + struct completion setup_done; + + init_completion(&setup_done); + initialize_work(...,&setup_done,...); + + /* run non-dependent code */ /* do setup */ + + wait_for_completion(&setup_done); complete(setup_done); + +这并不意味着调用wait_for_completion()和complete()有任何特定的时间顺序--如果调 +用complete()发生在调用wait_for_completion()之前,那么等待方将立即继续执行,因为 +所有的依赖都得到了满足;如果没有,它将阻塞,直到complete()发出完成的信号。 + +注意,wait_for_completion()是在调用spin_lock_irq()/spin_unlock_irq(),所以 +只有当你知道中断被启用时才能安全地调用它。从IRQs-off的原子上下文中调用它将导致难以检 +测的错误的中断启用。 + +默认行为是不带超时的等待,并将任务标记为“UNINTERRUPTIBLE”状态。wait_for_completion() +及其变体只有在进程上下文中才是安全的(因为它们可以休眠),但在原子上下文、中断上下文、IRQ +被禁用或抢占被禁用的情况下是不安全的--关于在原子/中断上下文中处理完成的问题,还请看下面的 +try_wait_for_completion()。 + +由于wait_for_completion()的所有变体都可能(很明显)阻塞很长时间,这取决于它们所等 +待的活动的性质,所以在大多数情况下,你可能不想在持有mutex锁的情况下调用它。 + + +wait_for_completion*()可用的变体: +--------------------------------- + +下面的变体都会返回状态,在大多数(/所有)情况下都应该检查这个状态--在故意不检查状态的情 +况下,你可能要做一个说明(例如,见arch/arm/kernel/smp.c:__cpu_up())。 + +一个常见的问题是不准确的返回类型赋值,所以要注意将返回值赋值给适当类型的变量。 + +检查返回值的具体含义也可能被发现是相当不准确的,例如,像这样的构造:: + + if (!wait_for_completion_interruptible_timeout(...)) + +...会在成功完成和中断的情况下执行相同的代码路径--这可能不是你想要的结果:: + + int wait_for_completion_interruptible(struct completion *done) + +这个函数在任务等待时标记为TASK_INTERRUPTIBLE。如果在等待期间收到信号,它将返回 +-ERESTARTSYS;否则为0:: + + unsigned long wait_for_completion_timeout(struct completion *done, unsigned long timeout) + +该任务被标记为TASK_UNINTERRUPTIBLE,并将最多超时等待“timeout”个jiffies。如果超时发生,则 +返回0,否则返回剩余的时间(但至少是1)。 + +超时最好用msecs_to_jiffies()或usecs_to_jiffies()计算,以使代码在很大程度上不受 +HZ的影响。 + +如果返回的超时值被故意忽略,那么注释应该解释原因 +(例如,见drivers/mfd/wm8350-core.c wm8350_read_auxadc():: + + long wait_for_completion_interruptible_timeout(struct completion *done, unsigned long timeout) + +这个函数传递一个以jiffies为单位的超时,并将任务标记为TASK_INTERRUPTIBLE。如果收到 +信号,则返回-ERESTARTSYS;否则,如果完成超时,则返回0;如果完成了,则返回剩余的时间 +(jiffies)。 + +更多的变体包括_killable,它使用TASK_KILLABLE作为指定的任务状态,如果它被中断,将返 +回-ERESTARTSYS,如果完成了,则返回0。它也有一个_timeout变体:: + + long wait_for_completion_killable(struct completion *done) + long wait_for_completion_killable_timeout(struct completion *done, unsigned long timeout) + +wait_for_completion_io()的_io变体的行为与非_io变体相同,只是将等待时间计为“IO等待”, +这对任务在调度/IO统计中的计算方式有影响:: + + void wait_for_completion_io(struct completion *done) + unsigned long wait_for_completion_io_timeout(struct completion *done, unsigned long timeout) + + +对完成发信号: +------------- + +一个线程想要发出信号通知继续的条件已经达到,就会调用complete(),向其中一个等待者发出信 +号表明它可以继续:: + + void complete(struct completion *done) + +... or calls complete_all() to signal all current and future waiters:: + + void complete_all(struct completion *done) + +即使在线程开始等待之前就发出了完成的信号,信号传递也会继续进行。这是通过等待者 +“consuming”(递减)“struct completion” 的完成字段来实现的。等待的线程唤醒的顺序 +与它们被排队的顺序相同(FIFO顺序)。 + +如果多次调用complete(),那么这将允许该数量的等待者继续进行--每次调用complete()将 +简单地增加已完成的字段。但多次调用complete_all()是一个错误。complete()和 +complete_all()都可以在IRQ/atomic上下文中安全调用。 + +在任何时候,只能有一个线程在一个特定的 “struct completion”上调用 complete() 或 +complete_all() - 通过等待队列自旋锁进行序列化。任何对 complete() 或 +complete_all() 的并发调用都可能是一个设计错误。 + +从IRQ上下文中发出完成信号 是可行的,因为它将正确地用 +spin_lock_irqsave()/spin_unlock_irqrestore()执行锁操作 + + +try_wait_for_completion()/completion_done(): +-------------------------------------------- + +try_wait_for_completion()函数不会将线程放在等待队列中,而是在需要排队(阻塞)线 +程时返回false,否则会消耗一个已发布的完成并返回true:: + + bool try_wait_for_completion(struct completion *done) + +最后,为了在不以任何方式改变完成的情况下检查完成的状态,可以调用completion_done(), +如果没有发布的完成尚未被等待者消耗,则返回false(意味着存在等待者),否则返回true:: + + bool completion_done(struct completion *done) + +try_wait_for_completion()和completion_done()都可以在IRQ或原子上下文中安全调用。 diff --git a/Documentation/translations/zh_CN/scheduler/index.rst b/Documentation/translations/zh_CN/scheduler/index.rst new file mode 100644 index 000000000..a8eaa7325 --- /dev/null +++ b/Documentation/translations/zh_CN/scheduler/index.rst @@ -0,0 +1,45 @@ +.. include:: ../disclaimer-zh_CN.rst + +:Original: Documentation/scheduler/index.rst + +:翻译: + + 司延腾 Yanteng Si <siyanteng@loongson.cn> + 唐艺舟 Tang Yizhou <tangyeechou@gmail.com> + +:校译: + + + +=============== +Linux调度器 +=============== + +.. toctree:: + :maxdepth: 1 + + completion + sched-arch + sched-bwc + sched-design-CFS + sched-domains + sched-capacity + sched-energy + schedutil + sched-nice-design + sched-stats + sched-debug + +TODOList: + + sched-deadline + sched-rt-group + + text_files + +.. only:: subproject and html + + Indices + ======= + + * :ref:`genindex` diff --git a/Documentation/translations/zh_CN/scheduler/sched-arch.rst b/Documentation/translations/zh_CN/scheduler/sched-arch.rst new file mode 100644 index 000000000..754a15c6b --- /dev/null +++ b/Documentation/translations/zh_CN/scheduler/sched-arch.rst @@ -0,0 +1,76 @@ +.. include:: ../disclaimer-zh_CN.rst + +:Original: Documentation/scheduler/sched-arch.rst + +:翻译: + + 司延腾 Yanteng Si <siyanteng@loongson.cn> + +:校译: + + + +=============================== +架构特定代码的CPU调度器实现提示 +=============================== + + Nick Piggin, 2005 + +上下文切换 +========== +1. 运行队列锁 +默认情况下,switch_to arch函数在调用时锁定了运行队列。这通常不是一个问题,除非 +switch_to可能需要获取运行队列锁。这通常是由于上下文切换中的唤醒操作造成的。见 +arch/ia64/include/asm/switch_to.h的例子。 + +为了要求调度器在运行队列解锁的情况下调用switch_to,你必须在头文件 +中`#define __ARCH_WANT_UNLOCKED_CTXSW`(通常是定义switch_to的那个文件)。 + +在CONFIG_SMP的情况下,解锁的上下文切换对核心调度器的实现只带来了非常小的性能损 +失。 + +CPU空转 +======= +你的cpu_idle程序需要遵守以下规则: + +1. 现在抢占应该在空闲的例程上禁用。应该只在调用schedule()时启用,然后再禁用。 + +2. need_resched/TIF_NEED_RESCHED 只会被设置,并且在运行任务调用 schedule() + 之前永远不会被清除。空闲线程只需要查询need_resched,并且永远不会设置或清除它。 + +3. 当cpu_idle发现(need_resched() == 'true'),它应该调用schedule()。否则 + 它不应该调用schedule()。 + +4. 在检查need_resched时,唯一需要禁用中断的情况是,我们要让处理器休眠到下一个中 + 断(这并不对need_resched提供任何保护,它可以防止丢失一个中断): + + 4a. 这种睡眠类型的常见问题似乎是:: + + local_irq_disable(); + if (!need_resched()) { + local_irq_enable(); + *** resched interrupt arrives here *** + __asm__("sleep until next interrupt"); + } + +5. 当need_resched变为高电平时,TIF_POLLING_NRFLAG可以由不需要中断来唤醒它们 + 的空闲程序设置。换句话说,它们必须定期轮询need_resched,尽管做一些后台工作或 + 进入低CPU优先级可能是合理的。 + + - 5a. 如果TIF_POLLING_NRFLAG被设置,而我们确实决定进入一个中断睡眠,那 + 么需要清除它,然后发出一个内存屏障(接着测试need_resched,禁用中断,如3中解释)。 + +arch/x86/kernel/process.c有轮询和睡眠空闲函数的例子。 + + +可能出现的arch/问题 +=================== + +我发现的可能的arch问题(并试图解决或没有解决)。: + +ia64 - safe_halt的调用与中断相比,是否很荒谬? (它睡眠了吗) (参考 #4a) + +sh64 - 睡眠与中断相比,是否很荒谬? (参考 #4a) + +sparc - 在这一点上,IRQ是开着的(?),把local_irq_save改为_disable。 + - 待办事项: 需要第二个CPU来禁用抢占 (参考 #1) diff --git a/Documentation/translations/zh_CN/scheduler/sched-bwc.rst b/Documentation/translations/zh_CN/scheduler/sched-bwc.rst new file mode 100644 index 000000000..90e931f4c --- /dev/null +++ b/Documentation/translations/zh_CN/scheduler/sched-bwc.rst @@ -0,0 +1,204 @@ +.. include:: ../disclaimer-zh_CN.rst + +:Original: Documentation/scheduler/sched-bwc.rst + +:翻译: + + 司延腾 Yanteng Si <siyanteng@loongson.cn> + +:校译: + + + +============ +CFS 带宽控制 +============ + +.. note:: + 本文只讨论了SCHED_NORMAL的CPU带宽控制。 + SCHED_RT的情况在Documentation/scheduler/sched-rt-group.rst中有涉及。 + +CFS带宽控制是一个CONFIG_FAIR_GROUP_SCHED扩展,它允许指定一个组或层次的最大CPU带宽。 + +一个组允许的带宽是用配额和周期指定的。在每个给定的”周期“(微秒)内,一个任务组被分配多 +达“配额”微秒的CPU时间。当cgroup中的线程可运行时,该配额以时间片段的方式被分配到每个cpu +运行队列中。一旦所有的配额被分配,任何额外的配额请求将导致这些线程被限流。被限流的线程将不 +能再次运行,直到下一个时期的配额得到补充。 + +一个组的未分配配额是全局跟踪的,在每个周期边界被刷新为cfs_quota单元。当线程消耗这个带宽时, +它以需求为基础被转移到cpu-local“筒仓”,在每次更新中转移的数量是可调整的,被描述为“片“(时 +间片)。 + +突发特性 +-------- +现在这个功能借来的时间是用于防范我们对未来的低估,代价是对其他系统用户的干扰增加。所有这些都 +有很好的限制。 + +传统的(UP-EDF)带宽控制是这样的: + + (U = \Sum u_i) <= 1 + +这既保证了每个最后期限的实现,也保证了系统的稳定。毕竟,如果U>1,那么每一秒钟的壁钟时间,我 +们就必须运行超过一秒钟的程序时间,显然会错过我们的最后期限,但下一个最后期限会更远,永远没有 +时间赶上,无边无界的失败。 + +突发特性观察到工作负载并不总是执行全部配额;这使得人们可以将u_i描述为一个统计分布。 + +例如,让u_i = {x,e}_i,其中x是p(95)和x+e p(100)(传统的WCET)。这实际上允许u更小,提 +高了效率(我们可以在系统中打包更多的任务),但代价是当所有的概率都一致时,会错过最后期限。然 +而,它确实保持了稳定性,因为只要我们的x高于平均水平,每一次超限都必须与低估相匹配。 + +也就是说,假设我们有两个任务,都指定了一个p(95)值,那么我们有一个p(95)*p(95)=90.25%的机 +会,两个任务都在他们的配额内,一切都很好。同时,我们有一个p(5)p(5)=0.25%的机会,两个任务同 +时超过他们的配额(保证最后期限失败)。在这两者之间有一个阈值,其中一个超过了,而另一个没有不足, +无法补偿;这取决于具体的CDFs。 + +同时,我们可以说,最坏的情况下的截止日期失败,将是Sum e_i;也就是说,有一个有界的迟延(在假 +设x+e确实是WCET的情况下)。 + +使用突发时的干扰是由错过最后期限的可能性和平均WCET来评价的。测试结果表明,当有许多cgroup或 +CPU未被充分利用时,干扰是有限的。更多的细节显示在: +https://lore.kernel.org/lkml/5371BD36-55AE-4F71-B9D7-B86DC32E3D2B@linux.alibaba.com/ + +管理 +---- +配额、周期和突发是在cpu子系统内通过cgroupfs管理的。 + +.. note:: + 本节描述的cgroupfs文件只适用于cgroup v1.对于cgroup v2,请参阅Control Group v2。 + :ref:`Documentation/admin-guide/cgroup-v2.rst <cgroup-v2-cpu>`. + +- cpu.cfs_quota_us:在一个时期内补充的运行时间(微秒)。 +- cpu.cfs_period_us:一个周期的长度(微秒)。 +- cpu.stat: 输出节流统计数据[下面进一步解释] +- cpu.cfs_burst_us:最大累积运行时间(微秒)。 + +默认值是:: + + cpu.cfs_period_us=100ms + cpu.cfs_quota_us=-1 + cpu.cfs_burst_us=0 + +cpu.cfs_quota_us的值为-1表示该组没有任何带宽限制,这样的组被描述为无限制的带宽组。这代表 +了CFS的传统工作保护行为。 + +写入不小于cpu.cfs_burst_us的任何(有效的)正值将配发指定的带宽限制。该配额或周期允许的最 +小配额是1ms。周期长度也有一个1s的上限。当带宽限制以分层方式使用时,存在额外的限制,这些在下 +面有更详细的解释。 + +向cpu.cfs_quota_us写入任何负值都会移除带宽限制,并使组再次回到无限制的状态。 + +cpu.cfs_burst_us的值为0表示该组不能积累任何未使用的带宽。它使得CFS的传统带宽控制行为没有 +改变。将不大于 cpu.cfs_quota_us 的任何(有效的)正值写入 cpu.cfs_burst_us 将配发未使用 +带宽累积的上限。 + +如果一个组处于受限状态,对该组带宽规格的任何更新都将导致其成为无限流状态。 + +系统范围设置 +------------ +为了提高效率,运行时间在全局池和CPU本地“筒仓”之间以批处理方式转移。这大大减少了大型系统的全 +局核算压力。每次需要进行这种更新时,传输的数量被描述为 "片"。 + +这是可以通过procfs调整的:: + + /proc/sys/kernel/sched_cfs_bandwidth_slice_us (default=5ms) + +较大的时间片段值将减少传输开销,而较小的值则允许更精细的消费。 + +统计 +---- +一个组的带宽统计数据通过cpu.stat的5个字段导出。 + +cpu.stat: + +- nr_periods:已经过去的执行间隔的数量。 +- nr_throttled: 该组已被节流/限制的次数。 +- throttled_time: 该组的实体被限流的总时间长度(纳秒)。 +- nr_bursts:突发发生的周期数。 +- burst_time: 任何CPU在各个时期使用超过配额的累计壁钟时间(纳秒)。 + +这个接口是只读的。 + +分层考虑 +-------- +该接口强制要求单个实体的带宽总是可以达到的,即:max(c_i) <= C。然而,在总体情况下,是明确 +允许过度订阅的,以便在一个层次结构中实现工作保护语义: + + 例如,Sum (c_i)可能超过C + +[ 其中C是父方的带宽,c_i是其子方的带宽。 ] + +.. note:: + 译文中的父亲/孩子指的是cgroup parent, cgroup children。 + +有两种方式可以使一个组变得限流: + + a. 它在一段时期内完全消耗自己的配额 + b. 父方的配额在其期间内全部用完 + +在上述b)情况下,即使孩子可能有剩余的运行时间,它也不会被允许,直到父亲的运行时间被刷新。 + +CFS带宽配额的注意事项 +--------------------- +一旦一个片断被分配给一个cpu,它就不会过期。然而,如果该cpu上的所有线程都无法运行,那么除了 +1ms以外的所有时间片都可以返回到全局池中。这是在编译时由min_cfs_rq_runtime变量配置的。这 +是一个性能调整,有助于防止对全局锁的额外争夺。 + +cpu-local分片不会过期的事实导致了一些有趣的罕见案例,应该被理解。 + +对于cgroup cpu限制的应用程序来说,这是一个相对有意义的问题,因为他们自然会消耗他们的全部配 +额,以及每个cpu-本地片在每个时期的全部。因此,预计nr_periods大致等于nr_throttled,并且 +cpuacct.用量的增加大致等于cfs_quota_us在每个周期的增加。 + +对于高线程、非cpu绑定的应用程序,这种非过期的细微差别允许应用程序短暂地突破他们的配额限制, +即任务组正在运行的每个cpu上未使用的片断量(通常每个cpu最多1ms或由min_cfs_rq_runtime定 +义)。这种轻微的突发只适用于配额已经分配给cpu,然后没有完全使用或在以前的时期返回。这个突发 +量不会在核心之间转移。因此,这种机制仍然严格限制任务组的配额平均使用量,尽管是在比单一时期更 +长的时间窗口。这也限制了突发能力,每个cpu不超过1ms。这为在高核数机器上有小配额限制的高线程 +应用提供了更好的更可预测的用户体验。它还消除了在使用低于配额的cpu时对这些应用进行节流的倾向。 +另一种说法是,通过允许一个片断的未使用部分在不同时期保持有效,我们减少了在不需要整个片断的cpu +时间的cpu-local 筒仓上浪费配额的可能性。 + +绑定cpu和非绑定cpu的交互式应用之间的互动也应该被考虑,特别是当单核使用率达到100%时。如果你 +给了这些应用程序一半的cpu-core,并且它们都被安排在同一个CPU上,理论上非cpu绑定的应用程序有 +可能在某些时期使用多达1ms的额外配额,从而阻止cpu绑定的应用程序完全使用其配额,这也是同样的数 +量。在这些情况下,将由CFS算法(见CFS调度器)来决定选择哪个应用程序来运行,因为它们都是可运行 +的,并且有剩余的配额。这个运行时间的差异将在接下来的交互式应用程序空闲期间得到弥补。 + +例子 +---- +1. 限制一个组的运行时间为1个CPU的价值:: + + 如果周期是250ms,配额也是250ms,那么该组将每250ms获得价值1个CPU的运行时间。 + + # echo 250000 > cpu.cfs_quota_us /* quota = 250ms */ + # echo 250000 > cpu.cfs_period_us /* period = 250ms */ + +2. 在多CPU机器上,将一个组的运行时间限制为2个CPU的价值 + + 在500ms周期和1000ms配额的情况下,该组每500ms可以获得2个CPU的运行时间:: + + # echo 1000000 > cpu.cfs_quota_us /* quota = 1000ms */ + # echo 500000 > cpu.cfs_period_us /* period = 500ms */ + + 这里较大的周期允许增加突发能力。 + +3. 将一个组限制在1个CPU的20%。 + + 在50ms周期内,10ms配额将相当于1个CPU的20%。:: + + # echo 10000 > cpu.cfs_quota_us /* quota = 10ms */ + # echo 50000 > cpu.cfs_period_us /* period = 50ms */ + + 通过在这里使用一个小的周期,我们以牺牲突发容量为代价来确保稳定的延迟响应。 + +4. 将一个组限制在1个CPU的40%,并允许累积到1个CPU的20%,如果已经累积了的话。 + + 在50ms周期内,20ms配额将相当于1个CPU的40%。而10毫秒的突发将相当于1个 + CPU的20%:: + + # echo 20000 > cpu.cfs_quota_us /* quota = 20ms */ + # echo 50000 > cpu.cfs_period_us /* period = 50ms */ + # echo 10000 > cpu.cfs_burst_us /* burst = 10ms */ + + 较大的缓冲区设置(不大于配额)允许更大的突发容量。 diff --git a/Documentation/translations/zh_CN/scheduler/sched-capacity.rst b/Documentation/translations/zh_CN/scheduler/sched-capacity.rst new file mode 100644 index 000000000..3a52053c2 --- /dev/null +++ b/Documentation/translations/zh_CN/scheduler/sched-capacity.rst @@ -0,0 +1,390 @@ +.. SPDX-License-Identifier: GPL-2.0 +.. include:: ../disclaimer-zh_CN.rst + +:Original: Documentation/scheduler/sched-capacity.rst + +:翻译: + + 唐艺舟 Tang Yizhou <tangyeechou@gmail.com> + +:校译: + + 时奎亮 Alex Shi <alexs@kernel.org> + +============= +算力感知调度 +============= + +1. CPU算力 +========== + +1.1 简介 +-------- + +一般来说,同构的SMP平台由完全相同的CPU构成。异构的平台则由性能特征不同的CPU构成,在这样的 +平台中,CPU不能被认为是相同的。 + +我们引入CPU算力(capacity)的概念来测量每个CPU能达到的性能,它的值相对系统中性能最强的CPU +做过归一化处理。异构系统也被称为非对称CPU算力系统,因为它们由不同算力的CPU组成。 + +最大可达性能(换言之,最大CPU算力)的差异有两个主要来源: + +- 不是所有CPU的微架构都相同。 +- 在动态电压频率升降(Dynamic Voltage and Frequency Scaling,DVFS)框架中,不是所有的CPU都 + 能达到一样高的操作性能值(Operating Performance Points,OPP。译注,也就是“频率-电压”对)。 + +Arm大小核(big.LITTLE)系统是同时具有两种差异的一个例子。相较小核,大核面向性能(拥有更多的 +流水线层级,更大的缓存,更智能的分支预测器等),通常可以达到更高的操作性能值。 + +CPU性能通常由每秒百万指令(Millions of Instructions Per Second,MIPS)表示,也可表示为 +per Hz能执行的指令数,故:: + + capacity(cpu) = work_per_hz(cpu) * max_freq(cpu) + +1.2 调度器术语 +-------------- + +调度器使用了两种不同的算力值。CPU的 ``capacity_orig`` 是它的最大可达算力,即最大可达性能等级。 +CPU的 ``capacity`` 是 ``capacity_orig`` 扣除了一些性能损失(比如处理中断的耗时)的值。 + +注意CPU的 ``capacity`` 仅仅被设计用于CFS调度类,而 ``capacity_orig`` 是不感知调度类的。为 +简洁起见,本文档的剩余部分将不加区分的使用术语 ``capacity`` 和 ``capacity_orig`` 。 + +1.3 平台示例 +------------ + +1.3.1 操作性能值相同 +~~~~~~~~~~~~~~~~~~~~ + +考虑一个假想的双核非对称CPU算力系统,其中 + +- work_per_hz(CPU0) = W +- work_per_hz(CPU1) = W/2 +- 所有CPU以相同的固定频率运行 + +根据上文对算力的定义: + +- capacity(CPU0) = C +- capacity(CPU1) = C/2 + +若这是Arm大小核系统,那么CPU0是大核,而CPU1是小核。 + +考虑一种周期性产生固定工作量的工作负载,你将会得到类似下图的执行轨迹:: + + CPU0 work ^ + | ____ ____ ____ + | | | | | | | + +----+----+----+----+----+----+----+----+----+----+-> time + + CPU1 work ^ + | _________ _________ ____ + | | | | | | + +----+----+----+----+----+----+----+----+----+----+-> time + +CPU0在系统中具有最高算力(C),它使用T个单位时间完成固定工作量W。另一方面,CPU1只有CPU0一半 +算力,因此在T个单位时间内仅完成工作量W/2。 + +1.3.2 最大操作性能值不同 +~~~~~~~~~~~~~~~~~~~~~~~~ + +具有不同算力值的CPU,通常来说最大操作性能值也不同。考虑上一小节提到的CPU(也就是说, +work_per_hz()相同): + +- max_freq(CPU0) = F +- max_freq(CPU1) = 2/3 * F + +这将推出: + +- capacity(CPU0) = C +- capacity(CPU1) = C/3 + +执行1.3.1节描述的工作负载,每个CPU按最大频率运行,结果为:: + + CPU0 work ^ + | ____ ____ ____ + | | | | | | | + +----+----+----+----+----+----+----+----+----+----+-> time + + workload on CPU1 + CPU1 work ^ + | ______________ ______________ ____ + | | | | | | + +----+----+----+----+----+----+----+----+----+----+-> time + +1.4 关于计算方式的注意事项 +-------------------------- + +需要注意的是,使用单一值来表示CPU性能的差异是有些争议的。两个不同的微架构的相对性能差异应该 +描述为:X%整数运算差异,Y%浮点数运算差异,Z%分支跳转差异,等等。尽管如此,使用简单计算方式 +的结果目前还是令人满意的。 + +2. 任务使用率 +============= + +2.1 简介 +-------- + +算力感知调度要求描述任务需求,描述方式要和CPU算力相关。每个调度类可以用不同的方式描述它。 +任务使用率是CFS独有的描述方式,不过在这里介绍它有助于引入更多一般性的概念。 + +任务使用率是一种用百分比来描述任务吞吐率需求的方式。一个简单的近似是任务的占空比,也就是说:: + + task_util(p) = duty_cycle(p) + +在频率固定的SMP系统中,100%的利用率意味着任务是忙等待循环。反之,10%的利用率暗示这是一个 +小周期任务,它在睡眠上花费的时间比执行更多。 + +2.2 频率不变性 +-------------- + +一个需要考虑的议题是,工作负载的占空比受CPU正在运行的操作性能值直接影响。考虑以给定的频率F +执行周期性工作负载:: + + CPU work ^ + | ____ ____ ____ + | | | | | | | + +----+----+----+----+----+----+----+----+----+----+-> time + +可以算出 duty_cycle(p) == 25%。 + +现在,考虑以给定频率F/2执行 *同一个* 工作负载:: + + CPU work ^ + | _________ _________ ____ + | | | | | | + +----+----+----+----+----+----+----+----+----+----+-> time + +可以算出 duty_cycle(p) == 50%,尽管两次执行中,任务的行为完全一致(也就是说,执行的工作量 +相同)。 + +任务利用率信号可按下面公式处理成频率不变的(译注:这里的术语用到了信号与系统的概念):: + + task_util_freq_inv(p) = duty_cycle(p) * (curr_frequency(cpu) / max_frequency(cpu)) + +对上面两个例子运用该公式,可以算出频率不变的任务利用率均为25%。 + +2.3 CPU不变性 +------------- + +CPU算力与任务利用率具有类型的效应,在算力不同的CPU上执行完全相同的工作负载,将算出不同的 +占空比。 + +考虑1.3.2节提到的系统,也就是说:: + +- capacity(CPU0) = C +- capacity(CPU1) = C/3 + +每个CPU按最大频率执行指定周期性工作负载,结果为:: + + CPU0 work ^ + | ____ ____ ____ + | | | | | | | + +----+----+----+----+----+----+----+----+----+----+-> time + + CPU1 work ^ + | ______________ ______________ ____ + | | | | | | + +----+----+----+----+----+----+----+----+----+----+-> time + +也就是说, + +- duty_cycle(p) == 25%,如果任务p在CPU0上按最大频率运行。 +- duty_cycle(p) == 75%,如果任务p在CPU1上按最大频率运行。 + +任务利用率信号可按下面公式处理成CPU算力不变的:: + + task_util_cpu_inv(p) = duty_cycle(p) * (capacity(cpu) / max_capacity) + +其中 ``max_capacity`` 是系统中最高的CPU算力。对上面的例子运用该公式,可以算出CPU算力不变 +的任务利用率均为25%。 + +2.4 任务利用率不变量 +-------------------- + +频率和CPU算力不变性都需要被应用到任务利用率的计算中,以便求出真正的不变信号。 +任务利用率的伪计算公式是同时具备CPU和频率不变性的,也就是说,对于指定任务p:: + + curr_frequency(cpu) capacity(cpu) + task_util_inv(p) = duty_cycle(p) * ------------------- * ------------- + max_frequency(cpu) max_capacity + +也就是说,任务利用率不变量假定任务在系统中最高算力CPU上以最高频率运行,以此描述任务的行为。 + +在接下来的章节中提到的任何任务利用率,均是不变量的形式。 + +2.5 利用率估算 +-------------- + +由于预测未来的水晶球不存在,当任务第一次变成可运行时,任务的行为和任务利用率均不能被准确预测。 +CFS调度类基于实体负载跟踪机制(Per-Entity Load Tracking, PELT)维护了少量CPU和任务信号, +其中之一可以算出平均利用率(与瞬时相反)。 + +这意味着,尽管运用“真实的”任务利用率(凭借水晶球)写出算力感知调度的准则,但是它的实现将只能 +用任务利用率的估算值。 + +3. 算力感知调度的需求 +===================== + +3.1 CPU算力 +----------- + +当前,Linux无法凭自身算出CPU算力,因此必须要有把这个信息传递给Linux的方式。每个架构必须为此 +定义arch_scale_cpu_capacity()函数。 + +arm和arm64架构直接把这个信息映射到arch_topology驱动的CPU scaling数据中(译注:参考 +arch_topology.h的percpu变量cpu_scale),它是从capacity-dmips-mhz CPU binding中衍生计算 +出来的。参见Documentation/devicetree/bindings/arm/cpu-capacity.txt。 + +3.2 频率不变性 +-------------- + +如2.2节所述,算力感知调度需要频率不变的任务利用率。每个架构必须为此定义 +arch_scale_freq_capacity(cpu)函数。 + +实现该函数要求计算出每个CPU当前以什么频率在运行。实现它的一种方式是利用硬件计数器(x86的 +APERF/MPERF,arm64的AMU),它能按CPU当前频率动态可扩展地升降递增计数器的速率。另一种方式是 +在cpufreq频率变化时直接使用钩子函数,内核此时感知到将要被切换的频率(也被arm/arm64实现了)。 + +4. 调度器拓扑结构 +================= + +在构建调度域时,调度器将会发现系统是否表现为非对称CPU算力。如果是,那么: + +- sched_asym_cpucapacity静态键(static key)将使能。 +- SD_ASYM_CPUCAPACITY_FULL标志位将在尽量最低调度域层级中被设置,同时要满足条件:调度域恰好 + 完整包含某个CPU算力值的全部CPU。 +- SD_ASYM_CPUCAPACITY标志将在所有包含非对称CPU的调度域中被设置。 + +sched_asym_cpucapacity静态键的设计意图是,保护为非对称CPU算力系统所准备的代码。不过要注意的 +是,这个键是系统范围可见的。想象下面使用了cpuset的步骤:: + + capacity C/2 C + ________ ________ + / \ / \ + CPUs 0 1 2 3 4 5 6 7 + \__/ \______________/ + cpusets cs0 cs1 + +可以通过下面的方式创建: + +.. code-block:: sh + + mkdir /sys/fs/cgroup/cpuset/cs0 + echo 0-1 > /sys/fs/cgroup/cpuset/cs0/cpuset.cpus + echo 0 > /sys/fs/cgroup/cpuset/cs0/cpuset.mems + + mkdir /sys/fs/cgroup/cpuset/cs1 + echo 2-7 > /sys/fs/cgroup/cpuset/cs1/cpuset.cpus + echo 0 > /sys/fs/cgroup/cpuset/cs1/cpuset.mems + + echo 0 > /sys/fs/cgroup/cpuset/cpuset.sched_load_balance + +由于“这是”非对称CPU算力系统,sched_asym_cpucapacity静态键将使能。然而,CPU 0--1对应的 +调度域层级,算力值仅有一个,该层级中SD_ASYM_CPUCAPACITY未被设置,它描述的是一个SMP区域,也 +应该被以此处理。 + +因此,“典型的”保护非对称CPU算力代码路径的代码模式是: + +- 检查sched_asym_cpucapacity静态键 +- 如果它被使能,接着检查调度域层级中SD_ASYM_CPUCAPACITY标志位是否出现 + +5. 算力感知调度的实现 +===================== + +5.1 CFS +------- + +5.1.1 算力适应性(fitness) +~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +CFS最主要的算力调度准则是:: + + task_util(p) < capacity(task_cpu(p)) + +它通常被称为算力适应性准则。也就是说,CFS必须保证任务“适合”在某个CPU上运行。如果准则被违反, +任务将要更长地消耗该CPU,任务是CPU受限的(CPU-bound)。 + +此外,uclamp允许用户空间指定任务的最小和最大利用率,要么以sched_setattr()的方式,要么以 +cgroup接口的方式(参阅Documentation/admin-guide/cgroup-v2.rst)。如其名字所暗示,uclamp +可以被用在前一条准则中限制task_util()。 + +5.1.2 被唤醒任务的CPU选择 +~~~~~~~~~~~~~~~~~~~~~~~~~ + +CFS任务唤醒的CPU选择,遵循上面描述的算力适应性准则。在此之上,uclamp被用来限制任务利用率, +这令用户空间对CFS任务的CPU选择有更多的控制。也就是说,CFS被唤醒任务的CPU选择,搜索满足以下 +条件的CPU:: + + clamp(task_util(p), task_uclamp_min(p), task_uclamp_max(p)) < capacity(cpu) + +通过使用uclamp,举例来说,用户空间可以允许忙等待循环(100%使用率)在任意CPU上运行,只要给 +它设置低的uclamp.max值。相反,uclamp能强制一个小的周期性任务(比如,10%利用率)在最高性能 +的CPU上运行,只要给它设置高的uclamp.min值。 + +.. note:: + + CFS的被唤醒的任务的CPU选择,可被能耗感知调度(Energy Aware Scheduling,EAS)覆盖,在 + Documentation/scheduler/sched-energy.rst中描述。 + +5.1.3 负载均衡 +~~~~~~~~~~~~~~ + +被唤醒任务的CPU选择的一个病理性的例子是,任务几乎不睡眠,那么也几乎不发生唤醒。考虑:: + + w == wakeup event + + capacity(CPU0) = C + capacity(CPU1) = C / 3 + + workload on CPU0 + CPU work ^ + | _________ _________ ____ + | | | | | | + +----+----+----+----+----+----+----+----+----+----+-> time + w w w + + workload on CPU1 + CPU work ^ + | ____________________________________________ + | | + +----+----+----+----+----+----+----+----+----+----+-> + w + +该工作负载应该在CPU0上运行,不过如果任务满足以下条件之一: + +- 一开始发生不合适的调度(不准确的初始利用率估计) +- 一开始调度正确,但突然需要更多的处理器功率 + +则任务可能变为CPU受限的,也就是说 ``task_util(p) > capacity(task_cpu(p))`` ;CPU算力 +调度准则被违反,将不会有任何唤醒事件来修复这个错误的CPU选择。 + +这种场景下的任务被称为“不合适的”(misfit)任务,处理这个场景的机制同样也以此命名。Misfit +任务迁移借助CFS负载均衡器,更明确的说,是主动负载均衡的部分(用来迁移正在运行的任务)。 +当发生负载均衡时,如果一个misfit任务可以被迁移到一个相较当前运行的CPU具有更高算力的CPU上, +那么misfit任务的主动负载均衡将被触发。 + +5.2 实时调度 +------------ + +5.2.1 被唤醒任务的CPU选择 +~~~~~~~~~~~~~~~~~~~~~~~~~ + +实时任务唤醒时的CPU选择,搜索满足以下条件的CPU:: + + task_uclamp_min(p) <= capacity(task_cpu(cpu)) + +同时仍然允许接着使用常规的优先级限制。如果没有CPU能满足这个算力准则,那么将使用基于严格 +优先级的调度,CPU算力将被忽略。 + +5.3 最后期限调度 +---------------- + +5.3.1 被唤醒任务的CPU选择 +~~~~~~~~~~~~~~~~~~~~~~~~~ + +最后期限任务唤醒时的CPU选择,搜索满足以下条件的CPU:: + + task_bandwidth(p) < capacity(task_cpu(p)) + +同时仍然允许接着使用常规的带宽和截止期限限制。如果没有CPU能满足这个算力准则,那么任务依然 +在当前CPU队列中。 diff --git a/Documentation/translations/zh_CN/scheduler/sched-debug.rst b/Documentation/translations/zh_CN/scheduler/sched-debug.rst new file mode 100644 index 000000000..5e17740c2 --- /dev/null +++ b/Documentation/translations/zh_CN/scheduler/sched-debug.rst @@ -0,0 +1,51 @@ +.. SPDX-License-Identifier: GPL-2.0 +.. include:: ../disclaimer-zh_CN.rst + +:Original: Documentation/scheduler/sched-debug.rst + +:翻译: + + 唐艺舟 Tang Yizhou <tangyeechou@gmail.com> + +============= +调度器debugfs +============= + +用配置项CONFIG_SCHED_DEBUG=y启动内核后,将可以访问/sys/kernel/debug/sched +下的调度器专用调试文件。其中一些文件描述如下。 + +numa_balancing +============== + +`numa_balancing` 目录用来存放控制非统一内存访问(NUMA)平衡特性的相关文件。 +如果该特性导致系统负载太高,那么可以通过 `scan_period_min_ms, scan_delay_ms, +scan_period_max_ms, scan_size_mb` 文件控制NUMA缺页的内核采样速率。 + + +scan_period_min_ms, scan_delay_ms, scan_period_max_ms, scan_size_mb +------------------------------------------------------------------- + +自动NUMA平衡会扫描任务地址空间,检测页面是否被正确放置,或者数据是否应该被 +迁移到任务正在运行的本地内存结点,此时需解映射页面。每个“扫描延迟”(scan delay) +时间之后,任务扫描其地址空间中下一批“扫描大小”(scan size)个页面。若抵达 +内存地址空间末尾,扫描器将从头开始重新扫描。 + +结合来看,“扫描延迟”和“扫描大小”决定扫描速率。当“扫描延迟”减小时,扫描速率 +增加。“扫描延迟”和每个任务的扫描速率都是自适应的,且依赖历史行为。如果页面被 +正确放置,那么扫描延迟就会增加;否则扫描延迟就会减少。“扫描大小”不是自适应的, +“扫描大小”越大,扫描速率越高。 + +更高的扫描速率会产生更高的系统开销,因为必须捕获缺页异常,并且潜在地必须迁移 +数据。然而,当扫描速率越高,若工作负载模式发生变化,任务的内存将越快地迁移到 +本地结点,由于远程内存访问而产生的性能影响将降到最低。下面这些文件控制扫描延迟 +的阈值和被扫描的页面数量。 + +``scan_period_min_ms`` 是扫描一个任务虚拟内存的最小时间,单位是毫秒。它有效地 +控制了每个任务的最大扫描速率。 + +``scan_delay_ms`` 是一个任务初始化创建(fork)时,第一次使用的“扫描延迟”。 + +``scan_period_max_ms`` 是扫描一个任务虚拟内存的最大时间,单位是毫秒。它有效地 +控制了每个任务的最小扫描速率。 + +``scan_size_mb`` 是一次特定的扫描中,要扫描多少兆字节(MB)对应的页面数。 diff --git a/Documentation/translations/zh_CN/scheduler/sched-design-CFS.rst b/Documentation/translations/zh_CN/scheduler/sched-design-CFS.rst new file mode 100644 index 000000000..307640240 --- /dev/null +++ b/Documentation/translations/zh_CN/scheduler/sched-design-CFS.rst @@ -0,0 +1,205 @@ +.. SPDX-License-Identifier: GPL-2.0 +.. include:: ../disclaimer-zh_CN.rst + +:Original: Documentation/scheduler/sched-design-CFS.rst + +:翻译: + + 唐艺舟 Tang Yizhou <tangyeechou@gmail.com> + +=============== +完全公平调度器 +=============== + + +1. 概述 +======= + +CFS表示“完全公平调度器”,它是为桌面新设计的进程调度器,由Ingo Molnar实现并合入Linux +2.6.23。它替代了之前原始调度器中SCHED_OTHER策略的交互式代码。 + +CFS 80%的设计可以总结为一句话:CFS在真实硬件上建模了一个“理想的,精确的多任务CPU”。 + +“理想的多任务CPU”是一种(不存在的 :-))具有100%物理算力的CPU,它能让每个任务精确地以 +相同的速度并行运行,速度均为1/nr_running。举例来说,如果有两个任务正在运行,那么每个 +任务获得50%物理算力。 --- 也就是说,真正的并行。 + +在真实的硬件上,一次只能运行一个任务,所以我们需要介绍“虚拟运行时间”的概念。任务的虚拟 +运行时间表明,它的下一个时间片将在上文描述的理想多任务CPU上开始执行。在实践中,任务的 +虚拟运行时间由它的真实运行时间相较正在运行的任务总数归一化计算得到。 + + + +2. 一些实现细节 +=============== + +在CFS中,虚拟运行时间由每个任务的p->se.vruntime(单位为纳秒)的值表达和跟踪。因此, +精确地计时和测量一个任务应得的“预期的CPU时间”是可能的。 + + 一些细节:在“理想的”硬件上,所有的任务在任何时刻都应该具有一样的p->se.vruntime值, + --- 也就是说,任务应当同时执行,没有任务会在“理想的”CPU分时中变得“不平衡”。 + +CFS的任务选择逻辑基于p->se.vruntime的值,因此非常简单:总是试图选择p->se.vruntime值 +最小的任务运行(也就是说,至今执行时间最少的任务)。CFS总是尽可能尝试按“理想多任务硬件” +那样将CPU时间在可运行任务中均分。 + +CFS剩下的其它设计,一般脱离了这个简单的概念,附加的设计包括nice级别,多处理,以及各种 +用来识别已睡眠任务的算法变体。 + + + +3. 红黑树 +========= + +CFS的设计非常激进:它不使用运行队列的旧数据结构,而是使用按时间排序的红黑树,构建出 +任务未来执行的“时间线”。因此没有任何“数组切换”的旧包袱(之前的原始调度器和RSDL/SD都 +被它影响)。 + +CFS同样维护了rq->cfs.min_vruntime值,它是单调递增的,跟踪运行队列中的所有任务的最小 +虚拟运行时间值。系统做的全部工作是:使用min_vruntime跟踪,然后用它的值将新激活的调度 +实体尽可能地放在红黑树的左侧。 + +运行队列中正在运行的任务的总数由rq->cfs.load计数,它是运行队列中的任务的权值之和。 + +CFS维护了一个按时间排序的红黑树,所有可运行任务以p->se.vruntime为键值排序。CFS从这颗 +树上选择“最左侧”的任务并运行。系统继续运行,被执行过的任务越来越被放到树的右侧 --- 缓慢, +但很明确每个任务都有成为“最左侧任务”的机会,因此任务将确定性地获得一定量CPU时间。 + +总结一下,CFS工作方式像这样:它运行一个任务一会儿,当任务发生调度(或者调度器时钟滴答 +tick产生),就会考虑任务的CPU使用率:任务刚刚花在物理CPU上的(少量)时间被加到 +p->se.vruntime。一旦p->se.vruntime变得足够大,其它的任务将成为按时间排序的红黑树的 +“最左侧任务”(相较最左侧的任务,还要加上一个很小的“粒度”量,使得我们不会对任务过度调度, +导致缓存颠簸),然后新的最左侧任务将被选中,当前任务被抢占。 + + + + +4. CFS的一些特征 +================ + +CFS使用纳秒粒度的计时,不依赖于任何jiffies或HZ的细节。因此CFS并不像之前的调度器那样 +有“时间片”的概念,也没有任何启发式的设计。唯一可调的参数(你需要打开CONFIG_SCHED_DEBUG)是: + + /sys/kernel/debug/sched/min_granularity_ns + +它可以用来将调度器从“桌面”模式(也就是低时延)调节为“服务器”(也就是高批处理)模式。 +它的默认设置是适合桌面的工作负载。SCHED_BATCH也被CFS调度器模块处理。 + +CFS的设计不易受到当前存在的任何针对stock调度器的“攻击”的影响,包括fiftyp.c,thud.c, +chew.c,ring-test.c,massive_intr.c,它们都能很好地运行,不会影响交互性,将产生 +符合预期的行为。 + +CFS调度器处理nice级别和SCHED_BATCH的能力比之前的原始调度器更强:两种类型的工作负载 +都被更激进地隔离了。 + +SMP负载均衡被重做/清理过:遍历运行队列的假设已经从负载均衡的代码中移除,使用调度模块 +的迭代器。结果是,负载均衡代码变得简单不少。 + + + +5. 调度策略 +=========== + +CFS实现了三种调度策略: + + - SCHED_NORMAL:(传统被称为SCHED_OTHER):该调度策略用于普通任务。 + + - SCHED_BATCH:抢占不像普通任务那样频繁,因此允许任务运行更长时间,更好地利用缓存, + 不过要以交互性为代价。它很适合批处理工作。 + + - SCHED_IDLE:它比nice 19更弱,不过它不是真正的idle定时器调度器,因为要避免给机器 + 带来死锁的优先级反转问题。 + +SCHED_FIFO/_RR被实现在sched/rt.c中,它们由POSIX具体说明。 + +util-linux-ng 2.13.1.1中的chrt命令可以设置以上所有策略,除了SCHED_IDLE。 + + + +6. 调度类 +========= + +新的CFS调度器被设计成支持“调度类”,一种调度模块的可扩展层次结构。这些模块封装了调度策略 +细节,由调度器核心代码处理,且无需对它们做太多假设。 + +sched/fair.c 实现了上文描述的CFS调度器。 + +sched/rt.c 实现了SCHED_FIFO和SCHED_RR语义,且比之前的原始调度器更简洁。它使用了100个 +运行队列(总共100个实时优先级,替代了之前调度器的140个),且不需要过期数组(expired +array)。 + +调度类由sched_class结构体实现,它包括一些函数钩子,当感兴趣的事件发生时,钩子被调用。 + +这是(部分)钩子的列表: + + - enqueue_task(...) + + 当任务进入可运行状态时,被调用。它将调度实体(任务)放到红黑树中,增加nr_running变量 + 的值。 + + - dequeue_task(...) + + 当任务不再可运行时,这个函数被调用,对应的调度实体被移出红黑树。它减少nr_running变量 + 的值。 + + - yield_task(...) + + 这个函数的行为基本上是出队,紧接着入队,除非compat_yield sysctl被开启。在那种情况下, + 它将调度实体放在红黑树的最右端。 + + - check_preempt_curr(...) + + 这个函数检查进入可运行状态的任务能否抢占当前正在运行的任务。 + + - pick_next_task(...) + + 这个函数选择接下来最适合运行的任务。 + + - set_curr_task(...) + + 这个函数在任务改变调度类或改变任务组时被调用。 + + - task_tick(...) + + 这个函数最常被时间滴答函数调用,它可能导致进程切换。这驱动了运行时抢占。 + + + + +7. CFS的组调度扩展 +================== + +通常,调度器操作粒度为任务,努力为每个任务提供公平的CPU时间。有时可能希望将任务编组, +并为每个组提供公平的CPU时间。举例来说,可能首先希望为系统中的每个用户提供公平的CPU +时间,接下来才是某个用户的每个任务。 + +CONFIG_CGROUP_SCHED 力求实现它。它将任务编组,并为这些组公平地分配CPU时间。 + +CONFIG_RT_GROUP_SCHED 允许将实时(也就是说,SCHED_FIFO和SCHED_RR)任务编组。 + +CONFIG_FAIR_GROUP_SCHED 允许将CFS(也就是说,SCHED_NORMAL和SCHED_BATCH)任务编组。 + + 这些编译选项要求CONFIG_CGROUPS被定义,然后管理员能使用cgroup伪文件系统任意创建任务组。 + 关于该文件系统的更多信息,参见Documentation/admin-guide/cgroup-v1/cgroups.rst + +当CONFIG_FAIR_GROUP_SCHED被定义后,通过伪文件系统,每个组被创建一个“cpu.shares”文件。 +参见下面的例子来创建任务组,并通过“cgroup”伪文件系统修改它们的CPU份额:: + + # mount -t tmpfs cgroup_root /sys/fs/cgroup + # mkdir /sys/fs/cgroup/cpu + # mount -t cgroup -ocpu none /sys/fs/cgroup/cpu + # cd /sys/fs/cgroup/cpu + + # mkdir multimedia # 创建 "multimedia" 任务组 + # mkdir browser # 创建 "browser" 任务组 + + # #配置multimedia组,令其获得browser组两倍CPU带宽 + + # echo 2048 > multimedia/cpu.shares + # echo 1024 > browser/cpu.shares + + # firefox & # 启动firefox并把它移到 "browser" 组 + # echo <firefox_pid> > browser/tasks + + # #启动gmplayer(或者你最喜欢的电影播放器) + # echo <movie_player_pid> > multimedia/tasks diff --git a/Documentation/translations/zh_CN/scheduler/sched-domains.rst b/Documentation/translations/zh_CN/scheduler/sched-domains.rst new file mode 100644 index 000000000..e814d4c01 --- /dev/null +++ b/Documentation/translations/zh_CN/scheduler/sched-domains.rst @@ -0,0 +1,72 @@ +.. SPDX-License-Identifier: GPL-2.0 +.. include:: ../disclaimer-zh_CN.rst + +:Original: Documentation/scheduler/sched-domains.rst + +:翻译: + + 唐艺舟 Tang Yizhou <tangyeechou@gmail.com> + +:校译: + + 司延腾 Yanteng Si <siyanteng@loongson.cn> + +====== +调度域 +====== + +每个CPU有一个“基”调度域(struct sched_domain)。调度域层次结构从基调度域构建而来,可 +通过->parent指针自下而上遍历。->parent必须以NULL结尾,调度域结构体必须是per-CPU的, +因为它们无锁更新。 + +每个调度域管辖数个CPU(存储在->span字段中)。一个调度域的span必须是它的子调度域span的 +超集(如有需求出现,这个限制可以放宽)。CPU i的基调度域必须至少管辖CPU i。每个CPU的 +顶层调度域通常将会管辖系统中的全部CPU,尽管严格来说这不是必须的,假如是这样,会导致某些 +CPU出现永远不会被指定任务运行的情况,直到允许的CPU掩码被显式设定。调度域的span字段意味 +着“在这些CPU中做进程负载均衡”。 + +每个调度域必须具有一个或多个CPU调度组(struct sched_group),它们以单向循环链表的形式 +组织,存储在->groups指针中。这些组的CPU掩码的并集必须和调度域span字段一致。->groups +指针指向的这些组包含的CPU,必须被调度域管辖。组包含的是只读数据,被创建之后,可能被多个 +CPU共享。任意两个组的CPU掩码的交集不一定为空,如果是这种情况,对应调度域的SD_OVERLAP +标志位被设置,它管辖的调度组可能不能在多个CPU中共享。 + +调度域中的负载均衡发生在调度组中。也就是说,每个组被视为一个实体。组的负载被定义为它 +管辖的每个CPU的负载之和。仅当组的负载不均衡后,任务才在组之间发生迁移。 + +在kernel/sched/core.c中,trigger_load_balance()在每个CPU上通过scheduler_tick() +周期执行。在当前运行队列下一个定期调度再平衡事件到达后,它引发一个软中断。负载均衡真正 +的工作由run_rebalance_domains()->rebalance_domains()完成,在软中断上下文中执行 +(SCHED_SOFTIRQ)。 + +后一个函数有两个入参:当前CPU的运行队列、它在scheduler_tick()调用时是否空闲。函数会从 +当前CPU所在的基调度域开始迭代执行,并沿着parent指针链向上进入更高层级的调度域。在迭代 +过程中,函数会检查当前调度域是否已经耗尽了再平衡的时间间隔,如果是,它在该调度域运行 +load_balance()。接下来它检查父调度域(如果存在),再后来父调度域的父调度域,以此类推。 + +起初,load_balance()查找当前调度域中最繁忙的调度组。如果成功,在该调度组管辖的全部CPU +的运行队列中找出最繁忙的运行队列。如能找到,对当前的CPU运行队列和新找到的最繁忙运行 +队列均加锁,并把任务从最繁忙队列中迁移到当前CPU上。被迁移的任务数量等于在先前迭代执行 +中计算出的该调度域的调度组的不均衡值。 + +实现调度域 +========== + +基调度域会管辖CPU层次结构中的第一层。对于超线程(SMT)而言,基调度域将会管辖同一个物理 +CPU的全部虚拟CPU,每个虚拟CPU对应一个调度组。 + +在SMP中,基调度域的父调度域将会管辖同一个结点中的全部物理CPU,每个调度组对应一个物理CPU。 +接下来,如果是非统一内存访问(NUMA)系统,SMP调度域的父调度域将管辖整个机器,一个结点的 +CPU掩码对应一个调度组。亦或,你可以使用多级NUMA;举例来说Opteron处理器,可能仅用一个 +调度域来覆盖它的一个NUMA层级。 + +实现者需要阅读include/linux/sched/sd_flags.h的注释:读SD_*来了解具体情况以及调度域的 +SD标志位调节了哪些东西。 + +体系结构可以把指定的拓扑层级的通用调度域构建器和默认的SD标志位覆盖掉,方法是创建一个 +sched_domain_topology_level数组,并以该数组作为入参调用set_sched_topology()。 + +调度域调试基础设施可以通过CONFIG_SCHED_DEBUG开启,并在开机启动命令行中增加 +“sched_verbose”。如果你忘记调整开机启动命令行了,也可以打开 +/sys/kernel/debug/sched/verbose开关。这将开启调度域错误检查的解析,它应该能捕获(上文 +描述过的)绝大多数错误,同时以可视化格式打印调度域的结构。 diff --git a/Documentation/translations/zh_CN/scheduler/sched-energy.rst b/Documentation/translations/zh_CN/scheduler/sched-energy.rst new file mode 100644 index 000000000..fdbf6cfee --- /dev/null +++ b/Documentation/translations/zh_CN/scheduler/sched-energy.rst @@ -0,0 +1,351 @@ +.. SPDX-License-Identifier: GPL-2.0 +.. include:: ../disclaimer-zh_CN.rst + +:Original: Documentation/scheduler/sched-energy.rst + +:翻译: + + 唐艺舟 Tang Yizhou <tangyeechou@gmail.com> + +============ +能量感知调度 +============ + +1. 简介 +------- + +能量感知调度(EAS)使调度器有能力预测其决策对CPU所消耗的能量的影响。EAS依靠 +一个能量模型(EM)来为每个任务选择一个节能的CPU,同时最小化对吞吐率的影响。 +本文档致力于介绍介绍EAS是如何工作的,它背后的主要设计决策是什么,以及使其运行 +所需的条件细节。 + +在进一步阅读之前,请注意,在撰写本文时:: + + /!\ EAS不支持对称CPU拓扑的平台 /!\ + +EAS只在异构CPU拓扑结构(如Arm大小核,big.LITTLE)上运行。因为在这种情况下, +通过调度来节约能量的潜力是最大的。 + +EAS实际使用的EM不是由调度器维护的,而是一个专门的框架。关于这个框架的细节和 +它提供的内容,请参考其文档(见Documentation/power/energy-model.rst)。 + + +2. 背景和术语 +------------- + +从一开始就说清楚定义: + - 能量 = [焦耳] (比如供电设备上的电池提供的资源) + - 功率 = 能量/时间 = [焦耳/秒] = [瓦特] + + EAS的目标是最小化能量消耗,同时仍能将工作完成。也就是说,我们要最大化:: + + 性能 [指令数/秒] + ---------------- + 功率 [瓦特] + +它等效于最小化:: + + 能量 [焦耳] + ----------- + 指令数 + +同时仍然获得“良好”的性能。当前调度器只考虑性能目标,因此该式子本质上是一个 +可选的优化目标,它同时考虑了两个目标:能量效率和性能。 + +引入EM的想法是为了让调度器评估其决策的影响,而不是盲目地应用可能仅在部分 +平台有正面效果的节能技术。同时,EM必须尽可能的简单,以最小化调度器的时延 +影响。 + +简而言之,EAS改变了CFS任务分配给CPU的方式。当调度器决定一个任务应该在哪里 +运行时(在唤醒期间),EM被用来在不损害系统吞吐率的情况下,从几个较好的候选 +CPU中挑选一个经预测能量消耗最优的CPU。EAS的预测依赖于对平台拓扑结构特定元素 +的了解,包括CPU的“算力”,以及它们各自的能量成本。 + + +3. 拓扑信息 +----------- + +EAS(以及调度器的剩余部分)使用“算力”的概念来区分不同计算吞吐率的CPU。一个 +CPU的“算力”代表了它在最高频率下运行时能完成的工作量,且这个值是相对系统中 +算力最大的CPU而言的。算力值被归一化为1024以内,并且可与由实体负载跟踪 +(PELT)机制算出的利用率信号做对比。由于有算力值和利用率值,EAS能够估计一个 +任务/CPU有多大/有多忙,并在评估性能与能量时将其考虑在内。CPU算力由特定体系 +结构实现的arch_scale_cpu_capacity()回调函数提供。 + +EAS使用的其余平台信息是直接从能量模型(EM)框架中读取的。一个平台的EM是一张 +表,表中每项代表系统中一个“性能域”的功率成本。(若要了解更多关于性能域的细节, +见Documentation/power/energy-model.rst) + +当调度域被建立或重新建立时,调度器管理对拓扑代码中EM对象的引用。对于每个根域 +(rd),调度器维护一个与当前rd->span相交的所有性能域的单向链表。链表中的每个 +节点都包含一个指向EM框架所提供的结构体em_perf_domain的指针。 + +链表被附加在根域上,以应对独占的cpuset的配置。由于独占的cpuset的边界不一定与 +性能域的边界一致,不同根域的链表可能包含重复的元素。 + +示例1 + 让我们考虑一个有12个CPU的平台,分成3个性能域,(pd0,pd4和pd8),按以下 + 方式组织:: + + CPUs: 0 1 2 3 4 5 6 7 8 9 10 11 + PDs: |--pd0--|--pd4--|---pd8---| + RDs: |----rd1----|-----rd2-----| + + 现在,考虑用户空间决定将系统分成两个独占的cpusets,因此创建了两个独立的根域, + 每个根域包含6个CPU。这两个根域在上图中被表示为rd1和rd2。由于pd4与rd1和rd2 + 都有交集,它将同时出现于附加在这两个根域的“->pd”链表中: + + * rd1->pd: pd0 -> pd4 + * rd2->pd: pd4 -> pd8 + + 请注意,调度器将为pd4创建两个重复的链表节点(每个链表中各一个)。然而这 + 两个节点持有指向同一个EM框架的共享数据结构的指针。 + +由于对这些链表的访问可能与热插拔及其它事件并发发生,因此它们受RCU锁保护,就像 +被调度器操控的拓扑结构体中剩下字段一样。 + +EAS同样维护了一个静态键(sched_energy_present),当至少有一个根域满足EAS +启动的所有条件时,这个键就会被启动。在第6节中总结了这些条件。 + + +4. 能量感知任务放置 +------------------- + +EAS覆盖了CFS的任务唤醒平衡代码。在唤醒平衡时,它使用平台的EM和PELT信号来选择节能 +的目标CPU。当EAS被启用时,select_task_rq_fair()调用find_energy_efficient_cpu() +来做任务放置决定。这个函数寻找在每个性能域中寻找具有最高剩余算力(CPU算力 - CPU +利用率)的CPU,因为它能让我们保持最低的频率。然后,该函数检查将任务放在新CPU相较 +依然放在之前活动的prev_cpu是否可以节省能量。 + +如果唤醒的任务被迁移,find_energy_efficient_cpu()使用compute_energy()来估算 +系统将消耗多少能量。compute_energy()检查各CPU当前的利用率情况,并尝试调整来 +“模拟”任务迁移。EM框架提供了API em_pd_energy()计算每个性能域在给定的利用率条件 +下的预期能量消耗。 + +下面详细介绍一个优化能量消耗的任务放置决策的例子。 + +示例2 + 让我们考虑一个有两个独立性能域的(伪)平台,每个性能域含有2个CPU。CPU0和CPU1 + 是小核,CPU2和CPU3是大核。 + + 调度器必须决定将任务P放在哪个CPU上,这个任务的util_avg = 200(平均利用率), + prev_cpu = 0(上一次运行在CPU0)。 + + 目前CPU的利用率情况如下图所示。CPU 0-3的util_avg分别为400、100、600和500。 + 每个性能域有三个操作性能值(OPP)。与每个OPP相关的CPU算力和功率成本列在能量 + 模型表中。P的util_avg在图中显示为"PP":: + + CPU util. + 1024 - - - - - - - Energy Model + +-----------+-------------+ + | Little | Big | + 768 ============= +-----+-----+------+------+ + | Cap | Pwr | Cap | Pwr | + +-----+-----+------+------+ + 512 =========== - ##- - - - - | 170 | 50 | 512 | 400 | + ## ## | 341 | 150 | 768 | 800 | + 341 -PP - - - - ## ## | 512 | 300 | 1024 | 1700 | + PP ## ## +-----+-----+------+------+ + 170 -## - - - - ## ## + ## ## ## ## + ------------ ------------- + CPU0 CPU1 CPU2 CPU3 + + Current OPP: ===== Other OPP: - - - util_avg (100 each): ## + + + find_energy_efficient_cpu()将首先在两个性能域中寻找具有最大剩余算力的CPU。 + 在这个例子中是CPU1和CPU3。然后,它将估算,当P被放在它们中的任意一个时,系统的 + 能耗,并检查这样做是否会比把P放在CPU0上节省一些能量。EAS假定OPPs遵循利用率 + (这与CPUFreq监管器schedutil的行为一致。关于这个问题的更多细节,见第6节)。 + + **情况1. P被迁移到CPU1**:: + + 1024 - - - - - - - + + Energy calculation: + 768 ============= * CPU0: 200 / 341 * 150 = 88 + * CPU1: 300 / 341 * 150 = 131 + * CPU2: 600 / 768 * 800 = 625 + 512 - - - - - - - ##- - - - - * CPU3: 500 / 768 * 800 = 520 + ## ## => total_energy = 1364 + 341 =========== ## ## + PP ## ## + 170 -## - - PP- ## ## + ## ## ## ## + ------------ ------------- + CPU0 CPU1 CPU2 CPU3 + + + **情况2. P被迁移到CPU3**:: + + 1024 - - - - - - - + + Energy calculation: + 768 ============= * CPU0: 200 / 341 * 150 = 88 + * CPU1: 100 / 341 * 150 = 43 + PP * CPU2: 600 / 768 * 800 = 625 + 512 - - - - - - - ##- - -PP - * CPU3: 700 / 768 * 800 = 729 + ## ## => total_energy = 1485 + 341 =========== ## ## + ## ## + 170 -## - - - - ## ## + ## ## ## ## + ------------ ------------- + CPU0 CPU1 CPU2 CPU3 + + **情况3. P依旧留在prev_cpu/CPU0**:: + + 1024 - - - - - - - + + Energy calculation: + 768 ============= * CPU0: 400 / 512 * 300 = 234 + * CPU1: 100 / 512 * 300 = 58 + * CPU2: 600 / 768 * 800 = 625 + 512 =========== - ##- - - - - * CPU3: 500 / 768 * 800 = 520 + ## ## => total_energy = 1437 + 341 -PP - - - - ## ## + PP ## ## + 170 -## - - - - ## ## + ## ## ## ## + ------------ ------------- + CPU0 CPU1 CPU2 CPU3 + + 从这些计算结果来看,情况1的总能量最低。所以从节约能量的角度看,CPU1是最佳候选 + 者。 + +大核通常比小核更耗电,因此主要在任务不适合在小核运行时使用。然而,小核并不总是比 +大核节能。举例来说,对于某些系统,小核的高OPP可能比大核的低OPP能量消耗更高。因此, +如果小核在某一特定时间点刚好有足够的利用率,在此刻被唤醒的小任务放在大核执行可能 +会更节能,尽管它在小核上运行也是合适的。 + +即使在大核所有OPP都不如小核OPP节能的情况下,在某些特定条件下,令小任务运行在大核 +上依然可能节能。事实上,将一个任务放在一个小核上可能导致整个性能域的OPP提高,这将 +增加已经在该性能域运行的任务的能量成本。如果唤醒的任务被放在一个大核上,它的执行 +成本可能比放在小核上更高,但这不会影响小核上的其它任务,这些任务将继续以较低的OPP +运行。因此,当考虑CPU消耗的总能量时,在大核上运行一个任务的额外成本可能小于为所有 +其它运行在小核的任务提高OPP的成本。 + +上面的例子几乎不可能以一种通用的方式得到正确的结果;同时,对于所有平台,在不知道 +系统所有CPU每个不同OPP的运行成本时,也无法得到正确的结果。得益于基于EM的设计, +EAS应该能够正确处理这些问题而不会引发太多麻烦。然而,为了确保对高利用率场景的 +吞吐率造成的影响最小化,EAS同样实现了另外一种叫“过度利用率”的机制。 + + +5. 过度利用率 +------------- + +从一般的角度来看,EAS能提供最大帮助的是那些涉及低、中CPU利用率的使用场景。每当CPU +密集型的长任务运行,它们将需要所有的可用CPU算力,调度器将没有什么办法来节省能量同时 +又不损害吞吐率。为了避免EAS损害性能,一旦CPU被使用的算力超过80%,它将被标记为“过度 +利用”。只要根域中没有CPU是过度利用状态,负载均衡被禁用,而EAS将覆盖唤醒平衡代码。 +EAS很可能将负载放置在系统中能量效率最高的CPU而不是其它CPU上,只要不损害吞吐率。 +因此,负载均衡器被禁用以防止它打破EAS发现的节能任务放置。当系统未处于过度利用状态时, +这样做是安全的,因为低于80%的临界点意味着: + + a. 所有的CPU都有一些空闲时间,所以EAS使用的利用率信号很可能准确地代表各种任务 + 的“大小”。 + b. 所有任务,不管它们的nice值是多大,都应该被提供了足够多的CPU算力。 + c. 既然有多余的算力,那么所有的任务都必须定期阻塞/休眠,在唤醒时进行平衡就足够 + 了。 + +只要一个CPU利用率超过80%的临界点,上述三个假设中至少有一个是不正确的。在这种情况下, +整个根域的“过度利用”标志被设置,EAS被禁用,负载均衡器被重新启用。通过这样做,调度器 +又回到了在CPU密集的条件下基于负载的算法做负载均衡。这更好地尊重了任务的nice值。 + +由于过度利用率的概念在很大程度上依赖于检测系统中是否有一些空闲时间,所以必须考虑 +(比CFS)更高优先级的调度类(以及中断)“窃取”的CPU算力。像这样,对过度使用率的检测 +不仅要考虑CFS任务使用的算力,还需要考虑其它调度类和中断。 + + +6. EAS的依赖和要求 +------------------ + +能量感知调度依赖系统的CPU具有特定的硬件属性,以及内核中的其它特性被启用。本节列出 +了这些依赖,并对如何满足这些依赖提供了提示。 + + +6.1 - 非对称CPU拓扑 +^^^^^^^^^^^^^^^^^^^ + + +如简介所提,目前只有非对称CPU拓扑结构的平台支持EAS。通过在运行时查询 +SD_ASYM_CPUCAPACITY_FULL标志位是否在创建调度域时已设置来检查这一要求是否满足。 + +参阅Documentation/scheduler/sched-capacity.rst以了解在sched_domain层次结构 +中设置此标志位所需满足的要求。 + +请注意,EAS并非从根本上与SMP不兼容,但在SMP平台上还没有观察到明显的节能。这一 +限制可以在将来进行修改,如果被证明不是这样的话。 + + +6.2 - 当前的能量模型 +^^^^^^^^^^^^^^^^^^^^ + +EAS使用一个平台的EM来估算调度决策对能量的影响。因此,你的平台必须向EM框架提供 +能量成本表,以启动EAS。要做到这一点,请参阅文档 +Documentation/power/energy-model.rst中的独立EM框架部分。 + +另请注意,调度域需要在EM注册后重建,以便启动EAS。 + +EAS使用EM对能量使用率进行预测决策,因此它在检查任务放置的可能选项时更加注重 +差异。对于EAS来说,EM的功率值是以毫瓦还是以“抽象刻度”为单位表示并不重要。 + + + +6.3 - 能量模型复杂度 +^^^^^^^^^^^^^^^^^^^^ + +任务唤醒路径是时延敏感的。当一个平台的EM太复杂(太多CPU,太多性能域,太多状态 +等),在唤醒路径中使用它的成本就会升高到不可接受。能量感知唤醒算法的复杂度为: + + C = Nd * (Nc + Ns) + +其中:Nd是性能域的数量;Nc是CPU的数量;Ns是OPP的总数(例如:对于两个性能域, +每个域有4个OPP,则Ns = 8)。 + +当调度域建立时,复杂性检查是在根域上进行的。如果一个根域的复杂度C恰好高于完全 +主观设定的EM_MAX_COMPLEXITY阈值(在本文写作时,是2048),则EAS不会在此根域 +启动。 + +如果你的平台的能量模型的复杂度太高,EAS无法在这个根域上使用,但你真的想用, +那么你就只剩下两个可能的选择: + + 1. 将你的系统拆分成分离的、较小的、使用独占cpuset的根域,并在每个根域局部 + 启用EAS。这个方案的好处是开箱即用,但缺点是无法在根域之间实现负载均衡, + 这可能会导致总体系统负载不均衡。 + 2. 提交补丁以降低EAS唤醒算法的复杂度,从而使其能够在合理的时间内处理更大 + 的EM。 + + +6.4 - Schedutil监管器 +^^^^^^^^^^^^^^^^^^^^^ + +EAS试图预测CPU在不久的将来会在哪个OPP下运行,以估算它们的能量消耗。为了做到 +这一点,它假定CPU的OPP跟随CPU利用率变化而变化。 + +尽管在实践中很难对这一假设的准确性提供硬性保证(因为,举例来说硬件可能不会做 +它被要求做的事情),相对于其他CPUFreq监管器,schedutil至少_请求_使用利用率 +信号计算的频率。因此,与EAS一起使用的唯一合理的监管器是schedutil,因为它是 +唯一一个在频率请求和能量预测之间提供某种程度的一致性的监管器。 + +不支持将EAS与schedutil之外的任何其它监管器一起使用。 + + +6.5 刻度不变性使用率信号 +^^^^^^^^^^^^^^^^^^^^^^^^ + +为了对不同的CPU和所有的性能状态做出准确的预测,EAS需要频率不变的和CPU不变的 +PELT信号。这些信号可以通过每个体系结构定义的arch_scale{cpu,freq}_capacity() +回调函数获取。 + +不支持在没有实现这两个回调函数的平台上使用EAS。 + + +6.6 多线程(SMT) +^^^^^^^^^^^^^^^^^ + +当前实现的EAS是不感知SMT的,因此无法利用多线程硬件节约能量。EAS认为线程是独立的 +CPU,这实际上对性能和能量消耗都是不利的。 + +不支持在SMT上使用EAS。 diff --git a/Documentation/translations/zh_CN/scheduler/sched-nice-design.rst b/Documentation/translations/zh_CN/scheduler/sched-nice-design.rst new file mode 100644 index 000000000..9107f0c0b --- /dev/null +++ b/Documentation/translations/zh_CN/scheduler/sched-nice-design.rst @@ -0,0 +1,99 @@ +.. SPDX-License-Identifier: GPL-2.0 +.. include:: ../disclaimer-zh_CN.rst + +:Original: Documentation/scheduler/sched-nice-design.rst + +:翻译: + + 唐艺舟 Tang Yizhou <tangyeechou@gmail.com> + +===================== +调度器nice值设计 +===================== + +本文档解释了新的Linux调度器中修改和精简后的nice级别的实现思路。 + +Linux的nice级别总是非常脆弱,人们持续不断地缠着我们,让nice +19的任务占用 +更少的CPU时间。 + +不幸的是,在旧的调度器中,这不是那么容易实现的(否则我们早就做到了),因为对 +nice级别的支持在历史上是与时间片长度耦合的,而时间片单位是由HZ滴答驱动的, +所以最小的时间片是1/HZ。 + +在O(1)调度器中(2003年),我们改变了负的nice级别,使它们比2.4内核更强 +(人们对这一变化很满意),而且我们还故意校正了线性时间片准则,使得nice +19 +的级别 _正好_ 是1 jiffy。为了让大家更好地理解它,时间片的图会是这样的(质量 +不佳的ASCII艺术提醒!):: + + + A + \ | [timeslice length] + \ | + \ | + \ | + \ | + \|___100msecs + |^ . _ + | ^ . _ + | ^ . _ + -*----------------------------------*-----> [nice level] + -20 | +19 + | + | + +因此,如果有人真的想renice任务,相较线性规则,+19会给出更大的效果(改变 +ABI来扩展优先级的解决方案在早期就被放弃了)。 + +这种方法在一定程度上奏效了一段时间,但后来HZ=1000时,它导致1 jiffy为 +1ms,这意味着0.1%的CPU使用率,我们认为这有点过度。过度 _不是_ 因为它表示 +的CPU使用率过小,而是因为它引发了过于频繁(每毫秒1次)的重新调度(因此会 +破坏缓存,等等。请记住,硬件更弱、cache更小是很久以前的事了,当时人们在 +nice +19级别运行数量颇多的应用程序)。 + +因此,对于HZ=1000,我们将nice +19改为5毫秒,因为这感觉像是正确的最小 +粒度——这相当于5%的CPU利用率。但nice +19的根本的HZ敏感属性依然保持不变, +我们没有收到过关于nice +19在CPU利用率方面太 _弱_ 的任何抱怨,我们只收到 +过它(依然)太 _强_ 的抱怨 :-)。 + +总结一下:我们一直想让nice各级别一致性更强,但在HZ和jiffies的限制下,以及 +nice级别与时间片、调度粒度耦合是令人讨厌的设计,这一目标并不真正可行。 + +第二个关于Linux nice级别支持的抱怨是(不那么频繁,但仍然定期发生),它 +在原点周围的不对称性(你可以在上面的图片中看到),或者更准确地说:事实上 +nice级别的行为取决于 _绝对的_ nice级别,而nice应用程序接口本身从根本上 +说是“相对”的: + + int nice(int inc); + + asmlinkage long sys_nice(int increment) + +(第一个是glibc的应用程序接口,第二个是syscall的应用程序接口) +注意,“inc”是相对当前nice级别而言的,类似bash的“nice”命令等工具是这个 +相对性应用程序接口的镜像。 + +在旧的调度器中,举例来说,如果你以nice +1启动一个任务,并以nice +2启动 +另一个任务,这两个任务的CPU分配将取决于父外壳程序的nice级别——如果它是 +nice -10,那么CPU的分配不同于+5或+10。 + +第三个关于Linux nice级别支持的抱怨是,负数nice级别“不够有力”,以很多人 +不得不诉诸于实时调度优先级来运行音频(和其它多媒体)应用程序,比如 +SCHED_FIFO。但这也造成了其它问题:SCHED_FIFO未被证明是免于饥饿的,一个 +有问题的SCHED_FIFO应用程序也会锁住运行良好的系统。 + +v2.6.23版内核的新调度器解决了这三种类型的抱怨: + +为了解决第一个抱怨(nice级别不够“有力”),调度器与“时间片”、HZ的概念 +解耦(调度粒度被处理成一个和nice级别独立的概念),因此有可能实现更好、 +更一致的nice +19支持:在新的调度器中,nice +19的任务得到一个HZ无关的 +1.5%CPU使用率,而不是旧版调度器中3%-5%-9%的可变范围。 + +为了解决第二个抱怨(nice各级别不一致),新调度器令调用nice(1)对各任务的 +CPU利用率有相同的影响,无论其绝对nice级别如何。所以在新调度器上,运行一个 +nice +10和一个nice +11的任务会与运行一个nice -5和一个nice -4的任务的 +CPU利用率分割是相同的(一个会得到55%的CPU,另一个会得到45%)。这是为什么 +nice级别被改为“乘法”(或指数)——这样的话,不管你从哪个级别开始,“相对” +结果将总是一样的。 + +第三个抱怨(负数nice级别不够“有力”,并迫使音频应用程序在更危险的 +SCHED_FIFO调度策略下运行)几乎被新的调度器自动解决了:更强的负数级别 +具有重新校正nice级别动态范围的自动化副作用。 diff --git a/Documentation/translations/zh_CN/scheduler/sched-stats.rst b/Documentation/translations/zh_CN/scheduler/sched-stats.rst new file mode 100644 index 000000000..c5e0be663 --- /dev/null +++ b/Documentation/translations/zh_CN/scheduler/sched-stats.rst @@ -0,0 +1,156 @@ +.. SPDX-License-Identifier: GPL-2.0 +.. include:: ../disclaimer-zh_CN.rst + +:Original: Documentation/scheduler/sched-stats.rst + +:翻译: + + 唐艺舟 Tang Yizhou <tangyeechou@gmail.com> + +============== +调度器统计数据 +============== + +第15版schedstats去掉了sched_yield的一些计数器:yld_exp_empty,yld_act_empty +和yld_both_empty。在其它方面和第14版完全相同。 + +第14版schedstats包括对sched_domains(译注:调度域)的支持,该特性进入内核 +主线2.6.20,不过这一版schedstats与2.6.13-2.6.19内核的版本12的统计数据是完全 +相同的(内核未发布第13版)。有些计数器按每个运行队列统计是更有意义的,其它则 +按每个调度域统计是更有意义的。注意,调度域(以及它们的附属信息)仅在开启 +CONFIG_SMP的机器上是相关的和可用的。 + +在第14版schedstat中,每个被列出的CPU至少会有一级域统计数据,且很可能有一个 +以上的域。在这个实现中,域没有特别的名字,但是编号最高的域通常在机器上所有的 +CPU上仲裁平衡,而domain0是最紧密聚焦的域,有时仅在一对CPU之间进行平衡。此时, +没有任何体系结构需要3层以上的域。域统计数据中的第一个字段是一个位图,表明哪些 +CPU受该域的影响。 + +这些字段是计数器,而且只能递增。使用这些字段的程序将需要从基线观测开始,然后在 +后续每一个观测中计算出计数器的变化。一个能以这种方式处理其中很多字段的perl脚本 +可见 + + http://eaglet.pdxhosts.com/rick/linux/schedstat/ + +请注意,任何这样的脚本都必须是特定于版本的,改变版本的主要原因是输出格式的变化。 +对于那些希望编写自己的脚本的人,可以参考这里描述的各个字段。 + +CPU统计数据 +----------- +cpu<N> 1 2 3 4 5 6 7 8 9 + +第一个字段是sched_yield()的统计数据: + + 1) sched_yield()被调用了#次 + +接下来的三个是schedule()的统计数据: + + 2) 这个字段是一个过时的数组过期计数,在O(1)调度器中使用。为了ABI兼容性, + 我们保留了它,但它总是被设置为0。 + 3) schedule()被调用了#次 + 4) 调用schedule()导致处理器变为空闲了#次 + +接下来的两个是try_to_wake_up()的统计数据: + + 5) try_to_wake_up()被调用了#次 + 6) 调用try_to_wake_up()导致本地CPU被唤醒了#次 + +接下来的三个统计数据描述了调度延迟: + + 7) 本处理器运行任务的总时间,单位是纳秒 + 8) 本处理器任务等待运行的时间,单位是纳秒 + 9) 本CPU运行了#个时间片 + +域统计数据 +---------- + +对于每个被描述的CPU,和它相关的每一个调度域均会产生下面一行数据(注意,如果 +CONFIG_SMP没有被定义,那么*没有*调度域被使用,这些行不会出现在输出中)。 + +domain<N> <cpumask> 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 + +第一个字段是一个位掩码,表明该域在操作哪些CPU。 + +接下来的24个字段是load_balance()函数的各个统计数据,按空闲类型分组(空闲, +繁忙,新空闲): + + + 1) 当CPU空闲时,load_balance()在这个调度域中被调用了#次 + 2) 当CPU空闲时,load_balance()在这个调度域中被调用,但是发现负载无需 + 均衡#次 + 3) 当CPU空闲时,load_balance()在这个调度域中被调用,试图迁移1个或更多 + 任务且失败了#次 + 4) 当CPU空闲时,load_balance()在这个调度域中被调用,发现不均衡(如果有) + #次 + 5) 当CPU空闲时,pull_task()在这个调度域中被调用#次 + 6) 当CPU空闲时,尽管目标任务是热缓存状态,pull_task()依然被调用#次 + 7) 当CPU空闲时,load_balance()在这个调度域中被调用,未能找到更繁忙的 + 队列#次 + 8) 当CPU空闲时,在调度域中找到了更繁忙的队列,但未找到更繁忙的调度组 + #次 + 9) 当CPU繁忙时,load_balance()在这个调度域中被调用了#次 + 10) 当CPU繁忙时,load_balance()在这个调度域中被调用,但是发现负载无需 + 均衡#次 + 11) 当CPU繁忙时,load_balance()在这个调度域中被调用,试图迁移1个或更多 + 任务且失败了#次 + 12) 当CPU繁忙时,load_balance()在这个调度域中被调用,发现不均衡(如果有) + #次 + 13) 当CPU繁忙时,pull_task()在这个调度域中被调用#次 + 14) 当CPU繁忙时,尽管目标任务是热缓存状态,pull_task()依然被调用#次 + 15) 当CPU繁忙时,load_balance()在这个调度域中被调用,未能找到更繁忙的 + 队列#次 + 16) 当CPU繁忙时,在调度域中找到了更繁忙的队列,但未找到更繁忙的调度组 + #次 + 17) 当CPU新空闲时,load_balance()在这个调度域中被调用了#次 + 18) 当CPU新空闲时,load_balance()在这个调度域中被调用,但是发现负载无需 + 均衡#次 + 19) 当CPU新空闲时,load_balance()在这个调度域中被调用,试图迁移1个或更多 + 任务且失败了#次 + 20) 当CPU新空闲时,load_balance()在这个调度域中被调用,发现不均衡(如果有) + #次 + 21) 当CPU新空闲时,pull_task()在这个调度域中被调用#次 + 22) 当CPU新空闲时,尽管目标任务是热缓存状态,pull_task()依然被调用#次 + 23) 当CPU新空闲时,load_balance()在这个调度域中被调用,未能找到更繁忙的 + 队列#次 + 24) 当CPU新空闲时,在调度域中找到了更繁忙的队列,但未找到更繁忙的调度组 + #次 + +接下来的3个字段是active_load_balance()函数的各个统计数据: + + 25) active_load_balance()被调用了#次 + 26) active_load_balance()被调用,试图迁移1个或更多任务且失败了#次 + 27) active_load_balance()被调用,成功迁移了#次任务 + +接下来的3个字段是sched_balance_exec()函数的各个统计数据: + + 28) sbe_cnt不再被使用 + 29) sbe_balanced不再被使用 + 30) sbe_pushed不再被使用 + +接下来的3个字段是sched_balance_fork()函数的各个统计数据: + + 31) sbf_cnt不再被使用 + 32) sbf_balanced不再被使用 + 33) sbf_pushed不再被使用 + +接下来的3个字段是try_to_wake_up()函数的各个统计数据: + + 34) 在这个调度域中调用try_to_wake_up()唤醒任务时,任务在调度域中一个 + 和上次运行不同的新CPU上运行了#次 + 35) 在这个调度域中调用try_to_wake_up()唤醒任务时,任务被迁移到发生唤醒 + 的CPU次数为#,因为该任务在原CPU是冷缓存状态 + 36) 在这个调度域中调用try_to_wake_up()唤醒任务时,引发被动负载均衡#次 + +/proc/<pid>/schedstat +--------------------- +schedstats还添加了一个新的/proc/<pid>/schedstat文件,来提供一些进程级的 +相同信息。这个文件中,有三个字段与该进程相关: + + 1) 在CPU上运行花费的时间(单位是纳秒) + 2) 在运行队列上等待的时间(单位是纳秒) + 3) 在CPU上运行了#个时间片 + +可以很容易地编写一个程序,利用这些额外的字段来报告一个特定的进程或一组进程在 +调度器策略下的表现如何。这样的程序的一个简单版本可在下面的链接找到 + + http://eaglet.pdxhosts.com/rick/linux/schedstat/v12/latency.c diff --git a/Documentation/translations/zh_CN/scheduler/schedutil.rst b/Documentation/translations/zh_CN/scheduler/schedutil.rst new file mode 100644 index 000000000..d1ea68007 --- /dev/null +++ b/Documentation/translations/zh_CN/scheduler/schedutil.rst @@ -0,0 +1,165 @@ +.. SPDX-License-Identifier: GPL-2.0 +.. include:: ../disclaimer-zh_CN.rst + +:Original: Documentation/scheduler/schedutil.rst + +:翻译: + + 唐艺舟 Tang Yizhou <tangyeechou@gmail.com> + +========= +Schedutil +========= + +.. note:: + + 本文所有内容都假设频率和工作算力之间存在线性关系。我们知道这是有瑕疵的, + 但这是最可行的近似处理。 + +PELT(实体负载跟踪,Per Entity Load Tracking) +============================================== + +通过PELT,我们跟踪了各种调度器实体的一些指标,从单个任务到任务组分片到CPU +运行队列。我们使用指数加权移动平均数(Exponentially Weighted Moving Average, +EWMA)作为其基础,每个周期(1024us)都会衰减,衰减速率满足y^32 = 0.5。 +也就是说,最近的32ms贡献负载的一半,而历史上的其它时间则贡献另一半。 + +具体而言: + + ewma_sum(u) := u_0 + u_1*y + u_2*y^2 + ... + + ewma(u) = ewma_sum(u) / ewma_sum(1) + +由于这本质上是一个无限几何级数的累加,结果是可组合的,即ewma(A) + ewma(B) = ewma(A+B)。 +这个属性是关键,因为它提供了在任务迁移时重新组合平均数的能力。 + +请注意,阻塞态的任务仍然对累加值(任务组分片和CPU运行队列)有贡献,这反映了 +它们在恢复运行后的预期贡献。 + +利用这一点,我们跟踪2个关键指标:“运行”和“可运行”。“运行”反映了一个调度实体 +在CPU上花费的时间,而“可运行”反映了一个调度实体在运行队列中花费的时间。当只有 +一个任务时,这两个指标是相同的,但一旦出现对CPU的争用,“运行”将减少以反映每个 +任务在CPU上花费的时间,而“可运行”将增加以反映争用的激烈程度。 + +更多细节见:kernel/sched/pelt.c + + +频率 / CPU不变性 +================ + +因为CPU频率在1GHz时利用率为50%和CPU频率在2GHz时利用率为50%是不一样的,同样 +在小核上运行时利用率为50%和在大核上运行时利用率为50%是不一样的,我们允许架构 +以两个比率来伸缩时间差,其中一个是动态电压频率升降(Dynamic Voltage and +Frequency Scaling,DVFS)比率,另一个是微架构比率。 + +对于简单的DVFS架构(软件有完全控制能力),我们可以很容易地计算该比率为:: + + f_cur + r_dvfs := ----- + f_max + +对于由硬件控制DVFS的更多动态系统,我们使用硬件计数器(Intel APERF/MPERF, +ARMv8.4-AMU)来计算这一比率。具体到Intel,我们使用:: + + APERF + f_cur := ----- * P0 + MPERF + + 4C-turbo; 如果可用并且使能了turbo + f_max := { 1C-turbo; 如果使能了turbo + P0; 其它情况 + + f_cur + r_dvfs := min( 1, ----- ) + f_max + +我们选择4C turbo而不是1C turbo,以使其更持久性略微更强。 + +r_cpu被定义为当前CPU的最高性能水平与系统中任何其它CPU的最高性能水平的比率。 + + r_tot = r_dvfs * r_cpu + +其结果是,上述“运行”和“可运行”的指标变成DVFS无关和CPU型号无关了。也就是说, +我们可以在CPU之间转移和比较它们。 + +更多细节见: + + - kernel/sched/pelt.h:update_rq_clock_pelt() + - arch/x86/kernel/smpboot.c:"APERF/MPERF frequency ratio computation." + - Documentation/translations/zh_CN/scheduler/sched-capacity.rst:"1. CPU Capacity + 2. Task utilization" + + +UTIL_EST / UTIL_EST_FASTUP +========================== + +由于周期性任务的平均数在睡眠时会衰减,而在运行时其预期利用率会和睡眠前相同, +因此它们在再次运行后会面临(DVFS)的上涨。 + +为了缓解这个问题,(一个默认使能的编译选项)UTIL_EST驱动一个无限脉冲响应 +(Infinite Impulse Response,IIR)的EWMA,“运行”值在出队时是最高的。 +另一个默认使能的编译选项UTIL_EST_FASTUP修改了IIR滤波器,使其允许立即增加, +仅在利用率下降时衰减。 + +进一步,运行队列的(可运行任务的)利用率之和由下式计算: + + util_est := \Sum_t max( t_running, t_util_est_ewma ) + +更多细节见: kernel/sched/fair.c:util_est_dequeue() + + +UCLAMP +====== + +可以在每个CFS或RT任务上设置有效的u_min和u_max clamp值(译注:clamp可以理解 +为类似滤波器的能力,它定义了有效取值范围的最大值和最小值);运行队列为所有正在 +运行的任务保持这些clamp的最大聚合值。 + +更多细节见: include/uapi/linux/sched/types.h + + +Schedutil / DVFS +================ + +每当调度器的负载跟踪被更新时(任务唤醒、任务迁移、时间流逝),我们都会调用 +schedutil来更新硬件DVFS状态。 + +其基础是CPU运行队列的“运行”指标,根据上面的内容,它是CPU的频率不变的利用率 +估计值。由此我们计算出一个期望的频率,如下:: + + max( running, util_est ); 如果使能UTIL_EST + u_cfs := { running; 其它情况 + + clamp( u_cfs + u_rt, u_min, u_max ); 如果使能UCLAMP_TASK + u_clamp := { u_cfs + u_rt; 其它情况 + + u := u_clamp + u_irq + u_dl; [估计值。更多细节见源代码] + + f_des := min( f_max, 1.25 u * f_max ) + +关于IO-wait的说明:当发生更新是因为任务从IO完成中唤醒时,我们提升上面的“u”。 + +然后,这个频率被用来选择一个P-state或OPP,或者直接混入一个发给硬件的CPPC式 +请求。 + +关于截止期限调度器的说明: 截止期限任务(偶发任务模型)使我们能够计算出满足 +工作负荷所需的硬f_min值。 + +因为这些回调函数是直接来自调度器的,所以DVFS的硬件交互应该是“快速”和非阻塞的。 +在硬件交互缓慢和昂贵的时候,schedutil支持DVFS请求限速,不过会降低效率。 + +更多信息见: kernel/sched/cpufreq_schedutil.c + + +注意 +==== + + - 在低负载场景下,DVFS是最相关的,“运行”的值将密切反映利用率。 + + - 在负载饱和的场景下,任务迁移会导致一些瞬时性的使用率下降。假设我们有一个 + CPU,有4个任务占用导致其饱和,接下来我们将一个任务迁移到另一个空闲CPU上, + 旧的CPU的“运行”值将为0.75,而新的CPU将获得0.25。这是不可避免的,而且随着 + 时间流逝将自动修正。另注,由于没有空闲时间,我们还能保证f_max值吗? + + - 上述大部分内容是关于避免DVFS下滑,以及独立的DVFS域发生负载迁移时不得不 + 重新学习/提升频率。 + diff --git a/Documentation/translations/zh_CN/sound/hd-audio/controls.rst b/Documentation/translations/zh_CN/sound/hd-audio/controls.rst new file mode 100644 index 000000000..54c028ab9 --- /dev/null +++ b/Documentation/translations/zh_CN/sound/hd-audio/controls.rst @@ -0,0 +1,102 @@ +.. SPDX-License-Identifier: GPL-2.0 + +Chinese translator: Huang Jianghui <huangjianghui@uniontech.com> +--------------------------------------------------------------------- +.. include:: ../../disclaimer-zh_CN.rst +以下为正文 +--------------------------------------------------------------------- +====================================== +高清音频编解码器特定混音器控件 +====================================== + + +此文件解释特定于编解码器的混音器控件. + +瑞昱编解码器 +------------ + +声道模式 + 这是一个用于更改环绕声道设置的枚举控件,仅在环绕声道打开时显示出现。 + 它给出要使用的通道数:"2ch","4ch","6ch",和"8ch"。根据配置,这还控 + 制多I/O插孔的插孔重分配。 + +自动静音模式 + 这是一个枚举控件,用于更改耳机和线路输出插孔的自动静音行为。如果内 + 置扬声器、耳机和/或线路输出插孔在机器上可用,则显示该控件。当只有 + 耳机或者线路输出的时候,它给出”禁用“和”启用“状态。当启用后,插孔插 + 入后扬声器会自动静音。 + + 当耳机和线路输出插孔都存在时,它给出”禁用“、”仅扬声器“和”线路输出+扬 + 声器“。当”仅扬声器“被选择,插入耳机或者线路输出插孔可使扬声器静音, + 但不会使线路输出静音。当线路输出+扬声器被选择,插入耳机插孔会同时使扬 + 声器和线路输出静音。 + + +矽玛特编解码器 +-------------- + +模拟环回 + 此控件启用/禁用模拟环回电路。只有在编解码器提示中将”lookback“设置为真 + 时才会出现(见HD-Audio.txt)。请注意,在某些编解码器上,模拟环回和正常 + PCM播放是独占的,即当此选项打开时,您将听不到任何PCM流。 + +交换中置/低频 + 交换中置和低频通道顺序,通常情况下,左侧对应中置,右侧对应低频,启动此 + 项后,左边低频,右边中置。 + +耳机作为线路输出 + 当此控制开启时,将耳机视为线路输出插孔。也就是说,耳机不会自动静音其他 + 线路输出,没有耳机放大器被设置到引脚上。 + +麦克风插口模式、线路插孔模式等 + 这些枚举控制输入插孔引脚的方向和偏置。根据插孔类型,它可以设置为”麦克风 + 输入“和”线路输入“以确定输入偏置,或者当引脚是环绕声道的多I/O插孔时,它 + 可以设置为”线路输出“。 + + +威盛编解码器 +------------ + +智能5.1 + 一个枚举控件,用于为环绕输出重新分配多个I/O插孔的任务。当它打开时,相应 + 的输入插孔(通常是线路输入和麦克风输入)被切换为环绕和中央低频输出插孔。 + +独立耳机 + 启用此枚举控制时,耳机输出从单个流(第三个PCM,如hw:0,2)而不是主流路由。 + 如果耳机DAC与侧边或中央低频通道DAC共享,则DAC将自动切换到耳机。 + +环回混合 + 一个用于确定是否启动了模拟环回路由的枚举控件。当它启用后,模拟环回路由到 + 前置通道。同样,耳机与扬声器输出也采用相同的路径。作为一个副作用,当设置 + 此模式后,单个音量控制将不再适用于耳机和扬声器,因为只有一个DAC连接到混 + 音器小部件。 + +动态电源控制 + 此控件决定是否启动每个插孔的动态电源控制检测。启用时,根据插孔的插入情况 + 动态更改组件的电源状态(D0/D3)以节省电量消耗。但是,如果您的系统没有提 + 供正确的插孔检测,这将无法工作;在这种情况下,请关闭此控件。 + +插孔检测 + 此控件仅为VT1708编解码器提供,它不会为每个插孔插拔提供适当的未请求事件。 + 当此控件打开,驱动将轮询插孔检测,以便耳机自动静音可以工作,而关闭此控 + 件将降低功耗。 + + +科胜讯编解码器 +-------------- + +自动静音模式 + 见瑞昱解码器 + + + +模拟编解码器 +------------ + +通道模式 + 这是一个用于更改环绕声道设置的枚举控件,仅在环绕声道可用时显示。它提供了能 + 被使用的通道数:”2ch“、”4ch“和”6ch“。根据配置,这还控制多I/O插孔的插孔重 + 分配。 + +独立耳机 + 启动此枚举控制后,耳机输出从单个流(第三个PCM,如hw:0,2)而不是主流路由。 diff --git a/Documentation/translations/zh_CN/sound/hd-audio/index.rst b/Documentation/translations/zh_CN/sound/hd-audio/index.rst new file mode 100644 index 000000000..d9885d53b --- /dev/null +++ b/Documentation/translations/zh_CN/sound/hd-audio/index.rst @@ -0,0 +1,14 @@ +.. SPDX-License-Identifier: GPL-2.0 +.. include:: ../../disclaimer-zh_CN.rst + +:Original: :doc:`../../../../sound/hd-audio/index` +:Translator: Huang Jianghui <huangjianghui@uniontech.com> + + +高清音频 +======== + +.. toctree:: + :maxdepth: 2 + + controls diff --git a/Documentation/translations/zh_CN/sound/index.rst b/Documentation/translations/zh_CN/sound/index.rst new file mode 100644 index 000000000..28d5dca34 --- /dev/null +++ b/Documentation/translations/zh_CN/sound/index.rst @@ -0,0 +1,22 @@ +.. SPDX-License-Identifier: GPL-2.0 +.. include:: ../disclaimer-zh_CN.rst + +:Original: :doc:`../../../sound/index` +:Translator: Huang Jianghui <huangjianghui@uniontech.com> + + +==================== +Linux 声音子系统文档 +==================== + +.. toctree:: + :maxdepth: 2 + + hd-audio/index + +.. only:: subproject and html + + Indices + ======= + + * :ref:`genindex` diff --git a/Documentation/translations/zh_CN/video4linux/omap3isp.txt b/Documentation/translations/zh_CN/video4linux/omap3isp.txt new file mode 100644 index 000000000..75e481985 --- /dev/null +++ b/Documentation/translations/zh_CN/video4linux/omap3isp.txt @@ -0,0 +1,277 @@ +Chinese translated version of Documentation/admin-guide/media/omap3isp.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: Laurent Pinchart <laurent.pinchart@ideasonboard.com> + Sakari Ailus <sakari.ailus@iki.fi> + David Cohen <dacohen@gmail.com> +Chinese maintainer: Fu Wei <tekkamanninja@gmail.com> +--------------------------------------------------------------------- +Documentation/admin-guide/media/omap3isp.rst 的中文翻译 + +如果想评论或更新本文的内容,请直接联系原文档的维护者。如果你使用英文 +交流有困难的话,也可以向中文版维护者求助。如果本翻译更新不及时或者翻 +译存在问题,请联系中文版维护者。 +英文版维护者: Laurent Pinchart <laurent.pinchart@ideasonboard.com> + Sakari Ailus <sakari.ailus@iki.fi> + David Cohen <dacohen@gmail.com> +中文版维护者: 傅炜 Fu Wei <tekkamanninja@gmail.com> +中文版翻译者: 傅炜 Fu Wei <tekkamanninja@gmail.com> +中文版校译者: 傅炜 Fu Wei <tekkamanninja@gmail.com> + + +以下为正文 +--------------------------------------------------------------------- +OMAP 3 图像信号处理器 (ISP) 驱动 + +Copyright (C) 2010 Nokia Corporation +Copyright (C) 2009 Texas Instruments, Inc. + +联系人: Laurent Pinchart <laurent.pinchart@ideasonboard.com> + Sakari Ailus <sakari.ailus@iki.fi> + David Cohen <dacohen@gmail.com> + + +介绍 +=== + +本文档介绍了由 drivers/media/video/omap3isp 加载的德州仪器 +(TI)OMAP 3 图像信号处理器 (ISP) 驱动。原始驱动由德州仪器(TI) +编写,但此后由诺基亚重写了两次。 + +驱动已在以下 OMAP 3 系列的芯片中成功使用: + + 3430 + 3530 + 3630 + +驱动实现了 V4L2、媒体控制器和 v4l2_subdev 接口。支持内核中使用 +v4l2_subdev 接口的传感器、镜头和闪光灯驱动。 + + +拆分为子设备 +========== + +OMAP 3 ISP 被拆分为 V4L2 子设备,ISP中的每个模块都由一个子设备 +来表示。每个子设备向用户空间提供一个 V4L2 子设备接口。 + + OMAP3 ISP CCP2 + OMAP3 ISP CSI2a + OMAP3 ISP CCDC + OMAP3 ISP preview + OMAP3 ISP resizer + OMAP3 ISP AEWB + OMAP3 ISP AF + OMAP3 ISP histogram + +ISP 中每个可能的连接都通过一个链接嵌入到媒体控制器接口中。详见例程 [2]。 + + +控制 OMAP 3 ISP +============== + +通常,对 OMAP 3 ISP 的配置会在下一帧起始时生效。在传感器垂直消隐期间, +模块变为空闲时完成配置。在内存到内存的操作中,视频管道一次处理一帧。 +应用配置应在帧间完成。 + +ISP 中的所有模块,除 CSI-2 和 (可能存在的)CCP2 接收器外,都必须 +接收完整的帧数据。因此,传感器必须保证从不发送部分帧数据给ISP。 + +Autoidle(自动空闲)功能至少在 3430 的 ISP 模块中确实存在一些问题。 +当 omap3isp 模块参数 autoidle 非零时,autoidle(自动空闲)功能 +仅在 3630 中启用了。 + + +事件机制 +====== + +OMAP 3 ISP 驱动在 CCDC 和统计(AEWB、AF 和 直方图)子设备中支持 +V4L2 事件机制接口。 + +CCDC 子设备通过 HS_VS 中断,处理 V4L2_EVENT_FRAME_SYNC 类型 +事件,用于告知帧起始。早期版本的驱动则使用 V4L2_EVENT_OMAP3ISP_HS_VS。 +当在 CCDC 模块中接收到起始帧的第一行时,会准确地触发事件。这个事件 +可以在 CCDC 子设备中“订阅”。 + +(当使用并行接口时,必须注意正确地配置 VS 信号极性。而当使用串行接收时 +这个会自动校正。) + +每个统计子设备都可以产生事件。每当一个统计缓冲区可由用户空间应用程序 +通过 VIDIOC_OMAP3ISP_STAT_REQ IOCTL 操作获取时,就会产生一个 +事件。当前存在以下事件: + + V4L2_EVENT_OMAP3ISP_AEWB + V4L2_EVENT_OMAP3ISP_AF + V4L2_EVENT_OMAP3ISP_HIST + +这些 ioctl 的事件数据类型为 struct omap3isp_stat_event_status +结构体。如果出现计算错误的统计,也同样会产生一个事件,但没有相关的统计 +数据缓冲区。这种情况下 omap3isp_stat_event_status.buf_err 会被 +设置为非零值。 + + +私有 IOCTL +========== + +OMAP 3 ISP 驱动支持标准的 V4L2 IOCTL 以及可能存在且实用的控制。但 +ISP 提供的许多功能都不在标准 IOCTL 之列,例如 gamma(伽马)表和统计 +数据采集配置等。 + +通常,会有一个私有 ioctl 用于配置每个包含硬件依赖功能的模块。 + +支持以下私有 IOCTL: + + VIDIOC_OMAP3ISP_CCDC_CFG + VIDIOC_OMAP3ISP_PRV_CFG + VIDIOC_OMAP3ISP_AEWB_CFG + VIDIOC_OMAP3ISP_HIST_CFG + VIDIOC_OMAP3ISP_AF_CFG + VIDIOC_OMAP3ISP_STAT_REQ + VIDIOC_OMAP3ISP_STAT_EN + +在 include/linux/omap3isp.h 中描述了这些 ioctl 使用的参数结构体。 +与特定 ISP 模块相关的 ISP 自身的详细功能在技术参考手册 (TRMs)中有 +描述,详见文档结尾。 + +虽然在不使用任何私有 IOCTL 的情况下使用 ISP 驱动是可能的,但这样无法 +获得最佳的图像质量。AEWB、AF 和 直方图(译者注:一般用于自动曝光和增益 +控制,以及图像均衡等)模块无法在未使用适当的私有 IOCTL 配置的情况下使用。 + + +CCDC 和 preview(预览)模块 IOCTL +=============================== + +VIDIOC_OMAP3ISP_CCDC_CFG 和 VIDIOC_OMAP3ISP_PRV_CFG IOCTL +被分别用于配置、启用和禁用 CCDC 和 preview(预览)模块的功能。在它们 +所控制的模块中,两个 IOCTL 控制多种功能。VIDIOC_OMAP3ISP_CCDC_CFG IOCTL +接受一个指向 omap3isp_ccdc_update_config 结构体的指针作为它的参数。 +同样的,VIDIOC_OMAP3ISP_PRV_CFG 接受一个指向 omap3isp_prev_update_config +结构体的指针。以上两个结构体定义位于 [1]。 + +这些结构体中的 update 域标识是否针对指定的功能更新配置,而 flag 域 +则标识是启用还是禁用此功能。 + +update 和 flag 位接受以下掩码值。CCDC 和 preview(预览)模块的 +每个单独功能都与一个 flag 关联(禁用或启用;在结构体中 flag 域的 +一部分)和一个指向功能配置数据的指针。 + +对于 VIDIOC_OMAP3ISP_CCDC_CFG,下面列出了 update 和 flag 域 +中的有效值。 这些值可能会在同一个 IOCTL 调用中配置多个功能。 + + OMAP3ISP_CCDC_ALAW + OMAP3ISP_CCDC_LPF + OMAP3ISP_CCDC_BLCLAMP + OMAP3ISP_CCDC_BCOMP + OMAP3ISP_CCDC_FPC + OMAP3ISP_CCDC_CULL + OMAP3ISP_CCDC_CONFIG_LSC + OMAP3ISP_CCDC_TBL_LSC + +针对 VIDIOC_OMAP3ISP_PRV_CFG 的相应值如下: + + OMAP3ISP_PREV_LUMAENH + OMAP3ISP_PREV_INVALAW + OMAP3ISP_PREV_HRZ_MED + OMAP3ISP_PREV_CFA + OMAP3ISP_PREV_CHROMA_SUPP + OMAP3ISP_PREV_WB + OMAP3ISP_PREV_BLKADJ + OMAP3ISP_PREV_RGB2RGB + OMAP3ISP_PREV_COLOR_CONV + OMAP3ISP_PREV_YC_LIMIT + OMAP3ISP_PREV_DEFECT_COR + OMAP3ISP_PREV_GAMMABYPASS + OMAP3ISP_PREV_DRK_FRM_CAPTURE + OMAP3ISP_PREV_DRK_FRM_SUBTRACT + OMAP3ISP_PREV_LENS_SHADING + OMAP3ISP_PREV_NF + OMAP3ISP_PREV_GAMMA + +在启用某个功能的时候,相关的配置数据指针不可为 NULL。在禁用某个功能时, +配置数据指针会被忽略。 + + +统计模块 IOCTL +============= + +统计子设备相较于其他子设备提供了更多动态配置选项。在图像处理流水线处于 +工作状态时,它们可以被启用、禁用和重配。 + +统计模块总是从 CCDC 中获取输入的图像数据(由于直方图内存读取未实现)。 +统计数据可由用户通过统计子设备节点使用私有 IOCTL 获取。 + +AEWB、AF 和 直方图子设备提供的私有 IOCTL 极大程度上反应了 ISP 硬件 +提供的寄存器级接口。有些方面纯粹和驱动程序的实现相关,这些将在下面讨论。 + +VIDIOC_OMAP3ISP_STAT_EN +----------------------- + +这个私有 IOCTL 启用/禁用 一个统计模块。如果这个申请在视频流启动前完成, +它将在视频流水线开始工作时生效。如果视频流水线已经处于工作状态了,它将在 +CCDC 变为空闲时生效。 + +VIDIOC_OMAP3ISP_AEWB_CFG, VIDIOC_OMAP3ISP_HIST_CFG and VIDIOC_OMAP3ISP_AF_CFG +----------------------------------------------------------------------------- + +这些 IOCTL 用于配置模块。它们要求用户应用程序对硬件有深入的认识。对 +大多数域的解释可以在 OMAP 的 TRM 中找到。以下两个域对于以上所有的 +私有 IOCTL 配置都很常见,由于他们没有在 TRM 中提及,故需要对其有 +更好的认识。 + +omap3isp_[h3a_af/h3a_aewb/hist]_config.buf_size: + +模块在内部处理自身缓冲。对模块数据输出所必需的缓存大小依赖于已申请的配置。 +虽然驱动支持在视频流工作时重新配置,但对于所需缓存量大于模块启用时内部 +所分配数量的情况,则不支持重新配置。在这种情况下将返回 -EBUSY。为了避免 +此类状况,无论是禁用/重配/启用模块,还是第一次配置时申请必须的缓存大小, +都应在模块禁用的情况下进行。 + +内部缓冲分配的大小需综合考虑所申请配置的最小缓存量以及 buf_size 域中 +所设的值。如果 buf_size 域在[minimum(最小值), maximum(最大值)] +缓冲大小范围之外,则应该将其调整到其范围中。驱动则会选择最大值。正确的 +buf_size 值将回写到用户应用程序中。 + +omap3isp_[h3a_af/h3a_aewb/hist]_config.config_counter: + +由于配置并未在申请之后同步生效,驱动必须提供一个跟踪这类信息的方法, +以提供更准确的数据。在一个配置被申请之后,返回到用户空间应用程序的 +config_counter 是一个与其配置相关的唯一值。当用户应用程序接收到 +一个缓冲可用或一个新的缓冲申请事件时,这个 config_counter 用于 +一个缓冲数据和一个配置的匹配。 + +VIDIOC_OMAP3ISP_STAT_REQ +------------------------ + +将内部缓冲队列中最早的数据发送到用户空间,然后丢弃此缓冲区。 +omap3isp_stat_data.frame_number 域与视频缓冲的 field_count +域相匹配。 + + +技术参考手册 (TRMs) 和其他文档 +========================== + +OMAP 3430 TRM: +<URL:http://focus.ti.com/pdfs/wtbu/OMAP34xx_ES3.1.x_PUBLIC_TRM_vZM.zip> +参考于 2011-03-05. + +OMAP 35xx TRM: +<URL:http://www.ti.com/litv/pdf/spruf98o> 参考于 2011-03-05. + +OMAP 3630 TRM: +<URL:http://focus.ti.com/pdfs/wtbu/OMAP36xx_ES1.x_PUBLIC_TRM_vQ.zip> +参考于 2011-03-05. + +DM 3730 TRM: +<URL:http://www.ti.com/litv/pdf/sprugn4h> 参考于 2011-03-06. + + +参考资料 +======= + +[1] include/linux/omap3isp.h + +[2] http://git.ideasonboard.org/?p=media-ctl.git;a=summary diff --git a/Documentation/translations/zh_CN/video4linux/v4l2-framework.txt b/Documentation/translations/zh_CN/video4linux/v4l2-framework.txt new file mode 100644 index 000000000..a88fcbc11 --- /dev/null +++ b/Documentation/translations/zh_CN/video4linux/v4l2-framework.txt @@ -0,0 +1,976 @@ +Chinese translated version of Documentation/driver-api/media/index.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: Mauro Carvalho Chehab <mchehab@kernel.org> +Chinese maintainer: Fu Wei <tekkamanninja@gmail.com> +--------------------------------------------------------------------- +Documentation/driver-api/media/index.rst 的中文翻译 + +如果想评论或更新本文的内容,请直接联系原文档的维护者。如果你使用英文 +交流有困难的话,也可以向中文版维护者求助。如果本翻译更新不及时或者翻 +译存在问题,请联系中文版维护者。 +英文版维护者: Mauro Carvalho Chehab <mchehab@kernel.org> +中文版维护者: 傅炜 Fu Wei <tekkamanninja@gmail.com> +中文版翻译者: 傅炜 Fu Wei <tekkamanninja@gmail.com> +中文版校译者: 傅炜 Fu Wei <tekkamanninja@gmail.com> + + +以下为正文 +--------------------------------------------------------------------- +V4L2 驱动框架概览 +============== + +本文档描述 V4L2 框架所提供的各种结构和它们之间的关系。 + + +介绍 +---- + +大部分现代 V4L2 设备由多个 IC 组成,在 /dev 下导出多个设备节点, +并同时创建非 V4L2 设备(如 DVB、ALSA、FB、I2C 和红外输入设备)。 +由于这种硬件的复杂性,V4L2 驱动也变得非常复杂。 + +尤其是 V4L2 必须支持 IC 实现音视频的多路复用和编解码,这就更增加了其 +复杂性。通常这些 IC 通过一个或多个 I2C 总线连接到主桥驱动器,但也可 +使用其他总线。这些设备称为“子设备”。 + +长期以来,这个框架仅限于通过 video_device 结构体创建 V4L 设备节点, +并使用 video_buf 处理视频缓冲(注:本文不讨论 video_buf 框架)。 + +这意味着所有驱动必须自己设置设备实例并连接到子设备。其中一部分要正确地 +完成是比较复杂的,使得许多驱动都没有正确地实现。 + +由于框架的缺失,有很多通用代码都不可重复利用。 + +因此,这个框架构建所有驱动都需要的基本结构块,而统一的框架将使通用代码 +创建成实用函数并在所有驱动中共享变得更加容易。 + + +驱动结构 +------- + +所有 V4L2 驱动都有如下结构: + +1) 每个设备实例的结构体--包含其设备状态。 + +2) 初始化和控制子设备的方法(如果有)。 + +3) 创建 V4L2 设备节点 (/dev/videoX、/dev/vbiX 和 /dev/radioX) + 并跟踪设备节点的特定数据。 + +4) 特定文件句柄结构体--包含每个文件句柄的数据。 + +5) 视频缓冲处理。 + +以下是它们的初略关系图: + + device instances(设备实例) + | + +-sub-device instances(子设备实例) + | + \-V4L2 device nodes(V4L2 设备节点) + | + \-filehandle instances(文件句柄实例) + + +框架结构 +------- + +该框架非常类似驱动结构:它有一个 v4l2_device 结构用于保存设备 +实例的数据;一个 v4l2_subdev 结构体代表子设备实例;video_device +结构体保存 V4L2 设备节点的数据;将来 v4l2_fh 结构体将跟踪文件句柄 +实例(暂未尚未实现)。 + +V4L2 框架也可与媒体框架整合(可选的)。如果驱动设置了 v4l2_device +结构体的 mdev 域,子设备和视频节点的入口将自动出现在媒体框架中。 + + +v4l2_device 结构体 +---------------- + +每个设备实例都通过 v4l2_device (v4l2-device.h)结构体来表示。 +简单设备可以仅分配这个结构体,但在大多数情况下,都会将这个结构体 +嵌入到一个更大的结构体中。 + +你必须注册这个设备实例: + + v4l2_device_register(struct device *dev, struct v4l2_device *v4l2_dev); + +注册操作将会初始化 v4l2_device 结构体。如果 dev->driver_data 域 +为 NULL,就将其指向 v4l2_dev。 + +需要与媒体框架整合的驱动必须手动设置 dev->driver_data,指向包含 +v4l2_device 结构体实例的驱动特定设备结构体。这可以在注册 V4L2 设备 +实例前通过 dev_set_drvdata() 函数完成。同时必须设置 v4l2_device +结构体的 mdev 域,指向适当的初始化并注册过的 media_device 实例。 + +如果 v4l2_dev->name 为空,则它将被设置为从 dev 中衍生出的值(为了 +更加精确,形式为驱动名后跟 bus_id)。如果你在调用 v4l2_device_register +前已经设置好了,则不会被修改。如果 dev 为 NULL,则你*必须*在调用 +v4l2_device_register 前设置 v4l2_dev->name。 + +你可以基于驱动名和驱动的全局 atomic_t 类型的实例编号,通过 +v4l2_device_set_name() 设置 name。这样会生成类似 ivtv0、ivtv1 等 +名字。若驱动名以数字结尾,则会在编号和驱动名间插入一个破折号,如: +cx18-0、cx18-1 等。此函数返回实例编号。 + +第一个 “dev” 参数通常是一个指向 pci_dev、usb_interface 或 +platform_device 的指针。很少使其为 NULL,除非是一个ISA设备或者 +当一个设备创建了多个 PCI 设备,使得 v4l2_dev 无法与一个特定的父设备 +关联。 + +你也可以提供一个 notify() 回调,使子设备可以调用它实现事件通知。 +但这个设置与子设备相关。子设备支持的任何通知必须在 +include/media/<subdevice>.h 中定义一个消息头。 + +注销 v4l2_device 使用如下函数: + + v4l2_device_unregister(struct v4l2_device *v4l2_dev); + +如果 dev->driver_data 域指向 v4l2_dev,将会被重置为 NULL。注销同时 +会自动从设备中注销所有子设备。 + +如果你有一个热插拔设备(如USB设备),则当断开发生时,父设备将无效。 +由于 v4l2_device 有一个指向父设备的指针必须被清除,同时标志父设备 +已消失,所以必须调用以下函数: + + v4l2_device_disconnect(struct v4l2_device *v4l2_dev); + +这个函数并*不*注销子设备,因此你依然要调用 v4l2_device_unregister() +函数。如果你的驱动器并非热插拔的,就没有必要调用 v4l2_device_disconnect()。 + +有时你需要遍历所有被特定驱动注册的设备。这通常发生在多个设备驱动使用 +同一个硬件的情况下。如:ivtvfb 驱动是一个使用 ivtv 硬件的帧缓冲驱动, +同时 alsa 驱动也使用此硬件。 + +你可以使用如下例程遍历所有注册的设备: + +static int callback(struct device *dev, void *p) +{ + struct v4l2_device *v4l2_dev = dev_get_drvdata(dev); + + /* 测试这个设备是否已经初始化 */ + if (v4l2_dev == NULL) + return 0; + ... + return 0; +} + +int iterate(void *p) +{ + struct device_driver *drv; + int err; + + /* 在PCI 总线上查找ivtv驱动。 + pci_bus_type是全局的. 对于USB总线使用usb_bus_type。 */ + drv = driver_find("ivtv", &pci_bus_type); + /* 遍历所有的ivtv设备实例 */ + err = driver_for_each_device(drv, NULL, p, callback); + put_driver(drv); + return err; +} + +有时你需要一个设备实例的运行计数。这个通常用于映射一个设备实例到一个 +模块选择数组的索引。 + +推荐方法如下: + +static atomic_t drv_instance = ATOMIC_INIT(0); + +static int drv_probe(struct pci_dev *pdev, const struct pci_device_id *pci_id) +{ + ... + state->instance = atomic_inc_return(&drv_instance) - 1; +} + +如果你有多个设备节点,对于热插拔设备,知道何时注销 v4l2_device 结构体 +就比较困难。为此 v4l2_device 有引用计数支持。当调用 video_register_device +时增加引用计数,而设备节点释放时减小引用计数。当引用计数为零,则 +v4l2_device 的release() 回调将被执行。你就可以在此时做最后的清理工作。 + +如果创建了其他设备节点(比如 ALSA),则你可以通过以下函数手动增减 +引用计数: + +void v4l2_device_get(struct v4l2_device *v4l2_dev); + +或: + +int v4l2_device_put(struct v4l2_device *v4l2_dev); + +由于引用技术初始化为 1 ,你也需要在 disconnect() 回调(对于 USB 设备)中 +调用 v4l2_device_put,或者 remove() 回调(例如对于 PCI 设备),否则 +引用计数将永远不会为 0 。 + +v4l2_subdev结构体 +------------------ + +许多驱动需要与子设备通信。这些设备可以完成各种任务,但通常他们负责 +音视频复用和编解码。如网络摄像头的子设备通常是传感器和摄像头控制器。 + +这些一般为 I2C 接口设备,但并不一定都是。为了给驱动提供调用子设备的 +统一接口,v4l2_subdev 结构体(v4l2-subdev.h)产生了。 + +每个子设备驱动都必须有一个 v4l2_subdev 结构体。这个结构体可以单独 +代表一个简单的子设备,也可以嵌入到一个更大的结构体中,与更多设备状态 +信息保存在一起。通常有一个下级设备结构体(比如:i2c_client)包含了 +内核创建的设备数据。建议使用 v4l2_set_subdevdata() 将这个结构体的 +指针保存在 v4l2_subdev 的私有数据域(dev_priv)中。这使得通过 v4l2_subdev +找到实际的低层总线特定设备数据变得容易。 + +你同时需要一个从低层结构体获取 v4l2_subdev 指针的方法。对于常用的 +i2c_client 结构体,i2c_set_clientdata() 函数可用于保存一个 v4l2_subdev +指针;对于其他总线你可能需要使用其他相关函数。 + +桥驱动中也应保存每个子设备的私有数据,比如一个指向特定桥的各设备私有 +数据的指针。为此 v4l2_subdev 结构体提供主机私有数据域(host_priv), +并可通过 v4l2_get_subdev_hostdata() 和 v4l2_set_subdev_hostdata() +访问。 + +从总线桥驱动的视角,驱动加载子设备模块并以某种方式获得 v4l2_subdev +结构体指针。对于 i2c 总线设备相对简单:调用 i2c_get_clientdata()。 +对于其他总线也需要做类似的操作。针对 I2C 总线上的子设备辅助函数帮你 +完成了大部分复杂的工作。 + +每个 v4l2_subdev 都包含子设备驱动需要实现的函数指针(如果对此设备 +不适用,可为NULL)。由于子设备可完成许多不同的工作,而在一个庞大的 +函数指针结构体中通常仅有少数有用的函数实现其功能肯定不合适。所以, +函数指针根据其实现的功能被分类,每一类都有自己的函数指针结构体。 + +顶层函数指针结构体包含了指向各类函数指针结构体的指针,如果子设备驱动 +不支持该类函数中的任何一个功能,则指向该类结构体的指针为NULL。 + +这些结构体定义如下: + +struct v4l2_subdev_core_ops { + int (*log_status)(struct v4l2_subdev *sd); + int (*init)(struct v4l2_subdev *sd, u32 val); + ... +}; + +struct v4l2_subdev_tuner_ops { + ... +}; + +struct v4l2_subdev_audio_ops { + ... +}; + +struct v4l2_subdev_video_ops { + ... +}; + +struct v4l2_subdev_pad_ops { + ... +}; + +struct v4l2_subdev_ops { + const struct v4l2_subdev_core_ops *core; + const struct v4l2_subdev_tuner_ops *tuner; + const struct v4l2_subdev_audio_ops *audio; + const struct v4l2_subdev_video_ops *video; + const struct v4l2_subdev_pad_ops *video; +}; + +其中 core(核心)函数集通常可用于所有子设备,其他类别的实现依赖于 +子设备。如视频设备可能不支持音频操作函数,反之亦然。 + +这样的设置在限制了函数指针数量的同时,还使增加新的操作函数和分类 +变得较为容易。 + +子设备驱动可使用如下函数初始化 v4l2_subdev 结构体: + + v4l2_subdev_init(sd, &ops); + +然后,你必须用一个唯一的名字初始化 subdev->name,并初始化模块的 +owner 域。若使用 i2c 辅助函数,这些都会帮你处理好。 + +若需同媒体框架整合,你必须调用 media_entity_pads_init() 初始化 v4l2_subdev +结构体中的 media_entity 结构体(entity 域): + + struct media_pad *pads = &my_sd->pads; + int err; + + err = media_entity_pads_init(&sd->entity, npads, pads); + +pads 数组必须预先初始化。无须手动设置 media_entity 的 type 和 +name 域,但如有必要,revision 域必须初始化。 + +当(任何)子设备节点被打开/关闭,对 entity 的引用将被自动获取/释放。 + +在子设备被注销之后,不要忘记清理 media_entity 结构体: + + media_entity_cleanup(&sd->entity); + +如果子设备驱动趋向于处理视频并整合进了媒体框架,必须使用 v4l2_subdev_pad_ops +替代 v4l2_subdev_video_ops 实现格式相关的功能。 + +这种情况下,子设备驱动应该设置 link_validate 域,以提供它自身的链接 +验证函数。链接验证函数应对管道(两端链接的都是 V4L2 子设备)中的每个 +链接调用。驱动还要负责验证子设备和视频节点间格式配置的正确性。 + +如果 link_validate 操作没有设置,默认的 v4l2_subdev_link_validate_default() +函数将会被调用。这个函数保证宽、高和媒体总线像素格式在链接的收发两端 +都一致。子设备驱动除了它们自己的检测外,也可以自由使用这个函数以执行 +上面提到的检查。 + +设备(桥)驱动程序必须向 v4l2_device 注册 v4l2_subdev: + + int err = v4l2_device_register_subdev(v4l2_dev, sd); + +如果子设备模块在它注册前消失,这个操作可能失败。在这个函数成功返回后, +subdev->dev 域就指向了 v4l2_device。 + +如果 v4l2_device 父设备的 mdev 域为非 NULL 值,则子设备实体将被自动 +注册为媒体设备。 + +注销子设备则可用如下函数: + + v4l2_device_unregister_subdev(sd); + +此后,子设备模块就可卸载,且 sd->dev == NULL。 + +注册之设备后,可通过以下方式直接调用其操作函数: + + err = sd->ops->core->g_std(sd, &norm); + +但使用如下宏会比较容易且合适: + + err = v4l2_subdev_call(sd, core, g_std, &norm); + +这个宏将会做 NULL 指针检查,如果 subdev 为 NULL,则返回-ENODEV;如果 +subdev->core 或 subdev->core->g_std 为 NULL,则返回 -ENOIOCTLCMD; +否则将返回 subdev->ops->core->g_std ops 调用的实际结果。 + +有时也可能同时调用所有或一系列子设备的某个操作函数: + + v4l2_device_call_all(v4l2_dev, 0, core, g_std, &norm); + +任何不支持此操作的子设备都会被跳过,并忽略错误返回值。但如果你需要 +检查出错码,则可使用如下函数: + + err = v4l2_device_call_until_err(v4l2_dev, 0, core, g_std, &norm); + +除 -ENOIOCTLCMD 外的任何错误都会跳出循环并返回错误值。如果(除 -ENOIOCTLCMD +外)没有错误发生,则返回 0。 + +对于以上两个函数的第二个参数为组 ID。如果为 0,则所有子设备都会执行 +这个操作。如果为非 0 值,则只有那些组 ID 匹配的子设备才会执行此操作。 +在桥驱动注册一个子设备前,可以设置 sd->grp_id 为任何期望值(默认值为 +0)。这个值属于桥驱动,且子设备驱动将不会修改和使用它。 + +组 ID 赋予了桥驱动更多对于如何调用回调的控制。例如,电路板上有多个 +音频芯片,每个都有改变音量的能力。但当用户想要改变音量的时候,通常 +只有一个会被实际使用。你可以对这样的子设备设置组 ID 为(例如 AUDIO_CONTROLLER) +并在调用 v4l2_device_call_all() 时指定它为组 ID 值。这就保证了只有 +需要的子设备才会执行这个回调。 + +如果子设备需要通知它的 v4l2_device 父设备一个事件,可以调用 +v4l2_subdev_notify(sd, notification, arg)。这个宏检查是否有一个 +notify() 回调被注册,如果没有,返回 -ENODEV。否则返回 notify() 调用 +结果。 + +使用 v4l2_subdev 的好处在于它是一个通用结构体,且不包含任何底层硬件 +信息。所有驱动可以包含多个 I2C 总线的子设备,但也有子设备是通过 GPIO +控制。这个区别仅在配置设备时有关系,一旦子设备注册完成,对于 v4l2 +子系统来说就完全透明了。 + + +V4L2 子设备用户空间API +-------------------- + +除了通过 v4l2_subdev_ops 结构导出的内核 API,V4L2 子设备也可以直接 +通过用户空间应用程序来控制。 + +可以在 /dev 中创建名为 v4l-subdevX 设备节点,以通过其直接访问子设备。 +如果子设备支持用户空间直接配置,必须在注册前设置 V4L2_SUBDEV_FL_HAS_DEVNODE +标志。 + +注册子设备之后, v4l2_device 驱动会通过调用 v4l2_device_register_subdev_nodes() +函数为所有已注册并设置了 V4L2_SUBDEV_FL_HAS_DEVNODE 的子设备创建 +设备节点。这些设备节点会在子设备注销时自动删除。 + +这些设备节点处理 V4L2 API 的一个子集。 + +VIDIOC_QUERYCTRL +VIDIOC_QUERYMENU +VIDIOC_G_CTRL +VIDIOC_S_CTRL +VIDIOC_G_EXT_CTRLS +VIDIOC_S_EXT_CTRLS +VIDIOC_TRY_EXT_CTRLS + + 这些 ioctls 控制与 V4L2 中定义的一致。他们行为相同,唯一的 + 不同是他们只处理子设备的控制实现。根据驱动程序,这些控制也 + 可以通过一个(或多个) V4L2 设备节点访问。 + +VIDIOC_DQEVENT +VIDIOC_SUBSCRIBE_EVENT +VIDIOC_UNSUBSCRIBE_EVENT + + 这些 ioctls 事件与 V4L2 中定义的一致。他们行为相同,唯一的 + 不同是他们只处理子设备产生的事件。根据驱动程序,这些事件也 + 可以通过一个(或多个) V4L2 设备节点上报。 + + 要使用事件通知的子设备驱动,在注册子设备前必须在 v4l2_subdev::flags + 中设置 V4L2_SUBDEV_USES_EVENTS 并在 v4l2_subdev::nevents + 中初始化事件队列深度。注册完成后,事件会在 v4l2_subdev::devnode + 设备节点中像通常一样被排队。 + + 为正确支持事件机制,poll() 文件操作也应被实现。 + +私有 ioctls + + 不在以上列表中的所有 ioctls 会通过 core::ioctl 操作直接传递 + 给子设备驱动。 + + +I2C 子设备驱动 +------------- + +由于这些驱动很常见,所以内特提供了特定的辅助函数(v4l2-common.h)让这些 +设备的使用更加容易。 + +添加 v4l2_subdev 支持的推荐方法是让 I2C 驱动将 v4l2_subdev 结构体 +嵌入到为每个 I2C 设备实例创建的状态结构体中。而最简单的设备没有状态 +结构体,此时可以直接创建一个 v4l2_subdev 结构体。 + +一个典型的状态结构体如下所示(‘chipname’用芯片名代替): + +struct chipname_state { + struct v4l2_subdev sd; + ... /* 附加的状态域*/ +}; + +初始化 v4l2_subdev 结构体的方法如下: + + v4l2_i2c_subdev_init(&state->sd, client, subdev_ops); + +这个函数将填充 v4l2_subdev 结构体中的所有域,并保证 v4l2_subdev 和 +i2c_client 都指向彼此。 + +同时,你也应该为从 v4l2_subdev 指针找到 chipname_state 结构体指针 +添加一个辅助内联函数。 + +static inline struct chipname_state *to_state(struct v4l2_subdev *sd) +{ + return container_of(sd, struct chipname_state, sd); +} + +使用以下函数可以通过 v4l2_subdev 结构体指针获得 i2c_client 结构体 +指针: + + struct i2c_client *client = v4l2_get_subdevdata(sd); + +而以下函数则相反,通过 i2c_client 结构体指针获得 v4l2_subdev 结构体 +指针: + + struct v4l2_subdev *sd = i2c_get_clientdata(client); + +当 remove()函数被调用前,必须保证先调用 v4l2_device_unregister_subdev(sd)。 +此操作将会从桥驱动中注销子设备。即使子设备没有注册,调用此函数也是 +安全的。 + +必须这样做的原因是:当桥驱动注销 i2c 适配器时,remove()回调函数 +会被那个适配器上的 i2c 设备调用。此后,相应的 v4l2_subdev 结构体 +就不存在了,所有它们必须先被注销。在 remove()回调函数中调用 +v4l2_device_unregister_subdev(sd),可以保证执行总是正确的。 + + +桥驱动也有一些辅组函数可用: + +struct v4l2_subdev *sd = v4l2_i2c_new_subdev(v4l2_dev, adapter, + "module_foo", "chipid", 0x36, NULL); + +这个函数会加载给定的模块(如果没有模块需要加载,可以为 NULL), +并用给定的 i2c 适配器结构体指针(i2c_adapter)和 器件地址(chip/address) +作为参数调用 i2c_new_client_device()。如果一切顺利,则就在 v4l2_device +中注册了子设备。 + +你也可以利用 v4l2_i2c_new_subdev()的最后一个参数,传递一个可能的 +I2C 地址数组,让函数自动探测。这些探测地址只有在前一个参数为 0 的 +情况下使用。非零参数意味着你知道准确的 i2c 地址,所以此时无须进行 +探测。 + +如果出错,两个函数都返回 NULL。 + +注意:传递给 v4l2_i2c_new_subdev()的 chipid 通常与模块名一致。 +它允许你指定一个芯片的变体,比如“saa7114”或“saa7115”。一般通过 +i2c 驱动自动探测。chipid 的使用是在今后需要深入了解的事情。这个与 +i2c 驱动不同,较容易混淆。要知道支持哪些芯片变体,你可以查阅 i2c +驱动代码的 i2c_device_id 表,上面列出了所有可能支持的芯片。 + +还有两个辅助函数: + +v4l2_i2c_new_subdev_cfg:这个函数添加新的 irq 和 platform_data +参数,并有‘addr’和‘probed_addrs’参数:如果 addr 非零,则被使用 +(不探测变体),否则 probed_addrs 中的地址将用于自动探测。 + +例如:以下代码将会探测地址(0x10): + +struct v4l2_subdev *sd = v4l2_i2c_new_subdev_cfg(v4l2_dev, adapter, + "module_foo", "chipid", 0, NULL, 0, I2C_ADDRS(0x10)); + +v4l2_i2c_new_subdev_board 使用一个 i2c_board_info 结构体,将其 +替代 irq、platform_data 和 add r参数传递给 i2c 驱动。 + +如果子设备支持 s_config 核心操作,这个操作会在子设备配置好之后以 irq 和 +platform_data 为参数调用。早期的 v4l2_i2c_new_(probed_)subdev 函数 +同样也会调用 s_config,但仅在 irq 为 0 且 platform_data 为 NULL 时。 + +video_device结构体 +----------------- + +在 /dev 目录下的实际设备节点根据 video_device 结构体(v4l2-dev.h) +创建。此结构体既可以动态分配也可以嵌入到一个更大的结构体中。 + +动态分配方法如下: + + struct video_device *vdev = video_device_alloc(); + + if (vdev == NULL) + return -ENOMEM; + + vdev->release = video_device_release; + +如果将其嵌入到一个大结构体中,则必须自己实现 release()回调。 + + struct video_device *vdev = &my_vdev->vdev; + + vdev->release = my_vdev_release; + +release()回调必须被设置,且在最后一个 video_device 用户退出之后 +被调用。 + +默认的 video_device_release()回调只是调用 kfree 来释放之前分配的 +内存。 + +你应该设置这些域: + +- v4l2_dev: 设置为 v4l2_device 父设备。 + +- name: 设置为唯一的描述性设备名。 + +- fops: 设置为已有的 v4l2_file_operations 结构体。 + +- ioctl_ops: 如果你使用v4l2_ioctl_ops 来简化 ioctl 的维护 + (强烈建议使用,且将来可能变为强制性的!),然后设置你自己的 + v4l2_ioctl_ops 结构体. + +- lock: 如果你要在驱动中实现所有的锁操作,则设为 NULL 。否则 + 就要设置一个指向 struct mutex_lock 结构体的指针,这个锁将 + 在 unlocked_ioctl 文件操作被调用前由内核获得,并在调用返回后 + 释放。详见下一节。 + +- prio: 保持对优先级的跟踪。用于实现 VIDIOC_G/S_PRIORITY。如果 + 设置为 NULL,则会使用 v4l2_device 中的 v4l2_prio_state 结构体。 + 如果要对每个设备节点(组)实现独立的优先级,可以将其指向自己 + 实现的 v4l2_prio_state 结构体。 + +- parent: 仅在使用 NULL 作为父设备结构体参数注册 v4l2_device 时 + 设置此参数。只有在一个硬件设备包含多一个 PCI 设备,共享同一个 + v4l2_device 核心时才会发生。 + + cx88 驱动就是一个例子:一个 v4l2_device 结构体核心,被一个裸的 + 视频 PCI 设备(cx8800)和一个 MPEG PCI 设备(cx8802)共用。由于 + v4l2_device 无法与特定的 PCI 设备关联,所有没有设置父设备。但当 + video_device 配置后,就知道使用哪个父 PCI 设备了。 + +如果你使用 v4l2_ioctl_ops,则应该在 v4l2_file_operations 结构体中 +设置 .unlocked_ioctl 指向 video_ioctl2。 + +请勿使用 .ioctl!它已被废弃,今后将消失。 + +某些情况下你要告诉核心:你在 v4l2_ioctl_ops 指定的某个函数应被忽略。 +你可以在 video_device_register 被调用前通过以下函数标记这个 ioctls。 + +void v4l2_disable_ioctl(struct video_device *vdev, unsigned int cmd); + +基于外部因素(例如某个板卡已被使用),在不创建新结构体的情况下,你想 +要关闭 v4l2_ioctl_ops 中某个特性往往需要这个机制。 + +v4l2_file_operations 结构体是 file_operations 的一个子集。其主要 +区别在于:因 inode 参数从未被使用,它将被忽略。 + +如果需要与媒体框架整合,你必须通过调用 media_entity_pads_init() 初始化 +嵌入在 video_device 结构体中的 media_entity(entity 域)结构体: + + struct media_pad *pad = &my_vdev->pad; + int err; + + err = media_entity_pads_init(&vdev->entity, 1, pad); + +pads 数组必须预先初始化。没有必要手动设置 media_entity 的 type 和 +name 域。 + +当(任何)子设备节点被打开/关闭,对 entity 的引用将被自动获取/释放。 + +v4l2_file_operations 与锁 +-------------------------- + +你可以在 video_device 结构体中设置一个指向 mutex_lock 的指针。通常 +这既可是一个顶层互斥锁也可为设备节点自身的互斥锁。默认情况下,此锁 +用于 unlocked_ioctl,但为了使用 ioctls 你通过以下函数可禁用锁定: + + void v4l2_disable_ioctl_locking(struct video_device *vdev, unsigned int cmd); + +例如: v4l2_disable_ioctl_locking(vdev, VIDIOC_DQBUF); + +你必须在注册 video_device 前调用这个函数。 + +特别是对于 USB 驱动程序,某些命令(如设置控制)需要很长的时间,可能 +需要自行为缓冲区队列的 ioctls 实现锁定。 + +如果你需要更细粒度的锁,你必须设置 mutex_lock 为 NULL,并完全自己实现 +锁机制。 + +这完全由驱动开发者决定使用何种方法。然而,如果你的驱动存在长延时操作 +(例如,改变 USB 摄像头的曝光时间可能需要较长时间),而你又想让用户 +在等待长延时操作完成期间做其他的事,则你最好自己实现锁机制。 + +如果指定一个锁,则所有 ioctl 操作将在这个锁的作用下串行执行。如果你 +使用 videobuf,则必须将同一个锁传递给 videobuf 队列初始化函数;如 +videobuf 必须等待一帧的到达,则可临时解锁并在这之后重新上锁。如果驱动 +也在代码执行期间等待,则可做同样的工作(临时解锁,再上锁)让其他进程 +可以在第一个进程阻塞时访问设备节点。 + +在使用 videobuf2 的情况下,必须实现 wait_prepare 和 wait_finish 回调 +在适当的时候解锁/加锁。进一步来说,如果你在 video_device 结构体中使用 +锁,则必须在 wait_prepare 和 wait_finish 中对这个互斥锁进行解锁/加锁。 + +热插拔的断开实现也必须在调用 v4l2_device_disconnect 前获得锁。 + +video_device注册 +--------------- + +接下来你需要注册视频设备:这会为你创建一个字符设备。 + + err = video_register_device(vdev, VFL_TYPE_VIDEO, -1); + if (err) { + video_device_release(vdev); /* or kfree(my_vdev); */ + return err; + } + +如果 v4l2_device 父设备的 mdev 域为非 NULL 值,视频设备实体将自动 +注册为媒体设备。 + +注册哪种设备是根据类型(type)参数。存在以下类型: + +VFL_TYPE_VIDEO: 用于视频输入/输出设备的 videoX +VFL_TYPE_VBI: 用于垂直消隐数据的 vbiX (例如,隐藏式字幕,图文电视) +VFL_TYPE_RADIO: 用于广播调谐器的 radioX + +最后一个参数让你确定一个所控制设备的设备节点号数量(例如 videoX 中的 X)。 +通常你可以传入-1,让 v4l2 框架自己选择第一个空闲的编号。但是有时用户 +需要选择一个特定的节点号。驱动允许用户通过驱动模块参数选择一个特定的 +设备节点号是很普遍的。这个编号将会传递给这个函数,且 video_register_device +将会试图选择这个设备节点号。如果这个编号被占用,下一个空闲的设备节点 +编号将被选中,并向内核日志中发送一个警告信息。 + +另一个使用场景是当驱动创建多个设备时。这种情况下,对不同的视频设备在 +编号上使用不同的范围是很有用的。例如,视频捕获设备从 0 开始,视频 +输出设备从 16 开始。所以你可以使用最后一个参数来指定设备节点号最小值, +而 v4l2 框架会试图选择第一个的空闲编号(等于或大于你提供的编号)。 +如果失败,则它会就选择第一个空闲的编号。 + +由于这种情况下,你会忽略无法选择特定设备节点号的警告,则可调用 +video_register_device_no_warn() 函数避免警告信息的产生。 + +只要设备节点被创建,一些属性也会同时创建。在 /sys/class/video4linux +目录中你会找到这些设备。例如进入其中的 video0 目录,你会看到‘name’和 +‘index’属性。‘name’属性值就是 video_device 结构体中的‘name’域。 + +‘index’属性值就是设备节点的索引值:每次调用 video_register_device(), +索引值都递增 1 。第一个视频设备节点总是从索引值 0 开始。 + +用户可以设置 udev 规则,利用索引属性生成花哨的设备名(例如:用‘mpegX’ +代表 MPEG 视频捕获设备节点)。 + +在设备成功注册后,就可以使用这些域: + +- vfl_type: 传递给 video_register_device 的设备类型。 +- minor: 已指派的次设备号。 +- num: 设备节点编号 (例如 videoX 中的 X)。 +- index: 设备索引号。 + +如果注册失败,你必须调用 video_device_release() 来释放已分配的 +video_device 结构体;如果 video_device 是嵌入在自己创建的结构体中, +你也必须释放它。vdev->release() 回调不会在注册失败之后被调用, +你也不应试图在注册失败后注销设备。 + + +video_device 注销 +---------------- + +当视频设备节点已被移除,不论是卸载驱动还是USB设备断开,你都应注销 +它们: + + video_unregister_device(vdev); + +这个操作将从 sysfs 中移除设备节点(导致 udev 将其从 /dev 中移除)。 + +video_unregister_device() 返回之后,就无法完成打开操作。尽管如此, +USB 设备的情况则不同,某些应用程序可能依然打开着其中一个已注销设备 +节点。所以在注销之后,所有文件操作(当然除了 release )也应返回错误值。 + +当最后一个视频设备节点的用户退出,则 vdev->release() 回调会被调用, +并且你可以做最后的清理操作。 + +不要忘记清理与视频设备相关的媒体入口(如果被初始化过): + + media_entity_cleanup(&vdev->entity); + +这可以在 release 回调中完成。 + + +video_device 辅助函数 +--------------------- + +一些有用的辅助函数如下: + +- file/video_device 私有数据 + +你可以用以下函数在 video_device 结构体中设置/获取驱动私有数据: + +void *video_get_drvdata(struct video_device *vdev); +void video_set_drvdata(struct video_device *vdev, void *data); + +注意:在调用 video_register_device() 前执行 video_set_drvdata() +是安全的。 + +而以下函数: + +struct video_device *video_devdata(struct file *file); + +返回 file 结构体中拥有的的 video_device 指针。 + +video_drvdata 辅助函数结合了 video_get_drvdata 和 video_devdata +的功能: + +void *video_drvdata(struct file *file); + +你可以使用如下代码从 video_device 结构体中获取 v4l2_device 结构体 +指针: + +struct v4l2_device *v4l2_dev = vdev->v4l2_dev; + +- 设备节点名 + +video_device 设备节点在内核中的名称可以通过以下函数获得 + +const char *video_device_node_name(struct video_device *vdev); + +这个名字被用户空间工具(例如 udev)作为提示信息使用。应尽可能使用 +此功能,而非访问 video_device::num 和 video_device::minor 域。 + + +视频缓冲辅助函数 +--------------- + +v4l2 核心 API 提供了一个处理视频缓冲的标准方法(称为“videobuf”)。 +这些方法使驱动可以通过统一的方式实现 read()、mmap() 和 overlay()。 +目前在设备上支持视频缓冲的方法有分散/聚集 DMA(videobuf-dma-sg)、 +线性 DMA(videobuf-dma-contig)以及大多用于 USB 设备的用 vmalloc +分配的缓冲(videobuf-vmalloc)。 + +请参阅 Documentation/driver-api/media/v4l2-videobuf.rst,以获得更多关于 videobuf +层的使用信息。 + +v4l2_fh 结构体 +------------- + +v4l2_fh 结构体提供一个保存用于 V4L2 框架的文件句柄特定数据的简单方法。 +如果 video_device 标志,新驱动 +必须使用 v4l2_fh 结构体,因为它也用于实现优先级处理(VIDIOC_G/S_PRIORITY)。 + +v4l2_fh 的用户(位于 V4l2 框架中,并非驱动)可通过测试 +video_device->flags 中的 V4L2_FL_USES_V4L2_FH 位得知驱动是否使用 +v4l2_fh 作为他的 file->private_data 指针。这个位会在调用 v4l2_fh_init() +时被设置。 + +v4l2_fh 结构体作为驱动自身文件句柄结构体的一部分被分配,且驱动在 +其打开函数中将 file->private_data 指向它。 + +在许多情况下,v4l2_fh 结构体会嵌入到一个更大的结构体中。这钟情况下, +应该在 open() 中调用 v4l2_fh_init+v4l2_fh_add,并在 release() 中 +调用 v4l2_fh_del+v4l2_fh_exit。 + +驱动可以通过使用 container_of 宏提取他们自己的文件句柄结构体。例如: + +struct my_fh { + int blah; + struct v4l2_fh fh; +}; + +... + +int my_open(struct file *file) +{ + struct my_fh *my_fh; + struct video_device *vfd; + int ret; + + ... + + my_fh = kzalloc(sizeof(*my_fh), GFP_KERNEL); + + ... + + v4l2_fh_init(&my_fh->fh, vfd); + + ... + + file->private_data = &my_fh->fh; + v4l2_fh_add(&my_fh->fh); + return 0; +} + +int my_release(struct file *file) +{ + struct v4l2_fh *fh = file->private_data; + struct my_fh *my_fh = container_of(fh, struct my_fh, fh); + + ... + v4l2_fh_del(&my_fh->fh); + v4l2_fh_exit(&my_fh->fh); + kfree(my_fh); + return 0; +} + +以下是 v4l2_fh 函数使用的简介: + +void v4l2_fh_init(struct v4l2_fh *fh, struct video_device *vdev) + + 初始化文件句柄。这*必须*在驱动的 v4l2_file_operations->open() + 函数中执行。 + +void v4l2_fh_add(struct v4l2_fh *fh) + + 添加一个 v4l2_fh 到 video_device 文件句柄列表。一旦文件句柄 + 初始化完成就必须调用。 + +void v4l2_fh_del(struct v4l2_fh *fh) + + 从 video_device() 中解除文件句柄的关联。文件句柄的退出函数也 + 将被调用。 + +void v4l2_fh_exit(struct v4l2_fh *fh) + + 清理文件句柄。在清理完 v4l2_fh 后,相关内存会被释放。 + + +如果 v4l2_fh 不是嵌入在其他结构体中的,则可以用这些辅助函数: + +int v4l2_fh_open(struct file *filp) + + 分配一个 v4l2_fh 结构体空间,初始化并将其添加到 file 结构体相关的 + video_device 结构体中。 + +int v4l2_fh_release(struct file *filp) + + 从 file 结构体相关的 video_device 结构体中删除 v4l2_fh ,清理 + v4l2_fh 并释放空间。 + +这两个函数可以插入到 v4l2_file_operation 的 open() 和 release() +操作中。 + + +某些驱动需要在第一个文件句柄打开和最后一个文件句柄关闭的时候做些 +工作。所以加入了两个辅助函数以检查 v4l2_fh 结构体是否是相关设备 +节点打开的唯一文件句柄。 + +int v4l2_fh_is_singular(struct v4l2_fh *fh) + + 如果此文件句柄是唯一打开的文件句柄,则返回 1 ,否则返回 0 。 + +int v4l2_fh_is_singular_file(struct file *filp) + + 功能相同,但通过 filp->private_data 调用 v4l2_fh_is_singular。 + + +V4L2 事件机制 +----------- + +V4L2 事件机制提供了一个通用的方法将事件传递到用户空间。驱动必须使用 +v4l2_fh 才能支持 V4L2 事件机制。 + + +事件通过一个类型和选择 ID 来定义。ID 对应一个 V4L2 对象,例如 +一个控制 ID。如果未使用,则 ID 为 0。 + +当用户订阅一个事件,驱动会为此分配一些 kevent 结构体。所以每个 +事件组(类型、ID)都会有自己的一套 kevent 结构体。这保证了如果 +一个驱动短时间内产生了许多同类事件,不会覆盖其他类型的事件。 + +但如果你收到的事件数量大于同类事件 kevent 的保存数量,则最早的 +事件将被丢弃,并加入新事件。 + +此外,v4l2_subscribed_event 结构体内部有可供驱动设置的 merge() 和 +replace() 回调,这些回调会在新事件产生且没有多余空间的时候被调用。 +replace() 回调让你可以将早期事件的净荷替换为新事件的净荷,将早期 +净荷的相关数据合并到替换进来的新净荷中。当该类型的事件仅分配了一个 +kevent 结构体时,它将被调用。merge() 回调让你可以合并最早的事件净荷 +到在它之后的那个事件净荷中。当该类型的事件分配了两个或更多 kevent +结构体时,它将被调用。 + +这种方法不会有状态信息丢失,只会导致中间步骤信息丢失。 + + +关于 replace/merge 回调的一个不错的例子在 v4l2-event.c 中:用于 +控制事件的 ctrls_replace() 和 ctrls_merge() 回调。 + +注意:这些回调可以在中断上下文中调用,所以它们必须尽快完成并退出。 + +有用的函数: + +void v4l2_event_queue(struct video_device *vdev, const struct v4l2_event *ev) + + 将事件加入视频设备的队列。驱动仅负责填充 type 和 data 域。 + 其他域由 V4L2 填充。 + +int v4l2_event_subscribe(struct v4l2_fh *fh, + struct v4l2_event_subscription *sub, unsigned elems, + const struct v4l2_subscribed_event_ops *ops) + + video_device->ioctl_ops->vidioc_subscribe_event 必须检测驱动能 + 产生特定 id 的事件。然后调用 v4l2_event_subscribe() 来订阅该事件。 + + elems 参数是该事件的队列大小。若为 0,V4L2 框架将会(根据事件类型) + 填充默认值。 + + ops 参数允许驱动指定一系列回调: + * add: 当添加一个新监听者时调用(重复订阅同一个事件,此回调 + 仅被执行一次)。 + * del: 当一个监听者停止监听时调用。 + * replace: 用‘新’事件替换‘早期‘事件。 + * merge: 将‘早期‘事件合并到‘新’事件中。 + 这四个调用都是可选的,如果不想指定任何回调,则 ops 可为 NULL。 + +int v4l2_event_unsubscribe(struct v4l2_fh *fh, + struct v4l2_event_subscription *sub) + + v4l2_ioctl_ops 结构体中的 vidioc_unsubscribe_event 回调函数。 + 驱动程序可以直接使用 v4l2_event_unsubscribe() 实现退订事件过程。 + + 特殊的 V4L2_EVENT_ALL 类型,可用于退订所有事件。驱动可能在特殊 + 情况下需要做此操作。 + +int v4l2_event_pending(struct v4l2_fh *fh) + + 返回未决事件的数量。有助于实现轮询(poll)操作。 + +事件通过 poll 系统调用传递到用户空间。驱动可用 +v4l2_fh->wait (wait_queue_head_t 类型)作为参数调用 poll_wait()。 + +事件分为标准事件和私有事件。新的标准事件必须使用可用的最小事件类型 +编号。驱动必须从他们本类型的编号起始处分配事件。类型的编号起始为 +V4L2_EVENT_PRIVATE_START + n * 1000 ,其中 n 为可用最小编号。每个 +类型中的第一个事件类型编号是为以后的使用保留的,所以第一个可用事件 +类型编号是‘class base + 1’。 + +V4L2 事件机制的使用实例可以在 OMAP3 ISP 的驱动 +(drivers/media/video/omap3isp)中找到。 diff --git a/Documentation/translations/zh_CN/virt/acrn/cpuid.rst b/Documentation/translations/zh_CN/virt/acrn/cpuid.rst new file mode 100644 index 000000000..6f7be5456 --- /dev/null +++ b/Documentation/translations/zh_CN/virt/acrn/cpuid.rst @@ -0,0 +1,56 @@ +.. SPDX-License-Identifier: GPL-2.0 +.. include:: ../../disclaimer-zh_CN.rst + +:Original: Documentation/virt/acrn/cpuid.rst + +:翻译: + + 司延腾 Yanteng Si <siyanteng@loongson.cn> + +:校译: + + 时奎亮 Alex Shi <alexs@kernel.org> + +.. _cn_virt_acrn_cpuid: + +============== +ACRN CPUID位域 +============== + +在ACRN超级管理器上运行的客户虚拟机可以使用CPUID检查其一些功能。 + +ACRN的cpuid函数是: + +函数: 0x40000000 + +返回:: + + eax = 0x40000010 + ebx = 0x4e524341 + ecx = 0x4e524341 + edx = 0x4e524341 + +注意,ebx,ecx和edx中的这个值对应于字符串“ACRNACRNACRN”。eax中的值对应于这个叶子 +中存在的最大cpuid函数,如果将来有更多的函数加入,将被更新。 + +函数: define ACRN_CPUID_FEATURES (0x40000001) + +返回:: + + ebx, ecx, edx + eax = an OR'ed group of (1 << flag) + +其中 ``flag`` 的定义如下: + +================================= =========== ================================ +标志 值 描述 +================================= =========== ================================ +ACRN_FEATURE_PRIVILEGED_VM 0 客户虚拟机是一个有特权的虚拟机 +================================= =========== ================================ + +函数: 0x40000010 + +返回:: + + ebx, ecx, edx + eax = (Virtual) TSC frequency in kHz. diff --git a/Documentation/translations/zh_CN/virt/acrn/index.rst b/Documentation/translations/zh_CN/virt/acrn/index.rst new file mode 100644 index 000000000..34605d87f --- /dev/null +++ b/Documentation/translations/zh_CN/virt/acrn/index.rst @@ -0,0 +1,25 @@ +.. SPDX-License-Identifier: GPL-2.0 +.. include:: ../../disclaimer-zh_CN.rst + +:Original: Documentation/virt/acrn/index.rst + +:翻译: + + 司延腾 Yanteng Si <siyanteng@loongson.cn> + +:校译: + + 时奎亮 Alex Shi <alexs@kernel.org> + +.. _cn_virt_acrn_index: + +============== +ACRN超级管理器 +============== + +.. toctree:: + :maxdepth: 1 + + introduction + io-request + cpuid diff --git a/Documentation/translations/zh_CN/virt/acrn/introduction.rst b/Documentation/translations/zh_CN/virt/acrn/introduction.rst new file mode 100644 index 000000000..7182415cb --- /dev/null +++ b/Documentation/translations/zh_CN/virt/acrn/introduction.rst @@ -0,0 +1,52 @@ +.. SPDX-License-Identifier: GPL-2.0 +.. include:: ../../disclaimer-zh_CN.rst + +:Original: Documentation/virt/acrn/introduction.rst + +:翻译: + + 司延腾 Yanteng Si <siyanteng@loongson.cn> + +:校译: + + 时奎亮 Alex Shi <alexs@kernel.org> + +.. _cn_virt_acrn_introduction: + +ACRN超级管理器介绍 +================== + +ACRN超级管理器是一个第一类超级管理器,直接在裸机硬件上运行。它有一个特权管理虚拟机,称为服 +务虚拟机,用于管理用户虚拟机和进行I/O仿真。 + +ACRN用户空间是一个运行在服务虚拟机中的应用程序,它根据命令行配置为用户虚拟机仿真设备。 +ACRN管理程序服务模块(HSM)是服务虚拟机中的一个内核模块,为ACRN用户空间提供管理程序服 +务。 + +下图展示了该架构。 + +:: + + 服务端VM 用户端VM + +----------------------------+ | +------------------+ + | +--------------+ | | | | + | |ACRN用户空间 | | | | | + | +--------------+ | | | | + |-----------------ioctl------| | | | ... + |内核空间 +----------+ | | | | + | | HSM | | | | 驱动 | + | +----------+ | | | | + +--------------------|-------+ | +------------------+ + +---------------------hypercall----------------------------------------+ + | ACRN超级管理器 | + +----------------------------------------------------------------------+ + | 硬件 | + +----------------------------------------------------------------------+ + +ACRN用户空间为用户虚拟机分配内存,配置和初始化用户虚拟机使用的设备,加载虚拟引导程序, +初始化虚拟CPU状态,处理来自用户虚拟机的I/O请求访问。它使用ioctls来与HSM通信。HSM通过 +与ACRN超级管理器的hypercalls进行交互来实现管理服务。HSM向用户空间输出一个char设备接口 +(/dev/acrn_hsm)。 + +ACRN超级管理器是开源的,任何人都可以贡献。源码库在 +https://github.com/projectacrn/acrn-hypervisor。 diff --git a/Documentation/translations/zh_CN/virt/acrn/io-request.rst b/Documentation/translations/zh_CN/virt/acrn/io-request.rst new file mode 100644 index 000000000..4b4e7186d --- /dev/null +++ b/Documentation/translations/zh_CN/virt/acrn/io-request.rst @@ -0,0 +1,99 @@ +.. SPDX-License-Identifier: GPL-2.0 +.. include:: ../../disclaimer-zh_CN.rst + +:Original: Documentation/virt/acrn/io-request.rst + +:翻译: + + 司延腾 Yanteng Si <siyanteng@loongson.cn> + +:校译: + + 时奎亮 Alex Shi <alexs@kernel.org> + +.. _cn_virt_acrn_io-request: + +I/O请求处理 +=========== + +客户虚拟机的I/O请求由超级管理器构建,由ACRN超级管理器服务模块分发到与I/O请求的地址范 +围相对应的I/O客户端。I/O请求处理的细节将在以下章节描述。 + +1. I/O请求 +---------- + +对于每个客户虚拟机,有一个共享的4KB字节的内存区域,用于超级管理器和服务虚拟机之间的 +I/O请求通信。一个I/O请求是一个256字节的结构体缓冲区,它是 "acrn_io_request" 结构 +体,当客户虚拟机中发生被困的I/O访问时,由超级管理器的I/O处理器填充。服务虚拟机中的 +ACRN用户空间首先分配一个4KB字节的页面,并将缓冲区的GPA(客户物理地址)传递给管理平 +台。缓冲区被用作16个I/O请求槽的数组,每个I/O请求槽为256字节。这个数组是按vCPU ID +索引的。 + +2. I/O客户端 +------------ + +一个I/O客户端负责处理客户虚拟机的I/O请求,其访问的GPA在一定范围内。每个客户虚拟机 +可以关联多个I/O客户端。每个客户虚拟机都有一个特殊的客户端,称为默认客户端,负责处理 +所有不在其他客户端范围内的I/O请求。ACRN用户空间充当每个客户虚拟机的默认客户端。 + +下面的图示显示了I/O请求共享缓冲区、I/O请求和I/O客户端之间的关系。 + +:: + + +------------------------------------------------------+ + | 服务VM | + |+--------------------------------------------------+ | + || +----------------------------------------+ | | + || | 共享页 ACRN用户空间 | | | + || | +-----------------+ +------------+ | | | + || +----+->| acrn_io_request |<-+ 默认 | | | | + || | | | +-----------------+ | I/O客户端 | | | | + || | | | | ... | +------------+ | | | + || | | | +-----------------+ | | | + || | +-|--------------------------------------+ | | + ||---|----|-----------------------------------------| | + || | | 内核 | | + || | | +----------------------+ | | + || | | | +-------------+ HSM | | | + || | +--------------+ | | | | + || | | | I/O客户端 | | | | + || | | | | | | | + || | | +-------------+ | | | + || | +----------------------+ | | + |+---|----------------------------------------------+ | + +----|-------------------------------------------------+ + | + +----|-------------------------------------------------+ + | +-+-----------+ | + | | I/O处理 | ACRN超级管理器 | + | +-------------+ | + +------------------------------------------------------+ + +3. I/O请求状态转换 +------------------ + +一个ACRN I/O请求的状态转换如下。 + +:: + + FREE -> PENDING -> PROCESSING -> COMPLETE -> FREE -> ... + +- FREE: 这个I/O请求槽是空的 +- PENDING: 在这个槽位上有一个有效的I/O请求正在等待 +- PROCESSING: 正在处理I/O请求 +- COMPLETE: 该I/O请求已被处理 + +处于COMPLETE或FREE状态的I/O请求是由超级管理器拥有的。HSM和ACRN用户空间负责处理其 +他的。 + +4. I/O请求的处理流程 +-------------------- + +a. 当客户虚拟机中发生被陷入的I/O访问时,超级管理器的I/O处理程序将把I/O请求填充为 + PENDING状态。 +b. 超级管理器向服务虚拟机发出一个向上调用,这是一个通知中断。 +c. upcall处理程序会安排一个工作者来调度I/O请求。 +d. 工作者寻找PENDING I/O请求,根据I/O访问的地址将其分配给不同的注册客户,将其 + 状态更新为PROCESSING,并通知相应的客户进行处理。 +e. 被通知的客户端处理指定的I/O请求。 +f. HSM将I/O请求状态更新为COMPLETE,并通过hypercalls通知超级管理器完成。 diff --git a/Documentation/translations/zh_CN/virt/guest-halt-polling.rst b/Documentation/translations/zh_CN/virt/guest-halt-polling.rst new file mode 100644 index 000000000..b798d1cf0 --- /dev/null +++ b/Documentation/translations/zh_CN/virt/guest-halt-polling.rst @@ -0,0 +1,87 @@ +.. include:: ../disclaimer-zh_CN.rst + +:Original: Documentation/virt/guest-halt-polling.rst + +:翻译: + + 司延腾 Yanteng Si <siyanteng@loongson.cn> + +:校译: + + 时奎亮 Alex Shi <alexs@kernel.org> + +.. _cn_virt_guest-halt-polling: + +======================================== +客户机停机轮询机制(Guest halt polling) +======================================== + +cpuidle_haltpoll驱动,与haltpoll管理器一起,允许客户机vcpus在停机前轮询 +一定的时间。 + +这为物理机侧的轮询提供了以下好处: + + 1) 在执行轮询时,POLL标志被设置,这允许远程vCPU在执行唤醒时避免发送 + IPI(以及处理IPI的相关成本)。 + + 2) 可以避免虚拟机退出的成本。 + +客户机侧轮询的缺点是,即使在物理机中的其他可运行任务中也会进行轮询。 + +其基本逻辑如下。一个全局值,即guest_halt_poll_ns,是由用户配置的,表示允 +许轮询的最大时间量。这个值是固定的。 + +每个vcpu都有一个可调整的guest_halt_poll_ns("per-cpu guest_halt_poll_ns"), +它由算法响应事件进行调整(解释如下)。 + +模块参数 +======== + +haltpoll管理器有5个可调整的模块参数: + +1) guest_halt_poll_ns: + +轮询停机前执行的最大时间,以纳秒为单位。 + +默认值: 200000 + +2) guest_halt_poll_shrink: + +当唤醒事件发生在全局的guest_halt_poll_ns之后,用于缩减每个CPU的guest_halt_poll_ns +的划分系数。 + +默认值: 2 + +3) guest_halt_poll_grow: + +当事件发生在per-cpu guest_halt_poll_ns之后但在global guest_halt_poll_ns之前, +用于增长per-cpu guest_halt_poll_ns的乘法系数。 + +默认值: 2 + +4) guest_halt_poll_grow_start: + +在系统空闲的情况下,每个cpu guest_halt_poll_ns最终达到零。这个值设置了增长时的 +初始每cpu guest_halt_poll_ns。这个值可以从10000开始增加,以避免在最初的增长阶 +段出现失误。: + +10k, 20k, 40k, ... (例如,假设guest_halt_poll_grow=2). + +默认值: 50000 + +5) guest_halt_poll_allow_shrink: + +允许缩减的Bool参数。设置为N以避免它(一旦达到全局的guest_halt_poll_ns值,每CPU的 +guest_halt_poll_ns将保持高位)。 + +默认值: Y + +模块参数可以从Debugfs文件中设置,在:: + + /sys/module/haltpoll/parameters/ + +进一步说明 +========== + +- 在设置guest_halt_poll_ns参数时应该小心,因为一个大的值有可能使几乎是完全空闲机 + 器上的cpu使用率达到100%。 diff --git a/Documentation/translations/zh_CN/virt/index.rst b/Documentation/translations/zh_CN/virt/index.rst new file mode 100644 index 000000000..f8dd13681 --- /dev/null +++ b/Documentation/translations/zh_CN/virt/index.rst @@ -0,0 +1,38 @@ +.. SPDX-License-Identifier: GPL-2.0 +.. include:: ../disclaimer-zh_CN.rst + +:Original: Documentation/virt/index.rst + +:翻译: + + 司延腾 Yanteng Si <siyanteng@loongson.cn> + +:校译: + + 时奎亮 Alex Shi <alexs@kernel.org> + +.. _cn_virt_index: + +=============== +Linux虚拟化支持 +=============== + +.. toctree:: + :maxdepth: 2 + + paravirt_ops + guest-halt-polling + ne_overview + acrn/index + +TODOLIST: + + kvm/index + uml/user_mode_linux_howto_v2 + +.. only:: html and subproject + + Indices + ======= + + * :ref:`genindex` diff --git a/Documentation/translations/zh_CN/virt/ne_overview.rst b/Documentation/translations/zh_CN/virt/ne_overview.rst new file mode 100644 index 000000000..2455b371a --- /dev/null +++ b/Documentation/translations/zh_CN/virt/ne_overview.rst @@ -0,0 +1,88 @@ +.. SPDX-License-Identifier: GPL-2.0 +.. include:: ../disclaimer-zh_CN.rst + +:Original: Documentation/virt/ne_overview.rst + +:翻译: + + 司延腾 Yanteng Si <siyanteng@loongson.cn> + +:校译: + + 时奎亮 Alex Shi <alexs@kernel.org> + +.. _cn_virt_ne_overview: + +============== +Nitro Enclaves +============== + +概述 +==== + +Nitro Enclaves(NE)是亚马逊弹性计算云(EC2)的一项新功能,允许客户在EC2实 +例中划分出孤立的计算环境[1]。 + +例如,一个处理敏感数据并在虚拟机中运行的应用程序,可以与在同一虚拟机中运行的 +其他应用程序分开。然后,这个应用程序在一个独立于主虚拟机的虚拟机中运行,即 +enclave。 + +一个enclave与催生它的虚拟机一起运行。这种设置符合低延迟应用的需要。为enclave +分配的资源,如内存和CPU,是从主虚拟机中分割出来的。每个enclave都被映射到一 +个运行在主虚拟机中的进程,该进程通过一个ioctl接口与NE驱动进行通信。 + +在这个意义上,有两个组成部分。 + +1. 一个enclave抽象进程——一个运行在主虚拟机客体中的用户空间进程,它使用NE驱动 +提供的ioctl接口来生成一个enclave虚拟机(这就是下面的2)。 + +有一个NE模拟的PCI设备暴露给主虚拟机。这个新的PCI设备的驱动被包含在NE驱动中。 + +ioctl逻辑被映射到PCI设备命令,例如,NE_START_ENCLAVE ioctl映射到一个enclave +启动PCI命令。然后,PCI设备命令被翻译成在管理程序方面采取的行动;也就是在运 +行主虚拟机的主机上运行的Nitro管理程序。Nitro管理程序是基于KVM核心技术的。 + +2. enclave本身——一个运行在与催生它的主虚拟机相同的主机上的虚拟机。内存和CPU +从主虚拟机中分割出来,专门用于enclave虚拟机。enclave没有连接持久性存储。 + +从主虚拟机中分割出来并给enclave的内存区域需要对齐2 MiB/1 GiB物理连续的内存 +区域(或这个大小的倍数,如8 MiB)。该内存可以通过使用hugetlbfs从用户空间分 +配[2][3]。一个enclave的内存大小需要至少64 MiB。enclave内存和CPU需要来自同 +一个NUMA节点。 + +一个enclave在专用的核心上运行。CPU 0及其同级别的CPU需要保持对主虚拟机的可用 +性。CPU池必须由具有管理能力的用户为NE目的进行设置。关于CPU池的格式,请看内核 +文档[4]中的cpu list部分。 + +enclave通过本地通信通道与主虚拟机进行通信,使用virtio-vsock[5]。主虚拟机有 +virtio-pci vsock模拟设备,而飞地虚拟机有virtio-mmio vsock模拟设备。vsock +设备使用eventfd作为信令。enclave虚拟机看到通常的接口——本地APIC和IOAPIC——从 +virtio-vsock设备获得中断。virtio-mmio设备被放置在典型的4 GiB以下的内存中。 + +在enclave中运行的应用程序需要和将在enclave虚拟机中运行的操作系统(如内核、 +ramdisk、init)一起被打包到enclave镜像中。enclave虚拟机有自己的内核并遵循标 +准的Linux启动协议[6]。 + +内核bzImage、内核命令行、ramdisk(s)是enclave镜像格式(EIF)的一部分;另外 +还有一个EIF头,包括元数据,如magic number、eif版本、镜像大小和CRC。 + +哈希值是为整个enclave镜像(EIF)、内核和ramdisk(s)计算的。例如,这被用来检 +查在enclave虚拟机中加载的enclave镜像是否是打算运行的那个。 + +这些加密测量包括在由Nitro超级管理器成的签名证明文件中,并进一步用来证明enclave +的身份;KMS是NE集成的服务的一个例子,它检查证明文件。 + +enclave镜像(EIF)被加载到enclave内存中,偏移量为8 MiB。enclave中的初始进程 +连接到主虚拟机的vsock CID和一个预定义的端口--9000,以发送一个心跳值--0xb7。这 +个机制用于在主虚拟机中检查enclave是否已经启动。主虚拟机的CID是3。 + +如果enclave虚拟机崩溃或优雅地退出,NE驱动会收到一个中断事件。这个事件会通过轮询 +通知机制进一步发送到运行在主虚拟机中的用户空间enclave进程。然后,用户空间enclave +进程就可以退出了。 + +[1] https://aws.amazon.com/ec2/nitro/nitro-enclaves/ +[2] https://www.kernel.org/doc/html/latest/admin-guide/mm/hugetlbpage.html +[3] https://lwn.net/Articles/807108/ +[4] https://www.kernel.org/doc/html/latest/admin-guide/kernel-parameters.html +[5] https://man7.org/linux/man-pages/man7/vsock.7.html +[6] https://www.kernel.org/doc/html/latest/x86/boot.html diff --git a/Documentation/translations/zh_CN/virt/paravirt_ops.rst b/Documentation/translations/zh_CN/virt/paravirt_ops.rst new file mode 100644 index 000000000..06b122bc9 --- /dev/null +++ b/Documentation/translations/zh_CN/virt/paravirt_ops.rst @@ -0,0 +1,41 @@ +.. SPDX-License-Identifier: GPL-2.0 +.. include:: ../disclaimer-zh_CN.rst + +:Original: Documentation/virt/paravirt_ops.rst + +:翻译: + + 司延腾 Yanteng Si <siyanteng@loongson.cn> + +:校译: + + 陈飞杨 Feiyang Chen <chenfeiyang@loongson.cn> + 时奎亮 Alex Shi <alexs@kernel.org> + +.. _cn_virt_paravirt_ops: + +============ +半虚拟化操作 +============ + +Linux提供了对不同管理程序虚拟化技术的支持。历史上,为了支持不同的虚拟机超级管理器 +(hypervisor,下文简称超级管理器),需要不同的二进制内核,这个限制已经被pv_ops移 +除了。Linux pv_ops是一个虚拟化API,它能够支持不同的管理程序。它允许每个管理程序 +优先于关键操作,并允许单一的内核二进制文件在所有支持的执行环境中运行,包括本机——没 +有任何管理程序。 + +pv_ops提供了一组函数指针,代表了与低级关键指令和各领域高级功能相对应的操作。 +pv-ops允许在运行时进行优化,在启动时对低级关键操作进行二进制修补。 + +pv_ops操作被分为三类: + +- 简单的间接调用 + 这些操作对应于高水平的函数,众所周知,间接调用的开销并不十分重要。 + +- 间接调用,允许用二进制补丁进行优化 + 通常情况下,这些操作对应于低级别的关键指令。它们被频繁地调用,并且是对性能关 + 键。开销是非常重要的。 + +- 一套用于手写汇编代码的宏程序 + 手写的汇编代码(.S文件)也需要半虚拟化,因为它们包括敏感指令或其中的一些代 + 码路径对性能非常关键。 |