728x90

➡️ 과제 설명(Gitbook)

 

[Krafton Jungle | TIL_22.01.13] Project 3. Stack Growth(1)

➡️ 과제 설명(Gitbook)

olive-su.tistory.com

 

 

✨ Summary

  • 페이지 폴트가 스택으로 인해 발생했는지 확인하고 실제 스택 영역을 확장한다.

 

👷 구현 순서

  1. vm_try_handle_fault
  2. vm_stack_growth

 


1. vm_try_handle_fault

  • 스택의 크기는 통상적으로 1MB이다.
  • 스택에 접근하는 경우에만 할당한다.

 

  • Original Code
/* vm/vm.c */

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;
    // struct page *page = NULL;
    /* TODO: Validate the fault */
    /* TODO: Your code goes here */

    /* page_fault로 부터 넘어온 인자
     * f : 페이지 폴트 발생 순간의 레지스터 값들을 담고 있는 구조체
     * addr : 페이지 폴트를 일으킨 가상주소
     * not_present : 페이지 존재 x (bogus fault), false인 경우 read-only페이지에 write하려는 상황
     * user : 유저에 의한 접근(true), 커널에 의한 접근(false) - rsp 값이 유저 영역인지 커널영역인지
     * write : 쓰기 목적 접근(true), 읽기 목적 접근(false)
    */

    // page fault 주소에 대한 유효성 검증
    // 커널 가상 주소 공간에 대한 폴트 처리 불가, 사용자 요청에 대한 폴트 처리 불가
    if (is_kernel_vaddr (addr) && user) // real fault
        return false;

    if (not_present){
        if (!vm_claim_page(addr)) return false;
        else return true;
    }
    return false;
}

 

  • code
/* vm/vm.c */

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;
    static void *STACK_MINIMUM_ADDR = USER_STACK - (1 << 20); // 스택 최대 크기(주소 하한선) -> 1MB
        // ↳ 스택은 아래로 증가하기 때문 (Ref. Gitbook)
        // struct page *page = NULL;
        /* TODO: Validate the fault */
        /* TODO: Your code goes here */

        /* page_fault로 부터 넘어온 인자
         * f : 페이지 폴트 발생 순간의 레지스터 값들을 담고 있는 구조체
         * addr : 페이지 폴트를 일으킨 가상주소
         * not_present : 페이지 존재 x (bogus fault), false인 경우 read-only페이지에 write하려는 상황
         * user : 유저에 의한 접근(true), 커널에 의한 접근(false) - rsp 값이 유저 영역인지 커널영역인지
         * write : 쓰기 목적 접근(true), 읽기 목적 접근(false)
        */

        // page fault 주소에 대한 유효성 검증
        // 커널 가상 주소 공간에 대한 폴트 처리 불가, 사용자 요청에 대한 폴트 처리 불가
        if (is_kernel_vaddr (addr) && user) // real fault        
            return false;

        // 페이지 폴트가 발생하는 스택 포인터 위치
    void *rsp_stack = f->rsp;
    if (not_present){
      if (!vm_claim_page(addr)){ // 스택을 증가 시켜야하는 경우, 즉 spt에 현재 할당된 스택 영역을 넘거가는 경우
                if (rsp_stack - sizeof(void*) <= addr && STACK_MINIMUM_ADDR <= addr && addr <= USER_STACK) {
                    vm_stack_growth(thread_current()->stack_bottom - PGSIZE);
                    return true;
                }
                return false;
        }
        else
            return true;
    }
    return false;
}

 

  • 페이지 폴트가 발생하는 경우
    1. 유효한 주소가 아닌 경우
    2. 실제 할당하려는 페이지가 없는 경우
    3. ✅ 스택 공간이 부족한 경우 → 현재 구현하려는 상황

 

  • !vm_claim_page(addr) → 가상 주소 addr에 대해서 페이지를 할당할 수 없는 경우
    • 해당 포인터가 스택에 접근하려는 경우인데, 기존 스택 페이지를 넘어가는 경우에는 스택 자체의 크기를 확장한다.

 

  • 새로 할당하려는 페이지가 스택을 위한 페이지인지 확인한다.
  • rsp_stack - sizeof(void*) == addr
    • rsp로 부터 -8(byte)를 한 위치가 요청하는 주소값인지 확인한다.
      💡 x86-64 포인터 변수의 크기 : 8byte
  • STACK_MINIMUM_ADDR <= addr
    • 스택 영역 사이즈의 최대 크기(1MB)를 넘어가는 지 확인한다.
