CString在内核中与HeapWalk和HeapLock / HeapUnlock死锁结合使用

我的目标是locking为我的进程堆分配的虚拟内存(以防止将其交换到磁盘的可能性)。

我使用下面的代码:

//pseudo-code, error checks are omitted for brevity struct MEM_PAGE_TO_LOCK{ const BYTE* pBaseAddr; //Base address of the page size_t szcbBlockSz; //Size of the block in bytes MEM_PAGE_TO_LOCK() : pBaseAddr(NULL) , szcbBlockSz(0) { } }; void WorkerThread(LPVOID pVoid) { //Called repeatedly from a worker thread HANDLE hHeaps[256] = {0}; //Assume large array for the sake of this example UINT nNumberHeaps = ::GetProcessHeaps(256, hHeaps); if(nNumberHeaps > 256) nNumberHeaps = 256; std::vector<MEM_PAGE_TO_LOCK> arrPages; for(UINT i = 0; i < nNumberHeaps; i++) { lockUnlockHeapAndWalkIt(hHeaps[i], arrPages); } //Now lock collected virtual memory for(size_t p = 0; p < arrPages.size(); p++) { ::VirtualLock((void*)arrPages[p].pBaseAddr, arrPages[p].szcbBlockSz); } } void lockUnlockHeapAndWalkIt(HANDLE hHeap, std::vector<MEM_PAGE_TO_LOCK>& arrPages) { if(::HeapLock(hHeap)) { __try { walkHeapAndCollectVMPages(hHeap, arrPages); } __finally { ::HeapUnlock(hHeap); } } } void walkHeapAndCollectVMPages(HANDLE hHeap, std::vector<MEM_PAGE_TO_LOCK>& arrPages) { PROCESS_HEAP_ENTRY phe = {0}; MEM_PAGE_TO_LOCK mptl; SYSTEM_INFO si = {0}; ::GetSystemInfo(&si); for(;;) { //Get next heap block if(!::HeapWalk(hHeap, &phe)) { if(::GetLastError() != ERROR_NO_MORE_ITEMS) { //Some other error ASSERT(NULL); } break; } //We need to skip heap regions & uncommitted areas //We're interested only in allocated blocks if((phe.wFlags & (PROCESS_HEAP_REGION | PROCESS_HEAP_UNCOMMITTED_RANGE | PROCESS_HEAP_ENTRY_BUSY)) == PROCESS_HEAP_ENTRY_BUSY) { if(phe.cbData && phe.lpData) { //Get address aligned at the page size boundary size_t nRmndr = (size_t)phe.lpData % si.dwPageSize; BYTE* pBegin = (BYTE*)((size_t)phe.lpData - nRmndr); //Get segment size, also page aligned (round it up though) BYTE* pLast = (BYTE*)phe.lpData + phe.cbData; nRmndr = (size_t)pLast % si.dwPageSize; if(nRmndr) pLast += si.dwPageSize - nRmndr; size_t szcbSz = pLast - pBegin; //Do we have such a block already, or an adjacent one? std::vector<MEM_PAGE_TO_LOCK>::iterator itr = arrPages.begin(); for(; itr != arrPages.end(); ++itr) { const BYTE* pLPtr = itr->pBaseAddr + itr->szcbBlockSz; //See if they intersect or are adjacent if(pLPtr >= pBegin && itr->pBaseAddr <= pLast) { //Intersected with another memory block //Get the larger of the two if(pBegin < itr->pBaseAddr) itr->pBaseAddr = pBegin; itr->szcbBlockSz = pLPtr > pLast ? pLPtr - itr->pBaseAddr : pLast - itr->pBaseAddr; break; } } if(itr == arrPages.end()) { //Add new page mptl.pBaseAddr = pBegin; mptl.szcbBlockSz = szcbSz; arrPages.push_back(mptl); } } } } } 

这种方法的作品,除了很less发生以下事情。 应用程序挂起,UI和一切,即使我试图用Visual Studiodebugging器运行它,然后尝试Break all ,它显示一个错误消息,没有用户模式线程正在运行:

该过程似乎是死锁(或不运行任何用户模式代码)。 所有的线程已经停止。

在这里输入图像说明

我试了几次。 当应用程序第二次挂断时,我使用任务pipe理器create dump file ,之后我将.dmp文件加载到Visual Studio中并对其进行分析。 debugging器显示死锁发生在内核的某处:

在这里输入图像说明

如果您查看调用堆栈:

在这里输入图像说明

它指向代码的位置,如下所示:

 CString str; str.Format(L"Some formatting value=%d, %s", value, etc); 

进一步试验,如果我从上面的代码中删除HeapLockHeapUnlock调用,它似乎不再挂起。 但是,然后HeapWalk有时可能会发出未处理的exception,访问冲突。

那么有什么build议如何解决呢?

Solutions Collecting From Web of "CString在内核中与HeapWalk和HeapLock / HeapUnlock死锁结合使用"

问题在于,您正在使用C运行时的内存管理,尤其是CRT的调试堆,同时持有操作系统的堆锁。

您发布的调用堆栈包含_free_dbg ,它在采取任何其他操作之前始终声明CRT调试堆锁,因此我们知道该线程存放CRT调试堆锁。 我们也可以看到,在死锁发生时,CRT在由_CrtIsValidHeapPointer进行的操作系统调用中; 唯一的这种调用是HeapValidateHEAP_NO_SERIALIZE没有指定。

所以调用堆栈已经发布的线程持有CRT调试堆锁,并试图声明操作系统的堆锁。

另一方面,工作者线程保存操作系统的堆锁并进行尝试声明CRT调试堆锁的调用。

QED。 经典的僵局情况。

在调试版本中,您将需要避免使用任何C或C ++库函数,这些函数可能在您持有相应的操作系统堆锁的同时分配或释放内存。

即使在发布版本中,您仍然需要避免任何可能在持有锁的情况下分配或释放内存的库函数,这可能是一个问题,例如,如果将未来std::vector实现假设更改为使其生效线程安全的。

我建议你完全避免这个问题,这可能是最好的办法是为你的工作线程创建一个专用堆,并从堆中取出所有必要的内存分配。 从处理中排除这个堆可能是最好的。 HeapWalk的文档没有明确说明枚举期间不应该修改堆,但是看起来有风险。