본문 바로가기

KAIST PINTOS

Project 2 디버깅

 이런식으로 53개가 fail이 됐다. 위에서 아래 순서로 디버깅 해봐야지. 

 

▶ argument passing

 먼저 아래 4개의 테스트케이스를 보자. 

     FAIL tests/userprog/args-single
     FAIL tests/userprog/args-multiple
     FAIL tests/userprog/args-many
     FAIL tests/userprog/args-dbl-space

args-a one to three 라고 쓰면 순서대로 나와야할 것이 three to one args-a 순서로 출력되고 있다. argument를 stack에 넣어주는 순서에 문제가 있었던 것 같아 아래처럼 바꿔주니까 위 4개 모두 통과해 49 of 95 tests failed가 됐다. 

	for (int k = argc-1; k > -1; k--){
		int arglen = strlen(argv[k]) + 1; 
		if_->rsp -= arglen;
		memcpy(if_->rsp, argv[k], arglen);
		argv[k] = if_->rsp;	
	}

	uintptr_t word_align = if_->rsp % 8;
	if_->rsp -= word_align;
	memset(if_->rsp, 0, word_align);

	if_->rsp -= 8;
	memset(if_->rsp, 0, 8);

//	for (int j = 0; j < argc; j++){ << 원래 이렇게 돼있었다. 
	for (int j = argc - 1; j >= 0; j--){ 
		if_->rsp -= 8;
		memcpy((char *)if_->rsp, &argv[j], 8);
	}

 

▶ create, open

syscall.c에 create와 open 관련된 코드들을 주석 처리 해놓아서 그런거였다;;; 주석 푸니까 해당 테케들은 통과 됐다. 이제 43/95가 됐다.

▶ close

FAIL tests/userprog/close-normal
FAIL tests/userprog/close-twice
pass tests/userprog/close-bad-fd

둘 다 똑같은 경로를 거쳐 커널 패닉이 뜬다. 진짜 모르겠는데 뭐지..

이유를 모르겠지만 왠지 priority-preempt를 먼저 해결해야할 것 같다는 생각이 든다. 

콜스택을 살펴보면

0x00000080042189ba: debug_panic (lib/kernel/debug.c:32)
0x0000008004219623: find_end_of_run (lib/kernel/list.c:342) 
0x0000008004219adb: list_sort (lib/kernel/list.c:401) 
0x000000800420a85e: sema_up (threads/synch.c:115) 
0x000000800421c6da: process_exit (userprog/process.c:316)
0x0000008004207398: thread_exit (threads/thread.c:327)
0x00000080042077f4: init_thread (threads/thread.c:479)

이렇게 뜨는데, 차례대로 살펴보자. 

 

preempt까지는 실행이 잘 되고 그 이후에 스레드를 종료해줘야하는데 그 과정이 문제인 것 같다. 

init_thread에 (내 생각엔) thread_exit()를 호출하는 부분이 없는데 그게 호출되고, 내부의 ifdef USERPROG를 타고 가서 process_exit으로 들어가는데, 거기서 스레드를 종료하는 과정에 문제가 있는 게 아닐까 하는 생각. 

sema_up()을 수정했더니 priority-preempt는 패스했지만 다른 테스트케이스들은 똑같이 돌아간다. 

이제 정말 print문을 하나씩 찍으면서 확인하는 수밖에 없을 것 같다. 그 전에 인터럽트가 어디서, 왜 발생하는지 정도는 알고 시작할 수 있으면 좋을텐데 감이 안잡힌다. 

 

open 먼저 고치자 하고 open에 있는 코드를 고쳤더니 close-normal까지 해결이 되었다. 여전히 close-twice는 해결되지 않는다. 이제까지 close-normal만 열어보고 따져봤으니, 다음 디버깅 때 close-twice부터 열어서 살펴볼 예정이다. 

open 코드를 고치고 보니 fork를 제외한 대부분의 테스트케이스가 패스됐다.

 

현재 시스템콜의 핸들러부터 fork와 관련된 모든 함수는 주석 처리된 상태이다. close-twice를 해결한 뒤 fork부터 디버깅을 시작하면 될 것 같다. (23/95)

 

