admin 管理员组

文章数量: 887031


2024年2月6日发(作者:dede动态模板错位)

嵌入式 C 语言程序的运行2011-08-16 15:05

我们做 C 语言中这么多年,都知道这样一句话, C 语言代码形成可执行程序,需要经过编

译->汇编- >链接三个阶段。背都背熟了,但是到底啥意思,每一步都会产生一些什么东

西,很多人都不是太了解。今天就详细的来说说这个问题:

先看下图,在这个图中,我详细的描述了,整个过程及中间的一些步骤:

代码段,只读数据段,读写数据段,未初始化数据段属于静态区域。栈和堆属于动态区

域。代码段,只读数据段和读写数据段将在连接之后产生,未初始化数据段将在程序初始化

的时候开辟,而堆和栈将在程序的运行中分配和释放。

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 )

主要工作

初始化阶段

未初始化数据段( BSS )

代码段( Code )

只读数据段( RO Data )

读写数据段( RW Data )

未初始化数据段( BSS )

堆( heap )

栈( stack )

开辟 BSS 段 并且清零

知识点:程序直接载入 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 )

读写数据段( RW Data )

初始化阶段

未初始化数据段( BSS )

代码段( Code )

只读数据段( RO Data )

读写数据段( RW Data )

运行阶段

未初始化数据段( BSS )

堆( heap )

知识点:程序在 ROM 或者 Flash 中以 XIP 形式运行的时候,不需要复制代码段和只读数据

段,但是需要在 RAM 中复制读写数据 段,并另辟未初始化数据段。

13.4.3 固化程序的加载运行

在某些时候,在存放程序的位置是不能运行程序的,例如程序存储在不能以 XIP 方式运行的

Nand-Flash 或者硬盘中,在这种情况下,必须将程序完全加载到 RAM 中才可以运行。固化

程序加载运行的内存布局如图 13-8所示:

复制 读写数据段到 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 )

代码段( Code )

只读数据段( RO Data )

读写数据段( RW Data )

未初始化数据段( BSS )

代码段( Code )

只读数据段( RO Data )

读写数据段( RW Data )

未初始化数据段( BSS )

堆( heap )

栈( stack )

主要工作

代码的映像

将程 序放置在 Flash 中

初始化阶段

加载 代码段和只读数据段到 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 运行。


本文标签: 程序 运行 数据 需要