SylixOS 系统中内存相关的代码放在SylixOS/kernel/vmm文件夹中,pagelib.c 主要是实现了页面的分配和回收。页面分配是以页面控制块进行管理的,配合哈希表。
以下先是对每个文件的源代码分析,然后最后分析调用关系。
首先看下载SylixOS 内存管理常用的结构体
pageLib.c 文件是管理页分配,相关的。
LW_VMM_ZONE 是区域zone 的结构体。在zone会有一个hash 表,这个哈希表用来存放当前空闲的页面。虚拟页面和物理页面都有zone。 LW_VMM_FREEARE 结构体就是这个哈希表中的数据使用使用的结构体。
LW_VMM_PAGE 是SylixOS 页面结构体,在内存关系中虚拟页面和物理页面都是 这个结构体表标记页面相关的信息,在下文总vmpage,页面控制块都是值这个结构体。在这个结构体的第一个是 LW_LIST_LINE,这个LW_LIST_LINE 是SylixOS 内核的链表结构。这里的 PAGE_lineFreeHash 将会被加入到上面结构体LW_VMM_FREEAREA 中FA_lineFreeHeader 的链表,从名称我们也能看出这是链表的表头。空闲的page所在的LW_VMM_PAGE 结构加入到这个链表中。
+---------------+
| LW_VMM_ZONE |
+---------------+
| |
+---------------+
| |
| |
| |
| |
+---------------+ +-----------+ +-----------+
|LW_VMM_FREEAREA| |LW_VMM_PAGE| |LW_VMM_PAGE|
+---------------+ +-----+-----+ +-----+-----+
| List_header +------->+prev | next+---------->priv | next|
+---------------+ +-----+-----+ +-----+-----+
| count | | | | |
+---------------+ +-----------+ +-----------+
大体关系如上图,在LW_VMM_ZONE 结构中包含了LW_VMM_FREEAREA结构的数组,因为哈希表是数组和链表的组合。所以到时根据分配的页表数目,使用算法生成index索引值。找到对应的数组,通过链表的方式查找拥有这么多空闲的页的LW_VMM_PAGE。
__pageZoneCreate 函数
/*********************************************************************************************************
** 函数名称: __pageZoneCreate
** 功能描述: 创建一个 page zone
** 输 入 : pvmzone 需要填充的结构体
** ulAddr 页面起始地址
** stSize 页面大小
** uiAttr 区域属性
** iPageType 页面类型
** 输 出 : ERROR CODE
** 全局变量:
** 调用模块:
*********************************************************************************************************/
ULONG __pageZoneCreate (PLW_VMM_ZONE pvmzone,
addr_t ulAddr,
size_t stSize,
UINT uiAttr,
INT iPageType)
{
PLW_VMM_PAGE pvmpage;
pvmpage = __pageCbAlloc(iPageType);
if (pvmpage == LW_NULL) {
_ErrorHandle(ERROR_KERNEL_LOW_MEMORY); /* 缺少内核内存 */
return (ERROR_KERNEL_LOW_MEMORY);
}
pvmzone->ZONE_ulFreePage = (ULONG)(stSize >> LW_CFG_VMM_PAGE_SHIFT);
pvmzone->ZONE_ulAddr = ulAddr;
pvmzone->ZONE_stSize = stSize;
pvmzone->ZONE_uiAttr = uiAttr;
lib_bzero(pvmzone->ZONE_vmfa, sizeof(LW_VMM_FREEAREA) * LW_CFG_VMM_MAX_ORDER);
_LIST_LINE_INIT_IN_CODE(pvmpage->PAGE_lineManage); /* 没有邻居 */
pvmpage->PAGE_ulCount = pvmzone->ZONE_ulFreePage;
pvmpage->PAGE_ulPageAddr = ulAddr;
pvmpage->PAGE_iPageType = iPageType;
pvmpage->PAGE_ulFlags = 0;
pvmpage->PAGE_bUsed = LW_FALSE;
pvmpage->PAGE_pvmzoneOwner = pvmzone; /* 记录所属 zone */
if (iPageType == __VMM_PAGE_TYPE_PHYSICAL) {
pvmpage->PAGE_ulMapPageAddr = PAGE_MAP_ADDR_INV; /* 没有映射关系 */
pvmpage->PAGE_iChange = 0; /* 没有变化过 */
pvmpage->PAGE_ulRef = 0ul; /* 引用计数初始为 0 */
pvmpage->PAGE_pvmpageReal = LW_NULL; /* 真实页面 */
} else {
pvmpage->PAGE_pvAreaCb = LW_NULL;
__pageInitLink(pvmpage);
}
__pageAddToFreeHash(pvmzone, pvmpage); /* 插入空闲表 */
return (ERROR_NONE);
}
此函数的功能是创建zone分区,关于zone分区是将一个指定内存段指定为zone。虚拟页面和物理页面都zone。
在SylixOS中zone的最大个数默认是8个。在创建zone时,调用者会传入起始地址和大小。属性目前为正常和DMA
iPageType 类型,分为物理页面和虚拟页面。物理页面为0,虚拟页面为1。
首先是调用__pageCbAlloc函数分配一个页面控制块,根据是物理页面还是虚拟页面。采取了不同的策略。
物理页面在系统启动时会提前分配好放到链表中,使用的时候从链表中分配一个,虚拟页面需要从堆中分配一个,分配提前预留出哈希表数组的空间。此时就获得了一个PLW_VMM_PAGE 结构的实体。然后给zone空间的地址,大小,属性,空闲页面数等赋值,LW_CFG_VMM_PAGE_SHIFT指定了页面的大小,默认是12,所以是右移12位,也就是页面大小为4K。然后给PLW_VMM_PAGE相关变量赋值,包含空闲页面个数,由于创建zone是在系统初始化时,页面还没被分配过,虚拟剩余页面和zone是一样的。然后标记物理还是虚拟页面。如果是物理页面还要赋值映射关系,引用计数等。如果是虚拟页面需要调用__pageInitLink函数,此函数是虚拟页面控制块中对应的物理页面控制块。也是通过哈希表的方式,因为这里是刚初始化,此时没有虚拟页面对物理页面的映射关系。
最后调用__pageAddToFreeHash函数,以下是__pageAddToFreeHash函数源码
首先通过vmpage 中空闲的页表数量,算出哈希表的index索引值。然后获取到free page 数组中对应的结构体,然后将这个vmpage加入到这个链表中。等分配页面时候,根据需要分配的数量来计算出哈希表索引值,然后就获得了对应数量分为的vmpage链表。__pageFreeHashIndex生成索引值非常简单,就是2的几次方。
举例:当前vm page 有个6个空闲页面时,index返回是3。也就是从哈希表中数组中找下标为3的。根据加入找对应链表加入进去。这样在分配页面时会有个问题,就是假如我要分配7个页面,我需要从哈希表中找3下标对应的链表。但是如果链表中的vm page都是剩余6空闲页面,那就不足。这样需要在分类的时候将索引值加1来解决这个问题。
__pageLink函数
此函数是在虚拟页中加入一个物理页面。原理和在zone中维护一个哈希表包含页面控制块一样,首先通过这个物理页面被映射到的虚拟页面地址,算出一个索引值。然后通过数组找到链表头,加入到链表中。
__pageGetMinContinue
此函数从指定的zone获取了第一个PLW_VMM_PAGE 结构体中空闲页面的数量。外部调用这个函数时可以查找链表中的下一个PLW_VMM_PAGE 结构,查找到最小的空闲页面数。
__pageTraversalLink
此函数非常简单以为在虚拟的vmpage中含有一个哈希表保存映射的物理地址,在__pageLink函数中功能类似,只不过这个是把哈希表中全部的数据读出来。
__pageFindLink
此函数是从虚拟页面控制块中哈希表中查找物理地址映射到指定虚拟地址的那个物理内存控制块。首先从虚拟地址算出哈希表index,然后获得链表头,这个链表中查找对应的。
__pageUnlink
此函数的功能是接触虚拟页面vmpage和物理页面的vmpage之间的关系。还是通过物理vmpage的虚拟地址算出哈希表的index。这个物理页面vmpage中断虚拟地址,是在虚拟地址和物理地址之间建立映射关系的时候设置的。可以看出重点还是找到索引值,然后获得链表头,然后从链表中删除去。
__pageMerge
此函数将两个虚拟地址vmpage合并,首先是将高地址的vmpage 从链表中删除,然后将高地址中包含的空闲页数量,加到地址的页面控制块中。这有个疑问就是如果当前低地址的页面控制块数量加上高地址的空闲页面数量已经大于当前哈希索引值对应的链表怎么办。最后时释放掉高地址所占的空间。(2019年3月18日。找到这个问题解答在合并扩展等虚拟操作块的时候,由于还在使用所以不进行重建哈希索引,是在释放时候才重新建立索引)。
__pageSplit
/*********************************************************************************************************
** 函数名称: __pageSplit
** 功能描述: 将一个虚拟页面控制块分离成两个
** 输 入 : pvmpage 主页面控制块
** ppvmpageSplit 被分离出的页面控制块
** ulPageNum pvmpage 需要保留的页面个数, 剩余部分将分配给 pvmpageSplit.
** pvAreaCb 新的 AreaCb.
** 输 出 : ERROR CODE
** 全局变量:
** 调用模块:
*********************************************************************************************************/
ULONG __pageSplit (PLW_VMM_PAGE pvmpage,
PLW_VMM_PAGE *ppvmpageSplit,
ULONG ulPageNum,
PVOID pvAreaCb)
{
PLW_VMM_PAGE pvmpageSplit;
if (pvmpage->PAGE_iPageType != __VMM_PAGE_TYPE_VIRTUAL) { /* 只能拆分虚拟页面 */
_ErrorHandle(ERROR_VMM_PAGE_INVAL);
return (ERROR_VMM_PAGE_INVAL);
}
if (pvmpage->PAGE_ulCount <= ulPageNum) {
_ErrorHandle(ERROR_VMM_LOW_PAGE); /* 缺少内核内存 */
return (ERROR_VMM_LOW_PAGE);
}
pvmpageSplit = __pageCbAlloc(pvmpage->PAGE_iPageType);
if (pvmpageSplit == LW_NULL) {
_ErrorHandle(ERROR_KERNEL_LOW_MEMORY); /* 缺少内核内存 */
return (ERROR_KERNEL_LOW_MEMORY);
}
pvmpageSplit->PAGE_bUsed = LW_TRUE; /* 正在使用的分页段 */
__pageStructSeparate(pvmpage,
pvmpageSplit,
ulPageNum,
pvmpage->PAGE_iPageType); /* 页面分离 */
pvmpageSplit->PAGE_pvAreaCb = pvAreaCb;
__pageInitLink(pvmpageSplit);
*ppvmpageSplit = pvmpageSplit;
return (ERROR_NONE);
}
此函数时在分配页面时会使用到,当分配页面的时候会调用,SylixOS采用的最佳适配原则分配空间,也就是尽量找和需要分配大小一致的vmpage取分配,vmpage 比需要分配的页面数量大时就需要对当前的vmpage进行切。首先判断当前传入的页面控制块是否是虚拟页面,只能对虚拟页面记性切割。是虚拟页面需要分配出一个虚拟页面控制块的空间。标记为已经使用。然后调用__pageStructSeparate函数
首先将新的vmpage加入到旧的vmpage的邻居链表中。在邻居链表中整个链表是根据地址从低到高链接起来的。由于新分配出来的vmpage地址是高于旧的,所以这里其实是相对往旧的vmpage右边(next)方向添加。然后配置新的vmpage相关参数。
然后调用__pageInitLink函数初始化控制块映射物理页面的哈希表。
__pageExpand
此函数实现对给定的vmpage中的空闲页面进行扩展。
/*********************************************************************************************************
** 函数名称: __pageExpand
** 功能描述: 将一个虚拟页面控制块扩大
** 输 入 : pvmpage 主页面控制块
** ulExpPageNum 需要扩大的页面个数
** 输 出 : ERROR CODE
** 全局变量:
** 调用模块:
*********************************************************************************************************/
ULONG __pageExpand (PLW_VMM_PAGE pvmpage,
ULONG ulExpPageNum)
{
PLW_LIST_LINE plineRight;
PLW_LIST_LINE plineDummyHeader = LW_NULL; /* 用于参数传递的头 */
PLW_VMM_PAGE pvmpageRight;
PLW_VMM_PAGE pvmpageNewFree;
INT iHashIndex;
PLW_VMM_FREEAREA pvmfaEntry;
PLW_VMM_ZONE pvmzone;
if (pvmpage->PAGE_iPageType != __VMM_PAGE_TYPE_VIRTUAL) { /* 只能拓展虚拟页面 */
_ErrorHandle(ERROR_VMM_PAGE_INVAL); /* 缺少内核内存 */
return (ERROR_VMM_PAGE_INVAL);
}
plineRight = _list_line_get_next(&pvmpage->PAGE_lineManage); /* 右分页段 */
if (plineRight) {
pvmpageRight = _LIST_ENTRY(plineRight, LW_VMM_PAGE,
PAGE_lineManage); /* 右分页段地址 */
} else {
_ErrorHandle(ERROR_VMM_LOW_PAGE); /* 缺少可供扩展的页面 */
return (ERROR_VMM_LOW_PAGE);
}
if ((pvmpageRight->PAGE_bUsed) ||
(pvmpageRight->PAGE_ulCount < ulExpPageNum)) {
_ErrorHandle(ERROR_VMM_LOW_PAGE); /* 缺少可供扩展的页面 */
return (ERROR_VMM_LOW_PAGE);
}
pvmzone = pvmpage->PAGE_pvmzoneOwner;
iHashIndex = __pageFreeHashIndex(pvmpageRight->PAGE_ulCount);
pvmfaEntry = &pvmzone->ZONE_vmfa[iHashIndex]; /* 获得右分页段 hash 入口 */
if (pvmpageRight->PAGE_ulCount > ulExpPageNum) { /* 右侧分段还有剩余 */
pvmpageNewFree = __pageCbAlloc(pvmpageRight->PAGE_iPageType);
if (pvmpageNewFree == LW_NULL) {
_ErrorHandle(ERROR_KERNEL_LOW_MEMORY); /* 缺少内核内存 */
return (ERROR_KERNEL_LOW_MEMORY);
}
_List_Line_Del(&pvmpageRight->PAGE_lineFreeHash,
&pvmfaEntry->FA_lineFreeHeader); /* 从空闲表中删除 */
pvmfaEntry->FA_ulCount--;
pvmpageRight->PAGE_bUsed = LW_TRUE; /* 正在使用的分页段 */
pvmpageNewFree->PAGE_bUsed = LW_FALSE; /* 没有使用的分页段 */
__pageStructSeparate(pvmpageRight,
pvmpageNewFree,
ulExpPageNum,
pvmpageRight->PAGE_iPageType); /* 页面分离 */
__pageAddToFreeHash(pvmzone, pvmpageNewFree); /* 将剩余页面插入空闲 hash 表 */
} else {
_List_Line_Del(&pvmpageRight->PAGE_lineFreeHash,
&pvmfaEntry->FA_lineFreeHeader); /* 从空闲表中删除 */
pvmfaEntry->FA_ulCount--;
pvmpageRight->PAGE_bUsed = LW_TRUE; /* 正在使用的分页段 */
}
pvmzone->ZONE_ulFreePage -= ulExpPageNum; /* 更新 zone 控制块 */
pvmpage->PAGE_ulCount += ulExpPageNum; /* 合并到 pvmpage 中 */
_List_Line_Del(&pvmpageRight->PAGE_lineManage,
&plineDummyHeader); /* 从邻居链表中删除 */
__pageCbFree(pvmpageRight); /* 释放页面控制块内存 */
return (ERROR_NONE);
}
首先判断是不是虚拟的页面控制块,然后在vmpage的邻居链表中得右边(也就是next)查找一个vmpage。因为下一个是比自己大的虚拟地址并且连续。然后根据链表找到对应的vmpageRight,然后获得vmpageRight对应的zone。然后判断vmpageRight数量是不是足够扩展的数量,判断是不是在使用状态,此时分两种情况当mvpageRight空闲数量大于需要扩展的数数量时此时需要将vmpageRight对应的哈希链表中删除,然后对虚拟页面控制块进行切割。将新切割出来的标记为未使用,原来的标记为使用加入到vmpage中。第二种是和扩展数量正好相等,此时就不需要切割,直接从当前的哈希对应的链表中删除,然后标记为使用。
以上两种情况最后都需要更新zone。因为是地址连续且是高地址,所以扩展直接vmpage空闲页面数量增加就可以。然后将vmpageRight从邻居链表中删除,释放对应的空间。
__pageAllocateAlign
此函数的功能是分配页面但是需要对齐的方式。
/*********************************************************************************************************
** 函数名称: __pageAllocateAlign
** 功能描述: 从指定的 zone 中分配出连续的页面, 同时满足指定的内存对齐关系.
** (使用 best fit 法则) (之前需要确保 zone 内空闲页数量)
** 输 入 : pvmzone 指定的区域
** ulPageNum 需要获取的页面数量
** stAlign 内存对齐关系 (必须是页面大小的整数倍)
** iPageType 页面类型
** 输 出 : 开辟出的页面控制块
** 全局变量:
** 调用模块:
*********************************************************************************************************/
PLW_VMM_PAGE __pageAllocateAlign (PLW_VMM_ZONE pvmzone,
ULONG ulPageNum,
size_t stAlign,
INT iPageType)
{
REGISTER INT i;
REGISTER INT iHashIndex;
REGISTER PLW_VMM_FREEAREA pvmfaEntry;
PLW_LIST_LINE plineFree;
PLW_VMM_PAGE pvmpageFit = LW_NULL;
PLW_VMM_PAGE pvmpageNewFreeLeft = LW_NULL;
PLW_VMM_PAGE pvmpageNewFreeRight = LW_NULL;
addr_t ulTemp;
addr_t ulFit = ~0;
addr_t ulAlignMask = (addr_t)(stAlign - 1);
ULONG ulLeftFreePageNum;
ULONG ulLeftFreePageNumFit = ~0ul;
iHashIndex = __pageFreeHashIndex(ulPageNum); /* 获得 hash 搜索起始点 */
for (i = iHashIndex; i < LW_CFG_VMM_MAX_ORDER; i++) { /* 从小到大搜索 */
pvmfaEntry = &pvmzone->ZONE_vmfa[i]; /* 获得空闲 hash 表入口 */
if (pvmfaEntry->FA_ulCount) {
for (plineFree = pvmfaEntry->FA_lineFreeHeader;
plineFree != LW_NULL;
plineFree = _list_line_get_next(plineFree)) { /* 遍历链表 */
addr_t ulPageAddr = ((PLW_VMM_PAGE)plineFree)->PAGE_ulPageAddr;
if ((ulPageAddr & ulAlignMask) == 0) { /* 本身就满足对齐条件 */
ulLeftFreePageNum = 0; /* 左端不需要空余页面 */
} else {
ulTemp = (ulPageAddr | ulAlignMask) + 1;
ulLeftFreePageNum = (ulTemp - ulPageAddr) >> LW_CFG_VMM_PAGE_SHIFT;
}
if (((PLW_VMM_PAGE)plineFree)->PAGE_ulCount >=
(ulPageNum + ulLeftFreePageNum)) { /* 此区域有足够大的空间 */
ulTemp = ((PLW_VMM_PAGE)plineFree)->PAGE_ulCount - ulPageNum;
/* 找出最接近分配大小的连续分页*/
if (ulTemp < ulFit) {
pvmpageFit = (PLW_VMM_PAGE)plineFree;
ulFit = ulTemp;
ulLeftFreePageNumFit = ulLeftFreePageNum;
}
}
}
if (ulFit != (~0ul)) { /* 匹配成功 ? */
break;
}
}
}
if (i >= LW_CFG_VMM_MAX_ORDER) {
return (LW_NULL); /* 无法开辟新的连续空间 */
}
if (ulLeftFreePageNumFit) { /* 需要左端打断 */
pvmpageNewFreeLeft = __pageCbAlloc(iPageType);
if (pvmpageNewFreeLeft == LW_NULL) {
_ErrorHandle(ERROR_KERNEL_LOW_MEMORY); /* 缺少内核内存 */
return (LW_NULL);
}
}
if (ulFit != ulLeftFreePageNumFit) { /* 右端也需要打断 */
pvmpageNewFreeRight = __pageCbAlloc(iPageType);
if (pvmpageNewFreeRight == LW_NULL) {
if (pvmpageNewFreeLeft) {
__pageCbFree(pvmpageNewFreeLeft); /* 释放左端控制块内存 */
}
_ErrorHandle(ERROR_KERNEL_LOW_MEMORY); /* 缺少内核内存 */
return (LW_NULL);
}
}
/*
* 将这个页面控制块从空闲 hash 表中删除.
*/
_List_Line_Del(&pvmpageFit->PAGE_lineFreeHash,
&pvmfaEntry->FA_lineFreeHeader); /* 从空闲表中删除 */
pvmfaEntry->FA_ulCount--;
/*
* 开始拆分操作, 先左端(低地址)后右端(高地址).
*/
if (ulLeftFreePageNumFit) { /* 需要左端打断 */
pvmpageFit->PAGE_bUsed = LW_FALSE; /* 左端保留分段 */
pvmpageNewFreeLeft->PAGE_bUsed = LW_TRUE; /* 新的分段将被使用 */
__pageStructSeparate(pvmpageFit,
pvmpageNewFreeLeft,
ulLeftFreePageNumFit, /* 左边忽略的页面 */
pvmpageFit->PAGE_iPageType); /* 页面分离 */
__pageAddToFreeHash(pvmzone, pvmpageFit); /* 将左端未使用区域加入空闲表 */
pvmpageFit = pvmpageNewFreeLeft; /* 去掉左端的分页控制块 */
}
if (ulFit != ulLeftFreePageNumFit) { /* 需要右端打断 */
pvmpageFit->PAGE_bUsed = LW_TRUE; /* 正在使用的分页段 */
pvmpageNewFreeRight->PAGE_bUsed = LW_FALSE; /* 没有使用的分页段 */
__pageStructSeparate(pvmpageFit,
pvmpageNewFreeRight,
ulPageNum, /* 需要开辟的页面数量 */
iPageType); /* 页面分离 */
__pageAddToFreeHash(pvmzone, pvmpageNewFreeRight); /* 将剩余页面插入空闲 hash 表 */
} else {
pvmpageFit->PAGE_bUsed = LW_TRUE; /* 正在使用的分页段 */
}
if (iPageType == __VMM_PAGE_TYPE_PHYSICAL) {
pvmpageFit->PAGE_ulMapPageAddr = PAGE_MAP_ADDR_INV; /* 没有映射关系 */
pvmpageFit->PAGE_iChange = 0; /* 没有变化过 */
pvmpageFit->PAGE_ulRef = 1ul; /* 引用计数初始为 1 */
pvmpageFit->PAGE_pvmpageReal = LW_NULL; /* 真实页面 */
} else {
pvmpageFit->PAGE_pvAreaCb = LW_NULL;
__pageInitLink(pvmpageFit);
}
pvmzone->ZONE_ulFreePage -= ulPageNum; /* 更新 zone 控制块 */
return (pvmpageFit);
}
首先是获得要分配页面数量的哈希表索引值,分配页面时从此索引值开始依次向上递增,每次都遍历对应哈希索引值得链表。此时分两种情况,虚拟页面控制块对应的起始地址是不是按照要求对其的,此时设置了一个偏移量,ulLeftFreePageNum。如果地址是对齐的这个偏移量是0,如果不对齐这个偏移量需要适当的增加,保证地址是对齐的。然后使用最佳匹配算法,算出空闲数量最接近的。如果ulLeftFreePageNumFit存在说明需要地址向高位移动以达到对齐。如果ulFit != ulLeftFreePageNumFit说明当前空闲页面说明大于移动后所需要数量,就是左右两边都需要切割。
将左右不分裁切掉后,重新索引到数据哈希表中。将中间部分保留。并被标记为使用。
__pageFree
此函数释放指定的vmpage,当vmpage从被使用状态,转回未被使用的状态时首先获得从左右邻居节点中查找低地址和高地址对应的vmpage。判断他们是否在使用,如果没有使用进行合并。如果在使用就不进行合并。合并的方式和以前一样,因为是在左右邻居树中,所以地址是连续的,对于左边的低地址,如果没有使用,低地址的vmpgae直接将剩余页面数增加。