kmem_cache_init初始化slab分配器 - linux内存管理(八)

看了下kmem_cache_init,涉及到不同MIGRATE间的buddy system的迁移,kmem_cache的构建,slab分配器头的构建、buddy system的伙伴拆分。

对于SMP系统,每个kmem_cache还有各个CPU的arraycache_init,这样每个CPU可以从各自的arraycache_init中获取缓存,如果不足,则从slab分配器中获得;当让slab分配器的三条链表也有一定的缓存作用,如果三条链表都已空了,则需要从buddy system中申请页。在申请页的时候,由于每个zone中都有各个CPU的缓存页per_cpu_pages链表,因此在申请页时既可从per_cpu_pages中申请,也可直接从buddy system中申请。当从buddy system中申请时,先从相同的MIGRATE链表的不同order的链表中迁移页,如果相同的MIGRATE链表不满足,则将根据fallbacks数组中指示的各个MIGRATE的后备MIGRATE类型链表来迁移页,由于可能从大的order中迁移出页链表,就需要考虑buddy的拆分……

这个函数跟踪了好几天,上述总结及下面的记录可能有误,望指正。

声明:下面的记录以初始化过程为重点,当内核正常执行时,某些函数内的执行路径可能有一定的偏差.

start_kernel()
    |---->page_address_init()
    | 
    |---->setup_arch(&command_line);
    |
    |---->setup_per_cpu_areas();
    |
    |---->build_all_zonelist()
    |
    |---->page_alloc_init()
    |
    |---->pidhash_init()
    |
    |---->vfs_caches_init_early()
    |
    |---->mm_init()
void mm_init(void)
  |-->mem_init()
  |     业务:bootmem迁移至伙伴系统
  |
  |-->kmem_cache_init()
  |     以slab分配器为参考
  |   (1)构建好了kmem_cache实例cache_cache(静态分配),且构建好了kmem_cache的slab
  |   分配器,并由initkmem_list3[0]组织, 相应的array为initarray_cache;
  |   (2)构建好了kmem_cache实例(管理arraycache_init),且构建好了
  |   arraycache_init的slab分配器,并由initkmem_list3[1]组织,相应的array为
  |   initarray_generic;
  |   (3)构建好了kmem_cache实例(管理kmem_list3),此时还未构建好kmem_list3的slab
  |   分配器,但是一旦申请sizeof(kmem_list3)空间,将构建kmem_list3分配器,并由
  |   initkmem_list[2]组织,其array将通过kmalloc进行申请;
  |   (4)为malloc_sizes的相应数组元素构建kmem_cache实例,并分配kmem_list3,用于组织
  |   slab链表,分配arraycache_init用于组织每CPU的同一个kmem_cache下的slab分配;
  |   (5)替换kmem_cache、malloc_sizes[INDEX_AC].cs_cachep下的arraycache_init
  |   实例;
  |   (6)替换kmem_cache、malloc_sizes[INDEX_AC].cs_cachep、
  |   malloc_sizes[INDEX_L3].cs_cachep下的kmem_list3实例;
  |   (7)g_cpucachep_up = EARLY;
  |
  |-->

