【嵌入式 - 关于MCU的内存分配】

一、MCU内存分配

MCU(微控制器)的内存分配是指如何在不同的内存区域(如程序存储器、数据存储器、堆栈等)中分配和管理内存。不同的MCU可能有不同的内存架构,但通常MCU的内存通常包括以下几部分:

1.1 内存分配

  1. 程序存储器(Flash/ROM): 存储程序代码和常量数据。Flash 是可擦写的,而 ROM 是只读的。
  2. 数据存储器(SRAM): 存储程序运行时的变量和数据。SRAM 是易失性的,即掉电后数据会丢失。
  3. 非易失性存储器(如EEPROM):存储需要在断电后保留的数据,容量较小,读写速度较慢。
    通常用于存储配置参数、校准数据等。
  4. 特殊功能寄存器(SFR): 用于控制和访问 MCU 的外设和功能模块。

1.2 内存区域

  1. 常量区:存储编译时确定的常量值,如字符串常量。
  2. 静态区(Static):存储全局变量和静态变量,程序运行期间始终存在。
  3. 栈区(Stack):执行函数时创建局部变量和函数参数,函数结束时释放。
  4. 堆区(Heap):动态分配的内存区域,使用malloc等函数申请内存,需手动释放。

1.3 数据段

  1. RW-data(读写数据存储区):存储已初始化的全局变量和静态变量。
  2. ZI-data(零初始化数据区):存储未初始化的全局变量和静态变量,程序开始运行时初始化为0。

1.4 内存分配示例

在典型的嵌入式系统中,内存分配通常通过链接脚本(Linker Script)进行定义。以下是一个基于 ARM Cortex-M 的 MCU 的简单链接脚本示例,用于定义内存布局:

bash 复制代码
/* Linker script for ARM Cortex-M MCU */

MEMORY
{
    FLASH (rx) : ORIGIN = 0x08000000, LENGTH = 256K
    SRAM (rwx) : ORIGIN = 0x20000000, LENGTH = 64K
}

SECTIONS
{
    /* Program code and read-only data */
    .text :
    {
        KEEP(*(.isr_vector))
        *(.text*)
        *(.rodata*)
        *(.glue_7)
        *(.glue_7t)
        *(.eh_frame)
    } > FLASH

    /* Read-write data */
    .data : AT (ADDR(.text) + SIZEOF(.text))
    {
        _sdata = .;
        *(.data*)
        _edata = .;
    } > SRAM

    /* Uninitialized data (BSS) */
    .bss :
    {
        _sbss = .;
        *(.bss*)
        *(COMMON)
        _ebss = .;
    } > SRAM

    /* Stack and heap */
    .stack (NOLOAD) :
    {
        _estack = .;
        . = . + 0x1000; /* Define stack size */
    } > SRAM

    .heap (NOLOAD) :
    {
        _sheap = .;
        . = . + 0x8000; /* Define heap size */
        _eheap = .;
    } > SRAM
}

二、自定义内存区域

嵌入式开发中,自定义内存区域以满足特定需求非常常见。这通常涉及编译器和链接器的配置。在上述链接脚本中,可以看到定义了 FLASH 和 SRAM 的起始地址和长度,并在 SECTIONS 部分定义了各个内存区域的分配。

通过合理地使用链接脚本和内存管理函数,开发者可以有效地自定义和管理 MCU 的内存区域,以满足特定的应用需求。

2.1 编译器配置

使用编译器选项指定特定内存区域的分配,例如通过段名(Section Name)标识不同内存区域。

在代码中使用特定关键字或属性指定变量或函数的存储区域。

2.1.1 示例:自定义变量位置

假设需要将某些特定变量放置在特定的内存区域,可以使用 __attribute__ 指令。以下是一个示例,将一个数组放置在特定的内存区域中:

c 复制代码
#include <stdint.h>

#define CUSTOM_SECTION __attribute__((section(".custom_section")))

CUSTOM_SECTION uint8_t custom_array[256];

int main(void)
{
    for (int i = 0; i < 256; ++i)
    {
        custom_array[i] = i;
    }
    while (1)
    {
        // Main loop
    }
}

2.2 链接器配置

在链接器脚本中定义内存布局,指定不同内存区域的起始地址、大小和属性。

将不同代码段和数据段映射到相应的内存区域中。

例如上述代码,需要在链接脚本中定义 .custom_section

bash 复制代码
SECTIONS
{
    /* Other sections... */

    .custom_section :
    {
        *(.custom_section)
    } > SRAM
}

2.3 内存管理

使用自定义的内存分配器管理堆区内存,根据应用需求优化。

例如,使用内存池减少内存碎片、提高分配速度。

2.3.1 使用内存管理函数

动态内存分配可以使用标准的 C 库函数,例如 malloc 和 free。这些函数分配和释放堆内存。

c 复制代码
#include <stdlib.h>

