SylixOS 内存管理源代码分析--phyPage.c
phyPage.c是对物理页面管理相关的内容。在SylixOS中虚拟页面和物理页面都是使用相同的数据结构管理,下文使用vmpage代表这个结构体,如下图
在物理页面中不管存在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 文件里指定的
当时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);
}
首先要获得一个交换区,这里其实是临时的虚拟地址的作用,需要把分配出来的物理地址先映射到这个虚拟地址上来
_G_ulVmmSwitchAddr 初始化时在__vmmVirtualCreate函数中,在首次创建虚拟zone时会空出一个页面的大小(默认页面大小为4K)当做赋值 给_G_ulVmmSwitchAddr。
然后调用上面讲到的__vmmPhysicalPageAlloc分配一个页面,将分配出来的物理页面和_G_ulVmmSwitchAddr虚拟地址进行映射。__vmmLibPageMap函数在pageTable.c文件中,上一篇文章分析过这个函数链接。此时原物理页面控制块vmpage映射的虚拟地址和新分配出来的vmpage 虚拟已经都有了,然后调用拷贝函数进行拷贝一个页面,这个拷贝函数在不同体系结构下实现不同,是用对应的汇编实现的。如果拷贝成功,需要将cache数据回写到内存中。最后设置物理地址的访问属性并且刷新TLB。
__vmmPhysicalPageRef
此函数功能是引用一个物理页面,在SylixOS中物理页面可能存在引用关系,vmpage并不是真实的物理页面。
当时引用其他页面时需要使用这个几个参数。
/*********************************************************************************************************
** 函数名称: __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会写等。