linux内核中kref的引入
首先,linux的内核是大内核,也就是说所有的功能都在一个内核中实现,没有像微内核那样被模块化实现,如此一来就会出现很多的共享数据结构,比如一个数据结构一个模块使用,另一个模块也使用,那么应该如何实现一个内核机制使得各个模块可以安全的使用共享数据就成了一件很重要的事情,这里的安全的意义就是绝对不能访问到一个已经被释放的内存数据结构。linux采用了一种懒惰的方式来管理共享数据结构,也就是说使用引用
首先,linux的内核是大内核,也就是说所有的功能都在一个内核中实现,没有像微内核那样被模块化实现,如此一来就会出现很多的共享数据结构,比如一个数据结构一个模块使用,另一个模块也使用,那么应该如何实现一个内核机制使得各个模块可以安全的使用共享数据就成了一件很重要的事情,这里的安全的意义就是绝对不能访问到一个已经被释放的内存数据结构。linux采用了一种懒惰的方式来管理共享数据结构,也就是说使用引用计数,如果有模块使用一个数据结构,那么在使用前就将该数据结构的引用计数加1,如果要释放,那么就减1,这样一来的,每一个使用者就只管自己就可以了,懒惰的计数管理逻辑会处理好什么时候彻底释放内存,基本上都是在引用计数为0的时候释放内存,当然也可以实现其他策略,事实上,引用计数法实现安全访问原理很简单,只要引用计数不为0,那么计数管理器就不会释放内存,而使用者在使用前要递增其引用计数,那么使用中该引用计数肯定不为0,该对应的数据结构也就不会被释放了,而只有当使用完这个数据结构之后才会递减引用计数,这个懒惰的管理方式保证只要本次访问的数据结构没有被释放就可以了,它不会去顾及别的使用者,然而这个机制很有效,不是吗?
原理很简单,已经有了,接下来内核如何实现它呢?最显然的方式就是在每一个需要共享的数据结构中设置一个原子类型的引用计数字段,然后在使用前和使用后调用atomic_inc和atomic_dec就可以了,在调用atomic_dec之后要判断一下是否引用计数为0,如果是的话就释放该数据结构,因此可以使用内核的另外一个函数atomic_dec_and_test,我们来评估一下这个方案,其实很不错,但是内核中会充斥着冗余的代码,比如只要使用该数据结构的地方都会存在atomic_dec_and_test调用,因此如果能将其封装成一个函数或者将这个引用计数封装成一个类似OO中类的东西,然后提供get递增方式二put递减方式就可以了,这就是kref的引入:
struct kref {
atomic_t refcount;
};
get操作就不说了,其实就是inc引用计数,其实如果内核是OO语言写的,get和put可以写到kref类里面的,并且struct也成了class,不过难道机器会在乎怎么写吗?它只在乎效率而不是可读性和可维护性,对机器来说,给它什么它就执行什么,21世纪给它一个16程序,它不会有任何怨言的。下面看一下put,注意提供了回调函数:
int kref_put(struct kref *kref, void (*release)(struct kref *kref))
{
...//一些安全相关的判断,release不能是kfree函数,但是也不是绝对不能是,主要是由于本身kref就是帮忙管理引用计数的,而release函数的本意应该是做kref宿主结构体的善后工作而不是kref本身的,再说一旦你调用kfree,那kref就没有了,引用计数都没有了还怎么管理,万一有别的模块在使用该结构体怎么办,内核中绝对杜绝访问到空指针的,但是策略的事谁也不好说,全凭程序员怎么定义和实现了
if (atomic_dec_and_test(&kref->refcount)) {
release(kref); //如果计数为0,那么就释放,这个回调函数由调用者提供
return 1;
}
return 0;
}
我个人觉得,按照封装的思想,下面的实现会好一点:
struct kref_V2 {
atomic_t refcount;
void (*release)(struct kref_V2 *kref);
void (*callback)(void *p); //将一个回调函数和该kref分离,不再在release中做完一切了
void * argv;
};
get操作就不说了,其实就是inc引用计数,其实如果内核是OO语言写的,get和put可以写到kref类里面的,并且struct也成了class,不过难道机器会在乎怎么写吗?它只在乎效率而不是可读性和可维护性,对机器来说,给它什么它就执行什么,21世纪给它一个16程序,它不会有任何怨言的。下面看一下put,注意提供了回调函数:
int kref_put(struct kref_V2 *kref)
{
...
if (atomic_dec_and_test(&kref->refcount)) {
release(kref);
if( kref->callback ) //如果计数器为0,那么就在这里面调用用户提供的清理函数,一般是内存释放或者设备停止
callback( kref->argv );
return 1;
}
return 0;
}
当然以上的实现仅仅从oo的角度来说比较好,但是内核中还是使用了第一种方式,因为你可以在release这个唯一的用户提供的回调函数中用container_of得到该kref宿主的地址,然后可以对它做任何事。总之,kref又是linux内核中关于OO的另外一个很好的例子。
更多推荐
所有评论(0)