linux 竞态与并发编程

news/2024/5/17 12:04:19/文章来源:https://blog.csdn.net/weixin_42109053/article/details/127011786

前言

提示:这里可以添加本文要记录的大概内容:

例如:随着人工智能的不断发展,机器学习这门技术也越来越重要,很多人都开启了学习机器学习,本文就介绍了机器学习的基础内容。


提示:以下是本篇文章正文内容,下面案例可供参考

一、并发与竞态

并发(concurrency) 指的是多个执行单元同时、并行被执行,而并发的执行单元对共享资源(硬件资源和软件上的全局变量、静态变量等)的访问则很容易导致竞态(race conditions)。

例如,对于globalmem设备,假设一个执行单元A对其写入3000个字符“a”,而另一个执行单元B对其写入4000个字符“b”,第三个执行单元C读取globalmem的所有字符。如果执行单元A、B的写操作如图7.1所示的顺序执行,执行单元C的读操作不会有问题。但是,如果执行单元A、B如图7.2所示的顺序执行,而执行单元C又“不合时宜”地读,则会读出3000个“b”。

在这里插入图片描述

1. 对称多处理器(SMP)的多个 CPU

SMP是一种紧耦合、共亨存储的系统模型,其体系结构如图7.3所示,它的特点是多个CPU使用共同的系统总线,因此可访问共同的外设和储存器。

在这里插入图片描述

2. 单 CPU 内进程与抢占它的进程

Liux2.6 内核支持抢占调度,一个进程在内核执行的时候可能被另一高优先级进程打断,进程与抢占它的进程访问共享资源的情况类似于SMP的多个CPU.

3. 中断(硬中断、软中断、Tasklet、底半部)与进程之间

中断可以打断正在执行的进程,如果中断处理程序访问进程正在访问的资源,则竞态也会发生。

此外,中断也有可能被新的更高优先级的中断打断,因此,多个中断之间本身也可能引起并发而导致竞态。

上述并发的发生情况除了 SMP 是真正的并行以外,其他的都是“宏观并行、微观串行”的,但其引发的实质问题和SMP相似。

解决竞态问题的途径是保证对共享资源的互斥访问,所谓互斥访问是指一个执行单元在访问共享资源的时候,其他的执行单元被禁止访问。
访问共享资源的代码区域称为临界区(critical sections),临界区需要以某种互斥机制加以保护。中断屏蔽、原子操作、自旋锁和信号量等是Linux设备驱动中可采用的互斥途径。


二、中断屏蔽

在单CPU范围内避免竞态的一种简单方法是在进入临界区之前屏蔽系统的中断。CPU一般都具备屏蔽中断和打开中断的功能,这项功能可以保证正在执行的内核执行路径不被中断处理程序所抢占,防止某些竞态条件的发生。

具体而言,中断屏蔽将使得中断与进程之间的并发不再发生,而且,由于Linux内核的进程调度等操作都依赖中断来实现,内核抢占进程之间的并发也就得以避免了。

中断屏蔽的使用方法为:

local_irq_disable() //屏蔽中断
...
critica1 section//临界区
...
local_irq_enable//开中断

由于Linux系统的异步I/O、进程调度等很多重要操作都依赖于中断,中断对于内核的运行非常重要,在屏蔽中断期间所有的中断都无法得到处理,因此长时间屏蔽中断是很危险的,有可能造成数据丢失甚至系统崩溃。这就要求在屏蔽了中断之后,当前的内核执行路径应当尽快地执行完临界区的代码。

local_irq_disable() 和 local_irq_enable() 都只能禁止和使能本CPU内的中断,因此,并不能解决SMP多CPU引发的竞态。因此,单独使用中断屏蔽通常不是一种值得推荐的避免竞态的方法,它适宜与自旋锁联合使用。

与 local_irq_disable() 不同的是,local_irq_save(flags) 除了进行禁止中断的操作以外,还保存目前CPU的中断位信息,local_irq_restore(flags) 进行的是与 local_irq_save(flags) 相反的操作。

如果只是想禁止中断的底半部,应使用 local_bh_disable(), 使能被 local_bh_disable() 禁止的底半部应该调用 local_bh_enable()。


三、自旋锁

1. 自旋锁的使用

自旋锁(spin lock) 是一种对临界资源进行互斥访问的典型手段,其名称来源于它的工作方式。为了获得一个自旋锁,在某CPU上运行的代码需先执行一个原子操作,该操作测试并设置(test-and-set)某个内存变量,由于它是原子操作,所以在该操作完成之前其他执行单元不可能访问这个内存变量。

