Linux Memory -- 三大allocator原理

Linux Memory – 三大allocator原理

boot memory allocator

  1. boot memory allocator的作用
    因爲內核裏面有很多memory struct,不可能在靜態編譯階段就靜態初始化所有的這些memory struct。另外,在系統啓動過程中,系統啓動後的物理內存分配器本身也需要初始化,如buddy allocator,那麼buddy allocator如何獲取內存來初始化自己呢 ?爲了達到這個目標,Linux設計了一種簡單的memory allocator:boot memory allocator
  2. boot memory allocator的原理
    在Linux內核中使用struct bootmem_data來描述一個boot memory allocator,其Node結構下的一個成員,也就是說每一個node都有一個boot memory allocator.
    boot memory allocator使用struct bootmem_data結構中的node_bootmem_map這個bitmap來呈現memory的狀態,一個bit代表一個物理頁框,也就是用struct page,如果一個bit爲1,表示該page已經被分配了,如果bit是0,則表示該page未被分配,爲了能夠滿足比一個page還小的內存塊的分配,boot memory allocator會使用last_pos來記住上次分配所使用的PFN以及上次分配所使用的page內的偏移:last_offset,下次分配的時候結合last_pos和last_offset將細小的內存塊分配儘量集中在相同的page中。

最新一次的分配能否和上一次分配合並。如果滿足以下條件就能合併,
1.上次分配所後的last_pos,與此次分配找到的page是連續的
2.上次分配之後的page中海油空閒的空間last_offset != 0 對齊大小小於PAGE_SIZE
3.無論當前的分配是否有與上次分配合並,last_pos和last_offse都將更新用來顯示當前分配所使用的是哪一個page以及該page使用了多少空間。如果該page全部被使用,則last_offset = 0。

boot memory

  1. boot memory allocator的缺點
    儘管boot memory allocator不會造成嚴重的內存碎片,但是每次分配過程需要線性掃描搜索內存來滿足當前的分配。因爲是檢查bitmaps,所以代價比較昂貴,尤其是first fit算法傾向將小塊內存放置在物理內存開頭,但是這些內存區域在分配大塊內存時,也需要掃描,所以該過程十分浪費。所以boot memory allocator在系統啓動後就被棄用的原因。
  2. boot memory allocator的接口
    (1) void * alloc_bootmem(unsigned long size)
    從ZONE_NORMAL中分配size個字節的內存。通過此函數分配的地址將會與L1 硬件緩存對齊從而最大效率利用硬件緩存
    (2)void * alloc_bootmem_pages(unsigned long size)
    從ZONE_NORMAL中分配滿足size的頁對齊的pages,如:不足一頁返回整頁,不足兩頁返回兩個整頁。
    (3)void * alloc_bootmem_low_pages(unsigned long size)
    從ZONE_NORMAL中分配滿足size的頁對齊的pages,如:不足一頁返回整頁,不足兩頁返回兩個整頁。
  3. boot memory allocator退休,將物理memory填充到buddy allocator中,移交給buddy allocator進行管理。
    retire

buddy allocator

  1. buddy allocator原理
    物理內存被分成11個order:0 ~ 10,每個order中連續page的個數是2order,如果一個order中可用的memory size小於期望分配的size,那麼更大order的內存塊會被對半切分,切分之後的兩個小塊互爲buddies。其中一個子塊用於分配,另一個空閒的。這些塊在必要時會連續減半,直到達到所需大小的memory 塊爲止,當一個block被釋放之後,會檢查它的buddies是否也是空閒的,如果是,那麼這對buddies將會被合併。
    order
struct free_area {
	struct list_head	free_list;
	unsigned long		*map;
};

每個order用結構體free_area 表示,其中free_list表示當前order中空閒塊的第一個page,map表示buddies的狀態。

2.釋放實例:
buddies

  1. buddy allocator的優缺點
    優點:由於將物理內存按照PFN將不同的page放入到不同order中,根據需要分配內存的大小,計算當前這次分配應該在哪個order中去找空閒的內存塊,如果當前order中沒有空閒,則到更高階的order中去查找,因此分配的效率比boot memory的線性掃描bitmap要快很多。
    缺點:
    (1)釋放page的時候調用方必須記住之前該page分配的order,然後釋放從該page開始的2order 個page,這對於調用者來說有點不方便(2)因爲buddy allocator每次分配必須是2order 個page同時分配,這樣當實際需要內存大小小於2order 時,就會造成內存浪費,所以Linux爲了解決buddy allocator造成的內部碎片問題,而引入slab allocator.

slab allocator

1.slab allocator的作用
<1>能夠分配更小塊的內存,可以幫助消除buddy allocator原本會造成的內部碎片問題
<2>緩存常用的object,因此內核不會在分配, 初始化和銷燬object上浪費時間。
<3>通過將object地址與L1或者L2硬件cache對齊後可以更好的利用硬件緩存
2.slab allocator的原理
slab allocator由cache --> slab --> object三個結構相結合地方式來描述,其中cache由 struct kmem_cache_s來描述,struct kmem_cache_s.中的成員struct kmem_list3 lists數組包含三個slab list:full,part,free, 這三個list是有struct slab結構組成的鏈表。其中struct slab中的s_mem是第一個object的起始地址,因此就可以通過cache --> slab --> object找到空閒的object。其中slab中的page 的prev指向cache,next指向slab。
而slab還分爲on-slab 和off-slab,是指當object的size特別大的時候,slab中新分配的page就全部放object,而struct slab結構單獨從通用slab中進行分配,如果object的size比較小,則可以將struct slab放置在新分配的page開頭,然後再放置object。

  1. object查找問題
    首先查找cache中part_list和free_list找到有空位的slab,然後在slab中通過struct free字段和和在struct slab中隱式的數組kmem_bufctl_t【】來查找下一個空閒的object,free表示當前空閒的object,而數組kmem_bufctl_t[free]表示下一個空閒的object,找到object的index後,通過slab中object的首地址就能找到該index 對應的object的首地址
  2. per-cpu cache,cache中被釋放的object並不會立即還給slab,而是放置在per-cpu cache中,當per-cpu cache中有一定數量的object後,再將這些object放置到全局cache中,當全局cache中的object滿了,再還原到slab中。這樣保證每個CPU能使用相同object。
  3. slab cache的回收問題
    cache使用者現在可以通過set_shrinker()註冊一個"shrink cache"的callback用於智能判斷和收縮slab。這個簡單的函數通過操作一個struct shrinker,其中有一個指針指向callback。幷包含一個seek字段用戶表示重新創建一個object的困難程度,然後再將shinker放入到shrinker_list中。
    在page回收的過程中,shrink_slab()函數被調用,來遍歷shrinker_list然後調用每個callback兩次。首先傳入參數0這表示該callback應該要返回它期望自己能釋放的page的個數。使用一個啓發的方式來決定是否值得調用該callback來回收pages。如果值得,該callback將被調用第二次,傳入要釋放的page的個數。
    每一個進程描述符struct task中有一個reclaim_state字段。當slab allocator釋放pages時,這個字段被更新爲這次釋放的page個數。在調用shrink_slab()之前,這個字段被設置爲0然後當shrink_cache()被調用後再次讀取該字段的值來決定要釋放多少個page。
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章