当前位置:首页 > 单片机 > 单片机
[导读]一、什么是栈对齐?栈的字节对齐,实际是指栈顶指针须是某字节的整数倍。因此下边对系统栈与MSP,任务栈与PSP,栈对齐与SP对齐 这三对概念不做区分。另外下文提到编译器的时候,实际上是对编译器汇编器连接器的统称。

一、什么是栈对齐?

栈的字节对齐,实际是指栈顶指针须是某字节的整数倍。因此下边对系统栈与MSP,任务栈与PSP,栈对齐与SP对齐 这三对概念不做区分。另外下文提到编译器的时候,实际上是对编译器汇编器连接器的统称。

之前对栈的8字节对齐理解的不透,就在网上查了好多有关栈字节对齐、还有一些ARM对齐伪指令的资料信息,又做了一些实验,把这些零碎的信息拼接在一起,总觉得理解透这个问题的话得长篇大论了。结果昨天看了AAPCS手册、然后查到了没有使用PRESERVE8伪指令出现错误的实例,突然觉得长篇大论不存在了,半篇小论这问题就能理顺了。

二、AAPCS栈使用规约

在ARM上编程,但凡涉及到调用,就需要遵循一套规约AAPCS:《Procedure Call Standard for the ARM Architecture》。这套规约里面对栈使用的约定如下:

5.2.1.1
Universal stack constraints
At all times the following basic constraints must hold:
Stack-limit < SP <= stack-base. The stack pointer must lie within the extent of the stack.
SP mod 4 = 0. The stack must at all times be aligned to a word boundary.
A process may only access (for reading or writing) the closed interval of the entire stack delimited by [SP, stack-base – 1] (where SP is the value of register r13).
Note
This implies that instructions of the following form can fail to satisfy the stack discipline constraints, even when reg points within the extent of the stack.
ldmxx reg, {..., sp, ...} // reg != sp
If execution of the instruction is interrupted after sp has been loaded, the stack extent will not be restored, so restarting the instruction might violate the third constraint.
5.2.1.2
Stack constraints at a public interface
The stack must also conform to the following constraint at a public interface:
SP mod 8 = 0. The stack must be double-word aligned.

可以看到,规约规定,栈任何时候都得4字节对齐,在调用入口得8字节对齐。

在这个约定里,栈的4字节对齐确实得任何时候都遵守,而且你想不遵守都难,因为SP的最后两位是硬件上保持0的。而对于8字节对齐,这就需要码农和编译器配合着来。需要说明的一点是,8字节对齐即使不遵守,一些情况下也没问题,只要主调和被调用例程两边把堆栈使用,传参,返回等处理好就行,也就是说两边有自己的一套约定就行。但是有时候,主调这边在调用严格遵守AAPCS的函数时,没有将栈保持在8字节对齐上,那就会出问题。

三、如何编程?

在cortex m3上编程时,对于AAPCS栈使用约定的遵守,总的来说就两条:

1. 汇编文件中需要我们亲自动手来保证遵守AAPCS栈使用约定。

(特别注意每次从汇编进入C的世界时,要保证汇编部分的编码在调用c接口时栈是8字节对齐的,不要疏忽了,因为c编译器可不负责调整。c编译器说你得送给我的SP就是8字节对齐的,我才能保证接下来的C部分没有结束之前,遵守AAPCS栈使用约定)

2. 在C文件中,由编译器来处理。

四、补充:

1. 由于程序的入口点为复位中断响应函数,一般我们都写在启动代码里,通常是一个汇编文件,然后经由汇编进入到C程序的main入口处,在调用main的时刻,为遵循AAPCS,就得在此时保持8字节对齐。

2. 对于MSP,Keil MDK为我们提供了一个用来初始化C运行库环境的函数_main,这个函数会调用_user_setup_stackheap函数,该函数将MSP的低三位清零,然后在进入main之前不对其进行更改,这样在进入main的时刻,MSP保证为8字节对齐的。

3. 对于PSP,一般在上多任务OS时会用它,对于PSP我们要比MSP更为操心点,因为MSP起码还可以通过调用_main来跳进main的方式保证进入C世界的时候是遵守约定的。而PSP全靠自己来保证每次进入C世界时是8字节对齐。

