借由ARM CORTEX-M芯片分析C程序加载和存储模型
2017-08-19 11:07
381 查看
https://zhuanlan.zhihu.com/p/22048373
写文章
![](https://pic2.zhimg.com/3b56a0511baf848ec844461fe2888635_r.jpg)
![](https://pic1.zhimg.com/a660a134666599e3ca16ace9a913466c_xs.jpg)
王小军
1 年前
阿军最近在忙着血氧手环嵌入式系统的技术预研,因为整个嵌入式的任务由阿军一个人负责,而阿军又对这个不太了解,只能从头研究,最近有点忙,过了八月份应该会更新比较频繁吧。
阿军把最近学习ARM芯片和做嵌入式编程的一点体会,如有错误欢迎指正。
---------------------------------------------------
本文着眼于以下内容:
ARM处理器基本的存储模型;
C语言示例说明程序在芯片中的存储模型;
C程序如何在ARM裸板上运行
C和嵌入式硬件开发
![](https://pic3.zhimg.com/9f0eada99861fe52e37db8114cd4174a_b.png)
arm 32位芯片地址宽度为32位,通过内部总线挂载设备:
高速总线:挂载RAM和GPIO
外设总线:挂载非易失性存储和外设
ARM CPU可以直接通过地址总线读写RAM(高速),读取非易失性存储(一般速度),擦除及写入非易失性存储(慢速),读写外设寄存器(一般速度)。
其中,非易失性存储用于存储代码、恒定数据,RAM用于存储指令执行过程中的临时数据,读写外设寄存器用于控制GPIO、UART等外设。
存储器
非易失性存储速度慢,便宜,代码断电保留;
易失性存储器速度快,价格贵,数据和代码断电时丢失;
为了综合利用两种存储器,设计了这样的软件架构:
数据分为固定不变的固定数据(代码也可以理解为一种数据),代码执行过程中一直存在的变量,代码执行过程中临时产生的变量
代码和固定的数据保存在非易失性存储中,断电从头开始执行,数据和代码一直保留;
全局变量和临时变量存储在易失性存储中,需要由非易失性存储器中的代码和数据初始化;
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语言的存储。(待补充)
现场分析nRF51822的启动代码及汇编执行顺序,了解程序是如何在CPU上加载启动的
启动代码(汇编)
其中调用的是__main()函数,而非main()函数,因为C语言在__main()函数里封装了两个函数:
__scatterload():完成全局变量从非易失性存储器到易失性存储器的复制与初始化;完成了堆栈的初始化
__rt_entry():完成堆栈管理寄存器的配置
这两个函数完成C语言运行时的构建,在构建好之后进入main函数执行程序
内存管理:全局与静态变量,堆栈初始大小的分配,malloc与free动态内存管理等
存储器操作:数据的获取与存储,如果同一片芯片FLASH或者操作其他存储器
外设管理:外设都是通过总线可以访问的寄存器,对寄存器的控制可以控制外设
芯片固件升级:以nRF51822为例,详细见下图:
![](https://pic3.zhimg.com/1925bc12d57118424b2989ede3db580e_b.png)
这里涉及到的技术点就是中断转发了,每一部分都可以按照正常的程序进行编写,MBR负责所有中断的转发,要注意这样转发会有us级的延迟。
写文章
![](https://pic2.zhimg.com/3b56a0511baf848ec844461fe2888635_r.jpg)
借由ARM CORTEX-M芯片分析C程序加载和存储模型
![](https://pic1.zhimg.com/a660a134666599e3ca16ace9a913466c_xs.jpg)
王小军
1 年前
阿军最近在忙着血氧手环嵌入式系统的技术预研,因为整个嵌入式的任务由阿军一个人负责,而阿军又对这个不太了解,只能从头研究,最近有点忙,过了八月份应该会更新比较频繁吧。
阿军把最近学习ARM芯片和做嵌入式编程的一点体会,如有错误欢迎指正。
---------------------------------------------------
本文着眼于以下内容:
ARM处理器基本的存储模型;
C语言示例说明程序在芯片中的存储模型;
C程序如何在ARM裸板上运行
C和嵌入式硬件开发
ARM处理器基本存储模型
nRF51822[ARM CORTEX-M0]寻址空间,如下图所示![](https://pic3.zhimg.com/9f0eada99861fe52e37db8114cd4174a_b.png)
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_SIZE ELSE ;#else Stack_Size EQU 2048 ;#define Stack_Size 2048 ENDIF ;#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_SIZE ELSE Heap_Size EQU 2048 ENDIF AREA HEAP, NOINIT, READWRITE, ALIGN=3 __heap_base Heap_Mem SPACE Heap_Size __heap_limit PRESERVE8 ;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 ; Reserved DCD 0 ; Reserved DCD 0 ; Reserved DCD 0 ; Reserved DCD 0 ; Reserved DCD 0 ; Reserved DCD 0 ; Reserved DCD SVC_Handler ;监控调用模式中断 DCD 0 ; Reserved DCD 0 ; Reserved DCD PendSV_Handler DCD SysTick_Handler ; External Interrupts DCD POWER_CLOCK_IRQHandler DCD RADIO_IRQHandler DCD UART0_IRQHandler DCD SPI0_TWI0_IRQHandler DCD SPI1_TWI1_IRQHandler DCD 0 ; Reserved DCD GPIOTE_IRQHandler DCD ADC_IRQHandler DCD TIMER0_IRQHandler DCD TIMER1_IRQHandler DCD TIMER2_IRQHandler DCD RTC0_IRQHandler DCD TEMP_IRQHandler DCD RNG_IRQHandler DCD ECB_IRQHandler DCD CCM_AAR_IRQHandler DCD WDT_IRQHandler DCD RTC1_IRQHandler DCD QDEC_IRQHandler DCD LPCOMP_IRQHandler DCD SWI0_IRQHandler DCD SWI1_IRQHandler DCD SWI2_IRQHandler DCD SWI3_IRQHandler DCD SWI4_IRQHandler DCD SWI5_IRQHandler DCD 0 ; Reserved DCD 0 ; Reserved DCD 0 ; Reserved DCD 0 ; Reserved DCD 0 ; Reserved DCD 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 mask Reset_Handler PROC EXPORT Reset_Handler [WEAK] ;[WEAK]修饰代表其他文件有函数定义优先调用 IMPORT SystemInit ;从外部调用SystemInit IMPORT __main ;从外部调用__main ;以下代码不同芯片不一样,这个是设定RAM块开启关闭的配置,这里配置为全部开启 MOVS R1, #NRF_POWER_RAMONx_RAMxON_ONMODE_Msk LDR R0, =NRF_POWER_RAMON_ADDRESS LDR R2, [R0] ORRS R2, R2, R1 STR R2, [R0] LDR R0, =NRF_POWER_RAMONB_ADDRESS LDR R2, [R0] ORRS R2, R2, R1 STR R2, [R0] LDR R0, =SystemInit BLX R0 ;无返回调用SystemInit LDR R0, =__main BX R0 ;有返回调用__main ENDP ; Dummy Exception Handlers (infinite loops which can be modified) NMI_Handler PROC EXPORT NMI_Handler [WEAK] B . ENDP HardFault_Handler\ PROC EXPORT HardFault_Handler [WEAK] B . ENDP SVC_Handler PROC EXPORT SVC_Handler [WEAK] B . ENDP PendSV_Handler PROC EXPORT PendSV_Handler [WEAK] B . ENDP SysTick_Handler PROC EXPORT SysTick_Handler [WEAK] B . ENDP Default_Handler PROC EXPORT 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_IRQHandler B . ENDP ALIGN ; User Initial Stack & Heap,编译器预处理命令 IF :DEF:__MICROLIB ;#ifdef __MICROLIB EXPORT __initial_sp ;堆栈的设置采用__MICROLIB库中的策略 EXPORT __heap_base EXPORT __heap_limit ELSE ;#else IMPORT __use_two_region_memory ;外部定义的两段存储模式函数 EXPORT __user_initial_stackheap ;用户分配堆栈的地址 ;寄存器R0,R2存储管理heap ;寄存器R1,R3管理statck __user_initial_stackheap PROC LDR R0, = Heap_Mem LDR R1, = (Stack_Mem + Stack_Size) LDR R2, = (Heap_Mem + Heap_Size) LDR R3, = Stack_Mem BX LR ENDP ALIGN ENDIF END
其中调用的是__main()函数,而非main()函数,因为C语言在__main()函数里封装了两个函数:
__scatterload():完成全局变量从非易失性存储器到易失性存储器的复制与初始化;完成了堆栈的初始化
__rt_entry():完成堆栈管理寄存器的配置
这两个函数完成C语言运行时的构建,在构建好之后进入main函数执行程序
C语言与嵌入式开发
ARM SOC的控制内存管理:全局与静态变量,堆栈初始大小的分配,malloc与free动态内存管理等
存储器操作:数据的获取与存储,如果同一片芯片FLASH或者操作其他存储器
外设管理:外设都是通过总线可以访问的寄存器,对寄存器的控制可以控制外设
芯片固件升级:以nRF51822为例,详细见下图:
![](https://pic3.zhimg.com/1925bc12d57118424b2989ede3db580e_b.png)
这里涉及到的技术点就是中断转发了,每一部分都可以按照正常的程序进行编写,MBR负责所有中断的转发,要注意这样转发会有us级的延迟。
相关文章推荐
- ARM芯片的启动程序的分析
- ARM芯片的启动程序的分析和总结(csdn)
- ARM芯片的启动程序的分析和总结
- ARM汇编基础-存储和加载指令
- Python 数据分析(一) 本实验将学习 pandas 基础,数据加载、存储与文件格式,数据规整化,绘图和可视化的知识
- libgdx示例-SuperJumper分析 1. 程序入口与初始化资源,加载配置信息
- 利用python进行数据分析-数据加载、存储与文件格式2
- SQLite入门与分析(八)---存储模型(2)
- gm8180:arm linux启动加载模块、运行程序
- 利用python进行数据分析之数据加载存储与文件格式
- SQLite入门与分析(八)---存储模型(3)
- 基于Cortex-A8裸机,点亮LED灯程序分析
- TensorFlow - 手写数字识别 (模型的存储与加载)
- cortex-A8 ARM核 opencv程序移植 (2)
- python 中bayes模型超参数并行网格搜索 程序分析
- Python 数据分析(一) 本实验将学习 pandas 基础,数据加载、存储与文件格式,数据规整化,绘图和可视化的知识
- ARM程序由于字节对齐引起的问题深入分析
- 文件分析IDA反汇编/反编译静态分析iOS模拟器程序(二)加载文件与保存数据库
- 一种基于ARM-Linux的FPGA程序加载方法
- gm8180:arm linux启动加载模块、运行程序 分类: arm-linux-Ubuntu 2013-07-22 16:30 416人阅读 评论(0) 收藏