HOWTO · C

mmap 함수를 사용하여 C에서 메모리에 쓰기

이 기사는 mmap() 함수에 대해 교육하고 이 함수를 사용하여 C에서 메모리에 쓰는 방법을 보여줍니다.

이 페이지의 내용

프로세스에서 mmap()는 메모리 매핑을 만듭니다. 쓰기 가능한 익명 매핑을 C 데이터용 메모리로 사용하거나, 공유 익명 매핑을 사용해 관련 프로세스 간에 변경 사항을 전달할 수 있습니다. 이 가이드에서는 여섯 인수와 매핑 실패 확인 방법을 설명하고, munmap()로 매핑을 해제하는 방법을 보여줍니다.

mmap()의 기능과 반환값

매핑은 프로세스의 가상 주소 공간 중 일정 범위를 파일, 장치 또는 익명 메모리에 연결합니다. 익명 메모리는 파일을 기반으로 하지 않으므로 변경 사항이 파일에 저장되지 않습니다. mmap()와 관련 상수를 사용하려면 <sys/mman.h>를 포함하세요.

이 함수는 여섯 인수를 받으며, 성공하면 새 매핑의 주소를 반환합니다. 실패하면 MAP_FAILED를 반환하므로 NULL이나 0이 아닌 이 실패 표시값과 비교해야 합니다. 호출이 실패하면 errno도 설정될 수 있으며, 이를 통해 실패 원인을 파악할 수 있습니다.

mmap()의 인수와 플래그 이해하기

인수는 순서대로 address, length, prot, flags, fd, offset입니다.

  • address는 시작 주소의 제안값입니다. NULL을 전달하면 커널이 적절한 주소를 선택합니다. 일반적으로 애플리케이션은 이렇게 사용합니다.
  • length는 매핑할 바이트 수입니다. 커널이 매핑 범위의 페이지 정렬을 처리하지만, 매핑을 해제할 때는 요청한 길이를 일관되게 사용하세요.
  • prot는 접근 권한을 설정합니다. 읽고 수정할 데이터에는 PROT_READ와 PROT_WRITE를 함께 지정하세요. 실행 가능한 메모리가 필요한 경우에만 PROT_EXEC를 추가합니다.
  • flags는 공유 방식과 메모리의 기반을 선택합니다. MAP_PRIVATE를 사용하면 변경 사항이 해당 프로세스에만 적용되고, MAP_SHARED를 사용하면 공유 매핑을 통해 변경 사항을 볼 수 있습니다. MAP_ANONYMOUS는 파일을 기반으로 하지 않는 메모리를 만듭니다. 이 플래그는 보통 두 공유 방식 플래그 중 하나와 함께 사용합니다.
  • fd는 파일 기반 매핑에서 파일을 지정합니다. 일반적인 Unix 계열 시스템에서는 MAP_ANONYMOUS를 사용할 때 파일 디스크립터가 무시되므로 예제에서 흔히 -1을 전달합니다.
  • offset은 파일에서 매핑을 시작할 바이트 위치를 지정합니다. 파일 기반 매핑에서는 시스템 페이지 크기의 배수여야 합니다. 익명 매핑에는 0을 사용하세요.

특정 주소 공간을 세심하게 제어해야 하는 경우가 아니라면 MAP_FIXED 사용을 피하세요. 지정된 범위의 기존 매핑을 대체할 수 있습니다. 이러한 플래그와 fork()는 여러 Unix 계열 시스템에서 제공되지만 모든 C 환경에서 사용할 수 있는 것은 아닙니다.

mmap()로 익명 메모리에 쓰기

이 완전한 예제는 정수 다섯 개를 저장할 공간을 매핑하고, 값을 쓰고 읽은 뒤 요청한 범위와 정확히 같은 크기로 매핑을 해제합니다. MAP_ANONYMOUS를 제공하는 Unix 계열 시스템을 전제로 합니다. 성공적으로 실행되면 아래에 표시된 출력을 출력합니다.

이 섹션의 코드와 표시되는 레이블 또는 출력은 영어로 유지됩니다.

#include <stdio.h>
#include <sys/mman.h>

