Linux中kmalloc内存分配函数的实现

内存分配外部接口函数kmalloc

c 复制代码
static inline void *kmalloc(size_t size, int flags)
{
        if (__builtin_constant_p(size)) {
                int i = 0;
#define CACHE(x) \
                if (size <= x) \
                        goto found; \
                else \
                        i++;
#include "kmalloc_sizes.h"
#undef CACHE
                {
                        extern void __you_cannot_kmalloc_that_much(void);
                        __you_cannot_kmalloc_that_much();
                }
found:
                return kmem_cache_alloc((flags & GFP_DMA) ?
                        malloc_sizes[i].cs_dmacachep :
                        malloc_sizes[i].cs_cachep, flags);
        }
        return __kmalloc(size, flags);
}

1. 函数定义

c 复制代码
static inline void *kmalloc(size_t size, int flags)
  • void *: 返回分配的内存地址
  • size_t size: 请求分配的内存大小
  • int flags: 分配标志(如 GFP_KERNEL, GFP_DMA 等)

2. 编译时常量优化

c 复制代码
if (__builtin_constant_p(size))
  • __builtin_constant_p(): GCC 内置函数,检查 size 是否是编译时常量
  • 如果是常量,编译器可以在编译时确定最佳的内存缓存池

3. 缓存大小查找

c 复制代码
int i = 0;
#define CACHE(x) \
        if (size <= x) \
                goto found; \
        else \
                i++;
#include "kmalloc_sizes.h"
#undef CACHE
  • 定义了一个宏 CACHE(x),接受参数 x
  • 功能:比较请求的 sizex,如果 size <= x 就跳转到 found,否则递增索引 i

包含头文件kmalloc_sizes.h

  • 头文件有下面内容

c 复制代码
...
       CACHE(256)
       CACHE(512)
       CACHE(1024)
       CACHE(2048)
       CACHE(4096)
       CACHE(8192)
       CACHE(16384)
       CACHE(32768)
       CACHE(65536)
       CACHE(131072)
   ...
  • 不断调用CACHE宏找到适合的缓存大小

查找过程:

  • 从最小的缓存大小开始比较
  • 如果 size <= 当前缓存大小,跳转到 found 标签
  • 否则递增索引 i,继续检查下一个更大的缓存

4. 大小超出范围处理

c 复制代码
{
        extern void __you_cannot_kmalloc_that_much(void);
        __you_cannot_kmalloc_that_much();
}
  • 如果请求的大小超过所有预定义的缓存大小
  • 声明并调用一个不存在的函数,导致链接错误
  • 这是一种编译时断言,确保不会请求过大的内存

5. 内存分配执行

c 复制代码
found:
        return kmem_cache_alloc((flags & GFP_DMA) ?
                malloc_sizes[i].cs_dmacachep :
                malloc_sizes[i].cs_cachep, flags);
  • 根据找到的缓存索引 i 选择相应的 slab 缓存
  • 检查 flags 是否包含 GFP_DMA
    • 如果是:使用 DMA 兼容的缓存 (cs_dmacachep)
    • 否则:使用普通缓存 (cs_cachep)
  • 调用 kmem_cache_alloc() 从选定的缓存中分配内存

6. 运行时大小处理

c 复制代码
return __kmalloc(size, flags);
  • 如果 size 不是编译时常量,调用通用的 __kmalloc() 函数
  • 在运行时动态确定合适的缓存大小

7. 设计优势

  1. 性能优化:对编译时常量大小进行快速路径优化
  2. 内存效率:使用预定义的 slab 缓存减少内存碎片
  3. 类型安全:编译时检查过大内存请求
  4. DMA 支持:特殊处理 DMA 内存需求

内核内存分配核心函数__kmalloc

c 复制代码
void * __kmalloc (size_t size, int flags)
{
        struct cache_sizes *csizep = malloc_sizes;

        for (; csizep->cs_size; csizep++) {
                if (size > csizep->cs_size)
                        continue;
#if DEBUG
                /* This happens if someone tries to call
                 * kmem_cache_create(), or kmalloc(), before
                 * the generic caches are initialized.
                 */
                BUG_ON(csizep->cs_cachep == NULL);
#endif
                return __cache_alloc(flags & GFP_DMA ?
                         csizep->cs_dmacachep : csizep->cs_cachep, flags);
        }
        return NULL;
}

1. 函数定义

c 复制代码
void * __kmalloc (size_t size, int flags)
  • void *: 返回分配的内存地址
  • size_t size: 请求分配的内存大小
  • int flags: 分配标志(如 GFP_KERNEL, GFP_DMA 等)

2. 初始化缓存大小指针

c 复制代码
struct cache_sizes *csizep = malloc_sizes;
  • malloc_sizes: 全局数组,包含所有预定义的 slab 缓存大小
  • csizep: 指向当前正在检查的缓存大小条目

3. 遍历缓存大小数组

c 复制代码
for (; csizep->cs_size; csizep++)
  • 循环条件:csizep->cs_size 不为 0(数组以 0 结尾)
  • 每次迭代:csizep++ 移动到下一个更大的缓存大小
  • 这是一个典型的以 NULL 结尾的数组遍历模式

4. 大小检查

c 复制代码
if (size > csizep->cs_size)
        continue;
  • 如果请求的 size 大于当前缓存的 cs_size
  • 跳过当前缓存,继续检查更大的缓存
  • 目的是找到第一个足够大的缓存

5. 调试检查

c 复制代码
#if DEBUG
        /* This happens if someone tries to call
         * kmem_cache_create(), or kmalloc(), before
         * the generic caches are initialized.
         */
        BUG_ON(csizep->cs_cachep == NULL);
#endif
  • 仅在调试模式启用
  • 检查缓存指针是否为 NULL
  • 这种情况发生在:内核初始化完成前就调用 kmalloc
  • BUG_ON(): 如果条件为真,触发内核错误

6. 内存分配执行

c 复制代码
return __cache_alloc(flags & GFP_DMA ?
         csizep->cs_dmacachep : csizep->cs_cachep, flags);
  • 检查 flags 是否包含 GFP_DMA
    • 如果是:使用 DMA 兼容缓存 (cs_dmacachep)
    • 否则:使用普通缓存 (cs_cachep)
  • 调用 __cache_alloc() 从选定的 slab 缓存中分配内存
  • 分配成功后立即返回内存地址

7. 分配失败处理

c 复制代码
return NULL;
  • 如果遍历完所有缓存大小都没有找到合适的缓存
  • 返回 NULL 表示分配失败

slab 分配器的核心缓存分配函数__cache_alloc

c 复制代码
static inline void * __cache_alloc (kmem_cache_t *cachep, int flags)
{
        unsigned long save_flags;
        void* objp;
        struct array_cache *ac;

        cache_alloc_debugcheck_before(cachep, flags);

        local_irq_save(save_flags);
        ac = ac_data(cachep);
        if (likely(ac->avail)) {
                STATS_INC_ALLOCHIT(cachep);
                ac->touched = 1;
                objp = ac_entry(ac)[--ac->avail];
        } else {
                STATS_INC_ALLOCMISS(cachep);
                objp = cache_alloc_refill(cachep, flags);
        }
        local_irq_restore(save_flags);
        objp = cache_alloc_debugcheck_after(cachep, flags, objp, __builtin_return_address(0));
        return objp;
}

1. 函数定义与变量声明

c 复制代码
static inline void * __cache_alloc (kmem_cache_t *cachep, int flags)
{
        unsigned long save_flags;
        void* objp;
        struct array_cache *ac;
  • 功能:从指定的 slab 缓存中快速分配对象
  • cachep: 指向目标 slab 缓存的指针
  • flags: 分配标志
  • save_flags: 保存中断状态
  • objp: 返回的对象指针
  • ac: 指向每CPU缓存结构的指针

2. 调试检查(分配前)

c 复制代码
        cache_alloc_debugcheck_before(cachep, flags);
  • 功能:分配前的调试验证
  • 检查缓存的有效性、状态等
  • 在调试模式下可能进行更严格的检查

3. 中断保护与获取每CPU缓存

c 复制代码
        local_irq_save(save_flags);
        ac = ac_data(cachep);
  • 功能:进入临界区并获取每CPU缓存
  • local_irq_save(): 保存当前中断状态并禁用中断
  • 防止在分配过程中被中断打断,保证操作的原子性
  • ac_data(cachep): 获取当前CPU的本地缓存数组

4. 快速路径:从每CPU缓存分配

c 复制代码
        if (likely(ac->avail)) {
                STATS_INC_ALLOCHIT(cachep);
                ac->touched = 1;
                objp = ac_entry(ac)[--ac->avail];
        }
  • 功能:每CPU缓存命中的快速分配

  • likely(ac->avail): 提示编译器每CPU缓存通常有可用对象

  • STATS_INC_ALLOCHIT: 统计缓存命中次数

  • ac->touched = 1: 标记缓存被访问过(用于缓存维护)

  • ac_entry(ac)[--ac->avail]: 从数组中获取最后一个可用对象并更新计数

    c 复制代码
    static inline void ** ac_entry(struct array_cache *ac)
    {
            return (void**)(ac+1);
    }
    • 返回的是紧接在 array_cache 结构体之后的内存地址

    text 复制代码
    +------------------------+ <-- ac 指针指向这里
    | struct array_cache     |
    |   unsigned int avail   |
    |   unsigned int limit   |
    |   unsigned int batchcount |
    |   unsigned int touched |
    +------------------------+ <-- ac_entry(ac) 返回这里
    | void* entry[0]         |  // 实际的对象指针数组
    | void* entry[1]         |
    | void* entry[2]         |
    | ...                    |
    | void* entry[limit-1]   |
    +------------------------+

5. 慢速路径:缓存未命中时的补充

c 复制代码
        else {
                STATS_INC_ALLOCMISS(cachep);
                objp = cache_alloc_refill(cachep, flags);
        }
  • 功能:每CPU缓存为空时的补充分配
  • STATS_INC_ALLOCMISS: 统计缓存未命中次数
  • cache_alloc_refill(): 从共享的slab中补充对象到每CPU缓存
  • 这个操作较慢,涉及 slab 页框的管理

6. 恢复中断状态

c 复制代码
        local_irq_restore(save_flags);
  • 功能:退出临界区
  • 恢复之前保存的中断状态
  • 允许中断继续处理

7. 调试检查(分配后)

c 复制代码
        objp = cache_alloc_debugcheck_after(cachep, flags, objp, __builtin_return_address(0));
  • 功能:分配后的调试验证
  • 检查分配的对象是否有效
  • __builtin_return_address(0): 获取调用者的返回地址,用于调试追踪

8. 返回分配的对象

c 复制代码
        return objp;
}

