C, C++ 문법

Win32 아키텍처 완전 해부 ?

디버그정 2008. 7. 27. 01:11

Win32 아키텍처 완전 해부 ?
가상 메모리

이번 호에서는 윈도우 32 아키텍처의 또 다른 면모인 메모리에 대해 공부하면서 윈도우 32의 메모리는 어떻게 구성되어 있으며 윈도우 32 시스템은 어떤 규칙으로 프로그램에 메모리를 지정하는지, 그리고 이미 지정된 가상 메모리의 상태와 현재 구성은 어떻게 알 수 있는지에 대해 예제 프로그램을 통해 알아본다.
윈도우 32의 메모리는 가상 메모리로 모든 프로세스는 실제 존재하는 메모리 칩의 용량과 무관하게 2의 32제곱, 즉 4GB의 가상 메모리 공간을 가지게 된다. 이것은 시스템에서 하나의 프로세스가 생성될 때마다 각 프로세스가 4GB 크기의 가상 메모리를 가진다는 것을 의미한다. 따라서  32비트 메모리 포인터는 이론적으로 0x00000000에서 시작하여 0xFFFFFFFF까지 4,294,967,296개의 값 중 어느 것이나 취할 수 있는데, 이것이 한 프로세스가 가지는 4GB의 메모리 공간이다. 윈도우 NT를 자주 사용하는 사람이라면 프로그램의 실행이 원활하게 작동하지 않을 때 자판의 Ctrl+Alt+Del 키를 사용하여 해당 애플리케이션이나 프로세스를 메모리에서 제거한 기억이 있을 것이다. 이렇게 해도 다른 프로세스에는 전혀 영향을 미치지 않고 문제의 프로세스만 지울 수 있다. 하지만 Ctrl+Alt+Del 키를 도스나 윈도우 3x OS에서 사용하면 전체 시스템이 재부팅 된다. 이것은 메모리 아키텍처의 차이 때문이다. 윈도우 3x나 도스는 윈도우 32처럼 각 프로세스가 독립된 4GB의 메모리 공간을 갖지 않고 윈도우 OS나 DLL 등의 윈도우 공유파일, 그리고 일반 윈도우 애플리케이션이 하나의 공통된 메모리 공간을 사용하기 때문이다.
윈도우 3x나 도스에서 사용하고 있는 이런 메모리 구조는 안전하지 못하다. 윈도우 OS 내의 모든 프로그램들은 이미 메모리에 올려진 다른 프로세스나 나중에 오게 될 프로세스까지 고려해야 하며, 모든 프로세스는 서로 견제해야 한다. 이렇게 하지 않으면 언제 어떻게 다른 프로세스에 의해 기존 프로세스가 방해받을지 모른다. 윈도우 3x에서는 윈도우 오퍼레이팅 시스템이 사용하는 코드 및 데이타 영역이 일반 프로그램이 사용하는 코드 및 데이타 영역과 구분되어 있지 않아 윈도우 시스템이 불안정하다. 하지만 윈도우 32 시스템을 사용하면 윈도우 OS는 시스템에 의해 일반 프로그램의 접근이 금지되므로 시스템 고장 확률이 훨씬 적어진다. 물론 아직까지 완전한 시스템을 찾을 수 없지만 윈도우 32 메모리 아키텍처를 사용하면 윈도우는 훨씬 안정성을 유지할 수 있다.

가상 메모리의 구조
위에서 모든 프로세스는 자신이 실제 소유하고 있는 메모리와 무관하게 4GB의 독립된 가상 메모리 공간을 갖는다고 말했다. 따라서 프로세스 A의 메모리 포인터 0x12345678은 프로세스 B의 메모리 포인터 0x12345678과 무관한 메모리 공간을 가리킨다. 그래서 프로세스 A와 프로세스 B는 서로 메모리 접근이 허용되지 않는다.
이 가상 메모리는 실제 설치된 메모리의 양과 무관하다고 했는데, 이것은 은행의 원리와 동일하다. 보통 은행은 모든 예금주가 한꺼번에 돈을 찾아가지 않는다는 가정하에 실제 은행이 보유하는 현금보다 많은 금액을 고객에게 빌려줄 수 있다. 이와 같은 원리로 윈도우 32 시스템은 실제 자신이 보유한 메모리의 양보다 많은 메모리를 여러 프로세스에게 빌려준다. 윈도우 32의 메모리 분배는 윈도우 98/95와 윈도우 NT가 약간 차이가 있다. 그림 1을 보면서 윈도우 98/95와 윈도우 NT가 어떠한 방법으로 4GB의 가상 메모리를 적용하고 있는지 알아보자.
우선 윈도우 9x와 윈도우 NT에서 비슷한 목적을 가진 메모리 공간을 같은 알파벳으로 나누어 보았다. 첫 번째 긴 사각형은 윈도우 9x의 메모리 전개도이고 그 아래의 두 번째 긴 사각형은 윈도우 NT의 메모리 전개도이다. 그리고 사각형 주변으로 표시된 육진법 숫자들은 가상 메모리의 위치를 의미한다.
가상 메모리 지역 A는 붉은 색으로 표시하였다. 이 지역은 프로그램 상의 메모리 오류를 잡아내기 위해 예약된 위치이다. 특히 윈도우 9x는 윈도우 16과 호환을 유지해야 하므로 처음 4MB 공간은 필수적이다. 어느 프로그램이든 지역 A로의 쓰기와 읽기가 금지되어 있다. 우리가 윈도우 프로그램을 실행할 때 윈도우 9x에서는 0x00400000가 베이스 메모리가 되며 윈도우 NT에서는 0x0001000이 베이스 메모리의 위치가 된다. 즉 어떤 프로그램이 실행될 때 메모리 상의 위치이다. 이 때 윈도우 NT와 윈도우 9x 프로그램의 호환성에 주의해야 한다. 윈도우 NT에 맞추어 프로그램을 링크한다면 그 실행 프로그램의 베이스 메모리는 0x10000이 되는데, 이 메모리는 윈도우 NT 상에서는 접근이 금지된 지역이므로 실행이 불가능하게 된다. 따라서 두 OS에서 하나의 프로그램을 실행시키고자 한다면 반드시 윈도우 NT에 맞추어 링크해야 한다. 이 베이스 값을 확인하려면 예제 프로그램의 첫 번째 프로그램인 시스템 정보(System Information) 메뉴를 실행시켜 그 첫째 값인 Base Memory Address를 윈도우 9x와 윈도우 NT에서 각각 실행한다. 
void *malloc( size_t size )나 void *calloc( size_t num, size_t size )를 사용할 때 반환값은 void 포인터로 이 포인터는 여기서 할당된 메모리 공간의 시작점을 가리킨다. 만약 시스템이 메모리 할당에 실패할 경우에는 NULL을 가리키게 되는데, 이것을 확인하지 않으면 나중에 프로그램이 실제 할당된 지역을 필요로 할 때 그림 1의 A 지역을 사용하려 하므로 오류를 발생하게 된다. 프로그래머는 항상 메모리를 할당한 뒤 그 결과를 확인할 필요가 있다. 다음과 같이 반환값이 NULL이 아닐 때 할당된 메모리를 사용하고, 사용이 끝나면 free를 통해 메모리를 시스템으로 되돌려 준다.

char *string;
string = malloc( int);
if( string == NULL ){
 /* 메모리 할당이 실패하였으므로 string 변수는 사용할 수 없다. */
}
else {
/* 메모리가 할당되었으므로 사용하고, 사용이 끝나면 free(string)를
   사용하여 다시 시스템으로 복귀시켜 준다. */
}

메모리 지역 B는 윈도우 9x에서는 0x00400000부터 0x7FFFFFFF로 처음 2GB에서 4MB를 제외한 부분이며, 윈도우 NT에서는 0x00010000부터 0x7FFEFFFF로서 역시 처음 2GB에서 64KB를 제외한 부분이다. 이 지역은 윈도우 32의 각 프로세스들이 자신들만의 특별한 데이터를 담고 있는 위치이며, 다른 프로세스와 공유되지 않는 메모리 공간이다. 이 메모리 공간 때문에 윈도우 32 애플리케이션에 문제가 있을 때 Ctrl+Alt+Del을 사용하여 해당 프로세스만 메모리에서 삭제할 수 있다.
윈도우 95에 존재하는 지역 C는 1GB의 메모리 공간으로 모든 프로세스가 공유하는 프로세스들이 주로 적재되어 있다. 예를 들어 Kernel32.dll, User32.dll, GDI32.dll 등이 있으며 이와 비슷한 역할을 하는 윈도우 NT의 모듈들은 메모리 공간 B에 적재된다. 따라서 윈도우 9x에서는 이들 프로세스들이 같은 메모리에 올려져 다른 프로세스에 의해 공유되는데 비해 윈도우 NT에서는 이들 프로세스가 각 프로세스의 비공유 메모리 지역에 따로 적재되어 사용된다.
마지막으로 메모리 공간 D는 윈도우 9x에서는 1GB, 윈도우 NT에서는 2GB이다. 윈도우 9x와 윈도우 NT가 모두 자신들의 OS와  디바이스 드라이브 등 시스템 레벨의 모듈을 적재하고 있다. 윈도우 NT에 존재하는 메모리의 부분 중 0x7FFF0000부터 0x7FFFFFFF는 윈도우 NT의 처음 64KB 지역과 비교적 같은 목적으로 정의되었다. 즉 OS가 존재하는 메모리로의 접근을 방지하기 위한 일종의 방화벽과 같다. 그러면 지금까지 나열한 메모리들을 상세히 알아보도록 하자.
윈도우 32 메모리는 region이라는 구역으로 구성되어 있다. 메모리 전체의 크기를 우리 나라 지리에 비교한다면 region은 도 한 개 정도를 의미한다. 윈도우의 메모리는 region들의 집합으로 이루어져 있으며, PC를 포함한 대개의 플랫폼은 64K로 지정되어 있다. 하나의 프로세스가 생성되면 그림 1의 파란색 부분이 주로 프로세스가 임의로 사용할 수 있는 지역이 되고, 새로운 메모리의 할당은 반드시 64K의 배수가 되는 지점에서 시작하도록 되어 있다. 이는 메모리 관리의 용이성 때문이다. 이렇게 윈도우 메모리를 할당하는 것을 Allocation Granularity라고 한다. 그리고 프로그래머가 메모리를 사용하기 전에 메모리를 예약(reserve)하게 되는데, 예약되는 메모리의 크기를 page라 하며 이는 메모리 할당이 가능한 최소 단위이다. page의 크기는 시스템마다 조금씩 다르지만 대개 4K나 8K로 되어 있고, 우리가 흔히 쓰는 PC에 사용되는 인텔 CPU의 경우는 4K, 즉 4096 바이트를 적용한다. 따라서 어떤 프로그램이 10KB의 메모리가 필요하다고 시스템에게 요구하면 시스템은 4K의 배수를 계산하여 12K를 내주게 된다. 가상 메모리를 예약할 때는 VirtualAlloc()을 사용하며 사용 후 다시 시스템으로 반환할 때는 VirtualFree() API를 사용한다.