如果测试结果表明锁已经空闲,则程序获得这个自旋锁并继续执行:如果测试结果表明锁仍被占用,程序将在一个小的循环内重复这个“测试并设置”操作,即进行所谓的“自旋”,通俗地说就是“在原地打转”。当自旋锁的持有者通过重置该变量释放这个自旋锁后,某个等待的“测试并设置”操作向其调用者报告锁已释放。

理解自旋锁最简单的方法是把它作为一个变量看待,该变量把一个临界区或者标记为“我当前在运行,请稍等一会”或者标记为“我当前不在运行,可以被使用”。如果A执行单元首先进入例程,它将持有自旋锁:当B执行单元试图进入同一个例程时,将获知自旋锁已被持有,需等到A执行单元释放后才能进入。

//定义一个自旋锁
spinlock_t lock;
spin_lock_init(&lock);spin_1ock(&1ock);//获取自旋锁,保护临界区
...//临界区
spin_un1ock(&lock);//解锁

自旋锁主要针对SMP或单CPU但内核可抢占的情况,对于单CPU和内核不支持抢占的系统,自旋锁退化为空操作。在单CPU和内核可抢占的系统中,自旋锁持有期间内核的抢占将被禁止。由于内核可抢占的单CPU系统的行为实际很类似于SMP系统,因此,在这样的单CPU系统中使用自旋锁仍十分必要。

尽管用了自旋锁可以保证临界区不受别的CPU和本CPU内的抢占进程打扰,但是得到锁的代码路径在执行临界区的时候还可能受到中断和底半部(BH)的影响。为了防止这种影响,就需要用到自旋锁的衍生。spin_lock()/spin unlock()是自旋锁机制的基础,它们和关中断local_irq_disable()/开中断local_irq_enable()、关底半部 local_bh_disable()/开底半部local_bh_enable()、关中断并保存状态字local_irq_save()/开中断并恢复状态local_irq_restore() 结合就形成了整套自旋锁机制,关系如下所示:

在这里插入图片描述

驱动工程师应谨慎使用自旋锁,而且在使用中还要特别注意如下几个问题。

  • 自旋锁实际上是忙等锁,当锁不可用时,CPU一直循环执行“测试并设置”该锁直到可用而取得该锁,CPU在等待自旋锁时不做任何有用的工作,仅仅是等待。因此,只有在占用锁的时间极短的情况下,使用自旋锁才是合理的。当临界区很大或有共享设备的时候,需要较长时间占用锁,使用自旋锁会降低系统的性能。
  • 自旋锁可能导致系统死锁。引发这个问题最常见的情况是递归使用一个自旋锁,即如果一个已经拥有某个自旋锁的CPU想第二次获得这个自旋锁,则该CPU将死锁。此外,如果进程获得自旋锁之后再阻塞,也有可能导致死锁的发生。copy from_user()、copy_to_user() 和 kmalloc() 等函数都有可能引起阻塞,因此在自旋锁的占用期间不能调用这些函数。

四、信号量

信号量的使用

信号量(semaphore)是用于保护临界区的一种常用方法,它的使用方式和自旋锁类似。与自旋锁相同,只有得到信号量的进程才能执行临界区代码。但是,与自旋锁不同的是,当获取不到信号量时,进程不会原地打转而是进入休眠等待状态。


五、自旋锁 vs 信号量

自旋锁和信号量都是解决互斥问题的基本手段,面对特定的情况,应该如何进行选择呢?选择的依据是临界区的性质和系统的特点。

从严格意义上说,信号量和自旋锁属于不同层次的互斥手段,前者的实现依赖于后者。在信号量本身的实现上,为了保证信号量结构存取的原子性,在多CPU中需要自旋锁来互斥。

信号量是进程级的,用于多个进程之间对资源的互斥,虽然也是在内核中,但是该内核执行路径是以进程的身份,代表进程来争夺资源的。如果竞争失败,会发生进程上下文切换,当前进程进入睡眠状态,CPU将运行其他进程。鉴于进程上下文切换的开销也很大,因此,只有当进程占用资源时间较长时,用信号量才是较好的选择。

当所要保护的临界区访问时间比较短时,用自旋锁是非常方便的,因为它节省上下文切换的时间。但是CPU得不到自旋锁会在那里空转直到其他执行单元解锁为止,所以要求锁不能在临界区里长时间停留,否则会降低系统的效率。

