第三十八章 linux-并发解决方法二(信号量)
第三十八章 linux-并发解决方法二(信号量)
文章目录
- 第三十八章 linux-并发解决方法二(信号量)
- 信号量的定义
- DOWN操作
- UP操作
相对于自旋锁,信号量的最大特点是允许调用它的线程进入睡眠状态·这意味着试图获得某一信号的进程会导致对处理器拥有权的丧失,也即出现进程的切换。
信号量的定义
struct semaphore {
raw_spinlock_t lock;//lock是个自旋锁变量,用于实现对信号量的另一个成员count的原子操作。
unsigned int count;//无符号整型变量count用于表示通过该信号量允许进入临界区的执行路径的个数。
struct list_head wait_list;//wait_list用于管理所有在该信号量上睡眠的进程,无法获得该信号量的进程将进入睡眠状态。
};
如果驱动程序中定义了一个struct semaphore型的信号量变量,需要注意的是不要直接对该变量的成员进行赋值,而应该使用sema_init函数来初始化该信号量。sema_init函数定义如下:
static inline void sema_init(struct semaphore *sem, int val)
{static struct lock_class_key __key;*sem = (struct semaphore) __SEMAPHORE_INITIALIZER(*sem, val);lockdep_init_map(&sem->lock.dep_map, "semaphore->lock", &__key, 0);
}
初始化主要通过__SEMAPHORE_INITIALIZER宏完成:
#define DEFINE_SEMAPHORE(name) \struct semaphore name = __SEMAPHORE_INITIALIZER(name, 1)
#define __SEMAPHORE_INITIALIZER(name, n) \
{ \.lock = __RAW_SPIN_LOCK_UNLOCKED((name).lock), \.count = n, \.wait_list = LIST_HEAD_INIT((name).wait_list), \
}
所以 sema_init(struct semaphore *sem, int val)调用会把信号量sem的lock值设定为解锁状态,count值设定为函数的调用参数val,同时初始化wait_list链表头。
DOWN操作
信号量上的主要操作是DOWN和UP,在Linux内核中对信号量的DOWN操作有:
- void down(struct semaphore *sem) 获取信号量,不建议使用此函数,因为是 UNINTERRUPTABLE 的睡眠。
- int down_interruptible(struct semaphore *sem) 可被中断地获取信号量,如果睡眠被信号中断,返回错误-EINTR。
- int down_killable (struct semaphore *sem) 可被杀死地获取信号量。如果睡眠被致命信号中断,返回错误-EINTR。
- int down_trylock(struct semaphore *sem) 尝试原子地获取信号量,如果成功获取,返回0,不能获取,返回1。
- int down_timeout(struct semaphore *sem, long jiffies) 在指定的时间jiffies内获取信号量,若超时未获取,返回错误-ETIME。
上面这些函数中,驱动程序使用最频繁的是down_interruptible函数,本节将重点讨论该函数,之后再对其他入操作的功能作一概述性的描述。
int down_interruptible(struct semaphore *sem)
{unsigned long flags;int result = 0;raw_spin_lock_irqsave(&sem->lock, flags);//保证对sem->count操作的原子性防止多个进程对sem->count同时操作//可能引起混乱if (likely(sem->count > 0))sem->count--;elseresult = __down_interruptible(sem);raw_spin_unlock_irqrestore(&sem->lock, flags);return result;
}
函数首先通过spin_lock_irqsave的调用来保证对sem->count操作的原子性防止多个进程对sem->count同时操作
可能引起混乱。如果代码成功进入临界区,则判断sem->count是否大于0:如果count大于0,表明当前进程可以获得信号量,就将count值减I,然后退出:如果count不大于0,表明当前进程无法获得该信号量,此时调用down_interruptible,由后者完成一个进程无法获得信号量时的操作,在内部调用__down_common(struct semaphore *sem,long state,longt timeout),调用时的参数state=TASK_INTERRUPTIBLE,timeout=LONG_MAX所以当一个进程无法获得信号量时,最终调用的函数为__down_common:
__down_interruptible->__down_common
static inline int __sched __down_common(struct semaphore *sem, long state,long timeout)
{struct task_struct *task = current;/*通过对一个struct semaphore_waiter变量waiter的使用,把当前进程放到信号量sem的成员变量wait所管理的队列中*/struct semaphore_waiter waiter;list_add_tail(&waiter.list, &sem->wait_list);waiter.task = task;waiter.up = false;for (;;) {if (signal_pending_state(state, task))//把当前进程的状态设置为TASK_INTERRUPTIBLEgoto interrupted;if (unlikely(timeout <= 0))goto timed_out;__set_task_state(task, state);raw_spin_unlock_irq(&sem->lock);timeout = schedule_timeout(timeout);//使当前进程进入睡眠状态,函数将停留在schedule_timeout调用上,直到再次被调度执行。raw_spin_lock_irq(&sem->lock);if (waiter.up)return 0;}timed_out:list_del(&waiter.list);return -ETIME;interrupted:list_del(&waiter.list);return -EINTR;
}
函数的功能是,首先通过对一个struct semaphore_waiter变量waiter的使用,把当前进程放到信号量sem的成员变量wait所管理的队列中,接着在一个for循环中把当前进程的状态设置为TASK_INTERRUPTIBLE,再调用schedule_timeout使当前进程进入睡眠状态,函数将停留在schedule_timeout调用上,直到再次被调度执行。当该进程再一次被调度执行时,schedule_timeout开始返回,接下来根据进程被再次调度的原因进行处理:如果waiter.up不为0,说明进程在信号量sem的wati_list队列中被该信号量的UP操作所唤醒,进程可以获得信号量,返回0。如果进程是因为被用户空间发送的信号所中断或者是超时引起的唤醒,则返回相应的错误代码。因此对面down_interruptible的调用总是应该坚持检查其返回值,以确定函数是已经获得了信号量还是因为操作被中断因而需要特别处理,通常驱动程序对返回的非0值要做的工作是返回-ERESTARTSYS,比如下面的代码段:
//定义一个信号量
struct semaphore demosem;
sema_init(&demosem,2);
if(down_interruptiable(&demosem));return -ERESTARTSYS;
然而对down_interruptible的调用最常见的可能还是返回0表明调用者获得了信号量。为了让讨论具体化,下面以一个例子来说明,假设一个信号量sem的count=2,说明允许有两个进程进入临界区,假设有进程A、B、C、D和E先后调用down_interruptible来获得信号量,那么进程A和B将得到信号量进入临界区,C、D和E将睡眠在sem的wait_list中,此时的情形如图2所示:

在接下来的UP操作中还会用到这里的例子,来讨论进程A和B结束临界区中的操作返回时执行UP操作对wait_list中进程C、D和E的影响。
在讨论完驱动程序最常使用的down_interruptible函数之后,再回过头来看看其他几种DOWN操作:
- void down(struct semaphore *sem);
与down_interruptible相比,down函数是不可中断的,这意味着调用它的进程如果无法获得信号量,将一直处于睡眠状态直到有别的进程释放了该信号量。从用户空间的角度,如果应用程序阻塞在了驱动程序的down函数中,将无法通过一些强制措施比如按Ctrl+D组合键等来结束该进程。因此,除非必要,否则驱动程序中应该避免使用down函数。 - int down_killable(struct semaphore *sem);
睡眠的进程可以因收到一些致命性信号(fatal signal)被唤醒而导致获取信号量的操作被中断,在驱动程序中极少使用。 - int down_trylock(struct semaphore *sem);
进程试图获得信号量,但若无法获得信号量则直接返回1而不进入睡眠状态,返回0意味着函数的调用者己经获得了信号量。 - int down_timeout(struct semaphore *sem, long jiffies);
函数在无法获得信号量的情况下将进入睡眠状态,但是处于这种睡眠状态有时间限制,如果在jiffies指明的时间到期时函数依然无法获得信号量,则将返回一错误码-ETIME,在到期前进程的睡眠状态为TASK_UNINTERRUPTIBLE。成功获得信号量的函数返回0。
UP操作
void up(struct semaphore *sem)
{unsigned long flags;raw_spin_lock_irqsave(&sem->lock, flags);if (likely(list_empty(&sem->wait_list)))sem->count++;else__up(sem);raw_spin_unlock_irqrestore(&sem->lock, flags);
}
如果信号量的wait_list队列为空,则表明没有其他进程正在等待该信号量,那么只要把sem的count加1即可。如果wait_list队列不为空,则说明有其他进程正睡眠在wait_list上等待该信号量,此时调用__up(sem)来唤醒进程:
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);waiter->up = true;wake_up_process(waiter->task);
}
下面在图2的基础上讨论此处的操作。__up函数首先用list_first_entry取得sem->wait_list链表上的第一个waiter节点C,然后将其从sem->wait_list链表中删除,waiter->up=1,最后调用wake_up_process来唤醒waiter C上的进程C。这样进程C将从之前down_interruptible。
调用中的timeout=schedule_timeout(timeout)处醒来,waiter->up=1,down_interruptible返回0,进程c获得信号量,进程D和E继续等待直到有进程释放信号量或者被用户空间中断掉。即使不是信号量的拥有者,也可以调用up函数来释放一个信号量,这点与下节介绍的mutex是不同的。
在Linux系统中,信号量的一个常见的用途是实现互斥机制,这种情况下信号量的count值为1,也就是任意时刻只允许一个进程进入临界区。为此Linux内核源码提供了一个宏DECLARE_MUTEX,专门用于这种用途的信号量定义和初始化:
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);waiter->up = true;wake_up_process(waiter->task);
}
该宏定义了一个count=1的信号量变量name,并初始化了相关成员。所以接下来就可以使用信号量的DOWN和UP操作来实现互斥,比如下面的这个用DECLARE_MUTEX定义的信号量来实现互斥的代码段:
//先用DECLARE_MUTEX定义一个全局的信号量demo_sem
DECLARE_MUTEX(demo_sem);//函数demo_write里面使用demo_sem作互斥用
int demo_write{//打算进入临界区,调用down_interruptible获得信号量if(down_interruptible(&down_sem)){return -ERESTARTSYS }//成功获取信号量进入临界区.....//离开临界区,调用up释放信号量up(&demo_sem)
}
相关文章:
第三十八章 linux-并发解决方法二(信号量)
第三十八章 linux-并发解决方法二(信号量) 文章目录第三十八章 linux-并发解决方法二(信号量)信号量的定义DOWN操作UP操作相对于自旋锁,信号量的最大特点是允许调用它的线程进入睡眠状态这意味着试图获得某一信号的进程…...
数据结构-考研难点代码突破(C++实现树型查找 - B树插入与遍历,B+树基本概念)
数据结构(C)[B树(B-树)插入与中序遍历,效率分析]、B树、B*树、B树系列应用 文章目录1. B树B树的插入与删除流程2. B树(MySQL)3. B树与B树对比4. C实现B树插入,中序遍历1. B树 B树类…...
Python可视化界面编程入门
Python可视化界面编程入门具体实现代码如所示: (1)普通可视化界面编程代码入门: import sys from PyQt5.QtWidgets import QWidget,QApplication #导入两个类来进行程序界面编程if __name__"__main__":#创建一个Appl…...
基于Java+SpringBoot+Vue前后端分离书店购书系统设计与实现
博主介绍:✌全网粉丝3W,全栈开发工程师,从事多年软件开发,在大厂呆过。持有软件中级、六级等证书。可提供微服务项目搭建与毕业项目实战✌ 博主作品:《微服务实战》专栏是本人的实战经验总结,《Spring家族及…...
Android:截屏/视频截图
需求描述 实现截取Android应用当前界面的功能,需包含界面中视频(此博客的参考代码以存储在设备本地的视频为例,未检验在线视频的情况)当前的播放帧截图。 调研准备 首先应用需要获取设备存储的读写权限,需要在Andro…...
leecode-C语言实现-28. 找出字符串中第一个匹配项的下标
一、题目给你两个字符串 haystack 和 needle ,请你在 haystack 字符串中找出 needle 字符串的第一个匹配项的下标(下标从 0 开始)。如果 needle 不是 haystack 的一部分,则返回 -1 。示例 1:输入:haystack …...
使用 Postman 实现 API 自动化测试
目录:导读 背景介绍 名词解析 使用说明 执行 API 测试 集成 CI 实现 API 自动化测试 写在最后 背景介绍 相信大部分开发人员和测试人员对 postman 都十分熟悉,对于开发人员和测试人员而言,使用 postman 来编写和保存测试用例会是一种比…...
k8s环境jenkins发布vue项目指定nodejs版本
k8s环境jenkins发布vue项目指定nodejs版本1、背景2、分析3、解决方法3.1、 找到配置镜像位置3.2、 制作新镜像3.3、 推送镜像到私有仓库3.4、 修改配置文件1、背景 发布一个前端项目,它需要nodejs 16.9.0版本支持,而kubesphere 3.2.0集成的jenkins 的镜…...
我应该把毕业设计做到什么程度才能过关?
本篇博客包含了狗哥多年职业生涯对于软件项目的一丢丢理解,也讲述了对于大学生毕业设计的一些理解。如果你还是懵懵懂懂就要离开学校了,被老师告知不得不做出一套毕业设计的时候,希望你可以看到这篇博客,让你有点头绪,…...
力扣-合作过至少三次的演员和导演
大家好,我是空空star,本篇带大家了解一道简单的力扣sql练习题。 文章目录前言一、题目:1050. 合作过至少三次的演员和导演二、解题1.正确示范①提交SQL运行结果2.正确示范②提交SQL运行结果3.正确示范③提交SQL运行结果4.正确示范④提交SQL运…...
【 PMU】信号生成、采样、分割、估计器应用和误差计算(Matlab代码实现)
👨🎓个人主页:研学社的博客💥💥💞💞欢迎来到本博客❤️❤️💥💥🏆博主优势:🌞🌞🌞博客内容尽量做到思维缜密…...
电子技术——AB类输出阶的偏置
电子技术——AB类输出阶的偏置 下面我们介绍两种AB类输出阶的偏置的方法。 使用二极管偏置 下图展示了电流源 III 加两个二极管的偏置方法: 因为输出阶需要大功率输出,因此输出推挽三极管可能是几何体积比较大的晶体管。对于二极管来说,并不…...
元宇宙营业厅,数字技术融合,赋能实体经济
在我国数字经济与虚拟服务市场规模扩大下,元宇宙营业厅强势来袭,从多场景、多内容,深耕高效协同的特色功能,基于多元化、灵活的交互体验,更大程度上解决线上业务办理抽象繁琐,线下业务办理的时空受限、业务…...
MySql面试精选—分库分表
目录 1、分库分表使用场景 2、常见的分库分表方案 3、常用的分库分表中间件...
Spring上下文生命周期
基于入口来分析 import org.springframework.context.annotation.AnnotationConfigApplicationContext; import org.springframework.context.annotation.ComponentScan; import org.springframework.context.annotation.Configuration;Configuration ComponentScan public cl…...
GitHub 标星 15w,如何用 Python 实现所有算法?
学会了 Python 基础知识,想进阶一下,那就来点算法吧!毕竟编程语言只是工具,结构算法才是灵魂。 新手如何入门 Python 算法? 几位印度小哥在 GitHub 上建了一个各种 Python 算法的新手入门大全。从原理到代码…...
LeetCode 700. 二叉搜索树中的搜索
LeetCode 700. 二叉搜索树中的搜索 难度:easy\color{Green}{easy}easy 难度:middle\color{orange}{middle}middle 难度:hard\color{red}{hard}hard 题目描述 给定二叉搜索树(BST)的根节点 rootrootroot 和一个整数值…...
【数据结构】树与二叉树
目录 1、树的概念及结构 1.1、概念 1、树的特点 2、树与非树 1.2、概念 (重要) 1.3、树的表示形式 2、二叉树(重点) 2.1、概念 2.2、二叉树的特点 2.3、两种特殊的二叉树 1、满二叉树 2、完全二叉树 2.4、二叉树的性…...
Stress压力工具的部署及使用
Stress压力工具的部署及使用 下载地址:wget https://fossies.org/linux/privat/old/stress-1.0.5.tar.gz 1.部署 进入目录执行./autogen.sh [rootiZ2ze1pj93eyq389c2ppi5Z stress-1.0.5]# ./autogen.sh ps:如果执行过程中缺包,安装对应的…...
[蓝桥杯 2020 省 AB3] 乘法表
题目描述九九乘法表是学习乘法时必须要掌握的。在不同进制数下,需要不同的乘法表。例如, 四进制下的乘法表如下所示:1*11 2*12 2*210 3*13 3*212 3*321请注意,乘法表中两个数相乘的顺序必须为样例中所示的顺序,不能随意交换两个乘…...
7.4.分块查找
一.分块查找的算法思想: 1.实例: 以上述图片的顺序表为例, 该顺序表的数据元素从整体来看是乱序的,但如果把这些数据元素分成一块一块的小区间, 第一个区间[0,1]索引上的数据元素都是小于等于10的, 第二…...
深入剖析AI大模型:大模型时代的 Prompt 工程全解析
今天聊的内容,我认为是AI开发里面非常重要的内容。它在AI开发里无处不在,当你对 AI 助手说 "用李白的风格写一首关于人工智能的诗",或者让翻译模型 "将这段合同翻译成商务日语" 时,输入的这句话就是 Prompt。…...
在rocky linux 9.5上在线安装 docker
前面是指南,后面是日志 sudo dnf config-manager --add-repo https://download.docker.com/linux/centos/docker-ce.repo sudo dnf install docker-ce docker-ce-cli containerd.io -y docker version sudo systemctl start docker sudo systemctl status docker …...
使用分级同态加密防御梯度泄漏
抽象 联邦学习 (FL) 支持跨分布式客户端进行协作模型训练,而无需共享原始数据,这使其成为在互联和自动驾驶汽车 (CAV) 等领域保护隐私的机器学习的一种很有前途的方法。然而,最近的研究表明&…...
JVM垃圾回收机制全解析
Java虚拟机(JVM)中的垃圾收集器(Garbage Collector,简称GC)是用于自动管理内存的机制。它负责识别和清除不再被程序使用的对象,从而释放内存空间,避免内存泄漏和内存溢出等问题。垃圾收集器在Ja…...
【CSS position 属性】static、relative、fixed、absolute 、sticky详细介绍,多层嵌套定位示例
文章目录 ★ position 的五种类型及基本用法 ★ 一、position 属性概述 二、position 的五种类型详解(初学者版) 1. static(默认值) 2. relative(相对定位) 3. absolute(绝对定位) 4. fixed(固定定位) 5. sticky(粘性定位) 三、定位元素的层级关系(z-i…...
C++.OpenGL (10/64)基础光照(Basic Lighting)
基础光照(Basic Lighting) 冯氏光照模型(Phong Lighting Model) #mermaid-svg-GLdskXwWINxNGHso {font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}#mermaid-svg-GLdskXwWINxNGHso .error-icon{fill:#552222;}#mermaid-svg-GLd…...
Mac下Android Studio扫描根目录卡死问题记录
环境信息 操作系统: macOS 15.5 (Apple M2芯片)Android Studio版本: Meerkat Feature Drop | 2024.3.2 Patch 1 (Build #AI-243.26053.27.2432.13536105, 2025年5月22日构建) 问题现象 在项目开发过程中,提示一个依赖外部头文件的cpp源文件需要同步,点…...
Springboot社区养老保险系统小程序
一、前言 随着我国经济迅速发展,人们对手机的需求越来越大,各种手机软件也都在被广泛应用,但是对于手机进行数据信息管理,对于手机的各种软件也是备受用户的喜爱,社区养老保险系统小程序被用户普遍使用,为方…...
Spring是如何解决Bean的循环依赖:三级缓存机制
1、什么是 Bean 的循环依赖 在 Spring框架中,Bean 的循环依赖是指多个 Bean 之间互相持有对方引用,形成闭环依赖关系的现象。 多个 Bean 的依赖关系构成环形链路,例如: 双向依赖:Bean A 依赖 Bean B,同时 Bean B 也依赖 Bean A(A↔B)。链条循环: Bean A → Bean…...
