为内核对象添加引用计数器 (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。