void kmem_cache_init(void)
  |-->use_alien_caches = 0;
  |   UMA体系
  |
  |-->for(i = 0; i < 3; i++)
  |--{
  |    kmem_list3_init(&initkmem_list3[i]);
  |    初始化initkmem_list3三链.
  |    if (i < 1)  cache_cache.nodelists[i] = NULL;
  |--}
  |
  |-->set_up_list3s(&cache_cache, CACHE_CACHE);
  |   对于UMA:
  |   即,cache_cache.nodelists[0] = &initkmem_list3[0];
  |      cache_cache.nodelists[0]->next_reap = jiffies
  |                  + REAPTIMEOUT_LIST3 
  |                  + ((unsigned long)cachep) % REAPTIMEOUT_LIST3;
  |
  |--内存 > 32M ==> slab_break_gfp_order = BREAK_GFP_ORDER_HI;
  |
  |-->INIT_LIST_HEAD(&cache_chain);
  |
  |-->list_add(&cache_cache.next, &cache_chain);
  |   将cache_cache挂入cache_chain链表
  |
  |-->cache_cache.colour_off = L1_CACHE_BYTES;
  |   关于cache line 和 cache block可能大多数人不会做区分,
  |   可以查阅CSAPP 2ed p_631.
  |   
  |-->cache_cache.array[smp_processor_id()] = &initarray_cache.cache;
  |
  |-->cache_cache.nodelists[0] = &initkmem_list3[0];
  |
  |-->cache_cache.buffer_size = offset(struct kmem_cache, nodelists)
  |                        + nr_node_ids * sizeof(struct kmem_list3 *);
  |   cache_cache.buffer_size存储一个kmem_cache实体需要的空间大小.
  |
  |-->cache_cache.buffer_size = ALIGN(cache_cache.buffer_size,
  |                              cache_line_size());
  |   提高缓存利用率,将buff_size对于到cache_line_size.
  |
  |-->cache_cache.reciprocal_buffer_size = 
  |               reciprocal_value(cache_cache.buffer_size);
  |   获取cache_cache.buffer_size的倒数值,原理:
  |   ( 1/X = (2 **32 / X ) >> 32 )
  |
  |-->for (order = 0; order < MAX_ORDER; order++)
  |--{
  |      cache_estimate(order, cache_cache.buffer_size,
  |       cache_line_size(); 0, &left_over, &cache_cache.num);
  |
  |      if(cache_cache.num) break;
  |--}
  |   因为cache_cache所占空间在2 ** 0 个页中已可存下,因此,
  |   &cache_cache.num存放在一个页中除去slab结构体可以存放下
  |   占空间(bufer_size + sizeof(kmem_bufctl_t))的实例的数量,
  |   left_over存放在一页中剩余的空间.
  |
  |--cache_cache.gfporder = order;
  |--cache_cache.colour = left_over / cache_cache.colour_off;
  |--cache_cache.slab_size = ALIGN(cache_cache.num 
  |                        * sizeof(kmem_bufctl_t) 
  |                        + sizeof(struct slab), cache_line_size());
  |
  |
  |-->struct cache_sizes *sizes = malloc_sizes; 
  |-->struct cache_names *names = caches_names;
  |   通过kmalloc分配的空间有绝大部分都会通过slab进行分配,因此先构造
  |   kmalloc分配空间的缓存.
  |
  |
  |  INDEX_AC就是与sizeof(struct arraycache_init :28==>0)适配的kmalloc可分配
  |  缓存的索引.
  |  INDEX_L3就是与sizeof(struct kmem_list3 :56==>1)适配的kmalloc可分配缓存的
  |  索引.
  |  为arraycache_init构造kmem_cache实例.
  |-->sizes[INDEX_AC].cs_cachep = 
  |      kmem_cache_create(names[INDEX_AC].name, sizes[INDEX_AC].cs_size,
  |                        ARCH_KMALLOC_MINALIGN,
  |                        ARCH_KMALLOC_FLAGS | SLAB_PANIC,
  |                        NULL);
  |     第一次调用kmem_cache_create,填充了initkmem_list3[0],该类链表上挂载了
  |     kmem_cache类型的slab分配器.
  |     第一次调用setup_cpu_cache,initkmem_list3[1]将被分配给与arraycache_init
  |     匹配的kmem_cache,但是由于arraycache_init的slab分配器还未构建好,因此,
  |     在第一次申请sizeof(arraycache_init)空间时,会把arraycache_init的slab
  |     分配器挂入initkmem_list3[1]类的链表下.
  |
  |-->if(INDEX_AC != INDEX_L3)
  |--{
  |   为kmem_list3构造kmem_cache实例
  |
  |   sizes[INDEX_L3].cs_cachep =
  |      kmem_cache_create(names[INDEX_L3].name, sizes[INDEX_L3].cs_size,
  |                        ARCH_KMALLOC_MINALIGN,
  |                        ARCH_KMALLOC_FLAGS|SLAB_PAINIC,
  |                        NULL);
  |     第二次调用kmem_cache_create,填充了initkmem_list3[1],该类链表上挂载了
  |     arraycache_init类型的slab分配器.
  |
  |     这已是第二次调用kmem_cache_create.
  |     在第二次调用时,arraycache_init的kmem_cache已初始化,但是arraycache_init
  |     的slab分配器还未构建好(相当于都为空),而setup_cpu_cache中将开始通过
  |     kmalloc申请sizeof(arraycache_init)空间,此时将同于kmem_cache分配器初始化
  |     过程一样,填充arraycache_init分配器.
  |     主要区被在于kmem_cache_create最后调用的 setup_cpu_cache,
  |     setup_cpu_cache中将设置g_cpucache_up,以标志初始化的不同阶段.
  |--}
  |
  |--slab_early_init = 0;
  |   (1)构建好了kmem_cache实例cache_cache,且构建好了kmem_cache的slab分配器,
  |   并由initkmem_list3[0]组织, 相应的array为initarray_cache.
  |   (2)构建好了kmem_cache实例(管理arraycache_init),且构建好了
  |   arraycache_init的slab分配器,并由initkmem_list3[1]组织,相应的array为
  |   initarray_generic.
  |   (3)构建好了kmem_cache实例(管理kmem_list3),此时还未构建好kmem_list3的slab
  |   分配器,但是一旦申请sizeof(kmem_list3)空间,将构建kmem_list3分配器,并由
  |   initkmem_list[2]组织,其array将通过kmalloc进行申请.
  |
  |
  |  开始为malloc_sizes中的其它空间大小够将kmem_cache实例.如下将是
  |  第[3..00)次调用seup_cpu_cache,因为arraycache_init和kmem_list3
  |  的kmem_cache已构造完成,因此将会通过kmalloc进行申请,而不会再使用静
  |  态的initarray_cache、initarray_generic、initkmem_list3等数据.
  |--while(sizes->cs_size != ULONG_MAX)
  |--{
  |    if(!sizes->cs_cachep)
  |    {
  |       sizes->cs_cachep = kmem_cache_create(names->name,
  |            sizes->cs_size,
  |            ARCH_KMALLOC_MINALIGN,
  |            ARCH_KMALLOC_FLAGS|SLAB_PANIC,
  |            NULL);
  |    }
  |    sizes++;
  |    names++;
  |--}
  |
  |
  |  我们知道initarray_cache和initarray_generic最终都会被释放掉,
  |  而相应于arraycache_init的slab分配器已可分配空间,因此,将拷贝
  |  initarray_cache.cache和initarray_generic.cache.
  |--struct array_cache *ptr;
  |  ptr = kmalloc(sizeof(struct arraycache_init), GFP_NOWAIT);
  |  memcpy(ptr, cpu_cache_get(&cache_cache),
  |         sizeof(struct arraycache_init));
  |  此处需要留意下,array_cache空间为20,arraycache_init空间为24,
  |  虽然不会造成错误,但是感觉不好.
  |  cache_cache.array[smp_processor_id()] = ptr;
  |
  |  ptr = kmalloc(sizeof(struct arraycache_init), GFP_NOWAIT);
  |  memcpy(ptr, cpu_cache_get(malloc_sizes[INDEX_AC].cs_cachep),
  |         sizeof(struct arraycache_init));
  |  malloc_sizes[INDEX_AC].cs_cachep->array[smp_processor_id()] = ptr;
  |
  |
  |  同initarray_ache和initarray_generic, initkmem_list3[3]最终都会被释放掉,
  |  此时相应于kmem_list3的slab分配器已可分配,因此,将拷贝initkmem_list3[0..2].
  |--init_list(&cache_cache, &initkmem_list3[0], 0);
  |  init_list(malloc_sizes[INDEX_AC].cs_cachep, 
  |            &initkmem_list3[SIZE_AC], 0);
  |  init_list(malloc_sizes[INDEX_L3].cs_cachep,
  |            &initkmem_list3[SIZE_L3], 0);
  |
  |--g_cpucache_up = EARLY.

