PCP(Per-CPU Pages)缓存的禁用与启用函数,用于在内存热插拔等需要“冻结”PCP 缓存的场景中,临时关闭某个 zone 的 PCP 机制。
一、zone_pcp_disable()—— 禁用 zone 的 PCP 缓存
void zone_pcp_disable(struct zone *zone) { mutex_lock(&pcp_batch_high_lock); __zone_set_pageset_high_and_batch(zone, 0, 0, 1); __drain_all_pages(zone, true); }逐步解析
1. 获取全局互斥锁
mutex_lock(&pcp_batch_high_lock);
前面讲过的
pcp_batch_high_lock:保护 PCP 的high/batch字段更新。获取它确保在禁用期间,没有其他更新者并发修改 PCP 配置。
注意:这把锁在
zone_pcp_enable()中才释放,形成disable/enable 的配对临界区。
2. 把 PCP 配置设为“禁用”状态
__zone_set_pageset_high_and_batch(zone, 0, 0, 1);
前面讲过的
__zone_set_pageset_high_and_batch():遍历所有 CPU,更新 PCP 的high_min、high_max、batch。参数
(0, 0, 1)的含义:high_min = 0、high_max = 0:高水位为 0,意味着 PCP 缓存不能保留任何页——一旦有页放入,立即触发批量归还。batch = 1:批量大小为 1,每次只处理一页。
效果:PCP 缓存形同虚设,所有分配/释放几乎直接走伙伴系统。
3. 强制排空所有 CPU 的 PCP 缓存
__drain_all_pages(zone, true);
前面讲过的
__drain_all_pages(zone, force_all_cpus):排空指定 zone 在所有 CPU 上的 PCP 缓存。force_all_cpus = true:强制在所有 CPU 上执行排空,跳过“非空检查”的竞态优化。这是为了确保彻底清空——因为内存热插拔要求该 zone 的 PCP 缓存中不能有任何页残留,否则隔离/下线会失败。
为什么需要force_all_cpus = true?
前面讲__drain_all_pages()时提到过:普通的非空检查存在竞态——一个 CPU 的 PCP 看起来是空的,但另一个 CPU 正好在释放页到该 PCP 且尚未增加count。内存热插拔对“彻底清空”有强要求,所以必须强制排空所有 CPU。
二、zone_pcp_enable()—— 重新启用 zone 的 PCP 缓存
void zone_pcp_enable(struct zone *zone) { __zone_set_pageset_high_and_batch(zone, zone->pageset_high_min, zone->pageset_high_max, zone->pageset_batch); mutex_unlock(&pcp_batch_high_lock); }逐步解析
1. 恢复 zone 的正常 PCP 配置
__zone_set_pageset_high_and_batch(zone, zone->pageset_high_min, zone->pageset_high_max, zone->pageset_batch);
从 zone 的
pageset_*字段中取回禁用前的正常配置:zone->pageset_high_min/pageset_high_max:该 zone 的高水位范围;zone->pageset_batch:批量大小。
这些值是在
zone_set_pageset_high_and_batch()中计算并保存的。通过
__zone_set_pageset_high_and_batch()重新分发到所有 CPU 的 PCP 结构。
2. 释放全局互斥锁
mutex_unlock(&pcp_batch_high_lock);
释放
pcp_batch_high_lock,结束 disable/enable 的临界区。之后其他更新者可以正常修改 PCP 配置。
三、disable/enable 的配对使用
这两个函数必须成对调用:
zone_pcp_disable(zone); // 加锁,禁用 PCP,排空 /* ... 执行需要 PCP 无残留的操作,如内存隔离、下线 ... */ zone_pcp_enable(zone); // 恢复 PCP,解锁
典型使用场景
1. 内存热插拔下线
offline_pages()在隔离 pageblock 之前,需要确保 PCP 缓存中没有该 zone 的页,否则这些页可能“躲过”隔离。所以先zone_pcp_disable(),下线完成后再zone_pcp_enable()。
2. CMA 分配
alloc_contig_range()在需要大块连续内存时,可能通过zone_pcp_disable()确保 PCP 缓存不干扰隔离。
3. 内存规整
某些极端场景下,规整需要 PCP 缓存无残留。
四、与前面内容的呼应
| 前面出现过的 | 这里的关联 |
|---|---|
pcp_batch_high_lock | disable 时获取,enable 时释放 |
__zone_set_pageset_high_and_batch() | 两函数都调用它更新 PCP 配置 |
__drain_all_pages(zone, true) | disable 时强制排空 |
zone->pageset_high_min/pageset_high_max/pageset_batch | enable 时恢复这些值 |
page_alloc_cpu_online()/page_alloc_cpu_dead() | CPU 热插拔也涉及 PCP 配置 |
内存热插拔offline_pages() | 典型调用场景 |
五、小结表
| 函数 | 操作 | 锁 |
|---|---|---|
zone_pcp_disable(zone) | 设置 PCP 为high=0, batch=1(禁用)+ 强制排空所有 CPU | 获取pcp_batch_high_lock |
zone_pcp_enable(zone) | 恢复 zone 的正常high_min/high_max/batch | 释放pcp_batch_high_lock |
一句话总结
zone_pcp_disable()和zone_pcp_enable()是 PCP 缓存的禁用/启用配对函数:disable 时获取pcp_batch_high_lock,把 zone 的 PCP 配置设为high=0, batch=1使其形同虚设,并强制排空所有 CPU 的 PCP 缓存以确保无页残留;enable 时从 zone 保存的pageset_*字段恢复正常配置并释放锁。它们用于内存热插拔、CMA 分配等需要 PCP 缓存彻底清空的场景,确保隔离和迁移操作不受 PCP 中滞留页的干扰。