▶ fork

현재 fork와 관련된 코드들이 모두 FAIL이 뜨는 상태이다. 

아... 일단 한가지 문제를 찾았다. __do_fork에서 *parent_if를 선언하고, 밑에 memcpy를 해주는 부분이 있는데, 주석 처리해놓은 원래 코드에서는 선언만 해놓고 뭘 넣어준 적이 없었다. 아무 것도 안넣어주고 카피를 시켰다.... 아무 것도 안알려주고 해보라고 시키는 학교랑 다를게 뭐야 시팔 미안하다.. 이 부분을 고치니까 (17/95)가 됐다. 

static void
__do_fork (void *aux) {
	struct intr_frame if_;
	struct thread *parent = (struct thread *) aux;
	struct thread *current = thread_current ();
	/* TODO: somehow pass the parent_if. (i.e. process_fork()'s if_) */
	// struct intr_frame *parent_if;
	struct intr_frame *parent_if = &parent -> fork_intr_frame;

	bool succ = true;

	/* 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);
    ...

 

집중이 안돼서 자잘하게만 손봤다. 여전히 17개의 테케가 남아있지만

여기까지만 해야지

더보기

pass tests/threads/priority-donate-chain
pass tests/userprog/args-none
pass tests/userprog/args-single
pass tests/userprog/args-multiple
pass tests/userprog/args-many
pass tests/userprog/args-dbl-space
pass tests/userprog/halt
pass tests/userprog/exit
pass tests/userprog/create-normal
pass tests/userprog/create-empty
pass tests/userprog/create-null
pass tests/userprog/create-bad-ptr
pass tests/userprog/create-long
pass tests/userprog/create-exists
pass tests/userprog/create-bound
pass tests/userprog/open-normal
pass tests/userprog/open-missing
pass tests/userprog/open-boundary
pass tests/userprog/open-empty
pass tests/userprog/open-null
pass tests/userprog/open-bad-ptr
pass tests/userprog/open-twice
pass tests/userprog/close-normal
FAIL tests/userprog/close-twice
pass tests/userprog/close-bad-fd
pass tests/userprog/read-normal
pass tests/userprog/read-bad-ptr
pass tests/userprog/read-boundary
pass tests/userprog/read-zero
pass tests/userprog/read-stdout
pass tests/userprog/read-bad-fd
pass tests/userprog/write-normal
pass tests/userprog/write-bad-ptr
pass tests/userprog/write-boundary
pass tests/userprog/write-zero
pass tests/userprog/write-stdin
pass tests/userprog/write-bad-fd
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
FAIL tests/userprog/exec-once
FAIL tests/userprog/exec-arg
FAIL tests/userprog/exec-boundary
FAIL tests/userprog/exec-missing
pass tests/userprog/exec-bad-ptr
FAIL tests/userprog/exec-read
FAIL tests/userprog/wait-simple
FAIL tests/userprog/wait-twice
FAIL tests/userprog/wait-killed
pass tests/userprog/wait-bad-pid
FAIL tests/userprog/multi-recurse
FAIL tests/userprog/multi-child-fd
FAIL tests/userprog/rox-simple
FAIL tests/userprog/rox-child
FAIL tests/userprog/rox-multichild
pass tests/userprog/bad-read
pass tests/userprog/bad-write
pass tests/userprog/bad-read2
pass tests/userprog/bad-write2
pass tests/userprog/bad-jump
pass tests/userprog/bad-jump2
pass tests/filesys/base/lg-create
pass tests/filesys/base/lg-full
pass tests/filesys/base/lg-random
pass tests/filesys/base/lg-seq-block
pass tests/filesys/base/lg-seq-random
pass tests/filesys/base/sm-create
pass tests/filesys/base/sm-full
pass tests/filesys/base/sm-random
pass tests/filesys/base/sm-seq-block
pass tests/filesys/base/sm-seq-random
FAIL tests/filesys/base/syn-read
pass tests/filesys/base/syn-remove
FAIL tests/filesys/base/syn-write
FAIL tests/userprog/no-vm/multi-oom
pass tests/threads/alarm-single
pass tests/threads/alarm-multiple
pass tests/threads/alarm-simultaneous
pass tests/threads/alarm-priority
pass tests/threads/alarm-zero
pass tests/threads/alarm-negative
pass tests/threads/priority-change
pass tests/threads/priority-donate-one
pass tests/threads/priority-donate-multiple
pass tests/threads/priority-donate-multiple2
pass tests/threads/priority-donate-nest
pass tests/threads/priority-donate-sema
pass tests/threads/priority-donate-lower
pass tests/threads/priority-fifo
pass tests/threads/priority-preempt
pass tests/threads/priority-sema
pass tests/threads/priority-condvar
pass tests/threads/priority-donate-chain
17 of 95 tests failed.

 

 

pintos -v -k -T 60 -m 20   --fs-disk=10 -p tests/userprog/close-twice:close-twice -p ../../tests/userprog/sample.txt:sample.txt -- -q   -f run close-twice

 

pintos -v -k -T 60 -m 20   --fs-disk=10 -p tests/userprog/exec-once:exec-once -p tests/userprog/child-simple:child-simple -- -q   -f run exec-once

 

pintos -v -k -T 60 -m 20   --fs-disk=10 -p tests/userprog/exec-arg:exec-arg -p tests/userprog/child-args:child-args -- -q   -f run exec-arg 

 

pintos -v -k -T 60 -m 20   --fs-disk=10 -p tests/userprog/exec-boundary:exec-boundary -p tests/userprog/child-simple:child-simple -- -q   -f run exec-boundary

 

pintos -v -k -T 60 -m 20   --fs-disk=10 -p tests/userprog/exec-missing:exec-missing -- -q   -f run exec-missing

 

pintos -v -k -T 60 -m 20   --fs-disk=10 -p tests/userprog/exec-read:exec-read -p ../../tests/userprog/sample.txt:sample.txt -p tests/userprog/child-read:child-read -- -q   -f run exec-read

 

pintos -v -k -T 60 -m 20   --fs-disk=10 -p tests/userprog/wait-simple:wait-simple -p tests/userprog/child-simple:child-simple -- -q   -f run wait-simple

 

pintos -v -k -T 60 -m 20   --fs-disk=10 -p tests/userprog/wait-twice:wait-twice -p tests/userprog/child-simple:child-simple -- -q   -f run wait-twice

 

pintos -v -k -T 60 -m 20   --fs-disk=10 -p tests/userprog/wait-killed:wait-killed -p tests/userprog/child-bad:child-bad -- -q   -f run wait-killed

 

...

 

▶ exec

테스트케이스들을 보니 exec 관련된 코드가 잘 돌아가지 않는 것 같아서 exec을 수정해보기로 했다. make check를 한 뒤 터미널을 확인하자.

가장 간단해보이는 exec-once 테스트케이스의 코드를 확인해보자. 

void
test_main (void) 
{
  msg ("I'm your father");
  exec ("child-simple");
}

test_main 내부의 exec()이 syscall.c에 있는 sys_exec()을 부르는 것 같다. child-simple.c를 타고 가서 보니 전체적인 흐름이 아래와 같은 것 같다.

실행중인 스레드가 "begin", "I'm your father"을 출력한다.

(자식 프로세스가) "run"을 출력하고, 81을 리턴한다. 

따라서 실행중이던 스레드가 exit(81)을 반환하고 종료된다.

 

sys_exec을 봐봐야지. 오....마이갓............. 일단 프린트문이 있어서 그걸 지워줬다. 휴 뭔가가 바뀔 줄 알았는데 여전히 17/95이다. sys_exec()의 코드를 보면

int sys_exec (const char *cmd_line){
	if (!is_user_vaddr(cmd_line)){
 		exit(-1);
	}

	if (&cmd_line != NULL)
		return process_exec(cmd_line);

	return -1;
}

유저 영역인지 확인해준 뒤, 아니라면 exit(-1) 호출 / 자식 프로세스 생성 성공 시 생성된 프로세스를 리턴, 실패 시 -1을 반환하는 코드로 맞게 짜준 것 같다. process_exec()에 문제가 있는 것 같으니까 process.c로 가서 해당 코드를 봐야겠다. 지금은 child-simple이 실행되지 않고 exit(-1)을 반환하며 종료되고 있다. 

int
process_exec (void *f_name) { 
	char *file_name = f_name;
	// char *file_name = malloc(128 * sizeof(char));
	// memcpy(file_name, f_name, strlen(f_name) + 1);

	bool success;

	/* We cannot use the intr_frame in the thread structure.
	 * This is because when current thread rescheduled,
	 * it stores the execution information to the member. */
	struct intr_frame _if;
	_if.ds = _if.es = _if.ss = SEL_UDSEG;
	_if.cs = SEL_UCSEG;
	_if.eflags = FLAG_IF | FLAG_MBS;

	/* We first kill the current context */
	process_cleanup ();

	/* And then load the binary */
	success = load (file_name, &_if);

	/* If load failed, quit. */
	palloc_free_page (file_name);
	if (!success)
		return -1;

	/* Start switched process. */
	do_iret (&_if);
	NOT_REACHED ();
}

