SylixOS 内存管理源代码分析--phyPage.c

phyPage.c是对物理页面管理相关的内容。在SylixOS中虚拟页面和物理页面都是使用相同的数据结构管理,下文使用vmpage代表这个结构体,如下图

SylixOS 内存管理源代码分析--phyPage.c

在物理页面中不管存在zone数组,还有一个内核内存信息的数组,用来保存代码段和数据段的大小。

/*********************************************************************************************************
  物理 zone 控制块数组
*********************************************************************************************************/
LW_VMM_ZONE                 _G_vmzonePhysical[LW_CFG_VMM_ZONE_NUM];     /*  物理区域                    */
/*********************************************************************************************************
  物理内存 text data 段大小
*********************************************************************************************************/
static LW_MMU_PHYSICAL_DESC _G_vmphydescKernel[2];                      /*  内核内存信息                */

 

 __vmmPhysicalCreate

此函数是物理内核页面创建函数

/*********************************************************************************************************
** 函数名称: __vmmPhysicalCreate
** 功能描述: 创建一个物理分页区域.
** 输 入  : pphydesc          物理区域描述表
** 输 出  : ERROR CODE
** 全局变量: 
** 调用模块: 
*********************************************************************************************************/
ULONG  __vmmPhysicalCreate (LW_MMU_PHYSICAL_DESC  pphydesc[])
{
    REGISTER ULONG  ulError = ERROR_NONE;
    static   ULONG  ulZone  = 0;                                        /*  可多次追尾添加内存          */
             INT    i;
             
    for (i = 0; ; i++) {
        if (pphydesc[i].PHYD_stSize == 0) {
            break;
        }
        
        _BugFormat(!ALIGNED(pphydesc[i].PHYD_ulPhyAddr, LW_CFG_VMM_PAGE_SIZE), LW_TRUE,
                   "physical zone vaddr 0x%08lx not page aligned.\r\n", 
                   pphydesc[i].PHYD_ulPhyAddr);
        
        switch (pphydesc[i].PHYD_uiType) {
        
        case LW_PHYSICAL_MEM_TEXT:
            if (_G_vmphydescKernel[LW_PHYSICAL_MEM_TEXT].PHYD_stSize) {
                _G_vmphydescKernel[LW_PHYSICAL_MEM_TEXT].PHYD_stSize += pphydesc[i].PHYD_stSize;
            
            } else {
                _G_vmphydescKernel[LW_PHYSICAL_MEM_TEXT] = pphydesc[i];
            }
            break;
            
        case LW_PHYSICAL_MEM_DATA:
            if (_G_vmphydescKernel[LW_PHYSICAL_MEM_DATA].PHYD_stSize) {
                _G_vmphydescKernel[LW_PHYSICAL_MEM_DATA].PHYD_stSize += pphydesc[i].PHYD_stSize;
            
            } else {
                _G_vmphydescKernel[LW_PHYSICAL_MEM_DATA] = pphydesc[i];
            }
            break;
        
        case LW_PHYSICAL_MEM_DMA:
            _BugHandle((pphydesc[i].PHYD_ulPhyAddr == (addr_t)LW_NULL), LW_TRUE,
                       "physical DMA zone can not use NULL address, you can move offset page.\r\n");
                                                                        /*  目前不支持 NULL 起始地址    */
            if (ulZone < LW_CFG_VMM_ZONE_NUM) {
                _BugFormat(__vmmLibVirtualOverlap(pphydesc[i].PHYD_ulPhyAddr, 
                                                  pphydesc[i].PHYD_stSize), LW_TRUE,
                           "physical zone paddr 0x%08lx size: 0x%08zx overlap with virtual space.\r\n",
                           pphydesc[i].PHYD_ulPhyAddr, pphydesc[i].PHYD_stSize);
            
                ulError = __pageZoneCreate(&_G_vmzonePhysical[ulZone], 
                                           pphydesc[i].PHYD_ulPhyAddr, 
                                           pphydesc[i].PHYD_stSize,
                                           LW_ZONE_ATTR_DMA, 
                                           __VMM_PAGE_TYPE_PHYSICAL);   /*  初始化物理 zone             */
                if (ulError) {
                    _DebugHandle(__ERRORMESSAGE_LEVEL, "kernel low memory.\r\n");
                    _ErrorHandle(ulError);
                    return  (ulError);
                }
                ulZone++;
            }
            break;
            
        case LW_PHYSICAL_MEM_APP:
            _BugHandle((pphydesc[i].PHYD_ulPhyAddr == (addr_t)LW_NULL), LW_TRUE,
                       "physical APP zone can not use NULL address, you can move offset page.\r\n");
                                                                        /*  目前不支持 NULL 起始地址    */
            if (ulZone < LW_CFG_VMM_ZONE_NUM) {
                ulError = __pageZoneCreate(&_G_vmzonePhysical[ulZone], 
                                           pphydesc[i].PHYD_ulPhyAddr, 
                                           pphydesc[i].PHYD_stSize,
                                           LW_ZONE_ATTR_NONE, 
                                           __VMM_PAGE_TYPE_PHYSICAL);   /*  初始化物理 zone             */
                if (ulError) {
                    _DebugHandle(__ERRORMESSAGE_LEVEL, "kernel low memory.\r\n");
                    _ErrorHandle(ulError);
                    return  (ulError);
                }
                ulZone++;
            }
            break;
            
        default:
            break;
        }
    }
    
    return  (ERROR_NONE);
}

