MDK编译过程和文件详解

MDK编译过程

MDK编译过程和文件详解_第1张图片

编译生成的不同的文件将在后面进行详细说明

1) 编译:MDK软件使用的编译器是armcc和armasm,它们根据每个c/c++和汇编文件编译成对应的以".o"为后缀的对象文件(Object Code,也称目标文件),其内容主要是从源文件编译得到机器码,包含了代码、数据以及调试使用的信息。

2)链接:链接器armlink把各个.o文件以及库文件链接成一个映像文件".axf"或“.elf”

3)格式转换:一般来说windows或linux系统使用的链接器直接生成可执行映像文件elf后,内核 根据该文件的加载信息后,就可以运行程序了,但在单片机平台上,需要把该文件的内容加载到芯片上,所以还需要对链接器生成的elf映像文件利用格式转换器fromelf转换成“.bin”或“.hex”文件,交给下载器下载到芯片的flash或ROM中。

具体工程的编译过程

举例一个流水灯的工程,以它为例进行讲解,其他工程也是一样的,只是文件有差异,打开工程之后,点击MDK的编译按钮,它会构建整个工程,构建的过程会在MDK的下方“Build Output”窗口提示输出信息,见下图所示:

MDK编译过程和文件详解_第2张图片

构建工程的提示输出主要分为6个部分,说明如下:

1)提示信息的第一部分说明构建过程调用的编译器,图中的编译器的名字是“V5.06(build 20)",后面附带了该编译器的所在文件夹。在电脑上打开该路径,可以看到该编译器包含图编译工具中的各个编译工具:如armcc、armasm、armar、armlink及fromelf,后面的四个工具已在MDK编译过程中讲解,而armar是用来把.o文件打包成lib文件的。

MDK编译过程和文件详解_第3张图片

 2)使用armasm编译汇编文件。图中列出了编译startup启动文件时的提示,编译后每个汇编源文件都对应有一个独立的.o文件

3)使用armcc编译c/c++文件。图中列出了工程中所有的c/c++文件的提示,同样地,编译后每个c/c++源文件都对应有一个独立的.o文件。

4)使用armlink链接对象文件,根据程序的调用把各个.o文件的内容链接起来,最后生成程序的axf映像文件,并附带程序各个域大小的说明,包括code、RO-data、RW-data、及ZI-data的大小。

5)使用fromelf生成下载格式文件,他根据axf映像文件转换成hex文件,并列出编译过程中出现的错误(Error)和警告(Warnning)数量。

6)最后一段提示给出了整个构建过程消耗的时间。

构建完成之后,可在工程的output及listing目录下找到以上过程生成的各种文件,见图编译后output及listing文件夹中的内容。

MDK编译过程和文件详解_第4张图片

 可以看到,每个C源文件都对应生成了.o、.d及.crf后缀的文件,还有一些额外的.dep、.hex、.axf、.htm、.lnp、.lst、及.map文件、

程序的组成、存储及运行

CODE、RO、RW、ZI Data域及堆栈空间

在工程的编译提示输出信息中有一个语句“Program Size:Code=xx RO-data=xx RW-data=xx ZI-data=xx”,它说明了程序各个域的大小,编译后,应用程序中所有具有同一性质的数据包括代码被归到一个域、程序在存储或运行的时候,不同的域会呈现不同的状态,这些域的意义如下:

Code:即代码域,它指的是编译器生成的机器指令,这些内容被存储到ROM区。

RO-data:Read Only data,即只读数据域,它指程序中用到的只读数据,这些数据被存储在ROM区,因而程序不能修改其内容。例如C语言中const关键字定义的变量就是典型的RO-data.

RW-data:Read Write data,即可读写数据域,它指初始化为非0值得可读写数据,程序刚运行时,这些数据具有非0的初始值,且运行的时候它们会常驻在RAM区,因而应用程序可以修改其内容。例如C语言中使用定义的全局变量,且定义时赋予非0值给该变量进行初始化。

ZI-data:Zero Initialie data,即0初始化数据,它指初始化为“0值”的可读写数据域,它与RW-data的区别是程序刚运行时这些数据初始值全都为0,而后续运行过程与RW-data的性质一样,它们也常驻在RAM区域,因而应用程序可以更改其内容,例如C语言中使用定义的全局变量,且定义时赋予“0值”给该变量进行初始化(若定义该变量时没有赋予初始值,编译器会把它当ZI-data来对待,初始化为0)

ZI-data的栈空间(Stack)及堆空间(Heap):在C语言中,函数内部定义的局部变量属于栈空间,进入函数的时候,从向栈空间申请内存给局部变量,退出时释放局部变量,归还内存空间。而使用malloc动态分配的变量属于堆空间。在程序中的栈空间和堆空间都是属于ZI-data区域的,这些空间都会被初始化值为0值。编译器给出的ZI-data占用的空间值中包含了堆栈的大小(经实际测试,若程序中完全没有使用malloc动态申请堆空间,编译器会优化,不把堆空间计算在内)

