6.4.3 堆内存申请
HeapAlloc函数完成堆内存的申请,该函数接受两个参数,目标堆对象和待申请内存的大小。若申请成功,则返回成功申请的内存基地址,否则返回NULL。该函数完成如下动作。
① 首先,做一个堆归属的合法性检查,即判断用户提供的堆对象是不是属于当前线程。若属于当前线程,则继续下一步的动作,否则直接返回NULL。
② 检查空闲链表,以寻找一块大小大于用户请求的空闲内存块。
③ 如果能够找到这样的内存块,则根据内存块的大小,确定是需要进一步拆分,还是直接返回用户。
④ 如果需要拆分,则把找到的内存块拆分成两块,然后把拆分后的两块内存块中的后一块,重新插入空闲链表,把第一块返回用户。若不需要拆分,则把找到的空闲块直接从空闲链表中删除,然后返回用户。
⑤ 如果从空闲链表中无法找到满足要求的空闲块,则调用VirtualAlloc函数,重新分配一个虚拟区域,并把该区域当成一块空闲块进行上述处理。
⑥ 返回用户分配的内存块地址,或者在失败的情况下返回NULL。
代码如下。
static LPVOID HeapAlloc(__HEAP_OBJECT* lpHeapObject,DWORD dwSize) { __VIRTUAL_AREA_NODE* lpVirtualArea =NULL; __FREE_BLOCK_HEADER* lpFreeBlock =NULL; __FREE_BLOCK_HEADER* lpTmpHeader =NULL; LPVOID lpResult =NULL; DWORD dwFlags =0L; DWORD dwFindSize =0L; if((NULL==lpHeapObject) || (0==dwSize)) //Parameter check. return lpResult; //__ENTER_CRITICAL_SECTION(NULL,dwFlags); if(lpHeapObject->lpKernelThread !=CURRENT_KERNEL_THREAD) //Check the heap's owner. { __LEAVE_CRITICAL_SECTION(NULL,dwFlags); return lpResult; } //__LEAVE_CRITICAL_SECTION(NULL,dwFlags); if(dwSize < MIN_BLOCK_SIZE) dwSize=MIN_BLOCK_SIZE; dwFindSize=dwSize+MIN_BLOCK_SIZE+sizeof(__FREE_BLOCK_HEADER);
上述代码完成了堆归属检查,并重新计算了用户所申请的内存块的大小。在当前的实现中,对于堆内存的申请最小尺寸是MIN_BLOCK_SIZE(定义为16字节),若用户请求的内存小于该数值,则调整为该数值。其中,dwFindSize是待查找的目标空闲块的大小。只所以在dwSize的基础上增加MIN_BLOCK_SIZE和控制头的尺寸,是为了确保任何空闲块的可用尺寸都应该大于MIN_BLOCK_SIZE。在找到一块空闲块之后,若该空闲块的大小大于dwFindSize,则需要对该空闲块进行分割,否则无需分割,直接返回给用户。
// //Now,check the free list,try to find a free block. // lpFreeBlock=lpHeapObject->FreeBlockHeader.lpNext; while(lpFreeBlock !=&lpHeapObject->FreeBlockHeader) { if(lpFreeBlock->dwBlockSize >=dwSize) //Find one. { if(lpFreeBlock->dwBlockSize >=dwFindSize) //Should split it //into two free blocks. { lpTmpHeader=(__FREE_BLOCK_HEADER*)((DWORD)lpFreeBlock+ dwSize +sizeof(__FREE_BLOCK_HEADER)); //Pointing to //second part. lpTmpHeader->dwFlags =BLOCK_FLAGS_FREE; lpTmpHeader->dwBlockSize=lpFreeBlock->dwBlockSize- dwSize -sizeof(__FREE_BLOCK_HEADER); //Calculate second part's size. // //Now,should replace the lpFreeBlock with lpTmpHeader. // lpTmpHeader->lpNext=lpFreeBlock->lpNext; lpTmpHeader->lpPrev=lpFreeBlock->lpPrev; lpTmpHeader->lpNext->lpPrev=lpTmpHeader; lpTmpHeader->lpPrev->lpNext=lpTmpHeader; lpFreeBlock->dwBlockSize=dwSize; lpFreeBlock->dwFlags |=BLOCK_FLAGS_USED; lpFreeBlock->dwFlags &=~BLOCK_FLAGS_FREE; //Clear the free flags. lpFreeBlock->lpPrev =NULL; lpFreeBlock->lpNext =NULL; lpResult =(LPVOID)((DWORD)lpFreeBlock +sizeof(__FREE_BLOCK_HEADER)); goto __TERMINAL; } else //Now need to split,return the block is OK. { // //Delete the free block from free block list. // lpFreeBlock->lpNext->lpPrev=lpFreeBlock->lpPrev; lpFreeBlock->lpPrev->lpNext=lpFreeBlock->lpNext; lpFreeBlock->dwFlags |=BLOCK_FLAGS_USED; lpFreeBlock->dwFlags &=~BLOCK_FLAGS_FREE; //Clear free bit. lpFreeBlock->lpNext =NULL; lpFreeBlock->lpPrev =NULL; lpResult=(LPVOID)((DWORD)lpFreeBlock+sizeof(__FREE_ BLOCK_HEADER)); goto __TERMINAL; } } lpFreeBlock=lpFreeBlock->lpNext; //Check the next block. } if(lpResult) //Have found a block. goto __TERMINAL;
上述代码完成空闲块链表的搜索,一旦找到一块满足要求尺寸(dwSize)的空闲块,则进一步判断该空闲块的大小是否大于dwFindSize。若大于,则可以进行进一步拆分,否则直接返回用户找到的内存块。在拆分的情况下,把拆分后的第二块内存重新插入空闲链表。
这时候,就可以很清楚地解释为什么只有在大于dwFindSize的时候才需要拆分了。因为在拆分后,实际上还需要在空闲块的开头预留16字节(空闲控制头的大小)作为空闲块的控制头,这样若找到的空闲块小于dwFindSize,则无法保证拆分后空闲块的大小会大于MIN_BLOCK_SIZE(这是空闲块的最小尺寸)。
如果从空闲链表中无法找到满足的空闲块,则需要扩充堆的内存池了,这时候,需要调用VirtualAlloc函数,从系统空间中重新申请一个虚拟区域,然后把该虚拟区域当作一个空闲块对待,插入堆的空闲链表,这时候,还需要把虚拟区域插入堆的虚拟区域链表。代码如下。
// //If can not find a statisfying block in above process,we should allocate //a new virtual area according to dwSize,insert the virtual area into //free list,then repeat above process. // lpVirtualArea=(__VIRTUAL_AREA_NODE*)GET_ KERNEL_MEMORY(sizeof(__ VIRTUAL_AREA_NODE)); if(NULL==lpVirtualArea) //Can not allocate kernel memory. goto __TERMINAL; lpVirtualArea->dwAreaSize =((dwSize+sizeof(__FREE_BLOCK_HEADER)) > DEFAULT_VIRTUAL_AREA_SIZE) ? (dwSize+sizeof(__FREE_BLOCK_HEADER)) : DEFAULT_VIRTUAL_ AREA_ SIZE; lpVirtualArea->lpStartAddress=GET_VIRTUAL_ AREA(lpVirtualArea-> dwAreaSize); if(NULL==lpVirtualArea->lpStartAddress) //Can not get virtual area. { RELEASE_KERNEL_MEMORY((LPVOID)lpVirtualArea); goto __TERMINAL; } // //Insert the virtual area node object into virtual area list //of current heap object. // lpVirtualArea->lpNext =lpHeapObject->lpVirtualArea; lpHeapObject->lpVirtualArea=lpVirtualArea; // //Now,insert the free block(virtual area) into free list of the //heap object. // lpTmpHeader=(__FREE_BLOCK_HEADER*)lpVirtualArea->lpStartAddress; lpTmpHeader->dwFlags |=BLOCK_FLAGS_FREE; lpTmpHeader->dwFlags &=~BLOCK_FLAGS_USED; //Clear the used flags. lpTmpHeader->dwBlockSize=lpVirtualArea->dwAreaSize-sizeof(__FREE_ BLOCK_HEADER); lpTmpHeader->lpNext=lpHeapObject->FreeBlockHeader.lpNext; lpTmpHeader->lpPrev=&lpHeapObject->FreeBlockHeader; lpTmpHeader->lpNext->lpPrev=lpTmpHeader; lpTmpHeader->lpPrev->lpNext=lpTmpHeader;
若调用VirtualAlloc函数也失败,则HeapAlloc函数只能返回NULL了,否则,在成功调用VirtualAlloc函数的情况下,堆的空闲链表中会增加一块满足要求的内存空闲块(新申请的虚拟区域),这时候,重新调用HeapAlloc函数,肯定是成功的,因此,HeapAlloc函数递归调用自己,然后把返回的结果返回给用户。
// //Now,call HeapAlloc again,this time must successful. // lpResult=HeapAlloc(lpHeapObject,dwSize); __TERMINAL: return lpResult; }