天天看點

基本的資料結構學習筆記:kref

本文簡單介紹了裝置驅動模型中最最簡單的一個資料結構:kref,它作為核心中最基本的引用計數而存在。

首先直覺地介紹該資料結構及操作它的一些方法,然後再介紹其具體的用法。參考:kref.h kref.c kref.txt

一、kref及操作kref的方法

struct kref

{

 atomic_t refcount;

};

可以看到kref結構體的成員隻有一個原子變量refcount,為什麼還要用kref結構體來包裝一下呢?

目前我所知道的有兩種說法:

1、為了友善編譯器做類型檢查(不是很懂......)

2、為了以後友善擴充(這個很好了解)

不管怎樣,反正目前這個結構體很簡單啦。另外,核心還提供了4個函數用來操作kref:

void  kref_set(struct kref *kref, int num);

void  kref_init(struct kref *kref);

void kref_get(struct kref *kref);

int  kref_put(struct kref *kref, void (*release) (struct kref *kref));

下面來看一下它們的源碼。

void kref_set(struct kref *kref, int num)

{

 atomic_set(&kref->refcount, num); //設定kref的引用計數為num

 smp_mb();             //......這個暫時就不管了吧,沒具體研究過

}

void kref_init(struct kref *kref)

{

 kref_set(kref, 1); //簡單地調用kref_set,将引用計數設定為1

}

void kref_get(struct kref *kref)

{

 WARN_ON(!atomic_read(&kref->refcount));//如果引用計數為0,核心将給出警告。不過,這樣的情況可能發生呢?引用計數為0了,kref不就被釋放了嗎?這句話的作用應該是捕獲程式設計錯誤,例如,在調用get之前沒有調用init等等

 atomic_inc(&kref->refcount);//原子地遞增引用計數

 smp_mb__after_atomic_inc();//......飄過

}

int kref_put(struct kref *kref, void (*release)(struct kref *kref))

{

 WARN_ON(release == NULL);//如果沒有指定release函數,核心給出警告

 WARN_ON(release == (void (*)(struct kref *))kfree);//如果指定的release函數是kfree,則核心給出警告,從注釋中可以看出

 if (atomic_dec_and_test(&kref->refcount)) {//原子地遞減引用計數,并檢測遞減後計數是否0

  release(kref);//沒有被引用了,調用注冊進的release函數釋放kref

  return 1;//傳回1,表示對象已被删除

 }

 return 0;

}

二、kref的用法

一般而言,都是将kref包含進一個自定義的結構體中,進而為包含它的結構體提供引用計數功能。

struct my_data

{

  .

 .

 struct kref refcount;

 .

 .

};

使用時,在配置設定了自定義結構體之後,必須對kref成員進行初始化:

struct my_data *data;

data = kmalloc(sizeof(*data), GFP_KERNEL);

if (!data)

 return -ENOMEM;

kref_init(&data->refcount); //初始化結構體data的引用計數為1

一旦擁有一個已初始化過的kref,那麼必須遵循以下3個規則(因為kref裡不存在任何lock,是以在程式設計時務必遵循規則,否則可能出錯):

1)如果對一個kref-ed的結構體指針做非局部性拷貝,特别是當将指針傳遞給另一個線程時,必須在傳遞之前調用kref_get()以增加kref-ed的結構體的引用計數:kref_get(&data->refcount);

 如果已經有一個有效的指針指向一個包含kref的結構體(引用計數不會為0),那麼在kref_get時可以不用“鎖”。

2)當完成對kref-ed結構體的使用時,必須要調用kref_put():kref_put(&data->refcount, data_release);

 如果這是對結構體的最後的引用,那麼data_release函數将被調用。

3)如果在沒有取得結構體的一個有效的指針時,嘗試去擷取kref-ed結構體的引用,則必須串行地通路kref-ed結構體,這樣在kref_get時不會發生kref_put,并且在kref_get期間結構體必須保持有效。

我們分析一段代碼,來加深對以上3個規則的了解。

代碼1:配置設定一個kref-ed結構體,并把它傳遞給另一個線程處理

void data_release(struct kref *ref)

{

 struct my_data *data = container_of(ref, 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);//規則1)

 task = kthread_run(more_data_handling, data, "more_data_handling");

 if (task == ERR_PTR(-ENOMEM))

 {

   rv = -ENOMEM;

   kref_put(&data->refcount, data_release);

   goto out;

 }

 .

 . do stuff with data here

 .

 out:

 kref_put(&data->refcount, data_release);

 return rv;

}

這種方式不必關心兩個線程通路data的順序,kref_put()函數知道data何時不再有任何引用并且釋放data。kref_get不需要“鎖”,因為已經有了一個

有效的指針data。

規則3)是最讓人煩的,舉例來說,有一個連結清單,其元素都是kref-ed結構,我們希望擷取連結清單的第一個元素的引用。

我們不能簡單地将連結清單的第一個元素pull出來,然後調用kref_get。這将違反規則3),因為我們還沒有擷取一個有效的指針。必須使用“鎖”:

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_q_entry, 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);

}

如果不想在整個release操作期間都保持“鎖”,可以使用kref_put的傳回值。

比如你不想在保持“鎖”的情況下調用kfree(因為沒有必要?)

你可以這麼使用kref_put:

static void release_entry(struct kref *ref)

{ }

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);

}

上面的這種方式,在release函數會調用其他比較耗時的函數時比較有用,因為畢竟一個“鎖”不能長時間的保持。

不過,還是推薦将所有的操作都放在release例程裡做,因為那樣的話代碼會很簡潔。

PS:關于kref的用法,基本是翻譯kref.txt的,有些東西還沒完全了解,待續吧......

其實隻要了解了那3個規則,就知道該如何使用kref了,可惜啊,沒有完全了解透,提幾個問題,待以後了解了再補上。

Q1:關于規則1,為何在将kref-ed結構體傳遞給另一個程序前,必須調用kref_get?不可以在另一個程序内部調用kref_get嗎?

Q2:為何不能将kfree傳遞給kref_put?為何要做這樣的限制?

繼續閱讀