目录

8.1linux竞争与并发知识讲解尽可能详细_csdn

8.1linux竞争与并发知识讲解(尽可能详细)_csdn

Linux是一个多任务操作系统,肯定会存在多个任务共同操作同一段内存或者设备的情况,多个任务甚至中断都能访问的资源叫做共享资源,就和共享单车一样。在驱动开发中要注意对共享资源的保护,也就是要处理对共享资源的并发访问。 比如共享单车,大家按照谁扫谁骑走的原则来共用这个单车,如果没有这个并发访问共享单车的原则存在,只怕到时候为了一辆单车要打起来了。

1、并发与竞争简介

https://i-blog.csdnimg.cn/img_convert/052732f517ada3e2d307917315f8b27d.png

打印机必须保证一次只能打印一份文档,只有打印完成以后才能打印其他的文档。

https://i-blog.csdnimg.cn/img_convert/ad73563b874a58010b40598cc72008c5.png

临界区 就是共享数据段,对于临界区必须保证一次只有一个线程访问。

也就是要保证 临界区是原子访问的 ,注意这里的==“原子==”不是正点原子的“原子”。

我们都知道,原子是化学反应不可再分的基本微粒,这里的 原子访问 就表示这一个访问 是一个步骤 , 不能再进行拆分 。如果 多个线程同时操作临界区就表示存在竞争 ,我们在编写驱动的时候一 定要注意避免并发和防止竞争访问 。

重点 :我们一般在编写驱动的时候就要考虑到并发与竞争,而不是驱动都编写完了然后再处理并发与竞争。

https://i-blog.csdnimg.cn/img_convert/164c07f566dcafc527adc1cf38b19c90.png

2、原子操作

2.1、原子整形操作 API 函数

一次只允许一个应用程序。

https://i-blog.csdnimg.cn/img_convert/d5b54e5f444b266d23ee35ce60fb6221.png

内核里面的东西已经做好!

https://i-blog.csdnimg.cn/img_convert/13a0cfb76048e01eb6eedd14cdfe4b64.png

https://i-blog.csdnimg.cn/img_convert/fa1bc86fe7c19a731c82e0bf7a8436ff.png

用的STM32MP157是32位的架构。

https://i-blog.csdnimg.cn/img_convert/17f1822c1deed01ec23d48829a4b0fe0.png

2.2、原子位操作 API 函数

位操作也是很常用的操作, Linux 内核也提供了一系列的原子位操作 API 函数, 只不过原子位操作不像原子整形变量那样有个 atomic_t 的数据结构,原子位操作是直接对内存进行操作 。

https://i-blog.csdnimg.cn/img_convert/dddec6946be37b4ef519a2e7a71dd161.png

https://i-blog.csdnimg.cn/img_convert/65af56fb69c73d6306644e609431383a.png

3、自旋锁

https://i-blog.csdnimg.cn/img_convert/ba04fec2f84d82c9b5f9d05c1ebc877a.png

Linux 内核使用结构体 spinlock_t 表示自旋锁,结构体定义如下所示:

https://i-blog.csdnimg.cn/img_convert/629ec895aa8774a585fb63470a228e0e.png

https://i-blog.csdnimg.cn/img_convert/82332a37d4dafd00b83909610bbf490f.png

https://i-blog.csdnimg.cn/img_convert/5dc682261848447f8737b78276d2ff11.png

3.1、自旋锁API函数

https://i-blog.csdnimg.cn/img_convert/81cf361da41ac45361bcccb2b17732c8.png

https://i-blog.csdnimg.cn/img_convert/35a60812643931b7eb10229d3d050a61.png

https://i-blog.csdnimg.cn/img_convert/e7ae4121c33885e567108678561a8e6c.png

https://i-blog.csdnimg.cn/img_convert/92abd7ec48948951deeab2c2b2bb252b.png

这个意思就是 :假如没有开启禁止本地中断,在线程A在获取锁后,中断开始想获取这个锁,但是因为线程A占了这个锁,所以中断没法执行,但是中断调用函数优先权比线程A高,线程A不可能执行,所以僵持下去了!

假如没有这个锁,线程A一旦被中断事件中断,就必须让出cpu使用。先中断后线程A。

最好的解决方法就是获取锁之前关闭本地中断。

上面有API函数进行处理!

https://i-blog.csdnimg.cn/img_convert/7296730f313450e3bc35fae6aa423a0f.png

