当前位置: 首页 > java >正文

第三十章 MDK的编译过程及文件类型

第三十章 MDK的编译过程及文件类型

目录

第三十章 MDK的编译过程及文件类型

1 编译过程

2 程序的组成、存储与运行

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

2.2 程序的存储与运行

2.3 编译工具链

2.3.1 armcc

2.3.2 armasm

2.3.4 armar、fromelf及用户指令

3.MDK工程的文件类型

4 源文件

4.1 Output目录下生成的文件

4.2 lib库文件

4.3 dep、d依赖文件


相信您已经非常熟练地使用MDK创建应用程序了,平时使用MDK编写源代码,然后编译生成机器码,再把机器码下载到STM32芯片上运行, 但是这个编译、下载的过程MDK究竟做了什么工作?它编译后生成的各种文件又有什么作用?本章节将对这些过程进行讲解, 了解编译及下载过程有助于理解芯片的工作原理,这些知识对制作IAP(bootloader)以及读写控制器内部FLASH的应用时非常重要。

1 编译过程

首先我们简单了解下MDK的编译过程,它与其它编译器的工作过程是类似的, 该过程见下图:

编译过程生成的不同文件将在后面的小节详细说明,此处先抓住主要流程来理解。

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

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

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

2 程序的组成、存储与运行

2.1 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 堆空间

2.2 程序的存储与运行

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

图中的左侧是应用程序的存储状态,右侧是运行状态,而上方是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,所以无法支持Linux和Windows系统。

当程序存储到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大小,若代码超出了芯片的存储器的极限, 编译器会提示错误,这时就需要裁剪程序了,裁剪时可针对超出的区域来优化。

2.3 编译工具链

在前面编译过程中,MDK调用了各种编译工具,平时我们直接配置MDK,不需要学习如何使用它们,但了解它们是非常有好处的。例如, 若希望使用MDK编译生成bin文件的,需要在MDK中输入指令控制fromelf工具;在本章后面讲解AXF及O文件的时候,需要利用fromelf工具查看其文件信息, 这都是无法直接通过MDK做到的。关于这些工具链的说明,在MDK的帮助手册《ARM Development Tools》都有详细讲解, 点击MDK界面的“help->uVision Help”菜单可打开该文件。

2.3.1 armcc

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

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

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

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

打开MDK的Options for Targe->c/c++菜单,可看到MDK对编译器的控制命令, 见下图,MDK的ARMCC编译选项:

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

了解这些,我们就可以查询具体的MDK编译选项的具体信息了,如c/c++选项中的“Optimization:Leve 1(-O1)”是什么功能呢? 首先可了解到它是“-O”命令,命令后还带个数字,查看MDK的帮助手册,在armcc编译器说明章节, 可详细了解,如编译器选项说明

利用MDK,我们一般不需要自己调用armcc工具,但经过这样的过程我们就会对MDK有更深入的认识,面对它的各种编译选项,就不会那么头疼了。

2.3.2 armasm

armasm是汇编器,它把汇编文件编译成O文件。与armcc类似, MDK对armasm的调用选项可在“Option for Target->Asm”页面进行配置, 见下图,armasm与MDK的编译选项:

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

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

2.3.4 armar、fromelf及用户指令

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

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

例如如果我们想利用fromelf生成bin文件,可以在MDK的“Option for Target->User”页中添加调用fromelf的指令, 见下图,在MDK中添加指令:

在User配置页面中,提供了三种类型的用户指令输入框,在不同组的框输入指令, 可控制指令的执行时间,分别是编译前(Before Compile c/c++ file)、 构建前(Before Build/Rebuild)及构建后(AfterBuild/Rebuild)执行。 这些指令并没有限制必须是arm的编译工具链,例如如果您自己编写了python脚本, 也可以在这里输入用户指令执行该脚本。

图中的生成bin文件指令调用了fromelf工具,紧跟后面的是工具的选项及输出文件名、输入文件名。由于fromelf是根据axf文件生成bin的, 而axf文件又是构建(build)工程后才生成,所以我们把该指令放到“After Build/Rebuild”一栏。

3.MDK工程的文件类型

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

后缀

说明

*.uvguix

MDK5 工程的窗口布局文件,在 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” 来包含)

