정글 사관학교 W12[핀토스]_WIL

정나린·2022년 12월 19일
1

[12주차 개괄]

저번주의 여파로 프로젝트3을 끝내지 못해서 프로젝트4를 시작하지 못했다. 프로젝트2를 기반으로 프로젝트4를 하는 선택지도 있었기때문에 고민을 했지만, 이미 시작한 프로젝트3을 잘 끝내 보고 싶은 마음이 컸다.

후.회.는. 하.지. 않.는.다.

이번 글은 프로젝트3을 하면서 겪었던 여러 에러들과 해결 방법에 대한 기록이다.

[Project 3-1: load() - writable]

  • 수정 전
bool writable = (phdr.p_flags && PF_W) != 0;
  • 수정 후
bool writable = (phdr.p_flags & PF_W) != 0;

load한 file이 writable한 file이라면, phdr.p_flags의 second-rightmost bit는 1일 것이다.
#define PF_W 2
위와 같이 PF_W는 이진수로 '10'이고, writable한 file인지 검사하는 마스크 역할을 한다.
수정 전의 경우 && 연산이므로 아무리 phdr.p_flags의 second-rightmost bit이 1이 아닐지라도 연산의 결과가 1이 나올 수 있다.(논리 연산이니까)
따라서 PF_W가 마스크로서의 역할을 하기 위해서는 &&가 아닌 &(비트 연산)이어야 한다.

🧐 프로젝트 2는 어떻게 통과했을까?

if (write)
	{
		thread_current()->my_exit_code = -1;
		thread_exit();
	}
execption.c의 page_fault()함수에서 위와 같이 적어주었다. 
프로젝트 2에서는 lazy loading이 사용되지 않았기 때문에 page_fault가 일어나는 경우가 유효한 page_fault인 경우밖에 없었으므로
위와 같이 처리했어도 문제가 없었던 것 같다.
(프로젝트 3에서는 이 부분은 삭제했다.)

[Project 3-2: vm_claim_page() vs vm_do_claim_page()]

처음엔 두 함수가 아예 다른 것인줄 알고 헷갈렸었다.
두 함수의 차이는 인자로 무엇을 받는지에 있지, 하는 기능은 같다.

1. vm_claim_page

/* Claim the page that allocate on VA. */
bool
vm_claim_page (void *va UNUSED) {
	struct page *page = NULL;
	/* TODO: Fill this function */
	page = spt_find_page(&thread_current()->spt, pg_round_down(va));
	if (page)
		return vm_do_claim_page(page);
	return false;
}

가상 주소(va)를 인자로 받는다.
페이지 정렬한 va로 해당 주소값으로 시작하는 페이지가 spt가 저장되어 있는지 확인한다.
있다면 그 페이지를 인자로 받는 vm_do_claim_page()를 호출한다.
(즉, 결국 vm_do_claim_page를 한다.)

2. vm_do_claim_page

/* Claim the PAGE and set up the mmu. */
static bool
vm_do_claim_page (struct page *page) {
	struct frame *frame = vm_get_frame ();
	bool result = false;

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

	/* TODO: Insert page table entry to map page's VA to frame's PA. */
	if (!pml4_set_page(thread_current()->pml4, page->va, frame->kva, page->writable)){
		return false;
	}
	result = swap_in(page, frame->kva);
	return result;
}

페이지(page)를 인자로 받는다.
페이지(유저 가상 공간의 페이지)와 프레임(물리 메모리)을 매핑하고
pml4에 유저 가상 공간의 페이지와 커널 가상 공간의 페이지를 매핑한 정보를 저장한다.
그리고 페이지 타입에 맞는 swap_in 함수를 호출한다. (이때 페이지 타입이 uninit->anon 혹은 file로 정해진다)

