看了下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;