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