Project3: Virtual Memory - Stack Growth

김민호·2025년 12월 6일

pintos

목록 보기
8/9

1. Stack Growth 개념

지금까지 구현한 프로젝트에서 USER_STACK은 단일 페이지로 고정되어 있었다. 이제 프로세스가 실행되면서 스택이 현재 크기를 초과할 경우, 필요에 따라 추가 페이지를 할당하여 스택을 확장해 주는 기능을 구현해야 한다.

핵심은 페이지 폴트가 발생했을 때, 이것이 유효한 스택 확장 요청인지 아니면 잘못된 메모리 접근인지를 구별하는 것이다.

식별 규칙 (Heuristic)

일반적으로 OS는 시그널 전달 등을 통해 스택 데이터를 수정할 수 있다. 하지만 우리가 다루는 x86-64 아키텍처의 경우, 스택 포인터를 조정하기 전에 접근 권한을 먼저 확인하는 특성이 있다.

이로 인해 PUSH 명령어 실행 시, 스택 포인터(rsp)가 갱신되기 전에 해당 주소에 접근을 시도하게 되고, 실제 rsp보다 8바이트 아래 위치에서 페이지 폴트가 발생할 수 있다. 따라서 유효한 스택 접근으로 판단하는 기준은 다음과 같다.

접근 주소(addr) >= rsp - 8

또한 과제 요구사항에 따라 스택의 최대 크기는 1MB로 제한한다.


2. 구현 전제: RSP 값의 추적

스택 확장의 유효성을 검사하려면 현재 시점의 사용자 스택 포인터(rsp) 값을 정확히 알고 있어야 한다. 페이지 폴트가 발생한 상황에 따라 rsp를 가져오는 방식이 다르다.

  • User Mode에서 발생: intr_frame의 rsp 멤버를 통해 스택 포인터를 바로 확인할 수 있다.
  • Kernel Mode에서 발생: 시스템 콜 처리 도중 페이지 폴트가 발생하여 커널 모드로 진입한 경우, intr_frame의 rsp는 커널 스택을 가리키게 된다. 따라서 유저의 rsp 값을 알 수 없게 된다.

해결 방안

유저 모드에서 커널 모드로 최초 전환되는 시점(예: syscall_handler)에 유저의 rsp 값을 별도로 저장해 두어야 한다.

  1. struct thread 구조체에 uintptr_t user_rsp 필드를 추가한다.
  2. 인터럽트 핸들러 도입부에서 f->rsp 값을 thread->user_rsp에 백업한다.
  3. vm_try_handle_fault에서 현재 상태가 유저 모드라면 f->rsp를, 커널 모드라면 thread->user_rsp를 사용하여 검증 로직을 수행한다.

3. vm_try_handle_fault 로직 수정

기존의 페이지 폴트 핸들러를 수정하여 스택 확장이 필요한 케이스를 식별하고 처리해야 한다.

처리 흐름

  1. 유효성 검사: 접근한 주소가 유저 영역(is_user_vaddr)인지 확인한다.
  2. Present 비트 확인: not_present가 false라면, 이미 페이지가 존재하는데 권한 문제로 접근한 것이므로 즉시 false를 반환한다.
  3. SPT 조회: spt_find_page를 통해 보조 페이지 테이블에 해당 주소가 있는지 확인한다.
    • SPT에 존재하는 경우: 기존 로직대로 Lazy Loading 등을 수행(vm_do_claim_page)한다.
    • SPT에 없는 경우: 이곳이 바로 Stack Growth를 검토해야 할 지점이다.

4. Stack Growth 상세 구현

SPT에 페이지가 없는 경우, 다음 세 가지 조건을 모두 만족할 때만 스택 확장을 수행한다.

확장 조건

  1. 주소 범위: 접근한 주소(addr)가 유저 영역 내에 존재해야 한다.
  2. RSP 근접성: addr이 현재 유저 스택 포인터(rsp)보다 크거나, 혹은 PUSH 명령어를 감안하여 rsp - 8 이상이어야 한다.
  3. 크기 제한: 확장 후 전체 스택의 크기가 1MB를 초과하지 않아야 한다. (USER_STACK 시작점부터 addr까지의 거리 계산)

vm_stack_growth 함수

위 조건이 충족되면 vm_stack_growth를 호출하여 실제 확장을 진행한다.

  1. Align: 접근한 주소를 페이지 단위로 내림(pg_round_down) 처리한다.
  2. Allocation: vm_alloc_page(VM_ANON | VM_MARKER_0, ...)를 호출하여 새로운 익명 페이지를 할당한다. 이때 스택 영역임을 표시할 수 있다.
  3. Claim: vm_claim_page를 호출하여 물리 프레임과 매핑하고, 필요하다면 memset 등으로 초기화한다.

이 과정을 통해 스택은 필요에 따라 1MB 한도 내에서 동적으로 늘어나게 된다.

profile
개발자를 꿈꾸고 있어요

0개의 댓글