天天看點

Linux 踩記憶體 slub,Linux SLUB 記憶體配置設定器分析

本文簡介

本文主要介紹了Linux SLUB配置設定的産生原因、設計思路及其代碼分析。适合于對Linux核心,特别是對Linux記憶體配置設定器感興趣的讀者。

1.為何需要SLUB?

Linux SLUB記憶體配置設定器合入Linux主分支已經整整10年了!并且是Linux目前預設的記憶體配置設定器。

然而,主流的Linux核心出版物仍然在分析SLAB而不是SLUB配置設定器。這似乎有點令人驚奇。

對于Linux來說,重要子產品合入核心時,都會在更新檔或者lwn文檔裡面詳細記錄其合入理由。SLUB配置設定器也不例外。我們看看作者在送出更新檔是怎麼說的。

1.1.準備工作

A、請使用git clone指令下載下傳一份Linux Next分支的代碼。

B、在源碼目錄下,用git log v2.6.22..v2.6.23 mm/slub.c,檢視其最初合入記錄。

C、找到最初合入更新檔的commit id,即81819f0fc828。

D、使用git show 81819f0fc828檢視更新檔的詳細資訊。

在檢視更新檔之前,有兩點需要特别注意:

1、在Linux社群,一般用SLUB、SLAB表示記憶體配置設定器算法及其實作。不管是SLUB還是SLAB,這兩種算法都會使用slab來組織記憶體對象。我們可以簡單的認為:一個slab由一個或者幾個頁框組成,每個頁框一般包含4096個位元組。在每一個slab裡面,包含一個或者多個待配置設定的對象。

2、更新檔描述一般比較精練,其閱讀對象是核心社群老手。如果看起來費力,也沒有關系。可以通讀本文後,回頭再來領會作者的意思。

1.2.作者怎麼說?

我們看看作者Christoph Lameter在更新檔中到底是怎麼解釋SLUB的:

這是一個新的slab配置設定器,它由mm/slab.c中現有代碼的複雜性所激發。它試圖處理現有SLAB實作中的種種不足之處。

A. 隊列管理

在SLAB配置設定器中,其中一個突出的問題是:幾種對象隊列管理的複雜性。SLUB則沒有這樣的隊列。相反,我們為每一個将要配置設定記憶體的CPU準備一個slab,并且直接使用slab中的對象,而不是将slab加入到隊列中。

B.對象隊列的存儲開銷

在每一個CPU中,每一個NUMA節點都存在SLAB對象隊列。即使是每CPU的緩存隊列,也持有一個隊列數組。這些數組為每一個CPU、每一個NUMA節點而包含一個隊列。對于非常大的系統來說,隊列自身的數量和可能包含在這些隊列中的對象的數量,将會成倍增長。 在我們使用1k個NUMA節點/處理器的系統中,我們有數G位元組用于存儲這些隊列的引用。這甚至不包括可能包含在這些隊列中的對象。人們擔心,所有記憶體有一天會被這些隊列消耗殆盡。

C. SLAB中繼資料開銷

SLAB在每個slab的起始處都有開銷。 這意味着資料不能在slab塊的起始處優雅的對齊。SLUB則将所有中繼資料儲存在相應的page_struct資料結構中。是以,對象可以在slab中優雅的對齊。例如,一個128位元組的對象将在128位元組的邊界對齊,并且可以緊緻的放入一個4k頁面,而沒有浪費位元組。SLAB則不能做到這一點。

D. SLAB有一個複雜的緩存回收器

對于UP系統來說,SLUB并不需要緩存回收器。在SMP系統中,則應當将每CPU slab放回到半滿連結清單中。不過,該操作不僅簡單,而且不需要周遊對象連結清單。SLAB則不然,在緩存回收期間,它會周遊每一個CPU,及CPU共享的、CPU獨有的對象隊列。這可能會引起較大的CPU卡頓。

E. SLAB具有複雜的NUMA政策層支援

