鴻蒙輕內(nèi)核A核源碼分析系列三物理內(nèi)存之一
想了解更多內(nèi)容,請(qǐng)?jiān)L問(wèn):
51CTO和華為官方合作共建的鴻蒙技術(shù)社區(qū)
從本篇開(kāi)始,我們分析下鴻蒙輕內(nèi)核A核的內(nèi)存管理部分,包括物理內(nèi)存、虛擬內(nèi)存、虛擬映射等部分。物理內(nèi)存(Physical memory)是指通過(guò)物理內(nèi)存條而獲得的內(nèi)存空間,相對(duì)應(yīng)的概念是虛擬內(nèi)存(Virtual memory)。虛擬內(nèi)存使得應(yīng)用進(jìn)程認(rèn)為它擁有一個(gè)連續(xù)完整的內(nèi)存地址空間,而通常是通過(guò)虛擬內(nèi)存和物理內(nèi)存的映射對(duì)應(yīng)著多個(gè)物理內(nèi)存頁(yè)。本文我們先來(lái)熟悉下OpenHarmony鴻蒙輕內(nèi)核提供的物理內(nèi)存(Physical memory)管理模塊。
本文中所涉及的源碼,以O(shè)penHarmony LiteOS-A內(nèi)核為例,均可以在開(kāi)源站點(diǎn)https://gitee.com/openharmony/kernel_liteos_a 獲取。如果涉及開(kāi)發(fā)板,則默認(rèn)以hispark_taurus為例。
我們首先了解了物理內(nèi)存管理的結(jié)構(gòu)體,接著閱讀了物理內(nèi)存如何初始化,然后分析了物理內(nèi)存的申請(qǐng)、釋放和查詢(xún)等操作接口的源代碼。
1、物理內(nèi)存結(jié)構(gòu)體介紹
1.1、物理內(nèi)存頁(yè)LosVmPage
鴻蒙輕內(nèi)核A核的物理內(nèi)存采用了段頁(yè)式管理,每個(gè)物理內(nèi)存段被分割為物理內(nèi)存頁(yè)。在頭文件kernel/base/include/los_vm_page.h中定義了物理內(nèi)存頁(yè)結(jié)構(gòu)體,以及內(nèi)存頁(yè)數(shù)組g_vmPageArray及數(shù)組大小g_vmPageArraySize。物理內(nèi)存頁(yè)結(jié)構(gòu)體LosVmPage可以和物理內(nèi)存頁(yè)一一對(duì)應(yīng),也可以對(duì)應(yīng)多個(gè)連續(xù)的內(nèi)存頁(yè),此時(shí)使用nPages指定內(nèi)存頁(yè)的數(shù)量。
- typedef struct VmPage {
- LOS_DL_LIST node; /**< 物理內(nèi)存頁(yè)節(jié)點(diǎn),掛在VmFreeList空閑內(nèi)存頁(yè)鏈表上 */
- PADDR_T physAddr; /**< 物理內(nèi)存頁(yè)內(nèi)存開(kāi)始地址*/
- Atomic refCounts; /**< 物理內(nèi)存頁(yè)引用計(jì)數(shù) */
- UINT32 flags; /**< 物理內(nèi)存頁(yè)標(biāo)記 */
- UINT8 order; /**< 物理內(nèi)存頁(yè)所在的鏈表數(shù)組的索引,總共有9個(gè)鏈表 */
- UINT8 segID; /**< 物理內(nèi)存頁(yè)所在的物理內(nèi)存段的編號(hào) */
- UINT16 nPages; /**< 連續(xù)物理內(nèi)存頁(yè)的數(shù)量 */
- } LosVmPage;
- extern LosVmPage *g_vmPageArray;
- extern size_t g_vmPageArraySize;
在文件kernel\base\include\los_vm_common.h中定義了內(nèi)存頁(yè)的大小、掩碼和邏輯位移值,可以看出每個(gè)內(nèi)存頁(yè)的大小為4KiB。
- #ifndef PAGE_SIZE
- #define PAGE_SIZE (0x1000U)
- #endif
- #define PAGE_MASK (~(PAGE_SIZE - 1))
- #define PAGE_SHIFT (12)
1.2、物理內(nèi)存段LosVmPhysSeg
在文件kernel/base/include/los_vm_phys.h中定義了物理內(nèi)存段LosVmPhysSeg等幾個(gè)結(jié)構(gòu)體。該文件的部分代碼如下所示。⑴處的宏是物理內(nèi)存伙伴算法中空閑內(nèi)存頁(yè)節(jié)點(diǎn)鏈表數(shù)組的大小,VM_PHYS_SEG_MAX表示系統(tǒng)支持的物理內(nèi)存段的數(shù)量。⑵處的結(jié)構(gòu)體用于伙伴算法中空閑內(nèi)存頁(yè)節(jié)點(diǎn)鏈表數(shù)組的元素類(lèi)型,除了記錄雙向鏈表,還維護(hù)鏈表上節(jié)點(diǎn)數(shù)量。⑶就是我們要介紹的物理內(nèi)存段,包含開(kāi)始地址,大小,內(nèi)存頁(yè)基地址,空閑內(nèi)存頁(yè)節(jié)點(diǎn)鏈表數(shù)組,LRU鏈表數(shù)組等成員。
- ⑴ #define VM_LIST_ORDER_MAX 9
- #define VM_PHYS_SEG_MAX 32
- ⑵ struct VmFreeList {
- LOS_DL_LIST node; // 空閑物理內(nèi)存頁(yè)節(jié)點(diǎn)
- UINT32 listCnt; // 空閑物理內(nèi)存頁(yè)節(jié)點(diǎn)數(shù)量
- };
- ⑶ typedef struct VmPhysSeg {
- PADDR_T start; /* 物理內(nèi)存段的開(kāi)始地址 */
- size_t size; /* 物理內(nèi)存段的大小,bytes */
- LosVmPage *pageBase; /* 物理內(nèi)存段第一個(gè)物理內(nèi)存頁(yè)結(jié)構(gòu)體地址 */
- SPIN_LOCK_S freeListLock; /* 伙伴算法雙向鏈表自旋鎖 */
- struct VmFreeList freeList[VM_LIST_ORDER_MAX]; /* 空閑物理內(nèi)存頁(yè)的伙伴雙向鏈表 */
- SPIN_LOCK_S lruLock; /* LRU雙向鏈表自旋鎖 */
- size_t lruSize[VM_NR_LRU_LISTS]; /* LRU大小 */
- LOS_DL_LIST lruList[VM_NR_LRU_LISTS];/* LRU雙向鏈表 */
- } LosVmPhysSeg;
- struct VmPhysArea {
- PADDR_T start; // 物理內(nèi)存區(qū)開(kāi)始地址
- size_t size; // 物理內(nèi)存區(qū)大小
- };
在kernel/base/vm/los_vm_phys.c文件中定義了物理內(nèi)存區(qū)數(shù)組g_physArea[],如下代碼所示,其中SYS_MEM_BASE為DDR_MEM_ADDR的宏名稱(chēng),DDR_MEM_ADDR和SYS_MEM_SIZE_DEFAULT定義在文件./device/hisilicon/hispark_taurus/sdk_liteos/board/target_config.h中,表示開(kāi)發(fā)板相關(guān)的物理內(nèi)存地址和大小。
- STATIC struct VmPhysArea g_physArea[] = {
- {
- .start = SYS_MEM_BASE,
- .size = SYS_MEM_SIZE_DEFAULT,
- },
- };
看下物理內(nèi)存區(qū)VmPhysArea和物理內(nèi)存段的LosVmPhysSeg區(qū)別,前者信息教少,主要記錄開(kāi)始地址和大小,為一塊物理內(nèi)存的最簡(jiǎn)單描述;后者除了物理內(nèi)存塊開(kāi)始地址和大小,還維護(hù)物理頁(yè)開(kāi)始地址,空閑物理頁(yè)伙伴鏈表,LRU鏈表,相應(yīng)的自旋鎖等信息。
上面提到了伙伴算法,先看下伙伴算法的示意圖,如下。每個(gè)物理內(nèi)存段都分割為一個(gè)一個(gè)的內(nèi)存頁(yè),空閑的內(nèi)存頁(yè)掛載在空閑內(nèi)存頁(yè)節(jié)點(diǎn)鏈表上。共有9個(gè)空閑內(nèi)存頁(yè)節(jié)點(diǎn)鏈表,這些鏈表組成鏈表數(shù)組。第一個(gè)鏈表上的內(nèi)存頁(yè)節(jié)點(diǎn)大小為1個(gè)內(nèi)存頁(yè),第二個(gè)鏈表上的內(nèi)存頁(yè)節(jié)點(diǎn)大小為2個(gè)內(nèi)存頁(yè),第三個(gè)鏈表上的內(nèi)存頁(yè)節(jié)點(diǎn)大小為4個(gè)內(nèi)存頁(yè),依次下去,第9個(gè)鏈表上的內(nèi)存頁(yè)節(jié)點(diǎn)大小為2^8個(gè)內(nèi)存頁(yè)。申請(qǐng)內(nèi)存、釋放內(nèi)存時(shí)會(huì)操作這些空閑內(nèi)存頁(yè)節(jié)點(diǎn)鏈表,后文詳細(xì)分析。