int main(void) {
    const size_t count = 5;
    const size_t length = count * sizeof(int);
    int *values = mmap(NULL, length, PROT_READ | PROT_WRITE,
                       MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);
    if (values == MAP_FAILED) {
        perror("mmap");
        return 1;
    }

    for (size_t i = 0; i < count; ++i) {
        values[i] = (int)(i * 10);
    }
    for (size_t i = 0; i < count; ++i) {
        printf("[%d] ", values[i]);
    }
    putchar('\n');

    if (munmap(values, length) == -1) {
        perror("munmap");
        return 1;
    }
    return 0;
}
[0] [10] [20] [30] [40]

매핑에 PROT_READ와 PROT_WRITE를 함께 지정했으므로 읽고 쓸 수 있습니다. 다른 프로세스가 쓰기 내용을 볼 필요가 없으므로 MAP_PRIVATE가 적절합니다. munmap()에는 매핑 주소와 처음 요청한 길이를 전달해야 합니다. 요소 개수를 전달하는 함수가 아닙니다.

프로세스 간에 익명 매핑 공유하기

MAP_SHARED로 만든 매핑을 사용하면 fork() 이후에도 변경 사항을 공유할 수 있습니다. 자식 프로세스와 부모 프로세스는 같은 매핑 영역에 접근합니다. 출력을 리디렉션했을 때 초기 줄이 중복되지 않도록 예제는 fork() 전에 출력을 비웁니다. 부모는 자식이 변경한 값을 읽기 전에 자식이 끝날 때까지 기다립니다. 앞선 예제와 마찬가지로 이 예제도 MAP_ANONYMOUS와 fork()를 지원하는 Unix 계열 환경을 전제로 합니다. 제공된 실행에서 아래에 표시된 출력이 나왔습니다.

이 섹션의 코드와 표시되는 레이블 또는 출력은 영어로 유지됩니다.

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

int main(void) {
    const size_t count = 5;
    const size_t length = count * sizeof(int);
    int *values = mmap(NULL, length, PROT_READ | PROT_WRITE,
                       MAP_SHARED | MAP_ANONYMOUS, -1, 0);
    if (values == MAP_FAILED) {
        perror("mmap");
        return 1;
    }

    for (size_t i = 0; i < count; ++i) {
        values[i] = (int)(i + 7);
    }
    printf("Initial values:");
    for (size_t i = 0; i < count; ++i) {
        printf(" %d", values[i]);
    }
    putchar('\n');
    fflush(stdout);

    pid_t child_pid = fork();
    if (child_pid == -1) {
        perror("fork");
        munmap(values, length);
        return 1;
    }
    if (child_pid == 0) {
        for (size_t i = 0; i < count; ++i) {
            values[i] *= 5;
        }
        if (munmap(values, length) == -1) {
            _exit(1);
        }
        _exit(0);
    }

    int status;
    if (waitpid(child_pid, &status, 0) == -1) {
        perror("waitpid");
        munmap(values, length);
        return 1;
    }
    if (!WIFEXITED(status) || WEXITSTATUS(status) != 0) {
        fputs("child process failed\n", stderr);
        munmap(values, length);
        return 1;
    }

    printf("Updated values:");
    for (size_t i = 0; i < count; ++i) {
        printf(" %d", values[i]);
    }
    putchar('\n');

    if (munmap(values, length) == -1) {
        perror("munmap");
        return 1;
    }
    return 0;
}
Initial values: 7 8 9 10 11
Updated values: 35 40 45 50 55

자식 프로세스는 공유 매핑에 값을 쓰고 종료합니다. waitpid()가 완료된 뒤 부모는 같은 변경 사항을 읽습니다. MAP_PRIVATE 매핑으로는 이러한 공유 동작을 구현할 수 없습니다. 두 프로세스는 종료하기 전에 각자 매핑을 해제합니다. 파일 기반 매핑에서 MAP_SHARED를 사용하면 매핑을 통해 변경 사항을 확인할 수 있지만, 파일에 변경 사항을 영구 저장하려면 추가 동기화와 파일 시스템 관련 고려 사항이 필요합니다.