가상 메모리의 확장
가상 메모리 공간을 떠나 이제는 실제 하드웨어에 설치된 메모리에 대해 알아보자. 만약 컴퓨터에 32MB의 메모리가 설치되어 있다면 최고 32MB까지 프로그램을 메모리에 실을 수 있다는 뜻이다. 윈도우 16 시대에는 ‘RAM SIZE = 적재 가능한 프로그램 크기’라는 공식이 어느 정도 맞아 떨어졌다. 물론 다른 메모리 관리 프로그램을 사용하여 프로그램에서 사용 가능한 메모리의 양을 확장하곤 했으나 이 공식에서 크게 벗어나지 않았다.
이후 swap space를 지원하는 CPU(intel386)의 등장과 함께 swap space의 활용은 큰 효과를 보게 되었다. swap space의 사용으로 사용자는 실제 자신의 컴퓨터에 설치된 메모리의 크기와 상관없이 프로그램을 실행할 수 있게 되었기 때문이다.
 예를 들어 설치된 메모리가 32MB라도 충분한 하드 드라이브 공간이 있으면 하드 드라이브의 일부를 실제 설치된 메모리처럼 보이게 한다. 이런 기술을 우리는 가상 메모리라고 하는데, 여기서 말하는 가상 메모리는 위에서 설명한 프로세스의 가상 메모리와는 별개이다. 즉 프로세스의 가상 메모리는 사용 가능한(또는 Addressable) 순수 가상 메모리를 의미하지만, swap space의 개념으로 사용되는 가상 메모리는 보통 Paging File Size라고 표현하며 하드 드라이브의 용량을 빌어 메모리로 사용한다. 그래서 애플리케이션 프로그램의 입장에서는 실제 그만한 메모리가 존재하는 것처럼 보인다. 윈도우 9x나 윈도우 NT에서 쉽게 이 가상 메모리를 확장할 수 있다. 윈도우 9x와 윈도우 NT의 사용자 인터페이스는 약간씩 다르지만 가상 메모리의 크기를 조절하는 절차는 비슷하다. 즉 start -> settings -> Control Panel -> System -> Performance -> Virtual Memory를 선택하여 원하는 대로 바꿔 줄 수 있는데 가상 메모리의 세팅은 그림 2와 같다. 
그림 2의 Paging File Size(MB)는 사용 가능한 가상 메모리 공간의 기본 크기와 최대치를 나타낸다. Initial Size와 Maximum Size는 편집이 가능한 텍스트 입력기로 원하는 크기를 입력한 후 Set 버튼을 눌러서 크기를 조절한다. 여기서 한가지 주의할 점은 Initial Size를 너무 크게 조정하면 실제 메모리가 처리할 수 있는 용량보다 많은 일을 처리할 수 있는 것이 아니라, 설치된 메모리의 용량 부족이 잦은 Paging File의 swap을 초래하여 결국 일을 제대로 처리하지 못하는 단계에 이를 수 있다는 것이다. 이런 상태를 윈도우 32에서는 thrashing이라고 부른다. 다음에 설명할 메모리 할당 알고리즘을 보면 쉽게 이해할 수 있다. 한가지 참고할 것은 윈도우 9x와 달리 윈도우 NT에서는 다중 페이징 파일을 사용할 수 있는 능력이 있다는 것이다. 그래서 다른 하드 드라이브에 있는 페이징 파일들을 읽고 쓰기가 용이하여 능률을 향상시킬 수 있다.

메모리 할당 알고리즘
그러면 윈도우 32에서 메모리 공간은 어떤 알고리즘을 통해 할당되는지 그림 3을 보면서 알아보자. 앞에서 우리는 모든 프로세스가 4GB의 가상 메모리 공간을 가진다고 하였다. 그런데 페이징 파일을 포함하여 실제 설치된 메모리 용량은 프로세스 메모리 공간보다 훨씬 작다. 32MB 메모리가 설치되어 있다면 페이징 파일은 기껏해야  50MB이며 최대 82MB까지 사용할 수 있다. 이들은 프로세스의 메모리 공간인 4GB에 훨씬 못 미친다. 그렇다면 하나의 스레드가 4GB 중의 메모리 영역 일부에 접근하고자 할 때 윈도우 32 OS는 어떻게 이 요청을 처리할까? 프로세스 메모리 공간과 RAM의 매핑이 이에 대한 답이다. 그림 3을 통해 자세히 살펴보자.
메모리 할당의 각 절차를 0부터 10까지 번호를 붙였는데, 그림이 복잡해 보이지만 간단한 알고리즘이다. 하나의 스레드에서 자신이 속한 프로세스 내의 어떤 데이터로 접근을 시도할 때(0), 윈도우 OS는 가장 먼저 해당 데이터가 RAM에 있는지 확인한다(1). 이 때 RAM에서 필요로 하는 데이터를 찾으면 프로세스 메모리 값은 RAM으로 연결되어(9) 스레드는 데이터로의 접근이 허용된다. 만약 필요로 하는 데이터가 RAM에 없는 경우 하드 드라이브의 페이징 파일을 탐색하게 되며(2), 여기서도 필요로 하는 데이터를 찾지 못할 경우 RAM과 하드 드라이브 양쪽 모두 원하는 데이터를 보유하고 있지 않으므로 데이터의 접근은 불가능하게 된다(3). 그런데 한번 사용한 데이터나 .EXE 실행파일은 RAM에 없으면 하드 드라이브에 존재하기 마련이다. 따라서 페이징 파일에서 데이터를 찾은 경우 OS는 사용되지 않은 RAM이 있는지를 확인한다(4). 사용할 수 있는 free RAM을 찾은 경우에는 데이터를 페이징 파일에서 RAM으로 올려주며(6), free RAM을 찾지 못한 경우는 메모리 캐시 알고리즘에 의해 필요한 RAM 페이지를 선택하게 된다(5). 그리고 이 RAM 페이지가 아직 페이징 파일로 update 되지 않았으면 update를 실행하고(8), 이미 update 된 상태라면 찾은 데이터를 본 RAM에 실어(6) 프로세스의 메모리 공간을 RAM과 매핑한 다음(9) 스레드는 데이터로의 접근이 허용된다(10).

리스트 1 : SYSTEM_INFO 스트럭처
/*  SYSTEM_INFO : 예제 메뉴, system information을 실행해 보라 */
/* *****************************************************/
typedef struct _SYSTEM_INFO {
 union {
       DWORD  dwOemId;
   /* 더 이상 사용하지 않음. wProcessorArchitecture을 참조할 것 */
        struct {
         WORD wProcessorArchitecture;
  /* 프로세스 아키텍처를 나타내며, 인텔 CPU는 그 값이 0임 */
            WORD wReserved;
        };
    };
    DWORD  dwPageSize; /* 메모리 최소 단위, 페이징 사이즈 */
    LPVOID lpMinimumApplicationAddress;/*메모리 베이스 값 */
    LPVOID lpMaximumApplicationAddress;/*접근 가능한 메모리 최대값*/
    DWORD  dwActiveProcessorMask;
    DWORD  dwNumberOfProcessors; /* 프로세스 설치 개수 */
    DWORD  dwProcessorType; /* 프로세스 타입: 486, 586, 등 */
    DWORD  dwAllocationGranularity;
 /* 메모리 할당 구역 크기: 윈도우의 경우는 64K 임 */
    WORD  wProcessorLevel; /* 5: 펜티엄, 4: 486,  3: 386 */
    WORD  wProcessorRevision;
} SYSTEM_INFO;

리스트 2 : MEMORYSTATUS 스트럭처
/*  MEMORYSTATUS:예제 프로그램 virtual Memory Status를 실행해 보라 */
/* ********************************************************/
typedef struct _MEMORYSTATUS {
    DWORD dwLength;        /* sizeof(MEMORYSTATUS) */
    DWORD dwMemoryLoad;    /* 메모리 사용율 */
    DWORD dwTotalPhys;     /* 총 설치된 메모리 용량 */
    DWORD dwAvailPhys;     /* 현재 할당 가능한 메모리 양*/
    DWORD dwTotalPageFile; /* 총 페이지 파일 크기 */
    DWORD dwAvailPageFile; /* 사용 가능한 페이지 파일 크기 */
    DWORD dwTotalVirtual;  /* 총 프로세스 가상 메모리 크기  */
    DWORD dwAvailVirtual;  /* 사용 가능한 가상 메모리 크기 */
} MEMORYSTATUS, *LPMEMORYSTATUS;

