返回首页

Zephyr 内存管理学习笔记

概述

Zephyr 提供了多种内存管理机制,适应从资源受限的嵌入式设备到复杂的应用需求。

内存分配方式对比

方式特点适用场景
静态内存编译时确定,无运行时开销固定大小的缓冲区、栈
内存池 (Memory Pool)固定块大小,无碎片频繁的固定大小分配
堆 (Heap)动态大小,可能有碎片大小变化的分配
内存映射MMU/MPU 支持内存保护、用户模式

1. 静态内存

编译时分配

/* 静态数组 */
static uint8_t tx_buffer[256];
static struct my_struct instance;

/* 线程栈 */
K_THREAD_STACK_DEFINE(my_stack, 1024);

/* 内核对象 */
struct k_mutex my_mutex;
struct k_sem my_sem;

宏定义分配

/* 静态定义消息队列缓冲区 */
K_MSGQ_DEFINE(my_msgq, sizeof(struct event), 10, 4);

/* 静态定义管道 */
K_PIPE_DEFINE(my_pipe, 256);

/* 静态定义堆栈 */
K_STACK_DEFINE(my_stack, 20);

2. 内存池 (Memory Pool)

内存池分配固定大小的块,无碎片问题。

基础内存池

/* 定义内存池 - 8字节块,最多16个 */
K_MEM_POOL_DEFINE(my_pool, 8, 8, 16, 4);

/* 分配内存 */
struct k_mem_block block;
int ret = k_mem_pool_alloc(&my_pool, &block, 8, K_FOREVER);
if (ret == 0) {
    /* 使用 block.data */
    memcpy(block.data, data, 8);
    
    /* 释放内存 */
    k_mem_pool_free(&block);
}

高级内存池 (k_mem_slab)

/* 定义 slab - 每个块 64 字节,共 10 个块 */
K_MEM_SLAB_DEFINE(my_slab, 64, 10, 4);

void *ptr;
/* 分配 */
if (k_mem_slab_alloc(&my_slab, &ptr, K_NO_WAIT) == 0) {
    /* 使用 ptr */
    memset(ptr, 0, 64);
    
    /* 释放 */
    k_mem_slab_free(&my_slab, ptr);
}

/* 查询状态 */
int used = 10 - k_mem_slab_num_free_get(&my_slab);
printk("Used blocks: %d\n", used);

内存池配置

CONFIG_MEM_SLAB_TRACE_MAX_UTILIZATION=y  # 跟踪最大使用量

3. 堆内存 (Heap)

标准堆分配

/* 需要启用堆 */
CONFIG_HEAP_MEM_POOL_SIZE=4096

/* 分配 */
void *ptr = k_malloc(256);
if (ptr) {
    /* 使用 */
    memset(ptr, 0, 256);
    
    /* 重新分配 */
    ptr = k_realloc(ptr, 512);
    
    /* 释放 */
    k_free(ptr);
}

对齐分配

/* 按 32 字节对齐分配 */
void *aligned_ptr = k_aligned_alloc(32, 256);
if (aligned_ptr) {
    /* aligned_ptr 是 32 字节对齐的 */
    k_free(aligned_ptr);
}

堆状态查询

/* 查询堆状态 */
struct sys_heap_stats stats;
k_heap_stats(&stats);

printk("Free: %zu bytes\n", stats.free_bytes);
printk("Allocated: %zu bytes\n", stats.allocated_bytes);
printk("Max alloc: %zu bytes\n", stats.max_alloc_bytes);

智能指针(C++)

#include <zephyr/kernel.h>

/* 使用 RAII 管理内存 */
std::unique_ptr<uint8_t[]> buffer(new uint8_t[256]);

/* 或者使用自定义删除器 */
auto deleter = [](void *p) { k_free(p); };
std::unique_ptr<uint8_t, decltype(deleter)> 
    ptr((uint8_t *)k_malloc(256), deleter);

4. 内存映射 (Memory Mapping)

用户模式内存域

#ifdef CONFIG_USERSPACE
/* 定义内存域 */
struct k_mem_partition partitions[] = {
    /* 代码段 */
    {.start = (uint32_t)&_image_text_start,
     .size = (uint32_t)&_image_text_size,
     .attr = K_MEM_PARTITION_P_RX_U_RX},
    
    /* 数据段 */
    {.start = (uint32_t)&_image_rodata_start,
     .size = (uint32_t)&_image_rodata_size,
     .attr = K_MEM_PARTITION_P_RO_U_RO},
};

struct k_mem_domain domain;
k_mem_domain_init(&domain, 2, partitions);

/* 将线程添加到域 */
k_mem_domain_add_thread(&domain, k_current_get());
#endif

MPU 保护

