티스토리 뷰

C++/General

32bit Windows 메모리 관리

엘키 2009. 3. 27. 12:18

32비트 윈도 운영체제에서 하나의 프로그램은 4GB의 메모리 영역을 가질 수 있다. 그러나 4GB를 모두 어플리케이션 마음대로 사용할 수 있는 것은 아니고, 상위 2GB만 프로그램이 사용할 수 있도록 하였고, 하위 2GB Windows가 실행된 프로그램을 관리하기 위한 코드가 적재된다. 위의 스택, 코드 등의 영역은 하위 2GB에 포함된다. 

가상
메모리상태 (Virtual Memory Status)

1.        램에 맵핑된 상태, 하드 디스크에 맵핑된 상태
2.        사용이 예약되어 읽거나 쓸 수 있는 상태
3.        초기 상태
 
등급
내용
Committed
물리적 메모리에 맵핑된 상태의 메모리 영역이다. Commit 상태의 메모리 영역은 읽거나 쓸 수 있다. VirtualAlloc()을 통하여 Commit 상태로 변경할 수 있으며, Commit 상태를 해제하여 Reserved 또는 Commit 상태로 바꿀 수 있다. WIN16 API LocalAlloc()을 이용하여 Commit 상태로 변경할 수도 있다.
Reserved
특정 크기가 메모리 영역의 사용을 예약해 놓은 상태이다. , 현재는 사용하지 않지만 앞으로 필요하게 될 부분이므로 다른 함수에 의해서 메모리가 할당될 때, 이 부분은 사용하지 말라는 뜻이다. 이 상태에서는 읽거나 쓸 수 없다. 왜냐하면, 물리적 메모리와 맵핑되지 않은 상태이기 때문이다. 사용하기 위해서는 commit 상태로 되어야 한다. 이때 사용되는 함수가 VirtualAlloc() 이다. Reserved 상태를 해제하려면, VirtualFree() 를 사용한다. Reserved 영역을 해제하면 Free 상태가 되고, 프로그램의 다른 부분에 의해 자유롭게 사용이 가능하게 된다.
Free
최초 가상 메모리가 생성될 때, 모든 가상 메모리 영역은 Free 상태에 놓이게 되는데, 읽거나 쓸 수 없는 빈 영역이라고 생각하면 된다. 이 영역을 Reserved 또는 Commit 상태로 변경할 수 있다. 물론 물리적 메모리와는 맵핑 되지 않은 상태이다.
 

가상 메모리 접근 등급(Memory Access Protection)

접근불가 PAGE_NOACCESS
읽기전용 PAGE_READONLY
읽기/쓰기 PAGE_READWRITE
 
등급
내용
PAGE_NOACCESS
접근이 금지된 상태
PAGE_READONLY
읽기 전용 상태. 중요한 데이터의 경우 덮어쓰기 등의 경우로 데이터를 잃어버리는 경우를 막기 위하여 사용된다.
PAGE_READWRITE
읽거나 쓸 수 있는 상태. 가장 일반적인 형태로 Commit된 메모리 페이지에 대하여 모든 권한을 부여한다.
 
Windows 운영체제는 메모리에 대하여 접근 제한 속성을 설정할 수 있도록 API를 제공한다. 이러한 내용들은 파일에 대한 접근 등급과도 유사하며, 커널 오브젝트의 특징인 보안 속성과도 유사하다. 속성을 설정할 때는 메모리를 할당할 때 사용하는 VirtualAlloc()을 사용하고 생성된 이후에 VirtualQuery()를 통해 할당된 메모리 영역의 속성을 확인할 수 있다.
 

32비트 윈도우 가상 메모리 구조(4GB)

Windows 운영체제가 제공하는 4GB의 메모리 영역을 차지하는 스택, , 코드 영역은 매우 작은 공간에 불과하며, 이외에도 많은 영역들이 존재한다.
 
0x00000000
0x00000FFF
NULL 값 할당 영역(4KB)
Private
2GB
0x00001000
0x003FFFFF
도스 및 16비트 어플리케이션 영역(4KB)
0x00400000
0x7FFFFFFF
프로세스 영역(사용자 영역)
User-Mode(1.99GB)
0x80000000
0xC0000000
공유 메모리 영역(메모리 맵 파일 영역)
Shared Memory-Mapped File(1GB)
Shared
2GB
0xFFFFFFFF
커널 영역
Kernel-Mode(1GB)
                                 Windows 98/Me 가상 메모리 구조
 
 
0x00000000
0x0000FFFF
0x00001000
NULL 값 할당 영역(4KB)
Private
2GB
0xBFFEFFFF
0xBFFF0000
0xBFFFFFFF
0xC0000000
프로세스 영역(사용자 영역)
User-Mode(1.99GB)
 
