Linux系统是个多任务操作系统,会存在多个任务同时访问一片内存区域,这些任务可能会相互覆盖这段内存中的数据,造成内存数据混乱。
系统并发产生的原因有很多,其中最基本的就是多线程并发访问,其他还有多核并发访问,抢占式并发访问,中断程序并发访问等。
为此,我又去翻看了一下freertos,其实这跟freertos很像。
于是,我又把FreeRTOS的信号量和互斥量复习了一遍,与Linux系统对比之后,发现在理解上比以前深刻很多。
信号量
信号量可以分为二值信号量、计数信号量、互斥信号量、递归信号量。
信号量可以实现任务之间同步或临界资源的互斥访问,协助一组相互竞争的任务来访问临界资源。
临界资源:任何时刻只能被一个任务访问的资源
二值信号量
二值信号量与互斥信号量很相似,但是互斥量拥有优先级继承机制,更适合对临界资源的访问。
互斥量的优先级继承机制:有优先级为高低中三个任务,低任务正在访问临界资源,当高任务申请访问临界资源时,进入阻塞态,同时低任务的优先级暂时提升与高任务优先级相同,等低任务退出临界资源访问时,恢复自己的低优先级。
上图就表示了优先级继承机制的作用。
如果没有优先级继承机制,那么便可能发生优先级翻转。
优先级翻转:同样是优先级为高中低三个任务。低任务正在访问临界资源,此时高任务申请访问,进入阻塞态。与此同时,中任务开始运行,因为它并不用访问临界资源,所以抢占了CPU的使用权,中任务开始运行。直到中任务运行结束,归还CPU使用权,低任务才能运行,运行完成之后高任务才能够开始运行。
为什么叫二值信号量呢?
因为信号量被获取了,信号量值就是0,信号资源被释放,信号量值就是1。
在多任务系统中,我们经常会使用这个二值信号量。
例如,某个任务需要等待一个标记,那么任务可以在轮询中查询这个标记有没有被置位。就像是一个人进了电话亭,那么另一个人想使用电话亭只能在外面等待,在周围转转,直到电话亭空了,才能进去打电话。
Linux系统在这里会引入一个自旋锁的概念,有点类似于上面的操作。
自旋锁:当一个线程要访问某个共享资源的时候要先获取相应的锁,锁只能被一个线程持有,只要此线程不释放该锁,那么其他线程就不能获取此锁,同时想要获取该锁的线程不会进入休眠或者干其他事情,只能在那里等待。
这样做其实会很浪费CPU的资源,一般使用于持有锁很短的场景,或者中断中。
在自旋锁的使用中,要注意被自旋锁保护的临界区一定不能调用任何能够引起睡眠和阻塞的API函数。
举个例子。
线程A得到锁暂时禁止内核抢占,如果线程A进入了休眠状态,CPU的使用权就会被释放出来。那么线程B开始运行,它也想要获得锁,但是锁又被A持有了,而内核又被禁止抢占。也就是说其他线程没有办法抢占,那么线程B就无法被调度出去,而线程A又无法运行,不能释放锁,死锁在此刻就发生了。
再者,线程A中有中断,而这个中断也要使用锁,也会发生死锁。
小插曲:什么是内核抢占?
参考这篇文章:http://t.csdn.cn/FBzml
(知识点get+1)
那么,对于持有锁比较长的线程呢?
这里引入的就是我们信号量的概念啦。
使用二值信号量,当任务获取信号量时,因为此时没有发生特定的事件,信号量为空,任务会进入阻塞态,直到事件的条件满足之后,信号量被释放,告知任务这个事件发生了,任务取得信号量执行,执行完毕不需要归还信号量。
这样就大大的节省了资源。
但是信号量并不能用于中断中,因为信号量会引起休眠,而中断不能休眠。
计数信号量
计数信号量可用于资源管理,允许多个任务获取信号量访问的共享资源,但会限制任务的最大数目。
互斥信号量(互斥体)
互斥量更多的时用于保护资源的互锁。
在FreeRTOS中,相比较二值信号量,它多了一个优先级继承机制,在之前已经介绍过,这个优先级继承机制只在任务中起作用。
在linux中使用的是互斥体。
递归互斥量
使用与同一个任务可能会多次获取互斥量的情况,避免同一任务多次递归持有造成死锁。
本文参考正点原子Linux驱动开发教程,野火基于STM32的FreeRTOS实战教程,只是自己的阶段性总结,大家可以自行到这两个教程里了解更详细的知识,有问题欢迎一起讨论。