목표
이번에는 Anonymous Memory가 아닌 File-Backed memory, 다시 말해 Memory Mapped Page에 대해 구현해보도록 한다.
File-Backed page
File-Backed page는 파일에 기반한 맵핑을 한다. 안에 내용은 디스크에서 존재하고 있는 파일을 복사한 것이다. page fault가 발생하면 바로 물리 프레임을 할당하고 파일의 데이터를 물리 메모리에 복사한다. 이때 I/O를 통해 데이터를 복사하는 것이 아닌 DMA 방식으로 디스크에서 파일을 복사한다.
이 때 유저 가상 페이지를 미리 가상주소 공간에 할당 해주는 것이 mmap이고 페이지와 물리 메모리가 연결된 경우 그 연결을 끊어 주는 것을 munmap이라 한다.
mmap() 시스템 콜에 의해 맵핑된 가상 메모리는 스택과 힙 사이의 미할당 공간(스택도 힙도 아닌)에 할당된다
memory maping page 구현 시에도 lazy loading 방식으로 할당되어야 한다.
vm_alloc_page로 페이지를 할당하고 해당 페이지에 page fault가 발생하였을 때 frame을 맵핑하고 해당 page에 해당하는 file 데이터를 메인 메모리에 올려준다.
mmap 구현
void *mmap (void *addr, size_t length, int writable, int fd, off_t offset);
만약 파일의 size가 PGSIZE보다 크면 마지막 페이지에는 남은 공간 만큼의 빈공간이 생긴다. 이 비어 있는 메모리를 0으로 초기화해주고 다시 디스크에 업데이트할 때는 버린다.
syscall mmap
void *
mmap(void *addr, size_t length, int writable, int fd, off_t offset)
{
if (addr == NULL || !is_user_vaddr(addr) || length == 0)
return NULL;
if (addr >= STACK_OUT_ADDR)
return NULL;
if (addr != pg_round_down(addr))
return NULL;
struct page *page = spt_find_page(&thread_current()->spt, addr);
if (page)
return NULL;
if (offset % PGSIZE != 0)
return NULL;
if (fd == 0 || fd == 1)
return NULL;
struct file *file_obj = get_file_from_fd_table(fd);
if (!file_obj || !filesize(fd))
return NULL;
return do_mmap(addr, length, writable, file_obj, offset);
}
do_mmap
void *
do_mmap(void *addr_, size_t length, int writable,
struct file *file, off_t offset)
{
file = file_reopen(file);
uint32_t read_bytes = file_length(file);
uint32_t zero_bytes = (length - read_bytes);
struct mmap_file mmap_file;
mmap_file.file = file;
list_init(&mmap_file.page_list);
list_push_back(&thread_current()->mmap_list, &mmap_file.mmap_elem);
void *addr = addr_;
while (read_bytes > 0)
{
size_t page_read_bytes = read_bytes < FISIZE ? read_bytes : FISIZE;
size_t page_zero_bytes = FISIZE - page_read_bytes;
struct file_information *file_inf = (struct file_information *)malloc(sizeof(struct file_information));
file_inf->file = file;
file_inf->ofs = offset;
file_inf->read_bytes = page_read_bytes;
if (!vm_alloc_page_with_initializer(VM_FILE, addr, writable, file_lazy_load_segment, file_inf))
return NULL;
struct page *file_page = page_lookup(addr);
file_page->file_inf = file_inf;
list_push_back(&mmap_file.page_list, &file_page->mmap_elem);
/* Advance. */
read_bytes -= page_read_bytes;
zero_bytes -= page_zero_bytes;
addr += FISIZE;
offset += page_read_bytes;
}
return addr_;
file_lazy_load_segment
static bool
file_lazy_load_segment(struct page *page, void *aux)
{
struct file *file = ((struct file_information *)aux)->file;
off_t offset = ((struct file_information *)aux)->ofs;
size_t page_read_bytes = ((struct file_information *)aux)->read_bytes;
size_t page_zero_bytes = FISIZE - page_read_bytes;
file_seek(file, offset); // file의 오프셋을 offset으로 바꾼다. 이제 offset부터 읽기 시작한다.
/* 페이지에 매핑된 물리 메모리(frame, 커널 가상 주소)에 파일의 데이터를 읽어온다. */
/* 제대로 못 읽어오면 페이지를 FREE시키고 FALSE 리턴 */
off_t read_off = file_read(file, page->frame->kva, page_read_bytes);
if (read_off != (int)page_read_bytes)
{
palloc_free_page(page->frame->kva);
return false;
}
/* 만약 1페이지 못 되게 받아왔다면 남는 데이터를 0으로 초기화한다. */
memset(page->frame->kva + page_read_bytes, 0, page_zero_bytes);
return true;
}
mummap(addr)
Dirty Bit
해당 페이지가 수정되었는지를 저장하는 비트이다. 페이지가 변경될 때마다 이 비트는 1이된다.
디스크에 변경 내용을 기록하고 나면 해당 페이지의 더티 비트는 다시 0으로 초기화 된다.
void do_munmap(void *addr)
{
struct page *page = spt_find_page(&thread_current()->spt, addr);
if (pml4_is_dirty(thread_current()->pml4, page->va))
{
file_write_at(page->file_inf->file, page->frame->kva, page->file_inf->read_bytes, page->file_inf->ofs);
pml4_set_dirty(thread_current()->pml4, page->va, false);
}
struct mmap_file *mmap_file = list_entry(&page->mmap_elem, struct mmap_file, mmap_elem);
struct list_elem *elem = list_begin(&mmap_file->page_list);
while (elem)
{
struct page *free_page = list_entry(elem, struct page, mmap_elem);
if (pml4_is_dirty(thread_current()->pml4, free_page->va))
{
file_write_at(free_page->file_inf->file, free_page->frame->kva, free_page->file_inf->read_bytes, free_page->file_inf->ofs);
pml4_set_dirty(thread_current()->pml4, free_page->va, false);
}
// vm_dealloc_page(free_page);
elem = elem->next;
pml4_clear_page(thread_current()->pml4, free_page->va);
}
}
PintOS Project3 GIthub 주소 PintOS