Off-Limit 영역(64KB)
0xFFFFFFFF
커널 영역
Kernel-Mode(2GB)
Shared
2GB
                                     Windows 2000 가상 메모리 구조
 
Private: 어플리케이션만의 독립적인 사용영역.
Shared: 커널에 의해서 공유되어 사용되는 물리적 메모리 영역을 맵핑해 놓은 부분. , 여러 프로세스가 공유해서 쓰는 커널 부분 또는 메모리 맵 파일 부분의 물리적 메모리 영역을 맵핑시켜 놓은 부분이다.
 
1.        NULL 포인터 영역
이 영역의 값은 모두 0이고 변경 불가능이다. , 운영체제에 의해 NULL 값으로 이미 정해진 절대 접근 금지 영역으로서 이 영역을 읽거나 쓰려고 시도할 경우 Access Violation 에러를 발생시키고, 프로세스는 종료된다. 시스템의 안정성 확보를 위해서 Windows가 할당해 놓은 구간이다.
 
2.        16비트 영역(MS-DOS Windows 3.x 어플리케이션 영역)
MS-DOS
Windows 3.1 운영체제는 16비트 기반이다. Window95 이후의 버전은 32비트로 업그레이드 되었지만, 기존과의 호환성을 위하여 16비트 또는 도스용 어플리케이션 및 디바이스를 사용할 수 있도록 일정 영역을 할당하고 있는데, NT 계열에서는 사용하지 않고 클라이언트 버전인 Windows 95/98/Me에서 사용된다. 32비트 기반의 어플리케이션이 이 영역을 사용하려고 하면 Access Violation 에러를 발생시키고, 프로세스는 운영체제에 의해 종료된다. 32비트, 64비트 Windows 운영체제인 NT4.0 이후의 버전에는 존재하지 않는 영역이다.
 
3.        프로세스 영역(어플리케이션 영역)
응용프로그램이 사용하는 영역. 응용프로그램 코드가 상주한다. 스택, , 코드 영역 등이 여기에 해당한다. 가상메모리, 힙 메모리 할당 등이 모두 프로세스 영역에서 이루어진다.
 
4.        중간 경계 영역
어플리케이션 영역과 공유 메모리 영역 중간의 64KB 크기 메모리 영역을 정하였다. 이 영역은 Private영역과 Shared영역을 구분하기 위한 완충지대를 둔 것이다. NULL 포인터 영역과 같이 접근을 시도할 경우 Access Violation을 일으킨다.
 
5.        공유 메모리 영역(메모리 맵 파일 영역)
Windows98
에서 사용되는 영역으로 운영체제, 즉 시스템이 사용하는 데이터 중 프로세스들과 공유되는 데이터들이 저장되는 영역이다. 또한 메모리 맵 파일(Memory Mapped File)을 사용하여 하드 디스크의 대용량 스트림 데이터를 사용할 때 Windows에 의해 사용되며, 프로세스간의 통신에도 사용된다
.
Windows 2000
이후에는 이 영역이 사라지고, 커널 모드가 최대 2GB를 사용하도록 구분되어 있다. 다시 말하면, Windows2000에서는 더욱 프로세스의 독립성과 안정성을 강조하고 있다고 볼 수 있다.
 
6.        커널 영역
운영체제 시스템 코드가 로드 되는 부분이 커널 모드 영역이다. 예를 들면, 스레드 스케줄링, 메모리 관리, 파일 시스템 코드, 네트워크 관련 코드, 그 밖의 기타 디바이스 드라이버 등이 로드 된다. 이 영역은 시스템 내의 모든 프로세스에게 공유된 메모리이다. 이것은 물리적 메모리에 한 번 로드 되며, 모든 프로세스가 공통으로 사용한다는 뜻인데, 프로세스가 이 영역을 직접 접근하는 것은 막고 있다. API함수를 통하여 얻은 핸들을 통해서만 이 영역을 사용할 수 있다.
 

WIN32 메모리 관련 API 및 오브젝트

 
WIN32 Application (32비트 윈도우 프로그램)
 
 
 
Local, Global Memory API
CRT Memory Function
 
WIN32 SubSystem
 
Heap Memory API
WIN32 Mapped File API
Virtual Memory API
Windows Virtual Memory Manager
Kernel
 
1.        Local, Global Memory API : LocalAlloc(), GlobalAlloc() 등의 win16 API
2.        Heap Memory API : HeapAlloc(), HeapFree() 등의 힙 메모리 관리 API
3.        Virtual Memory API : Windows 메모리 관리의 기본인 가상 메모리 API
4.        Memory Mapped File API : 메모리 맵 파일을 다루기 위한 API
 
