이 포스팅은 제가 친구와 PintOS 과제를 하면서 떠올린 생각이나 삽질을 하는 과정을 의식의 흐름대로 적은 글이며 글을 작성한 이후 원래 코드에서 일부 오타나 버그가 수정되었을 수 있습니다. 즉 이 포스팅은 정답을 알려주는 포스팅이 아님을 밝힙니다.
일단 fork()
를 잘못 짰는지, 여기서부터 계속 테스트가 실패하는 상황이다.
FAIL tests/userprog/fork-once
FAIL tests/userprog/fork-multiple
FAIL tests/userprog/fork-recursive
FAIL tests/userprog/fork-read
FAIL tests/userprog/fork-close
FAIL tests/userprog/fork-boundary
가장 기본적인 fork_once
의 결과를 보니 새로운 페이지를 할당받을 때 사용하는 pml4_get_page()
에서 is_user_vaddr (uaddr)
조건을 만족시키지 못해 에러가 발생했다.
FAIL
Kernel panic in run: PANIC at ../../threads/mmu.c:215 in pml4_get_page(): assertion `is_user_vaddr (uaddr)' failed.
Call stack: 0x80042185ab 0x800420d514 0x800421be0c 0x800420cf9e 0x800420d050 0x800420d0f5 0x800420d18d 0x800421bf0b 0x8004207e5e
Translation of call stack:
0x00000080042185ab: debug_panic (lib/kernel/debug.c:32)
0x000000800420d514: pml4_get_page (threads/mmu.c:217)
0x000000800421be0c: duplicate_pte (userprog/process.c:137)
0x000000800420cf9e: pt_for_each (threads/mmu.c:113)
0x000000800420d050: pgdir_for_each (threads/mmu.c:126)
0x000000800420d0f5: pdp_for_each (threads/mmu.c:139)
0x000000800420d18d: pml4_for_each (threads/mmu.c:152)
0x000000800421bf0b: __do_fork (userprog/process.c:192)
0x0000008004207e5e: kernel_thread (threads/thread.c:604)
이 함수는 process.c
의 duplicate_pte()
함수에서 호출되고, 이것은 __do_fork()
에서 호출된다.
static void
__do_fork(void *aux)
{
// ...
/* 1. Read the cpu context to local stack. */
memcpy(&if_, parent_if, sizeof(struct intr_frame));
/* 2. Duplicate PT */
current->pml4 = pml4_create();
if (current->pml4 == NULL)
goto error;
process_activate(current);
if (!pml4_for_each(parent->pml4, duplicate_pte, parent))
goto error;
// ...
}
어... 보니까 이걸 안 했네? 시키는 대로 써 보도록 하자.
static bool
duplicate_pte(uint64_t *pte, void *va, void *aux)
{
struct thread *current = thread_current();
struct thread *parent = (struct thread *)aux;
void *parent_page;
void *newpage;
bool writable;
/* 1. TODO: If the parent_page is kernel page, then return immediately. */
if (is_kern_pte(pte)) /* IMPLEMENTED IN PROJECT 2-3. */
return true; /* IMPLEMENTED IN PROJECT 2-3. */
/* 2. Resolve VA from the parent's page map level 4. */
parent_page = pml4_get_page(parent->pml4, va);
/* 3. TODO: Allocate new PAL_USER page for the child and set result to
* TODO: NEWPAGE. */
newpage = palloc_get_page(PAL_ZERO | PAL_USER); /* IMPLEMENTED IN PROJECT 2-3. */
/* 4. TODO: Duplicate parent's page to the new page and
* TODO: check whether parent's page is writable or not (set WRITABLE
* TODO: according to the result). */
memcpy(newpage, parent_page, PGSIZE); /* IMPLEMENTED IN PROJECT 2-3. */
writable = is_writable(pte); /* IMPLEMENTED IN PROJECT 2-3. */
/* 5. Add new page to child's page table at address VA with WRITABLE
* permission. */
if (!pml4_set_page(current->pml4, va, newpage, writable))
{
/* 6. TODO: if fail to insert page, do error handling. */
palloc_free_page(newpage);
}
return true;
}
이렇게 했더니, 자식 프로세스가 계속 -1로 종료되는 상황이었는데, wait()
함수에서 부모 스레드의 자식 리스트가 비어 있는 문제가 발생하였다. 이는 exit()
에서 자식 리스트를 벌써 빼 버렸기 때문에 발생한 문제라서, exit()
안에 현재 프로세스(자식)가 종료될 때 list_remove()
하는 부분을 뺐다. 사소한 실수라고 볼 수 있지...
int process_wait(tid_t child_tid UNUSED)
{
/* If no child with pid, it immediately fail and returns -1. */
struct thread *curr = thread_current();
if (list_empty(&curr->child_threads))
{
printf("List empty\n");
return -1;
}
// ...
}
pass tests/userprog/fork-once
FAIL tests/userprog/fork-multiple
pass tests/userprog/fork-recursive
pass tests/userprog/fork-read
pass tests/userprog/fork-close
pass tests/userprog/fork-boundary
하나가 실패했는데, 이것도 사소한 실수였다. 위에서 list_remove()
하는 거 뺐는데... wait()
에 안 넣었네ㅠㅠ
pass tests/userprog/fork-once
pass tests/userprog/fork-multiple
pass tests/userprog/fork-recursive
pass tests/userprog/fork-read
pass tests/userprog/fork-close
pass tests/userprog/fork-boundary
이것도 산뜻하게 커널 패닉 스타트인 것 같은데, exec-once
부터 보도록 하자.
FAIL tests/userprog/exec-once
FAIL tests/userprog/exec-arg
FAIL tests/userprog/exec-boundary
FAIL tests/userprog/exec-missing
FAIL tests/userprog/exec-bad-ptr
FAIL tests/userprog/exec-read
void
test_main (void)
{
msg ("I'm your father");
exec ("child-simple");
}
Executing 'exec-once':
(exec-once) begin
(exec-once) I'm your father
Page fault at 0x404330: not present error reading page in kernel context.
FAIL
Kernel panic in run: PANIC at ../../userprog/exception.c:97 in kill(): Kernel bug - unexpected interrupt in kernel
Call stack: 0x80042185ab 0x800421d3ed 0x800421d56c 0x80042094a3 0x80042098c1 0x800421c7cf 0x800421c3d2 0x800421df42 0x800421d8cb 0x800421d5e1
Translation of call stack:
0x00000080042185ab: debug_panic (lib/kernel/debug.c:32)
0x000000800421d3ed: kill (userprog/exception.c:103)
0x000000800421d56c: page_fault (userprog/exception.c:159 (discriminator 12))
0x00000080042094a3: intr_handler (threads/interrupt.c:352)
0x00000080042098c1: intr_entry (threads/intr-stubs.o:?)
0x000000800421c7cf: load (userprog/process.c:514)
0x000000800421c3d2: process_exec (userprog/process.c:292)
0x000000800421df42: _exec (userprog/syscall.c:345)
0x000000800421d8cb: syscall_handler (userprog/syscall.c:124)
0x000000800421d5e1: no_sti (userprog/syscall-entry.o:?)
이제는 익숙한 Page fault이다! 한번 load()
부터 뒤져보기로 하자. 문제는 여기서 발생했는데, process_cleanup()
에서 프로세스의 리소스가 지워지면서 f_name
이 가리키는 곳에 더 이상 파일 이름이 적혀 있지 않아 load()
에서 file_name
을 파싱할 때 에러가 나는 것이었다.
int process_exec(void *f_name)
{
char *file_name = f_name;
strlcpy(file_name, (char *)f_name, strlen(f_name) + 1);
bool success;
// ...
/* We first kill the current context */
process_cleanup();
/* And then load the binary */
success = load(file_name, &_if);
// ...
}
그래서, 이 부분에서 f_name
에 있는 파일 이름을 새로운 공간에 복사하여 사용하기로 하였다!
int process_exec(void *f_name)
{
/* DELETED IN PROJECT 2-3.
char *file_name = f_name;
*/
char *file_name = (char *)palloc_get_page(PAL_ZERO); /* IMPLEMENTED IN PROJECT 2-3. */
strlcpy(file_name, (char *)f_name, strlen(f_name) + 1);
bool success;
// ...
}
이 부분을 처리하고, 스레드가 한 번 만들어지면 이름이 바뀌지 않는 pintOS의 방식에 따라(테스트 케이스 돌려서 알아냈다) 최초에 thread_create()
을 할 때 파일 이름을 파싱해서 넣음으로써 문제를 해결할 수 있었다! 이번에는 없는 파일을 실행하려 할 때 실패했는데, 실패했을 때 프로세스를 종료시켜야 하기 때문에 _exec()
에서 실패 시 -1
을 반환하는 부분을 _exit(-1)
로 수정하였다.
FAIL tests/userprog/exec-missing
Test output failed to match any acceptable form.
Acceptable output:
(exec-missing) begin
load: no-such-file: open failed
exec-missing: exit(-1)
Differences in `diff -u' format:
(exec-missing) begin
load: no-such-file: open failed
- exec-missing: exit(-1)
int _exec(const char *file)
{
if (process_exec((void *)file) < 0)
// return -1;
_exit(-1);
NOT_REACHED();
}
pass tests/userprog/exec-once
pass tests/userprog/exec-arg
pass tests/userprog/exec-boundary
pass tests/userprog/exec-missing
pass tests/userprog/exec-bad-ptr
pass tests/userprog/exec-read
드디어 exec()
까지 수리 완료!! 다음에는 wait()
그리고 여러 파일의 동시 접근 문제가 남아 있다! 오늘은 그치만 여기까지 한 게 너무 기뻐서 퇴근해야지!