이번 포스팅에서는 어떤 함수에 어떤 로직을 구현해야 하는지 설명하도록 하겠다.
vm_try_handle_fault(struct intr_frame *f, void *addr, ...)rsp - 8 근처에서 발생했고, USER_STACK 범위 이내면 → vm_stack_growth()호출spt_find_page(), vm_do_claim_page() 통해 매핑까지 완료bool vm_try_handle_fault(struct intr_frame *f, void *addr, bool user, bool write, bool not_present)
{
struct supplemental_page_table *spt = &thread_current()->spt;
struct page *page = NULL;
// 현재 커널/유저 모드에 따라 올바른 RSP를 선택
uint64_t user_rsp = user ? f->rsp : thread_current()->user_rsp;
if (not_present)
{
// [1] Stack growth: 접근 주소가 RSP 아래거나, 전체 스택 범위 내이면 자동 확장 허용
if (user_rsp - 8 == addr ||
(USER_STACK - (1 << 20) <= user_rsp && user_rsp < addr && addr < USER_STACK))
{
vm_stack_growth(addr);
return true;
}
// [2] Lazy loading: SPT에서 해당 주소에 등록된 페이지가 있는지 확인
page = spt_find_page(spt, pg_round_down(addr));
// (1) 없는 주소이거나, (2) 쓰기 요청인데 read-only 페이지일 경우 → 강제 종료
if (page == NULL || (write && !page->writable))
exit(-1);
}
else if (write)
{
// Protection fault: 존재하는 페이지지만 쓰기 허용되지 않은 경우 → 종료
exit(-1);
}
// 정상적인 페이지 접근 → 물리 메모리에 매핑 시도 (lazy load 수행)
return vm_do_claim_page(page);
}
vm_stack_growth(void *addr)pg_round_down(addr) 를 기준으로 anonymous 페이지를 lazy 방식으로 등록vm_claim_page()로 물리 메모리 확보/* 유저 스택 확장용 함수
* - 접근한 주소를 기준으로 anonymous 페이지 할당 및 claim */
static void vm_stack_growth(void *addr UNUSED)
{
vm_alloc_page_with_initializer(VM_ANON, pg_round_down(addr), 1, NULL, NULL);
vm_claim_page(pg_round_down(addr));
}
vm_claim_page(void *va)vm_stack_growth() 내에서 사용됨bool
vm_claim_page (void *va UNUSED) {
struct page *page = NULL;
/* TODO: 이 함수를 구현하세요. */
page = spt_find_page(&thread_current()->spt, va);
if (page == NULL)
return false;
return vm_do_claim_page (page);
}
vm_do_claim_page(struct page *page)vm_do_claim_page (struct page *page) {
struct frame *frame = vm_get_frame ();
/* 연결 설정 */
frame->page = page;
page->frame = frame;
/* TODO: 페이지의 VA를 프레임의 PA에 매핑하기 위한 페이지 테이블 엔트리를 삽입하세요. */
struct thread *current = thread_current();
pml4_set_page(current->pml4, page->va, frame->kva, page->writable);
return swap_in(page, frame->kva);
}
Page Fault
└─> vm_try_handle_fault()
├─> 스택 접근 조건 검사 → vm_stack_growth()
│ ├─> vm_alloc_page_with_initializer()
│ │ └─> anon_initializer 설정 + SPT 등록
│ └─> vm_claim_page()
│ └─> vm_do_claim_page()
│ ├─> vm_get_frame()
│ ├─> pml4_set_page()
│ └─> swap_in()
└─> spt_find_page() → 매핑 여부 확인
Stack Growth는 구현할 함수가 사실 많지는 않았던 것 같다.
정리하면서 함수들을 작성했지만 정작 수정한 함수는 5개 이내였던 것 같다.
다만 구현을 하고 테스트를 돌리면서 원래 잘 작동되던 테스트 케이스들이 문제가 생기는 일이 많았다.
Stack Growth의 함수들을 완성하면
...
pass tests/vm/pt-grow-stack
pass tests/vm/pt-grow-bad
pass tests/vm/pt-big-stk-obj
pass tests/vm/pt-bad-addr
pass tests/vm/pt-bad-read
pass tests/vm/pt-write-code
pass tests/vm/pt-write-code2
pass tests/vm/pt-grow-stk-sc
...
이러한 결과가 나온다.
사실 이거보다 더 많이 통과되는 것 같은데 기존 로직의 문제인지 구현 코드의 문제인지
다른 블로그들을 찾아봤을 때, 그 블로그들의 결과보다 테스트가 적게 통과했다.
Project 3에 들어오니 디버깅 지옥이라서 정신적으로나 육체적으로나 많이 지치고 힘들어지는 것 같다.
그래도 마지막까지 화이팅 해보자 !