RCU浅析理解


  1. 概述

RCU - Read-Copy-Update (读时加锁,写时拷贝,读后更新)

为读写锁的升级版

特点:

  • 运行读者和写者并发执行;

  • 最大程度减少读者侧的开销;

  • 没有死锁问题

  • 没有优先级反之问题

  • 没有内存泄漏问题

  • 很好的实时延迟

  • 写者的同步开销比较大,写者之间需要互斥处理

  1. 基本思想

RCU锁的要素包括:

  • 读标志

如果一个Reader企图占据一把RCU锁,它是不需要付出任何代价的,只需要设置一个标志,让外界知道有Reader在占据这把RCU锁,多个Reader可以共同持有一把RCU锁。

  • 使用rcu_read_lockrcu_read_unlock来界定读者的临界区,访问受RCU保护的数据时,需要始终在该临界区域内访问;

  • 在访问受保护的数据之前,需要使用rcu_dereference来获取RCU-protected指针;

  • 当使用不可抢占的RCU时,rcu_read_lock/rcu_read_unlock之间不能使用可以睡眠的代码;

  • 写时拷贝

如果有一个Write企图更新RCU锁所保护的数据,那么它会首先查看该RCU锁的读标志,如果有该标志,说明有最少一个Reader持有了该RCU锁,它需要对原始数据make a copy,写这个副本并将更新过的副本保存在某处,等待时机用该副本更新原始数据。

  • 多个Updater更新数据时,需要使用互斥机制进行保护;

  • Updater使用rcu_assign_pointer来移除旧的指针指向,指向更新后的临界资源;

  • Updater使用synchronize_rcucall_rcu来启动Reclaimer,对旧的临界资源进行回收,其中synchronize_rcu表示同步等待回收,call_rcu表示异步回收;

  • 更新时机

这个时机就是用副本更新原始数据的时间点,这个时间点如何确定是RCU锁实现的算法核心,它直接可以确定所有的数据结构。确切来讲,Writer必须waitting for all readers leaving,方可Update原始数据。

  • Reclaimer回收的是旧的临界资源;

  • 为了确保没有读者正在访问要回收的临界资源,Reclaimer需要等待所有的读者退出临界区,这个等待的时间叫做宽限期Grace Period);

2.1 Grace Period, 以及Reclaimer如何对旧的临界区进行资源回收?

  • 中间的黄色部分代表的就是Grace Period,中文叫做宽限期,从RemovalReclamation,中间就隔了一个宽限期;

  • 只有当宽限期结束后,才会触发回收的工作,宽限期的结束代表着Reader都已经退出了临界区,因此回收工作也就是安全的操作了;

  • 宽限期是否结束,与处理器的执行状态检测有关,也就是检测静止状态Quiescent Status

  • RCU的性能与可扩展性依赖于它是否能有效的检测出静止状态(Quiescent Status),并且判断宽限期是否结束。

  • 图中Readers和Updater并发执行;

  • 当Updater执行Removal操作后,调用synchronize_rcu,标志着更新结束并开始进入回收阶段;

  • synchronize_rcu调用后,此时可能还有新的读者来读取临界资源(更新后的内容),但是,Grace Period只等待Pre-Existing的读者,也就是在图中的Reader-4, Reader-5。只要这些之前就存在的RCU读者退出临界区后,意味着宽限期的结束,因此就进行回收处理工作了;

  • synchronize_rcu并不是在最后一个Pre-ExistingRCU读者离开临界区后立马就返回,它可能存在一个调度延迟;

2.2 Quiescent Status

Quiescent Status,用于描述处理器的执行状态。当某个CPU正在访问RCU保护的临界区时,认为是活动的状态,而当它离开了临界区后,则认为它是静止的状态。当所有的CPU都至少经历过一次QS后,宽限期将结束并触发回收工作。

  • 在时钟tick中检测CPU处于用户模式或者idle模式,则表明CPU离开了临界区;

  • 在不支持抢占的RCU实现中,检测到CPU有context切换,就能表明CPU离开了临界区;

  • 从图中看:

  • 从清除QS开始检测,当检查到每个cpu都经历了一个QS,即认为一个宽限期结束,可开启下一个宽限期。

  1. linux实现

