小猫爪:嵌入式小知识06-KEIL scf分散加载文件解析-链接代码至RAM

1 前言

前面我们已经对IAR的icf链接文件进行了介绍,接下来我们再对MDK的scf链接文件进行相关介绍。(MDK的这个链接文件的名字叫做分散加载文件,其实两者的作用并没有什么不同,大同小异,都是为了将代码与地址一一对应)

相比较IAR的icf链接文件,section的定义是完全一样的,KEIL有自己默认的section段,区别就是名字起的不一样,在这里就不一一举例了,同样的我们也可以自定义setion。我们可以使用下面的语法在代码中定义自己的section:

    __attribute__((section(".my_section_name"))) content

其中content可以是code和data。

2 执行域和加载域

在介绍scf文件之前,我们先了解一下执行域和加载域。我们只需要知道以下几点:
①执行域就是代码执行时所在的位置(换句话就是程序执行时跳转的地址)
②加载域就是代码存储的位置
③执行域可以等于加载域
④在代码执行前,会存在一个搬运工,这个搬运工就是MDK自己的初始化函数__main,它会将代码和数据从加载域拷贝到执行域,当然如果执行域等于加载域,则不需要拷贝。

2 相关语法解析

说简单一点,KEIL的分散加载文件就决定了代码的执行域和加载域地址,我们可以非常方便的定义代码的组合。关于其语法这一篇文章《ARM Cortex-M底层技术(八)KEIL MDK 分散加载-2-语法》说的很细,大家可以自行去参考。

在这里我用非常土的语言简单描述一下常用到的东西。

在这里插入图片描述从图中可以看到,一个加载域描述里可以放n个执行域描述,然后一个执行域描述里又可以放n个section,.o文件。其中LOAD_ROM1和EXEC_ROM1分别是加载域和执行域的名字,没有什么规范,随便起名字。

其中* 号就是放置的意思,举个例子*.o,意思就是把所有的.o文件放入,那么后面的括号是什么意思呢?后面的括号是限定条件,比如*.o(+RO) 的意思就是把所有.o文件里的RO属性的数据放入。比如*(.my_code_section)的意思就是把my_code_section这个段放入当前域。再比如*(RESET, +FIRST)的意思就是把RESET段放入且放在开头的地方。

如果我要指定放一个特定的.o文件,则可以直接写my_o_filename.o,不需要在前面加* 放置符号,同样的后面也可以加括号限定项。例如:

加载域名字 起始地址(FIXED) 大小 {
    
     
  执行域名字 起始地址 大小 {
    
     
    my_o_filename.o(+RO)
  }

上面的意思就是把my_o_filename.o中的RO段放进去。中间有一个(FIXED),这个也是一个可选的修饰符,表示固定地址的意思,就是说我必须要把下面的这段数据放在这个地址处。

其中.ANY也是放入的意思,就是放其他还没有放的数据,后面的括号也是限定条件。具体后面再举例说明。

总结一下:非常简单,先定义一个加载域,在加载域里面定义执行域,在执行域里放东西。

3 实例解说

下面我们以一个完整的scf文件对它进行一些解释,在对照着语法,你就非常明白了。

#!armclang --target=arm-arm-none-eabi -mcpu=cortex-m7 -E -x c
/*
** ###################################################################
**     Processors:          MIMXRT1052CVJ5B
**                          MIMXRT1052CVL5B
**                          MIMXRT1052DVJ6B
**                          MIMXRT1052DVL6B
**
**     Compiler:            Keil ARM C/C++ Compiler
**     Reference manual:    IMXRT1050RM Rev.2.1, 12/2018 | IMXRT1050SRM Rev.2
**     Version:             rev. 1.0, 2018-09-21
**     Build:               b191015
**
**     Abstract:
**         Linker file for the Keil ARM C/C++ Compiler
**
**     Copyright 2016 Freescale Semiconductor, Inc.
**     Copyright 2016-2019 NXP
**     All rights reserved.
**
**     SPDX-License-Identifier: BSD-3-Clause
**
**     http:                 www.nxp.com
**     mail:                 [email protected]
**
** ###################################################################
*/

#if (defined(__ram_vector_table__))
  #define __ram_vector_table_size__    0x00000400
#else
  #define __ram_vector_table_size__    0x00000000
#endif

#define m_flash_config_start           0x60000000
#define m_flash_config_size            0x00001000

#define m_ivt_start                    0x60001000
#define m_ivt_size                     0x00001000

#define m_interrupts_start             0x60002000
#define m_interrupts_size              0x00000400

#define m_text_start                   0x60002400
#define m_text_size                    0x03FFDC00

#define m_interrupts_ram_start         0x80000000
#define m_interrupts_ram_size          __ram_vector_table_size__

#define  m_data_start                  (m_interrupts_ram_start + m_interrupts_ram_size)
#define  m_data_size                   (0x01E00000 - m_interrupts_ram_size)

#define m_ncache_start                 0x81E00000
#define m_ncache_size                  0x00200000

#define m_data2_start                  0x20000000
#define m_data2_size                   0x00020000

#define m_data3_start                  0x20200000
#define m_data3_size                   0x00040000

/* Sizes */
#if (defined(__stack_size__))
  #define Stack_Size                   __stack_size__
#else
  #define Stack_Size                   0x0400
#endif

#if (defined(__heap_size__))
  #define Heap_Size                    __heap_size__
#else
  #define Heap_Size                    0x0400
#endif

/*加载域定义, 还定义了属性和最大容量*/
LR_m_text m_flash_config_start m_text_start+m_text_size-m_flash_config_start {
    
       ; load region size_region
  /*定义执行域地址大小,并定义属性 加载地址等于执行地址*/
  RW_m_config_text m_flash_config_start FIXED m_flash_config_size {
    
     ; load address = execution address
    /*把section .boot_hdr.conf放入,且放在最开始的地方*/
    * (.boot_hdr.conf, +FIRST)
  }
  /*定义执行域地址大小,并定义属性 加载地址等于执行地址*/
  RW_m_ivt_text m_ivt_start FIXED m_ivt_size {
    
     ; load address = execution address
    * (.boot_hdr.ivt, +FIRST)  //把section ..boot_hdr.ivt放入,且放在最开始的地方
    * (.boot_hdr.boot_data)  //把section .boot_hdr.boot_data紧跟着放入
    * (.boot_hdr.dcd_data) //把section .boot_hdr.dcd_data紧跟着放入
  }

   /*定义执行域地址大小,并定义属性 加载地址等于执行地址*/
  VECTOR_ROM m_interrupts_start FIXED m_interrupts_size {
    
     ; load address = execution address
    * (.isr_vector,+FIRST)  //把中断向量表放入当前执行域最开始的地方
  }
  /*定义执行域地址大小,并定义属性 加载地址等于执行地址*/
  ER_m_text m_text_start FIXED m_text_size {
    
     ; load address = execution address
    * (InRoot$$Sections)  //把InRoot$$Sections放入当前执行域
    .ANY (+RO) //将其他没有放置的只读RO段放入当前执行域
  }
  VECTOR_RAM m_interrupts_start EMPTY 0 {
    
    
  }
  RW_m_data2 m_data2_start m_data2_size {
    
    
    * (RamFunction) 
  }
  /*定义执行域地址大小,并定义属性 加载地址等于执行地址*/
  RW_m_data m_data_start m_data_size-Stack_Size-Heap_Size {
    
     ; RW data
    .ANY (+RW +ZI)   //把当前所有的RW和ZI段放入当前执行域
  }
}

这样所生成的镜像文件,所以数据放置的顺序跟上面排布的顺序一致,如果两个加载域中间出现数据断层,则会直接将中间数据用0填充。

4 链接代码至RAM运行

相比较IAR,用KEIL来链接代码至RAM,方法则是显得非常的单一,就是修改加载域和执行域的地址。接下来我们以NXP的RT1050举几个使用例子吧,(这里FLASH地址为0x6000000, RAM起始地址为0x20000000):

4.1 链接单个section至RAM

先在代码中使用下面代码自定义section:

__attribute__((section(".my_code_section")))
void my_code_fun(void)
{
    
    
	printf("This is a test\r\n");
}

然后在分散加载文件中将自定义section的执行域放入RAM中,代码如下:

/*加载域名字  加载域大小  加载属性描述*/
LR_m_text m_flash_config_start m_text_start+m_text_size-m_flash_config_start {
    
       ; load region size_region
  .......
  .......
  /*执行域名字  执行域大小  执行域属性描述为RW型*/
  RW_m_data m_data_start m_data_size-Stack_Size-Heap_Size {
    
     ; RW data
	*(.my_code_section)   //链接自定义的my_code_section
    .ANY (+RW +ZI)
    * (RamFunction)
    * (NonCacheable.init)
    * (*NonCacheable)
  }
  ........
}

编译后查看map文件查看my_code_fun的链接地址:
在这里插入图片描述
运行代码,查看跳转地址:
在这里插入图片描述
(注意:如果我们想指定确定的代码和数据的执行域地址的话,比如我就想把这个代码或者数据放置在0x20001000处,这可以通过开辟多个执行域来实现,重点是在这个过程中需要通过对不同代码的大小分析,开辟的执行域不能存在重叠的情况。)

4.2 链接单个.o文件至RAM

链接单个.o文件也是相对比较简单的,与链接单个section是一样的,这里我们使用下面代码将fsl_gpio.o链接至RAM:

/*加载域名字  加载域大小  加载属性描述*/
LR_m_text m_flash_config_start m_text_start+m_text_size-m_flash_config_start {
    
       ; load region size_region
  .......
  .......
  /*执行域名字  执行域大小  执行域属性描述为RW型*/
  RW_m_data m_data_start m_data_size-Stack_Size-Heap_Size {
    
     ; RW data
	*fsl_gpio.o(+RO)  //链接指定的fsl_gpio.o文件
    .ANY (+RW +ZI)
    * (RamFunction)
    * (NonCacheable.init)
    * (*NonCacheable)
  }
  ........
}

编译后查看map文件查看my_code_fun的链接地址:
在这里插入图片描述运行代码,查看跳转地址:
在这里插入图片描述

4.3 链接整个代码至RAM

链接整个代码至RAM,为了保证芯片正常启动,这里我将启动文件以及SystemInit函数所在的文件链接至了FLASH(因为入口函数在启动文件中以及__main函数之前调用了SystemInit),其他部分则链接至了RAM,代码如下:

/*加载域名字  加载域大小  加载属性描述*/
LR_m_text m_flash_config_start m_text_start+m_text_size-m_flash_config_start {
    
       ; load region size_region
  .......
  .......
  ER_m_text m_text_start FIXED 0x4000 {
    
     ; load address = execution address
    * (InRoot$$Sections)    
	*startup_mimxrt1052.o(+RO)   //链接启动文件
	*system_mimxrt1052.o(+RO)   //链接SystemInit
  }
  /*执行域名字  执行域大小  执行域属性描述为RW型*/
  RW_m_data m_data_start m_data_size-Stack_Size-Heap_Size {
    
     ; RW data
    .ANY (+RO)
    .ANY (+RW +ZI)
    * (RamFunction)
    * (NonCacheable.init)
    * (*NonCacheable)
  }
  ........
}

查看文件发现基本上所有的代码都已经链接至了RAM,包括mian函数:

这里运行代码,查看跳转地址:
在这里插入图片描述
可以看到进入main函数,地址直接就跳到了RAM中。

注意1:我们不能将所有的代码的执行域都放入至RAM中,换句话说就是entry入口地址必须得是FLASH,因为必须运行完MDK的初始化函数__main,而这个函数执行了代码拷贝至RAM的工作,所以需要在FLASH中执行完__main后,RAM中才会有代码,之后再跳转进入RAM中运行, 所以我们至少需要将__main函数之前的代码链接至FLASH中,不然是无法正常启动的。如果想完全将代码放入RAM中运行,则是可以直接将代码下载至RAM中,或者像RT1050这种支持non-XIP启动的MCU,则需要修改头信息,让BootROM实现所有代码的拷贝,类似于non-XIP启动。)

注意2:如果想在正常启动过程中将中断向量表链接至RAM,则需要注意两个点,第一点就是将中断向量链接至RAM的同时还要保证芯片的正常启动,我们可以通过将与启动相关的部分中断向量表拷贝副本链接至FLASH保证正常启动,再将完整的中断向量链接至RAM。第二点则是需要通过修改SCB->VTOR寄存器改变中断向量映射地址。)

END

猜你喜欢

转载自blog.csdn.net/Oushuwen/article/details/109293183