[内核同步]自旋锁spin_lock、spin_lock_irq 和 spin_lock_irqsave 分析

关于进程上下文,中断上下文,请看这篇文章

Linux进程上下文和中断上下文内核空间和用户空间

自旋锁的初衷:在短期间内进行轻量级的锁定。一个被争用的自旋锁使得请求它的线程在等待锁重新可用的期间进行自旋(特别浪费处理器时间),所以自旋锁不应该被持有时间过长。如果需要长时间锁定的话, 最好使用信号量。

单处理器的自旋锁

  • 首先,自旋锁的目的如果在系统不支持内核抢占时,自旋锁的实现也是空的,因为单核只有一个线程在执行,不会有内核抢占,从而资源也不会被其他线程访问到。

  • 其次,支持内核抢占,由于自旋锁是禁止抢占内核的,所以不会有其他的进程因为等待锁而自旋.

  • 最后,只有在多cpu下,其他的cpu因为等待该cpu释放锁,而处于自旋状态,不停轮询锁的状态。所以这样的话,如果一旦自旋锁内代码执行时间较长,等待该锁的cpu会耗费大量资源,也是不同于信号量和互斥锁的地方。

简单来说,自旋锁在内核中主要用来防止多处理器中并发访问临界区,防止内核抢占造成的竞争。

自旋锁内睡眠禁止睡眠问题:如果自旋锁锁住以后进入睡眠,而此时又不能进行处理器抢占(锁住会disable prempt),其他进程无法获得cpu,这样也不能唤醒睡眠的自旋锁,因此不相应任何操作。

自旋锁为什么广泛用于内核:自旋锁是一种轻量级的互斥锁,可以更高效的对互斥资源进行保护。自旋锁本来就只是一个很简单的同步机制,在SMP之前根本就没这个东西,一切都是Event之类的同步机制,这类同步机制都有一个共性就是:一旦资源被占用都会产生任务切换,任务切换涉及很多东西的(保存原来的上下文,按调度算法选择新的任务,恢复新任务的上下文,还有就是要修改cr3寄存器会导致cache失效)这些都是需要大量时间的,因此用Event之类来同步一旦涉及到阻塞代价是十分昂贵的,而自旋锁的效率就远高于互斥锁。

在Linux内核中何时使用spin_lock,何时使用spin_lock_irqsave很容易混淆。首先看一下代码是如何实现的。

  • spin_lock

spin_lock -----> raw_spin_lock

1 static inline void __raw_spin_lock(raw_spinlock_t *lock)  
2 {  
3         preempt_disable();  
4         spin_acquire(&lock->dep_map, 0, 0, _RET_IP_);  
5         LOCK_CONTENDED(lock, do_raw_spin_trylock, do_raw_spin_lock);  
6 }   
  1. 只禁止内核抢占,不会关闭本地中断

  2. 为何需要关闭内核抢占:假如进程A获得spin_lock->进程B抢占进程A->进程B尝试获取spin_lock->由于进程B优先级比进程A高,先于A运行,而进程B又需要A unlock才得以运行,这样死锁。所以这里需要关闭抢占。这个原理RTOS的mutex/semaphore是否相同?

a. 因为ThreadX的semaphore,假如进程B获取sema失败,会一直等待,直到A进程释放,不会死锁。

b. Mutex: mutex获取一旦失败,进程会进入sleep,直到其他进程释放;而spin_lock则不同,会一直轮训访问,且直到时间片耗完。

  • spin_lock_irq

    spin_lock_irq------> raw_spin_lock_irq

1 static inline void __raw_spin_lock_irq(raw_spinlock_t *lock)  
2 {  
3         local_irq_disable();  
4         preempt_disable();  
5         spin_acquire(&lock->dep_map, 0, 0, _RET_IP_);  
6         LOCK_CONTENDED(lock, do_raw_spin_trylock, do_raw_spin_lock);  
7 }  
  1. 禁止内核抢占,且关闭本地中断

  2. 那么在spin_lock中关闭了内核抢占,不关闭中断会出现什么情况呢?假如中断中也想获得这个锁,会出现和spin_lock中举得例子相同。所以这个时候,在进程A获取lock之后,使用spin_lock_irq将中断禁止,就不会出现死锁的情况

  3. 在任何情况下使用spin_lock_irq都是安全的。因为它既禁止本地中断,又禁止内核抢占。

  4. spin_lock比spin_lock_irq速度快,但是它并不是任何情况下都是安全的。

  • spin_lock_irqsave

