在嵌入式编程中,栈是一个很重要的概念,不管是裸机编程还是基于RTOS编程。函数形参、局部变量、函数调用现场的保护及返回地址、中断函数执行前线程保护及中断嵌套的现场的保护都依赖于栈空间。栈空间不足,程序执行过程中栈溢出,极大可能的影响程序、系统的稳定,严重时会造成程序、系统的崩溃,所以堆栈溢出检测十分重要且必要。

什么是堆,什么是栈

   
堆和栈都是指预先分配的空间,有大小限制,两者通常是相邻的两个内存区域(RTOS中任务的堆和栈可能不相邻),供程序使用,堆和栈的最大差异是,堆空间通过xxmalloc接口动态使用。

堆栈大小的定义

   
在裸机编程时,栈大小在启动文件中定义;在基于RTOS编程时,RTOS自身的栈大小在启动文件中定义,任务/线程的栈由RTOS进行定义。通常在RTOS中我们采用动态方式创建任务/线程,这时任务的栈空间实际上在堆中,大小由输入参数决定;如果采用静态方式创建任务/线程,通常是创建静态数组作为任务/线程的栈空间。

管理堆栈的堆栈指针

   
不同的内核对于堆栈的定义、管理都有差异,比如堆栈的生长方向、基址地址等,这里我们讨论的范围限定于目前嵌入式领域使用广泛的Cortex-M3、Cortex-M4内核。Cortex-M3、Cortex-M4内核都具有双堆栈指针:MSP(主堆栈指针)、PSP(进程堆栈指针),两者在任何时刻都只能使用一个,复位后默认使用MSP。在RTOS系统下,MSP由RTOS自身使用,PSP由任务/线程使用;不使用RTOS时默认只有MSP被使用;中断处理使用MSP。这种双SP指针的设计,很好的为隔离系统(中断)与用户程序提供了支持。

堆、栈空间大小的确定

   
栈空间的大小有一个粗略的计算方式,每一级调用函数的局部变量、函数形参、函数返回地址空间是栈的基本占用空间,其他的在使用RTOS和裸机下有所不同。裸机下中断现场保护需要栈空间(由MSP管理),中断中的局部变量、中断嵌套也需要栈空间。在有RTOS时,内核的自动入栈的寄存器入栈时使用任务栈空间(由PSP管理),中断处理中的局部变量和中断嵌套过程使用系统栈空间(由MSP管理)。以上的空间为基本上可以认为是栈的基础空间,通常实际使用会在此基础上乘以一个1.5—2.0的系数,我个人通常选择乘以2.0。从内存分布上来说,除去栈空间后的可用空间(SOC本身所需内存空间以外的内存空间)可以作为堆空间使用。

   
需要知道的是,通过回调调用的函数的栈空间占用通常不好确定(实际的调用函数、调用深度可能改变),通过递归实现的函数的栈空间占用通常不好确定(实际的递归深度不确定)。

堆栈溢出检测机制

   
堆空间溢出的检测依赖于堆内存管理机制(通常只能通过减小程序中堆内存的使用量,或增加堆内存空间整体的大小的方式解决)。其实防止堆空间溢出比检测堆空间溢出更为实际,因为堆空间大小是已知的,每次堆空间使用的申请也是已知的,只要堆内存管理时,防止超量申请就能很好的预防。

   
而栈空间的使用通常却难以预知,所以检测相对预防更实际。栈溢出的检测方式有不少,如检测指针是否越界、栈尾部保护数据是否改变;如果处理器具有SP溢出检测单元还能触发异常(如ARMv8-M架构中的SP_Limit寄存器);如果处理器具有MPU(内存保护单元)还能设定内存保护区域,程序在内存保护区域读写,也会触发异常。

技术
下载桌面版
GitHub
Gitee
SourceForge
百度网盘(提取码:draw)
云服务器优惠
华为云优惠券
腾讯云优惠券
阿里云优惠券
Vultr优惠券
站点信息
问题反馈
邮箱:[email protected]
吐槽一下
QQ群:766591547
关注微信