3. 避免反弹缓冲区

本节提供关于在 Linux 2.4 内核上应用和使用反弹缓冲区补丁的信息。反弹缓冲区补丁由 Jens Axboe 编写,使支持直接内存访问 (DMA) I/O 到高地址物理内存的设备驱动程序能够避免反弹缓冲区。

本文档简要概述了 Linux 内核中的内存和寻址,然后介绍了为什么以及如何使用反弹缓冲区补丁。

3.1. Linux 2.4 内核中的内存和寻址

Linux 2.4 内核包含用于指定目标计算机中物理内存量的配置选项。默认情况下,配置限制为可以从 PAGE_OFFSET 开始直接映射到内核虚拟地址空间的内存量。在 i386 系统上,默认映射方案将内核模式可寻址性限制为物理内存的第一个千兆字节 (GB),也称为低内存。相反,高内存通常是 1 GB 以上的内存。高内存不能被内核直接访问或永久映射。对高内存的支持是一个选项,在Linux 内核配置期间启用。

3.2. 反弹缓冲区的问题

当对高内存执行 DMA I/O 时,会在低内存中分配一个区域,称为反弹缓冲区。当数据在设备和高内存之间传输时,它首先通过反弹缓冲区进行复制。

具有大量高内存和密集 I/O 活动的系统可能会创建大量的反弹缓冲区,这可能会导致内存短缺问题。此外,过多的反弹缓冲区数据副本可能会导致性能下降。

外围组件互连 (PCI) 设备通常寻址高达 4 GB 的物理内存。当反弹缓冲区用于低于 4 GB 的高内存时,时间和内存会被浪费,因为外围设备有能力直接寻址该内存。使用反弹缓冲区补丁可以减少甚至消除反弹缓冲区的使用。

3.3. 查找补丁

最新版本的反弹缓冲区补丁是 block-highmem-all-18b.bz2,可以从 Andrea Arcangeli 的 -aa 系列内核中获取,网址为 https://linuxkernel.org.cn/pub/linux/kernel/people/andrea/kernels/v2.4/

3.3.1. 配置 Linux 内核以避免反弹缓冲区

本节包含有关配置 Linux 内核以避免反弹缓冲区的信息。《Linux Kernel-HOWTO》在 http://www.linuxdoc.org/HOWTO/Kernel-HOWTO.html 中解释了重新编译 Linux 内核的过程。

以下内核配置选项是启用反弹缓冲区补丁所必需的

开发代码 - 要使配置器显示高 I/O 支持选项,请选择代码成熟度级别选项类别,并指定 "y" 为提示开发和/或不完整的代码/驱动程序.

高内存支持 - 要启用对大于 1 GB 的物理内存的支持,请选择处理器类型和特性类别,并从高内存支持选项中选择一个值。

高内存 I/O 支持 - 要启用对大于 1 GB 的物理地址的 DMA I/O,请选择处理器类型和特性类别,并在HIGHMEM I/O 支持选项中输入 "y"。此配置选项是由反弹缓冲区补丁引入的新选项。

3.4. 修改您的设备驱动程序以避免反弹缓冲区

如果您的设备驱动程序未在上面的启用的设备驱动程序部分列出,并且该设备能够进行高内存 DMA I/O,您可以修改您的设备驱动程序以使用反弹缓冲区补丁,如下所示。有关重新构建 Linux 设备驱动程序的更多信息,请访问 http://www.xml.com/ldd/chapter/book/index.html

  1. A.) 对于 SCSI 适配器驱动程序:设置highmem_io位在Scsi_Host_Template结构中。

    B.) 对于 IDE 适配器驱动程序:设置highmem位在ide_hwif_t结构中。

  2. 调用pci_set_dma_mask(struct pci_dev *pdev, dma_addr_t mask)以指定设备可以在 DMA 操作中成功使用的地址位数。

    如果使用指定的掩码可以支持 DMA I/O,pci_set_dma_mask()将设置pdev->dma_mask并返回 0。对于 SCSI 或 IDE,掩码值也将由中级驱动程序传递给blk_queue_bounce_limit(request_queue_t *q, u64 dma_addr)以便不会为设备直接可寻址的内存创建反弹缓冲区。SCSI 或 IDE 以外的驱动程序必须直接调用blk_queue_bounce_limit()

  3. 使用pci_map_page(dev, page, offset, size, direction),而不是pci_map_single(dev, address, size, direction)来映射内存区域,使其可被外围设备访问。pci_map_page()同时支持高内存和低内存。

    address参数对于pci_map_single()pageoffset参数对于pci_map_page(). 使用virt_to_page()宏来转换addresspageoffset。该virt_to_page()宏由包含 pci.h 定义。例如

    void *address;
    struct page *page;
    unsigned long offset;
    page = virt_to_page(address);
    offset = (unsigned long) address & ~PAGE_MASK;

    调用pci_unmap_page()在 DMA I/O 传输完成后移除由pci_map_page().

    Note

    pci_map_single()建立的映射,使用virt_to_bus(). virt_to_bus()仅处理低内存地址。支持高内存的驱动程序不应再调用virt_to_bus()bus_to_virt().

  4. 如果您的驱动程序调用pci_map_sg()来映射散布-收集 DMA 操作,您的驱动程序应设置pageoffset字段而不是address字段scatterlist结构。请参阅步骤 3 以转换addresspageoffset.

    Note

    如果您的驱动程序已在使用 PCI DMA API,请继续使用pci_map_page()pci_map_sg()作为适当的方式。但是,请勿使用address字段scatterlist结构中。