티스토리 뷰

IT/OS

윈도우의 메모리 관리-3

NineKY 2008. 1. 16. 11:10

윈도우의 메모리 관리-3

고동일 (diko@taff.co.kr)
 
가상 메모리 함수
앞에서 말했듯이 어떤 메모리 공간을 확보하거나 커밋하기 위해서는 VirtualAlloc이라는 Win32 함수를 사용합니다. 이 함수는 다음과 같습니다.

LPVOID VirtualAlloc(
LPVOID lpAddress,
SIZE_T dwSize,
DWORD flAllocationType,
DWORD flProtect
);

◆ 주소 공간에서 어떤 영역을 확보할 때
처음 파라미터 lpAddress는 시스템에게 확보시키고 싶은 주소 공간의 시작 주소를 나타냅니다. 보통의 경우 NULL 값을 주며 이는 시스템이 자동으로 비어 있는 주소를 정하도록 합니다. 주의할 점은 lpAddress 인자 값은 언제나 사용자 모드 파티션 범위 내의 주소 값이어야 합니다. 그렇지 않을 경우 VirtualAlloc은 NULL을 리턴합니다. 또한 앞에서도 언급했듯이 이 주소 값은 할당 세밀도의 단위에 맞춰져야 합니다. 만일 여러분이 6,554,624(64KB*100+1024B)의 주소에서부터 영역을 할당하고 싶다고 인자를 주면 시스템은 이를 내림하여 65,543,600(64KB*100)의 주소부터 영역을 확보할 것입니다.
dwSize 인자는 확보하고 싶은 주소의 크기를 바이트 단위로 나타냅니다. 앞에서도 말했지만 확보하고 싶은 영역의 크기는 반드시 시스템의 페이지 크기의 배수여야 합니다. x86 CPU인 경우 만일 여러분이 126KB 크기의 영역을 확보하려 한다면 시스템은 128KB 크기의 영역을 확보할 것입니다. flAllocationType 인자는 VirtualAlloc 함수를 이용해 메모리를 확보할 것인지 커밋할 것인지를 나타내며 각각 MEM_ RESERVE, MEM_COMMIT의 값을 가집니다. 시스템은 비어 있는 주소 중 어디에나 확보할 수 있기 때문에 차곡차곡 순서대로 영역이 확보될 것이라는 보장은 없습니다. 2000 계열에서는 MEM_TOP_DOWN이라는 플래그를 제공해 이를 해결합니다. MEM_TOP_DOWN 인자를 MEM_RESERVE와 함께 비트 OR 연산으로 주면 시스템은 사용 가능한 가장 높은 주소부터 영역을 확보하게 됩니다. flProtect 인자는 해당 영역에 할당할 보호 특성(protection attribute)을 지정합니다. 만일 여러분이 PAGE_READWRITE로 어떤 페이지의 메모리를 커밋하고 싶다면 사전에 그 영역의 메모리는 PAGE_ READWRITE로 확보되었어야 합니다.
설정할 수 있는 보호 특성 값에는 PAGE_NOACCESS, PAGE_READWRITE, PAGE_READONLY, PAGE_EXE CUTE, PAGE_EXECUTE_READ, PAGE_EXECUTE_ READWRITE, PAGE_WRITECOPY, PAGE_EXECUTE_ WRITECOPY 등의 값을 설정할 수 있습니다. 98 계열은 이 중에서 PAGE_NOACCESS, PAGE_READONLY, PAGE_ READWRITE만 지원합니다. 주의할 것은 이 보호 특성은 확보된 영역에 해당되는 것이며 커밋된 물리적 메모리와는 무관하다는 것입니다. 일단 메모리가 확보만 되고 커밋되지 않았다면 해당 주소에 대한 접근은 무조건 액세스 위반을 발생시킵니다.