[Project 3-3: vm_try_handle_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;
	uint64_t MAX_STACK = USER_STACK - (1 << 20);
	void *pg_aligned_addr = pg_round_down(addr);
	void *stack_pointer = NULL;

	/* TODO: Validate the fault */
	/* TODO: Your code goes here */
	if ((!not_present) && write)
	{
		return false;
	}

	if (is_kernel_vaddr(addr))
		return false;

	if (user)
		stack_pointer = f->rsp;
	else
		stack_pointer = thread_current()->stack_pointer;

	page = spt_find_page(spt, pg_aligned_addr);
	if (!page){
		if (stack_pointer - 8 <= addr && MAX_STACK < addr && addr <= USER_STACK){
			vm_stack_growth(pg_aligned_addr);
			return true;
		}
		else{
			return false;
		}
	}
	return vm_do_claim_page(page);
}

프로젝트2와 다르게 프로젝트3은 스택을 위한 페이지가 추가될 수 있다.
page_fault가 일어나는 여러 가지 상황 중에 stack으로 쓰고 있는 공간이 부족해 아직 할당되어 있지 않은 곳을 침범하는 경우도 있을 수 있다.
핀토스에서 stack의 최대 크기는 1MB이고 stack의 시작 지점인 USER_STACK에서 1MB만큼 떨어져 있는 곳에서 page_fault가 일어나면 이는 vm_stack_growth 함수를 호출해 페이지를 할당해줘야 한다.
즉, vm_try_handle_fault 함수에서 인자로 넘어온 addr이 bogus page fault인지 valid page fault인지를 검사하는 것 외에도 stack_growth 상황인지도 고려해야 한다.

[Project 3-4: file_reopen()]

supplemental_page_table_copy()에서 file_reopen() 함수를 사용하지 않아 몇몇 테스트 케이스가 잘 돌아가지 않았다.

/* Copy supplemental page table from src to dst */
bool
supplemental_page_table_copy (struct supplemental_page_table *dst,
		struct supplemental_page_table *src) {

	struct hash_iterator i;
	struct hash *h = &src->pages;

	hash_first(&i, h);
	while(hash_next(&i)){
		struct page *p = hash_entry(hash_cur(&i), struct page, hash_elem);
		enum vm_type type = page_get_type(p);

		void *va = p->va;
		bool writable = p->writable;

		if (p->operations->type == VM_UNINIT){
			vm_initializer *init = p->uninit.init;
			struct container *container = (struct container *)malloc(sizeof(struct container));
			container->file = file_reopen(((struct container*)p->uninit.aux)->file);
			container->offset = ((struct container *)p->uninit.aux)->offset;
			container->page_read_bytes = ((struct container *)p->uninit.aux)->page_read_bytes;
			container->page_zero_bytes = ((struct container *)p->uninit.aux)->page_zero_bytes;

			if (!vm_alloc_page_with_initializer(type, va, writable, init, container))
				return false;
		}
		else{
			if (!vm_alloc_page(type, va, writable))
				return false;
			if (!vm_claim_page(va))
				return false;
			memcpy(va, p->frame->kva, PGSIZE);
		}

	}
	return true;

}

supplemental_page_table_copy() 함수는 __do_fork()함수에서 호출된다.
부모 프로세스가 자식 프로세스를 만들 때, 부모의 spt에 있는 정보들을 자식의 spt도 가지고 있어야 한다.
이때 자식 혹은 부모가 먼저 실행되고 한 파일을 닫을 수 있는데, 이는 서로 독립적으로 일어나야 한다. 즉, A라는 파일을 부모 프로세스가 닫았다고 해서 자식이 해당 파일에 대해 read, write 를 못하면 안된다.
따라서 새롭게 할당받은 container에 멤버들을 넣어줄 때, 아래와 같이 해야 한다.

container->file = file_reopen(((struct container*)p->uninit.aux)->file);

file_reopen()은 인자로 받은 file의 inode의 open_cnt를 1만큼 증가시키고 file_open()을 한다.
file_open()은 주어진 file의 inode를 참고해서 파일을 열고 새로운 파일을 리턴한다.

struct *file2 = file_reopen(file1);

file2와 file1의 내용은 같지만, 서로 다른 객체인 것이다.
따라서 부모와 자식이 독립적은 같은 내용의 파일을 열고 닫을 수 있게 된다.

0개의 댓글