對於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頁面已經被切割成兩段,並且在公共的鄰居節點中。這個時候反而是增加了內存的髒度。後仔細看代碼發現,源碼中有這段:
<SPAN ><SPAN >struct kmem_cache_cpu {注意到struct page *page;了嗎?之前一直忽略它。並且真相在分配的函數裡面,
如果freelist無效的話,會多查詢一次page頁的地址。
2011年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共享同個頁面,這就違背了裡面緩存命中的功能。