深入理解Linux虚拟内存管理(六)(上):https://developer.aliyun.com/article/1597810
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; }
(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
深入理解Linux虚拟内存管理(六)(下):https://developer.aliyun.com/article/1597823