Wanzi interpretation of Hongmeng light kernel physical memory module

Abstract: This paper first understands the structure of physical memory management, then reads how to initialize physical memory, and then analyzes the source code of operation interfaces such as application, release and query of physical memory.

This article is shared from Huawei cloud community< Hongmeng light kernel A core source code analysis series 3 physical memory >, author: zhushy.

Physical memory refers to the memory space obtained through the physical memory module, and the corresponding concept is virtual memory. Virtual memory makes the application process think that it has a continuous and complete memory address space, which usually corresponds to multiple physical memory pages through the mapping of virtual memory and physical memory. In this article, let's get familiar with the physical memory management module provided by OpenHarmony Hongmeng light kernel.

The source code involved in this article, taking the OpenHarmony LiteOS-A kernel as an example, can be found on the open source site https://gitee.com/openharmony/kernel_liteos_a   obtain. If the development board is involved, it defaults to hispark_ Take Taurus as an example.

We first understood the structure of physical memory management, then read how to initialize physical memory, and then analyzed the source code of operation interfaces such as application, release and query of physical memory.

1. Introduction to physical memory structure

1.1. Physical memory page LosVmPage

The physical memory of Hongmeng light kernel A core adopts segment page management, and each physical memory segment is divided into physical memory pages. In the header file kernel/base/include/los_vm_page.h defines the physical memory page structure and the memory page array g_vmPageArray and array size g_vmPageArraySize. The physical memory page structure LosVmPage can correspond to physical memory pages one by one, or multiple consecutive memory pages. At this time, nPages is used to specify the number of memory pages.

typedef struct VmPage {
    LOS_DL_LIST         node;        /**< The physical memory page node is hung on the VmFreeList free memory page linked list */
    PADDR_T             physAddr;    /**< Physical memory page memory start address*/
    Atomic              refCounts;   /**< Physical memory page reference count */
    UINT32              flags;       /**< Physical memory page tag */
    UINT8               order;       /**< The index of the linked list array where the physical memory page is located. There are 9 linked lists in total */
    UINT8               segID;       /**< The number of the physical memory segment where the physical memory page is located */
    UINT16              nPages;      /**< Number of contiguous physical memory pages */
} LosVmPage;

extern LosVmPage *g_vmPageArray;
extern size_t g_vmPageArraySize;

In the file kernel\base\include\los_vm_common.h defines the size, mask and logical displacement value of memory pages. It can be seen that the size of each memory page is 4KiB.

#ifndef PAGE_SIZE
#define PAGE_SIZE                        (0x1000U)
#endif
#define PAGE_MASK                        (~(PAGE_SIZE - 1))
#define PAGE_SHIFT                       (12)

1.2. Physical memory segment LosVmPhysSeg

In the file kernel / base / include / Los_ vm_ Physics. H defines several structures such as the physical memory segment LosVmPhysSeg. Part of the code of this file is shown below. (1) the macro at is the size of the empty free memory page node linked list array of the physical memory partner algorithm, VM_PHYS_SEG_MAX indicates the number of physical memory segments supported by the system. (2) the structure at is used for the element type of the page node linked list array in the partner algorithm. In addition to recording the two-way linked list, it also maintains the number of nodes on the linked list. ⑶ is the physical memory segment we want to introduce, including start address, size, memory page base address, free memory page node linked list array, LRU linked list array and other members.

⑴  #define VM_LIST_ORDER_MAX    9
    #define VM_PHYS_SEG_MAX    32

⑵  struct VmFreeList {
        LOS_DL_LIST node;   // Free physical memory page node
        UINT32 listCnt;     // Number of free physical memory page nodes
    };

⑶  typedef struct VmPhysSeg {
        PADDR_T start;            /* The start address of the physical memory segment */
        size_t size;              /* Size of physical memory segment, bytes */
        LosVmPage *pageBase;      /* Physical memory segment first physical memory page structure address */

        SPIN_LOCK_S freeListLock; /* Partner algorithm bidirectional linked list spin lock */
        struct VmFreeList freeList[VM_LIST_ORDER_MAX];  /* Partner bidirectional linked list of free physical memory pages */

        SPIN_LOCK_S lruLock;  /* LRU Bidirectional linked list spin lock */
        size_t lruSize[VM_NR_LRU_LISTS];  /* LRU size */
        LOS_DL_LIST lruList[VM_NR_LRU_LISTS];/* LRU Bidirectional linked list */
    } LosVmPhysSeg;

    struct VmPhysArea {
        PADDR_T start;  // Physical memory area start address
        size_t size;    // Physical memory area size
    };

