Linux中slab缓存初始化kmem_cache_init函数和定时回收函数的实现

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 的热插拔

关键设计要点

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

设计目的:

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

定期清理未使用的内存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 结构
    • walklist_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): 释放缓存链信号量
  • 最终重新调度:
    • 无论成功与否都安排下一次回收
    • 确保回收机制持续运行
相关推荐
草莓熊Lotso3 小时前
Linux 进阶指令实操指南:文件查看、时间管理、搜索压缩全场景覆盖(附高频案例)
linux·运维·服务器
Cx330❀3 小时前
《Linux进阶指令实操指南》:文件查看、时间管理、搜索压缩全覆盖(附高频案例)
linux·运维·服务器
努力努力再努力wz4 小时前
【C++进阶系列】:万字详解unordered_set和unordered_map,带你手搓一个哈希表!(附模拟实现unordered_set和unordered_map的源码)
java·linux·开发语言·数据结构·数据库·c++·散列表
Small___ming4 小时前
【Linux基础学习】Linux Ubuntu 权限管理:从入门到精通
linux·学习·ubuntu
tan77º4 小时前
【项目】基于多设计模式下的同步&异步日志系统 - 项目介绍与前置知识
linux·c++·设计模式
yalipf5 小时前
忘记密码更改ubuntu18.08的密码--前提是要知道用户名work
linux·运维·ubuntu
怀旧,5 小时前
【Linux系统编程】3. Linux基本指令(下)
linux·开发语言·c++
艾莉丝努力练剑5 小时前
【C++STL :stack && queue (三) 】优先级队列的使用以及底层实现
linux·开发语言·数据结构·c++·stl
web安全工具库6 小时前
Makefile 模式规则精讲:从 %.o: %.c 到静态模式规则的终极自动化
linux·运维·c语言·开发语言·数据库·自动化