Contents ...
udn網路城邦
slub源码分析
2015/12/14 14:54
瀏覽189
迴響0
推薦0
引用0
從linux-2.6.23開內,SLUB取代了SLAB作為默認的內存分配機制,那麽,SLUB究竟有哪些有點呢?
一 SLAB被SLUB替代的原因
    SLAB 分配器多年以來一直位於 Linux 內核的內存管理部分的核心地帶,內核黑客們一般不願意主動去更改它的代碼,因為它實在是非常復雜,而且在大多數情況下,它的工作完成的相當不錯。但是,隨著大規模多處理器系統和 NUMA系統的廣泛應用,SLAB 分配器逐漸暴露出自身的嚴重不足:
較多復雜的隊列管理。在 SLAB 分配器中存在眾多的隊列,例如針對處理器的本地對象緩存隊列,slab 中空閑對象隊列,每個 slab 處於一個特定狀態的隊列中,甚至緩沖區控制結構也處於一個隊列之中。有效地管理這些不同的隊列是一件費力且復雜的工作。
slab 管理數據和隊列的存儲開銷比較大。每個 slab 需要一個 struct slab 數據結構和一個管理所有空閑對象的 kmem_bufctl_t(4 字節的無符號整數)的數組。當對象體積較少時,kmem_bufctl_t 數組將造成較大的開銷(比如對象大小為32字節時,將浪費 1/8 的空間)。為了使得對象在硬件高速緩存中對齊和使用著色策略,還必須浪費額外的內存。同時,緩沖區針對節點和處理器的隊列也會浪費不少內存。測試表明在一個 1000 節點/處理器的大規模 NUMA 系統中,數 GB 內存被用來維護隊列和對象的引用。
緩沖區內存回收比較復雜。
對 NUMA 的支持非常復雜。SLAB 對 NUMA 的支持基於物理頁框分配器,無法細粒度地使用對象,因此不能保證處理器級緩存的對象來自同一節點。
冗余的 Partial 隊列。SLAB 分配器針對每個節點都有一個 Partial 隊列,隨著時間流逝,將有大量的 Partial slab 產生,不利於內存的合理使用。
性能調優比較困難。針對每個 slab 可以調整的參數比較復雜,而且分配處理器本地緩存時,不得不使用自旋鎖。
調試功能比較難於使用
    總結起來,SLAB太過復雜,光是用在這套機制的管理上開銷就很大(時間和空間),導致性能較差,而且,代碼很難維護。
二 SLUB涉及的主要數據結構
1 kmem_cache: slab的描述結構,即每一個slab對應一個kmem_cache數據結構來描述
2 page:每一個物理頁框(page frame)對應的描述結構,一般稱作頁
3 object: 某一類特定的對象,比如進程描述符task_struct, 文件指針FILE等等
4 kmem_cache_cpu:每一個cpu對應的一個結構,起始他也是一個slab,可以理解為每
  cpu的slab,主要是為了smp時多個核可以並行訪問自己的slab。
5 kmem_cache_node: 每個內存節點(node)的slab描述,在 SLUB 中,沒有單獨的 Empty slab 隊列。每個 NUMA 節點使用 kmem_cache_node 結構維護一個處於 Partial 狀態的 slab 隊列。(這個理解有點模糊)
三 各結構之間的關系
1 每個page至少包含兩個object,否則內核將會按頁分配內存。
2 一個slab至少占用一個page,否則,將會出現同一頁中至少有兩種不同類型的object。
3 每一個slab還指向一個kmem_cache_node和一個kmem_cache_cpu結構(UP情況下)
4 內核在啟動初始化的過程中,默認初始化11個slab,即kmem_caches[12]數組。
四 SLUB初始化
在start_kernel函數中調用下面函數對11個slab進行初始化,這裏只是初始化數據結構,並沒有真正的申請物理頁框。
void __init kmem_cache_init(void)
{
 int i;
 int caches = 0;
 init_alloc_cpu();
 /* Able to allocate the per node structures */
 slab_state = PARTIAL;
 /* Caches that are not of the two-to-the-power-of size */
 if (KMALLOC_MIN_SIZE <= 64) {
  create_kmalloc_cache(&kmalloc_caches[1],
    "kmalloc-96", 96, GFP_KERNEL);
  caches++;
 }
 if (KMALLOC_MIN_SIZE <= 128) {
  create_kmalloc_cache(&kmalloc_caches[2],
    "kmalloc-192", 192, GFP_KERNEL);
  caches++;
 }
 for (i = KMALLOC_SHIFT_LOW; i < PAGE_SHIFT; i++) {
  create_kmalloc_cache(&kmalloc_caches[i],
   "kmalloc", 1 << i, GFP_KERNEL);
  caches++;
 }
... ... ... ...
}
五 SLUB的內存分配(UP情況下)

 先大體講一下整個分配的過程:
1 根據申請的內存大小,假設為size字節,如果size > PAGE_SIZE/2, 就直接分配一頁,否則,到kmem_caches[12]中尋找最合適的slab。
2 因為在SLUB的初始化的時候,並沒有真正的分配頁框,所以在第一次申請內存的時候才去做這個操作。首先,檢查每cpu的slab的freelist,即kmem_cache_cpu.freelist,因為是第一次肯定是空的
3 再去檢查每個node的partial鏈表,即kmem_cache_node.partial,此時肯定也是空的。
4 從buddy系統中分配2^order個頁框,order是在kmem_cache中指定的,並把這些頁框初始化成大小為size的object,並初始化響應的數據結構。
5 將分配到的2^order個頁框第一個頁框對應的page->freelist=object,即指向第一個object對象。並將page結構中的inuse賦值為這個頁框包含的object的數量
6 分配出第一個object,然後將page->freelist=NULL, 將每cpu的slab的freelist賦值為第二個object的地址。(kmem_cache_cpu.freelist = object[offset])
以後再有這種大小的object的請求,就會直接從每cpu的slab的freelist上分配。
六 SLUB的內存釋放
假設釋放地址為x的某個object。
1 通過虛擬地址或先行地址x,可以得到該object屬於哪個page,進而通過page->slab可以得到該object屬於哪個slab。
2 如果當前slab的page指針,即kmem_cache_cpu.page等於要釋放的object屬於的page,那麽,就直接把object釋放到kmem_cache_cpu.freelist中。
3 否則,page->inuse--,然後把object屬於的page添加到kmem_cache_node的partial中。
以上部分就是我對SLUB算法的簡單的理解。


限會員,要發表迴響,請先登入