본문 바로가기

프로그래밍(TA, AA)/C C++

[통신프로그래밍] 메모리 매핑

앞서 시스템프로그래밍 섹션에서 다루었던 프로그램은 대부분 단일 파일로 구성된 매우 간단한 것이었습니다. 하지만 업무 처리나 게임 등 실제 사용하는 응용 프로그램은 이렇게 간단하지 않습니다. 보통 하나 이상의 실행 파일이 생성되고, 이를 실행한 프로세스 사이에서 시그널이나 데이터를 주고받으며 작업을 처리합니다. 이렇게 프로세스 사이에서 데이터를 주고받으려면 통신 프로그래밍이 필요합니다. 통신 프로그래밍을 하면 유닉스 시스템에서 제공하는 다양한 통신 기능을 이용하는 응용 프로그램을 만들 수 있습니다. 유닉스 시스템이 제공하는 통신 기능은 크게 두가지로 구분할 수 있습니다. 하나는 동일한 시스템 안에 있는 프로세스 사이에서 통신을 수행하는 것이고, 두번째는 서로 다른 시스템에서 수행하고 있는 프로세스 사이에서 통신으로 데이터를 주고 받는 것입니다.


프로세스 간 통신

프로세스 간 통신(IPC, InterProcess Communication)은 동일한 유닉스 시스템 안에서 수행 중인 프로세스끼리 데이터를 주고받는 것입니다. IPC에는 파이프(pipe) 같은 특수 파일을 이용하거나, 메모리 매핑이나 공유 메모리 같은 메모리 영역을 이용하는 방법이 있습니다. 또한 메시지 큐, 공유 메모리, 세마포어 등 유닉스 시스템 V에서 제공하는 IPC 방법이 있습니다. 넓은 의미에서 프로세스 간 통신은 종료 상태(exit status)와 시그널 같은 정수값을 주고받는 것도 포함합니다. 자식 프로세스가 종료하면서 부모 프로세스에 종료 상태를 알리는 값을 보내는 것은 가장 간단한 형태의 프로세스 간 통신입니다. 종료 상태값은 대개 0이나 1이지만 프로그래머의 의도에 따라 다양한 정수값을 사용할 수 있습니다.


시그널 포스팅에서 살펴본 시그널도 프로세스 사이에서 데이터를 주고받으므로 프로세스간 통신 방법의 하나로 간주할 수 있습니다. 시그널의 경우 미리 정의된 상수를 주고받습니다. 이들 상수는 프로세스를 수행하는 도중에 프로세스에 알려야 할 다양한 이벤트를 정의해 놓은 것입니다. 시그널을 받은 프로세스는 시그널의 종류에 따라 적절히 대응해 작업할 수 있습니다.


네트워크를 이용한 통신

네트워크 환경이 발전하면서 메일이나 파일을 주고받거나, 클라이언트/서버 형태의 응용 프로그램 등 서로 다른 시스템 간 네트워크를 이용한 통신이 증가하고 있습니다. 유닉스 시스템에서 네트워크를 이요한 통신은 TCP/IP 프로토콜을 기본으로 하고 있으며, 소켓 라이브러리를 이용합니다. 이외에도 TLI(Transport Layer Interface)가 있지만 소켓 라이브러리에 밀려 거의 사용하지 않습니다. 소켓을 이용한 네트워크 프로그램은 이후에 다룹니다. 


여기에서는 다양한 프로세스 간 통신 방법 중 메모리 매핑을 이용한 통신 프로그래밍을 배워보겠습니다. 파일을 프로세스의 가상 메모리로 매핑하고 해제하는 함수, 파일의 크기를 조정하는 방법, 매핑된 메모리의 동기화 및 이를 이용해 데이터를 주고받는 방법을 다룹니다. 메모리 매핑 관련 함수로는 mmap, munmap, truncate, ftruncate, msync 등이 있습니다.


 기능

 함수원형

 메모리 매핑

 void *mmap(void *addr, size_t len, int prot, int flags, int fildes, off_t off);

 메모리 매핑 해제

 int munmap(void *addr, size_t len);

 파일 크기 조정

 int truncate(const char *path, off_t length);

 int ftruncate(int fildes, off_t length);

 매핑된 메모리 동기화

 int msync(void *addr, size_t len, int flags);



