누나랑 페이지폴트 흐름 따라갈래?

이제 코치님께서도 페이지폴트나는 플로우를 따라가보라 하셨으니 함 가보입시더

 

" 누나랑 페이지폴트 흐름 잡으러 갈래? "

 

아직 개념을 잘 모르겠다 하시면 전 글을 참고하십쇼

https://minhyay.tistory.com/159

 

VM 시작

유저프로그램까지의 pintos는 모든 사용자 프로세스가 물리 메모리에 올라갔다BUT현실의 운영체제는 그렇지 않다!-> 그래서 필요한것이 Virtual Memory(가상 메모리) 문제 상황:다수의 프로세스가 실

minhyay.tistory.com

 

가기 전 추억 한스푼....잘지내 케르베로스

 


페이지 폴트란?

프로세스가 아직 메모리에 로드되지 않은 가상 주소를 참조하려고 할 때 발생하는 예외

이때 운영체제는 이 참조가 정당한 요청인지, 아니면 불법 접근(bogus fault) 인지를 판단해서 처리해야 함

 

음.. 여기서 bogus pagefault라는 용어가 나오는데요

bogus폴트라는 것이 pintos gitbook에서는 지연로딩으로 인해 아직 메모리에 올라오지 않았을때 생기는 페이지폴트인 것인데,

이게 용어에 대해서 해석이 다 다르더라구요

그래서 일단 저는 pintos 과제 설명서 상에서의 해석기반으로 설명을 진행하겠습니다!~~

 

아무튼간 페이지 폴트가 발생하면, 크게 세가지로 분기를 타는데

Bogus 폴트

  • SPT에 이미 등록된 주소
  • 아직 메모리에 없을 뿐, 접근 자체는 정당함
  • vm_do_claim_page() → swap-in or lazy_load
  • 이후 pml4_set_page()로 PTE 등록

스택 확장 폴트

  • 아직 등록된 페이지는 없지만, 스택 확장 조건 만족
  • 조건 예:
    • addr ≥ rsp - 8
    • addr < PHYS_BASE
    • 최대 스택 크기 1MB 이하
  • vm_stack_growth() 호출
    • anon page로 등록
    • vm_do_claim_page() → zero page 할당
    • pml4_set_page() 매핑

유효한 페이지 폴트

  • 등록도 안 되어 있고, 스택 확장 조건도 아님
  • → 명백한 접근 오류
  • return falsepage_fault()에서 프로세스 종료 처리

흐름 요약도

   [1] 페이지 폴트 발생
    │
    │ CR2 → fault addr 저장됨
    ▼
[2] vm_try_handle_fault(addr)
    │
    ├─ addr가 SPT에 등록됨? ──▶ ✅ [bogus fault]
    │                             │
    │                             ▼
    │                         vm_do_claim_page()
    │                             │
    │                         vm_get_frame()
    │                             │
    │                         swap_in or lazy_load
    │                             │
    │                         pml4_set_page()
    │                             │
    │                         실행 재개
    │
    └─ addr가 SPT에 없음
         │
         ├─ 스택 확장 조건 만족? ─▶ ✅ [스택 확장 폴트]
         │                          │
         │                          ▼
         │                      vm_stack_growth(addr)
         │                          │
         │                      vm_alloc_page(VM_ANON)
         │                          │
         │                      SPT에 등록
         │                          ▼
         │                      vm_do_claim_page()
         │                          ▼
         │                      pml4_set_page()
         │                          ▼
         │                      실행 재개
         │
         └─ 둘 다 아니면 ─────────▶ ❌ [유효한 page fault]
                                     │
                                     ▼
                                  return false
                                  → process exit

그럼 이제 페이지폴트함수부터 시작해서 흘러가보겠어요

페이지 폴트가 발생하면, page_fault 함수가 호출됩니다

page_fault()은 fault 주소와 원인을 파악한 뒤, 커널이 처리 가능하면 처리하고, 아니면 프로세스를 종료하는 예외 핸들러

