linux RT-Preempt spin lock实现

一、spin_lock概述

Spinlock是linux内核中常用的一种互斥锁机制,和mutex不同,当无法持锁进入临界区的时候,当前执行线索不会阻塞,而是不断的自旋等待该锁释放。正因为如此,自旋锁也是可以用在中断上下文的。也正是因为自旋,临界区的代码要求尽量的精简,否则在高竞争场景下会浪费宝贵的CPU资源。

1. spin lock 的发展

(1) TAS和CAS

硬件对同步的支持-TAS和CAS指令 - 元思 - 博客园

锁只有一个原子变量,通过原子指令来修改自旋锁的状态(locked、unlocked)。问题是没有公平可言,无法让等待最长的那个任务优先拿到锁,为了解决这个问题引入了ticket spinlock。

610c9a3e1aa93e6436db6ece3f4163d8.png

如果thread4当前持锁,同一个cluster中的cpu7上的thread7和另外一个cluster中的thread0都在自旋等待锁的释放。当thread4释放锁的时候,由于cpu7和cpu4的拓扑距离更近,thread7会有更高概率可以抢到自旋锁,从而产生了不公平现象。

(2) ticket spinlock

类似排队叫号,只有任务手中事先领取的号和被叫到的号相等时才能持锁进入临界区。这解决了不公平的问题。但是出现叫号时,所有等待的任务所在的cpu都要读取内存,刷新对应的cache line,而只有获取锁的那个任务所在的cpu对cache line 的刷新才是有意义的,锁争抢的越激烈,无谓的开销也就越大。

但是这种自旋锁在持锁失败的时候会对自旋锁状态数据next成员进行++操作,当CPU数据巨大并且竞争激烈的时候,自旋锁状态数据对应的cacheline会在不同cpu上跳来跳去,从而对性能产生影响,

(3) MCS Lock

在ticket spinlock的基础上做一定的修改,让多个CPU不再等待同一个spinlock变量,而是基于各自的per-CPU的变量进行等待,那么每个CPU平时只需要查询自己对应的这个变量所在的本地cache line,仅在这个变量发生变化的时候,才需要读取内存和刷新这条cache line,这样就可以解决上述问题。要实现类似这样的spinlock的分身,其中的一种方法就是使用MCS lock。试图获取一个spinlock的每个CPU,都有一份自己的MCS lock。

(4) qspinlock

相比起Linux中只占4个字节的ticket spinlock,MCS lock多了一个指针,要多占4(或者8)个字节,消耗的存储空间是原来的2-3倍。qspinlock的首要目标就是改进原生的MCS lock结构体,尽量将原生MCS lock要包含的内容塞进4字节的空间里。

如果只有1个或2个CPU试图获取锁,那么只需要一个4字节的qspinlock就可以了,其所占内存的大小和ticket spinlock一样。当有3个以上的CPU试图获取锁,需要一个qspinlock加上(N-2)个MCS node。

qspinlock中加入”pending”位域,如果是两个CPU试图获取锁,那么第二个CPU只需要简单地设置”pending”为1,而不用另起炉灶创建一个MCS node。

试图加锁的CPU数目超过3个是小概率事件,但一旦发生,使用ticket spinlock机制就会造成多个CPU的cache line无谓刷新的问题,而qspinlock可以利用MCS node队列来解决这个问题。

可见,使用qspinlock机制来实现spinlock,具有很好的可扩展性,也就是无论当前锁的争抢程度如何,性能都可以得到保证。

Linux内核同步原语之自旋锁(Spin Lock)_mcs自旋锁-CSDN博客

2. spin lock 的命令规范

(1)spinlock,对于没有打上Linux-RT(实时Linux)的patch的系统,spin_lock只是简单地调用raw_spin_lock,实际上他们是完全一样的;如果打上这个patch之后,spin_lock会使用信号量完成临界区的保护工作,带来的好处是同一个CPU可以有多个临界区同时工作,而原有的体系因为禁止抢占的原因,一旦进入临界区,其他临界区就无法运行,新的体系在允许使用同一个临界区的其他进程进行休眠等待,而不是强占着CPU进行自旋操作。

(2)raw_spinlock,即便是配置了PREEMPT_RT也要顽强的spin

