从STM32F4XX启动文件以及存储器住址结构出发来探究一下堆和栈。

1、背景

对于计算机内存的堆栈概念,我接触计算机以来,都是蒙圈。今天决定对这两个概念整理一下。有什么不对的地方希望各位看客及时指正,别让我在错误的道路上越走越偏。

2、从STM32F4启动文件来理解栈

从STM32F4启动文件来理解栈对我来说是一举两得,既可以对启动文件在复习一遍,也可以对堆、栈概念更理解。

2.1看看STM32F4启动文件做了什么
;*                      - Set the initial SP
;*                      - Set the initial PC == Reset_Handler
;*                      - Set the vector table entries with the exceptions ISR address
;*                      - Configure the system clock and the external SRAM mounted on 
;*                        STM324xG-EVAL board to be used as data memory (optional, 
;*                        to be enabled by user)
;*                      - Branches to __main in the C library (which eventually
;*                        calls main()).
;*                      After Reset the CortexM4 processor is in Thread mode,
;*                      priority is Privileged, and the Stack is set to Main.

MDK-ARM 工具链的STM32F40xxx/41xxx的向量表;

1、初始化设置栈指针SP

2、初始化设置PC指针指向复位中断处理函数,即PC = Reset_Handler

3、以异常处理程序地址来设置中断向量表

4、配置系统时钟和外部SARM挂载到STM32xG-EVAL板子上用于数据存储

5、转到C库的__main(最后调用mian())

复位CortexM4之后,处理器处于线程模式,特权优先级。

2.2 设置栈指针SP

; Amount of memory (in bytes) allocated for Stack
; Tailor this value to your application needs
; <h> Stack Configuration
;   <o> Stack Size (in Bytes) <0x0-0xFFFFFFFF:8>
; </h>

Stack_Size      EQU     0x00000800                                                               ;栈的大小设置为2048字节(2K)

                AREA    STACK, NOINIT, READWRITE, ALIGN=3                       ;段名为STACK,未初始化,允许读写,8字节对齐
Stack_Mem       SPACE   Stack_Size                                                             ;分配空间,大小为2048Byte,并把首地址赋值给                                                                                                                             ;Stack_Men。
__initial_sp                                                                                                       ;初始化栈指针sp,指向栈的顶部,也就是上述空间                                                                                                                           ;的尾部,STM32采用满递减堆栈。

指定栈的大小,并分配存储空间----可根据应用程序需要调整。

EQU 汇编伪指令 -----将操作数赋予标号,两边的值完全相等。注:使用EQU伪指令给一个标号赋值后,此标号在整个源文件中值固定

AREA  汇编伪指令-----定义一个数据段或代码段。

语法格式如下: AREA 段名 属性1, 属性2,....     注:若段名以数字开头,则该段名需要用'|'括起来,如|1_test|

属性字段表示该代码段(数据段)的祥光属性,多个属性用逗号隔开。

SPACE 汇编伪指令----用于分配一片连续的存储区域并初始化为0. 表达式为要分配的字节数  SPACE亦可用% 来代替

从上述代码可知 栈是连续存储区域

2.3 设置堆的大小

; <h> Heap Configuration
;   <o>  Heap Size (in Bytes) <0x0-0xFFFFFFFF:8>
; </h>

Heap_Size       EQU     0x00000400                                                       ;堆的大小设置为1024(1K)的大小

                AREA    HEAP, NOINIT, READWRITE, ALIGN=3                   ;定义一个HEAP段,未初始化、可读可写,8字节对齐
__heap_base                                                                                           ;堆起始地址
Heap_Mem        SPACE   Heap_Size                                                      ;分配堆空间
__heap_limit                                                                                             ;堆限制大小

2.4、设置对齐与指令集

                PRESERVE8                                                                          ;保持下述空间保持8字节对齐
                THUMB                                                                                    ;使用THUMB指令集,要声明在任何使用THUMB指令集                                                                                                                     ;的语句之前

PRESERVE8 汇编伪指令------指定当前文本是否需要保持堆栈8字节对齐方式。其通过设置PRES8编译属性来通知连接器。

PRESERVE8                                  ;保持代码堆栈8字节对齐

RRESERVE8 {FALSE}                    ;不保持代码堆栈8字节对齐

2.5 设置中断向量表

; Vector Table Mapped to Address 0 at Reset
                AREA    RESET, DATA, READONLY                           ;定义一个段名称为RESET,数据段,只读,保持8字节对齐,由                                                                                                       ;PRESERVE8指定
                EXPORT  __Vectors                                                    ;申明一个全局标号,该标号可在其他的文件中应用。可用                                                                                                                 ;GLOBAL代替
                EXPORT  __Vectors_End                                            ;各标号表示为向量表开始、向量表结束、向量表大小
                EXPORT  __Vectors_Size

复位向量映射到地址0

__Vectors       DCD     __initial_sp               ; Top of Stack
                DCD     Reset_Handler              ; Reset Handler
                DCD     NMI_Handler                ; NMI Handler
                DCD     HardFault_Handler          ; Hard Fault Handler
                DCD     MemManage_Handler          ; MPU Fault Handler
                DCD     BusFault_Handler           ; Bus Fault Handler
                DCD     UsageFault_Handler         ; Usage Fault Handler
                DCD     0                          ; Reserved
                DCD     0                          ; Reserved
                DCD     0                          ; Reserved
                DCD     0                          ; Reserved
                DCD     SVC_Handler                ; SVCall Handler
                DCD     DebugMon_Handler           ; Debug Monitor Handler
                DCD     0                          ; Reserved
                DCD     PendSV_Handler             ; PendSV Handler
                DCD     SysTick_Handler            ; SysTick Handler

 

 

