이제 코치님께서도 페이지폴트나는 플로우를 따라가보라 하셨으니 함 가보입시더
" 누나랑 페이지폴트 흐름 잡으러 갈래? "
아직 개념을 잘 모르겠다 하시면 전 글을 참고하십쇼
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 false → page_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을 인자로 받아서
- initializer와 aux는 NULL로 넣고
- vm_alloc_page_with_initializer()를 호출하는 편의용 매크로
→ vm_alloc_page_with_initializer() 호출
- struct page 생성 및 초기화
- SPT에 해당 page 등록 (hash_insert)
→ 다시 vm_do_claim_page() 호출
- vm_get_frame()으로 새 프레임 확보 (부족하면 스왑 아웃)
- page와 frame 간 양방향 연결
- pml4_set_page()로 가상 주소를 물리 주소에 매핑
- swap_in()으로 데이터 불러오기 (스택은 anon이므로 zero-fill)
'크래프톤정글 > Pintos' 카테고리의 다른 글
| PintOS Project 3 _ Virtual Memory: Lazy Loading + Anonymous Page + Supplemental Page Table 구현(유저프로그램 통과 ver.) (2) | 2025.06.03 |
|---|---|
| lazy_load_segment? uninit_initialize? (0) | 2025.05.31 |
| VM 시작 (0) | 2025.05.30 |
| User Program _System Call(테스트 별 정리_read-nomal, bad, syn, rox) (0) | 2025.05.25 |
| User Program _System Call(exit, write, open, close, create, remove) (0) | 2025.05.21 |