嵌入式开发的流程与高层开发大体类似,编码—编译、链接—运行。中间当然可以有联机调试,重新编码等递归过程。但有一些不同之处。
(1)、首先,开发平台不同。受嵌入式平台处理能力所限,嵌入式开发一般都采用交叉编译环境开发。所谓交叉编译就是在A平台上编译B平台上运行的目标程序。在A平台上运行的B平台程序编译器就被称为交叉编译器。一个初入门者,建立一套这样的编译环境也许就要花掉几天的时间。
(2)、其次,调试方式不同。我们在Windows或者Linux上开发的程序可以马上运行察看运行结果,也可以利用IDE来调试运行过程,但是嵌入式开发者却至少需要作一系列工作才能达到这种地步。目前最流行的是采用JTAG方式连接到目标系统上,将编译成功的代码下载运行,高级的调试器几乎可以像VC环境一样任意的调试程序。
(3)、再者,开发者所了解层次结构不同。高层软件开发者把工作的重点放在对应用需求的理解和实现上。嵌入式开发者对整个过程细节必须比高层开发者有更深的认识。最大不同之处在于有操作系统支持的程序不需要你关心程序的运行地址以及程序链接后各个程序块最后的位置。像Windows,Linux这类需要MMU支持的操作系统,其程序都是放置在虚拟地址空间的一个固定的内存地址。不管程序在真正RAM空间的地址位置在哪里,最后都由MMU映射到虚拟地址空间的一个固定的地址。为什么程序的运行与存放的地址要相关呢?学过汇编原理,或者看过最后编译成机器码程序的人就知道,程序中的变量、函数最后都在机器码中体现为地址,程序的跳转,子程序的调用,以及变量调用最后都是CPU通过直接提取其地址来实现的。编译时指定的TEXT_BASE就是所有一切地址的参考值。如果你指定的地址与最后程序放置的地址不一致显然不能正常运行。但也有例外,不过不寻常的用法当然要付出不寻常的努力。
有两种方法可以解决这个问题:
一种方法是在程序的最起始编写与地址无关的代码,最后将后面的程序自搬移到你真正指定的TEXT_BASE然后跳转到你将要运行的代码处。
另一种方法是,TEXT_BASE指定为你程序的存放地址,然后将程序搬移到真正运行的地址,有一个变量将后者的地址记录下来作为参考值,在以后的符号表地址都以此值作为参考与偏移值合成为其真正的地址。听起来很拗口,实现起来也很难,在后面的内容中有更好的解决办法—用一个BootLoader支持。
另外,一个完整的程序必然至少有三个段TEXT(正文,也就是最后用程序编译后的机器指令)段、BSS(未初始变量)段DATA(初始化变量)段。前面讲到的TEXT_BASE只是TEXT段的基址,对于另外的BSS段和DATA段,如果最后的整个程序放在RAM中,那么三个段可以连续放置,但是,如果程序是放置在ROM或者FLASH这种只读存储器中,那么你还需要指定你的其他段的地址,因为代码在运行中是不改变的,而后两者却不同。这些工作都是在链接的时候完成,编译器必然为你提供了一些手段让你完成这些工作。还是那句话,有操作系统支持的编程屏蔽了这些细节,让你完全不用考虑这些头痛的问题。
但是嵌入式开发者没有那么幸运,他们总是在一个冷冰冰的芯片上从头做起。CPU上电复位总是从一个固定的地址去找程序,开始其繁忙的工作。对于我们的PC来说这个地址就是我们的BIOS程序,对于嵌入式系统,一般没有BIOS支持,RAM不能在掉电情况下保留你的程序,所以必须将程序存放在ROM或FLASH中,但是一般来讲,这些存储器的宽度和速度都无法与RAM相提并论。程序在这些存储器上运行会降低运行速率。大多数的方案是在此处存放一个BootLoader,BootLoader所完成的功能可多可少,一个基本的BootLoader只完成一些系统初始化并将用户程序搬移到一定地址,然后跳转到用户程序即交出CPU控制权,功能强大的BootLoad还可以支持网络、串口下载,甚至调试功能。但不要指望有一个像PC BIOS那样通用的BootLoader供你使用,至少你需要作一些移植工作使其符合你的系统,这个移植工作也是你开发的一个部分,作为嵌入式开发的入门者来讲,移植或者编写一个BootLoader会使你受益匪浅。
没有BootLoader行不行?当然可以,要么你就牺牲效率直接从ROM中运行,要么你就自己编写程序搬移代码去RAM运行,最主要的是,开发过程中你要有好的调试工具支持在线调试,否则你就得在改动哪怕一个变量的情况下都要去重新烧片验证。继续程序入口的话题,不管过程如何,程序最后在执行时都是变成了机器指令,一个纯的执行程序就是这些机器指令的集合。像我们在操作系统上的可运行程序都不是纯的执行程序,而是带有格式的。一般除了包含上面提到的几个段以外,还有程序的长度,校验以及程序入口——就是从哪儿开始执行用户程序。为什么有了程序地址还需要有程序的入口呢?这是因为你要真正开始执行的代码并非一定放置在一个文件的最开始,就算放在最开始,除非你去控制链接,否则在多文件的情况下,编译器也不一定将你的这段程序放置在最后程序的最顶端。像我们一般有操作系统支持的程序,只需在你的代码中有一个main作为程序入口—注意这个main只是大多数编译器约成定俗的入口,除非你利用了别人的初始化库,否则程序入口可以自行设定—即可。显然,带有格式的这种执行文件使用更加灵活,但需要BootLoader的支持。有关执行文件格式的内容可以看看ELF文件格式。