為核心物件新增引用計數器 (krefs)

作者:

Corey Minyard <minyard@acm.org>

作者:

Thomas Hellström <thomas.hellstrom@linux.intel.com>

本文大量內容摘自 Greg Kroah-Hartman 2004 年 OLS 關於 krefs 的論文和演示,可在以下連結找到:

引言

krefs 允許你為物件新增引用計數器。如果你的物件在多個地方使用並傳遞,並且沒有引用計數,那麼你的程式碼幾乎肯定是有問題的。如果你需要引用計數,krefs 是一個好方法。

要使用 kref,請像這樣將其新增到你的資料結構中:

struct my_data
{
    .
    .
    struct kref refcount;
    .
    .
};

kref 可以出現在資料結構中的任何位置。

初始化

你必須在分配 kref 後對其進行初始化。為此,請按如下方式呼叫 kref_init:

struct my_data *data;

data = kmalloc(sizeof(*data), GFP_KERNEL);
if (!data)
       return -ENOMEM;
kref_init(&data->refcount);

這會將 kref 中的引用計數設定為 1。

Kref 規則

一旦你擁有一個已初始化的 kref,你必須遵循以下規則:

  1. 如果你建立了指標的非臨時副本,特別是當它可以傳遞給另一個執行執行緒時,你必須在將其傳遞出去之前使用 kref_get() 增加引用計數。

    kref_get(&data->refcount);
    

    如果你已經擁有一個指向 kref 結構體的有效指標(引用計數不能變為零),你可以在沒有鎖的情況下執行此操作。

  2. 當你使用完一個指標後,你必須呼叫 kref_put()

    kref_put(&data->refcount, data_release);
    

    如果這是指向該指標的最後一個引用,則將呼叫釋放例程。如果程式碼從未在未持有有效指標的情況下嘗試獲取 kref 結構體的有效指標,則在沒有鎖的情況下執行此操作是安全的。

  3. 如果程式碼嘗試在未持有有效指標的情況下獲取 kref 結構體的引用,則必須序列化訪問,以便在 kref_get() 期間不會發生 kref_put(),並且結構體在 kref_get() 期間必須保持有效。

例如,如果你分配了一些資料,然後將其傳遞給另一個執行緒進行處理:

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

這樣,無論兩個執行緒以何種順序處理資料,kref_put() 都能處理何時資料不再被引用並釋放它。kref_get() 不需要鎖,因為我們已經擁有一個我們擁有引用計數的有效指標。put 不需要鎖,因為沒有任何東西在未持有指標的情況下嘗試獲取資料。

在上述示例中,kref_put() 將在成功和錯誤路徑中被呼叫 2 次。這是必要的,因為引用計數透過 kref_init()kref_get() 增加了 2 次。

請注意,規則 1 中的“之前”非常重要。你絕不應該這樣做:

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

不要自以為是地使用上述構造。首先,你可能不知道自己在做什麼。其次,你可能知道自己在做什麼(在某些涉及鎖定的情況下,上述做法可能是合法的),但其他不知道自己在做什麼的人可能會修改程式碼或複製程式碼。這是一種不好的風格。不要這樣做。

在某些情況下,你可以最佳化 gets 和 puts。例如,如果你完成了對一個物件的操作,並將其排隊給其他東西處理,或者傳遞給其他東西,那麼就沒有理由先執行 get 再執行 put:

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

直接進行排隊即可。對此的註釋總是受歡迎的。

enqueue(obj);
/* We are done with obj, so we pass our refcount off
   to the queue.  DON'T TOUCH obj AFTER HERE! */

最後一個規則(規則 3)是最難處理的。例如,假設你有一個 kref-ed 項的列表,你希望獲取第一個項。你不能只是從列表中取出第一個項並 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_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);
}

如果你需要作為釋放操作的一部分呼叫其他可能耗時或可能佔用相同鎖的例程,這會更有用。請注意,仍然首選在釋放例程中完成所有操作,因為它更整潔。

上述示例也可以使用 kref_get_unless_zero() 進行最佳化,方法如下:

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

