Stack Growth
프로젝트 2에서 스택은 USER_STACK에서 시작하는 단일 페이지(4KB) =였으나, 프로젝트 3에서 스택은 현재 크기를 초과하면 필요에 따라 추가 페이지를 할당한다! Stack Growth 개념은 이해했으나 구현에 벅찼지만, 뛰어난 동료 덕분에 구현을 마치고 이해할 수 있었다.
Page Fault 시, 스택 포인터
Stack Growth를 수행하기 전에 현재 스택 포인터의 값을 얻어야 한다. System Call 또는 User Program에 의해 발생한 Page Fault 루틴에서 각각 syscall_hanler() 또는 page_fault()에 전달된 struct intr_frame
의 rsp 멤버에서 검색할 수 있다.
또한, Page Fault로 잘못된 메모리 접근을 감지하는 경우, 커널에서 Page Fault가 발생하는 경우도 처리해야 한다. 프로세스가 intr_frame에 스택 포인터를 저장하는 것은 Exception으로 인해 유저 모드에서 커널 모드로 전환될 때 뿐이므로, page_fault()로 전달된 struct intr_frame
에서 rsp를 읽으면 유저 스택 포인터가 아닌 정의되지 않은 값을 얻을 수 있다. 따라서 커널에서 Page Fault가 발생했을 때의 rsp를 저장하는 방법을 생각해야 한다!
vm_try_handle_fault()
스택은 아래로 증가하기 때문에, 스택 영역을 넘어서 메모리에 접근하려고 할 때 Page Fault가 발생한다. 스택이 물리 메모리에 할당된 페이지로 가득 찼을 때, 새로운 스택 프레임을 위한 페이지가 필요하게 된다! 이때, 스택 포인터가 스택 영역을 넘어가는 주소를 참조하면 Page Fault가 발생한다. 이 페이지 폴트는 운영체제에게 스택 확장이 필요하다는 신호가 되며, 운영체제는 추가적인 스택 페이지를 할당하여 스택의 크기를 늘린다.
User program은 스택 포인터 아래의 스택에 쓸 경우 버그가 발생하는데, 이는 일반적인 실제 OS가 스택의 데이터를 수정하는 시그널을 전달하기 위해 프로세스를 언제든지 중단할 수 있기 때문이다. 하지만 x86-64 PUSH 명령어는 스택 포인터를 조정하기 전에 접근 권한을 검사하므로, 스택 포인터 아래 8바이트에 대해서 Page Fault를 발생시킬 수 있다.
// vm/vm.c
bool
vm_try_handle_fault (struct intr_frame *f UNUSED, void *addr,
bool user UNUSED, bool write, bool not_present) {
struct supplemental_page_table *spt = &thread_current ()->spt;
struct page *page = NULL;
/* Project 3. */
/* TODO: Validate the fault */
if (addr == NULL) {
return false;
}
else if (!is_user_vaddr (addr)) {
return false;
}
/* 스택 영역 체크 */
else if (USER_STACK - (1 << 20) <= addr && addr <= USER_STACK) {
if (f->rsp - 8 != addr) {
return false;
}
vm_stack_growth (addr);
if (pml4_get_page (thread_current ()->pml4, pg_round_down (addr)) == NULL) {
return false;
}
return true;
}
else if (not_present) {
page = spt_find_page (&spt->spt_hash, addr);
if (page == NULL) {
return false;
}
if (write && !page->writable) {
return false;
}
return vm_do_claim_page (page);
}
/* Project 3. */
return false;
}
vm_stack_growth()
// vm/vm.c
static void
vm_stack_growth (void *addr) {
void *stack_bottom = pg_round_down (addr);
int pg_cnt = 0;
while (true) {
void *upage = (uint64_t)stack_bottom + pg_cnt * PGSIZE;
if (spt_find_page (&thread_current ()->spt, upage) != NULL) {
break;
}
if (!vm_alloc_page (VM_ANON | VM_MARKER_0, upage, true)) {
break;
}
if (!vm_claim_page (upage)) {
break;
}
pg_cnt++;
}
}
- Page Fault가 발생한 인자 addr을 포함한 페이지를 스택의 최하단으로 잡는다.
- 최하단 페이지부터 순차적으로 올라가면서 페이지를 생성하고 claim한다.
- 스택은 Anonymous Page이기 때문에, VM_ANON으로 설정한다.
결과
Stack Growth를 구현하기 위해서는 vm_try_handle_fault()와 vm_stack_growth()를 구현해야 한다. 구현에 성공할 시, 약 58개의 fail이 뜬다. 추정치인 이유는 랜덤하게 1~2개의 결과가 튀기 때문이다.
'프로젝트 > Pint OS' 카테고리의 다른 글
[크래프톤 정글 2기] Day 84 (0) | 2023.06.26 |
---|---|
[Pint OS] Lazy loading(3) (0) | 2023.06.22 |
[Pint OS] Lazy loading(2) (0) | 2023.06.19 |
[Pint OS] Lazy loading(1) (0) | 2023.06.18 |
[Pint OS] System Calls (6) (0) | 2023.06.17 |