int main(void)
{
    int *dynamic_array = (int *)malloc(100 * sizeof(int));
    if (dynamic_array != NULL)
    {
        for (int i = 0; i < 100; ++i)
        {
            dynamic_array[i] = i;
        }
        free(dynamic_array);
    }
    while (1)
    {
        // Main loop
    }
}

三、开发者内存分配标准

3.1 开发者在进行内存分配时,应遵循以下标准:

安全性:

确保内存分配不会导致溢出或冲突,避免潜在的安全漏洞。为此,需要:

  • 分离代码和数据:将代码、只读数据和读写数据分开,以提高安全性和稳定性。
  • 保护关键数据:关键数据(如系统配置)应放在特定的保护区域,以避免被意外覆盖。
    效率:合理分配内存,减少碎片化,提高内存使用效率。
  • 动态内存管理:合理使用动态内存分配,避免内存泄漏。

可维护性:

代码应易于理解和维护,内存分配策略应清晰且一致。为此,需要:

  • 使用特定内存区域:有时需要将某些数据或代码放置在特定的内存区域,以满足硬件要求或优化性能。

性能:

考虑内存访问速度,优化性能,特别是在实时系统中。为此,需要:

  • 内存对齐:确保内存对齐,以提高访问效率。
  • 最小化碎片:尽量减少内存碎片,以提高内存利用率。

可扩展性:

预留足够空间以适应未来功能扩展。

符合架构:

遵循MCU内存架构,如哈佛架构或冯·诺依曼架构。

3.2 自定义内存段的原因

性能优化:

例如,将频繁访问的数据分配到高速内存区域以提高访问速度。

c 复制代码
#define FAST_CODE_SECTION __attribute__((section(".fast_code")))

FAST_CODE_SECTION void time_critical_function(void)
{
    // Time-critical code
}

在链接脚本中定义 .fast_code 区域:

bash 复制代码
SECTIONS
{
    /* Other sections... */

    .fast_code :
    {
        *(.fast_code)
    } > SRAM
}

功能隔离:

将不同功能的代码和数据分开存放,提高模块化和可维护性。

c 复制代码
#define CODE_SECTION __attribute__((section(".my_code")))
#define DATA_SECTION __attribute__((section(".my_data")))

CODE_SECTION void my_function(void)
{
    // Function code
}

DATA_SECTION int my_data;

在链接脚本中定义 .my_code 和 .my_data 区域:

bash 复制代码
SECTIONS
{
    /* Other sections... */

    .my_code :
    {
        *(.my_code)
    } > FLASH

    .my_data :
    {
        *(.my_data)
    } > SRAM
}

特定硬件支持:

某些硬件外设可能需要数据存储在特定的内存区域。例如,DMA(直接内存访问)传输可能要求数据放在特定的内存位置。

c 复制代码
#define DMA_BUFFER_SECTION __attribute__((section(".dma_buffer")))

DMA_BUFFER_SECTION uint8_t dma_buffer[256];

在链接脚本中定义 .dma_buffer 区域:

bash 复制代码
SECTIONS
{
    /* Other sections... */

    .dma_buffer :
    {
        *(.dma_buffer)
    } > SRAM
}

内存保护:

隔离关键数据,防止非授权访问或意外修改。

c 复制代码
#define PROTECTED_DATA_SECTION __attribute__((section(".protected_data")))

PROTECTED_DATA_SECTION uint32_t system_config[10];

在链接脚本中定义 .protected_data 区域:

bash 复制代码
SECTIONS
{
    /* Other sections... */

    .protected_data :
    {
        *(.protected_data)
    } > SRAM
}

特殊用途:

如固件版本号、固件升级代码、异常处理代码等特殊用途的代码和数据通常需要放在特定的内存段中。这有助于提高系统的可维护性和可靠性,并确保这些关键部分不会被意外覆盖或修改。以下是一些代码示例,展示如何在特定内存段中放置这些数据和代码。

链接器脚本示例

首先,需要在链接器脚本中定义特定的内存段。例如,我们可以定义三个内存段来存储固件版本号、固件升级代码和异常处理代码:

bash 复制代码
/* 链接器脚本示例 */

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

SECTIONS
{
    .text :
    {
        KEEP(*(.isr_vector)) /* 保持中断向量表 */
        *(.text*)            /* 程序代码 */
    } > FLASH

    .rodata : ALIGN(4)
    {
        *(.rodata*)         /* 只读数据 */
    } > FLASH

    .data : ALIGN(4)
    {
        PROVIDE(__data_start = .);
        *(.data*)           /* 初始化的全局变量 */
        PROVIDE(__data_end = .);
    } > RAM AT > FLASH

    .bss :
    {
        PROVIDE(__bss_start = .);
        *(.bss*)           /* 未初始化的全局变量 */
        PROVIDE(__bss_end = .);
    } > RAM

    /* 特定内存段 */
    .firmware_version :
    {
        *(.firmware_version*)
    } > FLASH

    .firmware_upgrade :
    {
        *(.firmware_upgrade*)
    } > FLASH

    .exception_handler :
    {
        *(.exception_handler*)
    } > FLASH

    /* 堆和栈的定义 */
    .heap :
    {
        . = ALIGN(4);
        PROVIDE(__heap_start = .);
        . = . + 0x8000;   /* 定义堆大小为32KB */
        PROVIDE(__heap_end = .);
    } > RAM

    .stack :
    {
        . = ALIGN(4);
        PROVIDE(__stack_start = .);
        . = . + 0x4000;    /* 定义栈大小为16KB */
        PROVIDE(__stack_end = .);
    } > RAM
}
C代码示例