void kmem_list3_init(struct kmem_list3 *parent)
  |-->INIT_LIST_HEAD(&parent->slabs_full);
  |   INIT_LIST_HEAD(&parent->slabs_partial);
  |   INIT_LIST_HEAD(&parent->slbas_free);
  |
  |-->parent->shared = NULL;
  |-->parent->alien = NULL;
  |-->parent->colour_next = 0;
  |-->spin_lock_init(&parent->list_lock);
  |-->parent->free_objects = 0;
  |-->parent->free_touched = 0;

void setup_list3s(struct kmem_cache *cachep, int index)
  |-->int node;
  |
  |-->for_each_online_node(node)
  |--{
  |     对于UMA,只有一个node;
  |   
  |     cachep->nodelists[node] = &initkmem_list3[index + node];
  |     cachep->nodelists[node]->next_reap = jiffies
  |                 + REAPTIMEOUT_LIST3
  |                 + ((unsigned long)cachep) % REAPTIMEOUT_LIST3;
  |--}
  |  对于如上的for循环,若是NUMA体系,是在干嘛呢?

我们先关注下内核初始化时的情形
struct kmem_cache *kmem_cache_create (const char *name,
    size_t size, size_t align, unsigned long flags, void (*ctor)(void*))
  |-->struct kmem_cache *cachep = NULL;
  |   gfp = GFP_NOWAIT;
  |   cachep = kmem_cache_zalloc(&cache_cache, GFP_NOWAIT);
  |            |-->kmem_cache_alloc(&cache_cache, GFP_NOWAIT 
  |            |                              | __GFP_ZERO);
  |                |-->void *ret = NULL;
  |                |   ret = __cache_alloc(&cache_cache, GFP_NOWAIT 
  |                |                              | __GFP_ZERO,
  |                |         __builtin_return_address(0));
  |                |   return ret;
  |
  |   业务:从cache_cache中分配出一个kmem_cache实例.
  |        我们需要注意,系统初始化时,cache_cache自身还未分配好
  |        (前边已设置了cache_cache的参数).初始化的过程很重要.!!!!!!
  |
  |-->size = ALIGN(size, align);
  |
  |-->left_over = calculate_slab_order(cachep, size, align, flags);
  |               构建kmem_cache;
  |
  |-->slab_size = ALIGN(cachep->num * sizeof(kmem_bufctl_t) 
  |                     + sizeof(struct slab), align);
  |
  |-->cachep->colour_off = cache_line_size();
  |   cachep->colour = leftover / cachep->colour_off;
  |   cachep->slab_size = slab_size;
  |   cachep->flags = flags;
  |   cachep->gfpflags = 0;
  |   cachep->buffer_size = size;
  |   cachep->reciprocal_buffer_size = reciprocal_value(size);
  |   cachep->ctor = ctor;
  |   cachep->name = name;
  |
  |-->if(setup_cpu_cache(cachep, gfp))
  |--{ xxxxx }
  |
  |--list_add(&cachep->next, &cache_chain);
  |  将新建的kmem_cache实例挂入cache_chain链表中.
  |--return cachep;