리스트 3 : MEMORY_BASIC_INFORMATION 스트럭처
/*MEMORY_BASIC_INFORMATION:virtual memory scanner를 실행해보라*/
/**********************************************************/
typedef struct _MEMORY_BASIC_INFORMATION {
    PVOID BaseAddress; /* 베이스 메모리 위치 */
    PVOID AllocationBase; /* 할당 베이스 메모리 위치 */
    DWORD AllocationProtect;  /* 초기 메모리 접근 방법 */
    DWORD RegionSize; /* 메모리 region 크기 */
    DWORD State;   /* committed, reserved, free */
    DWORD Protect; /* 현재 메모리 접근 방법 */
    DWORD Type; /* image, mapped, or private */
} MEMORY_BASIC_INFORMATION;

예제 프로그램의 실행
지금까지 설명한 메모리 아키텍처의 이해를 위해 몇 개의 예제 프로그램을 만들어 보자. 이 프로그램은 다음과 같은 윈도우 32 API를 주로 사용하고 있다.

·Void GetSystemInfo(LPSYSTEM_INFO lpSystemInfo) : 현 시스템의 페이지 파일의 값과 AllocationGranularity를 알아보기 위해 사용한다. 예제 프로그램의 메뉴 중 System Information을 참조한다.
·HMODULE GetModuleHandle(LPCTSTR lpModuleName) : 현재 메모리에 적재된 모듈의 베이스 메모리의 위치를 나타낼 때 사용하며, 윈도우 NT에서는 기본값이 0x10000이고 윈도우 9x는 기본값이 0x400000이다.
·VOID GlobalMemoryStatus(LPMEMORYSTATUS lpBuffer) : 현 시스템의 가상 메모리의 상태를 나타내기 위해 사용했으며, 메뉴의 Virtual Memory Status를 참조한다.
·DWORD VirtualQuery(LPCVOID lpAddress, PMEMORY_BASIC_ NFORMATION lpBuffer, WORD dwLength) : 시스템의 가상 메모리의 할당 상태를 나타내기 위해 사용하였으며 예제 프로그램 메뉴의 Virtual Memory Scanner를 참조하기 바란다.

프로그램에서 사용되는 스트럭처는 리스트 1, 2, 3에 정리되어 있다.

리스트 4의 소스 코드를 통해 메모리에 대한 전반적인 내용을 정리해 보자. 그리고 마지막으로 혼자 해볼 수 있는 간단한 과제를 리스트의 주석을 통해 소개하겠다. 이 예제를 실행하면 2초마다 윈도우 화면이 갱신된다.


리스트 4 : 가상 메모리 예제 프로그램의 전체 코드
#include <windows.h>
#include <tchar.h>
#include “resource.h”

#define TIMER_ONE 1
 /* 혼자 해보기의 virtual memory status에 사용할 변수 */

LRESULT CALLBACK myWndProc (HWND, UINT, WPARAM, LPARAM);
 /* 주 메시지 처리 엔진 */
BOOL    CALLBACK aboutProc (HWND, UINT, WPARAM, LPARAM);
LPSTR   convertToState(DWORD);
 /* MEMORY_BASIC_INFORMATION의 정수값을 스트링으로 변환 */
LPSTR   convertToAllocationProtect(DWORD);
 /* MEMORY_BASIC_INFORMATION의 정수값을 스트링으로 변환 */
LPSTR   convertToType(DWORD dwType);
 /* MEMORY_BASIC_INFORMATION의 정수값을 스트링으로 변환 */

int WINAPI WinMain(HINSTANCE hInstance,
 HINSTANCE hPrevInstance, PSTR szCmdLine, int iCmdShow)
{ /* 본 프로그램의 메인 엔트리 */
  static TCHAR szAppName[] = TEXT (“Win32 Memory
  Architecture”); /* 윈도우 등록에 사용할 이름 */
  HWND hwnd; /* 윈도우 메인의 핸들 */
  MSG msg; /* 메시지 펌프에 사용할 메시지 스트럭처 */
  WNDCLASS wndclass; /* 윈도우 클래스 스트럭처 */

   wndclass.style= CS_HREDRAW | CS_VREDRAW;
  /* 윈도우의 변화에 맞추어 다시 페인팅 함  */
  wndclass.lpfnWndProc = myWndProc;
  /* 윈도우 메시지 처리 엔진 이름 */
  wndclass.cbClsExtra = 0; /* 여유 바이트 할당 크기 */
  wndclass.cbWndExtra = 0; /* 여유 바이트 할당 크기 */
  wndclass.hInstance = hInstance; /* 윈도우 메인 인스턴스 */
  wndclass.hIcon = LoadIcon(NULL, MAKEINTRESOURCE
  (IDI_WINLOGO)); /* 본 프로그램의 아이콘  */
  wndclass.hCursor = LoadCursor(NULL, IDC_ARROW);
  /* 본 프로그램의 커서 */
  wndclass.hbrBackground = (HBRUSH)
  GetStockObject(WHITE_BRUSH); /* 백 그라운드 색 */
  wndclass.lpszMenuName = MAKEINTRESOURCE(IDR_MENU1);
  /* 사용할 메뉴 */
  wndclass.lpszClassName = szAppName;
  /* 등록될 윈도우 클래스 이름 */
 
  if(!RegisterClass (&wndclass))
  {        /* 윈도우 클래스의 등록이 실패하면 본 프로그램을 중지한다  */
  MessageBox(NULL, TEXT(“Failed Registering to Window”),
   szAppName, MB_ICONERROR);
  return -1;
  }
 /* 메인 윈도우를 생성한다. x, y의 시작점은 0, 0 이며, 가로 600 픽셀,
    세로 300 픽셀의 크기로 생성한다. */
  hwnd = CreateWindow(szAppName, szAppName,
  WS_OVERLAPPEDWINDOW, 0, 0, 600, 300, NULL, NULL,
   hInstance, NULL);
  ShowWindow(hwnd, iCmdShow);
  UpdateWindow(hwnd);

/* 메시지 펌프: 윈도우 프로그램이 실행되면 윈도우 OS는 각 실행 프로그램에 사용될 메시지
큐를 생성하여 해당 프로그램에서 입력되는 메시지들을 저장하기 시작한다. 여기 GetMessage()
는 시스템의 큐에서 메시지를 가져와 메시지 처리 엔진으로 전달하는 역할을 한다. */
  while(GetMessage(&msg, NULL, 0, 0))
 {
  TranslateMessage(&msg);
  DispatchMessage(&msg);
  }

  return msg.wParam;
}