(3)arch_spinlock,spin lock是和architecture相关的,arch_spinlock是architecture相关的实现

对于UP平台,所有的arch_spinlock_t都是一样的,定义如下:

typedef struct { } arch_spinlock_t;

什么都没有,一切都是空啊。当然,这也符合前面的分析,对于UP,即便是打开的preempt选项,所谓的spin lock也不过就是disable preempt而已,不需定义什么spin lock的变量。

对于SMP平台,这和arch相关,我们在下一节描述。

3. spin lock 特点

自旋锁在实现的时候调用preempt_disable关闭了内核抢占。也就是说运行在一个CPU的代码使用spin_lock加锁之后,基于该CPU的内核抢占就被禁止了。因此会产生以下影响:

  • 在单核系统:只需要禁止内核抢占,等同于关闭了进程切换,从而就不存在进程同步的问题。由于禁止了内核抢占,如果进程获取自旋锁之后,在临界区中睡眠,将会导致其他进程都无法获取CPU而运行,从而不能唤醒睡眠的自旋锁,因此禁止在自旋锁中使用睡眠等函数(除了中断,但是中断通常不会唤醒睡眠的自旋锁);
  • 在多核系统:虽然禁止了当前CPU内核抢占,但是如果存在多个CPU,仍然存在多个CPU对自旋锁共享变量同时访问的问题,因此在多核系统除了关闭CPU内核抢占、还需要通过独占指令ldrex、strex实现共享变量的互斥访问;

自旋锁的特点有:

  • spinlock是一种死等的锁机制;
  • semaphore可以允许多个执行单元进入,spinlock不行,一次只能有一个执行单元获取锁并进入临界区,其他的执行单元都是在门口不停的死等;
  • 执行时间短,由于spinlock死等这种特性,如果临界区执行时间太长,那么不断的在临界区门口“死等”的那些执行单元会浪费CPU;
  • 由于在中断上下文中是不允许睡眠的,因此spinlock可以在中断上下文中适用;而信号量和互斥锁都会导致睡眠,无法在中断上下文中使用;

思考:

Linux内核之禁止中断和禁止内核抢占_禁止中断,禁止内核抢占-CSDN博客 

真正的上锁前,为何要调用preempt_disable()来关闭抢占的case_preemption disabled-CSDN博客 

二、代码结构

最上层是通用自旋锁代码(体系结构无关,平台无关),这一层的代码提供了两种接口:spinlock接口和raw spinlock接口。在没有配置PREEMPT_RT情况下,spinlock接口和raw spinlock接口是一毛一样的。如果配置了PREEMPT_RT,spinlock接口走rt spinlock,底层是基于rtmutex的。也就是说这时候的spinlock不再禁止抢占,不再自旋等待,而是使用了支持PI的睡眠锁来实现,因此有了更好的实时性。而raw spinlock接口即便在配置了PREEMPT_RT下仍然保持传统自旋锁特性。

中间一层是区分SMP和UP的,在SMP和UP上,自旋锁的实现是不一样的。对于UP,自旋没有意义,因此spinlock的上锁和放锁操作退化为preempt disable和enable。SMP平台上,除了抢占操作之外还有正常自旋锁的逻辑,具体如何实现自旋锁逻辑是和底层的CPU architecture相关的,后面我们会详细描述。

最底层的代码是体系结构相关的代码,ARM64上,目前采用是qspinlock。和体系结构无关的Qspinlock代码抽象在qspinlock.c文件中,也就是本文重点要描述的内容。

在2024年9月的欧洲开源峰会上,Linux创始人Linus Torvalds宣布,“PREEMPT_RT”(实时Linux)补丁已被正式合并进Linux主线内核。 从Linux 6.12版本起,所有发行版将内置实时Linux代码,进一步拓宽Linux在任务关键型设备和工业硬件上的应用。

入口:

先看看linux rt spin lock的结构体:spinlock_types.h - include/linux/spinlock_types.h - Linux source code v6.13-rc3 - Bootlin Elixir Cross Referencer

#include <linux/rtmutex.h>typedef struct spinlock {struct rt_mutex_base	lock;
} spinlock_t;// include/linux/rtmutex.h
struct rt_mutex_base {raw_spinlock_t		wait_lock;struct rb_root_cached   waiters;struct task_struct	*owner;
};