首先判断当前物理区域的类型,地址,大小和属性都是在bsp的bspMap.h 文件里指定的

SylixOS 内存管理源代码分析--phyPage.c

当时TEXT和DATA段时将其统计到内核信息数组中。 如果是APP和DMA区域就要创建一个zone。这里创建的是物理的zone。zone分为物理的和虚拟地址的两种。调用__pageZoneCreate函数创建一个zone 。此函数在pageLib.c中,可以参考链接链接

创建完成zone以后,以下函数都是和分配空间释放空间相关。分配和释放操作会大量调用pageLib.c中的函数来实现。

 __vmmPhysicalPageAlloc 

此函数的功能是从zone中分配一个物理页面控制制块出来,页就是最开始介绍的结构体,里面包含了其实地址,和页面数,使用

vmpage代表这个结构体实体。

/*********************************************************************************************************
** 函数名称: __vmmPhysicalPageAlloc
** 功能描述: 分配指定的连续物理页面
** 输 入  : ulPageNum     需要分配的物理页面个数
**           uiAttr        需要满足的物理页面属性
**           pulZoneIndex  物理 zone 下标
** 输 出  : 页面控制块
** 全局变量: 
** 调用模块: 
*********************************************************************************************************/
PLW_VMM_PAGE  __vmmPhysicalPageAlloc (ULONG  ulPageNum, UINT  uiAttr, ULONG  *pulZoneIndex)
{
             INT            i;
    REGISTER PLW_VMM_ZONE   pvmzone;
    REGISTER PLW_VMM_PAGE   pvmpage = LW_NULL;
    
    for (i = 0; i < LW_CFG_VMM_ZONE_NUM; i++) {                         /*  优先选择 uiAttr 相同的 zone */
        pvmzone = &_G_vmzonePhysical[i];
        if (!pvmzone->ZONE_stSize) {                                    /*  无效 zone                   */
            break;
        }
        if (pvmzone->ZONE_uiAttr == uiAttr) {
            if (pvmzone->ZONE_ulFreePage >= ulPageNum) {
                pvmpage = __pageAllocate(pvmzone, ulPageNum, __VMM_PAGE_TYPE_PHYSICAL);
                if (pvmpage) {
                    *pulZoneIndex = (ULONG)i;
                    return  (pvmpage);
                }
            }
        }
    }
    
    for (i = 0; i < LW_CFG_VMM_ZONE_NUM; i++) {
        pvmzone = &_G_vmzonePhysical[i];
        if (!pvmzone->ZONE_stSize) {                                    /*  无效 zone                   */
            break;
        }
        if ((pvmzone->ZONE_uiAttr & uiAttr) == uiAttr) {                /*  其次选择拥有 uiAttr 的 zone */
            if (pvmzone->ZONE_ulFreePage >= ulPageNum) {
                pvmpage = __pageAllocate(pvmzone, ulPageNum, __VMM_PAGE_TYPE_PHYSICAL);
                if (pvmpage) {
                    *pulZoneIndex = (ULONG)i;
                    return  (pvmpage);
                }
            }
        }
    }
    
    return  (LW_NULL);
}

 此函数主要是使用__pageAllocate 函数去分配指定的页面数量,__pageAllocate函数在pagelib.c中实现,现在其实可以更好的理解这个pageLib.c名称,无论是虚拟页面还是物理页面分配时都以调用其中的函数,像库一样使用。根据注释很容易明白这段代码的意思,首先zone是存在属性的,分配物理页面首先在属性相同的zone去分配,如果没有分配失败,则在不同属性的zone去分配。还有一个函数__vmmPhysicalPageAllocZone是在指定zone中分配空闲物理页面出来。

