点击这里给我发消息【本站不支持IE浏览器】请使用浏览器极速模式浏览本站--效果更佳~!要辅助游戏社区 让游戏简单一点-www.ucxia.com

网站首页 / 精品软件 / 正文

linux内核部件分析(三)——记录生命周期的kref

2011-10-02 可可乐 精品软件 11417 ℃ 0 评论

       kref是一个引用计数器,它被嵌套进其它的结构中,记录所嵌套结构的引用计数,并在计数清零时调用相应的清理函数。kref的原理和实现都非常简单,但要想用好却不容易,或者说kref被创建就是为了跟踪复杂情况下地结构引用销毁情况。所以这里先介绍kref的实现,再介绍其使用规则。

       kref的头文件在include/linux/kref.h,实现在lib/kref.c。闲话少说,上代码。

struct kref {
	atomic_t refcount;
};
      可以看到,kref的结构中就包含一个atomic_t类型的计数值。atomic_t是原子类型,对其操作都要求是原子执行的,有专门的原子操作API执行,即使在多处理器间也保持原子性。使用atomic_t类型充当计数值,就省去了加锁去锁的过程。

void kref_set(struct kref *kref, int num)
{
	atomic_set(&kref->refcount, num);
	smp_mb();
}
kref_set 设置kref的初始计数值。具体计数值设置由原子操作atomic_set完成。之后还有一个smp_mb()是为了增加内存屏障,保证这一写操作会在之后的读写操作完成之前完成。

void kref_init(struct kref *kref)
{
	kref_set(kref, 1);
}

kref_init 初始化kref的计数值为1。

void kref_get(struct kref *kref)
{
	WARN_ON(!atomic_read(&kref->refcount));
	atomic_inc(&kref->refcount);
	smp_mb__after_atomic_inc();
}

kref_get递增kref的计数值。

int kref_put(struct kref *kref, void (*release)(struct kref *kref))
{
	WARN_ON(release == NULL);
	WARN_ON(release == (void (*)(struct kref *))kfree);

	if (atomic_dec_and_test(&kref->refcount)) {
		release(kref);
		return 1;
	}
	return 0;
}

kref_put递减kref的计数值,如果计数值减为0,说明kref所指向的结构生命周期结束,会执行release释放函数。

 

所以说kref的API很简单,kref_init和kref_set基本都是初始时才会用到,平时常用的就是kref_get和kref_put。一旦在kref_put时计数值清零,立即调用结束函数。

kref设计得如此简单,是为了能灵活地用在各种结构的生命周期管理中。要用好它可不简单,好在Documentation/kref.txt中为我们总结了一些使用规则,下面简单翻译一下。

对于那些用在多种场合,被到处传递的结构,如果没有引用计数,bug几乎总是肯定的事。所以我们需要kref。kref允许我们在已有的结构中方便地添加引用计数。

你可以以如下方式添加kref到你的数据结构中:

struct my_data {
    ...
    struct kref refcount;
    ...
};

kref可以出现在你结构中的任意位置。

在分配kref后你必须初始化它,可以调用kref_init,把kref计数值初始为1。

struct my_data *data;

data = kmalloc(sizeof(*data), GFP_KERNEL);
if(!data)
    return -ENOMEM;
kref_init(&data->refcount);

初始化之后,kref的使用应该遵循以下三条规则:

1) 如果你制造了一个结构指针的非暂时性副本,特别是当这个副本指针会被传递到其它执行线程时,你必须在传递副本指针之前执行kref_get:

kref_put(&data->refcount);


2)当你使用完,不再需要结构的指针,必须执行kref_put。如果这是结构指针的最后一个引用,release函数会被调用。如果代码绝不会在没有拥有引用计数的请求下去调用kref_get,在kref_put时就不需要加锁。

kref_put(&data->refcount, data_release);


3)如果代码试图在还没拥有引用计数的情况下就调用kref_get,就必须串行化kref_put和kref_get的执行。因为很可能在kref_get执行之前或者执行中,kref_put就被调用并把整个结构释放掉了。

 

例如,你分配了一些数据并把它传递到其它线程去处理:

void data_release(struct kref *kref)
{
    struct my_data *data = container_of(kref, struct my_data, refcount);
    kree(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);
    task = kthread_run(more_data_handling, data, "more_data_handling");
    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知道何时数据不再有引用计数,可以被销毁。kref_get()调用不需要加锁,因为在my_data_handler中调用kref_get时已经拥有一个引用。同样地原因,kref_put也不需要加锁。

要注意规则一中的要求,必须在传递指针之前调用kref_get。决不能写下面的代码:

task = kthread_run(more_data_handling, data, "more_data_handling");
if(task == ERR_PTR(-ENOMEM)) {
    rv = -ENOMEM;
    goto out;
}
else {
     /* BAD BAD BAD - get is after the handoff */
    kref_get(&data->refcount);


不要认为自己在使用上面的代码时知道自己在做什么。首先,你可能并不知道你在做什么。其次,你可能知道你在做什么(在部分加锁情况下上面的代码也是正确的),但一些修改或者复制你代码的人并不知道你在做什么。这是一种坏的使用方式。

当然在部分情况下也可以优化对get和put的使用。例如,你已经完成了对这个数据的处理,并要把它传递给其它线程,就不需要再做多余的get和put了。

/* Silly extra get and put */
kref_get(&obj->ref);
enqueue(obj);
kref_put(&obj->ref, obj_cleanup);


只需要做enqueue操作即可,可以在其后加一条注释。

enqueue(obj);
/* We are done with obj , so we pass our refcount off to the queue. DON'T TOUCH obj AFTER HERE! */


第三条规则是处理起来最麻烦的。例如,你有一列数据,每条数据都有kref计数,你希望获取第一条数据。但你不能简单地把第一条数据从链表中取出并调用kref_get。这违背了第三条,在调用kref_get前你并没有一个引用。你需要增加一个mutex(或者其它锁)。

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);
}


如果你不想在整个释放过程中都加锁,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函数中做完撤销工作会使代码看起来更整洁。



 






 


Tags:

猜你喜欢

本文暂时没有评论,来添加一个吧(●'◡'●)

欢迎 发表评论:

请填写验证码
«   2019年12月   »
1
2345678
9101112131415
16171819202122
23242526272829
3031
控制面板
您好,欢迎到访网站!
  查看权限
网站分类
搜索
最新留言
文章归档
网站收藏
友情链接
  • 订阅本站的 RSS 2.0 新闻聚合

网站公告★客服QQ游戏侠客服×

看片片提示:最新电影资源【少年的你】提供了。
看片片提示:XXX级电影资源【姐妹狂艳】已开放。
看片片提示:最新枪版电影资源【银河补习班】已开放。
辅助更新:皮皮搞笑【决斗英雄】自瞄加速已开放合作--【神仙工具】里有信息
看片片提示:最新枪版电影资源【蜘蛛侠:英雄远征】已开放。
恭喜张先生:预购QQ:52013 + 52014(我爱你一生,我爱你一世) 押金50000元
恭喜王先生:定购QQ:33***(靓号已激活)。 订购金500元已完成
看片片提示:赵哥需要的片片(日本妈妈2)已上架。 请注意观看
恭喜李先生:定购QQ:66***(靓号已激活)。 订购金1100元已完成
恭喜刘先生:定购QQ:520***(靓号已激活)。 订购金3300元已完成
恭喜张女士:定购QQ:888**0 + 888**1(靓号已激活)。 订购金4600元已完成
恭喜孙先生:盗号QQ:77***(盗号已交付)。 订购金5300元已完成
看片片提示:酒哥需要的片片(日本肉肉)已上架。 请注意观看
恭喜马女士,预购QQ:13520 + 13521(一生我爱你情侣号) 押金60000元
恭喜金女士:定购QQ:99***(靓号已激活)。 订购金1300元已完成
看片片提示:胖子需要的片片(金瓶梅)已上架。 请注意观看
看片片提示:三军需要的片片(老师好)已上架。 请注意观看
恭喜樊先生:定购QQ:1988****(生日号已激活)。 订购金200元已完成
恭喜王女士:定购QQ:7878** + 7878**(情侣号已激活)。 订购金3300元已完成
恭喜冯先生:盗号QQ:386****(盗号已交付)。 订购金300元已完成
恭喜刘女士:盗号QQ:999**(盗号已交付)。 订购金6300元已完成
★①:QQ靓号视频仅支持火狐浏览器~★②:承接QQ盗号,先盗号后付款 QQ81801116~★①:需要看片片的留言给我第二天就有了~★②:神仙工具就是游戏辅助(非外挂)各种内部或免费的都 有