SimpleIsBest.NET

유경상의 닷넷 블로그

시스템 메모리 정보 알아내기...

by 블로그쥔장 | 작성일자: 2005-11-23 오후 3:18:00
이 글은 오래된 전에 작성된 글입니다. 따라서 최신 버전의 기술에 알맞지 않거나 오류를 유발할 수 있습니다. 저자는 이 글에 대한 질문을 받지 않을 것입니다. 하지만 이 글이 리뉴얼 되면 이 글에 대한 질문을 하거나 토론을 할 수도 있습니다.
모 게시판에서 컴퓨터의 메모리 정보(물리메모리 크기, 가상메모리 등등)를 알아내는 방법을 질문하신 분이 있어서 몇 자 적어봅니다. 저의 경우, 이러한 문제(?)를 해결하는 일반적인 순서는 먼저 MSDN 등에서 해당 클래스가 있는지 살펴봅니다. 이 경우, 뭐 통밥으로 System.Environment 클래스나 System.Diagnostics 네임스페이스에서 찾아보는 거죠... 클래스를 찾지 못하는 경우, WIN32 API를 찾아 봅니다. 대부분의 경우 WIN32 API에서 이런 류의 정보를 제공하는 API는 반드시 있기 마련이죠. 그리고 WMI 클래스들에서도 동일한 정보를 대개 제공하기 때문에 WMI 클래스 역시 찾아 봅니다. 그리고 나서 최후의 확인 사살로 구글 뉴스 그룹 등에서 동일 문제에 대한 검색을 해봅니다. 이렇게 검색해 보는 이유는 정말로 닷넷 프레임워크에 해당 클래스가 없는지 알아보기 위해서 입니다.

이렇게 찾아본 결론은, WIN32 API를 PInvoke를 통해 호출하던가 아니면 WMI를 사용해서 메모리 정보를 얻는 것이더군요.

주) 이 글의 예제들은 모두 닷넷 프레임워크 2.0 RTM 버전으로 작성된 것입니다. 프레임워크 1.1 버전에 대해 테스트 해보지 않았지만 동일하게 작동될 것입니다.

Getting Memory Information in .NET

시스템 정보 하면 딱 떠오르는 것이 CPU 개수, 클럭(MHz?), 물리 메모리 크기, 하드 디스크 정도가 되겠다. 오늘은 닷넷 환경에서 시스템의 물리메모리 크기, 가상 메모리 크기 등의 메모리에 대한 정보를 알아내는 코드에 대해 간략히 구라를 풀어봐야 겠다.

시스템 메모리 정보 정도되면 닷넷 클래스 라이브러리에 해당 클래스가 있어서 간단히 쓰면 오죽 좋겠냐 마는 아쉽게도 이런 클래스는 없는 듯하다. 하지만 마이크로소프트의 닷넷 프레임워크 팀이 성의가 없거나 닭대가리 호구 이기 때문에 이런 클래스가 없다고 생각하지는 않는다. 이런 류의 시스템 정보는 100% WMI(Windows Management Instrumentation)를 통해 알아낼 수 있기 때문에 굳이 중복기능을 넣어 놓지 않았을 거라고 좋게 좋게 생각하며 넘어가자. 좋은게 좋은거 아닌가?

Using WMI

WMI(Windows Management Instrumentation)는 정확히 이러한 시스템 정보를 알아내고 기타 시스템을 관리하는 용도로 고안된 것이다. 여기서 WMI 가 뭐고 사용하는 방법을 운운하면 졸라 짜증만 나니, WMI에 생소한 사람이라면 WMI에 관련된 글을 읽어 보기 바란다(쫌 읽어 봐라... 매번 말하기 졸라 귀찮다... -_-;)

시스템 메모리에 대한 요약 정보를 제공하는 WMI 클래스는 Win32_OperatingSystem 클래스가 되겠다. 이 클래스는 운영체제에 대한 전반적인 정보와 더불어 물리 메모리의 크기, 가상 메모리의 전체 크기, 페이지 파일의 크기, 현재 사용가능한 가상 메모리 크기 등의 정보를 제공한다. 좀 더 간단하고 이해하기 쉬운 Win32_LogicalMemoryConfiguration 클래스 역시 동일한 메모리 정보를 제공하는데... MSDN에 의하면 이 클래스의 정보는 더 이상 지원하지 않는단다... 덴장... 따라서 이 클래스를 사용하지 않는 것이 좋을 듯 하다.

혹시나 하는 마음에 몇 가지 정보를 더 알려주자면, 물리 메모리 정보에 대한 클래스는 Win32_PhysicalMemoryArray 클래스로 부터 알아낼 수 있다. 이 녀석을 통해 Win32_PhysicalMemory 클래스의 인스턴스를 얻어낼 수 있고, Win32_PhysicalMemory 클래스 인스턴스는 어떤 메모리 뱅크에 어떤 크기의 메모리가 꼽혀있는지 까지도 알아낼 수 있다. 심심하면 코딩해 보길...

원하는 정보는 Win32_OperatingSystem 클래스를 액세스하면 되므로 간단한 WMI 코드를 통해 물리 메모리 크기, 가상 메모리 크기 등의 정보를 표시해 보자.

ManagementClass cls = new ManagementClass("Win32_OperatingSystem");
// ManagementClass cls = new ManagementClass("Win32_LogicalMemoryConfiguration");
ManagementObjectCollection instances cls.GetInstances();
// 사실상 싱글톤 객체이므로 이 코드는 1회만 수행된다.
foreach(ManagementObject info in instances) {
    Console.WriteLine(
"Memory Information ================================");
    
Console.WriteLine("Total Physical Memory : {0:#,###} KB", info["TotalVisibleMemorySize"]);
    
Console.WriteLine("Total Page File Size : {0:#,###} KB", info["SizeStoredInPagingFiles"]);
    
Console.WriteLine("Total Virtual Memory : {0:#,###} KB", info["TotalVirtualMemorySize"]);
    
Console.WriteLine("Free Physical Memory : {0:#,###} MB", info["FreePhysicalMemory"]);
    
Console.WriteLine("Free Virtual Memory : {0:#,###} MB", info["FreeVirtualMemory"]);
    
Console.WriteLine("Free Space in Page File : {0:#,###} KB", info["FreeSpaceInPagingFiles"]);
}

리스트1. 메모리 정보를 표시하는 예제 코드

Using Win32 API

WMI는 간단하고 직관적인 구조로 시스템 정보를 제공한다. 게다가 API 역시 직관적으로 사용할 수 있다. 단점은... Windows NT SP4 이상에서만 지원하므로 Windows 95/98/Me 에서 사용할 수 없다. 이들 운영체제는 이제 거의 사용되지 않으므로 무시할 수 있겠지만, 다른 어떤 이유에서 WMI 사용에 문제가 있다면 Win32 API 호출을 통해도 메모리 정보를 알아낼 수 있다.

Win32 API의 메모리 관련 함수들을 디비보면 GlobalMemoryStatus 라는 API가 있다. 글타... 요넘이 우리의 관심 대상이 되겠다. 이 함수의 도움말을 천천히 읽어보면 4GB 이상의 메모리를 가진 시스템도 지원하기 위해서는 GlobalMemoryStatusEx 함수를 쓰라고 나와 있다. 요즘에는 데스크톱에 2GB 를 꼽는 경우도 흔한데, 서버는 말할 필요도 없이 4GB를 넘는 경우가 많다. 따라서 기왕 하는거 GlobalMemoryStatusEx 함수를 쓰는 예제를 작성해 보겠다. GlobalMemoryStatusEx 함수 예제를 보면 금방 GlobalMemoryStatus 함수의 예제 역시 만들 수 있을 것이다. 아님 말고... -_-;

Win32 API를 닷넷에서 호출하려면 죽으나 사나 P/Invoke(Platform Invoke)를 사용해야 한다. 정수, 문자열 등의 스칼라 값만을 매개변수로 취하는 API는 함수 선언이 어렵지 않지만, 아쉽게도 호출하고자 하는 GlobalMemoryStatusEx는 구조체를 사용한다. 이 구조체는 MEMORYSTATUSEX 란 구조체로서 다행이도 그다지 복잡한 구조체는 아니다. 간단하게 C# 선언을 다음과 같이 할 수 있다.

[StructLayout(LayoutKind.Sequential)]
public struct MEMORYSTATUSEX
{
    
public uint dwLength;
    public uint 
dwMemoryLoad;
    public ulong 
ullTotalPhys;
    public ulong 
ullAvailPhys;
    public ulong 
ullTotalPageFile;
    public ulong 
ullAvailPageFile;
    public ulong 
ullTotalVirtual;
    public ulong 
ullAvailVirtual;
    public ulong 
ullAvailExtendedVirtual;
}

리스트2. Win32 MEMORYSTATUSEX 구조체에 대한 C# 선언