什么是栈

百度这么说:栈是一种特殊的线性表,是一种只允许在表的一端进行插入或删除操作的线性表。表中允许进行插入、删除操作的一端称为栈顶。表的另一端称为栈底。栈顶的当前位置是动态的,对栈顶当前位置的标记称为栈顶指针。当栈中没有数据元素时,称之为空栈。栈的插入操作通常称为进栈或入栈,栈的删除操作通常称为退栈或出栈。-----这是从数据结构的角度理解

 

 

 

 

简易理解:

客栈,即临时寄存的地方,计算机中的堆栈主要用来保存临时数据,局部变量和中断/调用子程序程序的返回地址。程序中栈主要是用来存储函数中的局部变量以及保存寄存器参数的,如果你用了操作系统,栈中还可能存储当前进线程的上下文。设置栈大小的一个原则是,保证栈不会下溢出到数据空间或程序空间.CPU在运行程序时,会自动的使用堆栈,所以堆栈指针SP就必须要在调用C程序前设定---多用汇编设定SP

CPU的内存RAM空间存放规律一般是分段的,从低地址向高地址,依次为:程序段(.text)、BSS段,上面还可能会有堆空间,然后最上面才是堆栈段。这样安排堆栈,是因为堆栈的特点决定的,堆栈的指针SP初始化一般在堆栈段的高地址,也就是内存的高地址,然后让堆栈指针向下增长(其实就是递减)。

这样做的好处就是堆栈空间远离了其他段,不会跟其他段重叠,造成修改其他段数据,而引起不可预料的后果,还有设置堆栈大小的原则,要保证栈不会下溢出到数据空间或者程序空间。所谓堆栈溢出,是指堆栈指针SP向下增长到其他段空间,如果栈指针向下增长到其他段空间,称为堆栈溢出。堆栈溢出会修改其他空间的值,严重情况下可造成死机.

 

2堆栈指针的设置

 

开始将堆栈指针设置在内部RAM,是因为不是每个板上都有外部RAM,而且外部RAM的大小也不相同,而且如果是SDRAM,还需要初始化,在内部RAM开始运行的一般是一个小的引导程序,基本上不怎么使用堆栈,因此将堆栈设置在内部RAM,但这也就要去改引导程序不能随意使用大量局部变量。

 

片内4K的SRAM,SDRAM大小64M,从0x30000000到0x33FFFFFF,当程序在片内SRAM运行的时候,sp的值设置为4096,当程序在SDRAM内运行的时候sp设置为0x34000000,当程序在内部SRAM运行,若已经初始化SDRAM,此时也可以将堆栈指针设置为0x34000000,更加防止了堆栈溢出。

 

3栈的整体作用

 

  1. 保存现场;

  2. 传递参数:汇编代码调用 C 函数时,需传递参数;

  3. 保存临时变量:包括函数的非静态局部变量以及编译器自动生成的其他临时变量;

 

1)  保存现场

现场,意思就相当于案发现场,总有一些现场的情况,要记录下来的,否则被别人破坏掉之后,你就无法恢复现场了。而此处说的现场,就是指 CPU 运行的时候,用到了一些寄存器,比如 r0,r1 等等,对于这些寄存器的值,如果你不保存而直接跳转到子函数中去执行,那么很可能就被其破坏了,因为其函数执行也要用到这些寄存器。因此,在函数调用之前,应该将这些寄存器等现场,暂时保持起来(入栈 push),等调用函数执行完毕返回后(出栈 pop),再恢复现场。这样CPU就可以正确的继续执行了。

 

保存寄存器的值,一般用的是 push 指令,将对应的某些寄存器的值,一个个放到栈中,把对应的值压入到栈里面,即所谓的压栈。然后待被调用的子函数执行完毕的时候,再调用 pop,把栈中的一个个的值,赋值给对应的那些你刚开始压栈时用到的寄存器,把对应的值从栈中弹出去,即所谓的出栈。其中保存的寄存器中,也包括 lr 的值(因为用 bl 指令进行跳转的话,那么之前的 PC 的值是存在 lr 中的),然后在子程序执行完毕的时候,再把栈中的 lr 的值 pop 出来,赋值给 PC,这样就实现了子函数的正确的返回

 

2)  传递参数

C 语言进行函数调用的时候,常常会传递给被调用的函数一些参数,对于这些 C 语言级别的参数,被编译器翻译成汇编语言的时候,就要找个地方存放一下,并且让被调用的函数能够访问,否则就没发实现传递参数了。对于找个地方放一下,分两种情况。一种情况是,本身传递的参数不多于 4 个,就可以通过寄存器 r0~r3 传送参数。因为在前面的保存现场的动作中,已经保存好了对应的寄存器的值,那么此时,这些寄存器就是空闲的,可以供我们使用的了,那就可以放参数。另一种情况是,参数多于 4 个时,寄存器不够用,就得用栈了。

 

3)  临时变量保存在栈中

包括函数的非静态局部变量以及编译器自动生成的其他临时变量

 

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值