Chinaunix首页 | 论坛 | 博客
  • 博客访问: 105654
  • 博文数量: 32
  • 博客积分: 0
  • 博客等级: 民兵
  • 技术积分: 318
  • 用 户 组: 普通用户
  • 注册时间: 2013-08-30 09:58
个人简介

经过长期对C语言的研究,目前只有两个方面不懂:这也不懂,那也不懂。 本人擅长 Ai、Fw、Fl、Br、Ps 等软件的安装卸载,精通 CSS、Java、PHP、C、C++、C#、Java、Ruby、Python、Objective-C等单词的拼写,熟悉 Windows、Linux、Mac、Android、iOS等系统的开关机 https://github.com/ianhom

文章分类

全部博文(32)

我的朋友

分类: C/C++

2015-06-25 22:11:40

过去的自己,你好!
    又是好久没有写东西了,工作和生活都很忙,总是没空思考。你以后会变得和我一样忙碌,千万不要安逸,因为所谓的“艰难困苦”来的比你想象的快得多。Life is tough, you must be tougher。
    今天就基于KL25系列的MCU向你介绍下内存结构。你以后会在工作中会使用模块化、面向对象方式的编程编写的你产品代码,不过那个时候你没有留意过内存的使用情况,只能说你比较幸运没有遇到问题而已。不过很快你就会遇到一个内存不够用的情况,然后你就会来思考内存里都存了什么。
    首先我介绍下分析环境:使用的平台是
FRDM-KL25Z128k FLASH + 16K RAM,编译环境为IAR,编译器优化等级为NONE,通过IAR.icf文件可以看出KL25芯片地址资源的分配情况。如下图所示,flash区域从0x00000000地址开始,大小为128kRAM区域由两个部分组成,由0x20000000这个地址分开,RAM1区域在0x20000000地址之前,大小为1/4个总RAM大小(16k),即总RAM区域的起始地址为0x1FFFF000RAM2区域在0x20000000地址之后,大小为3/4个总RAM大小,即总RAM区域的结束地址为0x20003000。这样我们就是知道要分析的RAM的地址空间了(0x1FFFF000~0x20003000),那这个RAM里又是怎样的结构模型呢

 RAM的结构模型大致分为7个部分:

RAM

 

 

 

 

 

 

 

 

下面结合IAR调试环境讲一下各个区域到底存放了什么

中断向量表:

为了快速响应中断操作,原本在0x00000000的中断向量表也复制了一份放到RAM区域中,大小为0x410字节。通过IARSymbolic Memory窗口可以观察到0x00000000~0x00000410区域的数据与0x1FFFF000~0x1FFFF410区域的数据是一致。

         

中断向量表是在startup阶段从FLASH复制到RAM中的,在startup.c文件中有:

void common_startup(void)

{

………………………….

…………………………

#elif (defined(IAR))

         /* Addresses for VECTOR_TABLE and VECTOR_RAM come from the linker file */

         extern uint32 __VECTOR_TABLE[];   //ICF文件中定义,地址为0x00000000

         extern uint32 __VECTOR_RAM[];    //ICF文件中定义,地址为0x1FFFF000

……………………….

……………………….

             /* Copy the vector table to RAM */

    if (__VECTOR_RAM != __VECTOR_TABLE)

    {

        for (n = 0; n < 0x104; n++)

            __VECTOR_RAM[n] = __VECTOR_TABLE[n];

    }

    ………………………

}

这里稍微扩展说一下中断向量表里的几个向量。地址为0x00000000的值为0x20002FF8,这个值其实是栈指针SP,地址为0x00000004的值为0x00000411,这个值其实是程序计数寄存器PCMCU上电后直接通过这个PC值就可以跳转到执行代码,如图所示:

       

至于为什么PC值为0x00000411而不是0x00000410,可以参考下M0权威指南。

0x00001F31default_isr中断向量,若没有提供中断处理函数,但启动并触发了硬件中断,则会转入该default_isr函数中;若需要提供相应的中断处理函数,仅需在ISR.H文件中修改即可。

所以,RAM区域第一块内容为中断向量表,区域为0x1FFFF000~0x1FFF410,大小为0x410字节。

RAM

