在內核初始化完成後,內存管理的責任由夥伴系統(高效、高速)承擔。前端
系統內存中的每一個物理內存頁(頁幀),都對應於一個struct page實例。每一個內存域都關聯了一個struct zone的實例,其中保存了用於管理夥伴數據的主要數組。node
1 struct zone { 2 ... 3 struct free_area free_area[MAX_ORDER]; //不一樣長度的空閒區域 4 ... 5 } ;
sruct free_area是一個輔助結構,以下所示。程序員
1 struct free_area { 2 struct list_head free_list[MIGRATE_TYPES]; //用於鏈接空閒頁的鏈表 3 unsigned long nr_free; //當前內存區中空閒頁塊的數目 4 };
階(order)是夥伴系統中一個很是重要的術語。它描述了內存分配的數量單位內存區的管理單位,內存塊的長度是2的order次方。圖1是夥伴系統中相互鏈接的內存區,內存區中第1頁內的鏈表元素,可用於將內存區維持在鏈表中。所以,也沒必要引入新的數據結構來管理物理上連續的頁,不然這些頁不可能在同一內存區中,MAX_ORDER根據硬件不一樣而設置不一樣的值,表示一次分配能夠請求的最大頁數的以2爲底的對數。算法
圖1 夥伴系統相互鏈接的內存區後端
夥伴沒必要是彼此鏈接的。若是一個內存區在分配其間分解爲兩半,內核會自動將未用的一半加入到對應的鏈表中。若是在將來的某個時刻,因爲內存釋放的緣故,兩個內存區都處於空閒狀態,可經過其地址判斷其是否爲夥伴。數組
基於夥伴系統的內存管理專一於某個結點的某個內存域,例如,DMA或高端內存域。但全部內存域和結點的夥伴系統都經過備用分配列表鏈接起來。如圖2所示。緩存
圖2 夥伴系統和內存域/結點之間的關係安全
Linux系統啓動並長期運行後,物理內存會產生不少碎片。這對用戶空間應用程序沒有問題(其內存經過頁表進行映射,物理內存分佈與應用程序看到的內存無關),但對內核來講,碎片是一個問題(大多數物理內存一致映射到地址空間內核部分)。網絡
(1)依據可移動性組織頁數據結構
文件系統的碎片主要經過碎片合併工具解決,不一樣於物理內存,許多物理內存頁不能移動到任意位置,阻礙了該方法的實施。內核處理避免碎片的方法是反碎片(版本2.6.24),試圖從最初開始儘量防止碎片。
內核將已分配頁劃分爲如下3種不一樣類型:
頁的可移動性,依賴該頁屬於3種類別的哪種。內核使用的反碎片技術,將具備相同可移動性的頁進行分組。根據頁的可移動性,將其分配到不一樣的列表中,防止不可移動的頁位於可移動內存區中間的狀況出現。這樣對於不可移動頁中仍然難以找到較大的連續空閒時間,但對可回收的頁就相對容易了。
內核定義了一些宏來表示遷移類型:
1 #define MIGRATE_UNMOVABLE 0 //類型 2 #define MIGRATE_RECLAIMABLE 1 //類型 3 #define MIGRATE_MOVABLE 2 //類型 4 #define MIGRATE_RESERVE 3 //向具備特定可移動性的列表請求分配內存失敗,從MIGRATE_RESERVE分配內存(緊急分配) 5 #define MIGRATE_ISOLATE 4 //不能從這裏分配,特殊的虛擬區域,用於跨越NUMA結點移動物理內存頁 6 #define MIGRATE_TYPES 5
對夥伴系統的主要數據結構影響是將空閒列表分解爲MIGRATE_TYPE個列表,代碼以下:
1 struct free_area { 2 struct list_head free_list[MIGRATE_TYPES]; 3 unsigned long nr_free; //全部列表上空閒頁的數目 4 };
內核提供了一個備用列表,規定了在指定列表中沒法知足分配請求時,接下來使用的遷移類型的種類。(在內核想要分配不可移動頁時,若是對應鏈表爲空,則後退到可回收頁鏈表,接下來到可移動頁鏈表,最後到緊急分配鏈表。)
頁可移動性分組特性老是編譯到內核中,但只有在系統中有足夠內存能夠分配到多個遷移類型對應的鏈表時,纔會起做用。兩個全局變量pageblock_order和pageblock_nr_pages提供每一個遷移鏈表對應的適當數量的內存。第一個表示內核認爲是「大」的一個分配階,pageblock_nr_pages則表示該分配階對應的頁數。若是體系結構提供了巨型頁機制,則pageblock_order一般定義爲巨型頁對應的分配階(IA-32巨型頁長度是4MB),若是體系結構不支持巨型頁,則將其定義爲第二高的分配階(MAX_ORDER-1)。若是各遷移類型的鏈表中沒有一塊較大的連續內存,那麼頁面遷移不會提供任何好處,所以在可用內存太少時內核會經過設置全局變量page_group_by_mobility爲0關閉該特性(一旦停用了頁面遷移特性,全部頁都是不可移動的)。
在內存子系統初始化期間,memmap_init_zone負責處理內存域的page實例。它將全部的頁最初都標記爲可移動的,此時若是須要分配不可移動的內存,則必須「盜取」(見4分配API)。實際上,啓動期間分配可移動內存區的狀況較少,分配器有很高的概率分配長度最大的內存區,並將其從可移動列表轉換到不可移動列表。因爲分配的內存區長度是最大的,所以不會向可移動內存中引入碎片。這種作法避免了啓動期間內核分配的內存(常常在系統的整個運行時間都不釋放)散佈到物理內存各處,從而使其餘類型的內存分配免受碎片的干擾,這也是頁可移動性分組框架的最重要的目標之一。
(2)虛擬可移動內存域
依據可移動性組織頁是防止物理內存碎片的一種可能方法,內核還提供了另外一種阻止該問題的手段:虛擬內存域ZONE_MOVABLE,其特性必須由管理員顯示激活。
基本思想:可用的物理內存劃分爲兩個內存域,一個用於可移動分配,一個用於不可移動分配。
kernelcore參數用來指定用於不可移動分配的內存數量(用於既不能回收也不能遷移的內存數量)。參數movablecore控制用於可移動內存分配的內存數量。若是同時指定兩個參數,內核會按照必定的方法進行計算,取指定值與計算值中較大的一個。
ZONE_MOVABLE並不關聯到任何硬件上有意義的內存範圍,該內存域中的內存取自高端內存域或普通內存域,所以稱虛擬內存域。
從物理內存域提取用於ZONE_MOVABLE的內存數量主要考慮如下兩個因素:
最後是計算結果,用於爲虛擬內存域ZONE_MOVABLE提取內存頁的物理內存域,保存在全局變量movable_zone中;對每一個結點來講,zone_movable_pfn[node_id]表示ZONE_MOVABLE在movable_zone內存域中所取得內存的起始地址。
(虛擬內存域具體的實如今4分配API中)
在啓動期間,各體系結構相關的代碼須要確立系統中各內存域的頁幀的邊界(max_zone_pfn數組);肯定各結點頁幀的分配狀況(全局變量early_node_map)。
(1)管理數據結構的建立
圖3概述了管理數據結構創建的過程。
圖3 管理數據結構構建過程示意圖
圖4 free_area_init_nodes的代碼流程圖
free_area_init_nodes代碼流程圖如圖4所示,完成如下工做:
(2)對各個結點建立數據結構
在內存域邊界已經肯定以後,free_area_init_nodes分別對各個內存域調用free_area_init_node建立數據結構。這涉及到幾個輔助函數(見圖4):
此時,空閒頁的數目(nr_free)當前仍然規定爲0,這顯然沒有反映真實狀況。直至停用bootmem分配器、普通的夥伴分配器生效,纔會設置正確的數值。
夥伴系統接口對於NUMA和UMA體系結構沒有差異,可是它只能分配2的整數冪個頁(分配必須指定階),內核中的細粒度分配只能藉助於slab分配器(或者slub、slob分配器)。
(1)分配掩碼
分配器API中的mask參數,稱爲掩碼,它包含了圖5所示的內容。 (GFP表示get free page)
圖5 GFP掩碼佈局
(2)內存分配宏
經過使用標誌、內存域修飾符和各個分配函數,內核提供了一種很是靈活的內存分配體系,全部接口函數均可以追溯到一個基本函數alloc_pages_node,如圖6所示。
圖6 夥伴系統的各分配函數之間關係
相似地,內存釋放函數也能夠歸約到一個主要的函數__free_pages,如圖7所示(只是調用參數不一樣)。
圖7 夥伴系統各內存釋放函數之間關係
free_pages和__free_pages之間的關係經過函數而不是宏創建,由於首先必須將虛擬地址轉換爲指向struct page的指針。
內核源代碼將__alloc_pages稱之爲「夥伴系統的心臟」,由於它處理的是實質性的內存分配。
(1)選擇頁
內核定義了一些函數使用的標誌,用於控制到達各水印指定的臨界狀態時的行爲。
#define ALLOC_NO_WATERMARKS 0x01 /* 徹底不檢查水印 */ #define ALLOC_WMARK_MIN 0x02 /* 使用pages_min水印 */ #define ALLOC_WMARK_LOW 0x04 /* 使用pages_low水印 */ #define ALLOC_WMARK_HIGH 0x08 /* 使用pages_high水印 */ #define ALLOC_HARDER 0x10 /* 試圖更努力地分配,即放寬限制 */ #define ALLOC_HIGH 0x20 /* 設置了__GFP_HIGH */ #define ALLOC_CPUSET 0x40 /* 檢查內存結點是否對應着指定的CPU集合 */
默認狀況下(即沒有因其餘因素帶來的壓力而須要更多的內存),只有內存域包含頁的數目至少爲zone->pages_high時,才能分配頁。這對應於ALLOC_WMARK_HIGH標誌。若是要使用較低(zone->pages_low)或最低(zone->pages_min)設置,則必須相應地設置ALLOC_WMARK_MIN或ALLOC_WMARK_LOW。ALLOC_HARDER通知夥伴系統在急
需內存時放寬分配規則。在分配高端內存域的內存時,ALLOC_HIGH進一步放寬限制。最後,ALLOC_CPUSET告知內核,內存只能從當前進程容許運行的CPU相關聯的內存結點分配,固然該選項只對NUMA系統有意義。
__alloc_pages是夥伴系統的主函數,函數比較復長,可用內存足夠時必要工做很快完成,可用內存太少或逐漸用完時,函數就會變得比較複雜。
在最簡單的情形中,分配空閒內存區只涉及調用一次get_page_from_freelist,而後返回所需數目的頁(由標號got_pg處的代碼處理)。
其餘狀況中,會進行屢次內存分配嘗試:
(2)移除選擇的頁
若是內核找到適當的內存域,具備足夠的空閒頁可供分配,那麼還有兩件事情須要完成。首先它必須檢查這些頁是不是連續的;其次,必須按夥伴系統的方式從free_lists移除這些頁,這可能須要分解並重排內存區。
內核將工做委託給輔助函數buffered_rmqueue完成,其代碼流程圖如圖8所示。
圖8 buffered_rmqueue代碼流程圖
首先,判斷階數,若爲0,則表示只請求一頁。此時,內核試圖藉助於per-CPU緩存加速請求的處理。若是緩存爲空,內核可藉機檢查緩存填充水平。若是per-CPU緩存中沒法找到適當的頁,則向緩存添加一些符合當前要求遷移類型的頁,而後從per-CPU列表移除一頁,接下來進一步處理。
若不是0,則表示請求多頁。內核調用__rmqueue(要求頁連續)會從內存域的夥伴列表中選擇適當的內存塊。若有必要,該函數會自動分解大塊內存,將未用的部分放回列表中。若分配失敗,則會返回NULL指針。全部失敗情形都跳轉到標號failed處理,這能夠確保內核到達當前點以後,page指向一系列有效的頁。在返回指針以前,prep_new_page須要作一些準備工做,以便內核可以處理這些頁(若是所選擇的頁出了問題,則該函數返回正值。在這種狀況下,分配將從頭從新開始)。
__free_pages是一個基礎函數,用於實現內核API中全部涉及內存釋放的函數。其代碼流程圖如圖9所示。
圖9 __free_pages代碼流程圖
__free_pages首先判斷所需釋放的內存是單頁仍是較大的內存塊?若是釋放單頁,則不還給夥伴系統,而是置於per-CPU緩存中,對極可能出如今CPU高速緩存的頁,則放置到熱頁的列表中。出於該目的,內核提供了free_hot_page輔助函數,該函數只是做一下參數轉換,接下來調用free_hot_cold_page。若是釋放多個頁,那麼__free_pages將工做委託給__free_pages_ok,最後到__free_one_page。與其名稱不一樣,該函數不只處理單頁的釋放,也處理複合頁釋放。
物理上連續的映射對內核是最優的,但不可能老是成功使用。對此,內核分配了其虛擬地址空間的一部分,用於創建連續映射。如圖10所示,在IA-32系統中,緊隨直接映射的前892 MiB物理內存,在插入的8 MiB安全隙以後,是一個用於管理不連續內存的區域。這一段具備線性地址空間的全部性質。經過修改負責該區域的內核頁表,能夠將其中的頁映射到物理內存的任何地方。每一個vmalloc分配的子區域都是自包含的,與其餘vmalloc子區域經過一個內存頁分隔。相似於直接映射和vmalloc區域之間的邊界,不一樣vmalloc子區域之間的分隔也是爲防止不正確的內存訪問操做。
圖10 IA-32系統上內核的虛擬地址空間中的vmalloc區域
(1)用vmalloc分配內存
vmalloc是一個接口函數,內核使用它來分配虛擬內存中連續但在物理內存中不必定連續的內存。
void *vmalloc(unsigned long size);
該函數只須要一個參數,用於指定所需內存區的長度(字節)。
內核對模塊的實現中,有不少使用vmalloc的地方,由於函數可能在任什麼時候候加載,若是模塊數比較多,那麼沒法保證有足夠的連續內存可用(尤爲是系統已經運行了比較長時間的狀況下)。由於用於vmalloc的內存頁老是必須映射在內核地址空間中,所以使用ZONE_HIGHMEM內存域的頁要優於其餘內存域。這使得內核能夠節省更寶貴的較低端內存域,而又不會帶來額外的壞處。
vmalloc的代碼流程圖如圖11所示。
圖11 vmalloc代碼流程圖
vmalloc的實現分爲三個部分,首先,get_vm_area在vmalloc地址空間中找到一個適當的區域。接下來從物理內存分配各個頁,最後將這些頁連續地映射到vmalloc區域中,完成分配虛擬內存的工做。
(2)備選映射方法
(3)釋放內存
有兩個函數用於向內核釋放內存,vfree用於釋放vmalloc和vmalloc_32分配的區域,而vunmap用於釋放由vmap或ioremap建立的映射。兩個函數都會歸結到__vunmap。其代碼流程圖如圖12所示。
圖12 __vunmap代碼流程圖
儘管vmalloc函數族可用於從高端內存域向內核映射頁幀,但這並非這些函數的實際用途。內核提供了其餘函數用於將ZONE_HIGHMEM頁幀顯式映射到內核空間。
(1)持久內核映射
若是須要將高端頁幀長期映射(做爲持久映射)到內核地址空間中,必須使用kmap函數。須要映射的頁用指向page的指針指定,做爲該函數的參數。若是沒有啓用高端支持,該函數只須要返回頁的地址;若是啓用了高端支持,則相似於vmalloc,內核首先必須創建高端頁和所映射到的地址之間的關聯,在虛擬地址空間中分配一個區域以映射頁幀,最後,內核必須記錄該虛擬區域的哪些部分在使用中,哪些仍然是空閒的。
內核在IA-32平臺上vmalloc區域以後分配了一個區域,從PKMAP_BASE到FIXADDR_START,該區域用於持久映射,不一樣體系結構使用的方案是相似的。
(pkmap_count是一容量爲LAST_PKMAP的整數數組,其中每一個元素都對應於一個持久映射頁。它其實是被映射頁的一個使用計數器,0意味着相關的頁沒有使用,1有特殊語義,n表明內核中有n-1處使用該頁(n≥2)。)
用kmap映射的頁,若是再也不須要,必須用kunmap解除映射。
(2)臨時內核映射
kmap函數不能用於中斷處理程序,由於它可能進入睡眠狀態(pkmap數組中沒有空閒位置時)。內核提供了kmap_atomic,該函數執行是原子的,比普通的kmap快速,不能用於可能進入睡眠的代碼,對於很快就須要一個臨時頁的簡短代碼是很是理想的。
kmap_atomic的定義在IA-3二、PPC、Sparc32上是特定於體系結構的,但這3種實現只有很是細微的差異,其原型是相同的。
void *kmap_atomic(struct page *page, enum km_type type) //page是一個指向高端內存頁的管理結構的指針,type定義了所需的映射類型
(內核的固定映射機制,使之能夠在內核地址空間中訪問用於創建原子映射的內存。能夠在FIX_KMAP_BEGIN和FIX_KMAP_END之間創建一個用於映射高端內存頁的區域,該區域位於fixed_addresses數組中,準確的位置須要根據當前活動的CPU和所需映射類型計算。)
在使用kmap_atomic時不會阻塞。若是發生阻塞,那麼另外一個進程可能創建一樣類型的映射,覆蓋現存的項。
kunmap_atomic函數從虛擬內存解除一個現存的原子映射,該函數根據映射類型和虛擬地址,從頁表刪除對應的項。
(3)沒有高端內存的計算機上的映射函數
許多體系結構不須要支持高端內存(好比AMD64),爲了避免須要老是區分高端內存和非高端內存體系結構,內核定義了幾個在普通內存實現兼容函數的宏(在支持高端內存的計算機上,若是停用了高端內存,也會使用這些宏)。
1 #ifdef CONFIG_HIGHMEM 2 ... 3 #else 4 static inline void *kmap(struct page *page) 5 { 6 might_sleep(); 7 return page_address(page); 8 } 9 #define kunmap(page) do { (void) (page); } while (0) 10 #define kmap_atomic(page, idx) page_address(page) 11 #define kunmap_atomic(addr, idx) do { } while (0) 12 #endif
相似於C語言中的malloc,slab分配器提供小塊內存,同時它也用做一個緩存,主要針對常常分配並釋放的對象。slab分配器將釋放內存塊保存在一個內部列表中,並不立刻返回給夥伴系統,以便下一次高速的內存分配。這樣內核沒必要使用夥伴系統算法,處理時間會變短,同時該內存塊仍然駐留在CPU告訴緩存的機率較高。
slab分配器有兩大好處:
在大型系統上僅slab的數據結構就須要不少GB內存。對嵌入式系統來講,slab分配器代碼量和複雜性都過高,所以誕生了slob分配器和slub分配器。
slob分配器進行了特別優化,以便減小代碼量。它圍繞一個簡單的內存塊鏈表展開,在分配內存時,使用了一樣簡單的最早適配算法(速度非最高效,不適用大型系統);
slub分配器經過將頁幀打包爲組,並經過struct page中未使用的字段來管理這些組,試圖最小化所需的內存開銷。
全部分配器的前端接口都是相同的。每一個分配器都實現了一組特定的函數,用於內存分配和緩存。
圖13闡釋了物理頁幀、夥伴系統、通用分配器與通常內核代碼接口關聯。
圖13 夥伴系統、通用分配器與通常內核代碼接口關聯示意圖
內核中通常的內存分配和釋放函數與C標準庫中等價函數的名稱相似,用法也幾乎相同。
與用戶空間程序設計相比,內核還包括percpu_alloc和percpu_free函數,用於爲各個系統CPU分配和釋放所需內存區。
全部活動緩存的列表保存在/proc/slabinfo中(終端輸入cat /proc/slabinfo便可查看),包含用於標識各個緩存的字符串名稱,緩存中活動對象的數量,緩存中對象的總數(已用和未用),所管理對象的長度(按字節計算),一個slab中對象的數量,每一個slab中頁的數量,活動slab的數量,在內核決定向緩存分配更多內存時,所分配對象的數量。
slab分配器由一個緊密地交織的數據和內存結構的網絡組。如圖14所示,slab緩存由保存管理性數據的緩存對象和保存被管理對象的各個slab。
圖14 slab分配器各個部分
每一個緩存只負責一種對象類型,或提供通常性的緩衝區。各個緩存中slab的數目各有不一樣,這與已經使用的頁的數目、對象長度和被管理對象的數目有關。
系統中全部的緩存都保存在一個雙鏈表中。這使得內核有機會依次遍歷全部的緩存。
(1)緩存的精細結構
圖15 slab緩存的精細結構
圖15描述了緩存各組成部分,除了管理性數據,緩存結構包括兩個特別重要的成員:
緩存結構指向一個數組,其中包含了與系統CPU數目相同的數組項。每一個元素都是一個指針,指向一個進一步的結構稱之爲數組緩存(array cache),其中包含了對應於特定系統CPU的管理數據(就整體來看,不是用於緩存)。管理性數據以後的內存區包含了一個指針數組,各個數組項指向slab中未使用的對象。
爲最好地利用CPU高速緩存,在分配和釋放對象時,採用後進先出原理(LIFO,last in first out)。內核假定剛釋放的對象仍然處於CPU高速緩存中,會盡快再次分配它。僅當per-CPU緩存爲空時,纔會用slab中的空閒對象從新填充它們。這樣,對象分配的體系就造成了一個三級的層次結構(分配成本和操做對CPU高速緩存和TLB的負面影響逐級升高):
(2)slab精細結構
用於每一個對象的長度進行了舍入,以知足某些對齊方式的要求,對於對齊方案,有兩種:
slab建立時使用標誌SLAB_HWCACHE_ALIGN,slab用戶能夠要求對象按硬件緩存行對齊;
若是不要求按硬件緩存行對齊,那麼內核保證對象按BYTES_PER_WORD對齊,該值是表示void指針所需字節的數目。
在32位處理器上,void指針須要4個字節。所以,對有6個字節的對象,則須要8 = 2×4個字節,多餘的字節稱爲填充字節。填充字節能夠加速對slab中對象的訪問,若是使用對齊的地址,那麼在幾乎全部的體系結構上,內存的訪問都會更快。
slab的起始處是管理結構,保存了全部的管理數據(和用於鏈接緩存鏈表的鏈表元素)。其後面是一個數組,每一個(整數)數組項對應於slab中的一個對象(只有在對象沒有分配時,相應的數組項纔有意義)。此時,它指定了下一個空閒對象的索引。因爲最低編號的空閒對象的編號還保存在slab起始處的管理結構中,內核無需使用鏈表或其餘複雜的關聯機制,便可找到當前可用的全部對象。數組的最後一項老是一個結束標記,值爲BUFCTL_END。管理數組與slab對象的關係如圖16所示。
圖16 slab中空閒對象的管理
管理數據能夠放置在slab自身,也能夠放置到使用kmalloc分配的不一樣內存區中。內核的選擇取決於slab的長度和已用對象的數量。
最後,內核經過對象自身(page結構的一個鏈表元素lru.next和lru.prev)識別slab(以及對象駐留的緩存)。根據對象的物理內存地址,找到相關的頁,從而在全局mem_map數組中找到對應的page實例。
slab系統帶有大量調試選項,代碼中遍及着預處理語句:
(1)數據結構
每一個緩存由kmem_cache結構的一個實例表示。結構內容以下:
1 struct kmem_cache { 2 /* 1) per-CPU數據,在每次分配/釋放期間都會訪問 */ 3 struct array_cache *array[NR_CPUS]; //指向數組的指針,每一個數組項都對應於系統中的一個CPU。每一個數組項都包含了另外一個指針,指向下文討論的array_cache結構的實例 4 /* 2) 可調整的緩存參數。由cache_chain_mutex保護 */ 5 unsigned int batchcount; //per-CPU列表爲空時,從緩存的slab中獲取對象的數目;還表示在緩存增加時分配的對象數目 6 unsigned int limit; //指定了per-CPU列表中保存的對象的最大數目 7 unsigned int shared; 8 unsigned int buffer_size; //指定了緩存中管理的對象的長度 9 u32 reciprocal_buffer_size; 10 /* 3) 後端每次分配和釋放內存時都會訪問 */ 11 unsigned int flags; /* 常數標誌 */ 12 unsigned int num; /* 每一個slab中對象的數量 */ 13 /* 4) 緩存的增加/縮減 */ 14 unsigned int gfporder; //指定了slab包含的頁數目以2爲底的對數 15 /* 強制的GFP標誌,例如GFP_DMA */ 16 gfp_t gfpflags; 17 size_t colour; //顏色的最大數目 18 unsigned int colour_off; //着色偏移 19 struct kmem_cache *slabp_cache; //slab頭部的管理數據存儲在slab外部時,指向分配所需內存的通常性緩存; slab頭部在slab上時,爲NULL指針 20 unsigned int slab_size; 21 unsigned int dflags; // 標誌集合,描述slab的「動態性質」 22 void (*ctor)(struct kmem_cache *, void *); //指向在對象建立時調用的構造函數 23 /* 5) 緩存建立/刪除 */ 24 const char *name; //是一個字符串,包含該緩存的名稱 25 struct list_head next; //用於將kmem_cache的全部實例保存在全局鏈表cache_chain上 26 /* 6) 統計量 */ 27 ... 28 struct kmem_list3 *nodelists[MAX_NUMNODES]; 29 };
(2)初始化
爲初始化slab數據結構,內核須要若干小內存塊(最適合由kmalloc分配),可是隻有slab系統啓用以後,才能使用kmalloc,於是內核藉助了一些技巧。
kmem_cache_init函數用於初始化slab分配器。它在內核初始化階段(start_kernel)、夥伴系統啓用以後調用。第一步:kmem_cache_init建立系統中的第一個slab緩存,以便爲kmem_cache的實例提供內存,內核使用的主要是在編譯時建立的靜態數據;第二步:kmem_cache_init接下來初始化通常性的緩存,用做kmalloc內存的來源(針對所需的各個緩存長度,分別調用kmem_cache_create);第三步:在kmem_cache_init的最後一步,把到如今爲止一直使用的數據結構的全部靜態實例化的成員,用kmalloc動態分配的版本替換。
(3)建立緩存
建立新的slab緩存必須調用kmem_cache_create,這是一個冗長的過程,其代碼示意圖如圖17所示。
圖17 kmem_cache_create的代碼流程圖
(4)分配對象
kmem_cache_alloc用於從特定的緩存獲取對象,它須要用於獲取對象的緩存,以及精確描述分配特徵的標誌變量兩個參數,結果多是指向分配內存區的指針,也可能分配失敗返回NULL。
圖18 kmem_cache_alloc的代碼流程圖
(5)緩存的增加
圖19描述了cache_grow代碼流程圖。
圖19 cache_grow的代碼流程圖
(6)釋放對象
當一個分配的對象再也不須要時,使用kmem_cache_free將其返回給slab分配器。圖20爲kmem_cache_free代碼流程圖。
圖20 kmem_cache_free的代碼流程圖
當即調用__cache_free,根據per-CPU緩存的狀態不一樣,執行如下兩種操做:
(7)銷燬緩存
若是要銷燬只包含未使用對象的一個緩存,則必須調用kmem_cache_destroy函數。該函數主要在刪除模塊時調用,此時須要將分配的內存都釋放。主要步驟以下:
若是不涉及對象緩存,而是傳統意義上的分配/釋放內存,則必須調用kmalloc和kfree函數。kmalloc和kfree實現爲slab分配器的前端,其語義儘量地模仿C標準庫malloc和free。
內核提供了一些命令直接做用於處理器的高速緩存和TLB,用於維護緩存內容的一致性,確保不出現不正確和過期的緩存項。
不一樣體系結構上,高速緩存和TLB的硬件實現不一樣,所以內核須要創建TLB和高速緩存的視圖,在其中考慮到各類不一樣的硬件實現方法,兼顧各個體系結構的特定性質。
TLB的語義抽象是將虛擬地址轉換爲物理地址的一種機制;
內核將高速緩存視爲經過虛擬地址快速訪問數據的一種機制,該機制無需訪問物理內存。數
據和指令高速緩存並不老是明確區分。
內核中各個特定於CPU的部分都必須提供下列函數(即便只是空操做),以便控制TLB和高速緩存:
內核對數據和指令高速緩存不做區分。若是須要區分,特定於處理器的代碼可根據vm_area_struct->flags的VM_EXEC標誌位是否設置,來肯定高速緩存包含的是指令仍是數據。
flush_cache_...和flush_tlb_...函數常常成對出現。
好比在使用fork複製進程地址空間時,操做的順序是:刷出高速緩存、操做內存、刷出TLB,緣由有兩個: