Microsoft MVP성태의 닷넷 이야기
글쓴 사람
정성태 (techsharer at outlook.com)
홈페이지
첨부 파일
 

C# - 윈도우 작업 관리자와 리소스 모니터의 메모리 값을 구하는 방법

다음의 그림을 보고 시작할까요? ^^

windows_memory_1.png

위의 수치를 보면서 작업 관리자와 리소스 모니터의 용어를 정리해 보겠습니다.

작업 관리자의 "MEMORY" 32GB == 리소스 모니터의 "Installed" 32,768MB
작업 관리자의 "Memory usage" 31.9GB == 리소스 모니터의 "Total" 32,683MB

작업 관리자의 "In use" 27.0GB == 리소스 모니터의 "In Use" 27,604MB
작업 관리자의 "Available" 4.9GB == 리소스 모니터의 "Available" == "Standby + Free" 4,977MB

작업 관리자의 "Cached" 4.9GB == 리소스 모니터의 "Cached" == "Modified + Standby" 4,938MB
작업 관리자의 "Hardware reserved" 84.5MB == 리소스 모니터의 "Hardware Reserved" 85MB

좀 더 자세한 설명은, 리소스 모니터의 용어에 마우스를 올려 보면 나오는 다음의 툴팁을 참고할 수 있습니다.

  • Hardware Reserved: Memory that is reserved for use by the BIOS and some drivers for other peripherals
  • In Use: Memory used by processes, drivers, or the operating system
  • Modified: Memory whoose contents must be written to disk before it can be used for another purpose
  • Standby: Memory that contains cached data and code that is not actively in use
  • Free: Memory that does not contain any valuable data, and that will be used first when processes, drivers or the operating system need more memory

  • Available: Amount of memory (including Standby and Free memory) that is immediately available for use by processes, drivers, or the operating system
  • Cached: Amount of memory (including Standby and Modified memory) containing cached data and code for rapid access by processes, drivers, or the operating system
  • Total: Amount of physical memory available to the operating system, device drivers, and processes
  • Installed: Amount of physical memory installed in the computer

자, 그럼 이제 쉽게 정리해 볼까요? ^^

우선, 여러분이 구매한 PC의 물리 메모리로 장착한 용량을 작업 관리자의 "Memory" 또는 리소스 모니터의 "Installed"를 통해서 알 수 있습니다. 하지만 대개의 경우 그 물리 메모리를 전부 쓸 수 있는 것은 아니고 컴퓨터의 장치 요소들 중에는 메모리를 요구하는 것이 있어 그런 것들에 할당되는 메모리는 사실상 응용 프로그램에서 사용할 수 없으므로 제외하게 됩니다. 그 메모리가 바로 작업 관리자와 리소스 모니터의 "Hardware reserved"입니다.

따라서 제 컴퓨터는 총 32,768MB의 RAM이 설치되어 있는데 그중 85MB를 제외한 32,683MB의 메모리만 쓸 수 있는 것입니다.




이제 32,683MB 메모리에서, 실질적으로 운영체제 및 프로그램이 사용하는 메모리를 빼야 합니다. 그것이 바로 "In Use"로 말 그대로 "사용 중"인 메모리입니다.

"사용 중"이긴 하지만, 동일한 프로그램을 올려 놓은 상황에서 만약 메모리가 (32GB가 아닌) 16GB라면 "In Use" 메모리가 줄어들 수도 있습니다. 왜냐하면 저것은 프로그램의 "Working Set"이기 때문에 메모리가 충분하면 더 올려놓고, 부족하면 시기적절하게 페이징 해버리기 때문입니다. 따라서 동일한 프로그램인데도 컴퓨터의 사양에 따라 다르게 나올 수 있으므로 특별한 상황이 아니라면 그렇게 다르다는 사실에 크게 주목하지 않아도 됩니다.

"In Use"를 제외하면 이제 Modified, Standby, Free 영역이 남습니다.

이 중에서 Modified와 Standby는 시스템의 성능을 높이기 위한 목적의 Cache 용도로 운영체제가 일부러, 나쁘게 말하면 ^^ 요행을 바라며 쓸데없이 사용 중인 메모리입니다. 하지만 Standby가 읽기 용도로 올려진 cache인 반면, 그중에서 운이 좋아 "쓰기"까지 진행된 영역이 "Modified"입니다. 이 2가지는 같은 cache 용도였지만 해당 메모리를 해제하는 시점의 처리 방식이 달라진다는 차이점이 있습니다. 즉, Standby는 별다른 조치 없이 바로 해제할 수 있지만 Modified는 그전에 반드시 디스크에 기록해야 하므로 부하가 발생하는 것입니다.

따라서, 운영체제는 특정 프로그램이 메모리를 요구했을 때 "Standby" 영역을 아무런 부하 없이 응용 프로그램에게 할당할 수 있기 때문에 "Standby"와 "Free" 영역을 합쳐 가용(Available) 메모리라고 하는 것입니다. 결국 리소스 모니터에서 "Free"가 39MB만 남았다고 해서 자신의 시스템이 현재 심각한 RAM 부족 현상이 발생하는 것은 아니라는 것을 의미합니다.




자, 그럼 "리소스 모니터"가 보여주는 메모리를 Win32 API를 이용해 프로그램으로 구해볼까요? ^^ 우선 시스템에 설치된 RAM 용량을 GetPhysicallyInstalledSystemMemory API로 가져올 수 있습니다.

GetPhysicallyInstalledSystemMemory function
; https://learn.microsoft.com/en-us/windows/desktop/api/sysinfoapi/nf-sysinfoapi-getphysicallyinstalledsystemmemory

public class SafeNativeMethods
{
    [DllImport("kernel32.dll", SetLastError = true)]
    [return: MarshalAs(UnmanagedType.Bool)]
    static extern bool GetPhysicallyInstalledSystemMemory(out ulong MemoryInKilobytes);
}

SafeNativeMethods.GetPhysicallyInstalledSystemMemory(out ulong physicalMemory);

그다음 "Hardware Reserved" 용량은 어떻게 구해야 할까요? 이 값은 리소스 모니터의 "Available" 메모리를 가져와 위에서 구한 physicalMemory에서 빼면 구할 수 있습니다. 이를 위해 GetPerformanceInfo API로부터,

GetPerformanceInfo function
; https://learn.microsoft.com/en-us/windows/desktop/api/psapi/nf-psapi-getperformanceinfo

Total 메모리(즉, Available)를 구하고,

[DllImport("psapi.dll", SetLastError = true)]
public static extern bool GetPerformanceInfo(out PERFORMANCE_INFORMATION pPerformanceInformation, uint cb);

PERFORMANCE_INFORMATION pi = new PERFORMANCE_INFORMATION();
pi.cb = (uint)Marshal.SizeOf(pi);
SafeNativeMethods.GetPerformanceInfo(out pi, pi.cb);

ulong pageSize = pi.PageSize.ToUInt64();
ulong physicalTotal = pi.PhysicalTotal.ToUInt64(); // == Available 메모리 용량

Console.WriteLine($"PhysicalTotal == \t{physicalTotal * pageSize / 1024 / 1024} MB");

이 값을 통해 "Hardware Reserved" 크기를 유추할 수 있습니다.

double reserved = (physicalMemory - (pi.Total / 1024.0));
long reservedMB = (long)Math.Round(reserved / 1024.0);
Console.WriteLine($"Reserved == \t\t\t{reservedMB} MB");




가용 메모리(Standby + Free)를 구할 수 있는 또 다른 API가 있습니다.

GlobalMemoryStatusEx function
; https://learn.microsoft.com/en-us/windows/desktop/api/sysinfoapi/nf-sysinfoapi-globalmemorystatusex

MEMORYSTATUSEX structure
; https://learn.microsoft.com/en-us/windows/desktop/api/sysinfoapi/ns-sysinfoapi-_memorystatusex

ullAvailPhys: The amount of physical memory currently available, in bytes. This is the amount of physical memory that can be immediately reused without having to write its contents to disk first. It is the sum of the size of the standby, free, and zero lists.


[return: MarshalAs(UnmanagedType.Bool)]
[DllImport("kernel32.dll", CharSet = CharSet.Auto, SetLastError = true)]
public static extern bool GlobalMemoryStatusEx(ref MEMORYSTATUSEX buffer);

MEMORYSTATUSEX globalMemoryStatus = new MEMORYSTATUSEX();
SafeNativeMethods.GlobalMemoryStatusEx(ref globalMemoryStatus);

Console.WriteLine($"MEMORYSTATUSEX.ullAvailPhys: {globalMemoryStatus.ullAvailPhys / 1024.0 / 1024.0} MB");

재미있는 것은 가용 메모리는 구해도 Standby와 Free를 개별적으로 구할 수는 없다는 점입니다. (아래에서 성능 카운터를 이용해 구하는 방법이 나옵니다.)

Standby의 경우 GetPerformanceInfo의 PERFORMANCE_INFORMATION.SystemCache로 근사치를 구할 수 있는데, 애석하게도 이 값은 리소스 모니터에서 나온 "Standby"에 비해서는 항상 작게 나옵니다.

마지막으로 구할 수 있는 수치가 "In Use + Modified"입니다. 이것은 (Installed가 아닌) 전체 메모리에서 Available 메모리를 빼면 됩니다.

ulong inuse_modified = (ulong) ((pi.Total - pi.Available) / 1024.0);
Console.WriteLine($"Total - Available - HardwareReserved: {inuse_modified / 1024.0:#,#.0} MB ({inuse_modified / 1024.0 / 1024.0:#,#.0} GB)");

마찬가지로 (성능 카운터의 도움을 받지 않고) API 수준에서 "In Use"와 "Modified"를 개별적으로 구할 수 있는 방법은 없습니다. (혹시, 아시는 분은 덧글 부탁드립니다. ^^)




Win32 API가 아닌, Performance Counter로 눈을 돌리면 이제 "Modified" 값을 구할 수 있습니다.

\Memory\Modified Page List Bytes 

이 값을 구할 수 있으니, 위에서 구했던 "inuse_modified" 값에서 빼면 "In Use" 용량도 구할 수 있습니다. 또한 다음의 성능 카운터는,

\Memory\Free & Zero Page List Bytes

리소스 관리자의 "Free" 영역의 수치이므로 역시 이전에 구했던 가용 메모리에서 빼면 "Standby" 용량도 구할 수 있습니다. 그럼, 전부 구했군요. ^^ 다음의 소스 코드는 위에서 설명한 내용을 바탕으로 리소스 모니터와 작업 관리자에서 보여주는 수치를 구합니다.

using System;
using System.Diagnostics;
using System.Threading;

class Program
{
    private static PerformanceCounter freeMemory;
    private static PerformanceCounter modifiedMemory;

