跳转至

LOS_MemBox

membox — 固定大小内存池 — LiteOS Membox

前置阅读:osal_kmalloc — 先理解 kmalloc 的碎片问题,才知道为什么需要 membox

学习目标

  • 理解 membox 的原理——预分配固定大小槽位 + 位图管理,alloc/free 都是 O(1),零碎片
  • 掌握 LOS_MemboxInit(pool, poolSize, blkSize)LOS_MemboxAlloc(pool)LOS_MemboxFree(pool, box) 的调用链
  • 能够在高频小对象场景中正确选择 membox 而非 kmalloc

基本概念

membox 原理

membox 的思路很简单:预先把一块连续内存切成大小相同的槽,用位图跟踪每个槽的状态。

flowchart TD
    subgraph 内存池 1024B
        direction LR
        S0[槽0 64B]
        S1[槽1 64B]
        S2[槽2 64B]
        S3[槽3 64B]
        SD[...]
        S15[槽15 64B]
    end
    subgraph 位图 16bit
        direction LR
        B0["0"]
        B1["1"]
        B2["0"]
        B3["1"]
        BD["..."]
        B15["0"]
    end
    S0 -.-> B0
    S1 -.-> B1
    S2 -.-> B2
  • alloc = 找位图中第一个 0(空闲),标为 1,返回对应槽地址——O(1)
  • free = 根据槽地址算出位图索引,标为 0——O(1)
  • 因为所有槽大小完全相同,永远不会碎片化

典型使用场景

场景 块大小 为什么用 membox
消息帧缓冲区 64B 每秒数千次 alloc/free,kmalloc 很快碎片化
定时器结构体池 32B 动态创建/销毁定时器,生命周期短
网络包描述符 128B 高频收发,需要稳定分配速度

不适合的场景:需要可变大小——如 OTA 固件暂存(128KB),用 kmalloc。

membox vs kmalloc