中断向量表

 

 

 

 

 

 

 

 

RAM中运行的代码:

代码一般是储存在FLASH中,但有些场合需要将代码加载到RAM中运行以获得更快的响应。在IAR环境中,在函数之前添加“__ramfunc”即可实现将该函数放到RAM中运行。因为RAM掉电后里面的内容就会丢失,不能永久性存放代码,所以函数其实还是存放在FLASH中,在上电启动的时候将flash中的代码复制到RAM区域中,同样在在startup.c文件的common_startup()函数实现复制:

void common_startup(void)

{

    ………………….

    ………………….

    /* Get addresses for any code sections that need to be copied from ROM to RAM.

     * The IAR tools have a predefined keyword that can be used to mark individual

     * functions for execution from RAM. Add "__ramfunc" before the return type in

     * the function prototype for any routines you need to execute from RAM instead

     * of ROM. ex: __ramfunc void foo(void);

     */

    #if (defined(IAR))

           uint8* code_relocate_ram = __section_begin("CodeRelocateRam");

uint8* code_relocate = __section_begin("CodeRelocate");

        uint8* code_relocate_end = __section_end("CodeRelocate");

 

        /* Copy functions from ROM to RAM */

        n = code_relocate_end - code_relocate;

        while (n--)

          *code_relocate_ram++ = *code_relocate++;

    #endif

}

 

在测试代码中,我使用PrintTest函数作为例子

__ramfunc void PrintTest()    //函数在RAM中运行

{

    Global_Var1++;

}

 

在从flash复制代码到RAM的环节中我们可以看到代码存放的位置,如图所示,PrintTest函数存放在地址为0x2078flash区域中,而复制的目的地址为0x1FFFF410,这正好是RAM里中断向量表之后的RAM区域。


程序继续运行完复制后,即可发现,0x1FFFF410~0x1FFFF420之间存放的就是PrintTest函数的代码。通过仿真观察汇编执行情况就可以看到执行PrintTest时就是在这个RAM区域。



所以,RAM区域第二块内容为运行在RAM上的代码段,区域为0x1FFFF410~0x1FFF420,大小为0x10字节。

RAM

中断向量表

RAM运行代码段

 

 

 

 

 

 

 

已初始化的全局/静态数据区:

全局变量是定义在函数以外的变量,其生命周期是程序运行整个过程,作用域是自定义位置之后有效;

静态变量是通过static修饰的全局变量或局部变量,静态全局变量的和全局变量一样,唯一的区别是静态全局变量只能本文件使用;静态局部变量的作用域与局部变量一样,但生命周期和存放位置和全局变量是一样的;

出于优化目的,由const的修饰的常量一般都被存放在FLASH中,因为这些常量不能修改只能读取,所以放在flash区域可以节约RAM的消耗,若需要将常量定义在RAM上,需要增加volatile修饰。

上述的全局变量、静态全局变量、静态局部变量RAM中的常量全部都放在RAM的全局/静态数据区。RAM的全局区/静态数据区根据变量/常量的初始化情况分为已初始化全局/静态数据区和未初始化全局/静态数据区。在测试程序中我定义了如下变量和常量:

 

unsigned char Global_Var1  = 0x55;    //在已初始化的全局区域

unsigned char Global_Var2  = 0;       //在未初始化的全局区域

const unsigned char Const_Global_Var1 = 0xAA;  //flash区域

const unsigned char Const_Global_Var2 = 0;     //flash区域

volatile const unsigned char Volatile_Const_Global_Var1 = 0xCC; //在已初始化的全局区域

volatile const unsigned char Volatile_Const_Global_Var2 = 0;    //在未初始化的全局区域

 

void TestFn()

{

    unsigned char Local_Var1  = 2;  //在栈区域

    unsigned char Local_Var2  = 0;  //在栈区域

    static unsigned char Static_Globle_Var1 = 3;  //在已初始化的全局区域

    static unsigned char Static_Globle_Var2 = 0;  //在未初始化的全局区域

    ……..

}