/*********************************************************************************************************
** 函数名称: __vmmPhysicalPageAllocZone
** 功能描述: 分配指定的连续物理页面 (指定物理区域)
** 输 入  : ulZoneIndex   物理区域
**           ulPageNum     需要分配的物理页面个数
**           uiAttr        需要满足的页面属性
** 输 出  : 页面控制块
** 全局变量: 
** 调用模块: 
*********************************************************************************************************/
PLW_VMM_PAGE  __vmmPhysicalPageAllocZone (ULONG  ulZoneIndex, ULONG  ulPageNum, UINT  uiAttr)

此函数只是在上一个函数的基础上少了在别的属性zone中查找。

以上分配函数并没有指定对齐,__vmmPhysicalPageAllocAlign函数指定了对齐方式

/*********************************************************************************************************
** 函数名称: __vmmPhysicalPageAllocAlign
** 功能描述: 分配指定的连续物理页面 (可指定对齐关系)
** 输 入  : ulPageNum     需要分配的物理页面个数
**           stAlign       内存对齐关系
**           uiAttr        需要满足的物理页面属性
**           pulZoneIndex  物理 zone 下标
** 输 出  : 页面控制块
** 全局变量: 
** 调用模块: 
*********************************************************************************************************/
PLW_VMM_PAGE  __vmmPhysicalPageAllocAlign (ULONG   ulPageNum, 
                                           size_t  stAlign, 
                                           UINT    uiAttr, 
                                           ULONG  *pulZoneIndex)

__vmmPhysicalPageAllocAlign函数和__vmmPhysicalPageAlloc函数基本相同,在分配的时候换成了__pageAllocateAlign函数来分配,当然此函数也在pageLib.c文件中。

__vmmPhysicalPageClone

此函数的功能是克隆一个物理页面。不过有个概念需要理解,克隆物理页面并不能直接对物理页面进行操作,需要将分配出来的物理页面映射为虚拟地址,然后对虚拟地址进行拷贝。