当线程开始时,禁止本地中断,获取自旋锁;当中断开始时,激活本地中断,释放自旋锁。

一般在线程中使用 spin_lock_irqsave/ spin_unlock_irqrestore ,在中断中使用 spin_lock/spin_unlock

例如:

https://i-blog.csdnimg.cn/img_convert/22addd2c231f573cbc8bb2c9b077d2ab.png

https://i-blog.csdnimg.cn/img_convert/b59ef5a22dc43c10739e87e3d34599ba.png

3.2、其他类型的锁

3.2.1、读写自旋锁

https://i-blog.csdnimg.cn/img_convert/3bd565ef56abccfecdbd167e5d46c2be.png

这个意思是 :只能一个线程进行写锁操作,支持多个线程进行读锁操作。修改数据只可以一个线程进行,读取数据可以多个线程同时进行。

https://i-blog.csdnimg.cn/img_convert/2554f6bdbd83ad2b2bd332e26fd8bf60.png

读写锁操作 API 函数分为两部分,一个是给读使用的,一个是给写使用的。

https://i-blog.csdnimg.cn/img_convert/3f62b3d7089e79e5456c77a3d58badab.png

https://i-blog.csdnimg.cn/img_convert/943c18237bcd8fe76f0bb34a06b93747.png

3.2.2、顺序锁

顺序锁在读写锁的基础上衍生而来的, 使用读写锁的时候读操作和写操作不能同时进行 。使用顺序锁的话可以允许在写的时候进行读操作,也就是实现同时读写, 但是不允许同时进行并发的写操作

虽然顺序锁的读和写操作可以同时进行,但是如果在读的过程中发生了写操作 , 最好重新进行读取 ,保证数据完整性。

顺序锁保护的资源不能是指针,因为如果在写操作的时候可能会导致指针无效,而这个时候恰巧有读操作访问指针的话就可能导致意外发生,比如读取野指针导致系统崩溃 。

Linux 内核使用 seqlock_t 结构体表示顺序锁。

https://i-blog.csdnimg.cn/img_convert/13467af32b86f3934ea642452d149418.png

关于顺序锁的 API 函数如表:

https://i-blog.csdnimg.cn/img_convert/c9bcf058f5ce6cc213d8de0e2f4e2735.png

3.3、顺序锁自旋锁使用注意事项

https://i-blog.csdnimg.cn/img_convert/9908680b58fd112e0e82d3b78a08b81e.png

4、信号量

https://i-blog.csdnimg.cn/img_convert/61b84452203969d47337518a9d22051a.png

https://i-blog.csdnimg.cn/img_convert/fed9a50921d4b5202d657a9b5aaa35fa.png

https://i-blog.csdnimg.cn/img_convert/10b455f1e833bc5b3e8b72521814ab4a.png

https://i-blog.csdnimg.cn/img_convert/38e8dfc0d7024d0b1303f2af7db61216.png

4.1信号量 API 函数

https://i-blog.csdnimg.cn/img_convert/813cf6673b590f017d76491a0b318738.png

https://i-blog.csdnimg.cn/img_convert/86e6ec634ea67c8a1af53b17922a99ea.png

https://i-blog.csdnimg.cn/img_convert/8928bb25666945636fe243a0b31bf4f4.png

5、互斥体

将信号量的值设置为 1 就可以使用信号量进行互斥访问了。

虽然可以通过信号量实现互斥,但是 Linux 提供了一个比信号量更专业的机制来进行互斥,它就是互斥体—mutex。

互斥访问表示 一次只有一个线程 可以访问共享资源,不能递归申请互斥体。

在我们编写 Linux 驱动的时候遇到需要互斥访问的地方建议使用 mutex 。

使用 mutex 结构体表示互斥体,定义如下:

https://i-blog.csdnimg.cn/img_convert/e4acc1b8a9deffafd1e68c449603ec29.png

5.1、互斥体 API 函数

https://i-blog.csdnimg.cn/img_convert/5387afe80e7a88ec47e55cf7573b75a1.png

互斥体的使用:

https://i-blog.csdnimg.cn/img_convert/52f3d5585933a6a7b1549b3e0f49bdf4.png

关于 Linux 中的并发和竞争就讲解到这里, Linux 内核还有很多其他的处理并发和竞争的机制,本章我们主要讲解了常用的 原子操作、自旋锁、信号量和互斥体 。以后我们在编写 Linux驱动的时候就会频繁的使用到这几种机制。