MCU启动文件小解一下


GD32启动文件主要做了以下工作:
初始化SP=_initial_sp , PC=Reset_Handler指针,设置堆栈大小,初始化中断向量表,调用SystemInit,_main,最终调用到main函数。

启动文件的一些指令

指令名称 作用
EQU 给数字常量取一个符号名,相当于 C 语言中的 define
AREA 汇编一个新的代码段或者数据段
ALIGN 编译器对指令或者数据的存放地址进行对齐,一般需要跟一个立即数,缺省表示 4 字节对齐。要注意的是,这个不是 ARM 的指令,是编译器的,这里放到一起为了方便。
SPACE 分配内存空间
PRESERVE8 当前文件堆栈需要按照 8 字节对齐
THUMB 表示后面指令兼容 THUMB 指令。在 ARM 以前的指令集中有 16 位的THUMBM 指令,现在 Cortex-M 系列使用的都是 THUMB-2 指令集,THUMB-2 是 32 位的,兼容 16 位和 32 位的指令,是 THUMB 的超级版。
EXPORT 声明一个标号具有全局属性,可被外部的文件使用
DCD 以字节为单位分配内存,要求 4 字节对齐,并要求初始化这些内存
PROC 定义子程序,与 ENDP 成对使用,表示子程序结束
WEAK 弱定义,如果外部文件声明了一个标号,则优先使用外部文件定义的标号,如果外部文件没有定义也不会出错。要注意的是,这个不是 ARM 的指令,是编译器的,这里放到一起为了方便。
IMPORT 声明标号来自外部文件,跟 C 语言中的 extern 关键字类似
LDR 从存储器中加载字到一个存储器中
BLX 跳转到由寄存器给出的地址,并根据寄存器的 LSE 确定处理器的状态,还要把跳转前的下条指令地址保存到 LR
BX 跳转到由寄存器/标号给出的地址,不用返回
B 跳转到一个标号
IF,ELSE,ENDIF 汇编条件分支语句,跟 C 语言的类似
END 到达文件的末尾,文件结束

主要是一些arm指令,ARM指令还能够从keil的IDE ->help 里找到
在这里插入图片描述
在这里插入图片描述

.s启动文件分析

栈空间分配

在这里插入图片描述EQU:宏定义的伪指令,给数字常量取一个符号名,类似与 C 中的 define。定义栈大小为 0x00000400 字节,即 1024B(1KB),常量的符号是 Stack_Size。
AREA: 汇编一个新的代码段或者数据段。段名为 STACK,段名可以任意命名;
NOINIT 表示不初始化; READWRITE 表示可读可写;ALIGN=3,表示按照 2^3 对齐,
即 8 字节对齐。
SPACE 分配内存指令,分配大小为 Stack_Size 字节连续的存储单元给栈空间。
__initial_sp 紧挨着 SPACE 放置,表示栈的结束地址,栈是从高往低生长,所以结束地址就是栈顶地址。
通过map文件查看栈的initial_sp可以看到恰好栈由栈顶高地址到低地址是0x400
在这里插入图片描述

堆空间管理

在这里插入图片描述
开辟堆的大小
为 0x00000000(0 字节),段名为 HEAP,不初始化,可读可写,8 字节对齐。__heap_base表示堆的起始地址,__heap_limit 表示堆的结束地址。堆和栈的生长方向相反的,堆是由低向高生长,而栈是从高往低生长。
这里我这只为0,因为我用了freertos里heap的方式进行内存管理,就不用系统默认的堆了,因为malloc缺点比较多,容易造成较大的内存碎片。

堆可以用于动态内存分配,比如通过malloc()、calloc()和 realloc()等函数
malloc() 函数在堆上分配参数size字节大小的内存,并返回指向新分配内存起始位置处的指针,其所分配的内存未经初始化。
calloc() 函数用于给一组相同对象分配内存。参数mumitems指定分配对象的数量,size指定每个对象的大小与malloc()不同,calloc()会将已分配的内存初始化为0。
realloc() 函数用来调整(通常是增加)一块内存的大小
在这里插入图片描述
PRESERVE8:指示编译器按照 8 字节对齐。
THUMB:指示编译器之后的指令为 THUMB 指令。

中断向量表定义

在这里插入图片描述
定义一个数据段,名字为 RESET, READONLY 表示只读。EXPORT 表示声明一个标号
具有全局属性,可被外部的文件使用。这里是声明了__Vectors、__Vectors_End 和__Vectors_Size 三个标号具有全局性,可被外部的文件使用。
当内核响应了一个发生的异常后,对应的异常服务例程(ESR)就会执行。为了决定 ESR的入口地址, 内核使用了向量表查表机制。向量表其实是一个 WORD(32 位整数)数组。
每个下标对应一种异常,该下标元素的值则是该 ESR 的入口地址。
在复位后,该寄存器的值为 0。因此,在地址 0 (即 FLASH 地址 0) 处必须包含一张向量表,用于初始时的异常分配。
当然,我们如果说有bootloader的话,我们的起始地址将不会在0,这时候就需要通过 NVIC 中的一个重定位寄存器来指出向量表的地址。
在这里插入图片描述

