***

__asm void MSR_MSP(u32 addr)     //__asm 关键字用于调用内联汇编程序,用来声明一个C语言函数的话,

                                                           // 代表这个函数内都是汇编语句

{

    MSR MSP, r0                                  //set Main Stack value    这里面的都是汇编程序

    BX r14

}


***

.H文件叫做头文件,一般只放一些​​#define​​ 常量以及一些函数的原型如int sum(int a[], int len);

.C 一般是放.H里原型函数的实现

.S文件里一般是汇编代码

***

USR_STACK_LEGTH EQU 64 ;定义用户模式堆栈长度为64字,EQU就是C语言中的宏定义作用

SPACE和DCD的区别在于:

SPACE和DCD的功能类似,SPACE申请一片内存空间,语法格式:标识符 SPACE length;

例如:UsrStackSpace SPACE USR_STACK_LEGTH*4 ;

这里申请了64个字(计算机中的字是4字节)的连续内存空间,这个连续空间的首地址宏定义为了标识符UsrStackSpace

SPACE USR_STACK_LEGTH*4 ;   申请了一个无名的空间


DCD每次仅仅申请一个字(32bit,即4字节)的内存空间,那么也就不用指定空间长度了,但是得指定这个内存空间的内容(即初始化),前面的标识符可以要也可以不要,如果不要,就代表这个申请的内存空间是无名的

例如:

ADDR DCD 1; 意思就是申请4字节的空间,同时这个空间赋值为1,且宏定义ADDR为这个空间首地址,所以可以任务ADDR就是这个空间的名字

DCD 1; 申请4字节空间,且赋值为1,无名


总结来说:SPACE和DCD的区别在于,SPACE申请指定长度的空间但不赋初值,DCD申请一个字的空间,并赋初值。若前面有标识符则代表宏定义为这个空间的首地址,也可以没有标识符

再来看看栈Stack常用的定义方式的代码:

USR_STACK_LEGTH EQU 64 ;定义用户模式堆栈长度为64字,EQU就是C语言中的宏定义作用

UsrStackSpace SPACE USR_STACK_LEGTH*4 ;为用户模式分配一个64字的堆栈空间,首地址为UsrStackSpace

StackUsr DCD UsrStackSpace + (USR_STACK_LEGTH-1)*4 ;定义一个字的空间,这个字的内容是栈的终点地址,

如下图所示:

嵌入式处理器DCD和SPACE区别,全局变量,局部变量,程序的栈,堆,程序编译结果的占用空间意思_单片机启动文件

StackUsr 指向的是DCD分配的一个字的空间,此空间存的是SPACE方式分配的一段连续内存的尾地址值

如果此时把这段连续内存空间作为栈来使用,且是向低地址增长的,那么栈底是UsrStackSpace + (USR_STACK_LEGTH-1)*4

即StackUsr 指向空间存的值,且此栈能入栈的数据所占空间最多能增长到UsrStackSpac,即​极限栈顶​ 。


**

ARM的三个常用寄存器

寄存器R13,即堆栈指针寄存器SP​,存储的是当前程序运行的栈的栈底地址,是个固定的值(因为入栈出栈都是向着地址减小的方向增长,所以栈底地址是这个栈最大的地址值)。不是指的是这个栈的栈顶地址,栈顶地址是在动态改变的。我们知道存储程序运行栈的栈顶是ESP寄存器,当前栈帧的栈底的是EBP寄存器,这两个都是会随着函数的调用与返回在动态改变的,看我这篇文章,​​C语言函数调用时候内存中栈的动态变化详细分析​​。

例如:片内4K的SRAM,SDRAM大小64M,从0x30000000到0x33FFFFFF,当程序在片内SRAM运行的时候,sp的值设置为4096,当程序在SDRAM内运行的时候sp设置为0x34000000。

