Linux内核之自旋锁和信号量

2023-05-16

Linux内核实现了多种同步方法,指令级支持的原子操作、自旋锁、信号量、互斥锁、完成量、大内核锁等等,我就挑比较有代表性的两个锁——自旋锁和信号量来分析。

自旋锁

Linux内核中最常用的锁就是自旋锁(spin lock),自旋锁最多只能被一个执行线程持有。如果一个执行线程试图获得一个被已经持有(即所谓争用)的自旋锁,那么该线程就会一直进行忙循环-旋转-等待锁重新可用。在任意时间,自旋锁都可以防止多于一个的执行线程同时进入临界区。自旋锁的实现和体系结构密切相关,代码往往通过汇编实现。

#define __LOCK(lock) \
  do { preempt_disable(); __acquire(lock); (void)(lock); } while (0)

#define __LOCK_BH(lock) \
  do { local_bh_disable(); __LOCK(lock); } while (0)

#define __LOCK_IRQ(lock) \
  do { local_irq_disable(); __LOCK(lock); } while (0)

#define __LOCK_IRQSAVE(lock, flags) \
  do { local_irq_save(flags); __LOCK(lock); } while (0)

#define __UNLOCK(lock) \
  do { preempt_enable(); __release(lock); (void)(lock); } while (0)

#define __UNLOCK_BH(lock) \
  do { preempt_enable_no_resched(); local_bh_enable(); __release(lock); (void)(lock); } while (0)

#define __UNLOCK_IRQ(lock) \
  do { local_irq_enable(); __UNLOCK(lock); } while (0)

#define __UNLOCK_IRQRESTORE(lock, flags) \
  do { local_irq_restore(flags); __UNLOCK(lock); } while (0)

#define _spin_lock(lock)			__LOCK(lock)
#define _spin_lock_nested(lock, subclass)	__LOCK(lock)
#define _read_lock(lock)			__LOCK(lock)
#define _write_lock(lock)			__LOCK(lock)
#define _spin_lock_bh(lock)			__LOCK_BH(lock)
#define _read_lock_bh(lock)			__LOCK_BH(lock)
#define _write_lock_bh(lock)			__LOCK_BH(lock)
#define _spin_lock_irq(lock)			__LOCK_IRQ(lock)
#define _read_lock_irq(lock)			__LOCK_IRQ(lock)
#define _write_lock_irq(lock)			__LOCK_IRQ(lock)
#define _spin_lock_irqsave(lock, flags)		__LOCK_IRQSAVE(lock, flags)
#define _read_lock_irqsave(lock, flags)		__LOCK_IRQSAVE(lock, flags)
#define _write_lock_irqsave(lock, flags)	__LOCK_IRQSAVE(lock, flags)
#define _spin_trylock(lock)			({ __LOCK(lock); 1; })
#define _read_trylock(lock)			({ __LOCK(lock); 1; })
#define _write_trylock(lock)			({ __LOCK(lock); 1; })
#define _spin_trylock_bh(lock)			({ __LOCK_BH(lock); 1; })
#define _spin_unlock(lock)			__UNLOCK(lock)
#define _read_unlock(lock)			__UNLOCK(lock)
#define _write_unlock(lock)			__UNLOCK(lock)
#define _spin_unlock_bh(lock)			__UNLOCK_BH(lock)
#define _write_unlock_bh(lock)			__UNLOCK_BH(lock)
#define _read_unlock_bh(lock)			__UNLOCK_BH(lock)
#define _spin_unlock_irq(lock)			__UNLOCK_IRQ(lock)
#define _read_unlock_irq(lock)			__UNLOCK_IRQ(lock)
#define _write_unlock_irq(lock)			__UNLOCK_IRQ(lock)
#define _spin_unlock_irqrestore(lock, flags)	__UNLOCK_IRQRESTORE(lock, flags)
#define _read_unlock_irqrestore(lock, flags)	__UNLOCK_IRQRESTORE(lock, flags)
#define _write_unlock_irqrestore(lock, flags)	__UNLOCK_IRQRESTORE(lock, flags)


在spinlock_api_up.h文件中定义了所有的自旋锁函数的实现,基本方式就是禁止中断->禁止抢占->获得锁,spin_lock这个函数没有禁止中断,因为它主要用于中断处理程序中保护临界区,中断处理程序本身就是禁止中断的。

因为自旋锁在同一时刻至多被一个执行线程持有,所以一个时刻只能有一个线程位于临界区内,这就是为多处理机器提供了防止并发访问所需的保护机制。注意在单处理器机器上,编译的时候并不会加入自旋锁。它仅仅被当做一个设置内核抢占机制是否被启用的开关。如果禁止内核抢占,那么在编译时自旋锁会被完全剔除出内核。

关于自旋锁的使用注意

1.自旋锁不可递归

Linux内核实现的自旋锁是不可递归的,这点不同于自旋锁在其他操作系统的实现,所以如果你试图得到一个你正在持有的锁,你必须自旋,等待你自己释放这个锁。但是你处于自旋忙等待中,你永远没有机会释放锁,内核就崩了。

2.中断重入会导致死锁

在中断处理程序中使用自旋锁时,一定要在获取锁之前,首先禁止本地中断(在当前处理器上的中断请求),否则,中断处理程序就会打断正在持有锁的内核代码,有可能会试图去争用这个已经被持有的自旋锁。这样一来,中断处理程序就会自旋,等待该锁重新可用,但是锁的持有者在这个中断处理程序执行完毕前不可能运行,所以就造成了死锁。这里要注意一点,需要关闭的只是本地中断(当前处理器)。如果中断发生在不同的处理器上,即使中断处理程序在同一锁上自旋,也不会妨碍锁的持有者(在不同处理器上)最终释放锁。

3.持有自旋锁时不可睡眠

持有自旋锁后如果进程睡眠了,那么就不知道何时才能重新被唤醒,此时如果还有另外的进程要获得这个自旋锁,那它就会一直忙等,非常浪费处理器资源


信号量

不同于自旋锁,Linux中的信号量是一种睡眠锁。如果有一个任务试图获得一个不可用(已经被占用)的信号量时,信号量会将其推进一个等待队列,然后让其睡眠。这时处理器能重获自由,从而去执行其他代码。当持有的信号量可用(被释放)后,处于等待队列的那个任务将被唤醒,并获得该信号量。

接下来看看信号量是怎么实现的

void down(struct semaphore *sem)
{
	unsigned long flags;

	spin_lock_irqsave(&sem->lock, flags);
	if (likely(sem->count > 0))
		sem->count--;
	else
		__down(sem);
	spin_unlock_irqrestore(&sem->lock, flags);
}
static noinline void __sched __down(struct semaphore *sem)
{
	__down_common(sem, TASK_UNINTERRUPTIBLE, MAX_SCHEDULE_TIMEOUT);
}
static inline int __sched __down_common(struct semaphore *sem, long state,
								long timeout)
{
	struct task_struct *task = current;
	struct semaphore_waiter waiter;


	//把当前进程加入信号量的等待队列
	list_add_tail(&waiter.list, &sem->wait_list);
	waiter.task = task;
	waiter.up = 0;


	for (;;) {
		//如果有信号要处理,那就当什么都没发生,直接退出
		if (state == TASK_INTERRUPTIBLE && signal_pending(task))
			goto interrupted;
		//如果当前进程收到了SIGKILL信号,代表这个进程要被杀死了,所以也啥都不干
		if (state == TASK_KILLABLE && fatal_signal_pending(task))
			goto interrupted;
		if (timeout <= 0)
			goto timed_out;
		__set_task_state(task, state);
		//调度之前一定要释放自旋锁
		spin_unlock_irq(&sem->lock);
		//延迟调度,如果timeout=MAX_SCHEDULE_TIMEOUT,和直接调动schedule函数没区别
		timeout = schedule_timeout(timeout);
		//重新拿到锁
		spin_lock_irq(&sem->lock);
		//如果是由up函数唤醒的返回正常,如果是用户空间的信号所中断或超时信号所引起的唤醒
		//那就接着执行,返回错误
		if (waiter.up)
			return 0;
	}


 timed_out:
	list_del(&waiter.list);
	return -ETIME;


 interrupted:
	list_del(&waiter.list);
	return -EINTR;
}
void up(struct semaphore *sem)
{
	unsigned long flags;


	spin_lock_irqsave(&sem->lock, flags);
	if (likely(list_empty(&sem->wait_list)))
		sem->count++;
	else
		__up(sem);
	spin_unlock_irqrestore(&sem->lock, flags);
}
static noinline void __sched __up(struct semaphore *sem)
{
	//找到第一个等待的进程
	struct semaphore_waiter *waiter = list_first_entry(&sem->wait_list,
						struct semaphore_waiter, list);
	//从等待队列中移除
	list_del(&waiter->list);
	//表明是该进程是up函数唤醒
	waiter->up = 1;
	//唤醒进程
	wake_up_process(waiter->task);
}

从上面的代码可以看到,信号量也是依赖于自旋锁的。
信号量分为计数信号量和二值信号量(也叫互斥信号量),计数信号量不能用来进行强制互斥,因为它允许多个执行线程同时访问临界区。相反,这种信号量用来对特定代码加以限制,内核使用它的机会不多,基本用到的都是互斥信号量(计数等于1的信号量),除互斥外,信号量还能进行进程同步,把计数初始化为0就可以实现进程间的同步。

再来说说自旋锁和信号量使用场景上的区别
  • 自旋锁适用于较短时间持有锁,信号量适用于较长时间持有锁
  • 自旋锁是忙等,而信号量是睡眠
  • 自旋锁可用于进程上下文和中断上下文,而信号量只能用于进程上下文
  • 自旋锁禁止内核抢占,信号量不是
  • 在你占用信号量的同时不能占用自旋锁。因为在你等待信号量时可能会睡眠,而在持有自旋锁时时不允许睡眠的
  • 自旋锁不会发生上下文切换,而信号量会(我猜这就是为什么Linux内核用的最多的是自旋锁,而不是信号量的原因)
本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

Linux内核之自旋锁和信号量 的相关文章

  • ROSERROR : CMake Error at /opt/ros/melodic/share/cv_bridge/cmake/cv_bridgeConfig.cmake:113 (message)

    产生这个的原因是 xff1a 在tx2中我把原来opencv4 1 1版本卸载了 xff0c 重新安装了opencv3 2 重新安装了cv bridge xff0c 在cv bridge中找opencv的默认路径不一样 xff0c 所以要修
  • ROSERROR : undefined reference to cv_bridge::toCvCopy

    解决办法 xff1a 参考原文 重新表述 xff1a 创建一个ROS功能包 xff0c 完成主要功能会使用到opencv 下面是package xml lt buildtool depend gt catkin lt buildtool d
  • ROSNOTE : 自动拍照

    参考原文是 xff1a 链接1 链接2 链接3 usr bin env python coding utf 8 import os import cv2 import time cam 61 cv2 VideoCapture 2 count
  • C++ : sin、cos、tan、arctan的使用

    原文参考 最重要的几条 xff1a C 43 43 中sin cos tan asin acos atan等三角函数的输入是弧度 xff0c 而不是角度注意tan atan等函数不能接受整数 xff0c tan 45 会报错 error C
  • C++ : 类的成员函数修改类中数据成员值

    遇到一个问题是 xff1a 在类中有一个数据成员 xff0c 是public的 xff0c 在类的成员函数中进行修改 xff0c 这个类的成员函数可能是要调用多次 xff0c 想知道是不是每一次调用都有效 写了一个测试函数 xff1a in
  • 临时存储空间

    迭代法 xff1a OpenCV与图像处理学习七 传统图像分割之阈值法 xff08 固定阈值 自适应阈值 大津阈值 xff09 slight smile的博客 CSDN博客 灰度直方图法 xff1a OpenCV实现灰度直方图 xff0c
  • 知网下载pdf

    知网论文 全PDF下载 xff0c 从此告别CAJ阅读器 知乎
  • OPNECV:读取二值图像

    image 61 cv2 imread 34 19 png 34 xff0c 1 在正常的cv imread后加上 1即可 xff0c 表示按照图片原有格式进行读取
  • Ubuntu 20.04桌面很卡的解决方案--亲测有效

    现象描述 xff1a Ubuntu 20 04开机后 xff0c 整个桌面卡顿严重 xff0c 鼠标滚动后桌面没响应 xff0c 几秒钟后屏幕变花 xff0c 图标重叠 xff0c 基本无法操作 打开终端也是两三秒钟后才显示终端界面 xff
  • 多任务学习综述

    An overview of multi task learning xff0c Yu Zhang and Qiang Yang xff08 Sep 2017 xff09 COMPUTER SCIENCE ABSRACT 多任务学习 mul
  • HaneWIN (windows上的NFS服务器)使用说明

    使用说明 HaneWIN官网 xff1a https hanewin net nfs e htm比较好用的版本下载地址 xff1a https download csdn net download fword 85908535使用说明 xf
  • CAN总线简明易懂教程(一)

    先看看工作原理 当 CAN 总线上的一个节点 xff08 站 xff09 发送数据时 xff0c 它以报文的形式广播给网络中所有节点 xff0c 对每个节点来说 xff0c 无论数据是否是发给自己的 xff0c 都对其接收 每组报文开头的1
  • 北斗导航系统、GPS、GLONASS信号频率

    民用方面 xff1a GLONASS xff1a L1 61 1602 43 0 5625 k MHz 和L2 61 1246 43 0 4375 k MHz L1 L2 61 9 7 GPS L1 1575 42 43 10 MHz L2
  • centos下安装Java

    文章目录 1 解压2 配置环境变量 1 解压 首先将压缩包放到 usr local路径下 xff0c 解压缩 span class token builtin class name cd span usr local span class
  • 图像语义理解

    本文转载 from xff1a http blog csdn net haitun425 article details 8802182 1 目标的检测 分类和识别都为图像语义的理解服务 理解是硬道理 xff1b 2 不在于图像理解模型是否
  • 我的嵌入式5年 VS 我被国嵌的视频坑了的日子

    在嵌入式的行业工作四五年了 xff0c 想想从当初的身无分文和什么都不会 xff0c 到现在的还算衣食无忧 xff0c 住行别想 xff0c 技术也有点提升 xff0c 进入了手机行业 xff0c 其中的酸甜苦辣只有自己知道 xff0c 从
  • 我程序人生的启蒙书

    是这本书 xff0c 大一的我接触了c和c 43 43 xff0c 为数学专业的我打开了通往另一个世界的道路 xff0c 做一名优秀的程序员 是这本书 xff0c 大一的我开始废寝忘食的学习 xff0c 自习室里往往就放着这一本数 xff0
  • C++面试题(三)——STL相关各种问题

    C 43 43 面试题 STL相关各种问题 tanglu2004 http blog csdn net worldwindjp STL相关的各种问题 1 xff0c 用过那些容器 最常用的容器就是 xff1a vector list map
  • 基于 Docker 搭建开发环境

    基于 Node 官方镜像 https hub docker com node 获取镜像 在本地 Terminal 中执行 docker pull node 以获取 node 镜像 xff0c 可在 docker desktop 中查看 创建
  • tomcat 9 与mysql 5 的连接

    1 jdk的安装 配置JAVA HOME变量 xff0c 将该变量设置到path中 2 tomcat 下载 最新版本apache tomcat 9 0 0 M9配置根目录CATALINA HOME 61 D apache tomcat 9