/*********************************************************************************************************
** 函数名称: __vmmPhysicalPageClone
** 功能描述: 克隆一个物理页面
** 输 入  : pvmpage       页面控制块
** 输 出  : 新的物理页面
** 全局变量: 
** 调用模块: 
*********************************************************************************************************/
PLW_VMM_PAGE  __vmmPhysicalPageClone (PLW_VMM_PAGE  pvmpage)
{
    PLW_VMM_PAGE    pvmpageNew;
    addr_t          ulSwitchAddr = __vmmVirtualSwitch();
    ULONG           ulZoneIndex;
    ULONG           ulError;
    
    if ((pvmpage->PAGE_ulCount != 1) ||
        (pvmpage->PAGE_iPageType != __VMM_PAGE_TYPE_PHYSICAL) ||
        (pvmpage->PAGE_ulMapPageAddr == PAGE_MAP_ADDR_INV)) {           /*  必须是有映射关系的单页面    */
        _ErrorHandle(ERROR_VMM_PHYSICAL_PAGE);
        return  (LW_NULL);
    }
    
    pvmpageNew = __vmmPhysicalPageAlloc(1, LW_ZONE_ATTR_NONE, &ulZoneIndex);
    if (pvmpageNew == LW_NULL) {
        _ErrorHandle(ERROR_VMM_LOW_PHYSICAL_PAGE);
        return  (LW_NULL);
    }
    
    ulError = __vmmLibPageMap(pvmpageNew->PAGE_ulPageAddr,              /*  使用 CACHE 操作             */
                              ulSwitchAddr, 1,                          /*  缓冲区虚拟地址              */
                              LW_VMM_FLAG_RDWR);                        /*  映射指定的虚拟地址          */
    if (ulError) {
        __vmmPhysicalPageFree(pvmpageNew);
        return  (LW_NULL);
    }
    
    KN_COPY_PAGE((PVOID)ulSwitchAddr, 
                 (PVOID)pvmpage->PAGE_ulMapPageAddr);                   /*  拷贝页面内容                */
               
    if (API_CacheAliasProb()) {                                         /*  cache 别名可能              */
        API_CacheClearPage(DATA_CACHE, 
                           (PVOID)ulSwitchAddr,
                           (PVOID)pvmpageNew->PAGE_ulPageAddr,
                           LW_CFG_VMM_PAGE_SIZE);                       /*  将数据写入内存并不再命中    */
    }
    
    __vmmLibSetFlag(ulSwitchAddr, 1, LW_VMM_FLAG_FAIL, LW_TRUE);        /*  VIRTUAL_SWITCH 不允许访问   */
    
    return  (pvmpageNew);
}

首先要获得一个交换区,这里其实是临时的虚拟地址的作用,需要把分配出来的物理地址先映射到这个虚拟地址上来 

SylixOS 内存管理源代码分析--phyPage.c

_G_ulVmmSwitchAddr 初始化时在__vmmVirtualCreate函数中,在首次创建虚拟zone时会空出一个页面的大小(默认页面大小为4K)当做赋值 给_G_ulVmmSwitchAddr。

然后调用上面讲到的__vmmPhysicalPageAlloc分配一个页面,将分配出来的物理页面和_G_ulVmmSwitchAddr虚拟地址进行映射。__vmmLibPageMap函数在pageTable.c文件中,上一篇文章分析过这个函数链接。此时原物理页面控制块vmpage映射的虚拟地址和新分配出来的vmpage 虚拟已经都有了,然后调用拷贝函数进行拷贝一个页面,这个拷贝函数在不同体系结构下实现不同,是用对应的汇编实现的。如果拷贝成功,需要将cache数据回写到内存中。最后设置物理地址的访问属性并且刷新TLB。

__vmmPhysicalPageRef

此函数功能是引用一个物理页面,在SylixOS中物理页面可能存在引用关系,vmpage并不是真实的物理页面。

SylixOS 内存管理源代码分析--phyPage.c

当时引用其他页面时需要使用这个几个参数。