void *__cache_alloc(struct kmem_cache *cachep, gfp_t flags, void *caller)
  |-->flags &= gfp_allowed_mask;
  |   初始化时,gfp_allowed_mask = GFP_BOOT_MASK;
  |
  |-->void *objp = NULL; 
  |   objp = __do_cache_alloc(cachep, flags); 
  |          |--> ____cache_alloc(cachep, flags); 
  |   获取cachep->buffer_size大小空间的起始地址. 
  |
  |-->if((flags & __GFP_ZERO) && objp)
  |      memset(objp, 0, obj_size(cachep));
  |
  |--return objp;
只看初始化
void *____cache_alloc(struct kmem_cache *cachep, gfp_t flags)
  |-->void *objp = NULL;
  |   struct array_cache *ac = cpu_cache_get(cachep);
  |
  |--if(ac->avail)
  |--{
  |    ac->touched = 1;
  |    objp = ac->entry[--ac->avail];
  |
  |    array_cache->entry存放的是每CPU的某空间大小的缓存.
  |    array_cache->avail指示有多少个缓存实例.
  |    系统初始化,与某kmem_cache相应的array_cache还未建立,
  |    因此将执行else分支.
  |--}
  |--else
  |--{
  |     objp = cache_alloc_refill(cachep, flags);
  |     填充每CPU的array_cache,并获得相应类型的kmem_cache实例空间起始地址.
  |--}
  |
  |--return objp;

关注初始化过程,与书写的程序执行顺序有偏差
void *cache_alloc_refill(struct kmem_cache *cachep, gfp_t flags)
  |
  |   因为系统初始化时,不存在kmem_cache分配器cache_cache,即
  |   kmem_list3实例的三条链表都是空的(不可再分配出空间),因此 
  |   需要扩展cache_cache.故将先执行标号must_grow处代码.
  |   此时cache_cache的缓存array_cache实例initarray_cache也为空,
  |   所以cache_cache的缓存也需要分配. 
  |   因此先看下列代码:
  |-->struct array_cache *ac = cpu_cache_get(cachep);
  |   if(!ac->avail)
  |      cache_grow(cachep, flags | GFP_THISNODE, node, NULL);
  |   cache_grow将分配新的slab,并加入slabs_free链表.
  |
  |   我们可以看出,kmem_cache的实例有部分缓存在了array_cache中,
  |   entry即为缓存起始地址,avail指示有几个缓存实例. 
  |
  |
  |
  |  分配好kmem_cache的实例cache_cache后,将会跳转到程序入口处的
  |  retry标号开始执行. 
  |-->struct array_cache *ac = cpu_cache_get(cachep);
  |   int batchcount = ac->batchcount;
  |   struct kmem_list3 *l3 = cachep->nodelist3[node];   
  |
  |--while(batchcount > 0)
  |--{
  |    struct list_head *entry; 
  |    entry是slabs_partial(半满),或者是slabs_free.
  |  
  |    struct slab *slabp = list_entry(entry, struct slab, list);
  |
  |    while(slabp->inuse < cachep->num && batchcount--)
  |    {  
  |       ac->entry[ac->avail++] = slab_get_obj(cachep, slabp, node);
  |
  |       array_cache->entry存放的是每CPU的某空间大小的缓存. 
  |    }
  |
  |    list_del(&slabp->list);
  |    if(slabp->free == BUFCTL_END)
  |       list_add(&slapb->list, &l3->slabs_full);
  |    else
  |       list_add(&slapb->list, &l3->slabs_partial);
  |    迁移链表.
  |--}
  |
  |--l3->free_objects -= ac->avail;
  |
  |--ac->touched = 1;
  |
  |--return ac->entry[--ac->avail];

int cache_grow(strut kmem_cache *cachep, gfp_t flags, int nodeid, 
               void *objp)
  |-->struct kmem_list3 *l3 = cachep->nodelists[nodeid];
  |
  |-->offset = l3->colour_next;
  |   l3->colour_next++;
  |   if (l3->colour_next >= cachep->colour)
  |      l3->colour_next = 0;
  |   offset *= cachep->colour_off;
  |   不明白offset调整的原理,为什么可以减少conflict cache呢??
  |
  |-->gfp_t local_flags = flags & (GFP_CONSTRAINT_MASK 
  |                                | GFP_RECLAIM_MASK);
  |   objp = kmem_getpages(cachep, local_flags, nodeid);
  |   根据flags 和 cachep->gfpflags 申请 2 ** cachep->gfporder个页,
  |   并获得页起始的虚拟地址.
  |
  |-->struct slab *slabp = NULL;
  |   slabp = alloc_slabmgmt(cachep, objp, offset, 
  |                 local_flags & ~GFP_CONSTRAINT_MASK, nodeid);
  |   根据cachep信息和offset构建slab描述符,并获取slab描述符的地址.
  |  (需考虑着色. 但是对于着色的原理、性能提升我并不清楚?????)
  |
  |-->slab_map_pages(cachep, slabp, objp);
  |   复用page->lru上的两个指针,使其分别指向cachep和slap
  |
  |-->cache_init_objs(cachep, slabp);
  |   初始化slab描述符后的kmem_bufctl_t数组.
  |
  |-->list_add_tail(&slabp->list, &(l3->slabs_free));
  |   将新分配的slab加入kmem_list3(initkmem_list3[0])中的slabs_free链表
  |   l3->free_objects += cachep->num;
  |
  |-->return 1;

