freeRTOS手册 第十二章 . 解决问题

22 篇文章 0 订阅
16 篇文章 6 订阅

如果我对本翻译内容享有所有权。允许任何人复制使用本文章,不会收取任何费用。如有平台向你收取费用与本人无任何关系

第十二章 . 解决问题

章节介绍和范围

本章重点关注FreeRTOS新用户可能遇到的共同问题。首先过去的一年请求源码功能,频率最高的3个问题已经解决;纠正中断优先级作业,栈溢出,不恰当的使用printf()。然后简略的用FAQ(常见问题)的格式指明了一些常见问题的可能原因和解决方案。
用configASSERT()可以提高立即捕获和识别许多源代码错误的能力。强烈建议在开发和调试的时候定义好configASSERT(),11.2章节有介绍configASSERT()。

中断优先级

注意这是引起的支持请求首要原因,在大部分版本FreeRTOS中,configASSERT()会立即捕获错误。
如果当前FreeRTOS支持中断嵌套,中断服务程序中有调用FreeRTOS函数,章节6.8中有说明,中断优先级需要小于configMAX_SYSCALL_INTERRUPT_PRIORITY。如果不这样做,就会影响关键代码,继而出现间歇性错误。
特别关注FreeRTOS运行在哪个处理器上:

  • 中断优先级默认有一个最高可能优先级,比如ARM Cortex或其他处理器上。在这些处理器上,使用FreeRTOS函数的中断优先级必须初始化。
  • 高优先级的数字化优先级任务会阻止低优先级的逻辑化任务,这看起来反直觉,且认人困惑。在ARM Cortex和一些其它处理器中就是这样。
  • 例如,一个中断优先级为5的任务可以被一个中断优先级4打断的处理器中。因此,如果设置configMAX_SYSCALL_INTERRUPT_PRIORITY为5,那么使用FreeRTOS函数的中断优先级就只能比5高。所以中断优先级5和6就是可用的,但中断优先级3就是不可用的。
  • 不同库的实现,特别是中断优先级的实现方法不同。然而,与ARM Cortex关联的库,中断优先级都是通过位移动后写入硬件寄存器。有些库会自动处理位移操作,有一些在写入硬件寄存器之前需要使用者自己进行移位操作。
  • 相同架构的不同库实现有不同的中断优先级值。比如Cortex-M处理器的一个制造商可能实现3个优先级位,另外一个制造商实现4位优先级位。
  • 中断的优先级位可以分为抢占优先级位和子优先级位。确保所有的位都指定为抢占优先级位,而不是子优先级位。
    在有的FreeRTOS版本,configMAX_SYSCALL_INTERRUPT_PRIORITY有另外一个名字configMAX_API_CALL_INTERRUPT_PRIORITY

堆溢出

堆溢出是第二个最常见的支持请求。FreeRTOS提供了多个功能帮助追踪和调试堆相关问题。

uxTaskGetStackHighWaterMark()函数

每个任务都有一个自己的堆,它的大小是在创建任务的时候指定的。uxTaskGetStackHighWaterMark()用于查询怎样关闭一个任务,其中分配给这个任务的堆已经溢出的情况。这个溢出后的值叫堆的高水位标志。

// uxTaskGetStackHighWaterMark()函数原型。列表173
UBaseType_t uxTaskGetStackHighWaterMark(TaskHandle_t xTask);

/* 参数
 * xTask: 将要被查询堆高水位值的任务句柄,可以查看xTaskCreate()函数的pxCreatedTask参数了解更多任务句柄相关信息。也可以传递一个NULL查询本任务的堆高水位标志。
 * 返回值: 任务执行过程中的堆扩张和收缩,以及处理中断过程中使用的堆数量。uxTaskGetStackHighWaterMark()返回自开始执行以来,堆保持的可以使用的最小数量。这个堆数量就是堆中因为任务使用最多的堆,而余下的数量。高水位标志越接近0,任务就越接近堆溢出。
 */ 

