FreeRTOS 资源管理

FreeRTOS 资源管理

简介

在多任务系统中,有这么一种情况,如果某个任务开始访问资源,但是发生了任务调度,在它从运行态退出前,还没有完成对资源的访问。那么,如果这个资源被其他任务修改,就会造成数据的遗失,甚至系统崩溃。

比如说有这么几种情况:

  • 访问外设
    如果一个外设端口的写入被打断,可能会产生错误。例如字符输出不连续等问题。
  • 读取、修改、写入
    在这里插入图片描述
    这是一个Non-atomic操作,它用了不止一条指令实现。他可以被打断。
  • Non-atomic变量访问
    更新结构体,更新大于架构最大变量大小的变量(比如说在16位机上更新32位变量)
  • 函数重入
    可重入的函数是可以安全地从多于一个任务或中断中调用的,如图
    在这里插入图片描述
    这个函数是可重入的。每一次访问都会创建变量lVar2的独立copy。
    在这里插入图片描述
    这个函数是不可重入的。由于lState变量是static类型的,每一次对函数的访问都是对同一个内存位置进行的(只有一个copy)。

互斥

为确保数据一致性,必须使用“互斥”技术管理任务之间或任务与中断之间共享的资源的访问。
目标是确保一旦任务开始访问一个不可重入且不具有线程安全性的共享资源,同一任务对该资源具有独占访问权限,直到资源返回到consistent状态。

临界区和挂起调度器

一般情况的临界区

临界区格式如下:
在这里插入图片描述

示例

在这里插入图片描述
这样可以实现printf不会被调度打断。

中断临界区

格式如下:
在这里插入图片描述

挂起调度器

与临界区相比,挂起调度器只能防止任务的访问,但是中断还是可以访问的!

API

vTaskSuspendAll()

调度程序通过调用vTaskSuspendAll()挂起。
挂起调度程序可防止发生上下文切换,但会使中断保持启用状态。
如果中断在调度程序挂起时请求上下文切换,则请求将挂起,并且仅在调度程序恢复时执行。
调度程序挂起时,不能调用FreeRTOS API函数。

xTaskResumeAll()

在这里插入图片描述
调度器可以调用xTaskResumeAll()恢复调度。

示例

在这里插入图片描述

互斥量和二值信号量

互斥量是二值信号量的一个特殊形式,它用来控制共享资源在多个任务之间被访问。
当在互斥场景中使用互斥量时,互斥量可以被视为与共享的资源相关联的令牌。要使任务合法地访问资源,它必须首先成功地“获取”令牌(作为令牌持有者)。当令牌持有者使用完资源后,必须将令牌“交回”。只有当令牌已返回时,另一个任务才能成功获取令牌,然后安全地访问同一共享资源。除非任务持有令牌,否则不允许它访问共享资源。
尽管互斥量和二值信号量都可以实现同步,但是他们是有区别的:

  • 一个互斥信号量必须返回
  • 一个用来同步的二值信号量一般不用返回
    如图所示:
    在这里插入图片描述
    configUSE_MUTEXES = 1时,可以启用互斥量。

API

xSemaphoreCreateMutex()

在这里插入图片描述
示例:
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
图解:
在这里插入图片描述

优先级反转

上图展示了使用互斥量的潜在陷阱之一。所描述的执行顺序显示了高优先级任务2必须等待低优先级任务1放弃互斥量的控制。以这种方式被低优先级任务延迟的高优先级任务称为“优先级反转”。当有中等优先级任务在高优先级任务等待信号量时开始执行,这个后果更加严重。结果将是高优先级任务等待低优先级任务,而低优先级任务甚至无法执行。最坏的情况如下图所示。
在这里插入图片描述
由于MP任务抢占了LP,导致LP无法释放互斥量,系统死锁。

优先级继承

互斥量有优先级继承机制,而二值信号量没有。优先级继承可以减轻优先级反转的问题,但是并不能完全解除。然而,优先级继承使系统时序分析复杂化,过度依赖优先级继承也是不可取的。
优先级继承的工作方式是暂时将互斥锁持有者的优先级提高到试图获取相同互斥锁的最高优先级任务的优先级。持有互斥锁的低优先级任务“继承”等待互斥锁的任务的优先级。如图所示。互斥锁持有者的优先级在返回互斥锁时自动重置为其原始值。
在这里插入图片描述

死锁 Deadlock(deadly embrace)

死锁是另一个使用互斥量的潜在陷阱
如果两个任务互相占有对方的资源,就会造成死锁
在这里插入图片描述
与优先级反转一样,避免死锁的最佳方法是在设计时考虑其发生的可能性,并设计系统以确保不会发生死锁。任务无限期地等待(没有超时)以获得互斥锁通常是一种不好的做法。相反,使用比预期的等待互斥锁的最长时间稍长的延时,然后在该时间内未能获取互斥量,可能会造成死锁。
一般在小系统中,死锁并不是大问题,设计者可以判断是否会发生死锁。

递归互斥

一个任务也有可能自己死锁,考虑如下情况:

  • 一个任务获得互斥量
  • 当此任务持有互斥量时,任务中调用其他函数再次请求同一个互斥量

在这个场景中,任务处于阻塞状态,等待互斥量返回,但是任务已经是互斥量的持有者。由于任务处于阻塞状态以等待自身,因此发生了死锁。

通过使用递归互斥代替标准互斥量,可以避免这种死锁。递归互斥量可以由同一个任务获取多次,并且仅在前一个递归互斥量被释放后才会返回。

API

xSemaphoreTakeRecursive()
xSemaphoreGiveRecursive()
示例

在这里插入图片描述

互斥量和任务调度

如果两个优先级不同的任务使用相同的互斥锁,那么FreeRTOS调度策略将清除任务的执行顺序;可以运行的最高优先级任务将被选择为进入运行状态的任务。例如,如果高优先级任务处于阻塞态以等待低优先级任务持有的互斥量,则高优先级任务将在低优先级任务返回互斥量时抢占低优先级任务。然后,高优先级任务将成为互斥锁持有者。

如果任务1和任务2具有相同的优先级,并且任务1处于阻止状态以等待任务2持有的互斥锁,则当任务2释放互斥量时,任务1不会抢占任务2。相反,任务2将保持在运行状态,任务1将简单地从阻塞状态移动到就绪状态。如图:
在这里插入图片描述
这是因为:

  • 两个任务优先级相同,
  • 如果一个任务在一个紧密的循环中使用互斥量,并且每次任务释放互斥量时都会发生上下文切换,那么该任务将只在短时间内保持运行状态。如果两个或多个任务在一个紧密的循环中使用同一个互斥锁,那么在任务之间快速切换将浪费处理时间。如图:
    在这里插入图片描述

Gatekeeper Tasks(看门任务)

看门任务提供了一种实现互斥的方法,而不存在优先级反转或死锁的风险。
看门任务是对资源具有唯一所有权的任务。只有看门任务才允许直接访问资源,任何其他需要访问资源的任务只能通过使用看门任务间接访问资源。

设置:
在这里插入图片描述
看门任务使用FreeRTOS队列对标准输出的访问。任务的内部实现不必考虑互斥,因为它是唯一允许直接访问标准输出的任务。看门任务的大部分时间都处于阻塞状态,等待消息到达队列。当消息到达时,看门任务只需将消息写入标准输出,然后返回到阻止状态以等待下一个消息。如图所示。
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
输出
在这里插入图片描述

发布了85 篇原创文章 · 获赞 17 · 访问量 1万+

猜你喜欢

转载自blog.csdn.net/lun55423/article/details/105723948