搜索
查看: 3542|回复: 8

[原创] RTOS 系统线程栈溢出检查实现方式【对比 FreeRTOS 和 RT-Thread】

[复制链接]

该用户从未签到

5

主题

23

帖子

0

蝴蝶豆

初级会员

最后登录
2019-7-17
发表于 2019-6-27 09:09:24 | 显示全部楼层 |阅读模式
本帖最后由 murphyzhao 于 2019-6-28 09:20 编辑

RTOS 系统线程栈溢出检查实现方式

在实现的时候主要考量哪些因素?我只想到下面两个方向,说说我自己的理解:

  • 线程栈溢出检查方式
  • 什么情况、什么时候检查线程栈

1. RT-Thread 线程栈的检查

1.1 线程栈溢出检查方式分析
线程栈溢出检查函数:
  1. static void _rt_scheduler_stack_check(struct rt_thread *thread)
  2. {
  3.     RT_ASSERT(thread != RT_NULL);

  4. #if defined(ARCH_CPU_STACK_GROWS_UPWARD)
  5.         if (*((rt_uint8_t *)((rt_ubase_t)thread->stack_addr + thread->stack_size - 1)) != '#' ||
  6. #else
  7.     if (*((rt_uint8_t *)thread->stack_addr) != '#' ||
  8. #endif
  9.         (rt_ubase_t)thread->sp <= (rt_ubase_t)thread->stack_addr ||
  10.         (rt_ubase_t)thread->sp >
  11.         (rt_ubase_t)thread->stack_addr + (rt_ubase_t)thread->stack_size)
  12.     {
  13.         rt_ubase_t level;

  14.         rt_kprintf("thread:%s stack overflow\n", thread->name);
  15. #ifdef RT_USING_FINSH
  16.         {
  17.             extern long list_thread(void);
  18.             list_thread();
  19.         }
  20. #endif
  21.         level = rt_hw_interrupt_disable();
  22.         while (level);
  23.     }
  24. #if defined(ARCH_CPU_STACK_GROWS_UPWARD)
  25.     else if ((rt_ubase_t)thread->sp > ((rt_ubase_t)thread->stack_addr + thread->stack_size))
  26.     {
  27.         rt_kprintf("warning: %s stack is close to the top of stack address.\n",
  28.                    thread->name);
  29.     }
  30. #else
  31.     else if ((rt_ubase_t)thread->sp <= ((rt_ubase_t)thread->stack_addr + 32))
  32.     {
  33.         rt_kprintf("warning: %s stack is close to end of stack address.\n",
  34.                    thread->name);
  35.     }
  36. #endif
  37. }
复制代码

从上面的代码中可以直观地看到,RT-Thread 线程栈的检查方式分为两种情况:

  • 栈向下增长
  • 栈向上增长


判断栈的增长方向这个是必须的,因为系统要适配不同的 CPU 架构,而有些 CPU 架构的栈增长方向是可配置的。

虽然根据栈的增长方向分为两种检查方式,但核心的检查功能确是一样的,通过判断栈尾地址内容是否为 "#" 来进行初步溢出判断,如果栈尾地址内容不是 "#",那么栈溢出。另外,代码还配合检查了线程栈帧地址是否超出栈的地址空间范围来辅助判断。

【这里感觉不严谨,如果线程栈刚好用到这个末尾,那就会误判;另外,无法检查其它任务改写栈的问题】


1.2. 什么时候检查

通常的做法就是在调度器切换线程的时候检查,调度器简化代码如下:

  1. void rt_schedule(void)
  2. {
  3.     rt_base_t level;
  4.     struct rt_thread *to_thread;
  5.     struct rt_thread *from_thread;

  6.     /* disable interrupt */
  7.     level = rt_hw_interrupt_disable();
  8.    
  9.     to_thread = _get_highest_priority_thread(&highest_ready_priority);
  10.    
  11.     from_thread         = rt_current_thread;
  12.     rt_current_thread   = to_thread;
  13.    
  14.     _rt_scheduler_stack_check(to_thread);
  15.    
  16.     rt_hw_interrupt_enable(level);
  17. }
复制代码


从上面的代码可以看到 RT-Thread 在调度器中调用 _rt_scheduler_stack_check(to_thread); 函数检查 to_thread 线程的线程栈。
【有个疑问:为什么仅仅检查 to_thread,不同时检查 from_thread?】

2. FreeRTOS 线程栈检查
1.1 线程栈溢出检查方式分析
线程栈溢出检查函数:


FreeRTOS 的栈溢出检查就有了一个特点,首先使用了 taskFIRST_CHECK_FOR_STACK_OVERFLOW 函数,如下所示:(这里粘贴的代码实在看不下去,放图吧)

carbon (2).png

然后,使用了 taskSECOND_CHECK_FOR_STACK_OVERFLOW 函数:

carbon (3).png

从上面的两段代码,可以看到,FreeRTOS 的栈溢出检查分为第一次和第二次,但同 RT-Thread 类似,也分了栈的增长方向。
第一次检查,仅仅判断栈顶指针是否超出了栈尾,具体的栈溢出操作实现交给了 vApplicationStackOverflowHook 回调函数。
第二次检查,通过内存比较函数,比较 pcEndOfStack 栈尾 20 个字节大小的空间是否与栈的填充值 tskSTACK_FILL_BYTE 匹配(填充值为 0xa5)。

当然,FreeRTOS 关键起作用的是第二次检查,看着相对还是比较靠谱的(相对 RT-Thread 仅检查一个字节)。FreeRTOS 这种检查方式肯定是与其线程栈的分配方式有关系的,因为它检查的是栈尾部的 20 字节,也就是说,FreeRTOS 为每一个线程栈都预留了 20 字节的空档,造成一部分空间的浪费。

1.2. 什么时候检查

当然,FreeRTOS 也是在调度器切换任务的时候检查。直接上代码了,如下:

  1. void vTaskSwitchContext( void )
  2. {
  3.         /* Check for stack overflow, if configured. */
  4.         taskFIRST_CHECK_FOR_STACK_OVERFLOW();
  5.         taskSECOND_CHECK_FOR_STACK_OVERFLOW();

  6.         /* Select a new task to run using either the generic C or port
  7.         optimised asm code. */
  8.         taskSELECT_HIGHEST_PRIORITY_TASK();
  9.         traceTASK_SWITCHED_IN();
  10. }
复制代码


如上,注意到一个问题,FreeRTOS 的栈检查,貌似是检查的 from_thread,而不是像 RT-Thread 一样检查的 to_thread?

总结

不知道设计栈溢出检查的时候有哪些考究,RT-Thread 和 FreeRTOS 都没有对同时检查 from_thread 和 to_thread。这两个系统的栈检查方式差不多,相对来说,感觉 FreeRTOS 的更严谨些,但都不能完全检查到,也没法检查线程栈被其它任务错误修改的情况。

----

如上分析,如有问题,请拍砖




评分

参与人数 1ST金币 +10 收起 理由
STMCU + 10 谢谢分享

查看全部评分

回复

使用道具 举报

该用户从未签到

5

主题

23

帖子

0

蝴蝶豆

初级会员

最后登录
2019-7-17
 楼主| 发表于 2019-6-28 09:21:15 | 显示全部楼层
这里上传代码还是很费劲,整理出来的代码没法看,就直接上图了,比较直观
回复 支持 反对

使用道具 举报

该用户从未签到

20

主题

1628

帖子

5

蝴蝶豆

论坛元老

最后登录
2022-6-7
发表于 2019-6-28 10:32:15 | 显示全部楼层
感谢楼主的分享,还没研究过嵌入式OS,支持一下楼主
回复 支持 反对

使用道具 举报

该用户从未签到

6

主题

1029

帖子

133

蝴蝶豆

金牌会员

最后登录
2021-4-24
发表于 2019-6-28 14:35:20 | 显示全部楼层
学习下
回复

使用道具 举报

该用户从未签到

0

主题

7

帖子

0

蝴蝶豆

新手上路

最后登录
2019-10-28
发表于 2019-6-28 18:06:59 | 显示全部楼层
楼主的代码截的很好看啊,怎么弄的?
回复 支持 反对

使用道具 举报

该用户从未签到

5

主题

23

帖子

0

蝴蝶豆

初级会员

最后登录
2019-7-17
 楼主| 发表于 2019-6-28 18:28:47 | 显示全部楼层
梦中的飞鸿 发表于 2019-6-28 18:06
楼主的代码截的很好看啊,怎么弄的?

用这个工具,非常好用 http://blog.csdn.net/u012349679/article/details/89787697
回复 支持 反对

使用道具 举报

该用户从未签到

5

主题

23

帖子

0

蝴蝶豆

初级会员

最后登录
2019-7-17
 楼主| 发表于 2019-6-29 09:04:52 | 显示全部楼层
梦中的飞鸿 发表于 2019-6-28 18:06
楼主的代码截的很好看啊,怎么弄的?

这个图是使用 Carbon 在线工具,使用参考 http://blog.csdn.net/u012349679/article/details/89787697 这个
回复 支持 反对

使用道具 举报

该用户从未签到

5

主题

23

帖子

0

蝴蝶豆

初级会员

最后登录
2019-7-17
 楼主| 发表于 2019-6-29 09:11:27 | 显示全部楼层
梦中的飞鸿 发表于 2019-6-28 18:06
楼主的代码截的很好看啊,怎么弄的?

Carbon 在线工具,百度好像直接搜不到,我还贴不了链接

放个图总可以吧
图片方式分享代码.png
回复 支持 反对

使用道具 举报

该用户从未签到

0

主题

5

帖子

0

蝴蝶豆

初级会员

最后登录
2020-6-15
发表于 2020-6-12 16:59:54 | 显示全部楼层
军涛,想不想干了,敢说RTT不好?价值观有问题哈哈哈哈哈
回复 支持 反对

使用道具 举报

您需要登录后才可以回帖 注册/登录

本版积分规则

关闭

站长推荐上一条 /3 下一条

Archiver|手机版|小黑屋|论坛-意法半导体STM32/STM8技术社区

GMT+8, 2024-4-27 06:08 , Processed in 1.202475 second(s), 45 queries .

Powered by Discuz! X3.4

Copyright © 2001-2024, Tencent Cloud.

快速回复 返回顶部 返回列表