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_SPINLOCKunsigned int magic, owner_cpu;void *owner;
#endif
#ifdef CONFIG_DEBUG_LOCK_ALLOCstruct 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.mzph.cn/news/142007.shtml

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

相关文章

Elasticsearch7 入门 进阶

1、全文检索 1.1、数据分类 按数据分类的话&#xff0c;主要可以分为以下三类&#xff1a; 结构化数据&#xff1a;固定格式、有限长度&#xff0c;比如mysql存的数据非结构化数据&#xff1a;不定长、无固定格式&#xff0c;比如邮件、Word文档、日志等半结构化数据&#xf…

【Opencv】cv::dnn::NMSBoxes()函数详解

本文通过原理和示例对cv::dnn::NMSBoxes&#xff08;&#xff09;进行解读&#xff0c;帮助大家理解和使用。 原理 cv::dnn::NMSBoxes是OpenCV库中的一个函数&#xff0c;用于在目标检测中处理多个预测框。在目标检测中&#xff0c;模型可能会为同一个物体生成多个预测框&…

传统企业数字化转型都要面临哪些挑战?_数据治理平台_光点科技

数字化转型已经成为传统企业发展的必经之路&#xff0c;但在这个过程中&#xff0c;企业往往会遭遇多方面的挑战。 1.文化和组织惯性 最大的挑战之一是企业文化和组织惯性的阻力。传统企业往往有着深厚的历史和根深蒂固的工作方式&#xff0c;员工和管理层可能对新的数字化工作…

海外媒体发稿:彭博社发稿宣传中,5种精准营销方式

在如今的信息发生爆炸时期&#xff0c;营销方式多种多样&#xff0c;但是充分体现精准营销并针对不同用户群体的需求并非易事。下面我们就根据彭博社发稿营销推广为例子&#xff0c;给大家介绍怎样根据不同用户人群方案策划5种精准营销方式。 1.界定总体目标用户人群在制订精准…

Spring IOC - Bean的生命周期之实例化

在Spring启动流程文章中讲到&#xff0c;容器的初始化是从refresh方法开始的&#xff0c;其在初始化的过程中会调用finishBeanFactoryInitialization方法。 而在该方法中则会调用DefaultListableBeanFactory#preInstantiateSingletons方法&#xff0c;该方法的核心作用是初始化…

【nlp】2.4 GRU模型

GRU模型 1 GRU介绍2 GRU的内部结构图2.1 GRU结构分析2.2 Bi-GRU介绍2.3 使用Pytorch构建GRU模型2.4 GRU优缺点3 RNN及其变体1 GRU介绍 GRU(Gated Recurrent Unit)也称门控循环单元结构, 它也是传统RNN的变体, 同LSTM一样能够有效捕捉长序列之间的语义关联, 缓解梯度消失或爆…

媒体聚焦丨四维图新旗下杰发科技王璐:设计决定芯片质量

编者按&#xff1a;新四化、软件定义汽车使汽车芯片成为了最新的半导体增长极&#xff0c;催生了汽车芯片的数量呈倍速增长&#xff0c;汽车芯片功能越来越复杂&#xff0c;迭代速度也越来越快。汽车芯片厂商从最初的设计开始&#xff0c;就要按照车规级芯片的要求对芯片进行全…

带你用Python制作超级经典的2048游戏(文末赠书)

名字:阿玥的小东东 学习:Python、C/C++ 主页链接:阿玥的小东东的博客_CSDN博客-python&&c++高级知识,过年必备,C/C++知识讲解领域博主 目录 2048游戏Python实现 本期赠书 2048游戏Python实现 2048游戏是一款非常流行的益智游戏,玩家需要通过合并数字方块来获得更…

Android APK打包的过程主要步骤

Android APK打包的过程可以概括为以下几个主要步骤&#xff1a; 编译源代码&#xff1a;将开发好的Java源代码编译成Dalvik字节码文件&#xff08;.dex文件&#xff09;&#xff0c;Android安卓该文件包含了Android平台上的运行程序的指令集。打包资源文件&#xff1a;将应用程…

0基础学习VR全景平台篇第120篇:极坐标处理接缝 - PS教程

上课&#xff01;全体起立~ 大家好&#xff0c;欢迎观看蛙色官方系列全景摄影课程&#xff01; 紧跟上节课&#xff0c;我们已经学会了怎么利用PS蒙版工具来对航拍全景图补天。但是在后续工作学习中&#xff0c;我们会遇到天空这部分存在部分接缝的问题&#xff0c;如图&…