메모리 맵 파일 API를 제외하고 모든 API는 가상 메모리 API를 이용하고 있다. 상위의 힙 메모리, 로컬/글로벌 메모리 API는 결국에 가상 메모리 API를 사용하여 구현되었다는 것이다. Windows 운영체제가 가상 메모리 API를 기반으로 만들어졌으니 당연한 일일 것이다.
 
 
구분
내용
Virtual Memory
대용량 객체 또는 구조체 배열 관리에 용이
Heap Memory
작은 용량의 많은 데이터 관리에 용이
Memory Mapped File
파일과 같은 대용량 스트림(stream) 데이터, 시스템 내 프로세스간의 공유 데이터 관리에 용이
 
가상 메모리는 Windows 가 메모리를 관리하는 기본 원칙과 같은 것으로, 메모리 맵 파일을 제외한 모든 메모리 관련 API 또는 오브젝트들은 가상 메모리 관련 함수들을 이용하여 특정 용도에 맞도록 보다 쉽게 구현된 것들이다. 따라서, 결국에는 가상 메모리 API 함수를 통하여 메모리를 사용하거나 관리한다고 이해하면 된다. 각각의 구체적인 특징과 사용방법을 살펴보기로 하자.
 

가상 메모리(Virtual Memory)

가상 메모리 API를 사용하여 메모리를 사용할 때는 시스템에 따른 페이지 크기로만 가능하므로, 불과 몇 바이트의 작은 크기를 사용할 때는 효과적이지 못하다. 따라서, 대용량의 구조체 또는 객체의 배열을 사용하고자 할 때 사용된다. 배열 사용이 가능한 이유는 연속된 메모리 영역을 할당할 수 있기 때문이다.
 

virtualAlloc()

1.        페이지 단위로 일정 크기의 메모리 영역을 할당한다.
2.        Free 상태의 페이지를 Reserved 상태로 바꾼다.
3.        Reserved 상태의 페이지를 Committed 상태로 바꾼다.
4.        Free 상태의 페이지를 Reserved 또는 Committed 상태로 바꾼다.
5.        PAGE_READWRITE, PAGE_READONLY, PAGE_NOACCESS 등의 접근 속성을 가진다.
 
LPVOID VirtualAlloc(
LPVOID lpAddress,                    // Resion to reserve or commit
SIZE_T dwSize,                          // 메모리 영역의 크기
DWORD flAllocationType,           // 할당 유형 선택
DWORD flProtect                        // 접근 속성 선택
);
 

virtualFree()

1.        Commit된 페이지를 Decommit시켜, Reserve된 상태로 바꾼다.
Decommit
로 인하여 맵핑된 물리적 메모리는 해제되며, 해당 물리적 메모리를 다른 프로세스가 사용 가능하도록 한다. Decommit라는 뜻은 페이지와 물리적 메모리와의 맵핑 상태를 해제하여 다른 프로세스 등이 사용할 수 있도록 한다는 뜻.
 
2.        Commit가 아닌 Reserve 또는 Free 상태의 메모리는 참조할 수 없다.
만일 참조하려 한다면, Access Violation 예외를 일으킨다. 메모리를 직접적으로 참조하거나 사용하려면, Commit 상태를 유지해야 한다.
 
BOOL VirtualFree(
LPVOID lpAddress,                    // committed 페이지 영역의 시작 주소
DWORD dwSize,                         // 메모리 영역의 크기
DWORD dwFreeType                 // Free, Reserved 상태로의 변경 중 선택
}
 

기타 함수

1.        VirtualProtect / VirtualProtectEx : 메모리 페이지의 접근 등급을 변경하는 API
2.        VirtualLock / VirtualUnlock : 특정 영역을 잠근다. 특정영역에 대한 사용을 중지 시키는 API
3.        VirtualQuery / VirtualQueryEx : 특정 영역의 메모리 정보를 얻는 API. 메모리 영역의 접근 등급, 상태 등을 구조체 MEMORY_BASIC_INFORMATION 를 통하여 알 수 있다.
 

힙 메모리