    static void Main(string[] args)
    {
        freeMemory = new PerformanceCounter("Memory", "Free & Zero Page List Bytes", true);
        modifiedMemory = new PerformanceCounter("Memory", "Modified Page List Bytes", true);

        while (true)
        {
            PERFORMANCE_INFORMATION pi = new PERFORMANCE_INFORMATION();
            pi.Initialize();
            SafeNativeMethods.GetPerformanceInfo(out pi, pi.cb);

            Console.WriteLine("[Resource Monitor]");

            SafeNativeMethods.GetPhysicallyInstalledSystemMemory(out ulong installedMemory);

            double reserved = (installedMemory - (pi.Total / 1024.0));
            ulong modified = (ulong)modifiedMemory.RawValue;
            ulong inuse = pi.Total - pi.Available - modified;

            long reservedMB = (long)Math.Round(reserved / 1024.0);
            Console.WriteLine($"Hardware Reserved: {reservedMB} MB");

            Console.WriteLine($"In Use: {inuse / 1024 / 1024} MB");
            Console.WriteLine($"Modified: {modified / 1024 / 1024} MB");

            ulong free = (ulong)freeMemory.RawValue;
            ulong standby = pi.Available - free;
            Console.WriteLine($"Standby: {standby / 1024 / 1024} MB");
            Console.WriteLine($"Free: {free / 1024 / 1024} MB");
            Console.WriteLine();
            Console.WriteLine($"Available: {pi.Available.MB()} MB");
            Console.WriteLine($"Cached: {(standby + modified).MB()} MB");
            Console.WriteLine($"Total: {pi.Total.MB()} MB");
            Console.WriteLine($"Installed: {installedMemory / 1024} MB");

            MEMORYSTATUSEX globalMemoryStatus = new MEMORYSTATUSEX();
            globalMemoryStatus.Initialize();
            SafeNativeMethods.GlobalMemoryStatusEx(ref globalMemoryStatus);

            Console.WriteLine();
            Console.WriteLine("[Task Manager]");

            Console.WriteLine($"Memory: {installedMemory / 1024.0 / 1024.0} GB");
            Console.WriteLine($"Memory usage: {pi.Total / 1024.0 / 1024.0 / 1024.0:#.0} GB");
            Console.WriteLine();
            Console.WriteLine($"In use: {inuse / 1024.0 / 1024.0 / 1024.0:#.0} GB");
            Console.WriteLine($"Available: {pi.Available / 1024.0 / 1024.0 / 1024.0:#.0} GB");
            Console.WriteLine($"Committed: {pi.Commit / 1024.0 / 1024.0 / 1024.0:#.0} / {globalMemoryStatus.ullTotalPageFile / 1024.0 / 1024.0 / 1024.0:#.0} GB");
            Console.WriteLine($"Cached: {(standby + modified) / 1024.0 / 1024.0 / 1024.0:#.0} GB");
            Console.WriteLine($"Paged pool: {pi.KernelPage / 1024.0 / 1024.0 / 1024.0:#.0} GB");
            Console.WriteLine($"Non-paged pool: {pi.KernelNonPage / 1024.0 / 1024.0:#} GB");

            Console.WriteLine();
            Thread.Sleep(1000);
        }
    }
}

그리고 다음은 이렇게 구한 수치와 실제 리소스 모니터/작업 관리자의 수치를 비교하는 모습입니다.

windows_memory_2.png

서로 구하는 시간 차가 약간 있는 것을 감안하면 대충 맞는 걸로 보입니다. ^^

(이 글의 예제 프로젝트는 github에 있습니다.)




그 외에, 메모리 사용량을 알아낼 수 있는 방법들을 검색해 보면 위에서 설명한 범위 내에서 구해지는 것들입니다. 예를 들어, WMI의 FreePhysicalMemory, TotalVisibleMemorySize 등을 사용할 수 있다고 나오는데,

wmic OS get FreePhysicalMemory, TotalVisibleMemorySize

실제로 구해지는 값을 보면, FreePhysicalMemory는 Available 용량을, TotalVisibleMemorySize는 물리 메모리에서 "Hardware Reserved" 용량을 뺀 값(단위: KB)에 해당합니다. 그 외에 TotalPhysicalMemory도 있는데,

wmic computersystem get TotalPhysicalMemory

마찬가지로 TotalVisibleMemorySize 값(단위: 바이트)과 같습니다.




기타 다음의 글들도 읽어보시면 도움이 될 것입니다.

작업 관리자에서의 "Commit size"가 가리키는 메모리의 의미
; https://www.sysnet.pe.kr/2/0/1850

The usable memory may be less than the installed memory on Windows 7-based computers
; https://support.microsoft.com/en-gb/help/978610/the-usable-memory-may-be-less-than-the-installed-memory-on-windows-7-b




[이 글에 대해서 여러분들과 의견을 공유하고 싶습니다. 틀리거나 미흡한 부분 또는 의문 사항이 있으시면 언제든 댓글 남겨주십시오.]







[최초 등록일: ]
[최종 수정일: 3/9/2024]

Creative Commons License
이 저작물은 크리에이티브 커먼즈 코리아 저작자표시-비영리-변경금지 2.0 대한민국 라이센스에 따라 이용하실 수 있습니다.
by SeongTae Jeong, mailto:techsharer at outlook.com

비밀번호

댓글 작성자
 




