【声明】本博客所有内容均为个人业余时间创作,所述技术案例均来自公开开源项目(如Github,Apache基金会),不涉及任何企业机密或未公开技术,如有侵权请联系删除
背景
上篇 blog
【OS】【Nuttx】【栈溢出】安全边距(一)
分析了栈溢出检测的过程,以及安全边距在没有独立 ISR 中断栈,和有独立的 ISR 中断栈两种场景下的计算过程,下面继续分析
栈溢出检测
最后一句可以看到,不管有没有独立的 ISR 中断栈,任务栈都应该至少多分配 200 字节作为安全边际

这也是工程实践中的宝贵经验,别把栈分配得刚刚好,一定要留点余量 ,这也符合中华的中庸之道,不把事情做绝,举个例子,如果通过性能分析(比如通过 Stack Monitor 进行染色),发现某个任务最多使用了 800 字节的栈空间

那这时候
- 错误做法 :给这个任务分配刚刚好 800 字节栈空间(中断一上来就会崩溃)

- 正确做法:分配 800 + 200 = 1000 字节(或向上对齐到 1024,多留些安全余量)

这样即使发生中断,任务栈也有足够空间保存上下文,不会破坏掉其他内存
不过接下来又引出一个问题,即使预留了 200 字节的安全余量,是否就能保证中断进来就是安全的呢?答案是不一定,这个问题也是嵌入式系统中,中断栈安全最核心,也最容易被忽视的风险点之一 :即使预留了 200 字节给上下文保存,但如果 ISR 中断服务程序内部声明了大数组,或者出现深度嵌套中断调用,也依然会出现栈溢出!,这也是真实世界中,导致系统崩溃的常见原因
Nuttx 这里的 200 字节,只考虑了硬件自动压栈的部分,也就是 CPU 在进入中断时,自动保存的寄存器(由 ARM 架构决定),这部分是确定性的,最小开销 ,但这 200 字节并没有考虑 ISR 函数本身的栈使用(所以不要误会,200 字节只是预留的最小安全距离,并不能保证中断栈就安全),这是因为
- 即便是 Per function Call 栈检查机制,其本身也无法监控 ISR,如下描述,R10(
rBS)在 ISR 中不会更新,所以__cyg_profile_func_enter在 ISR 里检查的是任务的栈底,而不是 ISR 的实际可用空间 ,即使 ISR 溢出,也不会触发 Hard Fault(除非刚好踩到 MPU 保护区域)

- 另外,ISR 的栈使用是软件行为,不可预测,编译器也不可能知道 ISR 会用多少栈空间,而开发者也可能不小心写个
char buf[512];在 ISR 里面
OK,在工程实践中,有一些方法可以尽量避免 ISR 栈溢出,下面来看下
- 使用独立的 ISR 栈 ,这也是之前提到两种场景之一,给中断提供独立的栈空间,可以减少对任务栈的消耗 ,比如 Nuttx 可以通过配置
CONFIG_ARCH_INTERRUPTSTACK,让系统为所有中断分配一个专用,足够大的栈空间(比如 1~2 KB),实现任务栈和 ISR 栈的完全分离 ,这样的话
1、任务栈只需要预留少量切换开销
2、ISR 的栈空间使用不会影响任何任务
3、可以单独为 ISR 栈空间做监控(虽然 Nuttx 默认不检查 ISR 栈空间) - 严格限制 ISR 的行为,比如规定在 ISR 中禁止
1、声明大数组
2、调用复杂函数(尤其是递归或深度嵌套)
3、使用 malloc 动态内存分配
ISR 应该只做一些简单的事情 ,比如
1、清中断标志位
2、发信号量,队列通知等
3、快速记录状态
4、做完简单的事情后,尽快退出,把耗时的工作交给任务处理
在 MISRA C,AUTOSAR 等安全标准都明确规定,ISR 必须短小,确定,无栈膨胀
OK,本篇先到这里,如有疑问,欢迎评论区留言讨论,祝各位功力大涨,技术更上一层楼!!!更多内容见下篇 blog
【OS】【Nuttx】【栈溢出】-finstrument-functions(一)