由于发现开发的程序有内存泄漏(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()
WCHAR * getTimeStamp()
{
SYSTEMTIME currentTime;
std::string timeString = "";
char tempBuffer[32];
//get the current time - use win API as CE does not support c time functions
GetLocalTime(¤tTime);
//Add hour
if(currentTime.wHour < 10)
timeString += "0";
timeString += _itoa(currentTime.wHour,tempBuffer,10);
//add delimiter
timeString += ":";
//add minute
if(currentTime.wMinute < 10)
timeString += "0";
timeString += _itoa(currentTime.wMinute,tempBuffer,10);
//add delimiter
timeString += ":";
//add second
if(currentTime.wSecond < 10)
timeString += "0";
timeString += _itoa(currentTime.wSecond,tempBuffer,10);
//add delimiter
timeString += " ";
//add day
if(currentTime.wDay < 10)
timeString += "0";
timeString += _itoa(currentTime.wDay,tempBuffer,10);
//add delimiter
timeString += "/";
//add month
if(currentTime.wMonth < 10)
timeString += "0";
timeString += _itoa(currentTime.wMonth,tempBuffer,10);
//add delimiter
timeString += "/";
//add year
timeString += _itoa(currentTime.wYear,tempBuffer,10);
//Copies it to a (char *) which is then passed to any function thatx wants TimeStamp
static WCHAR timeStamp[64];
swprintf(timeStamp, L"%S", timeString.c_str());
return timeStamp;
}
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
CreateToolhelp32Snapshot
CloseToolhelp32Snapshot
Process32First
Task Manager for Windows Mobile and Windows CE
What is Virtual Memory?