본문 바로가기
개발자 도전기/[OS] pintOS

pintOS | Project 3 | 버그잡자

by 답수 2021. 10. 22.
728x90
SMALL

 

 

현재 깃북을 따라 project 3을 구현하고 있고, Memory Mapped file까지 완료한 상태다. 여기에 Anonymous page 때 적지 않았던 부분들을 일단 구현했다. (현재는 swap in/out까지 구현 완료)

 

 

copy와 clean up 작업을 하기 위해서 spt를 재방문한다. spt를 재방문 하는 이유는 아까 구현했던 초기화와 관련된 함수들을 사용하기 때문이다. 먼저 copy하는 함수부터 보자.

bool supplemental_page_table_copy (struct supplemental_page_table *dst,
    struct supplemental_page_table *src);

 

함수 인자를 보면 dst와 src가 있는데, spt의 src에서 dst로 복사한다는 뜻이다. 이 함수는 자식이 부모의 실행 컨텍스트를 상속해야 할 때 사용된다. fork()함수처럼 자식 프로세스가 생성될 때의 과정을 생각하면 더 이해하기 쉬울 것 같다. src의 각각 페이지를 반복하면서 dst의 엔트리에 정확하게 복사한다. 이 과정에서 초기화되지 않은 페이지들은 할당하고 바로 claim해야 한다. 구현하면 다음과 같다.

/* Copy supplemental page table from src to dst */
bool supplemental_page_table_copy (struct supplemental_page_table *dst UNUSED, 
								   struct supplemental_page_table *src UNUSED) {
    struct hash_iterator i;
    hash_first (&i, &src->pages);
    while (hash_next (&i)) {	// src의 각각의 페이지를 반복문을 통해 복사
        struct page *parent_page = hash_entry (hash_cur (&i), struct page, hash_elem);   // 현재 해시 테이블의 element 리턴
        enum vm_type type = page_get_type(parent_page);		// 부모 페이지의 type
        void *upage = parent_page->va;						// 부모 페이지의 가상 주소
        bool writable = parent_page->writable;				// 부모 페이지의 쓰기 가능 여부
        vm_initializer *init = parent_page->uninit.init;	// 부모의 초기화되지 않은 페이지들 할당 위해 
        void* aux = parent_page->uninit.aux;

        if (parent_page->uninit.type & VM_MARKER_0) {
            setup_stack(&thread_current()->tf);
        }
        else if(parent_page->operations->type == VM_UNINIT) {	// 부모 타입이 uninit인 경우
            if(!vm_alloc_page_with_initializer(type, upage, writable, init, aux))
                return false;
        }
        else {
            if(!vm_alloc_page(type, upage, writable))
                return false;
            if(!vm_claim_page(upage))
                return false;
        }

        if (parent_page->operations->type != VM_UNINIT) {   //! UNIT이 아닌 모든 페이지(stack 포함)는 부모의 것을 memcpy
            struct page* child_page = spt_find_page(dst, upage);
            memcpy(child_page->frame->kva, parent_page->frame->kva, PGSIZE);
        }
    }
    return true;
}

 

 

다음은 supplemental_page_table_kill() 함수다. 이 함수는 프로세스가 종료될 때(process_exit) 호출된다. copy때와 마찬가지로 페이지 하나씩 반복하면서 삭제한다.

/* Free the resource hold by the supplemental page table */
void supplemental_page_table_kill (struct supplemental_page_table *spt UNUSED) {
	/* TODO: Destroy all the supplemental_page_table hold by thread and
	 * TODO: writeback all the modified contents to the storage. */
	struct hash_iterator i;

    hash_first (&i, &spt->pages);
    while (hash_next (&i)) {
        struct page *page = hash_entry (hash_cur (&i), struct page, hash_elem);

        if (page->operations->type == VM_FILE) {
            do_munmap(page->va);
            // destroy(page);
        }
    }
    hash_destroy(&spt->pages, spt_destructor);
}

 

이 때 spt에 해시 요소를 제거하기 위해 필요한 함수 spt_destructor는 아래처럼 구현

void spt_destructor(struct hash_elem *e, void* aux) {
    const struct page *p = hash_entry(e, struct page, hash_elem);
    free(p);
}

 

 

여기까지 구현하고 테스트를 돌려본다. 바로 커널 패닉이 온다. 그것도 프로젝트 1 부분에서;;

 

