Linux 竞争与并发(学习总结)

news2024/11/15 11:53:02

    在Linux驱动开发中,“并发”和“竞争”是两个重要的概念,它们涉及到多任务环境下资源的管理和使用。

并发 (Concurrency)

        并发指的是在同一时间段内,多个任务看似同时运行的现象。实际上,在单核处理器上,这通常是通过快速的任务切换来实现的,而在多核或多处理器环境中,则可以真正实现并行执行。并发可以提高系统的响应能力和资源利用率。在Linux系统中,并发不仅限于用户空间的应用程序,同样也存在于内核空间,特别是驱动程序中。驱动程序可能需要处理来自不同进程的请求,甚至是中断处理程序的并发执行。

竞争 (Contention)

        竞争指的是多个任务或进程试图同时访问或修改同一个共享资源时所发生的情况。如果没有适当的同步机制来协调这些访问,则可能导致竞态条件(race condition),即最终结果依赖于相对执行顺序的不确定性。例如,如果两个线程同时读取和修改同一个变量,那么根据它们执行的相对顺序,可能会导致数据不一致或其他未定义的行为。

处理方法        

处理竞争与并发的方法多种多样,这些方法旨在确保多个执行单元(如进程、线程或中断处理程序)在访问共享资源时的有序性和一致性。以下是一些主要的方法及其使用例子:

1. 原子操作(atomic operation)

定义:原子操作是指在执行过程中不会被其他线程或中断打断的操作。

使用例子:在Linux内核中,原子操作常用于对全局变量的增减操作,如计数器。假设有一个全局整型变量count,多个线程可能同时对其进行增减操作。通过使用原子操作API,如atomic_inc(&count)atomic_dec(&count),可以确保每次增减操作都是原子的,从而避免竞争条件。

以下是一些常见的原子整形操作API函数

  1. 定义原子变量初始化ATOMIC_INIT
    • 作用:定义并初始化一个原子变量,为其赋予一个特定的初始值。
    • 原型:ATOMIC_INIT(int i);
    • 参数:通常是一个整型值。
  2. 原子设置(atomic_set)
    • 作用:设置原子变量的值。
    • 原型:void atomic_set(atomic_t *v, int i);
    • 参数:v是指向原子变量的指针,i是要设置的值。
  3. 原子读取(atomic_read)
    • 作用:读取原子变量的值。
    • 原型:int atomic_read(const atomic_t *v);
    • 参数:v是指向原子变量的指针。
    • 返回值:返回原子变量的当前值。
  4. 原子增加(atomic_add)
    • 作用:给原子变量增加一个值。
    • 原型:void atomic_add(int i, atomic_t *v);
    • 参数:i是要增加的值,v是指向原子变量的指针。
  5. 原子减少(atomic_sub)
    • 作用:从原子变量中减少一个值。
    • 原型:void atomic_sub(int i, atomic_t *v);
    • 参数:i是要减少的值,v是指向原子变量的指针。
  6. 原子增加并返回(atomic_add_return / atomic_inc_return)
    • 作用:给原子变量增加一个值,并返回增加后的值。
    • 原型:int atomic_add_return(int i, atomic_t *v);
    • 或:int atomic_inc_return(atomic_t *v);(增加1并返回)
    • 参数:i是要增加的值(对于atomic_inc_return,此参数为隐式的1),v是指向原子变量的指针。
    • 返回值:返回增加后的值。
  7. 原子减少并返回(atomic_sub_return / atomic_dec_return)
    • 作用:从原子变量中减少一个值,并返回减少后的值。
    • 原型:int atomic_sub_return(int i, atomic_t *v);
    • 或:int atomic_dec_return(atomic_t *v);(减少1并返回)
    • 参数:i是要减少的值(对于atomic_dec_return,此参数为隐式的1),v是指向原子变量的指针。
    • 返回值:返回减少后的值。
  8. 原子增加如果非负(atomic_add_unless)
    • 作用:如果原子变量的当前值是非负的,则给它增加一个值。
    • 原型:int atomic_add_unless(atomic_t *v, int a, int u);
    • 参数:v是指向原子变量的指针,a是要增加的值,u是除非当前值等于此值时才进行操作的值(通常用于避免负值)。
    • 返回值:如果操作成功,则返回1;否则返回0。