Win32의 선언과 비교해 볼 때 특별한 사항이 없는 간단한 선언되겠다. DWORD 타입은 32비트 부호 없는 정수형이므로 닷넷의 System.UInt32 타입, 혹은 C#의 uint 타입과 매핑되며, DWORDLONG 타입은 64비트 부호 없는 정수형이므로 닷넷의 System.UInt64 타입, 혹은 C#의 ulong 타입과 매핑되므로 이것을 그대로 적용시킨 결과가 리스트2가 되겠다.

MEMORYSTATUSEX 구조체가 반환하는 정보는 WMI에서 반환하는 정보와 사뭇 다르다. 이점에 조심해야 할 것이다. MEMORYSTATUSEX 구조체의 각 멤버들이 의미하는 값에 대해서 MSDN을 잘 읽어보아야 혼동을 피할 수 있다. 예를 들어 ullTotalPageFile 멤버는 얼핏 보기에는 페이지 파일들의 크기를 반환하는 것 같지만, 실제로는 물리 메모리 + 페이지 파일의 크기로서 WMI의 가상메모리 크기를 지칭한다. 또한, ullTotalVirtual 멤버는 가상메모리의 전체 크기를 나타내는 것 같지만 실제로는 GlobalMemoryStatusEx 함수를 호출하는 프로세스(calling process)의 전체 주소 공간을 반환한다. 대개 이 값은 항상 2GB로 고정될 것이다. 이러한 사항들을 조심하지 않으면 나중에 윈도우 버그니 뭐니 딴 소리를 하게 될지도 모른다. 특히... 필자에게 따지지 말지어다...

이제 남은 것은 GlobalMemoryStatusEx 함수에 대한 extern 선언만이 남아 있을 뿐이다. 달랑 다음과 같이 선언하면 되겠다.

[DllImport("Kernel32.dll")]
private extern static bool GlobalMemoryStatusEx(ref MEMORYSTATUSEX lpBuffer);

하지만 이렇게만 선언하면 이 함수(메쏘드?)를 호출할 때마다 결과값이 true 인가 false 인가 판별한 이후, 만약 결과가 false 라면 또 GetLastError API 함수를 호출하여 어떤 오류가 발생했는지 오류코드 역시 알아내야 한다. 즉, 결과값에 의해 함수의 성공 실패가 결정되는 코드는 구조화된 코드로 바꾸기 대략 귀찮아 진다는 것이다. 그래서 항상 P/Invoke를 통해 API 함수를 호출할 때는 wrapper 메쏘드를 만들어 주는 것이 좋다. 이 wrapper 메쏘드에서 필요한 매개변수 변환 등을 수행하고 호출 후 결과값에 의해 예외(exception)을 발생하도록 해주는 것이 좋다. 리스트3 처럼 말이다.

using System;
using 
System.ComponentModel;
using 
System.Runtime.InteropServices;

public class 
Win32MemAPI
{
    [DllImport(
"kernel32", EntryPoint="GetLastError")]
    
private extern static int __GetLastError();
    
[DllImport("Kernel32.dll", EntryPoint="GlobalMemoryStatusEx", SetLastError=true)]
    
private extern static bool __GlobalMemoryStatusEx(ref MEMORYSTATUSEX lpBuffer);

    public static 
MEMORYSTATUSEX GlobalMemoryStatusEx()
    {
        MEMORYSTATUSEX memstat 
= new MEMORYSTATUSEX();

        
memstat.dwLength (uint)Marshal.SizeOf(memstat);
        if 
(__GlobalMemoryStatusEx(ref memstat) == false) {
            
int error __GetLastError();
            throw new 
Win32Exception(error);
        
}
        
return memstat;
    
}
}

리스트3. GlobalMemoryStatusEx 함수에 대한 닷넷 wrapper 메쏘드 구현

이 함수를 호출하는 코드는 매우 간단하다. 단순히 Win32MemAPI 클래스의 GlobalMemoryStatusEx 메쏘드를 호출하면 결과로서 MEMORYSTATUSEX 구조체를 반환할 것이고, 이 구조체의 값들을 '사용'하기만 하면 된다.

MEMORYSTATUSEX memstat Win32MemAPI.GlobalMemoryStatusEx();

Console.WriteLine("Memory Information using Win32 GlobalMemoryStatusEx() function ======");
Console.WriteLine("Total Physical Memory : {0:#,###} KB", memstat.ullTotalPhys / 1024);
Console.WriteLine("Total Virtual Memory (Physical + Page Files size) : {0:#,###} KB"
                                                          memstat.ullTotalPageFile / 
1024);
Console.WriteLine("Total Addr. Space (Current Process) : {0:#,###} KB", memstat.ullTotalVirtual / 1024);
Console.WriteLine("Free Physical Memory : {0:#,###} MB", memstat.ullAvailPhys / 1024);
Console.WriteLine("Free Virtual Memory : {0:#,###} KB", memstat.ullAvailPageFile / 1024);
Console.WriteLine("Free Addr. Space (Current Process) : {0:#,###} MB", memstat.ullAvailVirtual / 1024);

