테스트 케이스는 통과되나 다른 테스트 케이스에서 터질 가능성이 있는 코드입니다. 참고만 해주세요
목표
이번에는 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);
- addr에서 시작하는 연속적인 가상 페이지를 맵핑한다.
- mmap은 페이지 단위로 메모리를 할당받은 시스템 콜
- offset으로 부터 length만큼 fd에 해당하는 파일을 addr에 연결된 물리 메모리에 올린다
만약 파일의 size가 PGSIZE보다 크면 마지막 페이지에는 남은 공간 만큼의 빈공간이 생긴다. 이 비어 있는 메모리를 0으로 초기화해주고 다시 디스크에 업데이트할 때는 0은 버리고 내용부분만 업데이트한다.
- Q. mmap과 malloc의 차이
- 둘 다 메모리를 할당받는다는 점에서는 같다. mmap이 file-backed 메모리의 경우에만 해당하는 함수라고 오해한다는데, 그렇지 않은 경우도 많다. 그냥 mmap은 페이지 단위로 할당받으므로 malloc에 비해 큰 용량의 메모리를 할당할 수 있다는 것이 차이인듯.
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
- do_mmap은 load_segment와 매우 유사한 면이 있음.
- page를 할당할 때 VM_FILE로 하여 할당
- 이렇게 만들어진 page는 page fault가 발생하면 페이지를 초기화 하고 frame과 연결하고 file_inf에 저장된 파일의 정보로 물리메모리에 file 데이터를 복사한다.
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
- file_back_page에서 page fault 발생 시 맵핑된 물리 메모리에 파일 데이터를 복사하는 함수
- file의 offset을 aux로 받아온 offset으로 옮기고 물리 메모리에 파일에서 read_bytes만큼 데이터를 복사한다.
- PGSIZE가 되지 않는 read_bytes인 경우 남은 부분은 0으로 채워준다. memset()
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으로 초기화 된다.
💡 ADDR으로부터 연속된 유저 가상 페이지들의 변경 사항을 디스크의 파일에 업데이트하고 맵핑 정보를 지운다. 페이지를 지우는 것이 아닌 Present Bit을 0으로 만들어준다.
- 해당 page의 dirty bit가 1이면 수정사항이 있는 것임으로 file에 물리메모리에 있는 데이터를 write하여 준다. 그리고 해당 페이지의 dirty bit을 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);
}
}
'지난 글 모음' 카테고리의 다른 글
[sw 정글] pintos 3주차 - part 6: Copy on write(cow) ALL PASS (6) | 2022.06.21 |
---|---|
[sw 정글] pintos 3주차 - part 3: Stack Growth (0) | 2022.06.21 |
[sw 정글] pintos 3주차 - part 2: Anonymous page & Lazy Loading (0) | 2022.06.21 |
[sw 정글] pintos 3주차 - part 1: Memory Management (0) | 2022.06.21 |
[Malloc-Lab] 기본개념과 Implicit 구현하기 (3) | 2022.05.10 |