RCU通常被用来提供以读为主的数据结构的并发,从而读得开销是最小(通常认为读在内核和用户空间的消耗为0)。原理简单认为读的时候不保护,当读完检查版本,如果为旧版本则放弃。
RCU首先要保证能安全的浏览数据即使数据同时正在被修改,为此RCU采用发布-订阅的模式。例如初始化一个全局的NUll指针gp,gp指向malloc的一块区域并且初始化数据。
struct template{
int a;
int b;
int c;
};
strcut template *gp = NULL;
p = kmalloc(sizeof(*p),GFP_NERNEL);
p->a = 1;
p->b = 2;
p->c = 3;
gp = p;
但这样还是有问题的,如果分配gp在p初始化之前,那么同时读得话会读取未初始化的值。解决这个问题做了一个封装,rcu_assign_pointer。
将gp=p改成rcu_assign_pointer(gp,p),该函数发布新结构时,要求CPU在初始化p之后分配gp。不仅仅如此还要读要在合适的时候执行。
例如:
p = gp;
if(p != NULL){
do_something_with(p->a,p->b,p->c);
}
尽管这段代码对错误进行排除,但在《Memory Ordering in Modern Microprocessors》提出了p->a,p->b,p->c会被提前读取的。更容易理解的是编译器造成的此值被提前读取,编译器猜想p值并获取p->a,p->b,p->c然后获取这实际的p值和guess的值进行对比。无论如何我们需要保护起来这一块,我理解就是RCU只保护被动态分配并通过指针引用的数据结构。
rcu_read_lock();
p = rcu_dereference(gp);
if(p != NUll){
do_something_with(p->a,p->b,p->c);
}
rcu_read_unlock();
实际使用struct list_head
struct template{
struct list_head list;
int a;
int b;
int c;
};
LIST_HEAD(head);
p = kmalloc(sizeof(*p),GFP_KERNEL);
p->a = 1;
p->b =2;
p->c = 3;
list_add_rcu(&p->list,&head);