지금까지 구현한 프로젝트에서 USER_STACK은 단일 페이지로 고정되어 있었다. 이제 프로세스가 실행되면서 스택이 현재 크기를 초과할 경우, 필요에 따라 추가 페이지를 할당하여 스택을 확장해 주는 기능을 구현해야 한다.
핵심은 페이지 폴트가 발생했을 때, 이것이 유효한 스택 확장 요청인지 아니면 잘못된 메모리 접근인지를 구별하는 것이다.
일반적으로 OS는 시그널 전달 등을 통해 스택 데이터를 수정할 수 있다. 하지만 우리가 다루는 x86-64 아키텍처의 경우, 스택 포인터를 조정하기 전에 접근 권한을 먼저 확인하는 특성이 있다.
이로 인해 PUSH 명령어 실행 시, 스택 포인터(rsp)가 갱신되기 전에 해당 주소에 접근을 시도하게 되고, 실제 rsp보다 8바이트 아래 위치에서 페이지 폴트가 발생할 수 있다. 따라서 유효한 스택 접근으로 판단하는 기준은 다음과 같다.
접근 주소(
addr) >=rsp- 8
또한 과제 요구사항에 따라 스택의 최대 크기는 1MB로 제한한다.
스택 확장의 유효성을 검사하려면 현재 시점의 사용자 스택 포인터(rsp) 값을 정확히 알고 있어야 한다. 페이지 폴트가 발생한 상황에 따라 rsp를 가져오는 방식이 다르다.
intr_frame의 rsp 멤버를 통해 스택 포인터를 바로 확인할 수 있다.intr_frame의 rsp는 커널 스택을 가리키게 된다. 따라서 유저의 rsp 값을 알 수 없게 된다.유저 모드에서 커널 모드로 최초 전환되는 시점(예: syscall_handler)에 유저의 rsp 값을 별도로 저장해 두어야 한다.
struct thread 구조체에 uintptr_t user_rsp 필드를 추가한다.f->rsp 값을 thread->user_rsp에 백업한다.vm_try_handle_fault에서 현재 상태가 유저 모드라면 f->rsp를, 커널 모드라면 thread->user_rsp를 사용하여 검증 로직을 수행한다.기존의 페이지 폴트 핸들러를 수정하여 스택 확장이 필요한 케이스를 식별하고 처리해야 한다.
처리 흐름
is_user_vaddr)인지 확인한다.not_present가 false라면, 이미 페이지가 존재하는데 권한 문제로 접근한 것이므로 즉시 false를 반환한다.spt_find_page를 통해 보조 페이지 테이블에 해당 주소가 있는지 확인한다.vm_do_claim_page)한다.SPT에 페이지가 없는 경우, 다음 세 가지 조건을 모두 만족할 때만 스택 확장을 수행한다.
addr)가 유저 영역 내에 존재해야 한다.addr이 현재 유저 스택 포인터(rsp)보다 크거나, 혹은 PUSH 명령어를 감안하여 rsp - 8 이상이어야 한다.USER_STACK 시작점부터 addr까지의 거리 계산)위 조건이 충족되면 vm_stack_growth를 호출하여 실제 확장을 진행한다.
pg_round_down) 처리한다.vm_alloc_page(VM_ANON | VM_MARKER_0, ...)를 호출하여 새로운 익명 페이지를 할당한다. 이때 스택 영역임을 표시할 수 있다.vm_claim_page를 호출하여 물리 프레임과 매핑하고, 필요하다면 memset 등으로 초기화한다.이 과정을 통해 스택은 필요에 따라 1MB 한도 내에서 동적으로 늘어나게 된다.