从上述实现可以看出,rt spin lock的底层实现是支持优先级继承的rt mutex

// include/linux/spinlock_rt.hstatic __always_inline void spin_lock(spinlock_t *lock)
{rt_spin_lock(lock);
}// kernel/locking/spinlock_rt.c
void __sched rt_spin_lock(spinlock_t *lock) __acquires(RCU)
{spin_acquire(&lock->dep_map, 0, 0, _RET_IP_);//检查锁的有效性,一般是空操作__rt_spin_lock(lock);
}static __always_inline void __rt_spin_lock(spinlock_t *lock)
{rtlock_might_resched();rtlock_lock(&lock->lock);rcu_read_lock();migrate_disable();
}static __always_inline void rtlock_lock(struct rt_mutex_base *rtm)
{lockdep_assert(!current->pi_blocked_on);if (unlikely(!rt_mutex_cmpxchg_acquire(rtm, NULL, current)))rtlock_slowlock(rtm);
}

spin_acquire

#define spin_acquire(l, s, t, i)                lock_acquire_exclusive(l, s, t, NULL, i)
#define lock_acquire_exclusive(l, s, t, n, i)           lock_acquire(l, s, t, 0, 1, n, i)

lockdep.c - kernel/locking/lockdep.c - Linux source code v5.4.90 - Bootlin Elixir Cross Referencer

/** We are not always called with irqs disabled - do that here,* and also avoid lockdep recursion:*/
void lock_acquire(struct lockdep_map *lock, unsigned int subclass,int trylock, int read, int check,struct lockdep_map *nest_lock, unsigned long ip)
{unsigned long flags;if (unlikely(current->lockdep_recursion))return;raw_local_irq_save(flags);check_flags(flags);current->lockdep_recursion = 1;trace_lock_acquire(lock, subclass, trylock, read, check, nest_lock, ip);__lock_acquire(lock, subclass, trylock, read, check,irqs_disabled_flags(flags), nest_lock, ip, 0, 0);current->lockdep_recursion = 0;raw_local_irq_restore(flags);
}

 raw_local_irq_save 宏禁用硬件中断,因为自旋锁可能被硬件中断所获取。以这样的方式获取的话程序将不会被抢占。

raw_local_irq_restore 宏再次启动硬件中断。

主要工作将在 __lock_acquire 函数中定义,这个函数在lockdep.c - kernel/locking/lockdep.c - Linux source code v5.4.90 - Bootlin Elixir Cross Referencer 源代码文件中。

__lock_acquire 函数看起来很大。我们将试图去理解这个函数要做什么,但不是在这一部分。事实上这个函数于 Linux内核锁验证器 (lock validator) 密切相关,暂时先跳过分析。

https://elixir.bootlin.com/linux/v6.13-rc3/source/kernel/locking/rtmutex.c#L1875


static __always_inline void __sched rtlock_slowlock(struct rt_mutex_base *lock)
{unsigned long flags;DEFINE_WAKE_Q(wake_q);raw_spin_lock_irqsave(&lock->wait_lock, flags);rtlock_slowlock_locked(lock, &wake_q);preempt_disable();raw_spin_unlock_irqrestore(&lock->wait_lock, flags);wake_up_q(&wake_q);preempt_enable();
}

 

同步原语 - 自旋锁简介 - 《Linux 内核揭秘(中文版)》 - 书栈网 · BookStack

自旋锁spin_lock和raw_spin_lock_raw spin log-CSDN博客

linux 之 mutex、rt_mutex、spinlock_t 的实时性补丁分析_linux rt补丁中断能达到多少-CSDN博客

Linux并发与同步专题 (2)spinlock - ArnoldLu - 博客园

Linux内核同步 - spin_lock - AlanTu - 博客园

Linux内核机制—spin_lock - Hello-World3 - 博客园

自旋锁spin_lock、spin_lock_irq 和 spin_lock_irqsave 分析 - 裸睡的猪 - 博客园

自旋锁探秘-CSDN博客

linux同步机制-自旋锁 - 大奥特曼打小怪兽 - 博客园 

spinlock.h - include/linux/spinlock.h - Linux source code v5.4.90 - Bootlin Elixir Cross Referencer

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

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

