FreeRTOS笔记(十二)资源管理
上一文链接:FreeRTOS笔记(十一)延迟中断
考虑完多任务(中断)之间的通信后,还需要考虑多任务(中断)之间的资源访问,因为资源往往是互斥使用的,比如打印机、串口等等,一个任务在使用的时候,不允许另一个任务去打断,否则就会出现信息不一致的情况,造成混乱
而一般情况下资源的访问步骤必须是连续的,比如在C语言上代码if(0 == a) a = -1;
,其实在CPU内部进行了若干个步骤:加载寄存器的值 → 与0比较 → 根据结果跳转。如果在加载寄存器之后被打断,而且a原来非0但是被修改为0,那么再次重入的时候,本应该不去执行a = -1
,但是却执行了
01 - 互斥机制
在FreeRTOS中,访问资源是一段任务的代码,所以总是希望这段代码在执行的时候全程不要被打断,或者即使打断但是其它任务不能访问同样的资源。
全程不被打断其实过于苛刻,所以基本都是专注于能够被打断但是重入后依然没有问题的设计。任务被打断的原因有2个:中断(外部)的到来、调度器中断(内部)调度任务,可以简述为外部中断和任务中断,所以只要解决这2个问题,就可以实现资源互斥访问机制。
比如,因为原因都是中断,所以最粗暴的方式就是直接关闭所有中断(临界区),这样任务中断和外部中断都不能打断这段代码,但是这种方法太粗暴,FreeRTOS应该要实时响应外部中断,所以可以只关任务中断而打开外部中断(挂起调度器),这样还是有问题,系统有时候不能关闭任务切换,比如存在周期性任务,那么就可以用一种申请-释放的方式访问资源(互斥量),申请的一方即使被打断,后来者也不能访问,可是申请-释放的方式是公用的,容易导致死锁和优先级反转,所以原则上不应该相信其它任务可以很好管理资源,于是专门派一个任务负责资源的分配和释放(守护任务),其它任务不能公用,只能间接使用、
经过以上内容,可以了解到其实FreeRTOS提供了很多特性用于实现互斥机制,分别是临界区、挂起调度器、互斥量和守护任务,4中互斥各有优缺点,分别应用在不同的场合
1.1 - 临界区
FreeRTOS的临界区是关闭所有中断(除掉电中断、undef中断等除外),指宏 taskENTER_CRITICAL()
与taskEXIT_CRITICAL()
之间的代码区间,跟踪源码
taskENTER_CRITICAL() #进入临界区
portENTER_CRITICAL()
vPortEnterCritical()
uxCriticalNesting++; #计数加1,用于嵌套
portDISABLE_INTERRUPTS() #屏蔽中断
vPortRaiseBASEPRI()
msr basepri, configMAX_SYSCALL_INTERRUPT_PRIORITY
portEXIT_CRITICAL() #退出临界区
portEXIT_CRITICAL()
vPortExitCritical()
uxCriticalNesting--; #计数减1,用于嵌套
portENABLE_INTERRUPTS() #打开中断
vPortSetBASEPRI( 0 )
msr basepri, 0
对于taskENTER_CRITICAL()
,最后往basepri
寄存器中写入configMAX_SYSCALL_INTERRUPT_PRIORITY
,这个值是在FreeRTOSConfig.h中配置的,代表最大的可管理中断,详细可以看配置文件,在这里,可以理解为关闭所有的中断(除掉电中断、undef中断等除外),所以一旦调用taskENTER_CRITICAL()
,那么任务就一直处于运行态,直到taskEXIT_CRITICAL()
的出现。
而taskEXIT_CRITICAL()
,最后是往basepri
寄存器中写入0,意思是开启所有中断,所以临界区就是提供一种最直接粗暴的方式去访问资源。
1.2 - 挂起调度器
挂起调度器使得任务的执行过程不被其它任务打断,但是可以被外部中断打断,FreeRTOS提供了挂起/唤醒调度器的API
API | 功能 |
---|---|
vTaskSuspendAll() | 挂起调度器 |
xTaskResumeAll() | 唤醒调度器 |
跟踪源码
vTaskSuspendAll()
++uxSchedulerSuspended; #计数加1,用于嵌套和标记
xTaskResumeAll()
taskENTER_CRITICAL() #进入临界区
--uxSchedulerSuspended #计数减1,用于嵌套和标记
prvAddTaskToReadyList() #调出就绪任务
taskEXIT_CRITICAL() #退出临界区
vTaskSuspendAll()
挂起调度器只是简单地加1计数,因为这个uxSchedulerSuspended
全局变量会在Systick中断中使用(具体到xTaskIncrementTick()
函数),如果uxSchedulerSuspended
不为0(挂起),那么xTickCount
不再计数,表达系统心跳暂时停止,于是调度器也不会进行任务切换,可以回顾任务切换
1.3 - 互斥量
互斥量不需要关闭任何中断,它采用一种申请-释放的方式去访问资源,申请和释放的其实不是资源,而是代表资源的钥匙,在这种方式下,资源与一把钥匙绑定,要想访问资源,必须先拿到这把钥匙,没有钥匙的只能等待前者释放,所以即使拥有钥匙的一方被打断,后者也不能访问资源
FreeRTOS提供了互斥量的相关API,互斥量本质是一个长度为1的队列,可以回顾队列和通信
API | 功能 |
---|---|
xSemaphoreCreateMutex() | 创建互斥量 |
xSemaphoreGive() | 任务状态下给出信号量 |
xSemaphoreTake() | 任务状态下得到信号量 |
xSemaphoreTakeFromISR() | 中断状态下给出信号量 |
xSemaphoreTakeFromISR() | 中断状态下得到信号量 |
vSemaphoreDelete() | 删除信号量 |
1.4 - 守护任务
无论是临界区、挂起调度器还是互斥量,它们的使用都带来非常多的问题,核心原因是资源是公用的,资源的所有权和使用权都是公共的,任何一个任务都可以去直接操作,为了解决这个核心原因,可以把资源私有化,所有权和使用权都在一个任务A上面,其它任务只能间接去访问,比如把要写入的数据发给A,由A去写入,把要读的数据要求发给A,由A去读然后返回数据等等
守护任务的实现不需要什么特性或者机制的支持,是一个协议,设置好任务的代码逻辑后就可以实现,它非常干净利落,把资源私有化后,阻塞等待其它任务的要求
例如打印操作就是用守护任务实现的,任何想打印的任务只需要把数据以通信的方式传输给守护任务,守护任务负责数据的排队和正确逻辑,不需要任何的申请-释放
02 - 互斥机制的区别
互斥机制 | 本质 | 优点 | 缺点 |
---|---|---|---|
临界区 | 关闭任务中断和外部中断 | 确保资源访问不可能被打断,资源访问过程简单 | 其余任务停滞、外部中断得不到响应 |
挂起调度器 | 关闭任务中断 | 可以响应外部中断,不能被其它任务打断,资源访问过程简单 | 其余任务停滞 |
互斥量 | 资源需要申请和释放 | 不需要关中断,资源访问过程简单 | 容易出现死锁和优先级反转 |
守护任务 | 资源私有化,是一个协议 | 不再出现以上问题缺点 | 资源访问过程复杂,间接访问可能带来速度和效率问题 |
03 - 总结
- 资源一般需要互斥访问,因此需要互斥机制
- FreeRTOS可以实现4种互斥机制,临界区、挂起调度器、互斥量和守护任务
- 4种互斥机制各有优缺点,需要在不同的场合使用
本文地址:https://blog.csdn.net/Hxj_CSDN/article/details/85937911