以下是一些常见的原子操作API函数:

  1. 原子位设置(atomic_set_bit)
    • 作用:设置指定位置上的位。
    • 原型:void atomic_set_bit(int nr, void *addr);(在某些内核版本中,可能是void set_bit(int nr, volatile unsigned long *addr);
    • 参数:nr是要设置的位的位置(从0开始计数),addr是指向包含该位的内存地址的指针。
  2. 原子位清除(atomic_clear_bit)
    • 作用:清除指定位置上的位。
    • 原型:void atomic_clear_bit(int nr, void *addr);(在某些内核版本中,可能是void clear_bit(int nr, volatile unsigned long *addr);
    • 参数:与atomic_set_bit相同。
  3. 原子位翻转(atomic_flip_bit)
    • 作用:翻转(即取反)指定位置上的位。
    • 原型:int atomic_flip_bit(int nr, void *addr);(返回翻转前的位值;在某些内核版本中,可能没有直接的翻转函数,但可以通过其他方式实现)
    • 参数:与atomic_set_bit相同。
    • 返回值:返回翻转操作前该位的值。
  4. 原子位测试并设置(atomic_test_and_set_bit)
    • 作用:测试指定位置上的位,如果位为0则设置为1,并返回测试结果。
    • 原型:int atomic_test_and_set_bit(int nr, void *addr);(在某些内核版本中,可能是int test_and_set_bit(int nr, volatile unsigned long *addr);
    • 参数:与atomic_set_bit相同。
    • 返回值:如果位原来是0,则返回0,并且位被设置为1;如果位原来是1,则返回非0值。
  5. 原子位测试并清除(atomic_test_and_clear_bit)
    • 作用:测试指定位置上的位,如果位为1则清除为0,并返回测试结果。
    • 原型:int atomic_test_and_clear_bit(int nr, void *addr);(在某些内核版本中,可能是int test_and_clear_bit(int nr, volatile unsigned long *addr);
    • 参数:与atomic_set_bit相同。
    • 返回值:如果位原来是1,则返回非0值,并且位被清除为0;如果位原来是0,则返回0。
  6. 原子位测试(atomic_test_bit)
    • 作用:测试指定位置上的位是否为1。
    • 原型:int atomic_test_bit(int nr, void *addr);(在某些内核版本中,可能是通过test_bit宏或其他方式实现)
    • 参数:与atomic_set_bit相同。
    • 返回值:如果位是1,则返回非0值;如果位是0,则返回0

2. 自旋锁(Spinlock)

定义:自旋锁是一种轻量级的锁,用于保护临界区资源。当一个线程获得自旋锁后,其他线程将处于忙等待状态,直到锁被释放。

使用例子:在设备驱动中,当多个线程需要访问同一个硬件设备时,可以使用自旋锁来保护对硬件寄存器的访问。例如,在访问网络设备的发送队列时,可以先加自旋锁,访问完成后释放自旋锁。这样可以确保在任一时刻只有一个线程能够访问发送队列,从而避免竞争条件。

常见的自旋锁API函数的详细介绍:

1. 初始化自旋锁

spin_lock_init(spinlock_t *lock)

  • 作用:动态初始化一个自旋锁,将其设置为未锁状态。
  • 参数:指向spinlock_t类型变量的指针,该变量表示要初始化的自旋锁。
  • 注意事项:通常在动态分配的自旋锁或需要在运行时初始化的场景中使用。

DEFINE_SPINLOCK(name)

  • 作用:静态初始化一个自旋锁,并为其指定一个名字。
  • 参数:自旋锁的名字,该名字将用作变量名。
  • 注意事项:这通常用于全局或静态自旋锁的初始化,它在编译时分配并初始化自旋锁。

2. 加锁操作

spin_lock(spinlock_t *lock)

  • 作用:尝试获取自旋锁。如果锁当前未被持有,则立即获取锁;如果锁已被持有,则调用线程将忙等待直到锁被释放。
  • 参数:指向要获取的自旋锁的指针。
  • 注意事项:在持有锁期间,应避免调用可能导致线程休眠的函数,以免引发死锁。

spin_trylock(spinlock_t *lock)

  • 作用:尝试获取自旋锁,但不会忙等待。
  • 参数:指向要尝试获取的自旋锁的指针。
  • 返回值:如果成功获取锁,则返回非零值(真);如果锁已被持有,则返回零值(假)。
  • 注意事项:这个函数适用于那些不希望无限等待锁的场景。

3. 解锁操作

spin_unlock(spinlock_t *lock)

  • 作用:释放自旋锁。
  • 参数:指向要释放的自旋锁的指针。
  • 注意事项:必须与spin_lockspin_trylock配对使用。

4. 其他辅助函数

spin_is_locked(spinlock_t *lock)

  • 作用:检查自旋锁是否已被持有。
  • 参数:指向要检查的自旋锁的指针。
  • 返回值:如果锁被持有,则返回非零值(真);否则返回零值(假)。
  • 注意事项:这个函数通常用于调试或状态检查。

使用自旋锁的注意事项

  1. 锁持有时间:自旋锁的持有时间应尽可能短,以避免CPU资源的浪费和性能下降。
  2. 避免死锁:在自旋锁保护的临界区内,不应调用任何可能导致线程休眠的API函数,如mallocprintk(在某些情况下)、copy_from_user等。
  3. 中断和抢占
    • 在中断服务例程中使用自旋锁时,应注意中断的嵌套和优先级,以避免中断上下文中的死锁。
    • 在支持抢占的系统中,自旋锁会自动禁止内核抢占,但在单CPU系统中,应确保在持有锁期间不会触发抢占。
  4. 递归申请:应避免递归申请同一个自旋锁,因为这会导致死锁。
  5. 锁粒度:应合理设置锁的粒度,以平衡并发性和性能。过粗的锁粒度会导致不必要的等待和性能下降,而过细的锁粒度则可能增加锁管理的开销和复杂性。

3. 信号量(Semaphore)

定义:信号量是一种更通用的同步机制,它允许多个线程同时访问共享资源,但可以限制同时访问资源的数量。

使用例子:在数据库连接池中,可以使用信号量来限制同时访问数据库连接的线程数量。假设数据库连接池最大允许10个连接,则可以初始化一个信号量,其初始值为10。每当一个线程需要获取数据库连接时,它会尝试从信号量获取一个许可(通过sem_wait())。如果信号量的值大于0,则线程可以成功获取连接,并将信号量的值减1;如果信号量的值为0,则线程将被阻塞,直到有其他线程释放连接(通过sem_post())并增加信号量的值。

信号量API函数:

信号量的使用注意事项

  1. 适用场景
    • 信号量适用于那些占用资源比较久的场合,因为它可以使等待资源的线程进入休眠状态,从而避免忙等待带来的CPU资源浪费。
  2. 中断环境限制
    • 信号量不能用于中断中,因为中断处理例程不能休眠,而信号量的等待操作可能会导致休眠。
  3. 性能考虑
    • 如果共享资源的持有时间比较短,则不适合使用信号量,因为频繁的休眠和线程切换带来的开销会远大于信号量带来的同步优势。在这种情况下,应考虑使用自旋锁等轻量级的同步机制。
  4. 资源管理
    • 在使用信号量进行同步时,应确保在不再需要信号量时销毁它,以避免资源泄露。对于有名信号量,还需要注意信号量集的创建和删除操作,以确保资源得到正确管理。
  5. 原子性操作
    • 在多处理器系统中,信号量的操作通常是原子的,但访问共享资源的其他操作可能不是原子的。因此,在使用信号量进行同步时,还需要考虑其他可能的同步问题,并确保对共享资源的访问是安全的。

4. 互斥锁(Mutex)

定义:互斥锁是一种用于保护临界区资源的同步机制,确保在任何时候只有一个线程能够访问被保护的资源。

使用例子:在多线程程序中,当多个线程需要访问同一个全局变量时,可以使用互斥锁来防止竞争条件。例如,在一个银行转账系统中,当两个线程分别尝试从同一个账户中转账时,可以使用互斥锁来保护对该账户余额的访问。在访问账户余额之前,线程会先尝试获取互斥锁;如果获取成功,则进入临界区进行转账操作;操作完成后释放互斥锁。这样可以确保在任一时刻只有一个线程能够修改账户余额。

API函数:

互斥锁(mutex)的注意事项:

  1. 中断环境限制
    • 互斥锁可以导致休眠,因此它不能在中断处理例程中使用。中断处理例程需要快速执行完毕,并且不能进入休眠状态。在中断中应使用自旋锁等不会引起休眠的同步机制。
  2. 临界区调用限制
    • 和信号量类似,互斥锁保护的临界区内可以调用可能引起阻塞的API函数。这是因为互斥锁在等待资源可用时会将线程置于休眠状态,直到资源被释放并唤醒该线程。
  3. 互斥锁的持有与释放
    • 一次只有一个线程可以持有互斥锁,这保证了临界区内的资源访问是独占的。因此,必须由持有互斥锁的线程来释放它。如果其他线程尝试释放未持有的互斥锁,将导致未定义行为。
    • 互斥锁不能递归上锁和解锁。递归上锁意味着同一个线程尝试多次获取同一个互斥锁,这将导致死锁。同样地,如果一个线程没有持有互斥锁却尝试解锁它,也会导致错误。
  4. 避免死锁
    • 在设计多线程程序时,应确保互斥锁的使用不会导致死锁。死锁是一种情况,其中多个线程相互等待对方释放资源,从而无法继续执行。
  5. 性能考虑
    • 虽然互斥锁提供了强大的同步能力,但它们也可能引入一定的性能开销。特别是在高争用情况下,互斥锁可能导致频繁的上下文切换和线程休眠。因此,在性能敏感的场景中,应谨慎使用互斥锁,并考虑使用其他轻量级的同步机制(如自旋锁、读写锁等)。
  6. 资源泄露避免
    • 和信号量一样,在使用互斥锁时也应确保在不再需要时正确释放它,以避免资源泄露。资源泄露可能导致系统资源耗尽,从而影响系统的稳定性和性能。

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

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

相关文章

Android之LiveTemplate注释模板

目录 效果图步骤 效果图 步骤 1.首先通过File->Setting->Editor->LiveTemplate 我是放在Android下的,然后点击右侧(新版本的话不在右侧)加号, 点击(加号)之后,如图 /*** author:T…

RK3588 系列之3—rknn使用过程中遇到的bug

RK3588 系列之3—rknn使用过程中遇到的bug 1.librockchip_mpp.so: file format not recognized; treating as linker scrip2.Could not find a package configuration file provided by "OpenCV" with any of the following names参考文献 1.librockchip_…

java后端保存的本地图片通过ip+端口直接访问

直接上代码吧 package com.ydx.emms.datapro.controller;import org.springframework.context.annotation.Configuration; import org.springframework.web.servlet.config.annotation.ResourceHandlerRegistry; import org.springframework.web.servlet.config.annotation.…

Docker Image 命令

文章目录 目录 文章目录 1 . Docker镜像是什么? 2 . 镜像命令详解 docker images docker tag docker pull docker rmi docker save 总结 1 . Docker镜像是什么? Docker image 本质上是一个 read-only 只读文件, 这个文件包含了文件系统、 源码、库文件…

性能测试经典案例解析——远程培训系统

各位好,我是 道普云 一站式云测试SaaS平台。一个在软件测试道路上不断折腾十余年的萌新。 欢迎关注我的专栏和我的主页 道普云 文章内容具有一定门槛,建议先赞再收藏慢慢学习,有不懂的问题欢迎私聊我。 希望这篇文章对想提高软件测试水平…

负载均衡调度器--LVS

文章目录 集群和分布式集群分布式 LVS介绍LVS特点LVS工作原理LVS集群架构 LVS集群中的术语CIPVIPRSDIPRIP LVS集群的工作模式NAT模式DR模式DR模式的特点: TUN模式 LVS调度算法LVS相关软件ipvsadm 命令管理集群服务:增、改、删管理集群上的RS:增、改、删 创建集群 LV…

神经网络搭建的那点事

1.全连接网络 python: nn.Linear(in, out) matlab: layer fullyConnectedLayer(outputSize) layer fullyConnectedLayer(outputSize,Name,Value) 2.add和concat的区别 concat作用 concat是通道数的增加,也就是说描述图像本身的特征数(通道…

ITK-高斯滤波

作者:翟天保Steven 版权声明:著作权归作者所有,商业转载请联系作者获得授权,非商业转载请注明出处 高斯滤波原理 高斯滤波(Gaussian Blur)是数字图像处理中常见的一种平滑滤波器,旨在通过模糊处…

Python 从入门到实战8(字典)

我们的目标是:通过这一套资料学习下来,通过熟练掌握python基础,然后结合经典实例、实践相结合,使我们完全掌握python,并做到独立完成项目开发的能力。 上篇文章我们通过举例学习了python 中元组的定义及相关操作。今天…

css弹性盒子——flex布局

目录 ​编辑 一、flex容器的样式属性(父元素属性) display:flex 弹性盒子,实现水平排列,在父盒子设置,适用于单行/单列 justify-content 二、flex元素的样式属性(子元素属性) 1.flex-grow 2.flex-shrink 3.flex-basis 4.flex组合属性 flex:flex-…

【WPS Excel】复制表格时,提示“图片太大,超过部份将被截去“ 问题

WPS表格 2019版本 升级到 WPS最新版 WPS-支持多人在线协作编辑Word、Excel和PPT文档_WPS官方网站 使用最新版就能够解决这个问题,如果仍旧无法解决可以勾选如下配置 重启Excel解决。 请勾选:文件 - 选项 - 编辑 - 不提示且不压缩文件中的图像

无需更换摄像头,无需施工改造,降低智能化升级成本的智慧工业开源了。

智慧工业视觉监控平台是一款功能强大且简单易用的实时算法视频监控系统。它的愿景是最底层打通各大芯片厂商相互间的壁垒,省去繁琐重复的适配流程,实现芯片、算法、应用的全流程组合,从而大大减少企业级应用约95%的开发成本。用户只需在界面上…

Linux系统下载并配置vscode(无废话)写C++

首先下载visual studio code 1.可以在应用商店下载 2.进入vscode官网Documentation for Visual Studio Code点击download>>点击下载.rpm 下载好之后打开vscode,先下载中文拓展(可省略) 然后下载c语言拓展 下载完之后打开设置&#xf…

Vue 使用接口返回的背景图片和拼图图片进行滑动拼图验证

一、背景 前两天发了一篇 vue-monoplasty-slide-verify 滑动验证码插件使用及踩坑_vue-monoplasty-slide-verify 引用后不显示-CSDN博客 这两天项目又需要通过接口校验,接口返回了背景图片和拼图图片,于是在网上找了一篇帖子,vue 图片滑动…

了解一下HTTP 与 HTTPS 的区别

介绍: HTTP是超文本传输协议。规定了客户端(通常是浏览器)和服务器之间如何传输超文本,也就是包含链接的文本。通常使用TCP【1】/IP协议来传输数据,默认端口为80。 HTTPS是超文本传输安全协议,具有CA证书。…

羲和能源大数据平台——Python数据绘图方法

1. 写在前面 目前论文对绘图的美观度要求越来越高,在气象领域呈现维度高,时空关联的特性,为了充分展示数据在各个维度的特性,选用合适的绘图方法至关重要,下图给出了如今在科研领域中个常用的各类图像:线型…

远程教育与学习:探索远程控制技术在教育领域的新机遇

什么适合会用到远程控制工具?如果你是运维、是设计或者外勤需要办公的一些资料文件,有远程控制工具工具的话就能轻松解决这些情况。为了保证电脑的安全我建议从官方网站进行下载,比如从向日葵远程控制官网下载就可以得到向日葵的官方正版。这…

Docker Container 常用命令

文章目录 目录 文章目录 1 . 什么是容器? 2 . 容器命令清单 docker create docker run docker ps docker logs docker exec docker kill docker container inspect docker cp docker rm docker export 总结 1 . 什么是容器? 通俗地讲&a…

C++设计模式——Command命令模式

一,命令模式的定义 命令模式是一种行为型设计模式。在实际开发场景中,命令模式将一个请求的处理或者一个具体操作封装为一个对象,从而可以让开发者根据不同的请求参数来生成不同的执行函数。 命令模式的本质是对具体命令的拆解和封装&#…

【Git远程仓库】将本地仓库推送到github(踩坑记录)

上一篇博客已经介绍了git本地仓库的基本操作,接下来记录一下如何将本地仓库上传到远程仓库中 远程仓库:托管在因特网的版本库,保存版本库的历史记录,多人协作 1. 创建远程版本库,得到远程仓库git地址 2. 本地仓库添加…