__rmqueue_pcplist()是 PCP(Per-CPU Pages)缓存分配路径的核心内部函数,负责从当前 CPU 的 PCP 链表中取页,并在链表为空时触发批量补充。
核心作用与定位
它是rmqueue_pcplist()的下游函数,调用方必须已经持有 PCP 锁。它的职责是在 PCP 缓存中查找可用页块,如果目标链表为空,则从伙伴系统批量补充。
核心逻辑
static inline struct page * __rmqueue_pcplist(struct zone *zone, unsigned int order, int migratetype, unsigned int alloc_flags, struct per_cpu_pages *pcp, struct list_head *list) { struct page *page; do { if (list_empty(list)) { int batch = nr_pcp_alloc(pcp, zone, order); int alloced; alloced = rmqueue_bulk(zone, order, batch, list, migratetype, alloc_flags); pcp->count += alloced << order; if (unlikely(list_empty(list))) return NULL; } page = list_first_entry(list, struct page, lru); list_del(&page->lru); pcp->count -= 1 << order; } while (check_new_pcp(page, order)); return page; }逐步说明
1. 链表为空时批量补充
if (list_empty(list)) { int batch = nr_pcp_alloc(pcp, zone, order); int alloced = rmqueue_bulk(zone, order, batch, list, migratetype, alloc_flags); pcp->count += alloced << order; if (unlikely(list_empty(list))) return NULL; }nr_pcp_alloc()动态计算批量大小。rmqueue_bulk()从伙伴系统批量取页,填入 PCP 链表。更新
pcp->count(页数,非块数)。若补充后链表仍为空(伙伴系统无页),直接返回
NULL。
2. 从链表头部取页
page = list_first_entry(list, struct page, lru); list_del(&page->lru); pcp->count -= 1 << order;
从链表头部取页(LIFO),优先复用最近释放的页。
更新 PCP 计数。
3. 坏页检查与重试
} while (check_new_pcp(page, order));
调试配置下,用
check_new_pcp()校验取出的页。若发现坏页,循环重试,避免把损坏的页交给调用方。
2022 年的计数溢出修复
2022 年 Chen Wandun 发现了一个问题:当目标 order 的链表为空,而其他 order 的链表非空时,rmqueue_bulk()可能补充过多页,导致pcp->count超过pcp->high。
修复方案是在__rmqueue_pcplist()中增加一行钳制:
batch = min(batch, (high - pcp->count) >> order);
确保补充后不会超过高水位。这个修复后来被整合进nr_pcp_alloc()的逻辑中。
在分配路径中的位置
rmqueue_pcplist() └── pcp_spin_lock(...) // 获取 PCP 锁 └── __rmqueue_pcplist(zone, order, migratetype, alloc_flags, pcp, list) // ← 这里 ├── if (list_empty) rmqueue_bulk(...) // 批量补充 ├── list_del(...) // 从链表取页 └── while (check_new_pcp(...)) // 坏页重试
总结
__rmqueue_pcplist()是 PCP 缓存的取页执行器:它在持有 PCP 锁的前提下,检查目标链表是否为空,为空则通过nr_pcp_alloc()和rmqueue_bulk()从伙伴系统批量补充;随后从链表头部取页并更新计数,调试配置下通过check_new_pcp()重试坏页。它是rmqueue_pcplist()的核心实现,与nr_pcp_alloc()、rmqueue_bulk()紧密协作,构成 PCP 分配路径的完整链路。