Linux內核引用計數器kref結構


1、前言

struct kref結構體是一個引用計數器,它被嵌套進其它的結構體中,記錄所嵌套結構的引用計數。引用計數用於檢測內核中有多少地方使用了某個對象,每當內核的一個部分需要某個對象所包含的信息時,則該對象的引用計數加1,如果不需要相應的信息,則對該對象的引用計數減1,當引用計數為0時,內核知道不再需要該對象,將從內存中釋放該對象。

 

2、kref結構體

在Linux的內核源碼中,struct kref結構體的定義在include/linux/kref.h文件中,結構體定義如下所示:

struct kref {
    refcount_t refcount;
};

其中,refcount_t的類型定義如下所示:

typedef struct refcount_struct {
    atomic_t refs;
} refcount_t;

該數據結構比較簡單,它提供了一個原子引用計數值atomic_t refs,atomic_t是原子類型,對其操作都要求是原子執行。“原子”在這里意味着,對該變量的加1和減1操作在多處理器系統上也是安全的。

 

3、kref操作

(1)初始化引用計數

Linux內核中提供了初始化struct kref結構體的函數接口,如下:

#define KREF_INIT(n)    { .refcount = REFCOUNT_INIT(n), }

/**
 * kref_init - initialize object.
 * @kref: object in question.
 */
static inline void kref_init(struct kref *kref)
{
    refcount_set(&kref->refcount, 1);
}

KREF_INIT(n)是一個宏定義,該宏用於將引用計數變量初始化為n,另外Linux內核還提供了一個kref_init()函數接口,該函數的功能用於將引用計數變量初始化為1。

(2)讀取引用計數器的值

Linux內核中,讀取引用計數器的值的函數接口為kref_read(),該函數的定義如下:

static inline unsigned int kref_read(const struct kref *kref)
{
    return refcount_read(&kref->refcount);
}

該函數傳入的參數為struct kref指針,函數的返回值為讀取到的引用計數器的值。

(3)引用計數器加1操作

Linux內核中,引用計數器加1操作的函數接口為kref_get(),函數的定義如下所示:

static inline void kref_get(struct kref *kref)
{
    refcount_inc(&kref->refcount);
}

該函數的傳入參數為struct kref結構體體指針。

(4)引用計數器減1操作

同時,內核也提供了引用計數器減1操作的函數接口kref_put(),函數的定義如下所示:

static inline int kref_put(struct kref *kref, void (*release)(struct kref *kref))
{
    if (refcount_dec_and_test(&kref->refcount)) {
        release(kref);
        return 1;
    }
    return 0;
}

參數:

  kref:要減1操作的struct kref結構體指針

  release:函數指針,當引用計數器的值為0時,則調用此函數

返回值:

         成功:如果引用對象被成功釋放則返回1

         其他情況:返回0

 

4、kref的使用規則

在Documentation/kref.txt文件中總結了struct kref引用計數的一些規則,下面是該文件的簡單翻譯:

(1)介紹

對於哪些用在多種場合,被到處傳遞的結構,如果沒有引用計數,出現bug幾乎是肯定的事,因此,我們需要用到kref,它允許我們在已有的結構中方便地添加引用計數。

可以用如下的方式添加kref到已有的數據結構中:

struct my_data {
  …
  struct kref refcount;
  …
};

struct kref可以出現在自己定義結構體中的任意位置。

(2)初始化

在分配kref后,必須將kref進行初始化,可以調用kref_init()函數,將kref的計數值初始為1:

struct my_data *data;

data = kmalloc(sizeof(*data), GFP_KERNEL);
if (!data)
  return –ENOMEM;
kref_init(&data->refcount);

(3)kref的使用規則

初始化kref之后,kref的使用應該遵循以下三條規則:

1)如果你創建了一個結構指針的非暫時性副本,特別是當這個副本指針會被傳遞到其它執行線程時,你必須在傳遞副本指針之前執行kref_get():

kref_get(&data->refcount);

2)當你使用完,不再需要結構的指針,必須執行kref_put,如果這是結構指針的最后一個引用,release()函數會被調用,如果代碼絕不會在沒有擁有引用計數的請求下去調用kref_get(),在kref_put()時就不需要加鎖:

kref_put(&data->refcount, data_release);

3)如果代碼試圖在還沒有擁有引用計數的情況下就調用kref_get(),就必須串行化kref_put()和kref_get()的執行,因為很可能在kref_get()執行之前或者執行中,kref_put()就被調用並把整個結構釋放掉:

例如,你分配了一些數據並把它傳遞到其它線程去處理:

void data_release(struct kref *kref)
{
  struct my_data *data = container_of(kref, struct my_data, refcount);
  kfree(data);
}

void more_data_handling(void *cb_data)
{
  struct my_data *data = cb_data;
  …
  do stuff with data here
  …
  kref_put(&data->refcount, data_release);
}