spin_lock_irqsave------>__raw_spin_lock_irqsave

 1 static inline unsigned long __raw_spin_lock_irqsave(raw_spinlock_t *lock)2 {3     unsigned long flags;4 5     local_irq_save(flags);6     preempt_disable();7     spin_acquire(&lock->dep_map, 0, 0, _RET_IP_);8     /*9      * On lockdep we dont want the hand-coded irq-enable of
10      * do_raw_spin_lock_flags() code, because lockdep assumes
11      * that interrupts are not re-enabled during lock-acquire:
12      */
13 #ifdef CONFIG_LOCKDEP
14     LOCK_CONTENDED(lock, do_raw_spin_trylock, do_raw_spin_lock);
15 #else
16     do_raw_spin_lock_flags(lock, &flags);
17 #endif
18     return flags;
19 }
  1. 禁止内核抢占,关闭中断,保存中断状态寄存器的标志位

  2. spin_lock_irqsave在锁返回时,之前开的中断,之后也是开的;之前关,之后也是关。但是spin_lock_irq则不管之前的开还是关,返回时都是开的(?)。

  3. spin_lock_irq在自旋的时候,不会保存当前的中断标志寄存器,只会在自旋结束后,将之前的中断打开。

1. spin_lock/spin_unlock:

进程A中调用了spin_lock(&lock)然后进入临界区,此时来了一个中断(interrupt),该中断也运行在和进程A相同的CPU上,并且在该中断处理程序中恰巧也会spin_lock(&lock), 试图获取同一个锁。由于是在同一个CPU上被中断,进程A会被设置为TASK_INTERRUPT状态,中断处理程序无法获得锁,会不停的忙等,由于进程A被设置为中断状态,schedule()进程调度就无法再调度进程A运行,这样就导致了死锁!

但是如果该中断处理程序运行在不同的CPU上就不会触发死锁。因为在不同的CPU上出现中断不会导致进程A的状态被设为TASK_INTERRUPT,只是换出。当中断处理程序忙等被换出后,进程A还是有机会获得CPU,执行并退出临界区。所以在使用spin_lock时要明确知道该锁不会在中断处理程序中使用。

2. spin_lock_irq/spin_unlock_irq

spin_lock_irq----->raw_spin_lock_irq

spin_lock_irq 和 spin_unlock_irq, 如果你确定在获取锁之前本地中断是开启的,那么就不需要保存中断状态,解锁的时候直接将本地中断启用就可以啦

3. spin_lock_irqsave/spin_unlock_irqrestore

使用spin_lock_irqsave在于你不期望在离开临界区后,改变中断的开启/关闭状态!进入临界区是关闭的,离开后它同样应该是关闭的!

如果自旋锁在中断处理函数中被用到,那么在获取该锁之前需要关闭本地中断,spin_lock_irqsave 只是下列动作的一个便利接口:1 保存本地中断状态(这里的本地即当前的cpu的所有中断) 2 关闭本地中断 3 获取自旋锁 解锁时通过 spin_unlock_irqrestore完成释放锁、恢复本地中断到之前的状态等工作

参考:

  1. 什么情况下使用什么样的自旋锁:http://blog.csdn.net/wesleyluo/article/details/8807919

  2. 本地中断概念的理解:http://blog.csdn.net/adaptiver/article/details/6177646

  3. 深入理解自旋锁:http://blog.csdn.net/vividonly/article/details/6594195

  4. 自旋锁和互斥量区别:http://blog.csdn.net/kyokowl/article/details/6294341

  5. 自旋锁的临界区本地cpu不会发生任何进程调度:http://blog.chinaunix.net/uid-23769728-id-3367773.html

总结

这样是否可以这么说,spin_lock为了防止内核的抢占死锁,spin_lock_irq为了防止内核和中断的抢占死锁,spin_lock_irqsave 为了防止进入自旋状态丢掉之前的中断状态。

所以如果不懂的话,直接用spin_lock_irqsave是最靠谱的,如果懂的人,就可以选择用spin_lock

转自:

