你的浏览器版本过低,可能导致网站不能正常访问!
为了你能正常使用网站功能,请使用这些浏览器。

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

[复制链接]
murphyzhao 发布时间:2019-6-27 09:09
本帖最后由 murphyzhao 于 2019-6-28 09:20 编辑 , ], i0 Q4 a; `" {) m- A% L; h3 o
  O* Q7 P0 E9 _( n; r, ?2 A) a6 m
RTOS 系统线程栈溢出检查实现方式! s+ n; K- Q# g1 T- I* K

" _+ a) e+ F6 ~在实现的时候主要考量哪些因素?我只想到下面两个方向,说说我自己的理解:0 _$ y5 m# y& R& a" W* P0 s8 W
. C2 q" s0 X+ ], W: Y
  • 线程栈溢出检查方式
  • 什么情况、什么时候检查线程栈
    ; E% z/ r8 S" M- ^3 r
- t2 @+ V2 A5 x9 r* r9 G
1. RT-Thread 线程栈的检查; e5 R3 W! _4 H& e
' s2 R+ i- M) R9 V; ~; O4 b
1.1 线程栈溢出检查方式分析; Q9 P% T8 N5 D% ~; g
线程栈溢出检查函数:; T( j$ o: T& L1 q6 i) C
  1. static void _rt_scheduler_stack_check(struct rt_thread *thread)& u- P  R5 e- K: Q/ N& u
  2. {+ J! |8 Z7 n2 E
  3.     RT_ASSERT(thread != RT_NULL);4 I$ Z) |6 h# N- K2 s( M
  4. 0 A) n' v4 i, {% q4 k
  5. #if defined(ARCH_CPU_STACK_GROWS_UPWARD)
    0 O% M$ ?. ]4 H7 o$ _9 f
  6.         if (*((rt_uint8_t *)((rt_ubase_t)thread->stack_addr + thread->stack_size - 1)) != '#' ||
    1 k# k( ^+ h2 R1 A8 H0 V" O
  7. #else  K$ ?% G  D  P4 [2 c1 Q0 p
  8.     if (*((rt_uint8_t *)thread->stack_addr) != '#' ||7 K0 L* B3 b! c7 a
  9. #endif
    3 ~; P! K1 A  E. F" e
  10.         (rt_ubase_t)thread->sp <= (rt_ubase_t)thread->stack_addr ||
    + {5 t' A8 _' u" O
  11.         (rt_ubase_t)thread->sp >
    8 K6 G. [1 [- m9 u% t0 s
  12.         (rt_ubase_t)thread->stack_addr + (rt_ubase_t)thread->stack_size)1 c3 S! u! D2 O) I" L9 D
  13.     {
    . y9 @3 A7 l6 B3 f7 W1 }
  14.         rt_ubase_t level;# a% Z$ _+ {: g  _: T. E
  15. 1 J& g4 A% w( ~  p. a2 i# b( Z
  16.         rt_kprintf("thread:%s stack overflow\n", thread->name);$ w& `8 E9 x4 k8 H
  17. #ifdef RT_USING_FINSH$ K" C/ @$ w2 ?9 D/ T% S: W
  18.         {
    ( ?$ }  T, `/ a+ C
  19.             extern long list_thread(void);
    # O  Z8 B- S! |% {) C
  20.             list_thread();- F3 L' k, Z% h3 T
  21.         }
    2 u7 o" b1 J' h! U; s
  22. #endif
    " S; |. Q7 _& L
  23.         level = rt_hw_interrupt_disable();
    3 i7 C+ O. z6 R2 v$ k7 s; ~
  24.         while (level);
    5 T6 l1 }! i; l- T
  25.     }
    ; G( a5 {* q. ?8 d* L! F" _9 @
  26. #if defined(ARCH_CPU_STACK_GROWS_UPWARD)
    3 _  r" N3 l+ ]& ?) P' g! ~
  27.     else if ((rt_ubase_t)thread->sp > ((rt_ubase_t)thread->stack_addr + thread->stack_size))- p( `8 m2 [! i. w
  28.     {
    - G" i. D: V$ ~# {1 g
  29.         rt_kprintf("warning: %s stack is close to the top of stack address.\n",
    3 K  I. F% u+ f' y( a- W- q2 ?
  30.                    thread->name);
    . R  u0 R7 Z+ f. v% q1 C
  31.     }/ I6 ]9 T: x  L. d- Q: z
  32. #else
    0 Z3 J3 f' _% u( ?) h" w
  33.     else if ((rt_ubase_t)thread->sp <= ((rt_ubase_t)thread->stack_addr + 32))
    2 [' i" G! a: \: n/ E
  34.     {
    : A; v. \. h5 T
  35.         rt_kprintf("warning: %s stack is close to end of stack address.\n",
    * d7 F) T: u, w7 F
  36.                    thread->name);
    " X' k/ u! [5 ^  C( B# W9 q6 K! }3 ^
  37.     }
    0 S6 s1 G0 U+ p, U% Q" W
  38. #endif
    : H! a3 u1 P( Y+ N2 A  _) h
  39. }
