第五节:FreeRTOS 内存管理

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/ZenNaiHeQiao/article/details/83150424

在这里插入图片描述
信号量存在的意义(资源管理与控制)
FreeRTOS作为一个多任务系统,存在一个潜在风险。当一个任务在使用某个资源的过程中,便被切出运行态。此时如果有另一个任务或者中断来访问这个资源,则会导致数据损坏或者其他类似的错误。

例如:
访问外设如下情形,有两个任务都试图往LCD中写入数据:任务A准备往LCD中写入 Hello world,任务A被任务B抢占,但此时字符串才输出到Hello w。任务B往LCD写入Abort,Retry,Fail?然后进入阻塞态。任务A继续运行,完成剩余的字符输出orld现在LCD显示的是被破坏了的字符串Hello wAbort,Retry,Fail?orld。

例如:
读改写操作如下情形,当任务A刚完成读操作还未完成改写操作时,任务B抢占。任务B完成了对原数据的更新流程,进入阻塞态,任务A从被抢占处继续执行。其改操作仍然修改的是被B更新前的数据,并覆盖了B修改后的数据进行保存。从结果上来说相当于破坏了数据的值,使得任务B无效了。

例如:
变量的非原子访问情形,更新结构体的多个成员变量。或者更新的变量其长度超过了架构体系的自然长度(在8位机上更新32位数据)。如果这样的操作被中断,将可能岛主数据损坏或丢失。

例如:
函数重入情形.如果一个函数除了访问自己栈空间上分配的数据或是内核寄存器中的数据外,不会访问其他任何数据,则这个函数是可重入的。否则若在多个任务中调用,则可能产生数据破坏的问题。

这些例子说明:访问一个被多任务共享的资源的时候,需要采用互斥技术以保证数据在任何时候的一致性。确保任务从开始访问资源就具有排他性,直至这个资源又回复到完整状态。
当然最好的方式仍然是精心设计程序,尽力不要共享资源或者每个资源都通过单任务访问。

1:临界区与挂起调度器
基本临界区 是指宏 taskENTER_CRITICAL()与taskEXIT_CRITIAL()之间的代码区间。
临界区提供的互斥功能是一种非常简单粗暴的方式,其提供的仅仅是把所有中断都关闭掉,或者是关闭优先级在configMAX_SYSCAL_INTERRUPT_PRIORITY以下的中断依赖于具体FreeRTOS的移植.
但是需要注意的是:
临界区不能持续太长,否则会影响中断的响应事件。
同时临界区是可以嵌套使用的。

挂起调度器 挂起调度器可以保证一段代码取件不被其他任务打断。挂起调度器不会关闭中断。
vTaskSuspendAll()和vTaskResumeAll(),挂起调度器可以嵌套使用。

2:信号量
FreeRTOS中信号量分为:二值信号量、计数信号量、互斥信号量、递归互斥信号量。
  互斥功能可以看作是与共享资源相关联的令牌。一个任务想要访问资源,必须成功的take该资源对应的令牌。当资源使用完毕后,其必须马上give令牌,其他任务才能安全的访问该资源。
优先级反转问题:采用二值信号量提供互斥功能具有潜在缺陷。其中之一就是可能发生优先级反转。当低优先级任务持有令牌时,若高优先级的任务也要使用资源,则其必须被低优先级的任务阻塞,直到低优先级任务资源使用完毕。如果把这种行为进一步放大。若有3个任务A、B、C同时使用资源,他们优先级为A>B>C。当C先拿到互斥量,此时A要使用资源,则要等待C使用资源结束,此时若又有任务B执行,则C无法执行,会导致很久无法使得A获得资源的使用权。
优先级继承:为了减少优先级反转的影响,引入了互斥信号量时,FreeRTOS会提供一个优先级继承的功能,此时拿到互斥量的任务暂时会拥有等待互斥量任务的优先级。当互斥量归还时,该任务优先级自动还原。
死锁:虽然互斥信号量解决了优先级翻转的问题,但是互斥信号量存在一个潜在缺陷-“死锁”。
具体实现为:当两个任务都在等待被对方持有的资源时,两个任务都无法再继续执行。如下:任务A执行,并获得了互斥量X。任务A被任务B抢占。任务B获得互斥量Y,之后又试图获取互斥量X,但是已经被A持有所以B进入阻塞等待X被释放。任务A得以继续执行。其试图获取互斥量Y,但Y已经被B持有,任务A也进入阻塞。

