ARM64 linux并发与同步之经典自旋锁

news2024/11/20 13:43:49

1.3 经典自旋锁

在这里插入图片描述
在实际项目中临界区数据有可能会修改一个数据结构或者链表中的数据,在整个过程中要保证原子性,才不会影响数据的有效性,这个过程使用原子变量不合适,需要使用锁机制来完成,自旋锁(spinlock)是linux内核中常见的锁机制。

自旋锁特性:

  • 同一时刻只能被一个内核代码路径持有,另外的内核代码路径试图获取该锁需要一直忙等待,直到该自旋锁持有者释放该锁;
  • 自旋锁持有者需尽快完成临界区的执行任务,否则会造成等待的CPU浪费,特别是自旋锁临界区里不能睡眠;
  • 自旋锁可以在中断上下文中使用;
1.3.1 自旋锁的实现

下面展示的spinlock数据结构的定义。

<linux-5.15.73/include/linux/spinlock_types.h>


/* Non PREEMPT_RT kernels map spinlock to raw_spinlock */
typedef struct spinlock {
	union {
		struct raw_spinlock rlock;

#ifdef CONFIG_DEBUG_LOCK_ALLOC
# define LOCK_PADSIZE (offsetof(struct raw_spinlock, dep_map))
		struct {
			u8 __padding[LOCK_PADSIZE];
			struct lockdep_map dep_map;
		};
#endif
	};
} spinlock_t;

typedef struct raw_spinlock {
	arch_spinlock_t raw_lock;
#ifdef CONFIG_DEBUG_SPINLOCK
	unsigned int magic, owner_cpu;
	void *owner;
#endif
#ifdef CONFIG_DEBUG_LOCK_ALLOC
	struct lockdep_map dep_map;
#endif
} raw_spinlock_t;

早期的linux内核是使用一个简单的无符号类型的变量来表示是否持有锁,这样会带来一个问题,当前持有锁的代码路径刚刚释放,有可能又获取了该锁,这样对别的代码路径不公平,这样会导致整个系统的性能会差很多;

现在的spinlock都是“基于排队的FIFO”算法的自旋锁机制;
在这里插入图片描述
自旋锁的原型定义在include/linux/spinlock.h头文件中。

static __always_inline void spin_lock(spinlock_t *lock)
{
	raw_spin_lock(&lock->rlock);
}

static inline void __raw_spin_lock(raw_spinlock_t *lock)
{
	preempt_disable();
	spin_acquire(&lock->dep_map, 0, 0, _RET_IP_);
	LOCK_CONTENDED(lock, do_raw_spin_trylock, do_raw_spin_lock);
}

看到实现,首先会调用preempt_disable()来关闭内核抢占,这是自旋锁实现的关键点之一。那么为什么自旋锁临界区中不允许发生抢占呢?
如果自旋锁临界区中允许抢占,假设在临界区内发生中断,中断返回时会检查抢占调度,这里将有两个问题:一是抢占调度会导致持有锁的进程睡眠,这违背了自旋锁不能睡眠和快速执行完成的设计语义;二是抢占调度进程也可能会申请自旋锁,这样会导致发生死锁

 <arch/arm/include/asm/spinlock.h>

static inline void arch_spin_lock(arch_spinlock_t *lock)
{
    unsigned long tmp;
    u32 newval;
    arch_spinlock_t lockval;

    prefetchw(&lock->slock);    // gcc 内置预取指令,指定读取到最近的缓存以加速执行
    __asm__ __volatile__(
"1: ldrex   %0, [%3]\n"         // lockval = &lock->slock
"   add %1, %0, %4\n"           // newval = lockval + 1 << 16,等于 lockval.tickets.next +1;
"   strex   %2, %1, [%3]\n"     // 将 lock->slock = newval
"   teq %2, #0\n"               // 测试上一步操作的执行结果
"   bne 1b"                     // 如果执行 lock->slock = newval 失败,则跳到标号 1 处从头执行
    : "=&r" (lockval), "=&r" (newval), "=&r" (tmp)
    : "r" (&lock->slock), "I" (1 << TICKET_SHIFT)
    : "cc");

    // 没进行 +1 操作前的 lockval.tickets.next 是否等于 lockval.tickets.owner
    // 不相等时,调用 wfe 指令进入 idle 状态,等待 CPU event,被唤醒后继续判断锁变量是否相等
    while (lockval.tickets.next != lockval.tickets.owner) { 
        wfe();
        lockval.tickets.owner = ACCESS_ONCE(lock->tickets.owner);
    }
    // 内存屏障 
    smp_mb();
}

从上述的注释中大概可以看出加锁的实现:

