感覺面試的時候經(jīng)常會被問到這個問題,然后我也學(xué)習(xí)了一下Memcached的slab機制,發(fā)現(xiàn)很多服務(wù)器都是使用這種機制來分配內(nèi)存,所以決定學(xué)習(xí)一下。
首先,先對內(nèi)存分配中的伙伴系統(tǒng)有初步的了解:
在編程和使用的服務(wù)器軟件中,經(jīng)常需要分配一組連續(xù)的頁框,而頻繁地申請和釋放不同大小的連續(xù)頁框,必然導(dǎo)致在已分配頁框的內(nèi)存塊中分散了許多小塊的空閑頁框。這樣,即使這些頁框是空閑的,但要分配一個大塊的連續(xù)頁框就可能無法滿足。 而linux采用了伙伴系統(tǒng)來解決上述難題。把所有的空閑頁框分組為11個塊鏈表,每個塊鏈表分別包含大小為1,2,4,8,16,32,64,128,256,512和1024個連續(xù)頁框的頁框塊。最大可以申請1024個連續(xù)頁框,對應(yīng)4MB大小的連續(xù)內(nèi)存。每個頁框塊的第一個頁框的物理地址是該塊大小的整數(shù)倍。例如,大小為16個頁框的塊,其起始地址是16×212的倍數(shù)。假設(shè)要申請一個256個頁框的塊,先從256個頁框的鏈表中查找空閑塊,如果沒有,就去512個頁框的鏈表中找,找到了則將頁框塊分為2個256個頁框的塊,一個分配給應(yīng)用,另外一個移到256個頁框的鏈表中。如果512個頁框的鏈表中仍沒有空閑塊,繼續(xù)向1024個頁框的鏈表查找,如果仍然沒有,則返回錯誤。頁框塊在釋放時,內(nèi)核會主動將兩個互為伙伴的頁框塊合并為一個較大的頁框塊,成功后會試圖尋找伙伴并合并為更大的內(nèi)存塊,直至塊的大小超過上限或者沒有伙伴為止。互為伙伴的兩個內(nèi)存塊必須符合以下條件: 1、兩個塊具有相同的大小; 2、兩個塊的物理地址連續(xù); 3、第一個快的物理地址是兩個塊大小的整數(shù)倍。 slab分配機制則是對伙伴算法的改進(jìn),slab(Slab Allocation)的設(shè)計理念是基于對象緩沖的,基本想法是避免重復(fù)大量的初始化和清理操作。slab主要可以用于頻繁非配釋放的內(nèi)存對象。替代malloc/free 改進(jìn)的地方在于:它對內(nèi)存區(qū)的處理并不需要進(jìn)行初始化或回收。出于效率的考慮,Linux并不調(diào)用對象的構(gòu)造或析構(gòu)函數(shù),而是把指向這兩個函數(shù)的指針都置為空。Linux中引入Slab的主要目的是為了減少對伙伴算法的調(diào)用次數(shù)。
實際上,內(nèi)核經(jīng)常反復(fù)使用某一內(nèi)存區(qū)。例如,只要內(nèi)核創(chuàng)建一個新的進(jìn)程,就要為該進(jìn)程相關(guān)的數(shù)據(jù)結(jié)構(gòu)(task_struct、打開文件對象等)分配內(nèi)存區(qū)。當(dāng)進(jìn)程結(jié)束時,收回這些內(nèi)存區(qū)。因為進(jìn)程的創(chuàng)建和撤銷非常頻繁,因此,Linux的早期版本把大量的時間花費在反復(fù)分配或回收這些內(nèi)存區(qū)上。從Linux2.2開始,把那些頻繁使用的頁面保存在高速緩存中并重新使用。可以根據(jù)對內(nèi)存區(qū)的使用頻率來對它分類。對于預(yù)期頻繁使用的內(nèi)存區(qū),可以創(chuàng)建一組特定大小的專用緩沖區(qū)進(jìn)行處理,以避免內(nèi)碎片的產(chǎn)生。對于較少使用的內(nèi)存區(qū),可以創(chuàng)建一組通用緩沖區(qū)(如Linux2.0中所使用的2的冪次方)來處理,即使這種處理模式產(chǎn)生碎片,也對整個系統(tǒng)的性能影響不大。
硬件高速緩存的使用,又為盡量減少對伙伴算法的調(diào)用提供了另一個理由,因為對伙伴算法的每次調(diào)用都會“弄臟”硬件高速緩存,因此,這就增加了對內(nèi)存的平均訪問次數(shù)。
Slab分配模式把對象分組放進(jìn)緩沖區(qū)
對于小對象, 就把Slab的描述結(jié)構(gòu)slab_t放在該Slab中;對于大對象,則把Slab結(jié)構(gòu)游離出來,集中存放。關(guān)于Slab中的著色區(qū)再給予具體描述: 每個Slab的首部都有一個小小的區(qū)域是不用的,稱為“著色區(qū)(coloring area)”。著色區(qū)的大小使Slab中的每個對象的起始地址都按高速緩存中的”緩存行(cache line)”大小進(jìn)行對齊(80386的一級高速緩存行大小為16字節(jié),Pentium為32字節(jié))。因為Slab是由1個頁面或多個頁面(最多為32)組成,因此,每個Slab都是從一個頁面邊界開始的,它自然按高速緩存的緩沖行對齊。但是,Slab中的對象大小不確定,設(shè)置著色區(qū)的目的就是將Slab中第一個對象的起始地址往后推到與緩沖行對齊的位置。因為一個緩沖區(qū)中有多個Slab,因此,應(yīng)該把每個緩沖區(qū)中的各個Slab著色區(qū)的大小盡量安排成不同的大小,這樣可以使得在不同的Slab中,處于同一相對位置的對象,讓它們在高速緩存中的起始地址相互錯開,這樣就可以改善高速緩存的存取效率。 每個Slab上最后一個對象以后也有個小小的廢料區(qū)是不用的,這是對著色區(qū)大小的補償,其大小取決于著色區(qū)的大小,以及Slab與其每個對象的相對大小。但該區(qū)域與著色區(qū)的總和對于同一種對象的各個Slab是個常數(shù)。 每個對象的大小基本上是所需數(shù)據(jù)結(jié)構(gòu)的大小。只有當(dāng)數(shù)據(jù)結(jié)構(gòu)的大小不與高速緩存中的緩沖行對齊時,才增加若干字節(jié)使其對齊。所以,一個Slab上的所有對象的起始地址都必然是按高速緩存中的緩沖行對齊的。參考:深入分析Linux內(nèi)核源碼http://oss.org.cn/kernel-book/ Memcached 源碼分析新聞熱點
疑難解答