Slab 算法

        作爲一種出名的設計及算法 Slab 已經很早就出現在各大系統中了,Linux 也實現了 Slab,那 Slab 一般作什麼用途?解決了什麼問題?如何實現?爲什麼要這樣實現?這就是今天要討論的內容了。
        我們知道 Buddy 系統解決了物理內存分配的外碎片問題,但由於粒度太大,都是以頁爲單位,顯然用起來有些浪費,當如果要申請一些小的內存,並且會頻繁的申請相同數據結構的內存來存儲一些內核中的數據時,這時 Slab 便應運而生了。在內核中,經常會使用一些鏈表,鏈表中會申請許多相同結構的結構體,比如文件對象,進程對象等等,如果申請比較頻繁,那麼爲它們建立一個內存池,內存池中都是相同結構的結構體,當想申請這種結構體時,直接從這種內存池中取一個結構體出來,想必是有用且速度極快的。一個物理頁就可以作用這種內存池的載體,進而進行充分利用,減少了內碎片的產生。
        所以,首先,Slab 相當於內存池是思想,且是爲了解決內碎片而產生的。

        我們看一下 Slab 算法的結構圖。


        即使是專有結構的內存池,所以一個高速緩存即 kmem_cache ,就代表一個結構體的內存池,它有一個每 CPU 數據 array, 進一步加快了申請速度,解決了多 CPU 加鎖,且小數據緩存的目的,因爲保留少量的頻繁申請和釋放得來的空間,等下次申請時直接從這裏取得,由於結構簡單,所以速度極快。所以每一個 CPU 都會對應一個 array_cache 結構體,在內存佈局上,該結構體後面,緊接着有一個數組,數組項就是一些結構體內存的指針,vail  即可計算空閒項的下標,這樣,當出現一個申請請求時,直接從這裏取一個結構體塊的指針,(這裏的結構體我們稱之爲對象)然後返回,效率極高。釋放時,原理相同,細節很簡單,這裏不討論。
        那對象的具體內存是在哪裏呢?
        這裏稱之爲對象,你沒看錯,Slab 採用了面向對象的概念,每一個結構體我們看作是一個對象,它們有共用的構造和析構的方法,其實就是爲一些結構體賦值和釋放。它們存放的位置纔是 slab 的關鍵所在,在每一種對象內存池中,即 kmem_cache 中,有三個鏈表,slabs_partial, slabs_full, slabs_free, 鏈表元素都是 slab 結構體,而 slab 結構體所描述的就是對象的內存空間,顧名思義,這三個鏈表分別代表,slab 裏對象部分被裝滿,全滿,全空三種鏈表,重點就在這 slab 結構裏。
        每一個 Slab 描述了這種類型對象內存池中的一小部分內存,它會描述一段對象數組的使用情況,通過 slab 描述符可以得到一些未使用的對象的地址。從這句話裏,可以知道,一個 slab 描述的對象的內存都相當於數組般連續排列的。這個數組的起始地址非常重要,因爲考慮到了 Cache line,所以起始地址都會以 cache line 對齊,這樣理想情況下,對象會被裝入一整條 cache line 中,也容易再次命中。但如果兩個 slab 中的對象對齊到同一 cache line ,事必會造成 cache line 不命中而重新讀取 RAM,所以儘量使每個 slab 的對象的開始地址分配到不同 cache line 中,就有了每個 slab 都會有一個 colouroff 作爲隨機種子,來使對象起始地址分散到不同 cache line 中。但我認爲這種效果意義不大,但有了這個隨機種子,總是會起到一點效果的。
        對象所佔用的空間也會進行取整,規則如下:
        1. 如果對象的大小大於 cache line 的一半,那麼就以 L1_CACHE_BYTES 的倍數對齊對象;
        2.否則,對象大小就是 L1_CACHE_BYTES 的因子取整,這樣一個小對象,就不會跨越兩個 cache line。即 cache line 一直除以 2 ,找到一個剛好大於對象大小的值,作爲對象的大小來處理。
        這們,當對象的起始地址,以及大小決定以後,然後再從物理頁中安排這些對象數組就變得簡單了。此時有個情況,因爲當從夥伴系統中申請物理頁來作爲對象緩衝池時,既可以把 slab 的描述符與它描述的對象池放在一起,也可以分開放,放在一起,即 slab 描述符後面,就是對象池,反之,就是分開存儲,兩者皆可,取決於 slab 中存放的對象的個數。
        講到這裏,某一對象的緩衝池差不多建好了,那麼一個 slab 描述符如何描述自己的對象池呢,如上圖,每一個 slab 描述符後面,緊接着是它所描述對象的對象描述符,對象描述符是一個無符號整型數,只有在它所描述的對象空閒時纔有意義。當某一對象空閒時,它的描述符包含下一個空閒對象在該 slab 中的下標,最後一個空閒對象的對象描述符的值爲 BUFCTL_END. 這樣便形成了一個空閒鏈表。
        總結一下,slab 算法中申請某一對象的情景,當爲某一結構體創建一個高速緩存時,會調用,kmem_cache_create 來創建一個上圖中的結構,此時就可以從該高速緩衝申請該結構體的內存了,申請是通過 kmem_cache_alloc, 來分配,它首先檢查每 CPU 高速緩存中是否可以得到一個空閒對象,如果沒有,那麼它從 slab 鏈表中選取一個,得到空閒對象,去填充每 CPU 高速緩存,然後再從每 CPU 高速緩存中返回一個空閒對象的地址,釋放過程與之相反。

發佈了67 篇原創文章 · 獲贊 813 · 訪問量 16萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章