Notice
Recent Posts
Recent Comments
Link
«   2025/01   »
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
Tags
more
Archives
Today
Total
관리 메뉴

꾸준히

mmap 본문

System Programming/IPC

mmap

S210530 2023. 6. 3. 18:38

mmap (memory mapped I/O)

mmap은 장치나 파일을 메모리와 대응시키는 기법이다
POSIX 시스템에서 mmap의 구현은 파일기술자(fd)를 포인터 변수에 대응시키는 방법을 사용한다
따라서 먼저 파일을 열고 거기서 얻어진 파일기술자를 mmap에 넘기는 방식을 사용한다
mmap 대응이 완료되면 파일의 내용에 접근할 때 read, write 관련 명령을 사용할 필요 없이 포인터 변수를 이용하여 접근할 수 있다

int fd = open("/tmp/myfile", O_RDWR, 0664);
char *p_map = mmap((void *)0, 8192, PROT_READ|PROT_WRITE, MAP_SHARED, fd, 0);
close(fd);

위 예시를 보면 먼저 파일을 오픈하고, mmap 함수를 호출하는 2단계를 거친다
코드를 설명하면 open 함수로 /tmp/myfile 파일을 열고, 성공하면 fd에는 파일기술자가 리턴된다
그 후 mmap 함수를 호출해서 fd에 메모리맵을 대응시키고 있다
mmap 호출이 성공하면 p_map에는 파일과 대응된 가상 메모리 주소가 리턴된다
이후에는 p_map에 데이터를 쓰면 /tmp/myfile도 변경된다. 반대로 파일의 내용을 수정하면 p_map의 내용도 변경된다

mmap의 속성

1. mmap의 권한
mmap은 파일과 맵핑되는 것이므로 똑같은 개념의 권한으로 rwx(read, write, execute)가 존재한다
다만, mmap은 파일이 아니라 페이지, 즉 메모리 영역이므로 보호하기 위한 개념이 되어 용어가 달라진다
mmap에서는 rwx에 대응하는 권한 부분을 프로텍션(protection)이라고 부른다

2. mmap과 가상 메모리
가상 메모리에 기반을 둔 현대적인 운영체제는 물리 메모리의 크기보다 더 큰 공간을 사용하기 위해 블록 장치(주로 디스크)의 일부를 물리 메모리와 교환할 수 있는데 이 과정을 스왑(swap)이라고 부른다
스왑에서 물리 메모리에 있는 페이지를 블록 장치로 보내는 경우를 페이지 아웃(page out) 혹은 스왑 아웃(swap out)이라고 부른다 반대의 과정은 페이지 인(page in) 혹은 스왑 인(swap in)이라고 한다

3. mmap의 공유 방식
mmap은 공유(shared)나 사설(private) 방식 중에 선택해서 생성할 수 있다

- 공유 방식
공유 방식인 shared mmap은 대응된 파일과 mmap이 지속적으로 동기화된다는 점이고, 사설 방식인 private mmap은 처음 생성할 때만 파일 내용이 mmap에 복사된 뒤에 동기화가 끊어진다는 점이다
공유 방식은 여러 개의 프로세스가 하나의 메모리와 파일을 사용하는 것처럼 작동한다 어떤 한 프로세스가 mmap을 변경하면 동일한 mmap을 보는 다른 모든 프로세스도 변경된 내용을 보게 된다 다만, 파일의 동기화에는 시간차가 있을 수 있기 때문에 mmap으로 대응된 원본 파일을 다른 프로세스에서 직접 읽을 때는 최신의 데이터가 아닐 수도 있다
공유 방식의 mmap은 항상 원본 파일이 블록 장치에 존재하기 때문에 스왑 아웃의 대상이 되지 않는 장점이 있다