LRESULT CALLBACK myWndProc (HWND hwnd, UINT message,
 WPARAM wparam, LPARAM lparam)
{ /* txt[80]에 출력하고자 하는 메시지를 넣어 윈도우 화면으로 출력한다. */
     static TCHAR txt[80];

 /* 윈도우 페인트에 필요한 변수를 정의한다. */
 HDC hdc;  
 PAINTSTRUCT ps;
 RECT rect;

 /* 본 프로그램에서 메모리 구조를 알아보기 위해  주로 사용할 스트럭처들 */
 OSVERSIONINFO osinfo;  /* OS 버젼 정보 */
 SYSTEM_INFO si; /* 시스템 정보 */
 MEMORYSTATUS ms; /* 가상 메모리 상태 */
 STARTUPINFO sti;
 PROCESS_INFORMATION pi; /* 프로세스에 관한 정보 */
 MEMORY_BASIC_INFORMATION mbi; /* 메모리에 관한 전반적인 정보  */
 PVOID pvAddress = 0x00000000; /* 메모리 스캔 시작점 */

 static HINSTANCE hInstance;
 DWORD dwLength;

 HANDLE hFile; /* 메모리 스캔을 실행 후 결과를 저장할 파일 핸들 */
 DWORD dwNumToWrite, dwNumWritten;

 switch(message)
 {
 case WM_CREATE:
  hInstance = ((LPCREATESTRUCT)lparam)->hInstance;
  /* parent의 Instance 핸들을 저장 */
  /* 혼자 해보기에 사용될 타이머를 세팅한다. 이렇게 하면 2초마다
    WM_TIMER 메시지를 윈도우로 보낸다 */
  SetTimer(hwnd, TIMER_ONE, 2000, NULL);
  return 0;

 /* WM_PAINT를 받을 때마다 화면을 다시 페인팅 해준다. */
 case WM_PAINT:
  hdc = BeginPaint(hwnd, &ps);
  GetClientRect(hwnd, &rect);
  DrawText(hdc, TEXT(txt), -1, &rect, DT_LEFT
   | DT_VCENTER);
  EndPaint(hwnd, &ps);
  ZeroMemory (txt, _tcslen(txt));
  return 0;
 
 /* 사용자가 윈도우를 닫고자 할 때 실행해 준다 */
 case WM_DESTROY:
  KillTimer(hwnd,TIMER_ONE);
  PostQuitMessage(0);
  return 0;

 case WM_TIMER:
 /* 이 부분은 혼자 해보기의 과제인데, 의도적으로 비워두었다. case ID_VMSTAT의 copy start라고 명시된 부분부터 copy end 부분까지 여기에 복사하면, SetTimer() 동작에 의해 매 2초 동안 가상 메모리의 상태가 최신 정보로 바뀌게 된다. */
  /* ******************* */
  /* Copy your code here */
  /* ******************* */
  return 0;

 case WM_KEYDOWN:
  switch (wparam)
  { /* 본 프로그램 사용을 중지하고 싶을 때 Esc 키를 누르면 메뉴의
     Quit Program을 선택할 때와 같은 일을 한다 */
  case VK_ESCAPE:
   SendMessage(hwnd, WM_DESTROY, wparam, lparam);
  }
  return 0;

 case WM_COMMAND:
 /* 처음 두 바이트의 wparam 변수에 메뉴에서 입력되는 명령이 들어오게 된다 */
  switch (LOWORD(wparam))
  {
  case ID_SYSINFO:
  /* 시스템 정보를 담고 있다. 메뉴에서 system information을
     선택했을 때 실행된다. */
   GetSystemInfo(&si);
   wsprintf(txt,”\
Base Memory Address:%#X \n\
lpMinimumApplicationAddress: %#X\n\
lpMaximumApplicationAddress: %#X\n\n\
dwPageSize:%uKB \n\
dwAllocationGranularity:%luKB\n\n\
dwOemId:%2x \n\
wProcessorArchitecture:%x \n\
dwActiveProcessorMask: %x \n\
dwNumberOfProcessors:%x \n\
dwProcessorType:%u \n\
wProcessorLevel:%x \n \
wProcessorRevision:%u”,
  /* 실행 파일의 베이스 메모리는 GetModuleHandle을 통해 알 수 있다 */
  GetModuleHandle(NULL),
  si.lpMinimumApplicationAddress,
  si.lpMaximumApplicationAddress,
  si.dwPageSize/1024,
  si.dwAllocationGranularity/1024,
  si.dwOemId,
  si.wProcessorArchitecture,
  si.dwActiveProcessorMask,
  si.dwNumberOfProcessors,
  si.dwProcessorType,  
  si.wProcessorLevel,
  si.wProcessorRevision
  );
  /* wsprintf()를 통해 txt[]의 내용이 갱신되면 그 갱신된 내용을
   윈도우에 다시 페인팅 해야 하는데 이것은 WM_PAINT 메시지를 윈도우
     시스템에 보내서 가능하게 된다. 여러 가지 방법이 있으나,
   여기서는 InvalidateRect()을 사용하여 WM_PAINT를 보낸다.*/
   InvalidateRect(hwnd, NULL, TRUE);
   break;

  case ID_MYWININFO:  /* 여기서는 윈도우 OS의 정보를 나타낸다.
   my window information을 실행해 보라 */
   osinfo.dwOSVersionInfoSize = sizeof(OSVERSIONINFO);
   GetVersionEx(&osinfo);
   wsprintf(txt,
   “OS Version:%u.%u %s Build Number:%u Platform
    ID:%u\n”, osinfo.dwMajorVersion,
     osinfo.dwMinorVersion, osinfo.szCSDVersion,
   osinfo.dwBuildNumber, osinfo.dwPlatformId);
   InvalidateRect(hwnd, NULL, TRUE);
   break;
  case IDD_ABOUT:
   if(DialogBox(hInstance,MAKEINTRESOURCE
    (IDD_DIALOG1),hwnd, aboutProc))
   {
    InvalidateRect(hwnd, NULL, TRUE);
   }
   break;

  case ID_VMSTAT:
  /* 여기의 코드를 WM_TIMER에 복사하므로 이 프로그램은 매 2초마다
     가상 메모리의 상태를 갱신하여 윈도우에 페인팅해 준다 */
   /***************/
   /* Copy Starts */
   /***************/
     ms.dwLength=sizeof(MEMORYSTATUS);
   GlobalMemoryStatus(&ms); //virtual memory status
   wsprintf(txt,”\
dwMemoryLoad:%d\n\n\
dwTotalPhys:%uKB\n\
dwAvailPhys:%uKB\n\n\
dwTotalPageFile:%uKB\n\
dwAvailPageFile:%uKB\n\n\
dwTotalVirtual:%uKB\n\
dwAvailVirtual:%uKB\n\n”,
 ms.dwMemoryLoad, /* 메모리 사용률  */
 ms.dwTotalPhys/1024, /* 총 설치된 메모리 용량 */
 ms.dwAvailPhys/1024, /* 현재 사용 가능한 메모리 크기 */
 ms.dwTotalPageFile/1024, /* 총 페이징 파일 크기 */
 ms.dwAvailPageFile/1024, /* 현재 사용 가능한 페이징 파일 */
 ms.dwTotalVirtual/1024, /* 프로세스가 소유한 총 가상 메모리 */
 ms.dwAvailVirtual/1024); /* 사용 가능한 가상 메모리  */
 InvalidateRect(hwnd, NULL, TRUE);
 /***************/
 /* Copy Ends   */
 /***************/

 break;

 case ID_Quit:
  PostQuitMessage(0);
  break;

 case ID_VMEXER:
  MessageBox(hwnd,
   “Try this: Copy code from ID_VMSTAT and paste it
    under WM_TIMER”, “Virtal Memory Live Status”,
    MB_OK);    
   break;

 case ID_OPENMAPFILE:
  ZeroMemory (&sti, sizeof(sti));
     sti.cb = sizeof (sti);
     CreateProcess (NULL, “Write.exe VMScanmap.txt”, NULL,  
   NULL, FALSE, 0, NULL, NULL, &sti, &pi);
  break;
  case ID_VMSCAN:
   hFile=CreateFile(“VMScanmap.txt”, GENERIC_READ |
   GENERIC_WRITE, FILE_SHARE_READ | FILE_SHARE_WRITE,
   NULL, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL);
   if(!hFile)
   {
    MessageBox(hwnd, “Failed to Create VMMap File”,”
       Error”, MB_OK);
    break;
   }
   dwLength = sizeof(MEMORY_BASIC_INFORMATION);

   for(;;)
   {
   /*가상 메모리 전체를  구역마다 스캔하여 mbi 스트럭처에 담아준다.
     더 이상 스캔이 불가능하면 VirtualQuery는 중지되는데,
     윈도우 NT는 0x7FFF000까지이며,
     윈도우 95는 0x80000000까지이다 */
    if(!VirtualQuery(pvAddress, &mbi, dwLength))
    {
   MessageBox (hwnd, “Memory Scan Stopped. Please
      Open the map file to browse”,
    ”VMScan Message”,MB_OK);
     return 0;
    }
    wsprintf(txt, “\ Base Address:%x\n Allocation
    Base:%x\n Allocation Protect:%s\n\Region
    Size: %x \n State: %s\n Protect: %s\n Type
    : %s\n\n “,

    (PBYTE)mbi.BaseAddress, /* 베이스 메모리 위치 */
    (PBYTE)mbi.AllocationBase, /* 메모리 할당 베이스 */
    /* 초기 메모리 접근 방법 */
    convertToAllocationProtect
      (mbi.AllocationProtect),
    mbi.RegionSize, /* 메모리 구역 크기 */
    convertToState(mbi.State), /* 메모리 상태 표시 */
    /* 현 메모리 접근 방법 */
    convertToAllocationProtect (mbi.Protect),
    convertToType(mbi.Type) ); /* 메모리 타입 */

    dwNumToWrite = _tcslen(txt);
    if(!WriteFile(hFile, txt,dwNumToWrite,
     &dwNumWritten, NULL))
    {
     wsprintf(txt,”%x”, GetLastError());
     MessageBox(hwnd, txt, “WriteFile Error”,
      MB_OK);
     break;
    }
    pvAddress = (PVOID)((PBYTE)mbi.BaseAddress +
     mbi.RegionSize);
   }
  }
  CloseHandle (hFile);
  return 0;
 }
 return DefWindowProc (hwnd, message, wparam, lparam);
}