void *kmem_getpages(struct kmem_cache *cachep, gfp_t flags, int nodeid)
  |-->struct page *page = NULL;
  |   page = alloc_pages_exact_node(nodeid, 
  |          flags | cachep->gfpflags| __GFP_NOTRACK, cachep->gfporder)
  |          |--> __alloc_pages(flags, cachep->gfporder, 
  |          |                  node_zonelist(nodeid, flags));
  |   对于UMA,node_zonelist只有一个contig_page_data.node_zonelists
  |   根据(flags | __GFP_NOTRACK), 从相应类型的MIGRATE链表中获取2**order个页,
  |   可以从per_cpu_pages->lists中获取,也可从伙伴系中获取.
  |
  |   关于__alloc_pages函数,我已不想在跟下去了,分配一个页;
  |   上次和同学讨论下bootmem将页释放给buddy system时都是存放在MIGRATE_MOVABLE链表
  |   下,其它类型的链表都是空.
  |   因为此处是第一次向buddy system提出页申请,因此很有必要看一下如何从其它链表上
  |   分配空间.
  |
  |
  |-->nr_pages = (1 << cachep->gfporder);
  |
  |--if(cachep->flags & SLAB_RECLAIM_ACCOUNT)
  |   add_zone_page_state(page_zone(page), NR_SLAB_RECLAIMABLE,
  |                       nr_pages);
  |--else
  |   add_zone_page_state(page_zone(page), NR_SLAB_UNRECLAIMABLE,
  |                        nr_pages);
  |   在zone->vmstat中统计 可回收/不可回收 的页面数.
  |
  |
  |--for(i = 0; i < nr_pages; i++)
  |     __SetPageSlab(page + i);
  |   设置page->flags: PG_Slab
  |
  |-->return page_address(page);
  |   返回struct page对应的虚拟地址.(低端内存部分存在固定偏移,
  |   高端内存需再做页表映射,在初始化阶段,一定是低端内存)
  |   
 
  
struct page *__alloc_pages(gfp_t gfp_mask, unsigned int order,
        struct zonelist *zonelist)
  |-->return
  |   __alloc_pages_nodemask(gfp_mask, order, zonelist, NULL);
  |   根据gfp_mask从相应类型的MIGRATE链表中获取2**order个页,
  |   可以从per_cpu_pages->lists中获取,也可从伙伴系中获取. 

strut page *__alloc_pages_nodemask(gfp_t gfp_mask, unsigned int order)
  |-->enum zone_type high_zoneidx = gfp_zone(gfp_mask);
  |   因为高端内存管理还未初始化,此处只会得到低端内存区标志(不考虑DMA)
  |
  |-->int migratetype = allocflags_to_migratetype(gfp_mask);
  |   获取应从哪个链表获取页分配.
  |   按照以上的执行流程,migratetype为MIGRATE_UNMOVABLE(此时,该链表仍为
  |   空,下面会做链表迁移,可以看下fallbacks数组)
  |
  |-->first_zones_zonelist(zonelist, high_zoneidx, nodemask,
  |                        &preferred_zone);
  |   业务:high_zoneidx基本同于for_each_zone_zonelist中的offset作用,
  |        获取pglist_data.node_zonelist3[0..3]小于等于high_zoneidx
  |        的最大zone_idx所对应的zone,并存于preferred_zone中.
  |
  |-->page = get_page_from_freelist(gfp_mask | __GFP_HARDWALL, nodemask,
  |          order,zonelist, high_zoneidx, ALLOC_WMARK_LOW|ALLOC_CPUSET,
  |          preferred_zone, migratetype);
  |   获取2**order个页     
  |
  |-->return page;

 
  

struct page *get_page_from_freelist(gfp_t gfp_mask, nodemaks_t *nodemask,
        unsigned int order, struct zonelist *zonelist, int high_zoneidx,
        int alloc_flags, struct zone *preferred_zone, int migratetype)
  |--对该函数的分析,我们简化下:
  |  1、初始化阶段,高端内存管理区还未初始化
  |  2、watermark还未初始化
  |
  |  因此:
  |-->struct page *page = NULL;
  |   page = buffered_rmqueue(preferred_zone, zone, order, gfp_mask,
  |                           migratetype);
  |   order == 0 ==>从per_cpu_pages->lists数组相应类型的链表中获取1页;
  |   order != 0 ==> 从buddy system中直接获取页.
  |
  |-->return page;
 
  

