c语言程序的运行


13.4  C语言程序的运行

在嵌入式系统中,程序最终是要放置在内存中运行的,程序的几个段,最终会转化为内存中的几个区域。C语言可执行程序的内存布局如图13-5所示。

图13-5  C语言可执行程序的内存布局

在内存中,从低地址到高地址,依次是只读段、读写段、未初始化数据段、堆段、栈段。

映像文件中将包含代码段(Code)、只读数据段(RO Data)以及读写数据段(RW Data),未初始化数据段(BSS)将在程序的初始化阶段中开辟,堆栈在程序运行时动态开辟。

只读区(RO)包括了代码和只读数据,在内存区域中,代码段(Code)和只读数据段(Ro Data)的存放形式上基本没有区别。

对于程序运行时的内存使用,堆和栈一般是相向扩展的。堆的分配由程序决定,栈由编译器管理。

在以上概念中,只是一种内存分布,并没有考虑实际系统的情况。在实际的系统中,程序有载入和运行两个概念。嵌入式系统由两种内存,一种是可以固化只读的内存(如:ROM,Nor Flash),另一种是易失的可读写的内存(如:SRAM和SDRAM)。程序中的各个段也有需要固化和需要读写的。程序中的各段必须载入到内存的恰当位置,程序才可以运行。C语言各部分的需要固化和可写的情况如表13-2所示。

表13-2  C语言各部分的需要支持固化和可写的情况

需要固化

需要可写

