현재 깃북을 따라 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이 뜬다. 이 부분들도 언넝 고쳐보자. 물론 다음 포스팅에서 ㅋ
'개발자 도전기 > [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 |
댓글