LiteOS内存管理:TLSF算法

问题背景

TLSF算法主要是面向实时操作系统提出的,对于RTOS而言,执行时间的确定性是最根本的(吞吐量不一定高),然而传统的动态内存分配器(DMA, Dynamic Memory Allocator)存在两个主要问题

  1. 最坏情况执行时间不确定(not bounded)或者复杂度过高(bounded with a too important bound")
  2. 碎片化问题(fragmentation)

TLSF的提出较好地解决了以上两个问题: 将动态内存的分配与回收时间复杂度都降到了O(1)时间复杂度,并且采用了Good-fit的分配策略保证系统运行时不会产生过多碎片。

TLSF概要

TLSF(全称Two-Level Segregated Fit),从命名来看主要分为三部分

  1. Segregated Free List
  2. Two-Level Bitmap
  3. Good Fit

前两个是数据结构,第三个是分配策略。

TLSF主要采用两级位图(Two-Level Bitmap)与分级空闲块链表(Segregated Free List)的数据结构管理动态内存池(memory pool)以及其中的空闲块(free blocks),用Good-Fit的策略进行分配。下面我们先简单介绍一下这三者。

1. 分级空闲块链表(Segregated Free List)

还是采用拆分理解的方式,继续把Segregated Free List拆开,探究其设计思路和发展演变过程

  1. List: 隐式链表,管理所有内存块
  2. Free List:显式链表,只管理空闲块
  3. Segregated Free List:分级空闲块链表,按空闲块大小分级,用多个链表管理

List

链表是内存管理中最常见的数据结构,在一块内存块头部添加一个头结点,记录该block本身的信息以及前后继block的关系。


隐式链表

其中最简单的一种就是隐式链表,链接所有内存块, 只记录内存块大小,由于内存块紧密相连,通过头结点指针加内存块大小即可得到下一个内存块的位置。由于没有显式指明内存块的地址,而是通过计算得到,所以又叫做隐式链表。

当需要分配内存时,需要从第一块内存块开始检索,检查该内存块是否被分配以及内存块大小是被满足,直到找到大小合适的空闲块分配出去。

Free List

显式链表

隐式链表主要问题在于当内存分配时并不需要检索已分配的内存块,这浪费了不少时间,只需检索空闲块即可。因此,显式空闲块链表在空闲块头部添加一个指针域,指向下一个空闲块,这样检索时会跳过已分配的内存块(used blocks)。

Segregated Free List


分级空闲链表

上面提到的隐式链表和显式链表主要问题在于当空闲块个数为n时,检索复杂度在O(n)级别,速度较慢,分级空闲块链表优化了空闲块检索的复杂度,粗略计算大概降到O(log(n))级别。

分级空闲块链表(Segregated Free List)的设计思想是将空闲块按照大小分级,形成了不同块大小范围的分级(class),组内空闲块用链表链接起来。每次分配时先按分级大小范围查找到相应链表,再从相应链表挨个检索合适的空闲块,如果找不到,就在大小范围更大的一级查找,直到找到合适的块分配出去。

2. Two-Level Bitmap

上面我们介绍了分级空闲块链表的原理,但是我们并没有提及如何按照内存块大小分级。TLSF算法引入了位图(Bitmap)来解决这个问题。

位图(Bitmap)

Bitmap示意图

位图的优势:

  • 节省存储空间:用1-bit表示某个区间范围大小的空闲块是否存在
  • 位操作速度快:部分体系结构有加速特殊位操作的指令(如clz, ffs,fls)

维基百科上还有更多关于bitmap的优劣势,这里不详述,参照Free Space Bitmap

Segregated List + Two-Level Bitmap

当分级空闲块链表碰上位图,动态内存管理结构变化稍微有些大,下面这张图画得还算清晰(能依稀看到分级空闲块链表的影子就好:-P)。


TLSF采用了两级位图(Two-Level Bitmap)来管理不同大小范围的空闲块链(free block lists)。 上图中包含三个虚线矩形框分别是:

  1. 第一级位图(First-Level Bitmap),表示内存块的粗粒度范围,一般是2的幂次粒度(例如[ 2^n ~ 2^n+1 ])
  2. 第二级位图(Secend-Level Bitmap)是一个数组,一级位图中的每一位对应这个数组的一项,表示内存块的细粒度范围(例如[ 2^n + 0*2^n-2 ~ 2^n + 1*2^n-2 ])
  3. 第三个框是内存中真正的空闲内存块(free blocks)

内存分配与释放流程(简版)

有了TLSF的大体框架概念以后,就可以先看一下内存alloc与free的简要流程。(细节下一节结合源码探讨)
内存分配流程

  1. 在位图中搜索合适的空闲块大小范围,找到free list的头指针
  2. 基于free list的头指针检索list分配空闲块

内存释放流程

  1. 将需要释放的内存块置为空闲块
  2. 与该空闲块物理上相邻的空闲块合并
  3. 计算合并后的空闲块大小范围,检索位图找到对应的free list
  4. 将该内存块加入free list,返还给内存池

3. Good-fit 策略

内存结构和分配释放流程已经有了大致的了解,但是其中还有一个小问题并没有说明:

如何检索到大小合适的空闲块链?

常规思路:Best-fit(内部碎片最优)

Best-fit

常规思路是:找到能满足内存请求大小的最小空闲块,就会有下面的流程(以搜索大小为69字节的空闲块为例)

  1. 基于位运算找到请求大小所在的第一级位图(First-Level bitmap)对应的粗粒度范围([ 64 ~ 128 ]),也就是二级位图的索引
  2. 在粗粒度范围内,根据二级位图索引检索第二级位图(Second-Level bitmap)得到细粒度范围([ 68 ~ 70 ])
  3. 如上图所示,沿着右下角空闲块链表可以检索到69字节的那一块是Best-fit

看起来Best-fit 已经很不错了,但仍然还有提升空间。Best-fit策略最主要的问题还在于第三步,仍然需要检索对应范围的那一条空闲块链表,存在潜在的时间复杂度。

Good-fit(少量碎片换取O(1)时间复杂度)

Good-fit思路与Best-fit不同之处在于,Good-fit并不保证找到满足需求的最小空闲块,而是尽可能接近要分配的大小。

还以上述搜索大小为69字节的空闲块为例,Good-fit并不是找到[68 ~ 70]这一范围,而是比这个范围稍微大一点儿的范围(例如[71 ~ 73])。这样设计的好处就是[71 ~ 73]对应的空闲块链中每一块都能满足需求,不需要检索空闲块链表找到最小的,而是直接取空闲块链中第一块即可。整体上还不会造成太多碎片。

妙啊

TLSF源码剖析

这一节我们扒一扒LiteOS的源码,分析其中的数据结构和内存布局。

容我吐个槽,单纯学习TLSF算法可以参考这份代码,注释较多,命名方式也相对好理解

数据结构

1. 内存池管理:LOS_MEM_POOL_INFO

LOS_MEM_POOL_INFO对照示意图
// LOS_MEM_POOL_INFO负责管理一个内存池(Memory Pool),位于该内存池的头部(低地址)
typedef struct LOS_MEM_POOL_INFO {
    // 相当于一个NULL指针,空闲块链的头指针指向这里说明为空链
    LOS_MEM_DYN_NODE stBlock_null;

    // 内存池大小
    UINT32 uwPoolSize;

    // 空闲块list的位图信息
    // fl指空闲空间大小的粗粒度区间(例如2^16~2^17这样跨度比较大的区间)
    // sl指空闲空间大小的细粒度区间(例如(2^16 + 1*2^13) ~ (2^16 + 2*2^13)这样跨度稍微小的区间)
    UINT32 fl_bitmap;
    UINT32 sl_bitmap[FL_INDEX_COUNT];

    // 对应sl_bitmap,每一bit对应一个头指针,指向一个空闲块链
    // 若指向stBlock_null表示链为空,不存在该链不存在空闲块
    LOS_MEM_DYN_NODE *pstBlocks[FL_INDEX_COUNT][SL_INDEX_COUNT];
    
// 如果有多个内存池,则通过pNextPool链接
#if (LOSCFG_MEM_MUL_POOL == YES)
    VOID *pNextPool;
#endif

} LOS_MEM_POOL_INFO;

2. 内存块管理LOS_MEM_DYN_NODE

LOS_MEM_DYN_NODE
/*
 * Block header structure.
 *
 * There are several implementation subtleties involved:
 * - The prev_phys_block field is only valid if the previous pNode is free.
 * - The prev_phys_block field is actually stored at the end of the
 *   previous pNode. It appears at the beginning of this structure only to
 *   simplify the implementation.
 * - The pNext_free / pPrev_free fields are only valid if the pNode is free.
 */
typedef struct LOS_MEM_DYN_NODE {
    // 该字段不在本内存块内,实际在物理地址相邻的前一个节点末尾
    /* Points to the previous physical pNode. */
    struct LOS_MEM_DYN_NODE *pstPreNode;
    /* The size of this pNode, excluding the pNode header. */
    UINT32 uwSize;
    
    /* Next and previous free blocks. */
    // 空闲块(free block)通过双向链表链接
    // 使用块(used block)无此字段,当作存储使用
    struct LOS_MEM_DYN_NODE *pNext_free;
    struct LOS_MEM_DYN_NODE *pPrev_free;
} LOS_MEM_DYN_NODE;

空闲块和使用块复用同一数据结构,但在使用时并非所有字段都使用。

参考链接

附:LiteOS在Eclipse+QEMU虚拟机环境搭建

主要参考下面这两篇博客,从安装eclipe、配置到项目编译运行,挺完整的,Mac下也没什么问题,就是eclipse有点卡-_- !
Windows10如何安装LiteOS开发环境(一)
Windows10如何安装LiteOS开发环境(二)
提个醒:

  • bilibili复制粘贴不知道混入了啥字符,还是亲手打字比较好,不然编译错误
  • 第二篇博客中配置头文件前一定要先点击LiteOS工程选中整个工程
    配置头文件
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 159,219评论 4 362
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,363评论 1 293
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 108,933评论 0 243
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 44,020评论 0 206
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,400评论 3 287
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,640评论 1 219
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,896评论 2 313
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,597评论 0 199
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,327评论 1 244
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,581评论 2 246
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,072评论 1 261
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,399评论 2 253
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,054评论 3 236
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,083评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,849评论 0 195
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,672评论 2 274
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,585评论 2 270

推荐阅读更多精彩内容