1. LiteOS內核的內存管理
1.1. 內存管理
在系統運行的過程中,一些內存空間大小是不確定的,比如一些數據緩沖區,所以系統需要提供內存空間的管理能力,用戶可以在使用的時候申請需要的內存空間,使用完畢釋放該空間,以便再次利用。
Huawei LiteOS 的內存管理模塊通過對內存的申請/釋放操作,來管理用戶和OS對內存的使用,使內存的利用率和使用效率達到最優,同時最大限度地解決系統的內存碎片問題。
1.2. 動態內存管理
動態內存管理,即在內存資源充足的情況下,從系統配置的一塊比較大的連續內存(內存池),根據用戶需求,分配任意大小的內存塊。當用戶不需要該內存塊時,又可以釋放回系統供下一次使用。
與靜態內存相比,動態內存管理的好處是按需分配,缺點是內存池中容易出現碎片。
LiteOS動態內存支持 DLINK 和 BEST LITTLE 兩種標準算法。
1.2.1. DLINK 動態內存管理算法
DLINK動態內存管理結構如下圖所示:
第一部分
堆內存(也稱內存池)的起始地址及堆區域總大小。
第二部分
本身是一個數組,每個元素是一個雙向鏈表,所有free節點的控制頭都會被分類掛在這個數組的雙向鏈表中。
第三部分
占用內存池極大部分的空間,是用于存放各節點的實際區域。
1.2.2. BEST LITTLE 算法(重點)
LiteOS 的動態內存分配支持最佳適配算法,即 BEST LITTLE,每次分配時選擇內存池中最小最適合的內存塊進行分配。
LiteOS 動態內存管理在最佳適配算法的基礎上加入了 SLAB 機制,用于分配固定大小的內存塊,進而減小產生內存碎片的可能性。
LiteOS 內存管理中的 SLAB 機制支持可配置的 SLAB CLASS 數目及每個 CLASS 的最大空間。
現以 SLAB CLASS 數目為 4,每個 CLASS 的最大空間為 512 字節為例說明 SLAB 機制:
在內存池中共有 4 個 SLAB CLASS,每個 SLAB CLASS 的總共可分配大小為 512 字節,第一個 SLAB CLASS 被分為 32 個16 字節的 SLAB 塊,第二個 SLAB CLASS 被分為 16 個 3 2字節的 SLAB 塊,第三個 SLAB CLASS 被分為 8 個 64 字節的 SLAB 塊,第四個 SLAB CLASS 被分為 4 個 128 字節的 SLAB 塊。這 4 個 SLAB CLASS 是從內存池中按照最佳適配算法分配出來的。
初始化內存管理時,首先初始化內存池,然后在初始化后的內存池中按照最佳適配算法申請 4 個 SLAB CLASS,再逐個按照 SLAB 內存管理機制初始化 4 個 SLAB CLASS。
每次申請內存時,先在滿足申請大小的最佳 SLAB CLASS 中申請,(比如用戶申請 20 字節內存,就在 SLAB 塊大小為 32 字節的 SLAB CLASS 中申請),如果申請成功,就將 SLAB 內存塊整塊返回給用戶,釋放時整塊回收。如果滿足條件的 SLAB CLASS 中已無可以分配的內存塊,則繼續向內存池按照最佳適配算法申請。需要注意的是,如果當前的 SLAB CLASS 中無可用 SLAB 塊了,則直接向內存池申請,而不會繼續向有著更大 SLAB 塊空間的 SLAB CLASS 申請。
釋放內存時,先檢查釋放的內存塊是否屬于 SLAB CLASS,如果是 SLAB CLASS 的內存塊,則還回對應的 SLAB CLASS 中,否則還回內存池中。
1.2.3. 兩種動態內存管理方法的選擇
LiteOS動態內存管理的方法使用宏定義的方法使能,在用戶工程目錄下的OS_CONFIG中的target_config.h文件中配置。
在該文件中,找到下面這兩項宏定義,置為 YES 則表示使能:
開啟BEST LITTLE 算法
#define?LOSCFG_MEMORY_BESTFIT?YES
開啟SLAB機制
#define?LOSCFG_KERNEL_MEM_SLAB?YES
1.3. 動態內存管理的應用場景
內存管理的主要工作是動態的劃分并管理用戶分配好的內存區間。
動態內存管理主要是在用戶需要使用大小不等的內存塊的場景中使用。當用戶需要分配內存時,可以通過操作系統的動態內存申請函數索取指定大小內存塊,一旦使用完畢,通過動態內存釋放函數歸還所占用內存,使之可以重復使用。
2. 動態內存管理API
Huawei LiteOS 系統中的內存管理模塊管理系統的內存資源,主要提供內存的初始化、分配以及釋放功能。
Huawei LiteOS 系統中提供的內存管理 API 都是以 LOS 開頭,但是這些 API 使用起來比較復雜,所以本文中我們使用 Huawei IoT Link SDK 提供的統一API接口進行實驗,這些接口底層已經使用 LiteOS 提供的API實現,對用戶而言更為簡潔,API列表如下:
osal的api接口聲明在
相關的接口定義在osal.c中,基于LiteOS的接口實現在 liteos_imp.c文件中:
接口名 | 功能描述 |
---|---|
osal_malloc | 按字節申請分配動態內存空間 |
osal_free | 釋放已經分配的動態內存空間 |
osal_zalloc | 按字節申請分配動態內存空間,分配成功則初始化這塊內存所有值為0 |
osal_realloc | 重新申請分配動態內存空間 |
osal_calloc | 申請分配num個長度為size的動態內存空間 |
無論選擇使用哪種動態內存管理算法,都使用該API。
2.1. osal_malloc
osal_malloc接口用于按字節申請分配動態內存空間,其接口原型如下:
void?*osal_malloc(size_t?size){????void?*ret?=?NULL;????if((NULL?!=?s_os_cb)?&&(NULL?!=?s_os_cb->ops)?&&(NULL?!=?s_os_cb->ops->malloc)) ????{ ????????ret?=?s_os_cb->ops->malloc(size); ????}????return?ret; }
該接口的參數說明如下表:
參數 | 描述 |
---|---|
size | 申請分配的內存大小,單位Byte |
返回值 | 分配成功 - 返回內存塊指針 |
? | 分配失敗 - 返回NULL |
?
2.2. osal_free
osal_free接口用于釋放已經分配的動態內存空間,其接口原型如下:
void??osal_free(void?*addr){????if((NULL?!=?s_os_cb)?&&(NULL?!=?s_os_cb->ops)?&&(NULL?!=?s_os_cb->ops->free)) ????{ ????????s_os_cb->ops->free(addr); ????}????return; }
內存塊free之后,記得使內存塊指針為NULL,否則會成為野指針!
該接口的參數說明如下表:
參數 | 描述 |
---|---|
addr | 動態分配內存空間的指針 |
返回值 | 無返回值 |
?
2.3. osal_zalloc
osal_zalloc接口用于按字節申請分配動態內存空間,分配成功則初始化這塊內存所有值為0,其接口原型如下:
void?*osal_zalloc(size_t?size){????void?*ret?=?NULL;????if((NULL?!=?s_os_cb)?&&(NULL?!=?s_os_cb->ops)?&&(NULL?!=?s_os_cb->ops->malloc)) ????{ ????????ret?=?s_os_cb->ops->malloc(size);????????if(NULL?!=?ret) ????????{????????????memset(ret,0,size); ????????} ????}????return?ret; }
該接口的參數說明如下表:
參數 | 描述 |
---|---|
size | 申請分配的內存大小,單位Byte |
返回值 | 分配成功 - 返回內存塊指針 |
? | 分配失敗 - 返回NULL |
?
2.4. osal_realloc
osal_realloc接口用于重新申請分配動態內存空間,其接口原型如下:
void?*osal_realloc(void?*ptr,size_t?newsize){????void?*ret?=?NULL;????if((NULL?!=?s_os_cb)?&&(NULL?!=?s_os_cb->ops)?&&(NULL?!=?s_os_cb->ops->realloc)) ????{ ????????ret?=?s_os_cb->ops->realloc(ptr,newsize); ????}????return?ret; }
該接口的參數說明如下表:
參數 | 描述 |
---|---|
ptr | 已經分配了內存空間的指針 |
newsize | 申請分配的新的內存大小,單位Byte |
返回值 | 分配成功 - 返回內存塊指針 |
? | 分配失敗 - 返回NULL |
?
2.5. osal_calloc
osal_calloc接口用于申請分配num個長度為size的動態內存空間,其接口原型如下:
void?*osal_calloc(size_t?n,?size_t?size){????void?*p?=?osal_malloc(n?*?size);????if(NULL?!=?p) ????{????????memset(p,?0,?n?*?size); ????}????return?p; }
該接口的參數說明如下表:
參數 | 描述 |
---|---|
n | 申請分配內存塊的數目 |
size | 申請分配的每個內存塊的內存大小,單位Byte |
返回值 | 分配成功 - 返回內存塊指針 |
? | 分配失敗 - 返回NULL |
?
3. 動手實驗 —— 測試動態內存分配的最大字節
實驗內容
本實驗中將創建一個任務,從最小字節開始,不停的申請分配內存,釋放分配的內存,直到申請失敗,串口終端中觀察可以申請到的最大字節。
實驗代碼
首先打開上一篇使用的 HelloWorld 工程,基于此工程進行實驗。
在Demo文件夾右擊,新建文件夾osal_kernel_demo用于存放內核的實驗文件(如果已有請忽略這一步)。
接下來在此文件夾中新建一個實驗文件?osal_mem_demo.c,開始編寫代碼:
/*?使用osal接口需要包含該頭文件?*/#include?
編寫完成之后,要將我們編寫的 osal_mem_demo.c文件添加到makefile中,加入整個工程的編譯:
這里有個較為簡單的方法,直接修改Demo文件夾下的user_demo.mk配置文件,添加如下代碼:
#example?for?osal_mem_demoifeq?($(CONFIG_USER_DEMO),?"osal_mem_demo")???? ????user_demo_src??=?${wildcard?$(TOP_DIR)/targets/STM32L431_BearPi/Demos/osal_kernel_demo/osal_mem_demo.c} endif
添加位置如圖:
這段代碼的意思是:
如果 CONFIG_USER_DEMO 宏定義的值是osal_mem_demo,則將osal_mem_demo.c文件加入到makefile中進行編譯。
那么,如何配置 CONFIG_USER_DEMO 宏定義呢?在工程根目錄下的.sdkconfig文件中的末尾即可配置:
因為我們修改了mk配置文件,所以點擊重新編譯按鈕進行編譯,編譯完成后點擊下載按鈕燒錄程序。
實驗現象
程序燒錄之后,即可看到程序已經開始運行,在串口終端中可看到實驗的輸出內容:
linkmain:V1.2.1?AT?11:30:59?ON?Nov?28?2019 WELCOME?TO?IOT_LINK?SHELLLiteOS:/>access?1?bytes?memory?success! free?memory?success! access?2?bytes?memory?success! free?memory?success! access?4?bytes?memory?success! free?memory?success! access?8?bytes?memory?success! free?memory?success! access?16?bytes?memory?success! free?memory?success! access?32?bytes?memory?success! free?memory?success! access?64?bytes?memory?success! free?memory?success! access?128?bytes?memory?success! free?memory?success! access?256?bytes?memory?success! free?memory?success! access?512?bytes?memory?success! free?memory?success! access?1024?bytes?memory?success! free?memory?success! access?2048?bytes?memory?success! free?memory?success! access?4096?bytes?memory?success! free?memory?success! access?8192?bytes?memory?success! free?memory?success! access?16384?bytes?memory?success! free?memory?success! access?32768?bytes?memory?failed!
評論
查看更多