◆ 확보된 영역의 특정 페이지를 커밋할 때
VirtualAlloc으로 커밋할 때는 flAllocationType 인자 값을 MEM_COMMIT으로 주게 됩니다. 물론 lpAddress 인자에 커밋을 원하는 메모리의 시작 주소 값, dwSize에 커밋할 메모리의 크기를 주게 됩니다. 이 때 반드시 한번에 모든 영역을 커밋할 필요는 없습니다. 일반적으로 보호 특성은 확보할 때와 커밋할 때 같은 값을 주는 것이 보통이며 실제로는 PAGE_READWRITE를 가장 많이 사용하게 될 것입니다. 다음의 코드는 어떤 512KB 영역이 주소 6,553,600에서부터 확보됐을 때, 이로부터 2KB 후에서 6KB의 크기만큼 x86 CPU에서 커밋을 하는 코드입니다.

VirtualAlloc((PVOID) (6553600 + 2*1024), 6 * 1024, MEM_COMMIT, PAGE_READWRITE);

이 경우 시스템은 페이지 크기의 배수로 커밋을 해야 하기 때문에 8KB의 메모리를 커밋하게 되며 커밋된 주소 공간은 6,553,600부터 6,561,791(6553600+8KB-1)까지가 됩니다.

◆ 가상 메모리를 해제할 때
프로세스가 더 이상 어떤 가상 메모리를 사용하지 않을 경우엔 이를 해제시켜 주어야 합니다. 가상 메모리를 해제할 때에는 VirtualFree 함수를 이용합니다. VirtualFree 함수는 VirtualAlloc 함수와 정반대의 기능을 가지고 있으며 다음과 같은 함수입니다.
LPVOID VirtualFree(
LPVOID lpAddress,
SIZE_T dwSize,
DWORD flFreeType,
);

커밋된 페이지를 해제할 때는 flFreeType 인자에 MEM_ DECOMIT 값을, 확보된 영역을 해제할 때에는 MEM_ RELEASE 값을 주게 됩니다. 보통은 다음과 같이 dwSize에 0을 주어 어떤 영역이 6,553,600부터 확보됐을 때 해당 영역을 모두 디커밋하고 해제합니다.

VirtualFree(6553600, 0, MEM_DECOMMIT | MEM_RELEASE);

하지만 만일 영역 전체가 커밋된 것이 아니라면 반드시 커밋된 페이지를 디커밋시키고 해제해야 합니다.

메모리 맵 파일
메모리 맵 파일 언제 만드나요?
가상 메모리와 유사하게 메모리 맵 파일도 프로세스의 주소 공간에 특정 영역을 확보하고 이를 물리적 메모리에 맵핑할 수 있게 해 줍니다. 다른 점은 이 물리적 메모리가 가상 메모리는 페이징 파일인데 반해 메모리 맵 파일은 이미 디스크 상에 존재하는 어떤 다른 파일 자체라는 것입니다. 메모리 맵 파일은 보통 다음의 세 가지 경우에 사용됩니다.

◆ .exe 실행 파일이나 DLL들을 로드할 때 : 페이징 파일 크기를 줄이고 응용의 실행에 필요한 시간을 줄일 수 있습니다.
◆ 디스크의 데이터 파일에 접근할 때 : 디스크 I/O를 줄여주고 파일을 메모리에 버퍼링할 수 있습니다.
◆ 프로세스간 통신 : 시스템 상에서 수행중인 서로 다른 프로세스 간에 데이터를 주고받을 때 사용합니다.

메모리 맵 파일과 주소 공간
잠깐 기억을 되살릴 겸 어떤 쓰레드가 CreateProcess 함수를 호출할 때 운영체제가 하는 일들을 다시 한번 살펴보기로 합시다.