转载自正点原子启动文件解析
举个例子,如果发生了异常 SVCall,则 NVIC 会计算出偏移移量是 11x4=0x2C,然后从那里取出服务例程的入口地址并跳入。要注意的是这里有个另类:地址 0x0000 0000 并不是什么入口地址,而是给出了复位后 MSP 的初值。
灰色部分是系统内核中断,后面就是用户的中断。

堆空间定义

接下来是定义只读代码段,定义一个段命为.text,只读的代码段,在 CODE 区。
在这里插入图片描述

Reset_Handler复位程序

在这里插入图片描述
PROC、ENDP 一对伪指令作为程序的起始和结束。
通过EXPORT 和 week 来声明其全局属性,同时将其声明为弱函数,外部可以定义覆盖。
IMPORT 表示SystemInit和__main来自外部文件。
LDR表示将Systeminit和_main存储到寄存器R0中。
BLX 表示跳转到由寄存器给出的地址,并根据寄存器的 LSE 确定处理器的状态,还要把跳转前的下条指令地址保存到 LR。
BX 表示跳转到由寄存器/标号给出的地址,不用返回。这里表示切换到__main地址,最终调用 main 函数,不返回,进入 C 的世界。

HardFault_Handler

在这里插入图片描述
若定义了HardFault_Handler,则执行我们自己的HardFault_Handler,若没有定义,则运行B . 这条汇编,进行原地跳转。
其他的中断也是如下。
在这里插入图片描述

_main文件分析

当看到__main 函数时,估计有不少人认为这个是 main 函数的别名或是编译之后的名字,
否则在启动代码中再也无法找到和 main 相关的字眼了。可事实是,_main 和 main 是两个完全不同的函数。_main 代码是编译器自动创建的,因此无法找到_main 代码。MDK 文档中有一句说明:it is automatically craated by the linker when it sees a definition of main() 。大体意
思可以理解为:当编译器发现定义了 main 函数,那么就会自动创建_main。
程序经过汇编启动代码,执行到__main()后,可以看出有两个大的函数:
__scatterload():负责把 RW/RO 输出段从装载域地址复制到运行域地址,并完成了 ZI运行域的初始化工作。
1._scatterload_null:判断r10和r11是否相等,不相等就跳到_scatterload_copy
2._scatterload_copy 复制好之后,在会到_scatterload_null判断是否相等,相等后进入__scatterload_zeroinit
3.__scatterload_zeroinit:对ZI段进行清零,最后跳回__scatterload
然后跳转到__rt_entry()

__rt_entry() :负责初始化堆栈,完成库函数的初始化,最后自动跳转向 main()函数。
先调用 __user_setup_stackheap 函数,然后调用__user_libspace进行一些微库的初始化,最后到__user_setup_stackheap初始化堆栈,调用__rt _entry_main最后回到main。
在这里插入图片描述

用户堆栈初始化

在这里插入图片描述
ALIGN 表示对指令或者数据的存放地址进行对齐,一般需要跟一个立即数,缺省表示4 字节对齐。
首先判断是否定义了__MICROLIB。关于__MICROLIB 这个宏定义,我们是在 KEIL里面配置。
在这里插入图片描述
如果定义__MICROLIB,声明__initial_sp、__heap_base 和__heap_limit
这三个标号具有全局属性,可被外部的文件使用。__initial_sp 表示栈顶地址,__heap_base表示堆起始地址,__heap_limit 表示堆结束地址。
没有定义__MICROLIB,实际的情况就是我们没有定义__MICROLIB,所以使用默认的 C 库运行。堆栈的初始化由 C 库函数__main 来完成。
IMPORT 声明__use_two_region_memory 标号来自外部文件。
EXPORT 声明__user_initial_stackheap 具有全局属性,可被外部的文件使用。
标号__user_initial_stackheap,表示用户堆栈初始化程序入口。
接下来进行堆栈空间初始化,堆是从低到高生长,栈是从高到低生长,是两个互相独立的数据段,并且不能交叉使用。
保存堆起始地址。
保存栈大小。
保存堆大小。
保存栈顶指针。
跳转到 LR 标号给出的地址,不用返回。
END 表示到达文件的末尾,文件结束。
在这里插入图片描述

相关推荐

  1. MCU启动流程

    2024-01-28 09:20:03       13 阅读
  2. 使用python启动一个roslaunch文件

    2024-01-28 09:20:03       37 阅读
  3. 怎么给html文件本地启动一个服务去访问

    2024-01-28 09:20:03       12 阅读

最近更新

  1. TCP协议是安全的吗?

    2024-01-28 09:20:03       18 阅读
  2. 阿里云服务器执行yum,一直下载docker-ce-stable失败

    2024-01-28 09:20:03       19 阅读
  3. 【Python教程】压缩PDF文件大小

    2024-01-28 09:20:03       18 阅读
  4. 通过文章id递归查询所有评论(xml)

    2024-01-28 09:20:03       20 阅读

热门阅读

  1. 代码随想录算法训练59 | 单调栈part02

    2024-01-28 09:20:03       35 阅读
  2. xss跨站脚本攻击

    2024-01-28 09:20:03       33 阅读
  3. 【每日一题】YACS 243:5G通讯

    2024-01-28 09:20:03       31 阅读
  4. npm install 一直卡在 sill idealTree 解决方案

    2024-01-28 09:20:03       36 阅读
  5. k8s Ingress部署应用

    2024-01-28 09:20:03       33 阅读