RCU适用于需要频繁的读取数据,而相应修改数据并不多的情景,例如在文件系统中,经常需要查找定位目录,而对目录的修改相对来说并不多,这就是RCU发挥作用的最佳场景。
RCU非常适合对链表的操作
主要函数:
static inline void rcu_read_lock(void) //读者进入临界区
rcu_dereference(p) //读者用于获取共享资源的内存区指针
static inline void rcu_read_unlock(void) //读者退出临界区
rcu_assign_pointer(p, v) //用新指针更新老指针
void synchronize_rcu(void) //等待之前的读者完成读操作
RCU的写执行单元在访问它的共享资源前首先复制一个副本,然后对副本进行修改,最后使用一个回调机制在适当的时机把指向原来数据的指针重新指向新的被修改的数据,这个时机就是所有引用该数据的CPU都退出对共享数据读操作的时候。
等待适当时机的这一时期称为宽限期(GracePeriod)。
struct foo {
int a;
char b;
long c;
};
DEFINE_SPINLOCK(foo_mutex);
struct foo *gbl_foo;
void foo_read (void)
{
foo *fp = gbl_foo;
if ( fp != NULL )
dosomething(fp->a, fp->b , fp->c );
}
void foo_update( foo* new_fp )
{
spin_lock(&foo_mutex);
foo *old_fp = gbl_foo;
gbl_foo = new_fp;
spin_unlock(&foo_mutex);
kfee(old_fp);
}
如上的程序,是针对于全局变量gbl_foo的操作。
假设以下场景。有两个线程同时运行 foo_ read和foo_update的时候,当foo_ read执行完赋值操作后,线程发生切换;
此时另一个线程开始执行foo_update并执行完成。当foo_ read运行的进程切换回来后,运行dosomething 的时候,fp已经被删除,这将对系统造成危害。为了防止此类事件的发生,RCU里增加了一个新的概念叫宽限期(Grace period)。如下图所示:
1.读锁定
rcu_read_lock()
rcu_read_lock_bh()
2.读解锁
rcu_read_unlock()
rcu_read_unlock_bh()
使用RCU进行读的模式如下:
rcu_read_lock()
.../* 读临界区*/
rcu_read_unlock()扫描二维码关注公众号,回复: 4048826 查看本文章
3.同步RCU
synchronize_rcu()
该函数由RCU写执行单元调用,它将阻塞写执行单元,直到当前CPU上所有的已经存在(Ongoing)的读执行单元完成读临界区,写执行单元才可以继续下一步操作。synchronize_rcu()并不需要等待后续(Subsequent)读临界区的完成,如图所示。
4.挂接回调
void call_rcu(struct rcu_head *head, void (*func)(struct rcu_head *rcu));
作用和synchronize_rcu()一样,只是它不会使写执行单元阻塞,因而可以在中断上下文或软中断中使用。
该函数把函数func挂接到RCU回调函数链上,然后立即返回。挂接的回调函数会在一个宽限期结束(即所有已经存在的RCU读临界区完成)后被执行。
给RCU保护的指针赋一个新的值
rcu_assign_pointer(p, v)
rcu_dereference(p)
读端使用rcu_dereference()获取一个RCU保护的指针,之后既可以安全地引用它(访问它指向的区域)。一般需要在
rcu_read_lock() /rcu_read_unlock()保护的区间引用这个指针,例如:
rcu_read_lock();
irq_rt = rcu_dereference(kvm->irq_routing);
if (irq < irq_rt->nr_rt_entries)
hlist_for_each_entry(e, &irq_rt->map[irq], link) {
if (likely(e->type == KVM_IRQ_ROUTING_MSI))
ret = kvm_set_msi_inatomic(e, kvm);
else
ret = -EWOULDBLOCK;
break;
}
}
rcu_read_unlock();
上述代码取自virt/kvm/irq_comm.c的kvm_set_irq_inatomic()函数。
rcu_access_pointer(p)
读端使用rcu_access_pointer()获取一个RCU保护的指针,之后并不引用它。这种情况下,我们只关心指针本身的
值,而不关心指针指向的内容。比如我们可以使用该API来判断指针是否为NULL。
把rcu_assign_pointer()和rcu_dereference()结合起来使用,写端分配一个新的struct foo内存,并初始化其中的成员,之后把该结构体的地址赋值给全局的gp指针:
实例代码:
struct el {
struct list_head lp;
long key;
spinlock_t mutex;
int data;
/* Other data fields */
};
DEFINE_SPINLOCK(listmutex);
LIST_HEAD(head); //初始化链表
int search(long key, int *result
{
struct el *p;
rcu_read_lock();
list_for_each_entry_rcu(p, &head, lp){
if (p->key == key) {
*result = p->data;
rcu_read_unlock();
return 1;
}
}
rcu_read_unlock();
return 0;
}
int delete(long key)
{
struct el *p;
spin_lock(&listmutex);
list_for_each_entry(p, &head, lp){
if (p->key == key) {
list_del_rcu(&p->lp);
spin_unlock(&listmutex);
synchronize_rcu();
kfree(p);
return 1;
}
}
spin_unlock(&listmutex);
return 0;
}