https://www.cnblogs.com/aaronLinux/p/5890924.html


推荐阅读:

专辑|Linux文章汇总

专辑|程序人生

专辑|C语言

我的知识小密圈

关注公众号,后台回复「1024」获取学习资料网盘链接。

欢迎点赞,关注,转发,在看,您的每一次鼓励,我都将铭记于心~

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

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

相关文章

网络流24题 洛谷 3355 骑士共存

转换成最小割&#xff1b; #include <bits/stdc.h>using namespace std ;const int mx [ 9 ] { 2 , 2 , -2 , -2 , -1 , 1 , -1 , 1 } ; const int my [ 9 ] { -1 , 1 , -1 , 1 , 2 , 2 , -2 , -2 } ; const int N 100000 10 , inf 1e8 7 ;queue < int …

从零开始设计CPU

CPU&#xff0c;中文全称为中央处理单元&#xff0c;简称处理器&#xff0c;是现代电子计算机的核心器件。如果你想了解一台计算机是如何构建并工作的&#xff0c;那么深入了解CPU的设计非常有用。不过&#xff0c;这个美好的愿望是否会遭遇“骨感”的现实呢&#xff1f;毕竟一…

嵌入式OS入门笔记-以RTX为案例:一.简介

嵌入式OS入门笔记-以RTX为案例&#xff1a;一.简介 最近在做OS相关的项目&#xff0c;一方面涉及到大量&#xff08;通用&#xff09;操作系统的概念和理解&#xff0c;另一方面要深入到一个小实时操作系统的源代码中&#xff0c;并在此操作系统上开发应用。虽然说内容上并不难…

Linux进程O(1)调度算法,面试必考哦

进程调度有很多方法&#xff0c;这里只讨论Linux下的进程调度&#xff0c;先说下&#xff0c;这个是高端面试必考题&#xff0c;既然我发文了&#xff0c;大家最好看看&#xff0c;而且目前看到的写得最好的文章&#xff0c;推荐给大家。Linux是一个支持多任务的操作系统&#…

让Visual Studio 也支持JS代码折叠 —— 续 [ Visual Studio | Js | ScriptOutline | SmallOutline ]...

前言 上文让JS代码折叠的功能能用了&#xff0c;本文将对代码继续改进以期更好用、更实用&#xff0c;随后有介绍Visual Studio JS方面的几个插件。 文章 1. VS2003折叠代码的Micro 2. MSDN 3. Document Outline for Client Script in Visual Studio 2005 正文 …

嵌入式OS入门笔记-以RTX为案例:二.快速移植到RTX

嵌入式OS入门笔记-以RTX为案例&#xff1a;二.快速移植到RTX本篇笔记将简单介绍RTX&#xff0c;包括基本架构&#xff0c;如何在Keil中配置。需要安装ARM-MDK和一块硬件板&#xff0c;笔记以STM32F4Discovery为例子。1.为什么要用RTOS&#xff1f;尽管把所有程序放在一个大的循…

再解析下内核自旋锁和优先级翻转问题

[内核同步]自旋锁spin_lock、spin_lock_irq 和 spin_lock_irqsave 分析漫画|Linux 并发、竞态、互斥锁、自旋锁、信号量都是什么鬼&#xff1f;Linux内核自旋锁之前写的自旋锁的文章&#xff0c;现在再加一篇&#xff0c;可能单纯的一两次说明不能把问题说清楚。所以再写一篇文…

ios 逆向编程(环境搭建)

首先如果你想要逆向其他的APP 动态的查看 或者修改人家APP里面的东西 1&#xff0c; 首先要有一台越狱的手机 最好是9.1以下的&#xff0c;因为9.2以上&#xff08;包括9.2&#xff09;就不能完美越狱了 2&#xff0c;手机也要5s以上的&#xff08;因为从5S开始支持arm64架构&…

最大、最小堆的实现

最大最小堆 堆是一种经过排序的完全二叉树&#xff0c;其中任一非终端节点的数据值均不大于&#xff08;或不小于&#xff09;其左子节点和右子节点的值。 最大堆和最小堆是二叉堆的两种形式。 最大堆&#xff1a;根结点的键值是所有堆结点键值中最大者。 最小堆&#xff1a;根…

嵌入式OS入门笔记-以RTX为案例:四.简单的时间管理