运行过程中的堆检查 Overview

FreeRTOS中运行过程中堆检查包含2个选项。它们由FreeRTOSConfig.h中的configCHECK_FOR_STACK_OVERFLOW常量控制,这个常量会在编译时确定。它们都会增加上下文切换的时间。
堆溢出勾子(堆溢出回调)是内核检测到堆溢出时的一个函数。要使用堆溢出勾子函数需要:

  1. 设置FreeRTOSConfig.h中的configCHECK_FOR_STACK_OVERFLOW为1或者2,具体下面会介绍。
  2. 提供勾子函数的实现,用列174中指定的函数名和原型。
// 堆勾子函数原型。列表174
void vApplicationStackOverflowHook(TaskHandle_t *pxTask, signed char *pcTaskName);

堆溢出勾子函数用于简单的追踪和调试堆错误,但当发生堆溢出时没有实际的方法恢复堆空间。这个函数的2个参数就是发生堆溢出的任务句柄和名字。
堆溢出勾子函数会在中断上下文中调用。
一些微处理器会在检测到内存访问错误时生成一个错误,可能会出现调用堆溢出勾子函数之前就触发了这个错误。

运行时堆检测 - 方法1

第一个方法是设置configCHECK_FOR_STACK_OVERFLOW为1。
每次任务切出会保存它的执行上下文到堆中。很可能这时堆的使用就会达到顶峰。当configCHECK_FOR_STACK_OVERFLOW设置为1时,内核就会在上下文被保存时检测堆指针是否在可用的范围。如果发现推指针不在可用范围就会运行堆溢出勾子函数。
第一个方法执行非常快,但会丢失上下文切换之前的堆溢出。

运行时堆检测 - 方法2

第二个方法除了会进行方法1中已经说明的检测,还会附加更多的检测。需要设置configCHECK_FOR_STACK_OVERFLOW为2。
当创建一个任务,它的堆中存放的是实际的数据。方法2就是通过检测堆中最后的20字节堆空间数据是不是被覆盖来判断是否溢出。如果最后的20个字节的数据都被其它数据替代就会调用堆溢出勾子函数。
第二个方法比第一个方法慢,但还是比较快的,因为只测试最后20个字节数据。大多数时候它能捕获所有堆溢出,但还是有可能会丢失一些堆溢出。

不适当的使用printf()和sprintf()

不适当的使用printf()是一个常见的错误,程序开发者未意识到的情况下,增加更多的printf()的调用,为了增加调试信息,加剧问题。
很多交叉编译工具有提供可以用于嵌入式printf()的实现,尽管这是一个小事。printf()的实现可能不是线程安全的,也可能不适用于中断服务程序中,而且依赖于具体输出到那里,可能也会花很多的时间执行。
必须特别注意,专门为小型嵌入系统实现的printf()是不可用的,而是需要自己重新实现一个printf()作为替代,因为:

  • 只是简单的调用一个printf()和sprintf()函数会严重增加应用程序执行的大小
  • printf()和sprintf()可能会调用malloc(),除非使用heap_3方案分配堆,不然内存的分配计划可能就不可用。可以查看2.2章节(内存分配计划)了解更多信息
  • printf()和sprintf()可以需要一个堆栈多次相比于其它的需求

Printf-stdarg.c

很多FreeRTOS的实例程序用了一个叫做printf-stdarg.c的文件,它提供了一个最小且栈有效的snprintf(),可以用于替代标准库中的snprintf()版本。大多数情况,它会占用一个非常小的栈用于spnrintf()相关的函数。
printf-stdarg.c提供了一个立即将printf()要输出的字符打印出来的机制,它很慢,但用到的栈空间会减少很多。
注意FreeRTOS上下载的printf-stdarg.c不是都包含有snprintf()实现。复制那些没有实现snprintf()的只是简单的忽略缓存大小参数,就像它只是sprintf()的映像一样。
printf-stdarg.c是开源的,但是第三方所有,因此只是FreeRTOS许可证的部分。源代码目录顶部中包含了许可证的详细信息。

