《Mastering the FreeRTOS Real Time Kernel》读书笔记(6)资源管理

发布时间 2023-10-16 16:08:07作者: BD4RTZ

7.资源管理(互斥量)

在多任务系统中,如果一个任务开始访问资源,但在从运行状态转换出来之前没有完成访问,则可能会出现错误。如果任务使资源处于不一致状态,则任何其他任务或中断对同一资源的访问都可能导致数据损坏或其他类似问题。

这里的资源管理,应该是指计算机的外设资源,比如LCD显示器,寄存器,内存中的数据,甚至包括不确定是否可以重传的函数(线程安全与否)。每个任务都有访问和利用资源的权利,但是需要按照一定的规章制度,来规范特定资源的使用,避免出现争抢,不能仅仅按照内核安排。

为了确保始终保持数据一致性,必须使用“互斥”技术管理对任务之间或任务与中断之间共享的资源的访问。

7.2 关键区域和挂起程序

划关键区域代码

它们分别被对宏taskENTER_CCRITICAL()和taskEXIT_critical()的调用所包围。临界截面也称为临界区域。
在关键区域内的代码不会被低优先级的中断打断,也不会被任务打断。这样的方式非常粗暴,笨拙。在临界区域内的代码执行时间必须尽可能的短,不然会有打乱中断的触发。
这两个宏也有中断安全模式taskENTER_CRITICAL_FROM_ISR()和taskEXIT_CRITICAL_FROM _ISR()。
基本关键部分进入非常快,退出非常快,并且总是具有确定性,因此当受保护的代码区域非常短时,建议使用。

挂起调度器

当使用挂起时,只是停止了任务调度,所有中断均可以使用。挂起可以认为是阻塞的手动形式,阻塞状态是调度器分配的。
在执行一个代码执行时间比较长的任务时,建议使用挂起功能,挂起那些会与现在执行程序争抢资源的程序。

void vTaskSuspendAll( void ); //挂起所有任务

BaseType_t xTaskResumeAll( void ); //恢复所有任务

7.3 互斥量(和二进制信号量)

互斥是一种特殊类型的二进制信号量,用于控制对两个或多个任务之间共享的资源的访问。MUTEX一词源自“MUTual EXclusion”。在FreeRTOSConfig.h中,configUSE_MUTEXES必须设置为1才能使互斥对象可用。

当在互斥场景中使用时,互斥可以被认为是与共享资源相关联的令牌。要使任务合法访问资源,它必须首先成功地“获取”令牌(作为令牌持有者)。令牌持有者用完资源后,必须“归还”令牌。只有在返回令牌后,另一个任务才能成功获取令牌,然后安全地访问同一共享资源。不允许任务访问共享资源,除非它持有令牌。

结构和二进制信号量相似,但是用法和属性与其完全不同。

创建互斥量:

SemaphoreHandle_t xSemaphoreCreateMutex( void );

获取与归还互斥量:

xSemaphoreTake( xMutex, portMAX_DELAY );
{
……………………………………//被保护的程序
}
xSemaphoreGive( xMutex );

互斥量的潜在陷阱

优先级反转

高优先级的任务不能抢占持有令牌的低优先级任务。高优先级任务被延迟。

优先级继承

优先级继承是一种将优先级反转的负面影响降至最低的方案,但并没有完全修复,且会使时序分析复杂化。
优先级继承的工作原理是将互斥体持有者的优先级临时提高到试图获得相同互斥体的最高优先级任务的优先级。当互斥锁持有者将互斥锁交还时,其优先级会自动重置为其原始值。优先级继承功能会影响使用互斥对象的任务的优先级。因此,不能从中断服务例程中使用互斥对象。

死锁

当两个任务都在等待另一个所拥有的资源而无法继续执行时,就会发生死锁。
避免死锁的最佳方法是在设计时考虑其实际应用场景,并设计系统以确保不会发生死锁。在实践中,死锁在小型嵌入式系统中并不是一个大问题,因为系统设计者可以很好地理解整个应用程序,从而可以识别和消除可能发生死锁的区域。

递归互斥

任务本身也有可能发生死锁。如果一个任务多次尝试获取同一个互斥体,而没有首先返回互斥体。比如令牌所在的任务切换到了阻塞态,却没有归还令牌。

这种类型的死锁可以通过使用递归互斥来代替标准互斥来避免。同一任务可以多次“获取”递归互斥,并且只有在执行了一次“给予”递归互斥的调用之后,才会返回递归互斥。
标准互斥和递归互斥以类似的方式创建和使用:
xSemaphoreCreateMutex()对应xSemaphoreCreateRecursiveMutex();
xSemaphoreTake()对应xSemaphoreTakeRecursive();
xSemaphoreGive()对应xSemaphoreGiveRecursive();

互斥量与任务调度

如果两个优先级不同的任务使用相同的互斥,那么一旦低优先级任务返回互斥,高优先级任务就会抢占低优先级任务,不会让低优先级任务继续完成。
然而,当任务具有相同的优先级时,通常会对任务的执行顺序做出错误的假设。当任务2“给予”互斥时,任务1将不会抢占任务2。任务2将保持在运行状态,而任务1将简单地从阻止状态移动到就绪状态。
针对第二种情况,如果多个任务在紧密循环中使用互斥体,并且使用该互斥体的任务具有相同的优先级,则必须注意确保这些任务获得大致相等的处理时间。

7.4 看门人任务(Gatekeeper Tasks)

看门人?保安?守护任务?网守任务?
Gatekeeper任务提供了一种在没有优先级反转或死锁风险的情况下实现互斥的干净方法。看门人任务是对资源拥有唯一所有权的任务。只有看门人任务才被允许直接访问资源——任何其他需要访问资源的任务只能通过使用看门人的服务间接访问。

看来与守护任务(精灵任务不同),这个只针对互斥量章节的情况。

例子21:使用Gatekeeper重写vPrintString()

当任务想要将消息写入标准输出时,它不会直接调用打印函数,而是将消息发送给看门人。

看门人任务使用FreeRTOS队列来序列化对标准输出的访问。任务的内部实现不必考虑互斥,因为它是唯一允许直接访问标准输出的任务。

这是一种使用一个整合任务和队列,代替互斥量的方法。