英语

向内核对象添加引用计数器 (krefs)

作者:

Corey Minyard <minyard@acm.org>

作者:

Thomas Hellstrom <thellstrom@vmware.com>

其中很多内容是从 Greg Kroah-Hartman 2004 年关于 krefs 的 OLS 论文和演讲中摘取的,可以在以下位置找到:

简介

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_put() 不能在 kref_get() 期间发生,并且该结构在 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);

不要假设您知道自己在做什么并使用上面的构造。 首先,您可能不知道自己在做什么。 其次,您可能知道自己在做什么(在某些涉及锁定的情况下,上述情况可能是合法的),但其他不知道自己在做什么的人可能会更改代码或复制代码。 这是一种不好的风格。 不要这样做。

在某些情况下,您可以优化 get 和 put。 例如,如果您已完成某个对象,并将其排队用于其他用途或传递给其他用途,则没有理由先执行 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,并且您希望获取第一个项目。 您不能只是从列表中取出第一个项目并对其执行 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);
}

如果必须调用其他例程作为可能需要很长时间或可能声明同一锁的 free 操作的一部分,则此方法更有用。 请注意,仍然首选在释放例程中执行所有操作,因为它更简洁一些。

也可以使用 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() 可能会睡眠相当长的时间。