MMF의 사용목적은 크게 두가지로 나뉘어질수 있습니다.

- 대용량 파일을 한꺼번에 메모리에 올려 고속의 I/O 를 지원한다.
- 서로 다른 프로세스간의 메모리를 공유 할 수 있도록 지원한다.

디스크에 존재하는 파일을 메모리에 사상(연결, Mapping) 하여 얻은 주소에 입출력 하면, 디스크에 입출력이 된다.

일단 서버에서는 읽고/쓰기 모드로 열어서 내부에서 사용하고..
다른 한쪽 혹은 여러클라이언트 에서는 읽기 전용 모드로 열어서 사용하는 가장 간편한 방법을 사용해보자.
(사용하는 방식은 포인터를 얻어서 쓰기 때문에 메모리를 액세스하는것과 동일하다..)


MMF사용을 위해 window API가 제공하는 함수는 다음과 같습니다.


HANDLE WINAPI CreateFileMapping(

    HANDLE hFile,

    LPSECURITY_ATTRIBUTES lpAttributes,

    DWORD flProtect,

    DWORD dwMaximumSizeHigh,

    DWORD dwMaximumSizeLow,

    LPCTSTR lpName

); 

 MMF 커널 객체를 만들고 해당 핸들을 반환한다.


1) hFile : 매핑할 파일 핸들을 인자로 받는다. 이미 존재하고있는 파일을 맵핑하는 경우  CreateFile을 OPEN_EXSITING 형태로 사용해서 파일을 열어 핸들을 얻은 후 CreateFileMapping 인자로 넣어 주어야 한다.  HANDLE 값으로 INVALID_HANDLE_VALUE,즉 0xFFFFFFFF 값을 사용하고 dwMaximumSizeHighdwMaximumSizeLow를 지정하면 커널은 페이징 파일(PageFile.sys) 을 이용해서 메모리를 공유 한다. (즉 프로세스간의 통신 (Inter-Process Communication) 용도로 페이징 파일을 메모리에 할당해서 사용할 수 있습니다.


※ HANDLE hFile
사용자가 이미 만들어 놓은 파일 핸들이나, INVALID_HANDLE_VALUE 를 전달할 수 있으며, INVALID_HANDLE_VALUE 이
전달되었을 경우는 시스템의 페이지 파일을 할당
해서 사용한다. 
그러므로, 특별하게 큰 파일을 사용하는 경우가 아니라면 INVALID_HANDLE_VALUE 만으로도 충분하다.

※ LPCTSTR lpName
해당 공유 메모리를 확인하기 위한 고유한 이름을 나타낸다. 일반적인 클라이언트 OS 에서는 별 문제가 없겠지만 
서버 OS에 Terminal Service 가 구동중이라면 Prefix 에 따라 그 특성이 달라진다.
모든 세션에서 접근할 수 있으려면 Global\이름 과 같이 Global\ 라는 Prefix를 붙혀주어야 한다.


2) lpAttributes : 보안 속성

3) flProtect : 페이지 옵션이며 PAGE_READONLY, PAGE_READWRITE 등의 값이 들어간다.

4) dwMaximumSizeHigh : 매핑할 범위를 지정하는 상위 4바이트 
(용량이 이보다 작다면 0을 입력하고 다음 인자에 원하는 파일크기 및 범위를 지정한다.)

5) dwMaximumSizeLow : 매핑할 범위를 지정하는 하위 4바이트

6) lpName : 고유한 객체 이름을 부여한다.


HANDLE WINAPI OpenFileMapping(

    DWORD dwDesiredAccess,

    BOOL bInheritHandle,

    LPCTSTR lpName

);

이미 존재하는 파일-매핑 커널 오브젝트를 이용하여 데이터를 읽을 때 사용한다.

파일-매핑 커널 오브젝트에 접근할 권한이 있다면 해당 함수는 유효한 핸들을 반환한다. 

이미 생성되어있는 메모리상의 매핑된 파일의 이름을 가지고 있는 LOOK-UP 테이블을 찾아서 해당 이름을 가진 메모리가 있는지 찾습니다.


1) dwDesiredAccess : 어떤 동작을 수행할지 나타낸다. FILE_MAP_READ, FILE_MAP_ALL_ACCESS를 등의 옵션이 존재한다.

2) bInheritHandle : FALSE를 사용

3) lpName : 접근할 고유 객체의 이름을 사용한다.