(1) CreateProcess에 인자로 전달되어 실행시켜야 할 .exe 실행 파일을 찾습니다. 만일 찾지 못한다면 FALSE를 리턴합니다.
(2) 새로운 프로세스 커널 객체를 생성합니다.
(3) 새로운 프로세스를 위한 독립된 주소 공간을 생성합니다.
(4) 새로운 프로세스의 .exe 실행 파일을 저장하기에 충분한 주소 공간 영역을 확보합니다. 기본 값으로는 이 실행 파일이 위치하는 기본 주소인 0x00400000입니다. 만일 다른 주소를 바란다면 비주얼 C++의 경우 링커에 /BASE 옵션 값으로 바꾸어 설정할 수 있습니다.
(5) 실행 파일의 물리적 메모리는 페이징 파일 대신 해당 .exe 실행 파일에 의해 확보됐음을 명시합니다.
(6) 실행 파일이 사용하는 DLL들을 알아낸 후 각 DLL들을 LoadLibrary 함수를 이용해 로드합니다. DLL을 로드할 때의 동작은 앞의 4, 5번과 유사합니다. 여기서 4, 5번 과정이 메모리 맵 파일과 연관된 부분입니다. 시스템은 모든 실행 파일과 DLL을 로드한 후에 스타트업 코드를 실행하게 됩니다.

그럼 새로운 응용이 아니라 기존에 실행되고 있던 응용을 하나 더 실행시키면 어떻게 될까요? 시스템은 간단히 어떤 실행 파일의 이미지인지를 알 수 있는 메모리 맵 뷰를 하나 생성하고 새 프로세스 커널 객체를 만들게 됩니다. 메모리 맵 파일을 통해 한 응용의 여러 인스턴스들이 하나의 실행 코드와 데이터를 공유하게 되는 것입니다. <그림 4>는 어떤 응용을 빌드해 실행 파일을 생성하고 두 개의 인스턴스를 실행시켰을 때 실행 파일, 가상 메모리, 응용의 주소 공간의 모습을 보여주고 있습니다.


<그림 4> 메모리 맵 파일과 주소 공간

응용의 실행 파일은 여러 개의 섹션(section)으로 구분되어 있습니다. 여기서는 크게 실행 코드와 데이터 섹션으로 나눠져 있다고 가정해 봅시다. 가상 메모리 상의 메모리 페이지들이 코드와 섹션별로 배치되어 있음을 <그림 4>에서 볼 수 있습니다. 이 때 첫 번째 인스턴스가 데이터 페이지 2에 있는 전역 변수의 값을 바꾼다고 가정해 봅시다. 그림대로라면 이를 공유하고 있는 두 번째 인스턴스의 해당 변수의 값도 바뀌게 될 것이고 이는 보통 두 번째 인스턴스에서는 예상치 못한 일이기 때문에 심각한 문제를 일으킬 수 있습니다. 이를 막기 위해 시스템은 메모리 맵 파일 페이지의 값을 어떤 인스턴스가 변경하려고 할 때 가상 메모리 상에 새로운 페이지를 만들고 이 페이지에 원래 페이지의 값을 복사한 후 새 페이지에 변경된 값을 씁니다. 이를 카피 온 라이트(copy on write) 기법이라 합니다. <그림 5>는 변경 후의 주소 공간의 모습을 나타내고 있습니다.


<그림 5> Copy on write 후의 메모리 맵 파일과 주소 공간

메모리 맵 파일의 이용
메모리 맵 파일을 이용하기 위해서는 다음의 세 단계를 거칩니다.

(1) 메모리 맵 파일로 사용하고 싶은 파일을 식별하는 파일 커널 객체를 하나 만들어 엽니다.
(2) 파일 맵핑 커널 객체를 하나 만들어 시스템에 해당 파일의 크기와 어떻게 그 파일에 접근할지를 알립니다.
(3) 시스템에 파일 맵핑 커널 객체가 가리키는 모든 부분을 프로세스의 주소 공간에 맵핑하라고 알립니다.

반대로 메모리 맵 파일의 이용을 마칠 때는 다음의 세 단계를 거쳐야 합니다.

