本文简单介绍了设备驱动模型中最最简单的一个数据结构:kref,它作为内核中最基本的引用计数而存在。
首先直观地介绍该数据结构及操作它的一些方法,然后再介绍其具体的用法。参考:kref.h kref.c kref.txt
一、kref及操作kref的方法
struct kref
{
atomic_t refcount;
};
可以看到kref结构体的成员只有一个原子变量refcount,为什么还要用kref结构体来包装一下呢?
目前我所知道的有两种说法:
1、为了方便编译器做类型检查(不是很懂......)
2、为了以后方便扩展(这个很好理解)
不管怎样,反正目前这个结构体很简单啦。另外,内核还提供了4个函数用来操作kref:
void kref_set(struct kref *kref, int num);
void kref_init(struct kref *kref);
void kref_get(struct kref *kref);
int kref_put(struct kref *kref, void (*release) (struct kref *kref));
下面来看一下它们的源码。
/**
* kref_set - initialize object and set refcount to requested number.
* @kref: object in question.
* @num: initial reference counter
*/
void kref_set(struct kref *kref, int num)
{
atomic_set(&kref->refcount, num); //设置kref的引用计数为num
smp_mb(); //......这个暂时就不管了吧,没具体研究过
}
/**
* kref_init - initialize object.
* @kref: object in question.
*/
void kref_init(struct kref *kref)
{
kref_set(kref, 1); //简单地调用kref_set,将引用计数设置为1
}
/**
* kref_get - increment refcount for object.
* @kref: object.
*/
void kref_get(struct kref *kref)
{
WARN_ON(!atomic_read(&kref->refcount));//如果引用计数为0,内核将给出警告。不过,这样的情况可能发生呢?引用计数为0了,kref不就被释放了吗?这句话的作用应该是捕获编程错误,例如,在调用get之前没有调用init等等
atomic_inc(&kref->refcount);//原子地递增引用计数
smp_mb__after_atomic_inc();//......飘过
}
/**
* kref_put - decrement refcount for object.
* @kref: object.
* @release: pointer to the function that will clean up the object when the
* last reference to the object is released.
* This pointer is required, and it is not acceptable to pass kfree
* in as this function.
*
* Decrement the refcount, and if 0, call release().
* Return 1 if the object was removed, otherwise return 0. Beware, if this
* function returns 0, you still can not count on the kref from remaining in
* memory. Only use the return value if you want to see if the kref is now
* gone, not present.
*/
int kref_put(struct kref *kref, void (*release)(struct kref *kref))
{
WARN_ON(release == NULL);//如果没有指定release函数,内核给出警告
WARN_ON(release == (void (*)(struct kref *))kfree);//如果指定的release函数是kfree,则内核给出警告,从注释中可以看出
if (atomic_dec_and_test(&kref->refcount)) {//原子地递减引用计数,并检测递减后计数是否0
release(kref);//没有被引用了,调用注册进的release函数释放kref
return 1;//返回1,表示对象已被删除
}
return 0;
}
二、kref的用法
一般而言,都是将kref包含进一个自定义的结构体中,从而为包含它的结构体提供引用计数功能。
struct my_data
{
.
.
struct kref refcount;
.
.
};
使用时,在分配了自定义结构体之后,必须对kref成员进行初始化:
struct my_data *data;
data = kmalloc(sizeof(*data), GFP_KERNEL);
if (!data)
return -ENOMEM;
kref_init(&data->refcount); //初始化结构体data的引用计数为1
一旦拥有一个已初始化过的kref,那么必须遵循以下3个规则(因为kref里不存在任何lock,所以在编程时务必遵循规则,否则可能出错):
1)如果对一个kref-ed的结构体指针做非局部性拷贝,特别是当将指针传递给另一个线程时,必须在传递之前调用kref_get()以增加kref-ed的结构体的引用计数:kref_get(&data->refcount);
如果已经有一个有效的指针指向一个包含kref的结构体(引用计数不会为0),那么在kref_get时可以不用“锁”。
2)当完成对kref-ed结构体的使用时,必须要调用kref_put():kref_put(&data->refcount, data_release);
如果这是对结构体的最后的引用,那么data_release函数将被调用。
3)如果在没有取得结构体的一个有效的指针时,尝试去获取kref-ed结构体的引用,则必须串行地访问kref-ed结构体,这样在kref_get时不会发生kref_put,并且在kref_get期间结构体必须保持有效。
我们分析一段代码,来加深对以上3个规则的理解。
代码1:分配一个kref-ed结构体,并把它传递给另一个线程处理
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);//规则1)
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;
}
这种方式不必关心两个线程访问data的顺序,kref_put()函数知道data何时不再有任何引用并且释放data。kref_get不需要“锁”,因为已经有了一个
有效的指针data。
规则3)是最让人烦的,举例来说,有一个链表,其元素都是kref-ed结构,我们希望获取链表的第一个元素的引用。
我们不能简单地将链表的第一个元素pull出来,然后调用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_q_entry, 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);
}
如果不想在整个release操作期间都保持“锁”,可以使用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);
}
上面的这种方式,在release函数会调用其他比较耗时的函数时比较有用,因为毕竟一个“锁”不能长时间的保持。
不过,还是推荐将所有的操作都放在release例程里做,因为那样的话代码会很简洁。
PS:关于kref的用法,基本是翻译kref.txt的,有些东西还没完全理解,待续吧......
其实只要理解了那3个规则,就知道该如何使用kref了,可惜啊,没有完全理解透,提几个问题,待以后理解了再补上。
Q1:关于规则1,为何在将kref-ed结构体传递给另一个进程前,必须调用kref_get?不可以在另一个进程内部调用kref_get吗?
Q2:为何不能将kfree传递给kref_put?为何要做这样的限制?