复制代码
- t9 C& L8 T8 Z* ?/ i! Z
从上面的代码中可以直观地看到,RT-Thread 线程栈的检查方式分为两种情况:9 J9 q& Y- b7 ~% \, e4 I  T  ]2 \
- P) l) Q2 X" t7 n
  • 栈向下增长
  • 栈向上增长8 c1 {3 e' H$ L6 \, O
" k3 `! s' A3 ^9 r$ ^, k0 G

4 G( s! z# e3 V$ E0 t6 }- m5 j判断栈的增长方向这个是必须的,因为系统要适配不同的 CPU 架构,而有些 CPU 架构的栈增长方向是可配置的。- f# K& F5 [% i0 n% ~5 i
7 c9 ?6 S, a  R2 h% X) j, {
虽然根据栈的增长方向分为两种检查方式,但核心的检查功能确是一样的,通过判断栈尾地址内容是否为 "#" 来进行初步溢出判断,如果栈尾地址内容不是 "#",那么栈溢出。另外,代码还配合检查了线程栈帧地址是否超出栈的地址空间范围来辅助判断。
3 I- d5 R' g/ k% V0 u  z8 Z, @' r
4 y% h2 E/ n$ ~: v) [8 a# S【这里感觉不严谨,如果线程栈刚好用到这个末尾,那就会误判;另外,无法检查其它任务改写栈的问题】
" Y: ~3 ?# s% t# T# W! w' A
6 `2 O. R6 ?4 G
/ I* Y  s, e3 R% H5 ^: b1.2. 什么时候检查
+ u, ]4 u+ a5 g: G7 F: L" x. I- ~9 `! H
# ?8 K2 ~+ T; K3 t通常的做法就是在调度器切换线程的时候检查,调度器简化代码如下:8 j: G9 C7 W4 e- H7 f) x

. h  Z2 l! x3 V# [
  1. void rt_schedule(void)
    " u+ H" _9 e% k) g
  2. {3 @, H0 l1 c" t
  3.     rt_base_t level;% N/ |% O; n% K
  4.     struct rt_thread *to_thread;, A! B% [) P* h8 W
  5.     struct rt_thread *from_thread;
    2 e$ b+ t7 d/ |, Q/ q4 r& E( y
  6. 0 h$ {! F* i. |4 `' s
  7.     /* disable interrupt */
    2 `+ S7 V& ]( X& F: K7 ?0 ^1 J
  8.     level = rt_hw_interrupt_disable();
    1 P/ Y5 m8 W8 U* \/ ?
  9.    
    7 @0 ]+ t1 ~( e4 A+ }0 a# ^
  10.     to_thread = _get_highest_priority_thread(&highest_ready_priority);2 P& O5 Z5 X  }) s- @8 m
  11.    
    8 S* T2 P* u8 j! Y3 C* G
  12.     from_thread         = rt_current_thread;
    # ~% Q& k- v8 q
  13.     rt_current_thread   = to_thread;8 f- f+ Z) @, @. X
  14.    
      }2 W0 D2 e# v* j# K1 E6 `6 e5 j) c% [
  15.     _rt_scheduler_stack_check(to_thread);
    ( l9 z$ O2 I% ~/ j
  16.    
    4 I# M, _$ o( y! V6 L& C
  17.     rt_hw_interrupt_enable(level);
    % f% T1 v6 W* S* w6 E0 m
  18. }
