RT-Thread论坛
直播中

王辉

8年用户 1375经验值
私信 关注
[问答]

如何从 flash 把代码搬运到 ram 中运行的?

没有已经移植适配过的芯片 运行机制是从 flash 把代码搬运到 ram 中运行的


回帖(1)

张虎豹

2025-10-11 16:10:32

在嵌入式系统中,有时需要将代码从Flash存储器中复制到RAM中运行。这通常是因为以下几种原因:
1. Flash的访问速度可能比RAM慢,特别是当CPU运行速度很快时,在Flash中运行代码可能导致性能瓶颈。
2. 某些操作(如写Flash)不能在Flash中执行代码(因为需要擦写Flash时,不能同时从Flash取指令)。
3. 有时为了降低功耗,可能会将系统置于一种状态,此时Flash被关闭,因此需要将代码复制到RAM中执行。

下面将详细说明如何实现将代码从Flash搬运到RAM中运行的步骤。这里我们假设没有现成的移植适配,需要手动完成。

### 1. 理解链接脚本(Linker Script)
链接脚本用于控制可执行文件的内存布局。我们需要定义两个部分:一个用于在Flash中的存放(加载地址),另一个用于在RAM中的运行(运行地址)。

例如,我们有一个需要搬运的代码段,我们将其命名为`.ram_code`。在链接脚本中,我们指定这个段的加载地址(LMA)在Flash中,而运行地址(VMA)在RAM中。

### 2. 修改链接脚本
下面是一个简单的链接脚本示例(以GCC链接脚本为例):
```
MEMORY
{
    FLASH (rx) : ORIGIN = 0x08000000, LENGTH = 256K
    RAM (rwx)  : ORIGIN = 0x20000000, LENGTH = 64K
}

SECTIONS
{
    .text :
    {
        *(.text*)   /* 常规代码段,放在Flash中 */
    } > FLASH

    /* 其他段(如.data, .bss等)省略 */

    .ram_code :
    {
        . = ALIGN(4);
        _sram_code = .;          /* 在RAM中运行的代码的起始地址(VMA) */
        *(.ram_code)             /* 将ram_code段放在这里(运行地址) */
        . = ALIGN(4);
        _eram_code = .;          /* 在RAM中运行的代码的结束地址(VMA) */
    } > RAM AT > FLASH           /* 运行地址在RAM,但加载地址在Flash */

    _siram_code = LOADADDR(.ram_code);  /* 获取加载地址(在Flash中的地址) */
}
```
在这个例子中,`.ram_code`段的运行地址在RAM中(`> RAM`),但它的加载地址在Flash中(`AT > FLASH`)。我们通过`LOADADDR(.ram_code)`获取该段在Flash中的起始地址(LMA),并存储在变量`_siram_code`中。同时,我们在RAM中定义了运行时的起始地址`_sram_code`和结束地址`_eram_code`。

### 3. 标记需要搬运的函数
在C代码中,我们需要将特定函数或代码段标记为需要放在`.ram_code`段中。这通常通过编译器属性(attribute)实现。

例如,在GCC中:
```c
#define RAM_CODE __attribute__((section(".ram_code")))

void RAM_CODE my_function(void) {
    // 此函数将被放在.ram_code段中,因此需要从Flash复制到RAM运行
}
```

### 4. 编写搬运代码
在系统启动时(在进入main函数之前),需要将`.ram_code`段从Flash(加载地址)复制到RAM(运行地址)。这通常在启动文件(startup file)或C代码的初始化部分完成。

以下是一个简单的复制函数示例(通常在启动代码中调用):
```c
extern uint32_t _siram_code;   /* 在Flash中的加载地址(源地址) */
extern uint32_t _sram_code;    /* 在RAM中的运行起始地址(目标地址) */
extern uint32_t _eram_code;    /* 在RAM中的运行结束地址 */

void copy_ram_code(void) {
    uint32_t *src = &_siram_code;
    uint32_t *dst = &_sram_code;
    uint32_t size = (uint32_t)(&_eram_code - &_sram_code);

    // 复制数据,注意:如果size为0,则不复制
    if (size) {
        while (size--) {
            *dst++ = *src++;
        }
    }
}
```
注意:这里假设地址是32位对齐的,并且复制的大小以4字节为单位(因为我们使用uint32_t指针)。在链接脚本中我们已经使用了ALIGN(4)进行对齐。

### 5. 在启动过程中调用搬运函数
在启动代码中(如Reset_Handler),在调用main函数之前,需要先调用这个复制函数。顺序一般是:
- 设置栈指针
- 复制.data段(初始化数据)从Flash到RAM
- 清零.bss段
- 复制.ram_code段
- 调用main函数

注意:在复制.ram_code段之前,不能调用任何放在.ram_code段中的函数。

### 注意事项
1. 确保在复制之前,RAM已经初始化(对于某些硬件,可能需要先初始化内存控制器,但在大多数微控制器中,RAM是上电即可用的)。
2. 如果在复制.ram_code段之前就使用了其中任何函数,将导致错误(因为此时RAM中的代码还未被初始化)。
3. 复制函数本身不能放在.ram_code段中(因为它负责复制,必须在复制之前运行),所以复制函数应该放在普通的.text段(即Flash中)。
4. 如果使用中断,并且中断服务程序(ISR)也需要在RAM中运行,那么需要在复制完.ram_code段之后,再使能中断。

