RT-Thread05内存管理
在计算系统中,变量、中间数据⼀般存放在系统存储空间中,只有在实际使⽤时才将它们从存储空间调⼊到中央处理器内部进⾏运算。通常存储空间可以分为两种:内部存储空间和外部存储空间。内部存储空间访问速度⽐较快,能够按照变量地址随机地访问,也就是我们通常所说的RAM(随机存储器),或电脑的内存;⽽外部存储空间内所保存的内容相对来说⽐较固定,即使掉电后数据也不会丢失,这就是通常所讲的ROM(只读存储器),也可以把它理解为电脑的硬盘。在这⼀章中我们主要讨论内部存储空间的管理。
由于实时系统中对时间要求的严格性,内存分配往往要⽐通⽤操作系统要求苛刻得多。
⾸先,分配内存的时间必须是确定的。⼀般内存管理算法是根据需要存储的数据的长度在内存中去寻⼀个与这段数据相适应的空闲内存块,然后将数据存储在⾥⾯。⽽寻这样⼀个空闲内存块所耗费的时间是不确定的,因此对于实时系统来说,这就是不可接受的,实时系统必须要保证内存块的分配过程在可预测的确定时间内完成,否则实时任务对外部事件的响应也将变得不可确定。
其次,随着内存不断被分配和释放,整个内存区域会产⽣越来越多的碎⽚(因为在使⽤过程中,申请了⼀些内存,其中⼀些释放了,导致内存空间中存在⼀些⼩的内存块,它们地址不连续,不能够作为⼀整块的⼤内存分配出去),系统中还有⾜够的空闲内存,但因为它们地址并⾮连续,不能组成⼀块连续的完整内存块,会使得程序不能申请到⼤的内存。对于通⽤系统⽽⾔,这种不恰当的内存分配算法可以通过重新启
动系统来解决(每个⽉或者数个⽉进⾏⼀次),但是对于那些需要常年不间断地⼯作于野外的嵌⼊式系统来说,就变得让⼈⽆法接受了。
最后,嵌⼊式系统的资源环境也是不尽相同,有些系统的资源⽐较紧张,只有数⼗KB的内存可供分配,⽽有些系统则存在数MB的内存,如何为这些不同的系统,选择适合它们的⾼效率的内存分配算法,就将变得复杂化。
RT-Thread操作系统在内存管理上,根据上层应⽤及系统资源的不同,有针对性的提供了不同的内存分配管理算法。总体上可分为两类:静态分区内存管理与动态内存管理,⽽动态内存管理⼜根据可⽤内存的多少划分为两种情况:⼀种是针对⼩内存块的分配管理(⼩内存管理算法),另⼀种是针对⼤内存块的分配管理(SLAB管理算法)。
静态内存池管理
静态内存池⼯作原理
内存池管理结构⽰意图是内存池管理结构⽰意图。内存池(Memory Pool)是⼀种⽤于分配⼤量⼤⼩相同的⼩对象的技术。它可以极⼤加快内存分配/释放的速度。
内存池在创建时先向系统申请⼀⼤块内存,然后分成同样⼤⼩的多个⼩内存块,⼩内存块直接通过链表
连接起来(此链表也称为空闲链表)。每次分配的时候,从空闲链表中取出链头上第⼀个内存块,提供给申请者。从图中可以看到,物理内存中允许存在多个⼤⼩不同的内存池,每⼀个内存池⼜由多个空闲内存块组成,内核⽤它们来进⾏内存管理。当⼀个内存池对象被创建时,内存池对象就被分配给了⼀个内存池控制块,内存控制块的参数包括内存池名,内存缓冲区,内存块⼤⼩,块数以及⼀个等待线程队列。
内核负责给内存池分配内存池对象控制块,它同时也接收⽤户线程的分配内存块申请,当获得这些信息后,内核就可以从内存池中为内存池分配内存。内存池⼀旦初始化完成,内部的内存块⼤⼩将不能再做调整。
静态内存池控制块
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;
每⼀个内存池对象由上述结构组成,其中suspend_thread形成了⼀个申请线程等待列表,即当内存池中⽆可⽤内存块,并且申请线程允许等待时,申请线程将挂起在suspend_thread链表上。
静态内存池接⼝
创建内存池
创建内存池操作将会创建⼀个内存池对象并从堆上分配⼀个内存池。创建内存池是从对应内存池中分配和释放内存块的先决条件,创建内存池后,线程便可以从内存池中执⾏申请、释放等操作。创建内存池使⽤下⾯的函数接⼝,该函数返回⼀个已创建的内存池对象。
rt_mp_t rt_mp_create(const char* name, rt_size_t block_count, rt_size_t block_size);
使⽤该函数接⼝可以创建⼀个与需求的内存块⼤⼩、数⽬相匹配的内存池,前提当然是在系统资源允许的情况下(最主要的是动态堆内存资源)才能创建成功。创建内存池时,需要给内存池指定⼀个名称。然后内核从系统中申请⼀个内存池对象,然后从内存堆中分配⼀块由块数⽬和块⼤⼩计算得来的内存缓冲区,接着初始化内存池对象,并将申请成功的内存缓冲区组织成可⽤于分配的空闲块链表。
删除内存池
删除内存池将删除内存池对象并释放申请的内存。使⽤下⾯的函数接⼝:
rt_err_t rt_mp_delete(rt_mp_t mp);
删除内存池时,会⾸先唤醒等待在该内存池对象上的所有线程(返回-RT_ERROR),然后再释放已从内存堆上分配的内存池数据存放区域,然后删除内存池对象。
初始化内存池
初始化内存池跟创建内存池类似,只是初始化内存池⽤于静态内存管理模式,内存池控制块来源于⽤户在系统中申请的静态对象。另外与创建内存池不同的是,此处内存池对象所使⽤的内存空间是由⽤户指定的⼀个缓冲区空间,⽤户把缓冲区的指针传递给内存池对象控制块,其余的初始化⼯作与创建内存池相同。函数接⼝如下:
rt_err_t rt_mp_init(rt_mp_t mp, const char* name, void *start, rt_size_t size, rt_size_t block size);
初始化内存池时,把需要进⾏初始化的内存池对象传递给内核,同时需要传递的还有内存池⽤到的内存空间,以及内存池管理的内存块数⽬和块⼤⼩,并且给内存池指定⼀个名称。这样,内核就可以对该内存池进⾏初始化,将内存池⽤到的内存空间组织成可⽤于分配的空闲块链表。
脱离内存池
脱离内存池将把内存池对象从内核对象管理器中删除。脱离内存池使⽤下⾯的函数接⼝:
rt_err_t rt_mp_detach(rt_mp_t mp);
使⽤该函数接⼝后,内核先唤醒所有等待在该内存池对象上的线程,然后将内存池对象从内核对象管理器中删除。
分配内存块
void *rt_mp_alloc (rt_mp_t mp, rt_int32_t time);
如果内存池中有可⽤的内存块,则从内存池的空闲块链表上取下⼀个内存块,减少空闲块数⽬并返回这个内存块;如果内存池中已经没有空闲内存块,则判断超时时间设置:若超时时间设置为零,则⽴刻返回空内存块;若等待时间⼤于零,则把当前线程挂起在该内存池对象上,直到内存池中有可⽤的⾃由内存块,或等待时间到达。
释放内存块
任何内存块使⽤完后都必须被释放,否则会造成内存泄露,释放内存块使⽤如下接⼝:
void rt_mp_free (void *block);
使⽤该函数接⼝时,⾸先通过需要被释放的内存块指针计算出该内存块所在的(或所属于的)内存池对象,然后增加内存池对象的可⽤内存块数⽬,并把该被释放的内存块加⼊空闲内存块链表上。接着判断该内存池对象上是否有挂起的线程,如果有,则唤醒挂起线程链表上的⾸线程。
/*
* 程序清单:内存池例程
*
* 这个程序会创建⼀个静态的内存池对象,2 个动态线程。
* 两个线程会试图分别从内存池中获得内存块
*/
#include <rtthread.h>
#include "tc_comm.h"
static rt_uint8_t *ptr[48];
static rt_uint8_t mempool[4096];
static struct rt_mempool mp; /* 静态内存池对象 */
/* 指向线程控制块的指针 */
static rt_thread_t tid1 = RT_NULL;
static rt_thread_t tid2 = RT_NULL;
/* 线程 1 ⼊⼝ */
static void thread1_entry(void* parameter)
{
int i;
char *block;
while(1)
{
for (i = 0; i < 48; i++)
{
/* 申请内存块 */
rt_kprintf("allocate No.%d\n", i);
if (ptr[i] == RT_NULL)
{
ptr[i] = rt_mp_alloc(&mp, RT_WAITING_FOREVER);
}
}
/* 继续申请⼀个内存块,因为已经没有内存块,线程应该被挂起 */
block = rt_mp_alloc(&mp, RT_WAITING_FOREVER);
rt_kprintf("allocate the block mem\n");
/* 释放这个内存块 */
rt_mp_free(block);
block = RT_NULL;
}
}
/* 线程 2 ⼊⼝,线程 2 的优先级⽐线程 1 低,应该线程 1 先获得执⾏。*/
static void thread2_entry(void *parameter)
{
int i;
while(1)
{
rt_kprintf("try to release block\n");
for (i = 0 ; i < 48; i ++)
{
/* 释放所有分配成功的内存块 */
if (ptr[i] != RT_NULL)
{
rt_kprintf("release block %d\n", i);
rt_mp_free(ptr[i]);
ptr[i] = RT_NULL;
}
}
/* 休眠 10 个 OS Tick */
rt_thread_delay(10);
}
}
int mempool_simple_init()
{
int i;
for (i = 0; i < 48; i ++) ptr[i] = RT_NULL;
/* 初始化内存池对象 */
rt_mp_init(&mp, "mp1", &mempool[0], sizeof(mempool), 80); /* 创建线程 1 */
tid1 = rt_thread_create("t1",
thread1_entry, /* 线程⼊⼝是 thread1_entry */
RT_NULL, /* ⼊⼝参数是 RT_NULL */
thread技术THREAD_STACK_SIZE, THREAD_PRIORITY,
THREAD_TIMESLICE);
if (tid1 != RT_NULL)
rt_thread_startup(tid1);
else
tc_stat(TC_STAT_END | TC_STAT_FAILED);
/* 创建线程 2 */
tid2 = rt_thread_create("t2",
thread2_entry, /* 线程⼊⼝是 thread2_entry */
RT_NULL, /* ⼊⼝参数是 RT_NULL */
THREAD_STACK_SIZE, THREAD_PRIORITY + 1,
THREAD_TIMESLICE);
if (tid2 != RT_NULL)
rt_thread_startup(tid2);
else
tc_stat(TC_STAT_END | TC_STAT_FAILED);
return0;
}
#ifdef RT_USING_TC
static void _tc_cleanup()
{
/* 调度器上锁,上锁后,将不再切换到其他线程,仅响应中断 */ rt_enter_critical();
/* 删除线程 */
if (tid1 != RT_NULL && tid1->stat != RT_THREAD_CLOSE) rt_thread_delete(tid1);
if (tid2 != RT_NULL && tid2->stat != RT_THREAD_CLOSE) rt_thread_delete(tid2);
/* 执⾏内存池脱离 */
rt_mp_detach(&mp);
/* 调度器解锁 */
rt_exit_critical();
/
* 设置 TestCase 状态 */
tc_done(TC_STAT_PASSED);
}
int _tc_mempool_simple()
{
/* 设置 TestCase 清理回调函数 */
tc_cleanup(_tc_cleanup);
mempool_simple_init();
/* 返回 TestCase 运⾏的最长时间 */
return100;
}
/
* 输出函数命令到 finsh shell 中 */
FINSH_FUNCTION_EXPORT(_tc_mempool_simple, a memory pool
example);
#else
/* ⽤户应⽤⼊⼝ */
int rt_application_init()
{
mempool_simple_init();
return0;
}
#endif
动态内存管理
动态内存管理是⼀个真实的堆(Heap)内存管理模块,可以在当前资源满⾜的情况下,根据⽤户的需求分配任意⼤⼩的内存块。⽽当⽤户不需要再使⽤这些内存块时,⼜可以释放回堆中供其他应⽤分配使⽤。RT-Thread系统为了满⾜不同的需求,提供了两套不同的动态内存管理算法,分别是⼩堆内存管理算法和SLA B内存管理算法。
⼩堆内存管理模块主要针对系统资源⽐较少,⼀般⽤于⼩于2M内存空间的系统;⽽SLAB内存管理模块则主要是在系统资源⽐较丰富时,提供了⼀种近似多内存池管理算法的快速算法。两种内存管理模块在系统运⾏时只能选择其中之⼀或者完全不使⽤动态堆内存管理器。这两种管理模块提供的API接⼝完全相同。
警告:因为动态内存管理器要满⾜多线程情况下的安全分配,会考虑多线程间的互斥问题,所以请不要在中断服务例程中分配或释放动态内存块。因为它可能会引起当前上下⽂被挂起等待。
⼩内存管理模块
⼩内存管理算法是⼀个简单的内存分配算法。初始时,它是⼀块⼤的内存。当需要分配内存块时,将从这个⼤的内存块上分割出相匹配的内存块,然后把分割出来的空闲内存块还回给堆管理系统中。每个内
存块都包含⼀个管理⽤的数据头,通过这个头把使⽤块与空闲块⽤双向链表的⽅式链接起来,如内存块链表图所⽰:
每个内存块(不管是已分配的内存块还是空闲的内存块)都包含⼀个数据头,其中包括:
magic – 变数(或称为幻数),它会被初始化成0x1ea0(即英⽂单词heap),⽤于标记这个内存块是⼀个内存管理⽤的内存数据块;
used - 指⽰出当前内存块是否已经分配。
magic变数不仅仅⽤于标识这个数据块是⼀个内存管理⽤的内存数据块,实质也是⼀个内存保护字:如果这个区域被改写,那么也就意味着这块内存块被⾮法改写(正常情况下只有内存管理器才会去碰这块内存)。
内存管理的在表现主要体现在内存的分配与释放上,⼩型内存管理算法可以⽤以下例⼦体现出来。
版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系QQ:729038198,我们将在24小时内删除。
发表评论