(1) 시스템에 파일 맵핑 객체가 가리키는 모든 부분을 프로세스의 주소 공간에서 분리하라고 알립니다.
(2) 파일 맵핑 커널 객체를 닫습니다.
(3) 파일 커널 객체를 닫습니다.

그러면 지금부터 메모리 맵 파일을 이용해 여러 프로세스 간에 데이터 공유를 할 때를 가정해 앞의 과정들을 살펴보겠습니다. 일반적으로 메모리 맵 파일을 이용할 때에는 이미 존재하는 파일을 대상으로 CreateFile 함수를 이용해 새로운 파일 커널 객체를 생성합니다만, 프로세스간 데이터 공유로 이용할 때는 그냥 페이징 파일을 대상 파일로 이용하게 됩니다. 그렇다면 일단 짾번 과정은 넘어가서 짿번 과정인 파일 맵핑 객체를 만들어야 할 것입니다. 다음은 파일 맵핑 객체를 생성하는 코드입니다.

hFileMapping = CreateFileMapping(INVALID_HANDLE_VALUE,
NULL,
PAGE_READWRITE,
0,
0x1000,
“SharedMemMapFile”);

CreateFileMapping의 첫 번째 인자는 원래 메모리 맵 파일로 이용할 파일 커널 객체의 핸들을 전달하게 되어 있지만 이 값을 INVALID_HANDLE_VALUE(실제 값은 -1)로 주게 되면 다른 파일 대신 시스템의 페이징 파일을 이용하게 됩니다. 보호 특성은 서로 다른 프로세스에서 읽고 쓰려 하려는 것이니 PAGE_READWRITE를 주었고 사용할 메모리 맵 파일의 크기는 0x1000, 즉 4KB만큼 주었습니다. 마지막 인자는 이 파일 맵핑 커널 객체의 이름으로 다른 프로세스에서 이 메모리 맵 파일을 이용할 때 사용됩니다. 파일 맵핑 커널 객체가 성공적으로 생성됐으면 이제 세 번째 단계를 수행해야 하며 이는 MapViewOfFile 함수를 통해 이뤄집니다.

lpMapView = MapViewOfFile(hFileMapping,
FILE_MAP_READ | FILE_MAP_WRITE,
0, 0,
0);

첫 인자는 2단계에서 생성한 파일 맵핑 커널 객체의 핸들입니다. 세 번째와 네 번째는 메모리 맵 파일의 오프셋 값입니다. 여기서 메모리 맵 파일의 크기는 64비트이기 때문에 2개의 32비트 값 인자로 나누어 처리하는 것입니다. 마지막 인자는 사용할 뷰의 크기를 나타냅니다. 0이면 메모리 맵 파일 전체를 사용하게 됩니다. MapViewOfFile의 리턴 값은 void 포인터 값이며 이후 메모리의 포인터를 사용하듯 이용하면 됩니다.
다른 프로세스에서 이 메모리 맵 파일을 이용하려면 Open FileMapping 함수를 이용하면 됩니다. 다음은 앞서 정한 파일 맵핑 커널 객체의 이름을 이용해 다른 프로세스에서 쓰기용으로 파일 맵핑 커널 객체를 얻어내는 코드입니다.

hFileMapping = OpenFileMapping(FILE_MAP_WRITE, FALSE, “SharedMemMapFile”);

윈도우는 RPC, COM, OLE, DDE, 윈도우 메시지, 클립보드, 파이프, 메일슬롯 등의 다양한 프로세스간 통신 메커니즘을 제공하고 있으나 가장 하부에서는 이들 모두 이 메모리 맵 파일을 이용해 구현되어 있습니다. 따라서 관심 있는 분들은 좀더 자세히 공부해 보길 권합니다.
공지사항
최근에 올라온 글
최근에 달린 댓글
Total
Today
Yesterday
«   2024/12   »
1 2 3 4 5 6 7
8 9 10 11 12 13 14
15 16 17 18 19 20 21
22 23 24 25 26 27 28
29 30 31
글 보관함