由于RTT是实时操作系统,其对时间有着严格的要求,内存分配的时间往往要比通用操作系统要更苛刻。
首先,分配内存的时间必须是确定的。一般内存管理算法是根据需要存储的数据的长度在内存中去寻找一个与这段数据相适应的空闲内存块,然后将数据存储在里面。而寻找这样一个空闲内存块所耗费的时间是不确定的,这对于实时系统来说,是不可接受的,实时系统必须要保证内存块的分配过程在可预测的确定时间内完成,否则实时任务对外部事件的响应也将变得不可确定。
其次,随着内存不断被分配和释放,整个内存区域会产生越来越多的碎片(因为在使用过程中,申请了一些内存,其中一些释放了,导致内存空间中存在一些小的内存块,它们地址不连续,不能够作为一整块的大内存分配出去),系统中还有足够的空闲内存,但因为它们地址并非连续,不能组成一块连续的完整内存块,会使得程序不能申请到大的内存。对于通用系统而言,这种不恰当的内存分配算法可以通过重新启动系统来解决(每个月或者数个月进行一次),但是对于那些需要常年不间断地工作于野外的嵌入式系统来说,就变得让人无法接受了。所以实时操作系统的内存分配算法应该要尽可能妥善的改进碎片问题。
最后,嵌入式系统的资源环境也是不尽相同,有些系统的资源比较紧张,只有数十KB的内存可供分配,而有些系统则存在数MB的内存,如何为这些不同的系统,选择适合它们的高效率的内存分配算法,就将变得复杂化。所以实时操作系统的内存分配算法要尽可能多的适应内存不等的各种平台。
RTT操作系统在内存管理上,针对以上问题,提供了不同的内存分配算法。
大体上可分为两类:静态分区内存管理与动态内存管理,而动态内存管理又根据可用内存的多少划分为两种情况:一种是针对小内存块的分配管理(小内存管理算法),另一种是针对大内存块的分配管理(SLAB管理算法)。
这里先来看看其提供的一种名为内存池(Memory Pool)的内存分配管理算法,内存池是一种用于分配大量大小相同的小对象的技术。它可以极大加快内存分配/释放的速度。
内存池一旦初始化完成,内部的内存块大小将不能再做调整。每一个内存池其实就是一个链表,由于链表的大小全部相同,每次分配的时候,从链表中取出链头上第一个内存块,提供给申请者,每次释放内存,就把释放的内存重新加入链表即可。这种算法的优势是显而易见的,释放和分配内存都只需要O(1)的时间即可完成。当然也有很大的缺陷,只能分配固定的内存,对于不同大小的内存分配无法很好的满足。
下面就来看看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 */
};```
1.parent rt_object实例化,同样rt_mempool也是继承自rt_object
2.start_address 内存池起始地址
3.size 内存池总大小,size=(block_size + sizeof(uint8_t )) block_total_count
4.block_size 每个块的大小
5.block_list 空闲块所组成的列表
6.block_total_count 总内存块数量
7.block_free_count 空闲内存块数量
8.suspend_thread 由于等待空闲内存而挂起的线程列表
9.suspend_thread_count 挂起的线程总数
内存池内存分配算法相对来说比较简单,相关的函数如下:
```rt_err_t rt_mp_init(struct rt_mempool mp,
const char name,
void start,
rt_size_t size,
rt_size_t block_size);
rt_err_t rt_mp_detach(struct rt_mempool mp);
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);
void rt_mp_alloc(rt_mp_t mp, rt_int32_t time); void rt_mp_free(void block);```
一、rt_mp_init与rt_mp_create
```rt_mp_t rt_mp_create(const char name,
rt_size_t block_count,
rt_size_t block_size)
{
rt_uint8_t block_ptr;
struct rt_mempool *mp;
register rt_size_t offset;
RT_DEBUG_NOT_IN_INTERRUPT;
/* allocate object */
mp = (struct rt_mempool *)rt_object_allocate(RT_Object_Class_MemPool, name);
/* allocate object failed */
if (mp == RT_NULL)
return RT_NULL;
/* initialize memory pool */
block_size = RT_ALIGN(block_size, RT_ALIGN_SIZE);
mp->block_size = block_size;
mp->size = (block_size + sizeof(rt_uint8_t *)) * block_count;
/* allocate memory */
mp->start_address = rt_malloc((block_size + sizeof(rt_uint8_t *)) *
block_count);
if (mp->start_address == RT_NULL)
{
/* no memory, delete memory pool object */
rt_object_delete(&(mp->parent));
return RT_NULL;
}
mp->block_total_count = block_count;
mp->block_free_count = mp->block_total_count;
/* initialize suspended thread list */
rt_list_init(&(mp->suspend_thread));
mp->suspend_thread_count = 0;
/* initialize free block list */
block_ptr = (rt_uint8_t *)mp->start_address;
for (offset = 0; offset < mp->block_total_count; offset ++)
{
*(rt_uint8_t **)(block_ptr + offset * (block_size + sizeof(rt_uint8_t *)))
= block_ptr + (offset + 1) * (block_size + sizeof(rt_uint8_t *));
}
*(rt_uint8_t **)(block_ptr + (offset - 1) * (block_size + sizeof(rt_uint8_t *)))
= RT_NULL;
mp->block_list = block_ptr;
return mp;
}```
rt_mp_init和rt_mp_create的区别为,rt_mp_init用来初始化所需的内存块已经提前分配好的内存池,rt_mp_create则需要调用rt_malloc来分配内存池所需内存,所以使用rt_mp_create一定要其他内存分配方法配合才能进行。
rt_mp_create功能:
1.初始化object基类。
2.按照传入的参数初始化各种成员如: start_address,size,block_size,block_total_count,block_free_count等。
3.通过rt_malloc分配内存池所需的内存。
4.初始化空闲内存链表,将每个空闲的空间通过链表链接起来。
二、rt_mp_delete和rt_mp_detach
两者的功能均为删除内存池,rt_mp_delete和rt_mp_detach的区别为,rt_mp_delete用来删除所需的内存块已经提前分配好的内存池(主要是将其移出内核的对象容器),rt_mp_detach则需要调用rt_free来释放分配给内存池内存
```rt_err_t rt_mp_delete(rt_mp_t mp)
{
struct rt_thread *thread;
register rt_ubase_t temp;
RT_DEBUG_NOT_IN_INTERRUPT;
/* parameter check */
RT_ASSERT(mp != RT_NULL);
RT_ASSERT(rt_object_get_type(&mp->parent) == RT_Object_Class_MemPool);
RT_ASSERT(rt_object_is_systemobject(&mp->parent) == RT_FALSE);
/* wake up all suspended threads */
while (!rt_list_isempty(&(mp->suspend_thread)))
{
/* disable interrupt */
temp = rt_hw_interrupt_disable();
/* get next suspend thread */
thread = rt_list_entry(mp->suspend_thread.next, struct rt_thread, tlist);
/* set error code to RT_ERROR */
thread->error = -RT_ERROR;
/*
* resume thread
* In rt_thread_resume function, it will remove current thread from
* suspend list
*/
rt_thread_resume(thread);
/* decrease suspended thread count */
mp->suspend_thread_count --;
/* enable interrupt */
rt_hw_interrupt_enable(temp);
}
/* release allocated room */
rt_free(mp->start_address);
/* detach object */
rt_object_delete(&(mp->parent));
return RT_EOK;
}```
rt_mp_delete主要功能:
1.唤醒所有被该内存池阻塞的线程
2.释放内存池内存
3.删除基类object
三、rt_mp_alloc和rt_mp_free
使用内存池分配内存的函数,分别为释放和分配。
下面是rt_mp_alloc:
```void rt_mp_alloc(rt_mp_t mp, rt_int32_t time)
{
rt_uint8_t block_ptr;
register rt_base_t level;
struct rt_thread *thread;
rt_uint32_t before_sleep = 0;
/* get current thread */
thread = rt_thread_self();
/* disable interrupt */
level = rt_hw_interrupt_disable();
while (mp->block_free_count == 0)
{
/* memory block is unavailable. */
if (time == 0)
{
/* enable interrupt */
rt_hw_interrupt_enable(level);
rt_set_errno(-RT_ETIMEOUT);
return RT_NULL;
}
RT_DEBUG_NOT_IN_INTERRUPT;
thread->error = RT_EOK;
/* need suspend thread */
rt_thread_suspend(thread);
rt_list_insert_after(&(mp->suspend_thread), &(thread->tlist));
mp->suspend_thread_count++;
if (time > 0)
{
/* get the start tick of timer */
before_sleep = rt_tick_get();
/* init thread timer and start it */
rt_timer_control(&(thread->thread_timer),
RT_TIMER_CTRL_SET_TIME,
&time);
rt_timer_start(&(thread->thread_timer));
}
/* enable interrupt */
rt_hw_interrupt_enable(level);
/* do a schedule */
rt_schedule();
if (thread->error != RT_EOK)
return RT_NULL;
if (time > 0)
{
time -= rt_tick_get() - before_sleep;
if (time < 0)
time = 0;
}
/* disable interrupt */
level = rt_hw_interrupt_disable();
}
/* memory block is available. decrease the free block counter */
mp->block_free_count--;
/* get block from block list */
block_ptr = mp->block_list;
RT_ASSERT(block_ptr != RT_NULL);
/* Setup the next free node. */
mp->block_list = *(rt_uint8_t **)block_ptr;
/* point to memory pool */
*(rt_uint8_t **)block_ptr = (rt_uint8_t *)mp;
/* enable interrupt */
rt_hw_interrupt_enable(level);
RT_OBJECT_HOOK_CALL(rt_mp_alloc_hook,
(mp, (rt_uint8_t *)(block_ptr + sizeof(rt_uint8_t *))));
return (rt_uint8_t *)(block_ptr + sizeof(rt_uint8_t *));
}```
主要操作如下:
1.判断是否还有空余的内存块,若没有则把当前线程挂起,加入到内存池挂起列表,并开启线程的定时器,在一定时候后重新唤醒线程。
2.若还有空闲内存块,则返回空闲内存块的地址,并把内存块移除空闲内存列表,同时block_free_count减1.
3.调用rt_mp_alloc_hook钩子函数
接下来是rt_mp_free:
```void rt_mp_free(void block)
{
rt_uint8_t **block_ptr;
struct rt_mempool mp;
struct rt_thread *thread;
register rt_base_t level;
/* get the control block of pool which the block belongs to */
block_ptr = (rt_uint8_t **)((rt_uint8_t *)block - sizeof(rt_uint8_t *));
mp = (struct rt_mempool *)*block_ptr;
RT_OBJECT_HOOK_CALL(rt_mp_free_hook, (mp, block));
/* disable interrupt */
level = rt_hw_interrupt_disable();
/* increase the free block count */
mp->block_free_count ++;
/* link the block into the block list */
*block_ptr = mp->block_list;
mp->block_list = (rt_uint8_t *)block_ptr;
if (mp->suspend_thread_count > 0)
{
/* get the suspended thread */
thread = rt_list_entry(mp->suspend_thread.next,
struct rt_thread,
tlist);
/* set error */
thread->error = RT_EOK;
/* resume thread */
rt_thread_resume(thread);
/* decrease suspended thread count */
mp->suspend_thread_count --;
/* enable interrupt */
rt_hw_interrupt_enable(level);
/* do a schedule */
rt_schedule();
return;
}
/* enable interrupt */
rt_hw_interrupt_enable(level);
}```
主要操作如下:
1.调用rt_mp_free_hook钩子函数。
2.将block_free_count加1,并把释放的内存块重新加入到空闲列表中。
3.检查是否有被其阻塞线程,唤醒线程。
这里有几个问题,使用free的时候需要注意:
rt_mp_free没进行参数检查,如果传入了错误的指针,如NULL。可能会引起未知错误。
若使用rt_mp_free释放了不是当前mp分配的内存,也会引起同样问题。
原作者:yushigengyu