In kernel / base / VM / Los_ vm_ The physical memory area array is defined in the Phys. C file g_physArea [], as shown in the following code, where SYS_MEM_BASE is DDR_ MEM_ Macro name of addr, DDR_MEM_ADDR and SYS_MEM_SIZE_DEFAULT is defined in the file. / device/hisilicon/hispark_taurus/sdk_liteos/board/target_config.h indicates the physical memory address and size related to the development board.

STATIC struct VmPhysArea g_physArea[] = {
    {
        .start = SYS_MEM_BASE,
        .size = SYS_MEM_SIZE_DEFAULT,
    },
};

Look at the difference between the physical memory area VmPhysArea and the physical memory segment LosVmPhysSeg. The former has less information and mainly records the start address and size, which is the simplest description of a piece of physical memory; In addition to the physical memory block start address and size, the latter also maintains physical page start address, free physical page partner list, LRU list, corresponding spin lock and other information.

The partner algorithm is mentioned above. First look at the schematic diagram of the partner algorithm, as follows. Each physical memory segment is divided into memory pages one by one, and the free memory pages are mounted on the free memory page node linked list. There are 9 free memory page node linked lists, which form a linked list array. The size of the memory page node on the first linked list is 1 memory page, the size of the memory page node on the second linked list is 2 memory pages, and the size of the memory page node on the third linked list is 4 memory pages. In turn, the size of the memory page node on the ninth linked list is 2 ^ 8 memory pages. When applying for memory and releasing memory, these free memory page node linked lists will be operated, which will be analyzed in detail later.

2. Physical memory management module initialization

This section mainly explains how the physical memory management module is initialized. The core function is OsVmPageStartup(). Before explaining, I will first look at some internal functions in the process of physical memory initialization.

2.1 initialization internal function of physical memory management

2.1.1 function OsVmPhysSegCreate

The function OsVmPhysSegCreate is used to convert a specified physical memory area VmPhysArea into a physical memory segment LosVmPhysSeg. The two parameters passed in are the start memory address and size of the physical memory area. (1) indicates that the number of physical memory segments supported by the system is 32. If it exceeds 32, the conversion error will occur. ⑵ from the global array of physical memory segments G_ Get an available physical memory segment from vmphysseg. (3) if the physical memory segment seg is an array g_ For the first element in vmphysseg, skip the loop body and execute directly (5) set the start address and size of the physical memory segment. If it is not the first element and the start address of the previous physical memory segment is after the end address of the physical memory segment to be converted, execute the code at (4) to overwrite the previous physical memory segment. When configuring the physical memory area, you need to pay attention to the impact here.

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;
}

The function OsVmPhysSegAdd calls the above function OsVmPhysSegCreate to convert the configured multiple physical memory areas one by one. For the development board hispark_taurus has only one physical memory area configured.

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 function OsVmPhysInit

The function OsVmPhysInit continues to initialize the physical memory segment information. (1) loop the array of physical memory segments at. Here, it is not 32 cycles, but how many physical segments you cycle through. Traverse to each physical memory segment, and then execute (2) set the address of the first physical page structure of the current physical memory segment. Each physical memory page has its own structure LosVmPage, which is maintained in G applied through malloc memory heap_ Vmpagearray, which will be described in detail later. (3) seg - > size > > page_ Shift calculates the number of memory pages for the current memory segment, and then updates nPages, which is the physical memory page structure corresponding to the first memory page of the subsequent physical memory segment in array G_ Index in vmpagearray. (4) the functions OsVmPhysFreeListInit and OsVmPhysLruInit starting at initialize the partner bidirectional linked list and LRU bidirectional linked list, and then analyze these two functions.

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 function OsVmPhysFreeListInit

Each physical memory segment uses a linked list of 9 free physical memory page nodes to maintain free physical memory pages. The OsVmPhysFreeListInit function is used to initialize the linked list of free physical memory page nodes of the specified physical memory segment. Before and after the operation, you need to open and close the idle linked list spin lock. (1) traverse the free physical memory page node linked list array, and then execute (2) to initialize each bidirectional linked list. (3) initialize the number of free physical memory pages in each linked list to 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 function OsVmPhysLruInit

Similar to the previous function, the function OsVmPhysLruInit initializes the LRU linked list in the LRU linked list array of the specified physical memory segment. The LRU linked list is divided into five categories, which are defined by the enumeration type enum OsLruList. The code is relatively simple. Readers can read the code by themselves.

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 function OsVmPageInit

