1. LiteOS內(nèi)核的內(nèi)存管理
1.1. 內(nèi)存管理
在系統(tǒng)運(yùn)行的過程中,一些內(nèi)存空間大小是不確定的,比如一些數(shù)據(jù)緩沖區(qū),所以系統(tǒng)需要提供內(nèi)存空間的管理能力,用戶可以在使用的時(shí)候申請(qǐng)需要的內(nèi)存空間,使用完畢釋放該空間,以便再次利用。
Huawei LiteOS 的內(nèi)存管理模塊通過對(duì)內(nèi)存的申請(qǐng)/釋放操作,來(lái)管理用戶和OS對(duì)內(nèi)存的使用,使內(nèi)存的利用率和使用效率達(dá)到最優(yōu),同時(shí)最大限度地解決系統(tǒng)的內(nèi)存碎片問題。
1.2. 動(dòng)態(tài)內(nèi)存管理
動(dòng)態(tài)內(nèi)存管理,即在內(nèi)存資源充足的情況下,從系統(tǒng)配置的一塊比較大的連續(xù)內(nèi)存(內(nèi)存池),根據(jù)用戶需求,分配任意大小的內(nèi)存塊。當(dāng)用戶不需要該內(nèi)存塊時(shí),又可以釋放回系統(tǒng)供下一次使用。
與靜態(tài)內(nèi)存相比,動(dòng)態(tài)內(nèi)存管理的好處是按需分配,缺點(diǎn)是內(nèi)存池中容易出現(xiàn)碎片。
LiteOS動(dòng)態(tài)內(nèi)存支持 DLINK 和 BEST LITTLE 兩種標(biāo)準(zhǔn)算法。
1.2.1. DLINK 動(dòng)態(tài)內(nèi)存管理算法
DLINK動(dòng)態(tài)內(nèi)存管理結(jié)構(gòu)如下圖所示:
第一部分
堆內(nèi)存(也稱內(nèi)存池)的起始地址及堆區(qū)域總大小。
第二部分
本身是一個(gè)數(shù)組,每個(gè)元素是一個(gè)雙向鏈表,所有free節(jié)點(diǎn)的控制頭都會(huì)被分類掛在這個(gè)數(shù)組的雙向鏈表中。
第三部分
占用內(nèi)存池極大部分的空間,是用于存放各節(jié)點(diǎn)的實(shí)際區(qū)域。
1.2.2. BEST LITTLE 算法(重點(diǎn))
LiteOS 的動(dòng)態(tài)內(nèi)存分配支持最佳適配算法,即 BEST LITTLE,每次分配時(shí)選擇內(nèi)存池中最小最適合的內(nèi)存塊進(jìn)行分配。
LiteOS 動(dòng)態(tài)內(nèi)存管理在最佳適配算法的基礎(chǔ)上加入了 SLAB 機(jī)制,用于分配固定大小的內(nèi)存塊,進(jìn)而減小產(chǎn)生內(nèi)存碎片的可能性。
LiteOS 內(nèi)存管理中的 SLAB 機(jī)制支持可配置的 SLAB CLASS 數(shù)目及每個(gè) CLASS 的最大空間。
現(xiàn)以 SLAB CLASS 數(shù)目為 4,每個(gè) CLASS 的最大空間為 512 字節(jié)為例說(shuō)明 SLAB 機(jī)制:
在內(nèi)存池中共有 4 個(gè) SLAB CLASS,每個(gè) SLAB CLASS 的總共可分配大小為 512 字節(jié),第一個(gè) SLAB CLASS 被分為 32 個(gè)16 字節(jié)的 SLAB 塊,第二個(gè) SLAB CLASS 被分為 16 個(gè) 3 2字節(jié)的 SLAB 塊,第三個(gè) SLAB CLASS 被分為 8 個(gè) 64 字節(jié)的 SLAB 塊,第四個(gè) SLAB CLASS 被分為 4 個(gè) 128 字節(jié)的 SLAB 塊。這 4 個(gè) SLAB CLASS 是從內(nèi)存池中按照最佳適配算法分配出來(lái)的。
初始化內(nèi)存管理時(shí),首先初始化內(nèi)存池,然后在初始化后的內(nèi)存池中按照最佳適配算法申請(qǐng) 4 個(gè) SLAB CLASS,再逐個(gè)按照 SLAB 內(nèi)存管理機(jī)制初始化 4 個(gè) SLAB CLASS。
每次申請(qǐng)內(nèi)存時(shí),先在滿足申請(qǐng)大小的最佳 SLAB CLASS 中申請(qǐng),(比如用戶申請(qǐng) 20 字節(jié)內(nèi)存,就在 SLAB 塊大小為 32 字節(jié)的 SLAB CLASS 中申請(qǐng)),如果申請(qǐng)成功,就將 SLAB 內(nèi)存塊整塊返回給用戶,釋放時(shí)整塊回收。如果滿足條件的 SLAB CLASS 中已無(wú)可以分配的內(nèi)存塊,則繼續(xù)向內(nèi)存池按照最佳適配算法申請(qǐng)。需要注意的是,如果當(dāng)前的 SLAB CLASS 中無(wú)可用 SLAB 塊了,則直接向內(nèi)存池申請(qǐng),而不會(huì)繼續(xù)向有著更大 SLAB 塊空間的 SLAB CLASS 申請(qǐng)。
釋放內(nèi)存時(shí),先檢查釋放的內(nèi)存塊是否屬于 SLAB CLASS,如果是 SLAB CLASS 的內(nèi)存塊,則還回對(duì)應(yīng)的 SLAB CLASS 中,否則還回內(nèi)存池中。
1.2.3. 兩種動(dòng)態(tài)內(nèi)存管理方法的選擇
LiteOS動(dòng)態(tài)內(nèi)存管理的方法使用宏定義的方法使能,在用戶工程目錄下的OS_CONFIG中的target_config.h文件中配置。
在該文件中,找到下面這兩項(xiàng)宏定義,置為 YES 則表示使能:
開啟BEST LITTLE 算法
#define?LOSCFG_MEMORY_BESTFIT?YES
開啟SLAB機(jī)制
#define?LOSCFG_KERNEL_MEM_SLAB?YES
1.3. 動(dòng)態(tài)內(nèi)存管理的應(yīng)用場(chǎng)景
內(nèi)存管理的主要工作是動(dòng)態(tài)的劃分并管理用戶分配好的內(nèi)存區(qū)間。
動(dòng)態(tài)內(nèi)存管理主要是在用戶需要使用大小不等的內(nèi)存塊的場(chǎng)景中使用。當(dāng)用戶需要分配內(nèi)存時(shí),可以通過操作系統(tǒng)的動(dòng)態(tài)內(nèi)存申請(qǐng)函數(shù)索取指定大小內(nèi)存塊,一旦使用完畢,通過動(dòng)態(tài)內(nèi)存釋放函數(shù)歸還所占用內(nèi)存,使之可以重復(fù)使用。
2. 動(dòng)態(tài)內(nèi)存管理API
Huawei LiteOS 系統(tǒng)中的內(nèi)存管理模塊管理系統(tǒng)的內(nèi)存資源,主要提供內(nèi)存的初始化、分配以及釋放功能。
Huawei LiteOS 系統(tǒng)中提供的內(nèi)存管理 API 都是以 LOS 開頭,但是這些 API 使用起來(lái)比較復(fù)雜,所以本文中我們使用 Huawei IoT Link SDK 提供的統(tǒng)一API接口進(jìn)行實(shí)驗(yàn),這些接口底層已經(jīng)使用 LiteOS 提供的API實(shí)現(xiàn),對(duì)用戶而言更為簡(jiǎn)潔,API列表如下:
osal的api接口聲明在
相關(guān)的接口定義在osal.c中,基于LiteOS的接口實(shí)現(xiàn)在 liteos_imp.c文件中:
接口名 | 功能描述 |
---|---|
osal_malloc | 按字節(jié)申請(qǐng)分配動(dòng)態(tài)內(nèi)存空間 |
osal_free | 釋放已經(jīng)分配的動(dòng)態(tài)內(nèi)存空間 |
osal_zalloc | 按字節(jié)申請(qǐng)分配動(dòng)態(tài)內(nèi)存空間,分配成功則初始化這塊內(nèi)存所有值為0 |
osal_realloc | 重新申請(qǐng)分配動(dòng)態(tài)內(nèi)存空間 |
osal_calloc | 申請(qǐng)分配num個(gè)長(zhǎng)度為size的動(dòng)態(tài)內(nèi)存空間 |
無(wú)論選擇使用哪種動(dòng)態(tài)內(nèi)存管理算法,都使用該API。
2.1. osal_malloc
osal_malloc接口用于按字節(jié)申請(qǐng)分配動(dòng)態(tài)內(nèi)存空間,其接口原型如下:
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; }
該接口的參數(shù)說(shuō)明如下表:
參數(shù) | 描述 |
---|---|
size | 申請(qǐng)分配的內(nèi)存大小,單位Byte |
返回值 | 分配成功 - 返回內(nèi)存塊指針 |
? | 分配失敗 - 返回NULL |
?
2.2. osal_free
osal_free接口用于釋放已經(jīng)分配的動(dòng)態(tài)內(nèi)存空間,其接口原型如下:
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; }
內(nèi)存塊free之后,記得使內(nèi)存塊指針為NULL,否則會(huì)成為野指針!
該接口的參數(shù)說(shuō)明如下表:
參數(shù) | 描述 |
---|---|
addr | 動(dòng)態(tài)分配內(nèi)存空間的指針 |
返回值 | 無(wú)返回值 |
?
2.3. osal_zalloc
osal_zalloc接口用于按字節(jié)申請(qǐng)分配動(dòng)態(tài)內(nèi)存空間,分配成功則初始化這塊內(nèi)存所有值為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; }
該接口的參數(shù)說(shuō)明如下表:
參數(shù) | 描述 |
---|---|
size | 申請(qǐng)分配的內(nèi)存大小,單位Byte |
返回值 | 分配成功 - 返回內(nèi)存塊指針 |
? | 分配失敗 - 返回NULL |
?
2.4. osal_realloc
osal_realloc接口用于重新申請(qǐng)分配動(dòng)態(tài)內(nèi)存空間,其接口原型如下:
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; }
該接口的參數(shù)說(shuō)明如下表:
參數(shù) | 描述 |
---|---|
ptr | 已經(jīng)分配了內(nèi)存空間的指針 |
newsize | 申請(qǐng)分配的新的內(nèi)存大小,單位Byte |
返回值 | 分配成功 - 返回內(nèi)存塊指針 |
? | 分配失敗 - 返回NULL |
?
2.5. osal_calloc
osal_calloc接口用于申請(qǐng)分配num個(gè)長(zhǎng)度為size的動(dòng)態(tài)內(nèi)存空間,其接口原型如下:
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; }
該接口的參數(shù)說(shuō)明如下表:
參數(shù) | 描述 |
---|---|
n | 申請(qǐng)分配內(nèi)存塊的數(shù)目 |
size | 申請(qǐng)分配的每個(gè)內(nèi)存塊的內(nèi)存大小,單位Byte |
返回值 | 分配成功 - 返回內(nèi)存塊指針 |
? | 分配失敗 - 返回NULL |
?
3. 動(dòng)手實(shí)驗(yàn) —— 測(cè)試動(dòng)態(tài)內(nèi)存分配的最大字節(jié)
實(shí)驗(yàn)內(nèi)容
本實(shí)驗(yàn)中將創(chuàng)建一個(gè)任務(wù),從最小字節(jié)開始,不停的申請(qǐng)分配內(nèi)存,釋放分配的內(nèi)存,直到申請(qǐng)失敗,串口終端中觀察可以申請(qǐng)到的最大字節(jié)。
實(shí)驗(yàn)代碼
首先打開上一篇使用的 HelloWorld 工程,基于此工程進(jìn)行實(shí)驗(yàn)。
在Demo文件夾右擊,新建文件夾osal_kernel_demo用于存放內(nèi)核的實(shí)驗(yàn)文件(如果已有請(qǐng)忽略這一步)。
接下來(lái)在此文件夾中新建一個(gè)實(shí)驗(yàn)文件?osal_mem_demo.c,開始編寫代碼:
/*?使用osal接口需要包含該頭文件?*/#include?
編寫完成之后,要將我們編寫的 osal_mem_demo.c文件添加到makefile中,加入整個(gè)工程的編譯:
這里有個(gè)較為簡(jiǎn)單的方法,直接修改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中進(jìn)行編譯。
那么,如何配置 CONFIG_USER_DEMO 宏定義呢?在工程根目錄下的.sdkconfig文件中的末尾即可配置:
因?yàn)槲覀冃薷牧薽k配置文件,所以點(diǎn)擊重新編譯按鈕進(jìn)行編譯,編譯完成后點(diǎn)擊下載按鈕燒錄程序。
實(shí)驗(yàn)現(xiàn)象
程序燒錄之后,即可看到程序已經(jīng)開始運(yùn)行,在串口終端中可看到實(shí)驗(yàn)的輸出內(nèi)容:
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!
評(píng)論
查看更多