Linux內核部件分析--記錄生命週期的kref

kref是一個引用計數器,它被嵌套進其它的結構中,記錄所嵌套結構的引用計數,並在計數清零時調用相應的清理函數。kref的原理和實現都非常簡單,但要想用好卻不容易,或者說kref被創建就是爲了跟蹤複雜情況下地結構引用銷燬情況。所以這裏先介紹kref的實現,再介紹其使用規則。

kref的頭文件在include/linux/kref.h,實現在lib/kref.c。閒話少說,上代碼。

  1. struct kref {  
  2.     atomic_t refcount;  
  3. };  
      可以看到,kref的結構中就包含一個atomic_t類型的計數值。atomic_t是原子類型,對其操作都要求是原子執行的,有專門的原子操作API執行,即使在多處理器間也保持原子性。使用atomic_t類型充當計數值,就省去了加鎖去鎖的過程。
  1. void kref_set(struct kref *kref, int num)  
  2. {  
  3.     atomic_set(&kref->refcount, num);  
  4.     smp_mb();  
  5. }  
kref_set 設置kref的初始計數值。具體計數值設置由原子操作atomic_set完成。之後還有一個smp_mb()是爲了增加內存屏障,保證這一寫操作會在之後的讀寫操作完成之前完成。
  1. void kref_init(struct kref *kref)  
  2. {  
  3.     kref_set(kref, 1);  
  4. }  

kref_init 初始化kref的計數值爲1。

  1. void kref_get(struct kref *kref)  
  2. {  
  3.     WARN_ON(!atomic_read(&kref->refcount));  
  4.     atomic_inc(&kref->refcount);  
  5.     smp_mb__after_atomic_inc();  
  6. }  

kref_get遞增kref的計數值。

  1. int kref_put(struct kref *kref, void (*release)(struct kref *kref))  
  2. {  
  3.     WARN_ON(release == NULL);  
  4.     WARN_ON(release == (void (*)(struct kref *))kfree);  
  5.   
  6.     if (atomic_dec_and_test(&kref->refcount)) {  
  7.         release(kref);  
  8.         return 1;  
  9.     }  
  10.     return 0;  
  11. }  

kref_put遞減kref的計數值,如果計數值減爲0,說明kref所指向的結構生命週期結束,會執行release釋放函數

所以說kref的API很簡單,kref_init和kref_set基本都是初始時纔會用到,平時常用的就是kref_get和kref_put。一旦在kref_put時計數值清零,立即調用結束函數。

kref設計得如此簡單,是爲了能靈活地用在各種結構的生命週期管理中。要用好它可不簡單,好在Documentation/kref.txt中爲我們總結了一些使用規則,下面簡單翻譯一下。

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

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

  1. struct my_data {  
  2.     ...  
  3.     struct kref refcount;  
  4.     ...  
  5. };  

kref可以出現在你結構中的任意位置。

在分配kref後你必須初始化它,可以調用kref_init,把kref計數值初始爲1。

  1. struct my_data *data;  
  2.   
  3. data = kmalloc(sizeof(*data), GFP_KERNEL);  
  4. if(!data)  
  5.     return -ENOMEM;  
  6. kref_init(&data->refcount);  

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

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

  1. kref_put(&data->refcount);  

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

  1. kref_put(&data->refcount, data_release);  

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

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

  1. void data_release(struct kref *kref)  
  2. {  
  3.     struct my_data *data = container_of(kref, struct my_data, refcount);  
  4.     kree(data);  
  5. }  
  6.   
  7. void more_data_handling(void *cb_data)  
  8. {  
  9.     struct my_data *data = cb_data;  
  10.     .  
  11.     .  do stuff with data here  
  12.     .  
  13.     kref_put(&data->refcount, data_release);  
  14. }  
  15.   
  16. int my_data_handler(void)  
  17. {  
  18.     int rv = 0;  
  19.     struct my_data *data;  
  20.     struct task_struct *task;  
  21.     data = kmalloc(sizeof(*data), GFP_KERNEL);  
  22.      if (!data)  
  23.         return -ENOMEM;  
  24.     kref_init(&data->refcount);  
  25.     kref_get(&data->refcount);  
  26.     task = kthread_run(more_data_handling, data, "more_data_handling");  
  27.     if (task == ERR_PTR(-ENOMEM)){  
  28.          rv = -ENOMEM;  
  29.          goto out;  
  30.     }  
  31.     .  
  32.     .  do stuff with data here  
  33.     .  
  34. out:  
  35.     kref_put(&data->refcount, data_release);  
  36.     return rv;  
  37. }  

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

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

  1. task = kthread_run(more_data_handling, data, "more_data_handling");  
  2. if(task == ERR_PTR(-ENOMEM)) {  
  3.     rv = -ENOMEM;  
  4.     goto out;  
  5. }  
  6. else {  
  7.      /* BAD BAD BAD - get is after the handoff */  
  8.     kref_get(&data->refcount);  

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

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

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

只需要做enqueue操作即可,可以在其後加一條註釋。

  1. enqueue(obj);  
  2. /* 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(或者其它鎖)。

  1. static DEFINE_MUTEX(mutex);  
  2. static LIST_HEAD(q);  
  3. struct my_data  
  4. {  
  5.     struct kref refcount;  
  6.     struct list_head link;  
  7. };  
  8.   
  9. static struct my_data *get_entry()  
  10. {  
  11.     struct my_data *entry = NULL;  
  12.     mutex_lock(&mutex);  
  13.     if(!list_empty(&q)){  
  14.         entry = container_of(q.next, struct my_q_entry, link);  
  15.         kref_get(&entry->refcount);  
  16.     }  
  17.     mutex_unlock(&mutex);  
  18.     return entry;  
  19. }  
  20.           
  21. static void release_entry(struct kref *ref)  
  22. {  
  23.     struct my_data *entry = container_of(ref, struct my_data, refcount);  
  24.       
  25.     list_del(&entry->link);  
  26.     kfree(entry);  
  27. }  
  28.   
  29. static void put_entry(struct my_data *entry)  
  30. {  
  31.     mutex_lock(&mutex);  
  32.     kref_put(&entry->refcount, release_entry);  
  33.     mutex_unlock(&mutex);  
  34. }  

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

  1. static void release_entry(struct kref *ref)  
  2. {  
  3.       
  4. }  
  5.   
  6. static void put_entry(struct my_data *entry)  
  7. {  
  8.     mutex_lock(&mutex);  
  9.     if(kref_put(&entry->refcount, release_entry)){  
  10.         list_del(&entry->link);  
  11.         mutex_unlock(&mutex);  
  12.         kfree(entry);  
  13.     }  
  14.     else  
  15.         mutex_unlock(&mutex);  
  16. }  

如果你在撤銷結構的過程中需要調用其它的需要較長時間的函數,或者函數也可能要獲取同樣地互斥鎖,這樣做就很有用了。但要注意在release函數中做完撤銷工作會使代碼看起來更整潔。

http://www.linuxidc.com/Linux/2011-10/44627p3.htm
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章