01. 메모리 매핑과 해제

메모리 매핑은 파일을 프로세스의 메모리에 매핑하는 것입니다. 즉, 프로세스에 전달한 데이터를 저장한 파일을 직접 프로세스의 가상 주소 공간으로 매핑합니다. 따라서 read나 write 함수를 사용하지 않고도 프로그램 내부에서 정의한 변수를 사용해 파일에서 데이터를 읽거나 쓸 수 있습니다. 이 절에서는 파일을 메모리에 매핑하는 방법과 매핑된 메모리를 해제하는 방법을 살펴봅니다.


메모리 매핑 함수


mmap 함수를 사용하면 파일을 프로세스의 가상 메모리에 매핑할 수 있습니다. 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: 보호 모드
  • flags: 매핑된 데이터의 처리 방법을 지정하는 상수
  • fildes: 파일 기술자
  • off: 파일 오프셋


mmap 함수는 fildes가 가리키는 파일에서 off로 지정한 오프셋부터 len 크기만큼 데이터를 읽어 addr이 가리키는 메모리 공간에 매핑합니다. prot에는 읽기, 쓰기 등 보호 모드를 지정하고 flags에는 읽어온 데이터를 처리하기 위한 정보를 지정합니다.


첫번째 인자인 addr에는 매핑하려는 주소를 직접 지정하거나, 그렇지 않을 경우 NULL을 사용합니다. NULL을 지정하면 시스템이 가장 적합한 메모리로 데이터를 매핑합니다. 두번째 인자인 len에는 매핑할 메모리 공간의 크기를 지정합니다. 예를 들어, 파일 전체를 매핑할 경우 파일의 크기를 지정합니다. 세 번째 인자인 prot에는 매핑한 데이터를 읽기만 할지, 쓰기나 실행도 허용할지 등 보호 모드를 지정합니다.


prot에 사용할 수 있는 값은 상수로 정의되어 있으며, OR 연산자로 연결해 지정할 수 있습니다. prot 인자에 사용할 수 있는 상수는 다음과 같습니다.


  • PROT_READ: 매핑된 파일을 읽기만 한다.
  • PROT_WRITE: 매핑된 파일에 쓰기를 허용한다.
  • PROT_EXEC: 매핑된 파일을 실행할 수 있다.
  • PROT_NONE: 매핑된 파일에 접근할 수 없다.


prot에 PROT_WRITE를 지정하려면 flags 인자에 MAP_PRIVATE를 지정하거나, 파일을 쓰기 가능 상태로 열어야 합니다.


