Tutorial 13: Memory Mapped Files
이번에는, 메모리맵파일(=memory mapped file, MM파일)이 무엇인지와 사용법을 설명한다. 이장을 읽어보면, MM파일을 간단하게 사용할 수 있게 된다.
소스 | 리소스 스크립트 | 실행 결과 |
|
이전장의 예제를 유심히 살펴 보면, 중대한 결함을 발견할 것이다. 예를 들면, 확보한 메모리영역보다 큰파일을 읽게 되면 어떻게 될까? 혹은, 검색문자열이 메모리영역에 존재하지 않으면 어떻게 할까? 등이다. 전자의 해결책은, 전통적으로 파일을 다 읽을 때까지 일정크기의 데이터를 반복해서 읽어들이는 방법이다. 후자의 해결책은, 메모리블럭의 마지막에 특별한문자를 미리 설정하는 방법이 있다. 이런 것은 버퍼 오버플로우 에러(BOF)로 불려지며 프로그래머를 힘들게하는 매우 자주 일어날수 있는 버그들이다.
파일의 내용을 저장하기 위해서 아주 큰 영역을 확보하면 좋겠지만,그렇게 되면 한정되어있는 리소스를 쓸데없게 낭비하게 된다. 이것은 파일 매핑 이란 방법으로 해결할 수 있다. 파일 매핑을 사용함으로써, 파일의 모든내용을 메모리에 로드 한 것과 같이 작동 되게 할 수 있다. 이것은 메모리포인터를 사용해서, 파일데이터를 읽고 쓰기할 수가 있다. 방법은 매우 간단하다. 메모리처리 API 함수를 사용할 필요도 없고, 파일 IO의 API 함수로 파일을 분할할 필요도 없다.
파일 매핑은, 프로세스간에 데이터를 공유하는 용도로도 사용된다. 이런 목적으로 파일 매핑을 사용하는 경우는, 실제 파일이 없어도, 모든 프로세스가 공통적으로 참조할 수 있는 메모리블럭이라고 생각할 수 있다. 그러나, 프로세스간에 데이터를 공유하는 것은 실제로는 매우 어렵기 때문에, 쉽게 사용해서는 안된다. 정확하게 프로세스간,혹은 thread간에 데이터의 동기화를 하지 않는다면 프로그램은 아주 쉽게 깨지게 될것이다.
이 번장에서는, 파일 매핑의 이런 공유 메모리적인 사용 방법은 다루지 않는다. 단순히 파일 매핑을 파일과 메모리를 매핑 한다 라는 용도로만 사용한다. 실제로, PE로더(Windows가 제공하는 실행 파일 실행하는 모듈)은 실행 파일을 메모리에 매핑 할 경우에 파일 매핑을 사용한다. 이는 아주 편리해서, 하드 디스크에 존재하는 파일로부터 필요한 부분을 선택적으로 읽어들일 수가 있다. Win32에서는, 최대한 파일 매핑을 사용해야 한다.
그러나, 몇개의 제한이 있다. 한 번 MM파일을 생성하면, 그 세션에서는 MM파일의 크기를 변경할 수 없다. 그래서, 파일 매핑은 읽기전용 파일이나, 파일크기에 관계없는 파일의 조작 등에 적당하다. 그러나 이것은, 파일크기가 증가할 때에 파일 매핑을 사용할 수 없다는 것은 아니고, 파일크기가 얼마나 커질까를 예상해서, 그 크기에 근거해서 MM파일을 생성하면 좋다는 의미이다.
이것으로 설명은 충분할 것이다. 그럼 실제로 파일 매핑의 예를 살펴보자. 파일 매핑을 사용하려면 , 다음과 같은 단계를 따라야한다.
- CreateFile함수를 호출해서, 매핑 하고 싶은 파일을 오픈한다
- CreateFile 함수로 얻은 파일핸들을 인수로 해서 CreateFileMapping함수를 호출 한다. 이 함수에 의해, 지정된 파일에 대한 파일매핑오브젝트를 생성한다.
- MapViewOfFile함수를 호출해서, 매핑 하는영역이, 파일전체인지 일부분 인지를 선택한다. 이 함수는 MAP과 파일영역의 선두의 포인터를 돌려준다.
- 그 포인터를 이용해서 파일에 읽고 쓰기를 수행한다
- UnmapViewOfFile함수를 호출해서, 파일 매핑을 해제한다
- CreateFileMapping 함수로 생성한 파일 매핑 오브젝트를 인수로 해서 CloseHandle함수를 호출한다
- CreateFile 함수로 생성한 파일 핸들을 인수로 해서 한번 더CloseHandle함수를 호출해서, 파일을 닫는다
|
|
|
invoke CreateFile, ADDR buffer,\ GENERIC_READ ,\ 0,\ NULL, OPEN_EXISTING, FILE_ATTRIBUTE_ARCHIVE,\ NULL파일 오픈 다이얼로그 박스로 파일을 선택하면, CreateFile 함수로 그 파일을 오픈한다. 이때, 읽기전용을 의미하는 GENERIC_READ 플래그를 지정하고, 이 프로그램의 조작중에 다른프로세스로부터의 조작을 허가하지 않게 하기 위해서 dwShareMode 를 0으로 설정한다.
invoke CreateFileMapping, hFileRead, NULL, PAGE_READONLY, 0,0, NULL그리고, 오픈한 파일로부터 MM파일을 생성하기 위해서, CreateFileMapping 함수를 호출 한다. CreateFileMapping 함수의 프로토타입은 다음과 같다.
CreateFileMapping proto hFile : DWORD,\ lpFileMappingAttributes : DWORD,\ flProtect : DWORD,\ dwMaximumSizeHigh : DWORD,\ dwMaximumSizeLow : DWORD,\ lpName : DWORD가장 먼저 알아두어야 할 것은, CreateFileMapping 함수로 파일 전체를 메모리에 매핑 할 필요는 없다는 것이다. 파일의 일부를 매핑 해 두면 된다. 어느 정도의 영역을 매핑 할지는, dwMaximumSizeHigh와 dwMaximumSizeLow의 인수에 설정하는 값에 따르지만, 만약, 실제파일크기보다 큰 크기를 설정 했을 경우, 그 크기에 맞추어 파일 크기가 커진다. 파일크기와 같은크기의 메모리매핑영역으로 하고 싶다면, dwMaximumSizeHigh와 dwMaximumSizeLow의 양쪽 모두의 파라미터를 0 으로 설정하면 된다.
Windows에 기본 보안속성을 가진 MM파일을 생성하기 위해서, lpFileMappingAttributes 인수를 NULL로 설정하면 된다.
flProtect는 MM파일의 보호속성을 정의한다. 이 번장에서는, MM파일을 읽기전용으로 사용하기 때문에, PAGE_READONLY를 사용하고 있다. 주의할 것은, 이 속성은 CreateFile 함수에서 지정한 플래그와 충돌되지 않게 해야한다는 것이다. 충돌이란 CreateFile 함수에서는 읽기전용인데, CraeteFileMapping 함수에서는 읽기/쓰기가능이라는 것과 같은 경우를 의미하고,이런경우 CreateFileMapping 함수는 실패하게 된다.
lpName는 MM파일명을 가리키는 포인터로, 다른프로세스와 이 파일을 공유하는 경우는, 이름을 붙여야만 한다. 그러나, 이 예제에서는, 다른프로세스와 공유하지않기 때문에, 이 파라미터는 무시하고 있다.
mov eax, OFFSET buffer movzx edx, ofn.nFileOffset add eax, edx invoke SetWindowText, hWnd, eaxCreateFileMapping 함수가 성공적으로 수행되면, 윈도우의 캡션을 오픈한 파일명으로 변경하고 있다. 풀 패스형태로 버퍼에 저장되지만, 캡션에는 파일명만 표시하고 싶기 때문에, 그 풀 패스가 들어가 있는 버퍼의 주소에, OPENFILENAME 구조체의 멤버 nFileOffset를 추가해야 한다.
invoke EnableMenuItem, hMenu, IDM_OPEN, MF_GRAYED invoke EnableMenuItem, hMenu, IDM_SAVE, MF_ENABLED안전대책으로 유저가 한 번에 여러개의 파일을 오픈할 수 없게 하기 위해, Open 메뉴 아이템을 회색으로 처리하고, Save 메뉴 아이템만 선택가능하게 하고 있다. EnableMenuItem 함수는 메뉴 아이템의 속성을 변경하기 위해서 사용하는 함수이다.
이 후부터는, 유저가 「File」→「Save」메뉴를 선택하는지, 프로그램을 종료할지를 선택할 수있게 된다. 만약 유저가 종료를 선택하면, 보통 파일조작때와 같이, MM파일도 닫아주어야만 한다. 그 코드는 다음과 같다.
.ELSEIF uMsg==WM_DESTROY .if hMapFile! =0 call CloseMapFile .endif invoke PostQuitMessage, NULL위의 코드는, 윈도우 프로시저가 WM_DESTROY 메세지를 받았을 때의 처리를 하고있다.먼저 hMapFile이 0 인지를 체크하고 있다. 만약 0 이라면, 다음과 같이CloseMapFile 함수를 호출 하게 된다.
CloseMapFile PROC invoke CloseHandle, hMapFile mov hMapFile, 0 invoke CloseHandle, hFileRead ret CloseMapFile endpCloseMapFile 함수는, Windows로 제어를 돌려줄 때, 자원누출(Leak)을 없애기 위해, MM파일과 실제 파일을 닫는다. 유저가 데이터를 다른 파일에 저장하기 위해 Save 메뉴를 선택하면, 프로그램은 파일을 저장하는 다이얼로그 박스를 표시하고 ,저장 다이얼로그 박스에 유저가 새로운 파일명을 입력해서, CreateFile 함수로, 그 이름의 파일을 생성한다.
invoke MapViewOfFile, hMapFile, FILE_MAP_READ, 0,0,0 mov pMemory, eax출력 파일이 생성된 직후에, MapViewOfFile 함수를 호출 해서, 메모리에 MAP 하는 파일영역을 지정한다. 이 함수의 프로토타입은 다음과 같다.
MapViewOfFile proto hFileMappingObject : DWORD,\ dwDesiredAccess : DWORD,\ dwFileOffsetHigh : DWORD,\ dwFileOffsetLow : DWORD,\ dwNumberOfBytesToMap : DWORD
- dwDesiredAccess
파일에 어떤 처리를 하고 싶은지를 지정한다.예제에는, read가 적당하기 때문에, FILE_MAP_READ를 사용하고 있다.- dwFileOffsetHigh,dwFileOffsetLow
메모리에 MAP 하고 싶은 영역을 파일선두로부터 어느 부분까지인지를 지정한다. 예제에는, 파일 전체를 MAP 하기 때문에, 선두로부터 매핑을 시작하고 있다.- dwDesiredAccess
메모리에 MAP 하고 싶은 영역을 바이트 단위로 지정한다. 만약 파일 전체를(CreateFileMapping 함수에 의해) 매핑 하고 싶다면, 0 을 지정한다.MapViewOfFile 함수를 호출 하면, 메모리에 지정한 부분을 읽어들이고, 파일의 데이터가 저장되어 있는 메모리블럭의 포인터가 반환된다.
invoke GetFileSize, hFileRead, NULL파일의 크기를 얻기위해서, GetFileSize 함수를 호출 하면, eax 레지스터에 파일크기가 저장된다. 만약 크기가 4 GB이상이라면, 두번째 인수에 DWORD형태의 메모리주소를 지정함으로써, 상위 DWORD(=4바이트)가 그 두번째 인수에 저장되게 되지만, 이 예제에서는 그정도로 큰 파일을 다루지 않기 때문에 NULL을 지정하고 있다. 4 GB미만의 파일크기인 경우는 NULL을 지정하는 것이다.
invoke WriteFile, hFileWrite, pMemory, eax, ADDR SizeWritten, NULL메모리에 MAP 되고 있는 데이터를 파일에 출력한다
invoke UnmapViewOfFile, pMemory입력파일에 대한 처리가 끝나면, 메모리와의 매핑을 해제한다
call CloseMapFile invoke CloseHandle, hFileWrite그리고, 모든 파일을 닫는다
invoke SetWindowText, hWnd, ADDR AppName윈도우의 캡션을 원래대로 되돌린다
invoke EnableMenuItem, hMenu, IDM_OPEN, MF_ENABLED invoke EnableMenuItem, hMenu, IDM_SAVE, MF_GRAYEDOpen 메뉴를 선택 가능하게 하고, Save 메뉴를 회색으로 처리해서 사용하지 못하게 한다.