srcu浅析

本文代码基于linux内核4.19.195
之前写了rcu、rcu nocb的文章,感觉还差个srcu就完整了,现在补齐一下。
SRCU(Sleepable RCU)是rcu的一个变体,顾名思义,就是在rcu的读临界区中允许睡眠,而rcu在读临界区中是不运许睡眠的。许多文章详细的介绍了srcu的使用方法和局限,我们跳过这部分,直奔其原理。

对比rcu的数据结构,srcu也有相应的数据结构。
一个srcu_struct,代表一个逻辑srcu子系统。这个结构,在srcu相关函数中需要经常被调用到,而在rcu中基本没有使用,这主要是内核需要区分多种srcu,且每个子系统可能会定义自己的srcu;而rcu只有那几种,内核代码里早就写好了,从而在api中就封装掉了,开发者无需关注。
读者通过调用srcu_read_lock和srcu_read_unlock进出srcu读临界区

/** Counts the new reader in the appropriate per-CPU element of the* srcu_struct.* Returns an index that must be passed to the matching srcu_read_unlock().*/
int __srcu_read_lock(struct srcu_struct *sp)
{int idx;idx = READ_ONCE(sp->srcu_idx) & 0x1;this_cpu_inc(sp->sda->srcu_lock_count[idx]);/* 在对应的宽限期增加锁计数。cpu变量per_cpu_ref->c[idx]加1 */smp_mb(); /* B */  /* Avoid leaking the critical section. */return idx;//需要返回index
}
EXPORT_SYMBOL_GPL(__srcu_read_lock);/*** srcu_read_lock - register a new reader for an SRCU-protected structure.* @sp: srcu_struct in which to register the new reader.** Enter an SRCU read-side critical section.  Note that SRCU read-side* critical sections may be nested.  However, it is illegal to* call anything that waits on an SRCU grace period for the same* srcu_struct, whether directly or indirectly.  Please note that* one way to indirectly wait on an SRCU grace period is to acquire* a mutex that is held elsewhere while calling synchronize_srcu() or* synchronize_srcu_expedited().** Note that srcu_read_lock() and the matching srcu_read_unlock() must* occur in the same context, for example, it is illegal to invoke* srcu_read_unlock() in an irq handler if the matching srcu_read_lock()* was invoked in process context.*///读者进入临界区
static inline int srcu_read_lock(struct srcu_struct *sp) __acquires(sp)
{int retval;retval = __srcu_read_lock(sp);rcu_lock_acquire(&(sp)->dep_map);return retval;
}/*** srcu_read_unlock - unregister a old reader from an SRCU-protected structure.* @sp: srcu_struct in which to unregister the old reader.* @idx: return value from corresponding srcu_read_lock().** Exit an SRCU read-side critical section.*/
static inline void srcu_read_unlock(struct srcu_struct *sp, int idx)__releases(sp)
{rcu_lock_release(&(sp)->dep_map);__srcu_read_unlock(sp, idx);
}/** Removes the count for the old reader from the appropriate per-CPU* element of the srcu_struct.  Note that this may well be a different* CPU than that which was incremented by the corresponding srcu_read_lock().*///read unlock并没有修改srcu_idx,也就是说,一次宽限期内可以进入无数次读临界区,直到process_srcu修改srcu_idx
void __srcu_read_unlock(struct srcu_struct *sp, int idx)
{smp_mb(); /* C */  /* Avoid leaking the critical section. */this_cpu_inc(sp->sda->srcu_unlock_count[idx]);/* cpu变量per_cpu_ref->c[idx]减1 */
}
EXPORT_SYMBOL_GPL(__srcu_read_unlock);

可以看到,srcu_read_lock函数是有返回值的,且该返回值需要传递给srcu_read_unlock函数,这是srcu的特别之处,内核需要知道当前代码是准备进入哪个srcu的临界区。
进入临界区的原理很简单,获取当前的srcu的idx,对相关percpu的atomic变量进行原子加一操作;而离开临界区的原理也类似,根据srcu_read_lock的返回值,对相关percpu的另一atomic变量进行原子加一操作。这样只要将这两组percpu的atomic变量相加,只要和一样,就说明没有人在临界区里面了。
真的是这样吗?
首先为什么是percpu变量的和?
这是因为一个进程可能在cpu A上进的临界区,然后在cpu B上离开的临界区,这就导致记录进出临界区的变量在单个cpu上不一定是相等的。
那么,内核是如何检测当前这两组percpu的atomic变量的和是否相等呢?具体来说,每个核上可能会有进程在不断的进出srcu临界区,如何保证这个检测动作的原子性呢?要知道,多个数(cpu数量那么多)的求和,除非加锁,才能做好同步,但是很明显,srcu_read_lock函数和srcu_read_unlock函数都没有加锁动作,这是怎么回事?
回答这个问题,要看srcu是怎么处理一个宽限期的。
rcu的宽限期统计使用软中断实现,srcu使用工作队列实现宽限期统计,由函数process_srcu完成,其中函数srcu_advance_state完成了执行当前宽限期的状态机的工作。
call_srcu函数中,会根据情况调用srcu_funnel_gp_start,从而唤醒process_srcu相关的work,触发process_srcu的调用

