C语言程序运行在嵌入式中的方式
时间:2011-03-23 来源:☆&寒 烟☆
我们所做的程序最后都是要放到设备中运行的,鉴于嵌入式设备的特殊性,它的程序在运行方式上和PC机上还是有一些不同,今天就来给大家总结一下:
RAM调试运行:即在程序的调试阶段将主机的映像文件直接放置到目标系统的RAM中。此时,RAM既是程序的存储空间,也是程序的运行空间。
在嵌入式系统中,这是一种常用的调试方式(非通常的运行方式)。在通常的运行方式下,程序运行的起始地址一般不可能是RAM。RAM在掉电之后内容会丢失,因此系统上电的时候,RAM中一般不会有有效的程序。但是在程序的调试阶段,可以将程序直接载入RAM,然后在RAM的程序载入地址处运行程序。下图是嵌入式系统RAM中的调试程序的内存布局:
这是一种相对简单的形式,因为代码段的存储地址和运行地址是相同的,都是RAM(SDRAM或者SRAM)中的地址。在这种情况下,程序没有运行初始化阶段加载的问题。 从主机向目标机载入程序的时候,程序映像文件中代码段(code或text)、只读数据段、读写数据段依次载入目标系统RAM(SDRAM或者SRAM)的空间中。程序载入到目标机之后,将从代码区的地址开始运行,在运行的初始化阶段,将开辟未初始化数据区,并将其初始化为0,在运行时将动态开辟堆区和栈区。
在没有操作系统的情况下,开辟内存的工作都是由编译器生成的代码完成的,实现的原理是在映像文件中加入这些代码。主要工作包括:在程序运行时根据实际大小开辟未初始化的数据段;初始化栈区的指针,这个指针和物理内存的实际大小有关;在调用相关函数(malloc、free)时使用堆区,这些函数一般由调用库函数实现。下表列出了C语言程序在RAM中的调试过程。
阶段 |
涉及的部分 |
主要工作 |
程序的映像 |
代码段(Code) 只读数据段(RO Data) 读写数据段(RW Data) |
将程序放置在RAM中 |
初始化阶段 |
未初始化数据段(BSS) |
开辟BSS段 并且清零 |
运行阶段 |
代码段(Code) 只读数据段(RO Data) 读写数据段(RW Data) 未初始化数据段(BSS) 堆(heap) 栈(stack) |
运行RAM代 码段中的程序,动态地在RAM中开辟堆和栈 |
小结:程序直接载入RAM运行时,程序的加载位置和运行位置是一致的,因此不存在段复制的问题,需要在初始化阶段开辟未初始化区 域,在运行时使用堆栈。
固化程序的XIP运行:固化应用是一种嵌入式系统常用的运行方式,其前提是目标代码位于目标系统ROM(Flash)中。ROM中的区域包括映像文件的代码段(code或text)、只读数据段(RO Data)、读写数据段(RW Data),如下图所示:
代码的运行也是在ROM(Flash)中,因此,在编译过程中代码的存储地址和运行地址是相同的,由于上电时需要启动,因此该代码的位置一般是(0x0)。 在这种应用中,一件重要的事情就是将已初始化读写段的数据从Flash中复制到SDRAM中,由于已初始化读写段既需要固化,也需要在运行时修改,因此这一步是必须有的,在程序的初始化阶段需要完成这一步。
一般来说,在编译过程中需要定义读写段和未初始化段的地址。在程序中可获取这些地址,然后就可以在程序的中加入复制的代码,实现读写段的转移。下表列出了C语言程序的XIP运行过程:
阶 段 |
涉及的部分 |
主要工作 |
程序的映像 |
代码段(Code) 只读数据段(RO Data) 读写数据段(RW Data) |
程序 放置在Flash中 |
初始化阶段 |
读写数据段(RW Data) 未初始化数据段(BSS) |
复制 读写数据段到RAM中 开辟 未初始化段并且清零 |
运行阶段 |
代码段(Code) 只读数据段(RO Data) 读写数据段(RW Data) 未初始化数据段(BSS) 堆(heap) 栈(stack) |
运行Flash代 码段中的程序,动态地在RAM中开辟堆和栈 |
小结:程序在ROM或者Flash中以XIP形式运行的时候,不需要复制代码段和只读数据段,但是需要在RAM中复制读写数据 段,并另辟未初始化数据段。
固化程序的加载运行:在某些时候,在存放程序的位置是不能运行程序的,例如程序存储在不能以XIP方式运行的Nand-Flash或者硬盘中,在这种情况下,必须将程序完全加载到RAM中才可以运行。固化程序加载运行的内存布局如下图所示:
依照这种方式运行程序,需要将Flash中所有的内容全部复制到SDRAM或者SRAM中。在一般情况 下,SDRAM或者SRAM的速度要快于Flash。这样做的另外一个好处是可以加快程序的运行速度。也就是说,即使Flash可以运行程序,将程序加载 到RAM中运行也还有一定的优势。
这样做也产生了另外一个问题:代码段的载入地址和运行地址是不相同的,载入地址是在ROM(Flash)中, 但是运行的地址是在RAM(SDRAM或者SRAM)中。对于这个问题,不同的系统在加载程序的时候有不同的解决方式。
C语言固化程序的加载运行过程如下表所示:
阶 段 |
涉及的部分 |
主要工作 |
代码的映像 |
代码段(Code) 只读数据段(RO Data) 读写数据段(RW Data) |
将程 序放置在Flash中 |
初始化阶段 |
代码段(Code) 只读数据段(RO Data) 读写数据段(RW Data) 未初始化数据段(BSS) |
加载 代码段和只读数据段到RAM中 复制 读写数据段到RAM中 开辟 未初始化段并且清零 |
运行阶段 |
代码段(Code) 只读数据段(RO Data) 读写数据段(RW Data) 未初始化数据段(BSS) 堆(heap) 栈(stack) |
运行RAM代 码段中的程序,动态地在RAM中开辟堆和栈 |
小结:固化程序在加载运行时,需要复制代码段、只读数据段和读写数据段到RAM中,并另辟未初始化数据段,然后在RAM中运行程序(执行代码段)。
以这种加载方式的运行程序,另外一个重要的问题是:如何把代码移到RAM中。在有操作系统的情况下,代码的复制工作是由操作系统完成的,在没有操作系统的情况下,处理方式相对复杂,程序需要自我复制。显然,这种方式实现的前提是代码最初放置在可以以XIP方式执行的内存中。
程序本身复制的过程也是需要通过程序代码完成的,这时需要程序中的代码根据将包含自己的程序从ROM或者Flash中复制到RAM中。这是一个比较复杂的过程,程序的最前面部分是具有复制功能的代 码。系统上电后,从ROM或者Flash起始地址运行,具有复制功能的代码将全部代码段和其他需要复制的部分复制到RAM中,然后跳转到RAM中重新运行程序。
固化程序加载复制和跳转过程如下图所示。
在代码的前面一小部分是初始化的内容,这部分内容中有一部分是复制程序,这段复制程序将代码段复制至RAM中,当这段初始化程序运行完成后,将跳转到RAM中的某个地址运行。
注:本篇文章参考了电子工业出版社的《嵌入式Linux上的C语言编程实践一书》,在这里对本书作者再次表示感谢。