slab 分配器的缓存补充函数cache_alloc_refill

c 复制代码
static void* cache_alloc_refill(kmem_cache_t* cachep, int flags)
{
        int batchcount;
        struct kmem_list3 *l3;
        struct array_cache *ac;

        check_irq_off();
        ac = ac_data(cachep);
retry:
        batchcount = ac->batchcount;
        if (!ac->touched && batchcount > BATCHREFILL_LIMIT) {
                /* if there was little recent activity on this
                 * cache, then perform only a partial refill.
                 * Otherwise we could generate refill bouncing.
                 */
                batchcount = BATCHREFILL_LIMIT;
        }
        l3 = list3_data(cachep);

        BUG_ON(ac->avail > 0);
        spin_lock(&cachep->spinlock);
        if (l3->shared) {
                struct array_cache *shared_array = l3->shared;
                if (shared_array->avail) {
                        if (batchcount > shared_array->avail)
                                batchcount = shared_array->avail;
                        shared_array->avail -= batchcount;
                        ac->avail = batchcount;
                        memcpy(ac_entry(ac), &ac_entry(shared_array)[shared_array->avail],
                                        sizeof(void*)*batchcount);
                        shared_array->touched = 1;
                        goto alloc_done;
                }
        }
        while (batchcount > 0) {
                struct list_head *entry;
                struct slab *slabp;
                /* Get slab alloc is to come from. */
                entry = l3->slabs_partial.next;
                if (entry == &l3->slabs_partial) {
                        l3->free_touched = 1;
                        entry = l3->slabs_free.next;
                        if (entry == &l3->slabs_free)
                                goto must_grow;
                }

                slabp = list_entry(entry, struct slab, list);
                check_slabp(cachep, slabp);
                check_spinlock_acquired(cachep);
                while (slabp->inuse < cachep->num && batchcount--) {
                        kmem_bufctl_t next;
                        STATS_INC_ALLOCED(cachep);
                        STATS_INC_ACTIVE(cachep);
                        STATS_SET_HIGH(cachep);

                        /* get obj pointer */
                        ac_entry(ac)[ac->avail++] = slabp->s_mem + slabp->free*cachep->objsize;

                        slabp->inuse++;
                        next = slab_bufctl(slabp)[slabp->free];
#if DEBUG
                        slab_bufctl(slabp)[slabp->free] = BUFCTL_FREE;
#endif
                        slabp->free = next;
                }
                check_slabp(cachep, slabp);

                /* move slabp to correct slabp list: */
                list_del(&slabp->list);
                if (slabp->free == BUFCTL_END)
                        list_add(&slabp->list, &l3->slabs_full);
                else
                        list_add(&slabp->list, &l3->slabs_partial);
        }

must_grow:
        l3->free_objects -= ac->avail;
alloc_done:
        spin_unlock(&cachep->spinlock);

        if (unlikely(!ac->avail)) {
                int x;
                x = cache_grow(cachep, flags, -1);

                // cache_grow can reenable interrupts, then ac could change.
                ac = ac_data(cachep);
                if (!x && ac->avail == 0)       // no objects in sight? abort
                        return NULL;

                if (!ac->avail)         // objects refilled by interrupt?
                        goto retry;
        }
        ac->touched = 1;
        return ac_entry(ac)[--ac->avail];
}

1. 函数定义与变量声明

