[리눅스 프로그래밍] mmap 사용

Yoon Yeoung-jin·2022년 6월 18일
1

Linux

목록 보기
1/13

사용 헤더 파일

  • sys/mman.h

사용 함수

  • mmap
    void *mmap(void *addr, size_t length, int prot, int flags,int fd, off_t offset);
    
    파라미터
    * void *addr: 매핑될 주소
    * size_t length: 매핑할 길이
    * int prot: 파일에 대응되는 메모리 영역의 보호 특성을 결정하기 위한 값
    		- PROT_EXEC: 실행 가능한 페이지
    		- PROT_READ: 읽기 가능한 페이지
    		- PROT_WRITE: 쓰기 가능한 페이지
    		- PROT_NONE: 접근 불가능한 페이지
    * int flag: 매핑의 유형과 동작 구성 요소 결정 값
    		- MAP_FIXED: 기존 매핑과 겹칠 경우 중첩 페이지를 버리고 새 매핑으로 대체
    		- MAP_SHARED: 대응된 객체를 다른 모든 프로세스와 공유한다.
    		- MAP_PRIVATE: 데이터의 변경 내용을 공유하지 않는다.
        - MAP_ANONYMOUS: anonymous 모드로 진행
    * int fd: 파일 디스크립트
    * off_t offset: 매핑할 때, 옮길 데이터의 시작점 지정
    
    출력 값
    * 성공: 매핑된 주소
    * 실패: MAP_FAILED
  • munmap
    int munmap(void *addr, size_t length);
    
    파라미터
    * void *addr: 메모리 해제할 매핑 주소
    * size_t length: 매핑한 메모리를 해제할 길이 
    
    출력값
    * 성공: 0
    * 실패: -1

활용 용도

  • 다른 프로세스간 데이터의 교환 용도로 이용 가능
  • 파일 디스크립트를 직접 얻어서 하는 방식 (open, read 등)이 아닌 직접 메모리에 접근하여 데이터를 가져오는 방식으로 성능 향상을 기대할 수 있음.

anonymous 를 이용할 경우

  • mmap 함수에서 MAP_ANONYMOUS 플래그를 사용할 경우 특정 파일을 지정하지 않고 익명의 파일에 메모리를 매핑할 수 있다.
    • 이는 결국 FILE I/O 가 일어나지 않아 성능에서 이득을 볼 수 있다.
  • 이는 malloc 과는 다르게 fork()로 생성된 자식 프로세스들에게 해당 파일 디스크립터를 공유할 수 있다.
  • 사용 할 때 유의사항은 다음과 같다.
    • mmap 호출시 MAP_SHARED 와 같이 사용
    • mmap 호출 시 fd 값은 -1로 셋팅
    • mmap 호출시 offset argument 값은 무시된다.
    • “/dev/zero” 파일에 대해 mmap 하는 것은 anonymous mapping 과 같다.

예제 프로그램 1

  • 소스코드 (main.c)
/**
 * @file main.c
 * @author Yoon Yeoung Jin (alwns28@kookmin.ac.kr)
 * @brief 
 * @version 0.1
 * @date 2022-06-18
 * 
 * @copyright Copyright (c) 2022
 * 
 */

#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#include<unistd.h>
#include<errno.h>

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

typedef struct mmap_test_struct
{
    char name[20];
    int age;
    char hobby[30];
}MMAP_TEST_STRUCT;

#define FILE_PATH "/home/alwns28/mmap_example/mmap_test"

/**
 * @brief MMAP_TEST_STRUCT 구조체 출력 함수
 * 
 * @param in 출력할 MMAP_TEST_STRUCT 구조체
 */
void yj_print_struct(MMAP_TEST_STRUCT in)
{
    printf("SETTING DATA:\n");
    printf("    * name: %s\n", in.name);
    printf("    * age: %d\n", in.age);
    printf("    * hobby: %s\n", in.hobby);
    printf("================================\n");
}

/**
 * @brief MMAP_TEST_STRUCT 구조체 셋팅 함수
 * 
 * @param in_name 이름
 * @param in_age 나이
 * @param in_hobby 취미
 * @param out 셋팅된 MMAP_TEST_STRUCT 구조체
 */
void yj_setting_struct(const char *in_name, const int in_age, const char *in_hobby, MMAP_TEST_STRUCT *out)
{
    strncpy(out->name, in_name, sizeof(out->name));
    out->age = in_age;
    strncpy(out->hobby, in_hobby, sizeof(out->hobby));
}

/**
 * @brief 파일 생성 함수
 * 
 * @param message 입력할 MMAP_TEST_STRUCT 구조체 
 * @return int 0: 성공, -1: 실패
 */
int yj_create_file(const MMAP_TEST_STRUCT *message)
{
    int fd = 0;
    fd = open((const char *)FILE_PATH, O_CREAT|O_WRONLY, 0644);
    if(fd == -1){
        printf("open fail: %s\n", strerror(errno));
        return -1;
    }
    if(write(fd, (const void *)message, sizeof(*message)) == -1){
        printf("write fail: %s\n",strerror(errno));
        return -1;
    }
    close(fd);
    return 0;
}

