rcu入门

rcu入门

是什么

  • 可参考官方文档

  • Read-copy update,可以理解为,先读数据,修改之后,一次性替换旧数据

  • 是linux内核的同步机制,提供线程安全的并发访问

应用场景

  • 典型应用场景
    • 链表
    • 读多写少

实现

链表插入节点

  • 在A之前插入节点,分为3步

  • 1.new 新节点

  • 2.新节点next指针指向A

  • 3.前置节点的next指针,指向新节点

分析

  • 在2步之后时,所有遍历链表操作正常
  • 在3步之后,所有遍历链表操作也均正常
  • 因为改变指针是原子的,所以不会有问题

链表删除节点

  • 删除B节点,分为3步

  • A节点的next指针,指向C
  • 等待宽限期过去
  • delete B节点

分析

  • 1步之后,访问到A节点的线程,可以继续访问后续C节点;访问到B节点的线程,可以继续访问C节点
  • 2步,宽限期的含义是,等待所有访问B节点的线程,释放对B节点的访问
  • 3步,当没有线程访问B节点时,可以删除B节点

宽限期详解

  • 写需要加锁
  • 读不需要加锁

参考下面的例子

  • 两个线程,一个读,一个写
  • 假设,当读线程获取gbl_foo之后,线程切换
  • 此时写线程更新gbl_foo,销毁旧gbl_foo
  • 之后,读线程切换回来,发现gbl_foo已经是空指针
 1 struct foo{
 2     int a;
 3     char b;
 4     long c;
 5 };
 6 
 7 DEFINE_SPINLOCK(foo_mutex);
 8 
 9 void foo_read(void)
10 {
11     foo *fp = gbl_foo;
12     if( fp != NULL )
13     {
14         dosomthing(fp->a, fp->b, fp->c);
15     }
16 }
17 
18 void foo_update(foo * new_fp)
19 {
20     spin_lock(&foo_mutex);
21     foo *old_fp = gbl_foo;
22     gbl_foo = new_fp;
23     spin_unlock(&foo_mutex);
24 }

宽限期,即用来解决该问题

  • 写线程更新完gbl_foo之后,调用synchronize_rcu(),阻塞等待至宽限期结束,之后再释放旧指针

  • 读线程加rcu_read_lock()和 rcu_read_unlock(),并不实际加锁,而是进入宽限期

  • synchronize_rcu() 函数需要等待,在此之前所有调用rcu_read_lock() 函数的线程,进行rcu_read_unlock()

    这意味着,所有可能持有旧指针的线程不在使用旧指针

  • 实现上设计复杂的状态机等原理

  • 简单可理解为,rcu_read_lock() 和 rcu_read_unlock()之间不允许线程切换,当synchronize_rcu()之后,CPU都进行至少一次线程切换即可认为宽限期已过

 1 void foo_read(void)
 2 {
 3     rcu_read_lock();
 4     foo *fp = gbl_foo;
 5     if( fp != NULL )
 6         dosomthing(fp->a, fp->b, fp->c);
 7     rcu_read_unlock();
 8 }
 9 
10 void foo_update(foo *new_fp)
11 {
12     spin_lock(&foo_mutex);
13     foo *old_fp = gbl_foo;
14     gbl_foo = new_fp;
15     spin_unlock(&foo_mutex);
16     synchronize_rcu();
17     kfree(old_fp);
18 }
posted @ 2021-07-15 00:03  Jamgun  阅读(138)  评论(0编辑  收藏  举报