c 复制代码
static void* cache_alloc_refill(kmem_cache_t* cachep, int flags)
{
        int batchcount;
        struct kmem_list3 *l3;
        struct array_cache *ac;

功能:当每CPU缓存为空时,从共享slab中补充对象

  • batchcount:本次要补充的对象数量
  • l3:指向缓存的核心管理结构(包含所有slab列表)
  • ac:当前CPU的本地缓存

2. 初始检查与准备

c 复制代码
        check_irq_off();
        ac = ac_data(cachep);
retry:
        batchcount = ac->batchcount;
  • check_irq_off():验证中断已禁用(调用者应持有锁)
  • 获取当前CPU的本地缓存指针
  • 设置初始批量大小为配置的批处理数量

3. 智能批量大小调整

c 复制代码
        if (!ac->touched && batchcount > BATCHREFILL_LIMIT) {
                batchcount = BATCHREFILL_LIMIT;
        }

功能:根据缓存活跃度调整补充数量

  • 如果缓存最近未被访问(!ac->touched)且批量过大
  • 限制批量大小,将大请求分散成多个小请求

4. 获取共享缓存和锁

c 复制代码
        l3 = list3_data(cachep);
        BUG_ON(ac->avail > 0);
        spin_lock(&cachep->spinlock);
  • 获取缓存的全局管理结构
  • 验证本地缓存确实为空(调试检查)
  • 获取缓存的自旋锁,保护共享数据结构

5. 尝试从共享缓存获取(快速路径)

c 复制代码
        if (l3->shared) {
                struct array_cache *shared_array = l3->shared;
                if (shared_array->avail) {
                        if (batchcount > shared_array->avail)
                                batchcount = shared_array->avail;
                        shared_array->avail -= batchcount;
                        ac->avail = batchcount;
                        memcpy(ac_entry(ac), &ac_entry(shared_array)[shared_array->avail],
                                        sizeof(void*)*batchcount);
                        shared_array->touched = 1;
                        goto alloc_done;
                }
        }

功能:首先尝试从共享每CPU缓存获取对象

  • 检查是否存在共享缓存且其中有可用对象
  • 调整批量大小不超过共享缓存中的可用数量
  • 从共享缓存批量复制对象到本地缓存
  • 标记共享缓存为已访问
  • 成功后跳转到完成处理

6. 从slab列表获取对象(主要路径)

c 复制代码
        while (batchcount > 0) {
                struct list_head *entry;
                struct slab *slabp;
                /* Get slab alloc is to come from. */
                entry = l3->slabs_partial.next;
                if (entry == &l3->slabs_partial) {
                        l3->free_touched = 1;
                        entry = l3->slabs_free.next;
                        if (entry == &l3->slabs_free)
                                goto must_grow;
                }

功能:遍历slab列表寻找可用对象

  • 优先从部分空闲的slab列表(slabs_partial)获取
  • 如果部分空闲列表为空,尝试从完全空闲列表(slabs_free)获取
  • 如果两个列表都为空,需要增长缓存(goto must_grow

7. 从单个slab中提取对象

c 复制代码
                slabp = list_entry(entry, struct slab, list);
                check_slabp(cachep, slabp);
                check_spinlock_acquired(cachep);
                while (slabp->inuse < cachep->num && batchcount--) {
                        kmem_bufctl_t next;
                        STATS_INC_ALLOCED(cachep);
                        STATS_INC_ACTIVE(cachep);
                        STATS_SET_HIGH(cachep);

                        /* get obj pointer */
                        ac_entry(ac)[ac->avail++] = slabp->s_mem + slabp->free*cachep->objsize;

                        slabp->inuse++;
                        next = slab_bufctl(slabp)[slabp->free];
#if DEBUG
                        slab_bufctl(slabp)[slabp->free] = BUFCTL_FREE;
#endif
                        slabp->free = next;
                }

功能:从选中的slab中批量提取对象

  • slabp->s_mem + slabp->free*cachep->objsize:计算对象内存地址
    • slabp->s_mem:slab的起始存地址
    • slabp->free:下一个空闲对象的索引
    • cachep->objsize:每个对象的大小
    • 公式slab起始地址 + 空闲索引 × 对象大小
  • 更新slab使用计数(inuse++
  • 通过bufctl数组管理下一个空闲对象(slabp->free = next
  • 统计信息更新

8. 维护slab列表状态

c 复制代码
                list_del(&slabp->list);
                if (slabp->free == BUFCTL_END)
                        list_add(&slabp->list, &l3->slabs_full);
                else
                        list_add(&slabp->list, &l3->slabs_partial);
        }

功能:根据slab状态重新分类

  • 从当前列表中移除slab
  • 如果slab已满(free == BUFCTL_END),移到满slab列表
  • 如果还有空闲对象,移回部分空闲列表

9. 缓存增长处理

c 复制代码
must_grow:
        l3->free_objects -= ac->avail;
alloc_done:
        spin_unlock(&cachep->spinlock);
  • 更新空闲对象统计
  • 释放缓存锁

10. 缓存增长与重试逻辑

c 复制代码
        if (unlikely(!ac->avail)) {
                int x;
                x = cache_grow(cachep, flags, -1);

                ac = ac_data(cachep);
                if (!x && ac->avail == 0)
                        return NULL;

                if (!ac->avail)
                        goto retry;
        }

功能:如果补充失败,尝试增长缓存

  • cache_grow():分配新的slab页面
  • 重新获取本地缓存指针(中断可能被重新启用)
  • 如果增长失败且仍无对象,返回NULL
  • 如果增长成功且仍无对象,重试整个流程

11. 最终分配与返回

c 复制代码
        ac->touched = 1;
        return ac_entry(ac)[--ac->avail];
}
  • 标记本地缓存为已访问
  • 从补充后的缓存中分配一个对象返回

slab 缓存增长函数cache_grow

c 复制代码
static int cache_grow (kmem_cache_t * cachep, int flags, int nodeid)
{
        struct slab     *slabp;
        void            *objp;
        size_t           offset;
        int              local_flags;
        unsigned long    ctor_flags;

        /* Be lazy and only check for valid flags here,
         * keeping it out of the critical path in kmem_cache_alloc().
         */
        if (flags & ~(SLAB_DMA|SLAB_LEVEL_MASK|SLAB_NO_GROW))
                BUG();
        if (flags & SLAB_NO_GROW)
                return 0;

        ctor_flags = SLAB_CTOR_CONSTRUCTOR;
        local_flags = (flags & SLAB_LEVEL_MASK);
        if (!(local_flags & __GFP_WAIT))
                /*
                 * Not allowed to sleep.  Need to tell a constructor about
                 * this - it might need to know...
                 */
                ctor_flags |= SLAB_CTOR_ATOMIC;

        /* About to mess with non-constant members - lock. */
        check_irq_off();
        spin_lock(&cachep->spinlock);

        /* Get colour for the slab, and cal the next value. */
        offset = cachep->colour_next;
        cachep->colour_next++;
        if (cachep->colour_next >= cachep->colour)
                cachep->colour_next = 0;
        offset *= cachep->colour_off;

        spin_unlock(&cachep->spinlock);

        if (local_flags & __GFP_WAIT)
                local_irq_enable();

        /*
         * The test for missing atomic flag is performed here, rather than
         * the more obvious place, simply to reduce the critical path length
         * in kmem_cache_alloc(). If a caller is seriously mis-behaving they
         * will eventually be caught here (where it matters).
         */
        kmem_flagcheck(cachep, flags);


        /* Get mem for the objs. */
        if (!(objp = kmem_getpages(cachep, flags, nodeid)))
                goto failed;

        /* Get slab management. */
        if (!(slabp = alloc_slabmgmt(cachep, objp, offset, local_flags)))
                goto opps1;

        set_slab_attr(cachep, slabp, objp);

        cache_init_objs(cachep, slabp, ctor_flags);

        if (local_flags & __GFP_WAIT)
                local_irq_disable();
        check_irq_off();
        spin_lock(&cachep->spinlock);

        /* Make slab active. */
        list_add_tail(&slabp->list, &(list3_data(cachep)->slabs_free));
        STATS_INC_GROWN(cachep);
        list3_data(cachep)->free_objects += cachep->num;
        spin_unlock(&cachep->spinlock);
        return 1;
opps1:
        kmem_freepages(cachep, objp);
failed:
        if (local_flags & __GFP_WAIT)
                local_irq_disable();
        return 0;
}

1. 变量声明和标志检查

c 复制代码
struct slab     *slabp;
void            *objp;
size_t           offset;
int              local_flags;
unsigned long    ctor_flags;

if (flags & ~(SLAB_DMA|SLAB_LEVEL_MASK|SLAB_NO_GROW))
        BUG();
if (flags & SLAB_NO_GROW)
        return 0;
  • 声明局部变量
  • 检查标志位合法性,非法标志触发 BUG
  • 如果设置了 SLAB_NO_GROW(禁止增长),直接返回失败

2. 构造函数标志设置

c 复制代码
ctor_flags = SLAB_CTOR_CONSTRUCTOR;
local_flags = (flags & SLAB_LEVEL_MASK);
if (!(local_flags & __GFP_WAIT))
        ctor_flags |= SLAB_CTOR_ATOMIC;
  • 设置基本的构造函数标志
  • 提取内存分配标志
  • 如果不允许睡眠(无 __GFP_WAIT),设置原子构造函数标志

3. 缓存颜色计算

c 复制代码
check_irq_off();
spin_lock(&cachep->spinlock);

offset = cachep->colour_next;
cachep->colour_next++;
if (cachep->colour_next >= cachep->colour)
        cachep->colour_next = 0;
offset *= cachep->colour_off;

spin_unlock(&cachep->spinlock);
  • 验证中断已关闭
  • 获取缓存锁
  • 计算缓存颜色偏移(用于缓存对齐优化)
  • 颜色机制避免多个 slab 在 CPU 缓存中冲突

4. 中断管理和标志检查

c 复制代码
if (local_flags & __GFP_WAIT)
        local_irq_enable();

kmem_flagcheck(cachep, flags);
  • 如果允许睡眠,启用中断(可能进行页面回收)
  • 调试模式下检查标志合法性

5. 内存页面分配

c 复制代码
if (!(objp = kmem_getpages(cachep, flags, nodeid)))
        goto failed;
  • 分配连续的物理页面给新 slab
  • 失败则跳转到清理路径

6. slab 管理结构分配

c 复制代码
if (!(slabp = alloc_slabmgmt(cachep, objp, offset, local_flags)))
        goto opps1;
  • 分配并初始化 slab 管理结构
  • 失败则跳转到页面释放路径

7. slab 初始化和对象构造

c 复制代码
set_slab_attr(cachep, slabp, objp);
cache_init_objs(cachep, slabp, ctor_flags);
  • 设置 slab 属性
  • 初始化 slab 中的所有对象,调用构造函数

8. 激活新 slab

c 复制代码
if (local_flags & __GFP_WAIT)
        local_irq_disable();
check_irq_off();
spin_lock(&cachep->spinlock);

list_add_tail(&slabp->list, &(list3_data(cachep)->slabs_free));
STATS_INC_GROWN(cachep);
list3_data(cachep)->free_objects += cachep->num;
spin_unlock(&cachep->spinlock);
return 1;
  • 恢复中断状态
  • 获取缓存锁
  • 将新 slab 添加到空闲 slabs 列表
  • 更新统计信息和空闲对象计数
  • 返回成功

9. 错误处理路径

c 复制代码
opps1:
        kmem_freepages(cachep, objp);
failed:
        if (local_flags & __GFP_WAIT)
                local_irq_disable();
        return 0;
  • 释放已分配的页面
  • 恢复中断状态
  • 返回失败

slab 管理结构分配函数alloc_slabmgmt

c 复制代码
static struct slab* alloc_slabmgmt (kmem_cache_t *cachep,
                        void *objp, int colour_off, int local_flags)
{
        struct slab *slabp;

        if (OFF_SLAB(cachep)) {
                /* Slab management obj is off-slab. */
                slabp = kmem_cache_alloc(cachep->slabp_cache, local_flags);
                if (!slabp)
                        return NULL;
        } else {
                slabp = objp+colour_off;
                colour_off += cachep->slab_size;
        }
        slabp->inuse = 0;
        slabp->colouroff = colour_off;
        slabp->s_mem = objp+colour_off;

        return slabp;
}

1. 代码详细解释

c 复制代码
static struct slab* alloc_slabmgmt (kmem_cache_t *cachep,
                        void *objp, int colour_off, int local_flags)
  • cachep: slab 缓存描述符
  • objp: 新分配的页面内存起始地址
  • colour_off: 计算好的缓存颜色偏移量
  • local_flags: 内存分配标志
  • 返回: 初始化好的 slab 管理结构指针

1.1. OFF_SLAB 判断

c 复制代码
if (OFF_SLAB(cachep)) {

OFF_SLAB 含义

  • OFF_SLAB: slab 管理结构存储在 slab 外部(单独的缓存中)
  • !OFF_SLAB: slab 管理结构存储在 slab 内部(页面内存中)

决定因素

  • 对象大小较小 → 使用 ON_SLAB(管理结构在内部)
  • 对象大小较大 → 使用 OFF_SLAB(管理结构在外部)

1.2. OFF_SLAB 路径(外部管理结构)

c 复制代码
slabp = kmem_cache_alloc(cachep->slabp_cache, local_flags);
if (!slabp)
        return NULL;
  • 从专门的 slab 缓存中分配管理结构
  • cachep->slabp_cache:专门用于分配 slab 结构的缓存
  • 如果分配失败,返回 NULL

1.3. ON_SLAB 路径(内部管理结构)

c 复制代码
} else {
        slabp = objp+colour_off;
        colour_off += cachep->slab_size;
}
  • slabp = objp+colour_off:管理结构位于页面内存中的颜色偏移处
  • colour_off += cachep->slab_size:调整颜色偏移,跳过管理结构区域
  • cachep->slab_size:slab 管理结构的大小

1.4. 初始化 slab 结构

c 复制代码
slabp->inuse = 0;
slabp->colouroff = colour_off;
slabp->s_mem = objp+colour_off;
  • inuse = 0:初始时没有对象被使用
  • colouroff = colour_off:记录最终的颜色偏移
  • s_mem = objp+colour_off:设置对象内存区域的起始地址

2. 内存布局对比

2.1. ON_SLAB 布局(管理结构在内部)

复制代码
+-----------------------------------+ <-- objp (页面起始)
| struct slab      (管理结构)       | ← slabp 指向这里
+-----------------------------------+
| 颜色偏移区域     (colour_off)      |
+-----------------------------------+ <-- s_mem (对象起始)
| 对象0                            |
| 对象1                            |
| ...                              |
+-----------------------------------+

OFF_SLAB 布局(管理结构在外部)

复制代码
+-----------------------------------+ <-- objp (页面起始)
| 颜色偏移区域     (colour_off)      |
+-----------------------------------+ <-- s_mem (对象起始)
| 对象0                            |
| 对象1                            |
| ...                              |
+-----------------------------------+

另外在单独内存中:
+-----------------------------------+
| struct slab      (管理结构)       | ← slabp 指向这里(独立分配)
+-----------------------------------+

slab 页面属性设置函数set_slab_attr

c 复制代码
static void set_slab_attr(kmem_cache_t *cachep, struct slab *slabp, void *objp)
{
        int i;
        struct page *page;

        /* Nasty!!!!!! I hope this is OK. */
        i = 1 << cachep->gfporder;
        page = virt_to_page(objp);
        do {
                SET_PAGE_CACHE(page, cachep);
                SET_PAGE_SLAB(page, slabp);
                page++;
        } while (--i);
}

1. 代码详细解释

c 复制代码
static void set_slab_attr(kmem_cache_t *cachep, struct slab *slabp, void *objp)
  • cachep: slab 缓存描述符
  • slabp: slab 管理结构指针
  • objp: slab 所占内存的虚拟地址起始位置

1.1. 变量声明

c 复制代码
int i;
struct page *page;
  • i: 循环计数器,表示需要设置的页面数量
  • page: 指向物理页面描述符的指针

1.2. 计算页面数量

c 复制代码
i = 1 << cachep->gfporder;

解释

  • cachep->gfporder: 分配页面时的阶数(order)
  • 1 << gfporder: 计算这个 slab 占用的连续页面数量
  • 示例
    • gfporder = 01 << 0 = 1 个页面
    • gfporder = 11 << 1 = 2 个页面
    • gfporder = 21 << 2 = 4 个页面

1.3. 获取第一个页面描述符

c 复制代码
page = virt_to_page(objp);

解释

  • virt_to_page(): 将虚拟地址转换为对应的 struct page*
  • 获得 slab 内存起始地址对应的物理页面描述符
  • 这是 Linux 内核中虚拟地址到物理页面转换的标准方法

1.4. 循环设置页面属性

c 复制代码
do {
        SET_PAGE_CACHE(page, cachep);
        SET_PAGE_SLAB(page, slabp);
        page++;
} while (--i);

SET_PAGE_CACHE(page, cachep)

作用:将页面与 slab 缓存关联

c 复制代码
#define	SET_PAGE_CACHE(pg,x)  ((pg)->lru.next = (struct list_head *)(x))
  • 在页面的 LRU 链表字段中存储缓存指针
  • 这样通过页面就能找到它所属的 slab 缓存

SET_PAGE_SLAB(page, slabp)

作用:将页面与 slab 管理结构关联

c 复制代码
#define	SET_PAGE_SLAB(pg,x)   ((pg)->lru.prev = (struct list_head *)(x))
  • 在页面的 LRU 链表字段中存储 slab 结构指针
  • 这样通过页面就能找到对应的 slab 管理结构

page++

作用:移动到下一个连续的物理页面

slab 对象初始化函数cache_init_objs

c 复制代码
static void cache_init_objs (kmem_cache_t * cachep,
                        struct slab * slabp, unsigned long ctor_flags)
{
        int i;

        for (i = 0; i < cachep->num; i++) {
                void* objp = slabp->s_mem+cachep->objsize*i;
#if DEBUG
                /* need to poison the objs? */
                if (cachep->flags & SLAB_POISON)
                        poison_obj(cachep, objp, POISON_FREE);
                if (cachep->flags & SLAB_STORE_USER)
                        *dbg_userword(cachep, objp) = NULL;

                if (cachep->flags & SLAB_RED_ZONE) {
                        *dbg_redzone1(cachep, objp) = RED_INACTIVE;
                        *dbg_redzone2(cachep, objp) = RED_INACTIVE;
                }
                /*
                 * Constructors are not allowed to allocate memory from
                 * the same cache which they are a constructor for.
                 * Otherwise, deadlock. They must also be threaded.
                 */
                if (cachep->ctor && !(cachep->flags & SLAB_POISON))
                        cachep->ctor(objp+obj_dbghead(cachep), cachep, ctor_flags);

                if (cachep->flags & SLAB_RED_ZONE) {
                        if (*dbg_redzone2(cachep, objp) != RED_INACTIVE)
                                slab_error(cachep, "constructor overwrote the"
                                                        " end of an object");
                        if (*dbg_redzone1(cachep, objp) != RED_INACTIVE)
                                slab_error(cachep, "constructor overwrote the"
                                                        " start of an object");
                }
                if ((cachep->objsize % PAGE_SIZE) == 0 && OFF_SLAB(cachep) && cachep->flags & SLAB_POISON)
                        kernel_map_pages(virt_to_page(objp), cachep->objsize/PAGE_SIZE, 0);
#else
                if (cachep->ctor)
                        cachep->ctor(objp, cachep, ctor_flags);
#endif
                slab_bufctl(slabp)[i] = i+1;
        }
        slab_bufctl(slabp)[i-1] = BUFCTL_END;
        slabp->free = 0;
}

1. 代码详细解释

c 复制代码
static void cache_init_objs (kmem_cache_t * cachep,
                        struct slab * slabp, unsigned long ctor_flags)
  • cachep: slab 缓存描述符
  • slabp: 要初始化的 slab 管理结构
  • ctor_flags: 构造函数标志(如是否允许睡眠)

1.1. 循环初始化每个对象

c 复制代码
for (i = 0; i < cachep->num; i++) {
        void* objp = slabp->s_mem+cachep->objsize*i;
  • 遍历 slab 中的每个对象
  • cachep->num: 每个 slab 中的对象总数
  • objp: 计算当前对象的地址 = slab起始地址 + 对象大小 × 索引

1.2. 调试模式下的对象初始化

内存毒化(Poisoning)

c 复制代码
if (cachep->flags & SLAB_POISON)
        poison_obj(cachep, objp, POISON_FREE);

作用:用特殊模式填充空闲对象,用于检测使用未初始化内存

用户追踪

c 复制代码
if (cachep->flags & SLAB_STORE_USER)
        *dbg_userword(cachep, objp) = NULL;

作用:存储分配该对象的用户信息,用于调试内存泄漏

红区保护(Red Zone)

c 复制代码
if (cachep->flags & SLAB_RED_ZONE) {
        *dbg_redzone1(cachep, objp) = RED_INACTIVE;
        *dbg_redzone2(cachep, objp) = RED_INACTIVE;
}

作用:在对象前后添加保护区域,检测缓冲区溢出

1.3. 构造函数调用(调试模式)

c 复制代码
if (cachep->ctor && !(cachep->flags & SLAB_POISON))
        cachep->ctor(objp+obj_dbghead(cachep), cachep, ctor_flags);
  • 调用用户提供的构造函数初始化对象
  • 如果启用了内存毒化,跳过构造函数(避免覆盖毒化模式)
  • obj_dbghead(cachep): 计算调试头部的偏移量

1.4. 红区验证

c 复制代码
if (cachep->flags & SLAB_RED_ZONE) {
        if (*dbg_redzone2(cachep, objp) != RED_INACTIVE)
                slab_error(cachep, "constructor overwrote the end of an object");
        if (*dbg_redzone1(cachep, objp) != RED_INACTIVE)
                slab_error(cachep, "constructor overwrote the start of an object");
}

作用:检查构造函数是否意外覆盖了红区

1.5. 大对象页面映射

c 复制代码
if ((cachep->objsize % PAGE_SIZE) == 0 && OFF_SLAB(cachep) && cachep->flags & SLAB_POISON)
        kernel_map_pages(virt_to_page(objp), cachep->objsize/PAGE_SIZE, 0);

作用:对于跨页面的大对象,设置页面映射属性

1.6. 非调试模式的构造函数调用

c 复制代码
#else
if (cachep->ctor)
        cachep->ctor(objp, cachep, ctor_flags);
#endif
  • 在非调试模式下直接调用构造函数
  • 没有调试头部偏移计算

1.7. 初始化 bufctl 空闲链表

c 复制代码
slab_bufctl(slabp)[i] = i+1;

作用:建立对象的空闲链表

  • slab_bufctl(slabp)[i] = i+1 表示:对象 i 的下一个空闲对象是 i+1

1.8. 完成链表设置

c 复制代码
slab_bufctl(slabp)[i-1] = BUFCTL_END;
slabp->free = 0;
  • 将最后一个对象的 bufctl 设置为 BUFCTL_END
  • 设置 slab 的第一个空闲对象索引为 0
相关推荐
海底列车3 小时前
ubuntu-20.04.6升级OpenSSH_10.2p1
linux·服务器·ubuntu
陳錄生3 小时前
ubuntu 24.10安装MongoDB
linux·mongodb·ubuntu
做运维的阿瑞3 小时前
从传统Linux部署到容器化:实践对比与工程化指南
linux·运维·服务器
NiKo_W3 小时前
Linux 进程通信——基于建造者模式的信号量
linux·设计模式·建造者模式·system v
阿巴~阿巴~4 小时前
Centos 7/8 安装 Redis
linux·服务器·数据库·redis·centos
怀旧,4 小时前
【Linux系统编程】2. Linux基本指令(上)
linux·运维·服务器
骥龙4 小时前
1.2、网络安全攻防实验室搭建指南:VMware + Kali Linux + Win10 全流程
linux·安全·web安全
迎風吹頭髮4 小时前
Linux内核架构浅谈9-Linux内核的开源生态:开发者协作与版本迭代机制
linux·运维·架构
Wang's Blog4 小时前
Linux小课堂: 文件系统结构与核心命令解析
linux·运维·服务器