Linux内存管理之SLAB分配器
注:本文講述的SLAB相關代碼是基于Linux內核v4.7,代碼網址。
1、SLAB分配器的由來
在講SLAB分配器之前先說兩個概念: 內部碎片和外部碎片。
外部碎片指的是還沒有被分配出去(不屬于任何進程)但由于太小而無法分配給申請內存空間的新進程的內存空閑區域。外部碎片是除了任何已分配區域或頁面外部的空閑存儲塊。這些存儲塊的總和可以滿足當前申請的長度要求,但是由于它們的地址不連續或其他原因,使得系統無法滿足當前申請。簡單示例如下圖:
如果某進程現在需要向操作系統申請地址連續的32K內存空間,注意是地址連續,實際上系統中當前共有10K+23K=33K空閑內存,但是這些空閑內存并不連續,所以不能滿足進程的請求。這就是所謂的外部碎片,造成外部碎片的原因主要是進程或者系統頻繁的申請和釋放不同大小的一組連續頁框。Linux操作系統中為了盡量避免外部碎片而采用了伙伴系統(Buddy System)算法。
內部碎片就是已經被分配出去(能明確指出屬于哪個進程)卻不能被利用的內存空間;內部碎片是處于區域內部或頁面內部的存儲塊,占有這些區域或頁面的進程并不使用這個存儲塊,而在進程占有這塊存儲塊時,系統無法利用它。直到進程釋放它,或進程結束時,系統才有可能利用這個存儲塊。簡單示例如下圖:
某進程向系統申請了3K內存空間,系統通過伙伴系統算法可能分配給進程4K(一個標準頁面)內存空間,導致剩余1K內存空間無法被系統利用,造成了浪費。這是由于進程請求內存大小與系統分配給它的內存大小不匹配造成的。由于伙伴算法采用頁框(Page Frame)作為基本內存區,適合于大塊內存請求。在很多情況下,進程或者系統申請的內存都是4K(一個標準頁面)的,依然采用伙伴算法必然會造成系統內存的極大浪費。為滿足進程或者系統對小片內存的請求,對內存管理粒度更小的SLAB分配器就產生了。(注:Linux中的SLAB算法實際上是借鑒了Sun公司的Solaris操作系統中的SLAB模式)
2、SLAB分配器簡介
SLAB分配器實際上是建立在伙伴系統算法之上的,SLAB分配器使用的內存空間是通過伙伴算法進行分配的,只不過SLAB對這些內存空間實現了自己的算法進而對小塊內存進行管理。
在講解SLAB原理前,我們考慮下面場景:如果一個應用程序經常使用某一種類型的對象,或者說頻繁的創建、回收某一種類型的對象,那我們是不是可以嘗試將這類對象單獨存放起來,當進程不在使用時,我們暫時先不回收,等應用程序再使用時,我們把之前應該回收的對象在拿出來,只不過重新構造一下對象,這樣我們就減少了一次釋放、申請內存的操作了。
注:下文說明的緩存指的并不是真正的緩存,真正的緩存指的是硬件緩存,也就是我們通常所說的L1 cache、L2 cache、L3 cache,硬件緩存是為了解決快速的CPU和速度較慢的內存之間速度不匹配的問題,CPU訪問cache的速度要快于內存,如果將常用的數據放到硬件緩存中,使用時CPU直接訪問cache而不用再訪問內存,從而提升系統速度。下文中的緩存實際上是用軟件在內存中預先開辟一塊空間,使用時直接從這一塊空間中去取,是SLAB分配器為了便于對小塊內存的管理而建立的。
3、SLAB分配器原理
3.1 SLAB相關說明
? ? ?(1)SLAB與伙伴(Buddy)算法
? ? ? ?伙伴系統的相關介紹可以參加其他博客。在伙伴系統中,根據用戶請求,伙伴系統算法會為用戶分配2^order個頁框,order的大小從0到11。在上文中,提到SLAB分配器是建立在伙伴系統之上的。簡單來說,就是用戶進程或者系統進程向SLAB申請了專門存放某一類對象的內存空間,但此時SLAB中沒有足夠的空間來專門存放此類對象,于是SLAB就像伙伴系統申請2的冪次方個連續的物理頁框,SLAB的申請得到伙伴系統滿足之后,SLAB就對這一塊內存進行管理,用以存放多個上文中提到的某一類對象。
? ? ?(2)SLAB與對象
? ? ? ? ?對象實際上指的是某一種數據類型。一個SLAB只針對一種數據類型(對象)。為了提升對對象的訪問效率,SLAB可能會對對象進行對齊。
? ? ?(3)SLAB與per-CPU緩存
? ? ? ? ? ?為了提升效率,SLAB分配器為每一個CPU都提供了每CPU數據結構struct array_cache,該結構指向被釋放的對象。當CPU需要使用申請某一個對象的內存空間時,會先檢查array_cache中是否有空閑的對象,如果有的話就直接使用。如果沒有空閑對象,就像SLAB分配器進行申請。
3.2 SLAB相關數據結構
? ? ? ?注意,本節說明的SLAB代碼是基于Linux內核v4.7版本,代碼網址。
? ? ? ?SLAB分配器把對象分組放進高速緩存。每個高速緩存都是同種類型對象的一種“儲備”。包含高速緩存的主內存區被劃分為多個SLAB,每個SLAB由一個或多個連續的頁框組成,這些頁框中既包含已分配的對象,頁包含空閑的對象。
? ? ? ?在Linux內核中,SLAB高速緩存用struct kmem_cache表示,源代碼網址,其定義如下:
/*SLAB分配器高速緩存*/ struct kmem_cache {/*每CPU高速緩存指針,每一個CPU都會有一個該結構,其中存放了空閑對象*/struct array_cache __percpu *cpu_cache;/* 1) Cache tunables. Protected by slab_mutex *//*要轉移進本地高速緩存或者從本地高速緩存中轉移出去的對象的數量*/unsigned int batchcount;/*本地高速緩存中空閑對象的最大數目*/unsigned int limit;/*是否存在CPU共享的高速緩存*/unsigned int shared;/*對象對齊之后所占字節,也就是對象本身大小+為對齊對象而填充的字節*/unsigned int size;/*size的倒數*/struct reciprocal_value reciprocal_buffer_size; /* 2) touched by every alloc & free from the backend *//*描述高速緩存永久屬性的一組標志*/unsigned int flags; /* constant flags *//*每一個SLAB中包含的對象的個數*/unsigned int num; /* # of objs per slab *//* 3) cache_grow/shrink *//*一個單獨SLAB中包含的連續頁框數目的對數 order of pgs per slab (2^n) */unsigned int gfporder;/* 分配頁框時傳遞給伙伴系統的一組標識*/gfp_t allocflags;/*SLAB使用的顏色的數量*/size_t colour; /*SLAB中基本對齊偏移,當新SLAB著色時,偏移量的值需要乘上這個基本對齊偏移量,理解就是1個偏移量等于多少個B大小的值*/unsigned int colour_off; /* 空閑對象鏈表放在外部時使用,其指向的SLAB高速緩存來存儲空閑對象鏈表 */struct kmem_cache *freelist_cache;/* 空閑對象鏈表的大小 */unsigned int freelist_size;/* SLAB中存放的對象的構造函數*/void (*ctor)(void *obj);/* 4) cache creation/removal *//*高速緩存的名稱*/const char *name;/*高速緩存描述符的雙向鏈表指針*/struct list_head list;int refcount;/*SLAB中存放的對象的大小*/int object_size;int align;/* 5) statistics */ #ifdef CONFIG_DEBUG_SLABunsigned long num_active;unsigned long num_allocations;unsigned long high_mark;unsigned long grown;unsigned long reaped;unsigned long errors;unsigned long max_freeable;unsigned long node_allocs;unsigned long node_frees;unsigned long node_overflow;atomic_t allochit;atomic_t allocmiss;atomic_t freehit;atomic_t freemiss; #ifdef CONFIG_DEBUG_SLAB_LEAKatomic_t store_user_clean; #endif/*對象間的偏移*/int obj_offset; #endif /* CONFIG_DEBUG_SLAB */#ifdef CONFIG_MEMCGstruct memcg_cache_params memcg_params; #endif #ifdef CONFIG_KASANstruct kasan_cache kasan_info; #endif#ifdef CONFIG_SLAB_FREELIST_RANDOMvoid *random_seq; #endif/*一個節點高速緩存的指針數組,數組中的每一個元素指向每一個NUMA節點的高速緩存*/struct kmem_cache_node *node[MAX_NUMNODES]; };在struct kmem_cache結構體中,struct array_cache __percpu *cpu_cache和struct kmem_cache_node? *node[MAX_NUMNODES]這兩個屬性比較重要。array_cache針對的是每一個CPU的SLAB高速緩存,kmem_cache_node是針對每一個NUMA節點的SLAB高速緩存。其中,struct kmem_cache_node結構體定義如下:源代碼網址
/*NUMA節點高速緩存*/ struct kmem_cache_node {/*自旋鎖*/spinlock_t list_lock;#ifdef CONFIG_SLAB/*包含空閑對象和非空閑對象的SLAB描述符雙向循環鏈表*/struct list_head slabs_partial; /*不包含空閑對象的SLAB描述符雙向循環鏈表*/struct list_head slabs_full;/*只包含空閑對象的SLAB描述符的雙向循環鏈表*/struct list_head slabs_free;unsigned long free_objects;unsigned int free_limit;/*下一個被分配的SLAB的顏色*/unsigned int colour_next; /*本NUMA節點內被多個CPU所共享的本地高速緩存指針*/struct array_cache *shared; struct alien_cache **alien; /* on other nodes *//*兩次緩存收縮時的間隔*/unsigned long next_reap; /* updated without locking */int free_touched; /* updated without locking */ #endif#ifdef CONFIG_SLUBunsigned long nr_partial;struct list_head partial; #ifdef CONFIG_SLUB_DEBUGatomic_long_t nr_slabs;atomic_long_t total_objects;struct list_head full; #endif #endif };?在struct kmem_cache_node結構體中,有三個比較重要的屬性:slabs_partial、slabs_full、slabs_free,其中slabs_partial指向只有部分對象被使用的slab的描述符的鏈表,slabs_full指向所有對象都被使用的slab的描述符的鏈表,slabs_free指向所有對象都沒有被使用的slab的描述符的鏈表。
有關slab的描述符,其定義是在struct page中,相關代碼如下:源代碼網址
struct page {/* First double word block *//*頁描述符相關字段,與頁框有關的一組標志*/unsigned long flags;union {/*頁描述符相關字段*/struct address_space *mapping; /*與SLAB描述符有關,指向SLAB中第一個對象的地址*/void *s_mem; /* slab first object */atomic_t compound_mapcount; /* first tail page *//* page_deferred_list().next -- second tail page */};/* Second double word */struct {union {pgoff_t index; /* Our offset within mapping. *//*與SLAB描述符相關,指向SLAB中空閑對象鏈表*/void *freelist; /* sl[aou]b first free object *//* page_deferred_list().prev -- second tail page */};union { #if defined(CONFIG_HAVE_CMPXCHG_DOUBLE) && \defined(CONFIG_HAVE_ALIGNED_STRUCT_PAGE)/* Used for cmpxchg_double in slub */unsigned long counters; #elseunsigned counters; #endifstruct {union {atomic_t _mapcount;struct { /* SLUB */unsigned inuse:16;unsigned objects:15;unsigned frozen:1;};int units; /* SLOB */};/** Usage count, *USE WRAPPER FUNCTION** when manual accounting. See page_ref.h*/atomic_t _refcount;};unsigned int active; /* SLAB */};};/** Third double word block** WARNING: bit 0 of the first word encode PageTail(). That means* the rest users of the storage space MUST NOT use the bit to* avoid collision and false-positive PageTail().*/union {/*有多重用途,在SLAB中表示加入到kmem_cache中的SLAB描述符鏈表中*/struct list_head lru; struct dev_pagemap *pgmap; struct { /* slub per cpu partial pages */struct page *next; /* Next partial slab */ #ifdef CONFIG_64BITint pages; /* Nr of partial slabs left */int pobjects; /* Approximate # of objects */ #elseshort int pages;short int pobjects; #endif};struct rcu_head rcu_head; /* Used by SLAB* when destroying via RCU*//* Tail pages of compound page */struct {unsigned long compound_head; /* If bit zero is set *//* First tail page only */ #ifdef CONFIG_64BITunsigned int compound_dtor;unsigned int compound_order; #elseunsigned short int compound_dtor;unsigned short int compound_order; #endif};#if defined(CONFIG_TRANSPARENT_HUGEPAGE) && USE_SPLIT_PMD_PTLOCKSstruct {unsigned long __pad; pgtable_t pmd_huge_pte; /* protected by page->ptl */}; #endif};/* Remainder is not double word aligned */union {unsigned long private; #if USE_SPLIT_PTE_PTLOCKS #if ALLOC_SPLIT_PTLOCKSspinlock_t *ptl; #elsespinlock_t ptl; #endif #endifstruct kmem_cache *slab_cache; /* SL[AU]B: Pointer to slab */};#ifdef CONFIG_MEMCGstruct mem_cgroup *mem_cgroup; #endif#if defined(WANT_PAGE_VIRTUAL)void *virtual; /* Kernel virtual address (NULL ifnot kmapped, ie. highmem) */ #endif /* WANT_PAGE_VIRTUAL */#ifdef CONFIG_KMEMCHECK/** kmemcheck wants to track the status of each byte in a page; this* is a pointer to such a status block. NULL if not tracked.*/void *shadow; #endif#ifdef LAST_CPUPID_NOT_IN_PAGE_FLAGSint _last_cpupid; #endif }在struct page結構體中,與SLAB有關的比較重要的兩個屬性:s_mem和freelist,其中s_mem指向slab中的第一個對象的地址(或者已經被分配或者空閑)。freelist指向空閑對象鏈表。
在struct kmem_cache結構體中有一個屬性cpu_cache,cpu_cache是一個指向數組的指針,每個數組項都對應系統中的一個CPU,每個數組項都包含了一個指向struct array_cache的指針,其定義如下:源代碼網址
struct array_cache {/*保存了per-CPU緩存中可使用對象的指針的個數*/unsigned int avail;/*保存的對象的最大的數量*/unsigned int limit;/*如果per-CPU列表中保存的最大對象的數目超過limit值,內核會將batchcount個對象返回到slab*/unsigned int batchcount;/*從per—CPU緩存中移除一個對象時,此值將被設置為1,緩存收縮時,此時被設置為0.這使得內核能夠確認在緩存上一次收縮之后是否被訪問過,也是緩存重要性的一個標志*/unsigned int touched;/*一個偽指針數組,指向per-CPU緩存的對象*/void *entry[]; };說了這么多,你可能不太清楚上面的這些結構體到底都什么關系。為了更好的理解上面這些結構體之間的關系,請看下面的這幅圖:
具體的流程可以參考博客:https://blog.csdn.net/lukuen/article/details/6935068,請讀者一定要看一下這篇博客。
3.3 SLAB與分區頁框分配器
先介紹一下分區頁框分配器,分區頁框分配器用于處理對連續頁框組的內存分配請求。其中有一些函數和宏請求頁框,一般情況下,它們都返回第一個所分配的頁的線性地址,如果分配失敗,則返回NULL。這些函數簡介如下:
alloc_pages(gfp_mask,order):請求2^order個連續的頁框,他返回第一個所分配頁框的描述符的地址。分配失敗則返回NULL。__get_free_pages(gfp_mask,order):與函數alloc_pages(gfp_mask,orser)類似,但是此函數返回第一個所分配頁的線性地址。__free_pages(page,order):該函數先檢查page指向的頁描述符,如果該頁框未被保留(PG_reserved標志位為 0),就把描述符的count字段值減1。如果count字段的值變為0,就假定從與page對應的頁框開始的2^order個連續的頁框不再被使用。在這種情況下該函數釋放頁框。free_pages(addr,order):類似于__free_pages(),但是它接收的參數為要釋放的第一個頁框的線性地址addr。當SLAB分配器創建新的SLAB時,需要依靠分區頁框分配器來獲得一組連續的空閑頁框。為了達到此目的,需要調用kmem_getpages()函數,函數定義如下:源代碼網址
static struct page *kmem_getpages(struct kmem_cache *cachep, gfp_t flags,int nodeid)其中,參數cachep指向需要額外頁框的高速緩存的高速緩存描述符(請求頁框的個數存放在cache->gfporder字段中的order決定),flags說明如何請求頁框,nodeid指明從哪個NUMA節點的內存中請求頁框。與kmem_getpages()函數相對的是kmem_freepages(),kmem_freepages()函數可以釋放分配給slab的頁框。kmem_freepages()函數定義如下:源代碼網址
static void kmem_freepages(struct kmem_cache *cachep, struct page *page)3.4 SLAB與高速緩存
創建新的slab緩存需要調用函數kmem_cache_create()。該函數定義如下:源代碼網址
/** 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.* @align: The required alignment for the objects.* @flags: SLAB flags* @ctor: A constructor for the objects.** Returns a ptr to the cache on success, NULL on failure.* Cannot be called within a interrupt, but can be interrupted.* The @ctor is run when new pages are allocated by the cache.** 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_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.*/ struct kmem_cache *kmem_cache_create(const char *name, size_t size, size_t align,unsigned long flags, void (*ctor)(void *));kmem_cache_create()函數在調用成功時返回一個指向所構造的高速緩存的指針,失敗則返回NULL。
與kmem_cache_create()函數相對應的是銷毀高速緩存的函數kmem_cache_destroy(),該函數定義如下:源代碼網址
void kmem_cache_destroy(struct kmem_cache *s) {LIST_HEAD(release);bool need_rcu_barrier = false;int err;if (unlikely(!s))return;get_online_cpus();get_online_mems();kasan_cache_destroy(s);mutex_lock(&slab_mutex);s->refcount--;if (s->refcount)goto out_unlock;err = shutdown_memcg_caches(s, &release, &need_rcu_barrier);if (!err)err = shutdown_cache(s, &release, &need_rcu_barrier);if (err) {pr_err("kmem_cache_destroy %s: Slab cache still has objects\n",s->name);dump_stack();} out_unlock:mutex_unlock(&slab_mutex);put_online_mems();put_online_cpus();release_caches(&release, need_rcu_barrier); } EXPORT_SYMBOL(kmem_cache_destroy);需要注意的是:kmem_cache_destroy()函數銷毀的高速緩存中應該只包含未使用對象,如果一個高速緩存中含有正在使用的對象時調用kmem_cache_destroy()函數將會失敗,從kmem_cache_destroy()函數的源代碼中我們很容易看出。
下面看一個有關這兩個函數使用的示例:源代碼網址
#define KSM_KMEM_CACHE(__struct, __flags) kmem_cache_create("ksm_"#__struct,\sizeof(struct __struct), __alignof__(struct __struct),\(__flags), NULL)static int __init ksm_slab_init(void) {rmap_item_cache = KSM_KMEM_CACHE(rmap_item, 0);if (!rmap_item_cache)goto out;stable_node_cache = KSM_KMEM_CACHE(stable_node, 0);if (!stable_node_cache)goto out_free1;mm_slot_cache = KSM_KMEM_CACHE(mm_slot, 0);if (!mm_slot_cache)goto out_free2;return 0;out_free2:kmem_cache_destroy(stable_node_cache); out_free1:kmem_cache_destroy(rmap_item_cache); out:return -ENOMEM; }static void __init ksm_slab_free(void) {kmem_cache_destroy(mm_slot_cache);kmem_cache_destroy(stable_node_cache);kmem_cache_destroy(rmap_item_cache);mm_slot_cache = NULL; }上面的代碼很簡單,就是建立和銷毀rmap_item、mm_slot、stable_node三種數據類型的高速緩存。
3.5 SLAB與SLAB的對象
創建完成某一種數據類型或者某一種對象的高速緩存之后,我們可以從該對象的高速緩存中分配與釋放對象。其中,kmem_cache_alloc()函數用于從特定的緩存中獲取對象,該函數定義如下:源代碼網址
/*** 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(struct kmem_cache *cachep, gfp_t flags) {void *ret = slab_alloc(cachep, flags, _RET_IP_);kasan_slab_alloc(cachep, ret, flags);trace_kmem_cache_alloc(_RET_IP_, ret,cachep->object_size, cachep->size, flags);return ret; } EXPORT_SYMBOL(kmem_cache_alloc);從函數的名稱、參數、返回值、注釋中,我們很容易知道kmem_cache_alloc()函數從給定的slab高速緩存中獲取一個指向空閑對象的指針。實際上,進行獲取空閑對象的時候,會先從per-CPU緩存中也就是array_cache中查找空閑對象,如果沒有則會從kmem_cache_node中獲取空閑對象,如果也沒有則需要利用伙伴算法分配新的連續頁框,然后從新的頁框中獲取空閑對象。從kmem_cache_alloc()到大致的調用鏈如下:
kmem_cache_alloc()——>slab_alloc()——>__do_cache_alloc()——>____cache_alloc()——>cpu_cache_get()(這里實際上是從array_cache中獲取空閑對象)——>cache_alloc_refill()(這里會在array_cache中沒有空閑對象時執行)——>cpu_cache_get()(經過cache_alloc_refill()的執行基本保證array_cache中有空閑對象)——>返回可用空閑對象
對cache_alloc_refill()函數執行步驟分解如下:
cache_alloc_refill()——>嘗試從被一個NUMA節點所有CPU共享的緩沖中獲取空閑對象(源代碼注釋中寫道:See if we can refill from the shared array),如果有則返回可用對象,refill結束——>從kmem_cache_node中的slab中獲取空閑對象,有則返回,沒有就執行下一步——>kmem_getpages()
而kmem_cache_free()函數用于從特定的緩存中釋放對象,函數定義如下:源代碼網址
/*** 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(struct kmem_cache *cachep, void *objp) {unsigned long flags;cachep = cache_from_obj(cachep, objp);if (!cachep)return;local_irq_save(flags);debug_check_no_locks_freed(objp, cachep->object_size);if (!(cachep->flags & SLAB_DEBUG_OBJECTS))debug_check_no_obj_freed(objp, cachep->object_size);__cache_free(cachep, objp, _RET_IP_);local_irq_restore(flags);trace_kmem_cache_free(_RET_IP_, objp); } EXPORT_SYMBOL(kmem_cache_free);釋放對象與分配對象的過程類似,不再贅述。
3.6 SLAB著色
同一硬件高速緩存行可以映射RAM中很多不同的內存塊。相同大小的對象傾向于存放在硬件高速緩存內相同的偏移量處。在不同的SLAB內具有相同偏移量的度下行最終很有可能映射在同一硬件高速緩存行中。高速緩存的硬件可能因此而花費內存周期在同一高速緩存行與RAM內存單元之間來來往往傳送這兩個對象,而其他的硬件高速緩存行并未充分使用(以上語句出自《深入理解Linux內核》第三版第334頁)。SLAB分配器為了降低硬件高速緩存的這種行為,采用了SLAB著色(slab? coloring)的策略。所謂著色,簡單來說就是給各個slab增加不同的偏移量,設置偏移量的過程就是著色的過程。通過著色盡量使得不同的對象對應到硬件不同的高速緩存行上,以最大限度的利用硬件高速緩存,提升系統效率。
3.7 普通與專用高速緩存
高速緩存(指的不是硬件高速緩存)被在SLAB中被分成兩種類型:普通和專用。普通高速緩存只由SLAB分配器用于自己的目的,而專用高速緩存由內核的其余部分使用。專用的高速緩存是由kmem_cache_create()函數創建的,由kmem_cache_destroy()函數撤銷,用于存放對象(或具體數據類型),普通高速緩存是系統初始化期間調用keme_cache_init()建立的。普通高速緩存中分配和釋放空間使用kmalloc()和kfree()函數。下面是kmem_cache_init()函數的源代碼:源代碼網址
/** Initialisation. Called after the page allocator have been initialised and* before smp_init().*/ void __init kmem_cache_init(void) {int i;BUILD_BUG_ON(sizeof(((struct page *)NULL)->lru) <sizeof(struct rcu_head));kmem_cache = &kmem_cache_boot;if (!IS_ENABLED(CONFIG_NUMA) || num_possible_nodes() == 1)use_alien_caches = 0;for (i = 0; i < NUM_INIT_LISTS; i++)kmem_cache_node_init(&init_kmem_cache_node[i]);/** Fragmentation resistance on low memory - only use bigger* page orders on machines with more than 32MB of memory if* not overridden on the command line.*/if (!slab_max_order_set && totalram_pages > (32 << 20) >> PAGE_SHIFT)slab_max_order = SLAB_MAX_ORDER_HI;/* Bootstrap is tricky, because several objects are allocated* from caches that do not exist yet:* 1) initialize the kmem_cache cache: it contains the struct* kmem_cache structures of all caches, except kmem_cache itself:* kmem_cache is statically allocated.* Initially an __init data area is used for the head array and the* kmem_cache_node structures, it's replaced with a kmalloc allocated* array at the end of the bootstrap.* 2) Create the first kmalloc cache.* The struct kmem_cache for the new cache is allocated normally.* An __init data area is used for the head array.* 3) Create the remaining kmalloc caches, with minimally sized* head arrays.* 4) Replace the __init data head arrays for kmem_cache and the first* kmalloc cache with kmalloc allocated arrays.* 5) Replace the __init data for kmem_cache_node for kmem_cache and* the other cache's with kmalloc allocated memory.* 6) Resize the head arrays of the kmalloc caches to their final sizes.*//* 1) create the kmem_cache *//** struct kmem_cache size depends on nr_node_ids & nr_cpu_ids*/create_boot_cache(kmem_cache, "kmem_cache",offsetof(struct kmem_cache, node) +nr_node_ids * sizeof(struct kmem_cache_node *),SLAB_HWCACHE_ALIGN);list_add(&kmem_cache->list, &slab_caches);slab_state = PARTIAL;/** Initialize the caches that provide memory for the kmem_cache_node* structures first. Without this, further allocations will bug.*/kmalloc_caches[INDEX_NODE] = create_kmalloc_cache("kmalloc-node",kmalloc_size(INDEX_NODE), ARCH_KMALLOC_FLAGS);slab_state = PARTIAL_NODE;setup_kmalloc_cache_index_table();slab_early_init = 0;/* 5) Replace the bootstrap kmem_cache_node */{int nid;for_each_online_node(nid) {init_list(kmem_cache, &init_kmem_cache_node[CACHE_CACHE + nid], nid);init_list(kmalloc_caches[INDEX_NODE],&init_kmem_cache_node[SIZE_NODE + nid], nid);}}create_kmalloc_caches(ARCH_KMALLOC_FLAGS); }上面的代碼結合下面的圖,獲取會更容易理解一些:
在Linux中使用 sudo? cat? /proc/slabinfo就可以得到上面的信息,每一列的含義如下:
下面我們重點介紹kmalloc()函數,其源代碼如下:源代碼網址
/*** kmalloc - allocate memory* @size: how many bytes of memory are required.* @flags: the type of memory to allocate.** kmalloc is the normal method of allocating memory* for objects smaller than page size in the kernel.** The @flags argument may be one of:** %GFP_USER - Allocate memory on behalf of user. May sleep.** %GFP_KERNEL - Allocate normal kernel ram. May sleep.** %GFP_ATOMIC - Allocation will not sleep. May use emergency pools.* For example, use this inside interrupt handlers.** %GFP_HIGHUSER - Allocate pages from high memory.** %GFP_NOIO - Do not do any I/O at all while trying to get memory.** %GFP_NOFS - Do not make any fs calls while trying to get memory.** %GFP_NOWAIT - Allocation will not sleep.** %__GFP_THISNODE - Allocate node-local memory only.** %GFP_DMA - Allocation suitable for DMA.* Should only be used for kmalloc() caches. Otherwise, use a* slab created with SLAB_DMA.** Also it is possible to set different flags by OR'ing* in one or more of the following additional @flags:** %__GFP_COLD - Request cache-cold pages instead of* trying to return cache-warm pages.** %__GFP_HIGH - This allocation has high priority and may use emergency pools.** %__GFP_NOFAIL - Indicate that this allocation is in no way allowed to fail* (think twice before using).** %__GFP_NORETRY - If memory is not immediately available,* then give up at once.** %__GFP_NOWARN - If allocation fails, don't issue any warnings.** %__GFP_REPEAT - If allocation fails initially, try once more before failing.** There are other flags available as well, but these are not intended* for general use, and so are not documented here. For a full list of* potential flags, always refer to linux/gfp.h.*/ static __always_inline void *kmalloc(size_t size, gfp_t flags) {if (__builtin_constant_p(size)) {if (size > KMALLOC_MAX_CACHE_SIZE)return kmalloc_large(size, flags); #ifndef CONFIG_SLOBif (!(flags & GFP_DMA)) {int index = kmalloc_index(size);if (!index)return ZERO_SIZE_PTR;return kmem_cache_alloc_trace(kmalloc_caches[index],flags, size);} #endif}return __kmalloc(size, flags); }函數參數中的size表示請求分配的字節數,需要注意的是kmalloc()函數分配的是連續的物理內存。
與kmalloc()函數相對的是kfree()函數,其定義如下:源代碼網址
/*** kfree - free previously allocated memory* @objp: pointer returned by kmalloc.** If @objp is NULL, no operation is performed.** Don't free memory not originally allocated by kmalloc()* or you will run into trouble.*/ void kfree(const void *objp) {struct kmem_cache *c;unsigned long flags;trace_kfree(_RET_IP_, objp);if (unlikely(ZERO_OR_NULL_PTR(objp)))return;local_irq_save(flags);kfree_debugcheck(objp);c = virt_to_cache(objp);debug_check_no_locks_freed(objp, c->object_size);debug_check_no_obj_freed(objp, c->object_size);__cache_free(c, (void *)objp, _RET_IP_);local_irq_restore(flags); } EXPORT_SYMBOL(kfree);kfree()函數用于釋放由kmalloc()函數分配的連續內存空間。kmalloc()分配的是內核內存,但是其分配的大小是有限制的。
3.8 SLAB分配器體現的改進思想:
(1)SLAB分配器把內存區看成對象
(2)SLAB分配器吧對象分組放進高速緩存
(3)每個SLAB都是同種類型的內存對象
?
如上文內容有不當之處,請批評指針,謝謝!
?
總結
以上是生活随笔為你收集整理的Linux内存管理之SLAB分配器的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 关于远程连接access数据库问题
- 下一篇: linux 其他常用命令