Linux中slab缓存初始化kmem_cache_init函数和定时回收函数的实现
SLAB 分配器的初始化kmem_cache_init
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. 内存碎片抵抗设置
if (num_physpages > (32 << 20) >> PAGE_SHIFT)slab_break_gfp_order = BREAK_GFP_ORDER_HI;
- 目的:根据系统内存大小设置 SLAB 分配器的页面分配策略
- 逻辑:如果物理内存页数超过 32MB(转换为页数),则使用更大的页面阶数
- 作用:在内存充足的系统上使用更大的连续页面,减少碎片
2. 初始化缓存链
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
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. 计算缓存参数
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. 设置缓存颜色和大小
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 缓存
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 控制结构
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 缓存
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. 替换引导头数组
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 缓存
down(&cache_chain_sem);
list_for_each_entry(cachep, &cache_chain, next)enable_cpucache(cachep);
up(&cache_chain_sem);
- 获取缓存链信号量
- 遍历所有缓存,为每个缓存启用 CPU 缓存
enable_cpucache
:调整每 CPU 缓存到优化大小
11. 完成初始化
g_cpucache_up = FULL;
register_cpu_notifier(&cpucache_notifier);
- 设置全局状态标志为完全初始化
- 注册 CPU 通知回调,处理新 CPU 的热插拔
关键设计要点
- 引导过程复杂性:函数需要处理"鸡生蛋"问题 - 使用缓存来创建缓存本身
- 渐进式初始化:从静态数据过渡到动态分配的内存
- 性能优化:缓存对齐、缓存着色、每 CPU 缓存
- 错误处理:使用
SLAB_PANIC
确保关键缓存创建成功 - 可扩展性:支持 CPU 热插拔和动态缓存调整
CPU 热插拔通知回调cpucache_notifier
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_CPUcase 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. 函数定义和参数
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)
case CPU_UP_PREPARE:down(&cache_chain_sem);
- 目的: 在新 CPU 上线之前进行准备工作
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;
list_for_each_entry
: 遍历缓存链中的所有缓存alloc_arraycache
: 为新 CPU 分配每 CPU 缓存数组- 参数:
cpu
: CPU 编号cachep->limit
: 该缓存每 CPU 数组的大小限制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);
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
: 释放锁并恢复中断
}up(&cache_chain_sem);break;
- 结束缓存遍历循环
up(&cache_chain_sem)
: 释放缓存链信号量break
: 退出 switch 语句
3. CPU 上线完成阶段 (CPU_ONLINE)
case CPU_ONLINE:start_cpu_timer(cpu);break;
- 目的: CPU 完全上线后的后续处理
start_cpu_timer(cpu)
: 启动该 CPU 的缓存回收定时器- 作用: 开始定期回收该 CPU 的闲置缓存对象
4. CPU 下线处理 (CONFIG_HOTPLUG_CPU)
#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)
: 获取缓存链信号量
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 数量的减少
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 缓存数组结构本身的内存
}up(&cache_chain_sem);break;
#endif
5. 返回值和错误处理
return NOTIFY_OK;
bad:up(&cache_chain_sem);return NOTIFY_BAD;
NOTIFY_OK
: 正常处理完成bad:
标签: 错误处理路径(内存分配失败时)NOTIFY_BAD
: 通知处理失败
启动指定 CPU 的 SLAB 缓存回收定时器start_cpu_timer
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 的工作结构
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. 条件检查
if (keventd_up() && reap_work->func == NULL)
keventd_up()
- 功能: 检查内核工作队列系统是否已初始化并运行
- 返回: 布尔值,true 表示工作队列可用
reap_work->func == NULL
- 检查: 工作结构的函数指针是否为 NULL
- 目的: 防止重复初始化
- 场景:
- 如果
func == NULL
: 表示工作项尚未初始化,需要初始化 - 如果
func != NULL
: 表示工作项已初始化,避免重复设置
- 如果
4. 初始化工作结构
INIT_WORK(reap_work, cache_reap, NULL);
INIT_WORK
宏
reap_work
: 要初始化的工作结构指针cache_reap
: 实际执行的函数(SLAB 回收函数)NULL
: 传递给回收函数的数据(这里不需要)
5. 调度延迟工作
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
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. 信号量尝试获取
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. 遍历缓存链
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. 跳过无需回收的缓存
if (searchp->flags & SLAB_NO_REAP)goto next;
SLAB_NO_REAP
: 缓存标志位,表示该缓存不应被自动回收- 使用场景: 某些关键缓存(如缓存描述符自身)可能设置此标志
goto next
: 跳过当前缓存,继续处理下一个
4. 中断状态检查和锁定
check_irq_on();
spin_lock_irq(&searchp->spinlock);
check_irq_on()
: 调试检查,确保中断已开启- 在调试版本中可能检查中断状态
- 生产版本可能是空宏
spin_lock_irq(&searchp->spinlock)
:- 获取缓存的自旋锁
- 同时禁用本地中断(防止中断处理程序竞争)
5. 清空每 CPU 缓存
drain_array_locked(searchp, ac_data(searchp), 0);
ac_data(searchp)
: 获取当前 CPU 的每 CPU 缓存数组drain_array_locked(searchp, array, 0)
:- 将每 CPU 缓存中的空闲对象转移到全局空闲列表
- 参数
0
表示不强制清空,保留一些对象供快速分配
6. 回收时间间隔检查
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. 处理共享缓存和访问标记
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 数量
tofree = (searchp->free_limit+5*searchp->num-1)/(5*searchp->num);
详细解释:
- 公式分析: 这是一种向上取整的除法
- 计算逻辑: 释放约 1/5 的超限部分
- 策略: 渐进式释放,避免一次性释放过多影响性能
9. 释放空闲 slab 循环
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
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. 清理和重新调度
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)
: 释放缓存链信号量- 最终重新调度:
- 无论成功与否都安排下一次回收
- 确保回收机制持续运行