SLUB将NUMA政策處理轉交給頁面配置設定器。這意味着與SLAB配置設定器相比,配置設定過程負責的事情更少(當然,SLUB确實也不可避免的與頁面配置設定這一級互動),但是在2.6.13之前,這種情況也是存在的。在SLAB中,在特定NUMA節點上配置設定slab對象的SLAB應用,确定會存在性能問題。這是因為,頻繁的引用記憶體政策可能導緻一系列對象來自一個接一個的NUMA節點。SLUB将從一個節點獲得一個slab的所有對象,然後切換到下一個節點。

F.減少半滿slab連結清單的大小

SLAB有每節點的半滿清單。這意味着随着時間的推移,大量的半滿slab可能積累在這些連結清單中。僅僅當配置設定器發生在特定節點上時,這些半滿slab才被重用。SLUB有一個全局的半滿slab池,并将從該池中消耗slab以減少碎片。

G.可調節

SLUB對每個slab緩存都有複雜的調節能力。其中一個能力是可以仔細的維護隊列大小。 然而,填充隊列仍然需要使用自旋鎖,以保護slab。 SLUB有一個全局參數(min_slab_order)用于調節。增加最小slab order可以減少鎖開銷。slab oder越大,在每個CPU和半滿清單之間的頁面遷移越少,SLUB擴充得越好。

G.slab合并

我們經常使用具有類似參數的slab緩存。在建立階段,SLUB檢測到這些緩存,并将它們合并到相應的通用高速緩存中。這導緻記憶體使用更加有效。在所有緩存中,大約50%的緩存可以通過slab合并來消除。這也能夠減少slab碎片,因為部分配置設定的slab可以被重新填滿。通過在啟動時指定slub_nomerge參數,可以關閉slab合并功能。

請注意,合并可能會暴露核心中的未知BUG,因為被破壞的對象現在可能被放置在不同的地方,并破壞不同的相鄰對象。請啟用安全性檢查來找到這些潛在問題。

H.診斷

目前的slab診斷很難使用,這需要重新編譯核心。SLUB則包含總是可用的調試代碼(但不在熱點代碼路徑中)。可以通過“slab_debug”選項啟用SLUB診斷。可以指定參數來選擇一個或一組slab高速緩存來進行診斷。 這意味着系統以正常的性能運作,同時也使得競争條件更有可能被複現。

I.容錯

如果進行了基本的健全性檢查,則SLUB能夠檢測到常見的錯誤情況并盡可能恢複以使系統繼續運作。

J.追蹤

可以在啟動時通過slab_debug = T,選項啟用追蹤。然後,SLUB将記錄該slabcache上的所有操作,并在空閑時轉儲對象内容。

K.按需建立DMA緩存

通常,DMA緩存是不需要的。 如果kmalloc與_GFP_DMA一起使用,那麼隻需建立這個必要的單個slab緩存即可。對于沒有ZONE_DMA要求的系統,這項支援被完全消除。

L.性能提升

一些基準測試顯示,kernbench的速度提高了5-10%。SLUB的鎖開銷是基于底層配置設定塊的大小。如果我們能夠放心的配置設定更大order的頁面,則可以更進一步提高SLUB的性能。 防碎片更新檔可以使性能進一步提高。

關于NUMA對應用程式性能的影響,請參見:

http://cenalulu.github.io/linux/numa/

仔細檢視過SLAB和SLUB配置設定器的代碼後,筆者認為:SLUB代碼的清晰度、簡潔性優于SLAB。

目前,除了極個别的應用場景外,SLUB的性能也優于SLAB。這也是為什麼目前Linux版本預設使用SLUB配置設定器,同時也保留SLAB配置設定器代碼的原因。

2.SLUB概述

2.1.Linux記憶體配置設定的層次

Linux 踩記憶體 slub,Linux SLUB 記憶體配置設定器分析