4. 另外只要是汇编文件,可配合使用汇编命令armasm --diag_warning 1546,这样汇编器就会对一些SP没有8字节对齐的地方给出警告,但是我发现汇编器并不能保证检测到所有对SP造成8字节不对齐的操作,例如直接给SP载入一个立即数这种,汇编器就发现不了。我并没有对所有会影响SP的指令进行测试(原因是不熟悉。。。),不知道1546这个警告能覆盖多少指令,所以总的来讲,对汇编文件就是睁大自己的钛合金眼,争取大部分工作都放到C中去。

五. CORTEX-M3 中断控制器的栈对齐调整功能(该功能在r2p0版本以后的内核中均默认开启,STKALIGN位默认为1)

Cortex M3 NVIC CCR寄存器(控制与配置寄存器)的STKALIGN位置1,那么在发生中断时,进入中断响应函数前,内核会首先检查当前正在使用的栈指针是否8字节对齐,如果是,则正常将xPSR,PC,LR,SP,R0-R3入栈,如果不是,则先把SP-4,调整为8字节对齐,然后将xPSR第九位置1,接着把xPSR,PC,LR,SP,R0-R3入栈,再然后才进入中断响应函数。这样可以保证程序在运行过程中,如果在栈没有发生4字节对齐的地方发生中断了,进入到中断响应函数的时候也是遵守AAPCS栈使用约定的。如果中断服务程序是做任务切换的,那么前面的情况就是将任务栈调整为对齐,然后进入异常服务程序后使用系统栈,那如果系统栈本来就是不对齐的呢?通过中断来做任务切换的情况下,中断控制器并不会对系统栈进行调整,怎么办?其实这也不用担心,以μC/OS-II为例,在cortex-m3上通常使用PendSV异常来做任务切换,即将OSCtxSw以及OSIntCtxSw都设为仅完成PendSV异常触发功能,然后在PendSV异常服务程序中进行任务切换。由于上电时刻系统处于特权级模式,只要我们保证从上电开始到第一次系统调用,使用的栈都是系统栈MSP就可以了,这样即使第一次要进入任务切换时MSP不对齐,中断向量控制器也会给调整为8字节对齐状态,虽然这个第一次任务切换后除了中断再也不会使用MSP,但只要我们同时保证所有汇编部分都不会破坏8字节对齐规约,那么从此以后MSP都会是8字节对齐的。

六、关于ALIGN属性 与 PRESERVE8伪指令

在CORTEX M3芯片的启动代码中,这两个伪指令并非必不可少,可以不要这两个伪指令。但是有了这两个伪指令,可以在确保遵守AAPCS的道路上加一道保险,使得AAPCS栈使用约定的遵守在实际编程时变得稍微容易点。

当在段定义头(即AREA伪指令的相关代码)当中使用ALIGN=?时,ALIGN属性的作用为设定该代码段或数据段的首址的对齐位置,例如ALIGN=3就表示,该段首址将被安排在2^3=8字节对齐处。需要注意的是,除了AREA的ALIGN属性,还有一个同名的ALIGN指令,ALIGN指令使用在段内部的,用来调整ALIGN指令下一条命令或数据的对齐位置。

而PRESERVE8伪指令并不会对栈进行任何修改。PRESERVE8伪指令的使用有四种方法,分别如下,其中1、2的用法是等价的:

1. PRESERVE8

2. PRESERVE8 {TRUE}

3. PRESERVE8 {FALSE}

如果不写,那么由编译器来决定在编译过程中将汇编文件标识为PRES8属性还是~PRES8属性(也即加还是不加该伪指令),但经过实验,发现编译器在加不加这条伪指令上表现的并不完全可靠。。。所以最好明确的加上是 PRESERVE8 {TRUE}还是PRESERVE8 {FALSE}。那么这条伪指令起什么作用呢?

如果你想要告诉汇编器说:“在我这个汇编文件中保证栈的8字节对齐,我这个文件对栈的任何时刻的任何操作都是8字节对齐的”,那么你就把PRESERVE8伪指令用在汇编文件中,用以向汇编器通知前面你的保证内容。汇编器就知道你这个汇编文件是8字节对齐靠谱选手,将该文件标识为PRES8属性,然后如果在你这个汇编中调用了标示了需要8字节对齐属性的文件中的函数,连接的时候就不会报错。但是假如你把这个汇编文件标示为PRESERVE8 {FALSE},然后你又在这个文件中调用了标示了需要8字节对齐属性的文件中的函数,连接时就会给出错误信息。