Summary

이 예제들이 반환하는 값들은 작업 관리자(Task Manager)에서 보여지는 정보와 정확하게 일치한다. 예를 들어 필자의 노트북은 1GB 의 메모리를 갖고 있다. 아쌀하게 말하자면 1,048,576 KB의 물리 메모리가 있어야 하는데, 리스트2나 리스트3 코드를 수행하면 1,046,588 KB 물리 메모리로 표시된다. 졸라 쫄아서 작업 관리자의 '성능' 탭에서 '실제 메모리' 부분을 살펴보았다. 그랬더니 똑같이 1,046,588 KB 로 표시되어 있었다. 약 2MB의 메모리가 안 보이는 것인데, 왜 이렇게 되는지를 필자에게 묻지 마라. 필자도 모른다. 걍 필자의 예제코드가 시스템 정보와 일치하기 때문에 더 이상 신경쓰기 싫어 그냥 넘어갔다. 여러분도 그렇게 넘어가길... -_-;

WMI를 사용하건 Win32 API를 사용하건 그건 순전히 니 맘 되겠다. 필자는 가급적 WMI 와 친해지라고 말하고 싶다. WMI는 이외에도 다양한 시스템 정보를 제공할 뿐만 아니라 시스템 리부팅, 프로세스 중단, 서비스 종료 등의 작업까지도 수행할 수 있는 전천후 시스템 관리 도구이기 때문이다. 이번 예제처럼 간단한(?) 메모리 조회 부터 시작해서 WMI의 구조에 조금씩 친해지면 나중에는 다양한 응용을 수행할 수 있을 것이다. 그러기 위해서는 WMI 에 대한 다양한 자료들도 찾아보는 약간의 수고도 마다해서는 안 된다. 게으르면 뒤쳐질 수 밖에 없는 세상에서 밥 먹고 사는 개발자란 직업에 한 숨을 쉬면서 글을 마친당...



Comments (read-only)
#메모리가 약간 모자라는 문제.... / 정성태 / 2005-11-23 오후 10:53:00
사실, 저는 그 전에는 그런 부분에 관심도 없었는데요.
이번에 4GB 메모리 구성하면서.... 무쟈게 관심갖게 되었습니다. 왜냐면... 2GB 까지는 눈에 띄게 차이가 안 나는데요. 4GB 시스템에서는 작업 관리자에 3,407,244 KB 물리메모리가 나옵니다. 3.25GB 정도인데요... ^^;

구글을 통해 검색해봤지만, 내나 같은 소리이고... 보드 제작 회사에다가 직접 문의를 해봤는데.. "PCI 할당되는 시스템 메모리 값이 변경된다" 는 것이 그 답이었습니다. 그러면서, BIOS 회사에서 confidential 로 되어 있는 문서라면서 왠 PPT 파일을 하나 보내주더라고요.

1GB, 2GB 등에서도 PCI 메모리가 할당되긴 하는데,,, 유독 4GB 에서 그렇게 할당되는 이유가 이해되진 않지만... 암튼 그렇다고 하니... ^^; ( 저 역시 더 이상 신경쓰기 싫어서... )
#re: 시스템 메모리 정보 알아내기... / 블로그쥔장 / 2005-11-24 오전 9:22:00
그러게요... 저두 예제 코드 작성하면서 물리 메모리가 1GB 보다 작게 보고되서
시껍했지만... 그 양도 적고 Task Manager 에서 보고되는 값과 같길래 걍 무시했습니다만...
4GB 물리 메모리를 꼽았는데 3.25 GB 밖에 사용 못한다면 그건 좀 큰데요...
무지 신경쓰이시겠습니다... 뭔넘의 PCI 메모리가 그렇게 많이 쳐먹는댜... -_-;
#re: 시스템 메모리 정보 알아내기... / 조승태 / 2008-03-17 오전 11:17:00
메모리를 알아내야할 일이 있는데,
머리속에 퍼뜩 떠오르는 것이 WMI..
좀더 편한거 없나 하고 devpi* 검색하다가 또 쥔장님 블로그로 들어오게 되었네요.
공짜로 잘 먹겠습니다. ^^;;
다음번엔 알아서 WMI 찾아보겠습니다 ㅡ.ㅡ