### 总结
通过以上步骤,我们实现了将特定代码段从Flash搬运到RAM中运行。关键点在于链接脚本的配置、代码段的标记以及启动时的复制操作。在没有现成的移植适配的情况下,我们需要手动完成这些步骤,并确保顺序和正确性。

在嵌入式系统中,将代码从 Flash 搬运到 RAM 中运行是一种优化技术,主要用于解决以下问题:  



  • Flash 访问速度慢(尤其与高速 CPU 配合时)  

  • 避免执行 Flash 时的擦写冲突(如固件升级时)  

  • 降低功耗(关闭 Flash 时仍能运行关键代码)  


以下是完整的实现步骤(以 ARM Cortex-M 为例,无现成框架):




1. 链接脚本配置(关键步骤)


在链接脚本(如 .ld 文件)中定义代码的 加载地址(LMA = Flash)运行地址(VMA = RAM):  


MEMORY {
  FLASH (rx) : ORIGIN = 0x08000000, LENGTH = 512K
  RAM  (rwx) : ORIGIN = 0x20000000, LENGTH = 128K
}

SECTIONS {
  /* 标准 .text 段仍放在 Flash */
  .text : { *(.text*) } > FLASH

  /* 自定义段:在 Flash 中存储,但运行时在 RAM */
  .ram_code : {
    _sram_code = .;         /* RAM 运行起始地址 (VMA) */
    *(.ram_code)            /* 标记的代码段 */
    _eram_code = .;         /* RAM 运行结束地址 */
  } > RAM AT > FLASH        /* >RAM 指定 VMA, AT>FLASH 指定 LMA */

  _siram_code = LOADADDR(.ram_code); /* 获取 Flash 中的加载地址 (LMA) */
}



2. 标记需搬运的函数


在 C 代码中,通过编译器属性将函数放入 .ram_code 段:  


/* 定义段属性 */
#define __ram_code __attribute__((section(".ram_code")))

/* 声明需搬运的函数 */
void __ram_code critical_function(void) {
  // 高速或低功耗关键代码
}



3. 启动时搬运代码


在系统启动文件(如 startup.s)或初始化代码中,手动复制数据:  


extern uint32_t _siram_code;  /* Flash 中的源地址 (LMA) */
extern uint32_t _sram_code;   /* RAM 目标起始地址 (VMA) */
extern uint32_t _eram_code;   /* RAM 目标结束地址 */

void copy_ram_code(void) {
  uint32_t size = (uint32_t)(&_eram_code - &_sram_code);
  uint32_t *src = (uint32_t*)&_siram_code;
  uint32_t *dst = (uint32_t*)&_sram_code;

  /* 逐字复制(4字节对齐) */
  for (uint32_t i = 0; i < size; i++) {
    dst[i] = src[i];
  }
}


关键点:此复制代码必须放在 Flash 中执行(不可自身搬运),且需在调用 critical_function() 前运行。





4. 启动流程调整


在复位后立即搬运代码(通常在 Reset_Handler 中):  


Reset_Handler:
  /* 1. 初始化栈指针 */
  ldr sp, =_estack

  /* 2. 复制 .data 段(已初始化的全局变量)*/
  ldr r0, =_sdata
  ldr r1, =_edata
  ldr r2, =_sidata
  bl  memory_copy

  /* 3. 清零 .bss 段(未初始化的全局变量)*/
  ldr r0, =_sbss
  ldr r1, =_ebss
  bl  zero_memory

  /* 4. 搬运 .ram_code 段到 RAM */
  bl  copy_ram_code    /* 调用前述复制函数 */

  /* 5. 跳转主程序 */
  bl  main



5. 关键注意事项




  1. 地址对齐

    确保链接脚本中 .ram_code 段 4 字节对齐(避免非对齐内存访问错误)。




  2. 复制函数位置

    copy_ram_code() 必须编译到 普通 Flash 段(如 .text),不可自身被搬运。




  3. 初始化顺序

    必须在全局变量初始化(.data/.bss之后任何 RAM 函数调用之前搬运代码。




  4. 优化性能:  



    • 使用 DMA 加速搬运(如有)  

    • 避免在中断中调用 RAM 函数,除非中断向量表已处理(见下条)




  5. 中断处理

    若中断服务程序(ISR)需在 RAM 中运行:  



    • 修改中断向量表指向 RAM 中的函数  

    • 在启用中断前完成搬运






6. 验证是否成功




  • 检查符号地址

    critical_function 的地址应在 RAM 范围内(如 0x2000xxxx)。




  • 反汇编验证

    查看生成的可执行文件(如 .elf),确认:  



    • 函数代码存储在 Flash(LMA)  

    • 运行时地址指向 RAM(VMA)






常见问题解决




  • 崩溃在搬运后

    检查复制函数的长度计算(_eram_code - _sram_code 是否包含所有代码)。




  • 函数未搬运

    确认函数是否正确定义了 __ram_code 属性。




  • 性能未提升

    使用定时器测量 Flash vs RAM 中的函数执行时间。




通过以上步骤,即可在没有现成框架的芯片上实现从 Flash 到 RAM 的代码搬运,兼顾性能与功耗优化。

举报

更多回帖

发帖
×
20
完善资料,
赚取积分