rcu-bp关键代码解读
1 什么是TLS
原理在网上资料很多,这里不展开。
简单点说,动态申请的每线程变量。有一类比较熟悉的每线程变量是一个带__thread的每线程变量,两者的区别在于,TLS这类每线程变量是动态申请的。有以下一系列接口:
#include <pthread.h>
int pthread_key_create(pthread_key_t *key, void (*destructor)(void*));
int pthread_key_delete(pthread_key_t key);
int pthread_setspecific(pthread_key_t key, const void *value);
void *pthread_getspecific(pthread_key_t key);
一般使用方法是
- 创建一个关键字key,多线程仅需创建一次,销毁函数可以为null;
- 将需要保存的上下文设置到key-value对中
- 需要访问时,根据key取出value
- 销毁key
- 与__thread类型的变量就在于需要使用全局变量保存key。
2 BP锁结构体
struct rcu_reader {
/* Data used by both reader and synchronize_rcu() */
unsigned long ctr;
/* Data used for registry */
struct cds_list_head node __attribute__((aligned(CAA_CACHE_LINE_SIZE)));
pthread_t tid;
int alloc; /* registry entry allocated */
};
其中的ctr代表当前线程的锁状态,tid表示当前线程号,alloc是一个标记位,代表该锁状态结构体有没有被使用;因为要保存每线程的当前锁状态,bp代码中是以数组的形式访问的,第一次申请8个,第二次申请16个,第三次申请32个,以成倍增加的方式扩大。
锁的状态有以下三种,分别为,
enum rcu_state {
RCU_READER_ACTIVE_CURRENT, 当前读者,写者在第一步抓取这些读者
RCU_READER_ACTIVE_OLD, 老读者,写者在第二步检测这些读者
RCU_READER_INACTIVE, 不在读操作中
};
3 全局gp记录
struct rcu_gp {
/*
* Global grace period counter.
* Contains the current RCU_GP_CTR_PHASE.
* Also has a RCU_GP_COUNT of 1, to accelerate the reader fast path.
* Written to only by writer with mutex taken.
* Read by both writer and readers.
*/
unsigned long ctr;
} __attribute__((aligned(CAA_CACHE_LINE_SIZE)));
struct rcu_gp rcu_gp = { .ctr = RCU_GP_COUNT };
记录了全局的ctr,默认值为1.
4 几个宏
#define RCU_GP_COUNT (1UL << 0) -------------1
/* Use the amount of bits equal to half of the architecture long size */
#define RCU_GP_CTR_PHASE (1UL << (sizeof(long) << 2))-----------64位系统为2的32次,32位系统为2的16次
#define RCU_GP_CTR_NEST_MASK (RCU_GP_CTR_PHASE - 1)----------64位系统0xffffffff,32位系统0xffff
5 初始化
void rcu_bp_register(void);
每个需要使用的bprcu的线程只要调用上面的函数进行注册即可,这个函数的工作是:
- 如果是第一个线程注册,则调用pthread_key_create创建每线程变量
- 将该线程加入registry链表
- 初始化时锁ctr的值为0
- 将该rcu_reader设置进key-value对(pthread_setspecific)
6 加读锁
static inline void _rcu_read_lock_update(unsigned long tmp)/* tmp是当前线程的ctr */
{
/* 这段表示,当前的线程中ctr中的低位为全0时,取全局的ctr值 */
if (caa_likely(!(tmp & RCU_GP_CTR_NEST_MASK))) {
_CMM_STORE_SHARED(URCU_TLS(rcu_reader)->ctr, _CMM_LOAD_SHARED(rcu_gp.ctr));
urcu_bp_smp_mb_slave();
} else
/* 这段表示如果当前线程中的ctr中的低位不为0,ctr值加1, */
_CMM_STORE_SHARED(URCU_TLS(rcu_reader)->ctr, tmp + RCU_GP_COUNT);
}
7 解读锁
static inline void _rcu_read_unlock(void)
{
unsigned long tmp;
tmp = URCU_TLS(rcu_reader)->ctr;
urcu_assert(tmp & RCU_GP_CTR_NEST_MASK);
/* Finish using rcu before decrementing the pointer. */
urcu_bp_smp_mb_slave();
_CMM_STORE_SHARED(URCU_TLS(rcu_reader)->ctr, tmp - RCU_GP_COUNT);
cmm_barrier(); /* Ensure the compiler does not reorder us with mutex */
}
仅仅是在当前的ctr基础上减1.
8 加解锁总结
加锁时,如果当前ctr值为0,也就是初始值,即第一次上锁时,需要将ctr的值更新为全局的ctr值。
解锁时,无条件将该当前ctr值减1,所以加解锁必须是对称的。
参与rcu,上锁状态ctr低位值大于0,空闲状态等于0.
9 锁状态的转化
由下面这个函数解析。
static inline enum rcu_state rcu_reader_state(unsigned long *ctr)
{
unsigned long v;
/* 没有使用过,返回未进入临界区 */
if (ctr == NULL)
return RCU_READER_INACTIVE;
/*
* Make sure both tests below are done on the same version of *value
* to insure consistency.
*/
v = CMM_LOAD_SHARED(*ctr);
/* RCU_GP_CTR_NEST_MASK这个值为0xffffffff,即为ctr的低位值为0时,返回未进入临界区 */
if (!(v & RCU_GP_CTR_NEST_MASK))
return RCU_READER_INACTIVE;
/* 先看第三步的非运算,即里面的值为0,则是读状态,否则是老的读状态
再看第二部的与运算,检测第16的值是否为1,为1则是老的读状态,为0则是读状态
最后第一步的异或运算,相等则为0,则肯定是读状态,不相等,则要看第十六位是否相同,相同则为读状态,不相等则为老的读状态
总之,只要ctr的值不为0,就是读状态
*/
if (!((v ^ rcu_gp.ctr) & RCU_GP_CTR_PHASE))
return RCU_READER_ACTIVE_CURRENT;
/* 不识别状态 */
return RCU_READER_ACTIVE_OLD;
}
10 等待读者结束
static void wait_for_readers(struct cds_list_head *input_readers,
struct cds_list_head *cur_snap_readers,
struct cds_list_head *qsreaders)
{
unsigned int wait_loops = 0;
struct rcu_reader *index, *tmp;
/*
* Wait for each thread URCU_TLS(rcu_reader).ctr to either
* indicate quiescence (not nested), or observe the current
* rcu_gp.ctr value.
*/
for (;;) {
if (wait_loops < RCU_QS_ACTIVE_ATTEMPTS)
wait_loops++;
cds_list_for_each_entry_safe(index, tmp, input_readers, node) {
switch (rcu_reader_state(&index->ctr)) {
case RCU_READER_ACTIVE_CURRENT:
if (cur_snap_readers) {
cds_list_move(&index->node,
cur_snap_readers);
break;
}
/* Fall-through */
case RCU_READER_INACTIVE:
cds_list_move(&index->node, qsreaders);
break;
case RCU_READER_ACTIVE_OLD:
/*
* Old snapshot. Leaving node in
* input_readers will make us busy-loop
* until the snapshot becomes current or
* the reader becomes inactive.
*/
break;
}
}
if (cds_list_empty(input_readers)) {
break;
} else {
/* Temporarily unlock the registry lock. */
mutex_unlock(&rcu_registry_lock);
if (wait_loops >= RCU_QS_ACTIVE_ATTEMPTS)
(void) poll(NULL, 0, RCU_SLEEP_DELAY_MS);
else
caa_cpu_relax();
/* Re-lock the registry lock before the next loop. */
mutex_lock(&rcu_registry_lock);
}
}
}
这个是snap可能是snapshot的缩写,快照的意思,也就是代表当前正在读的线程合影。
RCU_READER_ACTIVE_CURRENT将处与读状态的线程移入正在读链表,不在读状态的线程移入qs链表,其他状态等待其变成正在读或者不读。
11 Rcu写者的同步等待
void synchronize_rcu(void)
{
CDS_LIST_HEAD(cur_snap_readers);
CDS_LIST_HEAD(qsreaders);
sigset_t newmask, oldmask;
int ret;
ret = sigfillset(&newmask);
assert(!ret);
ret = pthread_sigmask(SIG_BLOCK, &newmask, &oldmask);
assert(!ret);
mutex_lock(&rcu_gp_lock);
mutex_lock(&rcu_registry_lock);
if (cds_list_empty(®istry))
goto out;
/* All threads should read qparity before accessing data structure
* where new ptr points to. */
/* Write new ptr before changing the qparity */
smp_mb_master();
/*
* Wait for readers to observe original parity or be quiescent.
* wait_for_readers() can release and grab again rcu_registry_lock
* interally.
*/
wait_for_readers(®istry, &cur_snap_readers, &qsreaders);
/*
* Adding a cmm_smp_mb() which is _not_ formally required, but makes the
* model easier to understand. It does not have a big performance impact
* anyway, given this is the write-side.
*/
cmm_smp_mb();
/* Switch parity: 0 -> 1, 1 -> 0 */
CMM_STORE_SHARED(rcu_gp.ctr, rcu_gp.ctr ^ RCU_GP_CTR_PHASE);
/*
* Must commit qparity update to memory before waiting for other parity
* quiescent state. Failure to do so could result in the writer waiting
* forever while new readers are always accessing data (no progress).
* Ensured by CMM_STORE_SHARED and CMM_LOAD_SHARED.
*/
/*
* Adding a cmm_smp_mb() which is _not_ formally required, but makes the
* model easier to understand. It does not have a big performance impact
* anyway, given this is the write-side.
*/
cmm_smp_mb();
/*
* Wait for readers to observe new parity or be quiescent.
* wait_for_readers() can release and grab again rcu_registry_lock
* interally.
*/
wait_for_readers(&cur_snap_readers, NULL, &qsreaders);
/*
* Put quiescent reader list back into registry.
*/
cds_list_splice(&qsreaders, ®istry);
/*
* Finish waiting for reader threads before letting the old ptr being
* freed.
*/
smp_mb_master();
out:
mutex_unlock(&rcu_registry_lock);
mutex_unlock(&rcu_gp_lock);
ret = pthread_sigmask(SIG_SETMASK, &oldmask, NULL);
assert(!ret);
}
第一步,将参与本次rcu活动的线程挑选出来。
第二步,将全局ctr的值翻转,各位0->1, 1->0。全局ctr只有两种取值情况,
1和0x1 0000 0001。
第三步,等待所有读者退出。
第四步,同步等待结束。
12 Call_rcu写者的异步等待
异步与同步的区别在于写者是等待所有读者退出,本线程执行写操作,还是由call_rcu线程等待所有写者退出,由call_rcu线程执行写操作。因为没有实际研究这段代码,直接看调用栈
#0 0x000000fff066ab90 in syscall () from /lib/libc.so.6
(gdb) bt
#0 0x000000fff066ab90 in syscall () from /lib/libc.so.6
#1 0x000000fff08f7ea8 in smp_mb_master ()
at ../userspace-rcu-0.9.3/urcu.c:165
#2 0x000000fff08f817c in wait_for_readers (
input_readers=0xfff090e5f0 <registry>, cur_snap_readers=0xff60dfe4b8,
qsreaders=0xff60dfe4c8) at ../userspace-rcu-0.9.3/urcu.c:290
#3 0x000000fff08f84ec in synchronize_rcu_memb ()
at ../userspace-rcu-0.9.3/urcu.c:426
#4 0x000000fff08f9b50 in call_rcu_thread (arg=0xff640030b0)
at ../userspace-rcu-0.9.3/urcu-call-rcu-impl.h:362
#5 0x000000fff0a22040 in start_thread () from /lib/libpthread.so.0
#6 0x000000fff066f5c4 in __thread_start () from /lib/libc.so.6
与同步的操作类型,也是通过wait_for_readers的调用等待读者退出
13 为什么要使用异或操作
为了实现读者从不阻塞,而写者应该区别出这位读者是否是本次的读者。
假设存在两个读者群,一个写者。读者群1正在读操作中,写者进入等待读者状态,读者群2也进入了临界区,但是读者群2不应该被列入本次的探测中。
再看一次锁状态的解析
if (!(v & RCU_GP_CTR_NEST_MASK))
return RCU_READER_INACTIVE;
if (!((v ^ rcu_gp.ctr) & RCU_GP_CTR_PHASE))
return RCU_READER_ACTIVE_CURRENT;
return RCU_READER_ACTIVE_OLD;
第一段还是原来的逻辑,没参与本地读写操作。
第二段的操作,具体分析,
Ctr值 |
全局ctr值 |
结果 |
1 |
1 |
RCU_READER_ACTIVE_CURRENT |
1 |
0x1 0000 0001 |
RCU_READER_ACTIVE_OLD |
2 |
1 |
RCU_READER_ACTIVE_CURRENT |
0x1 0000 0001 |
1 |
RCU_READER_ACTIVE_OLD |
0x1 0000 0001 |
0x1 0000 0001 |
RCU_READER_ACTIVE_CURRENT |
所以只要非0,都是读状态,只是区别出是写者开始等待前的读者还是写者开始等待后的读者。
14 举例
有2个线程,一个写,一个读。全局ctr为1
- 读者1进入临界区,ctr值设置为1
- 写者1尝试写,选中了读者1,将其加入本次需要等待的链表,更新全局ctr为0x1 0000 0001,发生调度。
- 读者1离开临界区,但是又再次进入临界区,ctr值设置为0x1 0000 0001
- 写者开始检测读者1的当前状态,发现是当前读者,而不是老读者,移出该读者。所以这个相位的作用就是区别出该读者是否已经离开上一次的读操作。写者退出等待,继续往下执行。
__simple原创
转载请注明出处
rcu-bp关键代码解读的更多相关文章
- Android MVP模式 谷歌官方代码解读
Google官方MVP Sample代码解读 关于Android程序的构架, 当前(2016.10)最流行的模式即为MVP模式, Google官方提供了Sample代码来展示这种模式的用法. Repo ...
- weex官方demo weex-hackernews代码解读(上)
一.介绍 weex 是阿里出品的一个类似RN的框架,可以使用前端技术来开发移动应用,实现一份代码支持H5,IOS和Android.最新版本的weex已默认将vue.js作为前端框架,而weex-hac ...
- 在android程序中加入widget(窗口小部件)并与之交互的关键代码
摘要: widget(窗口小部件)可以增强应用程序的交互性, 是很多应用中都会用到的功能,本文不求大而全,但是会给出程序与widget交互的关键代码 正文: 其实widget是嵌入(embedded) ...
- 优秀开源代码解读之JS与iOS Native Code互调的优雅实现方案
简介 本篇为大家介绍一个优秀的开源小项目:WebViewJavascriptBridge. 它优雅地实现了在使用UIWebView时JS与ios 的ObjC nativecode之间的互调,支持消息发 ...
- SoftmaxLayer and SoftmaxwithLossLayer 代码解读
SoftmaxLayer and SoftmaxwithLossLayer 代码解读 Wang Xiao 先来看看 SoftmaxWithLoss 在prototext文件中的定义: layer { ...
- 实现手电筒Flash Light 关键代码
实现手电筒Flash Light 关键代码 实现Flash的逻辑 view.setOnClickListener(new OnClickListener() { @Override public vo ...
- Android之打开闪光灯关键代码
在AndroidManifest中注册相应的权限: <uses-permission android:name="android.permission.FLASHLIGHT" ...
- Hybrid----优秀开源代码解读之JS与iOS Native Code互调的优雅实现方案-备
本篇为大家介绍一个优秀的开源小项目:WebViewJavascriptBridge. 它优雅地实现了在使用UIWebView时JS与ios 的ObjC nativecode之间的互调,支持消息发送.接 ...
- Android开源代码解读-基于SackOfViewAdapter类实现类似状态通知栏的布局
一般来说,ListView的列表项都会采用相同的布局,只是填充的内容不同而已,这种情况下,Android提供了convertView帮我们缓存列表项,达到循环利用的目的,开发者也会使用ViewHold ...
随机推荐
- Anaconda在Windows上安装不上原因
倒腾了一下午安装Anaconda,[所有程序]中只有一个Anaconda的目录,目录中只有一个anaconda prompt,最后发现是因为环境中原先就安装了Java环境,将之前安装的java环境卸载 ...
- Elastic Stack-Elasticsearch使用介绍(五)
一.前言 前4篇将Elasticsearch用法的API和原理方面东西介绍了一下,相信大家对Elasticsearch有了一定的认知,接下我们主要从索引的建立到后期的一些优化做一些介绍: 二. ...
- 【网站公告】请大家不要发表任何涉及“得到App”的内容
大家好,今天我们收到来自杭州某某网络科技有限公司的维权骑士团队的邮件,说他们受某某(天津)文化传播有限公司委托,展开维权.园子里有些博主因为学习“得到App”的课程在博客中记了一些笔记,也被维权. 为 ...
- Python函数的装饰器修复技术(@wraps)
@wraps 函数的装饰器修复技术,可使被装饰的函数在增加了新功能的前提下,不改变原函数名称,还继续使用原函数的注释内容: 方便了上下文环境中不去更改原来使用的函数地方的函数名: 使用方法 from ...
- jQuery的一些简单基础知识
### 什么是jQuery?jQuery(js+Query)是一款优秀的JavaScript库,帮助开发人员用最少的代码做更多的事情,官网网站http://jquery.com/ ### 为什么学习j ...
- python在linux中用setproctitle自定义进程名
在linux中,如果想用python自定义进程名,需要用到第三方库setproctitle. #coding:utf8 import setproctitle setproctitle.setproc ...
- Element-UI动态更换主题
参考:vue-基于elementui换肤[自定义主题] 实践: 需求1.后期维护主题色不更换: 直接在线主题生成工具下载,在APP.VUE引入:(注意Element UI 版本1.3?2.0) 需求 ...
- 手动执行脚本可以运行,crontab自动执行无效的解决方法
在需要执行的脚本里加入环境变量即可,如下图:
- 论学好Linux系统的超级重要性
不知道各位在日常的工作生活中有没有接触过“rm -rf /*”这个命令,因为这个命令搞出来的事情可还不少呢!前段时间就在一个群里看到了有个小伙子,老板让他去维护一下服务器,这小伙也不太懂,就问群里的大 ...
- 1、roboguide新建工程文件
打开roboguide,软件界面如下,接下来讲解一下“打开和新建工程文件” 首先介绍一下新建工程文件,在工具栏中点击新建按钮或者在文件(file)的下拉菜单中点击新建工程文件(new cell),弹出 ...