嵌入式Linux驱动--并发与竞争

news/2025/1/23 9:39:13/

一.并发与竞争 

  • 定义:并发是指多个程序同时访问一个共享资源。当这种情况发生时,由于同时访问而产生的就是竞争问题。
  • 例子:例如A和B要打电话,但公共电话只有一部。因此,谁先打电话就形成了竞争。然而,电话是所有人都可以使用的,所以它被视为共享资源。
  • 在Linux中的体现:Linux是一个多任务操作系统,其中并发和竞争非常常见。因此在编写Linux驱动程序时需要特别考虑这些问题,否则在访问共享资源时容易出错,而且这些错误往往难以排查和定位。

1.并发

       CPU在工作时间内会依次开始执行任务一和任务二,但由于CPU的处理速度很快,它可以交替地执行这两个任务,使得它们看起来像是同时进行的。这种交替执行的方式就是并发。在实际的操作中,CPU可能会因为各种原因而暂停当前任务的执行,转而去执行其他任务,这被称为“上下文切换”。一旦条件允许,CPU会恢复到之前被暂停的任务上继续执行,直到所有任务都完成为止。

 

2.并行

        并行处理通过将两个任务分配到双核CPU的两个独立内核上,实现了任务的同步执行。每个内核可以同时运行不同的任务,从而提高了整体的处理效率和速度。这种技术允许系统更有效地利用资源,减少了等待时间,增强了用户体验。

3.并发加并行

        并发是指在一段时间内通过上下文切换使得多个任务快速交替执行,从而给人一种同时进行的错觉;而并行则是真正的同时执行多个任务。“双核CPU”可以同时在两个CPU上分别处理“任务一”和“任务二”,这就是并行。但同时,这两个任务又与其他的任务一起被安排和处理,体现了并发的特性。

并发带来的问题?

当有两个相同的驱动程序 A 和 B 并发执行且都需要修改变量 C时可能出现的情形。

  • 情况1:
    •  理想情况下,程序 A 先运行并在程序 B 运行前完成。此时,程序 A 和 B 完美运行,没有错误。
  • 情况2:
    • 程序 A 运行了一半,然后让程序 B 执行。B 执行完毕后返回执行 A。但是,在这种情况下,程序 B 的执行相当于什么也没做,因为最终变量 c 的值仍然是程序 A 设置的值。
  • 总结:
    • 虽然情况1是理想的,但我们无法预测程序 A 和 B 将会如何实际运行。如果不保护共享资源,可能会导致程序行为异常甚至系统崩溃。

 

Linux操作系统中导致并发访问的几种情况:

  1. 中断程序的并发访问:中断可以随时产生,一旦出现中断,当前正在执行的程序会被暂时挂起去处理中断任务。如果在处理中断的过程中修改了共享资源,就会引发并发问题。
  2. 抢占式并发访问:从Linux内核2.6版本开始,Linux支持抢占机制。这意味着任何时刻运行的进程都有可能被其他高优先级的进程抢占,从而可能导致对共享资源的并发访问。
  3. 多处理器(SMP)并发访问:在现代多核处理器架构下,不同的CPU核心可能会同时对同一个共享资源进行访问,这也会导致并发问题。

        这些情况说明了在多任务、多处理器环境下,如何有效地管理和同步对共享资源的访问是保证系统稳定性和正确性的关键。

二.原子操作 

         原子操作中的“原子”指的是化学反应中最小的微粒。在Linux上用原子形容一个操作或者一个函数是最小执行单位,是不可以被打断的。所以原子操作指的是该操作在执行完之前不会被任何事物打断。原子操作的目的是为了确保数据的完整性,防止出现竞态条件(Race Condition),即多个进程或线程同时对同一数据进行读写操作时可能导致的错误结果。原子操作一般用于整形变量或者位的保护。比我,我们定义一个变量a,如果程序A正在给变量a赋值,此时程序B也要来操作变量a,这时候就发生了并发与竞争。程序A的操作就有可能会被程序B打断。如果我们使用原子操作对变量a进行保护,就可以避免这种问题

        它定义了两个结构体atomic_tatomic64_t来描述原子变量。其中,atomic_t用于32位系统中,而atomic64_t则用于64位系统中。以下是具体的代码实现:

typedef struct {int counter;
} atomic_t;
#if defined(CONFIG_64BIT)
typedef struct {long counter;
} atomic64_t;
#endif


        这段代码首先定义了一个简单的整数计数器counter的结构体atomic_t,适用于32位系统。然后通过宏CONFIG_64BIT判断是否为64位系统,如果是的话,就定义另一个结构体atomic64_t,其计数器类型为long,以适应64位系统的需求。

atomic64_tV=ATOMIC64 INIT(0);//定义并初始化原子变量v=0
atomic64_set(&v,1);//设置v=1
atomic64_read(&v); ///读取v的值,此时v的值是1
atomic64_inc(&v);   //v的值加1,此时v的值是2

 三.自旋锁 

         自旋锁是一种用于保护共享资源的锁机制,特别是在多线程或并发编程场景中。它的基本原理是通过“原地等待”的方式来解决资源冲突。当一个线程获取到自旋锁后,其他试图获取同一把锁的线程不会进入休眠状态,而是会在一个循环中不断地检查锁的状态,直到能够成功获得锁为止。这种方式避免了上下文切换的开销,但会占用CPU资源,因为它不让出CPU的使用权。自旋锁既有针对中断的一套API,并且在中断中也可以使用自旋锁。


Linux内核中spinlock_t结构的定义:

typedef struct spinlock {union {struct raw_spinlock rlock;#ifdef CONFIG_DEBUG_LOCK_ALLOCstruct {u8 __padding[LOCK_PADSIZE];struct lockdep_map dep_map;};#endif /* CONFIG_DEBUG_LOCK_ALLOC */};
} spinlock_t;

 

    自旋锁注意事项:

  • 原地等待:由于自旋锁会“原地等待”,这意味着当线程尝试获取锁但未成功时,它会在一个循环中不断检查锁的状态直到获得锁。这种机制会持续占用CPU资源,因此持锁时间不能太长,临界区的代码量应尽量少。
  • 避免休眠或阻塞操作:在持有自旋锁的临界区内,不应调用可能导致线程进入休眠状态的操作(如某些I/O操作)。这是因为一旦线程休眠,自旋锁将无法释放,从而可能导致死锁或其他同步问题。
  • 适用场景:自旋锁通常适用于多核系统,特别是在那些需要快速响应且等待锁的时间很短的场景中。它们不适用于单核系统或在等待锁的过程中可能发生长时间延迟的情况。

总结来说,自旋锁适合于短时间的、轻量级的同步需求,但在使用时必须小心以避免过度消耗CPU资源和潜在的死锁风险。

  四.信号量 

        信号量的引入是为了更有效地管理资源的并发与竞争。在操作系统中,自旋锁通过“原地等待”的方式来处理资源竞争,但这种方式可能会浪费CPU资源,尤其是在需要长时间等待的情况下。
因此,信号量应运而生。它允许在一个进程等待资源时将其暂时挂起,从而避免CPU空转。这就像一个电话亭只有一个公共电话,当一个人在使用电话时,其他人需要等待。如果是自旋锁机制,其他人都必须一直轮询检测电话是否可用,这样会造成不便和资源浪费。而信号量则可以让第一个使用者占用电话,同时通知其他等待者稍等片刻,待其使用完毕后再依次让后续的人使用。
这种机制提高了系统的效率,因为它减少了不必要的资源消耗,并在多个任务间提供了更好的协作方式。这也是为什么信号量有时被称为“睡眠锁”,因为线程在等待时会进入休眠状态,而不是持续地消耗CPU资源。

信号量的工作方式:

  • 信号量本质:信号量本质上是一个全局变量,它的值可以根据实际情况自行设置(取值范围大于等于0)。
  • 访问控制:当有线程来访问资源时,信号量执行“减一”操作;访问完成以后,再执行“加一”操作。

信号量注意事项:

  1. 信号量的值不能小于0;
  2. 访问共享资源时,信号量执行“减一”操作,访问完成后执行“加一”操作;
  3. 当信号量的值为0时,想访问共享资源的线程必须等待,直到信号量大于0时,等待的线程才可以访问;
  4. 因为信号量会引起休眠,所以中断里面不能用信号量;
  5. 共享资源持有时间比较长,一般用信号量而不用自旋锁;
  6. 在同时使用信号量和自旋锁的时候,要先获取信号量,在使用自旋锁。因为信号量会导致睡眠。
  7. 这些注意事项旨在确保正确、安全地使用信号量进行多线程编程中的同步和互斥控制

五.互斥锁

         同一资源在同一时间内只能有一个访问者在访问,其他的访问者访问结束以后才可以访问这个资源,这就是互斥。互斥锁和信号量为1的情况很类似,但是互斥锁更加简洁高效。虽然互斥锁有优点,但在使用过程中需要注意的事项也更多(互斥锁也会引起休眠)。
 

互斥锁注意事项:

  1. 互斥锁会导致休眠,所以中断里面不能用互斥锁。
  2. 同一时刻只能有一个线程持有互斥锁,并且只有持有者可以解锁。
  3. 不允许递归上锁和解锁。

 


http://www.ppmy.cn/news/1565454.html

相关文章

Unity中在UI上画线

在UI中画一条曲线 我封装了一个组件,可以实现基本的画线需求. 效果 按住鼠标左键随手一画. 用起来也很简单,将组件挂到空物体上就行了,红色的背景是Panel. 你可以将该组件理解为一个Image,只不过形状更灵活一些罢了,所以它要放在下面的层级(不然可能会被挡住). 代码 可以…

ios文件管理,沙盒机制以及如何操作“文件”APP,把文件共享到文件app

首先,系统是一个整体,那每个app是相互独立的,系统为每个app分配了一定的存储空间,也就是我们说的沙盒,每个app有自己独立的沙盒,文件存储在沙盒中,正常情况下app相互之间数据是不可以共享以及访…

大华相机DH-IPC-HFW3237M支持的ONVIF协议

使用libONVIF C库。 先发现相机。 配置 lib目录 包含 编译提示缺的文件&#xff0c;到libonvif里面拷贝过来。 改UDP端口 代码 使用msvc 2022的向导生成空项目&#xff0c;从项目的main示例拷贝过来。 CameraOnvif.h #pragma once#include <QObject> #include &l…

Node.js 能做什么

一、服务器端开发 1. 构建 Web 服务器 使用内置的 http 模块或流行的框架&#xff08;如 Express、Koa 等&#xff09;创建 Web 服务器&#xff0c;处理 HTTP 请求和响应。可以处理各种类型的请求&#xff0c;如 GET、POST、PUT、DELETE 等&#xff0c;并返回相应的 HTML、JS…

《2024年度网络安全漏洞威胁态势研究报告》

2024年&#xff0c;全球网络安全领域继续面对日益严峻的挑战。在数字化转型的大背景下&#xff0c;漏洞利用成为网络攻击的重中之重。根据统计&#xff0c;全球新增漏洞数量再创新高&#xff0c;漏洞的复杂性加剧&#xff0c;修复周期也在不断缩短。然而&#xff0c;攻击者的手…

C++17 新特性解析:Lambda 捕获 this

C17 引入了许多改进和新特性&#xff0c;其中之一是对 lambda 表达式的增强。在这篇文章中&#xff0c;我们将深入探讨 lambda 表达式中的一个特别有用的新特性&#xff1a;通过 *this 捕获当前对象的副本。这个特性不仅提高了代码的安全性&#xff0c;还极大地简化了某些场景下…

R6学习打卡

&#x1f368; 本文为&#x1f517;365天深度学习训练营 中的学习记录博客&#x1f356; 原作者&#xff1a;K同学啊 LSTM-糖尿病预测 数据导入初始化模型定义损失训练模型模型评估个人总结 import torch.nn as nn import torch.nn.functional as F import torchvision,torchim…

OpenEuler学习笔记(九):安装 OpenEuler后配置和优化

安装OpenEuler后&#xff0c;可以从系统基础设置、网络配置、性能优化等方面进行配置和优化&#xff0c;以下是具体内容&#xff1a; 系统基础设置 更新系统&#xff1a;以root用户登录系统后&#xff0c;在终端中执行sudo yum update命令&#xff0c;对系统进行更新&#xf…