The RCU API, 2019 edition [LWN.net]

  • synchronize_rcu() may be used in place of synchronize_rcu_bh() and synchronize_sched(), as well as all current uses of synchronize_rcu_mult().

  • synchronize_rcu_expedited() may be used in place of synchronize_rcu_bh_expedited() and synchronize_sched_expedited().

  • call_rcu() may be used in place of call_rcu_bh() and call_rcu_sched().

  • rcu_barrier() may be used in place of rcu_barrier_bh() and rcu_barrier_sched().

  • get_state_synchronize_rcu() and cond_synchronize_rcu() may be used in place of get_state_synchronize_sched() and cond_synchronize_sched(), respectively.

3.1 RCU update Removal API

  • RCU写者,可以通过两种方式来等待宽限期的结束:

  1. 调用同步接口 synchronize_xxx 等待宽限期结束;

  1. 异步接口 call_rcu_xxx 注册资源释放回调函数。(等到宽限期结束后, rcu core会进行回调处理),分别如上图的左右两侧所示;

  • 从图中的接口调用来看,同步接口中实际会去调用异步接口,只是同步接口中增加了一个wait_for_completion睡眠等待操作,并且会将wakeme_after_rcu回调函数传递给异步接口,当宽限期结束后,在异步接口中回调了wakeme_after_rcu进行唤醒处理;

  • 目前内核中提供了三种RCU:

  1. 可抢占RCU:rcu_read_lock/rcu_read_unlock来界定区域,在读端临界区可以被其他进程抢占;

  1. 不可抢占RCU(RCU-sched)rcu_read_lock_sched/rcu_read_unlock_sched来界定区域,在读端临界区不允许其他进程抢占;

  1. 关下半部RCU(RCU-bh)rcu_read_lock_bh/rcu_read_unlock_bh来界定区域,在读端临界区禁止软中断;

3.1.1 __call_rcu 函数的调用流程如下,两个功能:

  • 注册回调函数,而回调的函数的维护是在rcu_data结构中的struct rcu_segcblist cblist字段中;

  • 判断是否需要开启新的宽限期GP;

3.1.2 那么通过__call_rcu注册的这些回调函数在哪里调用呢?答案是在RCU_SOFTIRQ软中断中:

  • invoke_rcu_core时,在该函数中调用raise_softirq接口,从而触发软中断回调函数rcu_process_callbacks 的执行;

  • 与宽限期GP相关的操作处理:

  • rcu_process_callbacks中会调用rcu_gp_kthread_wake唤醒内核线程,最终会在rcu_gp_kthread线程中执行;

  • User注册的RCU资源释放回调函数执行的操作,都在rcu_do_batch函数中执行,其中有两种执行方式:

  • 如果不支持优先级继承的话,直接调用即可;

  • 支持优先级继承,在把回调的工作放置在rcu_cpu_kthread内核线程中,其中内核为每个CPU都创建了一个rcu_cpu_kthread内核线程;

3.2 宽限期开始与结束

  • 内核分别为rcu_preempt_state, rcu_bh_state, rcu_sched_state创建了内核线程rcu_gp_kthread

  • rcu_gp_kthread内核线程主要完成三个工作:

  1. 创建新的宽限期GP;

  1. 等待强制静止状态,设置超时,提前唤醒说明所有处理器经过了静止状态;

  1. 宽限期结束处理。其中,前边两个操作都是通过睡眠等待在某个条件上。

3.3 静止状态检测及报告

对这种状态的检测通常都是周期性的进行,放置在时钟中断处理中就是情理之中了。

  • rcu_sched/rcu_bh类型的RCU中,当检测CPU处于用户模式或处于idle线程中,说明当前CPU已经离开了临界区,经历了一个QS静止状态,对于rcu_bh的RCU,如果没有出去softirq上下文中,也表明CPU经历了QS静止状态;

  • rcu_pending满足条件的情况下,触发软中断的执行,rcu_process_callbacks将会被调用;

  • rcu_process_callbacks回调函数中,对宽限期进行判断,并对静止状态逐级上报,如果整个树状结构都经历了静止状态,那就表明了宽限期的结束,从而唤醒内核线程去处理;

  • 顺便提一句,在rcu_pending函数中,rcu_pending->__rcu_pending->check_cpu_stall->print_cpu_stall的流程中,会去判断是否有CPU stall的问题,这个在内核中有文档专门来描述,不再分析了;

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值