FreeRTOS —— 12.故障排除

12.1本章简介和范围

本章重点介绍了FreeRTOS新用户所遇到的最常见问题。 首先,它着眼于三个问题,这些问题已被证明是多年来最常见的支持请求来源。 错误的中断优先级分配,堆栈溢出以及对printf()的不正确使用。 然后以FAQ的形式简要介绍其他常见错误,可能的原因及其解决方案。

通过立即捕获并识别许多最常见的错误源,使用configASSERT()可以提高生产率。 强烈建议在开发或调试FreeRTOS应用程序时定义configASSERT()。 configASSERT()在第11.2节中介绍。

12.2中断优先级

注意:这是造成支持请求的第一大原因,在大多数端口中定义configASSERT()都会立即捕获该错误!

如果正在使用的FreeRTOS端口支持中断嵌套,并且中断的服务例程使用FreeRTOS API,则必须将中断的优先级设置为configMAX_SYSCALL_INTERRUPT_PRIORITY或以下,如第6.8节“中断嵌套”中所述。 否则,将导致无效的关键部分,进而导致间歇性故障。

如果在以下情况下的处理器上运行FreeRTOS,请特别注意:

  • 中断优先级默认为具有最高可能的优先级,某些ARM Cortex处理器(可能是其他处理器)就是这种情况。 在这样的处理器上,使用FreeRTOS API的中断的优先级不能保持未初始化。
  • 数值高的优先级数字表示逻辑低的中断优先级,这似乎是违反直觉的,因此会造成混乱。 同样,在ARM Cortex处理器以及其他处理器上也是如此。
  • 例如,在这样的处理器上,可以将优先级为5的中断本身中断为优先级为4的中断。因此,如果configMAX_SYSCALL_INTERRUPT_PRIORITY设置为5,则只能分配任何使用FreeRTOS API的中断 优先级数值大于或等于5。在这种情况下,中断优先级5或6将有效,但中断优先级3绝对无效。
  • 不同的库实现期望以不同的方式指定中断的优先级。 同样,这与以ARM Cortex处理器为目标的库特别相关,在这些库中,中断优先级在写入硬件寄存器之前先进行了位移位。 一些库将自己执行位移位,而另一些库期望在将优先级传递到库函数之前执行位移位。
  • 相同体系结构的不同实现将实现不同数量的中断优先级位。 例如,一个制造商的Cortex-M处理器可以实现3个优先级位,而另一制造商的Cortex-M处理器可以实现4个优先级位。
  • 定义中断优先级的位可以分为定义抢占优先级的位和定义子优先级的位。 确保将所有位分配给指定抢占优先级,以便不使用子优先级。

在某些FreeRTOS端口中configMAX_SYSCALL_INTERRUPT_PRIORITY具有备用名称configMAX_API_CALL_INTERRUPT_PRIORITY。

12.3堆栈溢出

堆栈溢出是支持请求的第二大最常见来源。 FreeRTOS提供了多种功能来帮助捕获和调试与堆栈相关的问题1

uxTaskGetStackHighWaterMark()API函数

每个任务维护自己的堆栈,创建任务时将指定堆栈的总大小。 uxTaskGetStackHighWaterMark()用于查询任务溢出分配给它的堆栈空间有多接近。 此值称为堆栈“高水位标记”

在这里插入图片描述

Parameter Name/ Returned ValueDescription
xTask正在查询其堆栈高水位标记的任务(主题任务)的句柄—有关获取任务句柄的信息,请参阅xTaskCreate()API函数的pxCreatedTask参数。任务可以通过传递NULL代替有效的任务句柄来查询自己的堆栈高水位线。
Returned value任务执行和处理中断时,任务使用的堆栈数量会增加和减少。uxTaskGetStackHighWaterMark()返回自任务开始执行以来可用的最小剩余堆栈空间量。 这是当堆栈使用量达到最大(或最深)值时仍未使用的堆栈数量。 高水位标记越接近零,任务越接近其堆栈溢出。

运行时堆栈检查-概述

FreeRTOS包括两种可选的运行时堆栈检查机制。 这些由FreeRTOSConfig.h中的configCHECK_FOR_STACK_OVERFLOW编译时配置常量控制。 两种方法都增加了执行上下文切换所需的时间。

