FreeRtos——错误排查

printf-stdarg.c

当调用标准 C 库函数时,栈空间使用量可能会急剧上升,特别是 IO 与字符串处理函数,比如sprintf()。在 FreeRTOS 下载包中有一个名为 printf-stdarg.c 的文件。这个文件实现了一个栈效率优化版的小型 sprintf(),可以用来代替标准 C 库函数版本。在大多数情况下,这样做可以使得调用 sprintf()及相关函数的任务对栈空间的需求量小很多。
printf-stdarg.c 源代码开放,但是为第三方所有。所以此源代码的 license 独立于FreeRTOS。具体的 license 条款包含在该源文件的起始部分。

栈溢出

uxTaskGetStackHighWaterMark() API 函数
每个任务都独立维护自己的栈空间,栈空间总量在任务创建时进行设定。
uxTaskGetStackHighWaterMark()主要用来查询指定任务的运行历史中,其栈空间还差多少就要溢出。这个值被称为栈空间的”高水线(High Water Mark)”。

unsigned portBASE_TYPE uxTaskGetStackHighWaterMark( xTaskHandle xTask );
xTask 		被查询任务的句柄——欲知如何获得任务句柄,详情请参见 API 函数
			xTaskCreate()的参数 pxCreatedTask。
			如果传入 NULL 句柄,则任务查询的是自身栈空间的高水线。
返回值 		任务栈空间的实际使用量会随着任务执行和中断处理过程上下浮动。
			uxTaskGetStackHighWaterMark()返回从任务启动执行开始的运行
			历史中,栈空间具有的最小剩余量。这个值即是栈空间使用达到最深
			时的剩下的未使用的栈空间。这个值越是接近 0,则这个任务就越是
			离栈溢出不远了。

运行时栈侦测 —— 概述
FreeRTOS 包含两种运行时栈侦测机制,由 FreeRTOSConfig.h 中的配置常量
configCHECK_FOR_STACK_OVERFLOW 进行控制。这两种方式都会增加上下切换开销。
栈溢出钩子函数(或称回调函数)由内核在侦测到栈溢出时调用。要使用栈溢出钩子函数,需要进行以下配置:
在 FreeRTOSConfig.h 中把 configCHECK_FOR_STACK_OVERFLOW 设为 1 或 2。

void vApplicationStackOverflowHook( xTaskHandle *pxTask, signed portCHAR *pcTaskName );
pxTask  	跟上面那个一样,不过这个是指针型的
pcTaskName 	xTaskCreate()创建时的那个文件描述符

栈溢出钩子函数只是为了使跟踪调试栈空间错误更容易,而无法在栈溢出时对其进行恢复。函数的入口参数传入了任务句柄和任务名,但任务名很可能在溢出时已经遭到破坏。
栈溢出钩子函数还可以在中断的上下文中进行调用。某些微控制器在检测到内存访问错误时会产生错误异常,很可能在内核调用栈溢出钩子函数之前就触发了错误异常中断。

运行时栈侦测 —— 方法 1

当 configCHECK_FOR_STACK_OVERFLOW 设置为 1 时选用方法 1。任务被交换出去的时候,该任务的整个上下文被保存到它自己的栈空间中。这时任务栈的使用应当达到了一个峰值。当 configCHECK_FOR_STACK_OVERFLOW 设为1 时,内核会在任务上下文保存后检查栈指针是否还指向有效栈空间。一旦检测到栈指针的指向已经超出任务栈的有效范围,栈溢出钩子函数就会被调用
方法 1 具有较快的执行速度,但栈溢出有可能发生在两次上下文保存之间,这种情
况不会被侦测到。

运行时栈侦测 —— 方法 2