Windows는 기본적으로 페이지 단위의 가상 메모리 관리 기법으로 메모리를 관리한다. 이러한 Windows의 가상 메모리 관리 방법과 동일한 수준의 관리를 개발자가 할 수 있도록 가상 메모리 API를 제공한다. 따라서, 페이지 단위의 메모리 할당, commit, 해제 등의 작업을 개발자가 해야 한다.
32바이트를 사용하기 위해 4098바이트 페이지 전체를 물리적 메모리에 맵핑하여 사용해야 하므로, 작은 크기의 데이터에 사용하는 것은 부적절하다.
또한 가상 메모리 API는 각각의 페이지들을 관리하기 위한 페이지 디렉토리(Page Directory) 라는 것이 있고, 이는 64KB 크기의 실제의 물리적 메모리를 소모한다.
작은 크기의 데이터를 사용하는데 있어서 가상 메모리의 이와 같은 단점을 보완한 힙 메모리 API를 제공하고 있다.
 
힙 메모리 할당은 힙 메모리 API를 지칭하기도 하고,
malloc()계열의 C runtime library,
WIN16 GlobalAlloc(), LocalAlloc(),
OLE2 IMalloc 인터페이스,
힙 메모리 API를 총괄해서 부르기도 한다.
다음은 힙 메모리 오브젝트를 이용한 방법.
 
힙 메모리는 커널 오브젝트인 힙 오브젝트를 이용한다. 힙 오브젝트는 크기가 작은 메모리 영역을 효율적으로 사용할 수 있도록 한다. 그리고 가상 메모리 API에서는 페이지 단위로의 메모리 영역을 할당하고 해제하는 등의 작업도 맡아서 한다.
 
1.        장점
A.       페이지 단위의 메모리 할당(Reserve, Commit) 작업이 불필요하다.
B.       힙 메모리 영역의 크기가 자동으로 증가한다.
 
2.        단점
A.       속도가 느리다
힙 메모리 영역에서의 메모리 할당과 해제 작업은 다중 스레드 환경에서 동기화로 이루어진다. 어느 한 시점에서 힙 메모리를 할당하는 작업은 하나의 스레드에게만 허용되는데, 동시에 이 작업을 요청한 다른 스레드는 대기하므로 속도가 느려진다.
B.       메모리 영역을 제어할 수 없다.
C.       다른 메모리 영역으로의 침범이 우려된다.
 
프로세스가 생성될 때, 운영체제는 프로세스에게 기본적으로 1MB의 힙 메모리 영역을 할당한다. new, malloc을 이용한 메모리 사용은 이 영역에서 이루어진다. GetProcessHeap() 으로 이미 생성된 기본 힙 메모리 오브젝트의 핸들을 리턴할 수 있다.
 

새로운 힙 메모리 오브젝트 생성

 
HANDLE HeapCreate(
DWORD flOptions,                      // 힙 할당 옵션
SIZE_T dwInitialSize,                  // 최초 힙 메모리 영역 크기
SIZE_T dwMaximumSize            // 최대 힙 메모리 영역 크기
);
 

프로세스 기본 힙 메모리 오브젝트 참조

 
HANDLE GetProcessHeap(VOID);
// HeapCreate 하지 않고 기본 힙 메모리 오브젝트 사용시
 

현재 생성된 다수의 힙 메모리 오브젝트 참조

 
DWORD GetProcessHeaps(
DWORD NumberOfHeaps,           // ProcessHeaps로 얻을 힙 핸들의 최대개수
PHANDLE ProcessHeaps           // 힙 핸들 데이터 배열 포인터
);
 

힙 메모리 영역 할당 및 사용

 
LPVOID HeapAlloc(
HANDLE hHeap,            // 힙 메모리 오브젝트 핸들
DWORD dwFlags,          // 메모리 할당에 적용될 옵션
SIZE_T dwBytes           // 할당할 메모리 영역의 크기
);
 

힙 메모리 영역 해제 및 오브젝트 소멸

 
BOOL HeapFree(
HANDLE hHeap,            // 힙 메모리 오브젝트 핸들
DWORD dwFlags,          // 속성 설정
LPVOID lpMem              // 할당된 메모리 영역의 포인터
);
 
BOOL HeapDestroy(
  HANDLE hHeap             // 반환할 힙 메모리 오브젝트 핸들
);
 
포인터 배열, 즉 할당된 각각의 메모리 영역을 HeapFree() 로 모두 해제하고, 마지막으로 HeapDestroy() 로 해당 힙 메모리 오브젝트를 더 이상 사용하지 않을 것을 운영체제에게 알리고 있다 (CloseHandle() 을 사용하지 않고 HeapDestroy() 를 사용했다)
할당된 메모리 영역의 사용이 끝났으면 HeapFree() 로 메모리 영역을 해제하고, 전체 힙 메모리 영역에 대한 사용을 끝내려면 힙 메모리 오브젝트 핸들을 반환하면 된다
.

HeapReAlloc()    //
메모리 영역 또는 크기를 재할당
.