代码(Code

只读数据(RO data

读写数据(RW data

未初始化数据(BSS

堆(heap

栈(stack

在嵌入式系统中,经过编译的C语言程序可以通过操作系统运行,也可以在没有操作系统的情况下运行。程序存放的位置和运行的位置通常是不一样的。

一般情况下,经过编译后的程序存储在Flash或者硬盘中,在运行时需要将程序加载到RAM中。嵌入式系统的Nor Flash和硬盘还有一定的差别,在硬盘的程序必须加载到RAM中才可以运行,但是在Nor Flash中的程序可以通过XIP(eXcutive In Place)的方式运行。

在嵌入式系统中,C语言程序的运行包括3种类型:第一种是调试阶段的程序运行,这个阶段程序存放的位置和运行的位置是相同的;第二种是程序直接在Flash中运行(XIP);第三种是将Flash或者硬盘中的程序完全加载到RAM中运行。

在C语言程序的运行中,存在着两个基本的内存空间,一个是程序的存储空间,另一个是程序的运行空间。程序的存储空间必须包括代码段、只读数据段和读写数据段,程序的加载区域必须包括读写数据段和未初始化数据段如表13-3所示。

表13-3  C语言各部分使用的存储空间

代码

只读数据

读写数据

未初始化数据

程序的存储空间(ROM

需要

不需要

程序的加载空间(RAM

不需要

需要

由于程序放入系统后,必须包括所有需要的信息,代码表示要运行的机器代码,只读数据和读写数据包含程序中预先设置好的数据值,这些都是需要固化存储的,但是未初始化数据没有初值,因此只需要标示它的大小,而不需要存储区域。

在程序运行的初始化阶段,将进行加载动作,其中读写数据和未初始化数据都是要在程序中进行“写”操作,因此不可能放在只读的区域内,必须放入RAM中。当然,程序也可以将代码和只读数据放入RAM。

在程序运行后,堆和栈将在程序运行过程中动态地分配和释放。

13.4.1  RAM调试运行

本节介绍程序的一种特殊的运行方式,即在程序的调试阶段将主机的映像文件直接放置到目标系统的RAM中。在这种应用中,RAM既是程序的存储空间,也是程序的运行空间。

在嵌入式系统中,这是一种常用的调试方式,而不是通常的运行方式。在通常的运行方式下,程序运行的起始地址一 般不可能是RAM。RAM在掉电之后内容会丢失,因此系统上电的时候,RAM中一般不会有有效的程序。但是在程序的调试阶段,可以将程序直接载入RAM, 然后在RAM的程序载入地址处运行程序。

嵌入式系统RAM中的调试程序的内存布局如图13-6所示。

图13-6  RAM中的调试程序的内存布局

这是一种相对简单的形式,因为代码段的存储地址和运行地址是相同的,都是RAM(SDRAM或者SRAM)中的地址。在这种情况下,程序没有运行初始化阶段加载的问题。

从主机向目标机载入程序的时候,程序映像文件中代码段(code或text)、只读数据段、读写数据段依次载入目标系统RAM(SDRAM或者SRAM)的空间中。

程序载入到目标机之后,将从代码区的地址开始运行,在运行的初始化阶段,将开辟未初始化数据区,并将其初始化为0,在运行时将动态开辟堆区和栈区。

在没有操作系统的情况下,开辟内存的工作都是由编译器生成的代码完成的,实现的原理是在映像文件中加入这些代 码。主要工作包括:在程序运行时根据实际大小开辟未初始化的数据段;初始化栈区的指针,这个指针和物理内存的实际大小有关;在调用相关函数 (malloc、free)时使用堆区,这些函数一般由调用库函数实现。表13-4列出了C语言程序在RAM中的调试过程。

表13-4  C语言程序在RAM中的调试过程

阶段

涉及的部分

主要工作

程序的映像

代码段(Code

只读数据段(RO Data

读写数据段(RW Data

将程序放置在RAM

初始化阶段

未初始化数据段(BSS

开辟BSS段并且清零

运行阶段

代码段(Code

只读数据段(RO Data

读写数据段(RW Data

未初始化数据段(BSS

堆(heap

栈(stack

运行RAM代码段中的程序,动态地在RAM中开辟堆和栈

知识点:程序直接载入RAM运行时,程序的加载位置和运行位置是一致的,因此不存在段复制的问题,需要在初始化阶段开辟未初始化区域,在运行时使用堆栈。

13.4.2  固化程序的XIP运行

固化应用是一种嵌入式系统常用的运行方式,其前提是目标代码位于目标系统ROM(Flash)中。ROM中的区域包括映像文件的代码段(code或text)、只读数据段(RO Data)、读写数据段(RW Data)。

以XIP(在位置执行)方式运行程序时内存布局如图13-7所示。

代码的运行也是在ROM(Flash)中,因此,在编译过程中代码的存储地址和运行地址是相同的,由于上电时需要启动,因此该代码的位置一般是(0x0)。

在这种应用中,一件重要的事情就是将已初始化读写段的数据从Flash中复制到SDRAM中,由于已初始化读写段既需要固化,也需要在运行时修改,因此这一步是必须有的,在程序的初始化阶段需要完成这一步。

图13-7  XIP运行程序时的内存布局

一般来说,在编译过程中需要定义读写段和未初始化段的地址。在程序中可获取这些地址,然后就可以在程序的中加入复制的代码,实现读写段的转移。表13-5列出了C语言程序的XIP运行过程。

表13-5  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中复制读写数据段,并另辟未初始化数据段。

13.4.3  固化程序的加载运行

在某些时候,在存放程序的位置是不能运行程序的,例如程序存储在不能以XIP方式运行的Nand-Flash或者硬盘中,在这种情况下,必须将程序完全加载到RAM中才可以运行。固化程序加载运行的内存布局如图13-8所示:

 

图13-8  固化程序加载运行的内存布局

依照这种方式运行程序,需要将Flash中所有的内容全部复制到SDRAM或者SRAM中。在一般情况 下,SDRAM或者SRAM的速度要快于Flash。这样做的另外一个好处是可以加快程序的运行速度。也就是说,即使Flash可以运行程序,将程序加载 到RAM中运行也还有一定的优势。

这样做也产生了另外一个问题:代码段的载入地址和运行地址是不相同的,载入地址是在ROM(Flash)中,但是运行的地址是在RAM(SDRAM或者SRAM)中。对于这个问题,不同的系统在加载程序的时候有不同的解决方式。

C语言固化程序的加载运行过程如表13-6所示。

表13-6  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中重新运行 程序。

固化程序加载复制和跳转过程如图13-9所示。

图13-9  固化程序加载复制和跳转过程

在代码的前面一小部分是初始化的内容,这部分内容中有一部分是复制程序,这段复制程序将代码段复制至RAM中,当这段初始化程序运行完成后,将跳转到RAM中的某个地址运行。

13.4.4  C语言程序的运行总结

在上面几节,主要介绍了C语言运行时内存的使用 情况。其关注点是程序中主要的段,事实上,程序可能不仅包括了上述主要段,还可能包括一些头信息。程序实际的运行也分为在操作系统下运行和直接运行等情 况。在具有操作系统的情况下,程序由操作系统加载运行,加载的时候可执行程序可以是一个文件,这个文件将包含程序的主要段以及头信息。

对于Linux操作系统,目标程序是可执行的ELF(Executable and linking Format)格式;对于uCLinux操作系统,目标程序是Flat格式;对于需要在系统直接运行的程序,目标程序应该是纯粹的二进制代码,载入系统后,直接转到代码区地址执行。

事实上,无论运行环境如何,C语言程序在运行时所进行的动作都是类似的。程序在准备开始运行的时候,以下几个条件都是必不可少的:

1.代码段必须位于可运行的存储区。

2.读写数据段必须在可以读写的内存中,而且必须经过初始化。

3.未初始化数据段必须在可以读写的内存中开辟,并被清空。

对于第1点,代码段如果位于可以运行的存储区域中(例如Nor Flash或者RAM),它就不需要加载,可以直接运行;如果代码段位于不能运行的存储区域中(例如:Nand Flash或者硬盘)中,它就必须被加载到RAM运行。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章