Memory mapping
Linux

Memory mapping

Memory mapping

Memory mapping 은 파일을 프로세스의 메모리에 매핑하는 것이다. 일반적으로 File에 접근할 때 File I/O system call을 사용하여 접근한다. 매핑을 사용하게 되면 File을 프로세스의 가상 메모리 공간으로 매핑하게 된다. 즉 File I/O system call을 사용하지 않고도 접근이 가능하다.

mmap함수를 사용하면 파일을 프로세스의 가상 메모리에 매핑할 수 있다. 메모리에 매핑된 데이터는 파일 입출력 함수를 사용하지 않고 직접 읽고 쓸 수 있다.

mmap

#include<sys/mman.h>
void* mmap(void *addr, size_tlength, intprot, intflags, intfd, off_toffset);
  • addr : 매핑할 메모리 주소 hint (일반적으로 NULL 사용)
  • length : 매핑 할 길이 (단위 : bytes)
  • prot : Memory Protection Mode
  • flags : 매핑 형태와 동작 방식 지정
  • fd : 매핑할 파일의 file descriptor
  • offset : 매핑 시작 지점을 지정하는 offset

length & offset

  • 파일의 시작에서 offset 지점 부터 length 만큼 매핑
  • offset은 반드시 page size의 배수여야 함

Mapping은 |Page| 단위로 이루어짐

  • System page size 얻기
    • sysconf(_SC_PAGESIZE)
    • getpagesize(2) for linux

Memory protection (prot)

  • PROT_NONE: 접근이 불가능한 페이지
  • PROT_READ: '읽기'가 가능한 페이지
  • PROT_WRITE: '쓰기'가 가능한 페이지
  • PROT_EXEC: '실행'이 가능한 페이지

FD의 open mode와 충돌이 나면 안됨

 

Flags

  • MAP_FIXED: mmap() 시스템콜의 addr 매개변수를 원하는 메모리 주소 공간을 알려준다는 목적을 넘어 해당 주소의 메모리 공간이 아니면 호출이 실패하게끔 확고히 커널에 요청한다.
  • MAP_PRIVATE: 매핑된 메모리 공간의 '쓰기'가 발생하더라도 실제 파일과 해당 메모리 공간을 공유하고있는 다른 프로세스에 반영하지 않는다. 원본을 훼손하지 않고, 수정된 복사본이 생긴다. 
  • MAP_SHARED: 같은 파일을 메모리에 매핑한 모든 프로세스와 매핑된 메모리 영역을 공유한다. 다른 프로세스에 의해 수정됬다면 해당 영역에 '읽기'를 할때 반영된다.

Return

  • Success : Mapping address
  • Fail : MAP_FAILD

mmap() 시스템콜로 생성한 매핑된 메모리 영역을 해제하고 싶다면 munmap() 시스템콜을 사용합니다.

munmap

#include <sys/mman.h>

int munmap(void* addr, size_t len);
  • addr : Mapping 시작 주소
  • length : Mapping 길이
  • Return
    • Success : 0
    • fail : -1

mmap.c

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

#define CHECK_MMAP_SUCCESS(_addr) \
    if (_addr == MAP_FAILED)      \
    {                             \
        perror("mmap");           \
        exit(1);                  \
    }
#define printAddrs(msg)              \
    {                                \
        printf("%s\n", msg);         \
        printf("addr1 = %s", addr1); \
        printf("addr2 = %s", addr2); \
    }

int main(int argc,  char *argv[])
{
	int fd;
	caddr_t addr1, addr2;
	char fileName[255] = "hello.txt";

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

	int pageSize = getpagesize();

	addr1 = mmap(NULL, pageSize, PROT_READ | PROT_WRITE, MAP_SHARED, fd, (off_t)0);

	addr2 = mmap(NULL, pageSize, PROT_READ | PROT_WRITE, MAP_PRIVATE, fd, (off_t)0);

	close(fd);

	printf("%s", addr1);

	addr1[0] = '1'; printAddrs("After addr1[0] = 1");
	addr2[0] = '2'; printAddrs("After addr2[0] = 2");
	addr1[0] = '3'; printAddrs("After addr1[0] = 3");

	execlp("cat", "cat", fileName, NULL);

	return 0;

}

