6.4.4 堆内存释放

HeapFree函数完成堆内存的释放,该函数接受两个参数,待释放内存的起始地址,以及所属的堆对象。内存释放函数完成下列操作。

① 首先,把待释放的内存(实际上是一个空闲块)修改标志并插入空闲链表;

② 找到该空闲块所属的虚拟区域;

③ 对该虚拟区域发起一个合并空闲内存块的操作;

④ 完成块的合并操作后,检查当前虚拟内存区域是否是一个完整的内存块,即不包含尚未释放的内存,若是,则调用VirtualFree函数把该虚拟区域返回系统,否则返回。

之所以增加第④步操作,是为了实现一种“按需分配”的目的,一个策略就是尽量地返回不用的资源给操作系统,这样不会造成资源的浪费。如果不进行上述第 ④步操作,则可能出现当前线程申请了大量的虚拟区域而闲置不用,其他线程申请内存失败的情况。

HeapFree函数代码如下。

static VOID HeapFree(LPVOID lpStartAddr,__HEAP_OBJECT* lpHeapObj)
{
    __FREE_BLOCK_HEADER*   lpFreeHeader=NULL;
    __VIRTUAL_AREA_NODE*   lpVirtualArea=NULL;
    __VIRTUAL_AREA_NODE*   lpVirtualTmp=NULL;
    if((NULL==lpStartAddr) || (NULL==lpHeapObj)) //Invalid parameter.
        return;
    lpFreeHeader=(__FREE_BLOCK_HEADER*)((DWORD)lpStartAddr
     -sizeof(__FREE_BLOCK_HEADER));  //Get the block's header.
    if(!(lpFreeHeader->dwFlags & BLOCK_FLAGS_USED)) //Abnormal case.
        return;

上述代码根据待释放内存的起始地址,找到该内存块对应的控制头,然后检查控制头的标记,若标记不是空闲,则属于一种异常情况,否则继续执行。

//
//Now,check the block to be freed belong to which virtual area.
//
lpVirtualArea=lpHeapObj->lpVirtualArea;
while(lpVirtualArea)
{
    if(((DWORD)lpStartAddr > (DWORD)lpVirtualArea->lpStartAddress) &&
            ((DWORD)lpStartAddr < (DWORD)lpVirtualArea->lpStartAddress
         +lpVirtualArea->dwAreaSize))  //Belong to this virtual area.
    {
        break;
    }
    lpVirtualArea=lpVirtualArea->lpNext;
    }
    if(NULL==lpVirtualArea)  //Can not find a virtual area that countains
                        //the free block to be released.
    {
    //printf("\r\nFree a invalid block: can not find virtual area.");
    return;
    }

上述代码检查待释放内存属于哪个虚拟区域,找到对应的虚拟区域的目的是为了完成一个合并操作。若待释放内存无法与一个虚拟区域对应,则可能是一个不正常的操作,因此直接返回。

    //
    //Now,should insert the free block into heap object's free list.
    //
    lpFreeHeader->dwFlags |=BLOCK_FLAGS_FREE;
    lpFreeHeader->dwFlags &=~BLOCK_FLAGS_USED;  //Clear the used flags.
    lpFreeHeader->lpPrev =&lpHeapObj->FreeBlockHeader;
    lpFreeHeader->lpNext =lpHeapObj->FreeBlockHeader.lpNext;
    lpFreeHeader->lpPrev->lpNext=lpFreeHeader;
    lpFreeHeader->lpNext->lpPrev=lpFreeHeader;
    CombineBlock(lpVirtualArea,lpHeapObj);   //Combine this virtual area.

上述代码把待释放的空闲块插入了当前堆的空闲链表,然后发起一个合并操作,合并的对象就是待释放内存所属的虚拟区域。对于合并过程,后面会详细解释。

    //
    //Now,should check if the whole virtual area is a free block.
    //If so,delete the free block from free list,then release the
    //virtual area to system.
    //
    lpFreeHeader=(__FREE_BLOCK_HEADER*)(lpVirtualArea->lpStartAddress);
    if((lpFreeHeader->dwFlags & BLOCK_FLAGS_FREE) &&
  (lpFreeHeader->dwBlockSize+sizeof(__FREE_BLOCK_HEADER)
==lpVirtualArea->dwAreaSize))
    {
    //
    //Delete the free block from free list.
    //
    lpFreeHeader->lpPrev->lpNext=lpFreeHeader->lpNext;
    lpFreeHeader->lpNext->lpPrev=lpFreeHeader->lpPrev;
    //
    //Now,should delete the virtual area node object from
    //heap object's virtual list.
    //
    lpVirtualTmp=lpHeapObj->lpVirtualArea;
    if(lpVirtualTmp==lpVirtualArea)  //The first virtual node.
    {
        lpHeapObj->lpVirtualArea=lpVirtualArea->lpNext;
    }
    else   //Not the first one.
    {
        while(lpVirtualTmp->lpNext !=lpVirtualArea)
        {
            lpVirtualTmp=lpVirtualTmp->lpNext;
        }
        lpVirtualTmp->lpNext=lpVirtualArea->lpNext;  //Delete it.
    }
    //
    //Then,should release the virtual area and virtual area node
    //object.
    //
    RELEASE_VIRTUAL_AREA((LPVOID)lpVirtualArea->lpStartAddress);
    RELEASE_KERNEL_MEMORY((LPVOID)lpVirtualArea);
}
    return;
}

在完成了虚拟区域的合并之后,则检查当前虚拟区域本身是否是一个完整的空闲块。若是,则调用VirtualFree函数释放该虚拟区域,否则函数返回。对于虚拟区域本身是否是一个空闲块的判断方式十分简单,只需要判断虚拟区域的第一个内存块的长度加上控制头是否等于整个虚拟区域大小即可。若是,则整个虚拟区域是一块空闲块,否则就不是。

下面的CombineBlock函数完成了特定虚拟区域的空闲内存块合并工作。该函数操作过程如下。

① 从第一个空闲块开始,判断是否有连续的两块内存块其状态都是空闲。这里的连续是内存块地址的连续(相邻),而不是空闲块在空闲链表中的连续。

② 如果发现这样的两块内存,则把第二块从空闲链表中删除,合并到第一块中。

③ 继续执行上述操作,直到到达虚拟区域的末端。

代码如下。

    static VOID CombineBlock(__VIRTUAL_AREA_NODE* lpVirtualArea,__HEAP_
OBJECT* lpHeapObj)
    {
    __FREE_BLOCK_HEADER*          lpFirstBlock  =NULL;
    __FREE_BLOCK_HEADER*          lpSecondBlock =NULL;
    LPVOID                      lpEndAddr     =NULL;
    if((NULL==lpVirtualArea)  ||  (NULL==lpHeapObj))  //Invalid
parameters.
        return;
    lpEndAddr  =(LPVOID)((DWORD)lpVirtualArea->lpStartAddress
     +lpVirtualArea->dwAreaSize);

其中,lpEndAddr是一个结束标志,一旦待合并内存块的控制头地址跟该地址相同,则说明合并已经结束。

    lpFirstBlock=(__FREE_BLOCK_HEADER*)lpVirtualArea->lpStartAddress;
    lpSecondBlock=(__FREE_BLOCK_HEADER*)((DWORD)lpFirstBlock
 +sizeof(__FREE_BLOCK_HEADER)
 +lpFirstBlock->dwBlockSize);  //Now,lpSecondBlock pointing to
                                  //the second block.

初始化lpFirstBlock和lpSecondBlock变量,使得lpFirstBlock指向当前虚拟区域中的第一个内存块,lpSecondBlock指向第二块内存块,然后进入下面的循环。

    while(TRUE)
    {
    if(lpEndAddr==(LPVOID)lpSecondBlock) //Reach the end of the
virtual area.
        break;
    if((lpFirstBlock->dwFlags & BLOCK_FLAGS_FREE) &&
      (lpSecondBlock->dwFlags & BLOCK_FLAGS_FREE))  //Two blocks all
free,combine it.
    {
        lpFirstBlock->dwBlockSize+=lpSecondBlock->dwBlockSize;
        lpFirstBlock->dwBlockSize+=sizeof(__FREE_BLOCK_HEADER);
        //
        //Delete the second block from free list.
        //
        lpSecondBlock->lpNext->lpPrev=lpSecondBlock->lpPrev;
        lpSecondBlock->lpPrev->lpNext=lpSecondBlock->lpNext;
        lpSecondBlock=(__FREE_BLOCK_HEADER*)((DWORD)lpFirstBlock
         +sizeof(__FREE_BLOCK_HEADER)
         +lpFirstBlock->dwBlockSize);  //Update the second block.
        continue;   //Continue to next round.
    }
    if((lpFirstBlock->dwFlags & BLOCK_FLAGS_USED) ||
            (lpSecondBlock->dwFlags & BLOCK_FLAGS_USED))
                                    //Any block is used.
    {
        lpFirstBlock=lpSecondBlock;
        lpSecondBlock=(__FREE_BLOCK_HEADER*)((DWORD)lpFirstBlock
         +sizeof(__FREE_BLOCK_HEADER)
         +lpFirstBlock->dwBlockSize);
        continue;
    }
}
}

上述循环比较简单,只是判断lpFirstBlock的标志字段是否是空闲(FREE),若是,则进一步判断lpSecondBlock是否也空闲,如果空闲,则具备合并条件,合并lpFirstBlock和lpSecondBlock,然后更新lpSecondBlock,重新进入循环,否则(lpFirstBlock和lpSecondBlock中至少一个是非空闲块),则更新lpFirstBlock为lpSecondBlock,更新lpSecondBlock为lpSecondBlock的相邻块,继续下一轮迭代。