VM 프로젝트 두 번째 포스팅은 프로세스의 execute 과정을 알아보려고 한다.
프로세스의 exec는 user가 exec라는 시스템 콜을 커널에게 보내고 이를 받아서 커널이 process_exec 함수를 실행한다.
process_exec 함수 내에 load 함수가 있는데 이는 실행가능 목적파일의 ELF 데이터들을 프로세스의 가상메모리로 로딩하는 함수이다.
- CSAPP 7장 링커 참고
대부분의 정보는 pintOS 스켈레톤 코드에 로딩하는 코드가 있지만, User stack 과 kernel memory에 User Pool 부분에서 해주어야 하는 부분이 있다.
process_exec의 흐름을 살펴보면 다음 그림과 같다.
메모리를 효율적으로 관리하기 위해 demand paging일 때 가상 메모리와 물리 메모리를 매핑한다.
가상 메모리에 있는 페이지 단위의 세그먼트를 보조 페이지 테이블(spt)에 uninit type page 으로 넣어두고 #PF가 발생할 때, uninit page를 담아둔 aux를 사용하여 type에 맞게 initialize하며 가상메모리와 물리메모리를 완전히 매핑하는 것이다.
예를 들면 load_segment 함수에서 함수 포인터에 lazy_load_segment 함수를 넣고, aux에 lazy_load_argument를 넣어둔다. 이후 vm_alloc_page_with_initializer을 실행하는데, 여기서 uninit_new()함수에서 page구조체에 정보를 넣어주는 것을 포켓몬 이브이(uninit page)가 번개의돌( lazy_load_argument )을 가지고 쥬피썬더(anon page or file backed page)로 진화(lazy_load_segment)하는 것이라고 생각할 수 있다.
여기서 잡아야 할 개념은
- page fault가 왜 발생하는지 ?
- page fault가 어떤 용도로 사용되는지 ?
- 처음 page를 할당할 때 uninit page으로 할당하고 type에 따라 어떻게 initialize가 되는지?
- vm_do_claim 함수가 어떻게 진행되는지?
에 대한 답을 할 수 있어야 한다.
코드구현
// load 에서 호출하여 lazy_load_arg에 file에 대한 정보를 담아두는 함수
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 lazy_load_arg *lazy_load_arg = (struct lazy_load_arg*)malloc(sizeof(struct lazy_load_arg));
lazy_load_arg->file = file;
lazy_load_arg->offset = ofs;
lazy_load_arg->length = page_read_bytes;
if (!vm_alloc_page_with_initializer (VM_ANON, upage,
writable, lazy_load_segment, lazy_load_arg))
return false;
/* Advance. */
read_bytes -= page_read_bytes;
zero_bytes -= page_zero_bytes;
upage += PGSIZE;
ofs += page_read_bytes;
}
return true;
}
//page를 할당해서 어떤 페이지인지 정보를 담아두기
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;
/* Check wheter the upage is already occupied or not. */
if (spt_find_page (spt, upage) == NULL) {
/* TODO: Create the page, fetch the initialier according to the VM type,
* TODO: and then create "uninit" page struct by calling uninit_new. You
* TODO: should modify the field after calling the uninit_new. */
struct page *p = (struct page *)malloc(sizeof(struct page));
bool (*page_initializer) (struct page *, enum vm_type, void *kva);
switch (VM_TYPE(type))
{
case VM_ANON:
page_initializer = anon_initializer;
break;
case VM_FILE:
page_initializer = file_backed_initializer;
break;
default:
break;
}
uninit_new(p,upage,init,type,aux,page_initializer);
p->writable = writable;
/* TODO: Insert the page into the spt. */
return spt_insert_page(spt,p);
}
err:
return false;
}
bool spt_insert_page (struct supplemental_page_table *spt UNUSED,
struct page *page UNUSED) {
int succ = false;
/* TODO: Fill this function. */
if(hash_insert(&spt->hash_table,&page->hash_elem) == NULL)
succ = true;
return succ;
}
# page fault
bool vm_try_handle_fault (struct intr_frame *f UNUSED, void *addr UNUSED, bool user UNUSED, bool write UNUSED, bool not_present UNUSED) {
struct supplemental_page_table *spt UNUSED = &thread_current()->spt;
struct page *page = spt_find_page(&thread_current()->spt, addr);
/* TODO: Validate the fault */
if (addr == NULL || is_kernel_vaddr(addr))
return false;
/** Project 3: Copy On Write (Extra) - 접근한 메모리의 page가 존재하고 write 요청인데 write protected인 경우라 발생한 fault일 경우 write 0 읽기전용 write 1 쓰기 가능*/
if (!not_present && write)
return vm_handle_wp(page);
/** Project 3: Copy On Write (Extra) - 이전에 만들었던 페이지인데 swap out되어서 현재 spt에서 삭제하였을 때 stack_growth 대신 claim_page를 하기 위해 조건 분기 */
if (!page) {
/** Project 3: Stack Growth - stack growth로 처리할 수 있는 경우 */
/* stack pointer 아래 8바이트는 페이지 폴트 발생 & addr 위치를 USER_STACK에서 1MB로 제한 */
void *stack_pointer = user ? f->rsp : thread_current()->stack_pointer;
if (stack_pointer - 8 <= addr && addr >= USER_STACK-MAX_STACK_POINT && addr <= USER_STACK) {
vm_stack_growth(thread_current()->stack_bottom - PGSIZE);
return true;
}
return false;
}
return vm_do_claim_page(page); // demand page 수행
}
vm_do_claim_page에서 swap_in 에 의해 page type에 따라 초기화가 되고 lazy_load가 진행됨.
static bool uninit_initialize (struct page *page, void *kva) {
struct uninit_page *uninit = &page->uninit;
/* Fetch first, page_initialize may overwrite the values */
vm_initializer *init = uninit->init;
void *aux = uninit->aux;
/* TODO: You may need to fix this function. */
// initialize & lazy_load
return uninit->page_initializer (page, uninit->type, kva) &&
(init ? init (page, aux) : true);
}
// file page swap in
bool lazy_load_segment (struct page *page, void *aux) {
struct lazy_load_arg *lazy_load_arg = (struct lazy_load_arg *) aux;
struct file *file = lazy_load_arg->file;
off_t offset = lazy_load_arg->offset;
size_t page_read_bytes = lazy_load_arg->length;
size_t page_zero_bytes = PGSIZE - page_read_bytes;
file_seek(lazy_load_arg->file,lazy_load_arg->offset);
if (file_read(file, page->frame->kva, page_read_bytes) != (off_t)page_read_bytes) {
palloc_free_page(page->frame->kva);
return false;
}
memset(page->frame->kva + page_read_bytes, 0, page_zero_bytes);
return true;
}
// anon page swap in
static bool anon_swap_in (struct page *page, void *kva) {
struct anon_page *anon_page = &page->anon;
size_t slot = anon_page->slot;
size_t sector = slot * SECTORS_IN_PAGE;
if (slot == BITMAP_ERROR || !bitmap_test(swap_table, slot))
return false;
bitmap_set(swap_table, slot, false);
for (size_t i = 0; i < SECTORS_IN_PAGE; i++)
disk_read(swap_disk, sector + i, kva + DISK_SECTOR_SIZE * i);
sector = BITMAP_ERROR;
return true;
}
이렇게 하면 페이지를 spt에 추가하고 frame과 연결하여 매핑하고 pml4에 세팅한 후, page fault로 인해 매핑된 것을 확인할 수 있다.
page fault : 인스트럭션이 가상메모리 테이블을 참조했을 때 , 대응되는 실제 메모리 page가 존재하지 않는 상황이며, 디스크에서 데이터를 가져와야 할 때 발생한다. 페이지 오류 핸들러는 디스크에서 적절한 페이지를 로드해서 오류를 발생시킨 인스트럭션으로 제어를 넘겨준다. 이 인스트럭션이 다시 실행될 때 적절한 페이지는 메모리에 있게 되므로 인스트럭션은 오류를 발생하지 않고 동작할 수 있다.
page fault 가 발생하면 운영체제는 그 데이터를 메모리로 가져와서 마치 페이지 폴트가 전혀 발생하지 않은 것처럼 프로그램이 계속적으로 작동하게 해준다.
'PintOS' 카테고리의 다른 글
PintOS Project 3 : Virtual Memory (4) - mmap & munmap (0) | 2024.10.01 |
---|---|
PintOS Project 3 : Virtual Memory(3) - swap in & swap out LRU (0) | 2024.10.01 |
PintOS Project 3 : Virtual Memory (1) - supplemetal page table 보조 페이지 테이블 (0) | 2024.10.01 |
PintOS Project 2 : system call (2) (1) | 2024.09.18 |
PintOS Project 2 : system call (1) (2) | 2024.09.18 |