天天看点

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

本文简单介绍了设备驱动模型中最最简单的一个数据结构: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));

下面来看一下它们的源码。

void kref_set(struct kref *kref, int num)

{

 atomic_set(&kref->refcount, num); //设置kref的引用计数为num

 smp_mb();             //......这个暂时就不管了吧,没具体研究过

}

void kref_init(struct kref *kref)

{

 kref_set(kref, 1); //简单地调用kref_set,将引用计数设置为1

}

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();//......飘过

}

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)

{ }

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?为何要做这样的限制?

继续阅读