위가 고치기 전에 막 열어본 코드다. char *file_name은 process_exec 내부에서 선언된 변수이고, palloc으로 할당을 해준 적이 없는데(심지어 주석 처리된 코드도 palloc이 아니라 malloc이다;;) palloc_free_page()를 해주고있길래 위에 주석 처리 되어 있던 malloc() 코드 두 줄을 다시 써주고, palloc_free_page 대신 free(file_name)을 해주었다. 그랬더니 exec이랑 wait 관련된 테스트케이스가 통과돼서.... 7/95가 됐다.......... 왜지????????? 할당이 그렇게 중요한....... 일이었던가?.......... 오앰지......... 너무 까다롭다 핀토스.... 

 

이제 rox-~ 3개, syn-read, syn-write, multi-oom, close-twice 이렇게 7개가 남았다. 끌리는 순서대로 디버깅 해보겠다.  

 

▶ syn-read, syn-write

두 테케 모두 커널 패닉이 뜬다. 콜스택도 똑같다. 

exec을 고쳤던 것과 마찬가지로 sys_wait부터 보자. 역시나 sys_wait()에서는 pid가 널인지 확인해준 뒤 process_wait(pid)를 호출하는 것밖에 없다. 그렇다면 또 process_wait이 문제라는 소리^^

 

