
2025.06.09
오늘한 내용 : PintOS - Project3: Virtual Memory - stack growth 구현 완료
WEEK 13 : 정글 끝까지(PintOS) - Virtual Memory
5. 스택 확장(vm_stack_growth) (
vm/vm.c):스택이 ↓(작은 주소)로 넘어갈 때마다 새로운 익명 페이지를 할당.
vm_try_handle_fault()가 kernel context로 진입했을 때도 “실제로 fault_addr가 user 스택 영역이라면” 여전히 유저 스택을 늘려 줌.user==false 플래그로 단순히 걸러 버리면, 커널이 copy_from_user() 같은 함수로 유저 버퍼를 접근할 때 생긴 스택 폴트를 못 살려 주니까, 우리가 thread->user_rsp_saved를 저장해 두고 “커널 모드라도 user_rsp_saved 기반으로 스택 확장”을 허용했던 것.
/* Return true on success */
bool vm_try_handle_fault(struct intr_frame *f, void *addr,
bool user, bool write, bool not_present)
{
struct supplemental_page_table *spt UNUSED = &thread_current()->spt;
struct page *page = NULL;
/* TODO: Validate the fault */
/* TODO: Your code goes here */
/*
NULL 체크 및 커널 영역 접근은 실패
is_kernel_vaddr(addr) 는 “어떤 주소를 건드리려 했나” (대상 메모리 영역)
user 는 “어디서 폴트가 발생했나” (접근 주체의 권한 레벨)
(수정)user 검사 제외 : 유저 모드, 커널 영역 둘다 스택 확장이 필요할 수 있음
*/
if (addr == NULL || is_kernel_vaddr(addr))
{
return false;
}
/* fault된 가상 주소를 페이지 경계로 반올림 / 주소가 속한 페이지의 첫 부분 */
void *fault_page = pg_round_down(addr);
/* RSP 결정: user 모드면 f->rsp, kernel 모드면 저장해 둔 user_rsp_saved */
void *rsp = user ? f->rsp : thread_current()->rsp_stack;
/* spt에 예약된(매핑은 안되어 있는 : not_present)
uninit 페이지가 있으면 물리 메모리로 올리기 */
if (not_present)
{
page = spt_find_page(spt, fault_page);
if (page == NULL)
{
/* 기존 페이지가 없으면 스택 확장 조건 검사 */
if (addr < USER_STACK /* 유저 스택 영역 이내 */
&& (uintptr_t)USER_STACK - (uintptr_t)fault_page <= (1 << 20) /* 1MB 제한 */
&& (uintptr_t)addr >= (uintptr_t)rsp - 32 && (uintptr_t)fault_page + PGSIZE >= (uintptr_t)rsp - 32)
{
/* stack_bottom 에서 fault_page 까지 한 페이지만큼씩 순차 확장 */
void *stack_bottom = thread_current()->stack_bottom;
while (stack_bottom > fault_page)
{
void *next_page = stack_bottom - PGSIZE;
/* 1MB 한도 재검사 (안정성) */
if ((uintptr_t)USER_STACK - (uintptr_t)next_page > (1 << 20))
break;
/* 스택 확장 시도 */
vm_stack_growth(next_page);
stack_bottom = next_page;
/* 확장 후 최하단 경계 갱신 */
thread_current()->stack_bottom = fault_page;
}
}
/* SPT에 새 페이지가 등록되었으므로 다시 찾기 */
page = spt_find_page(spt, fault_page);
}
/* page가 존재하면 쓰기 권한 검사 후 claim */
if (page != NULL)
{
if (write && !page->writable)
return false;
return vm_do_claim_page(page);
}
return false;
}
}
syscall_handler())/ exception.c (page_fault())exception.c
---------------
static void page_fault(struct intr_frame *f)
{
...
#ifdef VM
/* For project 3 and later. */
/* 유저 모드 폴트일 때만 저장 */
if (user)
thread_current()->rsp_stack = f->rsp;
if (vm_try_handle_fault(f, fault_addr, user, write, not_present))
return;
#endif
...
}
-------------------------
syscall.c
-------------------------
void syscall_handler(struct intr_frame *f UNUSED)
{
...
/* Project3 VM : stack growth를 위한 추가 */
thread_current()->rsp_stack = f->rsp;
int syscall_num = (int)f->R.rax;
...
}
/* Growing the stack. */
static void
vm_stack_growth(void *addr)
{
/* 스택 영역은 프로그램 실행 중 생기는 빈 메모리를 담는 공간
=> anonmous page
*/
vm_alloc_page(VM_ANON, pg_round_down(addr), true);
}