LPVOID WINAPI MapViewOfFile(

    HANDLE hFileMappingObject,

    DWORD dwDesiredAccess,

    DWORD dwFileOffsetHigh,

    DWORD dwFileOffsetLow,

    SIZE_T dwNumberOfBytesToMap

);

 MMF 객체를 대상으로 실제 메모리 매핑을 수행하여 그 메모리의 시작 번지를 반환한다.


1) hFileMappingObject : MMF 객체 핸들을 연결

2) dwDesiredAccess : 접근 권한을 설정. FILE_MAP_READ, FILE_MAP_WRITE 등

3) dwFileOffsetHigh : 메모리 주소가 연결될 크기. 상위 4바이트

4) dwFileOffsetLow : 메모리 주소가 연결될 크기. 하위 4바이트

5) dwNumberOfBytesToMap : 오프셋으로부터 원하는 크기를 설정


WINAPI UnmapViewOfFile(LPCVOID lpBaseAddress);
WINAPI CloseHandle(HANDLE hObject);

MMF 객체를 사용하여 위의 두 함수를 통해 사용이 완료되었을때 해제해주어야 한다.

UnmapViewOfFile 호출시 작업내용이 실제 파일에도 적용된다.



# Server 역할 프로세스 코드

struct MYSTRUCT {

    bool a; float b; double c; char buffer[10];

};


HANDLE hMemoryMap = NULL;

LPBYTE pMemoryMap = NULL;


hMemoryMap = CreateFileMapping(

    (HANDLE)0xffffffff, NULL, PAGE_READWRITE, 0,

    sizeof(MYSTRUCT), L"fork_server"

);


if(!hMemoryMap) {

    MessageBox(NULL, L"Failed CreateFileMapping", L"Error", MB_OK);

    return FALSE;

}


pMemoryMap = (BYTE*)MapViewOfFile(

    hMemoryMap, FILE_MAP_ALL_ACCESS,

    0, 0, 0

);        


if(!pMemoryMap)

{

    CloseHandle(hMemoryMap);

    MessageBox(NULL, L"Failed MapViewOfFile",  L"Error", MB_OK);

    return FALSE;

}


MYSTRUCT* pStruct = (MYSTRUCT*)pMemoryMap;


pStruct->a = true;

pStruct->b = 0.1f;

pStruct->c = 0.0000001;

strcpy(pStruct->buffer, "data copy");


while(1)

{

    if(_kbhit() != 0)

        break;

}


if(pMemoryMap)

    UnmapViewOfFile(pMemoryMap);


if(hMemoryMap)

    CloseHandle(hMemoryMap);


# Client 역할 프로세스 코드

struct MYSTRUCT {

    bool a; float b; double c; char buffer[10];

};


HANDLE hMemoryMap = NULL;

LPBYTE pMemoryMap = NULL;


hMemoryMap = OpenFileMapping(FILE_MAP_READ, FALSE, L"fork_server");


if(!hMemoryMap) {

    MessageBox(NULL, L"Failed CreateFileMapping", L"Error", MB_OK);

    return FALSE;

}


pMemoryMap = (BYTE*)MapViewOfFile(

    hMemoryMap, FILE_MAP_READ,

    0, 0, 0

);        


if(!pMemoryMap)

{

    CloseHandle(hMemoryMap);

    MessageBox(NULL, L"Failed MapViewOfFile",  L"Error", MB_OK);

    return FALSE;

}


MYSTRUCT* pStruct = (MYSTRUCT*)pMemoryMap;


while(1)

{

    printf("%d\n", pStruct->a);

    printf("%f\n", pStruct->b);

    printf("%f\n", pStruct->c);

    printf("%s\n", pStruct->buffer);


    if(_kbhit() != 0)

        break;

}


if(pMemoryMap)

    UnmapViewOfFile(pMemoryMap);


if(hMemoryMap)

    CloseHandle(hMemoryMap);


※ 참고로, 서버프로그램이 종료되었을 경우, 클라이언트에서 메모리 맵드 파일의 현황만 가지고는 (서버가 죽었는지 살았는지) 알수 없다는 단점이 있다.
즉, 서버가 죽어도 클라이언트가 살아서 데이터를 읽는데는 전혀 문제가 없기 때문에.. 이러한 부분은 별도로 확인해 주어야한다.



출처 및 참고자료 : http://egloos.zum.com/anster/v/2156072  

  https://hellobird.tistory.com/3?category=768685

  https://crowback.tistory.com/198


+ Recent posts