复制代码
% c. w8 |2 b% L+ V+ c$ w" y

( |$ i* A+ f) M* s" ~8 S' g- v1 s* V从上面的代码可以看到 RT-Thread 在调度器中调用 _rt_scheduler_stack_check(to_thread); 函数检查 to_thread 线程的线程栈。
5 n; e; p- b9 k. q1 p( i5 ?% ]/ ^【有个疑问:为什么仅仅检查 to_thread,不同时检查 from_thread?】
) z' `( I; i9 ]  x& t  o) e3 U7 X  k) G
) ~1 F8 P0 t: e7 F: {2 D2. FreeRTOS 线程栈检查
4 a( P3 B5 w% Q  N. y1.1 线程栈溢出检查方式分析
0 h! p. p: u' v1 l线程栈溢出检查函数:$ K- N0 R9 \5 g2 v/ d7 f8 d8 Z  N. t7 V. v
5 d9 C$ {# G5 N( i1 Z2 W7 f

) l$ C4 u! a: {8 v& xFreeRTOS 的栈溢出检查就有了一个特点,首先使用了 taskFIRST_CHECK_FOR_STACK_OVERFLOW 函数,如下所示:(这里粘贴的代码实在看不下去,放图吧)
& P' f8 l: ?2 V, g1 f+ ~4 _
) P0 V/ Y8 A7 a% ` carbon (2).png
2 M& K* u3 E# Q% i9 C* }/ a' p! O! W5 k
然后,使用了 taskSECOND_CHECK_FOR_STACK_OVERFLOW 函数:8 b0 G, e: z0 a: U8 j" ]
: m' W* V6 B2 {: z' s
carbon (3).png
( u5 j4 h3 U  @" o' ?: D
% a, H1 ]& G# r  v) |! Q从上面的两段代码,可以看到,FreeRTOS 的栈溢出检查分为第一次和第二次,但同 RT-Thread 类似,也分了栈的增长方向。" q  t; h  Q8 U7 ]1 a( Z
第一次检查,仅仅判断栈顶指针是否超出了栈尾,具体的栈溢出操作实现交给了 vApplicationStackOverflowHook 回调函数。8 p4 K0 N3 c' O
第二次检查,通过内存比较函数,比较 pcEndOfStack 栈尾 20 个字节大小的空间是否与栈的填充值 tskSTACK_FILL_BYTE 匹配(填充值为 0xa5)。, J: ^* I5 a) \9 v+ Y8 x7 c

' y/ \& Q$ D. F: N) G- l/ D+ {8 ~当然,FreeRTOS 关键起作用的是第二次检查,看着相对还是比较靠谱的(相对 RT-Thread 仅检查一个字节)。FreeRTOS 这种检查方式肯定是与其线程栈的分配方式有关系的,因为它检查的是栈尾部的 20 字节,也就是说,FreeRTOS 为每一个线程栈都预留了 20 字节的空档,造成一部分空间的浪费。
  h# [" x) l! T8 o% L2 a, d
" f. ~: A, J( x' u1.2. 什么时候检查
+ {3 }* C# l7 j+ K
; x& B8 ^9 g+ p3 f+ b) o; e6 {0 o当然,FreeRTOS 也是在调度器切换任务的时候检查。直接上代码了,如下:3 s, C( A7 H( P7 h" B

6 D; F* @4 U6 e5 G
  1. void vTaskSwitchContext( void )4 z' S3 n+ t' ?# T$ S
  2. {
    1 X+ O# f& a  C2 Z. a- O2 x4 W
  3.         /* Check for stack overflow, if configured. */! u* D' G0 C! A$ k8 i7 Z
  4.         taskFIRST_CHECK_FOR_STACK_OVERFLOW();
    1 ~2 a5 m% k" x- |( R
  5.         taskSECOND_CHECK_FOR_STACK_OVERFLOW();
    2 \: t, `% T' O4 D! B3 S' I: d0 c( x; i
  6. 7 o8 _8 a6 {, I8 |
  7.         /* Select a new task to run using either the generic C or port! e+ k9 y- n/ u5 y
  8.         optimised asm code. */4 U4 c9 ~/ T; q% ?+ F0 B/ M
  9.         taskSELECT_HIGHEST_PRIORITY_TASK();0 M% r5 P. D0 Z! L4 q' ]
  10.         traceTASK_SWITCHED_IN();
    / R2 ?- \/ X# k3 N( A
  11. }
复制代码

5 I4 Z+ s  F7 N) a7 o# E- A5 H& V( O, u! m: {3 h
如上,注意到一个问题,FreeRTOS 的栈检查,貌似是检查的 from_thread,而不是像 RT-Thread 一样检查的 to_thread?* H( w- R6 }& D$ F" ~& Q$ [' i

4 ?; N5 J0 f, R3 V/ x总结
* m  b5 F( Z$ Z5 _7 {( ~* u, R! y# v# w# v) u1 j3 l6 P( V- w. m3 U+ N
不知道设计栈溢出检查的时候有哪些考究,RT-Thread 和 FreeRTOS 都没有对同时检查 from_thread 和 to_thread。这两个系统的栈检查方式差不多,相对来说,感觉 FreeRTOS 的更严谨些,但都不能完全检查到,也没法检查线程栈被其它任务错误修改的情况。
, Y+ M2 I- A; O9 f5 }, }) M5 ~1 A$ k5 q: G$ m& ]
----
; ]9 m2 x1 }/ K7 D! M, g. J* n7 m4 p# M+ V  Z: X/ g, K' ?5 _
如上分析,如有问题,请拍砖
# b  V2 f9 ?: y; F( |6 A* n. ]' p& Y6 E& O, a

. Q, A' E- g3 e4 q% E
; U5 c0 w$ I5 x# L4 S+ T- r; O7 ]! K5 q

评分

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

查看全部评分

收藏 1 评论8 发布时间:2019-6-27 09:09

举报

8个回答
murphyzhao 回答时间:2019-6-28 09:21:15
这里上传代码还是很费劲,整理出来的代码没法看,就直接上图了,比较直观
子曰好人 回答时间:2019-6-28 10:32:15
感谢楼主的分享,还没研究过嵌入式OS,支持一下楼主
Kevin_G 回答时间:2019-6-28 14:35:20
学习下
梦中的飞鸿 回答时间:2019-6-28 18:06:59
楼主的代码截的很好看啊,怎么弄的?
murphyzhao 回答时间:2019-6-28 18:28:47
梦中的飞鸿 发表于 2019-6-28 18:06: m9 [1 V4 D. E) r! z3 w7 c
楼主的代码截的很好看啊,怎么弄的?

6 r  x4 f! G6 h: O5 R5 Q用这个工具,非常好用 http://blog.csdn.net/u012349679/article/details/89787697
murphyzhao 回答时间:2019-6-29 09:04:52
梦中的飞鸿 发表于 2019-6-28 18:06
" O- l# m! p, g. r$ I7 R. K楼主的代码截的很好看啊,怎么弄的?
3 n5 R7 i4 q5 k% [3 l1 j/ H
这个图是使用 Carbon 在线工具,使用参考 http://blog.csdn.net/u012349679/article/details/89787697 这个
murphyzhao 回答时间:2019-6-29 09:11:27
梦中的飞鸿 发表于 2019-6-28 18:06
/ s3 I1 Y# w/ N8 b- i( r楼主的代码截的很好看啊,怎么弄的?
0 ?/ g5 Q' @# R1 i8 F* v
Carbon 在线工具,百度好像直接搜不到,我还贴不了链接( r9 b; H' P5 g# _7 }( ]2 i
) H* p8 y2 _# w2 e
放个图总可以吧& ~+ q* Q2 f- ?) [4 e
图片方式分享代码.png
0 C) t) O1 k0 r9 _0 v
ijcool 回答时间:2020-6-12 16:59:54
军涛,想不想干了,敢说RTT不好?价值观有问题哈哈哈哈哈

所属标签

相似分享

关于意法半导体
我们是谁
投资者关系
意法半导体可持续发展举措
创新和工艺
招聘信息
联系我们
联系ST分支机构
寻找销售人员和分销渠道
社区
媒体中心
活动与培训
隐私策略
隐私策略
Cookies管理
行使您的权利
关注我们
st-img 微信公众号
st-img 手机版