随机推荐

  • 刷完 LeetCode 是什么水平?能拿到什么水平的 offer?

    链接 xff1a https www zhihu com question 32019460 编辑 xff1a 深度学习与计算机视觉 声明 xff1a 仅做学术分享 xff0c 侵删 刷题是我们一贯的学习方式 xff0c 但是学霸和学渣的区
  • 开心网争车位 发布

    本软件第一次使用C 编写 xff0c 是开心001争车位的辅助软件 可以帮助你管理多个账号一起停车挣钱 xff0c 使支持和热爱 开心网 的玩家更方便 xff0c 请勿与用商业 使用方法 xff1a 本软件需依靠 net3 5 xff0c
  • 博士“申请考核制”经验

    作者 xff1a 花花 链接 xff1a https zhuanlan zhihu com p 126168158 本文转载自知乎 xff0c 作者已授权 xff0c 未经许可请勿二次转载 本文是作者真实的经历 xff0c 给打算申请国内院
  • 研究生新生要怎么看论文?

    链接 xff1a https www zhihu com question 304334959 编辑 xff1a 深度学习与计算机视觉 声明 xff1a 仅做学术分享 xff0c 侵删 问题 xff1a 经常各种看不懂论文 而且感觉好多论文
  • 考博热会出现吗?

    链接 xff1a https www zhihu com question 408008199 编辑 xff1a 深度学习与计算机视觉 声明 xff1a 仅做学术分享 xff0c 侵删 考研热已经是不争的事实了 xff0c 每年考研人数都在
  • 基于python的MongoDB入门教程

    总览 MongoDB是数据科学家常用的一种非结构化数据库本文我们讨论如何使用Python xff08 和PyMongo库 xff09 来使用MongoDB数据库 本文我们使用Python实现对MongoDB数据库的所有基本操作 结构化数据库
  • 同组博士师兄的结果复现不出来,我应该怎么办?

    链接 xff1a https www zhihu com question 502804990 编辑 xff1a 深度学习与计算机视觉 声明 xff1a 仅做学术分享 xff0c 侵删 今年研二 xff0c 老师给了一个课题 xff0c 让
  • SOC电源标志 说明 VCC、VSS、VDD、VEE、VPP、Vddf

    VBAT VBAT是电源电压 xff0c VCC xff1a C 61 circuit 表示电路的意思 即接入电路的电压 VDD xff1a D 61 device 表示器件的意思 即器件内部的工作电压 VSS xff1a S 61 ser
  • “error LNK2019: 无法解析的外部符号”原因总结

    C 43 43 工程编译时出现如下链接错误提示 xff1a 原因一 xff1a 缺少实现 只是在 h里面声明了某个方法 xff0c 没有在cpp里面实现 xff1b 我出现过这个问题 xff1b 类方法的实现未加类标识 xff1a 如 xf
  • 不支持S/W HEVC(H265)解码的有效解决方案

    最近从WIN7更换为WIN10后 xff0c PotPlayer播放器加速出现不同步情况 xff0c 网上查找了很多办法 xff0c 最终奏效 失败方法一 xff1a FFmpeg64 dll 下载FFmpeg64 dll xff08 ht
  • Win10打开任务管理器卡死的解决方法

    我的情况是刚开始装了win10家庭版 xff0c 但是安装一些软件后 xff0c 过段时间打开任务管理器就会莫名其妙的卡死 xff0c 我去重新装了原装系统 xff0c 换了固态硬盘 xff0c 清理了电脑 xff0c 还是会出现这个问题
  • 基于双目视觉的非标机械臂的空间定位流程(未完待续)

    文章目录 系统坐标系变换原理双目标定原理准备步骤 图像极线校正 对应点匹配 空间定位图像校正计算视差计算深度目标点空间定位 三维重建手眼标定 xff08 eye in hand xff09 问题故障解决下一步计划参考 系统 接上一次的非标机
  • 如何用VC++60编写查看二进制文件程序

    雷霆工作室 韩燕 在计算机应用中 xff0c 经常需要查看二进制文件的内容 目前 xff0c 在各种VC 43 43 书籍中介绍查看文本文件的文章很多 xff0c 但鲜有介绍查看二进制文件的文章 本文从功能设计 方案设计 编程实现以及技术要
  • Matlab代码导入STM32F103流程

    文章目录 软件准备STM32CubeMX简介配置STM32CUBEMX配置SIMULINKSIMULINK对STM32F103进行点灯试验一般算法导入到STM32问题故障解决参考 软件准备 安装MATLAB2019a xff0c 64位 下
  • 树莓派利用OpenCV的图像跟踪、人脸识别等

    文章目录 准备配置测试程序颜色识别跟踪人脸识别手势识别形状识别条码识别二维码识别 故障问题解决module 39 cv2 39 has no attribute 39 dnn 39 ImportError numpy core multia
  • Linux(ubuntu)安装AppImage步骤

    方法一 设置允许执行文件 xff0c 双击无反应 运行以下代码 xff0c 出错 panda6 1 0 x86 64 appimage 运行sudo apt get install fuse 直接输入以下 xff0c 即可运行 panda6
  • Solidworks导出URDF总结(Humble)

    环境 Solidwoks2021 SP5 xff1b Ubuntu22 04 xff1b ROS2 Humble 步骤 基本步骤参考 xff1a Solidworks导出URDF总结 xff08 Noetic xff09 本文只介绍不同之处
  • 博途V17(S7-1200)OPC-UA通信测试

    文章目录 环境 步骤 安装 博途端 UAExpert端 参考 环境 S7 1200 TIA Portal V17 笔记本 与PLC网线连接 Windows10 UaExpert 步骤 安装 TIA Portal v17 博途 安装教程附安装
  • LabVIEW调用Matlab函数方法总结

    文章目录 方法分类Matlab脚本节点方法Coder 43 VS方法 Net方法COM ActiveX方法 故障问题解决调用带有符号运算的方法 在 LabVIEW与Matlab混合编程进行图像处理 附带颜色栏Colorbar 的基础上做个简
  • Linux内核之自旋锁和信号量

    Linux内核实现了多种同步方法 xff0c 指令级支持的原子操作 自旋锁 信号量 互斥锁 完成量 大内核锁等等 xff0c 我就挑比较有代表性的两个锁 自旋锁和信号量来分析 自旋锁 Linux内核中最常用的锁就是自旋锁 spin lock