네번째 인자인 flags에는 매핑된 데이터를 처리하기 위한 다른 정보를 지정합니다. flags에 지정할 수 있는 값도 상수로 정의되어 있으며, OR 연산자로 연결해 지정할 수 있습니다.


  • MAP_SHARED: 다른 사용자와 데이터의 변경 내용을 공유한다. 이 플래그가 설정되어 있으면 쓰기 동작은 매핑된 메모리의 내용을 변경한다.
  • MAP_PRIVATE: 데이터의 변경 내용을 공유하지 않는다. 이 플래그가 설정되어 있으면 최초의 쓰기 동작에서 매핑된 메모리의 사본을 생성하고 매핑 주소는 사본을 가리킨다. 따라서 MAP_SHARED를 지정한 다른 사용자는 내용을 변경하며 작업할 수 있습니다. MAP_SHARED와 MAP_PRIVATE 플래그 중 하나는 반드시 지정해야 한다.
  • MAP_FIXED: 매핑할 주소를 정확히 지정한다. MAP_FIXED 플래그를 지정하고 mmap 함수가 성공하면 해당 메모리 영역의 내용은 매핑된 내용으로 변경된다. 해당 메모리 영역에 매핑된 파일이 있다면, 이 파일의 매핑은 해제되고 새 파일이 매핑된다. 이 플래그는 시스템이 메모리를 효율적으로 사용할 수 없게 하므로 사용을 권장하지 않는다.
  • MAP_NORESERVE: MAP_PRIVATE를 지정하면 시스템은 매핑에 할당된 메모리 공간만큼 스왑 영역을 할당한다. 이 스왑 영역은 매핑된 데이터의 사본을 저장하는 데 사용한다. MAP_NORESERVE를 지정하면 매핑된 데이터를 복사해놓기 위한 스왑 영역을 할당하지 않는다. 따라서 복사된 데이터가 없으므로 매핑된 데이터를 수정하려고 하면 오류가 발생한다.
  • MAP_ANON: 이 플래그가 설정되어 있고, 파일 기술자가 -1이면 mmap 함수는 익명의 메모리 영역 주소를 리턴한다. 이는 매핑할 파일로 /dev/zero를 지정하는 것과 동일하다.
  • MAP_ALIGN: 메모리 정렬(memory alignment)을 지정한다. addr 인자에 메모리 정렬값을 지정한다. 이 값은 0 또는 sysconf 함수에서 리턴한 페이지 크기의 배수여야 한다. 0을 지정하면 시스템이 적절한 값을 선택한다.
  • MAP_TEXT: 이 플래그는 매핑된 메모리 영역을 명령을 실행하는 영역으로 사용할 것임을 시스템에 알린다. 예를 들어, 동적 라이브러리 등을 매핑할 수 있다.
  • MAP_INITDATA: 매핑된 메모리 영역을 실행 파일을 위한 초기 데이터 영역으로 사용할 것임을 지정한다.


mmap 함수의 다섯 번째 인자인 fildes는 매핑할 파일의 파일 기술자입니다. 여섯 번째 인자인 off는 매핑할 파일의 파일 오프셋입니다. mmap 함수는 페이지 단위로 메모리 매핑을 실행합니다. 매핑된 영역의 마지막 페이지에 남는 부분은 0으로 채웁니다. 매핑된 메모리 영역을 벗어난 공간에 접근하려고 하면 SIGBUS 혹은 SIGSEGV 시그널이 발생합니다.


mmap 함수는 수행을 성공하면 매핑된 메모리의 시작 주소를 리턴한다. 매핑된 영역의 크기는 [시작 주소 + len]이 됩니다. 실패하면 상수 MAP_FAILED를 리턴합니다. MAP_FAILED는 <sys/mman.h>에 정의되어 있습니다.


mmap 함수와 기존 방식의 비교

파일을 읽고 쓰는 데는 2장에서 배운 파일 입출력 함수를 사용할 수도 있습니다. 하지만 파일 입출력 함수로 파일에 접근하는 것과 mmap 함수를 사용하는 것은 분명히 다릅니다.


먼저 파일 입출력 함수는 보통 다음과 같이 사용합니다.

fd = open(...);
lseek(fd, offset, whence);
read(fd, buf, len);


즉, 파일을 열고 필요에 따라 파일의 오프셋을 이동시키고 read 함수를 호출해 데이터를 buf로 읽어와서 작업합니다. mmap 함수를 사용하는 경우에는 이 작업을 다음과 같이 할 수 있습니다.

fd = open(..);
address = mmap((caddr_t) 0, len, (PROT_READ | PROT_WRITE), MAP-PRIVATE, fd, offset);


일단 파일을 여는 것은 같습니다. 이후 열린 파일의 내용을 mmap 함수를 사용해 메모리에 매핑하고, 이후의 작업은 address가 가리키는 메모리 영역의 데이터를 대상으로 수행합니다. 즉, 매번 read 함수로 데이터를 읽어올 필요가 없습니다.


명령행 인자로 파일명을 입력받아 해당 파일을 열고 메모리에 매핑해 보겠습니다.

#include <sys/mman.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <stdlib.h>
#include <stdio.h>

