嵌入式xip如何将部分代码放在ram中运行
时间:2023年10月29日 人气:...

目前嵌入式中,很多产品都是将程序(也叫做固件)烧录在flash中,产品上电启动后,就会从flash中加载运行程序。此处加载运行程序就可以分为两类:在flash上直接运行、从flash中加载至ram中运行,其中在flash上直接运行就叫做xip。这两种方式各有优缺,如xip可以减少ram大小,而flash要比ram便宜,理所当然可以大幅降低成本;但是完全加载至ram中运行速率却是最高的,要比xip执行效率更高。大多数时候,高速flash配合I-Cache、D-Cache速率是完全跟得上的,所以现在大多数产品普遍默认采用xip方式运行程序。

所以本文主要分析如何将程序烧录在flash中,然后将部分程序加载至ram中运行(如一个文件、一个函数等等)予以提高程序性能。此法不需要诸如jlink等jtag仿真工具,使用常规用法即可。

以arm为例,一般地址规划为:0x08000000为flash起始地址,0x20000000位ram起始地址。0x地址一般为rom/bootloader,cpu上电启动一般从0x0处启动,然后由rom/bootloader进行跳转才能执行到我们所编写的程序中。对于我们编写的程序通常会定义一个入口点,当进入我们的程序时,可以理解为就是执行这个入口点函数。

以编译工具链选择arm gcc为例,定义入口点函数entry_point,通常在链接文件中定义:

ENTRY(entry_point)

然后,就需要实现这个函数,一般都是使用汇编实现此函数,在这里就需要实现我们的目的:将flash中的代码拷贝至ram中。那么在实现之前,先需要了解,每个程序具有.text、.data、.bss等段,代码就存放在text段,已经初始化的全局变量存放在data段(加了const修饰的全局变量会被存放在text段)。在xip版本中,text段就保存在flash中,data段在ram中,那么方法就来了,我们就可以把我们希望放置在ram中运行的程序放在data段中,然后在执行进入入口点后,再从flash中将对应的这部分代码拷贝至data段内存,然后继续执行即可达到目的。

那么如何放在代码在data段呢,当然还是在链接文件中定义,如:

MEMORY
{
  FLASH (rx) : ORIGIN = 0x08000000, LENGTH = 4M
  RAM   (rwx): ORIGIN = 0x20000000, LENGTH = 512KB
}

ENTRY(entry_point)

SECTIONS
{
    ......
    
    .data :
    {
        ......
        __data_start__ = .;
        *(MY_CODE_RAM)
        *my_file.o(.text .text.*)
        *libmylib.a:my_file2.o(.text .text.*)
        __data_end__ = .;
        ......
    } > RAM
    
    ......
    
    .text :
    {
        ......
        __text_start__ = .;
        *(.text*)
        __text_end__ = .;
        ......
    } > FLASH
    
    ......
}

示例中,我定义了一个名叫“my_file.c”的文件、定义了将一个在libmylib.a中的文件“my_file2.c”,这俩文件中的全部代码会被放置在ram中。我还定义了一个段,只将指定的变量、函数放置在ram中,可以通过attribute关键字,如:

static uint8_t my_buffer[0x1000] __attribute__ ((section("MY_CODE_RAM")));

int __attribute__ ((section("MY_CODE_RAM"))) my_function(int a, char b)
{
    ......
}

于是,当我们在entry_point中执行时,判断__text_end__是否等于__data_start__,当不等于时则说明flash中有数据需要拷贝至ram中,这也就是所谓的LMA--->VMA拷贝。拷贝操作也很简单,通常使用汇编直接写的话可能难到好多英雄好汉,所以也可以使用c语言直白编写:

memcpy(&__data_start__, &__text_end__, &__data_end__ - &__data_start__);

拷贝后继续执行即可,无需多余的操作,这是编译器所保证的基本功能。

一般编译之后,可以通过查看map文件,搜索文件中的函数或指定的函数,就可以看到其地址是否已经生效在ram中。


接下来就到了最后一步,制作成烧写flash的bin文件,gcc工具链的话,一般会直接使用objcopy直接将elf生成bin文件,但是,这里多半会出现一种特别的情况,那就是生成的bin超级超级大,导致flash完全放不下这样的文件。

为什么会出现这种情况呢?那是因为,objcopy产生了大量的内存空洞,比如从flash基址0x08000000开始,text段只有0x10000大小,那么从0x08010000-0x1FFFFFFF范围就会全部使用0填充在bin中,这就导致了大量的内存空洞增大了bin文件。所以遇到这种情况,就不能直接使用objcopy这样的工具,需要自行根据elf文件制作bin文件。

一般我们程序中需要烧录在flash中的数据只需要text和data段,于是解析elf文件,分别找到text段和data段在elf文件中的位置和大小,按照先text、后data的顺序,将这俩段保存为bin文件即可(这就是为啥LMA--->VMA)。elf文件中的section header中指示的段大小是实际数据大小,没有内存空洞,所以按此制作出来的bin文件就会只有实际的text+data大小了。

如果想自己制作一个elf文件转bin文件工具,就可以按照上述思路制作,因为比较简单我就不贴代码了。这里我分享一个其他大佬的elf文件解析工程,供感兴趣的朋友参考。

此外更推荐使用python来实现elf转bin工具,因为python有个ELFFile库,操作解析elf文件,那简直是太简单了

from elftools.elf.elffile import ELFFile

elf = ELFFile(elf_file)
    for section in elf.iter_sections():
        if section.name == '.text':
            #section.header['sh_offset'] do things...
            #section.header['sh_size'] do things...

热门评论