본문 바로가기

[pintOS] stack_growth 구현기

@정소민fan2025. 6. 3. 19:50

이제 스택 확장을 구현해보자. 그 전에 과제 설명서부터 !!

늘 하던대로 과제 설명서를 캡처해서 올리고 싶지만... project 3부터는 과제 하나하나의 설명이 너무 길어서 캡처가 힘들다

내가 사용하던 과제 설명서 링크를 올려둘테니, 여기서 스택 확장 쪽을 참고하자

https://www.notion.so/1fb7bb7778c980f9976afc46cb450710#1fd7bb7778c9803b80cecad309319319

 

과제 설명서 | Notion

여러분의 OS는 여러 실행 스레드를 적절한 동기화와 함께 처리할 수 있고, 여러 사용자 프로그램도 동시에 로드할 수 있습니다.

verdant-bathtub-bae.notion.site

스택 확장의 조건이 어떻게 되느냐?

스택 접근처럼 보이는 페이지 폴트에서만 이를 처리해야 한다고 한다. 이 뜻이 참 애매모호하다. 스택 바텀에서 얼마나 떨어져 있는 곳까지를 스택 확장 폴트로 처리할 것인가?? 뭔가 명확한 기준이 있으면 좋겠는데... 과제 설명서에서는 이는 주어지지 않는다. 하지만 테스트 케이스를 까본 결과 스택 바텀에서 한 페이지까지는 스택 확장 폴트로 처리가 되는것 같다.

그래서 나는 vm.h 상수로 미리 스택 확장 폴트 범위를 정의해뒀다

#define STACK_GROW_RANGE 4192

왜 4096이 아니냐면... 그냥 오타가 났다. 이래도 테스트가 잘 통과하는걸 보니 문제는 없는 듯 하다.

 

그전에 또 !! 해줘야 할 것이 있다. 스택 바텀을 나타내는 rsp... 그런데 커널 모드에서 페이지 폴트가 발생하면 어떻게 될까? 커널 스택의 rsp 값이 인터럽트 프레임에 저장되게 된다. 그러면 우리는 이 rsp값으로 스택 확장 폴트를 판단하게 되고 당연히 테스트 여기저기서 펑펑 터지게 될 것이다.

 

그래서 !! 유저 모드에서의 rsp만 따로 저장해둘 필요가 있다 !!

그러면 유저 모드에서의 rsp는 어떻게 판단할 것인가? 시스템 콜은 항상 유저 모드에서만 발생한다. 그러면 이 때의 rsp값을 어딘가에 저장해두면 될 것 같다. 그럼 유저 모드에서의 페이지 폴트도 있지 않을까? 이 두가지 상황에서만 rsp값을 저장하면 될 것 같다.

 

struct thread
{
...
    /* project3 stack growth 유저스택 rsp */
    void *user_rsp;
...
}

이렇게 스레드 구조체에 user 스택의 rsp 값 필드를 만들어두자.

 

void syscall_handler(struct intr_frame *f UNUSED)
{
    /* 커널 내에서의 페이지 폴트 시 rsp가 망가지는 것을 대비 */
    if (f->cs == SEL_UCSEG)
        thread_current()->user_rsp = f->rsp;
    ...
}

그리고 시스템 콜의 진입점인 syscall.c의 syscall_handler에 user_rsp값을 저장해두자. SEL_UCSEG는 유저 모드임을 나타내는 상수이다. 물론 유저 모드에서만 시스템 콜이 발생하겠지만... 혹시 모르니까?

static void
page_fault(struct intr_frame *f)
{
    if (f->cs == SEL_UCSEG)
        thread_current()->user_rsp = f->rsp;
    ...
}

똑같이 exception.c의 page_fault의 맨 처음에서 user_rsp값을 저장해두자. 유저 모드에서도 페이지 폴트가 충분히 발생할 수 있으니까 !!

 

이러면 스택 확장을 위한 모든 준비가 끝났다.

vm_try_handle_fault

기존의 handle_fault 함수는 단순히 이 폴트가 bogus 폴트인지, 진짜 폴트인지 확인만 해 주었다. 이제는 스택 확장 폴트인지 확인도 해주어야 한다.

bool vm_try_handle_fault(struct intr_frame *f UNUSED, void *addr UNUSED,
                         bool user UNUSED, bool write UNUSED, bool not_present UNUSED)
{
...   
    addr = pg_round_down(addr);

    uintptr_t rsp = thread_current()->user_rsp; // 유저 스택의 rsp 가져오기

    if ((uintptr_t)addr >= rsp - STACK_GROW_RANGE && addr < USER_STACK && addr >= USER_STACK - (1 << 20))
    {
      vm_stack_growth(addr);
      return true;
    }
...
}

우리는 이렇게만 추가해주면 된다 !!

여기서는 딱 두가지 조건만 검사한다

  1. 폴트가 발생한 주소가 설정한 rsp - 폴트 확장 범위 (밑으로 자라니까 뺴기) 보다 크거나 작은가?
  2. 폴트 발생 주소가 유저 스택 주소 범위 내에 있는가? (유저 스택은 1MB 범위)

두 조건을 모두 만족하면 vm_stack_growth 함수를 호출한다

vm_stack_growth

static void
vm_stack_growth(void *addr UNUSED)
{
    /* 스택 최하단에 익명 페이지를 추가하여 사용
    * addr은 PGSIZE로 내림(정렬)하여 사용    */
    vm_alloc_page(VM_ANON, addr, true); // 스택 최하단에 익명 페이지 추가
    vm_claim_page(addr);
}

여기서 해주는 일은 별거 없다. 그냥 폴트 주소에 미초기화 페이지를 하나 만들어주고, vm_claim_page를 통해 바로 익명 페이지로 초기화만 시켜주면 된다 !!

 

아 쉽다 쉬워

정소민fan
@정소민fan :: 코딩은 관성이야

코딩은 관성적으로 해야합니다 즐거운 코딩 되세요

목차