/*********************************************************************************************************
** 函数名称: __vmmPhysicalPageRef
** 功能描述: 引用物理页面
** 输 入  : pvmpage       页面控制块
** 输 出  : NONE
** 全局变量: 
** 调用模块: 
*********************************************************************************************************/
PLW_VMM_PAGE  __vmmPhysicalPageRef (PLW_VMM_PAGE  pvmpage)
{
    PLW_VMM_PAGE    pvmpageFake = (PLW_VMM_PAGE)__KHEAP_ALLOC(sizeof(LW_VMM_PAGE));
    PLW_VMM_PAGE    pvmpageReal;
    
    if (pvmpageFake == LW_NULL) {
        _DebugHandle(__ERRORMESSAGE_LEVEL, "kernel low memory.\r\n");
        _ErrorHandle(ERROR_KERNEL_LOW_MEMORY);                          /*  缺少内核内存                */
        return  (LW_NULL);
    }
    
    *pvmpageFake = *pvmpage;
    
    if (LW_VMM_PAGE_IS_FAKE(pvmpage)) {
        pvmpageReal = LW_VMM_PAGE_GET_REAL(pvmpage);
    
    } else {
        pvmpageReal = pvmpage;
    }
    
    pvmpageReal->PAGE_ulRef++;                                          /*  真实页面引用++              */
    
    _INIT_LIST_LINE_HEAD(&pvmpageFake->PAGE_lineFreeHash);
    _INIT_LIST_LINE_HEAD(&pvmpageFake->PAGE_lineManage);
    
    pvmpageFake->PAGE_ulRef         = 0ul;                              /*  fake 页面, 引用计数无效     */
    pvmpageFake->PAGE_pvmpageReal   = pvmpageReal;
    pvmpageFake->PAGE_ulMapPageAddr = PAGE_MAP_ADDR_INV;
    
    return  (pvmpageFake);
}

首先需要判断当前的vmpage是否本身就不是一个真实的物理页面,是引用物理页面。 LW_VMM_PAGE_IS_FAKE这个宏首选判断PPAGE_pvmpageReal这个指针是否为空,如果不为空就代表这不是一个真实的页面,而是指向了其他页面。LW_VMM_PAGE_GET_REAL获得指向页面。如果PPAGE_pvmpageReal为空它本省就是个真实页面,pvmpageReal就直接等于vmpgage。在获得真实页面后pvmpageReal的引用计数加1.同事将新分配的vmpage指向pvmpageReal。

__vmmPhysicalPageFree 

此函数是释放一个物理页面控制块,不过需要根据是否是一个真实的页面进行不同的处理。

 

/*********************************************************************************************************
** 函数名称: __vmmPhysicalPageFree
** 功能描述: 回收指定的连续物理页面
** 输 入  : pvmpage       页面控制块
** 输 出  : NONE
** 全局变量: 
** 调用模块: 
*********************************************************************************************************/
VOID  __vmmPhysicalPageFree (PLW_VMM_PAGE  pvmpage)
{
    REGISTER PLW_VMM_ZONE   pvmzone = pvmpage->PAGE_pvmzoneOwner;
             PLW_VMM_PAGE   pvmpageReal;
    
    if (LW_VMM_PAGE_IS_FAKE(pvmpage)) {
        pvmpageReal = LW_VMM_PAGE_GET_REAL(pvmpage);
        __KHEAP_FREE(pvmpage);                                          /*  释放 fake 控制块            */
    
    } else {
        pvmpageReal = pvmpage;
        pvmpageReal->PAGE_ulMapPageAddr = PAGE_MAP_ADDR_INV;            /*  对应的地址不再有效          */
    }
    
    pvmpageReal->PAGE_ulRef--;
    
    if (pvmpageReal->PAGE_ulRef == 0) {
        __pageFree(pvmzone, pvmpageReal);                               /*  没有引用则直接释放          */
    }
}

如果是真实的页面同时没有引用,此时直接调用__pageFree释放函数,此函数在pageLib.c文件中。如果不是真实的页面,是其他的引用,首先释放vmpage的空间资源。因为不是真实的页面控制块都是从堆里分配出来的单独一个,真实的是在链表中。

然后将真实的引用页面引用值减1.

__vmmPhysicalPageFreeAll

此函数是遍历虚拟页面控制块中的物理地址哈希表,然后调用__vmmPhysicalPageFree 函数挨个释放。

__vmmPhysicalPageSetFlag

此函数是设置物理页面的属性,当时其实是通过虚拟地址调用将SylixOS系统标志转换物理页面属性函数实现,也就是说这个物理页面必须被映射过虚拟地址才能设置。

__vmmPhysicalPageFlush

此函数是刷cache,首先是这个物理地址也必须是映射过虚拟地址,通过虚拟地址调用sylixos封装的cache操作函数集。类似的还有物理页面的cache无效,cache会写等。