其他常见错误

现象: 项目中增加一个任务就引起崩溃
创建一个任务需要用到堆中的内存。很多实例程序的任务分配得特别大。这样这个任务创建后,就没有足够的空间给更多的任务,队列,事件组或信号量使用了。
空闲任务甚至RTOS守护任务都是调用vTaskStartScheduler()的时候自动创建。vTaskStartScheduler()只有在没有足够的空间分配给创建的任务的时候返回。可以在vTaskStartScheduler()后面包含一个空循环方便调试。
为了可以增加更多的任务,可以增加物理的堆大小,或者及时删除不用的任务。查看2.2章节,内存分配调度的更多信息。

现象: 中断服务程序中使用一个函数引起程序崩溃
不要在中断服务程序中使用不带FromISR后缀的)函数。特别不要在中断中创建一个关键代码,除非使用中断安全的宏。查看6.2章节,在中断服务程序中使用FreeRTOS函数了解很多细节。
在支持中断嵌套的FreeRTOS版本,不要在中断中使用任何中断优先级高于configMAX_SYSCALL_INTERRUPT_PRIORITY的FreeRTOS函数。查看章节6.8,中断嵌套了解更多信息。

现象: 程序有时候在中断服务程序中崩溃
首先确定中断没有引起栈溢出。有些版本的FreeRTOS只检测任务栈是否溢出,不会检查中断。
不同版本和不同编译器使用不同的方式定义中断。因此,第二件事是检查中断服务程序中的语法,宏,调用协议是否精确,是否和这个版本中手册中说明一致,或者这个版本提供的实例程序一致。
如果处理器上的程序,以低优先级的数字化任务阻止高优先级逻辑化任务,请确保每个中断的优先级都考虑到这一点,因为这种设计太反人类了。如果处理器上运行的程序,每个中断默认优先级时最大可能优先级,请确保每个中断优先级不会都是默认优先级。查看章节6.8,中断嵌套,和章节12.2,中断优先级查看很多详情。

现象: 试图创建第一个任务的时候,调度器就崩溃
确保FreeRTOS的中断句柄已经安装。可以查看当前版本的FreeRTOS手册或实例程序获取更多信息。
有些处理器开启调度器之前需要进入专用模式。最简单进入这种专有模式的方式,就是在C启动中,在调用main()之前。

现象: 中断突然被禁用,关键部分没有正确的嵌套
如果调度器开启之前调用FreeRTOS函数,那么中断就会被禁用,不要在第一个任务开始执行之前再次使能中断。这样做可以避免调度器开启之前,调度器处于未明确状态时,系统初始化因为调用FreeRTOS函数引起系统崩溃。
除非调用taskENTER_CRITICAL()taskEXIT_CRITICAL(),不然不要用任何方法改变处理器中断的优先级标识位和使能位。这些宏会保存一个调用嵌套深度的值,确保中断只有在调用嵌套被完全释放为0的时候才使能。小心一些库函数自身会使能或失能中断。

现象: 开启调度器之前程序就崩溃
中断服务程序会潜在的引起上下文切换,所以不能在开启调度器之前允许中断的执行。这同样适用于试图发送或接收数据给FreeRTOS对象的中断。这里的中断对象可以是队列,信号量。开启调度器之前不允许上下文切换。
开启调度器之前很多函数不能使用。最好限制只用于创建一些对象,比如任务,队列和信号量。至于使用这些对象,只能在vTaskStartScheduler()调用后。

现象: 调度器暂停后使用FreeRTOS函数,或者在关键部分使用FreeRTOS函数引起程序崩溃
通过调用vTaskSuspendAll()暂停调度器和调用vTaskResumeAll()恢复调度器。通过调用taskENTER_CRITICAL()进入关键区域,调用taskEXIT_CRITICAL()`退出关键部分。
不要在调度器暂停时和关键代码中调用FreeRTOS函数。

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值