对比项 membox kmalloc
分配速度 O(1),纳秒级 O(n),微秒级
碎片化 永远不会 长期运行会碎片化
块大小 固定,初始化时确定 可变,每次指定
内存利用率 可能浪费(未满槽也占着) 按需分配,利用率高
API 层 LiteOS 原生,los_membox.h OSAL 封装,osal_addr.h
ISR 可用 否(内部可能关中断) 是(GFP_ATOMIC
flowchart LR
    A[需要动态内存] --> B{块大小固定?}
    B -->|否| C[kmalloc]
    B -->|是| D{分配频率?}
    D -->|低频 偶尔| C
    D -->|高频 每秒百次+| E[membox]

涉及 API

API 谁调用 用途 头文件
UINT32 LOS_MemboxInit(VOID *pool, UINT32 poolSize, UINT32 blkSize) 入口 初始化 membox 池 los_membox.h
VOID *LOS_MemboxAlloc(VOID *pool) 任务 分配一个固定大小块(O(1)) los_membox.h
UINT32 LOS_MemboxFree(VOID *pool, VOID *box) 任务 释放一个块(O(1)) los_membox.h

membox 是 LiteOS 原生接口,无 OSAL 封装。使用时 #include "los_membox.h"

案例说明

做什么

创建一个 64B × 16 槽的 membox,循环 alloc/free 1000 次,统计平均耗时。同时用 kmalloc 做相同操作对比速度。验证 membox 的 O(1) 零碎片特性。

规格与功能

规格项 说明
池大小 64B × 16 = 1024B + 位图开销
块大小 64B(覆盖大多数传感器帧和消息结构体)
测试循环 1000 次 alloc + free
对比基准 kmalloc(64, GFP_KERNEL) + kfree 1000 次
对齐 __attribute__((aligned(4)))

程序运行流程:

  1. 静态数组 g_pool[1024] 对齐 → LOS_MemboxInit
  2. 循环 1000 次:alloc → 写入测试数据 → free
  3. 记录总 ticks,计算平均耗时
  4. 用 kmalloc 再做一遍,对比输出

案例流程

flowchart TD
    I[LOS_MemboxInit pool, 1024, 64] --> L
    L[循环 1000 次] --> A[LOS_MemboxAlloc]
    A --> U[写入测试数据]
    U --> F[LOS_MemboxFree]
    F --> D{1000 次?}
    D -->|否| A
    D -->|是| P[打印平均耗时]

案例操作指导

第一步:编译

fbb build ws63-liteos-app

第二步:烧录

第三步:验证

串口输出类似:

[membox] 1000 alloc+free: 350 ticks (avg 0.35 ticks/op)
[kmalloc] 1000 alloc+free: 8500 ticks (avg 8.50 ticks/op)
[membox] speedup: 24x

membox 比 kmalloc 快约 20~30 倍(取决于堆的碎片程度)。

关键配置

参数 说明
块大小 blkSize 64B 根据实际需求确定,所有 alloc 出来的块都这么大
槽数量 16 太少频繁满、太多浪费——按峰值并发量 × 1.5 估算
池大小 poolSize blkSize × 槽数 + 位图开销 init 时指定,静态数组大小要匹配
内存对齐 4 字节 __attribute__((aligned(4)))__aligned(4)
ISR 中 不建议 membox 内部可能关中断,ISR 中用 kmalloc(GFP_ATOMIC)

代码详解

membox 初始化

#include "los_membox.h"

#define BLK_SIZE  64
#define BLK_NUM   16
#define POOL_SIZE (BLK_SIZE * BLK_NUM)

/* 静态数组——编译期确定大小,放在 .bss 或 .data */
static uint8_t g_membox_pool[POOL_SIZE] __attribute__((aligned(4)));

static void app_entry(void)
{
    UINT32 ret;

    /* LOS_MemboxInit: pool, poolSize, blkSize
       poolSize 必须能容纳 blkSize × 数量 + 位图
       建议比 BLK_SIZE * BLK_NUM 稍大一些 */
    ret = LOS_MemboxInit(g_membox_pool, sizeof(g_membox_pool), BLK_SIZE);
    if (ret != LOS_OK) {
        printf("membox init failed!\n");
        return;
    }
    printf("membox init ok: %u slots of %u bytes\n", BLK_NUM, BLK_SIZE);
}
app_run(app_entry);

分配与释放

void membox_demo(void)
{
    void *blocks[BLK_NUM] = {NULL};
    int i;

    /* 分配全部 16 个槽 */
    for (i = 0; i < BLK_NUM; i++) {
        blocks[i] = LOS_MemboxAlloc(g_membox_pool);
        if (blocks[i] == NULL) {
            printf("alloc failed at slot %d\n", i);
            break;
        }
        /* 使用:写入测试数据 */
        memset(blocks[i], 0xA5, BLK_SIZE);
    }

    /* 释放前几个 */
    for (i = 0; i < 5; i++) {
        if (blocks[i] != NULL) {
            LOS_MemboxFree(g_membox_pool, blocks[i]);
            blocks[i] = NULL;
        }
    }

    /* 再 alloc——从刚释放的槽中分配,O(1) */
    void *new_block = LOS_MemboxAlloc(g_membox_pool);
    /* new_block 指向刚才释放的某个槽 */
}

注意 LOS_MemboxFree 需要传 pool 指针——free(pool, box),和 C 标准库 free(ptr) 不同。pool 指针必须和 alloc 时一致。

与 kmalloc 的速度对比

void benchmark(void)
{
    unsigned int start, elapsed;
    void *ptr;
    int i;

    /* membox 测速 */
    start = osal_get_tick();
    for (i = 0; i < 1000; i++) {
        ptr = LOS_MemboxAlloc(g_membox_pool);
        LOS_MemboxFree(g_membox_pool, ptr);
    }
    elapsed = osal_get_tick() - start;
    printf("[membox] 1000 ops: %u ticks\n", elapsed);

    /* kmalloc 测速 */
    start = osal_get_tick();
    for (i = 0; i < 1000; i++) {
        ptr = osal_kmalloc(BLK_SIZE, OSAL_GFP_KERNEL);
        osal_kfree(ptr);
    }
    elapsed = osal_get_tick() - start;
    printf("[kmalloc] 1000 ops: %u ticks\n", elapsed);
}

选择策略总结

你的需求 推荐
块大小固定,每秒 alloc/free 百次以上 membox
块大小可变,偶尔分配 kmalloc
块大小固定,但偶尔分配 都可以,kmalloc 更简单
ISR 中需要分配 kmalloc(GFP_ATOMIC)(membox 不推荐 ISR)
需要 OSAL 统一接口 kmalloc(membox 是 LiteOS 原生)