위와 같이 코드를 작성하고 실행하게 되면 아래와 같은 실행결과를 볼 수 있다.

처음 hello 파일이 addr1, addr2 매핑이 됩니다.

처음 hello에 0이 들어가 있는데 addr1에 1을 넣으면 어떻게 바뀌게 되는 지 확인해 보면

addr1의 flagS인 MAP SHARED 으로 인해 파일에도 1이 쓰여지게 됩니다. 이어서 addr2에 2를 넣게 된다면

addr1의 flagS인 MAP PRIVATE 으로 인해 Write 시, 복사본을 생성하여 원본 파일에 영향을 주지 않는다. 그 전까지 복사본 생성을 미룬다.(* 시스템에 따라 다를 수 있음)

 

마지막으로 addr1에 3을 넣게 된다면

위와 같이 addr1의 값이 그대로 hello 파일에 영향을 주는 것을 알 수 있다.

 

mmap함수로 메모리에 매핑된 파일의 내용은 백업 공간에 복사된다. MAP_SHARED 모드로 매핑한 메모리 영역의 백업 저장 장치는 파일이고, MAP_PRIVATE 모드로 매핑한 영역의 백업 저장 장치는 스왑 공간이다.

매핑된 메모리 영역과 백업 저장 장치의 내용이 일치하도록 동기화하는데는 msync함수를 사용한다.

 

msync

#include <sys/mman.h>
int msync(void *addr, size_t len, int flags);
  • addr : 매핑된 메모리의 시작주소
  • len : 메모리 영역의 크기
  • flags 
    • MS_SYNC : write-back이 끝날 때 까지 대기  msync함수가 리턴되지 않는다.
    • MS_ASYNC : write-back을 비동기적으로 수행 ( 기다리지 않고 바로 return)
    • MS_INVALIDATE : 메모리에 변경된 내용 무효화

강제로 Page write-back 수행

  • Memory의 내용과 파일 내용 동기화
  • fflush() 유사

truncate / ftruncate

#include <unistd.h>
int truncate(const char *path, off_t length);

path : 크기를 변경할 파일의 경로
length : 변경할 크기

 

만일 파일의 원래 크기가 length 인자보다 크면 초과 부분은 버린다. 파일의 크기가 length값보다 작으면 파일의 크기를 증가시키고 해당 부분을 0으로 채운다. 이 함수를 사용하려면 경로로 지정한 파일에 쓰기 권한이 있어야한다.

이 함수는 파일의 오프셋을 변경하지 않는다.

#include <unistd.h>
int ftruncate(int fildes, off_t length);

fildes :  크기를 변경할 파일의 파일기술자
length : 변경하려는 크기

 

파일의 기술자로 지정한 파일을 지정한 크기로 변경한다. 파일 기술자는 크기를 변경할 파일을 열고 리턴받은 값이어야한다. 이 함수는 regular file과 shared memory에서만 사용할 수 있다. 이 함수는 파일의 오프셋을 변경하지 않고, 디렉토리에 접근하거나 쓰기 권한이 없는 파일에 접근하면 오류가 생긴다.

 

참고 사이트

https://www.youtube.com/watch?v=SQYio0aKbgM 

 

https://jihooyim1.gitbooks.io/unixbasic/content/contents/08.html

 

8. 메모리 매핑 · UNIXBasic

 

jihooyim1.gitbooks.io

 

'Linux' 카테고리의 다른 글

PCIe ASPM Error  (0) 2023.07.02
i2c-tools  (0) 2023.01.28
SSD 자동 인식  (0) 2023.01.28
리눅스 커널 소스의 구조  (0) 2022.12.03
CANdevStudio  (0) 2022.05.19