在系統運行的過程當中,一些內存空間大小是不肯定的,好比一些數據緩衝區,因此係統須要提供內存空間的管理能力,用戶能夠在使用的時候申請須要的內存空間,使用完畢釋放該空間,以便再次利用。算法
Huawei LiteOS 的內存管理模塊經過對內存的申請/釋放操做,來管理用戶和OS對內存的使用,使內存的利用率和使用效率達到最優,同時最大限度地解決系統的內存碎片問題。shell
動態內存管理,即在內存資源充足的狀況下,從系統配置的一塊比較大的連續內存(內存池),根據用戶需求,分配任意大小的內存塊。當用戶不須要該內存塊時,又能夠釋放回系統供下一次使用。segmentfault
與靜態內存相比,動態內存管理的好處是按需分配,缺點是內存池中容易出現碎片。api
LiteOS動態內存支持 DLINK 和 BEST LITTLE 兩種標準算法。數組
DLINK動態內存管理結構以下圖所示:微信
堆內存(也稱內存池)的起始地址及堆區域總大小。app
自己是一個數組,每一個元素是一個雙向鏈表,全部free節點的控制頭都會被分類掛在這個數組的雙向鏈表中。函數
佔用內存池極大部分的空間,是用於存放各節點的實際區域。測試
LiteOS 的動態內存分配支持最佳適配算法,即 BEST LITTLE,每次分配時選擇內存池中最小最適合的內存塊進行分配。ui
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 中,不然還回內存池中。
LiteOS動態內存管理的方法使用宏定義的方法使能,在用戶工程目錄下的OS_CONFIG
中的target_config.h
文件中配置。
在該文件中,找到下面這兩項宏定義,置爲 YES 則表示使能:
#define LOSCFG_MEMORY_BESTFIT YES
#define LOSCFG_KERNEL_MEM_SLAB YES
內存管理的主要工做是動態的劃分並管理用戶分配好的內存區間。
動態內存管理主要是在用戶須要使用大小不等的內存塊的場景中使用。當用戶須要分配內存時,能夠經過操做系統的動態內存申請函數索取指定大小內存塊,一旦使用完畢,經過動態內存釋放函數歸還所佔用內存,使之能夠重複使用。
Huawei LiteOS 系統中的內存管理模塊管理系統的內存資源,主要提供內存的初始化、分配以及釋放功能。
Huawei LiteOS 系統中提供的內存管理 API 都是以 LOS 開頭,可是這些 API 使用起來比較複雜,因此本文中咱們使用 Huawei IoT Link SDK 提供的統一API接口進行實驗,這些接口底層已經使用 LiteOS 提供的API實現,對用戶而言更爲簡潔,API列表以下:
osal的api接口聲明在<osal.h>中,使用相關的接口須要包含該頭文件,關於函數的詳細參數請參考該頭文件的聲明。
相關的接口定義在osal.c中,基於LiteOS的接口實如今 liteos_imp.c文件中:
接口名 | 功能描述 |
---|---|
osal_malloc | 按字節申請分配動態內存空間 |
osal_free | 釋放已經分配的動態內存空間 |
osal_zalloc | 按字節申請分配動態內存空間,分配成功則初始化這塊內存全部值爲0 |
osal_realloc | 從新申請分配動態內存空間 |
osal_calloc | 申請分配num個長度爲size的動態內存空間 |
不管選擇使用哪一種動態內存管理算法,都使用該API。
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 |
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 | 動態分配內存空間的指針 |
返回值 | 無返回值 |
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 |
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 |
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 |
本實驗中將建立一個任務,從最小字節開始,不停的申請分配內存,釋放分配的內存,直到申請失敗,串口終端中觀察能夠申請到的最大字節。
首先打開上一篇使用的 HelloWorld 工程,基於此工程進行實驗。
在Demo文件夾右擊,新建文件夾osal_kernel_demo
用於存放內核的實驗文件(若是已有請忽略這一步)。
接下來在此文件夾中新建一個實驗文件 osal_mem_demo.c
,開始編寫代碼:
/* 使用osal接口須要包含該頭文件 */ #include <osal.h> /* 任務入口函數 */ static int mem_access_task_entry() { uint32_t i = 0; //循環變量 size_t mem_size; //申請的內存塊大小 uint8_t* mem_ptr = NULL; //內存塊指針 while (1) { /* 每次循環將申請內存的大小擴大一倍 */ mem_size = 1 << i++; /* 嘗試申請分配內存 */ mem_ptr = osal_malloc(mem_size); /* 判斷是否申請成功 */ if(mem_ptr != NULL) { /* 申請成功,打印信息 */ printf("access %d bytes memory success!\r\n", mem_size); /* 釋放申請的內存,便於下次申請 */ osal_free(mem_ptr); /* 將內存塊指針置爲NULL,避免稱爲野指針 */ mem_ptr = NULL; printf("free memory success!\r\n"); } else { /* 申請失敗,打印信息,任務結束 */ printf("access %d bytes memory failed!\r\n", mem_size); return 0; } } } /* 標準demo啓動函數,函數名不要修改,不然會影響下一步實驗 */ int standard_app_demo_main() { /* 建立任務,任務優先級爲11,shell任務的優先級爲10 */ osal_task_create("mem_access_task",mem_access_task_entry,NULL,0x400,NULL,11); return 0; }
編寫完成以後,要將咱們編寫的 osal_mem_demo.c文件添加到makefile中,加入整個工程的編譯:
這裏有個較爲簡單的方法,直接修改Demo文件夾下的user_demo.mk配置文件,添加以下代碼:
#example for osal_mem_demo ifeq ($(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 SHELL LiteOS:/>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!
能夠看到,系統啓動後,首先打印版本號,串口shell的優先級爲10,最早打印shell信息,接下來內存申請任務建立開始執行,在該芯片上最大能申請的空間爲 16384
字節。