int
process_wait (tid_t child_tid) {
	/* XXX: Hint) The pintos exit if process_wait (initd), we recommend you
	 * XXX:       to add infinite loop here before
	 * XXX:       implementing the process_wait. */

	struct thread *curr = thread_current();
	int result = -1;

	struct thread *t = get_child(child_tid);

	if (t == NULL) 
		return -1;

	sema_down(&t -> wait_semaphore);
	result = t -> exit_status;
	
	list_remove(&t -> child_elem);
	list_remove(&t -> elem);
	palloc_free_page(t);

	return result;
}

1. child_tid로 child를 찾아주고, 유효한지 확인해준다.

2. 자식이 신호를 보내기 전까지(sema_up을 호출하기 전까지) 부모는 기다린다(sema_down).

 

-----------------------2월 7일 결국 다시 git stash를 참지 못했다. 17개 pass된 시점으로 돌아간다. ---------------------

위에 기록해놓은대로 따라가다보니 다시 7개만 남은 상태로 돌아왔다. 뭐 하나만 좀 고쳐도 다 커널 패닉이 떠서 돌아버릴 것 같다. 

 

파일을 열기 전에 load sema, 중간에 에러가 발생했을 때 부모와 자식 관계를 끊도록 하는 orphan sema, 프로세스가 종료되는 것을 알리는 exit sema 이렇게 적어도 3개의 semaphore가 필요하다. 

 

sema_down: sema의 value가 0이 아닐때까지, while 루프 안에서 대기

sema_up: sema 를 기다리는 waiter들 중에서 앞의 녀석을 꺼내 unblock

'KAIST PINTOS' 카테고리의 다른 글

argument passing  (0) 2022.02.13
Process.c  (0) 2022.02.13
system call  (0) 2022.01.21
PROJECT 1 - Alarm Clock, Priority Scheduling  (0) 2022.01.20
fork()  (0) 2022.01.20