osal_kmalloc
动态内存分配 — kmalloc / kfree — OSAL kmalloc / kfree
前置阅读:多任务并发
学习目标
- 理解嵌入式 RTOS 动态内存的碎片风险和适用场景
- 掌握
osal_kmalloc(size, gfp)→ NULL 检查 →osal_kfree(ptr)→ptr = NULL的标准防泄漏写法 - 区分
OSAL_GFP_KERNEL(任务中使用,可阻塞)和OSAL_GFP_ATOMIC(ISR 中使用,不可阻塞) - 能够通过 alloc/free 计数排查内存泄漏
基本概念
什么时候用动态内存
嵌入式的首选是静态分配——编译期确定大小,无碎片、无泄漏风险。但以下场景必须用动态内存:
| 场景 | 为什么静态分配不行 |
|---|---|
| 可变长度协议帧缓冲区 | 帧长不确定(几十到几千字节),静态分配只能按最大帧长预留——浪费 |
| 运行时动态设备列表 | 设备数量不确定(可能连接 1 个或 8 个),静态定死不灵活 |
| OTA 固件暂存 | 临时大缓冲区(~128KB),用完全释放,长期占用太浪费 |
动态内存不是禁忌——关键是用对场景和用对方法。
碎片问题
碎片是动态内存最大的陷阱——不是总空闲够,是没有连续的块够大。
flowchart TD
subgraph 初始状态
A1[空闲 256B]
end
subgraph 分配后
A2[已用 64B] --> F2[空闲 192B]
end
subgraph 再分配再释放
A3[已用 64B] --> B3[空闲 64B] --> C3[已用 64B] --> D3[空闲 64B]
end
subgraph 碎片化结果
N[需要 128B 连续块 — 虽然空闲总计 128B<br/>但都是 64B 小块,分配失败]
end
碎片化后即使总空闲 128B,两个 64B 小块无法合并——
osal_kmalloc(128, ...)返回 NULL。
GFP 标志:何时可阻塞、何时不可
| 标志 | 使用上下文 | 行为 | 典型场景 |
|---|---|---|---|
OSAL_GFP_KERNEL |
任务 | 可能阻塞等待内存回收,分配成功率更高 | 正常业务代码 |
OSAL_GFP_ATOMIC |
ISR / tasklet | 绝不阻塞,分配不到立即返回 NULL | 中断上下文中临时分配 |
ISR 中必须用
OSAL_GFP_ATOMIC——用KERNEL可能导致 ISR 阻塞,系统崩溃。
分配生命周期
sequenceDiagram
participant T as 任务
participant H as 堆
T->>H: buf = osal_kmalloc(size, GFP_KERNEL)
alt 分配成功
H-->>T: 返回有效指针
T->>T: if (buf == NULL) 错误处理
T->>T: memset / 使用 buf
T->>H: osal_kfree(buf)
T->>T: buf = NULL
Note over T: 置 NULL 防野指针重用
else 分配失败 OOM
H-->>T: 返回 NULL
T->>T: 错误处理——降级或重试
end
涉及 API
| API | 谁调用 | 用途 | 头文件 |
|---|---|---|---|
void *osal_kmalloc(unsigned long size, unsigned int osal_gfp_flag) |
任务/ISR | 动态分配内存 | osal_addr.h |
void *osal_kzalloc(unsigned long size, unsigned int osal_gfp_flag) |
任务/ISR | 分配并清零(等价于 kmalloc + memset(0)) | osal_addr.h |
void osal_kfree(void *addr) |
任务/ISR | 释放内存 | osal_addr.h |
案例说明
做什么
任务中循环分配随机大小的缓冲区(模拟可变长度协议帧),用完立即释放。连续运行观察可用内存是否保持稳定——验证无内存泄漏。
规格与功能
| 规格项 | 说明 |
|---|---|
| 分配大小 | 32~256B 随机(模拟可变长度帧) |
| GFP 标志 | OSAL_GFP_KERNEL(任务中) |
| 释放时机 | 每次使用后立即释放 |
| 泄漏检测 | 维护 alloc 计数和 free 计数,差值持续增长 = 泄漏 |
程序运行流程:
- 任务循环 100 次:随机大小 → alloc → 检查 NULL → 使用 → free → 置 NULL
- 循环结束后打印 alloc/free 统计,确认一致
- 长时间运行(10 分钟+),观察可用内存趋势
案例流程
flowchart TD
S[开始] --> A[生成随机大小 32-256]
A --> B[osal_kmalloc size, GFP_KERNEL]
B --> C{buf == NULL?}
C -->|是| E[打印 OOM 错误]
C -->|否| D[使用 buffer]
D --> F[osal_kfree buf]
F --> G[buf = NULL]
G --> H{循环结束?}
H -->|否| A
H -->|是| I[打印 alloc/free 统计]
案例操作指导
第一步:编译
第二步:烧录
第三步:验证
串口输出类似:
[alloc] size=128, ptr=0x2000A000, free_remain=45000
[free] ptr=0x2000A000, free_remain=45128
...
[summary] alloc=100, free=100, leak=0
leak=0 且 free_remain 变化在合理范围内(碎片导致的微小波动正常)。
关键配置
| 参数 | 值 | 说明 |
|---|---|---|
| GFP 标志(任务中) | OSAL_GFP_KERNEL |
可能阻塞,分配成功率最高 |
| GFP 标志(ISR 中) | OSAL_GFP_ATOMIC |
绝不阻塞,分配失败即返回 NULL |
| alloc 后 | 必须检查 NULL | OOM(Out Of Memory)处理 |
| free 后 | 必须置 NULL | 防止野指针被误用 |
| 避免高频 alloc/free | 固定大小用 membox | kmalloc 高频调用加速碎片化 |
代码详解
标准防泄漏写法
#include "osal_addr.h"
/* 正确的 alloc/free 模式 */
void process_frame(unsigned int frame_len)
{
uint8_t *buf = NULL;
/* 1. 分配 */
buf = (uint8_t *)osal_kmalloc(frame_len, OSAL_GFP_KERNEL);
if (buf == NULL) {
printf("OOM: cannot alloc %u bytes\n", frame_len);
return; // 分配失败,优雅降级
}
/* 2. 使用 */
memset(buf, 0, frame_len);
// ... 协议解析等操作 ...
/* 3. 释放 + 置 NULL */
osal_kfree(buf);
buf = NULL; // 防止后续代码误用已释放的指针
}
可变长度分配
/* 根据协议帧头决定分配大小——这是静态分配做不到的 */
uint16_t payload_len = (frame_header[2] << 8) | frame_header[3];
uint8_t *payload = osal_kmalloc(payload_len, OSAL_GFP_KERNEL);
if (payload != NULL) {
memcpy(payload, &frame_data[4], payload_len);
process_payload(payload, payload_len);
osal_kfree(payload);
payload = NULL;
}
ISR 中的原子分配
/* ISR 中临时需要一块小缓冲——必须用 GFP_ATOMIC */
static int my_isr(int irq, void *dev)
{
uint8_t *tmp = osal_kmalloc(64, OSAL_GFP_ATOMIC);
if (tmp == NULL) {
/* 分配不到不阻塞,立即返回 */
return OSAL_IRQ_HANDLED;
}
/* 快速使用 */
memcpy(tmp, hw_fifo, 64);
osal_msg_queue_write_copy(g_queue_id, tmp, 64, OSAL_MSGQ_NO_WAIT);
osal_kfree(tmp); // ISR 中可以 free
return OSAL_IRQ_HANDLED;
}
ISR 中
kmalloc用GFP_ATOMIC、队列操作用NO_WAIT——两条规则都指向同一条原则:ISR 绝不阻塞。
内存泄漏排查
static int alloc_count = 0;
static int free_count = 0;
void *my_alloc(unsigned long size)
{
void *ptr = osal_kmalloc(size, OSAL_GFP_KERNEL);
if (ptr != NULL) alloc_count++;
return ptr;
}
void my_free(void *ptr)
{
if (ptr != NULL) {
osal_kfree(ptr);
free_count++;
}
}
/* 定期打印统计——差值持续增长 = 有泄漏 */
void print_mem_stats(void)
{
printf("alloc=%d free=%d leak=%d\n",
alloc_count, free_count, alloc_count - free_count);
}
如果
alloc_count - free_count持续增长,说明有代码路径 alloc 了但没有 free。排查方法:每次 alloc 时记录调用栈(或文件名+行号),找到未配对的 alloc。