80、【OS】【Nuttx】【启动】caller-saved 和 callee-saved 示例:栈空间对齐
【声明】本博客所有内容均为个人业余时间创作,所述技术案例均来自公开开源项目(如Github,Apache基金会),不涉及任何企业机密或未公开技术,如有侵权请联系删除
背景
接之前 blog
【OS】【Nuttx】【启动】caller-saved 和 callee-saved 示例:栈指针和帧指针(上)
【OS】【Nuttx】【启动】caller-saved 和 callee-saved 示例:栈指针和帧指针(下)
【OS】【Nuttx】【启动】caller-saved 和 callee-saved 示例:r7 寄存器
分析了栈指针和帧指针的一些概念,并演示了栈帧的操作,并解释了为什么选择 r7 寄存器作为帧指针,下面最后再补充点细节
栈空间对齐
首先来回顾下栈空间操作的主要流程
- 调用 push {r7, lr} 保存当前的帧指针(R7)和链接寄存器(LR),进入新的函数(main)时,需要保存旧的帧指针和返回地址,方便函数执行完后可以正确恢复现场并返回到调用者
- 通过 sub sp, sp, #8 指令来减少栈指针的值,实际上是向栈上申请了 8 个字节的空间,这个空间可以用来存储局部变量或者作为临时存储区域,这里的 8 字节是根据函数内部需要保留的数据量来决定,后面会详细讨论这个
- 在函数结尾,通过 adds r7, r7, #8 和 mov sp, r7 来释放之前分配的栈空间,并通过 pop {r7, pc} 恢复先前保存的 r7 寄存器状态,返回控制权到调用者
这里面有个细节,就是 sub sp, sp, #8 指令,这条指令向栈空间上申请了 8 个字节,下面来看下源码
在 main 函数这里,只有一个局部变量 c,在 32 位系统中,理论上只需要 4 个字节的空间,但是却分配了 8 字节空间,这里就涉及到了栈空间对齐,下面看下 AAPCS32 中对 栈空间对齐的官方描述
- Universal stack constraints:所有时间点都必须满足栈指针 4 字节对齐(SP mod 4 == 0)
- Stack constraints at a public interface:在函数调用入口处,还必须额外满足 8 字节对齐(SP mod 8 == 0)
下面再回过头来看刚才的栈空间操作流程,从对齐的视角来分析
- 首先将 r7,lr 进行压栈,r7 和 lr 分别占用两个 4 字节,所以 SP 这里减了一个 8 字节
- 由于只有一个 int 类型的局部变量,本来申请一个 4 字节空间足矣,但是必须要进行 8 字节对齐,所以这里 sub sp, sp, #8 申请了 8 字节空间
同样的,从对齐的视角,再来看 add_func
- 首先将 lr 进行压栈(r7 不用压栈,之前的 blog 【OS】【Nuttx】【启动】caller-saved 和 callee-saved 示例:叶子函数 有分析原因),lr 占 4 字节,所以 SP 减了 4 字节空间
- 申请栈空间时,首先 add_func 里面有两个局部变量 a 和 b 需要存储,a 和 b 都是 int 类型的,都占 4 字节,加起来 8 字节,本来申请 8 字节空间足矣,但是前面压栈时,只压了 4 字节,4 + 8 = 12 字节,还未对齐到 8 字节空间,所以这里需对齐到 12 字节,4 + 12 = 16,所以指令 sub sp, sp, #12 向栈空间上申请了 12 字节内存
先分析到这里,下篇 blog 继续