前言
在C中,可以通过malloc、calloc、realloc进行动态的、任意大小的 内存块申请,以及通过free进行内存块释放;在C++中,能够通过new及delete执行上述操作。这样的优点非常明显,它们使内存块的申请分配变得非常灵活方便。但是,由于它的灵活性,将会有如下缺点:
由于内存本身是连续的,在任意申请又释放达到一定次数后,在没有内存管理的情况下,它难免会出现内存碎片; 在内存操作过程中,若非原子操作,在多线程环境下将容易出现线程安全问题; RT-Thread中提供了内存池 的管理方法。它能够通过链表静态存储的方式(链表管理便于增加及删除,方便管理,但是遍历效率低),存储大量大小相同的小内存块,能够加快分配和释放时的效率,避免内存碎片的产生。并且,它是线程安全的,在内存分配时,若无可用内存块,申请线程将会挂起等待新释放的内存块。虽然它在每次分配时仍是需要遍历链表,效率不是非常高,但是能避免因为无内存管理而产生的性能问题。
数据结构
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中内存池的创建同其他对象类似。创建分为静态创建及动态创建。
静态创建由用户提供内存块,系统将其分为大小相同的小内存块,并且将它们连接为内存块链表。这里指向每个内存块的都是无符号字符型指针; 动态创建与静态创建类似,不同的是它的内存池对象及内存池是从堆上申请的,内存池大小是通过用户提供的单个内存块大小及内存块数量而定。 以下为源码:
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_uint8_t * block_ptr;
register rt_size_t offset;
RT_ASSERT ( mp != RT_NULL) ;
rt_object_init ( & ( mp-> parent) , RT_Object_Class_MemPool, name) ;
mp-> start_address = start;
mp-> size = RT_ALIGN_DOWN ( size, RT_ALIGN_SIZE) ;
block_size = RT_ALIGN ( block_size, RT_ALIGN_SIZE) ;
mp-> block_size = block_size;
mp-> block_total_count = mp-> size / ( mp-> block_size + sizeof ( rt_uint8_t * ) ) ;
mp-> block_free_count = mp-> block_total_count;
rt_list_init ( & ( mp-> suspend_thread) ) ;
mp-> suspend_thread_count = 0 ;
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 * ) ) ) =
( 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 RT_EOK;
}
rt_mp_t rt_mp_create ( const char * name,
rt_size_t block_count,
rt_size_t block_size)
{
. . .
mp = ( struct rt_mempool * ) rt_object_allocate ( RT_Object_Class_MemPool, name) ;
if ( mp == RT_NULL)
return RT_NULL;
block_size = RT_ALIGN ( block_size, RT_ALIGN_SIZE) ;
mp-> block_size = block_size;
mp-> size = ( block_size + sizeof ( rt_uint8_t * ) ) * block_count;
mp-> start_address = rt_malloc ( ( block_size + sizeof ( rt_uint8_t * ) ) *
block_count) ;
if ( mp-> start_address == RT_NULL)
{
rt_object_delete ( & ( mp-> parent) ) ;
return RT_NULL;
}
. . .
return mp;
}
删除
关于内存池的删除函数,它的主要工作是将删除前正在等待内存块资源的挂起线程依个唤醒。静态创建和动态创建在删除时的区别这里不多做赘述。
分配
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 ;
thread = rt_thread_self ( ) ;
level = rt_hw_interrupt_disable ( ) ;
while ( mp-> block_free_count == 0 )
{
if ( time == 0 )
{
rt_hw_interrupt_enable ( level) ;
rt_set_errno ( - RT_ETIMEOUT) ;
return RT_NULL;
}
RT_DEBUG_NOT_IN_INTERRUPT;
thread-> error = RT_EOK;
rt_thread_suspend ( thread) ;
rt_list_insert_after ( & ( mp-> suspend_thread) , & ( thread-> tlist) ) ;
mp-> suspend_thread_count++ ;
if ( time > 0 )
{
before_sleep = rt_tick_get ( ) ;
rt_timer_control ( & ( thread-> thread_timer) ,
RT_TIMER_CTRL_SET_TIME,
& time) ;
rt_timer_start ( & ( thread-> thread_timer) ) ;
}
rt_hw_interrupt_enable ( level) ;
rt_schedule ( ) ;
if ( thread-> error != RT_EOK)
return RT_NULL;
if ( time > 0 )
{
time -= rt_tick_get ( ) - before_sleep;
if ( time < 0 )
time = 0 ;
}
level = rt_hw_interrupt_disable ( ) ;
}
mp-> block_free_count-- ;
block_ptr = mp-> block_list;
RT_ASSERT ( block_ptr != RT_NULL) ;
mp-> block_list = * ( rt_uint8_t * * ) block_ptr;
* ( rt_uint8_t * * ) block_ptr = ( rt_uint8_t * ) mp;
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之后分配出一个空闲内存块返回。
释放
释放与分配执行的操作相反。这里首先将空闲内存块计数加1,之后将传入的释放内存块放入内存块链表,最后若存在等待空闲内存块的挂起线程,则唤醒,返回。
void rt_mp_free ( void * block)
{
rt_uint8_t * * block_ptr;
struct rt_mempool * mp;
struct rt_thread * thread;
register rt_base_t level;
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) ) ;
level = rt_hw_interrupt_disable ( ) ;
mp-> block_free_count ++ ;
* block_ptr = mp-> block_list;
mp-> block_list = ( rt_uint8_t * ) block_ptr;
if ( mp-> suspend_thread_count > 0 )
{
thread = rt_list_entry ( mp-> suspend_thread. next,
struct rt_thread ,
tlist) ;
thread-> error = RT_EOK;
rt_thread_resume ( thread) ;
mp-> suspend_thread_count -- ;
rt_hw_interrupt_enable ( level) ;
rt_schedule ( ) ;
return ;
}
rt_hw_interrupt_enable ( level) ;
}
其余
在分配和释放过程中,每次除了待操作的内存块外,还有一个内存池控制块。这些在初始化创建时都是已经固定的,“浓缩”进了空闲内存块计数及创建时的内存对齐中,因此在除内存池创建及删除的操作中不需要担心每次申请分配的内存块会超出内存池的大小或在释放时出现重复释放的问题。