将 configCHECK_FOR_STACK_OVERFLOW 设为 2 就可以选用方法 2。方法 2在方法 1 的基础上进行了一些补充。
**当创建任务时,任务栈空间中就预置了一个标记。方法 2 会检查任务栈的最后 20个字节,查看预置在这里的标记数据是否被覆盖。**如果最后 20 个字节的标记数据与预设值不同,则栈溢出钩子函数就会被调用。
方法 2 没有方法 1 的执行速度快,但测试仅仅 20 个字节相对来说也是很快的。这种方法应该可以侦测到任何时候发生的栈溢出,虽然理论上还是有可能漏掉一些情况,但这些情况几乎是不可能发生的。

其它常见错误

问题现象:在一个 Demo 应用程序中增加了一个简单的任务,导致应用程序崩溃
任务创建时需要在内存堆中分配空间。许多 Demo 应用程序定义的堆空间大小只够用于创建 Demo 任务——所以当任务创建完成后,就没有足够的剩余空间来增加其它的任务,队列或信号量。

空闲任务是在 vTaskStartScheduler()调用中自动创建的。如果由于内存不足而无法创建空闲任务,vTaskStartScheduler()会直接返回。在调用 vTaskStartScheduler()后加上一条空循环[for(;😉]可以使这种错误更加容易调试.

如果要添加更多的任务,可以增加内存堆空间大小,或是删掉一些已存在的 Demo任务。

问题现象:在中断中调用一个 API 函数,导致应用程序崩溃
除了具有后缀为”FromISR”函数名的 API 函数,千万不要在中断服务例程中调用其它 API 函数。

问题现象:有时候应用程序会在中断服务例程中崩溃
需要做的第一件事是检查中断是否导致了栈溢出。

在不同的移植平台和不同的编译器上,中断的定义和使用方法是不尽相同的——所以,需要做的第二件事是检查在中断服务例程中使用的语法,宏和调用约定是否符合Demo 程序的文档描述,以及是否和 Demp 程序中提供的中断服务例程范例相同。

如果应用程序工作在 Cotex M3 上,需要确定给中断指派优先级时,使用低优先级号数值表示逻辑上的高优先级中断,因为这种方式不太直观,所以很容易被忘记。一个比较常见的错误就是,在优先级高于 configMAX_SYSCALL_INTERRUPT_PRIORITY的中断中调用了 FreeRTOS API 函数。

问题现象:在启动第一个任务时,调度器就崩溃了

如果使用的是 ARM7,那么请确定调用 vTaskStartScheduler()时处理器处于管理模式(Supervisor mode)。最简单的方式就是在 main()之前的 C 启动态码中将处理器设置为管理模式。ARM7 的 Demo 应用程序就是这么做的。

如果处理器不在管理模式下,调度器是无法启动的

问题现象:临界区无法正确嵌套

除了 taskENTER_CRITICA()和 taskEXIT_CRITICAL(),千万不要在其它地方修改控制器的中断使能位或优先级标志。这两个宏维护了一个嵌套深度计数,所以只有当所有的嵌套调用都退出后计数值才会为 0,也才会使能中断。

问题现象:在调度器启动前应用程序就崩溃了
如果一个中断会产生上下文切换,则这个中断不能在调度器启动之前使能。这同样适用于那些需要读写队列或信号量的中断。在调度器启动之前,不能进行上下文切换。

还有一些 API 函数不能在调度器启动之前调用。在调用 vTaskStartScheduler()之前,最好是限定只使用创建任务,队列和信号量的 API 函数。

问题现象:在调度器挂起时调用 API 函数,导致应用程序崩溃
调用 vTaskSuspendAll()使得调度器挂起,而唤醒调度器调用 xTaskResumeAll()。
千万不要在调度器挂起时调用其它 API 函数。

问题现象:函数原型 pxPortInitialiseStack()导致编译失败
每种移植都需要定义一个对应的宏,以把正确的内核头文件加入到工程中。如果编译函数原型 pxPortInitialiseStack()时出错,这种现象基本上可以确定是因为没有正确定义相应的宏。请参见附录 4 以获得更多信息。

可以基本相应平台的 Demo 工程建立新的应用程序。这种方式就不用担心没有包含正确的文件,也不必担心没有正确地配置编译器选项。

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值