引言
了解rcu前,先引入一个问题:当多个读者在访问一个全局指针的时候,假设一个写者给全局指针设置了一个新的值,同时需要对旧的指针指向的内存进行销毁。这个时候写着在销毁旧指针的时候,读者可能还在访问该全局变量。
为了解决这个问题,可以通过加锁,或引用计数方式去实现。前者性能不佳;后者会有环形引用的风险;而RCU机制恰到好处。RCU通过机制将写者销毁旧数据的时机延迟到所有读者结束,它具备如下特点:
- 只针对动态分配,且通过指针访问的数据;
- 在读临界区不能sleep;
- 适合对读性能要求很高,对写性能要求不严的场景;
- 对新旧数据不是特别敏感,追求最终一致性;
1 使用介绍
1.1 定义变量
struct xxx_st __rcu *g_rcu_var;
注:struct的内部字段也可以定义为__rcu
1.2 读访问
rcu_read_lock();
struct xxx_st* xxx_tmp = rcu_dereference(g_rcu_var);
# 略略略... ...
rcu_read_unlock();
注:在rcu_read_lock/rcu_read_unlock之间不能阻塞,不能睡眠
1.3 写访问
进程上下文:
spin_lock(&g_var_lock);
struct xxx_st* xxx_old_ptr = rcu_dereference_protected(g_rcu_var, lockdep_is_held(&g_var_lock));
rcu_assign_pointer(g_rcu_var, xxx_new_ptr);
spin_unlock(&g_var_lock);
if(xxx_old_ptr) {
synchronize_rcu();
kfree(xxx_old_ptr);
}
中断上下文:
//struct xxx_st中嵌套struct rcu_head
struct xxx_st {
# 略略略... ...
struct rcu_head rhead;
# 略略略... ...
};
//销毁回调
static void xxx_free_rcu(struct rcu_head *rcu) {
struct xxx_st* xxx_ptr = container_of(rcu, struct xxx_st, rhead);
kfree(xxx_old_ptr);
}
//移除操作
spin_lock(&g_var_lock);
struct xxx_st* xxx_old_ptr = rcu_dereference_protected(g_rcu_var, lockdep_is_held(&g_var_lock));
rcu_assign_pointer(g_rcu_var, xxx_new_ptr);
spin_unlock(&g_var_lock);
if(xxx_old_ptr) {
call_rcu(&xxx_old_ptr->rhead, xxx_free_rcu);
}
注:g_var_lock还可以为struct mutex
1.4 同步
当rcu定义module代码中,假设上面的xxx_free_rcu调用时模块已经卸载,系统就会崩溃。所以在模块卸载时需要执行rcu_barrier进行同步等待数据销毁完毕后再执行退出。