做爲這個系列的第一篇,我先來描述一下slab系統。由於近些天有和同事,朋友討論過這個主題,並且以爲這個主 題還算比較典型,因此就做爲第一篇了。其實按照操做系統理論來說,進程管理應該更加劇要些,按照我本身的興趣來說,IO管理以及TCP/IP協議棧會更加 有份量,關於這些內容,我會陸續給出。
算法
Linux內核的slab來自一種很簡單的思想,即事先準備好一些會頻繁分配,釋放的數據結構。然而標準的slab實現太複雜且維護開銷巨大,所以便分化 出了更加小巧的slub,所以本文討論的就是slub,後面全部提到slab的地方,指的都是slub。另外又因爲本文主要描述內核優化方面的內容,並不 是基本原理介紹,所以想了解slab細節以及代碼實現的請自行百度或者看源碼。
緩存
下圖給出了單CPU上slab在分配和釋放對象時的情景序列:
網絡
能夠看出,很是之簡單,並且徹底達到了slab設計之初的目標。
數據結構
如今咱們簡單的將上面的模型擴展到多核心CPU,一樣差很少的分配序列以下圖所示:
負載均衡
咱們看到,在只有單一slab的時候,若是多個CPU同時分配對象,衝突是不可避免的,解決衝突的幾乎是惟一的辦法就是加鎖排隊,然而這將大大增長延遲,咱們看到,申請單一對象的整個時延從T0開始,到T4結束,這過久了。
多CPU無鎖化並行化操做的直接思路-複製給每一個CPU一套相同的數據結構。
不二法門就是增長「每CPU變量」。對於slab而言,能夠擴展成下面的樣子:
框架
若是覺得這麼簡單就結束了,那這就太沒有意義了。
ide
首先,咱們來看一個簡單的問題,若是單獨的某個CPU的slab緩存沒有對象可分配了,可是其它CPU的slab緩存仍有大量空閒對象的狀況,以下圖所示:
優化
這 是可能的,由於對單獨一種slab的需求是和該CPU上執行的進程/線程緊密相關的,好比若是CPU0只處理網絡,那麼它就會對skb等數據結構有大量的 需求,對於上圖最後引出的問題,若是咱們選擇從夥伴系統中分配一個新的page(或者pages,取決於對象大小以及slab cache的order),那麼長此以往就會形成slab在CPU間分佈的不均衡,更可能會所以吃掉大量的物理內存,這都是不但願看到的。
在繼續以前,首先要明確的是,咱們須要在CPU間均衡slab,而且這些必須靠slab內部的機制自行完成,這個和進程在CPU間負載均衡是徹底不一樣的, 對進程而言,擁有一個核心調度機制,好比基於時間片,或者虛擬時鐘的步進速率等,可是對於slab,徹底取決於使用者自身,只要對象仍然在使用,就不能剝 奪使用者繼續使用的權利,除非使用者本身釋放。所以slab的負載均衡必須設計成合做型的,而不是搶佔式的。
好了。如今咱們知道,從夥伴系統從新分配一個page(s)並非一個好主意,它應該是最終的決定,在執行它以前,首先要試一下別的路線。
如今,咱們引出第二個問題,以下圖所示:
spa
誰 也不能保證分配slab對象的CPU和釋放slab對象的CPU是同一個CPU,誰也不能保證一個CPU在一個slab對象的生命週期內沒有分配新的 page(s),這期間的複雜操做誰也沒有規定。這些問題該怎麼解決呢?事實上,理解了這些問題是怎麼解決的,一個slab框架就完全理解了。
操作系統
無級變速老是讓人嚮往。
若是一個CPU的slab緩存滿了,直接去搶同級別的別的CPU的slab緩存被認爲是一種魯莽且不道義的作法。那麼爲什麼不設置另一個slab緩存,獲 取它裏面的對象不像直接獲取CPU的slab緩存那麼簡單且直接,可是難度卻又不大,只是稍微增長一點消耗,這不是很好嗎?事實上,CPU的 L1,L2,L3 cache不就是這個方案設計的嗎?這事實上已經成爲cache設計的不二法門。這個設計思想一樣做用於slab,就是Linux內核的slub實現。
如今能夠給出概念和解釋了。
Linux kernel slab cache:一個分爲3層的對象cache模型。
Level 1 slab cache:一個空閒對象鏈表,每一個CPU一個的獨享cache,分配釋放對象無需加鎖。
Level 2 slab cache:一個空閒對象鏈表,每一個CPU一個的共享page(s) cache,分配釋放對象時僅須要鎖住該page(s),與Level 1 slab cache互斥,不互相包容。
Level 3 slab cache:一個page(s)鏈表,每一個NUMA NODE的全部CPU共享的cache,單位爲page(s),獲取後被提高到對應CPU的Level 1 slab cache,同時該page(s)做爲Level 2的共享page(s)存在。
共享page(s):該page(s)被一個或者多個CPU佔 有,每個CPU在該page(s)上均可以擁有互相不充圖的空閒對象鏈表,該page(s)擁有一個惟一的Level 2 slab cache空閒鏈表,該鏈表與上述一個或多個Level 1 slab cache空閒鏈表亦不衝突,多個CPU獲取該Level 2 slab cache時必須爭搶,獲取後能夠將該鏈表提高成本身的Level 1 slab cache。
該slab cache的圖示以下:
其行爲以下圖所示:
對於常規的對象分配過程,下圖展現了其細節:
事實上,對於多個CPU共享一個page(s)的狀況,還能夠有另外一種玩法,以下圖所示:
前面咱們簡短的體會了Linux內核的slab設計,不宜過長,太長了不易理解.可是最後,若是Level 3也沒有獲取page(s),那麼最終會落到終極的夥伴系統。
夥伴系統是爲了防內存分配碎片化的,因此它儘量地作兩件事:
咱們能夠經過下面的圖解來理解上面的原則:
注意,本文是關於優化的,不是夥伴系統的科普,因此我假設你們已經理解了夥伴系統。
鑑於slab緩存對象大多數都是不超過1個頁面的小結構(不只僅slab系統,超過1個頁面的內存需求相比1個頁面的內存需求,不多),所以會有大量的針 對1個頁面的內存分配需求。從夥伴系統的分配原理可知,若是持續大量分配單一頁面,會有大量的order大於0的頁面分裂成單一頁面,在單核心CPU上, 這不是問題,可是在多核心CPU上,因爲每個CPU都會進行此類分配,而夥伴系統的分裂,合併操做會涉及大量的鏈表操做,這個鎖開銷是巨大的,所以須要 優化!
Linux內核對夥伴系統針對單一頁面的分配需求採起的批量分配「每CPU單一頁面緩存」的方式!
每個CPU擁有一個單一頁面緩存池,須要單一頁面的時候,能夠無需加鎖從當前CPU對應的頁面池中獲取頁面。而當池中頁面不足時,系統會批量從夥伴系統中拉取一堆頁面到池中,反過來,在單一頁面釋放的時候,會擇優將其釋放到每CPU的單一頁面緩存中。
爲了維持「每CPU單一頁面緩存」中頁面的數量不會太多或太少(太多會影響夥伴系統,太少會影響CPU的需求),系統保持了兩個值,當緩存頁面數量低於 low值的時候,便從夥伴系統中批量獲取頁面到池中,而當緩存頁面數量大於high的時候,便會釋放一些頁面到夥伴系統中。
多 CPU操做系統內核中,關鍵的開銷就是鎖的開銷。我認爲這是一開始的設計致使的,由於一開始,多核CPU並無出現,單核CPU上的共享保護幾乎都是能夠 用「禁中斷」,「禁搶佔」來簡單實現的,到了多核時代,操做系統一樣簡單平移到了新的平臺,所以同步操做是在單核的基礎上後來添加的。簡單來說,目前的主 流操做系統都是在單核年代創造出來的,所以它們都是順應單核環境的,對於多核環境,可能它們一開始的設計就有問題。
無論怎麼說,優化操做的不二法門就是禁止或者儘可能減小鎖的操做。隨之而來的思路就是爲共享的關鍵數據結構建立"每CPU的緩存「,而這類緩存分爲兩種類型:
好比路由表之類的數據結構,你能夠用RCU鎖來保護,固然若是爲每個CPU都建立一個本地路由表緩存,也是不錯的,如今的問題是什麼時候更新它們,由於全部的緩存都是平級的,所以一種批量同步的機制是必須的。
比 如slab對象緩存這類,其生命週期徹底取決於使用者,所以不存在同步問題,然而卻存在管理問題。採用分級cache的思想是好的,這個很是相似於CPU 的L1/L2/L3緩存,採用這種平滑的開銷逐漸增大,容量逐漸增大的機制,並配合以設計良好的換入/換出等算法,效果是很是明顯的。