/**
 * @brief 파일 읽는 함수
 * 
 * @return int 0: 성공, -1: 실패
 */
int yj_read_file()
{
    MMAP_TEST_STRUCT out;

    int fd = 0;
    int out_bytes;
    fd = open((const char *)FILE_PATH, O_RDONLY);
    if(fd == -1){
        printf("open fail: %s\n", strerror(errno));
        return -1;
    }
    if((out_bytes = read(fd, (void *)&out, sizeof(out))) == -1){
        printf("read fail: %s\n", strerror(errno));
        return -1;
    }
    yj_print_struct(out);
    close(fd);
    return 0;
}

/**
 * @brief 파일에 있는 데이터를 mmap을 사용하여 메모리에 쓰는 함수
 * 
 */
void yj_write_mmap()
{
    int fd = 0;
    off_t mmap_offset = 0;
    struct stat buf;

    fd = open((const char *)FILE_PATH, O_RDWR);
    if(fd == -1){
        printf("open fail: %s\n", strerror(errno));
        return ;
    }
    fstat(fd, &buf);
    if(mmap((void *)NULL, (size_t)buf.st_size, PROT_READ | PROT_WRITE | PROT_EXEC, MAP_SHARED,fd, mmap_offset) == MAP_FAILED){
        printf("mmap fail : %s\n", strerror(errno));
        return ;
    }
    close(fd);
}

/**
 * @brief mmap에서 데이터를 읽어와서 출력하는 함수
 * 
 * @param out mmap에서 읽어올 MMAP_TEST_STRUCT 구조체
 */
void yj_read_mmap(MMAP_TEST_STRUCT *out)
{
    int fd = 0;
    off_t mmap_offset = 0;
    struct stat buf;
    fd = open((const char *)FILE_PATH, O_RDWR);
    if(fd == -1){
        printf("open fail: %s\n", strerror(errno));
        return ;
    }
    fstat(fd, &buf);
    out = (MMAP_TEST_STRUCT *)mmap((void *)NULL, (size_t)buf.st_size, PROT_READ, MAP_SHARED, fd, mmap_offset);
    if(out == MAP_FAILED){
        printf("mmap fail : %s\n", strerror(errno));
        return ;
    }
    yj_print_struct(*out);
    if(munmap((void *)out, sizeof(out)) == -1){
        printf("munmap() fail: %s\n", strerror(errno));
    }

    close(fd);
}

int main(int argc, char **argv)
{
    MMAP_TEST_STRUCT in;
    MMAP_TEST_STRUCT out;

    printf("= STEP 1: Setting struct data \n");
    yj_setting_struct("YOON YEOUNG JIN", 26, "Play Room Escape game", &in);
    yj_print_struct(in);
    printf("= STEP 2: Write file \n");
    if(yj_create_file((const MMAP_TEST_STRUCT *)&in) == 0){
        printf("        ==> SUCCESS\n");
    } else {
        printf("        ==> FAIL\n");
    }
    yj_read_file();
    printf("= STEP 3: Write memory to use mmap() \n");
    yj_write_mmap();
    printf("================================\n");
    printf("= STEP 4: Read memory to use mmap() and other MMAP_TEST_STRUCT \n");
    yj_read_mmap(&out);
    return 0;
}
  • Makefile
CC = gcc -m64
CFLAGS += -Wall -O2 -pthread
OBJS = main.o
TARGET = main

all = $(TARGET)

$(TARGET) : $(OBJS)
	$(CC) $(CFLAGS) -o $@ $^

.c.o:
	$(CC) $(CFLAGS) -c $<

clean:
	rm *.o
	rm $(TARGET)
	find -type f ! -name "*.c" ! -name "Makefile" ! -name "hello_world" -exec rm {} \;
  • 실행 화면

예제 프로그램 2

#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#include<unistd.h>
#include<fcntl.h>

#include<sys/types.h>
#include<sys/wait.h>
#include<sys/stat.h>
#include<sys/mman.h>

#define MAX_BUF_LEN 4096
#define SLEEP_UTIME 1000
#define FORK_NUM 5

typedef struct anon_info{
    int pid;
    char Message[MAX_BUF_LEN];
} ANON_INFO;

typedef struct data_info{
    int size;
    char Message[MAX_BUF_LEN];
} DATA_INFO;

static void print_help(const char *progname)
{
    printf("Usage: %s [-fa] (monitor|send Message)\n", progname);
    printf("    options:\n");
    printf("        -f  (monitor|send Message) File_path \n");
    printf("        -a\n");   
}

