背景

DMA 一致性分配先尝试 CMA 等连续内存路径,失败后回退到 buddy 分配器。buddy 只按 2 的幂 order 出页,回退路径一直用 get_order(size) 把请求向上取整成 order,再把整块占住。请求页数不是 2 的幂时,取整多出的尾部页也跟着这次分配被占住,直到释放才归还。

请求 3 页实际占 4 页
┌─────────────────────────────────────┐
│ DMA 请求 3 页,get_order 向上取整到 │
│ order 2                             │
└──────────────────┬──────────────────┘
                   ▼
┌─────────────────────────────────────┐
│ buddy 分出 4 页整块,全部归这次分配 │
└──────────────────┬──────────────────┘
                   ▼
┌─────────────────────────────────────┐
│ 多出的第 4 页被这次分配占住,别人用 │
│ 不上                                │
└─────────────────────────────────────┘

问题

  • DMA 回退路径用 get_order(size) 把请求向上取整到 2 的幂 order
  • 非 2 的幂请求把取整后的整块占住,尾部页闲置到释放才归还
  • 请求离 2 的幂越远浪费越大,如申请 3 页实际占 4 页

方案

把 DMA 回退路径的分配与释放换成 alloc_pages_exact_nid() 与 free_pages_exact() 这对精确分配接口。

alloc_pages_exact_nid() 仍按取整后的 order 从 buddy 拿整块,随后 make_alloc_exact() 把块切开:前面的请求页逐页独立引用计数留给调用者,多出的尾页逐页还给 buddy。释放侧 free_pages_exact() 按请求页数逐页归还,与分配对称。调用者从拿到块的那一刻起只占实际请求的页数。

切开后尾页还给 buddy
┌──────────────────────────────────────┐
│ alloc_pages_exact_nid 仍按 order 2   │
│ 从 buddy 拿 4 页                     │
└──────────────────┬───────────────────┘
                   ▼
┌──────────────────────────────────────┐
│ make_alloc_exact 把块切开,前 3 页留 │
│ 给调用者                             │
└──────────────────┬───────────────────┘
                   ▼
┌──────────────────────────────────────┐
│ 多出的第 4 页还给 buddy,可被其他分  │
│ 配复用                               │
└──────────────────────────────────────┘

替换覆盖回退路径的全部环节:dma-iommu、direct、ops_helpers 三条分配路径,加上 dma_free_contiguous() 的 buddy 回退释放路径。alloc_pages_exact_nid() 原本标注 __meminit 只供内存初始化路径使用,DMA 运行时会调它,标注随之去掉。

收益

作者未提供性能数据,从代码逻辑推断的预期收益:

  • 非 2 的幂 DMA 请求不再占住取整后的整块,尾部页立即可被其他分配复用
  • 请求离 2 的幂越远省得越多,最多接近整块的一半
  • CMA 主分配路径不变,只有 buddy 回退路径改变行为