On many GNU/Linux systems, the default limit is 8 MB. For this project, you should limit the stack size to be 1MB at maximum.
_Gitbook Stack Growth

 

  • addr <= USER_STACK
    • 스택 영역의 최대 값(0x47480000)을 넘어가는 지 확인한다.
      • 스택 시작 주소

 

 

😵‍💫 헷갈리는 용어 및 개념들

  • 스택은 아래로 확장된다.
  • %rsp(rsp_stack) : 스택의 공간에서 가장 끝부분을 가리키는 레지스터
  • %rsi(USER_STACK) : 스택의 시작 부분을 가리키는 레지스터
  • &struct thread → stack_bottom : 스택으로 할당된 페이지의 끝을 가리킨다.

 

 

Kernel Stack 🆚 User Stack

/* userprog/syscall.c */

void
syscall_handler (struct intr_frame *f UNUSED) {
    // TODO: Your implementation goes here.
    struct thread *curr = thread_current();
    #ifdef VM
        curr->rsp_stack = f->rsp;
  #endif

    ...

}
/* vm/vm.c */

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

        ...
        void *rsp_stack = is_kernel_vaddr(f->rsp) ? thread_current()->rsp_stack : f->rsp;
        if (rsp_stack - sizeof(void*) == addr && STACK_MINIMUM_ADDR <= addr && addr <= USER_STACK) {
            vm_stack_growth(thread_current()->stack_bottom - PGSIZE);
            return true;

        ...

}
  • 처음엔 커널 스택에 대한 요청인지, 유저 스택에 대한 요청인지 판단하기 위해 위와 같은 로직을 넣었다.
  • But, f→rsp 자체만 사용하면 커널 스택에 대한 요청인지 유저 스택에 대한 요청인지를 구분할 필요가 없다.

 

 

 


2. vm_stack_growth

  • 앞선 함수에서 스택 공간에 대한 요청임을 확인했다면 실제로 스택의 크기를 확장한다.
/* vm/vm.c */

static void
vm_stack_growth (void *addr UNUSED) {
    /* vm_try_handler 수정해서 stack growth인 경우 함수를 호출하도록 처리
       0. 증가 시점 : 할당해주지 않은 페이지에 rsp가 접근했을 때 : stack growth에 대한 page_fault 발생시
       1. stack_bottom 설정
       2. 확장 요청한 스택 사이즈 확인
       3. 스택 확장시, page 크기 단위로 해주기
       4. 확장한 페이지 할당 받기 
       * 커널에서 페이지 폴트 발생시, intr_frame 내의 rsp는 유저스택 포인터가 아닌 쓰레기 값을 가짐 -> 커널에서 발생시 유저 스택 포인터를 thread 구조체에 저장*/ 
    if(vm_alloc_page(VM_ANON | VM_MARKER_0, addr, 1)) { // 스택 마커 다시 표시
        vm_claim_page(addr); // 페이지, 프레임 연결
        thread_current()->stack_bottom -= PGSIZE; // 증가된 스택 사이즈 만큼 stack_bottom 옮겨주기
    }
}
  • ‘vm_try_handle_fault’ 에서 vm_stack_growth 를 위한 인자로
    thread_current()->stack_bottom - PGSIZE 을 넣어줬으므로 addr 는 한 페이지를 확장한 이후의 포인터 값이다.

 

  • ⭐ 새로운 페이지 할당인데, 기존 stack_bottom에서 ‘+’가 아니라 ‘-’인 이유?
    • 스택은 아래로(높은 주소에서 낮은 주소로) 증가하므로 - PGSIZE를 해준다!!

 

  • vm_alloc_page(VM_ANON | VM_MARKER_0, addr, 1)
    • addr에 대해 새 페이지를 할당한다.
    • 이에 맞춰서 스택 마커도 표시한다.

 

  • vm_claim_page(addr)
    • 할당한 페이지에 대해 프레임을 연결해준다.

 

  • thread_current()->stack_bottom -= PGSIZE
    • 페이지 할당, 프레임 연결이 모두 완료되었다면 새로 바뀐 stack_bottom의 값을 새로운 스택 크기의 범위로 설정해준다.

 

 

 

728x90

'🌱 Dev Diary > 📄 TIL' 카테고리의 다른 글

알쓸 정규표현식  (32) 2024.05.07
Project 3. Memory Mapped Files  (0) 2023.01.19
Project 3. Stack Growth(1)  (0) 2023.01.19
Project 3. Anonymous Page(2)  (0) 2023.01.19
Project 3. Anonymous Page(1)  (0) 2023.01.19