... [61]  62  63  64  65  66  67  68  69  70  71  72  73  74  75  ...
NoWriterDateCnt.TitleFile(s)
12121정성태1/20/20209749VS.NET IDE: 139. Visual Studio - Error List: "Could not find schema information for the ..."파일 다운로드1
12120정성태1/19/202011175.NET Framework: 878. C# DLL에서 Win32 C/C++처럼 dllexport 함수를 제공하는 방법 - 네 번째 이야기(IL 코드로 직접 구현)파일 다운로드1
12119정성태1/17/202011207디버깅 기술: 160. Windbg 확장 DLL 만들기 (3) - C#으로 만드는 방법
12118정성태1/17/202011870개발 환경 구성: 466. C# DLL에서 Win32 C/C++처럼 dllexport 함수를 제공하는 방법 - 세 번째 이야기 [1]
12117정성태1/15/202010870디버깅 기술: 159. C# - 디버깅 중인 프로세스를 강제로 다른 디버거에서 연결하는 방법파일 다운로드1
12116정성태1/15/202011357디버깅 기술: 158. Visual Studio로 디버깅 시 sos.dll 확장 명령어를 (비롯한 windbg의 다양한 기능을) 수행하는 방법
12115정성태1/14/202011094디버깅 기술: 157. C# - PEB.ProcessHeap을 이용해 디버깅 중인지 확인하는 방법파일 다운로드1
12114정성태1/13/202012960디버깅 기술: 156. C# - PDB 파일로부터 심벌(Symbol) 및 타입(Type) 정보 열거 [1]파일 다운로드3
12113정성태1/12/202013584오류 유형: 590. Visual C++ 빌드 오류 - fatal error LNK1104: cannot open file 'atls.lib' [1]
12112정성태1/12/202010123오류 유형: 589. PowerShell - 원격 Invoke-Command 실행 시 "WinRM cannot complete the operation" 오류 발생
12111정성태1/12/202013421디버깅 기술: 155. C# - KernelMemoryIO 드라이버를 이용해 실행 프로그램을 숨기는 방법(DKOM: Direct Kernel Object Modification) [16]파일 다운로드1
12110정성태1/11/202012013디버깅 기술: 154. Patch Guard로 인해 블루 스크린(BSOD)가 발생하는 사례 [5]파일 다운로드1
12109정성태1/10/20209917오류 유형: 588. Driver 프로젝트 빌드 오류 - Inf2Cat error -2: "Inf2Cat, signability test failed."
12108정성태1/10/20209975오류 유형: 587. Kernel Driver 시작 시 127(The specified procedure could not be found.) 오류 메시지 발생
12107정성태1/10/202010900.NET Framework: 877. C# - 프로세스의 모든 핸들을 열람 - 두 번째 이야기
12106정성태1/8/202012395VC++: 136. C++ - OSR Driver Loader와 같은 Legacy 커널 드라이버 설치 프로그램 제작 [1]
12105정성태1/8/202010997디버깅 기술: 153. C# - PEB를 조작해 로드된 DLL을 숨기는 방법
12104정성태1/7/202011667DDK: 9. 커널 메모리를 읽고 쓰는 NT Legacy driver와 C# 클라이언트 프로그램 [4]
12103정성태1/7/202014432DDK: 8. Visual Studio 2019 + WDK Legacy Driver 제작- Hello World 예제 [1]파일 다운로드2
12102정성태1/6/202011991디버깅 기술: 152. User 권한(Ring 3)의 프로그램에서 _ETHREAD 주소(및 커널 메모리를 읽을 수 있다면 _EPROCESS 주소) 구하는 방법
12101정성태1/5/202011336.NET Framework: 876. C# - PEB(Process Environment Block)를 통해 로드된 모듈 목록 열람
12100정성태1/3/20209356.NET Framework: 875. .NET 3.5 이하에서 IntPtr.Add 사용
12099정성태1/3/202011667디버깅 기술: 151. Windows 10 - Process Explorer로 확인한 Handle 정보를 windbg에서 조회 [1]
12098정성태1/2/202011274.NET Framework: 874. C# - 커널 구조체의 Offset 값을 하드 코딩하지 않고 사용하는 방법 [3]
12097정성태1/2/20209821디버깅 기술: 150. windbg - Wow64, x86, x64에서의 커널 구조체(예: TEB) 구조체 확인
12096정성태12/30/201911775디버깅 기술: 149. C# - DbgEng.dll을 이용한 간단한 디버거 제작 [1]
... [61]  62  63  64  65  66  67  68  69  70  71  72  73  74  75  ...