2.2 引用计数
软件开发常常面对将一个分配好内存的对象多次传递,并在多处使用的场景,我们需要在程序动态运行的现实下,明确知道这些对象使用完毕的时机,以便释放它所占用的内存。为此,需要引用计数:
• 防止内存泄漏:确保已分配的对象最终会被释放;
• 防止访问已释放的内存:确保不会使用已经被释放的对象。
数据结构kref为任何内核数据结构添加引用计数提供了一种简单但有效的方法,kref只有一个元素,其结构中的域如表2-1所示。
表2-1 kref结构中的域(来自文件include/linux/kref.h)
Linux内核提供了以下函数对引用计数进行操作。
1.void kref_init(struct kref *kref);
初始化对象,将对象的引用计数设置为1,而不是0;这是因为生成该对象的代码也需要一个最初的引用,以防止其他部分在调用kref_put时释放该对象。
2.void kref_get(struct kref *kref);
递增对象的引用计数。在这之前,确保引用计数不为0,否则打印一条警告消息。这可以防止常见的错误:不先调用kref_init,而直接调用kref_get。
3.int kref_put(struct kref *kref, void (*release) (struct kref *kref));
递减对象的引用计数。如果该计数减为0,则表明是该对象的最后一个引用,因此传入的release函数被调用,以回收这个对象用到的内存。
使用上述kref_init、kref_get和kref_put函数,再结合调用者提供的release函数,可以在任何内核结构中加入完整的引用计数。
首先,将kref结构嵌入到需要使用引用计数的结构之中。例如,要在结构foo中添加引用计数,应该如下定义:
struct foo { ... struct kref refcount; ... };
在定义时,需要将整个kref结构嵌入结构foo中,而不是一个指向kref结构的指针。
如果有一个foo结构,找到嵌入到其中的kref非常简单,只需要使用refcount成员。但是,处理kref的代码(例如release回调函数)常常遇到相反的问题:给出一个struct kref指针,如何找到包含它的结构的指针?我们并不建议将refcount作为foo结构的第一个域,不鼓励程序员在两种对象类型间进行愚蠢的强制转换(Linux内核也会有将一个数据结构作为另一个数据结构的第一个域,例如sock和inet_sock等,并在这两种的数据结构的对象之间进行强制转换。但这主要是为了实现类似面向对象语言的派生特性)。相反,你应该使用定义在<linux/kernel.h>中的container_of宏。
container_of(pointer, type, member)
其中,type为宿主对象的数据结构类型,member为结构中某个内嵌域(成员)的名字,pointer是指向宿主对象的member域的指针,这个宏的返回值是指向宿主对象的指针,如图2-2所示。
图2-2 使用container_of宏获得已知成员指针的宿主对象指针
contain_of宏的实现原理是,考虑成员member相对于类型为type的宿主结构的起始地址的偏移量。对于所有该类型的宿主对象,这个偏移量是固定的。并且可以在假设宿主对象地址值为0,通过返回member域的地址获得,即等于(unsigned long)(&((type *)0)->member)。这样,将宿主对象的member域的地址(pointer)减去这个偏移量,就可以得到宿主对象的地址,再将它转换为type类型的指针。
因此,对于上面的例子,通过指向refcount的指针kref得到指向包含它的类型为foo的结构的指针如下:
foo = container_of(kref, struct foo, refcount);
在创建foo对象时,除了为它分配内存空间,kref域也必须被初始化,否则无法使用引用计数功能。
struct foo *foo_create(void) { struct foo *foo; foo = kzalloc(struct foo, GFP_KERNEL); if (!foo) { return NULL; } kref_init(&foo->kref); return foo; }
在foo被创建时,该结构的内部引用计数被设置为1。因此,设置这个内核对象的代码(模块)必须调用一次kref_put()以最终释放这个引用。
现在就可以随意地递增或递减结构的引用计数了。在使用foo结构之前,要递增引用计数,调用函数kref_get。在使用完后,应该调用kref_put函数释放这个引用。
一般来说,大多数代码选择继续封装kref_get和kref_put以方便使用。
struct foo *foo_get(struct foo *foo) { if (foo) kref_get(&foo->kref); return foo; } void foo_put(struct foo *foo) { if (foo) { kref_put(&foo->kref, foo_release); } }
前面说过,这个包含了kref变量的结构的最初创建者,在使用完结构后,也应该调用这个函数。kfree函数不允许被直接调用,因为内核的其他部分可能还持有这个结构的有效引用。
在最后一个引用计数被释放时,被传入kref_put的函数foo_release将被调用,这个函数的目的是释放已分配的foo结构的内存。foo_release函数的原型接收的是指向struct kref的指针,因此需要用前面提到的container_of宏转换为指向struct foo结构的指针,然后再调用内存释放函数。
void foo_release(struct kref *kref) { struct foo *foo; foo = container_of(foo, struct foo, kref); kfree(foo); }
在kref_put函数被调用后,后续代码中不能继续使用foo结构体,因为它的内存可能已经被释放。