官术网_书友最值得收藏!

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)被釋放。

主站蜘蛛池模板: 尼勒克县| 漳浦县| 二手房| 张掖市| 巴林左旗| 荥阳市| 卢氏县| 义马市| 溧阳市| 同心县| 尉氏县| 怀柔区| 佛坪县| 三亚市| 舞钢市| 绍兴市| 墨江| 佛冈县| 宜昌市| 海门市| 华阴市| 台中市| 龙山县| 门头沟区| 大余县| 明光市| 云龙县| 泰兴市| 棋牌| 子长县| 驻马店市| 玉山县| 临沂市| 固始县| 邯郸市| 华坪县| 苏尼特左旗| 呼和浩特市| 新邵县| 松潘县| 五华县|