那么什么是标示了需要8字节对齐属性的文件呢?如果你的某个汇编文件,某些操作一定要栈8字节对齐才行,那么你就需要使用REQUIRE8伪指令来通知汇编器将该文件标识为REQ8属性,然后这个文件就是所谓的“标示了需要8字节对齐属性的文件”。

在文件较多,文件之间调用由繁多的情况下,通过PRESERVE8和REQUIRE8的配合,就能够在连接期间由编译器检查出我们写代码时不小心造成的破坏8字节对齐模块对需要8字节对齐模块的调用(经过实验发现,汇编之间是给出警告,汇编调用C则是给出错误,由于C文件中并不能直接用REQUIRE8,所以我猜编译器将C文件都通通标识为REQ8属性了,所以才会出错)。

REQUIRE8的用法同PRESERVE8。


本站声明: 本文章由作者或相关机构授权发布,目的在于传递更多信息,并不代表本站赞同其观点,本站亦不保证或承诺内容真实性等。需要转载请联系该专栏作者,如若文章内容侵犯您的权益,请及时联系本站删除。
换一批
延伸阅读

近日,东芝宣布推出新品——“M3H族”微控制器,该产品基于全球标准的Arm Cortex-M3内核,专为电机控制而设计,可满足消费设备和工业设备的各种需求。新产品的出现扩大了 “TXZ?系列”微控制器的产品阵容。

关键字: ARM cortex-m3 内核 微控制器 电源新品

已经是很久没有写文章了,因为没有时间,人家都说大四不考研,天天像过年,可我依旧没能有那份闲暇的时间。现在几乎人人的手里都是一款安卓智能机这是我非常羡慕的,而我手

关键字: adxl345 cortex-m3 单片机 电源技术解析 重力感应

1. 摘要 Cortex-M内核实现了一个高效异常处理模块,可以捕获非法内存访问和数个程序错误条件。本应用笔记从程序员角度描述Cortex-M Fault异常,并且讲述在软件开发周期中的Fault用法。 2. 简介...

关键字: cortex-m3 cortex-m4 fault异常

1.1 启动代码内容1) 硬件初始化:最起码的是要初始化堆栈指针。2) C语言环境 :在main函数调用之前要完成对一些变量的初始化。3) 应用初始化: 这主要取决于你的应用。比如设置系统的晶振、时钟。1.2 图解M3启...

关键字: cortex-m3 启动代码

有時候切換了編譯方式如從ARMCC轉變為GCC編譯器,編譯下載時會出現Error:FlashDownloadfailed:-"Cortex-M3"這個錯誤。目前,自己測試解決的方式是:刪除相同目錄下的...

关键字: cortex-m3 error keil

在项目处于调试期间,Fault处理程序可能只是一个断点指令,调试器遇到这个指令后停止程序的运行。默认情况下,由于非硬Fault被禁能,所有发生的非Fault都会上访成硬Fault,因此只要在硬Fault处理程序中设...

关键字: cortex-m3 cortex-m4 fault fault处理函数

struct的对齐问题是由一道笔试题想到的,笔试题如下:#includeusing namespace std; typedef union student {     char name[10];

关键字: c++ struct 字节对齐

1 Cortex-M3/4的Fault简介 Cortex-M3/4的Fault异常是由于非法的存储器访问(比如访问0地址、写只读存储位置等)和非法的程序行为(比如除以0等)等造成的。常见的4种异常及产生异常的情况如...

关键字: 4 cortex-m3 调试方法 hard fault

在编译下面一段代码时:STACK_TOPEQU0x20002000AREAReset,CODE,READONLYDCD0x20002000DCDStartENTRY;CODE16Startldrr2,=TestLDRD...

关键字: cortex-m3 keil编译 纯汇编 align地址

首先,我们举一个例子:    void f() { int* p=new int[5]; }     这条短短的一句话就包含了堆与栈,看到new,我们首先就应该想到,我们分配了一块堆内存,那么指针p呢

关键字:
关闭
关闭