static void
page_fault(struct intr_frame *f)
{
	bool not_present; /* True: not-present page, false: writing r/o page. */
	bool write;		  /* True: access was write, false: access was read. */
	bool user;		  /* True: access by user, false: access by kernel. */
	void *fault_addr; /* Fault address. */

	/* 폴트를 일으킨 주소, 즉 폴트의 원인이 된 가상 주소를 얻습니다.
   이 주소는 코드일 수도 있고 데이터일 수도 있습니다.
   이 주소가 반드시 폴트를 일으킨 명령어의 주소(즉, f->rip)는 아닙니다. */

	fault_addr = (void *)rcr2();

	/* 인터럽트를 다시 활성화합니다.
   (인터럽트는 CR2 레지스터가 변경되기 전에 안전하게 값을 읽기 위해 잠시 꺼두었던 것입니다.) */
	intr_enable();

	/* Determine cause. */
	not_present = (f->error_code & PF_P) == 0;
	write = (f->error_code & PF_W) != 0;
	user = (f->error_code & PF_U) != 0;

#ifdef VM
	/* For project 3 and later. */
	if (vm_try_handle_fault(f, fault_addr, user, write, not_present))
		return;
#endif

	/* Count page faults. */
	page_fault_cnt++;

	/* If the fault is true fault, show info and exit. */
	printf("Page fault at %p: %s error %s page in %s context.\n",
		   fault_addr,
		   not_present ? "not present" : "rights violation",
		   write ? "writing" : "reading",
		   user ? "user" : "kernel");
	kill(f);
}

 

 1. 폴트 주소 획득

  • 가장 먼저, CPU의 CR2 레지스터에서 fault가 발생한 가상 주소(fault address) 를 읽어옴
  • 이 주소는 실제로 문제가 된 메모리 주소이며, 예외를 유발한 명령어 위치와는 다를 수 있음

 2. 예외 원인 분석

  • CPU가 제공하는 error_code를 해석하여, 어떤 이유로 예외가 발생했는지를 판별함:
    • not_present: 해당 주소가 아직 메모리에 매핑되지 않은 경우
    • write: 쓰기 접근인지 여부
    • user: 사용자 모드에서 발생한 예외인지 여부
  • 이 세 가지 정보를 통해, 어떤 종류의 fault인지 파악할 수 있음

3. VM 핸들러에 처리 시도

  • 만약 Virtual Memory 기능이 활성화된 상태(Project 3 이상)라면,
  • vm_try_handle_fault() 함수를 호출하여 커널이 직접 fault를 해결할 수 있는지 시도함
    • 페이지가 SPT에 등록되어 있는 경우 → lazy loading 또는 swap-in
    • 스택 확장이 필요한 경우 → anon 페이지 생성 및 매핑
  • 이런 경우 fault는 복구되며, 예외 없이 실행이 계속됨

 4. 해결 실패 시 종료

  • 만약 vm_try_handle_fault()에서 fault를 해결하지 못했다면,
  • 이는 정상적이지 않은 접근(유효한 페이지 폴트)으로 판단됨
  • → 콘솔에 fault 원인을 출력하고,
  • → 해당 유저 프로세스를 kill() 함수를 통해 종료

저희는 VM프로젝트 구현중이니, vm_try_handle_fault로 넘어 갑니다

 

vm_try_handle_fault()페이지 폴트 예외가 발생했을 때,

해당 fault를 가상 메모리 계층에서 처리할 수 있는지 판단하고,

필요하다면 페이지를 할당하거나 스택을 확장하는 역할을 담당하는 함수

수정필요

/* Return true on success */
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 */

	return vm_do_claim_page(page);
}

여기서 생각해야할 것!은 

위에서 설명했던 보거스폴트인지, 스택확장 가능한 폴트인지, 유효한 폴트인지 분기를 타야합니다