1、先将 spinlock 结构体中的 next 变量 + 1,不管是否能够获得锁
2、判断 +1 操作之前,next 是否等于 owner,只有在 next 与 owner 相等时,才能完成加锁,否则就循环等待,从这里也可以看到,自旋锁并不是完全地自旋,而是使用了 wfe 指令。

要完整地理解加锁过程,就必须要提到解锁,因为这两者是相对的,解锁的实现很简单:就是将 spinlock 结构体中的 owner 进行 +1 操作,因此,当一个 spinlock 初始化时,next 和 onwer 都为 0。某个执行流 A 获得锁,next + 1,此时在其它执行流 B 眼中,next != owner,所以 B 等待。当 A 调用 spin_unlock 时,owner + 1。
此时 next == owner,所以 B 可以欢快地继续往下执行,这就是加解锁的逻辑。

static inline void arch_spin_unlock(arch_spinlock_t *lock)
{
	smp_mb();
	lock->tickets.owner++;
	dsb_sev();
}
1.3.2 自旋锁相关的API

觉得有必要先学习下自旋锁相关的有哪些接口可以使用,什么作用?

linux-5.15.73/include/linux/spinlock.h //可以看下该路径中包含所有的API
spin_lock_irqsave() 和 spin_unlock_irqrestore():类似于 spin_lock() 和 spin_unlock(),但同时会在获取锁时禁用本地中断,并在释放锁时恢复中断状态。这对于确保在关键区域内禁用中断以防止并发问题非常有用。

spin_lock_bh() 和 spin_unlock_bh():类似于 spin_lock() 和 spin_unlock(),但同时会在获取锁时禁用软中断(bottom half),并在释放锁时恢复软中断状态。这对于确保在关键区域内禁用软中断以防止并发问题非常有用。

spin_trylock_irqsave(), spin_trylock_bh() 等:类似于 spin_trylock(),但同时会在尝试获取锁时禁用中断或软中断。

注:软中断(bottom half)是一种处理机制,用于执行一些延迟敏感的任务,例如网络数据包处理、定时器处理等。软中断在适当的时机被触发,并在内核上下文中执行。由于软中断的执行可能与进程的执行并发进行,因此可能会引发并发访问的互斥问题。(比如中断下半部的softirq和tasklet)

spin_lock_irq 和spin_lock_irqsave区别?

答:spin_lock_irq 和 spin_lock_irqsave 都是用于在 Linux 内核中处理中断上半部的自旋锁函数,它们的区别在于对中断状态的处理方式:
spin_lock_irq 获取自旋锁的同时禁用本地CPU的中断响应。它不会保存当前的中断状态,因此在释放自旋锁后会恢复先前未知的中断状态。
spin_lock_irqsave 也会获取自旋锁并禁用本地CPU的中断响应,但它会在获取自旋锁之前保存当前的中断状态,并在释放自旋锁时根据保存的状态来恢复中断。这样可以确保在释放锁后,系统的中断状态与获取锁时一致。

1.3.3 自旋锁的变体spin_lock_irq()场景使用

