linux中的內核引用計數器文檔 /Documentation/kref.txt翻譯。
krefs能讓你往你的對象中添加一個引用計數器。如果你有一些需要在多處被使用和傳遞的對象,而你并沒有給這些對象中添加引用計數器的話,你的代碼肯定會有某些缺陷,會出現一些問題。如果你想使用引用計數器的話,krefs是你可以使用的方法。
為了使用一個kref,你需要向你的數據結構體中嵌入一個struct kref結構體。
可以使用如下的方式:
struct my_data
{
.
.
struct kref? refcount; //向結構體中加入了一個引用計數器。
.
.
};
這個struct kref結構體可以出現在這個數據結構體內的任何地方。
在你分配了這個struct kref結構體之后,你必須要對其進行初始化。為了完成對struct kref結構體的初始話,你可以調用 kref_init():
struct my_data *data;
data = kmalloc(sizeof(struct my_data), GFP_KERNEL);
if(!data)
return -ENOMEM;
kref_init(&data->refcount);
kref_init()函數的原型: void kref_init(struct kref *refcount);
kref_init()函數所做的工作:將struct my_data中的struct kref refcount成員設置為1;
一旦你完成了對struct kref結構體(引用計數器)的初始化,你必須遵守下面的這些規則:
規則一: 如果你對一個指針做了一次非臨時性的拷貝,特別是如果這個指針可能被傳遞到另一個可執行的線程中,那么你必須要在這個拷貝完成之前或這個指針被傳遞到另一個可執行線程之前,調用用 kref_get(struct kref *refcount)函數對這個引用計數器進行自加操作。如果你已經有了一個指向這個內嵌的struct kref結構體的指針的話(這個引用計數器的值不可 能為零)。
規則二:當你對一個指針的使用結束以后,你必須使用 kref_put()函數:kref_put(&data->refcount, data_release);如果這是對這個指針的最后一次引用的話,這個releas程序將被調用。如果代碼不再保持有一個有效的指針,不會再去獲取這個被嵌入的struct kref結構體的地址話,那么 直接通過調用 kref_put()函數而不需要使用鎖,是不會有問題。
規則三:如果代碼沒有持有一個有效的指針,而試圖去獲取對一個嵌入的struct kref結構體的引用的話,代碼必須連續訪問一個地方,這個地方在使用kref_get()過程中不能有kref_put()函數的出現,并且這個結構體必要要在kref_get()過程中保持有效。
例如:如果你分配了一些數據,然后把它們傳遞給一個線程去處理:
//當引用計數器的值為0時,通過這個函數來釋放不再使用的結構體。
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(struct kref *refcount, void (*func)(struct kref *refcount));
// kref_put()函數完成兩件事情:
1.對引用計數器進行自減;
2.注冊一個函數,當引用計數器的值為0時,啟動func函數對不再使用的結構體進行釋放。
kref_put(&data->refcount, data_release);
}
int my_data_handler(void)
{
int rv = 0;
struct my_data *data;
struct task_struct *task; // struct task_struct *task 用于指向一個新建的線程;
data = kmalloc(sizeof(*data), GFP_KERNEL);
if (!data)
return -ENOMEM;
kref_init(&data->refcount); // kref_init(struct kref *refcount)
// 對引用計數器進行初始化,即將refcount設置為1;
//在下面的more_data_handling線程中,要用到struct my_data類型的結構體,所以
//先使用 kref_get(struct kref *refcount)函數對引用計數器先進行一次自加操作。
kref_get(&data->refcount);
// struct task_struct * kthread_run(void (*kthread)(void *data), ? ? ? ? ? ? ???????????????????????????????????????????? void *data, thread_name)
// kthread???? : 線程函數;
// data?? ? ?? : 傳遞給線程的參數;
// thread_name : 線程的名稱;
// 使用kthread_run()函數創建的線程會立刻運行;
task = kthread_run(more_data_handling, data, "more_data_handling");
// ERR_PTR(int errno) 函數將 錯誤碼轉換稱為一個指針;
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()處理程序知道數據什么時候不再被引用并且釋放它。
下面的做法是違反了規則一,所以是錯誤的做法;
task = kthread_run(more_data_handling, data, "more_data_handling");
if (task == ERR_PTR(-ENOMEM))
{
rv = -ENOMEM;
goto out;
}
else
kref_get(&data->refcount);// 這個函數應該在kthread_run()運行之前被調用;
在一些情況下,你可能要優化這gets和puts。例如,如果你使用完了一個對象,然后把這個對象傳遞給其他的函數或將這個對象插入一個隊列中以便其他函數使用,就不能先進行一個get然后一個put。
下面的做法是錯誤的:
/* Silly extra get and put? 十分愚蠢的get和put*/
kref_get(&obj->ref);
enqueue(obj);
kref_put(&obj->ref, obj_cleanup);
而正確的做法是:僅僅需要一個入隊操作;
enqueue(obj);
而這個規則三是比較難以去處理的。例如:你有一個鏈表,并且鏈表中的每個項中都有一個嵌入的struct kref結構體,并且你希望從鏈表中獲取第一個數據項。你不能讓這個鏈表中的第一個數據脫離這個鏈表,然后kref_get()這個數據。那種做法違反了規則3,因為你并沒有持有一個有效的指針。你必須添加一個互斥鎖或其他的鎖。例如:
static DEFINE_MUTEX(mutex); // DEFINE_MUTEX(mutex) 定義了一個mutex互斥鎖并且對其進行初始化
static LIST_HEAD(q); // LIST_HEAD(q) 定義了一個q鏈表頭并對其進行初始化;
struct my_data
{
struct kref refcount; //給struct my_data中加入了一個引用計數器;
struct list_head link;
};
static struct my_data *get_entry()
{
struct my_data *entry = NULL;
mutex_lock(&mutex); // mutex_lock(int *mutex) 用于獲取一個互斥鎖;
if (!list_empty(&q))? // 如果鏈路不為空;
{
entry = container_of(q.next, struct my_q_entry, link);
kref_get(&entry->refcount);//引用計數器值加1;
}
mutex_unlock(&mutex);// mutex_unlock(&mutex) mutex_unlock(int *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()的返回值。
例如:
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);
}
總結一下:這個文檔主要介紹了三個函數
void kref_init(struct kref *refcount);//用于對內核引用計數器進行初始化;
void kref_get(struct kref *refcount); //對引用計數器進行加1操作;
int? kref_put(struct kref *refcount, void (*func)(struct kref *refcount));
// kref_put()先對引用計數器中的值進行減 1操作,返回0。
//當引用計數器的值為0的話,調用func函數來對不再使用的結構體釋放,然后返會正數。