diff options
Diffstat (limited to 'Documentation/translations/zh_CN')
20 files changed, 2717 insertions, 29 deletions
diff --git a/Documentation/translations/zh_CN/PCI/index.rst b/Documentation/translations/zh_CN/PCI/index.rst new file mode 100644 index 000000000000..5c96017e9f41 --- /dev/null +++ b/Documentation/translations/zh_CN/PCI/index.rst @@ -0,0 +1,36 @@ +.. SPDX-License-Identifier: GPL-2.0 +.. include:: ../disclaimer-zh_CN.rst + +:Original: Documentation/PCI/index.rst + +:翻译: + + 司延腾 Yanteng Si <siyanteng@loongson.cn> + +:校译: + + + +.. _cn_PCI_index.rst: + +=================== +Linux PCI总线子系统 +=================== + +.. toctree:: + :maxdepth: 2 + :numbered: + + pci + +Todolist: + + pciebus-howto + pci-iov-howto + msi-howto + sysfs-pci + acpi-info + pci-error-recovery + pcieaer-howto + endpoint/index + boot-interrupts diff --git a/Documentation/translations/zh_CN/PCI/pci.rst b/Documentation/translations/zh_CN/PCI/pci.rst new file mode 100644 index 000000000000..520707787256 --- /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位总线主站功能的设备需要驱动程序通过调用带有适当参数的 +``pci_set_dma_mask()`` 来“注册”这种功能。一般来说,在系统RAM高于4G物理地址的情 +况下,这允许更有效的DMA。 + +所有PCI-X和PCIe兼容设备的驱动程序必须调用 ``pci_set_dma_mask()`` ,因为它们 +是64位DMA设备。 + +同样,如果设备可以通过调用 ``pci_set_consistent_dma_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/admin-guide/index.rst b/Documentation/translations/zh_CN/admin-guide/index.rst index 460034cbc2ab..83db84282562 100644 --- a/Documentation/translations/zh_CN/admin-guide/index.rst +++ b/Documentation/translations/zh_CN/admin-guide/index.rst @@ -67,6 +67,7 @@ Todolist: cpu-load lockup-watchdogs unicode + sysrq Todolist: @@ -118,7 +119,6 @@ Todolist: rtc serial-console svga - sysrq thunderbolt ufs vga-softcursor 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 000000000000..8276d70f3b40 --- /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/core-api/assoc_array.rst b/Documentation/translations/zh_CN/core-api/assoc_array.rst new file mode 100644 index 000000000000..3649bf0d1488 --- /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 000000000000..9e81dbec71f8 --- /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/genalloc.rst b/Documentation/translations/zh_CN/core-api/genalloc.rst new file mode 100644 index 000000000000..3c78452aaa7c --- /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/gfp_mask-from-fs-io.rst b/Documentation/translations/zh_CN/core-api/gfp_mask-from-fs-io.rst new file mode 100644 index 000000000000..75d2997e9bc3 --- /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/index.rst b/Documentation/translations/zh_CN/core-api/index.rst index 72f0a36daa1c..d10191c45cf1 100644 --- a/Documentation/translations/zh_CN/core-api/index.rst +++ b/Documentation/translations/zh_CN/core-api/index.rst @@ -39,12 +39,14 @@ :maxdepth: 1 kobject - -Todolist: - kref assoc_array xarray + +Todolist: + + + idr circular-buffers rbtree @@ -101,19 +103,23 @@ Todolist: 如何在内核中分配和使用内存。请注意,在 :doc:`/vm/index` 中有更多的内存管理文档。 -Todolist: +.. 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 - mm-api - genalloc pin_user_pages - boot-time-mm - gfp_mask-from-fs-io 内核调试的接口 ============== diff --git a/Documentation/translations/zh_CN/core-api/irq/irq-affinity.rst b/Documentation/translations/zh_CN/core-api/irq/irq-affinity.rst index 7addd5f27a88..36b085226d0b 100644 --- a/Documentation/translations/zh_CN/core-api/irq/irq-affinity.rst +++ b/Documentation/translations/zh_CN/core-api/irq/irq-affinity.rst @@ -1,6 +1,6 @@ .. include:: ../../disclaimer-zh_CN.rst -:Original: Documentation/core-api/irq/irq-affinity +:Original: Documentation/core-api/irq/irq-affinity.rst :翻译: 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 000000000000..b9902af310c5 --- /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/memory-allocation.rst b/Documentation/translations/zh_CN/core-api/memory-allocation.rst new file mode 100644 index 000000000000..e17b87dfd1c8 --- /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 index 161f4d2c18cc..9b2841fb9a5f 100644 --- a/Documentation/translations/zh_CN/core-api/memory-hotplug.rst +++ b/Documentation/translations/zh_CN/core-api/memory-hotplug.rst @@ -1,6 +1,6 @@ .. include:: ../disclaimer-zh_CN.rst -:Original: Documentation/core-api/memory_hotplug.rst +:Original: Documentation/core-api/memory-hotplug.rst :翻译: @@ -63,7 +63,6 @@ memory_notify结构体的指针:: unsigned long start_pfn; unsigned long nr_pages; int status_change_nid_normal; - int status_change_nid_high; int status_change_nid; } @@ -74,9 +73,6 @@ memory_notify结构体的指针:: - status_change_nid_normal是当nodemask的N_NORMAL_MEMORY被设置/清除时设置节 点id,如果是-1,则nodemask状态不改变。 -- status_change_nid_high是当nodemask的N_HIGH_MEMORY被设置/清除时设置的节点 - id,如果这个值为-1,那么nodemask状态不会改变。 - - status_change_nid是当nodemask的N_MEMORY被(将)设置/清除时设置的节点id。这 意味着一个新的(没上线的)节点通过联机获得新的内存,而一个节点失去了所有的内 存。如果这个值为-1,那么nodemask的状态就不会改变。 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 000000000000..0ea43dc67953 --- /dev/null +++ b/Documentation/translations/zh_CN/core-api/mm-api.rst @@ -0,0 +1,110 @@ +.. include:: ../disclaimer-zh_CN.rst + +:Original: Documentation/core-api/mm-api.rst + +:翻译: + + 司延腾 Yanteng Si <siyanteng@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/readahead.c + +mm/filemap.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.h + +include/linux/mmzone.h 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 000000000000..29c33e7e0855 --- /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/xarray.rst b/Documentation/translations/zh_CN/core-api/xarray.rst new file mode 100644 index 000000000000..ff2d9bcb7c34 --- /dev/null +++ b/Documentation/translations/zh_CN/core-api/xarray.rst @@ -0,0 +1,371 @@ +.. SPDX-License-Identifier: GPL-2.0+ +.. include:: ../disclaimer-zh_CN.rst + +:Original: Documentation/core-api/xarray.rst + +:翻译: + + 司延腾 Yanteng Si <siyanteng@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也被用来存储错误(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/maintainer/pull-requests.rst b/Documentation/translations/zh_CN/maintainer/pull-requests.rst index f46d6f3f2498..ce9725f4674c 100644 --- a/Documentation/translations/zh_CN/maintainer/pull-requests.rst +++ b/Documentation/translations/zh_CN/maintainer/pull-requests.rst @@ -21,7 +21,7 @@ Harding <me@tobin.cc>。 原始邮件线程:: - http://lkml.kernel.org/r/20171114110500.GA21175@kroah.com + https://lore.kernel.org/r/20171114110500.GA21175@kroah.com 创建分支 diff --git a/Documentation/translations/zh_CN/process/5.Posting.rst b/Documentation/translations/zh_CN/process/5.Posting.rst index b0c65614844d..4ee7de13f373 100644 --- a/Documentation/translations/zh_CN/process/5.Posting.rst +++ b/Documentation/translations/zh_CN/process/5.Posting.rst @@ -23,7 +23,7 @@ :ref:`Documentation/translations/zh_CN/process/submitting-drivers.rst <cn_submittingdrivers>` 和 :ref:`Documentation/translations/zh_CN/process/submit-checklist.rst <cn_submitchecklist>`。 -何时邮寄 +何时寄送 -------- 在补丁完全“准备好”之前,避免发布补丁是一种持续的诱惑。对于简单的补丁,这 @@ -142,7 +142,7 @@ 一般来说,你越把自己放在每个阅读你变更日志的人的位置上,变更日志(和内核 作为一个整体)就越好。 -不消说,变更日志是将变更提交到版本控制系统时使用的文本。接下来将是: +不需要说,变更日志是将变更提交到版本控制系统时使用的文本。接下来将是: - 补丁本身,采用统一的(“-u”)补丁格式。使用“-p”选项来diff将使函数名与 更改相关联,从而使结果补丁更容易被其他人读取。 @@ -186,10 +186,10 @@ 在补丁中添加标签时要小心:只有Cc:才适合在没有指定人员明确许可的情况下添加。 -发送补丁 +寄送补丁 -------- -在寄出补丁之前,您还需要注意以下几点: +在寄送补丁之前,您还需要注意以下几点: - 您确定您的邮件发送程序不会损坏补丁吗?被邮件客户端更改空白或修饰了行的补丁 无法被另一端接受,并且通常不会进行任何详细检查。如果有任何疑问,先把补丁寄 diff --git a/Documentation/translations/zh_CN/process/howto.rst b/Documentation/translations/zh_CN/process/howto.rst index ee3dee476d57..2903d7161bc8 100644 --- a/Documentation/translations/zh_CN/process/howto.rst +++ b/Documentation/translations/zh_CN/process/howto.rst @@ -381,7 +381,7 @@ MAINTAINERS文件中可以找到不同话题对应的邮件列表。 内核社区的工作模式同大多数传统公司开发队伍的工作模式并不相同。下面这些例 子,可以帮助你避免某些可能发生问题: -用这些话介绍你的修改提案会有好处: +用这些话介绍你的修改提案会有好处:(在任何时候你都不应该用中文写提案) - 它同时解决了多个问题 - 它删除了2000行代码 @@ -448,8 +448,8 @@ Linux内核社区并不喜欢一下接收大段的代码。修改需要被恰当 保证修改分成很多小块,这样在整个项目都准备好被包含进内核之前,其中的一部 分可能会先被接收。 -必须了解这样做是不可接受的:试图将未完成的工作提交进内核,然后再找时间修 -复。 +你必须明白这么做是无法令人接受的:试图将不完整的代码提交进内核,然后再找 +时间修复。 证明修改的必要性 @@ -475,8 +475,8 @@ Linux内核社区并不喜欢一下接收大段的代码。修改需要被恰当 https://www.ozlabs.org/~akpm/stuff/tpp.txt -这些事情有时候做起来很难。要在任何方面都做到完美可能需要好几年时间。这是 -一个持续提高的过程,它需要大量的耐心和决心。只要不放弃,你一定可以做到。 +这些事情有时候做起来很难。想要在任何方面都做到完美可能需要好几年时间。这 +是一个持续提高的过程,它需要大量的耐心和决心。只要不放弃,你一定可以做到。 很多人已经做到了,而他们都曾经和现在的你站在同样的起点上。 diff --git a/Documentation/translations/zh_CN/process/submitting-patches.rst b/Documentation/translations/zh_CN/process/submitting-patches.rst index 4fc6d16f5196..3f1683cd4727 100644 --- a/Documentation/translations/zh_CN/process/submitting-patches.rst +++ b/Documentation/translations/zh_CN/process/submitting-patches.rst @@ -127,13 +127,13 @@ URL来查找补丁描述并将其放入补丁中。也就是说,补丁(系列)及其描述应该是独立的。 这对维护人员和审查人员都有好处。一些评审者可能甚至没有收到补丁的早期版本。 -描述你在命令语气中的变化,例如“make xyzzy do frotz”而不是“[这个补丁]make -xyzzy do frotz”或“[我]changed xyzzy to do frotz”,就好像你在命令代码库改变 +描述你在命令语气中的变化,例如“make xyzzy do frotz”而不是“[This patch]make +xyzzy do frotz”或“[I]changed xyzzy to do frotz”,就好像你在命令代码库改变 它的行为一样。 如果修补程序修复了一个记录的bug条目,请按编号和URL引用该bug条目。如果补丁来 自邮件列表讨论,请给出邮件列表存档的URL;使用带有 ``Message-ID`` 的 -https://lkml.kernel.org/ 重定向,以确保链接不会过时。 +https://lore.kernel.org/ 重定向,以确保链接不会过时。 但是,在没有外部资源的情况下,尽量让你的解释可理解。除了提供邮件列表存档或 bug的URL之外,还要总结需要提交补丁的相关讨论要点。 @@ -599,7 +599,7 @@ e-mail 标题中的“一句话概述”扼要的描述 e-mail 中的补丁。 将补丁与以前的相关讨论关联起来,例如,将bug修复程序链接到电子邮件和bug报告。 但是,对于多补丁系列,最好避免在回复时使用链接到该系列的旧版本。这样, 补丁的多个版本就不会成为电子邮件客户端中无法管理的引用序列。如果链接有用, -可以使用 https://lkml.kernel.org/ 重定向器(例如,在封面电子邮件文本中) +可以使用 https://lore.kernel.org/ 重定向器(例如,在封面电子邮件文本中) 链接到补丁系列的早期版本。 16) 发送git pull请求 |