由此,可以总结出自旋锁和信号量选用的3项原则。

  • 当锁不能被获取时,使用信号量的开销是进程上下文切换时间Tsw,使用自旋锁的开销是等待获取自旋锁(由临界区执行时间决定)Tcs,若Tcs比较小,应使用自旋锁,若Tcs很大,应使用信号量。

  • 信号量所保护的临界区可包含可能引起阻塞的代码,而自旋锁则绝对要避免用来保护包含这样代码的临界区。因为阻塞意味着要进行进程的切换,如果进程被切换出去后,另一个进程企图获取本自旋锁,死锁就会发生。

  • 信号量存在于进程上下文,因此,如果被保护的共享资源需要在中断或软中断情况下使用,则在信号量和自旋锁之间只能选择自旋锁。当然,如果一定要使用信号量,则只能通过 down_trylock() 方式进行,不能获取就立即返回以避免阻塞。


六、互斥体

尽管信号量已经可以实现互斥的功能,而且包含DECLARE_MUTEX()、init_MUTEX() 等定义信号量的宏或函数,从名字上看就体现出了互斥体的概念,但是 mutex 在Linux内核中还是真实地存在的。

下面代码定义名为my_mutex的互斥体并初始化它。

在这里插入图片描述
下面的两个函数用于获取互斥体。
在这里插入图片描述


七、小总结

并发和竞态广泛存在,中断屏蔽、原子操作、自旋锁和信号量都是解决并发问题的机制。中断屏蔽很少单独被使用,原子操作只能针对整数进行,因此自旋锁和信号量应用最为广泛。

自旋锁会导致死循环,锁定期间不允许阻塞,因此要求锁定的临界区小。信号量允许临界区阻塞,可以适用于临界区大的情况。

读写自旋锁和读写信号量分别是放宽了条件的自旋锁和信号量,它们允许多个执行单元对共享资源的并发读。

参考文档:《Linux设备驱动开发详解》

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

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

相关文章

Mapper代理开发

Mapper代理开发一. Mapper代理开发概述二. 使用Mapper代理要求(重点中的重点)三. 实操的代码main目录下的java目录中1. UserMapper接口2. User类3. MybatisDemo类main目录下的resources目录中1. UserMapper.xml2. logback.xml3. mybatis.xml一. Mapper代理开发概述 之前我们写的…

【SpringBoot2】02-SpringBoot中如何修改依赖的版本

Spring Boot 中修改依赖版本 如果默认版本号不合适,我们可以修改版本号。 首先,打开当前项目的 pom.xml 文件,查看 spring-boot-dependencies-2.7.4.pom 中规定当前依赖的版本。 例如,我想修改 MySQL 驱动版本,我先看…

OPSS-PEG-Acrylate,OPSS-PEG-AC,巯基吡啶-聚乙二醇-丙烯酸酯试剂供应

英文:OPSS-PEG-Acrylate,OPSS-PEG-AC 中文:巯基吡啶-聚乙二醇-丙烯酸酯 2、 CAS number; N/A 3、The category;Acrylate/Acrylamide PEG Orthopyridyl disulfide (OPSS) PEG 4、Molecular weight:巯基吡…

队列(C语言)

文章目录前言概念基本操作循环队列少用一个元素空间栈队列前言 本篇进行队列的学习。使用C语言实现 概念 排队是体现了“先来先服务”的原则。 在多道程序运行的计算机系统中,可以同时有多个作业运行,他们的运算结果都需要通过通道输出,若通…

[架构之路-3]:软件架构师也是魔法师,架构师应具备的四大方面的技能

目录 前言: 一、业务能力(业务领域)-- 面向业务 1.1 业务场景 1.2 业务技能 二、沟通能力(管理领域) -- 面向“人” 三、技术能力(计算机领域) -- 面向计算机 3.1 硬件技能 3.2 软件技能…

一个有点意思的网站 - 语雀

在这个平台上面创建了一个文档:CWIKIUS 语雀 Confluence Confluence 的问题就是太臃肿,不兼容 MD 格式。 但是,Confluence 和 JIRA 重度集成,因此成为很多公司文档的标配。 语雀 试用了下这个文档工具,整体上来说…

我们如何一键将录音转换成文字?

最近有很多小伙伴向我求助说,他的职业是一名记者,因为每次采访都要进行对话录音,可是每次结束后都需要再去对录音进行整理,花费了大量的时间。因此他总是在加班,他想改变这一现状却不知道该怎么办。其实我们不必如此麻…

platform.pk8 和platform.x509.pem转jks

/** OpenSSL */ 下载地址:http://slproweb.com/products/Win32OpenSSL.html 环境配置: openssl 安装后查看是否安装成功,需要以管理员身份运行cmd查看 cmd输入openssl出现下面显示,表示配置成功,openssl可以使用 pla…

