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

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()会变成可抢占和睡眠的锁,这一点需要特别注意。

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

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

【Mycat2实战】一、Mycat简介

1. 什么是Mycat 什么是Mycat Mycat是数据库中间件&#xff0c;所谓中间件数据库中间件是连接Java应用程序和数据库中间的软件。 为什么要用Mycat 遇到问题&#xff1a; Java与数据库的紧耦合高访问量高并发对数据库的压力读写请求数据不一致 2. Mycat与其他中间件区别 目前的…

IC行业秋招真实情况记录,快来看看吧~

2023年&#xff0c;IC行业人才竞争尤为激烈。为了更好的获取到面试的经验&#xff0c;不妨先来了解一下IC面试常见的问题&#xff0c;以及面试该准备的相关事项吧~ &#xff08;文末可领全部面试题目&#xff09; 什么是同步逻辑和异步逻辑&#xff1f; 同步逻辑是时钟之间…

固定式液压破碎工作臂系统柱塞液压泵站系统比例阀控制器

固定式液压破碎工作臂系统柱塞液压泵站系统比例阀控制器主要用于&#xff1a;矿山、矿井、采石场&#xff0c;砂石骨料场、冶金冶炼厂、铸造厂、水泥厂、钢包炉渣厂、电厂、港口和码头、辅助翻车机翻卸铁路敞车散料、建材、公路、铁路、水利和化学工业等众多液压控制部门。通过…

有私域和无私域有什么区别?

公域流量和私域流量的区别主要体现在以下几个方面&#xff1a; 1. 渠道区别&#xff1a;私域流量是个体独有的流量池&#xff0c;而公域流量的流量池是公共的。换句话说&#xff0c;私域流量是通过个人平台直接获取用户的渠道&#xff0c;而公域流量是依靠一个公共平台的流量来…

ctfshow sql入门174 175脚本

因为觉得脚本写的太烂了&#xff0c;二分法也迷迷糊糊的 主要是python怎么学的那么烂&#xff01;&#xff01; 再研究一下 174 布尔盲注 这是不使用二分法的 import requestsurl http://e9a1012f-6cb2-451d-9084-0d011dfcff89.challenge.ctf.show/api/v4.php flag for …

flutter背景图片设置

本地图片设置 1、在配置文件pubspec.yaml中&#xff0c;设置以下代码 assets:- assets/- assets/test/2、如果目录中没有assets文件夹&#xff0c;则创建一个文件夹&#xff0c;并且取名为assets&#xff0c;在此文件夹中存放图片资源即可&#xff0c;如果想分文件夹管理&…

为什么数据安全很重要?哪些措施保护数据安全?

数据安全很重要的原因是因为数据是现代社会的重要财产之一。很多组织和企业依赖数据来做出商业决策&#xff0c;管理客户关系&#xff0c;进行财务规划等等。如果这些数据泄露或遭到黑客攻击&#xff0c;那么就会影响企业的经济利益&#xff0c;甚至影响到个人的隐私和安全。此…

【算法|动态规划 | 区间dp No.1】AcWing 282. 石子合并

个人主页&#xff1a;兜里有颗棉花糖 欢迎 点赞&#x1f44d; 收藏✨ 留言✉ 加关注&#x1f493;本文由 兜里有颗棉花糖 原创 收录于专栏【AcWing算法提高学习专栏】【手撕算法系列专栏】 &#x1f354;本专栏旨在提高自己算法能力的同时&#xff0c;记录一下自己的学习过程&a…

《原则》思维导图

ProcessOn 《原则》是一本投资与管理领域的经典之作&#xff0c;作者瑞达利欧以生动的语言和深入浅出的方式&#xff0c;分享了他的投资原则和管理经验。这本书不仅适合金融从业者&#xff0c;也对一般读者有很大启发。 通过阅读《原则》&#xff0c;你将了解到如何建立有效的…

大模型时代的机器人研究

