RT-Thread內核的我們已經基本都學習過了,除了基本的線程操作和通信,
內核部分還有內存管理和中斷處理,本文主要就來說說內存管理相關問題。
目錄
前言
一、為什么要內存管理
二、RT-Thread 內存堆管理
2.1 RT-Thread 內存分配
2.2 RT-Thread內存堆管理方式
2.2.1 內存堆管理的3種方式
2.2.2 管理方式的程序配置
2.3 內存堆 API 函數
三、RT-Thread 內存池
3.1 內存池的位置
3.2 內存池程序配置和控制塊
3.3 內存池操作 API 函數
結語
前言
記得最初學習 RT-Thread ,對于內存管理我也是簡單看看然后一筆帶過,當時覺得用不上,在我做的一些傳感器單品項目上,對于內存管理確實知道與不知道沒什么關系,但是隨著認知的增長,項目復雜程度增加,發現內存管理還不可或缺,于是今時今日正好再次來更新 RT-Thread記錄,有必要好好的說一說。
RT-Thread 有2種內存管理方式,分別是動態內存堆管理和靜態內存池管理。
先不管這兩種方式是怎么實現的,首先要明白一個問題,為什么要內存管理?
本 RT-Thread 專欄記錄的開發環境:
RT-Thread記錄(一、RT-Thread 版本、RT-Thread Studio開發環境 及 配合CubeMX開發快速上手)
RT-Thread記錄(二、RT-Thread內核啟動流程 — 啟動文件和源碼分析)
RT-Thread 內核篇系列博文鏈接:
RT-Thread記錄(三、RT-Thread 線程操作函數及線程管理與FreeRTOS的比較)
RT-Thread記錄(四、RT-Thread 時鐘節拍和軟件定時器)
RT-Thread記錄(五、RT-Thread 臨界區保護)
RT-Thread記錄(六、IPC機制之信號量、互斥量和事件集)
RT-Thread記錄(七、IPC機制之郵箱、消息隊列)
一、為什么要內存管理
什么是內存管理?為什么需要內存管理?
在我們的程序設計中,一些數據需要的內存大小需要在程序運行過程中根據實際情況確定,在用戶需要一段內存空間時,向系統申請,系統選擇一段合適的內存空間分配給用戶,用戶使用完畢后,再釋放回系統,以便系統將該段內存空間回收再利用。
這樣子不斷的申請釋放,如果沒有內存管理,就會導致內存碎片,對程序產生極大的影響。
具體的原因我在下面博文有詳細說明:淺談 malloc 函數在單片機上的應用
如果看完上面的博文,就應該知道了內存管理的重要性。
我們確實直接在函數或者線程里面創建臨時變量使用 線程棧 或 者系統棧處理臨時變量,但是正如上面博文里面所說的,作為一個通用的操作系統,都會且必須得有自己的合理的內存管理方式。
這個在我們現在說明的 RT-Thread 操作系統上是內核已經實現好的,我們得了解它。
知道了為什么,那么接下來我們就來了解一下 RT-Thread 的這2種內存管理方式。
二、RT-Thread 內存堆管理
現在看看內存堆管理, 所謂堆,看過我博文的朋友應該已經很熟悉了,對于裸機中的堆的位置,大小設定都應該會有一個詳細的認知,如果還不知道堆,可以查看下面這篇博文一步到位的理解:
STM32的內存管理相關(內存架構,內存管理,map文件分析)
上文雖然是以STM32為例子說明,但是對于 C/C++ 程序編譯后的存儲數據段 在不同芯片上的順序基本都是一樣的,于是乎在我的又一篇博文中
就得到了一張內存分配的示意圖:
2.1 RT-Thread 內存分配
要理解 RT-Thread 內存堆管理,首先得知道它管理的是哪一塊內存,所以得知道 RT-Thread 內存分配。
其實細心的朋友會發現,我在前面文章《RT-Thread記錄(二、RT-Thread內核啟動流程 — 啟動文件和源碼分析)》
中提到過 RT-Thread 內存分配情況,在其中的 2.2.2 RT-Thread 堆和棧空間說明(與FreeRTOS不同)
有過說明:
通過上面的分析,我們可以得到 RT-Thread 下的內存分配的示意圖:
通過上面我們自己的分析,再結合官方說明文檔的說明,我們應該能完全明白了什么是 RT-Thread 的內存堆:
至此,我們可以確實的明白,RT-Thread 內存管理管理的是哪一部分的內存了。
2.2 RT-Thread內存堆管理方式
那么RT-Thread 對于內存堆,是如何管理的呢?
說明:對于這部分,我個人記錄是以知道為主,畢竟我們專欄以應用為主,我們了解會用即可,如果以后確實自己會需要自己寫內存管理,肯定會單獨更新一篇博文。
我們先前的基礎介紹直接使用官網說明,本節后部分會說明下程序中是怎么選擇使用的。
2.2.1 內存堆管理的3種方式
RT-Thread 內存堆管理又根據具體內存設備劃分為三種情況:
- 針對小內存塊的分配管理(小內存管理算法)
- 針對大內存塊的分配管理(slab 管理算法)
- 針對多內存堆的分配情況(memheap 管理算法)
這里套用官方的介紹做個簡單說明(如果需要深入了解可以去官網查看):
2.2.2 管理方式的程序配置
我們上面 簡單介紹了RT-Thread 內存堆管理的3種方式,雖然我們沒有詳細分析內部是如何實現,那么我們也得了解在 RT-Thread 工程中,是怎么配置使用哪一種管理方式,同時了解這些方式的實現在程序什么文件中。
在工程的rtconfig.h
中有關于內存管理方式的配置:
上圖是使用小內存堆管理算法。
如果是使用 slab 管理算法,需要宏定義如下:
#define RT_USING_SLAB
#define RT_USING_HEAP
如果是使用 memheap 管理算法,需要宏定義如下:
#define RT_USING_MEMHEAP_AS_HEAP
具體的實現文件是在工程 mem.c
文件中,如下圖:
RT-Thread 內存管理詳細的實現方式可以自行查看該文件,這里就不過多介紹。
2.3 內存堆 API 函數
對于 RT-Thread 內存堆管理,是有自己的 malloc
函數,不能直接用 c 語言庫中原始的malloc
函數。
其 三種管理算法提供的 API 都是相同的。
初始化:
首先是初始化函數:
/*小內存堆和slab 管理算法*/
void rt_system_heap_init(void* begin_addr, void* end_addr);
/*memheap 管理算法*/
rt_err_t rt_memheap_init(struct rt_memheap *memheap,
const char *name,
void *start_addr,
rt_uint32_t size)
我在《RT-Thread記錄(二、RT-Thread內核啟動流程 — 啟動文件和源碼分析)》板級硬件初始化 — rt_hw_board_init 小結中提到過 這個函數:
RT-Thread內存堆初始化函數是在系統啟動時候初始化的,而不是我們用戶在main函數中再初始化的。
內存管理操作函數:
簡單記錄一下:
/*
分配內存塊
參數:
nbytes 需要分配的內存塊的大小,單位為字節
返回 ——
分配的內存塊地址 成功
RT_NULL 失敗
*/
void *rt_malloc(rt_size_t nbytes);
/*
釋放內存塊
參數:
ptr 待釋放的內存塊指針
*/
void rt_free (void *ptr);
/*
重分配內存塊
參數 描述
rmem 指向已分配的內存塊
newsize 重新分配的內存大小
返回 ——
重新分配的內存塊地址 成功
*/
void *rt_realloc(void *rmem, rt_size_t newsize);
/*
分配多內存塊
參數 描述
count 內存塊數量
size 內存塊容量
返回 ——
指向第一個內存塊地址的指針 成功 ,并且所有分配的內存塊都被初始化成零。
RT_NULL 分配失敗
*/
void *rt_calloc(rt_size_t count, rt_size_t size);
/*
設置內存鉤子函數
參數 描述
hook 鉤子函數指針
*/
void rt_malloc_sethook(void (*hook)(void *ptr, rt_size_t size));
/*
上面函數的hook 函數接口
參數 描述
ptr 分配到的內存塊指針
size 分配到的內存塊的大小
*/
void hook(void *ptr, rt_size_t size);
/*
釋放內存鉤子函數
參數 描述
hook 鉤子函數指針
*/
void rt_free_sethook(void (*hook)(void *ptr));
/*
上面函數的hook 函數接口
參數 描述
ptr 待釋放的內存塊指針
*/
void hook(void *ptr);
釋放內存塊后要清空內存塊指針,不然會成為野指針。
三、RT-Thread 內存池
RT-Thread 的第二種內存管理方式是 內存池,內存池是一種內存分配方式,用于分配大量大小相同的小內存塊,它可以極大地加快內存分配與釋放的速度,且能盡量避免內存碎片化。
他是為了提高內存分配的效率,并且避免內存碎片而產生的。
基本介紹套用官方說明:
內存池屬于 內核對象!!支持線程掛起功能。
內核對線什么意思?就是我們前面說到的,線程,IPC機制,這些東西都是內核對象,所以存在 內存池 控制塊。
有一個點要注意,內存池申請的內存塊大小固定!
3.1 內存池的位置
內存池也是內存管理,那么他創建的之后在內存的什么位置呢?
首先明白內存池作為一個對象,其實可以認為是一個變量,那么他基本上就是屬于.bss段的東西了,在RT-Thread 中,他的位置應該是在 .bss段的。
我們按照上面的內存分配的示意圖說明一下內存池申請的內存位置:
為了驗證一下是否如此,可以初始化一個內存池,編譯后查看一下.map文件:
內存池屬于.bss段的數據,只不過他一般來說都是申請的相對比較大的一塊內存空間,然后在這個大空間內自己有自己的分配管理方式。
3.2 內存池程序配置和控制塊
內存池程序配置:
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;
內存池具體管理方法的實現可以自己查看下源碼,這里暫時不做深入研究。
3.3 內存池操作 API 函數
內存池作為內核對象,那么他的操作就和以前那些IPC機制,線程一樣的方式,分為動態創建,靜態初始化這些。
并不需要在 板級初始化的時候就初始化。用戶可以自己選擇用于不用。
簡單記錄說明一下內存池操作函數:
/*
創建內存池
參數 描述
name 內存池名
block_count 內存塊數量
block_size 內存塊容量
返回 ——
內存池的句柄 創建內存池對象成功
RT_NULL 創建失敗
*/
rt_mp_t rt_mp_create(const char* name,
rt_size_t block_count,
rt_size_t block_size);
/*
刪除內存池
參數 描述
mp rt_mp_create 返回的內存池對象句柄
返回 ——
RT_EOK 刪除成功
*/
rt_err_t rt_mp_delete(rt_mp_t mp);
/*
初始化內存池
參數 描述
mp 內存池對象
name 內存池名
start 內存池的起始位置
size 內存池數據區域大小
block_size 內存塊容量
返回 ——
RT_EOK 初始化成功
- 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);
/*
脫離內存池
參數 描述
mp 內存池對象
返回 ——
RT_EOK 成功
*/
rt_err_t rt_mp_detach(rt_mp_t mp);
/*
分配內存塊
參數 描述
mp 內存池對象
time 超時時間
返回 ——
分配的內存塊地址 成功
RT_NULL 失敗
*/
void *rt_mp_alloc (rt_mp_t mp, rt_int32_t time);
/*
釋放內存塊
參數 描述
block 內存塊指針
*/
void rt_mp_free (void *block);
結語
本文從為什么要內存管理說起,了解了 RT-Thread 的2種內存管理方式:內存堆 和 內存池。
相信不管你平時用不用動態內存申請,看了這篇文章以后也會對 RT-Thread 的內存管理有一定的了解,在以后需要用到動態內存分配的同時也不會手足無措!
但是特別注意,不管使用哪種方式,在申請的內存使用完之后都必須及時釋放,而且要注意清空相應的指針!
但是本文的重點在于理解 為什么要內存管理,和 內存管理管理的是哪一塊的內存,對于如何實現內存管理我們只是借用了官方的說明,還是沒有深入的研究分析。這個需要等以后我有機會自己寫一套內存管理方式的時候,或許會單獨開一篇文章深入分析。
謝謝!
審核編輯:湯梓紅
-
中斷處理
+關注
關注
0文章
94瀏覽量
11015 -
內存管理
+關注
關注
0文章
168瀏覽量
14188 -
RT-Thread
+關注
關注
31文章
1305瀏覽量
40386
發布評論請先 登錄
相關推薦
評論