與SLUB記憶體配置設定器相關的概念有幾個層次:

1、頁幀

概念

描述

存儲節點(Node)

CPU被劃分為多個節點(node), 記憶體則被分簇, 每個CPU對應一些本地實體記憶體, 即一個CPU-node對應一個記憶體簇bank,即每個記憶體簇被認為是一個節點

管理區(Zone)

每個實體記憶體節點node被劃分為多個記憶體管理區域, 用于表示不同範圍的記憶體

頁面(Page)

記憶體被細分為多個頁框, 頁框是最基本的頁面配置設定的機關。一個頁面的常見長度是4096位元組

在NUMA系統中,處理器被劃分成多個“節點”(node)。所有節點中的處理器都可以通路全部的系統實體存儲器,但是通路本節點内的存儲器所需要的時間,比通路某些遠端節點内的存儲器所花的時間要少得多。

各個節點又被劃分為記憶體管理區域, 一個管理區域通過struct zone來描述。低端範圍的記憶體管理區被稱為ZONE_DMA, 可直接映射到核心的正常記憶體域被稱為ZONE_NORMAL,不能直接進行線性映射的記憶體域被稱為ZONE_HIGHMEM, 即高端記憶體。

頁框(page frame)則代表了系統記憶體的最小機關, 每個頁幀用struct page來描述。

2、頁面配置設定器

通常,記憶體配置設定一般有兩種情況:大對象(大的連續空間配置設定)、小對象(小的空間配置設定)。對于大的對象(超過一個頁框的大小),可以使用頁面配置設定器進行配置設定。在Linux中,頁面配置設定器被稱為夥伴系統。其中常見的API是__get_free_pages、alloc_pages、free_pages、__free_pages。

夥伴系統把所有的空閑頁框分為11個(不同的版本有所差別)塊連結清單,每個塊連結清單中,分别包含大小為1,2,4,8,16,32,64,128,256,512和1024個連續頁框的頁框塊。假設要申請一個256個頁框的塊,則先從結點為256個連續頁框塊的連結清單中查找空閑塊,如果沒有,就去512個頁框的連結清單中找,找到了則将頁框塊分為2個256個頁框的塊,将其中一個配置設定給應用,另外一個移到256個頁框的空閑連結清單中。如果512個頁框的連結清單中仍沒有空閑塊,繼續在1024個頁框的連結清單進行查找—分割—配置設定和轉移。如果仍然沒有,則傳回錯誤。

使用過的頁框塊在釋放時,會主動将兩個連續的頁框塊合并為一個較大的頁框塊,然後作為節點插入相應的連結清單中。

夥伴系統很好地解決了外部碎片(頁框之間的碎片)問題。

3、對象配置設定器(SLUB)

夥伴系統配置設定記憶體時是基于頁框為機關的。如果想要配置設定小于一個頁框,例如幾十個位元組的記憶體,應該怎麼辦呢?此時就需要用對象配置設定器,例如 SLUB、SLOB、SLAB配置設定器。對象配置設定器是基于對象進行管理的,所謂的對象就是核心中的資料結構,例如task_struct,file_struct 等。相同類型的對象歸為一類,由kmem_cache資料結構進行描述。每個kmem_cache對象由一組slab組成,每個slab由一個或者多個頁框構成。在每個slab中,包含一個或者多個記憶體對象。每當要申請這樣一個對象時,對象配置設定器就從一個slab中配置設定一個對象出去。當要釋放對象時,将其重新儲存在slab的對象清單中,而不是直接傳回給夥伴系統,進而避免内部碎片。對象配置設定器在配置設定對象時,會使用最近釋放的對象的記憶體塊,是以其駐留在cpu高速緩存中的機率會大大提高。

對象配置設定器是針對于小對象的記憶體配置設定,它很好地解決了頁框内部的内部碎片問題。

4、調用SLUB配置設定記憶體的核心代碼

2.2.SLUB配置設定器