정확한 문제는 잘 모르겠지만, 일단 가장 최근에 작성한 함수가 spt를 kill하는 함수기 때문에 저 부분을 주석처리 해봤다.

 

다행히 프로젝트 1부분은 통과된다. 프로세스를 종료할 때 문제가 된다는 건데.. 일단 process_exit() -> process_cleanup() 함수를 보자. 

/* Free the current process's resources. */
static void process_cleanup (void) {
	struct thread *curr = thread_current ();

#ifdef VM
	supplemental_page_table_kill (&curr->spt);
#endif

	uint64_t *pml4;
	/* Destroy the current process's page directory and switch back
	 * to the kernel-only page directory. */
	pml4 = curr->pml4;
	if (pml4 != NULL) {
		/* Correct ordering here is crucial.  We must set
		 * cur->pagedir to NULL before switching page directories,
		 * so that a timer interrupt can't switch back to the
		 * process page directory.  We must activate the base page
		 * directory before destroying the process's page
		 * directory, or our active page directory will be one
		 * that's been freed (and cleared). */
		curr->pml4 = NULL;
		pml4_activate (NULL);
		pml4_destroy (pml4);
	}
}

 

ㅗㅜ... 저 코드대로라면 메모리를 load할 때마다 spt에 있는 데이터들을 다 삭제시켜버린다. 저 부분을 수정해보자.

static void process_cleanup (void) {
	struct thread *curr = thread_current ();

#ifdef VM
	// supplemental_page_table_kill (&curr->spt);
	if(!hash_empty(&curr->spt.pages))
		supplemental_page_table_kill (&curr->spt);
#endif
...
}

 

그리고 spt kill하는 함수도 주석처리 풀고 다시 테스트 돌려보자.

 

 

project 1 부분 통과 되는 걸 보니 spt kill하는 부분 문제는 고쳐진 것 같다. 그러나 전보다 진전이 없다 ㅠㅠ 어디서부터 고쳐야 할까...

 

일단 에러가 어떻게 발생하는지 한 번 보자. 제일 처음 fail 뜬 부분

 

'Page fault at 어쩌구: not present error reading page in kernel context.'

 

오호... 너 이자식 어디서 봤나 했더니 page_fault() in userporg/exception.c 에 있던 놈이다.

static void page_fault (struct intr_frame *f) {
	
    ...

#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);
	exit(-1);
}

 

정글 pintos 과정에 프로젝트 1,2 를 하고 3,4를 시작할 때 다른 조의 코드를 받아서 그 코드로 구현을 해야 한다. 그러다 보니 내가 짰던 코드와 다른 부분들이 많고, 전에 구현했던 코드라도 놓치는 부분들이 많을 수 있다.

 

전 프로젝트에서 page fault가 났을 때, -1을 리턴하고 프로세스를 종료해야 하기 때문에 저렇게 적어놨었다. 그런데 지금 터미널에서 fail 에러문구를 보면 저 프린트되어야 하는 놈들이 세상 밖으로 나오면 안 된다는 것. 즉 exit(-1) 부분 위치만 옮기면 될 것이라는거겠지?

