鴻蒙輕內核A核源碼分析系列三 物理內存(2)
鴻蒙輕內核A核源碼分析系列三 物理內存(2)

3.1.2.3 函數OsVmPhysLargeAlloc
當執行到這個函數時,說明空閑鏈表上的單個內存頁節點的大小已經不能滿足要求,超過了第9個鏈表上的內存頁節點的大小了。⑴處計算需要申請的內存大小。⑵從最大的鏈表上進行遍歷每一個內存頁節點。⑶根據每個內存頁的開始內存地址,計算需要的內存的結束地址,如果超過內存段的大小,則繼續遍歷下一個內存頁節點。
⑷處此時paStart表示當前內存頁的結束地址,接下來paStart >= paEnd表示當前內存頁的大小滿足申請的需求;paStart < seg->start和paStart >= (seg->start + seg->size)發生溢出錯誤,內存頁結束地址不在內存段的地址范圍內。⑸處表示當前內存頁的下一個內存頁結構體,如果該結構體不在空閑鏈表上,則break跳出循環。如果在空閑鏈表上,表示連續的空閑內存頁會拼接起來,滿足大內存申請的需要。⑹表示一個或者多個連續的內存頁的大小滿足申請需求。
STATIC LosVmPage *OsVmPhysLargeAlloc(struct VmPhysSeg *seg, size_t nPages) { struct VmFreeList *list = NULL; LosVmPage *page = NULL; LosVmPage *tmp = NULL; PADDR_T paStart; PADDR_T paEnd; ⑴ size_t size = nPages << PAGE_SHIFT; ⑵ list = &seg->freeList[VM_LIST_ORDER_MAX - 1]; LOS_DL_LIST_FOR_EACH_ENTRY(page, &list->node, LosVmPage, node) { ⑶ paStart = page->physAddr; paEnd = paStart + size; if (paEnd > (seg->start + seg->size)) { continue; } for (;;) { ⑷ paStart += PAGE_SIZE << (VM_LIST_ORDER_MAX - 1); if ((paStart >= paEnd) || (paStart < seg->start) || (paStart >= (seg->start + seg->size))) { break; } ⑸ tmp = &seg->pageBase[(paStart - seg->start) >> PAGE_SHIFT]; if (tmp->order != (VM_LIST_ORDER_MAX - 1)) { break; } } ⑹ if (paStart >= paEnd) { return page; } } return NULL; }
3.1.2.4 函數OsVmPhysFreeListDelUnsafe和OsVmPhysFreeListAddUnsafe
內部函數OsVmPhysFreeListDelUnsafe用于從空閑內存頁節點鏈表上刪除一個內存頁節點,名稱中有Unsafe字樣,是因為函數體內并沒有對鏈表操作加自旋鎖,安全性由外部調用函數保證。⑴處進行校驗,確保內存段和空閑鏈表索引符合要求。⑵處獲取內存段和空閑鏈表,⑶處空閑鏈表上內存頁節點數目減1,并把內存塊從空閑鏈表上刪除。⑷處設置內存頁的order索引值為最大值來標記非空閑內存頁。
STATIC VOID OsVmPhysFreeListDelUnsafe(LosVmPage *page) { struct VmPhysSeg *seg = NULL; struct VmFreeList *list = NULL; ⑴ if ((page->segID >= VM_PHYS_SEG_MAX) || (page->order >= VM_LIST_ORDER_MAX)) { LOS_Panic("The page segment id(%u) or order(%u) is invalid\n", page->segID, page->order); } ⑵ seg = &g_vmPhysSeg[page->segID]; list = &seg->freeList[page->order]; ⑶ list->listCnt--; LOS_ListDelete(&page->node); ⑷ page->order = VM_LIST_ORDER_MAX; }
和空閑鏈表上刪除對應的函數是空閑鏈表上插入空閑內存頁節點函數OsVmPhysFreeListAddUnsafe。⑴處更新內存頁的要掛載的空閑鏈表的索引值,然后獲取內存頁所在的內存段seg,并獲取索引值對應的空閑鏈表。執行⑵把空閑內存頁節點插入到空閑鏈表并更新節點數目。
STATIC VOID OsVmPhysFreeListAddUnsafe(LosVmPage *page, UINT8 order) { struct VmPhysSeg *seg = NULL; struct VmFreeList *list = NULL; if (page->segID >= VM_PHYS_SEG_MAX) { LOS_Panic("The page segment id(%d) is invalid\n", page->segID); } ⑴ page->order = order; seg = &g_vmPhysSeg[page->segID]; list = &seg->freeList[order]; ⑵ LOS_ListTailInsert(&list->node, &page->node); list->listCnt++; }
3.1.2.5 函數OsVmPhysPagesSpiltUnsafe
函數OsVmPhysPagesSpiltUnsafe用于分割內存塊,參數中oldOrder表示需要申請的內存頁節點對應的鏈表索引,newOrder表示實際申請的內存頁節點對應的鏈表索引。如果索引值相等,則不需要拆分,不會執行for循環塊的代碼。由于伙伴算法中的鏈表數組中元素的特點,即每個鏈表中的內存頁節點的大小等于2的冪次方個內存頁。在拆分時,依次從高索引newOrder往低索引oldOrder遍歷,拆分一個內存頁節點作為空閑內存頁節點掛載到對應的空閑鏈表上。⑴處開始循環從高索引到低索引,索引值減1,然后執行⑵獲取伙伴內存頁節點,可以看出,申請的內存塊大于需求時,會把后半部分的高地址部分放入空閑鏈表,保留前半部分的低地址部分。⑶處的斷言確保伙伴內存頁節點索引值是最大值,表示屬于空閑內存頁節點。⑷處調用函數把內存頁節點放入空閑鏈表。
STATIC VOID OsVmPhysPagesSpiltUnsafe(LosVmPage *page, UINT8 oldOrder, UINT8 newOrder) { UINT32 order; LosVmPage *buddyPage = NULL; for (order = newOrder; order > oldOrder;) { ⑴ order--; ⑵ buddyPage = &page[VM_ORDER_TO_PAGES(order)]; ⑶ LOS_ASSERT(buddyPage->order == VM_LIST_ORDER_MAX); ⑷ OsVmPhysFreeListAddUnsafe(buddyPage, order); } }
這里有必要放這一張圖,直觀演示一下。假如我們需要申請8個內存頁大小的內存節點,但是只有freeList[7]鏈表上才有空閑節點。申請成功后,超過了應用需要的大小,需要進行拆分。把2^7個內存頁分為2份大小為2^6個內存頁的節點,第一份繼續拆分,第二份掛載到freeList[6]鏈表上。然后把第一份2^6個內存頁拆分為2個2^5個內存頁節點,第一份繼續拆分,第二份掛載到freeList[5]鏈表上。依次進行下去,最后拆分為2份2^3個內存頁大小的內存頁節點,第一份作為實際申請的內存頁返回,第二份掛載到freeList[3]鏈表上。如下圖紅色部分所示。
另外,函數OsVmRecycleExtraPages會調用OsVmPhysPagesFreeContiguous來回收申請的多余的內存頁,后文再分析。
3.2 釋放物理內存頁接口
3.2.1 釋放物理內存頁接口介紹
和申請物理內存頁接口相對應著,釋放物理內存頁的接口有3個,分別用于滿足不同的釋放內存頁需求。函數LOS_PhysPagesFreeContiguous的傳入參數為要釋放物理頁對應的內核虛擬地址空間中的虛擬內存地址和內存頁數目。⑴處調用函數OsVmVaddrToPage把虛擬內存地址轉換為物理內存頁結構體地址,然后⑵處把內存頁的連續內存頁數目設置為0。⑶處調用函數OsVmPhysPagesFreeContiguous()釋放物理內存頁。函數LOS_PhysPageFree用于釋放一個物理內存頁,傳入參數為要釋放的物理頁對應的物理頁結構體地址。⑷處對引用計數自減,當小于等于0,表示沒有其他引用時才進一步執行釋放操作。該函數同樣會調用函數OsVmPhysPagesFreeContiguous()釋放物理內存頁。函數LOS_PhysPagesFree用于釋放掛在雙向鏈表上的多個物理內存頁,返回值為實際釋放的物理頁數目。⑸處遍歷內存頁雙向鏈表,從鏈表上移除要釋放的內存頁節點。⑹處代碼和釋放一個內存頁的函數代碼相同。⑺處計算遍歷的內存頁的數目,函數最后會返回該值。
VOID LOS_PhysPagesFreeContiguous(VOID *ptr, size_t nPages) { UINT32 intSave; struct VmPhysSeg *seg = NULL; LosVmPage *page = NULL; if (ptr == NULL) { return; } ⑴ page = OsVmVaddrToPage(ptr); if (page == NULL) { VM_ERR("vm page of ptr(%#x) is null", ptr); return; } ⑵ page->nPages = 0; seg = &g_vmPhysSeg[page->segID]; LOS_SpinLockSave(&seg->freeListLock, &intSave); ⑶ OsVmPhysPagesFreeContiguous(page, nPages); LOS_SpinUnlockRestore(&seg->freeListLock, intSave); } ...... VOID LOS_PhysPageFree(LosVmPage *page) { UINT32 intSave; struct VmPhysSeg *seg = NULL; if (page == NULL) { return; } ⑷ if (LOS_AtomicDecRet(&page->refCounts) <= 0) { seg = &g_vmPhysSeg[page->segID]; LOS_SpinLockSave(&seg->freeListLock, &intSave); OsVmPhysPagesFreeContiguous(page, ONE_PAGE); LOS_AtomicSet(&page->refCounts, 0); LOS_SpinUnlockRestore(&seg->freeListLock, intSave); } } ······ size_t LOS_PhysPagesFree(LOS_DL_LIST *list) { UINT32 intSave; LosVmPage *page = NULL; LosVmPage *nPage = NULL; LosVmPhysSeg *seg = NULL; size_t count = 0; if (list == NULL) { return 0; } LOS_DL_LIST_FOR_EACH_ENTRY_SAFE(page, nPage, list, LosVmPage, node) { ⑸ LOS_ListDelete(&page->node); ⑹ if (LOS_AtomicDecRet(&page->refCounts) <= 0) { seg = &g_vmPhysSeg[page->segID]; LOS_SpinLockSave(&seg->freeListLock, &intSave); OsVmPhysPagesFreeContiguous(page, ONE_PAGE); LOS_AtomicSet(&page->refCounts, 0); LOS_SpinUnlockRestore(&seg->freeListLock, intSave); } ⑺ count++; } return count; }
3.2.2 釋放物理內存頁內部接口實現
函數OsVmVaddrToPage把虛擬內存地址轉換為物理頁結構體地址。⑴處調用函數LOS_PaddrQuery()把虛擬地址轉為物理地址,該函數在虛實映射部分會詳細講述。⑵處遍歷物理內存段,如果物理內存地址處于物理內存段的地址范圍,則可以返回該物理地址對應的物理頁結構體地址。
LosVmPage *OsVmVaddrToPage(VOID *ptr) { struct VmPhysSeg *seg = NULL; ⑴ PADDR_T pa = LOS_PaddrQuery(ptr); UINT32 segID; for (segID = 0; segID < g_vmPhysSegNum; segID++) { seg = &g_vmPhysSeg[segID]; ⑵ if ((pa >= seg->start) && (pa < (seg->start + seg->size))) { return seg->pageBase + ((pa - seg->start) >> PAGE_SHIFT); } } return NULL; }
函數OsVmPhysPagesFreeContiguous()用于釋放指定數量的連續物理內存頁。⑴處根據物理內存頁獲取對應的物理內存地址。⑵處根據物理內存地址獲取空閑內存頁鏈表數組索引數值(TODO為什么物理內存地址和索引有對應關系?),⑶處獲取索引值對應的鏈表上的內存頁節點的內存頁數目。⑷處如果要釋放的內存頁數nPages小于當前鏈表上的內存頁節點的數目,則跳出循環執行⑹處代碼,去釋放到小索引的雙向鏈表上。⑸處調用函數OsVmPhysPagesFree()釋放指定鏈表上的內存頁,然后更新內存頁數量和內存頁結構體地址。
⑹處根據內存頁數量計算對應的鏈表索引,根據索引值計算鏈表上內存頁節點的大小。⑺處調用函數OsVmPhysPagesFree()釋放指定鏈表上的內存頁,然后更新內存頁數量和內存頁結構體地址。
VOID OsVmPhysPagesFreeContiguous(LosVmPage *page, size_t nPages) { paddr_t pa; UINT32 order; size_t n; while (TRUE) { ⑴ pa = VM_PAGE_TO_PHYS(page); ⑵ order = VM_PHYS_TO_ORDER(pa); ⑶ n = VM_ORDER_TO_PAGES(order); ⑷ if (n > nPages) { break; } ⑸ OsVmPhysPagesFree(page, order); nPages -= n; page += n; } while (nPages > 0) { ⑹ order = LOS_HighBitGet(nPages); n = VM_ORDER_TO_PAGES(order); ⑺ OsVmPhysPagesFree(page, order); nPages -= n; page += n; } }
函數OsVmPhysPagesFree()釋放內存頁到對應的空閑內存頁鏈表。⑴做傳入參數校驗。⑵處需要至少是倒數第二個鏈表,這樣內存頁節點可以和大索引鏈表上的節點合并。⑶處獲取內存頁對應的物理內存地址。⑷處的VM_ORDER_TO_PHYS(order)計算出鏈表索引值對應的物理地址,然后進行異或運算計算出伙伴頁的物理內存地址。⑸處物理地址轉換為內存頁結構體,進一步判斷如果內存頁不存在或者不在空閑鏈表上,則跳出循環while循環。否則執行⑹把伙伴頁從鏈表上移除,然后索引值加1。⑺處更新物理地址及其對齊的內存頁(TODO 沒有看懂)。當索引order為8,要插入到最后一個鏈表上時,則直接執行⑻插入內存頁到鏈表上。
VOID OsVmPhysPagesFree(LosVmPage *page, UINT8 order) { paddr_t pa; LosVmPage *buddyPage = NULL; ⑴ if ((page == NULL) || (order >= VM_LIST_ORDER_MAX)) { return; } ⑵ if (order < VM_LIST_ORDER_MAX - 1) { ⑶ pa = VM_PAGE_TO_PHYS(page); do { ⑷ pa ^= VM_ORDER_TO_PHYS(order); ⑸ buddyPage = OsVmPhysToPage(pa, page->segID); if ((buddyPage == NULL) || (buddyPage->order != order)) { break; } ⑹ OsVmPhysFreeListDel(buddyPage); order++; ⑺ pa &= ~(VM_ORDER_TO_PHYS(order) - 1); page = OsVmPhysToPage(pa, page->segID); } while (order < VM_LIST_ORDER_MAX - 1); } ⑻ OsVmPhysFreeListAdd(page, order); }
3.3 查詢物理頁地址接口
3.3.1 函數LOS_VmPageGet()
函數LOS_VmPageGet用于根據物理內存地址參數計算對應的物理內存頁結構體地址。⑴處遍歷物理內存段,調用函數OsVmPhysToPage根據物理內存地址和內存段編號計算物理內存頁結構體,該函數后文再分析。⑵處如果獲取的物理內存頁結構體不為空,則跳出循環,返回物理內存頁結構體指針。
LosVmPage *LOS_VmPageGet(PADDR_T paddr) { INT32 segID; LosVmPage *page = NULL; for (segID = 0; segID < g_vmPhysSegNum; segID++) { ⑴ page = OsVmPhysToPage(paddr, segID); ⑵ if (page != NULL) { break; } } return page; }
繼續看下函數OsVmPhysToPage的代碼。⑴處如果參數傳入的物理內存地址不在指定的物理內存段的地址范圍之內則返回NULL。⑵處計算物理內存地址相對內存段開始地址的偏移值。⑶處根據偏移值計算出偏移的內存頁的數目,然后返回物理內存地址對應的物理頁結構體的地址。
LosVmPage *OsVmPhysToPage(paddr_t pa, UINT8 segID) { struct VmPhysSeg *seg = NULL; paddr_t offset; if (segID >= VM_PHYS_SEG_MAX) { LOS_Panic("The page segment id(%d) is invalid\n", segID); } seg = &g_vmPhysSeg[segID]; ⑴ if ((pa < seg->start) || (pa >= (seg->start + seg->size))) { return NULL; } ⑵ offset = pa - seg->start; ⑶ return (seg->pageBase + (offset >> PAGE_SHIFT)); }
3.3.2 函數LOS_PaddrToKVaddr
函數LOS_PaddrToKVaddr根據物理地址獲取其對應的內核虛擬地址。⑴處遍歷物理內存段數組,然后在⑵處判斷如果物理地址處于遍歷到的物理內存段的地址范圍內,則執行⑶,傳入的物理內存地址相對物理內存開始地址的偏移加上內核態虛擬地址空間的開始地址就是物理地址對應的內核虛擬地址。
VADDR_T *LOS_PaddrToKVaddr(PADDR_T paddr) { struct VmPhysSeg *seg = NULL; UINT32 segID; for (segID = 0; segID < g_vmPhysSegNum; segID++) { ⑴ seg = &g_vmPhysSeg[segID]; ⑵ if ((paddr >= seg->start) && (paddr < (seg->start + seg->size))) { ⑶ return (VADDR_T *)(UINTPTR)(paddr - SYS_MEM_BASE + KERNEL_ASPACE_BASE); } } return (VADDR_T *)(UINTPTR)(paddr - SYS_MEM_BASE + KERNEL_ASPACE_BASE); }
3.4 其他函數
3.4.1 函數OsPhysSharePageCopy
函數OsPhysSharePageCopy用于復制共享內存頁。 ⑴處進行參數校驗, ⑵處獲取老內存頁, ⑶處獲取內存段。⑷處如果老內存頁引用計數為1,則把老物理內存地址直接賦值給新物理內存地址。⑸處如果內存頁有多個引用,則先轉化為虛擬內存地址,然后執行⑹進行內存頁的內容復制。⑺刷新新老內存頁的引用計數。
VOID OsPhysSharePageCopy(PADDR_T oldPaddr, PADDR_T *newPaddr, LosVmPage *newPage) { UINT32 intSave; LosVmPage *oldPage = NULL; VOID *newMem = NULL; VOID *oldMem = NULL; LosVmPhysSeg *seg = NULL; ⑴ if ((newPage == NULL) || (newPaddr == NULL)) { VM_ERR("new Page invalid"); return; } ⑵ oldPage = LOS_VmPageGet(oldPaddr); if (oldPage == NULL) { VM_ERR("invalid oldPaddr %p", oldPaddr); return; } ⑶ seg = &g_vmPhysSeg[oldPage->segID]; LOS_SpinLockSave(&seg->freeListLock, &intSave); ⑷ if (LOS_AtomicRead(&oldPage->refCounts) == 1) { *newPaddr = oldPaddr; } else { ⑸ newMem = LOS_PaddrToKVaddr(*newPaddr); oldMem = LOS_PaddrToKVaddr(oldPaddr); if ((newMem == NULL) || (oldMem == NULL)) { LOS_SpinUnlockRestore(&seg->freeListLock, intSave); return; } ⑹ if (memcpy_s(newMem, PAGE_SIZE, oldMem, PAGE_SIZE) != EOK) { VM_ERR("memcpy_s failed"); } ⑺ LOS_AtomicInc(&newPage->refCounts); LOS_AtomicDec(&oldPage->refCounts); } LOS_SpinUnlockRestore(&seg->freeListLock, intSave); return; }
總結
本文首先了解了物理內存管理的結構體,接著閱讀了物理內存如何初始化,然后分析了物理內存的申請、釋放和查詢等操作接口的源代碼。后續也會陸續推出更多的分享文章,敬請期待,有任何問題、建議,都可以留言給我。謝謝。
IoT 數據結構 輕量級操作系統 LiteOS
版權聲明:本文內容由網絡用戶投稿,版權歸原作者所有,本站不擁有其著作權,亦不承擔相應法律責任。如果您發現本站中有涉嫌抄襲或描述失實的內容,請聯系我們jiasou666@gmail.com 處理,核實后本網站將在24小時內刪除侵權內容。
版權聲明:本文內容由網絡用戶投稿,版權歸原作者所有,本站不擁有其著作權,亦不承擔相應法律責任。如果您發現本站中有涉嫌抄襲或描述失實的內容,請聯系我們jiasou666@gmail.com 處理,核實后本網站將在24小時內刪除侵權內容。