嵌入式软件面试之堆栈操作浅析

嵌入式软件面试之堆栈操作浅析

今天要分享的是嵌入式面试中一个常被问到的问题,那就是堆栈机制,它反映了程序跳转和异常以及中断时,处理器的行为方式,理解堆栈机制,有助于在程序调试中,更好的发现问题,减少无效DEBUG,做一个高效的嵌入式er。

那为什么要进行堆栈操作呢?

试想,你设计了一个基于stm32单片机的烟雾检测报警装置,在检测到烟雾浓度超标时,烟雾传感器给单片机输出一个低电平信号,触发单片机的外部中断,这时单片机就知道发生了浓度超标的异常,但这时候单片机正在执行其他程序,比如显示程序或者按键扫描程序。我们知道中断的优先级是要高于其他程序的,所以这时候处理器需要优先跳转到报警程序去执行,那么就有个问题,正在执行的程序可能使用了CPU中的寄存器用于保存数据,但是跳转到报警程序去之后,这段程序也可能也会使用到这些寄存器,从而导致跳转前的程序保存的数据丢失,为了解决这个问题,堆栈应运而生,它存储了跳转前需要保存的寄存器数据以及报警程序执行完毕后应该返回到哪个地址继续执行等这些信息。
嵌入式软件面试之堆栈操作浅析_第1张图片
上图展示了一个入栈和出栈的基本流程:当程序需要跳转到其他函数或者中断函数时,使用PUSH指令将需要保存的寄存器数据存储起来,在执行中断函数时原寄存器值可能被毁坏,在执行完中断函数后将之前存储的值弹出,还原到之前的寄存器中。

这里的堆栈机制的例举对象还是选择ARM的cortex-m3。在 Cortex-M3 中,使用PUSH和POP指令来进行堆栈操作。除了可以使用 PUSH 和 POP 指令来处理堆栈外,内核还会在异常处理的始末自动地执行 PUSH 与 POP 操作。

堆栈操作实质上就是对内存的读写操作,访问地址由 SP 给出。寄存器的数据通过 PUSH操作存入堆栈,以后用 POP 操作从堆栈中取回。在 PUSH 与 POP 的操作中,SP 的值会按堆栈的使用法则自动调整,以保证后续的 PUSH 不会破坏先前 PUSH 进去的内容。堆栈的功能就是把寄存器的数据临时备份在内存中,以便将来能恢复之——在一个任务或一段子程序执行完毕后恢复。正常情况下,PUSH 与 POP 必须成对使用,而且参与的寄存器,不论是身份还是先后顺序都必须完全一致。当 PUSH/POP 指令执行时,SP 指针的值也跟着自减/自增。

(主程序)
; R0=X, R1=Y, R2=Z
BL Fx1   ;跳转到子程序Fx1

;Fx1
PUSH {R0 } ;把 R0 存入栈 & 调整 SP
PUSH {R1} ;把 R1 存入栈 & 调整 SP
PUSH {R2} ;把 R2 存入栈 & 调整 SP
;执行 Fx1 的功能,中途可以改变 R0-R2 的值

POP {R2} ;恢复 R2 早先的值 & 再次调整 SP
POP {R1} ;恢复 R1 早先的值 & 再次调整 SP
POP {R0} ;恢复 R0 早先的值 & 再次调整 SP
BX LR ;返回

;回到主程序
;R0=X, R1=Y, R2=Z (调用 Fx1 的前后 R0-R2 的值完好无损,从寄存器上下文来看,就好像什么都没发
生过一样)

上述展示了一个伪代码的入栈与出栈的过程。

堆栈操作的进一步学习

如果参与的寄存器比较多,PUSH/POP 指令足够体贴,支持一次操作多个寄存器。像这样:

PUSH {R0-R2} ;压入 R0-R2
PUSH {R3-R5,R8, R12} ;压入 R3-R5,R8,以及 R12
在 POP 时,可以如下操作:
POP {R3-R5,R8, R12} ;弹出 R3-R5,R8,以及 R12
POP {R0-R2} ;弹出 R0-R2

PUSH/POP 对子还有这样一种特殊形式,形如:

PUSH {R0-R3, LR}
POP {R0-R3, PC}

注意:POP 的最后一个寄存器是 PC,并不是先前 PUSH 的 LR。这其实是一个返回的小技巧。与其按部就班地把先前 LR 的值弹回 LR,再复制给 PC 来返回;不如干脆绕过 LR,直接传给 PC!那不怕 LR 的值没有被恢复吗?不怕,因为 LR 在子程序调用中的唯一用处,就是在返回时提供返回地址。因此,在返回后,先前保存的返回地址就没有利用价值了,所以只要PC 得到了正确的值,不恢复也没关系。

cortex-m3的双堆栈机制

需要注意的是Cortex-M3 使用的是“向下生长的满栈”模型。堆栈指针 SP 指向最后一个被压入堆栈的 32 位数值。在下一次压栈时,SP 先自减 4,再存入新的数值。

R0寄存器入栈时的示意图:
嵌入式软件面试之堆栈操作浅析_第2张图片
R0寄存器出栈时示意图:
嵌入式软件面试之堆栈操作浅析_第3张图片
从前面几期我们已经知道了 CM3 的堆栈是分为两个:主堆栈和进程堆栈,CONTROL[1]决定如何选择。当 CONTROL[1]=0 时,只使用 MSP,此时用户程序和异常 handler 共享同一个堆栈。这也是复位后的缺省使用方式。
嵌入式软件面试之堆栈操作浅析_第4张图片
上图展示了默认情况下使用主堆栈的入栈出栈情况。
嵌入式软件面试之堆栈操作浅析_第5张图片
上图展示了用户使用线程堆栈,中断下进入Handler模式时使用主堆栈的例子。这个例子具体的应用场景是,在有OS的情况下,通过读取 PSP 的值,OS 就能够获取用户应用程序使用的堆栈,进一步地就知道了在发生异常时,被压入寄存器的内容,而且还可以把其它寄存器进一步压栈。OS 还可以修改 PSP,用于实现多任务中的任务上下文切换。

文中的部分截图来自网络,如有侵权,联系阿目删除。

ending~~

你可能感兴趣的:(面试,职场和发展,嵌入式硬件,汇编)