PintOS 3주차 Stack Growth에 대해서 정리해봄

Designated Hitter Jack·2023년 10월 23일

SW 사관학교 정글

목록 보기
30/44
post-thumbnail

stack growth 를 만들어야 하는 이유

지금까지의 과정에서 stack의 크기는 USER_STACK에서 시작하는 단일 페이지였고, 4kb밖에 안 되는 작은 용량이었다. 하지만 이제는 (실제 OS처럼) stack에 용량이 없다면 이를 추가적으로 할당하도록 만들어야 한다. 지금까지는 page fault == 메모리에 뭔가 해결해야 할 오류가 생긴 상황이었지만 이제는 (실제 OS처럼) stack에 추가적인 공간을 요구하는 것으로 만들어야 한다.

즉, 접근한 가상 주소에 매핑된 frame이 없어서 page fault가 발생한 경우 중, 접근한 가상 주소가 stack영역 내에 존재할 경우 추가 페이지를 할당하는 것이 stack growth의 핵심이다.

stack growth의 유의사항

우선적으로 접근한 주소가 stack 영역에 존재하는지부터 확인해야 한다.
많은 linux 시스템에서 스택영역을 8MB로 제한하고 있지만 PintOS에서는 1MB이다.
그렇기 때문에 우선 USER_STACK - 1MB라면 잘못된 접근이다.

그러나 USER_STACK ~ USER_STACK - 1MB 사이에 접근한 것이 맞더라도 전부 stack growth로 넘겨버리면 안 된다. rsp보다 높은 주소 값에 접근한 경우에만 stack growth를 실행해야 한다.

왜?

운영체제가 실행되다가 프로세스를 중단시키게 되면 실행 정보를 stack에 저장하게 되는데, 유저프로그램이 스택포인터 아래에 데이터를 써놓은 경우라면 이때 데이터가 덮어씌워지면서 변경될 수 있다.
그렇기 때문에 stack pointer 아래에서 쓰기를 시도하는 것을 막아야 한다.

예외상황

그러나 스택포인터 (rsp)보다 낮은 주소 (rsp - 8)에 접근했을 때에도 stack growth 로 해결할 수 있는 경우가 있는데 바로 push 명령어가 실행되는 상황이다.
x86-64 system의 push 명령어는 스택 포인터를 조정하기 전에 접근 권한을 확인한다. 그렇기 때문에 스택 포인터의 8바이트 아래에서 page fault가 발생할 수 있다.
따라서 rsp - 8 주소에서도 stack growth를 적용해야 한다.
push 명령은 주로 함수를 호출할 때 매개변수를 stack에 전달하거나, 함수 내의 local variable을 stack에 할당할 때 사용한다.

rsp

그렇기 때문에 stack 영역내에 존재하는 page fault를 stack growth로 해결하기 위해서는 rsp를 알아야 한다.
user program에서 발생한 page fault에서는 page_fault()에 전달된 intr_frame의 rsp에서 스택 포인터의 위치를 알 수 있다.
그러나 kernel mode에서는 똑같이 intr_frame의 rsp에 접근하게 된다면 커널 스택의 스택 포인터를 알게 되므로 이렇게 사용할 수 없다.
그렇기 때문에 user mode에서 kernel mode로 전환 시에 현재 struct thread의 멤버 rsp에 rsp 값을 기록해두어야 한다. 물론 rsp 멤버 역시 추가해주어야 한다.

vm_try_handle_fault()

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 */
	if (addr == NULL) {
		return false;
	}

	if (is_kernel_vaddr(addr)) {
 		return false;
	}

	//접근한 메모리의 physical page가 존재하지 않는 경우
	if (not_present) {
    	//스택 포인터의 위치 확인
		void *rsp = f -> rsp;
		if (!user) {
        	//커널 모드에서의 스택 포인터 위치 확인
			rsp = thread_current() -> rsp;
		}
		//stack 확장으로 처리할 수 있는 폴트
		if (USER_STACK - (1 << 20) <= rsp - 8 && rsp - 8 == addr && addr <= USER_STACK) {
			vm_stack_growth(addr);
		} else if (USER_STACK - (1<<20)<= rsp && rsp <= addr && addr <= USER_STACK) {
			vm_stack_growth(addr);
		}

		page = spt_find_page(spt, addr);
		if (page == NULL) {
			return false;
		}
		//writable 하지 않은 페이지에 write 요청한 경우
		if (write == 1 && page -> writable == 0) {
			return false;
		}
		return vm_do_claim_page(page);
	}
	
	return false;
}

위에서 설명했듯이 주소가 NULL인지, kernel 영역인지 확인 후, not_present 인자로 physical page에 매핑되어있지 않은지 확인한다. 그 다음 user mode 인지 kernel 모드인지에 따라 rsp에 다른 값을 지정해준다.

vm_stack_growth()

static void
vm_stack_growth (void *addr UNUSED) {
	//stack 크기를 증가시키기 위해서 anon page를 하나 이상 할당하여 주어진 주소가 더이상 예외주소가 되지 않도록 해야함
	//할당할 때 addr을 PGSIZE(4kb씩)로 내림하여 정렬 후 처리
	addr = pg_round_down(addr);
	if (vm_alloc_page(VM_ANON|VM_MARKER_0, addr, true)) {
		thread_current () -> stack_bottom -= PGSIZE;
	}
}
profile
Fear always springs from ignorance.

0개의 댓글