PintOS Project3-4 Memory Mapped Files

정글일지 39

Memory Mapped Files

개념

이전 anonymous page때와는 달리 memory-mapped page를 다뤄야 한다. anonymous page와 달리, file-backed mapping되어 있다. 페이지의 컨텐츠는 실존하는 파일들 그대로이다. page fault가 발생하면, physical frame이 즉시 할당되고 파일에서 메모리(프레임)로 컨텐츠들이 복사된다. memory-mapped page들이 매핑되지 않거나 swap-out 되면, 컨텐츠의 변동이 파일에도 반영된다.

함수

mmapmunmap함수를 다루게 된다. memory- mapped file을 위한 시스템 콜이다. VM system은 mmap 구역으로 페이지를 최대한 lazily하게 load해야하고 mmaped file 그 자체를 백업 저장소로 사용해야 한다. 자세히 말하자면, 데이터가 메모리 지역에 데이터를 작성하고 운영체제가 그 메모리를 어떤 이유로 ‘free’해야 한다면, 그 데이터를 파일에 다시 옮겨 작성할 수 있다. 그러면 free 한 이후라도 다시 필요해지면 disk의 file에서 그 데이터를 가져올 수 있다.

void *mmap(void *addr, size_t length, int writable, int fd, off_t offset);

addr이라는 프로세스의 가상 주소 공간에 fd를 이용하여 open한 파일을 length바이트 만큼 매핑하는데, 시작지점에서 offset바이트 만큼 떨어진 곳부터 매핑한다. 전체 파일은 addr에서 시작한 연속적인 가상 페이지에 매핑된다. 만약 파일의 길이가 PGSIZE의 배수가 아니면, 마지막 매핑된 페이지에서 파일이 끝난 이후 몇 바이트를 “stick out”해야 한다. page fault면 이 바이트들을 0으로 설정하고 페이지가 디스크에 다시 옮겨 써지면 삭제해야 한다. 성공하면 함수는 파일이 매핑된 가상 주소를 반환한다. 실패하면, 파일을 매핑하기에 타당하지 않은 주소를 의미하는 NULL을 반환해야 한다.

만약 fd로 열린 파일이 0 바이트 길이를 가진다면 mmap함수 호출은 실패한다. 주소 addr이 page-aligned가 아니거나, excutable load 시간에 매핑된 페이지나 스택을 포함해서, 매핑 페이지의 존재 범위가 매핑할 페이지의 길이보다 작으면 반드시 실패되야 한다. 리눅스에서, 만약 addr이 NULL이면 커널은 매핑할 적절한 주소를 찾는다. 우리는 편의성을 위해, 주어진 addr에만 mmap을 시도하면 된다. 그러므로, addr이 0이면, 무조건 실패해야 한다. 어떤 Pintos 코드는 가상 페이지 0이 매핑되지 않았다고 판단하기 때문이다. 또한 mmap은 길이 length가 0일 때 도 실패해야 한다. 마지막으로, console input과 ouput을 표시하는 file descriptor난 매핑될 수 없다.

memory-mapped page는 anonymous page처럼 lazy한 방식으로 할당되야 한다. vm_alloc_page_with_initializervm_alloc_page를 활용할 수 있다.

void munmap(void *addr);

아직 unmap(mapping을 취소)되지 않은 동일 프로세스가 mmaping하도록 하기 위해 있었던 이전 호출이 반환하는 가상 주소 addr을 구체적 범위로 가지는 매핑을 unmap해야한다. 풀어서 설명하자면, 이전에 함수 호출이 있었다. 이 호출은 A라는 프로세스가 매핑하도록 하는 호출이였다. 이 호출된 함수는 매핑된 가상 주소를 반환하게 되는데 그 주소가 addr이다. 이 addr을 unmap하는 함수에 대한 내용이다. 또한 프로세스 A는 아직 unmap되지 않았다.

모든 매핑은 묵시적으로 프로세스가 종료되면 exit를 통해서든, 다른 방법이든 unmap된다. 묵시적이든 명시적이든 매핑이 unmap되면, 그 종료된 프로세스가 작성한 모든 페이지들은 파일로 다시 옮겨적어 지고, 이 페이지들에는 무엇도 작성되선 안된다. 이후 프로세스의 가상 페이지 목록에서 삭제된다.