机器人研究的一个长期目标是开发能够在物理上不同的环境中执行无数任务的“多面手”机器人。对语言和视觉领域而言&#xff0c;大量的原始数据可以训练这些模型&#xff0c;而且有虚拟应用程序可用于应用这些模型。与上述两个领域不同&#xff0c;机器人技术由于被锚定在物理世…

教对象写代码

之前对象工作中需要获取地图上的一些数据, 手工找寻复制 费时费力, 逢此契机, 准备使用代码尽可能简化机械重复操作, 力图一劳永逸. 首选简洁易入门的Python. 下文就是对流程的总结, 及简述每步的意义. 并不Hack,重在感受编程的用途和基本工具的使用. 以百度地图为例,需求如下:…

探索图像形态学变换:理论与应用

图像形态学变换是数字图像处理领域中的重要技术&#xff0c;它通过对图像中的形状和结构进行操作和改变&#xff0c;来实现图像的分析、识别和增强等应用。本文将从图像形态学变换的基本原理和方法入手&#xff0c;介绍膨胀、腐蚀、开运算、闭运算等常见的形态学变换操作&#…

embedding的综述

1 一文读懂Embedding的概念&#xff0c;以及它和深度学习的关系 one-hot 变成地位稠密的向量&#xff0c;降维 什么是词嵌入&#xff1a;讲词汇表中的词或者词语映射成固定长度的向量。 具体过程&#xff1a; one-hot变成低维连续的向量 语义相近的词语&#xff0c;词语赌…

洗地机和扫地机怎么选?洗地机品牌怎么选?2023旗舰洗地机总结

洗地机是一种可以一次性完成吸尘、拖地、洗地以及除菌的多功能智能清洁神器&#xff0c;它可以轻松的应对各种地面的干湿垃圾&#xff0c;提高地面清洁同时让清洁过程变得更加高效&#xff0c;但是目前的洗地机那么多&#xff0c;我们怎么挑选到一款合适的洗地机呢&#xff1f;…

2023-2024-2 高级语言程序设计-二维数组

7-1 矩阵运算 给定一个nn的方阵&#xff0c;本题要求计算该矩阵除副对角线、最后一列和最后一行以外的所有元素之和。副对角线为从矩阵的右上角至左下角的连线。 输入格式: 输入第一行给出正整数n&#xff08;1<n≤10&#xff09;&#xff1b;随后n行&#xff0c;每行给出…

【Vue 本地项目运行https服务】

配置本地开发环境的https访问 1、下载证书生成库2、创建证书颁发机构3、创建证书4、创建成功后会有4个文件在我们项目根目录5、定位到ca.crt 文件所在在位置 双击 安装证书6、在vue.config.js中引入证书&#xff1b; 1、下载证书生成库 npm install -g mkcert2、创建证书颁发机…

Zookeeper 命令使用和数据说明

文章目录 一、概述二、命令使用2.1 登录 ZooKeeper2.2 ls 命令&#xff0c;查看目录树&#xff08;节点&#xff09;2.3 create 命令&#xff0c;创建节点2.4 delete 命令&#xff0c;删除节点2.5 set 命令&#xff0c;设置节点数据2.6 get 命令&#xff0c;获取节点数据 三、数…

优秀的接口自动化测试方案是啥样的?

1、引言 1.1 文档版本 版本 作者 审批 备注 V1.0 XXXX 创建测试方案文档 1.2 项目情况 项目名称 XXX 项目版本 V1.0 项目经理 XX 测试人员 XXXXX&#xff0c;XXX 所属部门 XX 备注 1.3 文档目的 本文档主要用于指导XXX-YY项目常用接口自动化测试工作的开…

Realistic fault detection of li-ion battery via dynamical deep learning

昇科能源、清华大学欧阳明高院士团队等的最新研究成果《动态深度学习实现锂离子电池异常检测》&#xff0c;用已经处理的整车充电段数据&#xff0c;分析车辆当前或近期是否存在故障。 思想步骤&#xff1a; 用正常电池的充电片段数据构造训练集&#xff0c;用如下的方式构造…