前言
复习一下linux内核中的同步管理机制,内核中用于同步管理的几个机制主要有:
1 2 3 4 5 6 7 8
| 自旋锁- 同一个时刻只能被一个代码路径持有锁,其他代码路径一直忙等待,不会休眠,适合占用锁比较短的场景,可以用于中断上下文 原子量- 适合临界区只是一个变量的情况,开销比较小 内存屏障-这个主要是为了阻止编译器的优化而进行指令重排,导致实际执行路径不符合预期的场景 信号量-适合生产者消费者模型,等待会进入睡眠状态,不能用于中断上下文,适合情况复杂,加锁时间较长的场景 互斥量-同一个时刻只有一个代码路径持有锁,会进入睡眠状态,不能用于中断上下文,比信号量简单,结构更小 读写锁-允许同时多个读,同时只能一个写,读写互斥 RCU- read-copy-update 目的是减少多CPU间缓存一致性的开销,允许写者在读者访问期间就修改内容,没有读者的时候更新内容
|
RCU
其他机制都是用了原子操作指令,多CPU争抢变量的时候会导致性能下降,同步开销比较大。RCU通过创建副本,在读者访问时就可以提前修改,没有读者时更新数据。可以在不阻塞读者的情况下更新数据结构。一般用来对一个指针指向的空间进行保护。RCU可以用来替换读写锁。
1 2 3 4 5 6 7 8 9
| // RCU 相关的API
rcu_read_lock()/rcu_read_unlock(): 读保护 rcu_dereference(): 获取被保护的指针 rcu_assign_pointer(): 写者更新指针 synchronize_rcu():同步等待读者访问结束后更新,但是不保证所有清理动作结束,会阻塞当前进程 call_rcu():注册一个毁掉函数,销毁旧数据,不会阻塞直接返回 rcu_barrier():等待所有回调调用结束,一般在模块卸载时使用
|
写者删除一个rcu时,可以通过synchronize_rcu等待没有读者访问了,再释放数据。在中断上下文中,则使用call_rcu来处理。
在可卸载的模块中,如果要卸载模块需要首先保证不再发送回调了,然后调用rcu_barrier确保前面的回调已经被调用了,最终才能卸载模块。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24
| list_del_rcu(p); synchronize_rcu(); kfree(p);
list_del_rcu(p); call_rcu(&p->rcu, p_callback);
static void p_callback(struct rcu_head *rp) { struct pstruct *p = container_of(rp, struct pstruct, rcu);
kfree(p); }
void test_exit(void) { // stop post delete callback
// call rcu_barrier
// return , allow unload module
}
|
测试代码:https://gitee.com/fishmwei/blog_code/blob/master/linux-kernel/rcu/myrcu.c
行动,才不会被动!
欢迎关注个人公众号 微信 -> 搜索 -> fishmwei,沟通交流。
博客地址: https://fishmwei.github.io
掘金主页: https://juejin.cn/user/2084329776486919