이런식으로 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 |