使用Docker本地安装部署Drawio绘图工具并实现公网访问

目录 前言 1. 使用Docker本地部署Drawio 2. 安装cpolar内网穿透工具 3. 配置Draw.io公网访问地址 4. 公网远程访问Draw.io 前言 提到流程图&#xff0c;大家第一时间可能会想到Visio&#xff0c;不可否认&#xff0c;VIsio确实是功能强大&#xff0c;但是软件为收费&…

spring-boot-starter-data-redis2.X连接redis7

由于redis7引入了acl机制&#xff0c;可以配置用户权限&#xff0c; 比如配置了一个普通用户 test&#xff0c;权限为 test_ 前缀的key可操作 springboot想要连接&#xff0c;并没有设置用户名的地方&#xff0c; 跟了源码&#xff0c;jedis客户端是支持的&#xff0c;但是s…

Zephyr-7B论文解析及全量训练、Lora训练

文章目录 一、Zephyr&#xff1a;Direct Distillation of LM Alignment1.1 开发经过1.1.1 Zephyr-7B-alpha1.1.2 Zephyr-7B-beta 1.2 摘要1.3 相关工作1.4 算法1.4.1 蒸馏监督微调&#xff08;dSFT&#xff09;1.4.2 基于偏好的AI反馈 (AIF&#xff09;1.4.3 直接蒸馏偏好优化&…

英伟达中国特供芯片是缩水版;华为 Mate60 Pro 国产零件价值占比 47%丨 RTE 开发者日报 Vol.84

开发者朋友们大家好&#xff1a; 这里是 「RTE 开发者日报」 &#xff0c;每天和大家一起看新闻、聊八卦。我们的社区编辑团队会整理分享 RTE &#xff08;Real Time Engagement&#xff09; 领域内「有话题的 新闻 」、「有态度的 观点 」、「有意思的 数据 」、「有思考的 文…

DEC 深度编码聚类函数

2. 辅助目标函数 要使用输入 (bt, groups, embed_dim) 计算 DEC 模型的目标分布&#xff0c;关键部分是使用软分配 q &#xff0c;其形状为 (bt, groups, max_cluster) 。这里&#xff0c; max_cluster 是您要定位的集群数量。当您沿该维度执行聚类时&#xff0c;需要跨 group…

wpf 命令概述

wpf 命令概述 命令是 Windows Presentation Foundation (WPF) 中的一种输入机制&#xff0c;与设备输入相比&#xff0c;它提供的输入处理更侧重于语义级别。 示例命令如许多应用程序均具有的“复制”、“剪切”和“粘贴”操作。 本概述定义 WPF 中有哪些命令、哪些类属于命令…

数据分析的流程:CRISP-DM方法和SEMMA方法

CRISP-DM方法 SEMMA方法 角色与职责&#xff1a;EDIT数字化模型

分布式事务的用途是什么?分布式事务产生的情景有哪些?分布式事务的解决方案和思路

分布式事务&#xff0c;指的就是在分布式的系统里面完成一些事务&#xff0c;下文介绍了分布式事务的用途是什么&#xff1f;分布式事务产生的情景有哪些等问题。 一、分布式事务的用途是什么&#xff1f; 分布式事务处理 (TP) 系统旨在协助在分布式环境中跨异类的事务识别资…

基于STM32+微信小程序设计的智能宠物喂养系统_2023升级版

基于STM32设计的智能宠物喂养系统(腾讯云IOT+微信小程序) 一、设计需求 【1】 项目背景 随着生活水平的提高,人们的生活节奏越来越快,无法照顾宠物的人群越来越多。而宠物的健康和幸福是人们非常关心的问题。为了解决这个问题,人们开始使用智能投喂器来帮助宠物。然而,传…

VMware 虚拟机开启后黑屏问题的解决方式

很好&#xff0c;现在是vm 虚拟机节目的连续剧了 首先&#xff0c;我们安装好了&#xff0c;vm软件。 其次&#xff0c;我们在vm中创建了虚拟机。 再其次&#xff0c;我们解决了&#xff0c;开启虚拟机计算机自动重启的问题。 最后我们遇到了这个问题&#xff1a;虚拟机开启后整…