內存堆管理器可以分配任意大小的內存塊,非常靈活和方便。但其也存在明顯的缺點:一是分配效率不高,在每次分配時,都要空閒內存塊查找;二是容易產生內存碎片。爲了提高內存分配的效率,並且避免內存碎片,RT-Thread 提供了另外一種內存管理方法:內存池(Memory Pool)。
內存池是一種內存分配方式,用於分配大量大小相同的小內存塊,它可以極大地加快內存分配與釋放的速度,且能儘量避免內存碎片化。此外,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_err_t rt_mp_init(rt_mp_t mp, const char *name, void *start, rt_size_t size, rt_size_t block_size){
rt_ubase_t offset;
rt_object_init(&(mp->parent), RT_Object_Class_MemPool, name); // 對象初始化
mp->start_address = start;
size = RT_ALIGN_DOWN(size, RT_ALIGN_SIZE);
mp->size = size;
block_size = RT_ALIGN(block_size, RT_ALIGN_SIZE);
mp->block_size = block_size;
mp->block_free_count = size / (block_size + sizeof(rt_ubase_t *));
mp->block_total_count = mp->block_free_count;
rt_list_init(&(mp->suspend_thread));
mp->suspend_thread_count = 0;
/* 形成單鏈表 */
for(offset = 0; offset < mp->block_total_count - 1; offset++){
*(rt_ubase_t **)((rt_uint8_t *)start + (block_size + sizeof(rt_ubase_t *)) * offset) =
(rt_ubase_t *)((rt_uint8_t *)start + (block_size + sizeof(rt_ubase_t *)) * (offset + 1));
}
*(rt_ubase_t **)((rt_uint8_t *)start + (block_size + sizeof(rt_ubase_t *)) * offset) = RT_NULL;
mp->block_list = (rt_uint8_t *)start;
return RT_EOK;
}
初始化後的內存分佈:
申請內存塊
void *rt_mp_alloc(rt_mp_t mp, rt_int32_t time){
rt_thread_t thread;
rt_uint8_t *block_ptr;
rt_ubase_t tmp;
rt_uint32_t before_sleep = 0;
thread = rt_thread_self();
tmp = rt_hw_interrupt_disable();
while(mp->block_free_count == 0){
if(time == 0){ // 無等待的直接返回
thread->error = -RT_ETIMEOUT;
rt_hw_interrupt_enable(tmp);
return RT_NULL;
}
thread->error = RT_EOK;
rt_thread_suspend(thread); // 等待的掛起線程
rt_list_insert_before(&(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(tmp);
rt_schedule(); // 調度
/* 超時或主動恢復線程 */
if(thread->error != RT_EOK){
return RT_NULL;
}
if(time > 0){
time = rt_tick_get() - before_sleep; // 未分配內存塊,且未超時繼續等待
if(time < 0){
time = 0;
}
}
tmp = rt_hw_interrupt_disable();
}
/* 執行到這說明有空閒塊了 */
block_ptr = mp->block_list;
mp->block_list = *(rt_uint8_t **)block_ptr; // .block_list總是指向下一個空閒塊
*(rt_uint8_t **)block_ptr = (rt_uint8_t *)mp;
mp->block_free_count--;
rt_hw_interrupt_enable(tmp);
return (void *)(block_ptr + sizeof(rt_uint8_t *));
}
假如申請了兩塊內存。內存分佈:
內存釋放
void rt_mp_free(void *block){
rt_uint8_t *block_ptr;
rt_mp_t mp;
rt_thread_t thread;
rt_ubase_t tmp;
RT_ASSERT(block != RT_NULL);
block_ptr = (rt_uint8_t *)((rt_uint32_t)block - sizeof(rt_uint8_t *));
mp = (rt_mp_t)(*(rt_uint8_t **)block_ptr); // 獲取該內存池控制塊
tmp = rt_hw_interrupt_disable();
*(rt_uint8_t **)block_ptr = (rt_uint8_t *)mp->block_list; // .next
mp->block_list = block_ptr; // 本塊又變成了鏈表中的第一塊空閒塊
mp->block_free_count++;
if(mp->suspend_thread_count > 0){ // 判斷是否有線程等待分配
thread = rt_list_entry(mp->suspend_thread.next, struct rt_thread, tlist);
rt_thread_resume(thread);
thread->error = RT_EOK;
mp->suspend_thread_count--;
rt_hw_interrupt_enable(tmp);
rt_schedule();
return ;
}
rt_hw_interrupt_enable(tmp);
}
上圖申請了兩塊內存,如果先釋放第二塊,再釋放第一塊。鏈表順序有所改變:
對於動態創建的內存池,只是多了一步申請內存的操作。