本帖最后由 murphyzhao 于 2019-6-28 09:20 编辑
; T0 Y/ N% r9 m! k$ n* r
" y) p/ a6 o0 X" ARTOS 系统线程栈溢出检查实现方式
& G% D3 g/ ~- N% z" F
9 O- ~3 v; f$ @1 X( x& a在实现的时候主要考量哪些因素?我只想到下面两个方向,说说我自己的理解:
7 ?* W$ |8 Y' G$ Z" S; @& v
+ k3 B! i/ E! h0 `6 _& ^- l- 线程栈溢出检查方式
- 什么情况、什么时候检查线程栈+ L3 j( P" C( ~
* i1 V+ M+ [: V, ?; \
1. RT-Thread 线程栈的检查
, f F- u! {6 v5 e" I6 c( \7 s/ R+ n+ S3 ~6 c1 f4 K' N
1.1 线程栈溢出检查方式分析
_2 J7 V { {: t7 n线程栈溢出检查函数:! Q) Z2 @8 n1 b# Y7 i0 D
- static void _rt_scheduler_stack_check(struct rt_thread *thread)
8 ^( S# u: x, K$ m9 D7 U - {% i0 _: g6 q5 x1 Z6 i
- RT_ASSERT(thread != RT_NULL);
" M( P6 A* d# Z0 p7 c - : d) C) c- f2 S/ j) q" t" |
- #if defined(ARCH_CPU_STACK_GROWS_UPWARD)8 t& l! C5 I; X& K2 {
- if (*((rt_uint8_t *)((rt_ubase_t)thread->stack_addr + thread->stack_size - 1)) != '#' ||
2 ]; k+ k% R" T* ~9 G; b - #else
; |, Z5 }; s' ?+ d5 B! p) n - if (*((rt_uint8_t *)thread->stack_addr) != '#' ||/ ^& G+ w' }( N/ q4 X
- #endif& ~, V) J$ A$ i9 k7 Y
- (rt_ubase_t)thread->sp <= (rt_ubase_t)thread->stack_addr ||
! L7 C+ f/ v* y) U - (rt_ubase_t)thread->sp >" l! T- G+ j! y
- (rt_ubase_t)thread->stack_addr + (rt_ubase_t)thread->stack_size)
6 ^9 _ I. E d6 S - {0 a* |0 F' j4 f- S
- rt_ubase_t level;2 Y6 r! `* G! l$ F6 E8 [' w
X# L6 g$ L! W+ N# t' B0 F0 E- rt_kprintf("thread:%s stack overflow\n", thread->name);/ K: i F" U/ M9 O; H7 l2 n
- #ifdef RT_USING_FINSH2 g! |# [ t! J4 [7 }
- {
; g4 ?0 v0 O& S1 e - extern long list_thread(void);
5 Z4 w( n: G! J) j* S - list_thread();( r$ j6 h$ d( ]# t8 V
- }" ?2 I n4 ?9 x! K& `0 ?1 g# O9 n
- #endif2 `( L9 m$ W$ n$ ?# r; V
- level = rt_hw_interrupt_disable();
N1 k! e1 O) z$ P3 g" n6 x; R9 O3 O0 O1 f - while (level);, _# O' V5 Y4 Z3 e' Z/ X7 U
- }
3 b0 r0 ` }1 A: z, Z& q p - #if defined(ARCH_CPU_STACK_GROWS_UPWARD); B( ]1 W8 J+ B2 W
- else if ((rt_ubase_t)thread->sp > ((rt_ubase_t)thread->stack_addr + thread->stack_size))% g t5 l4 U$ ]1 [% A# P
- {
3 g7 d; s) o! d/ G2 K* x6 R' ] - rt_kprintf("warning: %s stack is close to the top of stack address.\n",
0 H0 {" s Y; g& Z r* [7 G* T - thread->name);8 I5 K$ }# B1 P$ }2 H# l
- }3 [ O3 L6 F3 ]2 e3 X2 _
- #else1 \( G d$ f+ p9 o& a# q1 j3 @
- else if ((rt_ubase_t)thread->sp <= ((rt_ubase_t)thread->stack_addr + 32))/ {7 Z; P1 `2 q3 F; O# [0 n. I
- {( Q7 P9 f7 A' s3 S2 k
- rt_kprintf("warning: %s stack is close to end of stack address.\n",
8 w0 X* ]/ C, T \ - thread->name);9 O, T r0 X9 P% }
- }8 b, N& C3 g R) ~' P0 X6 z
- #endif) }- c. Z1 F& s: {
- }
复制代码 & J _, L& ^& Y+ i' _
从上面的代码中可以直观地看到,RT-Thread 线程栈的检查方式分为两种情况:
5 _ \5 S$ D! D$ H# T @2 A- _$ Z& j- X# ?; L" s, x
- 栈向下增长
- 栈向上增长6 L+ [) i, ]6 G- m" l2 y: I
4 i! I* T0 `! X+ c$ ]! B2 G8 Z! y
! P: }! J' B# t% M5 I: L判断栈的增长方向这个是必须的,因为系统要适配不同的 CPU 架构,而有些 CPU 架构的栈增长方向是可配置的。
U8 }, K/ W6 x8 u& P: v4 D% o2 _3 d. K( d
虽然根据栈的增长方向分为两种检查方式,但核心的检查功能确是一样的,通过判断栈尾地址内容是否为 "#" 来进行初步溢出判断,如果栈尾地址内容不是 "#",那么栈溢出。另外,代码还配合检查了线程栈帧地址是否超出栈的地址空间范围来辅助判断。
7 \: A; y2 u4 l0 O% ~; V* F! X+ M3 R$ |
【这里感觉不严谨,如果线程栈刚好用到这个末尾,那就会误判;另外,无法检查其它任务改写栈的问题】
$ Y- y1 M$ r; G9 l) e3 Q5 u# n+ A) b9 g6 E1 K' r* d% O0 A
8 n' v8 S7 F1 N6 t( ?$ a4 c1.2. 什么时候检查
# r# y; f8 ^8 ^% J
0 k' b6 Z8 J6 U3 i2 I0 N通常的做法就是在调度器切换线程的时候检查,调度器简化代码如下:
; H6 ~" O+ k5 B: r( E$ {( q4 F% [8 z1 K9 a8 j7 k
- void rt_schedule(void)
. X. Q' Q. f- i - {
3 c C5 r, D: \* S/ C - rt_base_t level;
5 @- h! P; d, C - struct rt_thread *to_thread;
* u8 j3 S" D/ \9 \ - struct rt_thread *from_thread;
" C0 s- ~! z F# E9 Z! d& A: i; h - * D* k8 {& y0 S5 b/ j [% q2 K5 D
- /* disable interrupt */' P- A: D0 l6 e
- level = rt_hw_interrupt_disable();
# i4 o2 ^7 [8 Q$ w4 P - 9 X4 D) P9 T3 A3 b- E
- to_thread = _get_highest_priority_thread(&highest_ready_priority);2 Z) N) {$ \' ~0 S
- " e7 L8 d) [2 H8 e
- from_thread = rt_current_thread;
7 H8 y3 `8 _ b6 `' A% V - rt_current_thread = to_thread;
) S( i9 S C) G- e/ `( F9 Q3 b4 J/ q -
3 d5 M4 q) Q0 B - _rt_scheduler_stack_check(to_thread);
2 P- h# c& [% G1 b5 ~ - 6 V& l. E& L3 A6 i4 x+ A
- rt_hw_interrupt_enable(level);
8 ~( G2 I. y" j8 W8 J" {' _ - }
复制代码 2 P8 B% @0 g* Q0 Z
7 |* l% i7 w3 E' j3 R
从上面的代码可以看到 RT-Thread 在调度器中调用 _rt_scheduler_stack_check(to_thread); 函数检查 to_thread 线程的线程栈。
2 ]# j$ l" A2 k9 p/ ]% q【有个疑问:为什么仅仅检查 to_thread,不同时检查 from_thread?】, s& d( L1 b7 L( M+ O8 I8 h, q3 t
' z/ Y1 E5 u/ A# \) N: E. i1 R2. FreeRTOS 线程栈检查$ J) @6 c8 J) K- ^; F
1.1 线程栈溢出检查方式分析+ T& d1 Y$ R$ y0 }: g8 a" F
线程栈溢出检查函数:
/ p$ j: v7 B2 x7 e* ]! {5 H( p' s _ N0 M8 i( N4 ]5 `" }0 n1 O
0 r' |/ ^ s: ~4 g
FreeRTOS 的栈溢出检查就有了一个特点,首先使用了 taskFIRST_CHECK_FOR_STACK_OVERFLOW 函数,如下所示:(这里粘贴的代码实在看不下去,放图吧)
- T4 D" C# k" Z) k) T5 k# J- H" E" t0 v: k2 S" Z/ o3 d
9 |0 Z) w. f, H
. {; \& M5 q5 P) G3 \2 n& F然后,使用了 taskSECOND_CHECK_FOR_STACK_OVERFLOW 函数:
: X( Q3 x8 m+ x1 S' u: \
, p0 ^8 Q5 c& _6 r) T6 @
, o5 y) Y4 [( [; C: A: o( N& }6 J2 K
2 z" s* P6 o8 E; c' t I" q) I; B7 E从上面的两段代码,可以看到,FreeRTOS 的栈溢出检查分为第一次和第二次,但同 RT-Thread 类似,也分了栈的增长方向。
' ?/ f+ ^7 a2 Z" {) g" P/ [ ~第一次检查,仅仅判断栈顶指针是否超出了栈尾,具体的栈溢出操作实现交给了 vApplicationStackOverflowHook 回调函数。
' N) X0 e& D7 k" L' B第二次检查,通过内存比较函数,比较 pcEndOfStack 栈尾 20 个字节大小的空间是否与栈的填充值 tskSTACK_FILL_BYTE 匹配(填充值为 0xa5)。
. J7 J0 L! n6 f* J0 i) R! h: x, @6 V; R) u6 H1 T
当然,FreeRTOS 关键起作用的是第二次检查,看着相对还是比较靠谱的(相对 RT-Thread 仅检查一个字节)。FreeRTOS 这种检查方式肯定是与其线程栈的分配方式有关系的,因为它检查的是栈尾部的 20 字节,也就是说,FreeRTOS 为每一个线程栈都预留了 20 字节的空档,造成一部分空间的浪费。$ w: j/ F! o' C; |9 ~, [
2 m' p5 h5 b2 g. i' p
1.2. 什么时候检查6 t! d$ C0 y4 g' Y1 D" x/ R( s- I. K% x
! U; i3 H( i Y9 W当然,FreeRTOS 也是在调度器切换任务的时候检查。直接上代码了,如下:
4 b, y/ y: \$ {( A6 I6 Z5 |1 a" f p
' j) z& O; Z$ U! [- void vTaskSwitchContext( void )
7 u! A2 U, w; ]8 u* c- m - {" @! v$ \. f' q# Y* G
- /* Check for stack overflow, if configured. */, N$ @( x- T) n! a; L' u5 l
- taskFIRST_CHECK_FOR_STACK_OVERFLOW();5 y# r7 b/ s2 z, I7 H) ^0 J
- taskSECOND_CHECK_FOR_STACK_OVERFLOW();
7 r1 b7 r4 e# b N) K$ U) R - 4 o; V" L; d3 k& w, k- ]* L& {
- /* Select a new task to run using either the generic C or port W* p) T1 ?7 |/ D& x k! }- o
- optimised asm code. */
5 p' c+ Q& m, t9 Z% R - taskSELECT_HIGHEST_PRIORITY_TASK();
u5 U" ^" y7 M' Q - traceTASK_SWITCHED_IN();6 G3 w: D( H/ t8 j* u s+ l
- }
复制代码 1 B! Y) w# G( D+ d, e
( T: q# T' g$ A7 y如上,注意到一个问题,FreeRTOS 的栈检查,貌似是检查的 from_thread,而不是像 RT-Thread 一样检查的 to_thread?# m! a0 @3 u, T% n9 M
: z9 _! l3 M* ^% O+ h$ ?6 U
总结, P8 _$ b) f T7 F V3 o
# Z6 L$ L+ _6 `# y: I不知道设计栈溢出检查的时候有哪些考究,RT-Thread 和 FreeRTOS 都没有对同时检查 from_thread 和 to_thread。这两个系统的栈检查方式差不多,相对来说,感觉 FreeRTOS 的更严谨些,但都不能完全检查到,也没法检查线程栈被其它任务错误修改的情况。
3 h3 m6 |8 p2 O5 I; @' G4 R% ^! P! t, k# M2 u# G6 |/ [* o" x
----# Y9 P" l/ M' x: T. ~1 J
0 g( d% Z3 q* j b" T% b: M如上分析,如有问题,请拍砖* ]! M" g) C" j% E" X- H
7 h1 j7 ~# w; Y: a) v: x
* N4 D- x9 B T# o+ f/ t1 g
4 u& q/ |) [5 M3 ]# _( J1 J2 @+ W7 x: C
|
用这个工具,非常好用 http://blog.csdn.net/u012349679/article/details/89787697
这个图是使用 Carbon 在线工具,使用参考 http://blog.csdn.net/u012349679/article/details/89787697 这个
Carbon 在线工具,百度好像直接搜不到,我还贴不了链接 V" I: J5 k L5 l
放个图总可以吧9 c& ?9 g9 k; f! p