/** This is the work-queue function that handles SRCU grace periods.*/
static void process_srcu(struct work_struct *work)
{struct srcu_struct *sp;sp = container_of(work, struct srcu_struct, work.work);srcu_advance_state(sp);srcu_reschedule(sp, srcu_get_delay(sp)); //确定是否需要启动下一个宽限期
}
/** Core SRCU state machine.  Push state bits of ->srcu_gp_seq* to SRCU_STATE_SCAN2, and invoke srcu_gp_end() when scan has* completed in that state.*/
static void srcu_advance_state(struct srcu_struct *sp)
{int idx;mutex_lock(&sp->srcu_gp_mutex);/** Because readers might be delayed for an extended period after* fetching ->srcu_idx for their index, at any point in time there* might well be readers using both idx=0 and idx=1.  We therefore* need to wait for readers to clear from both index values before* invoking a callback.** The load-acquire ensures that we see the accesses performed* by the prior grace period.*/idx = rcu_seq_state(smp_load_acquire(&sp->srcu_gp_seq)); /* ^^^ */if (idx == SRCU_STATE_IDLE) { //如果宽限期处于空闲状态spin_lock_irq_rcu_node(sp);if (ULONG_CMP_GE(sp->srcu_gp_seq, sp->srcu_gp_seq_needed)) { //如果宽限期没有注册回调函数WARN_ON_ONCE(rcu_seq_state(sp->srcu_gp_seq));spin_unlock_irq_rcu_node(sp);mutex_unlock(&sp->srcu_gp_mutex);return;}idx = rcu_seq_state(READ_ONCE(sp->srcu_gp_seq));if (idx == SRCU_STATE_IDLE)srcu_gp_start(sp); //启动宽限期Nspin_unlock_irq_rcu_node(sp);if (idx != SRCU_STATE_IDLE) {mutex_unlock(&sp->srcu_gp_mutex);return; /* Someone else started the grace period. */}}if (rcu_seq_state(READ_ONCE(sp->srcu_gp_seq)) == SRCU_STATE_SCAN1) {idx = 1 ^ (sp->srcu_idx & 1);if (!try_check_zero(sp, idx, 1)) {//是否所有读者都退出了宽限期(N-1)的临界区mutex_unlock(&sp->srcu_gp_mutex);return; /* readers present, retry later. */}srcu_flip(sp); //切换当前读者数组索引		rcu_seq_set_state(&sp->srcu_gp_seq, SRCU_STATE_SCAN2); //切换状态到SRCU_STATE_SCAN2}if (rcu_seq_state(READ_ONCE(sp->srcu_gp_seq)) == SRCU_STATE_SCAN2) {/** SRCU read-side critical sections are normally short,* so check at least twice in quick succession after a flip.*/idx = 1 ^ (sp->srcu_idx & 1);if (!try_check_zero(sp, idx, 2)) {//是否所有读者都退出了宽限期N的临界区mutex_unlock(&sp->srcu_gp_mutex);return; /* readers present, retry later. */}srcu_gp_end(sp);  /* Releases ->srcu_gp_mutex. */ //结束宽限期N}
}

从代码中可以看到,SRCU会经历三个状态:
SRCU_STATE_IDLE->SRCU_STATE_SCAN1->SRCU_STATE_SCAN2
一开始的时候,srcu初始化完毕会处于SRCU_STATE_IDLE状态。当有第一个人调用call_srcu的时候,会触发process_srcu的运行,进而调用srcu_gp_start启动当前宽限期N,将srcu的状态修改为SRCU_STATE_SCAN1后,process_srcu退出。
当再次调用到process_srcu时(这里还没弄明白是怎么被调用的,记个TODO后续学习),发现是SRCU_STATE_SCAN1状态,则调用try_check_zero判断前一个宽限期(N-1)是否完成,判断方法就是前面说的两组percpu变量的和。特别注意,这里的index取dx = 1 ^ (sp->srcu_idx & 1);
如果判断成功,即前一个宽限期的所有进程都退出了临界区,则调用srcu_flip,切换当前读者数组索引,也就是修改srcu_idx,这就是同步的关键。为什么这么说呢,因为调用了srcu_flip之后,srcu_read_lock返回的index,就和调用srcu_flip前不一样了,也就意味着,后续的srcu_read_lock,增加的atomic变量,和原先的srcu_read_lock增加的atomic变量,不再是一个变量了,这就能保证宽限期N的同步工作。
接下来,srcu进入SRCU_STATE_SCAN2状态,只需要等待try_check_zero返回成功,即可结束宽限期N。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/pingmian/21039.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

CCF-GESP 等级考试 2023年6月认证C++四级真题解析

2023年09月真题 一、单选题(每题2分,共30分) 第 1 题 高级语言编写的程序需要经过以下( )操作,可以生成在计算机上运行的可执行代码。 A. 编辑B. 保存C. 调试D. 编译 答案:D 解析:…

FS212E 系列PD协议

PD快充协议芯片FS212EL、FS212EH可以智能的识别插入的手机类型,选择最为合适的协议应对手机快充需要。兼容多类USB Type-C协议,包括TypeC协议、TypeC PD2.0、TypeC PD3.0、TypeC PD3.2等协议。集成OPTO输出,通过电阻直驱反馈光耦。FS212E 的调…

C++函数模版和C#的泛型函数的区别

函数模板和C#的泛型函数在概念上是非常相似的&#xff0c;都是用于创建能够处理多种数据类型的通用函数。它们的主要区别点在于语法和实现细节上&#xff1a; 语法&#xff1a; C 中的函数模板使用 template <typename T> 或者 template <class T> 来声明模板&am…

新手学习STM32还是ESP32

对于新手来说&#xff0c;选择学习STM32还是ESP32取决于个人的学习目标和背景。以下是针对这两种微控制器的详细分析&#xff0c;以便您做出更明智的选择&#xff1a; STM32 1. 处理器架构与性能 STM32采用单核或多核处理器架构&#xff0c;基于ARM Cortex-M0&#xff0c;M0…

PlantUML-使用文本来画时序图

介绍 PlantUML 是一个开源工具&#xff0c;用户可以使用纯文本描述来创建 UML (统一建模语言) 图形。由于它使用文本来描述图形&#xff0c;因此可以很容易地将这些描述与源代码一起存储在版本控制系统中。然后&#xff0c;PlantUML 负责将这些描述转换为图形。 资料 官方文…

k8s部署1.27.3版本,使用containerd模式

环境 操作系统&#xff1a;centos7.9 机器&#xff1a;1个master 和 1个node 节点&#xff08;云ECS&#xff09; 系统设置 # 所有机器设置hostname hostnamectl set-hostname master1 hostnamectl set-hostname node1# 所有机器增加内网ip和 master1 对应关系 vi /etc/ho…

杂牌记录仪TS视频流恢复方法

大多数的记录仪都采用了MP4/MOV文件方案&#xff0c;极少数的可能在用AVI文件&#xff0c;极极少数的在用TS文件方案。很多人可能不太解TS文件&#xff0c;这是一种古老的视频文件结构&#xff0c;下边这个案例我们来看下TS视频文件的恢复方法。 故障存储:8G存储卡/fat32文件系…

2-1RT-Thread线程管理-笔记

2-1RT-Thread线程管理-笔记 其中系统线程由内核创建&#xff0c;如main函数和空闲线程都属于系统线程&#xff0c;而用户线程是由应用程序所创建的。 对于资源较大的MCU可以适当设计较大的线程栈&#xff0c;也可以在初始化时设置一个具体的数值&#xff0c;如1K或2K字节。…

索引 ---- mysql

目录 1. 索引 1.1 概念 1.2 作用 1.3 使用场景 1.4 使用 1.4.1查看索引 1.4.2 创建索引 1.4.3 删除索引 1.5 注意事项 1.6 索引底层的数据结构 (面试经典问题) 1. 索引 1.1 概念 索引是一种特殊的文件&#xff0c;包含着对数据表里所有记录的引用指针。可以对表中的…

2006NOIP普及组真题 2. 开心的金明

线上OJ&#xff1a; 【06NOIP普及组】开心的金明 本题只要把 1、限定金额看成背包总容量 2、每件物品的价格 v 看成占用背包的体积 3、每件物品的价格v乘以权重w作为该物品的价值 则本题可套用标准的01背包问题模板&#xff1a; f [ j ] m a x ( f [ j ] , f [ j − v ] w …

Redis单线程

Redis是基于Reactor模式开发的网络事件处理器,这个处理器是单线程的,所 以redis是单线程的。 为什么它是单线程还那么快呢? 主要有以下几个原因: 一、纯内存操作 由于Redis是 纯内存操作,相比于磁盘来说,内存就快得多,这个是Redis快的主要 原因。 二、多路复用I/O机制(…

优先队列优化哈夫曼编码

前言 个人小记 一、代码 #include <stdio.h> #include <stdlib.h> #include <string.h> #include <time.h> #define MAX_NODE 80 #define MAX_STR 50 #define MAX_HEAP 100 #define father(i) ((i)/2) #define left(i) ((i)*2) #define right(i) …

11.3 指针和函数

11.3 指针和函数 本节必须掌握的知识点&#xff1a; 指针作为函数的参数 数组作为函数的参数 指针作为函数的返回值 在C语言中&#xff0c;指针的一个重要作用就是作为函数参数使用&#xff0c;本节将介绍这一重要作用。 11.3.1 指针作为函数的参数 实验一百一十三&#xff…

电磁兼容(EMC):BUCK变换器基本原理及传导辐射分析设计

目录 1. BUCK电路拓扑及原理 2. Buck拓扑电路电磁场分析 3.总结 开关电源替代线性电源&#xff0c;解决了效率和体积问题&#xff0c;但也带来了新的EMI问题。开关电源也是产品内部的强辐射源之一&#xff0c;基于透过现象看本质&#xff0c;将复杂问题简单化&#xff0c;本…

Xilinx FPGA 管脚的默认电平配置方法 XDC约束

目录 未使用的引脚&#xff0c;XDC约束其他已使用的引脚&#xff0c;XDC约束 未使用的引脚&#xff0c;XDC约束 set_property BITSTREAM.CONFIG.UNUSEDPIN PULLDOWN [current_design] set_property BITSTREAM.CONFIG.UNUSEDPIN PULLUP [current_design] set_property BITSTREA…

T检验——单样本t检验/两独立样本t检验/配对样本t检验

T检验——单样本t检验/两独立样本t检验/配对样本t检验 1.单样本t检验1.1 适用范围 2. &#xff08; 独立样本t检验&#xff09;两独立样本t检验3.ANOVA多组样本显著性检验&#xff08;2组以上&#xff09;4. 配对样本T检验 1.单样本t检验 1.1 适用范围 单样本t检验:即已知样本…

Linux如何设置系统发送告警脚本到邮箱

本次测试版本&#xff1a;centos7.6 第一步安装mailx yum install mailx 安装结果如下&#xff1a; [rootCentOS764 ~]# yum install mailx Loaded plugins: fastestmirror Loading mirror speeds from cached hostfile epel/x86_64/metalink …

成功解决“IndexError: deque index out of range”错误的全面指南

成功解决“IndexError: deque index out collections.deque out of range”错误的全面指南 引言 在Python编程中&#xff0c;collections.deque 是一个双端队列&#xff08;double-ended queue&#xff09;&#xff0c;支持从两端快速添加和删除元素。然而&#xff0c;与列表&…

Linux提权一

#信息收集 当前主机的操作系统 hostnamectl cat /etc/*-release lsb_release -a cat /etc/lsb-release # Debain cat /etc/redhat-release # Redhat cat /etc/centos-release # Centos cat /etc/os-release # Ubuntu cat /etc/issue 当前主机的内核版本 hostnamectl uname -a …

淘宝商品评论数据爬取:Python实战指南

淘宝作为中国领先的电商平台&#xff0c;其商品评论数据蕴含着丰富的用户洞察和市场信息。对于市场研究人员、产品开发者以及数据分析爱好者来说&#xff0c;能够自动爬取这些数据将极具价值。本文将提供一个使用Python进行淘宝商品评论数据爬取的详细指南&#xff0c;并强调在…