The function OsVmPageInit is used to initialize the initial value of the physical memory page. The function requires three parameters: the physical memory page structure address, the start address of the physical memory page, and the physical memory segment number. (1) initialize the linked list node of the memory page at. This linked list node is usually attached to the free memory page node linked list of the partner algorithm. (2) set the memory page flag as free memory page file at_ PAGE_ Free, which is defined by the enumeration type enum OsPageFlags. (3) set the reference count of memory pages to 0 at. (4) set the start address of the memory page at. (5) set the number of the physical memory segment where the memory page is located. (6) set the initial value of memory page order at. At this time, it does not belong to any free memory page node linked list. (7) set the nPages value of the memory page to 0. The macro VMPAGEINIT at (8) calls the function OsVmPageInit and automatically increases the page address of the memory page structure and the memory page pa address.

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 physical memory page initialization function VOID OsVmPageStartup(VOID)

After understanding the above internal functions, we officially start reading the physical memory page initialization function VOID OsVmPageStartup(VOID). When the system starts, this function is used to initialize the physical memory and divide the physical memory segment into physical memory pages. This function is called kernel / base / VM / Los_ vm_ UINT32 OsSysMemInit(VOID) in boot. C is called by the file platform / Los_ INT32 OsMain(VOID) function call in config. C. The code of the function is analyzed in detail below.

(1) g at_ The initial value of vmbootmembase is (uintptr)&__ bss_ End indicates that the available memory of the system is after the BSS segment; ROUNDUP is used for memory up alignment. The function OsVmPhysAreaSizeAdjust() is used to adjust the start address and size of the physical area. (2) OsVmPhysPageNumGet() at calculates how many physical memory pages the physical memory segment can divide. This line of code recalculates the number of physical memory pages. At this time, each physical page corresponds to a physical page structure, and the corresponding structure also occupies memory space. (3) calculate the size of the physical page structure array at, and each element of the array corresponds to each physical page structure LosVmPage. The next line calls the function OsVmBootMemAlloc to the physical page structure array g_vmPageArray requests memory space from address g_vmBootMemBase intercepts the specified length. (4) call the function OsVmPhysAreaSizeAdjust() again at to adjust the start address and size of the physical memory area to ensure memory page alignment. (5) call the function OsVmPhysSegAdd() to convert into a physical memory segment, and (6) call the OsVmPhysInit function to initialize the free physical memory page node linked list and LRU linked list of the physical memory segment. These internal functions have been analyzed above. (7) traverse each physical memory segment and obtain the total pages nPage of the traversed physical memory segment. (8) in order to improve the performance of initializing physical memory pages, the number of pages is divided into 8 copies, count is the number of memory pages per copy, and left is the number of memory pages remaining after being equally divided into 8 copies. (9) cyclically initialize the physical memory pages, and (10) initialize the remaining physical memory pages. (11) the function OsVmPageOrderListInit at (11) inserts the physical memory page into the free memory page node linked list. The function further calls the osvmphyspagesfreeconteguous function, and then analyzes the function later. After initialization, the memory pages on the physical memory segment are mounted on the free memory page node linked list.

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. Physical memory management module interface

After learning physical memory initialization, we will analyze the interface functions of the physical memory management module, including application, release, query and other functional interfaces.

3.1 applying for physical memory page interface

3.1.1 introduction to applying for physical memory page interface

There are three interfaces for applying for physical memory pages, which are respectively used to meet different application requirements. LOS_ The incoming parameter of the physpagesalloccontiguous function is the number of physical memory pages to be applied for, and the return value is the virtual memory address in the kernel virtual address space corresponding to the applied physical memory page. (1) call the function OsVmPhysPagesGet at to apply for the specified number of physical memory pages, and then call the function OsVmPageToVaddr at (2) to convert it to the kernel virtual memory address. Function Los_ Physpagelloc applies for a physical memory page, and the return value is the physical page structure address corresponding to the applied physical page. The code is relatively simple. See (3) and call the function OsVmPageToVaddr to pass in one_ The page parameter requests 1 physical memory page. Function LOS_PhysPagesAlloc is used to apply for nPages physical memory pages and hang them on the two-way linked list. The return value is the actual number of physical pages applied. (4) call the function OsVmPhysPagesGet() to apply for a physical memory page. If the application is successful and not empty, it will be inserted into the two-way linked list, and the number of successful physical pages will be increased by 1; If the application fails, the cycle will jump out. (6) return the number of physical pages actually applied for.

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 implementation of internal interface of physical memory page

