RCU机制分析

转自http://liu1227787871.blog.163.com/blog/static/205363197201283022655801/

一、原理概述

RCU(Read-Copy Update),顾名思义就是读-拷贝修改,它是基于其原理命名的。对于被RCU保护的共享数据结构,读者不需要获得任何锁就可以访问它,但写者在访问它时首先拷贝一个副本,然后对副本进行修改,最后使用一个回调(callback)机制在适当的时机把指向原来数据的指针重新指向新的被修改的数据。这个时机就是所有引用该数据的CPU都退出对共享数据的操作。
因此RCU实际上是一种改进的rwlock,读者几乎没有什么同步开销,它不需要锁,不使用原子指令,而且在除alpha的所有架构上也不需要内存栅(Memory Barrier),因此不会导致锁竞争,内存延迟以及流水线停滞。不需要锁也使得使用更容易,因为死锁问题就不需要考虑了。写者的同步开销比较大,它需要延迟数据结构的释放,复制被修改的数据结构,它也必须使用某种锁机制同步并行的其它写者的修改操作。读者必须提供一个信号给写者以便写者能够确定数据可以被安全地释放或修改的时机。有一个专门的垃圾收集器来探测读者的信号,一旦所有的读者都已经发送信号告知它们都不在使用被RCU保护的数据结构,垃圾收集器就调用回调函数完成最后的数据释放或修改操作。 RCU与rwlock的不同之处是:它既允许多个读者同时访问被保护的数据,又允许多个读者和多个写者同时访问被保护的数据(注意:是否可以有多个写者并行访问取决于写者之间使用的同步机制),读者没有任何同步开销,而写者的同步开销则取决于使用的写者间同步机制。但RCU不能替代rwlock,因为如果写比较多时,对读者的性能提高不能弥补写者导致的损失。
读者在访问被RCU保护的共享数据期间不能被阻塞,这是RCU机制得以实现的一个基本前提,也就说当读者在引用被RCU保护的共享数据期间,读者所在的CPU不能发生上下文切换,spinlock和rwlock都需要这样的前提。写者在访问被RCU保护的共享数据时不需要和读者竞争任何锁,只有在有多于一个写者的情况下需要获得某种锁以与其他写者同步。写者修改数据前首先拷贝一个被修改元素的副本,然后在副本上进行修改,修改完毕后它向垃圾回收器注册一个回调函数以便在适当的时机执行真正的修改操作。等待适当时机的这一时期称为grace period,而CPU发生了上下文切换称为经历一个quiescent state,grace period就是所有CPU都经历一次quiescent state所需要的等待的时间。垃圾收集器就是在grace period之后调用写者注册的回调函数来完成真正的数据修改或数据释放操作的。
比较是很读多写少的情况!

二、代码分析

1、rcu_read_lock 
rcu_read_lock  //读是可以嵌套的
      //禁止抢占
      //这是因为读操作是不允许阻塞的,不然会导致写操作延时太大
     //既然不允许阻塞,当然要禁止抢占
      preempt_disable();

2、 rcu_read_unlock 
rcu_read_unlock
      __rcu_read_unlock();
           preempt_enable(); //使能抢占

3、rcu_dereference
功能:获得一个被RCU保护的指针

4、rcu_assign_pointer
写者使用该函数来为被 RCU 保护的指针分配一个新的值. 这样是为了安全从写者到读者更改其值. 这个函数会返回一个新值

5、synchronize_rcu
synchronize_rcu 
      wait_rcu_gp(call_rcu);
           crf(&rcu.head, wakeme_after_rcu); //等待操作完成以后就会调用wakeme_after_rcu
            wait_for_completion(&rcu.completion); //等待条件为真,否则一直阻塞
那么为什么会调用wakeme_after_rcu呢,这个调用是在时钟中断里面实现的,我们来看一看时钟中断处理函数:
s3c2410_timer_interrupt(int irq, void *dev_id)
      timer_tick();
           update_process_times(user_mode(get_irq_regs()));
                 rcu_check_callbacks(cp u, user_tick);
                       rcu_sched_qs(cpu); //表示经过了一次quiescent state,即一次切换,不过这个切换也可以在schedule中标识
                       rcu_preempt_check_callbacks();
                             invoke_rcu_callbacks();
                                     raise_softirq(RCU_SOFTIRQ);
唤醒RCU软中断,很显然我们要看一看RCU软中断注册的是什么处理函数:
open_softirq(RCU_SOFTIRQ, rcu_process_callbacks);
rcu_process_callbacks
      __rcu_process_callbacks
            __rcu_reclaim(rn, list);
                   head->func(head);
这里就是执行call_rcu注册的回调函数了!
上面我们只是顺路走了过来,并没有关注细节的东西,以后用到再说吧,现在我是没有水平去分析!

下面我们接着分析:
wakeme_after_rcu
        complete(&rcu->completion); //标识完成,那么在这个条件下等待的进程就会被唤醒,也就是wait_rcu_gp
也就是说synchronize_rcu被唤醒了!

我们来总结一下synchronize_rcu这个函数,首先会调用call_rcu注册回调函数,然后在某个条件下等待。等待发生一个时钟中断的时候,就会唤醒rcu软中断,ruc软中断处理函数会判断是否经过了一个 grace period,也就是是否在写之前的读操作都退出了,如果都退出了那么就会执行回调函数,将条件置为真,等待被唤醒!所以synchronize_rcu这个函数的作用就是等待所有的读退出!

至于这个机制在内核中的使用,我们要去实际中去分析内核了!

你可能感兴趣的:(linux机制,linux内核,文件系统,linux内核修炼之道,[笔记])