Linux中kfree内存回收函数的实现
内核内存释放函数kfree
void kfree (const void *objp)
{kmem_cache_t *c;unsigned long flags;if (!objp)return;local_irq_save(flags);kfree_debugcheck(objp);c = GET_PAGE_CACHE(virt_to_page(objp));__cache_free(c, (void*)objp);local_irq_restore(flags);
}
1. 代码详细解释
void kfree (const void *objp)
objp
: 要释放的对象指针,由kmalloc
分配- 无返回值
1.1. NULL 指针检查
if (!objp)return;
安全防护:如果传入空指针,直接返回
- 避免对 NULL 指针操作导致系统崩溃
1.2. 保存并禁用中断
local_irq_save(flags);
关键操作:
local_irq_save(flags)
: 保存当前中断状态到 flags,并禁用中断- 为什么需要:防止在释放过程中被中断打断,保证操作的原子性
- 保护 slab 缓存数据结构的一致性
1.3. 调试检查
kfree_debugcheck(objp);
调试功能:在调试模式下进行各种完整性检查
1.4. 获取对象所属的缓存
c = GET_PAGE_CACHE(virt_to_page(objp));
关键查找:
virt_to_page(objp)
: 将对象虚拟地址转换为对应的物理页面描述符GET_PAGE_CACHE()
: 从页面描述符中获取关联的 slab 缓存
内存关联回顾:
// 在 set_slab_attr 中建立的关联:
// page->lru.next = (struct list_head*)cachep;
// 现在通过 GET_PAGE_CACHE 反向查找
1.5. 实际释放操作
__cache_free(c, (void*)objp);
核心释放:将对象释放回对应的 slab 缓存
c
: 对象所属的 slab 缓存(void*)objp
: 要释放的对象指针(去掉 const 限定)
1.6. 恢复中断状态
local_irq_restore(flags);
清理操作:恢复之前保存的中断状态
- 重新启用中断(如果之前是启用的)
- 保证系统中断响应的正常性
slab 缓存释放函数__cache_free
static inline void __cache_free (kmem_cache_t *cachep, void* objp)
{struct array_cache *ac = ac_data(cachep);check_irq_off();objp = cache_free_debugcheck(cachep, objp, __builtin_return_address(0));if (likely(ac->avail < ac->limit)) {STATS_INC_FREEHIT(cachep);ac_entry(ac)[ac->avail++] = objp;return;} else {STATS_INC_FREEMISS(cachep);cache_flusharray(cachep, ac);ac_entry(ac)[ac->avail++] = objp;}
}
1. 代码详细解释
static inline void __cache_free (kmem_cache_t *cachep, void* objp)
cachep
: 对象所属的 slab 缓存objp
: 要释放的对象指针- 无返回值
1.1. 获取每CPU缓存
struct array_cache *ac = ac_data(cachep);
关键操作:
ac_data(cachep)
: 获取当前CPU的本地缓存数组- 每个CPU有自己独立的对象缓存,避免锁竞争
1.2. 中断状态检查
check_irq_off();
验证:确保中断已被禁用
- 调用者(
kfree
)应该已经调用了local_irq_save
- 保证在原子上下文中操作,防止并发问题
1.3. 调试检查
objp = cache_free_debugcheck(cachep, objp, __builtin_return_address(0));
调试功能:
cache_free_debugcheck
: 进行各种释放前的调试检查__builtin_return_address(0)
: 获取调用者的返回地址,用于调试追踪- 可能返回调整后的对象指针(调试模式下)
1.4. 快速路径:每CPU缓存未满
if (likely(ac->avail < ac->limit)) {STATS_INC_FREEHIT(cachep);ac_entry(ac)[ac->avail++] = objp;return;
}
优化路径(最常见情况):
likely(ac->avail < ac->limit)
: 提示编译器每CPU缓存通常未满STATS_INC_FREEHIT
: 统计缓存命中次数ac_entry(ac)[ac->avail++] = objp
: 将对象添加到每CPU缓存数组- 直接返回,操作完成
1.5. 慢速路径:每CPU缓存已满
} else {STATS_INC_FREEMISS(cachep);cache_flusharray(cachep, ac);ac_entry(ac)[ac->avail++] = objp;
}
缓存刷新路径:
STATS_INC_FREEMISS
: 统计缓存未命中次数cache_flusharray(cachep, ac)
: 将部分对象从每CPU缓存刷新到共享slab- 然后将被释放的对象添加到腾出空间的每CPU缓存
每CPU缓存刷新函数cache_flusharray
static void cache_flusharray (kmem_cache_t* cachep, struct array_cache *ac)
{int batchcount;batchcount = ac->batchcount;
#if DEBUGBUG_ON(!batchcount || batchcount > ac->avail);
#endifcheck_irq_off();spin_lock(&cachep->spinlock);if (cachep->lists.shared) {struct array_cache *shared_array = cachep->lists.shared;int max = shared_array->limit-shared_array->avail;if (max) {if (batchcount > max)batchcount = max;memcpy(&ac_entry(shared_array)[shared_array->avail],&ac_entry(ac)[0],sizeof(void*)*batchcount);shared_array->avail += batchcount;goto free_done;}}free_block(cachep, &ac_entry(ac)[0], batchcount);
free_done:
#if STATS{int i = 0;struct list_head *p;p = list3_data(cachep)->slabs_free.next;while (p != &(list3_data(cachep)->slabs_free)) {struct slab *slabp;slabp = list_entry(p, struct slab, list);BUG_ON(slabp->inuse);i++;p = p->next;}STATS_SET_FREEABLE(cachep, i);}
#endifspin_unlock(&cachep->spinlock);ac->avail -= batchcount;memmove(&ac_entry(ac)[0], &ac_entry(ac)[batchcount],sizeof(void*)*ac->avail);
}
1. 代码详细解释
static void cache_flusharray (kmem_cache_t* cachep, struct array_cache *ac)
cachep
: slab 缓存描述符ac
: 要刷新的每CPU缓存
1.1. 批量大小设置
int batchcount;
batchcount = ac->batchcount;
批量确定:
batchcount
: 每次刷新操作处理的对象数量
1.2. 调试验证
#if DEBUGBUG_ON(!batchcount || batchcount > ac->avail);
#endif
调试检查:
!batchcount
: 批量大小不能为0batchcount > ac->avail
: 批量不能超过可用对象数- 在调试模式下触发错误如果条件不满足
1.3. 锁保护
check_irq_off();
spin_lock(&cachep->spinlock);
并发控制:
check_irq_off()
: 确认中断已禁用spin_lock(&cachep->spinlock)
: 获取缓存锁,保护共享数据结构
1.4. 尝试共享缓存转移
if (cachep->lists.shared) {struct array_cache *shared_array = cachep->lists.shared;int max = shared_array->limit-shared_array->avail;if (max) {if (batchcount > max)batchcount = max;memcpy(&ac_entry(shared_array)[shared_array->avail],&ac_entry(ac)[0],sizeof(void*)*batchcount);shared_array->avail += batchcount;goto free_done;}
}
共享缓存优先:
- 检查是否存在共享每CPU缓存
- 计算共享缓存剩余空间
max
- 如果有空间,批量复制对象到共享缓存
- 成功后跳转到清理步骤
1.5. 回退到slab释放
free_block(cachep, &ac_entry(ac)[0], batchcount);
直接释放:如果共享缓存不可用或已满,直接将对象释放回slab列表
1.6. 统计信息更新
#if STATS{int i = 0;struct list_head *p;p = list3_data(cachep)->slabs_free.next;while (p != &(list3_data(cachep)->slabs_free)) {struct slab *slabp;slabp = list_entry(p, struct slab, list);BUG_ON(slabp->inuse);i++;p = p->next;}STATS_SET_FREEABLE(cachep, i);}
#endif
统计功能:
- 统计完全空闲的slab数量
- 遍历
slabs_free
链表计数 - 验证空闲slab确实没有使用中的对象
- 更新统计信息
1.7. 清理和压缩
spin_unlock(&cachep->spinlock);
ac->avail -= batchcount;
memmove(&ac_entry(ac)[0], &ac_entry(ac)[batchcount],sizeof(void*)*ac->avail);
最终清理:
- 释放缓存锁
- 更新每CPU缓存的可用计数
- 使用
memmove
压缩缓存数组,移除已刷新的对象
安全内存移动函数memmove
void * memmove(void * dest,const void *src,size_t count)
{char *tmp, *s;if (dest <= src) {tmp = (char *) dest;s = (char *) src;while (count--)*tmp++ = *s++;}else {tmp = (char *) dest + count;s = (char *) src + count;while (count--)*--tmp = *--s;}return dest;
}
1. 代码详细解释
void * memmove(void * dest, const void *src, size_t count)
dest
: 目标内存地址src
: 源内存地址count
: 要移动的字节数- 返回: 目标内存地址
dest
1.1. 指针变量声明
char *tmp, *s;
tmp
: 用于遍历目标内存的指针s
: 用于遍历源内存的指针- 使用
char*
类型以便按字节操作
1.2. 重叠情况判断
if (dest <= src) {
关键判断:检查目标地址是否在源地址之前或相同
- 这决定了复制方向,防止数据被覆盖
1.3. 正向复制(目标在源之前)
tmp = (char *) dest;
s = (char *) src;
while (count--)*tmp++ = *s++;
从前往后复制:
- 初始化指针到各自内存的起始位置
- 循环
count
次,每次复制一个字节 - 指针递增,从低地址向高地址复制
1.4. 反向复制(目标在源之后)
} else {tmp = (char *) dest + count;s = (char *) src + count;while (count--)*--tmp = *--s;}
从后往前复制:
- 初始化指针到各自内存的末尾位置(地址 + count)
- 循环
count
次,每次复制一个字节 - 指针递减,从高地址向低地址复制
1.5. 返回结果
return dest;
返回目标地址,支持链式调用
批量对象释放到slab的函数free_block
static void free_block(kmem_cache_t *cachep, void **objpp, int nr_objects)
{int i;check_spinlock_acquired(cachep);/* NUMA: move add into loop */cachep->lists.free_objects += nr_objects;for (i = 0; i < nr_objects; i++) {void *objp = objpp[i];struct slab *slabp;unsigned int objnr;slabp = GET_PAGE_SLAB(virt_to_page(objp));list_del(&slabp->list);objnr = (objp - slabp->s_mem) / cachep->objsize;check_slabp(cachep, slabp);
#if DEBUGif (slab_bufctl(slabp)[objnr] != BUFCTL_FREE) {printk(KERN_ERR "slab: double free detected in cache '%s', objp %p.\n",cachep->name, objp);BUG();}
#endifslab_bufctl(slabp)[objnr] = slabp->free;slabp->free = objnr;STATS_DEC_ACTIVE(cachep);slabp->inuse--;check_slabp(cachep, slabp);/* fixup slab chains */if (slabp->inuse == 0) {if (cachep->lists.free_objects > cachep->free_limit) {cachep->lists.free_objects -= cachep->num;slab_destroy(cachep, slabp);} else {list_add(&slabp->list,&list3_data_ptr(cachep, objp)->slabs_free);}} else {/* Unconditionally move a slab to the end of the* partial list on free - maximum time for the* other objects to be freed, too.*/list_add_tail(&slabp->list,&list3_data_ptr(cachep, objp)->slabs_partial);}}
}
1. 代码详细解释
static void free_block(kmem_cache_t *cachep, void **objpp, int nr_objects)
cachep
: slab缓存描述符objpp
: 对象指针数组nr_objects
: 要释放的对象数量
1.1. 锁验证和统计更新
int i;
check_spinlock_acquired(cachep);
cachep->lists.free_objects += nr_objects;
- 验证已持有缓存锁
- 更新全局空闲对象统计
1.2. 遍历释放每个对象
for (i = 0; i < nr_objects; i++) {void *objp = objpp[i];struct slab *slabp;unsigned int objnr;
循环处理每个要释放的对象
1.3. 查找对象所属的slab
slabp = GET_PAGE_SLAB(virt_to_page(objp));
list_del(&slabp->list);
- 通过对象地址找到对应的slab结构
- 将slab从当前列表中移除(后续根据状态重新添加)
1.4. 计算对象索引
objnr = (objp - slabp->s_mem) / cachep->objsize;
对象索引计算:
objp - slabp->s_mem
: 对象在slab内的偏移量- 除以对象大小得到对象索引
1.5. 调试检查
check_slabp(cachep, slabp);
#if DEBUG
if (slab_bufctl(slabp)[objnr] != BUFCTL_FREE) {printk(KERN_ERR "slab: double free detected in cache '%s', objp %p.\n",cachep->name, objp);BUG();
}
#endif
- 检查slab完整性
- 调试模式下检测双重释放
1.6. 更新空闲链表
slab_bufctl(slabp)[objnr] = slabp->free;
slabp->free = objnr;
空闲链表操作:
- 将释放的对象添加到空闲链表头部
- 更新slab的空闲指针
1.7. 更新统计信息
STATS_DEC_ACTIVE(cachep);
slabp->inuse--;
- 减少活跃对象计数
- 减少slab内使用中对象计数
1.8. slab状态管理和重新分类
if (slabp->inuse == 0) {if (cachep->lists.free_objects > cachep->free_limit) {cachep->lists.free_objects -= cachep->num;slab_destroy(cachep, slabp);} else {list_add(&slabp->list,&list3_data_ptr(cachep, objp)->slabs_free);}
} else {list_add_tail(&slabp->list,&list3_data_ptr(cachep, objp)->slabs_partial);
}
根据slab状态决定:
- 完全空闲且超过限制:销毁slab
- 完全空闲:添加到空闲列表
- 部分使用:添加到部分使用列表尾部