8.1linux竞争与并发知识讲解(尽可能详细)_csdn
Linux是一个多任务操作系统,肯定会存在多个任务共同操作同一段内存或者设备的情况,多个任务甚至中断都能访问的资源叫做共享资源,就和共享单车一样。在驱动开发中要注意对共享资源的保护,也就是要处理对共享资源的并发访问。比如共享单车,大家按照谁扫谁骑走的原则来共用这个单车,如果没有这个并发访问共享单车的原则存在,只怕到时候为了一辆单车要打起来了。
1、并发与竞争简介
打印机必须保证一次只能打印一份文档,只有打印完成以后才能打印其他的文档。
临界区就是共享数据段,对于临界区必须保证一次只有一个线程访问。
也就是要保证临界区是原子访问的,注意这里的==“原子==”不是正点原子的“原子”。
我们都知道,原子是化学反应不可再分的基本微粒,这里的原子访问就表示这一个访问是一个步骤,不能再进行拆分。如果多个线程同时操作临界区就表示存在竞争,我们在编写驱动的时候一定要注意避免并发和防止竞争访问。
重点:我们一般在编写驱动的时候就要考虑到并发与竞争,而不是驱动都编写完了然后再处理并发与竞争。
2、原子操作
2.1、原子整形操作 API 函数
一次只允许一个应用程序。
内核里面的东西已经做好!
用的STM32MP157是32位的架构。
2.2、原子位操作 API 函数
位操作也是很常用的操作, Linux 内核也提供了一系列的原子位操作 API 函数,只不过原子位操作不像原子整形变量那样有个 atomic_t 的数据结构,原子位操作是直接对内存进行操作。
3、自旋锁
Linux 内核使用结构体 spinlock_t 表示自旋锁,结构体定义如下所示:
3.1、自旋锁API函数
这个意思就是:假如没有开启禁止本地中断,在线程A在获取锁后,中断开始想获取这个锁,但是因为线程A占了这个锁,所以中断没法执行,但是中断调用函数优先权比线程A高,线程A不可能执行,所以僵持下去了!
假如没有这个锁,线程A一旦被中断事件中断,就必须让出cpu使用。先中断后线程A。
最好的解决方法就是获取锁之前关闭本地中断。
上面有API函数进行处理!
当线程开始时,禁止本地中断,获取自旋锁;当中断开始时,激活本地中断,释放自旋锁。
一般在线程中使用 spin_lock_irqsave/ spin_unlock_irqrestore
,在中断中使用spin_lock/spin_unlock
。
例如:
3.2、其他类型的锁
3.2.1、读写自旋锁
这个意思是:只能一个线程进行写锁操作,支持多个线程进行读锁操作。修改数据只可以一个线程进行,读取数据可以多个线程同时进行。
读写锁操作 API 函数分为两部分,一个是给读使用的,一个是给写使用的。
3.2.2、顺序锁
顺序锁在读写锁的基础上衍生而来的,使用读写锁的时候读操作和写操作不能同时进行。使用顺序锁的话可以允许在写的时候进行读操作,也就是实现同时读写,但是不允许同时进行并发的写操作
。
虽然顺序锁的读和写操作可以同时进行,但是如果在读的过程中发生了写操作,最好重新进行读取,保证数据完整性。
顺序锁保护的资源不能是指针,因为如果在写操作的时候可能会导致指针无效,而这个时候恰巧有读操作访问指针的话就可能导致意外发生,比如读取野指针导致系统崩溃。
Linux 内核使用 seqlock_t 结构体表示顺序锁。
关于顺序锁的 API 函数如表:
3.3、顺序锁自旋锁使用注意事项
4、信号量
4.1信号量 API 函数
5、互斥体
将信号量的值设置为 1 就可以使用信号量进行互斥访问了。
虽然可以通过信号量实现互斥,但是 Linux 提供了一个比信号量更专业的机制来进行互斥,它就是互斥体—mutex。
互斥访问表示一次只有一个线程可以访问共享资源,不能递归申请互斥体。
在我们编写 Linux 驱动的时候遇到需要互斥访问的地方建议使用 mutex。
使用 mutex 结构体表示互斥体,定义如下:
5.1、互斥体 API 函数
互斥体的使用:
关于 Linux 中的并发和竞争就讲解到这里, Linux 内核还有很多其他的处理并发和竞争的机制,本章我们主要讲解了常用的原子操作、自旋锁、信号量和互斥体。以后我们在编写 Linux驱动的时候就会频繁的使用到这几种机制。