- 사설 방식
사설 방식은 처음 mmap이 생성된 뒤에 프로세스가 독자적으로 사용하는 메모리 공간으로 전용할 수 있다
사설 방식 mmap은 생성 후에 내용을 변경하더라도 대응했던 파일에는 아무런 변화가 생기지 않는다
그러나 내용이 변경된 사설 방식의 mmap 페이지는 해당 프로세스 고유의 메모리 공간으로 전환되므로 스왑 아웃이 가능한 페이지로 전용되므로 메모리가 낭비되거나 성능상의 이점이 사라질 수 있다
사설 mmap을 사용하면서 성능을 위해 스왑 아웃을 금지하고자 한다면 mlock으로 페이징 금지를 걸어두어야만 한다


mmap의 사용법

fd = open(argv[1], O_RDWR|O_CREAT, 0664); // mmap에 대응할 파일 열기
....
p_map = mmap((void *)0, 64, PROT_READ|PROT_WRITE, MAP_SHARED, fd, 0);
if (p_map == MAP_FAILED) {
    // error code (mmap 실패)
}
close(fd); // mmap만을 이용한다면 기존 파일은 닫아도 됨

// 쓰기나 읽기에 관련된 코드는 여기에 들어간다
if (msync(p_map, 64, MS_SYNC) == -1) {
    // error code (동기화 실패)
}

if(munmap(p_map, 64) == -1) {
    // error code (mmap 해제 실패)
}

위 코드에서 mmap은 읽기, 쓰기 프로텍션 권한의 공유 방식(shared mmap)이며 크기는 64바이트이다
mmap의 함수 원형을 보자

void * mmap(void *start, size_t length, int prot, int flags, int fd, off_t offset);

mmap은 성공하면 할당된 메모리 주소 번지가 리턴되고 실패하면 MAP_FAILED가 리턴된다
일반적으로 포인터를 리턴하는 함수들은 NULL이 실패인데 mmap은 다르므로 주의해야 한다

인수에서 start는 mmap으로 리턴받기 원하는 가상 주소 시작 번지이다
보통 0을 설정하면 자동으로 알맞은 주소를 할당한다 만일 고정된 주소를 할당받고자 한다면 flags에 MAP_FIXED를 세팅하고 start에는 페이지 크기의 경계에 맞도록 입력한다

length는 mmap을 생성할 메모리 크기로서 대응할 대상 파일이 mmap의 최소 length보다는 커야 한다 만약 파일 크기가 더 작다면 에러가 발생한다

proto는 메모리 보호(protection) 권한을 설정하는 플래그이다
mmap의 proto는 먼저 오픈된 파일, 즉 대응하고자 하는 파일 기술자의 권한과 맞춰주는 것이 좋다
만약 파일기술자가 읽기 전용으로 열렸는데 PROTO_WRITE를 설정하면 오류가 발생한다

PROTO_READ 해당 데이터는 읽기 가능하다
PROTO_WRITE 해당 데이터는 쓰기 가능하다
PROTO_EXEC 해당 데이터는 실행 가능하다
PROTO_NONE 해당 데이터는 접근이 불가능하다


flag에는 작동에 관련된 설정을 할 수 있다
MAP_SHARED와 MAP_PRIVATE는 동시에 쓸 수 없고 이외에도 몇 가지 플래그들은 같이 사용할 수 없는 때도 있다

MAP_SHARED 공유 가능한 메모리 맵으로 지정한다
MAP_PRIVATE 사설 메모리 맵으로 지정한다
MAP_FIXED 원하는 메모리 시작 번지를 지정한다
MAP_ANONYMOUS 장치와 연결되지 않은 익명 mmap을 생성한다
MAP_LOCKED 페이지 락을 이용한다
MAP_UNINITIALIZED 익명 mmap으로 할당된 공간을 초기화하지 않는다

 

다음은 msync의 함수의 원형이다

int msync(void *start, size_t lenght, int flags);

msync는 앞에서 메모리맵 주소값 start로부터 length 길이만큼 동기화한다
아래와 같은 플래그를 사용할 수 있으며 MS_ASYNC와 MS_SYNC는 동시에 사용할 수 없다

