https://zhuanlan.zhihu.com/p/22048373

写文章

借由ARM CORTEX-M芯片分析C程序加载和存储模型

王小军

1 年前

阿军最近在忙着血氧手环嵌入式系统的技术预研,因为整个嵌入式的任务由阿军一个人负责,而阿军又对这个不太了解,只能从头研究,最近有点忙,过了八月份应该会更新比较频繁吧。

阿军把最近学习ARM芯片和做嵌入式编程的一点体会,如有错误欢迎指正。

---------------------------------------------------

本文着眼于以下内容:

  1. ARM处理器基本的存储模型;
  2. C语言示例说明程序在芯片中的存储模型;
  3. C程序如何在ARM裸板上运行
  4. C和嵌入式硬件开发

ARM处理器基本存储模型

nRF51822[ARM CORTEX-M0]寻址空间,如下图所示

arm 32位芯片地址宽度为32位,通过内部总线挂载设备:

  • 高速总线:挂载RAM和GPIO
  • 外设总线:挂载非易失性存储和外设

ARM CPU可以直接通过地址总线读写RAM(高速),读取非易失性存储(一般速度),擦除及写入非易失性存储(慢速),读写外设寄存器(一般速度)。

其中,非易失性存储用于存储代码、恒定数据,RAM用于存储指令执行过程中的临时数据,读写外设寄存器用于控制GPIO、UART等外设。

C语言示例说明程序在芯片中的存储模型

硬件层的设计决定了软件层的设计

存储器

  • 非易失性存储速度慢,便宜,代码断电保留;
  • 易失性存储器速度快,价格贵,数据和代码断电时丢失;

为了综合利用两种存储器,设计了这样的软件架构:

  • 数据分为固定不变的固定数据(代码也可以理解为一种数据),代码执行过程中一直存在的变量,代码执行过程中临时产生的变量
  • 代码和固定的数据保存在非易失性存储中,断电从头开始执行,数据和代码一直保留;
  • 全局变量和临时变量存储在易失性存储中,需要由非易失性存储器中的代码和数据初始化;
  • CPU从非易失性存储器中加载指令,系统开始时必须初始化全局变量(把全局变量的初始值由非易失性存储移动到易失性存储器),初始化临时变量区。
  • 易失性存储器中用静态数据区存储静态变量和全局变量,在堆(通过malloc和free管理)和栈中存储临时变量。
  • CPU通过绝对寻址、间接寻址、寄存器寻址等多种方式获取数据

这个原因导致C中指针的引入异常方便与强大,不论是数据、寄存器、函数等都能通过指针直观的操作

  • 芯片中数据以字(4字节)、半字(2字节)、字节、位的模式存储与操作

对应C中的基本数据类型

...除此之外还有很多硬件和软件的对应,了解系统底层实现原理与熟练掌握C语言相辅相成。

C代码与ARM 存储器结构的对应

  • const 修饰符与固定数据

const 修饰的数据存储在非易失性存储器,运行时不能修改,比如FLASH、磁盘中

  • 全局变量、静态变量与静态数据区

全局数据和static 修饰的变量存储在易失性存储的静态数据区,在程序的整个生命周期内,其一直存在

  • 系统临时变量 与 栈(stack)

系统临时变量(比如函数中的变量,函数调用上下文等)保存在易失性存储器的栈中

注意:以上三种数据类型的存储一般由编译器自动控制,我们无法干预

  • 动态内存管理 与 堆(heap)

通过malloc和free手动申请及释放,这个用起来比较危险,可能会出现内存溢出,野指针等各种复杂的问题,但是给程序设计人员很大的自由,很强大

C库中的动态内存分配策略采用线性方案,就是寻找合适的没有被使用的堆区域,然后把内存分配出去

随着malloc和free的进行,就会产生内存碎片

采用合适的策略分配和整理内存是操作系统很重要的任务,例如FreeRTOS中就实现了5种动态内存管理函数

C语言之所以是嵌入式系统和操作系统可选择唯一语言的原因就是其强大的指针和对内存的精确管理

示例:编写简单的C语言,从其在MDK上编译连接生成的map文件,了解C语言的存储。(待补充)

C语言如何在ARM裸板上运行

nRF51822(ARM CORTEX-M0)的启动代码

现场分析nRF51822的启动代码及汇编执行顺序,了解程序是如何在CPU上加载启动的

启动代码(汇编)

IF :DEF: __STACK_SIZE                   ;预编译指令 #ifdef _STACK_SIZE
Stack_Size      EQU     __STACK_SIZE    ;#define Stack_Size __STACK_SIZEELSE                    ;#else
Stack_Size      EQU     2048            ;#define Stack_Size 2048ENDIF                   ;#endif;AREA 命令指示汇编器汇编一个新的代码段或数据段。
;段是独立的、指定的、不可见的代码或数据块,它们由链接器处理.
;段是独立的、命名的、不可分割的代码或数据序列。一个代码段是生成一个应用程序的最低要求  ;默认情况下,ELF 段在四字节边界上对齐。expression 可以拥有 0 到 31 的任何整数。
;段在 2expression 字节边界上对齐AREA    STACK, NOINIT, READWRITE, ALIGN=3   ;代码段名称为STACK,未初始化,允许读写,8字节对齐
Stack_Mem       SPACE      Stack_Size                          ;分配Stack_Size的栈空间,首地址赋给Stack_Mem
__initial_sp                                                ; 栈顶指针,全局变量;基本同栈,初始化分配堆IF :DEF: __HEAP_SIZE
Heap_Size       EQU     __HEAP_SIZEELSE
Heap_Size       EQU     2048ENDIFAREA    HEAP, NOINIT, READWRITE, ALIGN=3
__heap_base
Heap_Mem        SPACE   Heap_Size
__heap_limitPRESERVE8                                   ;8字节对齐THUMB                                       ;THUMB命令模式; Vector Table Mapped to Address 0 at Reset,重启时程序从这里运行AREA    RESET, DATA, READONLY               ;代码段名称为RESET,DATA类型,只读EXPORT  __Vectors                           ;中断向量表EXPORT  __Vectors_End                       ;中断向量表结束指针EXPORT  __Vectors_Size                      ;中断向量表大小__Vectors       DCD     __initial_sp                        ; Top of Stack 中断向量表首位为栈指针DCD     Reset_Handler                       ;重启中断DCD     NMI_Handler                         ;不可屏蔽中断DCD     HardFault_Handler                   ;硬件错误中断DCD     0                         ; ReservedDCD     0                         ; ReservedDCD     0                         ; ReservedDCD     0                         ; ReservedDCD     0                         ; ReservedDCD     0                         ; ReservedDCD     0                         ; ReservedDCD     SVC_Handler                         ;监控调用模式中断DCD     0                         ; ReservedDCD     0                         ; ReservedDCD     PendSV_HandlerDCD     SysTick_Handler; External InterruptsDCD     POWER_CLOCK_IRQHandlerDCD     RADIO_IRQHandlerDCD     UART0_IRQHandlerDCD     SPI0_TWI0_IRQHandlerDCD     SPI1_TWI1_IRQHandlerDCD     0                         ; ReservedDCD     GPIOTE_IRQHandlerDCD     ADC_IRQHandlerDCD     TIMER0_IRQHandlerDCD     TIMER1_IRQHandlerDCD     TIMER2_IRQHandlerDCD     RTC0_IRQHandlerDCD     TEMP_IRQHandlerDCD     RNG_IRQHandlerDCD     ECB_IRQHandlerDCD     CCM_AAR_IRQHandlerDCD     WDT_IRQHandlerDCD     RTC1_IRQHandlerDCD     QDEC_IRQHandlerDCD     LPCOMP_IRQHandlerDCD     SWI0_IRQHandlerDCD     SWI1_IRQHandlerDCD     SWI2_IRQHandlerDCD     SWI3_IRQHandlerDCD     SWI4_IRQHandlerDCD     SWI5_IRQHandlerDCD     0                         ; ReservedDCD     0                         ; ReservedDCD     0                         ; ReservedDCD     0                         ; ReservedDCD     0                         ; ReservedDCD     0                         ; Reserved__Vectors_End__Vectors_Size  EQU     __Vectors_End - __Vectors   ;计算中断向量表的大小AREA    |.text|, CODE, READONLY     ;代码段,|.text|表示由C语言产生的代码段,CODE类型,只读; Reset Handler     NRF_POWER_RAMON_ADDRESS              EQU   0x40000524  ; NRF_POWER->RAMON address
NRF_POWER_RAMONB_ADDRESS             EQU   0x40000554  ; NRF_POWER->RAMONB address
NRF_POWER_RAMONx_RAMxON_ONMODE_Msk   EQU   0x3         ; All RAM blocks on in onmode bit maskReset_Handler   PROCEXPORT  Reset_Handler             [WEAK]      ;[WEAK]修饰代表其他文件有函数定义优先调用IMPORT  SystemInit  ;从外部调用SystemInitIMPORT  __main      ;从外部调用__main;以下代码不同芯片不一样,这个是设定RAM块开启关闭的配置,这里配置为全部开启                MOVS    R1, #NRF_POWER_RAMONx_RAMxON_ONMODE_MskLDR     R0, =NRF_POWER_RAMON_ADDRESSLDR     R2, [R0]ORRS    R2, R2, R1STR     R2, [R0]LDR     R0, =NRF_POWER_RAMONB_ADDRESSLDR     R2, [R0]ORRS    R2, R2, R1STR     R2, [R0]LDR     R0, =SystemInitBLX     R0                                  ;无返回调用SystemInitLDR     R0, =__mainBX      R0                                  ;有返回调用__mainENDP; Dummy Exception Handlers (infinite loops which can be modified)NMI_Handler     PROCEXPORT  NMI_Handler               [WEAK]B       .ENDP
HardFault_Handler\PROCEXPORT  HardFault_Handler         [WEAK]B       .ENDP
SVC_Handler     PROCEXPORT  SVC_Handler               [WEAK]B       .ENDP
PendSV_Handler  PROCEXPORT  PendSV_Handler            [WEAK]B       .ENDP
SysTick_Handler PROCEXPORT  SysTick_Handler           [WEAK]B       .ENDPDefault_Handler PROCEXPORT   POWER_CLOCK_IRQHandler [WEAK]EXPORT   RADIO_IRQHandler [WEAK]EXPORT   UART0_IRQHandler [WEAK]EXPORT   SPI0_TWI0_IRQHandler [WEAK]EXPORT   SPI1_TWI1_IRQHandler [WEAK]EXPORT   GPIOTE_IRQHandler [WEAK]EXPORT   ADC_IRQHandler [WEAK]EXPORT   TIMER0_IRQHandler [WEAK]EXPORT   TIMER1_IRQHandler [WEAK]EXPORT   TIMER2_IRQHandler [WEAK]EXPORT   RTC0_IRQHandler [WEAK]EXPORT   TEMP_IRQHandler [WEAK]EXPORT   RNG_IRQHandler [WEAK]EXPORT   ECB_IRQHandler [WEAK]EXPORT   CCM_AAR_IRQHandler [WEAK]EXPORT   WDT_IRQHandler [WEAK]EXPORT   RTC1_IRQHandler [WEAK]EXPORT   QDEC_IRQHandler [WEAK]EXPORT   LPCOMP_IRQHandler [WEAK]EXPORT   SWI0_IRQHandler [WEAK]EXPORT   SWI1_IRQHandler [WEAK]EXPORT   SWI2_IRQHandler [WEAK]EXPORT   SWI3_IRQHandler [WEAK]EXPORT   SWI4_IRQHandler [WEAK]EXPORT   SWI5_IRQHandler [WEAK]
POWER_CLOCK_IRQHandler
RADIO_IRQHandler
UART0_IRQHandler
SPI0_TWI0_IRQHandler
SPI1_TWI1_IRQHandler
GPIOTE_IRQHandler
ADC_IRQHandler
TIMER0_IRQHandler
TIMER1_IRQHandler
TIMER2_IRQHandler
RTC0_IRQHandler
TEMP_IRQHandler
RNG_IRQHandler
ECB_IRQHandler
CCM_AAR_IRQHandler
WDT_IRQHandler
RTC1_IRQHandler
QDEC_IRQHandler
LPCOMP_IRQHandler
SWI0_IRQHandler
SWI1_IRQHandler
SWI2_IRQHandler
SWI3_IRQHandler
SWI4_IRQHandler
SWI5_IRQHandlerB .ENDPALIGN; User Initial Stack & Heap,编译器预处理命令IF      :DEF:__MICROLIB                         ;#ifdef __MICROLIBEXPORT  __initial_sp                            ;堆栈的设置采用__MICROLIB库中的策略EXPORT  __heap_baseEXPORT  __heap_limitELSE                        ;#elseIMPORT  __use_two_region_memory                 ;外部定义的两段存储模式函数EXPORT  __user_initial_stackheap                ;用户分配堆栈的地址;寄存器R0,R2存储管理heap
;寄存器R1,R3管理statck__user_initial_stackheap PROCLDR     R0, = Heap_MemLDR     R1, = (Stack_Mem + Stack_Size)LDR     R2, = (Heap_Mem + Heap_Size)LDR     R3, = Stack_MemBX      LRENDPALIGNENDIFEND

其中调用的是__main()函数,而非main()函数,因为C语言在__main()函数里封装了两个函数:

  • __scatterload():完成全局变量从非易失性存储器到易失性存储器的复制与初始化;完成了堆栈的初始化
  • __rt_entry():完成堆栈管理寄存器的配置

这两个函数完成C语言运行时的构建,在构建好之后进入main函数执行程序

C语言与嵌入式开发

ARM SOC的控制

  • 内存管理:全局与静态变量,堆栈初始大小的分配,malloc与free动态内存管理等
  • 存储器操作:数据的获取与存储,如果同一片芯片FLASH或者操作其他存储器
  • 外设管理:外设都是通过总线可以访问的寄存器,对寄存器的控制可以控制外设
  • 芯片固件升级:以nRF51822为例,详细见下图:

这里涉及到的技术点就是中断转发了,每一部分都可以按照正常的程序进行编写,MBR负责所有中断的转发,要注意这样转发会有us级的延迟。

借由ARM CORTEX-M芯片分析C程序加载和存储模型相关推荐

  1. 【转】DICOM:DICOM三大开源库对比分析之“数据加载”

    背景: 上一篇博文DICOM:DICOM万能编辑工具之Sante DICOM Editor介绍了DICOM万能编辑工具,在日常使用过程中发现,"只要Sante DICOM Editor打不开 ...

  2. Android 11.0 Settings源码分析 - 主界面加载

    Android 11.0 Settings源码分析 - 主界面加载 本篇主要记录AndroidR Settings源码主界面加载流程,方便后续工作调试其流程. Settings代码路径: packag ...

  3. ios应用程序加载分析(一)

    app启动分析+猜测 首先通过入口函数main断点查看 nothing ... 通过load入口断点查看 得到大致的堆栈关键信息 (反向调用信息如下) dyld - _dyld_start dyld ...

  4. ios应用程序加载分析(二)

    为了不至于分析链条发生断层,请参阅ios应用程序加载分析(一) _dyld_objc_notify_register ---- sNotifyObjcInit 是如何关联上的 sNotifyObjcI ...

  5. kohana分析之主程序加载流程

    本文是要切入kohana的内部,分析其启动过程: 1.index.php 单一入口主文件,基本上是设置模块路径,全局变量等.如错误信息,系统模块路径,网站目录等. 其他工作转给 require SYS ...

  6. 【Android 插件化】基于插件化的恶意软件的加载策略分析 ( 自定义路径加载插件 | 系统路径加载插件 | 用户同意后加载插件 | 隐藏恶意插件 )

    文章目录 一.自定义路径加载插件 二.系统路径加载插件 三.用户同意后加载插件 四.隐藏恶意插件 一.自定义路径加载插件 插件化应用中 , 宿主应用 加载 插件 APK , 需要获取该插件 APK 文 ...

  7. ARM汇编:加载和存储指令集(六大类)---LDR(ADR)、LDRB、LDRH、STR、STRB、STRH

    ARM的六大类指令集---LDR.LDRB.LDRH.STR.STRB.STRH ARM微处理器支持加载/存储指令用于在寄存器和存储器之间传送数据,加载指令用于将存储器中的数据传送到寄存器,存储指令则 ...

  8. ARPG手游性能分析报告:加载、GC、内存需重点关注

    总体性能 (1)iOS设备的CPU性能普遍高于Android设备: (2)Android设备上项目性能普遍偏低,CPU达标(超过33ms帧数比例<10%)比例仅为38.6%. 相较于MMORPG ...

  9. 【Spring源码分析】Bean加载流程概览

    代码入口 之前写文章都会啰啰嗦嗦一大堆再开始,进入[Spring源码分析]这个板块就直接切入正题了. 很多朋友可能想看Spring源码,但是不知道应当如何入手去看,这个可以理解:Java开发者通常从事 ...

