SylixOS 内存管理源代码分析--vmmMalloc.c
vmmMalloc.c文件内容是内核对外提供API函数,前面文章分析的各种函数都是最后汇集到这里,对外提供系统功能。
API_VmmMallocAlign
此函数是分配一个对齐的物理页面和虚拟页面并将其映射。
/*********************************************************************************************************
** 函数名称: API_VmmMallocAlign
** 功能描述: 分配逻辑上连续, 物理可能不连续的内存. (虚拟地址满足对齐关系)
** 输 入 : stSize 需要分配的内存大小
** stAlign 对齐关系
** ulFlag 访问属性. (必须为 LW_VMM_FLAG_TEXT 或 LW_VMM_FLAG_DATA)
** 输 出 : 虚拟内存首地址
** 全局变量:
** 调用模块:
API 函数
*********************************************************************************************************/
LW_API
PVOID API_VmmMallocAlign (size_t stSize, size_t stAlign, ULONG ulFlag)
{
REGISTER PLW_VMM_PAGE pvmpageVirtual;
REGISTER PLW_VMM_PAGE pvmpagePhysical;
REGISTER ULONG ulPageNum = (ULONG) (stSize >> LW_CFG_VMM_PAGE_SHIFT);
REGISTER size_t stExcess = (size_t)(stSize & ~LW_CFG_VMM_PAGE_MASK);
ULONG ulZoneIndex;
ULONG ulPageNumTotal = 0;
ULONG ulVirtualAddr;
ULONG ulError;
if (stAlign & (stAlign - 1)) {
_DebugHandle(__ERRORMESSAGE_LEVEL, "iAlign invalidate.\r\n");
_ErrorHandle(ERROR_VMM_ALIGN);
return (LW_NULL);
}
if (stAlign < LW_CFG_VMM_PAGE_SIZE) {
stAlign = LW_CFG_VMM_PAGE_SIZE;
}
#if LW_CFG_CACHE_EN > 0
if (API_CacheAliasProb() &&
(stAlign < API_CacheWaySize(DATA_CACHE))) { /* 有限修正 cache alias */
stAlign = API_CacheWaySize(DATA_CACHE);
}
#endif /* LW_CFG_CACHE_EN > 0 */
if (stExcess) {
ulPageNum++; /* 确定分页数量 */
}
if (ulPageNum < 1) {
_ErrorHandle(EINVAL);
return (LW_NULL);
}
__VMM_LOCK();
if (stAlign > LW_CFG_VMM_PAGE_SIZE) {
pvmpageVirtual = __vmmVirtualPageAllocAlign(ulPageNum, stAlign);/* 分配连续虚拟页面 */
} else {
pvmpageVirtual = __vmmVirtualPageAlloc(ulPageNum); /* 分配连续虚拟页面 */
}
if (pvmpageVirtual == LW_NULL) {
__VMM_UNLOCK();
_ErrorHandle(ERROR_VMM_VIRTUAL_PAGE);
return (LW_NULL);
}
ulVirtualAddr = pvmpageVirtual->PAGE_ulPageAddr; /* 起始虚拟内存地址 */
do {
ULONG ulPageNumOnce = ulPageNum - ulPageNumTotal;
ULONG ulMinContinue = __vmmPhysicalPageGetMinContinue(&ulZoneIndex, LW_ZONE_ATTR_NONE);
/* 优先分配碎片页面 */
if (ulPageNumOnce > ulMinContinue) { /* 选择适当的页面长度 */
ulPageNumOnce = ulMinContinue;
}
pvmpagePhysical = __vmmPhysicalPageAllocZone(ulZoneIndex, ulPageNumOnce, LW_ZONE_ATTR_NONE);
if (pvmpagePhysical == LW_NULL) {
_ErrorHandle(ERROR_VMM_LOW_PHYSICAL_PAGE);
goto __error_handle;
}
ulError = __vmmLibPageMap(pvmpagePhysical->PAGE_ulPageAddr, /* 使用 CACHE */
ulVirtualAddr,
ulPageNumOnce,
ulFlag); /* 映射为连续虚拟地址 */
if (ulError) { /* 映射错误 */
__vmmPhysicalPageFree(pvmpagePhysical);
_ErrorHandle(ulError);
goto __error_handle;
}
pvmpagePhysical->PAGE_ulMapPageAddr = ulVirtualAddr;
pvmpagePhysical->PAGE_ulFlags = ulFlag;
__pageLink(pvmpageVirtual, pvmpagePhysical); /* 将物理页面连接入虚拟空间 */
ulPageNumTotal += ulPageNumOnce;
ulVirtualAddr += (ulPageNumOnce << LW_CFG_VMM_PAGE_SHIFT);
} while (ulPageNumTotal < ulPageNum);
pvmpageVirtual->PAGE_ulFlags = ulFlag;
__areaVirtualInsertPage(pvmpageVirtual->PAGE_ulPageAddr,
pvmpageVirtual); /* 插入逻辑空间反查表 */
__VMM_UNLOCK();
MONITOR_EVT_LONG4(MONITOR_EVENT_ID_VMM, MONITOR_EVENT_VMM_ALLOC,
pvmpageVirtual->PAGE_ulPageAddr,
stSize, stAlign, ulFlag, LW_NULL);
return ((PVOID)pvmpageVirtual->PAGE_ulPageAddr); /* 返回虚拟地址 */
__error_handle: /* 出现错误 */
__vmmPhysicalPageFreeAll(pvmpageVirtual); /* 释放页面链表 */
__vmmVirtualPageFree(pvmpageVirtual); /* 释放虚拟地址空间 */
__VMM_UNLOCK();
return (LW_NULL);
}
此函数分配时虚拟地址是连续的,但是物理地址并不一定连续,分配时先从最小物理页面开始。函数首先判断size大小为几个页面。使用右移的方式LW_CFG_VMM_PAGE_SHIFT,默认都是12,代表页面时4K。不足一个页面大小时按照一个页面计算。
页面对齐方式必须按照页面大小整数倍对齐。对齐方式大于一个页面时调用__vmmVirtualPageAllocAlign函数,__vmmVirtualPageAllocAlign函数在调用__pageAllocateAlign分配。__pageAllocateAlign函数在pageLib.c文件中实现。
当对齐方式是一个页面大小时使用__vmmVirtualPageAlloc。最终还是调用__pageAllocate函数在pageLib.c实现。分配完成虚拟地址后是一个do while循环。这个循环内不断检测物理zone中最小的空闲页面数量。然后分配物理页面数,然后将物理地址和虚拟地址映射。重复进行知道最后全部的都映射完成。最后将虚拟地址加入到红黑树中,方便以后反查。红黑树的插入__areaVirtualInsertPage 函数在vmmArea.c文件中实现。因为物理地址可能不是连续的,所以调用了一个 __pageLink(pvmpageVirtual, pvmpagePhysical); 函数,此函数将物理地址加入到虚拟地址结构中的物理地址哈希表中。
API_VmmMallocAreaAlign
此函数分配虚拟地址但是并映射物理地址,等待出发缺页中断后再分配物理地址。
/*********************************************************************************************************
** 函数名称: API_VmmMallocAreaAlign
** 功能描述: 仅开辟虚拟空间, 当出现第一次访问时, 将通过缺页中断分配物理内存,
当缺页中断中无法分配物理页面时, 将收到 SIGSEGV 信号并结束线程.
** 输 入 : stSize 需要分配的内存大小
** stAlign 对齐关系
** pfuncFiller 当出现缺页时, 获取物理分页后的填充函数, 一般为 NULL.
** pvArg 填充函数参数.
** iFlag MAP_SHARED or MAP_PRIVATE
** ulFlag 访问属性. (必须为 LW_VMM_FLAG_TEXT 或 LW_VMM_FLAG_DATA)
** 输 出 : 虚拟内存首地址
** 全局变量:
** 调用模块:
API 函数
*********************************************************************************************************/
LW_API
PVOID API_VmmMallocAreaAlign (size_t stSize, size_t stAlign,
FUNCPTR pfuncFiller, PVOID pvArg, INT iFlags, ULONG ulFlag)
{
REGISTER PLW_VMM_PAGE pvmpageVirtual;
REGISTER PLW_VMM_PAGE_PRIVATE pvmpagep;
REGISTER ULONG ulPageNum = (ULONG) (stSize >> LW_CFG_VMM_PAGE_SHIFT);
REGISTER size_t stExcess = (size_t)(stSize & ~LW_CFG_VMM_PAGE_MASK);
ULONG ulError;
if (stAlign & (stAlign - 1)) {
_DebugHandle(__ERRORMESSAGE_LEVEL, "iAlign invalidate.\r\n");
_ErrorHandle(ERROR_VMM_ALIGN);
return (LW_NULL);
}
if (stAlign < LW_CFG_VMM_PAGE_SIZE) {
stAlign = LW_CFG_VMM_PAGE_SIZE;
}
#if LW_CFG_CACHE_EN > 0
if (API_CacheAliasProb() &&
(stAlign < API_CacheWaySize(DATA_CACHE))) { /* 有限修正 cache alias */
stAlign = API_CacheWaySize(DATA_CACHE);
}
#endif /* LW_CFG_CACHE_EN > 0 */
if (stExcess) {
ulPageNum++; /* 确定分页数量 */
}
if (ulPageNum < 1) {
_ErrorHandle(EINVAL);
return (LW_NULL);
}
__VMM_LOCK();
if (stAlign > LW_CFG_VMM_PAGE_SIZE) {
pvmpageVirtual = __vmmVirtualPageAllocAlign(ulPageNum, stAlign);/* 分配连续虚拟页面 */
} else {
pvmpageVirtual = __vmmVirtualPageAlloc(ulPageNum); /* 分配连续虚拟页面 */
}
if (pvmpageVirtual == LW_NULL) {
__VMM_UNLOCK();
_ErrorHandle(ERROR_VMM_VIRTUAL_PAGE);
return (LW_NULL);
}
pvmpagep = (PLW_VMM_PAGE_PRIVATE)__KHEAP_ALLOC(sizeof(LW_VMM_PAGE_PRIVATE));
if (pvmpagep == LW_NULL) {
__vmmVirtualPageFree(pvmpageVirtual);
__VMM_UNLOCK();
_ErrorHandle(ERROR_KERNEL_LOW_MEMORY);
return (LW_NULL);
}
ulError = __vmmLibPageMap(pvmpageVirtual->PAGE_ulPageAddr,
pvmpageVirtual->PAGE_ulPageAddr,
ulPageNum,
LW_VMM_FLAG_FAIL); /* 此段内存空间访问失效 */
if (ulError) { /* 映射错误 */
__KHEAP_FREE(pvmpagep);
__vmmVirtualPageFree(pvmpageVirtual); /* 释放虚拟地址空间 */
__VMM_UNLOCK();
_ErrorHandle(ERROR_VMM_VIRTUAL_PAGE);
return (LW_NULL);
}
pvmpagep->PAGEP_pfuncFiller = pfuncFiller;
pvmpagep->PAGEP_pvArg = pvArg;
pvmpagep->PAGEP_pfuncFindShare = LW_NULL;
pvmpagep->PAGEP_pvFindArg = LW_NULL;
pvmpagep->PAGEP_iFlags = iFlags;
pvmpagep->PAGEP_pvmpageVirtual = pvmpageVirtual; /* 建立连接关系 */
pvmpageVirtual->PAGE_pvAreaCb = (PVOID)pvmpagep; /* 记录私有数据结构 */
_List_Line_Add_Ahead(&pvmpagep->PAGEP_lineManage,
&_K_plineVmmVAddrSpaceHeader); /* 连入缺页中断查找表 */
pvmpageVirtual->PAGE_ulFlags = ulFlag; /* 记录内存类型 */
__areaVirtualInsertPage(pvmpageVirtual->PAGE_ulPageAddr,
pvmpageVirtual); /* 插入逻辑空间反查表 */
__VMM_UNLOCK();
MONITOR_EVT_LONG5(MONITOR_EVENT_ID_VMM, MONITOR_EVENT_VMM_ALLOC_A,
pvmpageVirtual->PAGE_ulPageAddr,
stSize, stAlign, iFlags, ulFlag, LW_NULL);
return ((PVOID)pvmpageVirtual->PAGE_ulPageAddr);
}
还是根据对齐方式和size,调用虚拟页面分配函数,有个比较特殊的地方。
ulError = __vmmLibPageMap(pvmpageVirtual->PAGE_ulPageAddr,
pvmpageVirtual->PAGE_ulPageAddr,
ulPageNum,
LW_VMM_FLAG_FAIL); /* 此段内存空间访问失效 */
这里设置的属性LW_VMM_FLAG_FAIL 标记,虽然调用映射函数,但是由于不能访问,所以会触发异常。异常中断结构体体如下
填充pvmpagep 相关的成员,加入到vmpage的私有成员中,然后加入到缺页中断查找链表中,在缺页中断中会使用这个链表。然后将虚拟地址加入到哈希红黑树中为了以后可以通过地址反查页面控制块。
API_VmmFreeArea
此函数 是释放虚拟地址,首先通过哈希红黑树反查虚拟地址对应的页面控制块vmpage。然后获得页面控制块的私有数据,私有数据是和缺页异常相关,所以需要从缺页异常的链表中删除。然后将虚拟页面映射的物理地址全部释放。释放是先查虚拟页面控制块中物理页面哈希表。查找到物理地址后调用__vmmPhysicalPageFree函数。__vmmPhysicalPageFree函数在phyPage.c文件夹中。然后调用__areaVirtualUnlinkPage函数从哈希红黑树中删除当前虚拟页面的节点。最后调用虚拟页面释放函数。
API_VmmSplitArea
API_VmmMergeArea
此函数是合并两个两个虚拟地址。
pvmpageVirtualL = __areaVirtualSearchPage(ulAddrL);
if (pvmpageVirtualL == LW_NULL) {
__VMM_UNLOCK();
_ErrorHandle(ERROR_VMM_VIRTUAL_PAGE); /* 无法反向查询虚拟页面控制块 */
return (ERROR_VMM_VIRTUAL_PAGE);
}
pvmpageVirtualR = __areaVirtualSearchPage(ulAddrR);
if (pvmpageVirtualR == LW_NULL) {
__VMM_UNLOCK();
_ErrorHandle(ERROR_VMM_VIRTUAL_PAGE); /* 无法反向查询虚拟页面控制块 */
return (ERROR_VMM_VIRTUAL_PAGE);
}
首先通过哈希红黑树反查虚拟页面控制块,__areaVirtualSearchPage函数是在vmmArea.c文件中提供的函数,此函数反查页面控制块。
if ((pvmpageVirtualL->PAGE_ulPageAddr + pvmpageVirtualL->PAGE_ulCount) !=
pvmpageVirtualR->PAGE_ulPageAddr) { /* 非连续页面不能进行合并 */
__VMM_UNLOCK();
_ErrorHandle(ERROR_VMM_VIRTUAL_ADDR);
return (ERROR_VMM_PAGE_INVAL);
判断虚拟页面是否连续。不连续直接返回错误。
pvmpagep = (PLW_VMM_PAGE_PRIVATE)pvmpageVirtualR->PAGE_pvAreaCb;
if (pvmpagep == LW_NULL) {
__VMM_UNLOCK();
_ErrorHandle(ERROR_VMM_PAGE_INVAL);
return (ERROR_VMM_PAGE_INVAL);
}
__pageTraversalLink(pvmpageVirtualR,
__vmmMergeAreaHook,
pvmpageVirtualL,
pvmpageVirtualR,
LW_NULL,
LW_NULL,
LW_NULL,
LW_NULL); /* 遍历对应的物理页面 */
__areaVirtualUnlinkPage(pvmpageVirtualR->PAGE_ulPageAddr,
pvmpageVirtualR);
_List_Line_Del(&pvmpagep->PAGEP_lineManage, &_K_plineVmmVAddrSpaceHeader);
__KHEAP_FREE(pvmpagep); /* 释放缺页中断管理模块 */
__pageMerge(pvmpageVirtualL, pvmpageVirtualR); /* 合并页面 */
获得页面控制块pvmpageVirtualR的私有数据pvmpagep,将其从缺页异常链表中删除。由于pvmpageVirtualR要被合并所以他们的物理页面哈希表全部重新挂载到pvmpageVirtualL上。__pageMerge调用__pageMerge函数释放。__pageMerge函数中pageLib.c函数中,之前一直好奇为什么合并后没有将新的页面索引到对应的哈希表中,现在发现由于合并时候虚拟页面还在使用,所以并不重建索引,如果不在使用那么重建索引。
备注:__pageFree函数最后如下:
将vmpage页面控制块标记为未使用,同时重新在哈希表中索引。
API_VmmPCountInArea
此函数是查虚拟地址控制块中有多少个物理页面。由于当前物理页面时由于缺页中断去分分配,所以并不是一个固定值。使用这个函数去查询。
首先还是通过哈希红黑树去反查虚拟地址对应的页面控制块,然后调用__pageTraversalLink函数去遍历虚拟页面控制块中的物理页面哈希表,每发现一个物理页面控制块就调用__vmmPCountInAreaHook函数。
__vmmPCountInAreaHook 分为两种情况,由于物理页面控制块可能是引用,不是真实的,所以先获得真实的物理页面控制块,然后将页面数量相加。如果是真实页面直接相加。
API_VmmInvalidateArea
此函数功能是释放掉映射好的物理页面
首先还是通过虚拟地址查找虚拟页面控制块,然后遍历整个物理哈希表。这里有个比较重要的地方
pvmpagep = (PLW_VMM_PAGE_PRIVATE)pvmpageVirtual->PAGE_pvAreaCb;
if (!pvmpagep) {
__VMM_UNLOCK();
_ErrorHandle(ERROR_VMM_VIRTUAL_PAGE); /* 无法反向查询虚拟页面控制块 */
return (ERROR_VMM_VIRTUAL_PAGE);
}
这里查找pvmapagep ,它存在才能证明这块地址虚拟地址和物理地址是通过缺页中断建立映射的。不是开始就映射好。才会右面释放物理页面等操作。__vmmInvalidateAreaHook函数功能也很简单
首先判断物理地址映射的虚拟地址是否正确,然后将物理地址映射的虚拟地址重新设置为不可访问,这样会像开始一样触发缺页中断。然后释放物理页面控制块。