您的位置:首页 > 理论基础 > 数据结构算法

基本的数据结构学习笔记:kref

2012-09-05 15:57 309 查看
本文简单介绍了设备驱动模型中最最简单的一个数据结构: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?为何要做这样的限制?
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: