MDK 分散加载文件剖析(一)

1、何为分散加载?

1.1 简介

        分散加载就是我们开发者能指定你的 代码 或者 数据变量 到指定的内存空间中运行。通知链接器把程序的某一部分连接在存储器的某个地址空间,我们可以通过编写一个分散加载文件来指定 ARM 连接器在生成映像文件时如何分配 Code、RO-Data, RW-Data, ZI-Data 等数据的存放地址。学习 MDK 分散加载文件之前需要引入如下知识。本文章的 MCU 以 stm32 为例。

1.2 基础知识

        一般 MCU 包含的存储空间有:片内 flash 与 片内 ram。flash 相当于硬盘,存储进去的东西掉电后能保存,这也就是为什么将代码烧录到 MCU 中去运行的原因之一,ram 相当于内存,存储程序中的变量。

        以 MDK 为例,编译完之后生成程序所占用的空间提示信息,如下图所示

 Build target 'rt-thread'
 compiling drv_lcd.c...
 linking...
 Program Size: Code=1346064 RO-data=204128 RW-data=8808 ZI-data=244308  
 After Build - User command #1: fromelf --bin .buildkeilObjrt-thread.axf --output rtthread.bin
 ".buildkeilObjrt-thread.axf" - 0 Error(s), 0 Warning(s).
 Build Time Elapsed:  00:00:15

上面提到的 Program Size 包含以下几个部分:

  • Code:代码段,存放程序的代码部分;

  • RO-data:只读数据段,存放程序中定义的常量;

  • RW-data:读写数据段,存放初始化为非 0 值的全局变量;

  • ZI-data:0 数据段,存放未初始化的全局变量及初始化为 0 的变量;

编译完工程会生成一个. map 的文件,该文件说明了各个函数占用的尺寸和地址,在文件的最后几行也说明了上面几个字段的关系:

 Total RO  Size (Code + RO Data)              1550192 (1513.86kB)
 Total RW  Size (RW Data + ZI Data)            253116 ( 247.18kB)
 Total ROM Size (Code + RO Data + RW Data)    1553520 (1517.11kB)

        程序运行之前,需要有文件实体被烧录到 MCU 的 flash 中,一般是 bin 或者 hex 文件,该被烧录文件称为可执行映像文件(image 文件)。首先我们需要了解一个image文件的构成。image 即编译的产物,我们编译 stm32 生成的 bin 文件此处称之为 image。一个image 文件由 RO 段和 RW 段组成,RO 段包含只读的代码段和常量,RW 段包含可读可写的全局变量和静态变量。因为程序刚运行时,RW 段还在 flash中,需要一段程序将这些变量复制到RAM中,stm32 的启动文件的 __main 函数帮我们完成了这一动作。RW段中初始值为0的段为 ZI 段,image 文件无需包含 ZI 段,因为 ZI 段包含的是全局或静态初始值为0的变量,只要在程序运行后,将对应的 RAM 区域清零即可。(此图出自野火)

        加载域:就是映像文件被静态存放的工作区域(一般指内部 flash)。

        运行域:程序运行起来的存储区域,由于 MCU 内部的 Flash 是可以运行代码的 (XIP 技术),但是不能用于变量也就是 RW-data 与 ZI-data 的加载,主要是因为变量是时时刻刻需要被修改的,运行几分钟就可能需要修改成百上千次,由于 flash 的擦写次数是有限制的,一般是10万次~100万次之间,如果把 RW 和 ZI 放在 Flash 上,后果可想而知。因此需要把 RW-data,ZI-data 放到 ram 中去执行,一般是内部 sram。

2、分散加载文件解读

        很多开发者会说我在开发项目时也没用到过分散加载文件啊? 事实上,mdk 默认帮你生成一个分散加载文件。以 stm32f103zet6 为例,通过以下配置打开分散加载文件。

     

        以下即为 MDK 提供的默认分散加载方式,我将解读每条代码的含义,解读之前必须了解的是,分散加载文件中必须要有一个加载域,建议两个运行域。

; *************************************************************
 ; *** Scatter-Loading Description File generated by uVision ***
 ; *************************************************************
 ​
 LR_IROM1 0x08000000 0x00080000  {    ; LR_IROM1 加载域名称 0x08000000 加载域起始地址 0x00080000 加载域大小
   ER_IROM1 0x08000000 0x00080000  {  ; ER_IROM1 执行域名称 0x08000000 执行域起始地址 0x00080000 执行域大小
    *.o (RESET, +First) ;RESET 段最先链接,RESET 段在启动文件中,代表中断向量表,将这张表放到 flash 中的起始地址
    *(InRoot$$Sections);链接__main函数,该函数用于RW段数据的拷贝和ZI段数据的清零,__main被编译器封装起来,用户看不到
    .ANY (+RO);加载所有匹配目标文件的只读属性数据,包含:Code、RW-Code、RO-Data。
    .ANY (+XO);此处不理解,可看 mdk 帮助手册进一步挖掘,删除掉也没出错。
   }
   RW_IRAM1 0x20000000 0x00010000  {  ;第二个运行域
    .ANY (+RW +ZI);.ANY 将所有的具有 RW、ZI 属性的变量链接到地址为 0x20000000 的区域。该区域为内部 sram 区。
   }
 }

C 语言中我们知道可以用 attribute 关键字将变量指定到某个地址,但是通过分散加载文件可以更方便的将整个文件包括函数地址,变量地址放到指定的地址处。比如 stm32H7 系列的单片机中,有高速内存区 TCM ( TCM : Tightly-Coupled Memory 紧密耦合内存),这块区域的访问速度极快,stm32H750 拥有高达 480Mhz 的主频,这块区域与内核速度一样。因此将对运行速度要求极高的代码,比如 GUI 绘图这一过程,将这些绘图代码放到 TCM 空间去运行是很有必要的。此时就需要通过分散加载文件指定某些代码在特定的地址空间去执行。

3、如何将某些代码放到指定的地址中运行

未完持续。

本图文内容来源于网友网络收集整理提供,作为学习参考使用,版权属于原作者。
THE END
分享
二维码
< <上一篇
下一篇>>