C# - 프로세스 메모리 중 Private Working Set 크기를 구하는 방법(Win32 API)
지난 글에 이어,
C# - 프로세스 메모리 중 Private Working Set 크기를 구하는 방법(성능 카운터, WMI)
; https://www.sysnet.pe.kr/2/0/13889
이번에는 Win32 API를 이용해 Private Working Set 크기를 구해볼 텐데요, Windows 10 22H2부터 psapi.dll에 추가된 PROCESS_MEMORY_COUNTERS_EX2 구조체를 이용하면,
PROCESS_MEMORY_COUNTERS_EX2 structure (psapi.h)
; https://learn.microsoft.com/en-us/windows/win32/api/psapi/ns-psapi-process_memory_counters_ex2
다음과 같이 간단하게 구할 수 있습니다.
using System.ComponentModel;
using System.Diagnostics;
using System.Runtime.InteropServices;
[StructLayout(LayoutKind.Sequential)]
public struct PROCESS_MEMORY_COUNTERS_EX2
{
// PROCESS_MEMORY_COUNTERS_EX
// https://learn.microsoft.com/en-us/windows/win32/api/psapi/ns-psapi-process_memory_counters_ex
public uint cb;
public uint PageFaultCount;
public UIntPtr PeakWorkingSetSize;
public UIntPtr WorkingSetSize;
public UIntPtr QuotaPeakPagedPoolUsage;
public UIntPtr QuotaPagedPoolUsage;
public UIntPtr QuotaPeakNonPagedPoolUsage;
public UIntPtr QuotaNonPagedPoolUsage;
public UIntPtr PagefileUsage;
public UIntPtr PeakPagefileUsage;
public UIntPtr PrivateUsage;
// _PROCESS_MEMORY_COUNTERS_EX2
// https://learn.microsoft.com/en-us/windows/win32/api/psapi/ns-psapi-process_memory_counters_ex2
public UIntPtr PrivateWorkingSetSize;
public ulong SharedCommitUsage;
}
internal class Program
{
[DllImport("psapi.dll", SetLastError = true)]
static extern bool GetProcessMemoryInfo(IntPtr hProcess, out PROCESS_MEMORY_COUNTERS_EX2 counters, uint size);
static void Main(string[] args)
{
ulong totalSize = 0;
foreach (Process process in Process.GetProcesses())
{
IntPtr pHandle = GetHandle(process);
if (pHandle == IntPtr.Zero)
{
continue;
}
PROCESS_MEMORY_COUNTERS_EX2 counters;
var handle = process.SafeHandle;
GetProcessMemoryInfo(handle.DangerousGetHandle(), out counters, (uint)Marshal.SizeOf(typeof(PROCESS_MEMORY_COUNTERS_EX2)));
Console.WriteLine($"{process.ProcessName}: {counters.PrivateWorkingSetSize}");
totalSize += (ulong)counters.PrivateWorkingSetSize;
}
Console.WriteLine($"Total: {totalSize}");
}
private static nint GetHandle(Process process)
{
try
{
return process.Handle;
}
catch (Win32Exception)
{
return IntPtr.Zero;
}
}
}
전체 프로세스를 열거하는데 약 20ms 정도가 걸리니 성능 카운터를 사용하는 것보다 월등하게 빠른 것이 마음에 드는군요. ^^
한 가지 문제점이라면, Process.Handle 속성을 사용해 대상 프로세스의 HANDLE 값을 구해오는 것에 보안상 문제가 있다는 점입니다.
실제로, 일반 사용자 권한으로 저 코드를 수행하면 SYSTEM 권한 등으로 실행 중인 Service 프로세스(예: w3wp.exe, svchost.exe)의 HANDLE 값을 가져오지 못합니다. 게다가 관리자 권한으로 실행한다고 해도 "
Protected Process"로 실행 중인 프로세스(예: smss.exe, wininit.exe)의 HANDLE 값을 가져오지 못합니다.
이렇게 사용자 권한에 따라 프로세스 정보가 제외되므로 Total 값을 구할 때 차이가 발생할 수 있다는 점도 고려해야 합니다.
[일반 사용자 권한으로 실행했을 때 총 크기]
20731105280
[관리자 권한으로 실행했을 때 총 크기]
20763201536
그런 정도만 제외한다면 WMI를 사용한 방법보다 훨씬 빠르고 간단하게 Private Working Set 크기를 구할 수 있으므로 나쁘지 않은 방법입니다. 참고로, WMI를 이용해 총 크기를 구하면 위와 같은 상황에서 상당한 차이의 값이 나옵니다.
[WMI를 이용한 총 크기]
44665249792
따라서, 총 크기도 중요하고 개별 프로세스의 값을 구하는 실행 속도도 중요하다면, 1) Win32 API를 이용해 의미 있는 프로세스들의 Private Working Set 크기는 구하고, 2)
총 크기는 별도로 Performance Counter를 이용해 구하는 것도 나쁘지 않을 것입니다.
(
첨부 파일은 이 글의 예제 코드를 포함합니다.)
[이 글에 대해서 여러분들과 의견을 공유하고 싶습니다. 틀리거나 미흡한 부분 또는 의문 사항이 있으시면 언제든 댓글 남겨주십시오.]