int my_data_handler(void)
{
  int rv = 0;
  struct my_data *data;
  struct task_struct *task;

  data = kmalloc(sizeof(*data), GFP_KERNEL);
  if (!data)
    return –ENOMEM;

  kref_init(&data->refcount);
  kref_get(&data->refcount);
  task = kthread_run(more_data_handling, data, “more_data_handling”);
  if (task == ERR_PTR(-ENOMEM)) {
    rv = -ENOMEM;
    goto out;
  }
  …

  do stuff with data here
  …

out:
  kref_put(&data->refcount, data_release);
  return rv;
}

這樣做,無論兩個線程的執行順序是怎么樣都無所謂,kref_put()知道何時數據不再有引用計數,結構體可以被銷毀,kref_get()調用不需要再加鎖,因為在my_data_handler()中調用kref_get()時已經擁有一個引用,同樣,kref_put()也不需要加鎖。

注意規則一中的要求,必須在傳遞指針之前調用kref_get(),決不能寫成下面的代碼:

task = kthread_run(more_data_handling, data, “more_data_handling”);

if (task == ERR_PTR(-ENOMEM)) {
    rv = -ENOMEM;
    goto out;
} else {
    /* BAD BAD BAD – get is after the handoff */
    kref_get(&data->refcount);

不要認為自己在使用上面的代碼時知道自己在做什么,首先,你可能並不知道你在做什么,其次,你可能知道你在做什么(在部分加鎖的情況下上面的代碼也是正確的),但一些修改或者復制你代碼的人並不知道在做什么,這是一種不好的使用方式。

當然,在部分情況下也可以優化對get和put的使用,例如,你已經完成了對這個數據的處理,並要把它傳遞給其它線程,就不需要做多多余的get和put了:

/* Silly extra get and put */
kref_get(&obj->ref);
enqueue(obj);
kref_put(&obj->ref, obj_cleanup);

只需要做enqueue操作即可,可以在最后加一條注釋:

enqueue(obj);
/* We are done with obj, so we pass our refcount off to the queue.  DON'T TOUCH obj AFTER HERE! */

第三條規則處理起來是最麻煩的,例如,你有一列數據,每條數據都有kref計數,你希望獲取第一條數據,但是你不能簡單地把第一條數據從鏈表中取出並調用kref_get(),這違背了第三條規則,在調用kref_get()以前你並沒有一個引用,因此,你需要增加一個mutex(或者其它鎖):

static DEFINE_MUTEX(mutex);
static LIST_HEAD(q);
struct my_data {
    struct kref refcount;
    struct list_head link;
};

static struct my_data *get_entry()
{
    struct my_data *entry = NULL;
    mutex_lock(&mutex);

    if (!list_empty(&q)) {
    entry = container_of(q.next, struct my_data, link);
    kref_get(&entry->refcount);
    }
    mutex_unlock(&mutex);
    return entry;
}

static void release_entry(struct kref *ref)
{
    struct my_data *entry = container_of(ref, struct my_data, refcount);
    list_del(&entry->link);
    kfree(entry);
}

static void put_entry(struct my_data *entry)
{
    mutex_lock(&mutex);
    kref_put(&entry->refcount, release_entry);
    mutex_unlock(&mutex);
}

如果你不想在整個釋放過程中都加鎖,kref_put的返回值就很有用了,例如,你不想在加鎖的情況下調用kfree,可以像下面這樣使用kref_put:

static void release_entry(struct kref *ref)
{
    /* All work is done after the return from kref_put() */
}

static void put_entry(struct my_data *entry)
{
    mutex_lock(&mutex);
    if (kref_put(&entry->refcount, release_entry)) {
    list_del(&entry->link);
    mutex_unlock(mutex);
    kfree(entry);
    } else
        mutex_unlock(&mutex);
}    

如果在撤銷結構體的過程中需要調用其它的需要更長時間的函數,或者函數也可能獲取同樣的互斥鎖,代碼可以進行優化:

static struct my_data *get_entry()
{
    struct my_data *entry = NULL;
    mutex_lock(&mutex);
    if (!list_empty(&q)) {
        entry = container_of(q.next, struct my_data, link);
        if (!kref_get_unless_zero(&entry->refcount))
            entry = NULL;
    }
    mutex_unlock(&mutex);
    return entry;
}

static void release_entry(struct kref *ref)
{
    struct my_data *entry = container_of(ref, struct my_data, refcount);

    mutex_lock(&mutex);
    list_del(&entry->link);
    mutex_unlock(&mutex);
    kfree(entry);
}

static void put_entry(struct my_data *entry)
{
    kref_put(&entry->refcount, release_entry);
} 

 

5、小節

在Linux內核中,使用了struct kref這個結構體來進行對象管理的引用計數,對該結構體的操作為“原子”操作,常用的函數接口有用來初始化引用計數器的kref_init()、引用計數加1操作的kref_get()和引用計數減1操作的kref_put(),通過引用計數,能夠方便地進行對象管理。

 

參考:

https://www.bbsmax.com/A/6pdDB7DXJw/

https://blog.csdn.net/qb_2008/article/details/6840387


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM