由于发现开发的程序有内存泄漏(Memory Leaks),所以编写了一个内存监控程序定位那个程序有Leaks。 所 谓Memory Leaks就是程序在运行的过程中没有释放已经不再使用的内存,Memory Leaks不会直接导致程序core dump或者系统崩溃。只有程序在运行过程中不断的请求分配内存,而得到这部分的内存一直没有释放,直到系统再没有空闲的内存可以分配了,系统会变慢(有 可能进行页交换所以变慢)甚至崩溃。 Wince(Windows Mobile同样适用,因为使用Wince的Kernel)的内存管理称谓虚拟内存结构(Virtual Memory Architecture)。在PC的世界,virtual memory是和物理内存(RAM)相对的概念,系统可以使用硬盘等设备作为内存使用,解决内存小的问题。但是在Wince里面,Virtual Memory的概念和PC不一样,他不是指硬盘或者其他物理设备,他是对物理内存的一个映射,在Wince里面所有应用程序都不可以直接操作物理内存的地 址,只有通过Virtual Memory来分配和管理内存。每个进程内存管理单元(memory management unit )负责这些内存从虚拟地址(virtual addresses)到物理地址(physical addresses)的映射。 上 图就是一个内存映射到例子,为什么Wince要通过内存管理单元来映射物理内存和虚拟内存,而不直接让应用程序访问物理内存呢?Wince是一个32位的 系统,理论上可以管理4G的内存,可惜由于当前硬件设备的局限性,Wince运行的硬件环境一般只有很小的内存(64M,128M),所以Wince系统 需要想方法节省物理内存的使用。在虚拟内存结构下,Wince依然提供4G虚拟内存给用户,其中2G用于kernel模式,2G用于用户进程。当程序需要 载入数据到内存时,Wince会检查该数据是否已经在物理内存里面,如果在,就建立多一个映射关联到虚拟内存,如上图中的Device Buffer被使用了2次,所以有两个虚拟内存地址,确映射到同一个物理内存地址上 。这样做的目的是Process理论上可以使用4G的内存,具体的映射由Wince来负责,Wince也可以因此提高物理内存的使用率。 由于虚拟内存结构,因此我们可以监控每个进程的虚拟内存使用情况,不能监控到具体的每个进程的物理内存使用率。虚拟内存主要分三类: Free: 没有分配或者使用的虚拟内存。 Reserved:被预订,但是还没有映射到物理内存的虚拟内存。 Committed:先被预订,同时已经映射到物理内存的虚拟内存,也就是正在在使用中的内存。 以下是内存监控的代码。 class MemoryMonitor { private: static const int TH32CS_SNAPNOHEAPS = 0x40000000; public: void ShowTotalMemoryInfo() { MEMORYSTATUS status; GlobalMemoryStatus(&status); wprintf(L"\n%s Memory Load:%ld, TotalPhys:%ld, AvailPhys:%ld, TotalVirtual:%ld, AvailVirtual:%ld \n", getTimeStamp(), status.dwMemoryLoad, status.dwTotalPhys, status.dwAvailPhys, status.dwTotalVirtual, status.dwAvailVirtual); } public: void ShowProcessesMemoryInfo() { HANDLE snapShot = INVALID_HANDLE_VALUE; try { snapShot = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS | TH32CS_SNAPNOHEAPS, 0); if (snapShot != INVALID_HANDLE_VALUE) { PROCESSENTRY32 processEntry; processEntry.dwSize = sizeof(PROCESSENTRY32); BOOL ret = Process32First(snapShot, &processEntry); while (ret == TRUE) { wprintf(L"%s %s, %X, MemoryBase:%X, ThreadCnt:%d, ", getTimeStamp(), processEntry.szExeFile, processEntry.th32ProcessID, processEntry.th32MemoryBase, processEntry.cntThreads); ShowMemoryInfo(processEntry.th32MemoryBase); ShowHeapInfo(processEntry.th32ProcessID); ret = Process32Next(snapShot, &processEntry); } if (snapShot != INVALID_HANDLE_VALUE) { CloseToolhelp32Snapshot(snapShot); } } else { DWORD err = GetLastError(); LPVOID lpMsgBuf; FormatMessage(FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS, NULL, err, 0, // Default language (LPTSTR) &lpMsgBuf, 0, NULL); wprintf(L"ERROR: %s", lpMsgBuf); } // } __except ( Filter(GetExceptionCode(), GetExceptionInformation()) ) } catch ( ![]() { if (snapShot != INVALID_HANDLE_VALUE) { CloseToolhelp32Snapshot(snapShot); } } } private: void ShowMemoryInfo(DWORD baseAddress) { DWORD startAddress = baseAddress; DWORD endAddress = startAddress + 0x40000000; DWORD address = startAddress; DWORD committedMemory = 0; DWORD reservedMemory = 0; DWORD freeMemory = 0; while (address < endAddress) { MEMORY_BASIC_INFORMATION memoryInfo; SIZE_T rv = ::VirtualQuery((LPVOID)address, &memoryInfo, sizeof(memoryInfo)); if (rv != 0) { if (memoryInfo.State == MEM_COMMIT) { committedMemory += memoryInfo.RegionSize; } if (memoryInfo.State == MEM_RESERVE) { reservedMemory += memoryInfo.RegionSize; } if (memoryInfo.State == MEM_FREE) { freeMemory += memoryInfo.RegionSize; } address += memoryInfo.RegionSize; } else { break; } } wprintf(L"Cmtd:%ld, Rsvd:%ld, Free:%ld, ", committedMemory, reservedMemory, freeMemory); } void ShowHeapInfo(DWORD processId) { HANDLE snapShot = INVALID_HANDLE_VALUE; DWORD heapSize = 0; // Need to use __try __except on ToolHelp API. // If a process is being destroyed (shutdown), the API crashes (AV on NULL pointer) // Can use try catch if /EHa compiler settings is used // __try try { snapShot = CreateToolhelp32Snapshot(TH32CS_SNAPHEAPLIST, processId); if (snapShot != INVALID_HANDLE_VALUE) { HEAPLIST32 heapList; HEAPENTRY32 heapEntry; heapList.dwSize = sizeof(HEAPLIST32); heapEntry.dwSize = sizeof(HEAPENTRY32); BOOL ret = Heap32ListFirst(snapShot, &heapList); while (ret == TRUE) { BOOL ret2 = Heap32First(snapShot, &heapEntry, heapList.th32ProcessID, heapList.th32HeapID); // Loop the blocks in the heaps to get the size while (ret2 == TRUE) { if (heapEntry.dwFlags != LF32_FREE) { heapSize += heapEntry.dwBlockSize; } ret2 = Heap32Next(snapShot, &heapEntry); } ret = Heap32ListNext(snapShot, &heapList); } wprintf(L"Heap:%ld\n", heapSize); if (snapShot != INVALID_HANDLE_VALUE) { CloseToolhelp32Snapshot(snapShot); } } else { wprintf(L"Heap:ERROR\n"); } // } __except ( Filter(GetExceptionCode(), GetExceptionInformation()) ) } catch ( ![]() { heapSize = 0; if (snapShot != INVALID_HANDLE_VALUE) { CloseToolhelp32Snapshot(snapShot); } } } };
GlobalMemoryStatus取出整个Wince系统的内存使用信息,包括负载,物理内存和虚拟内存信息。 CreateToolhelp32Snapshot能取出进程信息,进程的heap信息。 VirtualQuery能取出进程的虚拟内存信息。 在监控内存使用情况时,监控进程的heap信息十分重要,wince的stack是固定大少的,如果heap不断的增长,说明进程在不断的分配动态内存。 int _tmain(int argc, _TCHAR* argv[]) { MemoryMonitor memoryMonitor; while(1) { memoryMonitor.ShowTotalMemoryInfo(); memoryMonitor.ShowProcessesMemoryInfo(); ::Sleep(60000); } char str[127]; scanf(str); return 0; } 上面的代码演示每分钟打印一次内存信息。 int _tmain(int argc, _TCHAR* argv[]) { char* p; while(1) { p = new char[1024]; Sleep(1000); } return 0; } 上面是一个内存泄漏的程序,每秒钟泄漏1k的内存。可以用这个程序检验内存监控程序的有效性。 参考文档 Stanislav Pavlov, Pavel Belevsky : Windows? Embedded CE 6.0 Fundamentals GlobalMemoryStatus MEMORYSTATUS Task Manager for Windows Mobile and Windows CE What is Virtual Memory? |
|
来自: My镜像站 > 《WM/WinCE/Win32》