
memory mapped page는 파일을 기반으로 매핑하는 페이지이다.
내용을 0으로만 채우면 끝이던 anonymous page 와는 달리 실제 disk에 존재하는 파일의 데이터가 담긴다.
디스크에서 메모리로 올라온 파일은 만약 내용이 변경된 경우에는 매핑이 해제될 때 disk의 파일에도 변경 사항이 반영되어야 한다.
파일을 매핑하려면 system call mmap()과 munmap()부터 구현해야 한다.
실제 매핑/매핑 해제 함수는 do_mmap() 와 do_munmap()인데 이 함수에 전달하는 인자를 검사하는 과정을 system call mmap() 과 munmap()에서 실행해야 한다.
case SYS_MMAP:
f -> R.rax = mmap(f->R.rdi, f->R.rsi, f->R.rdx, f->R.r10, f->R.r8);
break;
......
void *mmap (void *addr, size_t length, int writable, int fd, off_t offset) {
//offset이 정렬되어있는가 검사
if (offset % PGSIZE != 0) {
return NULL;
}
//addr이 정렬되어있지 않거나 NULL인가 검사
if (addr != pg_round_down(addr) || addr == NULL) {
return NULL;
}
//addr이 커널영역인지 유저영역인지 확인
if (is_kernel_vaddr(addr) || is_kernel_vaddr((int)addr + length)) {
return NULL;
}
//파일의 길이가 0보다 큰지 검사
if (length <= 0) {
return NULL;
}
//spt-find를 통해 현재 페이지가 유효한 페이지인지 확인
if (spt_find_page(&thread_current () -> spt, addr)) {
return NULL;
}
//fd가 표준 입력 또는 표준 출력인지 확인
if (fd == 0 || fd == 1) {
return NULL;
}
//해당 fd를 통해 가져온 struct file이 유효한지 확인
struct file *file= get_file_from_fd(fd);
if (file == NULL || file_length(file) == 0) {
return NULL;
}
return do_mmap(addr, length, writable, file, offset);
}
mmap()함수에서는 do_mmap() 함수에 넘길 인자들의 유효성을 검사해야 한다.
여기서 간단한 찐빠실수를 저지르는 바람에 꽤 시간을 낭비했는데 do_mmap()까지 함수를 전부 작성해놓고도 mmap test를 거의 통과하지 못하는 실수가 생겼었다.
case SYS_MMAP:
mmap(f->R.rdi, f->R.rsi, f->R.rdx, f->R.r10, f->R.r8);
break;
mmap() 시스템콜을 이런식으로 쓰는 바람에 mmap 함수가 실행되는 return을 전혀 받아오고 있지 않았던 것이다..
그리고 type을 생각하지 않고 덧셈을 했더니
//addr이 커널영역인지 유저영역인지 확인
if (is_kernel_vaddr(addr) || is_kernel_vaddr(addr + length)) {
return NULL;
}
void* 형인 addr과 size_t인 length가 더해지지 않았기 때문에 통과하면 안 되는 addr이 조건문을 통과하고 있었다..
앞에서 받은 인수로 실제로 매핑을 하는 함수이다.
void *
do_mmap (void *addr, size_t length, int writable, struct file *file, off_t offset) {
struct file *dup_file = file_reopen(file);
//return 할 시작 주소
void *start_addr = addr;
//매핑을 위해서 사용하는 총 페이지 수
//length가 PGSIZE 이하라면 1 페이지
//length % PGSIZE 가 0이 아니라면 (length / PGSIZE) + 1
//length % PGSIZE 가 0이면 (length / PGSIZE)
int total_page_count = NULL;
if (length <= PGSIZE) {
total_page_count = 1;
} else if (length % PGSIZE == 0) {
total_page_count = (length / PGSIZE);
} else {
total_page_count = (length / PGSIZE) + 1;
}
// printf("total_page_count : %d\n", total_page_count);
size_t read_bytes = length < file_length(dup_file) ? length : file_length(dup_file);
size_t zero_bytes = PGSIZE - read_bytes % PGSIZE;
//내용이 PGSIZE에 align 되어있는지 확인
if ((read_bytes + zero_bytes) % PGSIZE != 0) {
return NULL;
}
//upage가 PGSIZE align되어있는지 확인
if (pg_ofs(addr) != 0) {
return NULL;
}
//ofs가 PGSIZE align 되어있는지 확인
if (offset % PGSIZE != 0) {
return NULL;
}
while (read_bytes > 0 || zero_bytes > 0) {
size_t page_read_bytes = read_bytes < PGSIZE ? read_bytes : PGSIZE;
size_t page_zero_bytes = PGSIZE - page_read_bytes;
struct lazy_load_aux *aux = (struct lazy_load_aux *)malloc(sizeof(struct lazy_load_aux));
aux -> file = dup_file;
aux -> ofs = offset;
aux -> read_bytes = read_bytes;
aux -> zero_bytes = zero_bytes;
aux -> writable = writable;
//printf("load segment; file: %p, ofs: %d, read_bytes: %d\n", file, ofs, page_read_bytes);
//vm_alloc_page_with_initializer의 4번째 인자가 load할 때 이용할 함수, 5번쨰 인자가 그 때 필요한 인자이다.
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);
// ASSERT (total_page_count != NULL);
p -> mapped_page_count = total_page_count;
offset += page_read_bytes;
read_bytes -= page_read_bytes;
zero_bytes -= page_zero_bytes;
addr += PGSIZE;
}
//파일의 크기에 따라 PGSIZE씩 증가하는 addr 대신 변하지 않았던 start_addr 반환
return start_addr;
}
파일을 reopen하는 이유는 유닉스 규칙에 따라 매핑은 매핑을 해제하는 munmap이 호출되거나 프로세스가 종료될 때까지 유효하다. (파일을 닫거나 제거해도 매핑은 해제되지 않는다.)
동일한 파일에 대해 여러 프로세스에 의해 여러 개의 매핑이 존재한다고 생각해 보자.
이때 한 프로세스에서 이 파일을 닫거나 제거하면 다른 프로세스에서도 이 매핑된 영역에 대한 참조가 유효하지 않게 되어버린다.
또한, 한 매핑에서 파일의 내용을 변경하면 다른 매핑에도 영향을 주게 된다.
file_reopen 함수를 사용하면 이 파일에 대한 새로운 파일 디스크립터를 얻게 되어 다른 매핑에 영향을 주거나 영향을 받지 않는 독립적인 매핑을 가질 수 있게 된다.
그리고 할당할 때 페이지 수를 기록해놓는 이유는 UNMAP 과정에서 할당했던 페이지 만큼 반환을 반복하기 위함이다.
case SYS_MUNMAP:
munmap(f->R.rdi);
break;
.....
void munmap (void *addr) {
return do_munmap(addr);
}
별거 없다. 단순히 do_munmap() 함수의 wrapper 역할만 해준다.
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 lazy_load_aux *lazy_load_aux = (struct lazy_load_aux *)page -> uninit.aux;
file_page -> file = lazy_load_aux -> file;
file_page -> ofs = lazy_load_aux -> ofs;
file_page -> read_bytes = lazy_load_aux -> read_bytes;
file_page -> zero_bytes = lazy_load_aux -> zero_bytes;
return true;
}
수정된 사항을 파일에 다시 기록하기 위해서는 매핑을 해제하는 시점에 해당 페이지에 매핑된 파일의 정보를 알 수 있어야 한다.
이를 위해서 file_backed page 가 초기화 될 때 호출되는 함수인 file_backed_initializer()에 파일에 대한 정보를 struct page에 추가해준다.
static void
file_backed_destroy (struct page *page) {
struct file_page *file_page UNUSED = &page->file;
//file_backed_destroy 전에 뭔가 기록했는지 확인하고
if (pml4_is_dirty(thread_current() -> pml4, page -> va)) {
//뭔가 기록되었다면 disk에 기록한 후 dirty bit을 0으로 되돌림
file_write_at(file_page -> file, page -> va, file_page -> read_bytes, file_page -> ofs);
// pml4_set_dirty(thread_current() -> pml4, page -> va, 0);
}
pml4_clear_page(thread_current() -> pml4, page -> va);
프로세스가 종료될 때도 매핑이 해제되어야 하므로 수정된 사항이 있다면 파일에 다시 기록하고 가상 페이지 목록에서 해당 페이지를 제거한다.
void
do_munmap (void *addr) {
// 연결된 물리 프레임과의 연결을 끊어야 한다.
// 주어진 addr을 통해서 spt로부터 page를 하나 찾는다.
struct supplemental_page_table *spt = &thread_current() -> spt;
struct page *target_page = spt_find_page(spt, addr);
struct file_page *file_page = &target_page->file;
//mapped_page_count == do_mmap()에서 정해진 반복문을 순회할 횟수
int count = target_page -> mapped_page_count;
// printf("count = %d", count);
struct lazy_load_aux *aux = (struct lazy_load_aux *) target_page -> uninit.aux;
if (target_page == NULL) {
return NULL;
}
for (int i = 0; i < count; i++) {
// if (target_page) {
// if (pml4_is_dirty(thread_current() -> pml4, target_page -> va)) {
// //뭔가 기록되었다면 disk에 기록한 후 dirty bit을 0으로 되돌림
// file_write_at(file_page -> file, target_page -> va, file_page -> read_bytes, file_page -> ofs);
// pml4_set_dirty(thread_current() -> pml4, target_page -> va, 0);
// }
destroy (target_page);
// }
pml4_clear_page(thread_current() -> pml4, target_page -> va);
if (target_page->frame)
palloc_free_page(target_page->frame->kva);
hash_delete(spt, &target_page ->hash_elem);
addr += PGSIZE;
target_page = spt_find_page(spt,addr);
}
}