int main(int argc, char *argv[]) {
    int fd;
    caddr_t addr;
    struct stat statbuf;

    if(argc != 2) {
        fprintf(stderr, "Usage : %s filename\n", argv[0]);
        exit(1);
    }

    if(stat(argv[1], &statbuf) == -1) {
        perror("stat");
        exit(1);
    }

    if((fd = open(argv[1], O_RDWR)) == -1) {
        perror("open");
        exit(1);
    }

    addr = mmap(NULL, statbuf.st_size, PROT_READ|PROT_WRITE, 
                MAP_SHARED, fd, (off_t)0);
    if(addr == MAP_FAILED) {
        perror("mmap");
        exit(1);
    }
    close(fd);

    printf("%s", addr);

    return 0;
}


메모리 매핑 해제 함수


munmap 함수는 mmap 함수를 사용해 매핑한 메모리 영역을 해제합니다. 메모리 매핑이 해제된 메모리 영역에 다시 접근하려고 하면 오류가 발생하므로 주의해야 합니다.


메모리 매핑 해제: munmap(2)

#include <sys/mman.h>

int munmap(void *addr, size_t len);

munmap 함수는 addr이 가리키는 영역에 len 크기만큼 할당해 매핑한 메모리를 해제합니다. 즉, munmap 메모리에 매핑된 [addr + len] 크기만큼 메모리 영역을 해제합니다. 첫번째 인자인 addr이 가리키는 메모리 주소가 mmap 함수에 지정한 위치가 아니면 어떻게 동작할지 정의되어 있지 않습니다. munmap 함수로 매핑을 해제한 메모리 영역에 접근하면 SIGBUS 혹은 SIGSEGV 시그널이 발생합니다. munmap 함수는 수행을 성공하면 0을, 실패하면 -1을 리턴합니다.


메모리 매핑을 해제하는 부분을 추가해 사용을 마친 메모리를 해제하는 프로그램을 만들어 보겠습니다.

#include <sys/mman.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <stdlib.h>
#include <stdio.h>

int main(int argc, char *argv[]){
    int fd;
    caddr_t addr;
    struct stat statbuf;

    if(argc != 2) {
        fprintf(stderr, "Usage : %s filename\n", argv[0]);
        exit(1);
    }

    if(stat(argv[1], &statbuf) == -1) {
        perror("stat");
        exit(1);
    }

    if((fd = open(argv[1], O_RDWR)) == -1) {
        perror("open");
        exit(1);
    }

    addr = mmap(NULL, statbuf.st_size, PROT_READ|PROT_WRITE, MAP_SHARED, fd, (off_t)0);

    if(addr == MAP_FAILED) {
        perror("mmap");
        exit(1);
    }
    close(fd);

    printf("%s", addr);

    if(munmap(addr, statbuf.st_size) == -1) {
        perror("munmap");
        exit(1);
    }

    printf("%s", addr);

    return 0;
}



메모리 매핑의 보호 모드 변경 함수


메모리로 매핑된 영역의 보호 모드를 변경할 대는 mprotect 함수를 사용합니다.


보호 모드 변경: mprotect(2)

#include <sys/mman.h>

int mprotect(void *addr, size_t len, int prot);


매핑된 메모리의 보호 모드는 mmap 함수로 메모리 매핑을 수행할 때 초깃값을 설정합니다. mprotect 함수는 addr로 지정한 주소에 len 크기만큼 매핑된 메모리의 보호 모드를 prot에 지정한 값으로 변경합니다. prot 인자에는 앞서 mmap 함수에서 설명한 PROT_READ, PROT_WRITE, PROT_EXEC, PROT_NONE을 사용합니다. mprotect 함수는 수행을 성공하면 0을, 실패하면 -1을 리턴합니다.




02. 파일의 확장과 메모리 매핑

존재하지 않거나 크기가 0인 파일은 mmap 함수를 사용해서 메모리에 매핑할 수 없습니다. 프로그램에서 빈 파일을 생성했다면 mmap으로 메모리에 매핑하기 전에 파일의 크기를 이 절에서는 파일의 크기를 확장하는 함수를 살펴봅니다. 



파일의 크기 확장 함수


파일의 크기를 변경할 때는 truncate 함수나 fruncate 함수를 사용합니다. 두 함수 모두 파일 크기를 확장하는 데는 물론 줄이는 데도 사용할 수 있습니다.