嵌入式OS入门笔记-以RTX为案例&#xff1a;四.简单的时间管理 上一节简单记录了进程task。有了进程以后&#xff0c;我们需要关心怎么样分配CPU资源&#xff08;或者运行时间&#xff09;给每个进程。那么就要引入排程&#xff08;scheduling&#xff09;的概念。排程一般都是O…

我等这个含蓄的技术男当上了CEO

大家好&#xff0c;祝大家五一节日快乐&#xff01;今天没有写技术文章&#xff0c;今天想吹一个人&#xff0c;他是我的朋友&#xff0c;他做公众号很久了&#xff0c;技术文章写的也不错&#xff0c;但是阅读和关注量一直没有上来&#xff0c;我之前好几次在公众号上转发了他…

广东总冠军

lets go tiger 看这篇文章之前&#xff0c;先看看我们看球的视频 恭喜广东拿下总冠军&#xff01; 恭喜胡明轩夺得FMVP&#xff01; 我当时预测的是周鹏或者胡明轩拿下FMVP&#xff0c;最后是胡明轩&#xff0c;广东后场三条枪表现都非常亮眼。如果是上一场广东夺冠&#xff0c…

每日一题(1) —— 数组计算

判断下面代码是否可执行&#xff1f;如果可执行&#xff0c;执行结果是多少&#xff1f; #include <stdio.h>int main(void) {int array[10] {0, 2, 3, 4, 5, 6, 7, 8, 9, 10};0[array] 1;printf("%d\n", (-1)[array 5]);return 0; } 分析&#xff1a; C语…

内存文章汇总,并剖析mmap

在看这篇文章之前&#xff0c;可以先看看下面这几篇文章Linux内存&#xff0c;先看这篇文章Linux内存寻址方式Linux虚拟内存TLBLinux物理内存初始化Linux io内存存在的意义~修改cmdline 把内存改成512MB用mtrace定位内存泄漏什么是内存泄漏&#xff1f;Linux内存管理slub分配器…

[综述泛读] A survey on web services composition (IJWGS, 2005)

Time: 2.5 hours Dustdar S, Schreiner W. "A survey on web services composition." International Journal of Web and Grid Services: 1-30. 2005 (30 pages, 单栏) (gs:169) Schahram Dustdar (维也纳技术大学, full prof) Dusdar是Distributed Systems Group的老…

韦老师的开发板和嵌入式书籍赠送

大家五一快乐&#xff01;我知道这个时候大家都没有什么心思学习&#xff0c;所以找了联合了几个朋友一起给大家送点东西。这几个技术号主都非常用心的给大家分享技术文章&#xff0c;我相信&#xff0c;跟他们一起&#xff0c;你们也能变得更加优秀。奖品包括&#xff1a;1. 韦…

每日一题(2)—— -2与2的比较

分析下面的代码&#xff0c;求运行结果。 #include <stdio.h>int main(void) {if(-2 > 2){printf("11111\r\n");}else{printf("22222\r\n");}return 0; }分析&#xff1a; -2和2都没有声明存储类型&#xff0c;编译器默认按int存储&#xff0c;所…

truffle unbox react 出坑指南

最近几天差点就被这鬼东西给逼疯了&#xff0c;truffle init 、truffle unbox webpack 不管我怎么运行都是对的&#xff0c;唯独truffle unbox react 不管在哪个windows都会报错&#xff0c;换了好几台电脑&#xff0c;心都累完了&#xff0c;还好我坚持了下来&#xff0c;找了…

单片机6年想转嵌入式Linux ,不知如何下手?

刷知乎看到下面这个提问。单片机6年想转嵌入式Linux &#xff0c;不知如何下手&#xff1f;现在挺尴尬&#xff0c;做的单片机产品总是感觉重复重复再重复&#xff0c;想学习点新东西&#xff0c;不知道如何转。说实话&#xff0c;这个问题自己关注了很久。今天就借题主这个问题…

每日一题(3)—— -2与2的比较(二)

分析下面的代码&#xff0c;求运行结果。 #include <stdio.h>int main(void) {if(-2L > 2UL){printf("11111\r\n");}else{printf("22222\r\n");}return 0; }分析&#xff1a; 常量后面接L表示long型存储&#xff0c;UL表示unsigned long型存储&a…