ARTICLE · INTELLIGENCE

战地情报 · 详情页

来自尧图项目组的一线实战观察与深度解析

page_alloc rmqueue_bulk

page_alloc rmqueue_bulk rmqueue_bulk()是批量补充 PCPPer-CPU Pages缓存的核心函数。它一次性从伙伴系统分配多个页填充到指定 CPU 的 PCP 链表中从而减少对zone-lock的获取次数是分配热路径上关键的批量化优化。函数签名与参数参数含义zone目标 zoneorder请求的阶数PCP 补充通常为 order-0count要补充的页数通常为pcp-batchlist目标链表调用方的 PCP 链表头migratetype迁移类型alloc_flags分配标志返回值实际补充的页数可能小于count若内存不足。核心流程1. 持锁与模式记忆enum rmqueue_mode rmqm RMQUEUE_NORMAL; unsigned long flags; spin_lock_irqsave(zone-lock, flags);获取zone-lock这是保护伙伴系统空闲链表的全局锁。引入rmqm变量用于在批量循环中记忆上次成功的 fallback 模式。2. 批量分配循环for (i 0; i count; i) { struct page *page __rmqueue(zone, order, migratetype, alloc_flags, rmqm); if (unlikely(page NULL)) break; /* 将页加入目标链表 */ list_add_tail(page-lru, list); }每次调用__rmqueue()分配一个页传入rmqm作为“模式指针”。__rmqueue()会从*rmqm指示的模式开始尝试如上次是通过steal成功的这次直接走steal避免每次从头扫描。分配成功后页被追加到调用方提供的链表中。3. 提前终止与返回spin_unlock_irqrestore(zone-lock, flags); return i; /* 实际分配的页数 */若__rmqueue()返回 NULL内存耗尽提前跳出循环。释放锁返回实际补充的页数。rmqm模式记忆的优化原理这是 2025 年补丁引入的关键优化。问题rmqueue_bulk()在持有zone-lock的整个循环中空闲链表不会被外部修改。如果首选策略RMQUEUE_NORMAL已失败对下一个页再次从 NORMAL 开始扫描是浪费——因为链表状态没变结果必然相同。解决__rmqueue()在*mode中记录上次成功的策略。下次调用直接从该策略开始跳过已知失败的前缀。第 1 次NORMAL 失败 → CMA 失败 → CLAIM 成功*mode CLAIM 第 2 次直接从 CLAIM 开始 → 成功跳过了 NORMAL/CMA 的扫描当某个策略成功后“补充”了 NORMAL 类型链表__rmqueue()会将*mode重置为RMQUEUE_NORMAL回归最优路径。在 PCP 机制中的位置rmqueue_bulk()由buffered_rmqueue()在 PCP 链表为空时调用buffered_rmqueue() └── if (list_empty(list)) pcp-count rmqueue_bulk(zone, 0, pcp-batch, list, migratetype, alloc_flags); └── for (i 0; i count; i) __rmqueue(zone, order, migratetype, alloc_flags, rmqm)关键设计点1. 锁内检查的优化历史上rmqueue_bulk()会在持锁循环内对每个分配到的页执行check_pcp_refill()。2023 年的补丁将检查移到锁释放后、仍持有 PCP 锁时执行显著减少了锁持有时间。2. 页序保持对于__GFP_COLD请求rmqueue_bulk()需要反转填充顺序以保持链表中的页按 PFN 递增排序这对某些 I/O 控制器的请求合并有性能帮助。3. 高阶页的尝试在 2.6 时代曾有尝试分配比order更高的连续块来填充 PCP 的补丁以改善缓存着色但现代内核的 PCP 补充主要聚焦于 order-0。总结rmqueue_bulk()是 PCP 缓存的批量补给站它在持有zone-lock的临界区内循环调用__rmqueue()分配多个页并通过rmqm模式记忆避免对已失败 fallback 策略的重复扫描。它通过批量化显著减少锁竞争是分配热路径性能的关键保障。
RELATED READING

延伸阅读

更多一线实战笔记与深度复盘,助您持续精进