파일을 닫거나 삭제하는 것은 그 매핑을 unmap하지 않는다. 한번 생성된 매핑은 munmap이 호출되거나 프로세스가 exit되기 전까지 유효하다. file_reopen함수를 사용하면 각 매핑들의 파일에 대한 분리되고 독립적인 참조를 얻을 수 있다.

위 문단을 더 자세하게 설명하자면 다음과 같다. 많은 운영체제에서, 파일은 file descriptor나 file handles에 의해 표시된다. 이 descriptor는 프로세스들이 읽어오거나 연관된 파일에 작성하는데 사용된다. 프로세스가 파일을 열면 운영 체제는 file descriptor를 반환하여서, 프로세스는 차후의 operation에서 파일에 대한 참조(refer)를 사용할 수 있다. 이 file deescriptor는 일반적으로 다양한 state information과 연관되어 있는데, read/write operation에 대한 현재 파일 offset이 그 예시이다. 파일을 mmap으로 메모리 매핑 하면 열린 파일의 file descriptor는 사용된다. 이것은 프로세스의 메모리 지역을 파일 section으로 매핑한다. 이후에 이 메모리 지역으로 읽고 쓰는것은 연관된 파일 section에도 똑같이 반영된다.

하지만, 똑같은 파일에 대해 다양하고 독립적인 메모리 매핑이 필요할 때도 있을 수 있다. 예를 들어, 읽고 있는 데이터를 가져올 파일의 한 매핑 지역과 데이터를 작성 할 파일의 다른 부분 매핑이 있을 수도 있다. 이 경우, 같은 file descriptor를 두 매핑에 모두 사용 하는 것은 좋은 생각이 아니다. file descriptor의 state(file offset같은)가 매핑들 사이에서 공유되게 된다.이것으로 인해 한 매핑에서 read operation이 다른 매핑의 write operation의 file offset에 영향을 끼치는 등 예상치 못한 문제를 야기할 수 있다.

이럴 때 file_reopen함수가 사용된다. file_reopen함수는 이미 열린 파일에서 새로운 file descriptor를 얻을 때 사용된다. 이 새로운 file descriptor는 원래 descriptor와 분리되어 있고 독립적이다. 다시 말해, 새 것은 자신만의 state information을 가진다. 이 새로운 file descriptor를 이용해서 파일의 새롭고 독립적인 memory mapping을 생성할 수 있다.

file_reopen을 사용하여 각 메모리 매핑에 관한 별도의 file descriptor를 만들어서, 각 매핑이 서로 독립적으로 작동하는 것을 알 수 있다. 이로 인해서 다른 매핑들간 예상치 못한 상호 작용을 방지할 수 있고, 프로그램이 좀더 예상 가능하고 이해 가능한 한도 내에서 작동하도록 해준다. 또 예를 들자면, 각 매핑을 별도로 관리하여 다른 것에 영향 없이 한 매핑을 닫을 수 도 있다.

만약 두개 이상의 프로세스들이 같은 파일을 매핑하면, 동시적인 데이터를 보기 위한 요건은 없다. Unix는 이 문제를 두 매핑이 똑같은 물리 페이지를 공유하게 하여서 다룬다. 또한 mmap 시스템 콜은 클라이언트가 페이지가 공유 가능한지, 사적인지를 구체적으로 정하도록 하는 argument를 갖고 있다.

void vm_file_init(void);

file-backed page subsytem을 초기화한다. 이 함수에서, file backed page와 관련된 것들을 설정할 수 있다.

bool file_backed_initializer(struct page *page, enum vm_type type, void *kva);

file-backed page를 초기화 한다. 이 함수는 우선 page->operation에 file-backed page에 관한 handler를 설정한다. 메모리와 관련된 파일과 같은 것들을 page 구조체에 설정할 수 도 있다.

static void file_backed_destroy(struct page *page);

연관된 파일을 닫아 file backed page를 파괴한다. 컨텐츠가 dirty하다면, 변동사항을 파일에 다시 옮겨 적어야 한다. 이 함수에서 page 구조체를 free할 필요까진 없다. file_backed_destroy 호출한 곳에서 다룰 문제이다.