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
- 功能:比较请求的
size
与x
,如果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
)
- 如果是:使用 DMA 兼容的缓存 (
- 调用
kmem_cache_alloc()
从选定的缓存中分配内存
6. 运行时大小处理
return __kmalloc(size, flags);
- 如果
size
不是编译时常量,调用通用的__kmalloc()
函数 - 在运行时动态确定合适的缓存大小
7. 设计优势
- 性能优化:对编译时常量大小进行快速路径优化
- 内存效率:使用预定义的 slab 缓存减少内存碎片
- 类型安全:编译时检查过大内存请求
- 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
)
- 如果是:使用 DMA 兼容缓存 (
- 调用
__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 = 0
→1 << 0 = 1
个页面gfporder = 1
→1 << 1 = 2
个页面gfporder = 2
→1 << 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