本文簡單介紹了裝置驅動模型中最最簡單的一個資料結構: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?為何要做這樣的限制?