HeapSize()          //
생성된 힙 메모리 영역의 크기를 확인
 

메모리 맵 파일(Memory Mapped File)

Memory Mapped File 이란 File Mapping Object에 의해 물리적 메모리에 맵핑된 상태의 파일을 말한다.
메모리 맵 파일을 이용하면 일반적인 파일 또는 파일의 일부분을 자신의 프로세스 메모리 영역에 맵핑하여 사용할 수 있으며, 맵핑된 메모리 파일의 일부 또는 전부를 뷰(View)라고 한다. 이 뷰를 참조하면 입출력 방식을 사용하지 않고도 메모리의 포인터 접근방식으로 파일을 접근할 수 있다.
디스크의 파일을 읽는 방법에는 입출력(Input/Output)을 이용하는 방법이 일반적이다. 일반적인 파일 입출력은 시스템이 수행하는 여러 가지 역할 가운데 비용이 큰 편에 속한다. 파일을 참조해야 하는 경우가 자주 발생하거나 파일의 용량 자체가 거대해서 복잡한 버퍼 등을 설계해야 할 필요가 있을 때 파일 입출력을 사용하는 것은 비효율적이다. 이에 반해 메모리 맵 파일을 생성하는 것은 물리적으로 적은 리소스를 소모하기 때문에 매우 거대한 파일을 적은 대가를 지불하고도 사용할 수 있다.
 
l        메모리 맵 파일의 용도 및 특징
A.       EXE 파일과 DLL 파일을 로드하여 프로세스를 실행할 때 시스템에 의해 사용한다.
B.       대용량의 가상 메모리 영역 할당 또는 대용량 파일의 처리에 적합하다.
C.       파일의 입출력 또는 버퍼 관리 등을 하지 않고 데이터 파일의 참조가 가능하다.
D.       동일한 메모리 맵 파일을 이용하여 프로세스간 데이터를 공유가 가능하다.
 

파일 맵핑 오브젝트 생성 – CreateFileMapping()

 
HANDLE CreateFileMapping(
HANDLE hFile,              // 파일 핸들
LPSECURITY_ATTRIBUTES lpAttributes,  // 보안 속성
DWORD flProtect,                       // 접근 속성
DWORD dwMaximumSizeHigh,    // 64비트 상위 DWORD 크기
DWORD dwMaximumSizeLow,    // 32비트 크기, 64비트 하위 DWORD 크기
LPCTSTR lpName                       // 문자열 이름
}
 

메모리 맵 파일 참조(파일 뷰 생성) – MapViewOfFile()

 
LPVOID MapViewOfFile(
HANDLE hFileMappingObject,                 // 파일 맵핑 오브젝트 핸들
DWORD dwDesiredAccess,                    // 접근 속성
DWORD dwFileOffsetHigh,
          // 맵핑이 시작되는 포인터의 offset High-Order DWORD
DWORD dwFileOffsetLow,
          // 맵핑이 시작되는 포인터의 offset Low-Order DWORD
SIZE_T dwNumberOfBytesToMap           // 맵핑할 바이트 수
)
 

파일 뷰 소멸 및 맵핑 오브젝트 변환 – UnmapViewOfFile, CloseHandle

사용이 끝난 파일 뷰는 UnmapViewOfFile()을 이용하여 해제하여 메모리 맵 파일과 가상 메모리와의 맵핑 상태를 해제한다. 메모리 맵 파일을 더 이상 사용할 필요가 없을 때, 커널 오브젝트인 파일 맵핑 오브젝트를 CloseHandle()을 사용하여 핸들을 반환한다. 마지막으로 디스크의 파일을 사용한 경우에는 파일을 역시 CloseHandle()을 이용하여 운영체제에게 반환하면 모든 과정이 끝나게 된다.
 

Access Violation을 방지하기 위한 함수

특정 메모리 영역의 포인터를 안전하게 사용하는 몇 가지 WIN32 API 함수
 
 
내용
IsBadCodePtr
현재의 프로세스가 해당 주소가 가리키는 영역을 읽을 수 있는지의 여부를 확인한다.
IsBadReadPtr
현재의 프로세스가 해당 주소가 가리키는 영역을 읽을 수 있는지의 여부를 확인한다.
IsBadStringPtr
현재의 프로세스가 해당 주소가 가리키는 영역을 문자열로 읽을 수 있는지의 여부 확인 하며, 시작 포인터로부터 NULL이 나올 때까지 유효성을 검사한다.
IsBadWritePtr
현재의 프로세스가 해당 주소가 가리키는 영역에 기록할 수 있는지의 여부를 확인한다.
 
댓글