堆栈溢出钩子(或堆栈溢出回调)是内核在检测到堆栈溢出时调用的函数。 要使用堆栈溢出钩子函数:

  1. 如以下小节所述,在FreeRTOSConfig.h中将configCHECK_FOR_STACK_OVERFLOW设置为1或2。
  2. 使用清单174中所示的确切函数名称和原型,提供钩子函数的实现。
    在这里插入图片描述

提供了堆栈溢出钩子以使捕获和调试堆栈错误更容易,但是没有真正的方法可以从堆栈溢出中恢复。 该函数的参数将在堆栈中溢出堆栈的任务的句柄和名称传递给钩子函数。

堆栈溢出钩子从中断的上下文中被调用。

一些微控制器在检测到错误的内存访问时会生成故障异常,并且有可能在内核有机会调用堆栈溢出挂钩函数之前触发故障。

运行时堆栈检查-方法1

当configCHECK_FOR_STACK_OVERFLOW设置为1时,选择方法1。

每次交换任务时,任务的整个执行上下文都会保存到其堆栈中。 这可能是堆栈使用量达到峰值的时间。 当configCHECK_FOR_STACK_OVERFLOW设置为1时,内核会在保存上下文后检查堆栈指针是否仍在有效堆栈空间内。 如果发现堆栈指针超出其有效范围,则调用堆栈溢出钩子。

方法1可以快速执行,但是会丢失上下文切换之间发生的堆栈溢出。

运行时堆栈检查-方法2

方法2对已经针对方法1进行的描述执行其他检查。当configCHECK_FOR_STACK_OVERFLOW设置为2时,将选择该方法。

创建任务时,其堆栈将填充有已知模式。 方法2测试任务堆栈空间的最后有效20个字节,以验证此模式未被覆盖。 如果20个字节中的任何一个已从其预期值更改,则调用堆栈溢出挂钩函数。

方法2的执行速度不如方法1快,但仍然相对较快,因为仅测试了20个字节。 最有可能的是,它将捕获所有堆栈溢出; 但是,有可能(但极不可能)丢失一些溢出。

12.4不当使用printf()和sprintf()

错误地使用printf()是常见的错误来源,并且不知道这一点,对于应用程序开发人员来说,通常在其后再添加更多对printf()的调用以帮助调试,并且这样做实际上使问题更加严重。

许多交叉编译器供应商将提供适用于小型嵌入式系统的printf()实现。 即使是这种情况,该实现可能也不是线程安全的,可能不适合在中断服务程序内部使用,并且根据输出的定向位置,花费相对较长的时间才能执行。

如果没有专门为小型嵌入式系统设计的printf()实现,而必须使用通用的printf()实现,则必须格外小心,例如:

  • 仅包含对printf()或sprintf()的调用会大大增加应用程序可执行文件的大小。
  • printf()和sprintf()可能会调用malloc(),如果正在使用非heap_3的内存分配方案,则该调用可能无效。 有关更多信息,请参见第2.2节“示例内存分配方案”。
  • printf()和sprintf()可能需要的堆栈比原来需要的堆栈大很多倍。

Printf-stdarg.c

许多FreeRTOS演示项目都使用名为printf-stdarg.c的文件,该文件提供了sprintf()的最小化和堆栈高效的实现,可以代替标准库版本使用。 在大多数情况下,这将允许将较小的堆栈分配给调用sprintf()和相关函数的每个任务。

printf-stdarg.c还提供了一种机制,用于逐个字符地将printf()输出定向到端口,该机制虽然速度较慢,但可以进一步减少堆栈使用量。

请注意,并非FreeRTOS下载中包含的printf-stdarg.c的所有副本都实现了snprintf()。 未实现snprintf()的副本直接忽略缓冲区大小参数,因为它们直接映射到sprintf()。

Printf-stdarg.c是开源的,但归第三方所有,因此与FreeRTOS分开许可。 许可条款包含在源文件的顶部。

12.5其他常见的错误来源

症状:向演示添加简单任务会导致演示崩溃

创建任务需要从堆中获取内存。 许多演示应用程序项目将堆的大小确定为足以创建演示任务的大小,在创建任务之后,将没有足够的堆剩余空间来添加任何其他任务,队列,事件组或信号量 。