最新文章

  1. 中科大影响超越清华北大
  2. [转]iis部署php项目
  3. [转]SQL交叉表实例
  4. 00_设计模式6大原则
  5. IDEA安装Spring Initializer插件
  6. BUUOJ misc 二维码
  7. 【机器学习】监督学习--(回归)多元线性回归
  8. github因网络问题无法git clone解决办法
  9. 面试官: MySQL 数据库的优化,你知道有哪些?
  10. Shell的脚本编程
  11. logback.xml日志配置文件,springboot
  12. 2-visio使用与卸载
  13. 存储程序式计算机特征,冯.诺依曼机模型是以运算器为中心的存储程序式的计算机模型,它由五大部分构成,即运算器、控制器、存储器、输入设备和输出设备...
  14. VS2019:添加现有项目 / 现有cpp文件
  15. Mud Puddles ( bfs )
  16. 【ParaView教程】2.13 保存截图和保存动画
  17. 实现对mysql增删改查_Java语言实现对MySql数据库中数据的增删改查操作的代码
  18. 江苏省赛 JSCPC2018 K. 2018
  19. 图片放上效果ImageHover.css
  20. Unity 按钮点击缩放

热门文章

  1. Python记录鼠标的点击与拖拽时间并在文本文档中保存记录
  2. TeamViewer14下载与安装教程
  3. Oracle12.1闪回功能
  4. 我的复习计划(很有借鉴意义)
  5. mysql datediff函数怎么用_datediff函数的使用方法是如何的?
  6. 单片机控制W5300
  7. 三元操作 三元操作符 if-else / ? :
  8. Image图片转化为JPG图片
  9. python画图plt函数学习
  10. 一条龙搭建短视频去水印小程序,支持图集解析