APUE에 보니까 그 이유가 나와있네요.
pp. 411-413 까지에 mmap을 이용한 파일복사 예제가 나와있는데,
output 파일의 사이즈를 설정할 필요가 있다고 합니다.
파일의 사이즈를 설정한다고 하는 것이 제가 생각하기로는 그 파일을 위한 공간을 할당한다는 의미로 해석을 해야 하는 것 같습니다. (사용자가 파일연산을 통하지 않고 곧바로 stat 정보의 file size 를 변경할 수 없지 않나요?) 
포인터를 사용해서 strcpy() 를 할 때, 대상 포인터가 가리키는 영역이 미리 할당되어 있어야 하는 것과 비슷한 생각이 드는데요. 여기서 포인터가 mmap한 주소가 되겠고, 실제파일영역이 mmap을 통해서 가리키는 저장공간이 되겠죠.

테스트를 해 보기 위해서 a.out 을 a.out2 라는 파일로 복사해 보았습니다.
% cp a.out a.out2
% a.out a.out a.out2
=> Bus Error

코드 상에 O_TRUNC 가 있어서 cp를 통해 a.out 을 a.out2 로 복사해 놓아도 open이 되면 0 byte로 변하게 되어서 여전히 Bus Error가 발생했습니다.
open의 O_TRUNC를 빼고 다시 컴파일해서 수행했더니 정상수행됐습니다.

복사하려는 파일보다 더 큰 크기를 가지는 파일을 대상파일로 설정해 보았습니다. 이 경우도 O_TRUNC는 빼 놓았구요.

% cp biggerfile cli.d
% ls -al cli cli.d
...
-rwxr-xr-x 1 ... 9888 1월 5일 1853 cli
-rw------- 1 ... 23720 3월 29일 1619 cli.d
% a.out cli cli.d
...
-rwxr-xr-x 1 ... 9888 1월 5일 1853 cli
-rw------- 1 ... 23720 3월 29일 1619 cli.d

정상수행됐지만 대상파일의 크기가 이전파일크기를 그대로 유지하네요. 예상됐던 결과지요. 아마 cli.d 의 앞쪽 9888 바이트만큼은 cli와 동일하겠죠?

결론. 
정상 수행을 위해서는 O_TRUNC 옵션을 빼면 안 되겠고, 
대상 파일크기 설정을 위해서는 대상파일이 원본파일만큼 크기를 가지도록 보장해 주어야 하겠고, lseek() 후 맨 끝에 한바이트 써주는 식으로 해주면 된다.

해당 원문입니다. 참조하세요.

Program 12.14 copies a file (similar to the cp(1) command) using memory mapped I/O. We first open both files and then call fstat to obtain the size of the input file. We need this size fo the call to mmap for the input file, plus we need to set the size of the output file. We call lseek and then write one byte to set the size of the output file. If we don't set the output file's size, the call to mmap for the output file is OK, but the fist reference to the associated memory region generates SIGBUS. ...

출 처 : http://kldp.org/node/1575

* 메모리 맵 - mmap() <파일 혹은 디바이스를 메모리와 대응>

; 파일(리눅스에서는 디바이스도 파일로 처리하므로 디바이스도 메모리 맵으로 연결 가능)을 처리하기 위해서는 보통 저수준으로는 파일 디스크립터를 이용하고, 고수준으로 접근하기 위해서는 파일 구조체 포인터를 이용하여 접근하게 된다. 하지만 이런 방식을 이용하면 버퍼를 거쳐서 실제 입출력을 하게된다. 하지만 mmap()을 이용하여 메모리 맵 방식으로 파일을 연결하게 되면 버퍼를 이용하는 것이 아니라 '페이지(page)'를 이용하여 데이터 처리가 가능해진다. 상대적으로 크기가 작은 버퍼에 비해 보통 4KB의 크기를 가지는 페이지를 이용하면 처리 가능한 크기와 처리 속도가 향상된다. 그렇기 때문에 데이터 크기가 크거나 빠른 처리를 해야하는 경우 메모리 맵 방식을 종종 사용하게된다. 메모리 맵을 사용하면 처리 속도가 빨라지는 예가 바로 처리하는 데이터가 큰 동영상을 볼 때이다. 그리고 실제 실행 파일도 윈도우든 리눅스든 실행 할 때 페이지로 분할되므로 메모리 맵 방식으로 매핑된다. 대표적으로 코드 영역은 데이터의 변경이 없기 때문에 메모리 맵을 이용해 맵핑하면 처리 속도는 물론이고 메모리 낭비도 없다. 

 

 

* 특징

1. 생성된 메모리 맵을 포인터를 이용하여 쉽게 사용 가능하다.

2. 파일로 연결하여 사용시 메모리-파일 사이의 동기화가 편하다.

3. IPC(프로세스간 통신)로 활용 가능하다.

4. 대용량의 데이터를 사용할 시 성능이 향상된다.

 

* 주의점

