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

来源:互联网 发布:网站空间绑定域名 编辑:程序博客网 时间:2024/05/17 21:45

本文简单介绍了设备驱动模型中最最简单的一个数据结构: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?为何要做这样的限制?