LPSTR convertToAllocationProtect(DWORD dwAllocationState)
{
 switch(dwAllocationState)
 {
  case PAGE_READONLY:
  return (“PAGE_READONLY”);

 case PAGE_READWRITE:
  return (“PAGE_READWRITE”);

 case PAGE_WRITECOPY:
  return (“PAGE_WRITECOPY”);

 case PAGE_EXECUTE:
  return (“PAGE_EXECUTE”);

 case PAGE_EXECUTE_READ:
  return (“PAGE_EXECUTE_READ”);
  
 case PAGE_EXECUTE_WRITECOPY:
  return (“PAGE_EXECUTE_WRITECOPY”);

 case PAGE_GUARD:
  return (“PAGE_GUARD”);
 
 case PAGE_NOACCESS:
  return (“PAGE_NOACCESS”);
 
 case PAGE_NOCACHE:
  return (“PAGE_NOCACHE”);
 }
 return (“UNKNOWN PROTECTION”);
}

LPSTR convertToState(DWORD dwState)
{
 switch(dwState)
 {
  case MEM_COMMIT:
   return “MEM_COMMIT”;
   
  case MEM_FREE:
   return “MEM_FREE”;
       
  case MEM_RESERVE:
   return “MEM_RESERVE”;
 }
 return “Unknown MEM_STATE”;
}

LPSTR convertToType(DWORD dwType)
{
 switch(dwType)
 {
  case MEM_IMAGE:
   return “MEM_IMAGE”;
   
  case MEM_MAPPED:
   return “MEM_MAPPED”;
       
  case MEM_PRIVATE:
   return “MEM_PRIVATE”;
 }
 return “Unknown MEM_TYPE”;
}

BOOL CALLBACK aboutProc (HWND hdlg, UINT message, WPARAM wparam,
 LPARAM lparam)
{
 switch (message)
 {
 case WM_INITDIALOG:
  return TRUE;

 case WM_COMMAND:
  switch (LOWORD(wparam))
  {
  case IDOK:
   EndDialog(hdlg, 0);
   return TRUE;
  }
  break;
 }

 return FALSE;

[출처] 1.VM|작성자 다다닥