《原子Linux驱动开发》基础阅读4:Linux并发与竞争
[复制链接]
Linux是一个多任务操作系统,一定会存在多个任务共同操作同一内存和设备的情况,多个任务或者中断都能访问的资源叫做共享资源,在驱动中要注意对共享资源的保护,也就是要处理对共享资源的并发访问。对并发控制的管理在linux驱动编写中至关重要。
那么什么是并发呢?并发就是多个用户同时访问同一个共享资源,我们分时访问的时候肯定是互不影响的,但当我们同时都要共同处理的时候,这个时候就有一个优先权的问题了。linux系统在多任务同时操作内存的时候,可能会出现相互覆盖这段内存中的数据,造成内存数据混乱这个问题严重的话,可能会导致系统崩溃。
并发产生的原因有很多:比如多线程并发访问,在多任务系统中,多线程访问是最主要的一个原因;第二个是抢占式并发访问,Linux内核是支持抢占的,也就是调度程序可以在任意时刻抢占正在运行的线程,从而影响其他线程;第三个原因就是中断式并发访问,就像stm32一样,硬件中断的权利是很大的;还有就是多核间的并发访问,arm架构的多核soc非常的常见,多核访问并发也是非常常见的。
那么什么是竞争呢?并发访问所带来的问题就是竞争,你学习stm32这种MCU时都接触过,像Freertos这样实时系统是有临界区的,必须保证只有一个线程访问,同时操作内存就会存在竞争,我们在编写驱动的时候,一定要注意避免并发和防止竞争,这个会在后续的程序运行中埋下隐患,这类问题其实是最不容易查找的,费时费力,所以我们在编写驱动的时候一定要考虑并发与竞争。
防止并发访问共享资源,也就是对共享资源的保护,包括我们程序中的变量数据找到需要保护的共享数据是重点也是难点,驱动程序各不相同,数据也是千变万化,一般全局变量基本都是要在设备结构体中保护的。
在这里,我们要了解一下原子操作,指的就是不能再进一步分割的操作,一般指变量或者是位操作,但是arm架构是不支持直接对寄存器进行读写操作的因此,Linux内核提供了一组原子操作的API函数来完成这这些功能,原子操作可以实现对整型变量或者未进行保护。
但实际使用环境中可能会有其他的部分需要保护,例如结构体变量,它不是整形变量,但是在线程对结构体使用期间应该禁止其他线程访问此结构体,这样的话原子操作就无法对其进行保护了,这个时候就要用到Linux内核中的自旋锁,当一个线程要访问共享资源的时候,首先获得相应的锁,锁只能被一个线程持有,只要此线程不释放,那么其他线程就不能获取此锁。自旋锁的自旋,也就是原地打转的意思,自旋的目的就是等待自旋锁可以用,把自旋锁比作一个变量,当变量等于一的时候,表示共享资源可用为零的时候,表示共享资源不可用。当线程a访问共享资源的时候,发现变量为零,那么线程a就会不断的查询变量的值,直到为一的时候才可以使用自旋锁。有一个缺点就是等待自旋锁的线程会一直处于自旋状态循环查询,这样就会浪费处理器时间,降低系统性能,所以自旋锁的持有时间不能太长,自旋锁适用于短时间内、轻量级,不适合长时间持有的场景。
自旋锁API函数如下:
使用自旋锁需要注意以下几点:
由于等待自旋锁的时候处于自旋状态,所以持有时间不能太长;
自选所保护的临界区内不能调用任何可能导致线程休眠的API,否则可能导致死锁;
不能递归申请自旋锁会导致自我锁死;
编写驱动程序时,必须考虑驱动的可移植性。因此,不管你用的是单核还是多核,都要当做多核soc来编写;
Linux内核提供信号量机制用于控制对共享资源的访问与自旋锁相比,信号量可以使线程进入休眠状态,信号量会提高处理器的使用效率,不用一直在那里进行自旋等待。但是,信号量的开销要比自旋锁大,信号量使线程进入休眠状态以后,会切换线程,切换线程就会有更大的开销。
Linux中提供了一个比信号量更专业的机制来进行互斥,它就是互斥体,互斥访问,表示一次只有一个线程可以访问共享资源,不能递归申请互斥体。
|