![在这里插入图片描述](https://img-blog.csdnimg.cn/0e020109db1c448a91ec562ba5bc4b3f.png

假设如上图场景,CPU0正在通过ioctl或者read去处理链表来获取数据,突然中断来了,中断中也有获取锁及对链表数据的处理,就会导致死锁,这个时候就需要spin_lock_irq()或者spin_lock_irqsave()出场了。

spin_lock_irq()拿到锁时会禁止本地cpu中断,从而保证ioctl或者read的操作的原子性,因此也印证了spin_lock拿锁之后的临界区要尽可能的短和快(男人不喜欢,哈哈)。

可能有的读者会有疑问,既然关闭了本地CPU的中断,那么别的CPU依然可以响应外部中断,这会不会也可能导致死锁呢?
答:自旋锁持有者在CPU0上,CPU1响应了外部中断且中断处理程序同样试图去获取该锁,因为CPU0上的自旋锁持有者也在继续执行,所以它很快会离开临界区并释放锁,这样CPU1上的中断处理程序可以很快获得该锁。

在上述场景中,如果CPU0在临界区中发生了进程切换,会是什么情况?注意,进入自旋锁之前已经显式地调用preempt_disable()函数关闭了抢占,因此内核不会主动发生抢占。但令人担心的是,驱动编写者主动调用睡眠函数,从而发生了调度。使用自旋锁的重要原则是拥有自旋锁的临界区代码必须原子地执行,不能休眠和主动调度。但在实际项目中,驱动代码编写者常常容易犯错误。如调用分配内存函数kmalloc()时,可能因为系统空闲内存不足而进入睡眠模式,除非显式地使用GFP_ATOMIC分配掩码

1.3.4 spin_lock()和raw_spin_lock()函数

若在一个项目中有的代码中使用spin_lock()函数,而有的代码使用raw_spin_lock()函数,并且spin_lock()函数直接调用raw_spin_lock()函数,这样可能会给读者造成困惑。

这要从Linux内核的实时补丁(RT-patch)说起。实时补丁旨在提升Linux内核的实时性,它允许在自旋锁的临界区内抢占锁,且在临界区内允许进程睡眠,这样会导致自旋锁语义被修改。当时内核中大约有10 000处使用了自旋锁,直接修改自旋锁的工作量巨大,但是可以修改那些真正不允许抢占和睡眠的地方,大概有100处,因此改为使用raw_spin_lock()函数。spin_lock()和raw_spin_lock()函数的区别如下。

在绝对不允许抢占和睡眠的临界区,应该使用raw_spin_lock()函数,否则使用spin_lock()。

因此对于没有更新实时补丁的Linux内核来说,spin_lock()函数可以直接调用raw_spin_lock(),对于更新实时补丁的Linux内核来说,spin_lock()会变成可抢占和睡眠的锁,这一点需要特别注意。

感谢学习,有疑问欢迎评论区交流。

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

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

相关文章

深度学习实战59-NLP最核心的模型:transformer的搭建与训练过程详解,手把手搭建与跑通

大家好,我是微学AI,今天给大家介绍一下深度学习实战59-NLP最核心的模型:transformer的搭建与训练过程详解,手把手搭建与跑通。transformer是一种基于自注意力机制的深度学习模型,由Vaswani等人在2017年的论文《Attention is All You Need》中提出。它最初被设计用来处理序…

香港科技大学广州|机器人与自主系统学域博士招生宣讲会—电子科技大学专场!!!(暨全额奖学金政策)

在机器人和自主系统领域实现全球卓越—机器人与自主系统学域 硬核科研实验室&#xff0c;浓厚创新产学研氛围&#xff01; 教授亲临现场&#xff0c;面对面答疑解惑助攻申请&#xff01; 一经录取&#xff0c;享全额奖学金1.5万/月&#xff01; &#x1f559;时间&#xff1a;…

防爆五参数气象仪的科技力量

WX-FBQ2 随着科技的不断进步&#xff0c;气象监测设备也在不断升级和完善。 防爆五参数气象仪是一种可以同时监测温度、湿度、压力、风速和风向五个基本气象参数的仪器。它采用了气象监测技术&#xff0c;不仅可以实时监测气象数据&#xff0c;还可以对数据进行分析和处理。 …

git使用patch进行补丁操作

文章目录 前言一、format-patch/am生成和应用补丁1、生成2、应用 二、patch文件解读 前言 在软件开发中&#xff0c;代码协作和版本管理是至关重要的。Git 是一个流行的分布式版本控制系统&#xff0c;它提供了各种功能来简化团队合作和代码管理。但是如何给已有项目打补丁&am…

基于单片机的智能考勤机(论文+源码)

1.系统设计 本课题为基于单片机的智能考勤机&#xff0c;其整个系统由STC89C52单片机&#xff0c;RC522 RFID模块&#xff0c;LCD液晶&#xff0c;按键等构成&#xff0c;在功能上&#xff0c;本系统智能考勤机主要应用在校园生活中&#xff0c;用户可以通过按键注销/注销相应的…

Skywalking流程分析_4(插件的加载和不同版本的识别)

插件的结构 之前我们介绍了插件的加载&#xff0c;接下来就是真正开始进行插件的执行了&#xff0c;首先要看下插件的结构是怎么样的&#xff0c;以阿里的druid数据源为例 skywalking-plugin.def: druid-1.xorg.apache.skywalking.apm.plugin.druid.v1.define.DruidPooledCo…

二进制原码、反码、补码、移码

机器数&#xff1a;一个数在计算机中的二进制表示形式&#xff0c;称为这个数的机器数。符号位&#xff1a;机器数是带符号的&#xff0c;在计算机中用最高位作为符号位&#xff0c;0为正数&#xff0c;1为负数。真值&#xff1a;机器数由于含有符号位&#xff0c;所以机器数的…

基于群居蜘蛛算法优化概率神经网络PNN的分类预测 - 附代码

基于群居蜘蛛算法优化概率神经网络PNN的分类预测 - 附代码 文章目录 基于群居蜘蛛算法优化概率神经网络PNN的分类预测 - 附代码1.PNN网络概述2.变压器故障诊街系统相关背景2.1 模型建立 3.基于群居蜘蛛优化的PNN网络5.测试结果6.参考文献7.Matlab代码 摘要&#xff1a;针对PNN神…

完整版指南:企业网络中的VXLAN-BGP-EVPN

随着互联网的发展&#xff0c;数据中心的数量和规模呈爆炸性增长趋势。数据中心业务不断增加&#xff0c;用户需求不断提高。随之而来的问题是数据中心的功能变得越来越复杂&#xff0c;运维管理变得越来越困难。VXLAN-BGP-EVPN的出现为企业网络带来了无限的可能性。 什么是VX…

中科驭数荣获北京市科学技术进步奖二等奖

近日&#xff0c;北京市人民政府发布京政发〔2023〕23号公告&#xff0c;2022年度北京市科学技术奖授奖清单揭晓&#xff0c;中国科学院计算技术研究所、北京控制工程研究所、中科驭数&#xff08;北京&#xff09;科技有限公司等产学研单位提报的“领域专用处理器关键技术及应…

竞赛 题目:基于LSTM的预测算法 - 股票预测 天气预测 房价预测

文章目录 0 简介1 基于 Keras 用 LSTM 网络做时间序列预测2 长短记忆网络3 LSTM 网络结构和原理3.1 LSTM核心思想3.2 遗忘门3.3 输入门3.4 输出门 4 基于LSTM的天气预测4.1 数据集4.2 预测示例 5 基于LSTM的股票价格预测5.1 数据集5.2 实现代码 6 lstm 预测航空旅客数目数据集预…

Linux 关闭对应端口号进程

查看当前的端口号是否在运行 找出端口号端口号进程 netstat -anp | grep 9000 关闭端口号 kill -9 [PID]

SAP ABAP 主动调用外部系统的REST接口(x-www-form-urlencoded)

如何在SAP ECC中调用外部系统提供的REST接口地址&#xff1f; Postman中使用Body中参数情况&#xff0c;使用链接的情况 x-www-form-urlencoded POST成功调用样例如下&#xff1a; SAP中实现如下&#xff1a; 1. 事务码STRUST,导入对方系统证书 2. 事务码SM59配置destinati…

hadoop 大数据集群环境配置 配置hadoop配置文件 hadoop(七)

1. 虚拟机的三台机器分别以hdfs 存储, mapreduce计算&#xff0c;yarn调度三个方面进行集群配置 hadoop 版本3.3.4 官网&#xff1a;Hadoop – Apache Hadoop 3.3.6 jdk 1.8 三台机器尾号为&#xff1a;22&#xff0c; 23&#xff0c; 24。&#xff08;没有用hadoop102, 103,10…

【kafka】windows安装启动

1.zookeeper的安装与启动 快速打开window powershell&#xff1a; windowx&#xff0c;选 2.kafka下载 —注意kafka和zookeeper需要版本匹配 安装路径 注意&#xff0c;kafka安装目录不能有空格。文件下载到&#xff1a; D:\Program_Files\kafka_2.12-3.6.0新建logs文件 修改c…

232.用栈实现队列(LeetCode)

思路 思路&#xff1a;利用两个栈实现队列先进先出的特性&#xff0c;先将元素导入一个栈内 模拟出队时&#xff0c;则将所有元素导入另一个栈内&#xff0c;此时元素顺序被反转过来&#xff0c;只需要取栈顶数据即可 那我们就可以将两个栈的功能分开&#xff0c;一个专门入pus…

verdi merge fsdb出现信号冲突的解决办法

前段时间介绍了verdi用 Edit Virtual File的方式把几个fsdb文件merge起来的方法 由于当时实验的时候只用了两个小的fsdb文件&#xff0c;每个fsdb文件中包含的信号量也比较少&#xff0c;所以并没有发现问题 我是用 Edit Virtual FIle把dump不同hier的fsdb文件merge到一起&am…

影响因子10月修正!多本期刊上涨,最高IF达54.8!

【SciencePub学术】 每年的影响因子基本都在6月底发布&#xff0c;但是由于数据不全等原因&#xff0c;部分期刊未能及时获得影响因子&#xff0c;或者影响因子有一定误差。因此&#xff0c;每年科睿唯安还会在10或11月份对当年的影响因子进行更新&#xff0c;主要包括补录和修…

驾驭数据与人工智能是人才培养的时代命题

2023年11月11日全国近千名计算机教育工作者共聚“海南博鳌亚洲论坛大酒店”&#xff0c;以“产教融合&#xff0c;供需共赢”为主题&#xff0c;“服务国家创新驱动发展&#xff0c;顺应全球新一轮科技革命和产业变革的趋势&#xff0c;培养集学科、技术和产业需求相融合的IT新…

Java封装一个根据指定的字段来获取子集的工具类

工具类 ZhLambdaUtils SuppressWarnings("all") public class ZhLambdaUtils {/*** METHOD_NAME*/private static final String METHOD_NAME "writeReplace";/*** 获取到lambda参数的方法名称** param <T> parameter* param function functi…