312 lines · plain
1.. include:: ../disclaimer-zh_CN.rst2 3:Original: Documentation/core-api/kref.rst4 5翻译:6 7司延腾 Yanteng Si <siyanteng@loongson.cn>8 9校译:10 11 <此处请校译员签名(自愿),我将在下一个版本添加>12 13.. _cn_core_api_kref.rst:14 15=================================16为内核对象添加引用计数器(krefs)17=================================18 19:作者: Corey Minyard <minyard@acm.org>20:作者: Thomas Hellstrom <thellstrom@vmware.com>21 22其中很多内容都是从Greg Kroah-Hartman2004年关于krefs的OLS论文和演讲中摘23录的,可以在以下网址找到:24 25 - http://www.kroah.com/linux/talks/ols_2004_kref_paper/Reprint-Kroah-Hartman-OLS2004.pdf26 - http://www.kroah.com/linux/talks/ols_2004_kref_talk/27 28简介29====30 31krefs允许你为你的对象添加引用计数器。如果你有在多个地方使用和传递的对象,32而你没有refcounts,你的代码几乎肯定是坏的。如果你想要引用计数,krefs是个33好办法。34 35要使用kref,请在你的数据结构中添加一个,如::36 37 struct my_data38 {39 .40 .41 struct kref refcount;42 .43 .44 };45 46kref可以出现在数据结构体中的任何地方。47 48初始化49======50 51你必须在分配kref之后初始化它。 要做到这一点,可以这样调用kref_init::52 53 struct my_data *data;54 55 data = kmalloc(sizeof(*data), GFP_KERNEL);56 if (!data)57 return -ENOMEM;58 kref_init(&data->refcount);59 60这将kref中的refcount设置为1。61 62Kref规则63========64 65一旦你有一个初始化的kref,你必须遵循以下规则:66 671) 如果你对一个指针做了一个非临时性的拷贝,特别是如果它可以被传递给另一个执68 行线程,你必须在传递之前用kref_get()增加refcount::69 70 kref_get(&data->refcount);71 72 如果你已经有了一个指向kref-ed结构体的有效指针(refcount不能为零),你73 可以在没有锁的情况下这样做。74 752) 当你完成对一个指针的处理时,你必须调用kref_put()::76 77 kref_put(&data->refcount, data_release);78 79 如果这是对该指针的最后一次引用,释放程序将被调用。如果代码从来没有尝试过80 在没有已经持有有效指针的情况下获得一个kref-ed结构体的有效指针,那么在没81 有锁的情况下这样做是安全的。82 833) 如果代码试图获得对一个kref-ed结构体的引用,而不持有一个有效的指针,它必84 须按顺序访问,在kref_put()期间不能发生kref_get(),并且该结构体在kref_get()85 期间必须保持有效。86 87例如,如果你分配了一些数据,然后将其传递给另一个线程来处理::88 89 void data_release(struct kref *ref)90 {91 struct my_data *data = container_of(ref, struct my_data, refcount);92 kfree(data);93 }94 95 void more_data_handling(void *cb_data)96 {97 struct my_data *data = cb_data;98 .99 . do stuff with data here100 .101 kref_put(&data->refcount, data_release);102 }103 104 int my_data_handler(void)105 {106 int rv = 0;107 struct my_data *data;108 struct task_struct *task;109 data = kmalloc(sizeof(*data), GFP_KERNEL);110 if (!data)111 return -ENOMEM;112 kref_init(&data->refcount);113 114 kref_get(&data->refcount);115 task = kthread_run(more_data_handling, data, "more_data_handling");116 if (task == ERR_PTR(-ENOMEM)) {117 rv = -ENOMEM;118 kref_put(&data->refcount, data_release);119 goto out;120 }121 122 .123 . do stuff with data here124 .125 out:126 kref_put(&data->refcount, data_release);127 return rv;128 }129 130这样,两个线程处理数据的顺序并不重要,kref_put()处理知道数据不再被引用并释131放它。kref_get()不需要锁,因为我们已经有了一个有效的指针,我们拥有一个132refcount。put不需要锁,因为没有任何东西试图在没有持有指针的情况下获取数据。133 134在上面的例子中,kref_put()在成功和错误路径中都会被调用2次。这是必要的,因135为引用计数被kref_init()和kref_get()递增了2次。136 137请注意,规则1中的 "before "是非常重要的。你不应该做类似于::138 139 task = kthread_run(more_data_handling, data, "more_data_handling");140 if (task == ERR_PTR(-ENOMEM)) {141 rv = -ENOMEM;142 goto out;143 } else144 /* BAD BAD BAD - 在交接后得到 */145 kref_get(&data->refcount);146 147不要以为你知道自己在做什么而使用上述构造。首先,你可能不知道自己在做什么。148其次,你可能知道自己在做什么(有些情况下涉及到锁,上述做法可能是合法的),149但其他不知道自己在做什么的人可能会改变代码或复制代码。这是很危险的作风。请150不要这样做。151 152在有些情况下,你可以优化get和put。例如,如果你已经完成了一个对象,并且给其153他对象排队,或者把它传递给其他对象,那么就没有理由先做一个get,然后再做一个154put::155 156 /* 糟糕的额外获取(get)和输出(put) */157 kref_get(&obj->ref);158 enqueue(obj);159 kref_put(&obj->ref, obj_cleanup);160 161只要做enqueue就可以了。 我们随时欢迎对这个问题的评论::162 163 enqueue(obj);164 /* 我们已经完成了对obj的处理,所以我们把我们的refcount传给了队列。165 在这之后不要再碰obj了! */166 167最后一条规则(规则3)是最难处理的一条。例如,你有一个每个项目都被krefed的列表,168而你希望得到第一个项目。你不能只是从列表中抽出第一个项目,然后kref_get()它。169这违反了规则3,因为你还没有持有一个有效的指针。你必须添加一个mutex(或其他锁)。170比如说::171 172 static DEFINE_MUTEX(mutex);173 static LIST_HEAD(q);174 struct my_data175 {176 struct kref refcount;177 struct list_head link;178 };179 180 static struct my_data *get_entry()181 {182 struct my_data *entry = NULL;183 mutex_lock(&mutex);184 if (!list_empty(&q)) {185 entry = container_of(q.next, struct my_data, link);186 kref_get(&entry->refcount);187 }188 mutex_unlock(&mutex);189 return entry;190 }191 192 static void release_entry(struct kref *ref)193 {194 struct my_data *entry = container_of(ref, struct my_data, refcount);195 196 list_del(&entry->link);197 kfree(entry);198 }199 200 static void put_entry(struct my_data *entry)201 {202 mutex_lock(&mutex);203 kref_put(&entry->refcount, release_entry);204 mutex_unlock(&mutex);205 }206 207如果你不想在整个释放操作过程中持有锁,kref_put()的返回值是有用的。假设你不想在208上面的例子中在持有锁的情况下调用kfree()(因为这样做有点无意义)。你可以使用kref_put(),209如下所示::210 211 static void release_entry(struct kref *ref)212 {213 /* 所有的工作都是在从kref_put()返回后完成的。*/214 }215 216 static void put_entry(struct my_data *entry)217 {218 mutex_lock(&mutex);219 if (kref_put(&entry->refcount, release_entry)) {220 list_del(&entry->link);221 mutex_unlock(&mutex);222 kfree(entry);223 } else224 mutex_unlock(&mutex);225 }226 227如果你必须调用其他程序作为释放操作的一部分,而这些程序可能需要很长的时间,或者可228能要求相同的锁,那么这真的更有用。请注意,在释放例程中做所有的事情还是比较好的,229因为它比较整洁。230 231上面的例子也可以用kref_get_unless_zero()来优化,方法如下::232 233 static struct my_data *get_entry()234 {235 struct my_data *entry = NULL;236 mutex_lock(&mutex);237 if (!list_empty(&q)) {238 entry = container_of(q.next, struct my_data, link);239 if (!kref_get_unless_zero(&entry->refcount))240 entry = NULL;241 }242 mutex_unlock(&mutex);243 return entry;244 }245 246 static void release_entry(struct kref *ref)247 {248 struct my_data *entry = container_of(ref, struct my_data, refcount);249 250 mutex_lock(&mutex);251 list_del(&entry->link);252 mutex_unlock(&mutex);253 kfree(entry);254 }255 256 static void put_entry(struct my_data *entry)257 {258 kref_put(&entry->refcount, release_entry);259 }260 261这对于在put_entry()中移除kref_put()周围的mutex锁是很有用的,但是重要的是262kref_get_unless_zero被封装在查找表中的同一关键部分,否则kref_get_unless_zero263可能引用已经释放的内存。注意,在不检查其返回值的情况下使用kref_get_unless_zero264是非法的。如果你确信(已经有了一个有效的指针)kref_get_unless_zero()会返回true,265那么就用kref_get()代替。266 267Krefs和RCU268==========269 270函数kref_get_unless_zero也使得在上述例子中使用rcu锁进行查找成为可能::271 272 struct my_data273 {274 struct rcu_head rhead;275 .276 struct kref refcount;277 .278 .279 };280 281 static struct my_data *get_entry_rcu()282 {283 struct my_data *entry = NULL;284 rcu_read_lock();285 if (!list_empty(&q)) {286 entry = container_of(q.next, struct my_data, link);287 if (!kref_get_unless_zero(&entry->refcount))288 entry = NULL;289 }290 rcu_read_unlock();291 return entry;292 }293 294 static void release_entry_rcu(struct kref *ref)295 {296 struct my_data *entry = container_of(ref, struct my_data, refcount);297 298 mutex_lock(&mutex);299 list_del_rcu(&entry->link);300 mutex_unlock(&mutex);301 kfree_rcu(entry, rhead);302 }303 304 static void put_entry(struct my_data *entry)305 {306 kref_put(&entry->refcount, release_entry_rcu);307 }308 309但要注意的是,在调用release_entry_rcu后,结构kref成员需要在有效内存中保留一个rcu310宽限期。这可以通过使用上面的kfree_rcu(entry, rhead)来实现,或者在使用kfree之前311调用synchronize_rcu(),但注意synchronize_rcu()可能会睡眠相当长的时间。312