PintOS/Project 3 : VIRTUAL MEMORY

[PintOS] Project 3 : VM - Stack Growth

넌뭐가그렇게중요해 2025. 6. 9. 11:16

PintOS의 가상 메모리 시스템에서 중요한 기능 중 하나는 Stack Growth, 즉 스택 확장입니다.
이 글에서는 페이지 폴트가 발생했을 때, 해당 주소가 스택 확장 대상이면 어떻게 메모리를 할당하는지,
그리고 그 과정에서 호출되는 함수들의 역할을 Control Flow와 함께 상세히 정리해 보겠습니다.


Stack Growth란?

프로그램 실행 중 지역 변수 선언, 함수 호출 등으로 스택 영역이 확장될 수 있습니다. 

PintOS에서는 스택이 커질 때마다 새로 페이지를 할당해줘야 하며,

이를 페이지 폴트(Page Fault)를 이용해 지연 할당 방식(lazy allocation)으로 처리합니다. 


전체 흐름(Control Flow)

[유저 프로그램에서 스택 영역에 접근 (ex: PUSH)]
        ↓
[page_fault() 호출]
        ↓
[vm_try_handle_fault() → SPT에 addr가 없음]
        ↓
[스택 확장 조건 검사: rsp - STACK_GROW_LIMIT 이하인지?]
        ↓
[조건 만족 → vm_stack_growth(addr) 호출]
        ↓
[vm_alloc_page(VM_ANON, addr, true)]
        ↓
[SPT에 anon page 등록 (초기화 함수: anon_initializer)]
        ↓
[vm_claim_page(addr)]
        ↓
[물리 프레임 할당 및 유저 주소 공간에 매핑]
        ↓
[프로세스 정상 실행 재개]

1. vm_try_handle_fault() – 페이지 폴트를 처리하고, 필요한 경우 스택 확장 수행하기

/* Return true on success */
/* bogus 폴트인지? 스택확장 폴트인지?
 * SPT 뒤져서 존재하면 bogus 폴트!!
 * addr이 유저 스택 시작 주소 + 1MB를 넘지 않으면 스택확장 폴트
 * 찐폴트면 false 리턴
 * 아니면 vm_do_claim_page 호출
 * 스택확장 폴트에서 valid를 확인하려면 유저 스택 시작 주소 + 1MB를 넘는지 확인
 * addr = thread 내의 user_rsp
 * addr은 user_rsp보다 크면 안됨
 * 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)
{
   // 현재 쓰레드의 SPT를 가져옴
   struct supplemental_page_table *spt UNUSED = &thread_current()->spt;
   // Fault가 발생한 주소를 페이지 단위로 정렬(페이지 시작 주소로 내림)
   addr = pg_round_down(addr);
   // 유저 스택의 rsp 가져오기
   uintptr_t rsp = thread_current()->user_rsp; 

   // spt page 찾기
   struct page *page = spt_find_page(spt, addr);

   
   // 스택 확장 판단 조건
   if (page == NULL                                   // 1. SPT에 존재 X
      && (uintptr_t)addr >= rsp - STACK_GROW_RANGE   // 2. 접근 주소가 현재 유저 스택보다 아래
      && addr < USER_STACK                           // 3. 접근 주소가 유저 영역(USER_STACK) 안쪽이고
      && addr >= USER_STACK - (1 << 20))             // 4. 접근 주소가 USER_STACK 기준으로 1MB 이상 확장되지 않은 경우
      {
         vm_stack_growth(addr);
         return true;
      }
      
   // 찐폴트 → 페이지가 없고 스택 확장 조건도 안됨
   if (page == NULL)
      return false;

   // 쓰기 접근인데 페이지가 쓰기 불가능한 경우 → 보호 위반
   if (write == true && !page->writable)
      return false;

   // Project 3 : VM extra COW
   if (write == true && page->writable && page->frame != NULL)
      return vm_handle_wp(page);
   
   // swap_in 함수 존재 여부 확인
   ASSERT(page->operations != NULL && page->operations->swap_in != NULL);
   
   // 실제 물리 메모리에 페이지를 할당하고 매핑
   return vm_do_claim_page(page);
}

1.1.  vm_try_handle_fault() 핵심 요약

  • 페이지 폴트 발생 시 호출되어, 접근 주소가 SPT에 등록되어 있는지 확인합니다.
  • 등록되어 있지 않다면, 해당 주소가 스택 확장 대상인지 검사합니다.
  • 조건을 만족하면 vm_stack_growth()를 호출합니다.

1.2. vm_try_handle_fault()를 이렇게 구현한 이유

  • 정상적인 접근인지 확인하는 단계입니다.
  • 예외적 스택 접근 외에는 허용하지 않기 위해, 조건을 세심하게 구성합니다.
  • Copy-on-Write 기능이 있는 경우, 여기서 쓰기 권한에 따라 vm_handle_wp()도 호출됩니다.

2. vm_stack_growth() – 유저 스택 확장을 위한 anonymous 페이지 생성 및 등록하기

/* Growing the stack. */
static void
vm_stack_growth(void *addr UNUSED)
{
   /* 스택 최하단에 익명 페이지를 추가하여 사용
    * addr은 PGSIZE로 내림(정렬)하여 사용 */
   vm_alloc_page(VM_ANON, addr, true); // 스택 최하단에 익명 페이지 추가
   /* 실제 물리 메모리 프레임을 할당하고, 페이지 활성화(claim)함
      이 작업이 있어야 페이지 폴트 해결 및 해당 주소 접근 가능
   */
   vm_claim_page(addr);
}

 

2.1.  vm_stack_growth() 핵심 요약 

  • 스택 확장 조건을 만족한 주소에 대해 새로운 anonymous 페이지를 할당합니다.
  • vm_alloc_page()를 통해 SPT에 struct page 객체를 등록하고,
  • 이후 vm_claim_page()로 물리 메모리까지 실제 할당합니다.

2.2. vm_stack_growth()를 이렇게 구현한 이유

  • 유저 스택의 지연 확장 처리: 실제 접근이 있을 때만 메모리를 할당해 효율적 메모리 사용이 가능합니다.
  • 직접 claim까지 연결해 줘야 프로세스가 정상적으로 실행을 재개할 수 있기 때문에, vm_claim_page()까지 호출합니다.