实用指南: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 的热插拔

关键设计要点

  1. 引导过程复杂性:函数需要处理"鸡生蛋"问题 - 使用缓存来创建缓存本身
  2. 渐进式初始化:从静态数据过渡到动态分配的内存
  3. 性能优化:缓存对齐、缓存着色、每 CPU 缓存
  4. 错误处理:使用 SLAB_PANIC 确保关键缓存创建成功
  5. 可扩展性:支持 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_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. 函数定义和参数

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

设计目的:

  1. 错开执行时间: 避免所有 CPU 同时进行缓存回收,减少性能抖动
  2. 负载均衡: 将回收操作分散到不同时间点

定期清理未使用的内存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 结构
    • walklist_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): 释放缓存链信号量
  • 最终重新调度:
    • 无论成功与否都安排下一次回收
    • 确保回收机制持续运行
posted on 2025-11-14 12:12  ljbguanli  阅读(0)  评论(0)    收藏  举报