本文档简要概述了 Linux 内核中的内存和寻址,然后介绍了为什么以及如何使用反弹缓冲区补丁。
Linux 2.4 内核包含用于指定目标计算机中物理内存量的配置选项。默认情况下,配置限制为可以从 PAGE_OFFSET 开始直接映射到内核虚拟地址空间的内存量。在 i386 系统上,默认映射方案将内核模式可寻址性限制为物理内存的第一个千兆字节 (GB),也称为低内存。相反,高内存通常是 1 GB 以上的内存。高内存不能被内核直接访问或永久映射。对高内存的支持是一个选项,在Linux 内核配置期间启用。
当对高内存执行 DMA I/O 时,会在低内存中分配一个区域,称为反弹缓冲区。当数据在设备和高内存之间传输时,它首先通过反弹缓冲区进行复制。
具有大量高内存和密集 I/O 活动的系统可能会创建大量的反弹缓冲区,这可能会导致内存短缺问题。此外,过多的反弹缓冲区数据副本可能会导致性能下降。
最新版本的反弹缓冲区补丁是 block-highmem-all-18b.bz2,可以从 Andrea Arcangeli 的 -aa 系列内核中获取,网址为 https://linuxkernel.org.cn/pub/linux/kernel/people/andrea/kernels/v2.4/。
本节包含有关配置 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"。此配置选项是由反弹缓冲区补丁引入的新选项。
反弹缓冲区补丁提供了内核基础设施,以及 SCSI 和 IDE 中级驱动程序修改,以支持 DMA I/O 到高内存。补丁中还包含对多个设备驱动程序的更新,以利用添加的支持。
如果应用了反弹缓冲区补丁,并且您配置内核以支持高内存 I/O,则许多 IDE 配置和下面列出的设备驱动程序执行 DMA I/O 时无需使用反弹缓冲区
aic7xxx_drv.o |
aic7xxx_old.o |
cciss.o |
cpqarray.o |
megaraid.o |
qlogicfc.o |
sym53c8xx.o |
如果您的设备驱动程序未在上面的启用的设备驱动程序部分列出,并且该设备能够进行高内存 DMA I/O,您可以修改您的设备驱动程序以使用反弹缓冲区补丁,如下所示。有关重新构建 Linux 设备驱动程序的更多信息,请访问 http://www.xml.com/ldd/chapter/book/index.html。
A.) 对于 SCSI 适配器驱动程序:设置highmem_io位在Scsi_Host_Template结构中。
B.) 对于 IDE 适配器驱动程序:设置highmem位在ide_hwif_t结构中。
调用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()。
使用pci_map_page(dev, page, offset, size, direction),而不是pci_map_single(dev, address, size, direction)来映射内存区域,使其可被外围设备访问。pci_map_page()同时支持高内存和低内存。
的address参数对于pci_map_single()与page和offset参数对于pci_map_page(). 使用virt_to_page()宏来转换address到page和offset。该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().
![]() | pci_map_single()建立的映射,使用virt_to_bus(). virt_to_bus()仅处理低内存地址。支持高内存的驱动程序不应再调用virt_to_bus()或bus_to_virt(). |
如果您的驱动程序调用pci_map_sg()来映射散布-收集 DMA 操作,您的驱动程序应设置page和offset字段而不是address字段scatterlist结构。请参阅步骤 3 以转换address到page和offset.
![]() | 如果您的驱动程序已在使用 PCI DMA API,请继续使用pci_map_page()或pci_map_sg()作为适当的方式。但是,请勿使用address字段scatterlist结构中。 |