Anonymous page
Lazy Loading(Demanding Paging)
lazy loading은 메모리 로딩이 필요한 시점까지 지연되는 디자인
가상 page만 할당해두고 필요한 page를 요청하면 page fault가 발생하고 해당 page를 type에 맞게 초기화하고 frame과 연결하고 userprogram으로 제어권을 넘긴다.
지연로딩 순서
vm_alloc_page_with_initializer
호출uninit_initialize
를 호출하고 이전에 설정한 initializer를 호출한다.lazy_load_segment
를 호출하여 필요한 데이터를 물리메모리에 올린다.생명주기
initialize
->(page_fault->lazy-load->swap-in>swap-out->...)->destroy
- Unintalized page 구현
vm_alloc_page_with_initializer
bool vm_alloc_page_with_initializer(enum vm_type type, void *upage, bool writable,
vm_initializer *init, void *aux)
{
ASSERT(VM_TYPE(type) != VM_UNINIT)
struct supplemental_page_table *spt = &thread_current()->spt;
if (spt_find_page(spt, upage) == NULL)
{
struct page *new_page = malloc(sizeof(struct page));
if (type == VM_ANON)
{
uninit_new(new_page, upage, init, type, aux, anon_initializer);
}
else if (type == VM_FILE)
{
uninit_new(new_page, upage, init, type, aux, file_backed_initializer);
}
new_page->writable = writable;
spt_insert_page(spt, new_page);
return true;
}
err:
return false;
}
- Anonymous Page구현
ANON page 타입에 대한 함수들 수정
Anonymous Page에서 중요한 점은 물리메모리와 맵핑될 때 초기화할 때 모든 데이터를 Zeroing해주어야한다.
anon_initializer
bool anon_initializer(struct page *page, enum vm_type type, void *kva)
{
/* page struct 안의 Union 영역은 현재 uninit page이다.
ANON page를 초기화해주기 위해 해당 데이터를 모두 0으로 초기화해준다.
Q. 이렇게 하면 Union 영역은 모두 다 0으로 초기화되나? -> 맞다. */
struct uninit_page *uninit = &page->uninit;
memset(uninit, 0, sizeof(struct uninit_page));
/* Set up the handler */
/* 이제 해당 페이지는 ANON이므로 operations도 anon으로 지정한다. */
page->operations = &anon_ops;
struct anon_page *anon_page = &page->anon;
anon_page->swap_index = -1;
return true;
}
- load_segment 구현 (수정)
project1, 2까지의 pintos는 디스크에서 실행시킬 파일 전체를 물리메모리에 올리고 페이지 테이블에 맵핑해주었다.
이제는 load_segment
를 수정하여 page를 만들고 file에 대한 정보만을 initializer로 넘겨준다. 디스크에 있는 데이터를 물리메모리에 바로 올리지 않는다.
load_segment
file_information
구조체를 추가하여 file을 load할때 필요한 file, offset, read_bytes를 저장하고 initializer를 호출하고 aux 인자로 넘겨준다.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);
/* upage 주소부터 1페이지 단위씩 UNINIT 페이지를 만들어 프로세스의 spt에 넣는다(vm_alloc_page_with_initializer).
이 때 각 페이지의 타입에 맞게 initializer도 맞춰준다. */
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;
struct file_information *file_inf = (struct file_information *)malloc(sizeof(struct file_information));
file_inf->file = file;
file_inf->ofs = ofs;
file_inf->read_bytes = page_read_bytes;
if (!vm_alloc_page_with_initializer(VM_ANON, upage,
writable, lazy_load_segment, file_inf))
return false;
/* Advance. */
read_bytes -= page_read_bytes;
zero_bytes -= page_zero_bytes;
upage += PGSIZE;
ofs += page_read_bytes;
}
return true;
}
Q. anon 페이지로 모든 page를 만들고 있는데 file에서 올리는 거면 왜 anon 페이지로 만들어 주지?
lazy_load_segment() 구현
static bool
lazy_load_segment(struct page *page, void *aux)
{
/* TODO: Load the segment from the file */
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 = PGSIZE - page_read_bytes;
file_seek(file, offset); // file의 오프셋을 offset으로 바꾼다. 이제 offset부터 읽기 시작한다.
/* 페이지에 매핑된 물리 메모리(frame, 커널 가상 주소)에 파일의 데이터를 읽어온다. */
/* 제대로 못 읽어오면 페이지를 FREE시키고 FALSE 리턴 */
if (file_read(file, page->frame->kva, page_read_bytes) != (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;
}
setup_stack() 재구현
기존의 setup_stack()를 SPT가 추가된 상황에 맞게 수정한다.
setup_stack은 page를 할당하고 바로 물리 메모리와 맵핑하여 준다. → page_fault가 발생전에 물리메모리에 바로 할당
static bool
setup_stack(struct intr_frame *if_)
{
bool success = false;
void *stack_bottom = (void *)(((uint8_t *)USER_STACK) - PGSIZE);
/* ANON 페이지로 만들 UNINIT 페이지를 stack_bottom에서 위로 PGSIZE만큼(1 PAGE) 만든다.
이 때 TYPE에 VM_MARKER_0 flag를 추가함으로써 이 페이지가 STACK에 있다는 것을 표시한다. */
if (vm_alloc_page_with_initializer(VM_ANON, stack_bottom, true, NULL, NULL))
{
success = vm_claim_page(stack_bottom);
};
if (success)
{
if_->rsp = USER_STACK;
thread_current()->stack_bottom = stack_bottom;
}
return success;
}
/* 구현 후 스택의 모습
------------------------- <---- USER_STACK == if_->rsp
| |
| NEW PAGE |
| |
| |
------------------------- <---- stack_bottom
*/
Q. 왜 스택은 lazy loading을 하지 않고 바로 물리메모리와 맵핑해 주는 것인가??
/* Set up stack. */
if (!setup_stack(if_))
goto done;
if_->rip = ehdr.e_entry;
argument_stack(if_, argv_cnt, argv_list);
check_address() 수정
수정전
void check_address(void *addr)
{
struct thread *cur = thread_current();
/* 주소 addr이 유저 가상 주소가 아니거나 pml4에 없으면 */
if (addr == NULL || !is_user_vaddr(addr) || pml4_get_page(cur->pml4, addr) == NULL)
{
exit(-1);
}
/* addr이 유저 가상 주소이고 동시에 pml4에 있으면 페이지를 리턴해야 한다. */
}
수정후
struct page* check_address(void *addr)
{
/* 주소 addr이 유저 가상 주소가 아니거나 pml4에 없으면 프로세스 종료 */
if (addr == NULL || !is_user_vaddr(addr))
{
exit(-1);
}
/* 유저 가상 주소면 SPT에서 페이지 찾아서 리턴 */
return spt_find_page(&thread_current()->spt, addr);
}
check_buffer
Supplemental Page Table - Revisit(fork, exec)
SPT 테이블에 대한 copy와 clean up을 위한 수정이 필요함
child를 만들때 copy, process를 종료할 때 destroy가 필요
supplemental_page_table_copy
구현 요구사항
/* 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)
{
struct hash_iterator parnet_hast_iter;
hash_first(&parnet_hast_iter, &src->hash);
while (hash_next(&parnet_hast_iter))
{
struct page *page = hash_entry(hash_cur(&parnet_hast_iter), struct page, hash_elem);
if (!vm_alloc_page_with_initializer(VM_ANON, page->va, page->writable, NULL, NULL))
{
return false;
}
struct page *child_page = spt_find_page(dst, page->va);
if (!vm_claim_page(page->va))
{
return false;
}
if (page->frame != NULL)
{
memcpy(child_page->frame->kva, page->frame->kva, PGSIZE);
}
}
return true;
}
supplemental_page_kill
SPT
에 모든 리소스를 해제한다.destroy(page)
로 리소스 해제uninit_destroy
anon_destroy
PintOS Project3 GIthub 주소 PintOS