系列文章目录
Linux 内核设计与实现
深入理解 Linux 内核
Linux 设备驱动程序
Linux设备驱动开发详解
深入理解Linux虚拟内存管理(一)
深入理解Linux虚拟内存管理(二)
深入理解Linux虚拟内存管理(三)
深入理解Linux虚拟内存管理(四)
深入理解Linux虚拟内存管理(五)
深入理解Linux虚拟内存管理(六)
深入理解Linux虚拟内存管理(七)
深入理解Linux虚拟内存管理(八)
深入理解Linux虚拟内存管理(九)
文章目录
一、slab 分配器
1、高速缓存控制
(1)创建高速缓存
(a)kmem_cache_create
这个函数的调用图如图 8.3 所示。这个函数负责创建一个新的高速缓存,然后根据大小进行批量处理。这个批量处理大约包括如下操作:
- 进行基本的有效性检查以防错误使用。
- 如果设置有 CONFIG_SLAB_DEBUG 则进行调试检查。
- 从 cache_cache slab 高速缓存中分配一个 kmem_cache_t。
- 将对象大小对齐为字大小。
- 计算在 slab 中有多少合适的对象。
- 将 slab 大小对齐为硬件高速缓存。
- 计算着色偏移。
- 初始化在高速缓存描述符中的其余字段。
- 将新高速缓存加入到高速缓存链。
// mm/slab.c
/**
* kmem_cache_create - Create a cache.
* @name: A string which is used in /proc/slabinfo to identify this cache.
* @size: The size of objects to be created in this cache.
* @offset: The offset to use within the page.
* @flags: SLAB flags
* @ctor: A constructor for the objects.
* @dtor: A destructor for the objects.
*
* Returns a ptr to the cache on success, NULL on failure.
* Cannot be called within a int, but can be interrupted.
* The @ctor is run when new pages are allocated by the cache
* and the @dtor is run before the pages are handed back.
* The flags are
*
* %SLAB_POISON - Poison the slab with a known test pattern (a5a5a5a5)
* to catch references to uninitialised memory.
*
* %SLAB_RED_ZONE - Insert `Red' zones around the allocated memory to check
* for buffer overruns.
*
* %SLAB_NO_REAP - Don't automatically reap this cache when we're under
* memory pressure.
*
* %SLAB_HWCACHE_ALIGN - Align the objects in this cache to a hardware
* cacheline. This can be beneficial if you're counting cycles as closely
* as davem.
*/
// 这一块进行基本的有效性检查防止错误使用。
// 这个函数的参数如下:
// name是该高速缓存的可读名。
// size是一个对象的大小。
// offset是用于指定高速缓存中对象的边界,但一般设为0。
// flags是静态高速缓存标志位。
// ctor是在slab创建时为每个对象调用的构造函数。
// dtor是相应的销毁函数。销毁函数可以使一个对象回到初始态。
kmem_cache_t *
kmem_cache_create (const char *name, size_t size, size_t offset,
unsigned long flags, void (*ctor)(void*, kmem_cache_t *, unsigned long),
void (*dtor)(void*, kmem_cache_t *, unsigned long))
{
const char *func_nm = KERN_ERR "kmem_create: ";
size_t left_over, align, slab_size;
kmem_cache_t *cachep = NULL;
/*
* Sanity checks... these are all serious usage bugs.
*/
// 在创建高速缓存时有各种bug的使用方法。
if ((!name) ||
// 这里用于可读名大于最大的高速缓存名(CACHE_MAXELEN)的情况。
((strlen(name) >= CACHE_NAMELEN - 1)) ||
// 中断处理程序不能创建高速缓存,因为需要访问中断安全的自旋锁以及信号量。
in_interrupt() ||
// 对象大小必须至少是一个字大小。slab分配器不适合于以单个字节为度量的对象。
(size < BYTES_PER_WORD) ||
// 最大可能的slab利用32个页面可以创建 2^MAX_OBJ_ORDER 个页面数。
(size > (1<<MAX_OBJ_ORDER)*PAGE_SIZE) ||
// 如果没有构造函数可用,也不能使用销毁函数。
(dtor && !ctor) ||
// 偏移不能在slab前,也不能超出第1个页面的边界。
(offset < 0 || offset > size))
// 调用BUG()退出。
BUG();
// 如果设置了 CONFIG_SLAB_CONFIG,则这一块进行调试检查。
#if DEBUG
// SLAB_DEBUG_INITIAL需要构造函数对对象进行检查以保证它们处于初始状
// 态,所以,必须退出一个构造函数。如果没有退出,则清除该标志。
if ((flags & SLAB_DEBUG_INITIAL) && !ctor) {
/* No constructor, but inital state check requested */
printk("%sNo con, but init state check requested - %s\n", func_nm, name);
flags &= ~SLAB_DEBUG_INITIAL;
}
// slab可能会被某种已知模式感染,以保证某个对象在分配之前没有被使用,但
// 是一个构造函数可能会破坏这种模式,它会错误地报告一个bug。如果存在这样
// 一个构造函数,则这里如果设置了 SLAB_POISON 就移除该标志位。
if ((flags & SLAB_POISON) && ctor) {
/* request for poisoning, but we can't do that with a constructor */
printk("%sPoisoning requested, but con given - %s\n", func_nm, name);
flags &= ~SLAB_POISON;
}
#if FORCED_DEBUG
// 仅有很少一部分对象是调试的红色区域。很大的红色区域对象将导致严重的碎片。
if ((size < (PAGE_SIZE>>3)) && !(flags & SLAB_MUST_HWCACHE_ALIGN))
/*
* do not red zone large object, causes severe
* fragmentation.
*/
flags |= SLAB_RED_ZONE;
// 如果没有构造函数,这里设置感染位。
if (!ctor)
flags |= SLAB_POISON;
#endif
#endif
/*
* Always checks flags, a caller might be expecting debug
* support which isn't available.
*/
// 调用所有可以调用的kmem_cache_alloc()来设置CREATE_MASK以
// 及所有允许设置的标志位。这里阻止调用者在没有这些标志位的时候使用调试标志位,如果
// 使用则调用BUG()。
BUG_ON(flags & ~CREATE_MASK);
/* Get cache's description obj. */
// 利用kmem_cache_alloc()从cache_cache中分配一个高速缓存描述符对象。
cachep = (kmem_cache_t *) kmem_cache_alloc(&cache_cache, SLAB_KERNEL);
// 如果内存不足.则转到oops.在那里处理OOM。
if (!cachep)
goto opps;
// 用0填充对象以防止意外地使用未初始化的数据。
memset(cachep, 0, sizeof(kmem_cache_t));
/* Check that size is in terms of words. This is needed to avoid
* unaligned accesses for some archs when redzoning is used, and makes
* sure any on-slab bufctl's are also correctly aligned.
*/
// 将对象大小对齐于某个字大小边界。
// 如果大小没有与某个字大小边界对齐,则……
if (size & (BYTES_PER_WORD-1)) {
// 增加一个对象的字大小,然后屏蔽低位。这能有效地将对象大小向上取整到下
// 一个字边界
size += (BYTES_PER_WORD-1);
size &= ~(BYTES_PER_WORD-1);
// 为了调试打印一个提示信息。
printk("%sForcing size word alignment - %s\n", func_nm, name);
}
#if DEBUG
// 如果调试可用,则必须稍微改变一下对齐方式。
if (flags & SLAB_RED_ZONE) {
/*
* There is no point trying to honour cache alignment
* when redzoning.
*/
// 如果slab将红色分区的话,就不需要尝试将硬件高速缓存中的东西对齐了。对象的
// 红色分区是从高速缓存边界处移动一个字的偏移。
flags &= ~SLAB_HWCACHE_ALIGN;
// 对象的大小增加两个BYTES_PER_WORD,以在对象的两端标记红色区域。
size += 2*BYTES_PER_WORD; /* words for redzone */
}
#endif
// 初始化对齐方式为一个字边界。如果调用者请求的是一个CPU高速缓存对齐方式,
// 则在这里改变。
align = BYTES_PER_WORD;
// 如果有请求,则这里将对象对齐到LI CPU高速缓存。
if (flags & SLAB_HWCACHE_ALIGN)
align = L1_CACHE_BYTES;
/* Determine if the slab management is 'on' or 'off' slab. */
// 如果对象比较大,在这里存储slab描述符off-slab,这将更好地允许打包对象到
// slab 中
if (size >= (PAGE_SIZE>>3))
/*
* Size is large, assume best to place the slab management obj
* off-slab (should allow better packing of objs).
*/
flags |= CFLGS_OFF_SLAB;
// 如果请求硬件高速缓存对齐,则对象的大小将与硬件高速缓存对齐
if (flags & SLAB_HWCACHE_ALIGN) {
/* Need to adjust size so that objs are cache aligned. */
/* Small obj size, can get at least two per cache line. */
/* FIXME: only power of 2 supported, was better */
// 如果对象符合对齐,则试着将对象打包到高速缓存中的一行。对于那些带有大
// L1高速缓存字节的系统(如Alpha和奔腾4),这很重要。align将被调整为对齐硬件高速缓存
// 边界的最小值。对大的L1高速缓存行,两个或者更多的小对象将可以填入到一行中。例如,
// 从32位高速缓存的两个对象将在奔腾4中填入一个高速缓存行。
while (size < align/2)
align /= 2;
// 将高速缓存大小向上取整为硬件高速缓存边界。
size = (size+align-1)&(~(align-1));
}
/* Cal size (in pages) of slabs, and the num of objs per slab.
* This could be made much more intelligent. For now, try to avoid
* using high page-orders for slabs. When the gfp() funcs are more
* friendly towards high-order requests, this should be changed.
*/
// 计算slab中将填入多少个对象,并在需要时调整slab大小。
do {
unsigned int break_flag = 0;
cal_wastage:
// kmem_cache_estimate。计算以gfp次序填入slab的对象数,并
// 计算剩余的字节数。
kmem_cache_estimate(cachep->gfporder, size, flags,
&left_over, &cachep->num);
// 在使用了 offslab slab描述符后,如果填入slab的对象数超过了 slab存放的对
// 象数,则设置break_flag。
if (break_flag)
break;
// 页面使用的次数不能超过MAX_GFP_ORDER(5)。
if (cachep->gfporder >= MAX_GFP_ORDER)
break;
// 如果对象不能填入,则跳转到next,在那里将增加高速缓存使用的gfporder。
if (!cachep->num)
goto next;
// 如果在高速缓存中没有slab描述符,但对象数超过了bufctl的off-slab所确定的数
// 目,测……
if (flags & CFLGS_OFF_SLAB && cachep->num > offslab_limit) {
/* Oops, this num of objs will cause problems. */
// 降低使用页面的次数。
cachep->gfporder--;
// 设置break_flag标志位,这样将退出循环。
break_flag++;
// 计算新的消耗数。
goto cal_wastage;
}
/*
* Large num of objs is good, but v. large slabs are currently
* bad for the gfp()s.
*/
// 除非填充了0号对象,slab_break_gfp_order将是不超过slab的次数。这里检查以保证不会超过次数。
if (cachep->gfporder >= slab_break_gfp_order)
break;
// 对外部碎片的粗略检查。如果高速缓存的消耗量小于八分之一,则可以接受。
if ((left_over*8) <= (PAGE_SIZE<<cachep->gfporder))
break; /* Acceptable internal fragmentation. */
next:
// 如果碎片过多,则这里增加gfp次,并重新计算存储的对象数目以及消耗量。
cachep->gfporder++;
} while (1);
// 在调整后如果对象还是不能填入高速缓存,则不能创建该对象。
if (!cachep->num) {
printk("kmem_cache_create: couldn't create cache %s.\n", name);
// 释放高速缓存描述符并设指针指向NULL。
kmem_cache_free(&cache_cache, cachep);
// 跳转到oops,在那里仅返回NULL指针。
cachep = NULL;
goto opps;
}
// 这一块将slab大小对齐于硬件高速缓存。
// slab_size是slab描述符的总大小,不是slab自身的大小。它是一个固定的slab_t结
// 构,大小为对象数乘以bufctl的结果。
slab_size = L1_CACHE_ALIGN(cachep->num*sizeof(kmem_bufctl_t)+sizeof(slab_t));
/*
* If the slab has been placed off-slab, and we have enough space then
* move it on-slab. This is at the expense of any extra colouring.
*/
// 如果有足够的空间留给slab描述符,则这里指定为放置off-slab描述符,这里
// 移除标志位并更新left_over字节量。这样做会影响到高速缓存着色,但是对off-slab描述符
// 而言,这不是问题。
if (flags & CFLGS_OFF_SLAB && left_over >= slab_size) {
flags &= ~CFLGS_OFF_SLAB;
left_over -= slab_size;
}
/* Offset must be a multiple of the alignment. */
// 计算着色偏移。
// offset是请求调用者页面中的偏移。这里保证所请求偏移在使用中的高速缓存
// 的正确对齐方式中。
offset += (align-1);
offset &= ~(align-1);
// 如果由于某个原因偏移为0,则这里设置它与CPU高速缓存对齐。
if (!offset)
offset = L1_CACHE_BYTES;
// 这个偏移用于在不同的高速缓存行中存入对象。对创建的每个slab,将给予不同的
// 颜色偏移。
cachep->colour_off = offset;
// 可以使用的不同偏移数。
cachep->colour = left_over/offset;
// 这一块初始化高速缓存的其他字段。
/* init remaining fields */
// 对只有一个页面的slab高速缓存,设置CFLAGS_OPTIMIZE标志位,这实际
// 上没有什么效果,因为并没有用到该标志位。
if (!cachep->gfporder && !(flags & CFLGS_OFF_SLAB))
flags |= CFLGS_OPTIMIZE;
// 设置高速缓存静态标志位。
cachep->flags = flags;
// 将gfpflags清0。这是个无效操作,因为可以使用memset()在分配了高速缓存描述
// 符后清除这些标志位。
cachep->gfpflags = 0;
// 如果slab用于DMA,则这里设置GFP_DMA标志位,这样伙伴分配器将使用ZONE_DMA。
if (flags & SLAB_CACHE_DMA)
cachep->gfpflags |= GFP_DMA;
// 为访问高速缓存初始化自旋锁。
spin_lock_init(&cachep->spinlock);
// 复制对象大小,如果需要,则按硬件高速缓存大小对齐。
cachep->objsize = size;
// 初始化slab链表。
INIT_LIST_HEAD(&cachep->slabs_full);
INIT_LIST_HEAD(&cachep->slabs_partial);
INIT_LIST_HEAD(&cachep->slabs_free);
// 如果描述符是off-slab的,这里分配和放置一个slab管理器以在slabp_cache使用。
if (flags & CFLGS_OFF_SLAB)
cachep->slabp_cache = kmem_find_general_cachep(slab_size,0);
// 设置指向构造函数和销毁函数的指针。
cachep->ctor = ctor;
cachep->dtor = dtor;
/* Copy name over so we don't have problems with unloaded modules */
// 复制可读名。
strcpy(cachep->name, name);
#ifdef CONFIG_SMP
// 如果per-CPU可用,这里为该高速缓存创建一个集(见8.5节)。
if (g_cpucache_up)
enable_cpucache(cachep);
#endif
// 这一块将新的高速缓存加入到高速缓存链表中。
/* Need the semaphore to access the chain. */
// 获取对高速缓存链表同步访问的信号量。
down(&cache_chain_sem);
{
struct list_head *p;
// 检查在高速缓存链表中的每个高速缓存,并保证没有其他的高速缓存具有相同
// 的名字。如果有相同的名字,则意味着创建了具有相同类型的两个高速缓存,这是一个严重
// 的 bug。
list_for_each(p, &cache_chain) {
// 从链表中获取高速缓存。
kmem_cache_t *pc = list_entry(p, kmem_cache_t, next);
/* The name field is constant - no lock needed. */
// 比较名字,如果相同则调用BUG()。值的注意的是并不删除新的高速缓存,这
// 个错误是开发时由于不规范编程而造成的,是一个常见的问题。
if (!strcmp(pc->name, name))
BUG();
}
}
/* There is no reason to lock our new cache before we
* link it in - no one knows about it yet...
*/
// 将高速缓存链到链表中。
list_add(&cachep->next, &cache_chain);
// 释放高速缓存链表信号量。
up(&cache_chain_sem);
opps:
// 返回新的高速缓存指针。
return cachep;
}
使用 kmem_cache_alloc 分配一个 kmem_cache_t,然后根据 size 计算 cachep->gfporder,最后把这个 kmem_cache_t 加入到全局链表中。
① ⇒ kmem_cache_alloc
② ⇒ kmem_cache_estimate
③ ⇒ kmem_cache_free
④ ⇒ kmem_find_general_cachep
(2)计算 slab 上的对象数量
(a)kmem_cache_estimate
在创建高速缓存时,可以存放多少个对象以及需要耗费多少空间是确定的。下面的函数计算有多少个对象可以存储,并且考虑到 slab 和 bufctls 必须以 on-slab 方式存放。
// mm/slab.c
/* Cal the num objs, wastage, and bytes left over for a given slab size. */
// 这个函数的参数如下:
// gfporder为每个slab分配2^gfporder个页面。
// size是每个对象的大小。
// flags是高速缓存标志位。
// left_over是slab中剩余的字节数,由调用者返回。
// num是填入slab的对象数,由调用者返回。
static void kmem_cache_estimate (unsigned long gfporder, size_t size,
int flags, size_t *left_over, unsigned int *num)
{
int i;
// wastage是一个递减函数。它从可能的最大消耗量开始。
size_t wastage = PAGE_SIZE<<gfporder;
// extra是需要存储kmem_bufctl_t的字节数。
size_t extra = 0;
// base是在slab开始处的可用内存地址
size_t base = 0;
// 如果slab描述符保存在高速缓存中,基地址开始于slab_t结构的末端,存放bufctl所
// 需的字节数是kmem_bufctl_t的大小。
if (!(flags & CFLGS_OFF_SLAB)) {
base = sizeof(slab_t);
extra = sizeof(kmem_bufctl_t);
}
// i变成slab可以拥有的对象数。
i = 0;
// 将高速缓存可以容纳的对象数加起来。i * size 是对象自身的大小。
// L1_CACHE_ALIGN(base + i * extra)有一点灵活性。这是一个计算需要存储在slab中每个
// 对象所需kmem_bufctl_t的内存量函数。由于它在slab的起点,所以它与L1高速缓存对齐,
// 这样slab中的第1个对象将与硬件高速缓存对齐。i* extra将计算为容纳该对象kmem_bufctl_t
// 所需的空间。由于消耗量以slab的大小计算,所以在这里用于负载。
while (i*size + L1_CACHE_ALIGN(base+i*extra) <= wastage)
i++;
// 由于前面循环计数直到slab溢出,所以对象的数量记为 i-1。
if (i > 0)
i--;
// SLAB_LIMIT是一个slab可存储对象的最大绝对值。它定义为OxffffFFFE,
// 因为这是kmem_bufctl_t可以容纳的最大无符号整数中最大的数值。
if (i > SLAB_LIMIT)
i = SLAB_LIMIT;
// num现在是slab可以容纳的对象数。
*num = i;
// 减去消耗对象所占据的空间。
wastage -= i*size;
// 减去由kmem_bufctl_t所占据的空间。
wastage -= L1_CACHE_ALIGN(base+i*extra);
// 现在消耗量计算为slab中剩下的空间。
*left_over = wastage;
}
计算 gfporder 页面可以容纳多少个 slab 对象。
(3)收缩高速缓存
kmem_cache_shrink() 的调用图如图 8.5 所示。提供两套收缩函数。kmem_cache_shrink() 从 slabs_free 中移除所有 slab ,并返回释放的页面数作为结果。__kmem_cache_shrink() 从 slabs_free 中释放所有的 slab ,然后验证 slabs_partial 和 slabs_free 是否为空。这在销毁高速缓存时比较重要,在那时它不关心释放的页面数,而只关心高速缓存是否为空。
(a)kmem_cache_shrink
这个函数进行基本的调试检查,然后获取高速缓存描述符,接着释放 slab。在这时,它也用于调用 drain_cpu_caches() 来释放在 per-CPU 高速缓存中的对象。很奇怪的是,由于对象可以在 per-CPU 高速缓存中分配,slab 可能不能被释放,这里却移除,并不使用。
// mm/slab.c
/**
* kmem_cache_shrink - Shrink a cache.
* @cachep: The cache to shrink.
*
* Releases as many slabs as possible for a cache.
* Returns number of pages released.
*/
// 参数是被收缩的高速缓存。
// 进行如下检查:
// 高速缓存不为NULL。
// 调用者不是一个异常。
// 高速缓存在高速缓存链表中,且不是一个坏指针。
int kmem_cache_shrink(kmem_cache_t *cachep)
{
int ret;
if (!cachep || in_interrupt() || !is_chained_kmem_cache(cachep))
BUG();
// 获取高速缓存描述符锁并关中断。
spin_lock_irq(&cachep->spinlock);
// 收缩高速缓存区。
ret = __kmem_cache_shrink_locked(cachep);
// 释放高速缓存锁并开中断。
spin_unlock_irq(&cachep->spinlock);
// 返回释放的页面数,但是并没有考虑消耗 CPU 时释放的对象。
return ret << cachep->gfporder;
}
(b)__kmem_cache_shrink
这个函数与 kmem_cache_shrink() 相似,除了它在高速缓存为空时返回。这在销毁高速缓存时比较重要,在那时,释放的内存量并不很重要,重要的是安全地删除高速缓存,且不泄漏内存。
// mm/slab.c
static int __kmem_cache_shrink(kmem_cache_t *cachep)
{
int ret;
// 从per-CPU对象高速缓存中移除所有对象。
drain_cpu_caches(cachep);
// 获取高速缓存描述符锁并关中断
spin_lock_irq(&cachep->spinlock);
// 释放slabs_free链表中的所有slab。
__kmem_cache_shrink_locked(cachep);
// 检查slabs_partial和slabs_full链表是否为空
ret = !list_empty(&cachep->slabs_full) ||
!list_empty(&cachep->slabs_partial);
// 释放高速缓存描述符锁并重新打开中断
spin_unlock_irq(&cachep->spinlock);
// 如果释放了高速缓存中所有slab,则返回。
return ret;
}
(c)__kmem_cache_shrink_locked
这里完成释放 slab 的实际工作。它将不断地销毁 slab 直到设置了增长标志位,表明高速缓存正在使用或者直到 slabs_free 中没有更多的 slab。
// mm/slab.c
/*
* Called with the &cachep->spinlock held, returns number of slabs released
*/
static int __kmem_cache_shrink_locked(kmem_cache_t *cachep)
{
slab_t *slabp;
int ret = 0;
/* If the cache is growing, stop shrinking. */
// 当高速缓存不再增长时,这里释放slab。
// 每创建一个 slab_t ,growing 增长一次,所以这里遍历所有的 slab_t ?
while (!cachep->growing) {
struct list_head *p;
// 获取slabs_free链表中最后一个slab。
p = cachep->slabs_free.prev;
if (p == &cachep->slabs_free)
break;
slabp = list_entry(cachep->slabs_free.prev, slab_t, list);
#if DEBUG
// 如果调试可用,则这里保证它目前不在使用中。如果不可用,则它首先就不会
// 在slabs_free链表中。
if (slabp->inuse)
BUG();
#endif
// 从链表中移除slab。
list_del(&slabp->list);
// 重新打开中断。调用这个函数时关闭了中断,所以需要尽快地释放中断。
spin_unlock_irq(&cachep->spinlock);
// 利用 kmem_slab_destroy() 删除 slab。
kmem_slab_destroy(cachep, slabp);
// 记录释放的slab数。
ret++;
// 获取高速缓存描述符锁并关中断。
spin_lock_irq(&cachep->spinlock);
}
return ret;
}
主要释放 kmem_cache_t->slabs_free 中的 slab_t 。
① ⇒ kmem_slab_destroy
(4)销毁高速缓存
当卸载某个模块时,如果创建了高速缓存,则它负责销毁高速缓存。由于在载入一个模块时已经保证没有两个相同名字的高速缓存。内核代码经常不销毁它自己的高速缓存,因为它们在整个系统生命周期中都一直存在。销毁一个高速缓存的步骤如下:
- 从高速缓存链表中删除该高速缓存。
- 收缩高速缓存,删除所有的 slab(见 8.1.8 小节)。
- 释放所有的 per-CPU 高速缓存( kfree() )。
- 从 cache_cache 中删除高速缓存描述符(见 8.3.3 小节)。
(a)kmem_cache_destroy
这个函数的调用图如图 8.7 所示。
// mm/slab.c
/**
* kmem_cache_destroy - delete a cache
* @cachep: the cache to destroy
*
* Remove a kmem_cache_t object from the slab cache.
* Returns 0 on success.
*
* It is expected this function will be called by a module when it is
* unloaded. This will remove the cache completely, and avoid a duplicate
* cache being allocated each time a module is loaded and unloaded, if the
* module doesn't have persistent in-kernel storage across loads and unloads.
*
* The cache must be empty before calling this function.
*
* The caller must guarantee that noone will allocate memory from the cache
* during the kmem_cache_destroy().
*/
int kmem_cache_destroy (kmem_cache_t * cachep)
{
// 有效性检查。保证cachep不为空,没有中断正尝试使用这个函数,以及高速
// 缓存没有标记为增长,表明它正在使用中。
if (!cachep || in_interrupt() || cachep->growing)
BUG();
/* Find the cache in the chain of caches. */
// 获取访问高速缓存链表的信号量。
down(&cache_chain_sem);
/* the chain is never empty, cache_cache is never destroyed */
// 从高速缓存链表中获取一个链表项。
if (clock_searchp == cachep)
clock_searchp = list_entry(cachep->next.next,
kmem_cache_t, next);
// 从高速缓存链表中删除该高速缓存。
list_del(&cachep->next);
// 释放高速缓存链表信号量。
up(&cache_chain_sem);
// 利用__kmem_cache_shrink()收缩高速缓存,释放所有的slab 。
if (__kmem_cache_shrink(cachep)) {
// 如果在高速缓存中还存在slab,则这个收缩函数返回真。如果它们是无法销
// 毁的高速缓存,则这里将其加入到高速缓存链表中,并报告错误。
printk(KERN_ERR "kmem_cache_destroy: Can't free all objects %p\n",
cachep);
down(&cache_chain_sem);
list_add(&cachep->next,&cache_chain);
up(&cache_chain_sem);
return 1;
}
#ifdef CONFIG_SMP
{
int i;
// 如果SMP可用,则利用 kfree() 删除per-CPU数据结构。
for (i = 0; i < NR_CPUS; i++)
kfree(cachep->cpudata[i]);
}
#endif
// 利用kmem_cache_free()从cache_cache中删除高速缓存描述符。
kmem_cache_free(&cache_cache, cachep);
return 0;
}
① ⇒ __kmem_cache_shrink
② ⇒ kfree
kfree 函数
③ ⇒ kmem_cache_free
(5)回收高速缓存
(a)kmem_cache_reap
这个函数的调用如图 8.4 所示。由于这个函数比较大,所以将其分成几个独立的小节。第 1 部分是一个简单的函数开始部分,第 2 部分选择要回收的高速缓冲,第 3 部分是释放 slab,基本的任务在 8.1.7 小节中描述。
// mm/slab.c
/**
* kmem_cache_reap - Reclaim memory from caches.
* @gfp_mask: the type of memory required.
*
* Called from do_try_to_free_pages() and __alloc_pages()
*/
// 惟一的一个参数是GFP标志位。惟一做的检查是对__GFP_WAIT标志位进行检
// 查。作为惟一的调用者,kswapd可以睡眠。这个参数实际上没有用。
int kmem_cache_reap (int gfp_mask)
{
slab_t *slabp;
kmem_cache_t *searchp;
kmem_cache_t *best_cachep;
unsigned int best_pages;
unsigned int best_len;
unsigned int scan;
int ret = 0;
// 调用者可以睡眠吗?如果可以,则在这里获取信号量。
if (gfp_mask & __GFP_WAIT)
down(&cache_chain_sem);
else
// 如果不可以睡眠,则这里试着获取信号量。如果没有信号量可用,则返回。
if (down_trylock(&cache_chain_sem))
return 0;
// REAP_SCANLEN(10)是要检查的高速缓存数目。
scan = REAP_SCANLEN;
best_len = 0;
best_pages = 0;
best_cachep = NULL;
// 设置searchp为上次回收时检查过的最后一个高速缓存。
searchp = clock_searchp;
// 这一块考察REAP_SCANLEN数量个高速缓存,并选择一个来释放。
do {
unsigned int pages;
struct list_head* p;
unsigned int full_free;
/* It's safe to test this without holding the cache-lock. */
if (searchp->flags & SLAB_NO_REAP)
goto next;
// 获取一个对高速缓存描述符的中断安全锁。
spin_lock_irq(&searchp->spinlock);
// 如果高速缓存在增长,这里跳过它。
if (searchp->growing)
goto next_unlock;
// 如果高速缓存最近增长过,则这里跳过它并清除标志位。
if (searchp->dflags & DFLGS_GROWN) {
searchp->dflags &= ~DFLGS_GROWN;
goto next_unlock;
}
#ifdef CONFIG_SMP
// 释放per-CPU对象到全局池中。
{
cpucache_t *cc = cc_data(searchp);
if (cc && cc->avail) {
__free_block(searchp, cc_entry(cc), cc->avail);
cc->avail = 0;
}
}
#endif
full_free = 0;
p = searchp->slabs_free.next;
// 计算在slab_free链表中的slab数量。
while (p != &searchp->slabs_free) {
slabp = list_entry(p, slab_t, list);
#if DEBUG
if (slabp->inuse)
BUG();
#endif
full_free++;
p = p->next;
}
/*
* Try to avoid slabs with constructors and/or
* more than one page per slab (as it can be difficult
* to get high orders from gfp()).
*/
// 计算所有slab持有的页面数量。
pages = full_free * (1<<searchp->gfporder);
// 如果对象有构造函数,则这里将其页面计数减为1/5,这样就不太可能选择它来回收。
if (searchp->ctor)
pages = (pages*4+1)/5;
// 如果slab由多于一页组成,这里减少页面计数到1/5。这是因为难以获取高次的页面。
if (searchp->gfporder)
pages = (pages*4+1)/5;
// 如果这是目前找到的最好的回收候选高速缓存,这里检查它是否适合回收。
if (pages > best_pages) {
// 记录新的最大量。
best_cachep = searchp;
// 记录best_len ,这样很容易知道空闲链表中 slab 一半的slab数量。
best_len = full_free;
best_pages = pages;
// 如果这个高速缓存适合回收,则 ...
if (pages >= REAP_PERFECT) {
// 更新 clock_searchp。
clock_searchp = list_entry(searchp->next.next,
kmem_cache_t,next);
// 转到perfect在那里释放一半的slab。
goto perfect;
}
}
next_unlock:
spin_unlock_irq(&searchp->spinlock);
next:
searchp = list_entry(searchp->next.next,kmem_cache_t,next);
// 在达到REAP_SCANLEN之前,且在还没有循环遍历整个高速缓存链表之前一直扫描
} while (--scan && searchp != clock_searchp);
// 这一块从选择的高速缓存中释放一半的slab.
// 更新clock_searchp 以准备下一次高速缓存回收。
clock_searchp = searchp;
// 如果没有找到一个高速缓存,则转到 out 以释放高速缓存链表并退出。
if (!best_cachep)
/* couldn't find anything to reap */
goto out;
// 获取高速缓存链表自旋锁,并关闭中断。cachep描述符必须持有一个中断安全的
// 锁,因为一些高速缓存可能在中断上下文中使用。slab分配器无法区分中断安全
// 和中断不安全的高速缓存。
spin_lock_irq(&best_cachep->spinlock);
perfect:
/* free only 50% of the free slabs */
// 调整best_len为要释放的slab数量。
best_len = (best_len + 1)/2;
// 释放 best_len 个 slab。
for (scan = 0; scan < best_len; scan++) {
struct list_head *p;
// 如果高速缓存正在增长,则在这里退出。
if (best_cachep->growing)
break;
// 从链表中获得一个slab。
p = best_cachep->slabs_free.prev;
// 如果在链表中没有一个slab,则在这里退出。
if (p == &best_cachep->slabs_free)
break;
// 获取slab指针。
slabp = list_entry(p,slab_t,list);
#if DEBUG
// 如果调试可用,则这里保证没有活动的对象在slab中。
if (slabp->inuse)
BUG();
#endif
// 从 slabs_free 链表中移除 slab。
list_del(&slabp->list);
// 如果可用则更新统计计数。
STATS_INC_REAPED(best_cachep);
/* Safe to drop the lock. The slab is no longer linked to the
* cache.
*/
// 释放高速缓存并开中断。
spin_unlock_irq(&best_cachep->spinlock);
// 释放 slab(见 8.2.8 小节)。
kmem_slab_destroy(best_cachep, slabp);
// 重新获取高速缓存描述符的自旋锁并关中断。
spin_lock_irq(&best_cachep->spinlock);
}
// 释放高速缓存描述符并开中断。
spin_unlock_irq(&best_cachep->spinlock);
// ret是已经释放的页面数。
ret = scan * (1 << best_cachep->gfporder);
out:
// 释放高速缓存信号量并返回释放的页面数。
up(&cache_chain_sem);
return ret;
}
考察 clock_searchp 开始 REAP_SCANLEN(10) 个 kmem_cache_t 里 slabs_free 中 slat_t 的数量。选择最多的进行释放。
2、slabs
(1)存储 slab 描述符
(a)kmem_cache_slabmgmt
这个函数或者为不在高速缓存中的 slab 描述符分配空间,或者在 slab 开始的地方为描述符和 bufctls 保留足够的空间。
// mm/slab.c
/* Get the memory for a slab management obj. */
// 这个函数的参数如下:
// cachep是slab要分配的高速缓存。
// objp是当调用该函数时指向slab的开始处。
// colour_off是这个slab的颜色偏移。
// local_flags是高速缓存的标志位。
static inline slab_t * kmem_cache_slabmgmt (kmem_cache_t *cachep,
void *objp, int colour_off, int local_flags)
{
slab_t *slabp;
// 如果slab描述符并不存放在高速缓存中,则...
if (OFF_SLAB(cachep)) {
/* Slab management obj is off-slab. */
// 从指定大小的高速缓存中分配内存。在创建高速缓存时,slabp_cache设置为分配
// 内存指定大小的高速缓存。
slabp = kmem_cache_alloc(cachep->slabp_cache, local_flags);
// 如果分配失败,这里返回。
if (!slabp)
return NULL;
} else {
/* FIXME: change to
slabp = objp
* if you enable OPTIMIZE
*/
// 在slab的起点保留空间。
// slab的地址将是slab的起点(objp)加上颜色偏移。
slabp = objp+colour_off;
// colour_off 计算为放置第一个对象的偏移位置。该地址对齐于L1高速缓存,
// cachep—>num * sizeof(kmem_bufctl_t) 是容纳slab中各对象的bufctls所需的空间大小,
// sizeof(slab_t)是slab描述符的大小。这里在slab起点已经有效地保留了空间。
colour_off += L1_CACHE_ALIGN(cachep->num *
sizeof(kmem_bufctl_t) + sizeof(slab_t));
}
// 在slab中使用的对象数为0。
slabp->inuse = 0;
// 更新 colouroff 以放置新对象。
slabp->colouroff = colour_off;
// 第1个对象的地址计算为slab的开始地址加上偏移。
slabp->s_mem = objp+colour_off;
return slabp;
}
kmem_cache_slabmgmt 函数主要用来分配一个 slab_t 对象(slab 管理对象):
- 如果 slab_t 和 slab 没放在一起(OFF_SLAB(cachep)),则调用 kmem_cache_alloc 分配一个 slab_t 。
- 否则,从 objp 内存处分配一个 slab_t ,同时调整 colour_off 偏移。
最后更新 slabp 中 inuse,colouroff ,s_mem 字段域。
① ⇒ kmem_cache_alloc
(b)kmem_find_general_cachep
如果 slab 描述符不在 slab 中保存,则这个函数在创建高速缓存时被调用,它将找到合适大小的高速缓存供使用,并将其存放在 slabp_cache 的高速缓存描述符中。
// mm/slab.c
// size是slab描述符的大小,gfpflags总是为0,因为DMA内存不需要slab描述符。
kmem_cache_t * kmem_find_general_cachep (size_t size, int gfpflags)
{
cache_sizes_t *csizep = cache_sizes;
/* This function could be moved to the header file, and
* made inline so consumers can quickly determine what
* cache pointer they require.
*/
// 从最小的大小开始,这里不断增加大小,直至找到一个足够大的缓冲区来存
// 放slab描述符的高速缓存。
for ( ; csizep->cs_size; csizep++) {
if (size > csizep->cs_size)
continue;
break;
}
// 返回一个普通的或DMA大小的高速缓存,这取决于传入的gfpflags标志位。
// 实际上,仅传回 cs_cachep。
return (gfpflags & GFP_DMA) ? csizep->cs_dmacachep : csizep->cs_cachep;
}
(2)创建 slab
(a)kmem_cache_grow
这个函数的调用图如图 8.11 所示。这个函数的基本步骤如下:
- 进行基本的有效性检查以防止错误使用。
- 计算该 slab 中对象的颜色偏移。
- 为 slab 分配内存,并获取 slab 描述符。
- 将 slab 使用的页面链接到 slab 和高速缓存描述符。
- 初始化 slab 中的对象。
- 将 slab 加入高速缓存。
// mm/slab.c
/*
* Grow (by 1) the number of slabs within a cache. This is called by
* kmem_cache_alloc() when there are no active objs left in a cache.
*/
// 这是一些基本的声明。这个函数的参数如下:
// cachep是要分配新slab的高速缓存
// flags创建slab的标志位。
static int kmem_cache_grow (kmem_cache_t * cachep, int flags)
{
slab_t *slabp;
struct page *page;
void *objp;
size_t offset;
unsigned int i, local_flags;
unsigned long ctor_flags;
unsigned long save_flags;
/* Be lazy and only check for valid flags here,
* keeping it out of the critical path in kmem_cache_alloc().
*/
// 这里进行基本的有效性检查以防止错误使用。在这里进行检查,而不是利用 kmem_cache_alloc()
// 来保护速度优先的路径。这里不需要在每次分配对象时都检查这些标志位。
// 保证仅使用允许的标志位来进行分配。
if (flags & ~(SLAB_DMA|SLAB_LEVEL_MASK|SLAB_NO_GROW))
BUG();
// 如果设置了这些标志位,则不增长高速缓存,实际上,从来不设置这些标志位。
if (flags & SLAB_NO_GROW)
return 0;
/*
* 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).
*/
// 如果在中断上下文中调用,保证设置了ATOMIC标志位。这样我们在调用
// kmem_getpages() 时不会睡眠。
if (in_interrupt() && (flags & SLAB_LEVEL_MASK) != SLAB_ATOMIC)
BUG();
// 这个标志位告知构造函数初始化对象。
ctor_flags = SLAB_CTOR_CONSTRUCTOR;
// local_flags 仅与页面分配器相关。
local_flags = (flags & SLAB_LEVEL_MASK);
// 如果设置了 SLAB_ATOMIC 标志位,则构造函数在创建一次新分配时就需要知道它。
if (local_flags == SLAB_ATOMIC)
/*
* Not allowed to sleep. Need to tell a constructor about
* this - it might need to know...
*/
ctor_flags |= SLAB_CTOR_ATOMIC;
// 计算该slab中对象的颜色偏移。
/* About to mess with non-constant members - lock. */
// 获取访问高速缓存描述符的中断安全锁。
spin_lock_irqsave(&cachep->spinlock, save_flags);
/* Get colour for the slab, and cal the next value. */
// 获取该slab中的对象偏移。
offset = cachep->colour_next;
// 移到下一个颜色偏移。
cachep->colour_next++;
// 如果达到colour,就没有更多的偏移,这里将 colour_next 重置为0。
if (cachep->colour_next >= cachep->colour)
cachep->colour_next = 0;
// colour_off是每个偏移的大小。所以 offset * colour_off 表明对象偏移的字节数。
offset *= cachep->colour_off;
// 将高速缓存标记为增长,这样 kmem_cache_reap() 将忽略该高速缓存。
cachep->dflags |= DFLGS_GROWN;
// 增加增长该高速缓存的调用者计数。
cachep->growing++;
// 释放自旋锁并重新打开中断。
spin_unlock_irqrestore(&cachep->spinlock, save_flags);
/* A series of memory allocations for a new slab.
* Neither the cache-chain semaphore, or cache-lock, are
* held, but the incrementing c_growing prevents this
* cache from being reaped or shrunk.
* Note: The cache could be selected in for reaping in
* kmem_cache_reap(), but when the final test is made the
* growing value will be seen.
*/
// 为slab分配内存并获取一个slab描述符。
/* Get mem for the objs. */
// 利用 kmem_getpages() 从页面分配器中为slab分配页面。
if (!(objp = kmem_getpages(cachep, flags)))
goto failed;
/* Get slab management. */
// 利用 kmem_cache_slabmgmt() 获取一个 slab 描述符。
if (!(slabp = kmem_cache_slabmgmt(cachep, objp, offset, local_flags)))
goto opps1;
// 将slab使用的页面链接到slab和高速缓存描述符。
/* Nasty!!!!!! I hope this is OK. */
// i是slab使用的页面数。每个页面都必须链接到slab和高速缓存描述符。
i = 1 << cachep->gfporder;
// objp是一个指向slab起点的指针。宏virt_to_page()将赋予struct page该地址值。
page = virt_to_page(objp);
// 将每个页面链接到slab字段以及高速缓存描述符。
do {
// SET_PAGE_CACHE()使用page->link_next字段将页面链接到高速缓存描述符。
SET_PAGE_CACHE(page, cachep);
// SET_PAGE_SLAB()使用page->link_prev字段将页面链接到高速缓存描述符。
SET_PAGE_SLAB(page, slabp);
// 设置PG_slab页面字段。整个PG_flags在表2.1中列出。
PageSetSlab(page);
// 移到slab中下一个页面进行链接。
page++;
} while (--i);
// 初始化对象
kmem_cache_init_objs(cachep, slabp, ctor_flags);
// 将slab加入到高速缓存中。
// 以一种中断安全的方式获取高速缓存描述符自旋锁。
spin_lock_irqsave(&cachep->spinlock, save_flags);
// 将增长计数减1。
cachep->growing--;
/* Make slab active. */
// 将slab加入到slabs_free链表的末尾。
list_add_tail(&slabp->list, &cachep->slabs_free);
// 如果设置了 STATS,这里增加 cachepf->grown 字段 STATS_INC_GROWN()
STATS_INC_GROWN(cachep);
// 设置失败为0,这个字段在其他地方没有用到。
cachep->failures = 0;
// 以一种中断安全的方式释放高速缓存描述符自旋锁。
spin_unlock_irqrestore(&cachep->spinlock, save_flags);
// 返回成功。
return 1;
// 这一块进行错误处理:
// 如果为slab分配了页面,则转到oopsl。必须在这里释放这些页面。
opps1:
kmem_freepages(cachep, objp);
failed:
// 为访问高速缓存描述符获取自旋锁。
spin_lock_irqsave(&cachep->spinlock, save_flags);
// 将增长计数减1。
cachep->growing--;
// 释放自旋锁。
spin_unlock_irqrestore(&cachep->spinlock, save_flags);
// 返回假。
return 0;
}
① ⇒ kmem_getpages
② ⇒ kmem_cache_slabmgmt
③ ⇒ kmem_cache_init_objs
④ ⇒ kmem_freepages
(3)销毁 slab
(a)kmem_slab_destroy
这个函数的调用图如图 8.13 所示。为了便于阅读,调试部分已经从这个函数中略去,它们几乎与分配对象时的调试部分相同。如何标记以及检查感染模式见 H.3.1.1。
// mm/slab.c
/* Destroy all the objs in a slab, and release the mem back to the system.
* Before calling the slab must have been unlinked from the cache.
* The cache-lock is not held/needed.
*/
static void kmem_slab_destroy (kmem_cache_t *cachep, slab_t *slabp)
{
// 如果有销毁函数可用,这里将对slab中每个对象调用它。
if (cachep->dtor
#if DEBUG
|| cachep->flags & (SLAB_POISON | SLAB_RED_ZONE)
#endif
) {
int i;
// 遍历slab中每个对象。
for (i = 0; i < cachep->num; i++) {
// 计算要销毁对象的地址。
void* objp = slabp->s_mem+cachep->objsize*i;
#if DEBUG
if (cachep->flags & SLAB_RED_ZONE) {
if (*((unsigned long*)(objp)) != RED_MAGIC1)
BUG();
if (*((unsigned long*)(objp + cachep->objsize
-BYTES_PER_WORD)) != RED_MAGIC1)
BUG();
objp += BYTES_PER_WORD;
}
#endif
// 调用销毁函数。
if (cachep->dtor)
(cachep->dtor)(objp, cachep, 0);
#if DEBUG
if (cachep->flags & SLAB_RED_ZONE) {
objp -= BYTES_PER_WORD;
}
if ((cachep->flags & SLAB_POISON) &&
kmem_check_poison_obj(cachep, objp))
BUG();
#endif
}
}
// 释放slab中使用的页面。
kmem_freepages(cachep, slabp->s_mem-slabp->colouroff);
// 如果slab描述符不在slab中,则这里使用它所用到的内存。
if (OFF_SLAB(cachep))
kmem_cache_free(cachep->slabp_cache, slabp);
}
① ⇒ kmem_freepages
② ⇒ kmem_cache_free
3、对象
这一节讨论如何管理对象。在这个地方,大部分的工作已经由它们的高速缓存管理器或者 slab 管理器完成。
(1)初始化 slab 中的对象
(a)kmem_cache_init_objs
这个函数大部分都与调试有关,所以从没有调试的部分开始,并在处理调试部分之前先进行详细的解释。在代码中标记的调试部分分别标记为 Part1 和 Part2 。
// mm/slab.c
// 这个函数的参数如下:
// cachep是对象初始化的高速缓存。
// slabp是存放对象的slab。
// ctor_flags是构造函数需要的标志,以标识这是否是一个原子性的分配。
static inline void kmem_cache_init_objs (kmem_cache_t * cachep,
slab_t * slabp, unsigned long ctor_flags)
{
int i;
// 初始化cache->num个对象
for (i = 0; i < cachep->num; i++) {
// slab中的基地址是s_mem。待分配的对象地址是 i *(单个对象的大小)。
void* objp = slabp->s_mem+cachep->objsize*i;
#if DEBUG
// 如果高速缓存是红色分区的,这里放置一个标记在对象的任一个末端。
if (cachep->flags & SLAB_RED_ZONE) {
// 放置一个标记在对象的起点。
*((unsigned long*)(objp)) = RED_MAGIC1;
// 放置一个标记在对象的末端口记住对象的大小的同时考虑了在红色分区可用时的
// 红色标记大小。
*((unsigned long*)(objp + cachep->objsize -
BYTES_PER_WORD)) = RED_MAGIC1;
// 为了方便在这一调试部分后调用构造函数.这里将objp指针加上红色标记的大小。
objp += BYTES_PER_WORD;
}
#endif
/*
* 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->ctor(objp, cachep, ctor_flags);
#if DEBUG
// 这是在构造函数存在并被调用发生后的调试块。
// objp指针以先前调试块的红色标记的大小向后移,所以这里将其移回去。
if (cachep->flags & SLAB_RED_ZONE)
objp -= BYTES_PER_WORD;
// 如果没有构造函数,这里以一种已知模式来感染对象,这样可以在后面跟踪
// 未初始化写时发现这个对象。
if (cachep->flags & SLAB_POISON)
/* need to poison the objs */
kmem_poison_obj(cachep, objp);
if (cachep->flags & SLAB_RED_ZONE) {
// 检查以保证在对象开始部分的红色标志保留给对象之前的跟踪写。
if (*((unsigned long*)(objp)) != RED_MAGIC1)
BUG();
// 检查以保证跟踪写没有在超过对象末端的地址发生。
if (*((unsigned long*)(objp + cachep->objsize -
BYTES_PER_WORD)) != RED_MAGIC1)
BUG();
}
#endif
// 宏slab_bufctl()将slabp转换为slab_t slab描述符,并向其中加入一个。这里将一
// 个指针指向slab描述符的末尾,然后将其转换为kmem_bufctl_t,并有效地给出bufctl
// 数组的起始。
slab_bufctl(slabp)[i] = i+1;
}
slab_bufctl(slabp)[i-1] = BUFCTL_END;
// 在bufctl数组中第1个空闲对象的下标是0。
// 这里讨论初始化对象的核心。下一步,将讨论调试的第1部分。
slabp->free = 0;
}
kmem_cache_init_objs 函数主要用来初始化 slab 对象。
(2)分配对象
(a)kmem_cache_alloc
这个函数的调用图如图 8.14 所示。这个小函数仅调用 __kmem_cache_alloc() 。
// mm/slab.c
/**
* kmem_cache_alloc - Allocate an object
* @cachep: The cache to allocate from.
* @flags: See kmalloc().
*
* Allocate an object from this cache. The flags are only relevant
* if the cache has no available objects.
*/
void * kmem_cache_alloc (kmem_cache_t *cachep, int flags)
{
return __kmem_cache_alloc(cachep, flags);
}
(b)__kmem_cache_alloc
// mm/slab.c
// 参数是要从中分配的高速缓存以及指定的分配标志位。
static inline void * __kmem_cache_alloc (kmem_cache_t *cachep, int flags)
{
unsigned long save_flags;
void* objp;
// 这个函数保证使用了 DMA 标志位的恰当组合。
kmem_cache_alloc_head(cachep, flags);
try_again:
// 关中断并保存标志位。这个函数用于中断,所以这里是在 UP 情形下提供同步的惟
// 一方式。
local_irq_save(save_flags);
#ifdef CONFIG_SMP
{
// 从这个CPU中获取per-CPU数据。
cpucache_t *cc = cc_data(cachep);
// 如果per-CPU可用,则 ...
if (cc) {
// 如果一个对象可用,则……
if (cc->avail) {
// 如果允许则更新高速缓存统计计数。
STATS_INC_ALLOCHIT(cachep);
// 获取一个对象并更新avail数值。
objp = cc_entry(cc)[--cc->avail];
} else {
// 如果不是这样,即没有对象可用,则 ...
// 如果激活则更新这个高速缓存的统计。
STATS_INC_ALLOCMISS(cachep);
// 分配batchcount个对象,除最后一个以外将它们都放置到per-CPU中,返回最后一
// 个给objp。
objp = kmem_cache_alloc_batch(cachep,cc,flags);
// 分配失败,所以跳转到 alloc_new_slab_nolock 来增长高速缓存并分配一个新的 slab。
if (!objp)
goto alloc_new_slab_nolock;
}
} else {
// 如果没有可用的per-CPU高速缓存,则这里释放高速缓存自旋锁并像UP情
// 形一样分配一个对象。例如,在初始化 cache_cache 时就会出现这种情形。
spin_lock(&cachep->spinlock);
objp = kmem_cache_alloc_one(cachep);
// 成功指定对象,所以释放高速缓存自旋锁。
spin_unlock(&cachep->spinlock);
}
}
#else
// kmem_cache_alloc_one() 从链表中分配一个对象并返回这个对象。
// 如果没有释放对象,则这个宏(注意它不是一个函数)将转到该函数末尾处的
// alloc_new_slab。
objp = kmem_cache_alloc_one(cachep);
#endif
// 重新开中断,返回已分配的对象。
local_irq_restore(save_flags);
return objp;
alloc_new_slab:
#ifdef CONFIG_SMP
spin_unlock(&cachep->spinlock);
// 如果 kmem_cache_alloc_one() 未能分配一个对象,则跳转到这里,这个时候
// 还持有自旋锁,所以必须在这里释放。
alloc_new_slab_nolock:
#endif
// 在这个标记处,slabs_partial中没有释放对象,slabs_free也为空,则需要一个新的slab.
local_irq_restore(save_flags);
// 分配一个新的slab (见8.2.2小节)。
if (kmem_cache_grow(cachep, flags))
/* Someone may have stolen our objs. Doesn't matter, we'll
* just come back here again.
*/
// 因为有一个新的slab可用,所以这里再次尝试。
goto try_again;
// 没有分配一个slab,所以这里返回失败。
return NULL;
}
(c)kmem_cache_alloc_head
这个简单函数保证从 slab 中分配时用到的 GFP 标记位和 slab 正确关联。如果该高速缓存用作 DMA ,则这个函数将保证调用者不会突然请求普通内存,反之亦然。
// mm/slab.c
// 参数是我们将从中分配的高速缓存,分配时需要的标志位。
static inline void kmem_cache_alloc_head(kmem_cache_t *cachep, int flags)
{
// 如果调用者请求用于DMA的内存,则 ...
if (flags & SLAB_DMA) {
// 这个高速缓存没有使用DMA内存,则调用BUG()。
if (!(cachep->gfpflags & GFP_DMA))
BUG();
} else {
// 如果不是这样,如果调用者没有请求DMA内存,且该高速缓存用于DMA,则这里
// 调用BUG()。
if (cachep->gfpflags & GFP_DMA)
BUG();
}
}
(d)kmem_cache_alloc_one
这是一个预处理宏。奇怪的是在这里没有把它写成一个内联函数,而是写成了一个预处理宏,在 __kmem_cache_alloc() 中优化了 goto。
// mm/slab.c
/*
* Returns a ptr to an obj in the given cache.
* caller must guarantee synchronization
* #define for the goto optimization 8-)
*/
#define kmem_cache_alloc_one(cachep) \
({ \
struct list_head * slabs_partial, * entry; \
slab_t *slabp; \
\
// 从slabs_partial链表中获得第一个slab。
slabs_partial = &(cachep)->slabs_partial; \
entry = slabs_partial->next; \
// 如果从这个链表中不能获得slab,从这里开始执行这一块。
if (unlikely(entry == slabs_partial)) { \
// 从 slabs_free 链表中获得第 1 个 slab。
struct list_head * slabs_free; \
slabs_free = &(cachep)->slabs_free; \
entry = slabs_free->next; \
// 如果slabs_free中没有slab,则跳转到alloc_new_slab(),这里跳转的标号在
// __kmem_cache_alloc()中,在那里把高速缓存增大一个slab。
if (unlikely(entry == slabs_free)) \
goto alloc_new_slab; \
// 如果不是这样,则从空闲链表中移除slab,并将其放到slabs_partial链表中,
// 因为将有一个对象从那里移除。
list_del(entry); \
list_add(entry, slabs_partial); \
} \
\
// 从链表上获取slab。
slabp = list_entry(entry, slab_t, list); \
// 从slab中分配一个对象。
kmem_cache_alloc_one_tail(cachep, slabp); \
})
(e)kmem_cache_alloc_one_tail
这个函数负责从 slab 中分配一个对象。这里大部分都是调试代码 。
// mm/slab.c
// 参数是高速缓存和要从中分配的slab。
static inline void * kmem_cache_alloc_one_tail (kmem_cache_t *cachep,
slab_t *slabp)
{
void *objp;
// 如果stats可用,则这里设置三个统计计数。ALLOCED是已经分配的对象
// 总数。ACTIVE是高速缓存中活跃对象的总数。HIGH是某个时间上活跃对象的最大数。
STATS_INC_ALLOCED(cachep);
STATS_INC_ACTIVE(cachep);
STATS_SET_HIGH(cachep);
/* get obj pointer */
// inuse是该slab上的活动对象数。
slabp->inuse++;
// 获取指向一个空闲对象的指针。s_mem 是指向slab上第1个对象的指针。free 是
// slab 中空闲对象的索引。index * 对象大小 是slab中的偏移。
objp = slabp->s_mem + slabp->free*cachep->objsize;
// 更新空闲指针为下一个空闲对象的索引。
slabp->free=slab_bufctl(slabp)[slabp->free];
// 如果slab是满的,则这里从slabs_partial链表中将它移除并放到slabs_full链表中。
if (unlikely(slabp->free == BUFCTL_END)) {
list_del(&slabp->list);
list_add(&slabp->list, &cachep->slabs_full);
}
#if DEBUG
// 调试代码。
// 如果对象由已知模式感染,则这里检查它是否是一次未初始化访问。
if (cachep->flags & SLAB_POISON)
if (kmem_check_poison_obj(cachep, objp))
BUG();
if (cachep->flags & SLAB_RED_ZONE) {
/* Set alloc red-zone, and check old one. */
// 如果红色分区可用,则这里检查对象开始处的标记并保证它是安全的。在后
// 面改变该红色标记以检查对象后面的写操作。
if (xchg((unsigned long *)objp, RED_MAGIC2) !=
RED_MAGIC1)
BUG();
// 检查对象末端的标记并在后面改变该红色标记,以检查对象后面的写操作。
if (xchg((unsigned long *)(objp+cachep->objsize -
BYTES_PER_WORD), RED_MAGIC2) != RED_MAGIC1)
BUG();
// 更新对象指针以指向红色标记后面的指针。
objp += BYTES_PER_WORD;
}
#endif
// 返回对象给调用者。
return objp;
}
(f)kmem_cache_alloc_batch
这个函数分配一批对象给一个对象的 CPU 高速缓存。它仅在 SMP 情形下使用。在许多方面,它和 kmem_cache_alloc_one() 类似。
// mm/slab.c
// 参数是要从中分配的高速缓存,要填充的per-CPU高速缓存以及分配标志位。
void* kmem_cache_alloc_batch(kmem_cache_t* cachep, cpucache_t* cc, int flags)
{
// batchcount是待分配的对象数目
int batchcount = cachep->batchcount;
// 获取访问高速缓存描述符的自旋锁。
spin_lock(&cachep->spinlock);
// 循环 batchcount 次数。
while (batchcount--) {
// 这个例子与 kmem_cache_alloc_one() 相同,它从 slabs_partial 或slabs_free中
// 选择一个slab来分配。如果没有,则退出循环。
struct list_head * slabs_partial, * entry;
slab_t *slabp;
/* Get slab alloc is to come from. */
slabs_partial = &(cachep)->slabs_partial;
entry = slabs_partial->next;
if (unlikely(entry == slabs_partial)) {
struct list_head * slabs_free;
slabs_free = &(cachep)->slabs_free;
entry = slabs_free->next;
if (unlikely(entry == slabs_free))
break;
list_del(entry);
list_add(entry, slabs_partial);
}
// 调用 kmem_cache_alloc_one_tail 并将其放入 per-CPU 高速缓存。
slabp = list_entry(entry, slab_t, list);
cc_entry(cc)[cc->avail++] =
kmem_cache_alloc_one_tail(cachep, slabp);
}
// 释放高速缓存描述符锁。
spin_unlock(&cachep->spinlock);
// 从这批分配的对象中取出一个并返回它。
if (cc->avail)
return cc_entry(cc)[--cc->avail];
// 如果没有分配对象,这里直接返回。__kmem_cache_alloc() 将通过一
// 个slab增大高速缓存并再次尝试。
return NULL;
}
(3)释放对象
(a)kmem_cache_free
// mm/slab.c
/**
* kmem_cache_free - Deallocate an object
* @cachep: The cache the allocation was from.
* @objp: The previously allocated object.
*
* Free an object which was previously allocated from this
* cache.
*/
// 参数是对象释放的高速缓存,以及对象本身。
void kmem_cache_free (kmem_cache_t *cachep, void *objp)
{
unsigned long flags;
#if DEBUG
// 如果调试可用,则首先利用CHECK_PAGE()进行检查以确定它是一个slab
// 页面。然后检查一个页面链表以保证它属于这个高速缓存(如图8.8所示)。
CHECK_PAGE(virt_to_page(objp));
if (cachep != GET_PAGE_CACHE(virt_to_page(objp)))
BUG();
#endif
// 关闭中断来保护该路径。
local_irq_save(flags);
// SMP 情形下, __kmem_cache_free() 释放对象到 per-CPU 高速缓存
// 中。普通情形下,释放到全局池中。
__kmem_cache_free(cachep, objp);
// 重新打开中断。
local_irq_restore(flags);
}
(b)__kmem_cache_free
// mm/slab.c
/*
* __kmem_cache_free
* called with disabled ints
*/
static inline void __kmem_cache_free (kmem_cache_t *cachep, void* objp)
{
#ifdef CONFIG_SMP
// 这种情形比较有趣,在这种情形下,如果有对象则释放到per-CPU高速缓存
// 获得这个per-CPU高速缓存的数据(见8.5.1小节)。
cpucache_t *cc = cc_data(cachep);
// 保证该页面是一个slab页面。
CHECK_PAGE(virt_to_page(objp));
// 如果per-CPU高速缓存可用,则这里试着使用它。这里并不是一直都可用。
// 例如,在销毁高速缓存的时候,per-CPU高速缓存已经不存在了。
if (cc) {
int batchcount;
// 如果可用的per-CPU高速缓存数目在极限值以下,这里将对象加入到空闲链
// 表中并返回。
if (cc->avail < cc->limit) {
STATS_INC_FREEHIT(cachep);
cc_entry(cc)[cc->avail++] = objp;
return;
}
// 如果可用,则更新统计计数。
STATS_INC_FREEMISS(cachep);
// 池已经溢出,所以将batchcount个对象释放到全局池中。
batchcount = cachep->batchcount;
// 更新可用(avail)对象的数目。
cc->avail -= batchcount;
// 释放一块对象到全局池。
free_block(cachep,
&cc_entry(cc)[cc->avail],batchcount);
// 释放请求的对象并将其放到per-CPU池中。
cc_entry(cc)[cc->avail++] = objp;
return;
} else {
// 如果per-CPU高速缓存不可用,则这里释放对象到全局池中。
free_block(cachep, &objp, 1);
}
#else
// 这种情形比较有趣。在这种情形下,如果有对象则释放到per-CPU高速缓存。
kmem_cache_free_one(cachep, objp);
#endif
}
① ⇒ cc_data 和 cc_entry
// mm/slab.c
/*
* cpucache_t
*
* Per cpu structures
* The limit is stored in the per-cpu structure to reduce the data cache
* footprint.
*/
typedef struct cpucache_s {
unsigned int avail;
unsigned int limit;
} cpucache_t;
#define cc_entry(cpucache) \
((void **)(((cpucache_t*)(cpucache))+1))
#define cc_data(cachep) \
((cachep)->cpudata[smp_processor_id()])
② ⇒ free_block
free_block 函数
(c)kmem_cache_free_one
// mm/slab.c
static inline void kmem_cache_free_one(kmem_cache_t *cachep, void *objp)
{
slab_t* slabp;
// 保证页面是一个slab页面。
CHECK_PAGE(virt_to_page(objp));
/* reduces memory footprint
*
if (OPTIMIZE(cachep))
slabp = (void*)((unsigned long)objp&(~(PAGE_SIZE-1)));
else
*/
// 获取页面的slab描述符。
slabp = GET_PAGE_SLAB(virt_to_page(objp));
#if DEBUG
// 如果设置了 SLAB_DEBUG_INITIAL 标志位,则调用构造函数来验证该对
// 象处于初始化的状态。
if (cachep->flags & SLAB_DEBUG_INITIAL)
/* Need to call the slab's constructor so the
* caller can perform a verify of its state (debugging).
* Called without the cache-lock held.
*/
cachep->ctor(objp, cachep, SLAB_CTOR_CONSTRUCTOR|SLAB_CTOR_VERIFY);
// 检查对象两端的红色标记。这将检查超出对象边界写和重复释放。
if (cachep->flags & SLAB_RED_ZONE) {
objp -= BYTES_PER_WORD;
if (xchg((unsigned long *)objp, RED_MAGIC1) != RED_MAGIC2)
/* Either write before start, or a double free. */
BUG();
if (xchg((unsigned long *)(objp+cachep->objsize -
BYTES_PER_WORD), RED_MAGIC1) != RED_MAGIC2)
/* Either write past end, or a double free. */
BUG();
}
// 以一种已知模式来感染释放的对象。
if (cachep->flags & SLAB_POISON)
kmem_poison_obj(cachep, objp);
// 这个函数确定该对象是这个slab和高速缓存的一部分。它将检查空闲链表
// (bufctl)以保证这不是一次重复释放。
if (kmem_extra_free_checks(cachep, slabp, objp))
return;
#endif
{
// 计算被释放对象的下标。
unsigned int objnr = (objp-slabp->s_mem)/cachep->objsize;
slab_bufctl(slabp)[objnr] = slabp->free;
// 由于这个对象已经被释放,所以更新bufctl来反映这一点。
slabp->free = objnr;
}
// 如果统计计数可用,则这里不激活在slab中的活跃对象。
STATS_DEC_ACTIVE(cachep);
/* fixup slab chains */
{
int inuse = slabp->inuse;
// 如果inuse到达0,则释放slab并将其移到slabs_free链表中。
if (unlikely(!--slabp->inuse)) {
/* Was partial or full, now empty. */
list_del(&slabp->list);
list_add(&slabp->list, &cachep->slabs_free);
} else if (unlikely(inuse == cachep->num)) {
// 如果使用的数量等于slab中的对象数量,则slab已满,所以将该slab移到 slabs_full 链表中。
/* Was full. */
list_del(&slabp->list);
list_add(&slabp->list, &cachep->slabs_partial);
}
}
}
kmem_cache_free_one 函数释放一个 slab 对象。
(d)free_block
这个函数仅用于 SMP 情形中 per-CPU 高速缓存变得过满时。它用于批量释放对象。
// mm/slab.c
// 参数如下:
// cachep是从中释放对象的高速缓存。
// objpp指向待释放的第一个对象的指针。
// len是待释放对象的数量。
#ifdef CONFIG_SMP
static void free_block (kmem_cache_t* cachep, void** objpp, int len)
{
// 获取对高速缓存描述符的锁。
spin_lock(&cachep->spinlock);
// __free_block() 来进行释放每个页面的实际工作。
__free_block(cachep, objpp, len);
// 释放锁。
spin_unlock(&cachep->spinlock);
}
#endif
(e)__free_block
这个函数负责释放 per-CPU 数组 objpp 中的每个对象。
// mm/slab.c
#ifdef CONFIG_SMP
// 参数是对象所属的cachep,对象链表(objpp)和待释放对象的数目(len)。
static inline void __free_block (kmem_cache_t* cachep,
void** objpp, int len)
{
// 循环len次。
for ( ; len > 0; len--, objpp++)
// 从数组中释放对象。
kmem_cache_free_one(cachep, *objpp);
}
#endif
① ⇒ kmem_cache_free_one
4、指定大小的高速缓存
(1)初始化指定大小的高速缓存
(a)kmem_cache_sizes_init
这个函数负责为适应普通或 DMA 内存的小内存缓冲区创建高速缓存对。
// mm/slab.c
/* Initialisation - setup remaining internal and general caches.
* Called after the gfp() functions have been enabled, and before smp_init().
*/
void __init kmem_cache_sizes_init(void)
{
// 获取指向cache_sizes数组的指针。
cache_sizes_t *sizes = cache_sizes;
// 高速缓存的可读名。它必须是指定大小的CACHE_NAMELEN,定义为20个字节长。
char name[20];
/*
* Fragmentation resistance on low memory - only use bigger
* page orders on machines with more than 32MB of memory.
*/
// 除非0个对象填入slab,否则 slab_break_gfp_order 决定了一个slab可以使用
// 的页面数。它静态地初始化为BREAK_GFP_ORDER_LO(1)。这里检查以确定是否有多于
// 32MB的内存,如果有,则允许使用BREAK_GFP_ORDER_HI个页面,这是因为有更多内存
// 可用时可以接受更多的内部碎片。
if (num_physpages > (32 << 20) >> PAGE_SHIFT)
slab_break_gfp_order = BREAK_GFP_ORDER_HI;
// 为需要的每个内存分配大小以创建两个高速缓存。
do {
/* 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. */
// 将可读的高速缓存名字存放于name
snprintf(name, sizeof(name), "size-%Zd",sizes->cs_size);
// 创建高速缓存,与L1高速缓存对齐
if (!(sizes->cs_cachep =
kmem_cache_create(name, sizes->cs_size,
0, SLAB_HWCACHE_ALIGN, NULL, NULL))) {
BUG();
}
/* Inc off-slab bufctl limit until the ceiling is hit. */
// 计算off-slab的bufctl限制,当slab描述符不在高速缓存中时它决定了高速缓
// 存可以存放的对象数目。
if (!(OFF_SLAB(sizes->cs_cachep))) {
offslab_limit = sizes->cs_size-sizeof(slab_t);
offslab_limit /= 2;
}
// 用于DMA的高速缓存可读名。
snprintf(name, sizeof(name), "size-%Zd(DMA)",sizes->cs_size);
// 创建对齐于L1高速缓存以及适合用于DMA的高速缓存。
sizes->cs_dmacachep = kmem_cache_create(name, sizes->cs_size, 0,
SLAB_CACHE_DMA|SLAB_HWCACHE_ALIGN, NULL, NULL);
// 如果分配高速缓存失败,则这是一个bug,如果在这之前没有可用的内存,则系统不
// 会启动。
if (!sizes->cs_dmacachep)
BUG();
// 移到cache_sizes数组中的下一个元素。
sizes++;
// 数组以最后一个元素0结尾。
} while (sizes->cs_size);
}
(2)kmalloc
(a)kmalloc
// mm/slab.c
/**
* kmalloc - allocate memory
* @size: how many bytes of memory are required.
* @flags: the type of memory to allocate.
*
* kmalloc is the normal method of allocating memory
* in the kernel.
*
* The @flags argument may be one of:
*
* %GFP_USER - Allocate memory on behalf of user. May sleep.
*
* %GFP_KERNEL - Allocate normal kernel ram. May sleep.
*
* %GFP_ATOMIC - Allocation will not sleep. Use inside interrupt handlers.
*
* Additionally, the %GFP_DMA flag may be set to indicate the memory
* must be suitable for DMA. This can mean different things on different
* platforms. For example, on i386, it means that the memory must come
* from the first 16MB.
*/
void * kmalloc (size_t size, int flags)
{
// cache_sizes 是每个指定大小的高速缓存数组(见8. 4节)。
cache_sizes_t *csizep = cache_sizes;
// 从最小的高速缓存开始,这里检查每个高速缓存,直至找到了满足请求的足
// 够大的高速缓存。
for (; csizep->cs_size; csizep++) {
if (size > csizep->cs_size)
continue;
// 如果分配用于DMA,则这里从cs_dmacachep中分配一个对象,否则从cs_cachep中
// 分配一个对象。
return __kmem_cache_alloc(flags & GFP_DMA ?
csizep->cs_dmacachep : csizep->cs_cachep, flags);
}
// 如果没有一个足够大的指定大小的高速缓存或者无法分配一个对象,则这里返回失败。
return NULL;
}
(3)kfree
(a)kfree
这个函数的调用图如图 8.17 所示。值得注意的是这个函数与带调试标志的 kmem_cache_free() 几乎一样。
// mm/slab.c
/**
* kfree - free previously allocated memory
* @objp: pointer returned by kmalloc.
*
* Don't free memory not originally allocated by kmalloc()
* or you will run into trouble.
*/
void kfree (const void *objp)
{
kmem_cache_t *c;
unsigned long flags;
// 如果指针为NULL则返回。如果一个调用者使用kmalloc。并在捕捉异常路径上
// 直接调用了 kfree()就有可能发生这种情形。
if (!objp)
return;
// 关闭中断。
local_irq_save(flags);
// 保证这个对象页面是slab中的页面。
CHECK_PAGE(virt_to_page(objp));
// 获取这个指针所属的高速缓存(见8. 2节)。
c = GET_PAGE_CACHE(virt_to_page(objp));
// 释放内存对象。
__kmem_cache_free(c, (void*)objp);
// 重新打开中断。
local_irq_restore(flags);
}
5、Per-CPU 对象高速缓存
per-CPU 对象的结构以及如何向 per-CPU 中加入或移除对象,在 8.5.1 小节和 8.5.2 小节有详细讨论。
(1)启动 Per-CPU 高速缓存
(a)enable_all_cpucaches
这个函数上锁高速缓存链表,并启动每个高速缓存的 cpucache 。这在启动 cache_cache 和指定大小的高速缓存之后很重要。
// mm/slab.c
static void enable_all_cpucaches (void)
{
struct list_head* p;
// 获取高速缓存链表的信号量。
down(&cache_chain_sem);
// 获取链表中第1个高速缓存。
p = &cache_cache.next;
// 遍历整个高速缓存链表。
do {
// 从链表中获取一个高速缓存,这里的代码将跳过链表中的第1个高速缓存。但由于
// cache_cache很少使用,所以它不需要一个cpucache。
kmem_cache_t* cachep = list_entry(p, kmem_cache_t, next);
// 启动 cpucache。
enable_cpucache(cachep);
// 移到链表中的下一个高速缓存。
p = cachep->next.next;
} while (p != &cache_cache.next);
// 释放高速缓存链表信号量。
up(&cache_chain_sem);
}
(b)enable_cpucache
这个函数基于高速缓存中对象的大小计算 cpucache 大小,然后调用 kmem_tune_cpucache() 来进行实际的分配工作。
// mm/slab.c
static void enable_cpucache (kmem_cache_t *cachep)
{
int err;
int limit;
/* FIXME: optimize */
// 如果一个对象大于一页,则返回以避免为该对象创建一个per-CPU高速缓
// 存,因为per-CPU高速缓存的代价比较高。
if (cachep->objsize > PAGE_SIZE)
return;
// 如果一个对象大于1 KB,则这里将cpucache保持为小于3 MB的大小。这
// 里对124个对象的限制是考虑到cpucache描述符的大小。
if (cachep->objsize > 1024)
limit = 60;
// 对更小的对象,这里仅保证高速缓存不会超过3 MB。
else if (cachep->objsize > 256)
limit = 124;
else
limit = 252;
// 为cpucache分配内存。
err = kmem_tune_cpucache(cachep, limit, limit/2);
if (err)
// 如果分配失败,则打印一条错误信息。
printk(KERN_ERR "enable_cpucache failed for %s, error %d.\n",
cachep->name, -err);
}
(c)kmem_tune_cpucache
这个函数负责为 cpucache 分配内存。对系统中的每个 CPU,kmalloc 给每个 cpucache 分配一块足够大的内存并填充 ccupdate_struct_t 结构。函数 swap_call_function_all_cpus() 然后调用 do_ccupdate_local() ,它将高速缓存描述符中的旧信息用新信息替换。
// mm/slab.c
/* called with cache_chain_sem acquired. */
// 这个函数的参数如下:
// cachep是分配给cpucache的高速缓存。
// limit可以存在于cpucache中的对象总数。
// batchcount是在cpucache为空时一次分配的对象数。
static int kmem_tune_cpucache (kmem_cache_t* cachep, int limit, int batchcount)
{
ccupdate_struct_t new;
int i;
/*
* These are admin-provided, so we are more graceful.
*/
// 在高速缓存中的对象数目不能为负数。
if (limit < 0)
return -EINVAL;
// 不能分配负数个对象。
if (batchcount < 0)
return -EINVAL;
// 不能一次分配大于限制数量的对象。
if (batchcount > limit)
return -EINVAL;
// 如果限制为整数,则必须提供batchcount。
if (limit != 0 && !batchcount)
return -EINVAL;
// 用0填充来更新结构。
memset(&new.new,0,sizeof(new.new));
// 如果提供了限制,则这里为cpucache分配内存。
if (limit) {
// 对每个CPU,这里分配一1个cpucache。
for (i = 0; i< smp_num_cpus; i++) {
cpucache_t* ccnew;
// 所需的内存是limit个指针和cpucache描述符的大小。
ccnew = kmalloc(sizeof(void*)*limit+
sizeof(cpucache_t), GFP_KERNEL);
// 如果内存不足则在这里清理并退出。
if (!ccnew)
goto oom;
// 填充cpucache描述符的字段。
ccnew->limit = limit;
ccnew->avail = 0;
// 填充 ccupdate_update_t 结构的信息。
new.new[cpu_logical_map(i)] = ccnew;
}
}
// 告知ccupdate_update_t结构正在更新哪个高速缓存。
new.cachep = cachep;
// 获取对高速缓存描述符的中断安全锁并设置它的batchcount。
spin_lock_irq(&cachep->spinlock);
cachep->batchcount = batchcount;
spin_unlock_irq(&cachep->spinlock);
// 获取 CPU 以更新它自身的 cpucache 信息。do_ccupdate_local() 用
// new 中的新cpucache替换高速缓存描述符中的旧cpucache。
smp_call_function_all_cpus(do_ccupdate_local, (void *)&new);
// 在 smp_call_function_all_cpus() 后,旧的 cpucache 在 new 中。
// 这一块代码遍历它们,释放它们中所有对象并删除旧的cpucache。
for (i = 0; i < smp_num_cpus; i++) {
cpucache_t* ccold = new.new[cpu_logical_map(i)];
if (!ccold)
continue;
local_irq_disable();
free_block(cachep, cc_entry(ccold), ccold->avail);
local_irq_enable();
kfree(ccold);
}
// 返回成功。
return 0;
oom:
// 内存不足时,这里删除所有已经分配的cpucache,并返回失败。
for (i--; i >= 0; i--)
kfree(new.new[cpu_logical_map(i)]);
return -ENOMEM;
}
(2)更新 Per-CPU 信息
(a)smp_call_function_all_cpus
这个函数调用所有 CPU 的 func() 函数。在 slab 分配器上下文中,func() 为 do_ccupdate_local() 参数是 ccupdate_struct_t 。
// mm/slab.c
/*
* Waits for all CPUs to execute func().
*/
static void smp_call_function_all_cpus(void (*func) (void *arg), void *arg)
{
// 关闭局部中断并调用该CPU的这个函数。
local_irq_disable();
func(arg);
local_irq_enable();
// 对其他CPU,这里调用函数smp_call_function(),它是一个特定体系结构的函数,将
// 不在这里做进一步讨论。
if (smp_call_function(func, arg, 1, 1))
BUG();
}
typedef struct ccupdate_struct_s
{
kmem_cache_t *cachep;
cpucache_t *new[NR_CPUS];
} ccupdate_struct_t;
(b)do_ccupdate_local
这个函数用这个 CPU 中的 info 信息替换高速缓存描述符中的 cpucache 信息。
// mm/slab.c
static void do_ccupdate_local(void *info)
{
// info 是一个指向 ccupdate_struct_t 的指针于它将被传入 smp_call_function_all_cpus()
ccupdate_struct_t *new = (ccupdate_struct_t *)info;
// ccupdate_struct_t 部分是一个指向这个cpucache所属高速缓存的指针。cc_data() 返
// 回这个处理器的cpucache_t。
cpucache_t *old = cc_data(new->cachep);
// 将新的cpucache放入高速缓存描述符中。cc_data()返回指向这个CPU的cpucache指针。
cc_data(new->cachep) = new->new[smp_processor_id()];
// 将new中的指针替换为旧的cpucache,这样后面可以用例如smp_call_function_call_cpus(),
// kmem_tune_cpucache() 来删除它
new->new[smp_processor_id()] = old;
}
(3)清理 Per-CPU 高速缓存
(a)drain_cpu_caches
调用这个函数来清理 per-CPU 高速缓存中的所有对象。当需要释放 slab 来收缩高速缓存的时候就调用它。如果对象处于 per-CPU 高速缓存,即使没有使用也可以说该 slab 可以被释放。
// mm/slab.c
static void drain_cpu_caches(kmem_cache_t *cachep)
{
ccupdate_struct_t new;
int i;
// 将更新结构清空,因为马上将清空所有的数据。
memset(&new.new,0,sizeof(new.new));
// 设置new.cachep 指向 cachep,这样 smp_call_function_all_cpus()
// 知道哪个高速缓存受影响。
new.cachep = cachep;
// 获取高速缓存描述符信号量。
down(&cache_chain_sem);
// do_ccupdate_local() 利用 new
// 中的cpucache_t信息来替换高速缓存描
// 述符中的cpucache_t信息,这样它们在这里可以改变。
smp_call_function_all_cpus(do_ccupdate_local, (void *)&new);
// 对系统中的每个CPU, ...
for (i = 0; i < smp_num_cpus; i++) {
// 获取该CPU中的cpucache描述符。
cpucache_t* ccold = new.new[cpu_logical_map(i)];
// 如果由于某个原因这个结构不存在,或其中不存在对象,则移到下一个CPU。
if (!ccold || (ccold->avail == 0))
continue;
// 在这个处理器上关闭中断。有可能在其他地方中断处理程序的分配将试着访问
// per-CPU高速缓存。
local_irq_disable();
// 利用 free_block() 释放对象块。
free_block(cachep, cc_entry(ccold), ccold->avail);
// 重新打开中断。
local_irq_enable();
// 显示没有对象可用。
ccold->avail = 0;
}
// 每个CPU的信息已经更新,所以这里对每个CPU调用do_ccupdate_local()
// 来将信息重新放入到高速缓存描述符中。
smp_call_function_all_cpus(do_ccupdate_local, (void *)&new);
// 释放高速缓存链表中的信号量。
up(&cache_chain_sem);
}
6、初始化 slab 分配器
(1)kmem_cache_init
这个函数将做如下工作:
- 初始化高速缓存链表。
- 初始化访问高速缓存链表的 mutex。
- 计算 cache_cache 颜色。
// mm/slab.c
/* Initialisation - setup the `cache' cache. */
void __init kmem_cache_init(void)
{
size_t left_over;
// 初始化访问高速缓存链表的semaphore信号量。
init_MUTEX(&cache_chain_sem);
// 初始化高速缓存链表。
INIT_LIST_HEAD(&cache_chain);
// kmem_cache_estimate() 计算对象的数量以及消耗的字节数。
kmem_cache_estimate(0, cache_cache.objsize, 0,
&left_over, &cache_cache.num);
// 如果kmem_cache_t不能存放在页面中,肯定是某个地方出现了很严重的错误。
if (!cache_cache.num)
BUG();
// colour 是 L1高速缓存对齐时可用的不同高速缓存行数目。
cache_cache.colour = left_over/cache_cache.colour_off;
// colour_next表明要使用的下个高速缓存行,它从0开始。
cache_cache.colour_next = 0;
}
① ⇒ kmem_cache_estimate
7、与伙伴分配器的接口
(1)kmem_getpages
这里为 slab 分配器分配页面。
// mm/slab.c
/* Interface to system's page allocator. No need to hold the cache-lock.
*/
static inline void * kmem_getpages (kmem_cache_t *cachep, unsigned long flags)
{
void *addr;
/*
* If we requested dmaable memory, we will get it. Even if we
* did not request dmaable memory, we might get it, but that
* would be relatively rare and ignorable.
*/
// 分配所请求的无论是何种标志,都取决于高速缓存的标志位。如果需要DMA内存,
// 则惟一可以追加的标志位是ZONE_DMA。
flags |= cachep->gfpflags;
// 利用 __get_free_pages() 从伙伴分配器中分配。
addr = (void*) __get_free_pages(flags, cachep->gfporder);
/* Assume that now we have the pages no one else can legally
* messes with the 'struct page's.
* However vm_scan() might try to test the structure to see if
* it is a named-page or buffer-page. The members it tests are
* of no interest here.....
*/
// 返回页面,如果分配失败则返回NULL。
return addr;
}
(2)kmem_freepages
这里为 slab 分配器释放页面。在调用伙伴分配器 API 之前,它将从页面标志位中移除 PG_slab 位。
// mm/slab.c
/* Interface to system's page release. */
static inline void kmem_freepages (kmem_cache_t *cachep, void *addr)
{
// 检索用于原始分配的幂次。
unsigned long i = (1<<cachep->gfporder);
// 获取该地址的结构页面。
struct page *page = virt_to_page(addr);
/* free_pages() does not clear the type bit - we do that.
* The pages have been unlinked from their cache-slab,
* but their 'struct page's might be accessed in
* vm_scan(). Shouldn't be a worry.
*/
// 清除每一页中的 PG_slab 位。
while (i--) {
PageClearSlab(page);
page++;
}
// 利用 free_pages() 从伙伴分配器中释放页面。
free_pages((unsigned long)addr, cachep->gfporder);
}
二、kmalloc 分析
1、kmalloc
kmem_cache_sizes_init 函数初始化 cache_sizes。
kmalloc 函数从 cache_sizes 中选择最小可容纳 size 的 cache_sizes_t ,然后根据 GFP_DMA 是使用 cs_dmacachep 还是 cs_cachep 高速缓存。
// mm/slab.c
void * kmalloc (size_t size, int flags)
{
// cache_sizes 是每个指定大小的高速缓存数组(见8. 4节)。
cache_sizes_t *csizep = cache_sizes;
// 从最小的高速缓存开始,这里检查每个高速缓存,直至找到了满足请求的足
// 够大的高速缓存。
for (; csizep->cs_size; csizep++) {
if (size > csizep->cs_size)
continue;
// 如果分配用于DMA,则这里从cs_dmacachep中分配一个对象,否则从cs_cachep中
// 分配一个对象。
return __kmem_cache_alloc(flags & GFP_DMA ?
csizep->cs_dmacachep : csizep->cs_cachep, flags);
}
// 如果没有一个足够大的指定大小的高速缓存或者无法分配一个对象,则这里返回失败。
return NULL;
}
2、__kmem_cache_alloc
// mm/slab.c
// 参数是要从中分配的高速缓存以及指定的分配标志位。
static inline void * __kmem_cache_alloc (kmem_cache_t *cachep, int flags)
{
unsigned long save_flags;
void* objp;
// 这个函数保证使用了 DMA 标志位的恰当组合。
kmem_cache_alloc_head(cachep, flags);
try_again:
// 关中断并保存标志位。这个函数用于中断,所以这里是在 UP 情形下提供同步的惟
// 一方式。
local_irq_save(save_flags);
// kmem_cache_alloc_one() 从链表中分配一个对象并返回这个对象。
// 如果没有释放对象,则这个宏(注意它不是一个函数)将转到该函数末尾处的
// alloc_new_slab。
// objp = kmem_cache_alloc_one(cachep);
{
struct list_head * slabs_partial, * entry;
slab_t *slabp;
// 从slabs_partial链表中获得第一个slab。
slabs_partial = &(cachep)->slabs_partial;
entry = slabs_partial->next;
// 如果从这个链表中不能获得slab,从这里开始执行这一块。
if (unlikely(entry == slabs_partial)) {
// 从 slabs_free 链表中获得第 1 个 slab。
struct list_head * slabs_free;
slabs_free = &(cachep)->slabs_free;
entry = slabs_free->next;
// 如果slabs_free中没有slab,则跳转到alloc_new_slab(),这里跳转的标号在
// __kmem_cache_alloc()中,在那里把高速缓存增大一个slab。
if (unlikely(entry == slabs_free))
goto alloc_new_slab;
// 如果不是这样,则从空闲链表中移除slab,并将其放到slabs_partial链表中,
// 因为将有一个对象从那里移除。
list_del(entry);
list_add(entry, slabs_partial);
}
// 从链表上获取slab。
slabp = list_entry(entry, slab_t, list);
// 从slab中分配一个对象。
objp = kmem_cache_alloc_one_tail(cachep, slabp);
}
// 重新开中断,返回已分配的对象。
local_irq_restore(save_flags);
return objp;
alloc_new_slab:
// 在这个标记处,slabs_partial中没有释放对象,slabs_free也为空,则需要一个新的slab.
local_irq_restore(save_flags);
// 分配一个新的slab (见8.2.2小节)。
if (kmem_cache_grow(cachep, flags))
/* Someone may have stolen our objs. Doesn't matter, we'll
* just come back here again.
*/
// 因为有一个新的slab可用,所以这里再次尝试。
goto try_again;
// 没有分配一个slab,所以这里返回失败。
return NULL;
}
kmem_cache_alloc_head 函数,判断 flags 和 kmem_cache_t 中 DMA 标志是否一致。
kmem_cache_alloc_one 函数、 kmem_cache_alloc_one_tail 函数、kmem_cache_grow 函数。
__kmem_cache_alloc 函数主要执行以下几步:
- 如果 cachep ->slabs_partial 链表中有 slab_t,则从中分配一个。
- 否则,如果 cachep ->slabs_free 链表中有 slab_t,则从中分配一个。
- 否则,调用 kmem_cache_grow 函数分配一个 slab 管理器。然后重复上面步骤。
找到一个 slab 管理器( slab_t)后,调用 kmem_cache_alloc_one_tail 函数从 slab 中分配一个对象。
三、kfree 分析
1、kfree
// mm/slab.c
void kfree (const void *objp)
{
kmem_cache_t *c;
unsigned long flags;
// 如果指针为NULL则返回。如果一个调用者使用kmalloc。并在捕捉异常路径上
// 直接调用了 kfree()就有可能发生这种情形。
if (!objp)
return;
// 关闭中断。
local_irq_save(flags);
// 保证这个对象页面是slab中的页面。
CHECK_PAGE(virt_to_page(objp));
// 获取这个指针所属的高速缓存(见8. 2节)。
c = GET_PAGE_CACHE(virt_to_page(objp));
// 释放内存对象。
__kmem_cache_free(c, (void*)objp);
// 重新打开中断。
local_irq_restore(flags);
}