MS_ASYNC msync 함수는 바로 리턴되며, 동기화는 비동기로 진행된다
따라서 함수 리턴 뒤에도 동기화는 예약될 뿐 보장할 수는 없다
MS_SYNC msync 함수는 동기화를 마칠 때까지 리턴되지 않는다
동기화는 바로 진행되며 리턴과 동시에 동기화의 완료가 보장된다
MS_INVALIDATE 메모리에 쓰인 값을 무효화하고 파일에서 다시 데이터를  로딩하여 메모리에 덮어쓰게 된다

 

mmap을 더는 필요로 하지 않는다면 munmap으로 해제할 수 있다
munmap은 현재 프로세스가 더 사용하지 않는 경우 연결을 끊는 의미일 뿐 실제로 시스템 어딘가에 있는 mmap 세그먼트를 삭제하는 것을 의미하지는 않는다 시스템에 있는 어떤 프로세스도 mmap을 쓰지 않는 경우에만 시스템은 mmap을 퇴출시키게 된다

int munmap(void *start, size_t length);

 

sample code

#define _GNU_SOURCE
#define _XOPEN_SOURCE	700
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <fcntl.h>
#include <errno.h>
#include <string.h>
#include <sys/mman.h>
#define MMAP_FILENAME	"mmapfile.dat"
#define MMAP_SIZE		4*1024*10

int main(int argc, char *argv[])
{
	int fd, flag_mmap = MAP_SHARED;
	char *p_map, a_input[100];

	// 파일 기술자 얻기
	if ((fd = open(MMAP_FILENAME, O_RDWR|O_CREAT, 0664)) == -1) {
		printf("Fail: open(): (%d:%s)\n", errno, strerror(errno));
		return EXIT_FAILURE;
	}

	// 파일 크기를 최소 mmap 크기로 늘린다
	if (ftruncate(fd, MMAP_SIZE) == -1) {
		printf("Fail: ftruncate(): (%d:%s)\n", errno, strerror(errno));
		return EXIT_FAILURE;
	}
	
	// mmap 생성
	if ((p_map = mmap((void *)0, MMAP_SIZE, PROT_READ|PROT_WRITE, flag_mmap, fd, 0)) == MAP_FAILED) {
		printf("Fail: mmap(): (%d:%s)\n", errno, strerror(errno));
		return EXIT_FAILURE;
	}
	close(fd);
	printf("* mmap file : %s\n", MMAP_FILENAME);
	
	while(1) {

		printf("'*' print current mmap otherwise input text to mmap. >>");
		if (fgets(a_input, sizeof(a_input), stdin) == NULL) {
			// error
		}
		
		if (a_input[0] == '*') {
			printf("Current mmap -> '%.*s'\n", MMAP_SIZE, p_map);
		}
		else {
			a_input[strlen(a_input)-1] = 0; // 개행 문자 제거
			memcpy(p_map, a_input, strlen(a_input));

			// 동기화
			if (msync(p_map, MMAP_SIZE, MS_SYNC) == -1) {
				printf("Fail: msync(): (%d:%s)\n", errno, strerror(errno));
				return EXIT_FAILURE;
			}
		}
	}
	
	return EXIT_SUCCESS;
}

공유된 mmap을 테스트하기 위해서는 적어도 2개 이상의 터미널을 열고 실행해야 한다
한 터미널에서 아무 내용이나 입력하고 다른 터미널에서 *를 입력해 내용을 확인한다

<터미널 1>

./mmap_ran
* mmap file : mmapfile.dat
'*' print current mmap otherwise input text to mmap. >>test mmap

<터미널 2>

./mmap_ran
* mmap file : mmapfile.dat
'*' print current mmap otherwise input text to mmap. >>*
Current mmap -> 'test mmap'

 

이는 하나의 mmap을 공유하고 있기 때문이며 만일 MAP_SHARED 대신 MAP_PRIVATE로 수정한다면 내용은 공유되지 않는다 당연히 MAP_PRIVATE로 만든 mmap은 파일의 내용을 변경시키지 않는다