一:準備知識:
前面咱們分析過了大內存分配的實現機制,事實上,若爲小塊內存而請求整個頁面,這樣對於內存來講是一種極度的浪費。所以linux採用了slab來管理小塊內存的分配與釋放。Slab最先是由sun的工程師提出。它的提出是基於如下因素考慮的:
1:內核函數常常傾向於反覆請求相同的數據類型。好比:建立進程時,會請求一塊內存來存放mm結構。
2:不一樣的結構使用不一樣的分配方法能夠提升效率。一樣,若是進程在撤消的時候,內核不把mm結構釋放掉,而是存放到一個緩衝區裏,之後如有請求mm存儲空間的行爲就能夠直接從緩衝區中取得,而不需從新分配內存.
3:前面咱們曾分析過,若是夥伴系統頻繁分配,釋放內存會影響系統的效率,以此,能夠把要釋放到的內存放到緩衝區中,直至超過一個閥值才把它釋放至夥伴系統,這樣能夠在必定程度上緩減減夥伴系統的壓力
4:爲了緩減「內碎片」的產生,一般能夠把小內存塊按照2的倍數組織在一塊兒,這一點和夥伴系統相似
二:slab分配器概貌:
Slab將緩存分爲兩種:一種是專用高速緩存,另一種是普通高速緩存。請注意,這裏所說的高速緩存和硬件沒有必然的關係,它只是slab分配器中的一個軟件概念。
專用高速緩存中用來存放內核使用的數據結構,例如:mm,skb,vm等等
普通高速緩存是指存放通常的數據,好比內核爲指針分配一段內存
全部的高速緩存區都經過鏈表的方式組織在一塊兒,它的首結點是cache_chain
另外,普通高速緩存將分配區分爲32*(2^0),32*(2^1),32*(2^2) ….32*(2^12)大小,共13個區域大小,另外,每一個大小均有兩個高速緩存,一個爲DMA高速緩存,一個是常規高速緩存。它們都存放在一個名這cache_size的表中.
Slab分配器把每個請求的內存稱之爲對象(和oop設計方法中的對象相似,都有初始化與析構).對象又存放在slab中.slab又按照空,末滿,全滿所有連接至高速緩存中
linux
相當於早期的Lookaside
良好的操做系統性能部分依賴於操做系統有效管理資源的能力。在過去,堆內存管理器是實際的規範,可是其性能會受到內存碎片和內存回收需求的影響。如今,Linux® 內核使用了源自於 Solaris 的一種方法,可是這種方法在嵌入式系統中已經使用了很長時間了,它是將內存做爲對象按照大小進行分配。本文將探索 slab 分配器背後所採用的思想,並介紹這種方法提供的接口和用法。
動態內存管理
內存管理的目標是提供一種方法,爲實現各類目的而在各個用戶之間實現內存共享。內存管理方法應該實現如下兩個功能:
最小化管理內存所需的時間
最大化用於通常應用的可用內存(最小化管理開銷)
內存管理其實是一種關於權衡的零和遊戲。您能夠開發一種使用少許內存進行管理的算法,可是要花費更多時間來管理可用內存。也能夠開發一個算法來有效地管理內存,但卻要使用更多的內存。最終,特定應用程序的需求將促使對這種權衡做出選擇。
每一個內存管理器都使用了一種基於堆的分配策略。在這種方法中,大塊內存(稱爲 堆)用來爲用戶定義的目的提供內存。當用戶須要一塊內存時,就請求給本身分配必定大小的內存。堆管理器會查看可用內存的狀況(使用特定算法)並返回一塊內存。搜索過程當中使用的一些算法有 first-fit(在堆中搜索到的第一個知足請求的內存塊)和 best-fit(使用堆中知足請求的最合適的內存塊)。當用戶使用完內存後,就將內存返回給堆。
這種基於堆的分配策略的根本問題是碎片(fragmentation)。當內存塊被分配後,它們會以不一樣的順序在不一樣的時間返回。這樣會在堆中留下一些洞,須要花一些時間纔能有效地管理空閒內存。這種算法一般具備較高的內存使用效率(分配須要的內存),可是卻須要花費更多時間來對堆進行管理。
另一種方法稱爲 buddy memory allocation,是一種更快的內存分配技術,它將內存劃分爲 2 的冪次方個分區,並使用 best-fit 方法來分配內存請求。當用戶釋放內存時,就會檢查 buddy 塊,查看其相鄰的內存塊是否也已經被釋放。若是是的話,將合併內存塊以最小化內存碎片。這個算法的時間效率更高,可是因爲使用 best-fit 方法的緣故,會產生內存浪費。
本文將着重介紹 Linux 內核的內存管理,尤爲是 slab 分配提供的機制。
slab 緩存
Linux 所使用的 slab 分配器的基礎是 Jeff Bonwick 爲 SunOS 操做系統首次引入的一種算法。Jeff 的分配器是圍繞對象緩存進行的。在內核中,會爲有限的對象集(例如文件描述符和其餘常見結構)分配大量內存。Jeff 發現對內核中普通對象進行初始化所需的時間超過了對其進行分配和釋放所需的時間。所以他的結論是不該該將內存釋放回一個全局的內存池,而是將內存保持爲針對特定目而初始化的狀態。例如,若是內存被分配給了一個互斥鎖,那麼只需在爲互斥鎖首次分配內存時執行一次互斥鎖初始化函數(mutex_init)便可。後續的內存分配不須要執行這個初始化函數,由於從上次釋放和調用析構以後,它已經處於所需的狀態中了。
Linux slab 分配器使用了這種思想和其餘一些思想來構建一個在空間和時間上都具備高效性的內存分配器。
圖 1 給出了 slab 結構的高層組織結構。在最高層是 cache_chain,這是一個 slab 緩存的連接列表。這對於 best-fit 算法很是有用,能夠用來查找最適合所須要的分配大小的緩存(遍歷列表)。cache_chain 的每一個元素都是一個 kmem_cache 結構的引用(稱爲一個 cache)。它定義了一個要管理的給定大小的對象池。
圖 1. slab 分配器的主要結構
每一個緩存都包含了一個 slabs 列表,這是一段連續的內存塊(一般都是頁面)。存在 3 種 slab:
slabs_full
徹底分配的 slab
slabs_partial
部分分配的 slab
slabs_empty
空 slab,或者沒有對象被分配
注意 slabs_empty 列表中的 slab 是進行回收(reaping)的主要備選對象。正是經過此過程,slab 所使用的內存被返回給操做系統供其餘用戶使用。
slab 列表中的每一個 slab 都是一個連續的內存塊(一個或多個連續頁),它們被劃分紅一個個對象。這些對象是從特定緩存中進行分配和釋放的基本元素。注意 slab 是 slab 分配器進行操做的最小分配單位,所以若是須要對 slab 進行擴展,這也就是所擴展的最小值。一般來講,每一個 slab 被分配爲多個對象。
因爲對象是從 slab 中進行分配和釋放的,所以單個 slab 能夠在 slab 列表之間進行移動。例如,當一個 slab 中的全部對象都被使用完時,就從 slabs_partial 列表中移動到 slabs_full 列表中。當一個 slab 徹底被分配而且有對象被釋放後,就從 slabs_full 列表中移動到 slabs_partial 列表中。當全部對象都被釋放以後,就從 slabs_partial 列表移動到 slabs_empty 列表中。
slab 背後的動機
與傳統的內存管理模式相比, slab 緩存分配器提供了不少優勢。首先,內核一般依賴於對小對象的分配,它們會在系統生命週期內進行無數次分配。slab 緩存分配器經過對相似大小的對象進行緩存而提供這種功能,從而避免了常見的碎片問題。slab 分配器還支持通用對象的初始化,從而避免了爲同一目而對一個對象重複進行初始化。最後,slab 分配器還能夠支持硬件緩存對齊和着色,這容許不一樣緩存中的對象佔用相同的緩存行,從而提升緩存的利用率並得到更好的性能。web