Linux 踩記憶體 slub,Linux SLUB 記憶體配置設定器分析

通過上圖可以看到,SLUB配置設定器的主要資料結構是kmem_cache。相對于SLAB而言,該結構簡化了不少。沒有了隊列的相關字段。

每個處理器都有一個本地的活動slab,由kmem_cache_cpu結構描述。并且,在SLUB中,沒有單獨的空slab隊列。每個NUMA節點使用kmem_cache_node結構維護一個處于半滿狀态的slab隊列,作為備用slab緩存池。

Linux 踩記憶體 slub,Linux SLUB 記憶體配置設定器分析

如上圖所示,在SLUB配置設定器中,一個slab就是一組連續的實體記憶體頁框,被劃分成了固定數目的對象。slab沒有額外的空閑對象隊列,而是重用了空閑對象自身的存儲空間并将其連結起來,這既節省了對象中繼資料空間,也大大簡化了代碼的複雜度。

在SLAB配置設定器中,每個slab需要一些中繼資料空間,存放在每個slab起始處,或者申請單獨的存儲空間,存儲在slab之外。SLUB與此不同,它的slab沒有額外的描述結構。由于它的slab描述字段較少,是以它在代表實體頁框的page結構中重用了_mapcount等字段。在SLUB中,這些字段被解釋為SLUB所需要的freelist,inuse和slab等含義。幸運的是,這并不會令page資料結構膨脹。

2.2.1.快速配置設定流程

Linux 踩記憶體 slub,Linux SLUB 記憶體配置設定器分析

正常情況下,在同一個kmem_cache描述符上進行反複的申請、釋放操作後,相應的資料如上圖所示:目前kmem_cache描述符的CPU緩存slab中,freelist連結清單有可用的空閑對象。

在這種情況下,當核心申請配置設定對象時,可以直接從所在處理器的kmem_cache_cpu 結構的freelist字段獲得第一個空閑對象的位址,然後更新 freelist 字段,使其指向下一個空閑對象。然後将摘除的空閑對象傳回給調用者。如下圖所示:

Linux 踩記憶體 slub,Linux SLUB 記憶體配置設定器分析

2.2.3.慢速配置設定流程

當CPU緩存slab不存在,或者緩存slab中的freelist已經變空以後,SLUB會嘗試從CPU所在NUMA節點的半滿連結清單中,找到一個可用的半滿slab,放到CPU緩存slab中。并嘗試從該緩存slab中配置設定對象。

Linux 踩記憶體 slub,Linux SLUB 記憶體配置設定器分析

2.2.4.最慢速配置設定流程

最慢速的情況,是CPU緩存slab的freelist為空,并且NUMA節點的半滿slab連結清單也為空。這種情況下,隻能從夥伴系統中配置設定新的頁面并填充到CPU緩存slab中。

這種情況下,最大的開銷是在夥伴系統中需要使用全局的自旋鎖。

Linux 踩記憶體 slub,Linux SLUB 記憶體配置設定器分析

2.2.5.快速釋放流程

Linux 踩記憶體 slub,Linux SLUB 記憶體配置設定器分析

最快速的釋放流程是:被釋放的對象剛好可以放回到CPU緩存slab中,并且不需要做任何額外的處理。

2.2.6.慢速釋放流程

Linux 踩記憶體 slub,Linux SLUB 記憶體配置設定器分析

在釋放對象時,如果遇到如下情況,則需要進入慢速釋放流程:

1、slab由全滿變為半滿,此時需要将slab加入到節點的半滿連結清單中。

2、slab變為全空,此時需要将頁面釋放回夥伴系統。

3.SLUB代碼分析

3.1.相關資料結構

