project#2까지 구현한 흐름
lazy load segment를 알아보기 전에 load segment를 알아보자.
pintos에서는 load_segment() 함수를 활용해 kernel주소(kpage)에 file을 모두 load 시킨다. 그리고 유저스택 page(upage)와 kernel주소(kpage)을 install_page() 함수로 매핑(mapping) 시켜준다.
위의 Flowchart에서 load_segment()가 호출되는 순간 file이 전부 kpage에 올라가는 것이다.
Project3에서는 필요할 때만(요청이 올 때만), 필요한 file만 kpage에 올리고 싶어서 lazy_load_segment() 함수를 구현하고자 한다.
<pintos에서 제공되는 기존 load_segment 코드>
static bool
load_segment(struct file *file, off_t ofs, uint8_t *upage,
uint32_t read_bytes, uint32_t zero_bytes, bool writable)
{
ASSERT((read_bytes + zero_bytes) % PGSIZE == 0);
ASSERT(pg_ofs(upage) == 0);
ASSERT(ofs % PGSIZE == 0);
file_seek(file, ofs);
while (read_bytes > 0 || zero_bytes > 0)
{
/* Do calculate how to fill this page.
* We will read PAGE_READ_BYTES bytes from FILE
* and zero the final PAGE_ZERO_BYTES bytes. */
size_t page_read_bytes = read_bytes < PGSIZE ? read_bytes : PGSIZE;
size_t page_zero_bytes = PGSIZE - page_read_bytes;
/* Get a page of memory. */
uint8_t *kpage = palloc_get_page(PAL_USER);
if (kpage == NULL)
return false;
/* Load this page. */
if (file_read(file, kpage, page_read_bytes) != (int)page_read_bytes)
{
palloc_free_page(kpage);
return false;
}
memset(kpage + page_read_bytes, 0, page_zero_bytes);
/* Add the page to the process's address space. */
if (!install_page(upage, kpage, writable))
{
printf("fail\n");
palloc_free_page(kpage);
return false;
}
/* Advance. */
read_bytes -= page_read_bytes;
zero_bytes -= page_zero_bytes;
upage += PGSIZE;
}
return true;
}
위의 설명한대로 구현하기 위해선, thread 구조체에 spt(=supplemental page table)를 추가해준다. 이는 원하는 시점(제대로 된 page fault 발생)에 file을 load하기 위해 필요하다.
1. 처음에 load_segment를 실행해주면, vm_alloc_page_with_initializer() 함수의 인자로 lazy_load_segment 함수와 file에 대한 각종 정보들을 전달해준다.
2. vm_alloc_page_with_initializer() 함수에서 여러 정보들을 spt에 넣어준다.
3. 이후 정상적인 page fault가 발생하면, lazy_load_segment() 함수를 불러와서 원하는 file을 kpage에 load 시켜주도록 한다.
따라서, load_segment 코드도 일부 바뀐다. 바뀐 load_segment 코드와 lazy_load_segment 함수를 아래에서 확인해보자.
static bool
load_segment(struct file *file, off_t ofs, uint8_t *upage,
uint32_t read_bytes, uint32_t zero_bytes, bool writable)
{
ASSERT((read_bytes + zero_bytes) % PGSIZE == 0);
ASSERT(pg_ofs(upage) == 0);
ASSERT(ofs % PGSIZE == 0);
while (read_bytes > 0 || zero_bytes > 0)
{
/* Do calculate how to fill this page.
* We will read PAGE_READ_BYTES bytes from FILE
* and zero the final PAGE_ZERO_BYTES bytes. */
size_t page_read_bytes = read_bytes < PGSIZE ? read_bytes : PGSIZE;
size_t page_zero_bytes = PGSIZE - page_read_bytes;
/* TODO: Set up aux to pass information to the lazy_load_segment. */
struct container* container;
container = palloc_get_page(PAL_ZERO | PAL_USER);
container->file = file;
container->page_read_bytes = page_read_bytes;
container->writable = writable;
container->offset = ofs;
if (!vm_alloc_page_with_initializer(VM_ANON, upage,
writable, lazy_load_segment, container))
return false;
/* Advance. */
read_bytes -= page_read_bytes;
zero_bytes -= page_zero_bytes;
ofs += page_read_bytes;
upage += PGSIZE;
}
return true;
}
static bool
lazy_load_segment(struct page *page, void *aux)
{
struct frame *frame = page->frame;
/* TODO: Load the segment from the file */
/* TODO: This called when the first page fault occurs on address VA. */
/* TODO: VA is available when calling this function. */
struct container *container = (struct container*) aux;
struct file *file = container->file;
size_t page_read_bytes = container->page_read_bytes;
size_t page_zero_bytes = PGSIZE - page_read_bytes;
bool writable = container->writable;
off_t offset = container->offset;
file_seek(file, offset);
if (file_read(file, frame->kva, page_read_bytes) != (int)page_read_bytes)
{
return false;
}
memset(frame->kva + page_read_bytes, 0, page_zero_bytes);
return true;
}