각 분기마다의 로직을 짜서 구현이 필요합니다

 

if bogus fault?

 

vm_do_claim_page()

주어진 page에 대해 물리 메모리를 확보하고,

MMU (PML4)에 가상 주소와 물리 주소를 매핑해주는 함수

수정필요

/* PAGE를 요구하고 mmu를 설정합니다*/
static bool
vm_do_claim_page(struct page *page)
{
	struct frame *frame = vm_get_frame();
	/** TODO: vm_get_frame이 실패하면 swap_out
	 */

	/* Set links */
	frame->page = page;
	page->frame = frame;

	/* TODO: Insert page table entry to map page's VA to frame's PA. */
	pml4_set_page(thread_current()->pml4, page->va, frame->kva, page->writable);

	return swap_in(page, frame->kva);
}
  • 프레임을 할당하고,
  • PML4에 VA → PA 매핑 등록하고,
  • 디스크에서 데이터가 필요하면 swap-in

그럼 이제 페이지타입에 따라 적절한 swap_in 함수가 호출됩니다

 

그전에!

vm_get_frame호출되고 있는거 보이시죠

한 번 보고갑시다

static struct frame *
vm_get_frame(void)
{
    void *kva = palloc_get_page(PAL_USER);
    struct frame *frame = NULL;

    if (kva == NULL) {
        // 프레임 부족 → 교체 진행 필요
        frame = vm_evict_frame();     // 너가 따로 구현해야 할 함수
        swap_out(frame->page);        // 페이지를 디스크로 내보냄
        pml4_clear_page(thread_current()->pml4, frame->page->va); // 매핑 제거
        kva = frame->kva;             // 비워진 프레임 재사용
    } else {
        // 새 프레임 확보 성공 → frame 구조체 할당
        frame = malloc(sizeof(struct frame));
        frame->kva = kva;
    }

    frame->page = NULL; // 아직 아무 페이지와 연결되지 않은 상태
    list_push_back(&frame_table, &frame->elem); // 프레임 테이블에 추가

    ASSERT(frame != NULL);
    ASSERT(frame->page == NULL);
    return frame;
}

페이지폴트가 나면

→ vm_do_claim_page로 가서 여기서 vm_get_frame을 호출함

 

→ 만약 이 프레임이 kva가 없다면(palloc할당 실패라면) → swap_out진행

 

→할당 성공시 → malloc으로 프레임 테이블 늘림 →구조체 할당

 

아직 아무 페이지와 매핑도ㅣ지않음

 

why? 이 함수는 그냥 프레임을 주기만함 어느 페이지와 매핑되는지 상관절대 X

→ 이제 다시 do_claim으로 가서 거기서 매핑이 진행되는거임

if stack_growth?

수정필요

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

 

  • 인자로 들어온 주소 addr페이지 크기(4KB)로 정렬해서
  • 익명 페이지(VM_ANON)를 할당하고
  • writable = true로 설정해서
  • vm_alloc_page()SPT에 등록만 해줌

실제로 메모리에 할당되지는 않고, lazy loading용 uninit 상태로 들어감

#define vm_alloc_page(type, upage, writable) \
	vm_alloc_page_with_initializer((type), (upage), (writable), NULL, NULL)
  • type, upage, writable을 인자로 받아서
  • initializerauxNULL로 넣고
  • vm_alloc_page_with_initializer()를 호출하는 편의용 매크로

→ vm_alloc_page_with_initializer() 호출

  • struct page 생성 및 초기화
  • SPT에 해당 page 등록 (hash_insert)

→ 다시 vm_do_claim_page() 호출

  • vm_get_frame()으로 새 프레임 확보 (부족하면 스왑 아웃)
  • pageframe 간 양방향 연결
  • pml4_set_page()로 가상 주소를 물리 주소에 매핑
  • swap_in()으로 데이터 불러오기 (스택은 anon이므로 zero-fill)