與SLAB相比,SLUB配置設定器的最大特點,就是簡化設計理念,同時也保留了SLAB配置設定器的基本思想:每個緩沖區由多個小的 slab 組成,每個 slab 包含固定數目的對象。SLUB 配置設定器簡化了kmem_cache,slab 等相關的管理資料結構,摒棄了SLAB 配置設定器中衆多的隊列概念。為了保證核心其它子產品能夠無縫遷移到 SLUB 配置設定器,SLUB也保留了原有SLAB配置設定器所有的接口API 函數。

本文所列的資料結構和源代碼均摘自Linux核心 2.6.24版本。

3.1.1.kmem_cache

每個核心對象緩沖區都是由kmem_cache類型的資料結構來描述的,下列出了它的主字段:

3.1.2.page結構相關字段

在SLAB配置設定器中,slab中不但儲存了記憶體對象,同時還儲存了一些中繼資料。相反的,在SLUB配置設定器中,slab 沒有額外的中繼資料,例如空閑對象隊列,而是将空閑對象指針放在了空閑對象之中。同時,沒有專門的資料結構來描述slab,而是在代表實體頁框的 page結構中複用如下字段來表示slab相關資訊:

üfreelist:slab中第一個空閑對象的指針

üinuse:slab中已配置設定對象。如果等于slab中對象總數,即代表slab全滿

üslab:所屬緩沖區 kmem_cache 結構的指針

在每一個slab中,這些中繼資料儲存在第一個實體頁框的page結構中。

3.1.3.kmem_cache_cpu

3.1.4.kmem_cache_node

在SLUB中,沒有單獨的空slab隊列。所有空slab都會被直接歸還回夥伴系統,而不會緩存在SLUB中。在每一個kmem_cache結構中,與 NUMA 節點相關的資料結構使用 kmem_cache_node來表示,它維護一個處于半滿狀态的slab隊列。下表列出它的主要字段:

3.2.API

在Linux中,三種對象配置設定器SLOB\SLAB\SLUB都提供了統一的API,以保證調用接口的一緻性。下表列出主要的API函數:

3.3.函數實作

以下代碼分析基于linux 2.6.24版本,可以通過如下連結檢視相應版本的代碼:

http://elixir.free-electrons.com/linux/v2.6.24/source

SLUB配置設定的主要代碼位于mm/slub.c和include/linux/slub_def.h中。

3.3.1.kmem_cache_create

kmem_cache_create建立一個kmem_cache對象,其原型如下:

struct kmem_cache *kmem_cache_create(const char *name, size_t size,

size_t align, unsigned long flags,

void (*ctor)(struct kmem_cache *, void *))

參數及傳回值含義:

name:kmem_cache的名稱,在proc中使用

size:kmem_cache所管理的對象記憶體大小

align:記憶體對齊要求

flags:建立标志,如SLAB_HWCACHE_ALIGN

ctor:對象建構函數,在初始化對象時調用

傳回值:建立的kmem_cache對象

該函數實作如下:

1、獲得slub_lock寫鎖(3041行),該鎖保護全局kmem_cache連結清單。

2、查找與目前建立參數比對的,可以合并的kmem_cache對象(3042行)。

3、如果這樣的kmem_cache對象存在(3042行),那麼:

A、增加kmem_cache對象的引用計數(3046行)。

B、修正kmem_cache對象的對象大小(3051行)。

C、周遊所有CPU,修改所有CPU緩存slab中的對象大小(3057行)。

D、改變kmem_cache對象inuse值(3059行),該值表示slab對,每個對象的中繼資料在對象中的偏移。我猜測,這裡可能會存在BUG,如果有哪位讀者通過更新檔記錄能夠證明真的有BUG,請告訴我一下:[email protected]。

E、釋放slub_lock寫鎖(3060行)。

F、在sys檔案系統中,為新建立的kmem_cache對象建立别名,将其連結到原對象上(3061行)。

G、傳回比對的kmem_cache對象(3063行)。

4、否則,沒有比對的kmem_cache對象,必須要新建立一個。首先為kmem_cache描述符配置設定記憶體(3066行)。

5、如果記憶體配置設定成功(3067行),則:

A、調用kmem_cache_open将kmem_cache描述符準備就緒(3068行)。

B、如果kmem_cache_open執行成功(3068行),那麼:

BA、将kmem_cache描述符添加到全局slab_caches連結清單中(3070行)。

BB、釋放slub_lock寫鎖(3071行)。

BC、在sys檔案系統中,為新建立的kmem_cache對象建立檔案對象(3072行)。

BD、傳回新建立的kmem_cache對象(3074行)。

C、否則,建立kmem_cache不成功,釋放其描述符(3076行)。

6、釋放slub_lock寫鎖(3078行)。

7、運作到此,說明建立過程中出現錯誤(3080行)。

8、如果調用者傳入了SLAB_PANIC标志,則将系統hung住(3082行)。

9、否則傳回NULL(3084行)。

kmem_cache_open對新建立的kmem_cache對象進行初始化,其實作如下:

1、将kmem_cache描述符置0(2063行)

2、設定其name,ctor等初始值(2064行)

3、計算對象長度、在夥伴系統中配置設定slab頁面的order值(2271行)。如果值過大,無法通過SLUB記憶體配置設定器管理,則傳回錯誤。

4、設定防碎片調節參數(2276行)

5、為kmem_cache對象初始化NUMA節點緩存相關的資料結構(2278行)。注意,在系統初始化階段,需要調用boot記憶體配置設定函數來配置設定相關資料結構。在SLUB初始化完畢後,由SLUB系統自身來配置設定相應的資料結構。

6、為kmem_cache對象初始化每CPU緩存slab資料結構(2281行)

7、如果初始化失敗,則釋放前面配置設定的NUMA節點緩存資料結構(2283行)

3.3.2.kmem_cache_destroy

該函數是kmem_cache_create相對應的反初始化函數,其實作比較簡單。讀者可以自行分析。

3.3.3.kmem_cache_alloc

kmem_cache_alloc是SLUB記憶體配置設定器的配置設定接口。位于slub.c的1593行。其函數原型是:

void *kmem_cache_alloc(struct kmem_cache *s, gfp_t gfpflags)

參數及傳回值含義如下:

s:kmem_cache描述符

gfpflags:記憶體配置設定标志,如GFP_ATOMIC、GFP_KERNEL。當需要從夥伴系統中配置設定slab時,将此參數傳遞給夥伴系統。

傳回值:配置設定成功的對象位址,如果失敗則傳回NULL。

它直接調用slab_alloc從slab中配置設定對象位址。slab_alloc的實作如下:

1、關閉本地CPU中斷(1575行)。在此,關閉中斷有兩個目的:A、避免中斷打斷目前slab_alloc的執行,造成邏輯錯誤。因為随後的代碼需要維護kmem_cache_cpu資料結構。B、防止程序被遷移到其他CPU上執行。

2、獲得目前CPU對應的kmem_cache_cpu資料結構。該資料結構是目前CPU緩存的,用于記憶體配置設定的slab(1576行)。

3、如果(1)CPU緩存的slab沒有空閑對象了,或者(2)調用者希望從特定NUMA節點中配置設定資料,而緩存的slab所在的節點與之并不比對(1577行),那麼:

A、調用__slab_alloc配置設定對象,這是慢速配置設定過程(1579行)。

4、否則(1581行):

A、從kmem_cache_cpu資料結構中,獲得第一個空閑對象(1582行)。

B、将kmem_cache_cpu資料結構的空閑對象後移到下一個空閑對象。下一個空閑對象指針儲存在目前空閑對象的offset偏移處(1583行)。

5、恢複本地CPU中斷(1585主)。

6、如果(1)調用者要求将對象初始化為0,并且(2)成功配置設定了對象(1587行),那麼:

調用memset将對象置0(1588行)

7、傳回所配置設定的對象,可能為NULL(1590行)。

3.3.4.__slab_alloc