调用vTaskStartScheduler()时,会自动创建空闲任务以及可能的RTOS守护程序任务。 仅当没有足够的堆内存来创建这些任务时,vTaskStartScheduler()才会返回。 包括一个空循环[for(;;); 调用vTaskStartScheduler()之后,可以更轻松地调试此错误。

为了能够添加更多任务,请增加堆大小或删除一些现有的演示任务。 有关更多信息,请参见第2.2节“示例内存分配方案”。

症状:在中断中使用API函数会导致应用程序崩溃

除非API函数的名称以“ … FromISR()”结尾,否则请勿在中断服务例程中使用API函数。 特别是,除非使用中断安全宏,否则不要在中断内创建关键部分。 有关更多信息,请参见第6.2节“从ISR使用FreeRTOS API”。

在支持中断嵌套的FreeRTOS端口中,请勿在已被分配高于configMAX_SYSCALL_INTERRUPT_PRIORITY的中断优先级的中断中使用任何API函数。 有关更多信息,请参见第6.8节“中断嵌套”。

症状:有时应用程序在中断服务程序中崩溃

首先要检查的是中断不会引起堆栈溢出。 一些端口仅检查任务中的堆栈溢出,而不检查中断中的堆栈溢出。

各个端口之间以及编译器之间,中断的定义和使用方式不同。 因此,要检查的第二件事是,中断服务例程中使用的语法,宏和调用约定与所使用的端口所提供的文档页面上的描述完全相同,并且与该端口所提供的演示应用程序中所显示的完全相同。

如果应用程序在使用低优先级数字表示逻辑高优先级的处理器上运行,请确保将分配给每个中断的优先级考虑在内,因为这似乎违反直觉。 如果应用程序在将每个中断的优先级默认为最大可能优先级的处理器上运行,请确保不要将每个中断的优先级保留为其默认值。 有关更多信息,请参见第6.8节“中断嵌套”和第12.2节“中断优先级”。

症状:尝试启动第一个任务时,调度程序崩溃

确保已安装FreeRTOS中断处理程序。 有关使用中的FreeRTOS端口的信息,请参阅文档页面,有关示例,请参阅为该端口提供的演示应用程序。

在启动调度程序之前,某些处理器必须处于特权模式。 实现此目的的最简单方法是在调用main()之前,将处理器置于C启动代码内的特权模式。

症状:中断被意外禁用,或者关键部分无法正确嵌套

如果在调度程序启动之前调用了FreeRTOS API函数,则将有意使中断保持禁用状态,直到第一个任务开始执行之前,中断才会再次启用。 这样做是为了保护系统免于因系统初始化期间,启动调度程序之前以及调度程序可能处于不一致状态而试图使用FreeRTOS API函数的中断所导致的崩溃。

除了调用taskENTER_CRITICAL()和taskEXIT_CRITICAL()以外,不要使用任何其他方法更改微控制器的中断允许位或优先级标志。 这些宏会保留其调用嵌套深度的计数,以确保仅在调用嵌套已完全展开为零时才再次启用中断。 请注意,某些库函数本身可能会启用和禁用中断。

症状:即使在调度程序启动之前,应用程序也会崩溃

在调度程序启动之前,不允许执行可能导致上下文切换的中断服务例程。 这同样适用于任何尝试发送到FreeRTOS对象或从FreeRTOS对象接收或接收的中断服务例程,例如队列或信号量。 直到调度程序启动后,上下文切换才能发生。

在调度程序启动之后,才能调用许多API函数。 在调用vTaskStartScheduler()之后,最好将API的使用限制为仅创建诸如任务,队列和信号量之类的对象,而不是使用这些对象。

症状:在调度程序挂起时或从关键部分内部调用API函数会导致应用程序崩溃

调度程序通过调用vTaskSuspendAll()挂起,并通过调用xTaskResumeAll()恢复(未挂起)。 通过调用taskENTER_CRITICAL()进入关键部分,通过调用taskEXIT_CRITICAL()退出关键部分。

在调度程序挂起时或在关键部分内部,请勿调用API函数。


  1. 这些功能在FreeRTOS Windows端口中不可用。 ↩︎

  • 1
    点赞
  • 17
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值