向内核对象添加引用计数器 (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,您必须遵循以下规则:
如果您创建指针的非临时副本,尤其是当它可以传递给另一个执行线程时,您必须在传递之前使用 kref_get() 递增引用计数。
kref_get(&data->refcount);
如果您已经拥有指向 kref 结构的有效指针(引用计数不能为零),则可以执行此操作而无需锁定。
当您不再需要指针时,必须调用 kref_put()。
kref_put(&data->refcount, data_release);
如果这是指向指针的最后一个引用,将调用释放例程。 如果代码永远不会尝试在不持有有效指针的情况下获取指向 kref 结构的有效指针,则可以安全地执行此操作而无需锁定。
如果代码尝试在不持有有效指针的情况下获取对 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()
可能会睡眠相当长的时间。