__slab_alloc就SLUB的慢速配置設定流程,如下:

1、如果目前CPU緩存的slab還不存在(1496行),則:

A、跳轉到new_slab标簽處,從夥伴系統中配置設定slab頁面(1497行)。

2、鎖住頁面(1499行)。實際上,PG_locked主要用于磁盤IO時的頁面鎖定,防止形成并發問題。但是在SLUB配置設定器中,相應的頁并不會被磁盤IO交換出去。這裡僅僅是借用PG_locked标志來保護page結構中,與SLUB配置設定器相關的幾個字段。

3、如果slab所在的NUMA節點編号與所要求的不比對,也就是調用者希望在特定NUMA節點上配置設定對象,而目前緩存的slab位于另外的節點(1500行),那麼

A、跳轉到another_slab(1501行),配置設定另外一個slab并緩存到目前CPU。

4、擷取目前slab的第一個空閑對象(1503行)。

5、如果目前slab沒有空閑對象(1504行),那麼:

A、跳轉到another_slab(1505行),配置設定另外一個slab并緩存到目前CPU。

6、如果使用者希望調試目前slab(1506行),那麼:

A、跳轉到debug标簽(1507行),略。

7、擷取目前slab的第一個空閑對象(1509行)。

8、将slab的空閑對象指針下移到下一個空閑對象,并将其交給kmem_cache_cpu對象管理,此後空閑連結清單不再屬于slab對象(1510行)。

9、slab已經被托管到目前CPU緩存中了,設定slab的對象占用值為該slab中,所有的對象個數(1511行)。

10、slab中所有對象已經交給kmem_cache_cpu進行管理,那麼slab的空閑對象連結清單也應當設定為NULL(1512行)。

11、設定kmem_cache_cpu的節點号為頁面所在的節點(1513行)。

12、slab中的相關資料結構已經設定完畢,釋放頁面鎖(1514行)。

13、傳回配置設定成功的對象。

14、當CPU中緩存的slab對象kmem_cache_cpu,與調用者期望的NUMA節點不一緻時,跳轉到這裡,調用deactivate_slab解除目前slab與CPU之前的綁定關系(1518行)。

15、當緩存的slab還沒有配置設定頁面時,跳轉到這裡,為目前CPU配置設定可用頁面(1520行)。

16、調用get_partial(1521行),優先從特定NUMA節點中獲得一個半滿slab。

17、如果成功的從NUMA節點中獲得一個半滿slab(1522行),那麼:

A、設定目前CPU緩存的slab為該slab(1523行)。

B、跳轉到1502行,開始從緩存的slab中配置設定對象(1524行)。

18、否則,需要從夥伴系統中配置設定新頁。如果配置設定标志允許在頁面不足時睡眠等待(1527行),那麼:

A、強制打開中斷(1528行)。因為在關中斷下等待睡眠是非法的,而上層調用函數關閉了中斷,是以此處必須打開。

19、從夥伴系統中配置設定新的頁面,形成一個slab(1530行)。

20、如果配置設定标志允許在頁面不足時睡眠等待(1532行),則說明前面強制打開了中斷,那麼:

A、強制關閉中斷(1533行),與1528行比對。

21、如果成功的從夥伴系統中配置設定到頁面(1535行),那麼:

A、獲得目前CPU緩存的slab對象(1536行)。

B、如果目前CPU緩存的slab對象真實有效(1537行),那麼:

BA、調用flush_slab解除目前slab對象與CPU之間的關系(1538行)。

C、為slab而鎖住新配置設定的頁面(1539行)。

D、設定目前頁面frozen标志,表示目前配置設定的頁面用于目前CPU的頁面配置設定,避免被其他CPU競争走(1540行)。

E、将新配置設定的頁面與目前CPU綁定(1541行),這樣新頁面将可用于SLUB配置設定器。

F、跳轉到1502行(1542行),進入正常的配置設定流程。