*.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

构建工程的日志记录文件

*.lst

C 及汇编编译器产生的列表文件

*.map

链接器生成的列表文件,包含存储器映像分布

*.ini

仿真、下载器的脚本文件

这些文件主要分为MDK相关文件、源文件以及编译、链接器生成的文件。我们以“多彩流水灯”工程为例讲解各种文件的功能。

4 源文件

源文件是工程中我们最熟悉的内容了,它们就是我们编写的各种源代码,MDK支持c、cpp、h、s、inc类型的源代码文件, 其中c、cpp分别是c/c++语言的源代码,h是它们的头文件,s是汇编文件,inc是汇编文件的头文件,可使用“$include”语法包含。 编译器根据工程中的源文件最终生成机器码。

4.1 Output目录下生成的文件

点击MDK中的编译按钮,它会根据工程的配置及工程中的源文件输出各种对象和列表文件, 在工程的“Options for Targe->Output->Select Folder for Objects”和 “Options for Targe->Listing->Select Folder for Listings”选项配置它们的输出路径, 见下图,设置Output输出路径 和图 设置Listing输出路径:

4.2 lib库文件

在某些场合下我们希望提供给第三方一个可用的代码库,但不希望对方看到源码,这个时候我们就可以把工程生成lib文件(Library file)提供给对方, 在MDK中可配置“Options for Target->Create Library”选项把工程编译成库文件, 见下图,生成库文件或可执行文件:

工程中生成可执行文件或库文件只能二选一,默认编译是生成可执行文件的,可执行文件即我们下载到芯片上直接运行的机器码。

得到生成的*.lib文件后,可把它像C文件一样添加到其它工程中,并在该工程调用lib提供的函数接口, 除了不能看到*.lib文件的源码,在应用方面它跟C源文件没有区别。

4.3 dep、d依赖文件

*.dep和*.d文件(Dependency file)记录的是工程或其它文件的依赖,主要记录了引用的头文件路径,其中*.dep是整个工程的依赖, 它以工程名命名,而*.d是单个源文件的依赖,它们以对应的源文件名命名。这些记录使用文本格式存储,我们可直接使用记事本打开, 见下两图,工程的dep文件内容和bsp文件的内容:

http://www.xdnf.cn/news/12520.html

相关文章:

  • C++递归语句完全指南:从原理到实践
  • python模块——tqdm
  • 代付业务怎么理解?
  • [假面骑士] 龙骑浅谈
  • 【信息系统项目管理师-论文真题】2025上半年(第一批)论文详解(包括解题思路和写作要点)
  • Java并发容器和原子类
  • CppCon 2015 学习:How to Make Your Data Structures Wait-Free for Reads
  • FPGA没有使用的IO悬空对漏电流有没有影响
  • 什么是质量管理工具?质量管理工具有哪些优势?
  • C#中datagridview单元格value为{}大括号
  • C++优选算法 438. 找到字符串中所有字母异位词
  • 【Dv3Admin】系统视图菜单按钮管理API文件解析
  • CodeTop100 Day24
  • 【UEFI系列】SEC阶段讲解
  • 2024年第十五届蓝桥杯青少Scratch初级组-国赛—画矩形
  • Python-15(类与对象)
  • 人工智能初学者可以从事哪些岗位?
  • 逻辑卷和硬盘配额(补充)
  • 会计 - 合并1- 业务、控制、合并日
  • 6个月Python学习计划 Day 16 - 迭代器、生成器表达式、装饰器入门
  • 【汇编逆向系列】八、函数调用包含混合参数-8种参数传参,条件跳转指令,转型指令,movaps 16字节指令
  • 第16届蓝桥杯青少Scratch 4月stema——飞翔的小燕子
  • 二叉树基础全解:存储方式、遍历原理与查找树对比
  • Go垃圾回收参数调优:实现低延迟服务的实战指南
  • MongoDB检查慢查询db.system.profile.find 分析各参数的作用
  • 一篇文章实现Android图片拼接并保存至相册
  • 4082N信号频谱分析仪
  • 设置应用程序图标
  • Android设备推送traceroute命令进行网络诊断
  • 晨控CK-FR102ANS与欧姆龙NX系列PLC配置EtherNet/IP通讯配置操作手册