這有助於移除 put_entry() 中圍繞 kref_put() 的互斥鎖,但重要的是 kref_get_unless_zero 必須包含在查詢表中查詢條目的相同臨界區內,否則 kref_get_unless_zero 可能會引用已釋放的記憶體。請注意,在不檢查其返回值的情況下使用 kref_get_unless_zero 是非法的。如果你確定(透過已經擁有一個有效指標)kref_get_unless_zero() 將返回 true,那麼請改用 kref_get()

Krefs 和 RCU

函式 kref_get_unless_zero 也使得在上述示例中使用 rcu 鎖定進行查詢成為可能:

struct my_data
{
        struct rcu_head rhead;
        .
        struct kref refcount;
        .
        .
};

static struct my_data *get_entry_rcu()
{
        struct my_data *entry = NULL;
        rcu_read_lock();
        if (!list_empty(&q)) {
                entry = container_of(q.next, struct my_data, link);
                if (!kref_get_unless_zero(&entry->refcount))
                        entry = NULL;
        }
        rcu_read_unlock();
        return entry;
}

static void release_entry_rcu(struct kref *ref)
{
        struct my_data *entry = container_of(ref, struct my_data, refcount);

        mutex_lock(&mutex);
        list_del_rcu(&entry->link);
        mutex_unlock(&mutex);
        kfree_rcu(entry, rhead);
}

static void put_entry(struct my_data *entry)
{
        kref_put(&entry->refcount, release_entry_rcu);
}

但請注意,在呼叫 release_entry_rcu 後,struct kref 成員需要在 rcu 寬限期內保持在有效記憶體中。這可以透過像上面那樣使用 kfree_rcu(entry, rhead) 來實現,或者透過在使用 kfree 之前呼叫 synchronize_rcu() 來實現,但請注意 synchronize_rcu() 可能會休眠相當長的時間。

函式和結構體

void kref_init(struct kref *kref)

初始化物件。

引數

struct kref *kref

相關物件。

void kref_get(struct kref *kref)

增加物件的引用計數。

引數

struct kref *kref

物件。

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

減少物件的引用計數。

引數

struct kref *kref

物件。

void (*release)(struct kref *kref)

指向當物件的最後一個引用被釋放時將清理物件的函式的指標。

描述

減少引用計數,如果為 0,則呼叫 release。呼叫者不能將 NULL 或 kfree() 作為釋放函式。

返回

如果此呼叫移除了物件,則返回 1,否則返回 0。請注意,如果此函式返回 0,則此函式返回時可能已有其他呼叫者移除了該物件。僅當你想要確定物件是否已完全釋放時,此返回值才確定。

int kref_put_mutex(struct kref *kref, void (*release)(struct kref *kref), struct mutex *mutex)

減少物件的引用計數。

引數

struct kref *kref

物件。

void (*release)(struct kref *kref)

指向當物件的最後一個引用被釋放時將清理物件的函式的指標。

struct mutex *mutex

保護釋放函式的互斥鎖。

描述

kref_lock() 的此變體在持有 mutex 的情況下呼叫 release 函式。release 函式將釋放互斥鎖。

int kref_put_lock(struct kref *kref, void (*release)(struct kref *kref), spinlock_t *lock)

減少物件的引用計數。

引數

struct kref *kref

物件。

void (*release)(struct kref *kref)

指向當物件的最後一個引用被釋放時將清理物件的函式的指標。

spinlock_t *lock

保護釋放函式的自旋鎖。

描述

kref_lock() 的此變體在持有 lock 的情況下呼叫 release 函式。release 函式將釋放鎖。

int kref_get_unless_zero(struct kref *kref)

增加物件的引用計數,除非它為零。

引數

struct kref *kref

物件。

描述

此函式旨在簡化物件引用計數週圍的鎖定,這些物件可以從查詢結構中查詢,並在物件解構函式中從該查詢結構中移除。對此類物件的操作至少需要圍繞查詢 + kref_get 的讀鎖,以及圍繞 kref_put + 從查詢結構中移除的寫鎖。此外,RCU 實現變得極其複雜。透過查詢後跟一個帶有返回值檢查的 kref_get_unless_zero,kref_put 路徑中的鎖定可以推遲到從查詢結構的實際移除,並且 RCU 查詢變得微不足道。

返回

如果增加成功,則為非零。否則返回 0。