一、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
深入理解Linux虚拟内存管理(六)(中):https://developer.aliyun.com/article/1597819