mmap() 에 대해서
Memory Mapped I/O
는 마이크로프로세서(CPU)가 입출력 장치를 접근할 때, 입출력과 메모리의 주소 공간을 분리하지 않고 하나의 메모리 공간에 취급하여 배치하는 방식이다. -위키피디아-
회사에서 검색 엔진 공부를 하고 있는데, 엔진 내부에서 데이터 사용을 위하여 Memory Store를 사용함.
여기서 mmap
을 사용하여 데이터 파일과 메모리를 동기화 하여 사용하고 있는데, mmap 에 대해서 명확하게 이해하기 위해서 정리를 해 봄
Memory Mapped I/O란?
- 표준 파일 입출력의 대안으로 Application 의
파일
을메모리
에 맵핑할 수 있는 인터페이스. 메모리 주소
-파일 단어
사이가 1대1 대응이 된다는 것- → 이를 통해서 Application(CPU) 에서는 데이터를 접근할 때 메모리에 상주하는 데이터처럼 메모리를 통해 파일에 직접 접근할 수 있음.
- 또한 메모리 주소에 직접 쓰는 것만으로 디스크에 있는 파일에 기록할 수 있음.
mmap()
mmap()
을 호출하면 fd가 가리키는 파일의 offset 위치에서 len 바이트만큼 메모리에 맵핑하도록 커널에 요청한다.
1
2
3
4
5
6
7
8
#include <sys/mman.h>
void * mmap (void *addr,
size_t len,
int prot,
int flags,
int fd,
off_t offset);
addr
- addr가 포함되면 메모리에서 해당 주소를 선호한다고 커널에 알려줌
- 그저 힌트일 뿐이며 대부분
0
을 넘겨줌
len
- fd 가 가리키는 파일의
offset
위치에서len
바이트만큼 메모리에 맵핑하도록 커널에 요청함.
- fd 가 가리키는 파일의
prot
- 접근권한을 지정
맵핑에 원하는 메모리 보호 정책을 명시
PROT_NONE
: 접근 불가PROT_READ
: 읽기 가능PROT_WRITE
: 쓰기 가능PROT_EXEC
: 실행 가능
flag
- 맵핑의 유형과 그 동작에 관한 몇 가지 요소를 명시
MAP_FIXED
: mmap()의 addr 인자를 힌트가 아니라 요구사항으로 취급하도록 함MAP_PRIVATE
: 맵핑이 공유되지 않음을 명시. 파일은 copy-on-write 로 맵핑됨.MAP_SHARED
: 같은 파일을 맵핑한 모든 프로세스와 맵핑을 공유MAP_SHARED
와MAP_PRIVATE
를 함께 지정하면 안됨.
- 반환
- 메모리 맵핑의 실제 시작 주소를 반환한다.
- fd를 맵핑하면 해당 파일의 참조 카운터가 증가한다. → 따라서 파일을 맵핑한 후에 fd를 닫더라도 프로세스는 여전히 맵핑된 주소에 접근할 수 있다.
- 예시
- fd가 가리키는 파일의 첫 바이트부터 len 바이트까지를 읽기 전용으로 맵핑한다.
1 2 3 4 5
void *p; p = mmap (0, len, PROT_READ, MAP_SHARED, fd, 0); if (p == MAP_FAILED) perror ("mmap");
- mmap() 에 전달하는 인자가 맵핑하는 과정
How does mmap work on?
- mmap 실행 시, Virtual Memory Address에 file 주소 매핑
- 해당 메모리 접근 시 1) Page Fault Interrupt 발생 / 2) OS에서 File data 복사해서 Physical Memory 에 넣어줌
- 메모리 Read 시, 해당 물리 페이지 데이터를 읽음
- 메모리 Write 시, 해당 물리 페이지 데이터 수정 후, 페이지 상태의 dirty bit 가 1로 수정 됨
- File Close 시, 물리 페이지 데이터가 File에 업데이트 됨
- 궁금한 점
- Process의 메모리 공간에서 정확히 어떤 위치에 어떻게 매핑이 되는지?
- mmap 을 사용할 때
flag
의MAP_SHARED
와MAP_PRIVATE
에서 구분됨. - MAP_SHARED 를 사용했을 때는 Process의 Shared Memory 영역에 매핑이 되고, MAP_PRIVATE를 사용하면 Private Memory 영역에 매핑이 되는데 Private Memory 영역은 프로세스의 Data 영역과 Heap 영역을 의미함.
- mmap 을 사용할 때
- mmap으로 매핑된 파일 write 을 수행하고, 동시에 해당 파일을 read() 했을 때 동기화가 바로 되는 것인지?
- 예시)
- A 프로세스가 mmap() 으로 foo.txt 파일을 메모리로 읽어서 수정 작업을 진행함.
- 동시에 B 프로세스가 fopen() 으로 동일한 foo.txt 파일을 읽고 있음.
- 그럼 일관성이 유지되면서 동기화가 될까?
- → fopen() 과 mmap() 은 메커니즘이 다르기 때문에 race condition이 일어날 수 있음.
- mmap() 으로 file 을 읽고 수정했을 때, 실제로 해당 변경이 Disk에 있는 파일로 동기화 되는 타이밍은 아래와 같음
MAP_PRIVATE
를 사용한 경우- 수정 작업은 메모리에만 적용되며, 디스크 파일은 수정되지 않음
MAP_SHARED
를 사용한 경우- 메모리에 반영되면서, 프로세스들이 동시에 접근하는 파일 시스템을 통해 디스크 파일에도 자동으로 반영됨.
- 예시)
munmap()
mmap()
으로 생성한 맵핑을 해제하기 위한munmap()
시스템 콜을 제공함.
1
2
3
#include <sys/mman.h>
int munmap (void *addr, size_t len);
- 페이지 크기로 정렬된
addr
에서 시작해서len
바이트만큼 이어지는 프로세스 주소 공간에 존재하는 페이지를 포함하는 맵핑을 해제함.- 맵핑 해제하고 다시 접근하면
SIGSEGV
시그널이 발생함.
- 맵핑 해제하고 다시 접근하면
- 성공 시 0을 반환, 실패 시 -1을 반환하고 errno 설정
- 예제
[addr, addr+len]
사이에 포함된 페이지를 담고 있는 메모리 영역에 대한 맵핑을 해제함.
1 2
if (munmap (addr, len) == −1) perror ("munmap");
- exit() 에 의해서 자동으로 munmap 이 수행되긴 하지만, 그래도 직접 해주자
맵핑 예제
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
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <sys/mman.h>
// 인자로 파일 이름을 받음
int main (int argc, char *argv[])
{
struct stat sb;
off_t len;
char *p;
int fd;
if (argc < 2) {
fprintf (stderr, "usage: %s <file>\n", argv[0]);
return 1;
}
fd = open (argv[1], O_RDONLY); // 인자로 넘겨받은 파일을 연다
if (fd == −1) {
perror ("open");
return 1;
}
if (fstat (fd, &sb) == −1) { // fstat : 주어진 파일에 대한 정보 반환
perror ("fstat");
return 1;
}
if (!S_ISREG (sb.st_mode)) { // 주어진 파일이 디바이스 파일이나 디렉터리가 아닌 일반 파일인지 점검
fprintf (stderr, "%s is not a file\n", argv[1]);
return 1;
}
p = mmap (0, sb.st_size, PROT_READ, MAP_SHARED, fd, 0); // 맵핑 수행
if (p == MAP_FAILED) {
perror ("mmap");
return 1;
}
if (close (fd) == −1) {
perror ("close");
return 1;
}
for (len = 0; len < sb.st_size; len++)
putchar (p[len]);
if (munmap (p, sb.st_size) == −1) {
perror ("munmap");
return 1;
}
return 0;
}
mmap()의 장점
read()
와write()
시스템 콜을 사용하는 것보다mmap()
을 이용해서 파일을 조작하는 것이 좀 더 유용하다.- read, write 시스템 콜 사용할 때 발생하는 불필요한 복사를 방지할 수 있음.
- 사용자 영역의 버퍼로 데이터를 읽고 써야 하기 때문에 추가적인 복사가 발생함.
- (잠재적인 페이지 폴트 가능성을 제외하면) 시스템 콜 호출이나 컨텍스트 스위칭 오버헤드가 발생하지 않음
- 여러 개의 프로세스가 같은 객체를 메모리에 맵핑한다면 데이터는 모든 프로세스 사이에서 공유된다.
lseek()
같은 시스템 콜을 사용하지 않고도 맵핑영역 탐색 가능
mmap()의 단점
- 메모리 맵핑은 항상 페이지 크기의 정수배만 가능하다.
- 예) 페이지 크기가 4k 이고 7byte를 맵핑하면 4089 byte가 낭비됨!
- 메모리 맵핑은 반드시 프로세스의 주소 공간에 딱 맞아야한다.
- 다양한 사이즈의 맵핑이 있다면
파편화
가 일어남
- 다양한 사이즈의 맵핑이 있다면
- 메모리 맵핑과 관련 자료구조를 커널 내부에서 생성, 유지하는데 오버헤드가 발생한다.
이중 복사 제거 방법으로 방지할 수 있음
읽기 요청마다 표준 입출력 버퍼를 가리키는 포인터를 반환하는 대체 구현을 통해 데이터를 표준 입출력 버퍼에서 직접 읽을 수 있음 → 불필요한 복사 피함
→ 맵핑하려는 파일이 크거나 (낭비되는 공간이 전체 맵팽에서 낮은 비율일 때), 맵핑된 파일의 전체 크기가 페이지 크기로 딱 맞아 떨어질 때 (낭비되는 공간이 없는 경우) mmap() 의 장점을 극대화할 수 있음
Reference
- https://www.youtube.com/watch?v=8hVLcyBkSXY
- https://bannavi.tistory.com/80
- 시스템 프로래밍 책