当前位置: 首页 > news >正文

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

内存分配外部接口函数kmalloc

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. 函数定义

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

2. 编译时常量优化

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

3. 缓存大小查找

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

  • 头文件有下面内容

  • ...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. 大小超出范围处理

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

5. 内存分配执行

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. 运行时大小处理

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

7. 设计优势

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

内核内存分配核心函数__kmalloc

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);
#endifreturn __cache_alloc(flags & GFP_DMA ?csizep->cs_dmacachep : csizep->cs_cachep, flags);}return NULL;
}

1. 函数定义

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

2. 初始化缓存大小指针

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

3. 遍历缓存大小数组

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

4. 大小检查

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

5. 调试检查

#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. 内存分配执行

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. 分配失败处理

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

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

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. 函数定义与变量声明

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. 调试检查(分配前)

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

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

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

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

        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]: 从数组中获取最后一个可用对象并更新计数

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

    • +------------------------+ <-- 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. 慢速路径:缓存未命中时的补充

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

6. 恢复中断状态

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

7. 调试检查(分配后)

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

8. 返回分配的对象

        return objp;
}

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

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 DEBUGslab_bufctl(slabp)[slabp->free] = BUFCTL_FREE;
#endifslabp->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);elselist_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? abortreturn NULL;if (!ac->avail)         // objects refilled by interrupt?goto retry;}ac->touched = 1;return ac_entry(ac)[--ac->avail];
}

1. 函数定义与变量声明

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. 初始检查与准备

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

3. 智能批量大小调整

        if (!ac->touched && batchcount > BATCHREFILL_LIMIT) {batchcount = BATCHREFILL_LIMIT;}

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

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

4. 获取共享缓存和锁

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

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

        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列表获取对象(主要路径)

        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中提取对象

                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 DEBUGslab_bufctl(slabp)[slabp->free] = BUFCTL_FREE;
#endifslabp->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列表状态

                list_del(&slabp->list);if (slabp->free == BUFCTL_END)list_add(&slabp->list, &l3->slabs_full);elselist_add(&slabp->list, &l3->slabs_partial);}

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

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

9. 缓存增长处理

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

10. 缓存增长与重试逻辑

        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. 最终分配与返回

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

slab 缓存增长函数cache_grow

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. 变量声明和标志检查

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. 构造函数标志设置

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

3. 缓存颜色计算

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. 中断管理和标志检查

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

5. 内存页面分配

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

6. slab 管理结构分配

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

7. slab 初始化和对象构造

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

8. 激活新 slab

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. 错误处理路径

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

slab 管理结构分配函数alloc_slabmgmt

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. 代码详细解释

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 判断

if (OFF_SLAB(cachep)) {

OFF_SLAB 含义

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

决定因素

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

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

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

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

} 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 结构

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

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. 代码详细解释

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

1.1. 变量声明

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

1.2. 计算页面数量

i = 1 << cachep->gfporder;

解释

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

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

page = virt_to_page(objp);

解释

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

1.4. 循环设置页面属性

do {SET_PAGE_CACHE(page, cachep);SET_PAGE_SLAB(page, slabp);page++;
} while (--i);

SET_PAGE_CACHE(page, cachep)

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

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

SET_PAGE_SLAB(page, slabp)

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

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

page++

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

slab 对象初始化函数cache_init_objs

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);
#elseif (cachep->ctor)cachep->ctor(objp, cachep, ctor_flags);
#endifslab_bufctl(slabp)[i] = i+1;}slab_bufctl(slabp)[i-1] = BUFCTL_END;slabp->free = 0;
}

1. 代码详细解释

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

1.1. 循环初始化每个对象

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

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

内存毒化(Poisoning)

if (cachep->flags & SLAB_POISON)poison_obj(cachep, objp, POISON_FREE);

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

用户追踪

if (cachep->flags & SLAB_STORE_USER)*dbg_userword(cachep, objp) = NULL;

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

红区保护(Red Zone)

if (cachep->flags & SLAB_RED_ZONE) {*dbg_redzone1(cachep, objp) = RED_INACTIVE;*dbg_redzone2(cachep, objp) = RED_INACTIVE;
}

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

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

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

1.4. 红区验证

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. 大对象页面映射

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. 非调试模式的构造函数调用

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

1.7. 初始化 bufctl 空闲链表

slab_bufctl(slabp)[i] = i+1;

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

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

1.8. 完成链表设置

slab_bufctl(slabp)[i-1] = BUFCTL_END;
slabp->free = 0;
  • 将最后一个对象的 bufctl 设置为 BUFCTL_END
  • 设置 slab 的第一个空闲对象索引为 0
http://www.dtcms.com/a/469196.html

相关文章:

  • 【Spring Security】Spring Security 概念
  • 杂记 12
  • 织梦程序如何搭建网站洛阳凯锦腾网业有限公司
  • Socket网络编程(2)-command_server
  • vscode 连接远程服务器同步方法
  • 传统数据安全措施与云计算数据安全的区别
  • Linux下如何在vim里使用异步编译和运行?
  • Python高效实现Excel转PDF:无Office依赖的轻量化方案
  • 做网站PPPOE网络可以吗一个好网站设计
  • 混淆矩阵在金融领域白话解说
  • 深耕金融调研领域,用科学调研破解银行服务困境(市场调研)
  • 未备案网站处理系统写作墨问题 网站
  • 【Linux】手搓日志(附源码)
  • Excel 下拉选项设置 级联式
  • pycharm自动化测试初始化
  • nacos3.0.4升级到3.1.0
  • linux入门5.5(高可用)
  • JAVA·数组的定义与使用
  • Transformer 面试题及详细答案120道(81-90)-- 性能与评估
  • 可以做软件的网站有哪些功能中国新闻社待遇
  • 【鉴权架构】SpringBoot + Sa-Token + MyBatis + MySQL + Redis 实现用户鉴权、角色管理、权限管理
  • 三星S25Ultra/S24安卓16系统Oneui8成功获取完美root权限+LSP框架
  • ffmpeg 播放视频 暂停
  • 老题新解|大整数的因子
  • Eureka的自我保护机制
  • 探索颜色科学:从物理现象到数字再现
  • AirSim_SimJoyStick
  • 第五部分:VTK高级功能模块(第149章 Remote模块 - 远程模块类)
  • 道可云人工智能每日资讯|《政务领域人工智能大模型部署应用指引》发布
  • 自己做网站哪家好win10 wordpress安装教程视频