1.学习原因        

        Linux 是一个多任务操作系统,肯定会存在多个任务共同操作同一段内存或者设备的情况,
多个任务甚至中断都能访问的资源叫做共享资源,就和共享单车一样。在驱动开发中要注意
对共享资源的保护,也就是要处理对共享资源的并发访问。本章我们就来学习一下如何在 Linux 驱动中处理并发。

2.产生并发原因

        现在的 Linux 系统并发产生的原因很复杂,总结一下有下面几个主
要原因:
①、多线程并发访问,Linux 是多任务(线程)的系统,所以多线程访问是最基本的原因。
②、抢占式并发访问,从 2.6 版本内核开始,Linux 内核支持抢占,也就是说调度程序可
以在任意时刻抢占正在运行的线程,从而运行其他的线程。
③、中断程序并发访问,这个无需多说,学过 STM32 的同学应该知道,硬件中断的权利
可是很大的。
④、SMP(多核)核间并发访问,现在 ARM 架构的多核 SOC 很常见,多核 CPU 存在核间
并发访问。
        并发访问带来的问题就是竞争,学过 FreeRTOS 和 UCOS 的同学应该知道临界区这个概
念,所谓的临界区就是共享数据段,对于临界区必须保证一次只有一个线程访问,也就是要保证临界区是原子访问的。我们都知道,原子化学反应不可再分的基本微粒,这里的原子访问就表示这一个访问是一个步骤,不能再进行拆分。

3.保护对象(数据)

        前面一直说要防止并发访问共享资源,换句话说就是要保护共享资源,防止进行并发访问。那么问题来了,什么是共享资源?在程序中什么是共享资源?也就是保护的内容是什么?我们保护的不是代码,而是数据!某个线程的局部变量不需要保护,我们要保护的是多个线程都会访问的共享数据(比如一个整形的全局变量 a )。

4.原子操作

        线程A是a=10,线程B是a=20;下面有两种可能:

        按照上图所示的流程,线程 A 最终将变量 a 设置为了 20,而并不是要求的 10!线程 B
没有问题。这就是一个最简单的设置变量值的并发与竞争的例子,要解决这个问题就要保证
示例代码 22.2.1 中的三行汇编指令作为一个整体运行,也就是作为一个原子存在。
        Linux 内核提供了一组原子操作 API 函数来完成此功能,Linux 内核提供了两组原子操作 API 函数,一组是对整形变量进行操作的,一组是对位进行操作的,我们接下来看一下这些 API 函数。

4.1原子整形操作 API 函数

        如果要使用原子操作 API 函数,首先要先定义一个 atomic_t 的变量。

atomic64_t a;                                         //定义 a

示例代码 22.2.4 原子变量和 API 函数使用
Atomic64_t v = ATOMIC64_INIT(0);
/* 定义并初始化原子变零 v=0 */
Atomic64_set(&v, 10);
/* 设置 v=10 */
Atomic64_read(&v);
/* 读取 v 的值,肯定是 10 */
Atomic64_inc(&v);
/* v 的值加 1v=11 */

4.2原子位操作 API 函数

5.自旋锁

        原子操作只能对整形变量或者位进行保护,但是,在实际的使用环境中怎么可能只有整
形变量或位这么简单的临界区。举个最简单的例子,设备结构体变量就不是整型变量,我们
对于结构体中成员变量的操作也要保证原子性,在线程 A 对结构体变量使用期间,应该禁止
其他的线程来访问此结构体变量,这些工作原子操作都不能胜任,需要本节要讲的锁机制,
Linux 内核中就是自旋锁。        

        当一个线程要访问某个共享资源的时候首先要先获取相应的锁,锁只能被一个线程持有,只要此线程不释放持有的锁,那么其他的线程就不能获取此锁。对于自旋锁而言,如果自旋锁正在被线程 A 持有,线程 B 想要获取自旋锁,那么线程 B 就会处于忙循环-旋转-等待状态, 线程 B 不会进入休眠状态或者说去做其他的处理,而是会一直傻傻的在那里“转圈圈”的等 待锁可用。

        在使用自旋锁之前,肯定要先定义一个自旋锁变量,定义方法如下所示:
spinlock_t lock;                                         //定义自旋锁
        定义好自旋锁变量以后就可以使用相应的 API 函数来操作自旋锁。

        

示例代码 22.3.2 自旋锁使用示例
1 DEFINE_SPINLOCK(lock);
/* 定义并初始化一个自旋锁 */
2
3 /* 线程 A */
4 void functionA (){
5 unsigned long flags;
/* 中断状态 */
6 spin_lock_irqsave(&lock, flags);
/* 获取锁 */
7 /* 临界区 */
8 spin_unlock_irqrestore(&lock, flags);
/* 释放锁 */
9 }
10
11 /* 中断服务函数 */
12 void irq() {
13 spin_lock(&lock);
/* 获取锁 */
14 /* 临界区 */
15 spin_unlock(&lock);
/* 释放锁 */
16 }

Logo

openEuler 是由开放原子开源基金会孵化的全场景开源操作系统项目,面向数字基础设施四大核心场景(服务器、云计算、边缘计算、嵌入式),全面支持 ARM、x86、RISC-V、loongArch、PowerPC、SW-64 等多样性计算架构

更多推荐