相关文章

设计模式-工厂模式/抽象工厂模式

工厂模式 定义 定义一个创建对象的接口&#xff0c;让子类决定实列化哪一个类&#xff0c;工厂模式使一个类的实例化延迟到其子类&#xff1b; 工厂方法模式是简单工厂模式的延伸。在工厂方法模式中&#xff0c;核心工厂类不在负责产品的创建&#xff0c;而是将具体的创建工作…

C++ 文字识别OCR

一.引言 文字识别&#xff0c;也称为光学字符识别&#xff08;Optical Character Recognition, OCR&#xff09;&#xff0c;是一种将不同形式的文档&#xff08;如扫描的纸质文档、PDF文件或数字相机拍摄的图片&#xff09;中的文字转换成可编辑和可搜索的数据的技术。随着技…

计算机网络 (39)TCP的运输连接管理

前言 TCP&#xff08;传输控制协议&#xff09;是一种面向连接的、可靠的传输协议&#xff0c;它在计算机网络中扮演着至关重要的角色。TCP的运输连接管理涉及连接建立、数据传送和连接释放三个阶段。 一、TCP的连接建立 TCP的连接建立采用三次握手机制&#xff0c;其过程如下&…

机器学习(1):线性回归概念

1 线性回归基础 1.1 什么是线性 例如&#xff1a;汽车每小时60KM&#xff0c;3小时可以行使多长距离&#xff1f;已知汽车的速度&#xff0c;则汽车的行使距离只与时间唯一相关。在二元的直角坐标系中&#xff0c;描出这一关系的图是一条直线&#xff0c;所以称为线性关系。 线…

1,Linux环境变量基本定义(基于Ubuntu示例进行讲解)

linux环境变量的概念 Linux环境变量&#xff08;准确说应该是shell变量&#xff09;&#xff0c;是直接存储在操作系统中的一组键值对&#xff08;dict类型&#xff09;&#xff0c;用于配置系统和应用程序的操作行为。 【有经验的描述】&#xff1a;它们的工作原理很简单&am…

STM32入门教程-示例程序(按键控制LED光敏传感器控制蜂鸣器)

1. LED Blink&#xff08;闪烁&#xff09; 代码主体包含&#xff1a;LED.c key.c main.c delay.c&#xff08;延时防按键抖动&#xff09; 程序代码如下&#xff08;涉及RCC与GPIO两个外设&#xff09;&#xff1a; 1.使用RCC使能GPIO时钟 RCC_APB2PeriphClockC…

java springboot3.x jwt+spring security6.x实现用户登录认证

springboot3.x jwtspring security6.x实现用户登录认证 什么是JWT JWT&#xff08;JSON Web Token&#xff09;是一种开放标准&#xff08;RFC 7519&#xff09;&#xff0c;它用于在网络应用环境中传递声明。通常&#xff0c;JWT用于身份验证和信息交换。JWT的一个典型用法是…

基于Socket和ServerSocket自定义协议的实现多端通信、消息发送、群聊发送

通信基础 Socket Socket套接字是实现网络通信的重要接口&#xff0c;Socket绑定的应用进程&#xff0c;目标Ip的端口号&#xff0c;以及数据传输对应的输入输出缓冲区。一个数据包到达一台计算机物理设备后&#xff0c;经过网络协议栈的解析&#xff0c;然后由操作系统调度到对…

【Uniapp-Vue3】Prop校验与prop默认值用法及循环遍历数组对象

一、prop校验 如果我们在想要限制prop的类型&#xff0c;就可以在接收prop的时候对接收类型进行限制&#xff1a; defineProps({ 属性名:{ type:类型 } }) 需要注意类型的首字母大写 但是设置了传入参数类型限制并不能严格限制&#xff0c;只会在后台进行提示&#xff1a; 二、…

Java Stream流操作List全攻略:Filter、Sort、GroupBy、Average、Sum实践

在Java 8及更高版本中&#xff0c;Stream API为集合处理带来了革命性的改变。本文将深入解析如何运用Stream对List进行高效的操作&#xff0c;包括筛选&#xff08;Filter&#xff09;、排序&#xff08;Sort&#xff09;、分组&#xff08;GroupBy&#xff09;、求平均值&…