VUE v-bind 数据绑定

动态的绑定一个或多个 attribute,也可以是组件的 prop。缩写: : 或者 . (当使用 .prop 修饰符) 期望: any (带参数) | Object (不带参数) 参数: attrOrProp (可选的) 修饰符:.camel ——将短横线命名的 attribute 转变为驼峰式命名。 .prop ——强制绑定为 DOM property。…

kafka 安装

目录 Docker安装 1.安装Docker 2.搜索docker镜像 3.安装Zookeeper 4. 安装kafka 5.启动kafka ​​​​​​​ Linux安装 1.kafka下载 2.安装JDK 3.安装zookeeper 4.安装kafka 5.启动kafka zookeeper上查看kafka的节点 1.进入zookeeper容器 2.运行客户端 3.查看ka…

MongoDB --- 聚合查询

什么是聚合查询 聚合操作处理数据记录并返回计算结果。聚合操作组值来自多个文档,可以对分组数据执行各种操作以回单个结果。聚合操作包含三类:单一作用聚合、聚合管道、MapReduce(在5.x已经弃用)。 单一作用聚合 提供了对常见聚合过程的简单访问,操作都从单个集合聚合文…

网络笔记大全(超详细)

目录 OSI七层参考模型 应用层 表示层 会话层 传输层 网络层 数据链路层 物理层 封装和解封装 应用层 传输层 网络层 数据链路层 物理层 PDU --- 协议数据单元应用层 --- 报文 传输层 --- 段 网络层 --- 包 数据链路层 --- 帧 物理层 --- 比特流 Sof --- 帧首…

日本25年来首次干预以支撑日元汇率

日本周四自 1998 年以来首次干预外汇市场,以支撑暴跌的日元,此前日本央行决定维持超低利率,这一决定已对日元造成冲击。 KlipC 风险经理 Philip Nucci 周五表示:“他们(在外汇市场)采取了果断行动&#xff…

pytorch神经网络入门(三)

一、建立简单的卷积神经网络 import torch from torch import nnclass ConvNet(nn.Module):def __init__(self):super(ConvNet, self).__init__()self.conv1 nn.Sequential(nn.Conv2d(1, 16, 3, 1, 1),nn.ReLU(),nn.AvgPool2d(2, 2))self.conv2 nn.Sequential(nn.Conv2d(16,…

Vue学习第29天——路由的props配置项的详解与案例(对比组件props配置项)

目录一、组件的props配置项1、作用2、理解3、用法二、路由的props配置项1、作用2、理解3、用法① props值为对象② props值为布尔值③ props值为函数4、接收参数三、props配置项搭配params传参案例练习四、props配置项搭配query传参案例练习五、总结在学习路由的props配置项之前…

python机器人编程——差速机器人小车的控制,控制模型、轨迹跟踪,轨迹规划、自动泊车(中)未完待续...

目录一、前言二、轨迹的跟随控制策略(1)利用模型预测控制(MPC)的思想控制(2) 仿真验证一、前言 本篇我们依然试着用一些浅显的数学知识,来研究和实现一下常用机器人小车(如AGV&…

异常值检测!最佳统计方法实践(代码实现)!

💡 作者:韩信子ShowMeAI 📘 Python3◉技能提升系列:https://www.showmeai.tech/tutorials/56 📘 数据分析实战系列:https://www.showmeai.tech/tutorials/40 📘 本文地址:https://ww…

Mysql数据库高阶语句

目录 一,正则表达式 1,以“.”代替任意一个字符 2,匹配前面字符多次 3,匹配前面字符至少一次 4,匹配字符串 5,匹配包含或者关系的记录 6,匹配指定字符集中的任意一个 二,运算符 1、算数运算 2…

linux在线安装JDK1.8

​​​​​创建文件路径 [rootlocalhost ~]# cd /usr/local/ [rootlocalhost local]# mkdir java [rootlocalhost local]# cd java 在线下载连接地址 wget --no-check-certificate --no-cookies --header "Cookie: oraclelicenseaccept-securebackup-cookie" http:…

MyBatis——案例——查询-查询所有

查询-查询所有数据1、创建相应Mapper接口文件 以及Mapper配置信息文件 修改配置文件中 namespace :2、编写接口方法:Mapper 接口参数:无结果:List<Brand>3、编写SQL语句(接口文件中按Alt+回车快速编写)4、执行方法,测试(1)获取 SQLSessionFactory 对象//1、获取…