2.2 引用計數
軟件開發常常面對將一個分配好內存的對象多次傳遞,并在多處使用的場景,我們需要在程序動態運行的現實下,明確知道這些對象使用完畢的時機,以便釋放它所占用的內存。為此,需要引用計數:
? 防止內存泄漏:確保已分配的對象最終會被釋放;
? 防止訪問已釋放的內存:確保不會使用已經被釋放的對象。
數據結構kref為任何內核數據結構添加引用計數提供了一種簡單但有效的方法,kref只有一個元素,其結構中的域如表2-1所示。
表2-1 kref結構中的域(來自文件include/linux/kref.h)
Linux內核提供了以下函數對引用計數進行操作。
1.void kref_init(struct kref *kref);
初始化對象,將對象的引用計數設置為1,而不是0;這是因為生成該對象的代碼也需要一個最初的引用,以防止其他部分在調用kref_put時釋放該對象。
2.void kref_get(struct kref *kref);
遞增對象的引用計數。在這之前,確保引用計數不為0,否則打印一條警告消息。這可以防止常見的錯誤:不先調用kref_init,而直接調用kref_get。
3.int kref_put(struct kref *kref, void (*release) (struct kref *kref));
遞減對象的引用計數。如果該計數減為0,則表明是該對象的最后一個引用,因此傳入的release函數被調用,以回收這個對象用到的內存。
使用上述kref_init、kref_get和kref_put函數,再結合調用者提供的release函數,可以在任何內核結構中加入完整的引用計數。
首先,將kref結構嵌入到需要使用引用計數的結構之中。例如,要在結構foo中添加引用計數,應該如下定義:
struct foo { ... struct kref refcount; ... };
在定義時,需要將整個kref結構嵌入結構foo中,而不是一個指向kref結構的指針。
如果有一個foo結構,找到嵌入到其中的kref非常簡單,只需要使用refcount成員。但是,處理kref的代碼(例如release回調函數)常常遇到相反的問題:給出一個struct kref指針,如何找到包含它的結構的指針?我們并不建議將refcount作為foo結構的第一個域,不鼓勵程序員在兩種對象類型間進行愚蠢的強制轉換(Linux內核也會有將一個數據結構作為另一個數據結構的第一個域,例如sock和inet_sock等,并在這兩種的數據結構的對象之間進行強制轉換。但這主要是為了實現類似面向對象語言的派生特性)。相反,你應該使用定義在<linux/kernel.h>中的container_of宏。
container_of(pointer, type, member)
其中,type為宿主對象的數據結構類型,member為結構中某個內嵌域(成員)的名字,pointer是指向宿主對象的member域的指針,這個宏的返回值是指向宿主對象的指針,如圖2-2所示。

圖2-2 使用container_of宏獲得已知成員指針的宿主對象指針
contain_of宏的實現原理是,考慮成員member相對于類型為type的宿主結構的起始地址的偏移量。對于所有該類型的宿主對象,這個偏移量是固定的。并且可以在假設宿主對象地址值為0,通過返回member域的地址獲得,即等于(unsigned long)(&((type *)0)->member)。這樣,將宿主對象的member域的地址(pointer)減去這個偏移量,就可以得到宿主對象的地址,再將它轉換為type類型的指針。
因此,對于上面的例子,通過指向refcount的指針kref得到指向包含它的類型為foo的結構的指針如下:
foo = container_of(kref, struct foo, refcount);
在創建foo對象時,除了為它分配內存空間,kref域也必須被初始化,否則無法使用引用計數功能。
struct foo *foo_create(void) { struct foo *foo; foo = kzalloc(struct foo, GFP_KERNEL); if (!foo) { return NULL; } kref_init(&foo->kref); return foo; }
在foo被創建時,該結構的內部引用計數被設置為1。因此,設置這個內核對象的代碼(模塊)必須調用一次kref_put()以最終釋放這個引用。
現在就可以隨意地遞增或遞減結構的引用計數了。在使用foo結構之前,要遞增引用計數,調用函數kref_get。在使用完后,應該調用kref_put函數釋放這個引用。
一般來說,大多數代碼選擇繼續封裝kref_get和kref_put以方便使用。
struct foo *foo_get(struct foo *foo) { if (foo) kref_get(&foo->kref); return foo; } void foo_put(struct foo *foo) { if (foo) { kref_put(&foo->kref, foo_release); } }
前面說過,這個包含了kref變量的結構的最初創建者,在使用完結構后,也應該調用這個函數。kfree函數不允許被直接調用,因為內核的其他部分可能還持有這個結構的有效引用。
在最后一個引用計數被釋放時,被傳入kref_put的函數foo_release將被調用,這個函數的目的是釋放已分配的foo結構的內存。foo_release函數的原型接收的是指向struct kref的指針,因此需要用前面提到的container_of宏轉換為指向struct foo結構的指針,然后再調用內存釋放函數。
void foo_release(struct kref *kref) { struct foo *foo; foo = container_of(foo, struct foo, kref); kfree(foo); }
在kref_put函數被調用后,后續代碼中不能繼續使用foo結構體,因為它的內存可能已經被釋放。
- Unreal Engine:Game Development from A to Z
- Matplotlib 3.0 Cookbook
- 四向穿梭式自動化密集倉儲系統的設計與控制
- JSP從入門到精通
- 愛犯錯的智能體
- 空間機械臂建模、規劃與控制
- Mastering pfSense
- Learning Linux Shell Scripting
- Linux Shell Scripting Cookbook(Third Edition)
- Cloudera Hadoop大數據平臺實戰指南
- PHP求職寶典
- 樂高創意機器人教程(中級 上冊 10~16歲) (青少年iCAN+創新創意實踐指導叢書)
- 精通ROS機器人編程(原書第2版)
- PyTorch深度學習
- 新世紀Photoshop CS6中文版應用教程