以下內容轉載自安富萊電子: http://forum.armfly.com/forum.php
內存管理介紹
在 ANSI C 中,可以用 malloc()和 free()2 個函數動態的分配內存和釋放內存,但是,在嵌入式實時
操作系統中,調用 malloc()和 free()卻是危險的,因為多次調用這兩個函數會把原來很大的一塊連續內場
區域逐漸地分割成許多非常小而且彼此又不相鄰的內存塊,也就是內存碎片。由於這些內存碎片的大量存
在,使得程序到后來連一段非常小的連續內存也分配不到。另外,由於內存管理算法上的原因,malloc()
和 free()函數的執行時間是不確定的。
在 RTX 中,操作系統把連續的大塊內存按分區來管理。每個分區中包含整數個大小相同的內存塊。如
圖 18.1 所示:
利用這種機制,就可以得到和釋放固定大小的內存塊。 這樣內存的申請和釋放函數的執行時間就是確定的了。
在一個系統中可以有多個內存分區,這樣,應用程序就可以從不同的內存分區中得到不同大小的內存
塊。但是特定的內存塊在釋放時,必須重新放回到它以前所屬於的內存分區。顯然,采用這樣的內存管理
算法,上面的內存碎片文件就得到了解決。
其實 RTX 的內存管理也非常好理解,可以理解成一個二維數組,比如我們定義一個二維數組為:
uint8_t mpool[10][32]。 對應到 RTX 的內存管理上就是定義了 10 個內存塊,每塊大小是 32 字節。
如果還需要其它大小的內存塊,還可以多定義幾個其它大小的。
內存管理 API 函數
使用如下 7 個函數可以實現 RTX 的內存管理:
_declare_box
_declare_box8
_init_box
_init_box8
_alloc_box
_calloc_box
_free_box
函數_declare_box
函數原型:
#define _declare_box( \
pool, \ /* 內存池變量名 */
size, \ /* 內存塊大小,單位字節 */
cnt ) \ /* 內存池中內存塊的個數 */
U32 pool[((size+3)/4)*(cnt) + 3]
函數描述:
函數_declare_box 用於定義一塊內存池。
第 1 個參數填寫內存池的變量名。
第 2 個參數填寫內存塊的大小,單位字節。
第 3 個參數填寫內存池中內存塊的個數。
使用這個函數要注意以下問題:
1. 宏定義中通過操作(size+3)/4 保證了每個內存塊大小是 4 字節的倍數,從而也就保證了每個內存塊
的首地址是 4 字節對齊的(4 字節對齊的含義是地址對 4 求余等於 0)。這里初學者也要注意數據類
型的字節對齊問題。
基本數據類型的字節對齊問題
這個問題在 MDK5 安裝目錄里面的文檔 DUI0375G_02_mdk_armcc_user_guide.pdf 里面有詳細的說明,
簡單的說明, 數據類型有幾個字節, 那么這個數據類型的變量就是幾字節對齊,比如 32 位的 int 型
有 4 個字節,那么此 int 型定義的變量就是 4 字節對齊,對於初學者要牢牢的記住這個知識點。
其實這個在keil的幫助文檔里面就有,善於利用ide的幫助文檔,這是最直接快捷的熟悉編譯器的方式。
2. 內存池中額外定義的 12 個字節用於將內存塊做指針鏈表,方便動態的申請和釋放。
函數_declare_box8
函數原型:
#define _declare_box8( \
pool, \ /* 內存池變量名 */
size, \ /* 內存塊大小,單位字節 */
cnt ) \ /* 內存池中內存塊的個數 */
U64 pool[((size+7)/8)*(cnt) + 2]
函數描述:
函數_declare_box 用於定義一塊內存池。
第 1 個參數填寫內存池的變量名。
第 2 個參數填寫內存塊的大小,單位字節。
第 3 個參數填寫內存池中內存塊的個數。
使用這個函數要注意以下問題:
1. 宏定義中通過操作(size+7)/8 保證了每個內存塊大小是 8 字節的倍數,從而也就保證了每個內存塊
的首地址是 8 字節對齊的(8 字節對齊的含義是地址對 8 求余等於 0)。這里初學者也要注意數據類
型的字節對齊問題。
2. 內存池中額外定義的 16 個字節用於將內存塊做指針鏈表,方便動態的申請和釋放。
函數_init_box
函數原型:
int _init_box (
void* box_mem, /* 內存池首地址 */
U32 box_size, /* 內存池大小,單位字節 */
U32 blk_size ); /* 內存塊大小, 單位字節 */
函數描述:
函數_init_box 用於內存池的初始化,初始化時用到的參數都是源自於_declare_box。
第 1 個參數填寫內存池的首地址。
第 2 個參數填寫內存池的大小,單位字節。
第 3 個參數填寫內存塊大小,單位字節。
使用這個函數要注意以下問題:
1. 強烈建議跟函數_declare_box 一起使用。用戶不要自己去初始化這個函數,用_declare_box 聲明的
才是最保險的。
2. 如果用戶沒有使用函數_declare_box 進行定義,那么要保證內存池首地址是 4 字節對齊的。
3. 如果用戶沒有使用函數_declare_box 進行定義,那么要保證內存池的大小 box_size 至少有 12 個字節,
因為這個 12 個字節是用於將內存塊做成指針鏈表,方便動態申請和釋放。
函數_init_box8
函數原型:
int _init_box (
void* box_mem, /* 內存池首地址 */
U32 box_size, /* 內存池大小,單位字節 */
U32 blk_size ); /* 內存塊大小, 單位字節 */
函數描述:
函數_init_box8 用於內存池的初始化,初始化時用到的參數都是源自於_declare_box8。
第 1 個參數填寫內存池的首地址。
第 2 個參數填寫內存池的大小,單位字節。
第 3 個參數填寫內存塊大小,單位字節。
使用這個函數要注意以下問題:
1. 強烈建議跟函數_declare_box8 一起使用。用戶不要自己去初始化這個函數,用_declare_box 聲明的
才是最保險的。
2. 如果用戶沒有使用函數_declare_box8 進行定義,那么要保證內存池首地址是 8 字節對齊的。
3. 如果用戶沒有使用函數_declare_box8 進行定義,那么要保證內存池的大小 box_size 至少有 16 個字
節,因為這個 16 個字節是用於將內存塊做成指針鏈表,方便動態申請和釋放。
函數_alloc_box
函數原型:
void *_alloc_box (
void* box_mem ); /* 內存池的首地址 */
函數描述:
函數_alloc_box 用於從首地址是 box_mem 的內存池中申請一個內存塊。
第 1 個參數填寫內存池的首地址。
使用這個函數要注意以下問題:
1. 調用此函數前,一定要使用函數_init_box 或者_init_box8 進行初始化。
2. 函數_alloc_box 支持重入,而且是線程安全的,也即是說用戶可以沒有限制的在主函數和中斷中調用此函數。
函數_free_box
函數原型:
int _free_box (
void* box_mem, /* 內存池首地址 */
void* box ); /* 要釋放的內存塊首地址 */
函數描述:
函數_free_box 用於釋放使用函數_alloc_box 申請的內存塊。
第 1 個參數填寫內存池的首地址。
第 2 個參數填寫要釋放的內存塊首地址。
使用這個函數要注意以下問題:
1. 此函數的第二個參數必須要填寫正確,也就是用戶使用的時候最好配套_alloc_box 一起使用。
2. 函數_free_box 支持重入,而且是線程安全的,也即是說用戶可以沒有限制的在主函數和中斷中調用此函數。
代碼練兵場:
串口打印:
這里有兩點需要說明:
第一,為什么按鍵按下之后,先打印消息隊列的輸出,因為消息處理任務優先級高於按鍵處理任務(如果按鍵處理任務優先級高於消息處理,就會先打印按鍵消息);
第二,在下面函數中采用官方demo的方式:
我們可以使用void *pMsg,然后在wait中給&pMsg,最后打印的時候轉換成自己想要的類型,也可以:
直接給出想要的類型在wait函數中強轉,安富萊就是使用的后者。