SLAB 分配器的初始化kmem_cache_init
c
void __init kmem_cache_init(void)
{
size_t left_over;
struct cache_sizes *sizes;
struct cache_names *names;
/*
* Fragmentation resistance on low memory - only use bigger
* page orders on machines with more than 32MB of memory.
*/
if (num_physpages > (32 << 20) >> PAGE_SHIFT)
slab_break_gfp_order = BREAK_GFP_ORDER_HI;
/* Bootstrap is tricky, because several objects are allocated
* from caches that do not exist yet:
* 1) initialize the cache_cache cache: it contains the kmem_cache_t
* structures of all caches, except cache_cache itself: cache_cache
* is statically allocated.
* Initially an __init data area is used for the head array, it's
* replaced with a kmalloc allocated array at the end of the bootstrap.
* 2) Create the first kmalloc cache.
* The kmem_cache_t for the new cache is allocated normally. An __init
* data area is used for the head array.
* 3) Create the remaining kmalloc caches, with minimally sized head arrays.
* 4) Replace the __init data head arrays for cache_cache and the first
* kmalloc cache with kmalloc allocated arrays.
* 5) Resize the head arrays of the kmalloc caches to their final sizes.
*/
/* 1) create the cache_cache */
init_MUTEX(&cache_chain_sem);
INIT_LIST_HEAD(&cache_chain);
list_add(&cache_cache.next, &cache_chain);
cache_cache.colour_off = cache_line_size();
cache_cache.array[smp_processor_id()] = &initarray_cache.cache;
cache_cache.objsize = ALIGN(cache_cache.objsize, cache_line_size());
cache_estimate(0, cache_cache.objsize, cache_line_size(), 0,
&left_over, &cache_cache.num);
if (!cache_cache.num)
BUG();
cache_cache.colour = left_over/cache_cache.colour_off;
cache_cache.colour_next = 0;
cache_cache.slab_size = ALIGN(cache_cache.num*sizeof(kmem_bufctl_t) +
sizeof(struct slab), cache_line_size());
/* 2+3) create the kmalloc caches */
sizes = malloc_sizes;
names = cache_names;
while (sizes->cs_size) {
/* For performance, all the general caches are L1 aligned.
* This should be particularly beneficial on SMP boxes, as it
* eliminates "false sharing".
* Note for systems short on memory removing the alignment will
* allow tighter packing of the smaller caches. */
sizes->cs_cachep = kmem_cache_create(names->name,
sizes->cs_size, ARCH_KMALLOC_MINALIGN,
(ARCH_KMALLOC_FLAGS | SLAB_PANIC), NULL, NULL);
/* Inc off-slab bufctl limit until the ceiling is hit. */
if (!(OFF_SLAB(sizes->cs_cachep))) {
offslab_limit = sizes->cs_size-sizeof(struct slab);
offslab_limit /= sizeof(kmem_bufctl_t);
}
sizes->cs_dmacachep = kmem_cache_create(names->name_dma,
sizes->cs_size, ARCH_KMALLOC_MINALIGN,
(ARCH_KMALLOC_FLAGS | SLAB_CACHE_DMA | SLAB_PANIC),
NULL, NULL);
sizes++;
names++;
}
/* 4) Replace the bootstrap head arrays */
{
void * ptr;
ptr = kmalloc(sizeof(struct arraycache_init), GFP_KERNEL);
local_irq_disable();
BUG_ON(ac_data(&cache_cache) != &initarray_cache.cache);
memcpy(ptr, ac_data(&cache_cache), sizeof(struct arraycache_init));
cache_cache.array[smp_processor_id()] = ptr;
local_irq_enable();
ptr = kmalloc(sizeof(struct arraycache_init), GFP_KERNEL);
local_irq_disable();
BUG_ON(ac_data(malloc_sizes[0].cs_cachep) != &initarray_generic.cache);
memcpy(ptr, ac_data(malloc_sizes[0].cs_cachep),
sizeof(struct arraycache_init));
malloc_sizes[0].cs_cachep->array[smp_processor_id()] = ptr;
local_irq_enable();
}
/* 5) resize the head arrays to their final sizes */
{
kmem_cache_t *cachep;
down(&cache_chain_sem);
list_for_each_entry(cachep, &cache_chain, next)
enable_cpucache(cachep);
up(&cache_chain_sem);
}
/* Done! */
g_cpucache_up = FULL;
/* Register a cpu startup notifier callback
* that initializes ac_data for all new cpus
*/
register_cpu_notifier(&cpucache_notifier);
/* The reap timers are started later, with a module init call:
* That part of the kernel is not yet operational.
*/
}
函数功能概述
kmem_cache_init
是 SLAB 分配器的核心初始化函数,负责在内核启动早期建立内存缓存管理系统。它通过一个复杂的引导过程来创建管理其他缓存的基础缓存结构
代码详细分析
1. 内存碎片抵抗设置
c
if (num_physpages > (32 << 20) >> PAGE_SHIFT)
slab_break_gfp_order = BREAK_GFP_ORDER_HI;
- 目的:根据系统内存大小设置 SLAB 分配器的页面分配策略
- 逻辑:如果物理内存页数超过 32MB(转换为页数),则使用更大的页面阶数
- 作用:在内存充足的系统上使用更大的连续页面,减少碎片
2. 初始化缓存链
c
init_MUTEX(&cache_chain_sem);
INIT_LIST_HEAD(&cache_chain);
list_add(&cache_cache.next, &cache_chain);
init_MUTEX
:初始化保护缓存链的信号量(互斥锁)INIT_LIST_HEAD
:初始化缓存链表的头节点list_add
:将cache_cache
添加到缓存链表中- 作用:建立缓存管理的基础数据结构
3. 初始化 cache_cache
c
cache_cache.colour_off = cache_line_size();
cache_cache.array[smp_processor_id()] = &initarray_cache.cache;
cache_cache.objsize = ALIGN(cache_cache.objsize, cache_line_size());
colour_off
:设置缓存着色偏移量为缓存行大小,避免缓存伪共享array
:为当前 CPU 设置初始的每 CPU 缓存数组objsize
:对齐对象大小到缓存行边界
4. 计算缓存参数
c
cache_estimate(0, cache_cache.objsize, cache_line_size(), 0,
&left_over, &cache_cache.num);
if (!cache_cache.num)
BUG();
cache_estimate
:计算一个页面 slab 中可以容纳的对象数量left_over
:计算 slab 中剩余的空间BUG()
:如果无法容纳任何对象,触发内核错误
5. 设置缓存颜色和大小
c
cache_cache.colour = left_over/cache_cache.colour_off;
cache_cache.colour_next = 0;
cache_cache.slab_size = ALIGN(cache_cache.num*sizeof(kmem_bufctl_t) +
sizeof(struct slab), cache_line_size());
colour
:计算可用的颜色数量(缓存着色)colour_next
:初始化下一个颜色索引slab_size
:计算 slab 的总大小(包括管理数据和对象)
6. 创建 kmalloc 缓存
c
sizes = malloc_sizes;
names = cache_names;
while (sizes->cs_size) {
sizes->cs_cachep = kmem_cache_create(names->name,
sizes->cs_size, ARCH_KMALLOC_MINALIGN,
(ARCH_KMALLOC_FLAGS | SLAB_PANIC), NULL, NULL);
- 遍历预定义的缓存大小数组
malloc_sizes
- 为每种大小创建通用的 kmalloc 缓存
ARCH_KMALLOC_MINALIGN
:确保缓存对齐SLAB_PANIC
:如果创建失败则内核崩溃
7. 处理 off-slab 控制结构
c
if (!(OFF_SLAB(sizes->cs_cachep))) {
offslab_limit = sizes->cs_size-sizeof(struct slab);
offslab_limit /= sizeof(kmem_bufctl_t);
}
sizes->cs_size
:当前缓存的对象大小sizeof(struct slab)
:slab 管理结构的大小- 结果 :从对象大小中减去管理结构占用的空间,得到实际可用于存储对象的空间
sizeof(kmem_bufctl_t)
:每个对象控制结构的大小- 结果 :计算一个 slab 中最多能容纳的对象数量
8. 创建 DMA 缓存
c
sizes->cs_dmacachep = kmem_cache_create(names->name_dma,
sizes->cs_size, ARCH_KMALLOC_MINALIGN,
(ARCH_KMALLOC_FLAGS | SLAB_CACHE_DMA | SLAB_PANIC),
NULL, NULL);
- 为每种大小创建专门的 DMA 缓存
SLAB_CACHE_DMA
:标志表示该缓存用于 DMA 操作
9. 替换引导头数组
c
ptr = kmalloc(sizeof(struct arraycache_init), GFP_KERNEL);
local_irq_disable();
BUG_ON(ac_data(&cache_cache) != &initarray_cache.cache);
memcpy(ptr, ac_data(&cache_cache), sizeof(struct arraycache_init));
cache_cache.array[smp_processor_id()] = ptr;
local_irq_enable();
- 用动态分配的数组替换静态的引导期数组
- 禁用中断确保操作原子性
- 复制原有数据到新分配的内存
- 更新缓存指针指向新数组
10. 启用每 CPU 缓存
c
down(&cache_chain_sem);
list_for_each_entry(cachep, &cache_chain, next)
enable_cpucache(cachep);
up(&cache_chain_sem);
- 获取缓存链信号量
- 遍历所有缓存,为每个缓存启用 CPU 缓存
enable_cpucache
:调整每 CPU 缓存到优化大小
11. 完成初始化
c
g_cpucache_up = FULL;
register_cpu_notifier(&cpucache_notifier);
- 设置全局状态标志为完全初始化
- 注册 CPU 通知回调,处理新 CPU 的热插拔
关键设计要点
- 引导过程复杂性:函数需要处理"鸡生蛋"问题 - 使用缓存来创建缓存本身
- 渐进式初始化:从静态数据过渡到动态分配的内存
- 性能优化:缓存对齐、缓存着色、每 CPU 缓存
- 错误处理 :使用
SLAB_PANIC
确保关键缓存创建成功 - 可扩展性:支持 CPU 热插拔和动态缓存调整
CPU 热插拔通知回调cpucache_notifier
c
static struct notifier_block cpucache_notifier = { &cpuup_callback, NULL, 0 };
static int __devinit cpuup_callback(struct notifier_block *nfb,
unsigned long action,
void *hcpu)
{
long cpu = (long)hcpu;
kmem_cache_t* cachep;
switch (action) {
case CPU_UP_PREPARE:
down(&cache_chain_sem);
list_for_each_entry(cachep, &cache_chain, next) {
struct array_cache *nc;
nc = alloc_arraycache(cpu, cachep->limit, cachep->batchcount);
if (!nc)
goto bad;
spin_lock_irq(&cachep->spinlock);
cachep->array[cpu] = nc;
cachep->free_limit = (1+num_online_cpus())*cachep->batchcount
+ cachep->num;
spin_unlock_irq(&cachep->spinlock);
}
up(&cache_chain_sem);
break;
case CPU_ONLINE:
start_cpu_timer(cpu);
break;
#ifdef CONFIG_HOTPLUG_CPU
case CPU_DEAD:
/* fall thru */
case CPU_UP_CANCELED:
down(&cache_chain_sem);
list_for_each_entry(cachep, &cache_chain, next) {
struct array_cache *nc;
spin_lock_irq(&cachep->spinlock);
/* cpu is dead; no one can alloc from it. */
nc = cachep->array[cpu];
cachep->array[cpu] = NULL;
cachep->free_limit -= cachep->batchcount;
free_block(cachep, ac_entry(nc), nc->avail);
spin_unlock_irq(&cachep->spinlock);
kfree(nc);
}
up(&cache_chain_sem);
break;
#endif
}
return NOTIFY_OK;
bad:
up(&cache_chain_sem);
return NOTIFY_BAD;
}
函数功能概述
cpuup_callback
是 CPU 热插拔事件的处理函数,负责在 CPU 上线或下线时动态调整 SLAB 分配器的每 CPU 缓存结构,确保内存分配在多 CPU 环境下的正确性和性能
函数工作流程图
CPU热插拔事件触发
↓
switch(action) 根据事件类型处理
↓
CPU_UP_PREPARE: CPU上线准备
├─ 遍历所有缓存
├─ 为新CPU分配array_cache
├─ 更新缓存指针和限制
└─ 完成准备
↓
CPU_ONLINE: CPU上线完成
└─ 启动CPU定时器
↓
CPU_DEAD/CPU_UP_CANCELED: CPU下线
├─ 遍历所有缓存
├─ 清理该CPU的array_cache
├─ 释放内存对象
└─ 更新限制
↓
返回通知结果
代码详细分析
1. 函数定义和参数
c
static int __devinit cpuup_callback(struct notifier_block *nfb,
unsigned long action,
void *hcpu)
{
long cpu = (long)hcpu;
kmem_cache_t* cachep;
nfb
: 通知块指针(这里未使用)action
: 事件类型(CPU上线、下线等)hcpu
: 指向CPU编号的指针cpu = (long)hcpu
: 将CPU指针转换为long类型的CPU编号cachep
: 用于遍历所有缓存的指针
2. CPU 上线准备阶段 (CPU_UP_PREPARE)
c
case CPU_UP_PREPARE:
down(&cache_chain_sem);
- 目的: 在新 CPU 上线之前进行准备工作
down(&cache_chain_sem)
: 获取缓存链信号量,确保操作原子性
c
list_for_each_entry(cachep, &cache_chain, next) {
struct array_cache *nc;
nc = alloc_arraycache(cpu, cachep->limit, cachep->batchcount);
if (!nc)
goto bad;
list_for_each_entry
: 遍历缓存链中的所有缓存alloc_arraycache
: 为新 CPU 分配每 CPU 缓存数组- 参数:
cpu
: CPU 编号cachep->limit
: 该缓存每 CPU 数组的大小限制cachep->batchcount
: 批量操作的对象数量
if (!nc) goto bad
: 如果分配失败,跳转到错误处理
c
spin_lock_irq(&cachep->spinlock);
cachep->array[cpu] = nc;
cachep->free_limit = (1+num_online_cpus())*cachep->batchcount
+ cachep->num;
spin_unlock_irq(&cachep->spinlock);
spin_lock_irq
: 获取缓存自旋锁并禁用中断cachep->array[cpu] = nc
: 将新分配的每 CPU 缓存数组设置到缓存结构中cachep->free_limit
计算:num_online_cpus()
: 当前在线 CPU 数量(1+num_online_cpus())*cachep->batchcount
: 所有 CPU 的批量操作容量+ cachep->num
: 加上一个 slab 中的对象数量- 避免系统卡在"应该回收但无法回收"的状态
- 回收后仍保持足够的缓存水平,避免性能波动
- 作用: 动态调整空闲对象限制,适应变化的 CPU 数量
free_limit
是一个阈值 ,当全局空闲对象数量超过这个值时,系统会开始回收内存而不是继续缓存
spin_unlock_irq
: 释放锁并恢复中断
c
}
up(&cache_chain_sem);
break;
- 结束缓存遍历循环
up(&cache_chain_sem)
: 释放缓存链信号量break
: 退出 switch 语句
3. CPU 上线完成阶段 (CPU_ONLINE)
c
case CPU_ONLINE:
start_cpu_timer(cpu);
break;
- 目的: CPU 完全上线后的后续处理
start_cpu_timer(cpu)
: 启动该 CPU 的缓存回收定时器- 作用: 开始定期回收该 CPU 的闲置缓存对象
4. CPU 下线处理 (CONFIG_HOTPLUG_CPU)
c
#ifdef CONFIG_HOTPLUG_CPU
case CPU_DEAD:
/* fall thru */
case CPU_UP_CANCELED:
down(&cache_chain_sem);
#ifdef CONFIG_HOTPLUG_CPU
: 只在支持 CPU 热插拔时编译CPU_DEAD
: CPU 已下线CPU_UP_CANCELED
: CPU 上线被取消- 两个case共享相同处理逻辑
down(&cache_chain_sem)
: 获取缓存链信号量
c
list_for_each_entry(cachep, &cache_chain, next) {
struct array_cache *nc;
spin_lock_irq(&cachep->spinlock);
/* cpu is dead; no one can alloc from it. */
nc = cachep->array[cpu];
cachep->array[cpu] = NULL;
cachep->free_limit -= cachep->batchcount;
- 遍历所有缓存清理下线 CPU 的资源
spin_lock_irq(&cachep->spinlock)
: 获取缓存锁nc = cachep->array[cpu]
: 获取该 CPU 的缓存数组指针cachep->array[cpu] = NULL
: 清空指针,防止后续访问cachep->free_limit -= cachep->batchcount
: 减少空闲限制,反映 CPU 数量的减少
c
free_block(cachep, ac_entry(nc), nc->avail);
spin_unlock_irq(&cachep->spinlock);
kfree(nc);
free_block(cachep, ac_entry(nc), nc->avail)
:ac_entry(nc)
: 获取缓存数组中的对象指针数组nc->avail
: 当前可用的对象数量- 作用: 将该 CPU 缓存中所有对象释放回全局池
spin_unlock_irq(&cachep->spinlock)
: 释放缓存锁kfree(nc)
: 释放每 CPU 缓存数组结构本身的内存
c
}
up(&cache_chain_sem);
break;
#endif
5. 返回值和错误处理
c
return NOTIFY_OK;
bad:
up(&cache_chain_sem);
return NOTIFY_BAD;
NOTIFY_OK
: 正常处理完成bad:
标签: 错误处理路径(内存分配失败时)NOTIFY_BAD
: 通知处理失败
启动指定 CPU 的 SLAB 缓存回收定时器start_cpu_timer
c
static void __devinit start_cpu_timer(int cpu)
{
struct work_struct *reap_work = &per_cpu(reap_work, cpu);
/*
* When this gets called from do_initcalls via cpucache_init(),
* init_workqueues() has already run, so keventd will be setup
* at that time.
*/
if (keventd_up() && reap_work->func == NULL) {
INIT_WORK(reap_work, cache_reap, NULL);
schedule_delayed_work_on(cpu, reap_work, HZ + 3 * cpu);
}
}
函数功能概述
start_cpu_timer
负责为特定 CPU 初始化并启动一个延迟工作项,用于定期回收该 CPU 的 SLAB 缓存中未使用的对象,防止内存长期闲置
代码详细分析
1. 获取每 CPU 的工作结构
c
struct work_struct *reap_work = &per_cpu(reap_work, cpu);
per_cpu(reap_work, cpu)
: 这是一个每 CPU 变量宏- 作用 : 获取指定 CPU 的
reap_work
变量地址 - 每 CPU 变量特点 :
- 每个 CPU 有自己独立的副本
- 访问不需要加锁(因为其他 CPU 访问的是自己的副本)
reap_work
: 工作队列结构,用于异步执行回收任务
3. 条件检查
c
if (keventd_up() && reap_work->func == NULL)
keventd_up()
- 功能: 检查内核工作队列系统是否已初始化并运行
- 返回: 布尔值,true 表示工作队列可用
reap_work->func == NULL
- 检查: 工作结构的函数指针是否为 NULL
- 目的: 防止重复初始化
- 场景 :
- 如果
func == NULL
: 表示工作项尚未初始化,需要初始化 - 如果
func != NULL
: 表示工作项已初始化,避免重复设置
- 如果
4. 初始化工作结构
c
INIT_WORK(reap_work, cache_reap, NULL);
INIT_WORK
宏
reap_work
: 要初始化的工作结构指针cache_reap
: 实际执行的函数(SLAB 回收函数)NULL
: 传递给回收函数的数据(这里不需要)
5. 调度延迟工作
c
schedule_delayed_work_on(cpu, reap_work, HZ + 3 * cpu);
schedule_delayed_work_on
函数
- 功能: 在指定 CPU 上调度一个延迟执行的工作项
- 参数 :
cpu
: 目标 CPU 编号reap_work
: 要调度的工作结构HZ + 3 * cpu
: 延迟时间(jiffies)
延迟时间计算:HZ + 3 * cpu
HZ
: 系统时钟频率,通常为 100(10ms tick)或 1000(1ms tick)- 如果 HZ=100,表示 1 秒 = 100 jiffies
3 * cpu
: 为每个 CPU 添加不同的偏移量- 实际延迟 :
- CPU0:
HZ + 0
- CPU1:
HZ + 3
- CPU2:
HZ + 6
- ...
- CPU0:
设计目的:
- 错开执行时间: 避免所有 CPU 同时进行缓存回收,减少性能抖动
- 负载均衡: 将回收操作分散到不同时间点
定期清理未使用的内存cache_reap
c
static void cache_reap(void *unused)
{
struct list_head *walk;
if (down_trylock(&cache_chain_sem)) {
/* Give up. Setup the next iteration. */
schedule_delayed_work(&__get_cpu_var(reap_work), REAPTIMEOUT_CPUC + smp_processor_id());
return;
}
list_for_each(walk, &cache_chain) {
kmem_cache_t *searchp;
struct list_head* p;
int tofree;
struct slab *slabp;
searchp = list_entry(walk, kmem_cache_t, next);
if (searchp->flags & SLAB_NO_REAP)
goto next;
check_irq_on();
spin_lock_irq(&searchp->spinlock);
drain_array_locked(searchp, ac_data(searchp), 0);
if(time_after(searchp->lists.next_reap, jiffies))
goto next_unlock;
searchp->lists.next_reap = jiffies + REAPTIMEOUT_LIST3;
if (searchp->lists.shared)
drain_array_locked(searchp, searchp->lists.shared, 0);
if (searchp->lists.free_touched) {
searchp->lists.free_touched = 0;
goto next_unlock;
}
tofree = (searchp->free_limit+5*searchp->num-1)/(5*searchp->num);
do {
p = list3_data(searchp)->slabs_free.next;
if (p == &(list3_data(searchp)->slabs_free))
break;
slabp = list_entry(p, struct slab, list);
BUG_ON(slabp->inuse);
list_del(&slabp->list);
STATS_INC_REAPED(searchp);
/* Safe to drop the lock. The slab is no longer
* linked to the cache.
* searchp cannot disappear, we hold
* cache_chain_lock
*/
searchp->lists.free_objects -= searchp->num;
spin_unlock_irq(&searchp->spinlock);
slab_destroy(searchp, slabp);
spin_lock_irq(&searchp->spinlock);
} while(--tofree > 0);
next_unlock:
spin_unlock_irq(&searchp->spinlock);
next:
;
}
check_irq_on();
up(&cache_chain_sem);
/* Setup the next iteration */
schedule_delayed_work(&__get_cpu_var(reap_work), REAPTIMEOUT_CPUC + smp_processor_id());
}
函数功能概述
cache_reap
是 SLAB 分配器的周期性回收函数,负责清理各 CPU 的本地缓存和全局空闲列表中未使用的内存 slab,防止内存长期闲置,同时平衡内存使用效率和分配性能
代码详细分析
1. 信号量尝试获取
c
if (down_trylock(&cache_chain_sem)) {
/* Give up. Setup the next iteration. */
schedule_delayed_work(&__get_cpu_var(reap_work), REAPTIMEOUT_CPUC + smp_processor_id());
return;
}
down_trylock(&cache_chain_sem)
: 非阻塞方式尝试获取缓存链信号量- 成功获取返回 0
- 获取失败返回非 0
- 如果获取失败: 说明其他线程正在操作缓存链
schedule_delayed_work(&__get_cpu_var(reap_work), REAPTIMEOUT_CPUC + smp_processor_id())
:__get_cpu_var(reap_work)
: 获取当前 CPU 的回收工作结构REAPTIMEOUT_CPUC + smp_processor_id()
: 延迟时间加上 CPU ID 偏移- 目的: 稍后重试,避免阻塞
return
: 直接返回,不执行本次回收
2. 遍历缓存链
c
list_for_each(walk, &cache_chain) {
kmem_cache_t *searchp;
struct list_head* p;
int tofree;
struct slab *slabp;
searchp = list_entry(walk, kmem_cache_t, next);
list_for_each(walk, &cache_chain)
: 遍历缓存链表中的所有缓存- 变量声明:
searchp
: 当前处理的缓存指针p
: 临时链表指针tofree
: 要释放的 slab 数量slabp
: slab 结构指针
searchp = list_entry(walk, kmem_cache_t, next)
:- 从链表节点获取包含它的
kmem_cache_t
结构 walk
是list_head
指针,通过next
字段找到父结构
- 从链表节点获取包含它的
3. 跳过无需回收的缓存
c
if (searchp->flags & SLAB_NO_REAP)
goto next;
SLAB_NO_REAP
: 缓存标志位,表示该缓存不应被自动回收- 使用场景: 某些关键缓存(如缓存描述符自身)可能设置此标志
goto next
: 跳过当前缓存,继续处理下一个
4. 中断状态检查和锁定
c
check_irq_on();
spin_lock_irq(&searchp->spinlock);
check_irq_on()
: 调试检查,确保中断已开启- 在调试版本中可能检查中断状态
- 生产版本可能是空宏
spin_lock_irq(&searchp->spinlock)
:- 获取缓存的自旋锁
- 同时禁用本地中断(防止中断处理程序竞争)
5. 清空每 CPU 缓存
c
drain_array_locked(searchp, ac_data(searchp), 0);
ac_data(searchp)
: 获取当前 CPU 的每 CPU 缓存数组drain_array_locked(searchp, array, 0)
:- 将每 CPU 缓存中的空闲对象转移到全局空闲列表
- 参数
0
表示不强制清空,保留一些对象供快速分配
6. 回收时间间隔检查
c
if(time_after(searchp->lists.next_reap, jiffies))
goto next_unlock;
searchp->lists.next_reap = jiffies + REAPTIMEOUT_LIST3;
time_after(searchp->lists.next_reap, jiffies)
:- 检查是否到达下一次回收时间
- 如果
next_reap > jiffies
,说明还未到时间
goto next_unlock
: 跳过本次回收,解锁并继续下一个缓存searchp->lists.next_reap = jiffies + REAPTIMEOUT_LIST3
:- 设置下一次回收时间
REAPTIMEOUT_LIST3
是回收间隔
7. 处理共享缓存和访问标记
c
if (searchp->lists.shared)
drain_array_locked(searchp, searchp->lists.shared, 0);
if (searchp->lists.free_touched) {
searchp->lists.free_touched = 0;
goto next_unlock;
}
- 共享缓存处理: 如果存在共享每 CPU 缓存,也清空它
free_touched
检查:- 该标记表示最近有分配/释放活动
- 如果被设置,说明缓存正在活跃使用,跳过本次回收
- 清除标记以便下次检查
8. 计算要释放的 slab 数量
c
tofree = (searchp->free_limit+5*searchp->num-1)/(5*searchp->num);
详细解释:
- 公式分析: 这是一种向上取整的除法
- 计算逻辑: 释放约 1/5 的超限部分
- 策略: 渐进式释放,避免一次性释放过多影响性能
9. 释放空闲 slab 循环
c
do {
p = list3_data(searchp)->slabs_free.next;
if (p == &(list3_data(searchp)->slabs_free))
break;
slabp = list_entry(p, struct slab, list);
BUG_ON(slabp->inuse);
list_del(&slabp->list);
STATS_INC_REAPED(searchp);
list3_data(searchp)->slabs_free.next
: 获取空闲 slab 链表第一个节点- 链表空检查: 如果指向链表头,说明没有空闲 slab,退出循环
slabp = list_entry(p, struct slab, list)
: 获取 slab 结构BUG_ON(slabp->inuse)
: 调试检查,确保 slab 确实完全空闲list_del(&slabp->list)
: 从空闲链表中移除STATS_INC_REAPED(searchp)
: 增加回收统计计数
10. 安全释放 slab
c
searchp->lists.free_objects -= searchp->num;
spin_unlock_irq(&searchp->spinlock);
slab_destroy(searchp, slabp);
spin_lock_irq(&searchp->spinlock);
} while(--tofree > 0);
free_objects -= searchp->num
: 更新空闲对象计数- 关键技巧 : 释放锁后再销毁 slab
spin_unlock_irq(&searchp->spinlock)
: 释放锁,允许其他操作slab_destroy(searchp, slabp)
: 实际释放内存页(可能耗时)spin_lock_irq(&searchp->spinlock)
: 重新获取锁继续操作
- 安全性: slab 已从链表移除,其他线程不会访问到
while(--tofree > 0)
: 继续释放直到达到目标数量
11. 清理和重新调度
c
next_unlock:
spin_unlock_irq(&searchp->spinlock);
next:
;
}
check_irq_on();
up(&cache_chain_sem);
/* Setup the next iteration */
schedule_delayed_work(&__get_cpu_var(reap_work), REAPTIMEOUT_CPUC + smp_processor_id());
- 跳转标签 :
next_unlock
: 解锁当前缓存next
: 继续下一个缓存
up(&cache_chain_sem)
: 释放缓存链信号量- 最终重新调度:
- 无论成功与否都安排下一次回收
- 确保回收机制持续运行