static int do_monitoring(const char *file_path)
{
    int fd; 
    DATA_INFO *buf;
    DATA_INFO *tmp_buf;
    
    fd = open(file_path, O_CREAT | O_RDWR, 0644);
    if(fd == -1){
        perror("open()");
        return -1;
    }
    
    if(ftruncate(fd, sizeof(DATA_INFO)) == -1){ /* 파일을 넣을 데이터 만큼 늘려놓는다. */
        perror("ftruncate()");
        close(fd);
        return -1;
    }

    buf = (DATA_INFO *)mmap(NULL, sizeof(DATA_INFO), PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);
    if(buf == MAP_FAILED){
        perror("mmap()");
        close(fd);
        return -1;
    }

    buf->size = 0;
    memset(buf->Message, 0, sizeof(char) * MAX_BUF_LEN);
    
    close(fd);

    tmp_buf = (DATA_INFO *)malloc(sizeof(DATA_INFO));
    memset((void *)tmp_buf, 0, sizeof(DATA_INFO));
    memcpy((void *)tmp_buf, (const void *)buf, sizeof(DATA_INFO));
    printf("[monitor] client said [%s], Message size: %d\n", tmp_buf->Message, tmp_buf->size);

    while(1){
        if(sizeof(tmp_buf) < sizeof(buf)){
            free(tmp_buf);
            tmp_buf = (DATA_INFO *)malloc(sizeof(DATA_INFO));
            memset((void *)tmp_buf, 0, sizeof(DATA_INFO));
            memcpy((void *)tmp_buf, (const void *)buf, sizeof(DATA_INFO));
            printf("[monitor] client said [%s], Message size: %d\n", tmp_buf->Message, tmp_buf->size);
        } else {
            if(memcmp((const void *)tmp_buf, (const void *)buf, sizeof(DATA_INFO))){
                memcpy((void *)tmp_buf, (const void *)buf, sizeof(DATA_INFO));
                printf("[monitor] client said [%s], Message size: %d\n", tmp_buf->Message, tmp_buf->size);

            }
        }
        usleep(SLEEP_UTIME);
    }
    
    free(tmp_buf);
    munmap(buf, sizeof(DATA_INFO));
    return 0;
}

static int do_send_data(const char *file_path, const char *message)
{
    int fd;
    fd = open(file_path, O_RDWR);
    DATA_INFO *buf;
    if(fd == -1){
        perror("open()");
        return -1;
    }
    buf = (DATA_INFO *)mmap(NULL, sizeof(DATA_INFO), PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);
    if(buf == MAP_FAILED){
        close(fd);
        perror("mmap()");
        return -1;
    }
    buf->size = sizeof(message);
    strncpy(buf->Message, message, sizeof(buf->Message));
    munmap(buf, sizeof(DATA_INFO));
    close(fd);
    return 0;
}

ANON_INFO *mmap_init(void)
{
    ANON_INFO *info;
    info = mmap(NULL, sizeof(ANON_INFO), PROT_READ | PROT_WRITE, MAP_SHARED | MAP_ANONYMOUS, -1, 0);
    if(info == MAP_FAILED){
        perror("mmap()");
        return NULL;
    }
    memset((void *)info, 0, sizeof(ANON_INFO));
    return info;
}

static int anon_send_data(int index, ANON_INFO *info)
{
    sleep(index + 2);
    info->pid = getpid();
    snprintf(info->Message, sizeof(info->Message), "anonymous test %d", index);
    return 0;
}

static int anon_monitor(ANON_INFO *info)
{
    int n = 0;
    ANON_INFO local;

    memset((void *)&local, 0, sizeof(local));
    while(1){
        if(memcmp((const void *)&local, (const void *)info, sizeof(local))){
            printf("    * pid = %d, Message = %s\n", info->pid, info->Message);
            memcpy((void *)&local, (const void *)info, sizeof(local));
            n ++;
            if(n == FORK_NUM) break;
        }
        usleep(SLEEP_UTIME);
    }
    munmap((void *)info, sizeof(ANON_INFO));
    return 0;
}

static int do_anon(void)
{
    int i;
    pid_t pid;
    ANON_INFO *info;
    printf("* START ANONYMOUS MODE \n");
    info = mmap_init();
    for(i=0; i<FORK_NUM; i++){
        pid = fork();
        if(pid > 0){
            /* parent */
        } else if (pid == 0){
            /* child */
            anon_send_data(i, info);
            munmap((void *)info, sizeof(ANON_INFO));
            return 0;
        } else {
            perror("fork()");
            return -1;
        }
    }
    anon_monitor(info);
    printf("* END ANONYMOUS MODE\n");
    for(i=0; i<FORK_NUM; i++){
        pid = wait(NULL);
        if(pid == -1){
            perror("wait()");
            return -1;
        }
        printf("    [PID: %d] Terminate\n", pid);
    }
    return 0;
}

int main(int argc, char **argv)
{
    if(argc < 2){
        goto main_err;
    }
    switch (argv[1][1])
    {
    case 'f':
        if(argc < 4){
            goto main_err;
        }
        if(!strcmp((const char *)argv[2], "monitor")){
            do_monitoring((const char *)argv[3]);
        } else if(!strcmp((const char *)argv[2], "send")){
            if(argc < 5){
                goto main_err;
            }
            do_send_data((const char *)argv[4], (const char *)argv[3]);
        } else {
            goto main_err;
        }
        break;
    case 'a':
        /* anonymous mode */
        do_anon();
        break;
    default:
        goto main_err;
    }
    return 0;
main_err:
    print_help(argv[0]);
    return -1;
}
profile
신기한건 다 해보는 사람

0개의 댓글