vm_try_handle_fault()에 stack growth 추가 - sihyun10/pintos-lab-vm GitHub Wiki

들어가기에 앞서

Q. stack growth가 필요할까?

  • 유저 스택은 사용자 프로세스의 실행 중에 동적으로 커질 수 있다.
  • 가상 주소를 접근했는데, 매핑된 프레임이 없다면 page fault가 발생한다.
  • 이때 그 주소가 유저 스택 영역 내이며, 조건을 만족한다면 새로운 페이지를 할당해서 스택을 확장한다.

Q. stack growth 유의사항

  • 스택의 최대 크기는 1MB이다.
    • 즉, 유저 스택은 USER_STACK에서 최대 1MB 아래까지 확장 가능하다.
  • 예외적으로 rsp - 8 주소에 접근하는 경우도 허용된다.
    • 이는 push 명령을 고려한 예외상황이다.

vm_try_handle_fault() 함수 설명

프로세스가 유저 스택 내의 아직 매핑 되지 않은 주소에 접근하여 page fault가 발생한 경우,
그 주소가 스택 영역이라면 새로운 페이지를 할당하여 stack growth를 수행하는 함수다.

bool vm_try_handle_fault(struct intr_frame *f UNUSED, void *addr UNUSED,
                         bool user UNUSED, bool write UNUSED, bool not_present UNUSED);
  • f : 페이지 폴트 발생 순간의 레지스터 값들을 담고 있는 구조체
  • addr : page fault가 발생한 주소
  • user : 유저 모드에서 발생했는지?
  • write : 쓰기 접근이었는지?
  • not_present : 페이지가 존재하지 않아 발생한 fault인지?

[stack growth 조건] _ vm_try_handle_fault() 내에서

  1. 페이지가 존재하지 않아야 한다 (not_present == true)
  2. 유저 모드에서 발생한 페이지 폴트여야 한다 (user == true)
    • 유저 스택은 유저 모드에서만 접근 가능하기 때문이다
  3. fault 주소(addr)는 현재 유저 스택 포인터(rsp)보다 같거나 높은 주소여야 한다
    • 일반적으로 addr >= rsp - 8와 같은 범위를 허용한다 (push 고려)
  4. fault 주소(addr)는 USER_STACK보다 작거나 같아야 한다 (스택은 USER_STACK 아래 방향으로 성장)
  5. fault 주소(addr)는 USER_STACK - 1MB보다 크거나 같아야 한다. (최대 스택 크기 1MB)

→ 위 조건을 모두 만족할 경우, 해당 주소에 대해 stack growth를 수행한다.


  • USER_STACK : 사용자 스택의 시작 주소
  • 최대 스택 크기 : 1MB

vm.h 파일에 정의

Pintos에서는 최대 스택 크기를 1MB로 제한한다.
(1 << 20)은 1MB = 2^20 bytes를 의미한다.
따라서 STACK_LIMIT는 스택이 자랄 수 있는 가장 아래 주소, 즉 스택의 바닥 주소이다.

/* 최대 스택 크기 1MB 제한 */
#define STACK_LIMIT (USER_STACK - (1 << 20))  

페이지가 존재하지 않아 fault가 발생한 경우에 처리해주어야 한다.

  if (not_present)
  {
    void *rsp = f->rsp;
    if (!user)
      rsp = thread_current()->user_rsp;

    // Stack Growth 판단
    if ((uintptr_t)addr >= (uintptr_t)STACK_LIMIT &&
        (uintptr_t)addr <= (uintptr_t)USER_STACK &&
        (uintptr_t)addr >= (uintptr_t)rsp - 8)
    {
      vm_stack_growth(addr);
      return true;
    }

rsp 결정

void *rsp = f->rsp;
if (!user)
  rsp = thread_current()->user_rsp;
  • 유저 모드에서 발생한 fault라면 f->rsp
  • 커널 모드이면 user_rsp참조

syscall.c 파일에서 syscall_handler()

#ifdef VM
  // 유저 스택 포인터 저장
  thread_current()->user_rsp = f->rsp;
#endif

user mode -> kernel mode 진입 직후,
시스템 콜 발생 시 유저 스택 포인터(rsp)값을 저장해두어야
이후 page fault가 발생 시 스택 접근인지 여부를 판단 가능하다.

Stack Growth 조건

if ((uintptr_t)addr >= (uintptr_t)STACK_LIMIT &&
    (uintptr_t)addr <= (uintptr_t)USER_STACK &&
    (uintptr_t)addr >= (uintptr_t)rsp - 8)
  • Stack 영역 범위 제한 (최대 1MB)
  • rsp - 8 이상 위치만 허용 (addr >= rsp - 8)
    • addr == rsp - 8인 경우 && addr >= rsp 인 경우를 합친거다.

최종 코드

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;

  //  잘못된 주소거나 커널 영역 접근 거부
  if (addr == NULL || is_kernel_vaddr(addr))
    return false;

  // 페이지가 존재하지 않아 fault가 발생한 경우
  if (not_present)
  {
    void *rsp = f->rsp;
    if (!user)
      rsp = thread_current()->user_rsp;

    // Stack Growth 판단
    if ((uintptr_t)addr >= (uintptr_t)STACK_LIMIT &&
        (uintptr_t)addr <= (uintptr_t)USER_STACK &&
        (uintptr_t)addr >= (uintptr_t)rsp - 8)
    {
      vm_stack_growth(addr);
      return true;
    }

    // 기존 SPT에서 찾기
    struct page *page = spt_find_page(spt, addr);
    if (page == NULL)
      return false;

    // 쓰기 불가능한 페이지에 write 시도한 경우
    if (write && !page->writable)
      return false;

    // 페이지를 메모리에 매핑
    return vm_do_claim_page(page);
  }
  return false;
}

stack_growth 하는 시점

stack_growth 설명