linux中的内核引用计数器文档 /Documentation/kref.txt翻译。
krefs能让你往你的对象中添加一个引用计数器。如果你有一些需要在多处被使用和传递的对象,而你并没有给这些对象中添加引用计数器的话,你的代码肯定会有某些缺陷,会出现一些问题。如果你想使用引用计数器的话,krefs是你可以使用的方法。
为了使用一个kref,你需要向你的数据结构体中嵌入一个struct kref结构体。
可以使用如下的方式:
struct my_data
{
.
.
struct kref refcount; //向结构体中加入了一个引用计数器。
.
.
};
这个struct kref结构体可以出现在这个数据结构体内的任何地方。
在你分配了这个struct kref结构体之后,你必须要对其进行初始化。为了完成对struct kref结构体的初始话,你可以调用 kref_init():
struct my_data *data;
data = kmalloc(sizeof(struct my_data), GFP_KERNEL);
if(!data)
return -ENOMEM;
kref_init(&data->refcount);
kref_init()函数的原型: void kref_init(struct kref *refcount);
kref_init()函数所做的工作:将struct my_data中的struct kref refcount成员设置为1;
一旦你完成了对struct kref结构体(引用计数器)的初始化,你必须遵守下面的这些规则:
规则一: 如果你对一个指针做了一次非临时性的拷贝,特别是如果这个指针可能被传递到另一个可执行的线程中,那么你必须要在这个拷贝完成之前或这个指针被传递到另一个可执行线程之前,调用用 kref_get(struct kref *refcount)函数对这个引用计数器进行自加操作。如果你已经有了一个指向这个内嵌的struct kref结构体的指针的话(这个引用计数器的值不可 能为零)。
规则二:当你对一个指针的使用结束以后,你必须使用 kref_put()函数:kref_put(&data->refcount, data_release);如果这是对这个指针的最后一次引用的话,这个releas程序将被调用。如果代码不再保持有一个有效的指针,不会再去获取这个被嵌入的struct kref结构体的地址话,那么 直接通过调用 kref_put()函数而不需要使用锁,是不会有问题。
规则三:如果代码没有持有一个有效的指针,而试图去获取对一个嵌入的struct kref结构体的引用的话,代码必须连续访问一个地方,这个地方在使用kref_get()过程中不能有kref_put()函数的出现,并且这个结构体必要要在kref_get()过程中保持有效。
例如:如果你分配了一些数据,然后把它们传递给一个线程去处理:
//当引用计数器的值为0时,通过这个函数来释放不再使用的结构体。
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(struct kref *refcount, void (*func)(struct kref *refcount));
// kref_put()函数完成两件事情:
1.对引用计数器进行自减;
2.注册一个函数,当引用计数器的值为0时,启动func函数对不再使用的结构体进行释放。
kref_put(&data->refcount, data_release);
}
int my_data_handler(void)
{
int rv = 0;
struct my_data *data;
struct task_struct *task; // struct task_struct *task 用于指向一个新建的线程;
data = kmalloc(sizeof(*data), GFP_KERNEL);
if (!data)
return -ENOMEM;
kref_init(&data->refcount); // kref_init(struct kref *refcount)
// 对引用计数器进行初始化,即将refcount设置为1;
//在下面的more_data_handling线程中,要用到struct my_data类型的结构体,所以
//先使用 kref_get(struct kref *refcount)函数对引用计数器先进行一次自加操作。
kref_get(&data->refcount);
// struct task_struct * kthread_run(void (*kthread)(void *data), void *data, thread_name)
// kthread : 线程函数;
// data : 传递给线程的参数;
// thread_name : 线程的名称;
// 使用kthread_run()函数创建的线程会立刻运行;
task = kthread_run(more_data_handling, data, "more_data_handling");
// ERR_PTR(int errno) 函数将 错误码转换称为一个指针;
if (task == ERR_PTR(-ENOMEM)) {
rv = -ENOMEM;
goto out;
}
.
. do stuff with data here
.
out:
kref_put(&data->refcount, data_release);
return rv;
}
我们不需要关心这两个线程处理数据的顺序,这个 kref_put()处理程序知道数据什么时候不再被引用并且释放它。
下面的做法是违反了规则一,所以是错误的做法;
task = kthread_run(more_data_handling, data, "more_data_handling");
if (task == ERR_PTR(-ENOMEM))
{
rv = -ENOMEM;
goto out;
}
else
kref_get(&data->refcount);// 这个函数应该在kthread_run()运行之前被调用;
在一些情况下,你可能要优化这gets和puts。例如,如果你使用完了一个对象,然后把这个对象传递给其他的函数或将这个对象插入一个队列中以便其他函数使用,就不能先进行一个get然后一个put。
下面的做法是错误的:
/* Silly extra get and put 十分愚蠢的get和put*/
kref_get(&obj->ref);
enqueue(obj);
kref_put(&obj->ref, obj_cleanup);
而正确的做法是:仅仅需要一个入队操作;
enqueue(obj);
而这个规则三是比较难以去处理的。例如:你有一个链表,并且链表中的每个项中都有一个嵌入的struct kref结构体,并且你希望从链表中获取第一个数据项。你不能让这个链表中的第一个数据脱离这个链表,然后kref_get()这个数据。那种做法违反了规则3,因为你并没有持有一个有效的指针。你必须添加一个互斥锁或其他的锁。例如:
static DEFINE_MUTEX(mutex); // DEFINE_MUTEX(mutex) 定义了一个mutex互斥锁并且对其进行初始化
static LIST_HEAD(q); // LIST_HEAD(q) 定义了一个q链表头并对其进行初始化;
struct my_data
{
struct kref refcount; //给struct my_data中加入了一个引用计数器;
struct list_head link;
};
static struct my_data *get_entry()
{
struct my_data *entry = NULL;
mutex_lock(&mutex); // mutex_lock(int *mutex) 用于获取一个互斥锁;
if (!list_empty(&q)) // 如果链路不为空;
{
entry = container_of(q.next, struct my_q_entry, link);
kref_get(&entry->refcount);//引用计数器值加1;
}
mutex_unlock(&mutex);// mutex_unlock(&mutex) mutex_unlock(int *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()的返回值。
例如:
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);
}
总结一下:这个文档主要介绍了三个函数
void kref_init(struct kref *refcount);//用于对内核引用计数器进行初始化;
void kref_get(struct kref *refcount); //对引用计数器进行加1操作;
int kref_put(struct kref *refcount, void (*func)(struct kref *refcount));
// kref_put()先对引用计数器中的值进行减 1操作,返回0。
//当引用计数器的值为0的话,调用func函数来对不再使用的结构体释放,然后返会正数。