RT-Thread内核学习之内存管理

    技术2026-03-03  8

    简介

    内存分类:内部存储空间和外部存储空间。内部存储空间:访问速度比较快,能够按照变量地址随机的访问,即RAM(随机存储器),可以把它理解为电脑的内存;外部存储空间:所保存的内容比较固定,即使掉点后数据也不会丢失,即ROM(只读存储器),可以把它理解为电脑的硬盘。关于以上两种内存管理方式,分别是动态内存堆管理和静态内存池管理。

    内存管理的功能特点

    由于实时系统中对时间的要求非常严格,内存管理往往要比通用操作系统要求苛刻得多:

    分配内存的时间必须是确定的。一般内存管理算法是根据需要存储的数据的长度在内存中去寻找一个与这段数据相适应的空闲内存块,然后将数据存储在里面。随着内存不断被分配和释放,整个内存区域会产生越来越多的碎片,系统还有足够的空闲内存,但因为它们地址并非连续,不能组成一块连续的完整内存块,会使得程序不能申请到大的内存。针对不同的系统,选择适合他们的高效率内存分配算法。

    RT-Thread根据上层应用及系统资源的不同,有针对性地提供了不同的内存分配管理算法。总体上可分为两类:内存堆管理与内存池管理,而内存堆管理又根据具体内存设备划分为三种情况:

    针对小内存块的分配管理(小内存管理算法);针对大内存块的分配管理(slab管理算法);针对多内存堆的分配情况(memheap管理算法)。

    内存堆管理

    一个ARM程序包含3部分:RO,RW和ZI,解释如下:

    RO是程序中的指令和常量,RO就是readonly;RW是程序中的已初始化变量,RW就是read/write;ZI是程序中的未初始化的变量,ZI就是zero. 内存堆管理用于管理一段连续的内存空间,如下图所示,RT-Thread将“ZI段结尾处”到内存尾部的空间用做内存堆。 内存堆可以在当前资源满足的情况下,根据用户的需求分配任意大小的内存块。而当用户不需要再使用这些内存块时又可以释放回堆中供其他应用分配使用。 小内存管理算法:针对系统资源比较少,一般用于小于2MB内存空间的系统;slab内存管理算法:主要在系统资源比较丰富时,提供一种近似多内存池管理算法;memheap管理算法:适用于系统存在多个内存堆的情况,可以将多个内存“粘帖”在一起,形成一个大的内存堆。

    小内存管理算法

    小内存管理算法是一个简单的内存分配算法,初始时,它是一块大的内存,当需要分配内存块时,将从这个大的内存块上分割出相匹配的内存块,然后把分割出来的空闲块还给堆管理系统。每个内存块都包含一个管理用的数据头,通过这个头把使用块与空闲块用双向链表的方式链接起来,如下图所示:

    magic:变数(或称为幻数),它会被初始化成0x1ea0(即英文单词heap),用于标记这个内存块是一个内存管理用的内存数据块,实质也是一个内存保护字;used:指示出当前内存块是否已经分配。

    如下图所示为内存分配情况,空闲链表指针lfree初始指向32字节的内存块。当用户线程要再分配一个 64 字节的内存块时,但此 lfree 指针指向的内存块只有 32 字节并不能满足要求,内存管理器会继续寻找下一内存块,当找到再下一块内存块,128 字节时,它满足分配的要求。因为这个内存块比较大,分配器将把此内存块进行拆分,余下的内存块(52 字节)继续留在 lfree 链表中,如下图分配 64 字节后的链表结构所示。

    在每次分配内存块前,都会留出 12 字节数据头用于 magic、used 信息及链表节点使用。

    slab管理算法

    RT-Thread的slab分配器是在 DragonFly BSD 创始人 Matthew Dillon 实现的 slab 分配器基础上,针对嵌入式系统优化的内存分配算法。 RT-Thread的slab分配器实现主要是去掉了其中的对象构造及析构过程,只保留了纯粹的缓冲型的内存池算法。 slab分配器会根据对象的大小分成多个区(zone),也可以看成每类对象有一个内存池,如下图所示:

    一个zone的大小在32K到128K字节之间,分配器会在堆初始化时根据堆的大小自动调整。

    系统中的zone最多包括72中对象,一次最大能够分配16K的内存空间,如果超出了16K那么直接从页分配器中分配。

    每个zone上分配的内存块大小是固定的,能够分配相同大小内存块的zone会链接在一个链表中,而72种对象的zone链表则放在一个数组中统一管理。

    内存分配器主要进行两种操作:内存分配和内存释放。

    memheap管理算法

    memheap管理算法适用于系统含有多个地址可不连续的内存堆。使用memheap内存管理可以简化系统存在多个内存堆时的使用:当系统中存在多个内存堆的时候,用户只需要在系统初始化时将多个所需的memheap初始化,并开启memheap功能就可以方便的把多个memheap黏合起来用于系统的heap分配。
    工作机制

    如下图所示:

    首先将多块内存加入memheap_item链表进行黏合。当分配内存块时,会先从默认内存堆去分配内存,当分配不到时会查找memheap_item链表,尝试从其他的内存堆上分配内存块。

    内存堆配置和初始化

    在使用内存堆时:

    void rt_system_heap_init(void* begin_addr, void* end_addr);

    在使用memheap堆内存时:

    rt_err_t rt_memheap_init(struct rt_memheap *memheap, const char *name, void *start_addr, rt_uint32_t size)

    内存堆的管理方式

    对内存堆的操作如下图所示,包含:初始化、申请内存块、释放内存,所有使用完成后的动态内存都应该被释放,以供其他程序的申请使用。

    内存池

    内存堆管理器可以分配任意大小的内存块,但存在明显缺点:

    分配效率不高,在每次分配时,都要在空闲内存块查找;容易产生内存碎片。 为了提高内存分配的效率,并且避免内存碎片,提供了**“内存池”**的内存管理方法。

    特点

    用于分配大量大小相同的小内存块,可以极大的加快内存分配与释放的速度,且能尽量避免内存碎片化;支持线程挂起功能,当内存池中无空闲内存块时,申请线程会被挂起,直到内存池中有新的可用内存块,再将挂起的申请线程唤醒。内存池的线程挂起功能非常适合需要通过内存资源进行同步的场景,例如播放音乐时,播放器线程会对音乐文件进行解码,然后发送到声卡驱动,从而驱动硬件播放音乐,如下图所示。

    内存池工作机制

    内存池控制块

    内存池控制块是操作系统用于管理内存池的一个数据结构,它会存放内存池的一些信息,例如内存池数据区域开始地址,内存块大小和内存块列表等,也包含内存块与内存块之间连接用的链表结构,因内存块不可用而挂起的线程等待事件集合等。

    struct rt_mempool { struct rt_object parent; void *start_address; /* 内存池数据区域开始地址 */ rt_size_t size; /* 内存池数据区域大小 */ rt_size_t block_size; /* 内存块大小 */ rt_uint8_t *block_list; /* 内存块列表 */ /* 内存池数据区域中能够容纳的最大内存块数 */ rt_size_t block_total_count; /* 内存池中空闲的内存块数 */ rt_size_t block_free_count; /* 因为内存块不可用而挂起的线程列表 */ rt_list_t suspend_thread; /* 因为内存块不可用而挂起的线程数 */ rt_size_t suspend_thread_count; }; typedef struct rt_mempool* rt_mp_t;
    内存块分配机制

    * 内存池在创建时先向系统申请一大块内存,然后分成同样大小的多个小内存块,小内存块直接通过链表链接起来(此链表也成为空闲链表); * 每次分配时,从空闲链表中取出链头上第一个内存块,提供给申请者; * 下图所示,物理内存中允许存在多个大小不同的内存池,每一个内存池又由多个空闲内存块组成,内核用它们来进行内存管理。

    内核负责给内存池分配内存池控制块,它同时也接收用户线程的分配内存块申请,当获得这些信息后,内核就可以从内存池中为内存池分配内存。内存池一旦初始化完成,内部的内存块大小将不能再做调整。

    内存池的管理方式

    内存池的相关接口如下图所示,对内存池的操作包含:创建 / 初始化内存池、申请内存块、释放内存块、删除 / 脱离内存池,但不是所有的内存池都会被删除,这与设计者的需求相关,但是使用完的内存块都应该被释放。

    参考链接

    RT-Thread 内存管理

    Processed: 0.011, SQL: 9