[Pintos] Project3 Stack Growth 6/20

UBIN·2023년 6월 20일
0

spt_kill

/* Free the resource hold by the supplemental page table */
void supplemental_page_table_kill (struct supplemental_page_table *spt) {
	/* TODO: Destroy all the supplemental_page_table hold by thread and
	 * TODO: writeback all the modified contents to the storage. */
	hash_clear(&spt->spt_hash, action_func);
}

/* buckets의 모든 bucket들을 돌면서 해당 bucket에 들어있는 모든 
   h_elem을 destructor 함수의 인자로 넘긴다. */
void hash_clear (struct hash *h, hash_action_func *destructor) {
	size_t i;

	for (i = 0; i < h->bucket_cnt; i++) {
		struct list *bucket = &h->buckets[i];

		if (destructor != NULL)
			while (!list_empty (bucket)) {
				struct list_elem *list_elem = list_pop_front (bucket);
				struct hash_elem *hash_elem = list_elem_to_hash_elem (list_elem);
				destructor (hash_elem, h->aux);
			}

		list_init (bucket);
	}

	h->elem_cnt = 0;
}

/* 받은 h_elem으로 page를 찾아 destroy 함수의 인자로 넘긴다. */
void action_func(struct hash_elem *e, void *aux) {
	struct page *page = hash_entry(e, struct page, h_elem);

	if(page) {
		destroy(page);
	}
}

/* vm_type에 따라 호출되는 destroy 함수가 나뉜다. */
static void anon_destroy (struct page *page) {
	struct anon_page *anon_page = &page->anon;
	free(page);
}

static void uninit_destroy (struct page *page) {
	struct uninit_page *uninit UNUSED = &page->uninit;
	/* TODO: Fill this function.
	 * TODO: If you don't have anything to do, just return. */
	free(page);
}

process_cleanup()에서 supplemental_page_table_kill()을 호출하게 된다.
spt 테이블에 들어있는 모든 page에 대해 메모리를 해제 해준다.

stack growth

bool vm_try_handle_fault (struct intr_frame *f, void *addr, bool user, bool write, bool not_present) {
	struct supplemental_page_table *spt = &thread_current()->spt;
	struct page *page = NULL;

	if (addr == NULL)
		return false;
	else if (is_kernel_vaddr(addr))
		return false;
	else if (USER_STACK >= addr && addr >= USER_STACK - (1 << 20)) {
		if (f->rsp - 8 != addr)
			return false;

		vm_stack_growth(addr);
		return true;
	}
	else if (not_present) {
		page = spt_find_page(&spt->spt_hash, addr);

		if (page == NULL)
			return false;

		return vm_do_claim_page(page);
	}

	return false;
}

stack_growth를 구현하기에 앞서 vm_try_handle_fault 함수를 수정했다.
본래와 비교해서 예외 처리를 조금 더 해줬을 뿐이다.

이 중에서 stack_growth와 관련 있는 부분은 아래와 같다.

else if (USER_STACK >= addr && addr >= USER_STACK - (1 << 20)) {
		if (f->rsp - 8 != addr)
			return false;

		vm_stack_growth(addr);
		return true;
}

깃북 Stack Growth 부분을 살펴보면 다음과 같이 설명한다.

  1. 스택이 현재 크기를 초과하면 필요에 따라 추가 페이지를 할당한다.
  2. 추가 페이지는 스택에 접근하는 경우에만 할당한다.

이해 안된 부분

You will need to be able to obtain the current value of the user program's stack pointer. Within a system call or a page fault generated by a user program, you can retrieve it from the rsp member of the struct intr_frame passed to syscall_handler() or page_fault(), respectively. If you depend on page faults to detect invalid memory access, you will need to handle another case, where a page fault occurs in the kernel. Since the processor only saves the stack pointer when an exception causes a switch from user to kernel mode, reading rsp out of the struct intr_frame passed to page_fault() would yield an undefined value, not the user stack pointer. You will need to arrange another way, such as saving rsp into struct thread on the initial transition from user to kernel mode.

당신은 User Program의 스택 포인터의 현재 값을 얻을 수 있어야 합니다. System Call 또는 User Program에 의해 발생한 Page Fault 루틴에서 각각 syscall_handler()또는 page_fault()에 전달된 struct intr_frame의 rsp멤버에서 검색할 수 있습니다.
잘못된 메모리 접근을 감지하기 위해 Page Fault에 의존하는 경우, 커널에서 Page Fault가 발생하는 경우도 처리해야 합니다. 프로세스가 스택 포인터를 저장하는 것은 예외로 인해 유저 모드에서 커널 모드로 전환될 때 뿐이므로 page_fault()로 전달된 struct intr_frame 에서 rsp를 읽으면 유저 스택 포인터가 아닌 정의되지 않은 값을 얻을 수 있습니다. 유저 모드에서 커널 모드로 전환 시 rsp를 struct thread에 저장하는 것과 같은 다른 방법을 준비해야 합니다.

-> thread 구조체에 rsp를 추가해서 뭘 어떻게 하라는거 같은데 잘 모르겠다....

test case: pt-grow-stack

이 경우에는 stack_growth가 호출되는 경우이다. 0x4747ee80에서 push 명령어를 하게 된다면 0x4747ee80 - 8 = 0x4747ee78을 참조하게 되고 page fault가 발생한다.

현재 stack area에는 0x4747f000의 page 1개만 할당한 상태인데 어떻게 rsp가 이미 할당되지 않은 부분을 가르키고 있는지 궁금했다.
push, pop과 같은 명령어는 -8, +8의 주소를 참조하지만 다른 명령. 예를 들어 sub/add와 같은것은 참조하는게 아니다. 즉, pt-grow-stack.c의 test_main 함수에서 변수를 선언하면서 그 크기인 0x1110을 sub하게 된다.
원래의 rsp에서 sub 0x1110을 진행하게 되어 0x4747ee80을 가르키고 있고, 이 상태에서 push를 하려다 보니 page fault가 발생한것이다.

test case: pt-grow-bad

이 때는 stack_growth가 발생하면 안되는 상황이다. 바로 비정상 종료를 해야한다. 이러한 경우가 어떻게 발생하는지는 모르겠다... 그래서 f->rsp - 8 != addr 이면 return false로 따로 처리해줬다.

stack_growth 함수는 다음과 같이 구현해주었다.

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 + PGSIZE * pg_cnt;
		
		if (spt_find_page(&thread_current()->spt, upage) != NULL)
			break;

		vm_alloc_page(VM_ANON | VM_MARKER_0, upage, true);
		vm_claim_page(upage);

		pg_cnt++;
	}
}

page fault가 발생한 주소부터 위로 쭉 page를 생성해주다가 이미 존재하는 page를 만나면 종료한다.

번외

  • stack_growth에서 page를 여러개 만들어줄 때
    -> 위에서 설명한것처럼 0x1110과 같이 rsp가 한번에 여러개의 page size만큼 sub이 된다면 그 사이의 영역에 대해 page를 전부 할당해주어야하기 때문.

  • rsp가 이미 유효한 메모리에 존재하지 않을 때 push말고 pop을 해주게 된다면 rsp + 8을 참조하게 될텐데 아래처럼 바꿔야 하지 않을까 생각을 했지만 stack_growth를 하고 나서도 page는 할당 되었지만 해당 주소에 아무것도 없을텐데 pop을 하는게 맞나? 라는 생각이 들어 따로 해주지 않았다.

f->rsp - 8 != addr => (f->rsp - 8) != addr && (f->rsp + 8) != addr
profile
ubin

0개의 댓글