3、守护任务
守护任务提供了一种干净利落的方法来实现互斥功能,而不用担心优先级反转和死锁。
守护任务就是对某个资源具有唯一所有权的任务。只有守护任务才可以直接访问其守护的资源,其他任务要想获得资源只能间接的通过守护任务提供的服务。
守护程序的实现机制:利用队列,当其他任务想使用某个资源的时候,通过队列发送消息给守护程序,由守护程序对资源进行操作。

守护进程是唯一可以访问资源的进程,其他任务和中断通过写队列方式,实现对资源间接访问。

static void prvStdioGatekeeperTask( void *pvParameters )
{
char *pcMessageToPrint;
/* 这是唯一允许直接访问终端输出的任务。任何其它任务想要输出字符串,都不能直接访问终端,而是将要
输出的字符串发送到此任务。并且因为只有本任务才可以访问标准输出,所以本任务在实现上不需要考虑互斥
和串行化等问题。 */
for( ;; )
{
/* 等待信息到达。指定了一个无限长阻塞超时时间,所以不需要检查返回值 – 此函数只会在成功收到
消息时才会返回。 */
xQueueReceive( xPrintQueue, &pcMessageToPrint, portMAX_DELAY );
/* 输出收到的字符串。 */
printf( "%s", pcMessageToPrint );
fflush( stdout );
/* Now simply go back to wait for the next message. */
}
}

关于二值信号量和互斥信号量的区别
优先级翻转的讲解:
在使用二值信号量的时候会遇到很常见的问题:优先级翻转。优先级翻转在可剥夺内核中是非常常见的。但是在实时操作系统中是不允许出现这种情况的,这样会破坏任务的预期顺序,可能导致严重的后果。
举例:
如图任务优先级H>M>L,H和L任务都可以访问共享资源(必须先获得使用权),任务L先获得信号量的使用权占有了资源,此后任务H执行剥夺L的CPU使用权进入执行,执行中需要使用资源,但是资源已经被L占用被挂起,只能跳回L。任务M优先级高于L,执行中同样剥夺了L的CPU使用权后,执行后释放CPU给L,L执行后释放共享资源后才会切换到任务H等待这个信号量。在这里要注意一个问题:任务H要等待任务L释放资源之后才能获得权限。相当于H优先级降低到L的优先级水平上了。当M剥夺L的CPU时候,M的优先级实际上是高于H 的,导致优先级翻转。
在这里插入图片描述
解决任务优先级翻转问题:互斥信号量
互斥信号量起始就是一个拥有优先级继承的二值信号量。在同步的应用中(任务与任务或者中断与任务之间同步)二值信号量最为合适。互斥信号量适用于那些需要互斥访问的应用中。在互斥访问中互斥信号量相当于一把钥匙,当任务想要访问资源时候必须先获得这个钥匙,当使用资源以后就必须归还这个钥匙,这样其任务才能拿着这把钥匙访问资源。
当一个互斥信号量被一个低优先级的任务访问使用,此时有高优先级也尝试使用互斥信号量的话会被阻塞。但是这个高优先级的任务会将低优先级的任务提升到和自己相同的优先级,这个过程叫做优先级的继承。
优先级继承尽可能的降低了高优先级任务处于阻塞的时间,并且将已经出现的“优先级翻转”的影响降低到最低。

优先级继承并不能完全消除优先级翻转的,他只是尽可能的降低优先级翻转带来的影响。
硬实时应用中应该在设计之初就避免优先级翻转的发生。互斥信号量不能用于中断服务函数。因为:互斥信号量有优先级继承的机制,所以只能用在任务中。中断服务函数中不能因为要等待互斥信号量而设置阻塞时间进入阻塞状态。

猜你喜欢

转载自blog.csdn.net/ZenNaiHeQiao/article/details/83150424