경로명을 사용한 파일의 크기 확장: truncate(3)

#include <unistd.h>

int truncate(const char *path, off_t length);

truncate 함수를 사용하면 path에 지정한 파일의 크기를 length로 지정한 크기로 변경할 수 있습니다. 만일 파일의 원래 크기가 length보다 크면 length 길이를 초과하는 부분은 버립니다. 파일의 크기가 length보다 작으면 파일의 크기를 증가시키고 해당 부분을 0으로 채웁니다. truncate 함수를 사용하려면 경로로 지정한 파일에 대한 쓰기 권한이 있어야 합니다. 이 함수는 파일의 오프셋을 변경하지 않습니다. truncate 함수는 수행을 성공하면 0을, 실패하면 -1을 리턴합니다.



파일 기술자를 사용한 파일의 크기 확장: ftruncate(3)

#include <unistd.h>

int ftruncate(int fildes, off_t length);

ftruncate 함수는 파일의 경로명 대신 파일 기술자를 받습니다. 즉, fildes에 지정한 파일의 크기를 length로 지정한 크기로 변경합니다. 이때 파일 기술자는 크기를 변경할 파일을 열고 리턴받은 값이어야 합니다. 파일의 원래 크기가 length보다 크면 length 길이를 초과하는 ㅂ분을 버립니다. 파일의 크기가 length보다 작으면 파일의 크기를 증가시키고 해당 부분을 0으로 채웁니다. ftruncate 함수는 일반 파일과 공유 메모리에만 사용할 수 있습니다. 이 함수는 파일의 오프셋을 변경하지 않습니다. ftruncate 함수로 디렉토리에 접근하거나 쓰기 권한이 없는 파일에 접근하면 오류가 발생합니다. ftruncate 함수는 수행을 성공하면 0을, 실패하면 -1을 리턴합니다.


새로 생성한 m.dat 파일의 크기를 페이지 크기만큼 증가시켜 메모리를 매핑하고, 매핑된 메모리에 문자열을 출력해보겠습니다.

#include <sys/types.h>
#include <sys/mman.h>
#include <fcntl.h>
#include <unistd.h>
#include <stdlib.h>
#include <stdio.h>
#include <string.h>

int main(void) {
    int fd, pagesize, length;
    caddr_t addr;

    pagesize = sysconf(_SC_PAGESIZE); //페이지 크기란?
    length = 1 * pagesize;

    if((fd = open("m.dat", O_RDWR | O_CREAT | O_TRUNC, 0666)) == -1) {
        perror("open");
        exit(1);
    }

    if(ftruncate(fd, (off_t) length) == -1){
        perror("ftruncate");
        exit(1);
    }

    addr = mmap(NULL, length, PROT_READ|PROT_WRITE, MAP_SHARED, fd, (off_t)0);
    if(addr == MAP_FAILED) {
        perror("mmap");
        exit(1);
    }

    close(fd);

    strcpy(addr, "Ftruncate Test\n");

    return 0;
}


03. 매핑된 메모리 동기화와 데이터 교환

유닉스 시스템은 메모리에 매핑된 파일의 내용을 백업 공간에 복사해둡니다. 따라서 매핑된 메모리의 내용과 백업 내용이 일치하도록 동기화해야 합니다. 메모리 매핑 기능을 사용해 매핑한 데이터를 부모 프로세스와 자식 프로세스가 공유하도록 할 수 있습니다. 공유를 사용하면 부모 프로세스와 자식 프로세스가 데이터를 주고받을 수 있습니다. 즉, 프로세스 간 통신에 메모리 매핑을 이용할 수 있는 것입니다. 이 절에서는 메모리 동기화와 데이터 교환 관련 함수를 살펴보겠습니다.



매핑된 메모리의 동기화 함수


mmap 함수로 메모리에 매핑된 파일의 내용은 백업 공간에 복사됩니다. MAP_SHARED 모드로 매핑한 메모리 영역에 백업 저장 장치는 파일이고, MAP_PRIVATE 모드로 매핑한 영역의 백업 저장 장치는 스왑 공간입니다. 매핑된 메모리 영역과 백업 저장 장치의 내용이 일치하도록 동기화하는 데는 msync 함수를 사용합니다.


