pintos-kaist 프로젝트 : System call (5)

Leesoft·2023년 3월 8일
0

학교 과제 : PintOS

목록 보기
5/6
post-thumbnail

이 포스팅은 제가 친구와 PintOS 과제를 하면서 떠올린 생각이나 삽질을 하는 과정을 의식의 흐름대로 적은 글이며 글을 작성한 이후 원래 코드에서 일부 오타나 버그가 수정되었을 수 있습니다. 즉 이 포스팅은 정답을 알려주는 포스팅이 아님을 밝힙니다.

fork() 수리

일단 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.cduplicate_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() 수리

이것도 산뜻하게 커널 패닉 스타트인 것 같은데, 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() 그리고 여러 파일의 동시 접근 문제가 남아 있다! 오늘은 그치만 여기까지 한 게 너무 기뻐서 퇴근해야지!

profile
🧑‍💻 이제 막 시작한 빈 집 블로그...

0개의 댓글