RT-Thread--内存管理
内存管理的功能特点
RT-Thread 操作系统在内存管理上,根据上层应用及系统资源的不同,有针对性地提供了不同的内存分配管理算法。总体上可分为两类:内存堆管理与内存池管理,而内存堆管理又根据具体内存设备划分为三种情况:
第一种是针对小内存块的分配管理(小内存管理算法);
第二种是针对大内存块的分配管理(slab 管理算法);
第三种是针对多内存堆的分配情况(memheap 管理算法)
内存堆管理
- 内存堆管理用于管理一段连续的内存空间,如下图所示,RT-Thread 将 “ZI 段结尾处” 到内存尾部的空间用作内存堆。
- 小内存管理算法主要针对系统资源比较少,一般用于小于 2MB 内存空间的系统;
- slab 内存管理算法则主要是在系统资源比较丰富时,提供了一种近似多内存池管理算法的快速算法;
- RT-Thread 还有一种针对多内存堆的管理算法,即 memheap 管理算法。memheap 方法适用于系统存在多个内存堆的情况,它可以将多个内存 “粘贴” 在一起,形成一个大的内存堆;
- 注意:因为内存堆管理器要满足多线程情况下的安全分配,会考虑多线程间的互斥问题,所以不要在中断服务例程中分配或释放动态内存块,因为它可能会引起当前上下文被挂起等待;
小内存管理里算法
- 当需要分配内存块时,将从这个大的内存块上分割出相匹配的内存块,然后把分割出来的空闲内存块还回给堆管理系统中。每个内存块都包含一个管理用的数据头,通过这个头把使用块与空闲块用双向链表的方式链接起来,如图所示:
- 每个内存块都包含一个数据头:magic 和 used
- magic:用来标记这个内存块是一个内存管理的内存数据块,也是一个内存保护字,如果这个区域被改写,那么这个内存块就被非法改写了;
- used :用来表示当前内存块是否被分配;
struct heap_mem
{
/* magic and used flag */
rt_uint16_t magic;
rt_uint16_t used; rt_size_t next, prev; #ifdef RT_USING_MEMTRACE
rt_uint8_t thread[]; /* thread name */
#endif
};
- 内存分配过程:
- 设定:空闲链表指针Ifree初始指向32字节的内存块,当用户线程需要分配一个64字节的内存块时,Ifree指针指向的内存块不能满足要求,内存管理器就会继续寻找下一个内存块,当找到时(128字节),就会进行内存分配,如果内存块比较大,分配器就会把内存块进行拆分,余下的内存(52字节)继续留在Ifree链表中,如下图所示;
- 注意:在内次分配内存块前,都会留出12字节用于magic、used信息及节点使用,返回给应用的地址实际上是这块内存块 12 字节以后的地址,前面的 12 字节数据头是用户永远不应该碰的部分(注:12 字节数据头长度会与系统对齐差异而有所不同)。
- 释放:释放内存时,分配器会查看前后相邻的内存是否是空闲,如果空闲,就回合并成一个大的空闲内存块;
slab管理算法
- RT-Thread 的 slab 分配器的实现是建立在 slab分配器上的,针对嵌入式仙童优化的内存分配算法。(slab是linux系统中的一种内存分配机制)
- RT-Thread 的 slab 分配器实现主要是去掉了其中的对象构造及析构过程,只保留了纯粹的缓冲型的内存池算法。slab 分配器会根据对象的大小分成多个区(zone),也可以看成每类对象有一个内存池,如下图所示:
- 一个 zone 的大小在 32K 到 128K 字节之间,分配器会在堆初始化时根据堆的大小自动调整。系统中的 zone 最多包括 72 种对象,一次最大能够分配 16K 的内存空间,如果超出了 16K 那么直接从页分配器中分配。每个 zone 上分配的内存块大小是固定的,能够分配相同大小内存块的 zone 会链接在一个链表中,而 72 种对象的 zone 链表则放在一个数组(zone_array[])中统一管理。
- 内存分配:
- 假设分配一个 32 字节的内存,slab 内存分配器会先按照 32 字节的值,从 zone array 链表表头数组中找到相应的 zone 链表。如果这个链表是空的,则向页分配器分配一个新的 zone,然后从 zone 中返回第一个空闲内存块。如果链表非空,则这个 zone 链表中的第一个 zone 节点必然有空闲块存在(否则它就不应该放在这个链表中),那么就取相应的空闲块。如果分配完成后,zone 中所有空闲内存块都使用完毕,那么分配器需要把这个 zone 节点从链表中删除。
- 内存释放:分配器需要找到内存块所在的 zone 节点,然后把内存块链接到 zone 的空闲内存块链表中。如果此时 zone 的空闲链表指示出 zone 的所有内存块都已经释放,即 zone 是完全空闲的,那么当 zone 链表中全空闲 zone 达到一定数目后,系统就会把这个全空闲的 zone 释放到页面分配器中去。
memheap管理算法
- memheap 管理算法适用于系统含有多个地址可不连续的内存堆。使用 memheap 内存管理可以简化系统存在多个内存堆时的使用:当系统中存在多个内存堆的时候,用户只需要在系统初始化时将多个所需的 memheap 初始化,并开启 memheap 功能就可以很方便地把多个 memheap(地址可不连续)粘合起来用于系统的 heap 分配;
- 在开启 memheap 之后原来的 heap 功能将被关闭,两者只可以通过打开或关闭 RT_USING_MEMHEAP_AS_HEAP 来选择其一;
- memheap 工作机制如下图所示,首先将多块内存加入 memheap_item 链表进行粘合。当分配内存块时,会先从默认内存堆去分配内存,当分配不到时会查找 memheap_item 链表,尝试从其他的内存堆上分配内存块。应用程序不用关心当前分配的内存块位于哪个内存堆上,就像是在操作一个内存堆。
内存堆配置和初始化
- 在使用内存堆时,必须要在系统初始化的时候进行堆的初始化;
/**
* @ingroup SystemInit
*
* This function will initialize system heap memory.
*
* @param begin_addr the beginning address of system heap memory.
* @param end_addr the end address of system heap memory.
*/
void rt_system_heap_init(void *begin_addr, void *end_addr)- 在使用 memheap 堆内存时,必须要在系统初始化的时候进行堆内存的初始化;
/*
* The initialized memory pool will be:
* +-----------------------------------+--------------------------+
* | whole freed memory block | Used Memory Block Tailer |
* +-----------------------------------+--------------------------+
*
* block_list --> whole freed memory block
*
* The length of Used Memory Block Tailer is 0,
* which is prevents block merging across list
*/
rt_err_t rt_memheap_init(struct rt_memheap *memheap,
const char *name,
void *start_addr,
rt_uint32_t size)
内存堆的管理方式
- 对内存堆的操作包含:初始化、申请内存块、释放内存,所有使用完成后的动态内存都应该被释放,以供其他程序的申请使用;
内存分配和释放
- 从内存堆上分配用户指定大小的内存块,t_malloc 函数会从系统堆空间中找到合适大小的内存块,然后把内存块可用地址返回给用户;
/**
* Allocate a block of memory with a minimum of 'size' bytes.
*
* @param size is the minimum size of the requested block in bytes.
*
* @return pointer to allocated memory or NULL if no free memory was found.
*/
void *rt_malloc(rt_size_t size)应用程序使用完从内存分配器中申请的内存后,必须及时释放,否则会造成内存泄漏
/**
* This function will release the previously allocated memory block by
* rt_malloc. The released memory block is taken back to system heap.
*
* @param rmem the address of memory which will be released
*/
void rt_free(void *rmem)
重分配内存块
- 在已分配内存块的基础上重新分配内存块的大小(增加或缩小)
/**
* This function will change the previously allocated memory block.
*
* @param rmem pointer to memory allocated by rt_malloc
* @param newsize the required new size
*
* @return the changed memory block address
*/
void *rt_realloc(void *rmem, rt_size_t newsize)
分配多内存块
- 从内存堆中分配连续内存地址的多个内存块;
/**
* This function will contiguously allocate enough space for count objects
* that are size bytes of memory each and returns a pointer to the allocated
* memory.
*
* The allocated memory is filled with bytes of value zero.
*
* @param count number of objects to allocate
* @param size size of the objects to allocate
*
* @return pointer to allocated memory / NULL pointer if there is an error
*/
void *rt_calloc(rt_size_t count, rt_size_t size)
设置内存钩子函数
- 在分配内存块过程中,用户可设置一个钩子函数。设置的钩子函数会在内存分配完成后进行回调。回调时,会把分配到的内存块地址和大小做为入口参数传递进去;
/**
* This function will set a hook function, which will be invoked when a memory
* block is allocated from heap memory.
*
* @param hook the hook function
*/
void rt_malloc_sethook(void (*hook)(void *ptr, rt_size_t size))
{
rt_malloc_hook = hook;
}在释放内存时,用户可设置一个钩子函数;
/**
* This function will set a hook function, which will be invoked when a memory
* block is released to heap memory.
*
* @param hook the hook function
*/
void rt_free_sethook(void (*hook)(void *ptr))
{
rt_free_hook = hook;
}
内存堆管理应用示例
- 创建一个动态的线程,这个线程会动态申请内存并释放,每次申请更大的内存,当申请不到的时候就结束;
#include <rtthread.h> #define THREAD_PRIORITY 25
#define THREAD_STACK_SIZE 512
#define THREAD_TIMESLICE 5 /* 线程入口 */
void thread1_entry(void *parameter)
{
int i;
char *ptr = RT_NULL; /* 内存块的指针 */ for (i = ; ; i++)
{
/* 每次分配 (1 << i) 大小字节数的内存空间 */
ptr = rt_malloc( << i); /* 如果分配成功 */
if (ptr != RT_NULL)
{
rt_kprintf("get memory :%d byte\n", ( << i));
/* 释放内存块 */
rt_free(ptr);
rt_kprintf("free memory :%d byte\n", ( << i));
ptr = RT_NULL;
}
else
{
rt_kprintf("try to get %d byte memory failed!\n", ( << i));
return;
}
}
} int dynmem_sample(void)
{
rt_thread_t tid = RT_NULL; /* 创建线程 1 */
tid = rt_thread_create("thread1",
thread1_entry, RT_NULL,
THREAD_STACK_SIZE,
THREAD_PRIORITY,
THREAD_TIMESLICE);
if (tid != RT_NULL)
rt_thread_startup(tid); return ;
}
/* 导出到 msh 命令列表中 */
MSH_CMD_EXPORT(dynmem_sample, dynmem sample);运行结果:
\ | /
- RT - Thread Operating System
/ | \ 3.1. build Aug
- Copyright by rt-thread team
msh >dynmem_sample
msh >get memory : byte
free memory : byte
get memory : byte
free memory : byte
…
get memory : byte
free memory : byte
get memory : byte
free memory : byte
try to get byte memory failed!
内存池
- 内存堆管理器可以分配任意大小的内存块,非常灵活和方便,但是:分配效率不高,在每次分配时,都要空闲内存块查找,并且容易产生内存碎片;
- 内存池是一种内存分配方式,用于分配大量大小相同的小内存块,它可以极大地加快内存分配与释放的速度,且能尽量避免内存碎片化;
- RT-Thread 的内存池支持线程挂起功能,当内存池中无空闲内存块时,申请线程会被挂起,直到内存池中有新的可用内存块,再将挂起的申请线程唤醒;
- 内存池的线程挂起功能非常适合需要通过内存资源进行同步的场景,例如播放音乐时,播放器线程会对音乐文件进行解码,然后发送到声卡驱动,从而驱动硬件播放音乐。
- 播放器线程需要解码数据时,就会向内存池请求内存块,如果内存块已经用完,线程将被挂起,否则它将获得内存块以放置解码的数据;
- 而后播放器线程把包含解码数据的内存块写入到声卡抽象设备中 (线程会立刻返回,继续解码出更多的数据);
- 当声卡设备写入完成后,将调用播放器线程设置的回调函数,释放写入的内存块,如果在此之前,播放器线程因为把内存池里的内存块都用完而被挂起的话,那么这是它将被将唤醒,并继续进行解码。
内存池工作机制
内存池控制块
- 内存池控制块是操作系统用于管理内存池的一个数据结构,它会存放内存池的一些信息,例如内存池数据区域开始地址,内存块大小和内存块列表等,也包含内存块与内存块之间连接用的链表结构,因内存块不可用而挂起的线程等待事件集合等。
在 RT-Thread 实时操作系统中,内存池控制块由结构体 struct rt_mempool 表示
struct rt_mempool
{
struct rt_object parent; /**< inherit from rt_object */ void *start_address; /**< memory pool start */
rt_size_t size; /**< size of memory pool */ rt_size_t block_size; /**< size of memory blocks */
rt_uint8_t *block_list; /**< memory blocks list */ rt_size_t block_total_count; /**< numbers of memory block */
rt_size_t block_free_count; /**< numbers of free memory block */ rt_list_t suspend_thread; /**< threads pended on this resource */
rt_size_t suspend_thread_count; /**< numbers of thread pended on this resource */
};
typedef struct rt_mempool *rt_mp_t;内存块分配机制
- 内存池在创建时先向系统申请一大块内存,然后分成同样大小的多个小内存块,小内存块直接通过链表连接起来(此链表也称为空闲链表)。每次分配的时候,从空闲链表中取出链头上第一个内存块,提供给申请者。从下图中可以看到,物理内存中允许存在多个大小不同的内存池,每一个内存池又由多个空闲内存块组成,内核用它们来进行内存管理。当一个内存池对象被创建时,内存池对象就被分配给了一个内存池控制块,内存控制块的参数包括内存池名,内存缓冲区,内存块大小,块数以及一个等待线程队列。
- 内存池一旦初始化完成,内部的内存块大小将不能再做调整;
- 每一个内存池对象由上述结构组成,其中 suspend_thread 形成了一个申请线程等待列表,即当内存池中无可用内存块,并且申请线程允许等待时,申请线程将挂起在 suspend_thread 链表上;
内存池的管理方式
- 内存池控制块是一个结构体,其中含有内存池相关的重要参数,在内存池各种状态间起到纽带的作用,对内存池的操作包含:创建 / 初始化内存池、申请内存块、释放内存块、删除 / 脱离内存池,但不是所有的内存池都会被删除,这与设计者的需求相关,但是使用完的内存块都应该被释放。
创建和删除内存池
- 创建内存池操作将会创建一个内存池对象并从堆上分配一个内存池。创建内存池是从对应内存池中分配和释放内存块的先决条件,创建内存池后,线程便可以从内存池中执行申请、释放等操作;
/**
* This function will create a mempool object and allocate the memory pool from
* heap.
*
* @param name the name of memory pool
* @param block_count the count of blocks in memory pool
* @param block_size the size for each block
*
* @return the created mempool object
*/
rt_mp_t rt_mp_create(const char *name,
rt_size_t block_count,
rt_size_t block_size)删除内存池将删除内存池对象并释放申请的内存
/**
* This function will delete a memory pool and release the object memory.
*
* @param mp the memory pool object
*
* @return RT_EOK
*/
rt_err_t rt_mp_delete(rt_mp_t mp)初始化和脱离内存池
- 初始化内存池跟创建内存池类似,只是初始化内存池用于静态内存管理模式,内存池控制块来源于用户在系统中申请的静态对象。另外与创建内存池不同的是,此处内存池对象所使用的内存空间是由用户指定的一个缓冲区空间,用户把缓冲区的指针传递给内存池控制块,存池块个数 = size / (block_size + 4 链表指针大小),计算结果取整数。
/**
* This function will initialize a memory pool object, normally which is used
* for static object.
*
* @param mp the memory pool object
* @param name the name of memory pool
* @param start the star address of memory pool
* @param size the total size of memory pool
* @param block_size the size for each block
*
* @return RT_EOK
*/
rt_err_t rt_mp_init(struct rt_mempool *mp,
const char *name,
void *start,
rt_size_t size,
rt_size_t block_size)脱离内存池将把内存池对象从内核对象管理器中脱离
/**
* This function will detach a memory pool from system object management.
*
* @param mp the memory pool object
*
* @return RT_EOK
*/
rt_err_t rt_mp_detach(struct rt_mempool *mp)分配和释放内存块
- 从指定的内存池中分配一个内存块;
- 如果内存池中有可用的内存块,则从内存池的空闲块链表上取下一个内存块,减少空闲块数目并返回这个内存块;如果内存池中已经没有空闲内存块,则判断超时时间设置:若超时时间设置为零,则立刻返回空内存块;若等待时间大于零,则把当前线程挂起在该内存池对象上,直到内存池中有可用的自由内存块,或等待时间到达;
/**
* This function will allocate a block from memory pool
*
* @param mp the memory pool object
* @param time the waiting time
*
* @return the allocated memory block or RT_NULL on allocated failed
*/
void *rt_mp_alloc(rt_mp_t mp, rt_int32_t time)任何内存块使用完后都必须被释放,否则会造成内存泄露;
/**
* This function will release a memory block
*
* @param block the address of memory block to be released
*/
void rt_mp_free(void *block)
内存池应用示例
- 创建一个静态的内存池对象,2 个动态线程。一个线程会试图从内存池中获得内存块,另一个线程释放内存块内存块;
#include <rtthread.h> static rt_uint8_t *ptr[];
static rt_uint8_t mempool[];
static struct rt_mempool mp; #define THREAD_PRIORITY 25
#define THREAD_STACK_SIZE 512
#define THREAD_TIMESLICE 5 /* 指向线程控制块的指针 */
static rt_thread_t tid1 = RT_NULL;
static rt_thread_t tid2 = RT_NULL; /* 线程 1 入口 */
static void thread1_mp_alloc(void *parameter)
{
int i;
for (i = ; i < ; i++)
{
if (ptr[i] == RT_NULL)
{
/* 试图申请内存块 50 次,当申请不到内存块时,
线程 1 挂起,转至线程 2 运行 */
ptr[i] = rt_mp_alloc(&mp, RT_WAITING_FOREVER);
if (ptr[i] != RT_NULL)
rt_kprintf("allocate No.%d\n", i);
}
}
} /* 线程 2 入口,线程 2 的优先级比线程 1 低,应该线程 1 先获得执行。*/
static void thread2_mp_release(void *parameter)
{
int i; rt_kprintf("thread2 try to release block\n");
for (i = ; i < ; i++)
{
/* 释放所有分配成功的内存块 */
if (ptr[i] != RT_NULL)
{
rt_kprintf("release block %d\n", i);
rt_mp_free(ptr[i]);
ptr[i] = RT_NULL;
}
}
} int mempool_sample(void)
{
int i;
for (i = ; i < ; i ++) ptr[i] = RT_NULL; /* 初始化内存池对象 */
rt_mp_init(&mp, "mp1", &mempool[], sizeof(mempool), ); /* 创建线程 1:申请内存池 */
tid1 = rt_thread_create("thread1", thread1_mp_alloc, RT_NULL,
THREAD_STACK_SIZE,
THREAD_PRIORITY, THREAD_TIMESLICE);
if (tid1 != RT_NULL)
rt_thread_startup(tid1); /* 创建线程 2:释放内存池 */
tid2 = rt_thread_create("thread2", thread2_mp_release, RT_NULL,
THREAD_STACK_SIZE,
THREAD_PRIORITY + , THREAD_TIMESLICE);
if (tid2 != RT_NULL)
rt_thread_startup(tid2); return ;
} /* 导出到 msh 命令列表中 */
MSH_CMD_EXPORT(mempool_sample, mempool sample);运行结果:
\ | /
- RT - Thread Operating System
/ | \ 3.1. build Aug
- Copyright by rt-thread team
msh >mempool_sample
msh >allocate No.
allocate No.
allocate No.
allocate No.
allocate No.
…
allocate No.
allocate No.
thread2 try to release block
release block
allocate No.
release block
allocate No.
release block
release block
release block
release block
…
release block
release block
release block
参考
- 《RT-Thread 编程指南》
RT-Thread--内存管理的更多相关文章
- Java自动内存管理机制学习(一):Java内存区域与内存溢出异常
备注:本文引用自<深入理解Java虚拟机第二版> 2.1 运行时数据区域 Java虚拟机在执行Java程序的过程中把它所管理的内存划分为若干个不同的数据区域.这些区域都有各自的用途,以及创 ...
- <Linux内核源码>内存管理模型
题外语:本人对linux内核的了解尚浅,如果有差池欢迎指正,也欢迎提问交流! 首先要理解一下每一个进程是如何维护自己独立的寻址空间的,我的电脑里呢是8G内存空间.了解过的朋友应该都知道这是虚拟内存技术 ...
- JVM内存管理:深入Java内存区域与OOM
Java与C++之间有一堵由内存动态分配和垃圾收集技术所围成的高墙,墙外面的人想进去,墙里面的人却想出来. 概述: 对于从事C.C++程序开发的开发人员来说,在内存管理领域,他们即是拥有最高权力的皇帝 ...
- Unity游戏开发中的内存管理_资料
内存是手游的硬伤——Unity游戏Mono内存管理及泄漏http://wetest.qq.com/lab/view/135.html 深入浅出再谈Unity内存泄漏http://wetest.qq.c ...
- jvm--3.内存管理
5.JVM内存管理 JAVA虚拟机在执行java程序的过程中,会把它管理的内存分成若干个不同的数据区域. ----------------------------------------------- ...
- Linux堆内存管理深入分析(上)
Linux堆内存管理深入分析(上半部) 作者:走位@阿里聚安全 0 前言 近年来,漏洞挖掘越来越火,各种漏洞挖掘.利用的分析文章层出不穷.从大方向来看,主要有基于栈溢出的漏洞利用和基于堆溢出的漏洞 ...
- iOS开发系列—Objective-C之内存管理
概述 我们知道在程序运行过程中要创建大量的对象,和其他高级语言类似,在ObjC中对象时存储在堆中的,系统并不会自动释放堆中的内存(注意基本类型是由系统自己管理的,放在栈上).如果一个对象创建并使用后没 ...
- Android 内存管理 &Memory Leak & OOM 分析
转载博客:http://blog.csdn.net/vshuang/article/details/39647167 1.Android 进程管理&内存 Android主要应用在嵌入式设备当中 ...
- 【转】Java内存管理:深入Java内存区域
转自:http://www.cnblogs.com/gw811/archive/2012/10/18/2730117.html 本文引用自:深入理解Java虚拟机的第2章内容 Java与C++之间有一 ...
- DPDK内存管理(1)
1 前言 DPDK将利用hugepage预留的物理内存统一的组织管理起来,然后以库的方式对外提供使用的接口.下图展示了DPDK中内存有关的模块的相互关系. rte_eal 是统一 ...
随机推荐
- vue项目开发优化
1 按需引入ui组件 比如elementUI,不要直接在main.js中全局引入所有的组件,可以根据elementui的说明文件,按需引入 项目中的组件 2 异步引入路由组件 使用 { path:'/ ...
- docker中的fastdfs
准备环节)(本文遗漏当初出现的一个问题由于是docker装的fastdfs所以tracker storage client,nginx,nginx module都在同一个容器中只需要修改配置 特别注意 ...
- Java学习,从入门到放弃(二)Linux配置mvn
其实网上的教程很多,随便拿一个,比如:https://www.cnblogs.com/chuijingjing/p/10430649.html 但在实践过程中,发现可能需要将JAVA_HOME也加到 ...
- BOOT目录磁盘占用满处理
背景:Ubuntu:16.04 查看已安装启动镜像 dpkg --get-selections |grep linux-image 这里会列出目前已经安装的启动镜像,一般分两种,一种状态为“insta ...
- Arm 环境上面libgdiplus的简单安装配置
1. 下载libgdiplus的包 wget http://download.mono-project.com/sources/libgdiplus/libgdiplus0-6.0.4.tar.gz ...
- 关于tomcat9的startup.bat闪退问题&乱码
1.打开apache-tomcat-9.0.22的bin目录 选择图中文件 记事本打开 2.更改文件 setclasspath.bat .0_172\jre 3.保存 startup ----- ...
- IDEA创建web项目详细过程
相关软件:Intellij Idea2017.jdk16.tomcat Intellij Idea直接安装(可根据需要选择自己设置的安装目录),jdk使用1.6/1.7/1.8都可以,主要是配置好系统 ...
- Python18之函数定义及调用,注释
一.函数定义 def 函数名(形参1,形参2...): 函数体 return 返回值 (可以返回任何东西,一个值,一个变量,或是另一个函数的返回值,如果函数没有返回值,可以省略retu ...
- closed channel
func Test_chanel(t *testing.T) { c := make(chan int, 1) go func() { time.Sleep(time.Second * 3) clos ...
- Spring AOP日志实现(四)--Bean的设计
日志Bean的设计: 类名及方法名: