8.메모리 매핑
8-1. 개요
지금까지 작성한 프로그램은 대부분 단일 파일로 구성된 매우 간단한 것이었다. 하지만 업무 처리나 게임 등 실제 사용하는 응용 프로그램은 이렇게 간단하지 않다. 보통 하나 이상의 실행파일이 생성되고, 이를 실행한 프로세스 사이에서 시그널이나 데이터를 주고 받으며 작업을 처리한다.
이렇게 프로세스 사이에서 데이터를 주고 받으려면 통신 프로그래밍이 필요하다. 통신 프로그래밍을 하면 유닉스 시스템에서 제공하는 다양한 통신 기능을 이용하는 응용 프로그램을 만들 수 있다. 유닉스 시스템이 제공하는 통신 기능은 크게 두가지로 구분할 수 있다.
하나는 동일한 시스템 안에 있는 프로세스 사이에서 통신을 수행하는 것이고, 두번째는 서로 다른 시스템에서 수행하고 있는 프로세스 사이에서 통신으로 데이터를 주고 받는 것이다.
프로세스간 통신
InterProcess Communication(IPC)은 동일한 유닉스 시스템 안에서 수행중인 프로세스 끼리 데이터를 주고받는 것이다. IPC에는 파이프(Pipe)같은 특수 파일을 이용하거나 메모리 매핑이나 공유 메모리 같은 메모리영역을 이용하는 방법이 있다.
메시지 큐, 공유 메모리, 세마포어 등 유닉스 시스템V에서 제공하는 IPC방법이 있다.
넓은 의미에서 프로세스간 통신은 exit status와 시그널 같은 정수값을 주고받는 것도 포함한다. 자식 프로세스가 종료하면서 부모 프로세스에 종료 상태를 알리는 값을 보내는 것은 가장 간단한 형태의 프로세스 간 통신이다.종료 상태값은 대개 0이나 1이지만 프로그래머의 의도에 따라 다양한 정수값을 사용할 수 있다.
시그널도 프로세스 사이에서 데이터를 주고받으므로 프로세스간 통신방법중 하나로 간주할 수 있다.
시그널의 경우 미리 정의된 상수를 주고받는다. 이들 상수는 프로세스를 수행하는 도중에 프로세스에 알려야할 다양한 이벤트를 정의해 놓은 것이다. 시그널을 받은 프로세스는 시그널의 종류에 따라 적절히 대응해 작업할 수 있다.
네트워크를 이용한 통신
네트워크 환경이 발전하면서 메일이나 파일을 주고받거나 클라이언트/서버 형태의 응용 프로그램 등 서로 다른 시스템간 네트워크를 이용한 통신이 늘어나고 있다.
유닉스 시스템에서 네트워크를 이용한 통신은 TCP/IP프로토콜을 기본으로 하고 있으며, 소켓 라이브러리를 이용한다.
TLI(Transport Layer Interface)도 있지만 소켓 라이브러리에 말려 거의 사용하지 않는다.
이 장에서는 다양한 프로세스 간 통신 방법 중 메모리 매핑을 사용한 통신 프로그래밍을 배운다. 파일을 프로세스의 가상 메모리로 매핑하고 해제하는 함수, 파일의 크기를 조정하는 방법, 매핑된 메모리의 동기화 및 이를 이용해 데이터를 주고 받는 방법을 배운다.
메모리 매핑 함수 종류
기능 | 함수 원형 |
---|---|
메모리 매핑 | void *mmap(void *addr, size_t len, int prot, int flags, int fildes, off_1 off); |
메모리 매핑 해제 | int munmap(void *addr, size_t len); |
파일 크기 조정 | int truncate(const char *path, off_t length); int truncate(int fildes, off_t length); |
매핑된 메모리 동기화 | int msync(void *addr, size_t len, int flags); |
8-2. 메모리 매핑과 해제
Memory mapping 은 파일을 프로세스의 메모리에 매핑하는 것이다. 즉 프로세스에 전달할 데이터를 저장한 파일을 직접 프로세스의 가상 주소 공간으로 매핑한다. read와 write함수를 사용하지 않고도 프로그램 내부에서 정의한 변수를 사용해 파일에서 데이터를 읽거나 쓸 수 있다.
8-2-1. 메모리 매핑 함수
mmap함수를 사용하면 파일을 프로세스의 가상 메모리에 매핑할 수 있다. 메모리에 매핑된 데이터는 파일 입출력 함수를 사용하지 않고 직접 읽고 쓸 수 있다.
- 메모리 메핑 : mmap(2)
#include <sys/mman.h>
void *mmap(void *addr, size_t len, int prot, int flags, int fildes, off_t off);
* addr : 매핑할 메모리 주소
* len : 메모리 공간의 주소
* prot : 보호 모드
- PROT_READ : 매핑된 파일을 읽기만 한다.
- PROT_WRITE : 매핑된 파일에 쓰기를 허용한다.
- PROT_EXEC : 매핑된 파일을 실행할 수 있다
- PROT_NONE : 매핑된 파일에 접근할 수 있다.
* flags: 매핑된 데이터의 처리 방법을 지정하는 상수
* fildes : 파일 기술자
* off : 파일 오프셋
- mmap 함수와 기존 방식의 비교
* 파일 입출력 함수
fd = open(...);
lseek(fd, offset, whence);
read(fd, buf, len);
* mmap 함수
fd = open(...);
address = mmap((caddr_t) 0, len, (PROT_READ | PROT_WRITE), MAP_PRIVATE, fd, offset);
mmap함수는 열린 파일의 내용을 메모리에 매핑하고 address가 가리키는 메모리 영역의 데이터를 대상으로 작업을 수행하므로 매번 read함수로 데이터를 읽지 않아도 된다.
8-2-2. 메모리 매핑 해제 함수
munmap 함수는 mmap 함수를 사용해 매핑한 메모리 영역을 해제한다. 메모리 매핑이 해제된 메모리 영역에 다시 접근하려고 하면 오류가 발생한다.
- 메모리 메핑 해제 : munmap(2)
#include <sys/mman.h>
int munmap(void *addr, size_t len);
* addr : 매핑된 메모리의 시작 주소
* len: 메모리 영역의 크기
munmap 함수는 addr이 가리키는 영역에 len크기만큼 할당해 매핑한 메모리를 해제한다.
munmap메모리에 매핑된 (addr+len)크기만큼 메모리영역을 해제한다. 첫번째 인자 addr이 가리키는 메모리 주소가 mmap함수에 지정한 위치가 아니면 어떻게 동작할지 정의되어 있지 않다. munmap함수로 매핑을 해제한 메모리영역에 접근하면 SIGBUS 혹은 SIGSEGV 시그널이 발생한다.
8-2-3. 메모리 매핑의 보호 모드 변경 함수
메모리로 매핑된 영역의 보호모드를 변경시 mprotect함수를 쓴다.
- 보호모드 변경 : mprotect2(2)
#include <sys/mman.h>
int mprotect(void *addr, size_t len, int prot);
* addr : 매핑된 메모리의 시작주소
* prot : 보호모드(PROT_READ, PROT_WRITE, PROT_EXEC, PROT_NONE)
* len : 메모리 영역의 크기
매핑된 메모리의 보호 모드는 mmap함수로 메모리 매핑을 할 때 초기값을 설정한다. 이 함수는 addr로 지정한 주소에 len크기 만큼 매핑된 메모리의 보호모드를 prot에 지정한 값을 변경한다.
8-3. 파일의 확장과 메모리 매핑
존재하지 않거나 크기가 0인 파일은 mmap함수를 사용해서 메모리를 매핑할 수 없다.
프로그램에서 빈 파일을 생성했다면 mmap으로 메모리에 매핑하기 전에 파일의 크기를 확장해야한다.
8-3-1. 파일의 크기 확장 함수
- 경로명을 사용한 파일의 크기 확장 : truncate(3)
#include <unistd.h>
int truncate(const char *path, off_t length);
* path : 크기를 변경할 파일의 경로
* length : 변경할 크기
만일 파일의 원래 크기가 length 인자보다 크면 초과 부분은 버린다. 파일의 크기가 length값보다 작으면 파일의 크기를 증가시키고 해당 부분을 0으로 채운다. 이 함수를 사용하려면 경로로 지정한 파일에 쓰기 권한이 있어야한다.
이 함수는 파일의 오프셋을 변경하지 않는다.
- 파일 기술자를 사용한 파일의 크기 확장 : ftruncate(3)
#include <unistd.h>
int ftruncate(int fildes, off_t length);
* fildes : 크기를 변경할 파일의 파일기술자
* length : 변경하려는 크기
파딜의 기술자로 지정한 파일을 지정한 크기로 변경한다. 파일 기술자는 크기를 변경할 파일을 열고 리턴받은 값이어야한다.
이 함수는 regular file과 shared memory에서만 사용할 수 있다. 이 함수는 파일의 오프셋을 변경하지 않고, 디렉토리에 접근하거나 쓰기 권한이 없는 파일에 접근하면 오류가 생긴다.
8-4. 매핑된 메모리 동기화와 데이터 교환
유닉스 시스템은 메모리에 매핑된 파일의 내용을 백업 공간에 복사해둔다. 매핑된 메모리의 내용과 백업 내용이 일치하도록 동기화해야한다. 메모리 매핑 기능을 사용해 매핑한 데이터를 부모 프로세스와 자식 프로세스가 공유할 수 있어야한다. 공유를 사용하면 부모 프로세스와 자식 프로세스가 데이터를 주고 받을 수 있다.
즉, 프로세스간 통신에 메모리 매핑을 사용할 수 있다는 의미이다.
8-4-1. 매핑된 메모리의 동기화 함수
mmap함수로 메모리에 매핑된 파일의 내용은 백업 공간에 복사된다. MAP_SHARED 모드로 매핑한 메모리 영역의 백업 저장 장치는 파일이고, MAP_PRIVATE 모드로 매핑한 영역의 백업 저장 장치는 스왑 공간이다.
매핑된 메모리 영역과 백업 저장 장치의 내용이 일치하도록 동기화하는데는 msync함수를 사용한다.
- 매핑된 메모리 동기화 : msync(3)
#include <sys/mman.h>
int msync(void *addr, size_t len, int flags);
* addr : 매핑된 메모리의 시작주소
* len : 메모리 영역의 크기
* flags : 동기화 동작
- MS_ASYNC : 비동기 쓰기 작업 수행.msync함수는 즉시 리턴하고 함수가 리턴한 후 적절한 시점에 쓰기 작업을 수행한다.
- MS_SYNC : 쓰기 작업을 완료할 때까지 msync함수가 리턴되지 않는다. 메모리의 크기가 클경우 비교적 시간이 걸릴 수 있다.
- MS_INVAILDATE : 메모리에 복사된 내용을 무효화한다.
msync 함수는 인자로 지정한 addr로 시작하는 메모리 영역에서 [addr+len]만큼의 내용을 백업 저장 장치로 기록하도록 한다.