news 2026/6/26 8:29:11

用C语言实现单片机malloc功能:TLSF算法实现单片机malloc函数及单片机malloc原理详解和测试

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
用C语言实现单片机malloc功能:TLSF算法实现单片机malloc函数及单片机malloc原理详解和测试

、传统内存管理与TLSF算法
在嵌入式实时系统(RTOS)开发中,内存分配是一个让人又爱又恨的话题。传统堆分配器(如ptmalloc)虽然功能强大,但存在两个致命缺陷:

1、分配时间不确定:最坏情况下需要遍历整个空闲链表,时间复杂度O(n),无法满足硬实时要求;

2、内存碎片严重:频繁的分配/释放导致大量无法利用的内存碎片;

TLSF(Two-Level Segregated Fit) 算法正是为解决这些问题而生。它由西班牙马德里理工大学于2005年提出,能够在O(1)时间复杂度内完成内存分配与释放,且碎片率极低。本文将基于TLSF代码,深入剖析其设计原理与工程实践。

二、设计需求分析
2.1 实时性需求

指标传统分配器TLSF目标
分配时间O(n) 不稳定O(1) 确定上限
释放时间O(1)~O(n)O(1) 严格保证
最坏情况可能遍历MB级内存固定32次位运算

2.2 嵌入式场景约束
// 我们的目标平台资源极其有限

#define ALIGN_BYTES 0x04 // 4字节对齐(适配32位ARM) #define BIT_SIZE_CONFIG 8 // 位图使用8位(节省RAM) #define BLOCK_Min_SIZE (sizeof(List_Node)) // 最小块16字节

关键约束:

  • 确定性:中断服务程序(ISR)中也能安全调用

  • 可预测性:内存碎片必须可控

  • 内存对齐:内存必须对齐

三、核心设计方法:两级分离适配
3.1 设计哲学:分而治之
TLSF的核心思想是"将不同大小的内存块分类管理"。就像图书馆把书籍按类别分区存放,找书时直接去对应区域,而不是遍历整个书库。

3.2 两级索引架构

┌─────────────────────────────────────────────────────────┐ │ 第一级索引(FLI: First Level Index) │ │ 按2的幂次方分档:16, 32, 64, 128, 256, 512, 1K, 2K... │ │ 共32档,覆盖4GB地址空间 │ ├─────────────────────────────────────────────────────────┤ │ 第二级索引(SLI: Second Level Index) │ │ 每档内细分为8个子区间,均匀划分 │ │ 例如256字节档:256~287, 288~319, ..., 480~511 │ └─────────────────────────────────────────────────────────┘

代码实现:

// 将大小映射到两级索引 static inline UINT_32 Change_Index(UINT_32 size, int Position_bit, UINT_32 alignment) { UINT_32 align_bit = 0x01 << alignment; return (Position_bit << alignment) | ((align_bit - 1) & (size >> (Position_bit - alignment))); } // 向上取整到合适的档 static UINT_32 Align_Index(UINT_32 size, UINT_32 alignment) { int Position_bit = Get_highest_bit_position(size); return (Change_Index(size, Position_bit, alignment) + (((0x01 << (Position_bit - alignment)) - 1) & size ? 1 : 0)); }

示例: 请求200字节内存

  • Position_bit = 8 (128 < 200 ≤ 256)

  • 第二级索引 = (200 >> (8-3)) & 0x07 = 6

  • 最终索引 = (8 << 3) | 6 = 70

3.3 位图加速查找

