페이지 폴트가 스택 영역에 대한 페이지 폴트일때 우리는 스택영역을 확장 시켜주어야 한다. 스택영역은 아래로 자라나는 것을 기억해야한다. 우선 스택영역을 확장 시키기 위해서는 현재 스택 포인터 위치를 알아야 한다. 따라서 스레드 구조체 안에 스택 포인터와 스택 바텀을 저장해 둔다. 인터럽트 프레임을 통해 조회 할 수도 있지만, rsp가 커널 영역에 있을 수도 있고 문맥 교환 도중 페이지 폴트가 일어나면 문제가 될 수도 있기 때문이다.(추측)
스택을 늘리는 것은 페이지 폴트가 발생했을 때 이므로 페이지 폴트 핸들러에 스택 확장을 추가해 주면 된다.
/* Return true on success */
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 = NULL;
/* TODO: Validate the fault */
/* TODO: Your code goes here */
if (is_kernel_vaddr(addr)) {
return false;
}
void *rsp_stack = is_kernel_vaddr(f->rsp) ? thread_current()->rsp_stack : f->rsp;
if (not_present){
if (!vm_claim_page(addr)) {
if (rsp_stack - 8 <= addr && USER_STACK - 0x100000 <= addr && addr <= USER_STACK) {
vm_stack_growth(thread_current()->stack_bottom - PGSIZE);
return true;
}
return false;
}
else
return true;
}
return false;
}
스택의 경우 vm_claim_page에서 false를 반환받아 If 문을 실행하게 된다. 이때 여러 조건들이 있는데 이 조건들은 깃북을 참고하면 파악할 수 있다. 우선 x86 시스템 에서는 cpu가 8바이트 단위로 데이터를 읽기 때문에 rsp_stack 보다 8바이트 안에 위치해야 한다. 그리고 스택은 1MB가 크기의 한계치 이므로 USER_STACK - 0x100000 보다는 위에 있어야 하며 USER_STACK 보다 아래에 있어야 한다.
이 조건을 만족하면 vm_stack_growth를 통해 스택 영역을 확장한다.
static void vm_stack_growth(void *addr UNUSED)
{
/* stack에 해당하는 ANON 페이지를 UNINIT으로 만들고 SPT에 넣어준다.
그 후 바로 claim해서 물리 메모리와 매핑해준다.*/
if (vm_alloc_page(VM_ANON | VM_MARKER_0, addr, 1)){
vm_claim_page(addr);
/* stack_bottom 갱신 */
thread_current()->stack_bottom -= PGSIZE;
}
}