매핑된 메모리 동기화: msync(3)

#include <sys/mman.h>

int msync(void *addr, size_t len, int flags);

msync 함수는 인자로 지정한 addr로 시작하는 메모리 영역에서 [addr + len]만큼의 내용을 백업 저장 장치로 기록하도록 합니다. flags는 msync 함수의 동작을 지시하는 값으로 다음 중 하나를 지정합니다.


  • MS_ASYNC: 비동기 쓰기 작업을 수행한다. msync 함수는 즉시 리턴하고, 함수가 리턴한 후 적절한 시점에 쓰기 작업을 수행한다.
  • MS_SYNC: 쓰기 작업을 완료할 때까지 msync 함수가 리턴하지 않는다. 메모리의 크기가 클 경우 비교적 시간이 걸릴 수 있다.
  • MS_INVALIDATE: 메모리에 복사되어 있는 내용을 무효화한다.


msync 함수는 수행을 성공하면 0을, 실패하면 -1을 리턴합니다. 다음은 매핑된 메모리의 내용을 일부 수정하고, 동기화 함수를 사용해 백업파일을 동기화하는 예제입니다.

#include <sys/mman.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <stdlib.h>
#include <stdio.h>

int main(int argc, char *argv[]) {
    int fd;
    caddr_t addr;
    struct stat statbuf;

    if(argc != 2) {
        fprintf(stderr, "Usage : %s filename\n", argv[0]);
        exit(1);
    }

    if(stat(argv[1], &statbuf) == -1) {
        perror("stat");
        exit(1);
    }

    if((fd = open(argv[1], O_RDWR)) == -1) {
        perror("open");
        exit(1);
    }

    addr = mmap(NULL, statbuf.st_size, PROT_READ|PROT_WRITE, MAP_SHARED, fd, (off_t)0);

    if (addr == MAP_FAILED) {
        perror("mmap");
        exit(1);
    }
    close(fd);

    printf("%s", addr);

    printf("-----------\n");
    addr[0] = 'D';
    printf("%s", addr);

    msync(addr, statbuf.st_size, MS_SYNC);

    return 0;
}
  • 36행: 매핑한 메모리의 내용을 출력한다.
  • 39행: 매핑한 메모리 영역의 첫번째 바이트를 수정한다. 메모리 매핑 기능을 이용하면 데이터의 내용을 배열로 처리할 수 있다.
  • 40행: 변경된 내용을 출력한다.
  • 42행: msync 함수를 MS_SYNC 모드로 실행해 동기화 작업을 수행한다.
  • 실행 경과를 보면 mmap.dat 파일의 내용이 변경되었음을 확인할 수 있다.



메모리 매핑을 이용한 데이터 교환


메모리 매핑 함수를 사용하면 매핑된 파일을 부모 프로세스와 자식 프로세스가 공유하도록 만들 수 있습니다. 즉, 부모 프로세스나 자식 프로세스가 매핑된 메모리의 내용을 변경하면 다른 프로세스도 변경 내용르 알 수 있습니다.


메모리에 매핑된 파일을 두 프로세스가 공유해 데이터를 주고받게 만들어보겠습니다.

#include <sys/types.h>
#include <sys/mman.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <stdlib.h>
#include <stdio.h>