ExplaineR:集成K-means聚类算法的SHAP可解释性分析 | 可视化混淆矩阵、决策曲线、模型评估与各类SHAP图

集成K-means聚类算法的SHAP可解释性分析 加载数据集并训练机器学习模型 SHAP 分析以提取特征对预测的影响 通过混淆矩阵可视化模型性能 决策曲线分析 模型评估&#xff08;多指标和ROC曲线的目视检查&#xff09; 带注释阈值的 ROC 曲线 加载 SHAP 结果以进行下游分析 与…

【Python通过UDP协议传输视频数据】(界面识别)

提示&#xff1a;界面识别项目 前言 随着网络通信技术的发展&#xff0c;视频数据的实时传输在各种场景中得到了广泛应用。UDP&#xff08;User Datagram Protocol&#xff09;作为一种无连接的协议&#xff0c;凭借其低延迟、高效率的特性&#xff0c;在实时性要求较高的视频…

浅谈云计算03 | 云计算的技术支撑(云使能技术)

云计算的技术支撑 一、定义与内涵1.1 定义与内涵 二、云计算使能技术架构2.1 宽带网络和 Internet 架构2.2 数据中心技术2.3 虚拟化技术2.4 Web 技术2.5 多租户技术2.6 服务技术 一、定义与内涵 1.1 定义与内涵 云计算技术包含一些基础的关键技术&#xff0c;这里称为使能技术…

QGraphicsView QGraphicsScene QGraphicsItem

QGraphicsView && QGraphicsScene &#xff1a; QGraphicsView 是 Qt 中用于显示和交互处理 2D 图形的控件&#xff0c;QGraphicsView 用于显示 QGraphicsScene 中的内容。 .h #ifndef MAINWINDOW_H #define MAINWINDOW_H#include <QMainWindow> #include &…

【大数据】机器学习-----线性模型

一、线性模型基本形式 线性模型旨在通过线性组合输入特征来预测输出。其一般形式为&#xff1a; 其中&#xff1a; x ( x 1 , x 2 , ⋯ , x d ) \mathbf{x}(x_1,x_2,\cdots,x_d) x(x1​,x2​,⋯,xd​) 是输入特征向量&#xff0c;包含 d d d 个特征。 w ( w 1 , w 2 , ⋯ ,…

基于千帆(ERNIE-Functions-8K)Function Calling的简单使用

1.Function Calling是什么&#xff1f; 1.1 概念 Function calling是一种将LLM(大语言模型Large language model)连接外部工具的能力&#xff0c;LLM经过微调后&#xff0c;可以检测出何时需要调用函数以及需要调用函数的方法名及参数&#xff0c;并返回给调用方以便调用外部…

Deep Attentional Guided Image Filtering

This work achieved the first place in the real depth map SR challenge held in ACM ICMR 2021. 目的&#xff1a;从一个target image(如低分辨的深度图)和guidance image(如高分辨的RGB图)&#xff0c;得到输出(如高分辨的深度图)。 主要思想是考虑了target和guidance之间的…

SpringBoot+Lombok项目实体属性名xXxx格式,前端接收不到

问题解析 今天发现后端传给前端的实体类中&#xff0c;有属性为xXxxx格式的&#xff0c;前端也使用相同名称接收&#xff0c;结果却不显示值&#xff01;研究了一会发现接口请求回来后&#xff0c;原xXxxx的属性名&#xff0c;会被转为全小写。具体原因为&#xff1a;使用Lombo…

高级运维:shell练习2

1、需求&#xff1a;判断192.168.1.0/24网络中&#xff0c;当前在线的ip有哪些&#xff0c;并编写脚本打印出来。 vim check.sh #!/bin/bash# 定义网络前缀 network_prefix"192.168.1"# 循环遍历1-254的IP for i in {1..254}; do# 构造完整的IP地址ip"$network_…

为深度学习创建PyTorch张量 - 最佳选项

为深度学习创建PyTorch张量 - 最佳选项 正如我们所看到的&#xff0c;PyTorch张量是torch.Tensor​ PyTorch类的实例。张量的抽象概念与PyTorch张量之间的区别在于&#xff0c;PyTorch张量为我们提供了一个可以在代码中操作的具体实现。 在上一篇文章中&#xff0c;我们看到了…