那么如果跑操作系统的话,每一个app(Windows系统就指进程,ucos等小型嵌入式系统指的就是一个任务task)都会有自己独立的栈(自己在程序里通过定义一个全局大数组实现的),在这个栈内进行自己的运行和函数调用,被调函数的形参分配,局部变量分配,返回地址存放等,函数返回时候就一一回收(即栈的作用,看我这篇文章,​​C语言函数调用时候内存中栈的动态变化详细分析​​)。因此,当操作系统发生了app切换的时候,把cpu一些寄存器的关于此app必要的状态值保存在本栈里即可,就可切换到下一个app运行了,下次切换回来的时候再把cpu寄存器的值从本app栈里之前保存好的状态值出栈赋值给这些寄存器即可。​这就是栈的作用​。

程序计数器R15(PC)8086中的EIP寄存器就相当于现代cpu的PC寄存器了,都是存放下一条待执行指令的地址的作用,不同cpu叫法不一样

连接寄存器R14(LR)​不知道干啥的,没兴趣了解


**

嵌入式处理器DCD和SPACE区别,全局变量,局部变量,程序的栈,堆,程序编译结果的占用空间意思_全局变量_02

Code:表示程序所占用 FLASH 的大小(FLASH)。

RO-data:即 Read Only-data,表示程序定义的常量,如 const 类型(FLASH)。

RW-data:即 Read Write-data,表示已被初始化的全局变量(SRAM)

ZI-data:即 Zero Init-data,表示未被初始化的全局变量(SRAM)

程序编译后提示的结果,这里可以看出编译器并不检查各个函数的局部变量总和占用的空间,因为每个函数能够被调用,编译器是不知道的,因为很多函数是if else这样的条件运行的,而这些条件值能不能满足,可能是依赖按键输入的,所以函数的执行与否是动态的,只有程序跑起来了才知道(所以这些函数的形参和里面的局部变量只有此函数运行起来才会在​​中分配空间,函数调用结束又会在栈中回收这些空间,所以占用大小在动态改变,关于程序的栈的概念和作用,参考我这篇文章,​​C语言函数调用时候内存中栈的动态变化详细分析​​),编译器是无法知道的,所以编译器在编译阶段是不需要也无法统计出局部变量的占用空间大小的(因为这些都是分配在程序的栈内的),所以也就不能跟单片机启动文件中默认设置的栈的大小相比较,从而报错。现在我们来实验一下,打开stm32h743启动文件startup_stm32f10x_hd.s

我们看到栈默认只分配了4*16*16B = 1KB,堆仅为512B

嵌入式处理器DCD和SPACE区别,全局变量,局部变量,程序的栈,堆,程序编译结果的占用空间意思_stm32存储分配_03

也就是说函数的局部变量这些最大只能占用1KB,但是现在我在main函数里面定义一个1025B的数组,实际上明显会栈溢出,但是编译器却没有报错,如下图(注:警告是指我程序中定义的这个数组arr未使用)

嵌入式处理器DCD和SPACE区别,全局变量,局部变量,程序的栈,堆,程序编译结果的占用空间意思_SPACE_04

并没有报错,虽然我们知道这个程序一旦运行起来(因为main函数一定会执行)肯定会栈溢出的,那栈溢出(​本文说的栈溢出是指程序的栈的占用空间占用超过了最大空间,不是说栈内的数组越界造成的被调函数返回地址的覆盖问题​)后有什么后果呢,对于有堆栈溢出检测的单片机(比如stm32,有时候好像也检测不了,看我一年半后就遇到了这个问题,折腾了我四五小时,服了,​​stm32函数内定义了大数组堆栈溢出tft液晶屏幕显示异常​​),那么会触发硬件错误中断函数HardFault_Handler,程序转入这个中断函数执行后,我们可以在这个函数里进行复位操作(单片机复位会首先进入复位中断函数,里面执行所有的初始化所有的东西,感兴趣的可以去启动文件里面看看具体做了哪些操作),从而使得程序重新跑起来。假如没有堆栈溢出检测的单片机(比如51单片机),那么程序可能会跑飞,从而死机等等发生。