1.3、物理內(nèi)存伙伴位圖
上文提到伙伴算法,還需要了解下伙伴位圖。在伙伴算法中,每個(gè)鏈表的索引都對(duì)應(yīng)一個(gè)位圖。 位圖的某位對(duì)應(yīng)于兩個(gè)伙伴塊,為1就表示其中一塊忙,為0表示兩塊都閑或都在使用 。系統(tǒng)每次分配和回收伙伴塊時(shí)都要對(duì)它們的伙伴位 跟1進(jìn)行異或運(yùn)算 。所謂異或是指剛開(kāi)始時(shí),兩個(gè)伙伴塊都空閑,它們的伙伴位為0,如果其中一塊被使用,異或后得1;如果另一塊也被使用,異或后得0;如果前面一塊回收了異或后得1;如果另一塊也回收了異或后得0。位圖用于在釋放內(nèi)存頁(yè)塊時(shí),判斷兩塊內(nèi)存是否屬于地址連續(xù)的伙伴內(nèi)存塊。
在文件kernel/base/include/los_vm_phys.h中定義了2個(gè)比較重要的和伙伴位圖相關(guān)的宏,如下。⑴處的宏VM_ORDER_TO_PHYS(order)表示對(duì)應(yīng)每個(gè)空閑鏈表都有一個(gè)位來(lái)標(biāo)記伙伴內(nèi)存塊。⑵處宏VM_PHYS_TO_ORDER(phys)把物理內(nèi)存地址轉(zhuǎn)換為空閑鏈表索引。那么問(wèn)題是,物理內(nèi)存地址和索引有對(duì)應(yīng)關(guān)系?物理地址已基于內(nèi)存頁(yè)大小進(jìn)行對(duì)齊。理論上這個(gè)值可大可小,不明白為什么這么設(shè)計(jì)?TODO。
- ⑴ #define VM_ORDER_TO_PHYS(order) (1 << (PAGE_SHIFT + (order)))
- ⑵ #define VM_PHYS_TO_ORDER(phys) (min(LOS_LowBitGet((phys) >> PAGE_SHIFT), VM_LIST_ORDER_MAX - 1))
2、物理內(nèi)存管理模塊初始化
本節(jié)主要講解物理內(nèi)存管理模塊是如何初始化的,核心函數(shù)是OsVmPageStartup()。在講解之前,會(huì)先看下物理內(nèi)存初始化過(guò)程中的一些內(nèi)部函數(shù)。
2.1 物理內(nèi)存管理初始化內(nèi)部函數(shù)
2.1.1 函數(shù)OsVmPhysSegCreate
函數(shù)OsVmPhysSegCreate用于把指定的一個(gè)物理內(nèi)存區(qū)VmPhysArea轉(zhuǎn)換為物理內(nèi)存段LosVmPhysSeg。傳入的2個(gè)參數(shù)分別為物理內(nèi)存區(qū)的開(kāi)始內(nèi)存地址和大小。⑴處表示系統(tǒng)支持的物理內(nèi)存段的數(shù)量為32個(gè),超過(guò)則轉(zhuǎn)換錯(cuò)誤。⑵處從物理內(nèi)存段全局?jǐn)?shù)組g_vmPhysSeg中獲取一個(gè)可用的物理內(nèi)存段。⑶處如果物理內(nèi)存段seg為數(shù)組g_vmPhysSeg中的第一個(gè)元素,則跳過(guò)循環(huán)體直接執(zhí)行⑸設(shè)置物理內(nèi)存段的開(kāi)始地址和大小。如果不為第一個(gè)元素,并且前一個(gè)物理內(nèi)存段的開(kāi)始地址在要轉(zhuǎn)換的物理內(nèi)存段的結(jié)束地址之后,則執(zhí)行⑷處代碼覆蓋前一個(gè)物理內(nèi)存段。在配置物理內(nèi)存區(qū)的時(shí)候,需要注意這里的影響。
- STATIC INT32 OsVmPhysSegCreate(paddr_t start, size_t size)
- {
- struct VmPhysSeg *seg = NULL;
- ⑴ if (g_vmPhysSegNum >= VM_PHYS_SEG_MAX) {
- return -1;
- }
- ⑵ seg = &g_vmPhysSeg[g_vmPhysSegNum++];
- ⑶ for (; (seg > g_vmPhysSeg) && ((seg - 1)->start > (start + size)); seg--) {
- ⑷ *seg = *(seg - 1);
- }
- ⑸ seg->start = start;
- seg->size = size;
- return 0;
- }
函數(shù)OsVmPhysSegAdd調(diào)用上述函數(shù)OsVmPhysSegCreate依次把配置的多個(gè)物理內(nèi)存區(qū)一一進(jìn)行轉(zhuǎn)換,對(duì)于開(kāi)發(fā)板hispark_taurus只配置了一塊物理內(nèi)存區(qū)域。
- VOID OsVmPhysSegAdd(VOID)
- {
- INT32 i, ret;
- LOS_ASSERT(g_vmPhysSegNum < VM_PHYS_SEG_MAX);
- for (i = 0; i < (sizeof(g_physArea) / sizeof(g_physArea[0])); i++) {
- ret = OsVmPhysSegCreate(g_physArea[i].start, g_physArea[i].size);
- if (ret != 0) {
- VM_ERR("create phys seg failed");
- }
- }
- }
2.1.2 函數(shù)OsVmPhysInit
函數(shù)OsVmPhysInit繼續(xù)初始化物理內(nèi)存段信息。⑴處循環(huán)物理內(nèi)存段數(shù)組,這里不是循環(huán)32次,而是多少個(gè)物理段就循環(huán)遍歷多少次。遍歷到每一個(gè)物理內(nèi)存段,然后執(zhí)行⑵設(shè)置當(dāng)前物理內(nèi)存段的第一個(gè)物理頁(yè)結(jié)構(gòu)體的地址,每一個(gè)物理內(nèi)存頁(yè)都有自己的結(jié)構(gòu)體LosVmPage,這些結(jié)構(gòu)體維護(hù)在通過(guò)malloc內(nèi)存堆申請(qǐng)的g_vmPageArray數(shù)組里,后文會(huì)詳細(xì)講述。⑶處seg->size >> PAGE_SHIFT計(jì)算當(dāng)前內(nèi)存段對(duì)于的內(nèi)存頁(yè)數(shù)量,然后更新nPages,這是后續(xù)物理內(nèi)存段第一個(gè)內(nèi)存頁(yè)對(duì)應(yīng)的的物理內(nèi)存頁(yè)結(jié)構(gòu)體在數(shù)組g_vmPageArray中索引。⑷處開(kāi)始的函數(shù)OsVmPhysFreeListInit和OsVmPhysLruInit初始化伙伴雙向鏈表和LRU雙向鏈表,后續(xù)分析這2個(gè)函數(shù)。
- VOID OsVmPhysInit(VOID)
- {
- struct VmPhysSeg *seg = NULL;
- UINT32 nPages = 0;
- int i;
- for (i = 0; i < g_vmPhysSegNum; i++) {
- ⑴ seg = &g_vmPhysSeg[i];
- ⑵ seg->pageBase = &g_vmPageArray[nPages];
- ⑶ nPages += seg->size >> PAGE_SHIFT;
- ⑷ OsVmPhysFreeListInit(seg);
- OsVmPhysLruInit(seg);
- }
- }
2.1.3 函數(shù)OsVmPhysFreeListInit
每個(gè)物理內(nèi)存段使用9個(gè)空閑物理內(nèi)存頁(yè)節(jié)點(diǎn)鏈表來(lái)維護(hù)空閑物理內(nèi)存頁(yè)。OsVmPhysFreeListInit函數(shù)用于初始化指定物理內(nèi)存段的空閑物理內(nèi)存頁(yè)節(jié)點(diǎn)鏈表。操作前后需要開(kāi)啟、關(guān)閉空閑鏈表自旋鎖。⑴處遍歷空閑物理內(nèi)存頁(yè)節(jié)點(diǎn)鏈表數(shù)組,然后執(zhí)行⑵初始化每個(gè)雙向鏈表。⑶處把每個(gè)鏈表中的空閑物理內(nèi)存頁(yè)的數(shù)量初始化為0。
- STATIC INLINE VOID OsVmPhysFreeListInit(struct VmPhysSeg *seg)
- {
- int i;
- UINT32 intSave;
- struct VmFreeList *list = NULL;
- LOS_SpinInit(&seg->freeListLock);
- LOS_SpinLockSave(&seg->freeListLock, &intSave);
- for (i = 0; i < VM_LIST_ORDER_MAX; i++) {
- ⑴ list = &seg->freeList[i];
- ⑵ LOS_ListInit(&list->node);
- ⑶ list->listCnt = 0;
- }
- LOS_SpinUnlockRestore(&seg->freeListLock, intSave);
- }
2.1.4 函數(shù)OsVmPhysLruInit
和上個(gè)函數(shù)類(lèi)似,函數(shù)OsVmPhysLruInit初始化指定物理內(nèi)存段的LRU鏈表數(shù)組中的LRU鏈表。LRU鏈表分五類(lèi),由枚舉類(lèi)型enum OsLruList定義。代碼較簡(jiǎn)單,讀者自行閱讀代碼即可。
- STATIC VOID OsVmPhysLruInit(struct VmPhysSeg *seg)
- {
- INT32 i;
- UINT32 intSave;
- LOS_SpinInit(&seg->lruLock);
- LOS_SpinLockSave(&seg->lruLock, &intSave);
- for (i = 0; i < VM_NR_LRU_LISTS; i++) {
- seg->lruSize[i] = 0;
- LOS_ListInit(&seg->lruList[i]);
- }
- LOS_SpinUnlockRestore(&seg->lruLock, intSave);
- }
2.1.5 函數(shù)OsVmPageInit
函數(shù)OsVmPageInit用于初始化物理內(nèi)存頁(yè)的初始值,該函數(shù)需要3個(gè)參數(shù),分別是物理內(nèi)存頁(yè)結(jié)構(gòu)體地址,物理內(nèi)存頁(yè)的開(kāi)始地址,物理內(nèi)存段編號(hào)。⑴處初始化內(nèi)存頁(yè)的鏈表節(jié)點(diǎn),這個(gè)鏈表節(jié)點(diǎn)通常會(huì)掛載在伙伴算法的空閑內(nèi)存頁(yè)節(jié)點(diǎn)鏈表上。⑵處設(shè)置內(nèi)存頁(yè)標(biāo)記為空閑內(nèi)存頁(yè)FILE_PAGE_FREE,該值由枚舉類(lèi)型enum OsPageFlags定義。⑶處設(shè)置內(nèi)存頁(yè)的引用計(jì)數(shù)為0。⑷處設(shè)置內(nèi)存頁(yè)的開(kāi)始地址。⑸處設(shè)置內(nèi)存頁(yè)所在的物理內(nèi)存段的編號(hào)。⑹處設(shè)置內(nèi)存頁(yè)順序order初始值,此時(shí)不屬于任何空閑內(nèi)存頁(yè)節(jié)點(diǎn)鏈表。⑺處設(shè)置內(nèi)存頁(yè)的nPages數(shù)值為0。⑻處的宏VMPAGEINIT調(diào)用函數(shù)OsVmPageInit并自動(dòng)增加內(nèi)存頁(yè)結(jié)構(gòu)體page地址和內(nèi)存頁(yè)pa地址。
- STATIC VOID OsVmPageInit(LosVmPage *page, paddr_t pa, UINT8 segID)
- {
- ⑴ LOS_ListInit(&page->node);
- ⑵ page->flags = FILE_PAGE_FREE;
- ⑶ LOS_AtomicSet(&page->refCounts, 0);
- ⑷ page->physAddr = pa;
- ⑸ page->segID = segID;
- ⑹ page->order = VM_LIST_ORDER_MAX;
- ⑺ page->nPages = 0;
- }
- ...
- #define VMPAGEINIT(page, pa, segID) do { \
- ⑻ OsVmPageInit(page, pa, segID); \
- (page)++; \
- (pa) += PAGE_SIZE; \
- } while (0)
2.2 物理內(nèi)存頁(yè)初始化函數(shù)VOID OsVmPageStartup(VOID)
了解上述幾個(gè)內(nèi)部函數(shù)后,我們正式開(kāi)始閱讀物理內(nèi)存頁(yè)初始化函數(shù)VOID OsVmPageStartup(VOID)。系統(tǒng)在啟動(dòng)時(shí),該函數(shù)用于初始化物理內(nèi)存,把物理內(nèi)存段劃分割為為物理內(nèi)存頁(yè)。該函數(shù)被kernel/base/vm/los_vm_boot.c中的UINT32 OsSysMemInit(VOID)調(diào)用,進(jìn)一步被文件platform/los_config.c中的INT32 OsMain(VOID)函數(shù)調(diào)用。下面詳細(xì)分析下函數(shù)的代碼。
⑴處的g_vmBootMemBase初始值為(UINTPTR)&__bss_end,表示系統(tǒng)可用內(nèi)存在bss段之后;ROUNDUP用于內(nèi)存向上對(duì)齊。函數(shù)OsVmPhysAreaSizeAdjust()用于調(diào)整物理區(qū)的開(kāi)始地址和大小。⑵處的 OsVmPhysPageNumGet()計(jì)算物理內(nèi)存段可以劃分多少物理內(nèi)存頁(yè),此行代碼重新計(jì)算物理內(nèi)存頁(yè)數(shù)目,此時(shí)每個(gè)物理頁(yè)對(duì)應(yīng)一個(gè)物理頁(yè)結(jié)構(gòu)體,相應(yīng)結(jié)構(gòu)體也占用內(nèi)存空間。 ⑶處計(jì)算物理頁(yè)結(jié)構(gòu)體數(shù)組的大小,數(shù)組的每個(gè)元素對(duì)應(yīng)每個(gè)物理頁(yè)結(jié)構(gòu)體LosVmPage。接下來(lái)一行調(diào)用函數(shù)OsVmBootMemAlloc為物理頁(yè)結(jié)構(gòu)體數(shù)組g_vmPageArray申請(qǐng)內(nèi)存空間,申請(qǐng)的內(nèi)存空間從地址g_vmBootMemBase截取指定的長(zhǎng)度。⑷處再次調(diào)用函數(shù)OsVmPhysAreaSizeAdjust()用于調(diào)整物理內(nèi)存區(qū)的開(kāi)始地址和大小,確?;趦?nèi)存頁(yè)對(duì)齊。⑸處調(diào)用函數(shù)OsVmPhysSegAdd()轉(zhuǎn)換為物理內(nèi)存段,⑹處調(diào)用OsVmPhysInit函數(shù)初始化物理內(nèi)存段的空閑物理內(nèi)存頁(yè)節(jié)點(diǎn)鏈表和LRU鏈表。上文分析過(guò)這幾個(gè)內(nèi)部函數(shù)。⑺處遍歷每個(gè)物理內(nèi)存段,獲取遍歷到的物理內(nèi)存段的總頁(yè)數(shù)nPage。⑻處為提升初始化物理內(nèi)存頁(yè)的性能,把頁(yè)數(shù)分為8份,count為每份的內(nèi)存頁(yè)的數(shù)目,left為等分為8份后剩余的內(nèi)存頁(yè)數(shù)。⑼處循環(huán)初始化物理內(nèi)存頁(yè),⑽處初始化剩余的物理內(nèi)存頁(yè)。⑾處的函數(shù)OsVmPageOrderListInit把物理內(nèi)存頁(yè)插入到空閑內(nèi)存頁(yè)節(jié)點(diǎn)鏈表,該函數(shù)進(jìn)一步調(diào)用OsVmPhysPagesFreeContiguous函數(shù),后續(xù)再分析該函數(shù)。初始化完成后,物理內(nèi)存段上的內(nèi)存頁(yè)都掛載到空閑內(nèi)存頁(yè)節(jié)點(diǎn)鏈表上了。
- VOID OsVmPageStartup(VOID)
- {
- struct VmPhysSeg *seg = NULL;
- LosVmPage *page = NULL;
- paddr_t pa;
- UINT32 nPage;
- INT32 segID;
- ⑴ OsVmPhysAreaSizeAdjust(ROUNDUP((g_vmBootMemBase - KERNEL_ASPACE_BASE), PAGE_SIZE));
- /*
- * Pages getting from OsVmPhysPageNumGet() interface here contain the memory
- * struct LosVmPage occupied, which satisfies the equation:
- * nPage * sizeof(LosVmPage) + nPage * PAGE_SIZE = OsVmPhysPageNumGet() * PAGE_SIZE.
- */
- ⑵ nPage = OsVmPhysPageNumGet() * PAGE_SIZE / (sizeof(LosVmPage) + PAGE_SIZE);
- ⑶ g_vmPageArraySize = nPage * sizeof(LosVmPage);
- g_vmPageArray = (LosVmPage *)OsVmBootMemAlloc(g_vmPageArraySize);
- ⑷ OsVmPhysAreaSizeAdjust(ROUNDUP(g_vmPageArraySize, PAGE_SIZE));
- ⑸ OsVmPhysSegAdd();
- ⑹ OsVmPhysInit();
- for (segID = 0; segID < g_vmPhysSegNum; segID++) {
- ⑺ seg = &g_vmPhysSeg[segID];
- nPage = seg->size >> PAGE_SHIFT;
- ⑻ UINT32 count = nPage >> 3; /* 3: 2 ^ 3, nPage / 8, cycle count */
- UINT32 left = nPage & 0x7; /* 0x7: nPage % 8, left page */
- ⑼ for (page = seg->pageBase, pa = seg->start; count > 0; count--) {
- /* note: process large amount of data, optimize performance */
- VMPAGEINIT(page, pa, segID);
- VMPAGEINIT(page, pa, segID);
- VMPAGEINIT(page, pa, segID);
- VMPAGEINIT(page, pa, segID);
- VMPAGEINIT(page, pa, segID);
- VMPAGEINIT(page, pa, segID);
- VMPAGEINIT(page, pa, segID);
- VMPAGEINIT(page, pa, segID);
- }
- for (; left > 0; left--) {
- ⑽ VMPAGEINIT(page, pa, segID);
- }
- ⑾ OsVmPageOrderListInit(seg->pageBase, nPage);
- }
- }
3、物理內(nèi)存管理模塊接口
學(xué)習(xí)過(guò)物理內(nèi)存初始化后,接下來(lái)我們會(huì)分析物理內(nèi)存管理模塊的接口函數(shù),包含申請(qǐng)、釋放、查詢(xún)等功能接口。
3.1 申請(qǐng)物理內(nèi)存頁(yè)接口
3.1.1 申請(qǐng)物理內(nèi)存頁(yè)接口介紹
申請(qǐng)物理內(nèi)存頁(yè)的接口有3個(gè),分別用于滿足不同的申請(qǐng)需求。LOS_PhysPagesAllocContiguous函數(shù)的傳入?yún)?shù)為要申請(qǐng)物理內(nèi)存頁(yè)的數(shù)目,返回值為申請(qǐng)到的物理內(nèi)存頁(yè)對(duì)應(yīng)的內(nèi)核虛擬地址空間中的虛擬內(nèi)存地址。⑴處調(diào)用函數(shù)OsVmPhysPagesGet申請(qǐng)指定數(shù)目的物理內(nèi)存頁(yè),然后⑵處調(diào)用函數(shù)OsVmPageToVaddr轉(zhuǎn)換為內(nèi)核虛擬內(nèi)存地址。函數(shù)LOS_PhysPageAlloc申請(qǐng)一個(gè)物理內(nèi)存頁(yè),返回值為申請(qǐng)到的物理頁(yè)對(duì)應(yīng)的物理頁(yè)結(jié)構(gòu)體地址。代碼比較簡(jiǎn)單,見(jiàn)⑶處,調(diào)用函數(shù)OsVmPageToVaddr傳入ONE_PAGE參數(shù)申請(qǐng)1個(gè)物理內(nèi)存頁(yè)。函數(shù)LOS_PhysPagesAlloc用于申請(qǐng)nPages個(gè)物理內(nèi)存頁(yè),并掛在雙向鏈表list上,返回值為實(shí)際申請(qǐng)到的物理頁(yè)數(shù)目。⑷處循環(huán)調(diào)用函數(shù)OsVmPhysPagesGet()申請(qǐng)一個(gè)物理內(nèi)存頁(yè),如果申請(qǐng)成功不為空,則插入到雙向鏈表,申請(qǐng)成功的物理頁(yè)的數(shù)目加1;如果申請(qǐng)失敗則跳出循環(huán)。⑹返回實(shí)際申請(qǐng)到的物理頁(yè)的數(shù)目。
- VOID *LOS_PhysPagesAllocContiguous(size_t nPages)
- {
- LosVmPage *page = NULL;
- if (nPages == 0) {
- return NULL;
- }
- ⑴ page = OsVmPhysPagesGet(nPages);
- if (page == NULL) {
- return NULL;
- }
- ⑵ return OsVmPageToVaddr(page);
- }
- ......
- LosVmPage *LOS_PhysPageAlloc(VOID)
- {
- ⑶ return OsVmPhysPagesGet(ONE_PAGE);
- }
- size_t LOS_PhysPagesAlloc(size_t nPages, LOS_DL_LIST *list)
- {
- LosVmPage *page = NULL;
- size_t count = 0;
- if ((list == NULL) || (nPages == 0)) {
- return 0;
- }
- while (nPages--) {
- ⑷ page = OsVmPhysPagesGet(ONE_PAGE);
- if (page == NULL) {
- break;
- }
- ⑸ LOS_ListTailInsert(list, &page->node);
- count++;
- }
- ⑹ return count;
- }
3.1.2 申請(qǐng)物理內(nèi)存頁(yè)內(nèi)部接口實(shí)現(xiàn)
3個(gè)內(nèi)存頁(yè)申請(qǐng)函數(shù)都調(diào)用了函數(shù)OsVmPhysPagesGet,下文會(huì)詳細(xì)分析申請(qǐng)物理內(nèi)存頁(yè)內(nèi)部接口實(shí)現(xiàn)。
3.1.2.1 函數(shù)OsVmPhysPagesGet
函數(shù)OsVmPhysPagesGet用于申請(qǐng)指定數(shù)量的物理內(nèi)存頁(yè),返回值為物理內(nèi)存頁(yè)結(jié)構(gòu)體地址。⑴處遍歷物理內(nèi)存段數(shù)組,對(duì)遍歷到的物理內(nèi)存段執(zhí)行⑵處代碼,調(diào)用函數(shù)OsVmPhysPagesAlloc()從指定的內(nèi)存段中申請(qǐng)指定數(shù)目的物理內(nèi)存頁(yè)。如果申請(qǐng)成功,則執(zhí)行⑶把內(nèi)存頁(yè)的引用計(jì)數(shù)初始化為0,根據(jù)注釋?zhuān)绻沁B續(xù)的內(nèi)存頁(yè),則第一個(gè)內(nèi)存頁(yè)持有引用計(jì)數(shù)數(shù)值。接下來(lái)以后更新內(nèi)存頁(yè)的數(shù)量,并返回申請(qǐng)到的內(nèi)存頁(yè)的結(jié)構(gòu)體地址;如果申請(qǐng)失敗則繼續(xù)循環(huán)申請(qǐng)或者返回NULL。
- STATIC LosVmPage *OsVmPhysPagesGet(size_t nPages)
- {
- UINT32 intSave;
- struct VmPhysSeg *seg = NULL;
- LosVmPage *page = NULL;
- UINT32 segID;
- for (segID = 0; segID < g_vmPhysSegNum; segID++) {
- ⑴ seg = &g_vmPhysSeg[segID];
- LOS_SpinLockSave(&seg->freeListLock, &intSave);
- ⑵ page = OsVmPhysPagesAlloc(seg, nPages);
- if (page != NULL) {
- /* the first page of continuous physical addresses holds refCounts */
- ⑶ LOS_AtomicSet(&page->refCounts, 0);
- page->nPages = nPages;
- LOS_SpinUnlockRestore(&seg->freeListLock, intSave);
- return page;
- }
- LOS_SpinUnlockRestore(&seg->freeListLock, intSave);
- }
- return NULL;
- }
3.1.2.2 函數(shù)OsVmPhysPagesAlloc
從上文的介紹,我們知道物理內(nèi)存段包含一個(gè)空閑內(nèi)存頁(yè)節(jié)點(diǎn)鏈表數(shù)組,數(shù)組大小為9。數(shù)組中的每個(gè)鏈表上的內(nèi)存頁(yè)節(jié)點(diǎn)的大小等于2的冪次方個(gè)內(nèi)存頁(yè),例如:第0個(gè)鏈表上掛載的空閑內(nèi)存節(jié)點(diǎn)的大小為2的0次方個(gè)內(nèi)存頁(yè),即1個(gè)內(nèi)存頁(yè);第8個(gè)鏈表上掛載的內(nèi)存頁(yè)節(jié)點(diǎn)的大小為2的8次方個(gè)內(nèi)存頁(yè),即256個(gè)內(nèi)存頁(yè)。相同大小的內(nèi)存塊掛在同一個(gè)鏈表上進(jìn)行管理。
分析函數(shù)OsVmPhysPagesAlloc之前,先看下函數(shù)OsVmPagesToOrder,該函數(shù)根據(jù)指定的物理頁(yè)的數(shù)目計(jì)算屬于空閑內(nèi)存頁(yè)節(jié)點(diǎn)鏈表數(shù)組中的第幾個(gè)雙向鏈表。當(dāng)nPages為最小1時(shí),order取值為0;當(dāng)為2時(shí),order取值1…等于取底為2的對(duì)數(shù)Log2(nPages)。
- #define VM_ORDER_TO_PAGES(order) (1 << (order))
- ......
- UINT32 OsVmPagesToOrder(size_t nPages)
- {
- UINT32 order;
- for (order = 0; VM_ORDER_TO_PAGES(order) < nPages; order++);
- return order;
- }
繼續(xù)分析下函數(shù)OsVmPhysPagesAlloc(),該函數(shù)基于傳入?yún)?shù)從指定的內(nèi)存段申請(qǐng)指定數(shù)目的內(nèi)存頁(yè)。⑴處調(diào)用的函數(shù)上文已經(jīng)講述,根據(jù)內(nèi)存頁(yè)數(shù)目計(jì)算出鏈表數(shù)組索引值。如果索引值小于鏈表最大索引值VM_LIST_ORDER_MAX,則執(zhí)行⑵從小內(nèi)存頁(yè)節(jié)點(diǎn)向大內(nèi)存頁(yè)節(jié)點(diǎn)循環(huán)各個(gè)雙向鏈表。⑶處獲取雙向鏈表,如果空閑鏈表為空則繼續(xù)循環(huán);如果不為空,則執(zhí)行⑷獲取鏈表上的空閑內(nèi)存頁(yè)結(jié)構(gòu)體。
如果根據(jù)內(nèi)存頁(yè)數(shù)計(jì)算出的數(shù)組索引值大于等于鏈表最大索引值VM_LIST_ORDER_MAX,說(shuō)明空閑鏈表上并沒(méi)有這么大塊的內(nèi)存頁(yè)節(jié)點(diǎn),需要從物理內(nèi)存段上申請(qǐng),需要執(zhí)行⑸調(diào)用函數(shù)OsVmPhysLargeAlloc()申請(qǐng)大的內(nèi)存頁(yè)。如果申請(qǐng)不到內(nèi)存頁(yè)則申請(qǐng)失敗,返回NULL;如果申請(qǐng)到合適的內(nèi)存頁(yè),則繼續(xù)執(zhí)行后續(xù)DONE標(biāo)簽代碼。這些代碼從空閑鏈表中刪除,拆分,多余的空閑內(nèi)存頁(yè)插入空閑鏈表等,后文繼續(xù)分析調(diào)用的這些函數(shù)。先看下這些參數(shù)的實(shí)際傳入?yún)?shù),order為要申請(qǐng)的內(nèi)存頁(yè)對(duì)應(yīng)的鏈表數(shù)組索引,newOrder為實(shí)際申請(qǐng)的內(nèi)存頁(yè)對(duì)應(yīng)的鏈表數(shù)組索引。⑹處的for循環(huán)條件中,&page[nPages]為需要申請(qǐng)的內(nèi)存頁(yè)結(jié)構(gòu)體的結(jié)束地址,&tmp[1 << newOrder]表示伙伴算法中空閑內(nèi)存頁(yè)節(jié)點(diǎn)鏈表上的內(nèi)存塊的結(jié)束地址。這里為啥使用for循環(huán)呢,上面申請(qǐng)內(nèi)存時(shí),應(yīng)該申請(qǐng)了多個(gè)內(nèi)存節(jié)點(diǎn)拼接起來(lái)了??聪垄颂幍暮瘮?shù)的傳入?yún)?shù),&page[nPages]為需要申請(qǐng)的內(nèi)存頁(yè)結(jié)構(gòu)體的結(jié)束地址,往后的部分被拆分放入空閑鏈表。(1 << min(order, newOrder))表示實(shí)際申請(qǐng)的內(nèi)存頁(yè)的數(shù)目。
- STATIC LosVmPage *OsVmPhysPagesAlloc(struct VmPhysSeg *seg, size_t nPages)
- {
- struct VmFreeList *list = NULL;
- LosVmPage *page = NULL;
- LosVmPage *tmp = NULL;
- UINT32 order;
- UINT32 newOrder;
- ⑴ order = OsVmPagesToOrder(nPages);
- if (order < VM_LIST_ORDER_MAX) {
- ⑵ for (newOrder = order; newOrder < VM_LIST_ORDER_MAX; newOrder++) {
- ⑶ list = &seg->freeList[newOrder];
- if (LOS_ListEmpty(&list->node)) {
- continue;
- }
- ⑷ page = LOS_DL_LIST_ENTRY(LOS_DL_LIST_FIRST(&list->node), LosVmPage, node);
- goto DONE;
- }
- } else {
- newOrder = VM_LIST_ORDER_MAX - 1;
- ⑸ page = OsVmPhysLargeAlloc(seg, nPages);
- if (page != NULL) {
- goto DONE;
- }
- }
- return NULL;
- DONE:
- for (tmp = page; tmp < &page[nPages]; tmp = &tmp[1 << newOrder]) {
- ⑹ OsVmPhysFreeListDelUnsafe(tmp);
- }
- OsVmPhysPagesSpiltUnsafe(page, order, newOrder);
- ⑺ OsVmRecycleExtraPages(&page[nPages], nPages, ROUNDUP(nPages, (1 << min(order, newOrder))));
- return page;
- }
想了解更多內(nèi)容,請(qǐng)?jiān)L問(wèn):
51CTO和華為官方合作共建的鴻蒙技術(shù)社區(qū)