RCU在功耗和性能场景下的扩展

RCU概念

    ReadCopy Update (RCU) 是Linux的一种同步机制,把写数据看成一个update操作,可分为两个阶段removal和reclamation。removal阶段将旧数据的指针替换为新数据的指针,可以与 reader 并发,reclamation阶段在等待旧数据的读操作完成后将其回收或直接释放,两个阶段之间的间隔也被称之为grace period。从代码的实现上来说grace period是所有CPU都经历一次quiescent state所需要的等待时间,CPU发生一次上下文切换称为经历一个quiescent state,采用这样的实现方式是因为CPU能够切换上下文的话,必然已经运行过rcu_read_unlock或者根本就没有rcu_read_lock。

                  

    RCU可以理解为改进版的rwlock,读者不需要锁,并且read端除alpha外的所有架构都不需要加上memorybarrier,因此几乎没有读者的同步开销。与rwlock不同的是RCU允许读写并发,但是多个写者更新相同临界区时还是需要用锁保护的。如下左图为临界区长度为零时RCU相较于读写锁的性能优势,横轴为CPU个数纵轴为CPU时间开销。右图为临界区长度增加时RCU和读写锁的开销比较,横轴为临界区长度(毫秒)纵轴为CPU时间开销。

 

 

    在CPU数目众多的场景下,由于每等待一个CPU经历一次quiescent state都需要操作一个全局的cpumask字段,因此扩展出了分级RCU,另外在realtime 系统下也有Preemptible RCU,用户态还有URCU,随着业务场景的需求变化,RCU也在逐渐扩展。

 

RCU主要API

rcu_read_lock() //读临界区开始前关抢占

rcu_read_unlock() //读临界区结束后开抢占

synchronize_sched() //同步等待之前的读访问完成然后回收内存

call_rcu() //注册内存回收函数,异步等待之前的读访问完成后运行回调函数。

rcu_assign_pointer() //更新临界区数据并执行memorybarrier,让其他 CPU 知晓已更新的指针值。

rcu_dereference() //获取RCU保护的数据

 

RCU适用场景

(1)RCU只能保护一个指针,该指针指向动态分配的数据结构。RCU读者要么读到旧值要么读到新值,如果不是一个32bit(32位处理器)数据的话在读写并发的时候可能会读到中间态数据。

(2)写者不能改变临界区的数据类型。

(3)读多写少的场景,对writer的性能没有特别要求而对reader的性能要求极高。

(4)reader端对新旧数据不敏感。

 

CPU隔离的performance场景下RCU的扩展

    以8核心CPU为例,BootLoader给Kernel传入的cmdline包含”isolcpus=1-7”也就是在SMP均衡调度算法中将CPU1-7孤立出来,同时依然可以将进程按需绑定到 “孤立CPU”运行。这样做的好处是,CPU1-7可以只运行一个业务相关的用户态进程而尽量不被内核调度打扰以保证实时性。

 

      ① RCU_NOCB

    RCU调用的rcu_process_callbacks是在timer软中断里触发的RCU_SOFTIRQ,对于实时需求来说,写端的RCUcallback不可避免地会增加OS jitter和schedulinglatency。如果是在smart phone上,也有可能在一定程度上减小cpu的idle rate从而略微影响功耗。于是,RCU_NOCB的设计就被引入了,将CPU1-7上的callbackoffload到kthread并绑定到CPU0上(bootloader传入"rcu_nocbs=1-7"),也就是有些平台上看到的线程"rcuox/N",“x”对应RCU-bh,RCU-preempt,RCU-sched 分别为"b", "p", “s”,N为具体某个CPU。天下没有免费的午餐,NOCB的使用只是将call back的负载转移到别的CPU而已。

 

    ② Expedited quiescent state

    前面讲到经历一次quiescent state是用CPU切换一次上下文来判断的,那么某些业务场景下CPU不关抢占也不切换上下文(CPU idle或者 CPU只运行一个用户态业务进程),这种情况下RCU写端等待超时后经典RCU的做法是直接调用force_quiescent_state再调用smp_send_reschedule函数向其他所有cpu发送一次resched  ipi强制调度,这就影响了功耗和性能。

    RCU引入了dynticks变量并将处于该场景的CPU状态称谓expedited quiescentstate, 在per-cpu 字段rcu_data里加入包含dynticks变量的rcu_dynticks结构体,该值初始化为1,每次进入用户态的时候调用rcu_user_enter加1,出用户态的时候调用rcu_user_exit加1,这样当dynticks为偶数时就认为该CPU处于expedited quiescent state不需要等待该CPU切换上下文,CPU进入idle模式也同理,不过真实的场景没这么简单,会考虑到很多种情况以优化代码。需要注意的是,user模式下的expedited quiescent state必须打开宏CONFIG_NO_HZ_FULL,该宏起到stop tick的作用,忽略tick的条件依次为当前运行的进程为SCHED_FIFO,或者当前运行的进程为SCHED_RR并且与其相同优先级的runlist里只有一个进程,或者当前CPU的运行队列进程数不多于1个。

 

参考文献

perfbook

https://github.com/urcu/userspace-rcu

https://wiki.linuxfoundation.org/realtime/documentation/technical_details/rcu

  • 1
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值