當在用戶模式下運行進程請求額外內存時,從內核維護的空閑頁幀列表上分配頁面。這個列表通常使用頁面置換算法來填充,如前所述,它很可能包含散布在物理內存中的空閑頁面。也要記住,如果用戶進程請求單個字節內存,那么就會導致內部碎片,因為進程會得到整個幀。
用于分配內核內存的空閑內存池通常不同于用于普通用戶模式進程的列表。這有兩個主要原因:
- 內核需要為不同大小的數據結構請求內存,其中有的小于一頁。因此,內核應保守地使用內存,并努力最小化碎片浪費。這一點非常重要,因為許多操作系統的內核代碼或數據不受調頁系統的控制。
- 用戶模式進程分配的頁面不必位于連續物理內存。然而,有的硬件設備與物理內存直接交互,即無法享有虛擬內存接口帶來的便利,因而可能要求內存常駐在連續物理內存中。
下面討論兩個策略,以便管理用于內核進程的空閑內存:“伙伴系統”和 slab 分配。
伙伴系統
伙伴系統從物理連續的大小固定的段上進行分配。從這個段上分配內存,采用 2 的冪分配器來滿足請求分配單元的大小為 2 的冪(4KB、 8KB、16KB 等)。請求單元的大小如不適當,就圓整到下一個更大的 2 的冪。例如,如果請求大小為 11KB,則按 16KB 的段來請求。
讓我們考慮一個簡單例子。假設內存段的大小最初為 256KB,內核請求 21KB 的內存。最初,這個段分為兩個伙伴,稱為 AL 和 AR,每個的大小都為 128KB;這兩個伙伴之一進一步分成兩個 64KB 的伙伴,即 BL 和 BR。然而,從 21KB 開始的下一個大的 2 的冪是 32KB,因此 BL 或 BR 再次劃分為兩個 32KB 的伙伴 CL 和 CR。因此,其中一個 32KB 的段可用于滿足 21KB 請求。這種方案如下圖所示,其中 CL 段是分配給 21KB 請求的。

伙伴系統分配
伙伴系統的一個優點是:通過稱為合并的技術,可以將相鄰伙伴快速組合以形成更大分段。例如,在圖 1 中,當內核釋放已被分配的 CL 時,系統可以將 CL 和 CR 合并成 64KB 的段。段 BL 繼而可以與伙伴 BR 合并,以形成 128KB 段。最終,可以得到原來的 256KB 段。
伙伴系統的明顯缺點是:由于圓整到下一個 2 的冪,很可能造成分配段內的碎片。例如,33KB 的內存請求只能使用 64KB 段來滿足。事實上,我們不能保證因內部碎片而浪費的單元一定少于 50%。
slab分配
分配內核內存的第二種策略稱為slab分配。每個slab由一個或多個物理連續的頁面組成,每個cache由一個或多個slab組成,每個內核數據結構都有一個cache。
例如,用于表示進程描述符、文件對象、信號量等的數據結構都有各自單獨的cache。每個cache 含有內核數據結構的對象實例(稱為object)。例如,信號量cache有信號量對象,進程描述符cache有進程描述符對象,等等。

slab 分配
上圖顯示了slab、cache及object 三者之間的關系。該圖顯示了2個大小為3KB 的內核對象和3個大小為7KB的對象,它們位于各自的cache中。slab分配算法采用 cache來存儲內核對象。在創建 cache 時,若干起初標記為free的對象被分配到 cache。cache內的對象數量取決于相關slab的大小。例如,12KB slab(由3個連續的4KB頁面組成)可以存儲6個2KB對象。最初,cache內的所有對象都標記為空閑。當需要內核數據結構的新對象時,分配器可以從cache上分配任何空閑對象以便滿足請求。從cache上分配的對象標記為used(使用)。
讓我們考慮一個場景,這里內核為表示進程描述符的對象從slab分配器請求內存。在 Linux 系統中,進程描述符屬于 struct task_struct 類型,它需要大約1.7KB的內存。當Linux內核創建一個新任務時,它從cache中請求 struct task_struct對象的必要內存。cache 利用已經在slab中分配的并且標記為 free (空閑)的 struct task_struct對象來滿足請求。
在Linux中,slab可以處于三種可能狀態之一:
- 滿的:slab的所有對象標記為使用。
- 空的:slab上的所有對象標記為空閑。
- 部分:slab上的對象有的標記為使用,有的標記為空閑。
slab分配器首先嘗試在部分為空的slab中用空閑對象來滿足請求。如果不存在,則從空的slab 中分配空閑對象。如果沒有空的slab可用,則從連續物理頁面分配新的slab,并將其分配給cache;從這個slab上,再分配對象內存。slab分配器提供兩個主要優點:
- 沒有因碎片而引起內存浪費。碎片不是問題,因為每個內核數據結構都有關聯的cache,每個 cache都由一個或多個slab組成,而slab按所表示對象的大小來分塊。因此,當內核請求對象內存時,slab 分配器可以返回剛好表示對象的所需內存。
- 可以快速滿足內存請求。因此,當對象頻繁地被分配和釋放時,如來自內核請求的情況,slab 分配方案在管理內存時特別有效。分配和釋放內存的動作可能是一個耗時過程。然而,由于對象已預先創建,因此可以從cache 中快速分配。再者,當內核用完對象并釋放它時,它被標記為空閑并返回到cache,從而立即可用于后續的內核請求。
slab 分配器首先出現在 Solaris 2.4 內核中。由于通用性質,Solaris 現在也將這種分配器用于某些用戶模式的內存請求。最初,Linux使用的是伙伴系統;然而,從版本2.2開始,Linux 內核采用 slab 分配器。
現在,最近發布的 Linux 也包括另外兩個內核內存分配器,SLOB和SLUB分配器(Linux 將 slab 實現稱為SLAB)。
簡單塊列表(SLOB)分配器用于有限內存的系統,例如嵌入式系統。SLOB工作采用3個對象列表:小(用于小于 256 字節的對象)、中(用于小于1024字節的對象)和大(用于小于頁面大小的對象)。內存請求采用首先適應策略,從適當大小的列表上分配對象。
從版本2.6.24開始,SLUB分配器取代SLAB,成為Linux內核的默認分配器。SLUB通過減少SLAB 分配器所需的大量開銷,來解決slab分配的性能問題,一個改變是,在SLAB分配下每個slab 存儲的元數據,移到Linux內核用于每個頁面的結構 page。此外,對于SLAB分配器,每個CPU都有隊列以維護每個cache內的對象,SLUB會刪除這些隊列。
對于具有大量處理器的系統,分配給這些隊列的內存量是很重要的。因此,隨著系統處理器數量的增加,SLUB性能也更好。