Wince和Windows Mobile下的内存监控
由于发现开发的程序有内存泄漏(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:先被预订,同时已经映射到物理内存的虚拟内存,也就是正在在使用中的内存。
以下是内存监控的代码。
{
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不断的增长,说明进程在不断的分配动态内存。
{
MemoryMonitor memoryMonitor;
while(1)
{
memoryMonitor.ShowTotalMemoryInfo();
memoryMonitor.ShowProcessesMemoryInfo();
::Sleep(60000);
}
char str[127];
scanf(str);
return 0;
}
上面的代码演示每分钟打印一次内存信息。
{
char* p;
while(1)
{
p = new char[1024];
Sleep(1000);
}
return 0;
}
上面是一个内存泄漏的程序,每秒钟泄漏1k的内存。可以用这个程序检验内存监控程序的有效性。
参考文档
Stanislav Pavlov, Pavel Belevsky : Windows® Embedded CE 6.0 Fundamentals
出处:http://procoder.cnblogs.com
本作品由Jake Lin创作,采用知识共享署名-非商业性使用-禁止演绎 2.5 中国大陆许可协议进行许可。 任何转载必须保留完整文章,在显要地方显示署名以及原文链接。如您有任何疑问或者授权方面的协商,请给我留言。