struct buffered_rmqueue(struct zone *preferred_zone, struct zone *zone,
        int order, gfp_t gfp_flags, int migratetype)
  |--struct page *page = NULL;
  |  int cold = !!(gfp_flags & __GFP_COLD);
  |
  |--if(order == 0)
  |--{
  |    struct per_cpu_pages *pcp = NULL;
  |    pcp = &this_cpu_ptr(zone->pageset)->pcp;
  |    struct list_head = NULL;
  |    list_head = &pcp->lists[migratetype];
  |
  |
  |    if(list_empty(list)) //开始时,只有MIGRATE_MOVABLE不为空
  |    { pcp->count += rmqueue_bulk(zone, 0, pcp->batch, list,
  |                                 migratetype, cold);}
  |    即,获取pcp->batch个页,并将页挂入pcp->lists[migratetype]中,
  |    同时修正pcp->count;
  |
  |
  |    if(cold) page = list_entry(list->prev, struct page, lru);
  |    else page = list_entry(list->next, struct page, lru);
  |    关于“冷/热页”的问题,我不太理解!!!!
  |    从per_cpu_pages->lists的相应类型链表上,取出一个页.
  |
  |    list_del(&page->lru);
  |    将获得的页又从per_cpu_pages上取下,从此处我们也可看出,对于单个的页,
  |    将先从per_cpu_pages->lists[MIGRATE_PCPTYPES]上获取,如果对应类型的
  |    pcp链表上没有,则会直接从伙伴系统上区下页链表,并挂入相应类型的
  |    per_cpu_pages->lists链表上.
  |
  |    pcp->count--;
  |--}
  |
  |--else
  |--{
  |   与order = 0的主要区别在于,order = 0可以通过per_cpu_pages进行分配,
  |   而order != 0则没有相应的缓存,只能调用__rmqueue函数进行分配.
  |--}
  |
  |-->if(prep_new_page(page, order, gfp_flags)) goto again;
  |    prep_new_page函数中将对struct page,物理页做适当的修改.
  |
  |-->return page;
 
  
int rmqueue_bulk(struct zone *zone, unsigned int order, 
                 unsigned long count, struct list_head *list, 
                 int migratetype, int cold)
  |--int i = 0;
  |
  |--for(i = 0; i < count; ++i)
  |--{
  |    struct page *page = __rmqueue(zone, order, migratetype);
  |
  |    if(cold == 0) list_add(&page->lru, list);
  |    else list_add_tail(&page->lru, list);
  |    将申请到的页放到list链表中.
  |
  |    set_page_private(page, migratetype);
  |    list = &page->lru;
  |--}
  |
  |-->__mod_zone_page_state(zone, NR_FREE_PAGES, -(i << order));
  |   因为从某个链表上的项已经用完,故需从其它链表上迁移处新的项,
  |   完成迁移后,需要跟新zone->vm_state的相关统计量,此处做跟新.
  |
  |-->return i; //返回申请到的页数
 
  
/* 1、从不同order上具有相同类型的链表上申请页
 * 2、从其它类型的链表上迁移页到制定的链表上,并申请页
 */
struct page *__rmqueue(struct zone *zone, unsigned int order,
        int migratetype)
  |-->struct page *page = NULL;
  |
  |-->page = __rmqueue_smallest(zone, order, migratetype)
  |   先尝试从其它order上具有相同迁移类型的链表上获取页,但是由于这是初
  |   始化首次调用,因此其它order上的相同迁移类型的链表也为空.
  |
  |   那么此时只能从不同迁移类型的链表上申请页.
  |-->if(unlikely(!page) && migratetype != MIGRATE_RESERVE)
  |--{
  |    page = __rmqueue_fallback(zone, order, migratetype);
  |    if(!page)
  |    { migratetype = MIGRATE_RESERVE; goto retry_rserve;}
  |--}
  |
  |--return page;
 
  

从其它类型的链表上迁移页
/*
int fallbacks[MIGRATE_TYPES][MIGRATE_TYPES-1] = {
    [MIGRATE_UNMOVABLE] = { MIGRATE_RECLAIMABLE, MIGRATE_MOVABLE,   MIGRATE_RESERVE },
    [MIGRATE_RECLAIMABLE] = { MIGRATE_UNMOVABLE, MIGRATE_MOVABLE,   MIGRATE_RESERVE },
    [MIGRATE_MOVABLE] = { MIGRATE_RECLAIMABLE, MIGRATE_UNMOVABLE, MIGRATE_RESERVE },
    [MIGRATE_RESERVE] = { MIGRATE_RESERVE, MIGRATE_RESERVE,   MIGRATE_RESERVE },
  };
 */