The three memory page application functions all call the function OsVmPhysPagesGet. The implementation of the internal interface of the applied physical memory page will be analyzed in detail below.

3.1.2.1 function OsVmPhysPagesGet

The function OsVmPhysPagesGet is used to apply for a specified number of physical memory pages, and the return value is the physical memory page structure address. (1) traverse the array of physical memory segments, execute the code in (2) on the traversed physical memory segments, and call the function OsVmPhysPagesAlloc() to apply for a specified number of physical memory pages from the specified memory segments. If the application is successful, execute (3) to initialize the reference count of the memory page to 0. According to the notes, if it is a continuous memory page, the first memory page holds the reference count value. Next, update the number of memory pages and return the structure address of the requested memory page; If the application fails, continue to cycle the application or return 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 function OsVmPhysPagesAlloc

From the above introduction, we know that the physical memory segment contains a linked list array of free memory page nodes, with an array size of 9. The size of the memory page node on each linked list in the array is equal to the power of 2 memory pages. For example, the size of the free memory node mounted on the 0th linked list is 0 memory pages to the power of 2, that is, 1 memory page; The size of the memory page node mounted on the 8th linked list is the 8th power of 2, that is, 256 memory pages. Memory blocks of the same size are hung on the same linked list for management.

Before analyzing the function OsVmPhysPagesAlloc, first look at the function OsVmPagesToOrder, which calculates the number of bidirectional linked lists belonging to the free memory page node linked list array according to the specified number of physical pages. When nPages is the minimum 1, the order value is 0; When it is 2, the order value of 1... Is equal to the logarithm Log2(nPages) with the bottom of 2.

#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;
}

Continue to analyze the function OsVmPhysPagesAlloc(), which requests a specified number of memory pages from the specified memory segment based on the passed in parameters. The function called at (1) has been described above, and the index value of the linked list array is calculated according to the number of memory pages. If the index value is less than the maximum index value of the linked list VM_LIST_ORDER_MAX, execute (2) to cycle each bidirectional linked list from small memory page node to large memory page node. (3) obtain the two-way linked list at. If the free linked list is empty, continue the cycle; If it is not empty, execute (4) to obtain the free memory page structure on the linked list.

If the array index value calculated according to the number of memory pages is greater than or equal to the maximum index value VM of the linked list_ LIST_ ORDER_ Max, indicating that there is no such large memory page node on the free linked list, which needs to be applied from the physical memory segment. It needs to execute (5) call the function OsVmPhysLargeAlloc() to apply for a large memory page. If the memory page cannot be applied for, the application fails and returns NULL; If an appropriate memory page is requested, continue to execute the subsequent DONE tag code. These codes are deleted from the free linked list, split, and redundant free memory pages are inserted into the free linked list. Later, we will continue to analyze these functions called. First, look at the actual input parameters of these parameters. Order is the linked list array index corresponding to the memory page to be applied, and newOrder is the linked list array index corresponding to the memory page actually applied. In the for loop condition at (6), & page [npages] is the end address of the memory page structure to be applied, &tmp [1 < < newOrder] represents the end address of the memory block on the free memory page node linked list of the partner algorithm. Why use the for loop here? When applying for memory, multiple memory nodes should be applied for splicing. Look at the incoming parameters of the function at (7), & page [npages] is the end address of the memory page structure to be applied, and the subsequent parts are split into the free linked list. (1 < < min (order, newOrder)) indicates the number of actually applied memory pages.

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;
}

3.1.2.3 function OsVmPhysLargeAlloc

When this function is executed, it indicates that the size of a single memory page node on the free linked list cannot meet the requirements and exceeds the size of the memory page node on the 9th linked list. (1) calculate the memory size to be applied at. ⑵ traverse each memory page node from the largest linked list. (3) calculate the end address of the required memory according to the start memory address of each memory page. If it exceeds the size of the memory segment, continue to traverse the next memory page node.

(4) at this time, paStart indicates the end address of the current memory page, and then paStart > = paend indicates that the size of the current memory page meets the requirements of the application; An overflow error occurs in paStart < seg - > start and paStart > = (seg - > start + seg - > size), and the end address of the memory page is not within the address range of the memory segment. (5) represents the structure of the next memory page of the current memory page. If the structure is not on the free linked list, break jumps out of the loop. If it is on the free linked list, it means that continuous free memory pages will be spliced to meet the needs of large memory applications. (6) indicates that the size of one or more consecutive memory pages meets the application requirements.

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 functions OsVmPhysFreeListDelUnsafe and OsVmPhysFreeListAddUnsafe

The internal function OsVmPhysFreeListDelUnsafe is used to delete a memory page node from the linked list of free memory page nodes. The word Unsafe is in the name because there is no spin lock on the linked list operation in the function body, and the security is guaranteed by the external calling function. (1) check at to ensure that the memory segment and free linked list index meet the requirements. (2) obtain the memory segment and free linked list at (3), reduce the number of memory page nodes on the free linked list by 1, and delete the memory block from the free linked list. (4) set the order index value of the memory page as the maximum value to mark the non free memory page.

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;
}

The function corresponding to the deletion on the free linked list is the node function OsVmPhysFreeListAddUnsafe for inserting free memory pages on the free linked list. (1) update the index value of the free linked list to be mounted of the memory page at, then obtain the memory segment seg where the memory page is located, and obtain the free linked list corresponding to the index value. (2) insert the free memory page node into the free linked list and update the number of nodes.

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 function osvmphyspagesspillunsafe

The function osvmphyspagesspillunsafe is used to split memory blocks. In the parameters, oldOrder represents the linked list index corresponding to the memory page node to be applied for, and newOrder represents the linked list index corresponding to the memory page node actually applied for. If the index values are equal, no splitting is required and the code of the for loop block is not executed. Due to the characteristics of the elements in the linked list array in the partner algorithm, that is, the size of the memory page node in each linked list is equal to the power of 2 memory pages. During splitting, traverse from the high index newOrder to the low index oldOrder in turn, split a memory page node and mount it on the corresponding free linked list as a free memory page node. (1) start the cycle from high index to low index, reduce the index value by 1, and then execute (2) to obtain the partner memory page node. It can be seen that when the applied memory block is greater than the demand, the high address part of the second half will be put into the free linked list, and the low address part of the first half will be retained. (3) the assertion at ensures that the index value of the partner memory page node is the maximum value, indicating that it belongs to the free memory page node. (4) call the function at to put the memory page node into the free linked list.

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);
    }
}

It is necessary to put this picture here for visual demonstration. Suppose we need to apply for 8 memory nodes with the size of memory pages, but only the freeList[7] linked list has free nodes. After the application is successful, it exceeds the size required by the application and needs to be split. Divide 2 ^ 7 memory pages into 2 nodes with a size of 2 ^ 6 memory pages. Continue to split the first and mount the second on the freeList[6] linked list. Then, split the first 2 ^ 6 memory pages into 2 2 ^ 5 memory page nodes, continue to split the first, and mount the second on the freeList[5] linked list. Proceed in sequence. Finally, it is divided into two memory page nodes with the size of 2 ^ 3 memory pages. The first is returned as the actually applied memory page, and the second is mounted on the freeList[3] linked list. As shown in the red part of the figure below.

In addition, the function OsVmRecycleExtraPages will call osvmphyspagesreecontiguous to reclaim the applied redundant memory pages, which will be analyzed later.

3.2 release physical memory page interface

3.2.1 introduction to page interface for releasing physical memory

Corresponding to the interface for applying for physical memory pages, there are three interfaces for releasing physical memory pages, which are respectively used to meet different requirements for releasing memory pages. Function Los_ The passed in parameters of physpagesfreeconfiguration are the virtual memory address and the number of memory pages in the kernel virtual address space corresponding to the physical page to be released. (1) call the function OsVmVaddrToPage at to convert the virtual memory address into the physical memory page structure address, and then set the number of consecutive memory pages of the memory page to 0 at (2). (3) call the function osvmphyspagesfreecontiniguous() at to release the physical memory page. Function LOS_PhysPageFree is used to release a physical memory page. The passed in parameter is the physical page structure address corresponding to the physical page to be released. (4) the reference count is self decremented at. When it is less than or equal to 0, it indicates that there are no other references, and then the release operation is further executed. This function will also call the function osvmphyspagesfreecontiniguous() to free up physical memory pages. Function LOS_PhysPagesFree is used to release multiple physical memory pages hung on the two-way linked list. The return value is the actual number of released physical pages. (5) traverse the memory page bidirectional linked list at and remove the memory page node to be released from the linked list. The code at (6) is the same as the function code for releasing a memory page. (7) calculate the number of memory pages traversed, and the function will return this value at the end.

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 implementation of internal interface for releasing physical memory pages

3.2.2.1 function OsVmVaddrToPage

The function OsVmVaddrToPage converts the virtual memory address to the physical page structure address. (1) call function Los at_ Paddrquery() converts a virtual address into a physical address. This function will be described in detail in the virtual real mapping section. (2) traverse the physical memory segment at. If the physical memory address is within the address range of the physical memory segment, the physical page structure address corresponding to the physical address can be returned.

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;
}

3.2.2.2 function osvmphyspagesfreeconteguous

The function OsVmPhysPagesFreeContiguous() frees a specified number of contiguous physical memory pages. (1) obtain the corresponding physical memory address according to the physical memory page. (2) obtain the index value of the free memory page linked list array according to the physical memory address (TODO, why does the physical memory address correspond to the index?) and (3) obtain the number of memory pages of the memory page node on the linked list corresponding to the index value. (4) if the number of memory pages nPages to be released is less than the number of memory page nodes on the current linked list, jump out of the loop and execute the code at (6) to release it to the two-way linked list with small index. (5) call the function OsVmPhysPagesFree() at to release the memory pages on the specified linked list, and then update the number of memory pages and the address of the memory page structure.

(6) calculate the corresponding linked list index according to the number of memory pages, and calculate the size of memory page nodes on the linked list according to the index value. (7) call the function OsVmPhysPagesFree() at to release the memory pages on the specified linked list, and then update the number of memory pages and the address of the memory page structure.

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;
    }
}

3.2.2.3 function OsVmPhysPagesFree

The function OsVmPhysPagesFree() releases the memory page to the corresponding free memory page linked list. (1) check the incoming parameters. ⑵ at least the penultimate linked list is required, so that the memory page nodes can be merged with the nodes on the large index linked list. (3) obtain the physical memory address corresponding to the memory page at. (4) VM at_ ORDER_ TO_ Phys (order) calculates the physical address corresponding to the linked list index value, and then performs XOR operation to calculate the physical memory address of the partner page. (5) convert the physical address at to the memory page structure, and further judge that if the memory page does not exist or is not on the free linked list, it will jump out of the loop while loop. Otherwise, execute (6) to remove the partner page from the linked list, and then add 1 to the index value. (7) update the physical address and its aligned memory page at (TODO does not understand). When the index order is 8 and you want to insert it into the last linked list, execute (8) directly to insert the memory page into the linked list.

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 query physical page address interface

3.3.1 function LOS_VmPageGet()

Function LOS_VmPageGet is used to calculate the corresponding physical memory page structure address according to the physical memory address parameter. (1) traverse the physical memory segment, call the function OsVmPhysToPage to calculate the physical memory page structure according to the physical memory address and memory segment number, and the function will be analyzed later. (2) if the physical memory page structure obtained at is not empty, the loop will jump out and the physical memory page structure pointer will be returned.

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;
}

Continue to look at the code of the function OsVmPhysToPage. (1) if the physical memory address passed in by the parameter is not within the address range of the specified physical memory segment, NULL will be returned. (2) calculate the offset value of the physical memory address from the memory segment start address at. (3) calculate the number of offset memory pages according to the offset value, and then return the address of the physical page structure corresponding to the physical memory address.

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 function LOS_PaddrToKVaddr

Function LOS_PaddrToKVaddr obtains its corresponding kernel virtual address according to the physical address. (1) traverse the physical memory segment array at (2), and then judge at (2). If the physical address is within the address range of the traversed physical memory segment, execute (3). The offset of the incoming physical memory address from the physical memory start address plus the start address of the kernel state virtual address space is the kernel virtual address corresponding to the physical address.

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 other functions

3.4.1 OsPhysSharePageCopy function

The OsPhysSharePageCopy function is used to copy shared memory pages. Parameter verification is performed at (1), old memory pages are obtained at (2), and memory segments are obtained at (3). (4) if the old memory page reference count is 1, the old physical memory address is directly assigned to the new physical memory address. (5) if the memory page has multiple references, first convert it to the virtual memory address, and then execute (6) to copy the contents of the memory page. (7) refresh the reference count of new and old memory pages.

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;
}

summary

This paper first understands the structure of physical memory management, then reads how to initialize physical memory, and then analyzes the source code of operation interfaces such as application, release and query of physical memory. More sharing articles will be launched in the future. Please look forward to it. If you have any questions or suggestions, you can leave a message to me. thank you.

 

Click focus to learn about Huawei cloud's new technologies for the first time~

Tags: gitee harmonyos liteos

Posted on Mon, 08 Nov 2021 08:28:26 -0500 by lottos