int main(int argc, char *argv[]) {
    int fd;
    pid_t pid;
    caddr_t addr; // a legacy BSD type(low level calls like mmap)
    struct stat statbuf;

    if(argc != 2) {
        fprintf(stderr, "Usage : %s filename\n", argv[0]);
        exit(1);
    }

    if(stat(argv[1], &statbuf) == -1) { //stat 함수로 파일의 속성 정보를 검색해 파일의 크기를 알아낸다.
        perror("stat");
        exit(1);
    }

    if((fd = open(argv[1], O_RDWR)) == -1) { // 메모리에 매핑할 데이터 파일을 연다.
        perror("open");
        exit(1);
    }
    // 파일을 메모리에 매핑한다.
    addr = mmap(NULL, statbuf.st_size, PROT_READ|PROT_WRITE, MAP_SHARED, fd, (off_t)0);

    if(addr == MAP_FAILED) {
        perror("mmap");
        exit(1);
    }
    close(fd);

    switch (pid = fork()) { // fork 함수를 실행해 자식 프로세스를 생성한다. 자식 프로세스는 부모 프로세스를 상속하므로 매핑된 메모리의 시작 주소인 addr을 공유한다.
        case -1: /* fork failed */
            perror("fork");
            exit(1);
            break;
        case 0: /* child process */
            printf("1. Child Process : addr=%s", addr);
            sleep(1);
            addr[0] = 'x';  // 자식 프로세스가 addr[0]의 내용을 'x'로 변경한다. 54행에 해당하는 출력 내용을 보면 자식 프로세스가 변경한 값을 부모 프로세스로 읽을 수 있음을 확인할 수 있다.
            printf("2. Child Process : addr=%s", addr);
            sleep(2);
            printf("3. Child Process : addr=%s", addr);
            break;
        default: /* parent process */
            printf("1. Parent process : addr=%s", addr);
            sleep(2);
            printf("2. Parent process : addr=%s", addr);
            addr[1] = 'y';  // 46행과 반대로 부모 프로세스가 addr[1]의 내용을 'y'로 변경한다.
            printf("3. Parent process : addr=%s", addr);
            break;
    }
}

[root@dev-web ch08]# ./ex8_5.out mmap.dat

1. Parent process : addr=DANBIT BOOK

1. Child Process : addr=DANBIT BOOK

2. Child Process : addr=xANBIT BOOK

2. Parent process : addr=xANBIT BOOK

3. Parent process : addr=xyNBIT BOOK

3. Child Process : addr=xyNBIT BOOK


실행 결과를 보면 변경 내용을 부모 프로세스와 자식 프로세스가 각각 읽고 있음을 알 수 있습니다. 이 예제에서는 부모 프로세스와 자식 프로세스가 간단하게 출력만 수행하기 때문에 sleep 함수를 사용해 실행 시간을 조정했습니다. 실제로는 wait 함수를 사용하거나 시그널을 사용해 읽고 쓰는 순서를 조조정해야 합니다.


요약

통신 프로그램의 개념 

두 개 이상의 프로세스 사이에서 시그널이나 데이터를 주고받으며 작업을 처리하려면 통신 기능을 이용해 프로그래밍해야 합니다. 유닉스 시스템이 제공하는 통신 기능은 크게 두 가지로 구분할 수 있습니다.


  • 프로세스 간 통신: 동일한 시스템 안에서 프로세스 사이에서 통신을 수행한다.
  • 네트워크 통신: 서로 다른 시스템에서 수행하고 있는 프로세스 사이에서 통신을 수행한다.



프로세스 간 통신

프로세스 간 통신(IPC, InterProcess Communication)은 동일한 유닉스 시스템 안에서 수행 중인 프로세스끼리 데이터를 주고받는 것입니다. IPC에는 메모리 매핑, 파이프, 시스템 V에서 제공하는 메시지 큐, 공유 메모리, 세마포어 등의 방법이 있습니다.



네트워크를 이용한 통신

유닉스 시스템에서 서로 다른 시스템 간 네트워크를 이용한 통신은 TCP/IP 프로토콜을 기본으로 하고 있습니다. TCP/IP 프로토콜을 이용한 프로그램을 작성할 수 있도록 제공되는 인터페이스에는 소켓 인터페이스와 TLI가 있으나 주로 소켓 인터페이스를 사용합니다.



메모리 매핑 관련 함수

메모리 매핑 기능은 파일을 프로세스의 가상 주소 공간으로 매핑하는 것이다. 따라서 read나 write 함수를 사용하지 않고도 프로그램에서 데이터를 읽거나 쓸 수 있다. 메모리 매핑 함수를 사용하면 매핑된 파일을 부모 프로세스와 자식 프로세스에서 공유하도록 만들고 이를 이용해 통신할 수 있습니다.