Memory Mapped File는 파일을 기반으로 매핑하는 페이지이다.
페이지 내부에는 실제 파일 데이터가 담겨있다.
mmap()의 작동과정
pml4
에 매핑 )dirty
)를 수정mmap
and munmap
System Callmmap
void *mmap (void *addr, size_t length, int writable, int fd, off_t offset);
PGSIZE
)의 배수가 아닌 경우, 마지막 페이지의 일부 바이트가 파일 끝을 넘어서게 됩니다. 이 부분은 페이지 폴트가 발생했을 때 0으로 설정하고, 페이지를 다시 디스크에 쓸 때는 무시됩니다.vm_alloc_page_with_initializer
vm_alloc_page
// syscall.c
...
switch (syscall_num)
{
...
**case SYS_MMAP: // system call에 MMAP 함수 매핑
f->R.rax = mmap(f->R.rdi, f->R.rsi, f->R.rdx, f->R.r10, f->R.r8);
break;**
}
// syscall.c
void *mmap (void *addr, size_t length, int writable, int fd, off_t offset) {
// - length가 0인 경우, addr이 0인 경우, fd == 0 or fd == 1인 경우
if ( length <= 0 || addr == 0 || fd == 0 || fd == 1 )
return NULL;
// - addr이 page_aligned 되지 않은 경우
if (pg_ofs(addr) != 0)
return NULL;
// - addr이 커널영역의 주소라면 (시작 주소와, 끝 주소를 모두 검사)
if (!is_user_vaddr(addr) || !is_user_vaddr(addr + length - 1))
return NULL;
// - fd로 열린 파일의 크기가 0인 경우
struct file* f = process_get_file(fd);
if ( f == NULL || file_length(f) == 0 )
return NULL;
// - 기존 매핑된 페이지 집힙과 겹치는 경우
struct thread *curr = thread_current();
void *end_addr = addr + length;
for (void *va = addr; va < end_addr; va += PGSIZE) {
struct page p;
struct hash_elem *e;
p.va = va;
e = hash_find(&curr -> spt, &p.elem);
if (e != NULL)
return NULL;
}
return do_mmap(addr, length, writable, f, offset);
}
// file.c
/* Do the mmap */
void * do_mmap (void *addr, size_t length, int writable,
struct file *file, off_t offset) {
// 동일한 매핑에 대해서 서로다른 프로세스의 결과물이 서로에게 영향을 주면 바람직하지 않다.
// 사용할 때 `file_reopen`을 통해서 새로운 파일 디스크립터를 통해 파일을 생성하면 다른 매핑에 영향을 주지않고 독립적인 파일 핸들링이 가능해진다.
struct file *reopen_f = file_reopen(file);
off_t fl = file_length(reopen_f);
size_t read_bytes = fl < length ? fl : length ; //매핑할 총 바이트 수로 초기화
size_t zero_bytes = PGSIZE - read_bytes % PGSIZE; // 마지막에 0으로 채울 바이트의 크기
int page_count = length <= PGSIZE ? 1 : length % PGSIZE ? length / PGSIZE + 1 // 연속적으로 매핑된 페이지의 총 개수
: length / PGSIZE; // 모든 페이지에 일괄적으로 동일한 페이지 개수를 전달하기 위해 미리 계산해서 넘겨줌
void *start_addr = addr; // 시작 주소 리턴을 위해 저장
ASSERT((read_bytes + zero_bytes) % PGSIZE == 0)
ASSERT(pg_ofs(addr) == 0)
ASSERT(offset % PGSIZE == 0)
// 총 길이 read_bytes(length)만큼 page 단위로 반복하면서 가상 페이지에 매핑
while ( read_bytes > 0 || zero_bytes > 0 ) {
// 최대 페이지 크기 만큼, 읽을 파일 데이터의 크기를 결정
size_t page_read_bytes = read_bytes < PGSIZE ? read_bytes : PGSIZE;
// 읽은 데이터의 크기와 결합하여 페이지 단위를 맞출 0값의 크기를 계산
size_t page_zero_bytes = PGSIZE - page_read_bytes;
// page initialize를 위한 인자 전달
struct aux_struct *aux = (struct aux_struct *)malloc(sizeof(struct aux_struct)); // aux( lazy_load_segment를 위한 데이터 )를 저장할 공간을 동적할당.
if (aux == NULL)
return false;
// 파일 관련 데이터를 aux 인자에 적재
aux -> file = reopen_f;
aux -> offset = offset;
aux -> read_bytes = page_read_bytes;
aux -> zero_bytes = page_zero_bytes;
// File Backed Page를 Lazy Loading을 활용하여 초기화
if (!vm_alloc_page_with_initializer(VM_FILE, addr, writable, lazy_load_segment, aux))
return NULL;
// 파일에 할당한 페이지를 연쇄적으로 해제하기 위해 분리한 파일의 개수를 각 파일 구조체에 저장
struct page * p = spt_find_page(&thread_current() -> spt, start_addr);
p -> page_count = page_count;
// 페이지에 적재할 다음 파일 데이터로 이동
read_bytes -= page_read_bytes;
zero_bytes -= page_zero_bytes;
offset += page_read_bytes;
addr += PGSIZE;
}
return start_addr;
}
여기서 왜 mmap과 do_mmap을 구분해두었을까?
- 각 함수의 구조를 통해서 책임을 분리하기 위해서 사용한다. mmap 함수 내부에서는 입력값의 유효성, 초기설정을 처리하는 부분이고 실제 우리가 생각하는 파일 데이터를 실제 메모리와 매핑하는 작업은 do_mmap에서 수행한다.
mmap을 통해 파일 데이터들은 연속된 페이지에 저장된다. 이후 munmap에서도 동일하게 연속된 페이지를 삭제해주어야하기 때문에, 페이지 구조체 내부에 연속된 페이지 개수를 의미하는 어떠한 추가적인 멤버변수가 필요하다.
// vm.h
struct page {
...
int page_count;
...
}
추가적으로 여기서 lazy_load_segment()함수를 사용하기 위해서 기존 static 함수였던 lazy_load_segment
를 static하지 않은 함수로 변경하고 헤더파일에 선언하여 import 가능하게 한다.
// process.h
bool lazy_load_segment(struct page *page, void *aux);
// process.c
bool lazy_load_segment(struct page *page, void *aux) {
...
}
munmap
void munmap (void *addr);
// syscall.c
...
switch (syscall_num)
{
...
case SYS_MUNMAP: **// system call에 MMAP 함수 매핑**
munmap(f->R.rdi);
break;
}
// syscall.c
void munmap(void *addr) {
if (addr == NULL || is_user_vaddr(addr))
return NULL;
do_munmap(addr);
}
// file.c
/* Do the munmap */
void do_munmap (void *addr) { // - addr는 매핑 해제되지 않은 프로세서의 mmap에 대한 이전 호출에서 반환된 가상 주소
uintptr_t cur_addr = addr;
struct thread *curr = thread_current();
struct page *p = spt_find_page(&curr -> spt, cur_addr); // 매핑 해제할 페이지 조회
// 매핑된 페이지를 페이지 개수만큼 순회
// addr의 경우 항상 매핑된 File Backe Page의 첫 페이지 주소를 가리키고 있기 떄문에 페이지 수만큼 반복문을 실행
for (int c = 0; c < p -> page_count; c++) {
// 페이지 제거
if (p)
destroy(p);
// 다음 페이지 확인
cur_addr += PGSIZE;
p = spt_find_page(&curr -> spt, cur_addr);
}
}
File Backed Page를 핸들링하기 위해서 추가적인 file_page의 멤버 변수와 이를 초기화 하는 initializer 함수의 추가 개선이 필요하다.
File Backed Page가 초기화 하기 위해
struct file* file;
→ 대상 파일 데이터off_t offset;
→ 파일을 읽을 위치size_t read_bytes;
→ offset으로 부터 읽을 데이터의 크기size_t zero_bytes;
→ 페이지 정렬을 위해 채울 0 데이터 크기PGSIZE - read_bytes
계산값으로 대체 가능)를 file_page 구조체의 멤버 변수로 추가한다.
// file.h
struct file_page {
struct file* file;
off_t offset;
size_t read_bytes;
size_t zero_bytes;
};
File Backed Page를 초기화하는 함수를 구성하는 file_backed_initializer
함수를 수정한다. 인자로 받은 aux를 현재 페이지에 할당된 파일 데이터로 초기화 인자들을 복사한다.
// file.c
/* Initialize the file backed page */
bool
file_backed_initializer (struct page *page, enum vm_type type, void *kva) {
/* Set up the handler */
page->operations = &file_ops;
struct file_page *file_page = &page->file;
struct aux_struct *aux = (struct aux*)page -> uninit.aux;
file_page -> file = aux -> file;
file_page -> offset = aux -> offset;
file_page -> read_bytes = aux -> read_bytes;
file_page -> zero_bytes = aux -> zero_bytes;
return true;
}
munmap 과정에서 실행될 file_backed_destroy
함수를 수정한다.
File Backe Page가 메모리에서 해제될 때, pml4의 dirty 인자를 확인하여 수정되었다면 원본 파일에 수정내용을 반영하고 pml4 매핑 해제, 메모리에서 삭제한다.
/* Destory the file backed page. PAGE will be freed by the caller. */
static void
file_backed_destroy (struct page *page) {
struct thread *curr = thread_current();
struct file_page *file_page UNUSED = &page->file;
if (pml4_is_dirty(curr -> pml4, page -> va)) { // 파일이 dirty한 상태이면 수정된 부분 기록
lock_acquire(&filesys_lock);
file_write_at(file_page -> file, page -> va, file_page -> read_bytes, file_page -> offset); // 수정된 파일 페이지 만큼 파일 부분 수정
pml4_set_dirty(curr -> pml4, page -> va, 0); // 페이지의 dirty 인자 수정
lock_release(&filesys_lock);
}
pml4_clear_page(curr -> pml4, page -> va); // 페이지 제거
}
File Backed Page를 대상으로 fork하는 경우 타입에 맞게 자식 페이지를 생성할 수 있도록 페이지의 초기화 인자(aux)를 복사하고, initializer함수를 직접 호출하여 페이지를 생성한다. 이후 직접 매핑까지 완료한다.
// vm.c
/* Copy supplemental page table from src to dst */
bool supplemental_page_table_copy (struct supplemental_page_table *dst UNUSED,
struct supplemental_page_table *src UNUSED)
{
...
else if ( type == VM_FILE ) {
// 자식 페이지 초기화를 위한 부모 페이지의 정보 복사
struct aux_struct *aux = (struct aux*)malloc(sizeof(struct aux_struct));
aux -> file = curr_page -> file.file;
aux -> offset = curr_page -> file.offset;
aux -> read_bytes = curr_page -> file.read_bytes;
aux -> zero_bytes = curr_page -> file.zero_bytes;
// 자식 파일 페이지 생성을 위한 초기화를 위한 객체 생성
if(!vm_alloc_page_with_initializer(VM_FILE, upage, writable, NULL, aux))
return false;
struct page *file_page = spt_find_page(dst, upage);
// file-backed page 초기화
file_backed_initializer(file_page, type, NULL);
// 기존 부모 프로세스가 사용하는 프레임과 연결 -> 두 프로세스가 동일한 파일을 매핑하는 경우 물리 프레임을 공유
file_page -> frame = curr_page -> frame;
pml4_set_page(thread_current() -> pml4, file_page -> va, curr_page -> frame -> kva);
continue;
}
...
}
pass tests/userprog/args-none
pass tests/userprog/args-single
pass tests/userprog/args-multiple
pass tests/userprog/args-many
pass tests/userprog/args-dbl-space
pass tests/userprog/halt
pass tests/userprog/exit
pass tests/userprog/create-normal
pass tests/userprog/create-empty
pass tests/userprog/create-null
pass tests/userprog/create-bad-ptr
pass tests/userprog/create-long
pass tests/userprog/create-exists
pass tests/userprog/create-bound
pass tests/userprog/open-normal
pass tests/userprog/open-missing
pass tests/userprog/open-boundary
pass tests/userprog/open-empty
pass tests/userprog/open-null
pass tests/userprog/open-bad-ptr
pass tests/userprog/open-twice
pass tests/userprog/close-normal
pass tests/userprog/close-twice
pass tests/userprog/close-bad-fd
pass tests/userprog/read-normal
pass tests/userprog/read-bad-ptr
pass tests/userprog/read-boundary
pass tests/userprog/read-zero
pass tests/userprog/read-stdout
pass tests/userprog/read-bad-fd
pass tests/userprog/write-normal
pass tests/userprog/write-bad-ptr
pass tests/userprog/write-boundary
pass tests/userprog/write-zero
pass tests/userprog/write-stdin
pass tests/userprog/write-bad-fd
pass tests/userprog/fork-once
pass tests/userprog/fork-multiple
pass tests/userprog/fork-recursive
pass tests/userprog/fork-read
pass tests/userprog/fork-close
pass tests/userprog/fork-boundary
pass tests/userprog/exec-once
pass tests/userprog/exec-arg
pass tests/userprog/exec-boundary
pass tests/userprog/exec-missing
pass tests/userprog/exec-bad-ptr
pass tests/userprog/exec-read
pass tests/userprog/wait-simple
pass tests/userprog/wait-twice
pass tests/userprog/wait-killed
pass tests/userprog/wait-bad-pid
pass tests/userprog/multi-recurse
pass tests/userprog/multi-child-fd
pass tests/userprog/rox-simple
pass tests/userprog/rox-child
pass tests/userprog/rox-multichild
pass tests/userprog/bad-read
pass tests/userprog/bad-write
pass tests/userprog/bad-read2
pass tests/userprog/bad-write2
pass tests/userprog/bad-jump
pass tests/userprog/bad-jump2
pass tests/vm/pt-grow-stack
pass tests/vm/pt-grow-bad
pass tests/vm/pt-big-stk-obj
pass tests/vm/pt-bad-addr
pass tests/vm/pt-bad-read
pass tests/vm/pt-write-code
pass tests/vm/pt-write-code2
pass tests/vm/pt-grow-stk-sc
pass tests/vm/page-linear
pass tests/vm/page-parallel
pass tests/vm/page-merge-seq
**FAIL tests/vm/page-merge-par
FAIL tests/vm/page-merge-stk
FAIL tests/vm/page-merge-mm**
pass tests/vm/page-shuffle
pass tests/vm/mmap-read
pass tests/vm/mmap-close
pass tests/vm/mmap-unmap
pass tests/vm/mmap-overlap
pass tests/vm/mmap-twice
**FAIL tests/vm/mmap-write**
pass tests/vm/mmap-ro
pass tests/vm/mmap-exit
pass tests/vm/mmap-shuffle
pass tests/vm/mmap-bad-fd
pass tests/vm/mmap-clean
pass tests/vm/mmap-inherit
pass tests/vm/mmap-misalign
pass tests/vm/mmap-null
pass tests/vm/mmap-over-code
pass tests/vm/mmap-over-data
pass tests/vm/mmap-over-stk
pass tests/vm/mmap-remove
pass tests/vm/mmap-zero
pass tests/vm/mmap-bad-fd2
pass tests/vm/mmap-bad-fd3
pass tests/vm/mmap-zero-len
**FAIL tests/vm/mmap-off
FAIL tests/vm/mmap-bad-off
pass** tests/vm/mmap-kernel
pass tests/vm/lazy-file
pass tests/vm/lazy-anon
**FAIL tests/vm/swap-file
FAIL tests/vm/swap-anon
FAIL tests/vm/swap-iter
FAIL tests/vm/swap-fork**
pass tests/filesys/base/lg-create
pass tests/filesys/base/lg-full
pass tests/filesys/base/lg-random
pass tests/filesys/base/lg-seq-block
pass tests/filesys/base/lg-seq-random
pass tests/filesys/base/sm-create
pass tests/filesys/base/sm-full
pass tests/filesys/base/sm-random
pass tests/filesys/base/sm-seq-block
pass tests/filesys/base/sm-seq-random
**FAIL tests/filesys/base/syn-read**
pass tests/filesys/base/syn-remove
**FAIL tests/filesys/base/syn-write**
pass tests/threads/alarm-single
pass tests/threads/alarm-multiple
pass tests/threads/alarm-simultaneous
pass tests/threads/alarm-priority
pass tests/threads/alarm-zero
pass tests/threads/alarm-negative
pass tests/threads/priority-change
pass tests/threads/priority-donate-one
pass tests/threads/priority-donate-multiple
pass tests/threads/priority-donate-multiple2
pass tests/threads/priority-donate-nest
pass tests/threads/priority-donate-sema
pass tests/threads/priority-donate-lower
pass tests/threads/priority-fifo
pass tests/threads/priority-preempt
pass tests/threads/priority-sema
pass tests/threads/priority-condvar
pass tests/threads/priority-donate-chain
**FAIL tests/vm/cow/cow-simple**
13 of 141 tests failed.