對於slub不熟的同學可以先跳過了,涉及的東西比較細緻。
簡單來說slub的結構是n(cpu數)個kmem_cache_cpu,和乙個kmem_cache_node組成。其中kmem_cache_cpu的目的是為了從技術層面上提高cpu命中快取,以及在同乙個頁面上不出現乙個髒的記憶體(即不同時被多個cpu持有)。我把這個實現機制手工在windows下實現了一套,在開啟多個kmem_cache_cpu的時候出現了個問題:
1.在釋放物件的時候,判斷是否是當前kmem_cache_cpu頁面所在
2.如果是,則直接插入
3.如果不是,釋放到鄰居節點
如果是單個kmem_cache_cpu肯定沒問題,但是在多個kmem_cache_cpu下,很可能會把其中乙個kmem_cache_cpu已經持有的頁面釋放到鄰居節點。舉個例子:
假設乙個頁面位址是0-9,
kmem_cache_cpu1,持有a頁面,空閒的情況為a0-a5
kmem_cache_cpu2,持有b頁面,
當輪到kmem_cache_cpu2執行的時候,乙個a頁面的位址a6釋放,程式檢測到非b頁面,則直接釋放到鄰居節點。那麼這個時候a頁面已經被切割成兩段,並且在公共的鄰居節點中。這個時候反而是增加了記憶體的髒度。後仔細看**發現,原始碼中有這段:
struct kmem_cache_cpu ;
注意到
struct page *page;了嗎?之前一直忽略它。並且真相在分配的函式裡面,
如果freelist無效的話,會多查詢一次page頁
的位址。
2023年1月15日增
細緻看了下,覺得這個地方還是有bug。
釋放上就兩個步驟
1)走本地page
2)node節點
申請上比較複雜
1)走本地freelist
2)走本頁page
3)走鄰居節點
4)走系統
並且做了一些看似應該釋放做的事:
1)如果page和freelist都為空,則走deactivate_slab();
deactivate_slab中有很多相容的判斷,要一一捨棄。閱讀原始碼最痛苦的就是這點。
其中用到了個值page->inuse。這個值很奇怪,它指的是在kmem_cache_cpu當前+使用中的物件,如果釋放了則
進行減一,所以你看不到他的++。
1)如果page->inuse大於0,並且freelist還有值,加入鄰居。這個估計是相容的**,之前的判斷freelist不為空
2)反之,加到鄰居鍊錶。
3)如果當前鄰居鍊錶滿,則釋放掉
2)如果freelist為空,page不為空,很可能其他的kmem_cache_cpu釋放了物件,則走load_freelist:
疑問一:
就是加入鄰居鍊錶和釋放加入鄰居鍊錶的衝突。從條件上看,其實兩個都是依賴page->freelist是否為空加入,但是在deactivate_slab加入鄰居節點後,並沒有對page->freelist進行處理,如果這時候有物件釋放,是否會造成重複加入?
這個疑問是我弄錯了,如果page->inuse==0,表示物件都已經釋放,不會觸發再次釋放
疑問二:在多個kmem_cache_cpu情況下,釋放的元素都是放到共同的鄰居頁面,很有可能被其他的kmem_cache_cpu直接取到,這樣就造成2個不同的kmem_cache_cpu共享同個頁面,這就違背了裡面快取命中的功能。
Linux系統的記憶體分配
bss block started by symbol 通常是指用來存放程式中未初始化的全域性變數和靜態變數的一塊記憶體區域。特點是 可讀寫的,在程式執行之前bss段會自動清0。所以,未初始的全域性變數在程式執行之前已經成0了。資料段 資料段 data segment 通常是指用來存放程式中已初始化...
Linux下的記憶體分配
在空間中的記憶體分配如圖所示 通過以下程式可以更加清晰的看出位址之間的聯絡 include int add int a,int const e int main void char str2 10 printf allover eval a p n a printf allover null b p...
Linux下的記憶體分配
1.夥伴演算法 一種物理記憶體分配和 的方法,物理記憶體所有空閒頁都記錄在buddy鍊錶中。首選,系統建立乙個鍊錶,鍊錶中的每個元素代表一類大小的物理記憶體,分別為2的0次方 1次方 2次方,個頁大小,對應4k 8k 16k的記憶體,沒一類大小的記憶體又有乙個鍊錶,表示目前可以分配的物理記憶體。例如...