struct page *__rmqueue_fallback(struct zone *zone, int order,
        int start_migratetype)
  |--做了一定的简化,先做下说明:
  |  当从其它链表迁移时,按照order从大到小的顺序选择被迁移的链表;
  |  如果一次性迁移的链表上的页数目太多(不小于一个页块的页数的一半)时,
  |  将会把迁移出的链表放在相应类型的其它order链表上,并且将页块属性修
  |  该成相应的MIGRATE类型;
  |  
  |  而后又将取出的链从相应的MIGRATE类型链表上取下,并按照buddy system要求,
  |  将链表上的页按照order大小依次放到小于所取下order的同类型的链表上.
  |
  |  比如:在系统初始化过程中,所有order的MIGRATE_UNMOVABLE类型链表都为空,
  |       那么将从order = 10的MIGRATE_MOVABLE类型链表上取下一个buddy,
  |       并一次下放到order = [9..0]的MIGARTE_UNMOVABLE类型链表上,
  |       可以看出只下放了1023个页,因为传入的参数order = 0,正好请求1页,
  |       故,多出的1页将不会再在伙伴系统的链表中,而是交由slab来管理.
  |
  |--struct free_area *area = NULL;
  |  struct page *page = NULL;
  |  int current_order = 0;
  |
  |-->for(current_order = MAX_ORDER - 1; 
  |       current_order >= order; 
  |       --current_order)
  |--{
  |     for(i = 0; i < MIGRATE_TYPES - 1; i++)
  |     {
  |       更具fallbacks,从备用链表上选取一个类型的非空链表,
  |       记链表类型为migratetype.
  |      
  |       area = &(zone->free_area[current_order]);
  |       page = list_entry(area->free_list[migratetype].next,
  |                         struct page, lru); 
  |       area->nr_free--; 
  |      
  |       如上文所述,可能需要修改页块的属性,迁移链表到制定的order的类型下:
  |       move_freepages_block(zone, page, start_migratetype);
  |       set_pageblock_migratetype(page, start_migratetype);
  |
  |      list_del(&page->lru);
  |      rmv_page_order(page);
  |
  |      expand(zone, page, order, current_order, area,
  |             start_migratetype);
  |      buddy system中被取下的一个链分别放到order ~ (current_order - 1)的
  |      start_migratetype链表上.
  |
  |      return page;//该页将不会存在于伙伴系统中,开始交由上级(slab)管理.
  |     }
  |--}
int prep_new_page(struct page *page, int order, gfp_t gfp_flags)
  |-->set_page_private(page, 0);
  |   set_page_refcount(page);
  |  
  |-->kernel_map_pages(page, 1 << order, 1);
  |
  |-->if(gfp_flags & __GFP_ZERO)
  |     prep_zero_page(page, order, gfp_flags);
  |   使用临时映射,将物理页清零.
  |
  |-->if(order && (gfp_flags & __GFP_ORDER))
  |     prep_compound_page(page, order);
  |   关于compound_page,不明白什么意思????
 
  
/**根据cachep信息和offset获取slab描述符的地址. 
 * 需考虑着色. 但是对于着色的原理、性能提升我并不清楚?????) 
 */
struct slab *alloc_slabmgmt(struct kmem_cache *cachep, void *objp,
        int colour_off, gfp_t local_flags, int nodeid) 
  |-->struct slab *slabp; 
  |--if(OFF_SLAB(cachep))
  |--{ 如果slab描述符在外部,我们此处看看else的情形}
  |
  |--else 
  |--{
  |     slabp = objp + colour_off;
  |     slab结构体偏移页起始空间colour_off大小.
  |     colour_off += cachep->slab->slab_size;
  |     被管理的小内存实体的偏移量偏移页起始空间colour_off
  |--}
  |
  |--slabp->inuse = 0;
  |  slabp->colouroff = colour_off;
  |     被管理的小内存实体的偏移量偏移页起始空间colour_off
  |  slabp->s_mem = objp + colour_off;
  |     被管理的小内存实体的起始地址存于slabp->s_mem中
  |  slabp->nodeid = nodeid; 
  |  slabp->free = 0;
  |
  |--return slabp;     

复用page->lru上的两个指针,使其分别指向cachep和slap
void slab_map_pages(struct kmem_cache *cache, struct slab *slab,
        void *addr)
  |-->int nr_pages = 1;
  |   struct page *page = virt_to_page(addr);
  |
  |--if(!PageCompound(page)) nr_pages <<= cache->gfporder;
  |
  |--do
  |--{
  |     page_set_cache(page, cache);
  |     |-->page->lru.next = (struct list_head *)cache;
  |
  |     page_set_slab(page, slab);
  |     |-->page->lru.prev = (struct list_head *)slab;
  |
  |     page++;
  |--}while(--nr_pages);
void cache_init_objs(struct kmem_cache *cachep, struct slab *slabp)
  |--for(i = 0; i < cachep->num; i++)
  |--{
  |    slab_bufctl(slabp)[i] = i + 1;
  |    紧接着slab描述符的是kmem_bufctl_t 数组
  |--}
  |
  |--slab_bufctl(slabp)[i - 1] = BUFCTL_END;
 
  