综上所述,以程序的组件构成为例,他们所属的区域类别见表程序组件所属的区域。

程序组件            所属类别
机器代码指令 Code
常量 RO-data
初值非0的全局变量 RW-data
初值为0的全局变量 ZI-data
局部变量 ZI-data栈空间
使用malloc动态分配的空间 ZI-data堆空间

程序的存储与运行

RW-data和ZI-data它们仅仅是初始值不一样而已,为什么编译器非要把它们分开?这就涉及到程序的存储状态了。应用程序具有静止状态和运行状态。静止态 的程序被存储在非易失性存储器中,如STM32的内部FLASH,因而系统掉电后也能正常保存。但是当程序在运行状态的时候,程序常常需要修改一些暂存数据,由于运行速度的要求,这些数据往往被存放在内存中(RAM),掉电之后这些数据会丢失。因此,程序在静止与运行的时候,它们在存储器中的表现是不一样的。见图应用程序的加载视图和执行视图。

MDK编译过程和文件详解_第5张图片

 图中的左侧是应用程序的存储状态,右侧是运行状态,而上方是RAM存储区域,下方是ROM存储器区域。

程序在存储状态时,RO节(RO section)及RW节都被保存在ROM区。当程序开始运行时,内核直接从ROM中读取代码,并且在执行主体代码之前,会先执行一段加载代码,它把RW节数从ROM复制到RAM,并且在RAM加入ZI节,ZI节的数据都被初始化为0.加载完后,RAM区准备完毕,正式开始执行主体程序。

编译生成的RW-data的数据属于图中的RW节,ZI-data的数据属于图中的ZI节。是否需要掉电保存,这就是把RW-data与ZI-data区别开来的原因,因为在RAM创建数据的时候,默认值为0,如果有的数据要求初值非0,那就需要使用ROM记录该初始值,运行时在复制到RAM。

STM32的RO区域不需要加载到SRAM,内核直接从flash读取指令运行。计算机系统的应用程序运行过程很类似,不过计算机系统的程序在存储状态时位于硬盘,执行的时候甚至会把上述的RO区域(代码、只读数据)加载到内存,加快运行速度,还有虚拟内存管理单元(MMU)辅助加载数据,使得可以运行比物理内存还大的应用程序。而STM32没有MMU,所以无法支持Windows和Linux系统。

当程序存储到STM32芯片内部FLASH时(即ROM区),它占用的空间是Code、RO-data及RW-data的总和,所以如果这些内容比STM32的FLASH空间大,程序就无法被正常保存了。当程序在执行的时候,需要占用内部SRAM空间(即RAM区),占用的空间包括RW-data和ZI-data。应用程序在各个状态时各个区域的组成见表程序状态区域的组成。

程序状态与区域 组成
程序执行时的只读区域(RO) Code+RO data
程序执行时的可读写区域(RW) RW data  + ZI data
程序存储时占用的ROM区 Code + RO data + RW data

在MDK中,我们建立的工程一般会选择芯片型号,选择后就有确定的FLASH和SRAM大小。若代码超出了芯片存储器的极限,编译器会提示错误,这时就需要裁剪程序了,裁剪时可针对超出的区域来优化。

编译工具链

在前面编译过程中,MDK调用了各种编译工具,平时我们直接配置MDK,不需要学习如何使用它们,但了解他们是非常有好处的,例如若希望使用MDK编译生成bin文件的,需要在MDK中输入指令控制fromelf工具,在后面讲解AXF和o文件的时候,需要利用fromelf工具查看其文件信息,这都是无法直接通过MDK做到的。关于这些工具链的说明,在MDK的帮助手册有讲解。

设置环境变量

调用这些编译工具,需要用Windows的命令行提示符工具,为了让命令行方便的找到这些工具,我们先把工具链的目录添加到环境变量中。查看本机工具链所在的具体目录可根据之前编译信息输出的文件路径找到。

armcc、armasm及armlink

armcc用于把c/c++文件编译成ARM指令代码,编译后会输出ELF格式的O文件(对象,目标文件),在命令行中输入“armcc”回车可调用该工具,它会打印帮助说明,见图armcc的帮助提示, 

MDK编译过程和文件详解_第6张图片

 帮助提示中分3部分,其中第一部分是armcc版本信息,第二部分是命令的用法,第三部分是主要命令选项。

根据命令用法,armcc [options] fileq file2 ... filen,在options位置可以输入下面的"--arm"、“--cpu list”选项,若选项带文件输入,则把文件名填充在file1 file2 ...的位置,这些文件一般是c/c++文件。

例如根据它们的帮助说明,“--cpu list”可列出编译器支持的所有cpu,我们在命令中输入“armcc --cpu list”,可查看cpulist中的cpu表。

MDK编译过程和文件详解_第7张图片

 打开MDK的option for target->c/c++菜单,可以看到MDK对编译器的控制命令,见图MDK的ARMCC编译选项。

MDK编译过程和文件详解_第8张图片

 从该图中的命令可以看到,它调用了-c、-cpu -D -g -O0等编译选项,当我们修改MDK的编译配置时,可看到该控制命令也会有相应的变化,然而我们无法在该编译选项中输入命令,只能通过MDK提供的选项修改。

