Linux中处理CPU离线时清理CPU缓存page_alloc_init函数的实现
注册CPU离线通知page_alloc_init
static int page_alloc_cpu_notify(struct notifier_block *self,unsigned long action, void *hcpu)
{int cpu = (unsigned long)hcpu;long *count;if (action == CPU_DEAD) {/* Drain local pagecache count. */count = &per_cpu(nr_pagecache_local, cpu);atomic_add(*count, &nr_pagecache);*count = 0;local_irq_disable();__drain_pages(cpu);local_irq_enable();}return NOTIFY_OK;
}
#endif /* CONFIG_HOTPLUG_CPU */void __init page_alloc_init(void)
{hotcpu_notifier(page_alloc_cpu_notify, 0);
}
1. 函数功能
处理CPU离线事件,清理该CPU的本地页面缓存,确保内存管理的正确性
2. 代码详细解释
2.1. 函数定义和参数解析
static int page_alloc_cpu_notify(struct notifier_block *self,unsigned long action, void *hcpu)
static
:函数只在当前文件内可见page_alloc_cpu_notify
:CPU通知回调函数struct notifier_block *self
:通知块结构指针unsigned long action
:事件动作类型void *hcpu
:包含CPU编号的指针
2.2. 变量声明
int cpu = (unsigned long)hcpu;long *count;
int cpu = (unsigned long)hcpu
:将void指针转换为CPU编号hcpu
实际上是一个指向CPU编号的指针,需要强制类型转换
long *count
:指向每CPU页面缓存计数的指针
2.3. CPU离线事件处理
if (action == CPU_DEAD) {
action == CPU_DEAD
:检查是否是CPU离线事件CPU_DEAD
:表示一个CPU核心已经被关闭(热插拔移除)- 只有在CPU离线时才执行后续清理操作
2.4. 合并页面缓存计数
/* Drain local pagecache count. */count = &per_cpu(nr_pagecache_local, cpu);atomic_add(*count, &nr_pagecache);*count = 0;
count = &per_cpu(nr_pagecache_local, cpu)
:- 获取即将离线CPU的本地页面缓存计数指针
per_cpu
宏获取特定CPU的变量实例
atomic_add(*count, &nr_pagecache)
:- 原子操作将本地计数加到全局计数中
- 确保离线CPU的页面缓存被正确统计
*count = 0
:将本地计数清零
2.5. 清理CPU的页面
local_irq_disable();__drain_pages(cpu);local_irq_enable();
local_irq_disable()
:禁用本地CPU的中断- 防止在页面清理过程中被中断打断
__drain_pages(cpu)
:核心操作 - 排空指定CPU的页面- 释放该CPU持有的所有空闲页面
- 将这些页面返回到全局内存池中
local_irq_enable()
:重新启用中断
2.6. 函数返回
}return NOTIFY_OK;
}
return NOTIFY_OK
:返回通知处理成功NOTIFY_OK
:表示通知已被成功处理- 即使不是
CPU_DEAD
事件也返回OK,因为只关心CPU离线事件
2.7. 条件编译结束
#endif /* CONFIG_HOTPLUG_CPU */
详细解释:
- 结束
#ifdef CONFIG_HOTPLUG_CPU
的条件编译块 - 只有在配置了CPU热插拔支持时,这段代码才会被编译
2.8. 初始化函数
void __init page_alloc_init(void)
{hotcpu_notifier(page_alloc_cpu_notify, 0);
}
void __init page_alloc_init(void)
:页面分配器初始化函数__init
表示该函数只在系统初始化期间使用
hotcpu_notifier(page_alloc_cpu_notify, 0)
:- 注册CPU热插拔通知回调
page_alloc_cpu_notify
:回调函数指针
排空指定CPU的每CPU页面缓存__drain_pages
#if defined(CONFIG_PM) || defined(CONFIG_HOTPLUG_CPU)
static void __drain_pages(unsigned int cpu)
{struct zone *zone;int i;for_each_zone(zone) {struct per_cpu_pageset *pset;pset = &zone->pageset[cpu];for (i = 0; i < ARRAY_SIZE(pset->pcp); i++) {struct per_cpu_pages *pcp;pcp = &pset->pcp[i];pcp->count -= free_pages_bulk(zone, pcp->count,&pcp->list, 0);}}
}
1. 函数功能
当CPU离线或系统挂起时,清空指定CPU在所有内存区域中的每CPU页面缓存,将这些页面返回到伙伴系统中
2. 代码详细解释
2.1. 条件编译和函数定义
#if defined(CONFIG_PM) || defined(CONFIG_HOTPLUG_CPU)
static void __drain_pages(unsigned int cpu)
#if defined(CONFIG_PM) || defined(CONFIG_HOTPLUG_CPU)
:条件编译- 只有在配置了电源管理(
CONFIG_PM
)或CPU热插拔(CONFIG_HOTPLUG_CPU
)时才编译此函数
- 只有在配置了电源管理(
static void __drain_pages(unsigned int cpu)
:函数定义static
:函数只在当前文件内可见__drain_pages
:内部函数,通常以__
前缀表示unsigned int cpu
:要清理的CPU编号
2.2. 变量声明
struct zone *zone;int i;
struct zone *zone
:指向内存区域的指针int i
:循环计数器,用于遍历每CPU页面的迁移类型
2.3. 遍历所有内存区域
for_each_zone(zone) {
for_each_zone(zone)
:宏,遍历系统中所有的内存区域(zone)- 包括:ZONE_DMA, ZONE_NORMAL, ZONE_HIGHMEM等
- 对于每个zone,都需要清理指定CPU的页面缓存
2.4. 获取每CPU页面集
struct per_cpu_pageset *pset;pset = &zone->pageset[cpu];
struct per_cpu_pageset *pset
:指向每CPU页面集的指针pset = &zone->pageset[cpu]
:获取指定CPU在该zone中的页面集zone->pageset
:指向每CPU页面集数组的指针[cpu]
:索引到特定CPU的页面集
2.5. 遍历所有迁移类型
for (i = 0; i < ARRAY_SIZE(pset->pcp); i++) {
i = 0
:从第一个迁移类型开始i < ARRAY_SIZE(pset->pcp)
:遍历所有迁移类型pset->pcp
:每CPU页面数组,包含不同迁移类型的页面列表ARRAY_SIZE
:计算数组大小的宏
2.6. 获取每CPU页面结构
struct per_cpu_pages *pcp;pcp = &pset->pcp[i];
struct per_cpu_pages *pcp
:指向每CPU页面结构的指针pcp = &pset->pcp[i]
:获取特定迁移类型的每CPU页面结构pset->pcp[i]
:第i个迁移类型的每CPU页面缓存
2.7. 释放页面并更新计数
pcp->count -= free_pages_bulk(zone, pcp->count,&pcp->list, 0);
参数分解:
free_pages_bulk(zone, // 目标内存区域pcp->count, // 要释放的页面数量&pcp->list, // 页面链表头0) // 冷热页面标志(0表示不区分)
执行过程:
-
free_pages_bulk()
:批量释放页面到伙伴系统- 将
pcp->list
链表中的所有页面释放回zone的伙伴系统 - 返回实际成功释放的页面数量
- 将
-
pcp->count -= ...
:更新页面计数- 从当前计数中减去已释放的页面数量
- 如果所有页面都成功释放,
pcp->count
将变为0
很好的问题!通过GDB的输出,我们可以更清楚地理解这些数据结构的关系。让我详细解释:
3. 数据结构关系解析
完整的数据结构层次
struct per_cpu_pageset {struct per_cpu_pages pcp[2]; // 包含2个per_cpu_pages结构
}struct per_cpu_pages {int count; // 当前链表中的页面数量int low; // 低水位线int high; // 高水位线 int batch; // 批量操作大小struct list_head list; // 页面链表头
}
3.1. 详细关系解释
3.2. 为什么是pcp[2]
? - 冷热页面机制
Linux内核使用冷热页面概念来优化内存分配性能:
// pcp[0] 和 pcp[1] 的区别:
pcp[0]: 热页面 (Hot Pages) - 很可能在CPU缓存中
pcp[1]: 冷页面 (Cold Pages) - 不在CPU缓存中
4. 冷热页面的作用和区别
分配时的选择策略
// 当需要分配页面时:
if (分配标志包含 __GFP_COLD) {// 从冷页面链表分配 (pcp[1])// 用于不太可能被立即访问的数据
} else {// 从热页面链表分配 (pcp[0]) // 用于可能被频繁访问的数据
}
实际使用场景
热页面 (pcp[0]
):
- 进程堆栈
- 进程数据段
- 内核数据结构
- 预期会被频繁访问
冷页面 (pcp[1]
):
- DMA缓冲区
- 文件读写缓存
- 一次性使用的临时数据
- 不太可能被CPU频繁访问
5. 水位线控制机制
结构体字段作用
struct per_cpu_pages {int count; // 当前链表中的页面数量int low; // 低水位线 - 当count低于此值时补充页面int high; // 高水位线 - 当count高于此值时返还页面 int batch; // 每次补充或返还的批量大小struct list_head list; // 实际的页面链表
}
6. 总结
per_cpu_pageset
和 per_cpu_pages
的关系是:
- 容器与内容:
pageset
包含2个pages
结构(冷和热) - 性能优化:通过冷热分离提高CPU缓存利用率
- 批量管理:使用水位线机制智能补充和返还页面
- 每CPU隔离:每个CPU有自己的缓存,避免锁竞争
在__drain_pages
中,这个设计意味着:
- 需要清理两次(冷页面和热页面)
- 确保CPU离线时所有缓存页面都被释放
- 维持内存管理的正确性和完整性