/* 启用 MPU */
CONFIG_ARM_MPU=y

/* 定义保护区域 */
struct arm_mpu_region regions[] = {
    /* 闪存 - 只读 */
    {.base = 0x08000000,
     .attr = REGION_RAM_ATTR},
    
    /* RAM - 读写 */
    {.base = 0x20000000,
     .attr = REGION_RAM_ATTR},
};

5. 内存管理最佳实践

静态 vs 动态

/* ✅ 推荐:静态分配 */
static uint8_t buffer[MAX_SIZE];

/* ❌ 避免:频繁动态分配 */
for (int i = 0; i < 1000; i++) {
    void *ptr = k_malloc(size);  /* 碎片! */
    /* ... */
    k_free(ptr);
}

/* ✅ 推荐:使用内存池 */
K_MEM_SLAB_DEFINE(packet_slab, 64, 10, 4);
for (int i = 0; i < 1000; i++) {
    void *ptr;
    if (k_mem_slab_alloc(&packet_slab, &ptr, K_NO_WAIT) == 0) {
        /* ... */
        k_mem_slab_free(&packet_slab, ptr);
    }
}

内存对齐

/* ✅ 对齐到缓存行 */
#define CACHE_LINE_SIZE 32
__aligned(CACHE_LINE_SIZE) static uint8_t dma_buffer[1024];

/* 或运行时对齐 */
void *aligned_ptr = k_aligned_alloc(CACHE_LINE_SIZE, size);

栈溢出保护

/* 启用栈保护 */
CONFIG_STACK_SENTINEL=y
CONFIG_STACK_CANARIES=y

/* 线程栈大小设计 */
#define THREAD_STACK_SIZE 2048  /* 根据实际需求 */
K_THREAD_STACK_DEFINE(my_stack, THREAD_STACK_SIZE);

/* 运行时检查栈使用量 */
size_t unused = k_thread_stack_space_get(my_thread_id);
printk("Unused stack: %zu bytes\n", unused);

内存泄漏检测

/* 启用内存跟踪 */
CONFIG_TRACING=y
CONFIG_TRACING_BACKEND_RAM=y

/* 或使用自定义钩子 */
void *my_malloc(size_t size, const char *file, int line)
{
    void *ptr = k_malloc(size);
    LOG_INF("ALLOC: %p size=%zu at %s:%d", ptr, size, file, line);
    return ptr;
}
#define k_malloc(s) my_malloc(s, __FILE__, __LINE__)

6. 内存配置参数

# 内核内存
CONFIG_MAIN_STACK_SIZE=2048           # 主线程栈
CONFIG_IDLE_STACK_SIZE=256            # 空闲线程栈
CONFIG_ISR_STACK_SIZE=2048            # 中断栈

# 堆内存
CONFIG_HEAP_MEM_POOL_SIZE=8192        # 堆总大小

# 内存池
CONFIG_MEM_SLAB_TRACE_MAX_UTILIZATION=y

# 内存保护
CONFIG_ARCH_HAS_USERSPACE=y
CONFIG_USERSPACE=y
CONFIG_MPU_STACK_GUARD=y
CONFIG_STACK_SENTINEL=y

7. 内存调试技巧

打印内存布局

extern char _image_rom_start[];
extern char _image_rom_end[];
extern char _image_ram_start[];
extern char _image_ram_end[];
extern char __bss_start[];
extern char __bss_end[];

void print_memory_layout(void)
{
    printk("ROM: %p - %p (size: %d)\n",
           _image_rom_start, _image_rom_end,
           _image_rom_end - _image_rom_start);
    
    printk("RAM: %p - %p (size: %d)\n",
           _image_ram_start, _image_ram_end,
           _image_ram_end - _image_ram_start);
    
    printk("BSS: %p - %p (size: %d)\n",
           __bss_start, __bss_end,
           __bss_end - __bss_start);
}

运行时内存统计

#include <zephyr/kernel.h>

void print_memory_stats(void)
{
    /* 栈统计 */
    printk("Stack usage:\n");
    k_thread_foreach(thread_stats_cb, NULL);
    
    /* 堆统计 */
    struct sys_heap_stats stats;
    k_heap_stats(&stats);
    printk("Heap: free=%zu, alloc=%zu\n",
           stats.free_bytes, stats.allocated_bytes);
}

8. 常见陷阱

  1. 内存碎片:频繁的大小不一的 k_malloc/k_free
  2. 栈溢出:线程栈设置过小
  3. 越界访问:数组/缓冲区溢出
  4. 使用已释放内存:悬挂指针
  5. 内存泄漏:忘记 k_free
  6. 对齐错误:DMA 缓存对齐要求

参考链接


*学习笔记创建时间: 2026-03-19*