static void page_fault (struct intr_frame *f) {
	
    ...

#ifdef VM
	/* For project 3 and later. */
	if (vm_try_handle_fault (f, fault_addr, user, write, not_present))
		return;
#endif
	exit(-1);
    
	/* 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);
}

 

이와 같은 에러들은 careate-bad-ptr, open-bad-ptr, exec-bad-ptr 과 같은 테스트케이스에서 발생했었다. 결과를 보자.

 

오....!  전보다 3개나 더 통과됐다.

 

딱 예상했던 부분들 pass!!! 계속 디버깅하즈아아 다음 에러!

 

 

directory.c 에 있는 dir_lookup() 함수 내에서 ASSERT (name != NULL); 이 부분에서 커널 패닉이 온다고 한다.

 

'그럼 저 조건만 지우면 되는거 아니야?' 라는 단순한 생각으로 올바른 방법이 아닌 줄 알면서도 호기심으로 시도는 해봤다. 다른 테스트들에서 갑자기 fail이 후두두두 나올 것을 예상하면서.

 

예상했던 그런 다이나믹한 결과는 나오지 않았다. 다만 한 개의 조건을 지우면 다른 함수의 다른 조건이 또 문제가 되고, 이런식으로 계속계속 꼬리에 꼬리를 물면서 fail이 나왔다. 굳이 건들지 말라는 부분은 건드리는 짓 안 해야지. 한 번 청개구리 짓 해보고 싶었다..

 

여하튼 다시 본론으로 돌아와서! 출력문 보면 filesys_open() 시스템 콜이 호출될 때 패닉이 발생한다. 현재 구현된 open() 함수를 보자.

int open(const char *file) {
	check_address(file);
	struct file *fileobj = filesys_open(file);
	// filesys_open()은 return file_open(inode) -> file_open()은 return file 이므로, 
	// fileobj = 리턴 값으로 받은 file이 됨

	if(fileobj == NULL)
		return -1;

	int fd = add_file_to_fdt(fileobj);

	if (fd == -1)
		file_close(fileobj);
	
	return fd;		
}

 

참조를 누르면서 계속 타고 들어가보자.

struct file *filesys_open (const char *name) {
	struct dir *dir = dir_open_root ();
	struct inode *inode = NULL;

	if (dir != NULL)
		dir_lookup (dir, name, &inode);
	dir_close (dir);

	return file_open (inode);
}

/*-----------------------------------------*/

bool dir_lookup (const struct dir *dir, const char *name,
		struct inode **inode) {
	struct dir_entry e;

	ASSERT (dir != NULL);
	ASSERT (name != NULL);

	if (lookup (dir, name, &e, NULL))
		*inode = inode_open (e.inode_sector);
	else
		*inode = NULL;

	return *inode != NULL;
}

 

테스트 파일을 보면 open(NULL)만 있다. 그럼 open() 함수에서 받는 인자가 NULL일 때 -1을 리턴해서 filesys_open() 함수로 진입하지 못 하게 하면 되지 않을까? 일단 한 번 해보자.

int open(const char *file) {
	check_address(file);

	// open_null 테스트 패스 위해
	if (file == NULL) {
		return -1;
	}

	struct file *fileobj = filesys_open(file);
	// filesys_open()은 return file_open(inode) -> file_open()은 return file 이므로, 
	// fileobj = 리턴 값으로 받은 file이 됨

	if (fileobj == NULL)
		return -1;

	int fd = add_file_to_fdt(fileobj);

	if (fd == -1)
		file_close(fileobj);
	
	return fd;		
}

 

저 if문 하나 넣고 돌렸더니

 

오 통과함!

 

 

근데 왜 쉬부렁 fail 개수는 전하고 똑같음?

 

그나마 project 2까지의 부분들은 All pass 되긴 함

 

 

성급하지 말고 다시 에러 하나씩 찾아보자구...

 

다음은 mmap과 munmap.  이 부분들은 저번에 do_mmap()과 do_munmap()함수를 구현했지만, 하나도 통과가 되지 않는다. 

 

그리고... 너무 어이가 없게 시스템 콜 핸들러 부분에 호출하는 함수들을 추가하지 않았었다 ㅠㅠ 지금 바로 추가...

/* The main system call interface */
void syscall_handler (struct intr_frame *f) {

#ifdef VM
		thread_current()->rsp_stack = f->rsp;
#endif

	char *fn_copy;
	int siz;
	switch (f->R.rax) {
	...
	 // for VM
	case SYS_MMAP:
		f->R.rax = mmap(f->R.rdi, f->R.rsi, f->R.rdx, f->R.r10, f->R.r8);
		break;
	case SYS_MUNMAP:
		munmap(f->R.rdi);
		break;
	default:
		exit(-1);
		break;
	}
}

 

돌려보자.

 

 

어머? 개꿀??

 

시스템 콜 핸들러 수정하자마자 mmap 관련 테스트들 다 통과! 패스되지 않은 것들은 뭐지?

 

마지막 cow-simple은 아직 구현하지 않은 부분이니 빼면 두 군데에서 fail이 뜬다. 이 부분들도 언넝 고쳐보자. 물론 다음 포스팅에서 ㅋ

 

 

728x90
LIST

'개발자 도전기 > [OS] pintOS' 카테고리의 다른 글

pintOS | Project 4 | Introduction  (0) 2021.10.28
pintOS | Project 3 회고  (0) 2021.10.28
pintOS | Project 3 | Swap In/Out  (0) 2021.10.21
pintOS | Project 3 | Memory Mapped Files  (0) 2021.10.20
pintOS | Project 3 | Stack Growth  (0) 2021.10.20

댓글