typedef struct Memory_Head { volatile TPED_BIT Map_bit[FREE_LIST_SIZE]; // 第一级位图:32个8位组 volatile List_Node* Free_List_Head[FREE_LIST_SIZE][8]; // 256个空闲链表头 } Memory_Head;

查找过程(O(1)关键):

static int Find_Block(Memory_Head* Head, UINT_32 Index_len, UINT_32 x) { UINT_32 Bit_mask = 0x01 << Head->Map_unit_size; // 8 UINT_32 Bit_position = Head->Map_unit_size; // 3 // 步骤1:检查当前档是否有空闲块 if (Head->Map_bit[x >> Bit_position] < (0x01U << (x & (Bit_mask - 1)))) { // 步骤2:当前档无可用块,跳到下一个第一级档 x += Bit_mask - (x & (Bit_mask - 1)); while ((x >> Bit_position) < Index_len && !Head->Map_bit[x >> Bit_position]) x += Bit_mask; } // 步骤3:在找到的第一级档中,遍历第二级位图 Temp_bit = Head->Map_bit[x >> Bit_position]; for (UINT_32 i = (x & (Bit_mask - 1)); i < Bit_mask; i++, x++) { if (Temp_bit & (0x01U << i)) return x; // 找到! } return -1; }

时间复杂度分析:

  • 第一级跳转:最多32次位运算

  • 第二级扫描:最多8次位运算

  • 总计:固定40次基本操作 → O(1)

四、功能实现详解
4.1 内存块结构设计

typedef struct Base_Node { struct { volatile UINT_32 Block_Size : 31; // 块大小(支持2GB) volatile UINT_32 Used_flag : 1; // 使用标志 }; volatile void* Pre_addr; // 物理相邻前块(用于合并) } Base_Node; typedef struct List_Node { Base_Node Base; struct List_Node* Prev; // 链表前驱(同大小档) struct List_Node* Next; // 链表后继 } List_Node;

设计亮点:

  • 物理邻接指针(Pre_addr):实现O(1)合并,无需遍历

  • 逻辑链表指针(Prev/Next):同档空闲块快速组织

  • 边界标记:末尾哨兵块防止越界

4.2 内存分配流程示意图

4.3 初始化:构建初始空闲块

extern unsigned int Init_TLSF(Memory_Head* Head, void* Memory_addr, UINT_32 Memory_size, void (*Lock)(void), void (*Unlock)(void)) { // 4字节对齐内存大小 Memory_size = ALIGN_SIZE(Memory_size); // 初始化管理头 Head->Total_Size = Memory_size; Head->Available_Size = 0; Head->Map_unit_size = Get_highest_bit_position(sizeof(Head->Map_bit[0]) << 3); if(Get_highest_bit_position(BLOCK_Min_SIZE) < Head->Map_unit_size) return 0; // 清零位图与链表 for (UINT_32 i = 0; i < FREE_LIST_SIZE; i++) { Head->Map_bit[i] = 0; for (UINT_32 j = 0; j < 8; j++) Head->Free_List_Head[i][j] = NULL; } // 创建唯一大空闲块 List_Node* Node = (List_Node*)Memory_addr; Node->Base.Pre_addr = NULL; Node->Base.Used_flag = 1; // 临时标记,Add_Node会清零 Node->Base.Block_Size = Memory_size - BLOCK_Min_SIZE; Add_Node(Head, Node); // 加入空闲链表,更新位图 // 放置末尾哨兵(防止向后合并越界) Node = (List_Node*)((BYTE*)Memory_addr + (Memory_size - BLOCK_Min_SIZE)); Node->Base.Pre_addr = Memory_addr; Node->Base.Used_flag = 1; // 永久占用 Node->Base.Block_Size = BLOCK_Min_SIZE; Head->End_Addr_Node = Node; return 1; }

4.4 内存分配:分割与适配

extern void* TLSF_malloc(Memory_Head* Head, UINT_32 size) { // 参数校验与对齐 if(size == 0 || size > Head->Available_Size) return NULL; size += ALIGN_SIZE(sizeof(Base_Node)); // 包含元数据 size = (size < BLOCK_Min_SIZE) ? BLOCK_Min_SIZE : ALIGN_SIZE(size); // O(1)查找合适空闲块 UINT_32 Index = Align_Index(size, Head->Map_unit_size); int Node_index = Find_Block(Head, FREE_LIST_SIZE, Index); if (Node_index == -1) return NULL; // 取出块并分割(如果剩余足够大) List_Node* Node = Take_Out_Node(Head, Node_index); return (void*)((BYTE*)Partition_Node(Head, Node, size) + ALIGN_SIZE(sizeof(Base_Node))); }

分割策略(关键优化):

最小分配快:
对于每次通过malloc( size )分配的字节大小size在TLSF内部查找匹配时往往存在一个最小匹配块,当分配的字节小于这个匹配块时都会会返回这个最小匹配块。这个最小匹配块就是上图的最小地址匹配块的大小,低于这个最小匹配块时不可以再度被分割。

static void* Partition_Node(Memory_Head* Head, void* addr, UINT_32 size) { List_Node* Node = (List_Node*)addr; if (Node->Base.Block_Size >= (size + BLOCK_Min_SIZE)) { // 分割出剩余空闲块 List_Node* Divide_Node = (List_Node*)((BYTE*)addr + size); List_Node* Next_Node = (List_Node*)((BYTE*)addr + Node->Base.Block_Size); Divide_Node->Base.Block_Size = Node->Base.Block_Size - size; Divide_Node->Base.Used_flag = 0; Divide_Node->Base.Pre_addr = addr; Next_Node->Base.Pre_addr = Divide_Node; // 更新物理邻接 Node->Base.Block_Size = size; Add_Node(Head, Divide_Node); // 剩余块回归空闲链表 } Node->Base.Used_flag = 1; return Node; }

4.5 内存释放:立即合并

extern void TLSF_free(Memory_Head* Head, void* addr) { if (Head == NULL || addr == NULL) return; // 回退到块头,合并相邻空闲块 Merge_Node(Head, (BYTE*)addr - ALIGN_SIZE(sizeof(Base_Node))); }

双向合并(碎片抑制核心):

static void Merge_Node(Memory_Head* Head, void* addr) { List_Node* Node = (List_Node*)addr; List_Node* Pre_Node = (List_Node*)Node->Base.Pre_addr; List_Node* Next_Node = (List_Node*)((BYTE*)Node + Node->Base.Block_Size); // 向前合并 if (Pre_Node && !Pre_Node->Base.Used_flag) { Delete_Node(Head, Pre_Node); Pre_Node->Base.Block_Size += Node->Base.Block_Size; Next_Node->Base.Pre_addr = Pre_Node; Node = Pre_Node; } // 向后合并 if (!Next_Node->Base.Used_flag) { Delete_Node(Head, Next_Node); Node->Base.Block_Size += Next_Node->Base.Block_Size; Next_Node = (List_Node*)((BYTE*)Node + Node->Base.Block_Size); Next_Node->Base.Pre_addr = Node; } Add_Node(Head, Node); // 合并后的块回归空闲链表 }

4.6 线程安全封装

extern void* TLSF_malloc_Safe(Memory_Head* Head, UINT_32 size) { if (Head->Lock) Head->Lock(); // 获取互斥锁 void* ptr = TLSF_malloc(Head, size); if (Head->Unlock) Head->Unlock(); // 释放锁 return ptr; }

设计优势: 锁机制外置,支持:

  • 关中断(裸机RTOS)

  • 互斥量(多任务系统)

  • 自旋锁(SMP多核)

五、高级功能:realloc的优化实现

extern void* TLSF_realloc(Memory_Head* Head, void* addr, UINT_32 size) { List_Node* Node = (List_Node*)((BYTE*)addr - ALIGN_SIZE(sizeof(Base_Node))); UINT_32 Old_size = Node->Base.Block_Size; List_Node* Next_Node = (List_Node*)((BYTE*)Node + Old_size); size = ALIGN_SIZE(size) + ALIGN_SIZE(sizeof(Base_Node)); size = (size < BLOCK_Min_SIZE) ? BLOCK_Min_SIZE : ALIGN_SIZE(size); // 场景1:当前块足够大,直接分割 if (Old_size >= size) { if (Old_size >= (size + BLOCK_Min_SIZE)) { // 分割并尝试与后邻居合并 List_Node* Free_Node = (List_Node*)((BYTE*)Node + size); Node->Base.Block_Size = size; Free_Node->Base.Block_Size = Old_size - size; Free_Node->Base.Pre_addr = Node; if(Next_Node->Base.Used_flag == 0) { // 与后邻居合并,减少碎片 Delete_Node(Head, Next_Node); Free_Node->Base.Block_Size += Next_Node->Base.Block_Size; ((List_Node*)((BYTE*)Free_Node + Free_Node->Base.Block_Size))->Base.Pre_addr = Free_Node; } Add_Node(Head, Free_Node);
版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/6/26 8:25:40

Facebook高ROAS打法

Facebook高ROAS的打法。它不是某一种秘技&#xff0c;而是一套严密的系统工程。核心思维转变&#xff1a;从“找到对的人”到“找到对的动机”过去我们热衷于研究兴趣标签&#xff0c;现在更高效的思路是测试“用户为什么买”。因为Facebook的算法比你更懂谁是你的用户&#xf…

作者头像 李华
网站建设 2026/6/26 8:25:07

网络安全常见的20个漏洞及防御方式

本文汇总了全网最全的常见网络安全漏洞、对应绕过技巧与防御方案&#xff0c;搭配多张辅助图示&#xff0c;碰到相关安全问题的开发者、网络安全学习者可直接参考使用&#xff0c;适合小白及程序员学习。 全网最全常见的网络安全漏洞、绕过技巧和防御方式&#xff0c;有碰到这…

作者头像 李华
网站建设 2026/6/26 8:24:55

复杂国际媒体环境下企业如何做好全球化品牌传播?

2026年&#xff0c;中国企业全球化布局正式迈入提质增效的“深水区”。海外媒体传播不再是简单的信息曝光、内容铺陈&#xff0c;已然升级为企业构建全球化品牌、撬动海外市场增量的核心战略抓手。面对日趋复杂的国际舆论环境、差异化的地域文化壁垒以及碎片化的全球传播格局&a…

作者头像 李华
网站建设 2026/6/26 8:22:07

2026年,这家现货当天发的新吨袋供应商,究竟有何独特魅力?

在包装材料领域&#xff0c;吨袋作为一种常见且重要的产品&#xff0c;广泛应用于各个行业。而苏州淞镕包装材料有限公司&#xff08;以下简称“苏州淞镕包装”&#xff09;&#xff0c;在2026年凭借其独特的优势&#xff0c;成为众多企业青睐的新吨袋供应商。那么&#xff0c;…

作者头像 李华
网站建设 2026/6/26 8:20:17

AiM XLog赛车车载路试数采系统:赛道性能数据一体化解决方案

在赛车研发、整车路试、性能调校领域&#xff0c;精准、全面的车辆数据是优化圈速、排查车辆工况、迭代动力系统的核心依据。AiM 全套车载数采解决方案&#xff0c;以 XLog 便携式防水记录仪为核心主机&#xff0c;搭配完整拓展硬件与专业分析软件&#xff0c;构建覆盖定位、总…

作者头像 李华
网站建设 2026/6/26 8:18:03

实地走访固体饮料代工工厂,定制粉剂原料配比靠谱吗?

做健康食品这行快五年了&#xff0c;最怕的就是两件事&#xff1a;一是客户拿着“网红配方”来找我&#xff0c;说要一模一样的产品&#xff0c;结果一检测&#xff0c;原料掺假、配比瞎调&#xff0c;喝不出问题算运气好&#xff1b;二是小批量试单&#xff0c;工厂爱答不理&a…

作者头像 李华