接下来,在C代码中使用特定的段属性将数据和代码放置到这些内存段中。

c 复制代码
/* 将固件版本号放在 .firmware_version 段中 */
__attribute__((section(".firmware_version"))) const char firmware_version[] = "1.0.0";

/* 将固件升级代码放在 .firmware_upgrade 段中 */
__attribute__((section(".firmware_upgrade"))) void firmware_upgrade(void) {
    // 固件升级逻辑
    // 这段代码将被放置在 .firmware_upgrade 段中
}

/* 将异常处理代码放在 .exception_handler 段中 */
__attribute__((section(".exception_handler"))) void exception_handler(void) {
    // 异常处理逻辑
    // 这段代码将被放置在 .exception_handler 段中
}
int main(void) {
    printf("Firmware Version: %s\n", firmware_version);
    // 某些条件下调用固件升级代码
    if (upgrade_needed) {
        firmware_upgrade();
    }
    // 初始化异常处理
    set_exception_handler(exception_handler);
    
    // 主程序逻辑
    while(1);
    return 0;
}

__attribute__((section(".firmware_version"))):将firmware_version数组放置在.firmware_version段中。这个段在链接器脚本中被定义为存储在FLASH中。
__attribute__((section(".firmware_upgrade"))):将firmware_upgrade函数放置在.firmware_upgrade段中。
__attribute__((section(".exception_handler"))):将exception_handler函数放置在.exception_handler段中。

通过这种方式,开发者可以确保特定的代码和数据被存储在预定义的内存区域中,从而满足应用的特定需求。

3.3 完整示例代码

以下是一个完整的代码示例,展示了如何根据不同的原因自定义内存段:

c 复制代码
#include <stdint.h>

#define DMA_BUFFER_SECTION __attribute__((section(".dma_buffer")))
#define FAST_CODE_SECTION __attribute__((section(".fast_code")))
#define PROTECTED_DATA_SECTION __attribute__((section(".protected_data")))

DMA_BUFFER_SECTION uint8_t dma_buffer[256];
PROTECTED_DATA_SECTION uint32_t system_config[10];

FAST_CODE_SECTION void time_critical_function(void)
{
    // Time-critical code
}

int main(void)
{
    // Initialize system configuration
    for (int i = 0; i < 10; ++i)
    {
        system_config[i] = i;
    }

    // Use dma_buffer
    for (int i = 0; i < 256; ++i)
    {
        dma_buffer[i] = i;
    }

    // Call time-critical function
    time_critical_function();

    while (1)
    {
        // Main loop
    }

    return 0;
}

链接脚本

bash 复制代码
MEMORY
{
    FLASH (rx) : ORIGIN = 0x08000000, LENGTH = 256K
    SRAM (rwx) : ORIGIN = 0x20000000, LENGTH = 64K
}

SECTIONS
{
    /* Other sections... */

    .dma_buffer :
    {
        *(.dma_buffer)
    } > SRAM

    .fast_code :
    {
        *(.fast_code)
    } > FLASH

    .protected_data :
    {
        *(.protected_data)
    } > SRAM

    /* Other sections... */
}

通过这种方式,开发者可以根据具体需求和硬件要求,自定义内存区域,实现更高效和可靠的内存管理。

在嵌入式开发中,自定义内存区域是一个重要的技术,可以帮助开发者优化程序性能、提高系统可靠性以及满足特定的应用需求。通过合理配置编译器、链接器和内存管理,可以将关键数据和代码放置在特定的内存段中,确保系统的稳定运行。

相关推荐
yangpipi-9 分钟前
数据结构(C语言版)-2.栈和队列
c语言·开发语言·数据结构
还在学习进步12 分钟前
C语言第九周课——经典算法
c语言·开发语言·算法
励志成为嵌入式工程师1 小时前
c语言选择排序
c语言·算法·排序算法
三小尛1 小时前
希尔排序(C语言)
c语言·数据结构·排序算法
風清掦1 小时前
C/C++每日一练:编写一个查找子串的位置函数
c语言·c++·算法
三小尛1 小时前
插入排序(C语言)
c语言·开发语言
WolvenSec1 小时前
C/C++逆向:结构体逆向分析
c语言·开发语言·c++·网络安全
还在学习进步3 小时前
C语言第十一周课——函数的调用
c语言·算法·排序算法
iot鑫鹏4 小时前
STM32问题集
stm32·单片机·嵌入式硬件
爱吃喵的鲤鱼4 小时前
linux 用C语言编写自己的myshell
linux·运维·服务器·c语言·算法