1.优先级反转
假设有三个任务,任务一的优先级为1,任务二的优先级为2,任务三的优先级为3,并且任务一与任务三需要信号量,任务二不需要,且他们的执行顺序为任务一->任务二->任务三。
任务一开始执行任务时,申请得到了信号量,执行他的任务,执行一段时间后,任务二优先级大于任务一,于是任务一被阻塞,开始执行任务二。任务二执行一段时间后,任务三开始执行,当任务三需要信号量,而此时信号量被任务一占用,任务一又被任务二阻塞,不能释放信号量,因此此时任务三会在申请信号量时被阻塞,所以调度器会执行任务二,当任务二执行完成后,任务三还没有得到信号量还是在被阻塞,执行任务一,最后才是任务三。
简单来说就是任务一被任务二阻塞了,不能释放信号量,而任务三又获得不了信号量,也被阻塞,只能执行任务二。
解决方法:使用互斥量
2.互斥量(锁)
使用互斥锁可以避免多任务的竞争冒险,提高系统的稳定性和可靠性,同时它还可以解决信号量的优先级反转问题,它具备了继承功能。
流程:
互斥量初始值为1
任务A想访问临界资源,先获得并占有互斥量,然后开始访问
任务B也想访问临界资源,也要先获得互斥量:被别人占有了,于是阻塞
任务A使用完毕,释放互斥量;任务B被唤醒、得到并占有互斥量,然后开始访问临界资源
任务B使用完毕,释放互斥量
3.互斥量函数
创建:
释放和获得:
优先级继承(领导临时提拔你):
假设持有互斥锁的是任务A,如果更高优先级的任务B也尝试获得这个锁
任务B说:你既然持有宝剑,又不给我,那就继承我的愿望吧
于是任务A就继承了任务B的优先级
这就叫:优先级继承
等任务A释放互斥锁时,它就恢复为原来的优先级
互斥锁内部就实现了优先级的提升、恢复
4.事件组
事件组的核心就是可以唤醒多个任务。
事件组可以简单地认为就是一个整数,其中的高8位留给内核使用,只能用其他的位来表示事件:
1.每一位表示一个事件;
2.每一位事件的含义由程序员决定,比如:Bit0 表示用来串口是否就绪,Bit1表示按键是否被按下;
3.这些位,值为 1 表示事件发生了,值为 0 表示事件没发生;
4.一个或多个任务、ISR 都可以去写这些位;
5.一个或多个任务、ISR 都可以去读这些位;
6.可以等待某一位、某些位中的任意一个,也可以等待多位
两大主要区别:
唤醒谁?
队列、信号量:事件发生时,只会唤醒一个任务
事件组的事件发生时,会唤醒所有符号条件的任务,简单地说它有"广播"的作用
是否清除事件?
队列、信号量:是消耗型的资源,队列的数据被读走就没了;信号量被获取后就减少了
事件组:被唤醒的任务有两个选择,可以让事件保留不动,也可以清除事件
- 事件组函数
创建:
删除:
设置事件:
等待事件:
5.FreeRTOS为什么需要两套API函数
FreeRTOS中很多API函数都有两套:一套在任务中使用,另一套在ISR中使用。后者的函数名含有"FromISR"后缀。
为什么要引入两套API函数?
很多API函数会导致任务计入阻塞状态:运行这个函数的任务进入阻塞状态;比如写队列时,如果队列已满,可以进入阻塞状态等待一会
ISR调用API函数时,ISR不是"任务",ISR不能进入阻塞状态
所以,在任务中、在ISR中,这些函数的功能是有差别的
6.ISR函数里的xHigherPriorityTaskWoken参数
xHigherPriorityTaskWoken的含义是:是否有更高优先级的任务被唤醒了。如果为pdTRUE,则意味着后面要进行任务切换。还是以写队列为例。任务A调用 xQueueSendToBack() 写队列,有几种情况发生:
1.队列满了,任务A阻塞等待,另一个任务B运行
2.队列没满,任务A成功写入队列,但是它导致另一个任务B被唤醒,任务B的优先级更高:任务B先运行
3.队列没满,任务A成功写入队列,即刻返回
可以看到,在任务中调用API函数可能导致任务阻塞、任务切换,这叫做"context switch",上下文切换。这个函数可能很长时间才返回,在函数的内部实现了任务切换。
xQueueSendToBackFromISR() 函数也可能导致任务切换,但是不会在函数内部进行切换,而是返回一个参数:表示是否需要切换,函数原型与用法如下:
pxHigherPriorityTaskWoken参数,就是用来保存函数的结果:是否需要切换
*pxHigherPriorityTaskWoken等于pdTRUE:函数的操作导致更高优先级的任务就绪了,ISR应该进行任务切换;等于pdFALSE:没有进行任务切换的必要
ISR中有可能多次调用"FromISR"函数,如果在"FromISR"内部进行任务切换,会浪费时间。解决方法是:在"FromISR"中标记是否需要切换,在ISR返回之前再进行任务切换。
7.屏蔽/使能中断、暂停/恢复调度器
互斥功能是如何实现的?
要独占式地访问临界资源,有3种方法:
公平竞争:比如使用互斥量,谁先获得互斥量谁就访问临界资源,这部分内容前面讲过。
1、谁要跟我抢,我就灭掉谁:
2、中断要跟我抢?我屏蔽中断
3、其他任务要跟我抢?我禁止调度器,不运行任务切换
屏蔽中断:
1.在任务中屏蔽中断:
2.在ISR中屏蔽中断:
屏蔽调度器:
如果有别的任务来跟你竞争临界资源,你可以把中断关掉:这当然可以禁止别的任务运行,但是这代价太大了。它会影响到中断的处理。
如果只是禁止别的任务来跟你竞争,不需要关中断,暂停调度器就可以了:在这期间,中断还是可以发生、处理。
8.中断延时处理
简单来说,就是我在ISR中断处理函数中,只记录发生了中断,好比记录一个标志位,而真正的处理是在任务中进行,这样就不会影响ISR。