linux 源代码分析8:Slab内存分配
Slab内存分配
kmem_cache_alloc函数
kmem_cache_alloc函数在一个特点slab缓存分配内存,在mm/slab.c中实现代码如下:
3764 void *kmem_cache_alloc(structkmem_cache *cachep, gfp_t flags)
3765 {
3766 void *ret = __cache_alloc(cachep, flags, __builtin_return_address(0));
3767
3768 trace_kmem_cache_alloc(_RET_IP_, ret,
3769 obj_size(cachep), cachep->buffer_size, flags);
3770
3771 return ret;
3772 }
kmem_cache_alloc函数的关键是对__cache_alloc的调用。
__cache_alloc
__cache_alloc在mm/slab.c中实现代码如下:
3592 static __always_inline void *
3593 __cache_alloc(struct kmem_cache*cachep, gfp_t flags, void *caller)
3594 {
3595 unsigned long save_flags;
3596 void *objp;
3597
3598 flags &= gfp_allowed_mask;
3599
3600 lockdep_trace_alloc(flags);
3601
3602 if (slab_should_failslab(cachep, flags))
3603 return NULL;
3604
3605 cache_alloc_debugcheck_before(cachep, flags);
3606 local_irq_save(save_flags);
3607 objp = __do_cache_alloc(cachep, flags);
3608 local_irq_restore(save_flags);
3609 objp = cache_alloc_debugcheck_after(cachep, flags, objp, caller);
3610 kmemleak_alloc_recursive(objp, obj_size(cachep), 1, cachep->flags,
3611 flags);
3612 prefetchw(objp);
3613
3614 if (likely(objp))
3615 kmemcheck_slab_alloc(cachep,flags, objp, obj_size(cachep));
3616
3617 if (unlikely((flags & __GFP_ZERO) && objp))
3618 memset(objp, 0,obj_size(cachep));
3619
3620 return objp;
3621 }
3606行__cache_alloc函数中关闭了中断,3607调用__do_cache_alloc函数进行分配,3608行开启中断。
3612是预加载缓存的代码。
3617-3618行对__GFP_ZERO选项进行了处理,就是把分配的内存块初始化为0。
其他是关于kmemcheck和调试代码。
__do_cache_alloc函数
__do_cache_alloc在mm/slab.c中实现代码如下:
3584 static __always_inline void *
3585 __do_cache_alloc(struct kmem_cache*cachep, gfp_t flags)
3586 {
3587 return ____cache_alloc(cachep, flags);
3588 }
__do_cache_alloc是直接对____cache_alloc的调用。
____cache_alloc函数
____cache_alloc在mm/slab.c中实现代码如下:
3291 static inline void*____cache_alloc(struct kmem_cache *cachep, gfp_t flags)
3292 {
3293 void *objp;
3294 struct array_cache *ac;
3295
3296 check_irq_off();
3297
3298 ac = cpu_cache_get(cachep);
3299 if (likely(ac->avail)) {
3300 STATS_INC_ALLOCHIT(cachep);
3301 ac->touched = 1;
3302 objp =ac->entry[--ac->avail];
3303 } else {
3304 STATS_INC_ALLOCMISS(cachep);
3305 objp =cache_alloc_refill(cachep, flags);
3306 /*
3307 * the 'ac' may be updated bycache_alloc_refill(),
3308 * and kmemleak_erase()requires its correct value.
3309 */
3310 ac = cpu_cache_get(cachep);
3311 }
3312 /*
3313 * To avoid a false negative, if anobject that is in one of the
3314 * per-CPU caches is leaked, we needto make sure kmemleak doesn't
3315 * treat the array pointers as areference to the object.
3316 */
3317 if (objp)
3318 kmemleak_erase(&ac->entry[ac->avail]);
3319 return objp;
3320 }
____cache_alloc分两条路径进行分配,当前cpu的对象缓存不空,从当前cpu的缓存堆栈中弹出一个对象就可以了,如果缓存堆栈已经为空,要从三链表中的slab块中取出一批对象到对象缓存中,然后再从对象缓存中弹出一个对象。
3298获得当前cpu的对象缓存。
3300-3302行是对象缓存中不空的情况,3301行更新slab缓存堆栈的touched成员,表示slab缓存堆栈,被访问过。3302行从slab缓存堆栈弹出一个指针。
3305是当前cpu的对象缓存为空情况,调用cache_alloc_refill函数进行分配。
cache_alloc_refill函数
cache_alloc_refill是slab内存分配中比较关键的函数,在mm/slab.c中实现代码如下:
3123 static void *cache_alloc_refill(structkmem_cache *cachep, gfp_t flags)
3124 {
3125 int batchcount;
3126 struct kmem_list3 *l3;
3127 struct array_cache *ac;
3128 int node;
3129
3130 retry:
3131 check_irq_off();
3132 node = numa_mem_id();
3133 ac = cpu_cache_get(cachep);
3134 batchcount = ac->batchcount;
3135 if (!ac->touched && batchcount > BATCHREFILL_LIMIT) {
3136 /*
3137 * If there was little recentactivity on this cache, then
3138 * perform only a partial refill. Otherwise we could generate
3139 * refill bouncing.
3140 */
3141 batchcount =BATCHREFILL_LIMIT;
3142 }
3143 l3 = cachep->nodelists[node];
3144
3145 BUG_ON(ac->avail > 0 || !l3);
3146 spin_lock(&l3->list_lock);
3147
3148 /* See if we can refill from the shared array */
3149 if (l3->shared && transfer_objects(ac, l3->shared,batchcount)) {
3150 l3->shared->touched = 1;
3151 goto alloc_done;
3152 }
3153
3154 while (batchcount > 0) {
3155 struct list_head *entry;
3156 struct slab *slabp;
3157 /* Get slab alloc is to comefrom. */
3158 entry =l3->slabs_partial.next;
3159 if (entry ==&l3->slabs_partial) {
3160 l3->free_touched =1;
3161 entry =l3->slabs_free.next;
3162 if (entry ==&l3->slabs_free)
3163 gotomust_grow;
3164 }
3165
3166 slabp = list_entry(entry,struct slab, list);
3167 check_slabp(cachep, slabp);
3168 check_spinlock_acquired(cachep);
3169
3170 /*
3171 * The slab was either onpartial or free list so
3172 * there must be at least oneobject available for
3173 * allocation.
3174 */
3175 BUG_ON(slabp->inuse >=cachep->num);
3176
3177 while (slabp->inuse <cachep->num && batchcount--) {
3178 STATS_INC_ALLOCED(cachep);
3179 STATS_INC_ACTIVE(cachep);
3180 STATS_SET_HIGH(cachep);
3181
3182 ac->entry[ac->avail++] = slab_get_obj(cachep, slabp,
3183 node);
3184 }
3185 check_slabp(cachep, slabp);
3186
3187 /* move slabp to correct slabplist: */
3188 list_del(&slabp->list);
3189 if (slabp->free ==BUFCTL_END)
3190 list_add(&slabp->list, &l3->slabs_full);
3191 else
3192 list_add(&slabp->list, &l3->slabs_partial);
3193 }
3194
3195 must_grow:
3196 l3->free_objects -= ac->avail;
3197 alloc_done:
3198 spin_unlock(&l3->list_lock);
3199
3200 if (unlikely(!ac->avail)) {
3201 int x;
3202 x = cache_grow(cachep, flags |GFP_THISNODE, node, NULL);
3203
3204 /* cache_grow can reenableinterrupts, then ac could change. */
3205 ac = cpu_cache_get(cachep);
3206 if (!x && ac->avail== 0) /* no objects in sight? abort*/
3207 return NULL;
3208
3209 if (!ac->avail)/* objects refilled by interrupt? */
3210 goto retry;
3211 }
3212 ac->touched = 1;
3213 return ac->entry[--ac->avail];
3214 }
当前cpu上的缓存堆栈为空的时候,会调用cache_alloc_refill从三链表中的slab块分配一批对象到缓存堆栈。cache_alloc_refill的关键步骤是找到当前cpu所在节点的三链表,从三链表的slabs_partial或slabs_free队列中的slab块中分配内存到slab缓存堆栈。如果三链表中也没有空闲对象的时候,需要从伙伴系统分配内存。
3132获得本地节点号
3133行获得本cpu的缓存堆栈
3134-3142计算一次填充的内存块数量
3143行获得节点三链表
3149-3152行,在三链表中包含一个共享缓存堆栈,如果三链表的共享标志位置位,则从三链表中的共享堆栈中移动一些空闲内存指针到需要填充的缓存堆栈(ac)
3154进入一个循环,直到填充到缓存堆栈的指针数量足够
3158-3164行从三链表中获取一个slab块,从代码中知道,会先获取三链表slabs_partial队列中的块,这样做的好处是可以尽可能的保持完全空闲的块,有利于内存回收。slabs_partial队列中的块是不完全空闲块,就是说块中有些部分已经分配出去有些还是空闲。如果slabs_partial队列为空,则会获取slabs_free队列中的slab块,如果slabs_free都为空,则跳到标号must_grow处执行,从伙伴系统分配内存。
3177-3185行,3182-3183行调用slab_get_obj从一个slab块中分配一块内存并把返回地址压入堆栈,slab_get_obj函数中后面分析。3185行调用check_slabp函数对slab块做些检查。
3188-3192行把slab块从三链表中原来的队列中移除,并依据slab块是否包含空闲空间加入slabs_full队列或slabs_partial队列。从代码中看到,slabp->free == BUFCTL_END时表示slab块已经不包含可以分配出去的空闲空间。
3200-3211行,3202调用cache_grow从伙伴系统分配空间,这时候是因为缓存堆栈和三链表都不包含空闲对象。因为刚才调用cache_grow函数,slab缓存的三链表指针可以已经改变,3205行重新获取一次。3206-3207如果没有从伙伴系统申请到内存并且三链表中没有可用的内存,则返回空指针。3209如果三链表中已经有可用的内存,则重试。
3212-3213设置缓存堆栈的访问标记,并且从堆栈中弹出指针并返回。
cache_grow函数
当slab缓存的缓存内存数据不足时,也就是说缓存堆栈和三链表都不包含空闲对象时,会调用cache_grow函数,在cache_grow函数中向伙伴系统申请分配slab块,添加到三链表的空闲链表,cache_grow函数在mm/slab.c中实现代码如下:
2931static int cache_grow(struct kmem_cache *cachep,
2932 gfp_t flags, int nodeid, void*objp)
2933 {
2934 struct slab *slabp;
2935 size_t offset;
2936 gfp_t local_flags;
2937 struct kmem_list3 *l3;
2938
2939 /*
2940 * Be lazy and only check for validflags here, keeping it out of the
2941 * critical path inkmem_cache_alloc().
2942 */
2943 BUG_ON(flags & GFP_SLAB_BUG_MASK);
2944 local_flags = flags &(GFP_CONSTRAINT_MASK|GFP_RECLAIM_MASK);
2945
2946 /* Take the l3 list lock to change the colour_next on this node */
2947 check_irq_off();
2948 l3 = cachep->nodelists[nodeid];
2949 spin_lock(&l3->list_lock);
2950
2951 /* Get colour for the slab, and cal the next value. */
2952 offset = l3->colour_next;
2953 l3->colour_next++;
2954 if (l3->colour_next >= cachep->colour)
2955 l3->colour_next = 0;
2956 spin_unlock(&l3->list_lock);
2957
2958 offset *= cachep->colour_off;
2959
2960 if (local_flags & __GFP_WAIT)
2961 local_irq_enable();
2962
2963 /*
2964 * The test for missing atomic flag isperformed here, rather than
2965 * the more obvious place, simply toreduce the critical path length
2966 * in kmem_cache_alloc(). If a calleris seriously mis-behaving they
2967 * will eventually be caught here(where it matters).
2968 */
2969 kmem_flagcheck(cachep, flags);
2970
2971 /*
2972 * Get mem for the objs. Attempt to allocate a physical page from
2973 * 'nodeid'.
2974 */
2975 if (!objp)
2976 objp = kmem_getpages(cachep,local_flags, nodeid);
2977 if (!objp)
2978 goto failed;
2979
2980 /* Get slab management. */
2981 slabp = alloc_slabmgmt(cachep, objp, offset,
2982 local_flags &~GFP_CONSTRAINT_MASK, nodeid);
2983 if (!slabp)
2984 goto opps1;
2985
2986 slab_map_pages(cachep, slabp, objp);
2987
2988 cache_init_objs(cachep, slabp);
2989
2990 if (local_flags & __GFP_WAIT)
2991 local_irq_disable();
2992 check_irq_off();
2993 spin_lock(&l3->list_lock);
2994
2995 /* Make slab active. */
2996 list_add_tail(&slabp->list, &(l3->slabs_free));
2997 STATS_INC_GROWN(cachep);
2998 l3->free_objects += cachep->num;
2999 spin_unlock(&l3->list_lock);
3000 return 1;
3001 opps1:
3002 kmem_freepages(cachep, objp);
3003 failed:
3004 if (local_flags & __GFP_WAIT)
3005 local_irq_disable();
3006 return 0;
3007 }
cache_grow函数实现从伙伴系统分配内存,有三个比较重要的步骤,计算颜色偏移量,从伙伴系统分配内存块,构建slab块的控制数据。
2952-2958行的代码是对颜色的计算。
2975-2976行,如果传进的地址为空,则分配一块内存。这里节点号是由上层函数传进来的,在cache_alloc_refill->cache_grow的调用流程中,节点号的由numa_mem_id()函数返回,也就是正运行这段代码的cpu所在的节点的号码。
2981行是计算slab块的控制数据存放地址,控制数据存放地址有两种情况,一种是直接放在刚才申请到的内存块中,另外一种方法是另外申请一块内存来存放控制数据。
2981行调用函数slab_map_pages设置struct page的slab数据,这样可以根据逻辑地址管理找到管理这个逻辑地址的struct kmem_cache地址和struct slab地址(kfree函数中需要根据地址找到structkmem_cache地址),一页数据属于slab的时候,lru链表是没有作用的,正好把lru链表的next用来保存struct kmem_cache的地址,prev用来保struct slab地址。
2988行调用cache_init_objs初始化salb块的空闲编号列表。
2996行把slab块加入三链表的空闲链表。
2998行更新空闲对象计数。
kmem_getpages函数
kmem_getpages在mm/slab.c中实现代码如下:
1787static void *kmem_getpages(struct kmem_cache *cachep, gfp_t flags, int nodeid)
1788 {
1789 struct page *page;
1790 int nr_pages;
1791 int i;
1792
1793 #ifndef CONFIG_MMU
1794 /*
1795 * Nommu uses slab's for processanonymous memory allocations, and thus
1796 * requires __GFP_COMP to properlyrefcount higher order allocations
1797 */
1798 flags |= __GFP_COMP;
1799 #endif
1800
1801 flags |= cachep->gfpflags;
1802 if (cachep->flags & SLAB_RECLAIM_ACCOUNT)
1803 flags |= __GFP_RECLAIMABLE;
1804
1805 page = alloc_pages_exact_node(nodeid, flags | __GFP_NOTRACK,cachep->gfporder);
1806 if (!page) {
1807 if (!(flags &__GFP_NOWARN) && printk_ratelimit())
1808 slab_out_of_memory(cachep, flags, nodeid);
1809 return NULL;
1810 }
1811
1812 nr_pages = (1 << cachep->gfporder);
1813 if (cachep->flags & SLAB_RECLAIM_ACCOUNT)
1814 add_zone_page_state(page_zone(page),
1815 NR_SLAB_RECLAIMABLE,nr_pages);
1816 else
1817 add_zone_page_state(page_zone(page),
1818 NR_SLAB_UNRECLAIMABLE,nr_pages);
1819 for (i = 0; i < nr_pages; i++)
1820 __SetPageSlab(page + i);
1821
1822 if (kmemcheck_enabled && !(cachep->flags & SLAB_NOTRACK)){
1823 kmemcheck_alloc_shadow(page,cachep->gfporder, flags, nodeid);
1824
1825 if (cachep->ctor)
1826 kmemcheck_mark_uninitialized_pages(page,nr_pages);
1827 else
1828 kmemcheck_mark_unallocated_pages(page, nr_pages);
1829 }
1830
1831 return page_address(page);
1832 }
Slab向伙伴系统申请内存工作真正是在kmem_getpages函数中完成的。
1805行调用alloc_pages_exact_node函数向伙伴系统申请内存。alloc_pages_exact_node是伙伴系统在一个特定节点中分配内存的函数。
1831行根据page结构转换为逻辑地址返回。
alloc_slabmgmt函数
slab块,也就是slab从伙伴申请分配到的块,由结构structslab描述,一slab块个struct slab存放在哪里呢?structslab的存放地址是由alloc_slabmgmt函数求得的,alloc_slabmgmt在mm/slab.c中实现代码如下:
2772 static struct slab*alloc_slabmgmt(struct kmem_cache *cachep, void *objp,
2773 intcolour_off, gfp_t local_flags,
2774 int nodeid)
2775 {
2776 struct slab *slabp;
2777
2778 if (OFF_SLAB(cachep)) {
2779 /* Slab management obj isoff-slab. */
2780 slabp =kmem_cache_alloc_node(cachep->slabp_cache,
2781 local_flags, nodeid);
2782 /*
2783 * If the first object in theslab is leaked (it's allocated
2784 * but no one has a referenceto it), we want to make sure
2785 * kmemleak does not treat the->s_mem pointer as a reference
2786 * to the object. Otherwise wewill not report the leak.
2787 */
2788 kmemleak_scan_area(&slabp->list, sizeof(struct list_head),
2789 local_flags);
2790 if (!slabp)
2791 return NULL;
2792 } else {
2793 slabp = objp + colour_off;
2794 colour_off +=cachep->slab_size;
2795 }
2796 slabp->inuse = 0;
2797 slabp->colouroff = colour_off;
2798 slabp->s_mem = objp + colour_off;
2799 slabp->nodeid = nodeid;
2800 slabp->free = 0;
2801 return slabp;
2802 }
OFF_SLAB宏定义如下:
361 #defineOFF_SLAB (x) ((x)->flags &CFLGS_OFF_SLAB)
这样根据OFF_SLAB宏的返回值,如果structkmem_cache的成员flags的标志位是否设置CFLGS_OFF_SLAB来区分,struct slab是存放在从伙伴系统已经分配的块中还是另外申请空间用来存放struct slab结构。
2780行是申请单独的空间用来存放struct slab的情况,空间分配是调用kmem_cache_alloc_node函数实现的,在struct kmem_cache结构成员slabp_cache执行的slab缓存中分配。
2793-2794行struct slab是存放在slab块偏移colour_off处,为什么不存放在slab的开头?因为struct slab也可能被缓存,存放在偏移colour_off处比存放在slab块开头处发生缓存冲突的可能性小些。
2978行,综合2974行,slabp->s_mem的值是objp+cachep->slab_size。cachep->slab_size是缓存的slab的控制数据的总的长度,包含struct slab和空闲编号链表和对其因素。cachep->slab_size的值在kmem_cache_create中计算。
转载请注明来源,欢迎对文章中的引用来源进行考证,欢迎指出任何有错误或不够清晰的表达。可以在下面评论区评论,也可以邮件至 ancjf@163.com
文章标题:linux 源代码分析8:Slab内存分配
本文作者:ancjf
发布时间:2013-06-04, 19:47:29
最后更新:2020-04-18, 15:57:53
原始链接:http://ancjf.com/2013/06/04/linux-3-4-10-%E5%86%85%E6%A0%B8%E5%86%85%E5%AD%98%E7%AE%A1%E7%90%86%E6%BA%90%E4%BB%A3%E7%A0%81%E5%88%86%E6%9E%908-Slab%E5%86%85%E5%AD%98%E5%88%86%E9%85%8D/版权声明: "署名-非商用-相同方式共享 4.0" 转载请保留原文链接及作者。