kmem_bufctl_t *slab_bufctl(struct slab *slabp)
  |--return (kmem_bufctl_t *)(slabp + 1);
void *slab_get_obj(struct kmem_cache *cachep, struct slab *slabp,
        int nodeid)
  |-->void *objp = index_to_obj(cachep, slabp, slapb->free);
  |   根据slabp->free索引号,获得cachep->buffer_size空间大小的起始地址
  |
  |-->slabp->inuse++;
  |
  |-->kmem_bufctl_t next;
  |   next = slabp_bufctl(slabp)[slabp->free];
  |   从此处我们可看出slab描述符后的kmem_bufctl_t数组的作用:
  |   每个数组元数存放的是下一个未被使用的待分配空间的索引.
  |
  |-->slabp->free = next;
  |
  |-->return objp

size_t calculate_slab_order(struct kmem_cache *cachep,
                  size_t size, size_t align, unsigned long flags)
  |-->unsigned long offslab_limit;
  |   size_t left_over = 0;
  |   int gfporder = 0;
  |
  |--for(gfporder = 0; gfporder <= KMALLOC_MAX_ORDER; gfporder++)
  |--{
  |     unsigned int num;
  |     size_t remainder = 0;
  |     cache_estimate(gfporder, size, align, flags, &remainder, &num);
  |     cachep->num = num;
  |     cachep->gfporder = gfporder;
  |     leftover = remainder;
  |--}
  |
  |--return left_over;
 
  
int setup_cpu_cache(struct kmem_cache *cachep, gfp_t gfp)
  |--if(g_cpucache_up == FULL)
  |    return enable_cpucache(cachep, gfp);
  |
  |  以下与初始化的不同阶段相关联
  |--if(g_cpucache_up == NONE)
  |--{
  |     g_cpucache_up 值为 NONE,意味着是首次调用kmem_cache_create;
  |     
  |     cachep->array]smp_process_id()] = &initarray_generic.cache;
  |
  |     setup_list3s(cachep, 1)
  |     |-->cachep->nodelists[0] = &initkmem_list3[1];
  |     |   cachep->nodelists[0]->next_reap = jiffies 
  |     |       + REAPTIMEOUT_LIST3
  |     |       + ((unsigned long)cachep) % REAPTIMEOUT_LIST3;
  |
  |     g_cpucache_up = PARTIAL_AC;
  |--}
  |--else
  |--{
  |     第二次调用kmem_cache_create;
  |     第一次调用时只构建了array_cache的kmem_cache实例,但是
  |     并没有分配号array_cache的slab分配器,此处就开始调用kmalloc,
  |     我们跟踪一下,看是如何完成sizeof(arraycache_init)的分配的.
  |     cachep->array[smp_processor_id()] = 
  |          kmalloc(sizeof(struct arraycache_init), gfp);
  |
  |     if(g_cpucache_up == PARTIAL_AC)
  |     {
  |        set_up_list3s(cachep, 2);
  |        |-->cachep->nodelists[0] = &initkmem_list3[2];
  |        |   cachep->nodelists[0]->next_reap = jiffies 
  |        |       + REAPTIMEOUT_LIST3
  |        |       + ((unsigned long)cachep) % REAPTIMEOUT_LIST3;
  |
  |        g_cpucache_up = PARTIAL_L3;
  |     }
  |     else
  |     {
  |        cachep->nodelists[0] = kmalloc_node(sizeof(struct kmem_list3),
  |                                            gfp, 0);
  |        kmem_list3_init(cachep->nodelists[0]);
  |     }
  |--}
  |
  |--cachep->nodelists[0]->next_reap = jiffies
  |      + REAPTIMEOUT_LIST3
  |      + ((unsigned long)cachep) % REAPTIMEOUT_LIST3;
  |
  |--cpu_cache_get(cachep)->avail = 0;
  |  cpu_cache_get(cachep)->limit = 1;
  |  cpu_cache_get(cachep)->batchcount = 1;
  |  cpu_cache_get(cachep)->touched = 0;
  |  cachep->batchcount = 1;
  |  cachep->limit = 1;
  |  return 0;

void *kmalloc(size_t size, gfp_t flags)
  |-->reutrn __kmalloc(size, flags)
      |-->return __do_kmalloc(size, flags, NULL);

void *__do_malloc(size_t size, gfp_t flags, void *caller)
  |-->struct kmem_cache *cachep;
  |   void *ret;
  |
  |-->cachep = __find_general_cachep(size, flags);
  |   在malloc_sizes数组中寻找大于size的最小的kmem_cache缓存实例.
  |
  |-->ret = __cache_alloc(cachep, flags, caller);
  |   同与cache_cache的分配过程:
  |   先尝试从array_cache中分配,其次从slab分配,若没有相应的slab
  |   分配器,则从buddy system中分配页来构建slab份额器,并将slab
  |   分配器挂入kmem_cache实例的kmem_list3实例的某个链表下.
  |
  |--return ret;

你可能感兴趣的:(Linux内核-内存管理,内存,linux内核)