Code:表示程序所占用 FLASH 的大小(FLASH)。

RO-data:即 Read Only-data,表示程序定义的常量,如 const 类型(FLASH)。

RW-data:即 Read Write-data,表示已被初始化的全局变量(SRAM)

ZI-data:即 Zero Init-data,表示未被初始化的全局变量(SRAM)(​这里面已经包含了启动文件中定义的堆和栈的总大小1KB+512B=1536B,也就是程序运行的栈其实是定义在全局变量区的​)可以看到我把栈改大了512B,这时候未被初始化的全局变量(SRAM)区的占用也跟着大了512字节。因为程序中函数调用大多数都是并行调用的,也就是一个函数调用结束了才调用下一个函数,所以栈的占用空间是不断占用回收的,只要不是几十层函数的深层调用或者本函数深层的递归调用,实际上1KB已经足够使用了,如果不够,那就自己把这个栈大小改大一点吧,只是会多占用一点全局变量的区域(其实全局变量哪来这么多可定义的呢,现在都推崇模块化编程,少用全局变量)。

嵌入式处理器DCD和SPACE区别,全局变量,局部变量,程序的栈,堆,程序编译结果的占用空间意思_SPACE_05

我们看到

RW-data:即 Read Write-data,表示已被初始化的全局变量(SRAM)

ZI-data:即 Zero Init-data,表示未被初始化的全局变量(SRAM)

的总和实际上就是程序能够占的所有的空间了,编译器会把这个占的总和跟在keil软件里面设置的ram总大小进行比较,如果超过了就编译报错,如下图:程序使用的ram的首地址和大小都可以自己指定,而且可以两个同时勾选,说明可以使用两个内存块,还可以使用外部内存块等作为编译器默认自动为程序分配全局变量区域大小的内存芯片区域,0x80000是512KB,那么刚刚编译结果占用全局区大小就会跟这个512KB大小比较从而决定是否报错。具体关于stm32h743内存分配看我这篇文章,​​stm32h743单片机嵌入式学习笔记3-内存管理原理​

嵌入式处理器DCD和SPACE区别,全局变量,局部变量,程序的栈,堆,程序编译结果的占用空间意思_SPACE_06


关于内存中程序的空间分配,下图是冯诺伊曼体系计算机的,而单片机是哈佛体系的,区别就是code代码的执行位置不同,冯诺伊曼体系计算机是会从外存复制指令代码到内存执行,而哈佛体系的代码就在外存(flash,硬盘等等)执行的(关于具体,可以看我这篇文章,​​计算机冯诺伊曼体系结构和哈佛体系结构区别和处理器性能评判标准​​)。我们可以看出stack是在内存的尾巴位置,他的上面是堆heap,当没有堆栈(就是栈stack)溢出检测的单片机(比如51单片机)栈溢出后,栈顶就跑到heap区域了,但是如果这个段没有malloc定义程序数据,事实上可能程序并不会崩溃,这样安排栈的位置在最低端,且紧邻着heap的好处就体现出来了,栈可以去覆盖到别的区域的数据,但是别的区域数据却不能覆盖栈的数据(栈是用于支持函数运行的),所以程序运行会相对更稳定。而heap内的分配使用malloc函数,地址从低向高分配的方式速度更快,而heap的数据最好不要溢出分配到全局变量区(里面的数据基本上都是程序需要的,一旦被覆盖,程序就允许出错误的结果),但是栈的占用是动态的,可以可能是可以堆的数据占过去一点,,所以总体来看,把堆和栈的位置关系这样摆放,且放在高地址区域是,这也可以看出把栈安排在这里了,所以程序的栈的增长方式是向低地址增长的(向高地址还是低地址增长速度是一样的,知识栈顶指针加减移动罢了),就可以这样解释出来了。

嵌入式处理器DCD和SPACE区别,全局变量,局部变量,程序的栈,堆,程序编译结果的占用空间意思_SPACE_07