首先,linux的核心是大核心,也就是說所有的功能都在一個核心中實作,沒有像微核心那樣被子產品化實作,如此一來就會出現很多的共享資料結構,比如一個資料結構一個子產品使用,另一個子產品也使用,那麼應該如何實作一個核心機制使得各個子產品可以安全的使用共享資料就成了一件很重要的事情,這裡的安全的意義就是絕對不能通路到一個已經被釋放的記憶體資料結構。linux采用了一種懶惰的方式來管理共享資料結構,也就是說使用引用計數,如果有子產品使用一個資料結構,那麼在使用前就将該資料結構的引用計數加1,如果要釋放,那麼就減1,這樣一來的,每一個使用者就隻管自己就可以了,懶惰的計數管理邏輯會處理好什麼時候徹底釋放記憶體,基本上都是在引用計數為0的時候釋放記憶體,當然也可以實作其他政策,事實上,引用計數法實作安全通路原理很簡單,隻要引用計數不為0,那麼計數管理器就不會釋放記憶體,而使用者在使用前要遞增其引用計數,那麼使用中該引用計數肯定不為0,該對應的資料結構也就不會被釋放了,而隻有當使用完這個資料結構之後才會遞減引用計數,這個懶惰的管理方式保證隻要本次通路的資料結構沒有被釋放就可以了,它不會去顧及别的使用者,然而這個機制很有效,不是嗎?
原理很簡單,已經有了,接下來核心如何實作它呢?最顯然的方式就是在每一個需要共享的資料結構中設定一個原子類型的引用計數字段,然後在使用前和使用後調用atomic_inc和atomic_dec就可以了,在調用atomic_dec之後要判斷一下是否引用計數為0,如果是的話就釋放該資料結構,是以可以使用核心的另外一個函數atomic_dec_and_test,我們來評估一下這個方案,其實很不錯,但是核心中會充斥着備援的代碼,比如隻要使用該資料結構的地方都會存在atomic_dec_and_test調用,是以如果能将其封裝成一個函數或者将這個引用計數封裝成一個類似OO中類的東西,然後提供get遞增方式二put遞減方式就可以了,這就是kref的引入:
struct kref {
atomic_t refcount;
};
get操作就不說了,其實就是inc引用計數,其實如果核心是OO語言寫的,get和put可以寫到kref類裡面的,并且struct也成了class,不過難道機器會在乎怎麼寫嗎?它隻在乎效率而不是可讀性和可維護性,對機器來說,給它什麼它就執行什麼,21世紀給它一個16程式,它不會有任何怨言的。下面看一下put,注意提供了回調函數:
int kref_put(struct kref *kref, void (*release)(struct kref *kref))
{
...//一些安全相關的判斷,release不能是kfree函數,但是也不是絕對不能是,主要是由于本身kref就是幫忙管理引用計數的,而release函數的本意應該是做kref宿主結構體的善後工作而不是kref本身的,再說一旦你調用kfree,那kref就沒有了,引用計數都沒有了還怎麼管理,萬一有别的子產品在使用該結構體怎麼辦,核心中絕對杜絕通路到空指針的,但是政策的事誰也不好說,全憑程式員怎麼定義和實作了
if (atomic_dec_and_test(&kref->refcount)) {
release(kref); //如果計數為0,那麼就釋放,這個回調函數由調用者提供
return 1;
}
return 0;
我個人覺得,按照封裝的思想,下面的實作會好一點:
struct kref_V2 {
void (*release)(struct kref_V2 *kref);
void (*callback)(void *p); //将一個回調函數和該kref分離,不再在release中做完一切了
void * argv;
int kref_put(struct kref_V2 *kref)
...
release(kref);
if( kref->callback ) //如果計數器為0,那麼就在這裡面調用使用者提供的清理函數,一般是記憶體釋放或者裝置停止
callback( kref->argv );
當然以上的實作僅僅從oo的角度來說比較好,但是核心中還是使用了第一種方式,因為你可以在release這個唯一的使用者提供的回調函數中用container_of得到該kref宿主的位址,然後可以對它做任何事。總之,kref又是linux核心中關于OO的另外一個很好的例子。
本文轉自 dog250 51CTO部落格,原文連結:http://blog.51cto.com/dog250/1273481