警告:我不是 linux 内核方面的专家。
LDD book(这可能是更好的开始阅读)表示 DMA 池更适合较小的 dma 区域(比页面短)-https://static.lwn.net/images/pdf/LDD3/ch15.pdf 第 447 页或https://www.oreilly.com/library/view/linux-device-drivers/0596005903/ch15.html,“DMA 池”部分:
DMA 池是一种用于小型、一致的 DMA 映射的分配机制。从dma_alloc_coherent 获得的映射可能最小为一页。如果您的设备需要比这更小的 DMA 区域,您可能应该使用 DMA 池。 DMA 池在您可能想要对嵌入在较大结构中的小区域执行 DMA 的情况下也很有用。一些非常模糊的驱动程序错误已被追溯到与小 DMA 区域相邻的结构字段的缓存一致性问题。为避免此问题,您应该始终明确地为 DMA 操作分配区域,远离其他非 DMA 数据结构。 ...分配由dma_pool_alloc处理
https://www.kernel.org/doc/Documentation/DMA-API-HOWTO.txt 中也有同样的说明
如果您的驱动程序需要大量较小的内存区域,您可以编写
自定义代码来细分 dma_alloc_coherent() 返回的页面,
或者您可以使用 dma_pool API 来执行此操作。 dma_pool 就像
一个 kmem_cache,但它使用 dma_alloc_coherent(),而不是 __get_free_pages()。
此外,它了解对齐的常见硬件约束,
比如需要在 N 字节边界上对齐的队列头。
因此,DMA 池针对较小的分配进行了优化。您可以为每个小的 dma 内存单独使用dma_alloc_coherent(具有更大的开销),或者您可以尝试构建自己的池(更多自定义代码来管理偏移和分配)。但是 DMA 池已经实现并且可以使用。
应针对您的案例分析方法的性能。
网络驱动中的动态dma注册示例(用于skb片段):
https://elixir.bootlin.com/linux/v4.6/source/drivers/net/ethernet/realtek/r8169.c
static struct sk_buff *rtl8169_alloc_rx_data
mapping = dma_map_single(d, rtl8169_align(data), rx_buf_sz,
DMA_FROM_DEVICE);
static int rtl8169_xmit_frags
mapping = dma_map_single(d, addr, len, DMA_TO_DEVICE);
static netdev_tx_t rtl8169_start_xmit
mapping = dma_map_single(d, skb->data, len, DMA_TO_DEVICE);
static void rtl8169_unmap_tx_skb
dma_unmap_single(d, le64_to_cpu(desc->addr), len, DMA_TO_DEVICE);
就地为 dma 注册 skb 片段(如果 NIC 芯片支持 sg dma)比将每个片段从 skb 复制到某些 DMA 内存中更好。查看“了解 Linux 网络内部结构”一书的“dev_queue_xmit 函数”部分和第 21 章;和skb_linearize
DMA 池使用示例 - nvme 驱动程序(prp 为 part of Submission Queue Element,物理区域页面,64 位指针,以及“PRP 列表包含通常没有偏移的 PRP 列表。”):
https://elixir.bootlin.com/linux/v4.6/source/drivers/nvme/host/pci.c#L1807
static int nvme_setup_prp_pools(struct nvme_dev *dev)
{
dev->prp_page_pool = dma_pool_create("prp list page", dev->dev,
PAGE_SIZE, PAGE_SIZE, 0);
static bool nvme_setup_prps
prp_list = dma_pool_alloc(pool, GFP_ATOMIC, &prp_dma);
static void nvme_free_iod
dma_pool_free(dev->prp_small_pool, list[0], prp_dma);