今天來看看RTT中的內存分配。關於內存分配,RTT官網提供了十分完整的演算法說明,有興趣的可以跳轉文章

6.內存管理 - RT-Thread 文檔中心?

www.rt-thread.org
圖標

由於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分配的內存,也會引起同樣問題。

推薦閱讀:

查看原文 >>
相关文章