专注电子技术学习与研究
当前位置:单片机教程网 >> MCU设计实例 >> 浏览文章

代码是如何跑起来的?

作者:佚名   来源:本站原创   点击数:  更新时间:2011年07月13日   【字体:

如果你已经准备好了,那么我们开始先了解一下让人疯狂的处理器。虽然处理器的制作工艺极其复杂,然而它的工作确极其的简单。处理器就是在不停地将数据移来移去,变来变去。因此我们的工作就是告诉处理器你要的数据在哪里,你要怎样操作这些数据。当然这是一种相当通俗的说法,实际上要比这复杂得多,我在后面会介绍一下Cortex-M3核执行指令的细节。虽然Cortex-M3核是为嵌入式片上系统所设计的,但它已经非常接近通用处理器了,通用处理器就是PC机上的CPU。因此以Cortex-M3核来解释本文所要探讨的问题已经足够了。要清楚的知道代码是如何运行的,一个很重要的问题就不能被回避,那就是程序是如何存储的。

程序是如何存储的?
    程序是由可执行代码和数据构成的,理解这一点是很了不起的。但是代码和数据却是分开存储的,为了详细了解程序在存储器内的分布情况,我将一个由IAR EWARM软件生成的存储器镜像文件的最后一部分列在这里,以供讲解用,在后面的叙述中我将用Flash代替存储器这个称呼,因为大量的单片机存储器都是基于Flash技术的。因此当你看到Flash时,你要知道这是在说存储器。



引用图片


 上面那张图片向你展示了,你的程序是如何在Flash内部分布的。但是有的读者并不清楚那张图里所提供的信息,下面将叙述理解上面那张图所必须掌握的。

SEGMENT

    SEGMENT的中文意思是段。就像我在开始所叙述的那样,代码与数据是分开存储的,它们被安排到不同的Flash空间内,这些不同的Flash空间就是这里的段。因此你很自然的就会想到,段应该包括代码段与数据段,就是CODE  SEGMENT和DATA   SEGMENT。没错,就像你想的那样,代码段用来存储代码,而数据段用来存储数据。为了让代码更加的安全以保证不会因非法操作而改变代码,通常要为段设置一些访问规则,这些规则构成了段的属性。例如,代码段都是可执行的,只读不可修改的。而数据段是不可执行的,可读可修改的。如果试图将数据段的数据作为代码来执行,将会引起故障。当你了解的更深入时,你就会理解这样做的重要性。然而为了更有效的管理数据和代码,实际中对代码段与数据段做了更为细致的划分,这就是你为什么会看到上图中SEGMENT下面会有诸如INTVEC、ICODE和CODE等这么多段名字的原因。当你清楚了程序是按照段来存储的,那么对于上图中的那个表我想你已经有了一个大致的认识,但是你还不清楚,为什么会有那么多的段。因此下一步的工作,就是我们一起来分析那些段。

这些段都是干什么的?
  
    这一节的内容是和编程息息相关的,你会在这里看到你所写的代码最终被存储到了Flash的什么地方,当你理解了这节所介绍到的内容,你就会发现,指导你编写正确代码的原则会变得越来越少。我们关注的仍然是上面那个表,其中各个字段的含义如下:

SEGMENT           ——段的名称

START ADDRES      ——段的起始地址,十六进制表示

END ADDRES        ——段的结束地址,十六进制表示

SIZE              ——段的大小,以字节为单位,十六进制表示

ALIGN             ——段的对齐长度,是2的ALIGN次幂。这里ALIGN都是2,就是说所有的段都是4个字节对齐的。

    其中有两个字段SPACE和TYPE没有说到,这两个字段和我们要讨论的无关,另一点就是我确实记不清楚了,因此也不敢胡乱的瞎写!

    我们先看数据段,第一个数据段是DATA_ID,这个段的起始地址是0x080017E0,结束地址是0x080018A0,长度为0x18个字节,注意这里的数据都是十六进制的。如果你使用C语言定义了一个全局变量a,并且为他赋了一个初值,就像下面这样:

int a = 8;

那么这个变量a就会被分配到DATA_ID段。又或者你定义了一个局部静态变量,并且也要赋初值,像这样:

void t(void)
{
    static int b = 5;
}

    那么这个局部静态变量b也会被分配到DATA_ID段。因此DATA_ID段是为具有整个程序生命周期并初始化的数据分配的空间,记住,一定是初始化的整个程序生命周期的变量。例如像下面的这两个变量c和d就不会被分配到DATA_ID段,而是被分配到了DATA_Z段,因为这两个变量没有被初始化。
int c;
void t(void)
{
    static int d;
}
    这样你也知道了,具有整个程序生命周期且没有被初始化的变量被分配到DATA_Z段,在C语言中,这样的变量是被编译器初始化为0的,因此DATA_Z中的Z是Zero的第一个字母,而DATA_ID中的I你可以理解为Init的第一个字母,因此DATA_ID段是初始化的数据段,而DATA_Z段是零初始化的数据段。

int m = 0;

    这个m是被分配到DATA_ID段的,虽然它是被初始化为0,但它是我们手动初始化的。

    我们再看DATA_C段,这是一个很容易理解的数据段,它是用来分配被标记为const类型的数据,就像下面这样的数据。

const int n = 8;

    还有一个DATA_I段,细心的读者会发现,这个段的起始地址一下子变大了很多,事实上从0x20000000开始是RAM的起始地址,而0x08000000对应的是flash 的起始地址,因此上面两个个数据段DATA_ID段和DATA_C段都是在Flash内,而DATA_I段和DATA_Z段是在RAM中分配的。如果你在仔细一点会发现DATA_I段与DATA_ID段的大小是一样的,这是偶然还是它们之间有着某种联系,事实上是DATA_I段是DATA_ID段的副本,它们是一样的,不过一个在Flash内,一个在RAM内。因为程序最终是在RAM内执行的,所以必须将这些数据复制到RAM内。而DATA_C段是为常量分配的,是不变化的,所以在RAM内没有对应的空间。而DATA_Z段是零初始化的数据段,既然知道了这一点,就没有必要在Flash内分配这些数据了,Flash内只存储零初始化数据所需要的空间,在程序运行时再到RAM中去分配,因此DATA_Z段是被分配到RAM内的。
  
    最后一个CSTACK段也是在RAM内,这个段是为栈使用的,局部非静态变量和子函数返回地址还有发生中断时的现场保护,都要使用栈,如果你使用过汇编,你就会对这些非常清楚。由于局部非静态变量是在栈内分配的,所以它的值是不确定的,使用时最好先初始化。

关闭窗口

相关文章