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:先被预订,同时已经映射到物理内存的虚拟内存,也就是正在在使用中的内存。

以下是内存监控的代码。

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);
            }
        }
    }
};
getTimeStamp()

 

GlobalMemoryStatus取出整个Wince系统的内存使用信息,包括负载,物理内存和虚拟内存信息。

CreateToolhelp32Snapshot能取出进程信息,进程的heap信息。

VirtualQuery能取出进程的虚拟内存信息。

在监控内存使用情况时,监控进程的heap信息十分重要,wincestack是固定大少的,如果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

CreateToolhelp32Snapshot

CloseToolhelp32Snapshot

Process32First

Task Manager for Windows Mobile and Windows CE

What is Virtual Memory?

posted @ 2009-04-01 14:29  Jake Lin  阅读(6735)  评论(12编辑  收藏  举报