- 메모리 맵은 바로 파일의 처리하는게 아니라 가상 메모리로 활용되는 페이지에 맵핑하는 방식이다. 그러므로 파일과 해당 메모리 맵이 된 페이지가 다른 공간이다. 그러므로 커널에 의해 여유 시간에 동기화(둘의 데이터가 같아지는..)가 될 때까지 서로 다른 데이터를 가질 수 있다. 그러므로 동기화에 대한 주의를 할 필요가 있다. <개발자가 직접 커널에 동기화를 명령 할 수 있는 함수도 있다. - fsync()>

 IPC로 사용 할 때에도 프로세스간 동기화에 대한 주의도 필요하다.

 

* 2가지 방식

1. 공유 메모리 맵 방식 (Shred Memory-Map)

메모리 맵 변경 시 원본 파일과 데이터가 동기화가 되는 방식

2. 복사 메모리 맵 방식 (Private Memory-Map)

처음 메모리 맵에 매핑 될때 파일의 내용을 읽어와서 복사하고 그 이후 동기화 하지 않는 방식.

 

 

* 메모리 맵 생성 함수 - mmap()

 

void* mmap(void *state, size_t length, int prot, int flags, int fd, ott_t offset);

 

- void *state

; 할당받기 원하는 메모리 주소. 보통 0을 써서 커널이 적합한 공간을 임의로 할당해 주소를 받을 수 있고, 직접 입력하여 사용해도된다. 하지만 직접 입력하는 경우 해당 시스템의 페이지 (배수)단위로 주소 값을 설정해줘야 한다.

 

- size_t length

; 메모리 맵을 할 크기. 바이트 단위로 설정한다.

 

- int prot

; 메모리 보호 메커니즘. 플래그 형식이므로 비트 연산으로 복수 속성으로 지정 가능하다.

 + PROT_EXEC; 해당 페이지 실행 가능.

 + PROT_READ; 해당 페이지 읽기 가능.

 + PROT_WRITE; 해당 페이지 쓰기 가능.

 + PROT_NONE; 해당 페이지 접근 불가.

 => 매핑할 파일 디스크립터와 속성이 같아야한다.

 

- int flags

 + MAP_SHARED; 공유 메모리 맵 방식.

 + MAP_PRIVATE; 복사 메모리 맵 방식.

 + MAP_FIXED ; 메모리 시작 번지 지정시 사용.

 =>MAP_SHARED/MAP_PRIVATE 둘 중에 한개만 지정해야된다.

 

- int fd

 ; 메모리 맵 방식을 사용할 파일 디스크립터.(파일 혹은 디바이스)

 

- ott_t offset

 ; 해당 파일 디스크립터에서 메모리 맵을 시작할 오프셋 값.

 

 

+ return value

; 메모리 맵핑이 이루어진 가상 메모리 주소. 실패시 MAP_FAILED가 발생하고 errno에 해당 상황에 대한 값이 설정된다.

 

 

* 메모리 맵 해제 - munmap()

; 메모리 맵을 사용하고 자원을 해제 할 때 사용한다.

 

int munmap(void *start, size_t length);

 

- void *start

; 메모리 맵핑이 시작된 주소. mmap()의 반환 값을 넣으면된다.

 

- size_t length

; 메모리 맵을 한 길이. mmap() 사용시 size_t length 인자와 크기를 주면된다.

 

+ return value

;성공 0, 실패 -1

 

 

* 메모리 맵과 파일의 동기화 - fsync()

; 메모리 맵에 데이터를 갱신해도 바로 파일과 동기화가 이루어지는 것은 아니다. 커널이 여유 있을 때 동기화를 수행한다. 그러므로 개발자가 직접 동기화를 보장하고 싶을 때 사용한다. 그리고 munmap()을 하여 메모리 맵을 해제 할때에도 동기화를 해주면 데이터가 보장된다.

 

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

 

- void *start

; mmap()를 통해 리턴 받은 메모리 맵의 시작 주소.

 

-  size_t length

; 동기화를 할 길이. 시작 주소로 부터 길이를 지정하면 된다.

 

- int flags

 + MS_ASYNC

 ; 비동기 방식. 동기화(Memory->File)하라는 명령만 내리고 결과에 관계 없이 바로 리턴.

   그렇기 때문에 동기화가 된 건지 알수 없다.

 + MS_SYNC

 ; 동기 방식. 동기화(Memory->File)가 될때까지 블럭 상태로 대기한다.

 + MS_INVALIDATE

 ; 현재 메모리 맵을 무효화하고 파일의 데이터로 갱신. 즉 File->Memory

 

 

===================================================================================

       ...

#define MMAP_FULENAME "test_mmap"

#define MMAP_SIZE 64

       ...

int main()

{

     int fd;

     char *p_mmap;      //메모리 맵으로 사용할 데이터 타입

       ...

     fd = open(MMAP_FILENAME, O_RDWR|O_CREAT, 0664);

       ...

     p_mmap = (char*)mmap((void*)0, MMAP_SIZE, PROT_READ|PROT_WRITE,

                                                                     MAP_SHARED, fd, 0);

     //공유 메모리 방식을 사용한다. 읽기/쓰기 가능

       ...

     //memcpy(); 등으로 메모리 맵 데이터 갱신.

       ...

     msync(p_mmap, MAP_SIZE, MS_SYNC);

       ...

     munmap(p_mmap);

     return 0;

}

  

=> 메모리 맵 할 파일 이름만 같으면 IPC도 된다.


출 처 : http://no1rogue.blog.me/30097158983

+ Recent posts