메모리 맵 파일

메모리 맵 파일(Memory mapped file, MMF, 메모리 사상 파일)은 운영 체제에서 파일을 다루는 방법중 하나이다. 물리 디스크 파일, 장치, 공유 메모리 객체와 같이 운영 체제에서 파일로 다루는 모든 대상에 대해서 사용 가능하며, 메모리 맵 파일을 통해 프로세스의 가상 메모리 주소 공간에 파일을 매핑한 뒤 가상 메모리 주소에 직접 접근하는 것으로 파일 읽기/쓰기를 대신한다.
기본 파일 입출력 API에 비해서 저렴한 비용을 사용하여 파일의 내용에 접근할 수 있도록 해 주며, 메모리와 파일 간의 자료 전송은 운영 체제가 자동으로 처리하므로 보다 간단하게 사용할 수 있다. 다른 프로세스에서 동일한 파일에 메모리 맵 파일을 이용하여 접근할 경우 공유 메모리처럼 사용하는 것도 가능하다.

구조

메모리 맵 파일을 사용하면 디스크에 있는 파일의 블록들과 프로세스의 페이지가 연결된다. 이 상태에서는 가상 메모리에 사상만 되어 있을 뿐이며, 실제 메모리에 파일을 불러오는 시기는 해당 주소에 읽거나 쓰기 작업을 시도할 때 이루어진다. 이 작업은 운영 체제에서 가상 메모리를 다루는 데 사용하는 페이징 기법과 같이 페이지 부재를 일으키는 방식을 이용한다. 일반적으로 파일에서 불러오는 자료는 페이지 크기와 같은 4KiB이지만, 몇몇 운영 체제에서는 하드디스크의 낮은 임의 접근 시간을 고려해 4KiB보다 많은 자료를 페이지로 올릴 수도 있다.
이 때문에 메모리 맵 파일로 작업 중인 데이터가 실제 파일로 바로 반영되지는 않으며, 페이지 교체 알고리즘에 따라 해당 페이지가 메모리에서 내려갈 경우, 또는 메모리 맵 파일을 닫는 과정에서 반영된다. 이것은 메모리 맵 파일과 파일 입출력 API를 동시에 사용할 때 문제가 되는데, 메모리 맵 파일을 통해 변경된 자료는 즉시 반영되지 않으므로 그 지점에 해당하는 자료를 파일 입출력 API를 사용하여 읽어오려 할 때 동기화 문제가 발생하게 된다. 따라서 메모리 맵 파일을 사용할 때에는 읽기 전용으로만 사용하는 목적이 아닌 이상 파일 입출력 API를 사용하지 않는 것이 좋다.
오른쪽 위의 예제 그림은 메모리 맵 파일을 사용할 때 프로세스의 가상 주소와 물리 주소, 그리고 파일들의 관계를 알 수 있다. 각 블록은 페이지 단위로 나타낸 것이다. 예제에서는 프로세스 2개와 파일 2개가 나오며, 이중 b2는 메모리 맵 파일의 공유를, b3는 사상이 되었으나 아직 메모리에 적재되지 않은 것을 나타낸다..

장점

직접적인 파일 입출력을 수행할 필요가 없다. 메모리 맵 파일을 사용하면 파일 내용이 메모리 주소에 사상되므로 파일을 모두 불러온 것처럼 메모리 주소를 이용하여 연산 작업을 할 수 있어 코드가 깔끔해지고 유지보수가 편해진다.
버퍼나 파일 처리를 위한 추가적인 자료 구조가 필요 없다. 운영 체제에서 페이징 기법을 사용하여 파일의 내용을 관리하며, 페이지 크기(보통 4KiB)에 따라 적절히 파일의 내용을 읽고 쓸 수 있으며 파일 반영(flush) 작업도 수행해준다.
대용량의 자료를 처리할 때도 매우 효율적이다. 파일에 접근할 때 지연 적재를 이용하므로, 파일의 크기가 매우 크더라도 필요한 부분만 파일에서 불러와 작업할 수 있으며, 작업이 끝난 데이터는 자동으로 파일에 반영된다.
전통적인 파일 입출력 API보다 속도가 빠르다. API는 시스템 호출을 사용하기 때문에 작업을 수행하는 동안 유저 모드와 커널 모드를 전환하는 데 필요한 인터럽트가 오버헤드로 작용하게 된다. 메모리 맵 파일은 4KiB 단위로 자료를 미리 불러올 때 발생하는 페이지 부재외의 모든 작업이 실제 메모리상에서 이루어지므로 대부분 파일 API를 통한 파일 처리보다 빠르다.

단점

POSIX와 윈도에서 파일의 크기(즉, EOF의 위치)를 변경할 수 없다. 파일 입출력 API에서는 간편한 방법으로 파일의 크기를 변경하는 방법을 제공한다. 메모리 맵 파일은 파일의 크기를 바꿀 수는 없으며 메모리 맵 파일을 사용하기 이전, 또는 이후에만 파일의 크기를 바꿀 수 있다.
메모리 맵 파일을 이용한 접근은 최적의 파일 접근 방식은 아니다. 파일 입출력 API를 사용한 방법이 API의 오버헤드인 것에 비해 메모리 맵 파일에서는 페이지 부재중의 데이터 전송 시간이 오버헤드로 작용한다. 따라서 데이터베이스와 같이 파일을 읽고 쓰는데 오버헤드를 최소로 줄인 자료구조와 알고리즘과 메모리 맵 파일을 비교할 때 오히려 메모리 맵 파일이 느릴 수도 있다.
크기가 지나치게 큰 파일을 처리하는 데 어려움이 있다. IA-32 기반 시스템에서 하나의 프로세스에서 PAE 기술을 사용하지 않고 사용 가능한 최대 크기는 4GB로 제한된다. 따라서 프로세스의 메모리 주소를 사용하는 메모리 맵 파일이 한번에 다룰 수 있는 크기는 이보다 작아지며, 이 크기를 넘어가는 파일을 메모리 맵 파일로 다루려 할 때 계산이 복잡해질 수 있다.

용도

대부분 운영 체제에서는 프로세스를 실행할 때 실행 파일의 각 세그먼트를 메모리에 사상하기 위해 메모리 맵 파일을 이용한다. 이 경우 하나의 프로세스가 동시에 여러 번 동작할 때 실행 코드와 같은 읽기 전용 세그먼트를 공유할 수 있어 중복된 내용만큼의 메모리를 절약할 수 있다. 그리고 메모리 맵 파일이 가지는 지연 적재 특징 덕분에 실행 파일에서 사용하지 않는 부분이 있을 때도 메모리를 최적화하여 사용할 수 있다.
또한, 파일의 내용이 프로그래밍 언어에서 직접 다룰 수 있는 구조체와 같은 형식을 가지고 작성되어 있으면, 메모리 맵 파일을 이용해 자료에 바로 접근하는 방법을 사용하기도 한다. 이 경우 위에 서술한 바와 같이 많은 장점을 얻을 수 있다.
또 다른 사용 용도는 프로세스 간 통신 수단이다. 현대의 운영 체제에서는 하나의 프로세스는 고유의 메모리 주소만 갖게 되어 다른 프로세스의 메모리에 접근할 수 없다. 따라서 프로세스 간 통신을 위해 공유 메모리를 사용하는 방법이 고안되어 있으며 메모리 맵 파일은 이 공유 메모리를 사용하는 방법이 될 수 있다.

POSIX

POSIX에서는 mmap이라는 함수를 통해 메모리 맵 파일 기능을 제공하고 있다. MAP_SHARED라는 플래그를 통해 다른 프로세스에서 같은 파일에 대해 메모리 맵 파일 기능을 사용할 수 있도록 할 수 있어 공유 메모리로도 사용할 수 있다. POSIX를 따르는 운영 체제들(리눅스, 유닉스, 솔라리스 등)에서는 약간의 차이는 있지만 같은 기능과 특징을 가진 메모리 맵 파일 기능을 제공한다.

윈도

윈도 API에서는 CreateFileMapping() 함수와 MapViewOfFile()를 통해 메모리 맵 파일 기능을 제공하고 있다.
POSIX를 따르는 다른 운영 체제와는 달리 윈도에서는 공유 메모리를 메모리 맵 파일을 통해서만 사용할 수 있다.
(예제)