22、否則,從夥伴系統中配置設定頁面失敗,向調用者傳回NULL(1544行)。

23、後續代碼用于調試(1545行),略。

當解除slab與CPU之間的綁定關系時,會調用deactivate_slab函數。該函數會将CPU緩存對象的freelist中的對象,還給slab對象。分析如下:

1、獲得slab對象的第一個頁面(1398行)。

2、周遊CPU緩存對象freelist(1404行)。

3、獲得第一個空閑對象(1409行)。

4、使CPU緩存對象空閑連結清單指向下一個空閑對象(1409行),也就是将freelist的第一個對象從CPU緩存對象中摘除。

5、将slab對象的第一個對象連結到剛剛摘除下來的隊列後面(1412行)。

6、将slab的空閑連結清單頭指向剛剛摘除的對象(1413行)。也就是将剛摘除的對象連結到slab的空閑連結清單中。

7、遞減slab的使用計數(1414行)。

8、循環,直到将所有空閑連結清單中的對象全部返還給slab(1415行)。

9、CPU緩存對象已經解決與slab頁面的綁定,設定其slab頁面對象為NULL(1416行)。

10、調用unfreeze_slab(1417行)。該函數将頁面歸還給kmem_cache的NUMA節點緩存,或者将其歸還給夥伴系統,視情況而定。

3.3.5.kmem_cache_free

kmem_cache_free是SLUB記憶體配置設定器的釋放接口。位于mm/slub.c的第1697行。其函數原型是:

void kmem_cache_free(struct kmem_cache *s, void *x)

參數及傳回值含義如下:

s:kmem_cache描述符

x:要釋放的記憶體對象位址

kmem_cache_free的實作如下:

1、根據對象位址,找到其所在的slab對象(1724行),具體查找方法如下:

A、根據對象位址找到其頁面page結構。

B、在page結構中,根據first_page找到夥伴系統中的領頭頁面。

2、調用slab_free将對象傳回給slab。

3.3.6.slab_free

slab_free實作真正的釋放工作,其實作如下:

1、關閉目前CPU本地中斷(1707行)。

2、安全性檢查工作(1708行),略。

3、獲得目前CPU緩存slab對象(1709行)。

4、如果(1)要釋放的記憶體位于CPU緩存slab對象中,并且(2)目前CPU緩存slab對象的節點編号大于等于0(一般是滿足的,小于0的情況,隻存在于作者的調試代碼中)(1710行),那麼進入快速釋放流程:

A、将freelist連結到被釋放對象的後面(1711行)

B、将freelist指向目前對象(1712行),實際上是将目前對象置為freelist頭。

5、否則調用__slab_free進入慢速釋放流程。

6、打開中斷。

__slab_free的實作如下:

1、獲得slab的自旋鎖(1643行)

2、處理slab的調試資訊(1645行),略。

3、将slab的空閑連結清單連結到目前對象後面(1649行)。

4、将目前對象作為slab的空閑連結清單頭(1650)。

5、遞減slab的使用計數(1651行)。

6、如果目前頁面有frozen标志(1653行),表示該slab由目前CPU鎖定,其他CPU不能在此slab中配置設定。是以不能将其歸還給節點或者夥伴系統。那麼:

A、跳轉到un_lock标簽(1654行),并退出。

7、如果目前slab全部對象都被釋放了(1656行),那麼:

A、跳轉到slab_empty,将頁面釋放回夥伴系統。

8、如果在釋放對象之前,slab是全滿的,現在變為半滿了(1664行),那麼:

A、調用add_partial_tail将其添加到NUMA節點的半滿連結清單中。

9、釋放slab的自旋鎖。

10、如果(1)釋放目前對象之前,slab為半滿狀态。(2)目前已經全空(1672行),那麼:

A、從NUMA半滿連結清單中摘除(1676行)。

11、釋放slab的自旋鎖。

12、将頁面歸還給夥伴系統(1680行)。