目录
前言
Linux是一个多任务操作系统,肯定会存在多个任务共同操作同一段内存或者设备的情况,
多个任务甚至中断都能访问的资源叫做共享资源。在驱动开发中要注意对共享资源的保护,也就是要处理对共享资源的并发访问。这些任务可能会相互覆盖这段内存中的数据,造成内存数据混乱。针对这个问题必须要做处理,严重的话可能会导致系统崩溃。
Linux产生并发的原因主要由一下几种:
① 多线程并发访问, Linux 是多任务(线程)的系统,所以多线程访问是最基本的原因。
② 抢占式并发访问,从 2.6 版本内核开始, Linux 内核支持抢占,也就是说调度程序可以
在任意时刻抢占正在运行的线程,从而运行其他的线程。
③ 中断程序并发访问,这个无需多说,学过 STM32 的同学应该知道,硬件中断的权利可
是很大的。
④ SMP(多核)核间并发访问,现在 ARM 架构的多核 SOC 很常见,多核 CPU 存在核间并
发访问
防止并发访问共享资源,换句话说就是要保护共享资源,防止进行并发访问。Linux提供了以下几种方式并发和竞争的处理方法。
一、原子操作
原子操作(atomic operation)指的是由多步操作组成的一个操作。如果该操作不能原子地执行,则要么执行完所有步骤,要么一步也不执行,不可能只执行所有步骤的一个子集。
○ 原子操作就是指不能再进一步分割的操作。
○ 原子操作一旦开始,就一直运行到结束。
○ 原子操作可以是一个步骤,也可以是多个操作步骤,但是其顺序不可以被打乱,也不可以被切割而只执行其中的一部分。
1、原子操作函数
Linux 内核定义了叫做 atomic_t 的结构体来完成整形数据的原子操作。
typedef struct {
int counter;
} atomic_
如果要使用原子操作 API 函数,首先要先定义一个 atomic_t 的变量,如下所示:
atomic_t a;
原子操作API函数:
![](https://img-blog.csdnimg.cn/9cd3420ddcdb48859d8bf83ac903cfef.png?x-oss-process=image/watermark,type_d3F5LXplbmhlaQ,shadow_50,text_Q1NETiBAYW5pZW9v,size_20,color_FFFFFF,t_70,g_se,x_16)
原子位操作 API 函数 :
二、自旋锁
○ 当一个线程要访问某个共享资源的时候首先要先获取相应的锁,锁只能被一个线程持有,只要此线程不释放持有的锁,那么其他的线程就不能获取此锁。
○ 中断里面可以使用自旋锁,但是在中断里面使用自旋锁的时候,在获取锁之前一定要先禁止本地中断(即本 CPU 中断,对于多核 SOC 来说会有多个 CPU 核),否则可能导致锁死现象的发生。
缺点:等待自旋锁的线程会一直处于自旋状态,浪费处理器时间,降低系统性能,所以自旋锁的持有时间不能太长。
○ 自旋锁适用于短时期的轻量级加锁,如果临界区比较大,运行时间比较长的话要选择其他的并发处理方式,比如信号量和互斥体。
自旋锁保护的临界区内不能调用任何可能导致线程休眠的 API 函数,否则的话可能导致死锁。
○ 不能递归申请自旋锁,因为一旦通过递归的方式申请一个你正在持有的锁,那么你就必须“自旋”,等待锁被释放,然而你正处于“自旋”状态,根本没法释放锁。
自旋锁API函数:
上图自旋锁API函数适用于SMP或支持抢占的单CPU下线程之间的并发访问,也就是用于线程与线程之间,被自旋锁保护的临界区一定不能调用任何能够引起睡眠和阻塞的API 函数,否则的话会可能会导致死锁现象的发生。
自旋锁中断API函数:
![](https://img-blog.csdnimg.cn/afac22be965447639eb57c2db4357b7c.png?x-oss-process=image/watermark,type_d3F5LXplbmhlaQ,shadow_50,text_Q1NETiBAYW5pZW9v,size_20,color_FFFFFF,t_70,g_se,x_16)
一般在线程中使用 spin_lock_irqsave/spin_unlock_irqrestore,在中断中spin_lock/spin_unlock,
下半部竞争处理函数:
三、信号量
特点:
- 信号量可以使等待资源线程进入休眠状态,因此适用于那些占用资源比较久的场合。
- 信号量不能用于中断中,因为信号量会引起休眠,中断不能休眠。
- 共享资源的持有时间比较短时不适合使用信号量,因为频繁的休眠、切换线程引起的开销要远大于信号量带来的优势。
Linux 内核使用 semaphore 结构体表示信号量,结构体内容如下所示:
struct semaphore {
raw_spinlock_t lock;
unsigned int count;
struct list_head wait_list;
};
要想使用信号量就得先定义,然后初始化信号量。
信号量的 API 函数:
信号量的使用如下所示:
struct semaphore sem; /* 定义信号量 */
sema_init(&sem, 1); /* 初始化信号量 */
down(&sem); /* 申请信号量 */
/* 临界区 */
up(&sem); /* 释放信号量 *//
四、互斥体
- mutex 可以导致休眠,因此不能在中断中使用 mutex,中断中只能使用自旋锁。
- 和信号量一样,mutex 保护的临界区可以调用引起阻塞的 API 函数。
- 因为一次只有一个线程可以持有 mutex,因此,必须由 mutex 的持有者释放 mutex。并且 mutex 不能递归上锁和解锁。
Linux 内核使用 mutex 结构体表示互斥体,定义如下:struct mutex { /* 1: unlocked, 0: locked, negative: locked, possible waiters */ atomic_t count; spinlock_t wait_lock; };
互斥体API函数:
-