全局变量Global_Var1的值为0x55,定义在RAM中的常量Volatile_Const_Global_Var1的值为0xCC,定义在子函数中的静态局部变量Static_Globle_Var1的值为0x03;通过仿真观察上述变量/常量的地址为0x1FFF420,紧接着RAM代码段之后

      

     从上图可以看出,虽然全局变量Global_Var1Global_Var2在程序中是一起定义的,但是因为Global_Var1定义时初始化为非0的值,所以Global_Var2并没有与Global_Var1定义在同一个RAM区域(Global_Var2定义在未初始化全局/静态数据区);对于静态局部变量Static_Globle_Var1虽然是局部变量,但是也定义在了已初始化全局/静态数据区;如前述const修饰的常量定义在了flash区域中(即使编译优化等级为NONE),而RAM中常量Static_Globle_Var1也定义在了已初始化全局/静态数据区。

上图是运行到main以后的RAM视图,可以发现他们的值已经被初始化,而RAM掉电后数据会丢失,那这里的0x55,0xCC,0x03都保存在哪里呢?这里再次回到startup.c文件的common_startup()函数中:

void common_startup(void)

{

    ……….

    #elif (defined(IAR))

         data_ram = __section_begin(".data");

         data_rom = __section_begin(".data_init");

         data_rom_end = __section_end(".data_init");

         n = data_rom_end - data_rom;

    #endif               

    ……..

         /* Copy initialized data from ROM to RAM */

         while (n--)

                   *data_ram++ = *data_rom++;

         ……..

}

通过仿真该程序可知,初始化的数据存放在0x00002164flash区域,目的地址为0x1FFFF420RAM区域,数据内容为0x55,0xCC,0x03。如下图

             

所以,RAM区域第三块内容为已初始化的全局/静态数据区,区域为0x1FFFF420~0x1FFF424,大小为0x4字节。

RAM

中断向量表

RAM运行代码段

已初始化的全局/静态数据区

 

 

 

 

 

 

未初始化的全局/静态数据区:

前面已经介绍了已初始化的全局变量、RAM中的常量、静态局部变量的存放地址。相对的,未初始化的全局变量、RAM中的常量、静态局部变量存放在未初始化的全局/静态数据区。虽说是未初始化,但不代表这些数据就是掉电后RAM中的随机数据,而是在程序运行后全部赋值为0。通过测试程序观察RAM如图:


因为在运行到main函数之前,有部分硬件初始化代码会定义部分全局/静态变量,所以测试程序定义的全局变量Global_Var2RAM中的常量Volatile_Const_Global_Var2、定义在子函数中的静态局部变量Static_Globle_Var2是定义在非用户定义未初始化全局/静态变量之后的。由上图可以看出这些为初始化的变量/常量地址从0x1FFFF424开始,紧接着已初始化全局/静态数据区之后。

再来看一下数值0是何时被赋值的,再看startup.c文件的common_startup()函数中:

Void common_startup(void)

{

    ……….

    #elif (defined(IAR))

         bss_start = __section_begin(".bss");

         bss_end = __section_end(".bss");

    #endif

 

    /* Clear the zero-initialized data section */

    n = bss_end - bss_start;

    while(n--)

      *bss_start++ = 0;

         ……..

}

通过仿真可以看到,未初始化的全局/静态数据区地址为0x1FFFF424
     

所以,RAM区域第四块内容为未初始化的全局/静态数据区,区域为0x1FFFF424~0x1FFF453,大小为0x30字节。

RAM

中断向量表

RAM运行代码段

已初始化的全局/静态数据区

未初始化的全局/静态数据区

 

 

 

 

 

堆区域:

说到内存大家经常提到堆栈,其实堆和栈是两个东西。平日所说的堆栈其实指的是栈(STACK),而堆(HEAP)是用于用户自定义使用的RAM区域。前面讨论的各种变量都是在编译时由编译器分配内存地址,而如果用户想在程序运行时分配内存地址(内存动态分配)的话,就需要使用到堆区域。堆区域的大小在.ICF中定义(__ICFEDIT_size_heap__ = (2*1024)),使用方法是通过malloc函数进行分配,堆区域的位置可以通过编译后的.MAP文件查看(很多信息都可以在.MAP里查看)。由下图可知,堆区域为0x1FFFF458 ~ 0x1FFFC58,堆起始地址没有紧接着未初始化全局/静态数据区,而是从0x1FFFF458这个8字节对齐的位置开始,是因为在.ICF文件中如如下定义:

define block HEAP      with alignment = 8, size = __ICFEDIT_size_heap__     { };


通过测试程序来观察堆的操作:

void TestFn()

{

    ……..

    unsigned char *pHeap_Var = NULL;  //pHeap_Var这个指针变量在栈区域

       

    pHeap_Var = malloc(sizeof(unsigned char));    //数据在堆区域

    *pHeap_Var = 4;

   

    ……..

    printf("The Heap_Var = %d, the address is 0x%x, the address of point is 0x%x\n\n " , *pHeap_Var, pHeap_Var,&pHeap_Var);

    ……..

}

如下图,malloc返回的地址0x1FFFFC48在堆区域中,其值为0x04。你可能会问,为什么是从堆的高地址开始分配内存,这里扩展讲一下:空堆中如何分配内存(所谓的堆增长方向)是由malloc函数决定的,KL25+IAR开发环境中使用的是自己的malloc函数,malloc时是从高地址向低地址分配;而C标准库中的malloc是从低地址向高地址分配的。


所以,RAM区域第五块内容为堆区域,区域为0x1FFFF458~0x1FFFC58,大小为0x800字节。

RAM

中断向量表

RAM运行代码段

已初始化的全局/静态数据区

未初始化的全局/静态数据区

堆区域

 

 

 

 

栈区域:

栈区域是存放局部变量,子函数的返回地址和参数的区域,增长方向是从高地址向低地址增长(由硬件决定)。栈的大小和位置是在.ICF文件中定义,如图所示,栈大小为1k字节,栈的位置是由栈顶__BOOT_STACK_ADDRESS决定的。


这里可能和其他环境不同:如果栈的位置由栈底决定,这样在堆区域之后指定栈底地址,这样栈区域就紧接着堆区域,RAM的空白区就集中在RAM的高地址区域;而对于KL25+IAR环境,栈的位置是由栈顶决定,这个栈顶在默认的.ICF文件中被定义在RAM最高地址-8的位置上,也就是说栈没有紧接着堆区域来安排,而是贴着RAM最高地址的区域安排,这样在堆和栈之间就会有未使用的RAM空白区域。如果栈顶未定义在RAM最高地址-8的位置,比如RAM最高地址-0x80,则会在RAM最高地址区域留下0x80个字节的空白区域。至于为什么默认的.ICF将栈顶定义在RAM最高地址-8的位置,还没有找到明确的说明。我试过将栈顶直接定义在RAM的最高地址后运行暂无异常。(M0内核压栈是,SP-4,然后再将数据压到SP所指的地址上)

通过测试程序,可以看到局部变量在栈中的位置。

void TestFn()

{

    unsigned char Local_Var1  = 2;  //在栈区域

    unsigned char Local_Var2  = 0;  //在栈区域

    static unsigned char Static_Local_Var1 = 3;  //在已初始化的全局区域

    static unsigned char Static_Local_Var2 = 0;  //在未初始化的全局区域

    unsigned char *pHeap_Var = NULL;  //pHeap_Var这个指针变量在栈区域

    ……

}


上图可见初始化的局部变量Local_Var1和未初始化的局部变量Local_Var2都定义在栈区域上,地址分别为0x20002FD90x20002FD8

所以,RAM区域第六块内容为栈区域,区域为0x20002BF8~0x20002FF8,大小为0x400字节。剩下的区域就是未使用的空白区,大小约12k字节。至此KL25IAR环境下的RAM结构模型如下表所示。

RAM

中断向量表

RAM运行代码段

已初始化的全局/静态数据区

未初始化的全局/静态数据区

堆区域

空白区(约12k字节)

栈区域

空白区(8个字节)

上述分析仅为KL25IAR环境的一个实例,不同的芯片在不同的环境下或许有不同的组织方式,但大体的模型应该是类似的。对RAM的结构有了大概的了解后,能更好的使用RAM资源,同时有助于提高代码质量。RAM中每块区域都有值得深入研究的细节,以后在做探讨。

阅读(2458) | 评论(0) | 转发(1) |
给主人留下些什么吧!~~