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

圖2-2 使用container_of宏獲得已知成員指針的宿主對(duì)象指針
contain_of宏的實(shí)現(xiàn)原理是,考慮成員member相對(duì)于類型為type的宿主結(jié)構(gòu)的起始地址的偏移量。對(duì)于所有該類型的宿主對(duì)象,這個(gè)偏移量是固定的。并且可以在假設(shè)宿主對(duì)象地址值為0,通過返回member域的地址獲得,即等于(unsigned long)(&((type *)0)->member)。這樣,將宿主對(duì)象的member域的地址(pointer)減去這個(gè)偏移量,就可以得到宿主對(duì)象的地址,再將它轉(zhuǎn)換為type類型的指針。
因此,對(duì)于上面的例子,通過指向refcount的指針kref得到指向包含它的類型為foo的結(jié)構(gòu)的指針如下:
foo = container_of(kref, struct foo, refcount);
在創(chuàng)建foo對(duì)象時(shí),除了為它分配內(nèi)存空間,kref域也必須被初始化,否則無法使用引用計(jì)數(shù)功能。
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被創(chuàng)建時(shí),該結(jié)構(gòu)的內(nèi)部引用計(jì)數(shù)被設(shè)置為1。因此,設(shè)置這個(gè)內(nèi)核對(duì)象的代碼(模塊)必須調(diào)用一次kref_put()以最終釋放這個(gè)引用。
現(xiàn)在就可以隨意地遞增或遞減結(jié)構(gòu)的引用計(jì)數(shù)了。在使用foo結(jié)構(gòu)之前,要遞增引用計(jì)數(shù),調(diào)用函數(shù)kref_get。在使用完后,應(yīng)該調(diào)用kref_put函數(shù)釋放這個(gè)引用。
一般來說,大多數(shù)代碼選擇繼續(xù)封裝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); } }
前面說過,這個(gè)包含了kref變量的結(jié)構(gòu)的最初創(chuàng)建者,在使用完結(jié)構(gòu)后,也應(yīng)該調(diào)用這個(gè)函數(shù)。kfree函數(shù)不允許被直接調(diào)用,因?yàn)閮?nèi)核的其他部分可能還持有這個(gè)結(jié)構(gòu)的有效引用。
在最后一個(gè)引用計(jì)數(shù)被釋放時(shí),被傳入kref_put的函數(shù)foo_release將被調(diào)用,這個(gè)函數(shù)的目的是釋放已分配的foo結(jié)構(gòu)的內(nèi)存。foo_release函數(shù)的原型接收的是指向struct kref的指針,因此需要用前面提到的container_of宏轉(zhuǎn)換為指向struct foo結(jié)構(gòu)的指針,然后再調(diào)用內(nèi)存釋放函數(shù)。
void foo_release(struct kref *kref) { struct foo *foo; foo = container_of(foo, struct foo, kref); kfree(foo); }
在kref_put函數(shù)被調(diào)用后,后續(xù)代碼中不能繼續(xù)使用foo結(jié)構(gòu)體,因?yàn)樗膬?nèi)存可能已經(jīng)被釋放。
- 大數(shù)據(jù)導(dǎo)論:思維、技術(shù)與應(yīng)用
- 輕松學(xué)Java Web開發(fā)
- Windows XP中文版應(yīng)用基礎(chǔ)
- VMware Performance and Capacity Management(Second Edition)
- Windows內(nèi)核原理與實(shí)現(xiàn)
- 統(tǒng)計(jì)學(xué)習(xí)理論與方法:R語(yǔ)言版
- Dreamweaver CS6精彩網(wǎng)頁(yè)制作與網(wǎng)站建設(shè)
- 零起點(diǎn)學(xué)西門子S7-200 PLC
- Cloud Security Automation
- 網(wǎng)絡(luò)存儲(chǔ)·數(shù)據(jù)備份與還原
- Python文本分析
- Instant Slic3r
- 電氣自動(dòng)化工程師自學(xué)寶典(基礎(chǔ)篇)
- 巧學(xué)活用WPS
- Getting Started with LevelDB