了解这些,我们就可以查询具体的 MDK 编译选项的具体信息了,如 c/c++ 选项中的“ Optimization
Leve 1 -O1 )”是什么功能呢?首先可了解到它是“ -O ”命令,命令后还带个数字,查看 MDK
帮助手册,在 armcc 编译器说明章节,可详细了解,如图 编译器选项说明
armasm
armasm 是汇编器,它把汇编文件编译成 O 文件。与 armcc 类似, MDK armasm 的调用选项可 在“Option for Target->Asm ”页面进行配置,见图 armasm MDK 的编译选项
MDK编译过程和文件详解_第9张图片

 MDK编译过程和文件详解_第10张图片

armlink

armlink是链接器,它把各个O文件链接组合在一起生成ELF格式的AXF文件,AXF文件时可执行的,下载器把该文件中的指令代码下载到芯片后,芯片就能运行程序了,利用armlink还可以控制程序存储到指定的ROM或RAM地址。在MDK可在“Options for Target->Linker”页面配置armlink选项。 见图armlink与MDK的配置选项。

MDK编译过程和文件详解_第11张图片

MDK编译过程和文件详解_第12张图片

 链接器默认是根据芯片类型的存储器分布来生成程序的,该存储器分布被记录在工程里的sct后缀的文件中,有特殊需要的话可自行编辑该文件,改变链接器的链接方式。

armar和fromelf及用户指令

armar工具用于把工程打包成库文件,fromelf可根据axf文件生成hex,bin文件,hex和bin文件是大多数下载器支持的 下载文件格式。

在MDK中,针对armar和fromelf工具的选项几乎没有,仅集成了生成hex和lib的选项, 见图控制fromelf生成hex及控制armar生成lib的配置。

MDK编译过程和文件详解_第13张图片

 例如如果我们想要利用fromelf生成bin文件,可以在MDK的“option for target -> user"页中添加调用fromelf的指令,见图在MDK中添加如下指令。

MDK编译过程和文件详解_第14张图片

在User配置页面中,提供了3种类型的用户输入指令入框,在不同组的框输入指令,可控制指令

的执行时间,分别是编译前 (Before Compile c/c++ fifile) 、构建前 (Before Build/Rebuild) 及构建后
(AfterBuild/Rebuild) 执行。这些指令并没有限制必须是 arm 的编译工具链,例如如果您自己编写
python 脚本,也可以在这里输入用户指令执行该脚本。
图中的生成 bin 文件指令调用了 fromelf 工具,紧跟后面的是工具的选项及输出文件名、输入文件
名。由于 fromelf 是根据 axf 文件生成 bin 的,而 axf 文件又是构建 (build) 工程后才生成,所以我
们把该指令放到“ After Build/Rebuild ”一栏

 

MDK工程的文件类型

除了上述编译过程生成的文件,MDK工程中还包括了各种各样的文件,下面我们统一介绍,MDK工程的常见文件类型见表MDK常见的文件类型。

后缀 说明
Project 目录下的工程文件
*.uvguix MDK工程的窗口布局文件,在MDK4中*.UVGUI后缀的文件功能相同       
*uvprojx MDK5的工程文件,它使用了XML格式记录工程结构,双击它可以打开整个工程,
在mDK4中,*.UVPROJ后缀的文件功能相同
*.uvoptx        MDK5的工程配置选项,包含debugger trace configuration breakpooints以及当前
打开的文件,在MDK4中*.UVOPT后缀的文件功能相同
*.ini 某些下载器的配置记录文件
源文件
*.c C语言源文件
*.cpp         C++源文件
*.h C/C++的头文件
*.s 汇编语言的源文件
*.inc 汇编语言的头文件(使用“$include”来包含)
Output 目录下的文件
*.lib 库文件
*.dep 整个工程的依赖文件
*.d 描述了对应.o的依赖的文件
*.crf 交叉引用文件,包含了浏览信息(定义,引用及标识符)
*.o 可重定位的对象文件(目标文件)
*.bin 二进制格式的映像文件,是纯粹的FLASH映像,不包含任何额外信息
*.hex Intel Hex格式的映像文件,可理解为带存储地址的描述的bin文件
*.elf 由GCC编译生成的文件,功能跟axf文件一样,该文件不可重定位
*.axf 由ARMCC编译生成的可执行对象文件,可用于调试,该文件不可重定位
*.sct 链接器控制文件(分散加载)
*.scr 连接器产生的分散加载文件
*.lnp mdk生成的连接输入文件,可用于调用链接器时的命令输入
*.htm 链接器生成的静态调用图文件
*.build_log.htm 构建工程的日志记录文件
Listing目录下的文件
*.lst C及汇编编译器产生的列表文件
*.map 链接器生成的列表文件,包含存储器映像分布
其它
*.ini 仿真、下载器的脚本文件

你可能感兴趣的:(单片机,嵌入式硬件,MDK,文件类型)