/* Clones the current process as `name`. Returns the new process's thread id, or
* TID_ERROR if the thread cannot be created. */
tid_t process_fork(const char *name, struct intr_frame *if_)
{
thread_current()->user_tf = *if_;
/* Clone current thread to new thread.*/
return thread_create(name, PRI_DEFAULT, __do_fork, thread_current());
}
#ifndef VM
/* Duplicate the parent's address space by passing this function to the
* pml4_for_each. This is only for the project 2. */
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 = false;
/* 1. TODO: If the parent_page is kernel page, then return immediately. */
if (is_kern_pte(pte)) // 이거 주석처리하면 오류 생기는 걸로 봐서 맞게 한듯
return false;
/* 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_USER);
/* 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);
if (is_writable(pte))
writable = true;
/* 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. */
pml4_destroy(current->pml4);
return false;
}
return true;
}
#endif
/* A thread function that copies parent's execution context.
* Hint) parent->tf does not hold the userland context of the process.
* That is, you are required to pass second argument of process_fork to
* this function. */
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 = &parent->user_tf;
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);
#ifdef VM
supplemental_page_table_init(¤t->spt);
if (!supplemental_page_table_copy(¤t->spt, &parent->spt))
goto error;
#else
if (!pml4_for_each(parent->pml4, duplicate_pte, parent))
goto error;
#endif
/* TODO: Your code goes here.
* TODO: Hint) To duplicate the file object, use `file_duplicate`
* TODO: in include/filesys/file.h. Note that parent should not return
* TODO: from the fork() until this function successfully duplicates
* TODO: the resources of parent.*/
// 파일 식별자 복사
for (int i = 2; i < 64; i++)
current->fdt[i] = file_duplicate(parent->fdt[i]);
process_init();
// 부모 리스트에 자식 추가
list_push_back(&parent->child_list, ¤t->child_elem);
/* Finally, switch to the newly created process. */
if (succ)
do_iret(&if_);
error:
thread_exit();
}
int process_wait(tid_t child_tid UNUSED)
{
/* XXX: Hint) The pintos exit if process_wait (initd), we recommend you
* XXX: to add infinite loop here before
* XXX: implementing the process_wait. */
int i = 0;
while (i <= 1 << 29)
i++;
// 유효하지 않은 TID면 return -1
if (child_tid < 0 || 64 < child_tid)
return -1;
// 자식 리스트 훑으며 tid에 해당하는거 찾음
struct list_elem *e;
struct thread *found_t;
tid_t found_tid = -1;
int exit_status;
struct list *child_list = &thread_current()->child_list;
// 현재 문제: child_list에 자식이 추가되지 않음
for (e = list_begin(child_list); e != list_end(child_list); e = list_next(e))
{
// 이미 wait 하고 있던거였다면, 안 기다리고 즉시 return -1
// wait 하려고 봤더니 커널이 이미 죽였으면 리스트에서 삭제하고 return -1
// printf("is it working? 2\n");
found_t = list_entry(e, struct thread, elem);
if (found_t->tid == child_tid)
{
found_tid = found_t->tid;
if (found_t->child_sema.value < 0) // 이미 wait 하던 거였다면 return -1
return -1;
// 부모가 sema_down을 한다. 자식은 exit할 때 sema_up을 한다
// 다 기다렸다면 자신이 갖고 있는 자식 리스트에서 pid를 제거
else
{
sema_down(&found_t->child_sema);
exit_status = found_t->status;
list_remove(&found_t->child_elem);
// printf("is it working? 3\n");
return exit_status;
}
}
}
// printf("is it working? 4\n");
// TID에 해당하는 자식이 없었다면
if (found_tid == -1)
return -1;
return -1;
}
여기까지 해놨는데 작동을 안하니까 어떻게 감을 잡아야될지 전혀 모르겠어서
https://yskisking.tistory.com/245
여기 있는 답지 보고 하려고 했는데 처음보는 로직이 많아서 도움이 안됨
돌아버리겠다
일단 자식이 부모 리스트에 들어가는거 __do_fork에 넣으면 안되는 건 얻었고
자식 찾는 함수 따로 빼면 좋은 것도 얻었음
struct list_elem *e;
struct list *child_list = &parent->child_list;
for (e = list_begin(child_list); e != list_end(child_list); e = list_next(e)) // 순회를 못 때려버림
printf("리스트에 들어있는 것은: %s\n", list_entry(e, struct thread, child_elem)->name);
list_entry할 때 elem이 아니라 child_elem으로 했어야 됐는데 이것 때문에 왜 안 넣어지지? (사실 넣어졌는데 리스트를 이상하게 사용하고 있었음) 로 한참동안 시간낭비했다 너무 바보같다
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 = &parent->user_tf;
bool succ = true;
list_push_back(&parent->child_list, ¤t->child_elem); // 바로 자식 리스트에 들어가버리기
printf("%s는 %s의 자식이 되었다!\n", current->name, parent->name);
struct list_elem *e;
struct list *child_list = &parent->child_list;
for (e = list_begin(child_list); e != list_end(child_list); e = list_next(e))
printf("리스트에 들어있는 것은: %s\n", list_entry(e, struct thread, child_elem)->name);
근데 __do_fork에서 자식이 부모의 리스트로 들어가고 난 직후엔 리스트 순회 가능한데
int process_wait(tid_t child_tid UNUSED)
{
/* XXX: Hint) The pintos exit if process_wait (initd), we recommend you
* XXX: to add infinite loop here before
* XXX: implementing the process_wait. */
int i = 0;
while (i <= 1 << 29)
i++;
// 유효하지 않은 TID면 return -1
if (child_tid < 0 || 64 < child_tid)
return -1;
struct list_elem *e;
struct list *child_list = &thread_current()->child_list;
for (e = list_begin(child_list); e != list_end(child_list); e = list_next(e)) // 순회를 못 때려버림
printf("리스트에 들어있는 것은: %s\n", list_entry(e, struct thread, child_elem)->name);
process_wait에서 순회할 땐 아무것도 출력이 안됨
이상해서 이름 출력 찍어봤더니 thread_current()가 wait-simple이 아니라 main이었음.
이러니까 당연히 리스트에 없지.
근데 왜 main임???
결국 물어봤음
main도 wait 해야되는거 맞다고 함.
__do_fork가 아니라 thread_create()에서 자식 리스트에 들어가기 해줘야 했음
// 자식 리스트 훑으며 tid에 해당하는거 찾음
struct thread *find_child(tid_t tid)
{
struct list_elem *e;
struct thread *found_t;
struct list *child_list = &thread_current()->child_list;
for (e = list_begin(child_list); e != list_end(child_list); e = list_next(e))
{
found_t = list_entry(e, struct thread, child_elem);
if (found_t->tid == tid)
return found_t;
}
return NULL;
}
int process_wait(tid_t child_tid UNUSED)
{
/* XXX: Hint) The pintos exit if process_wait (initd), we recommend you
* XXX: to add infinite loop here before
* XXX: implementing the process_wait. */
// int i = 0;
// while (i <= 1 << 29)
// i++;
// 유효하지 않은 TID면 return -1
if (child_tid < 0 || 64 < child_tid)
return -1;
// struct list_elem *e;
// struct list *child_list = &thread_current()->child_list;
// printf("지금부터 %s의 리스트 순회 시작\n", thread_current()->name);
// for (e = list_begin(child_list); e != list_end(child_list); e = list_next(e)) // 순회를 못 때려버림
// printf("리스트에 들어있는 것은: %s\n", list_entry(e, struct thread, child_elem)->name);
struct thread *child = find_child(child_tid);
if (child == NULL)
return -1;
else
{
sema_down(&child->child_sema);
list_remove(&child->child_elem);
}
// 이미 wait 하고 있던거였다면, 안 기다리고 즉시 return -1
// wait 하려고 봤더니 커널이 이미 죽였으면 리스트에서 삭제하고 return -1
// 부모가 sema_down을 한다. 자식은 exit할 때 sema_up을 한다
// 다 기다렸다면 자신이 갖고 있는 자식 리스트에서 pid를 제거
// TID에 해당하는 자식이 없었다면
return -1;
}
tid_t thread_create(const char *name, int priority,
thread_func *function, void *aux)
{
(생략)
#ifdef USERPROG
t->fdt = malloc(64 * sizeof(struct file *)); // 동적 할당 (없어지지 말라고)
memset(t->fdt, 0, 64 * sizeof(struct file *)); // 모든 포인터를 0으로 초기화 // t->fdt = {0};, memset(&t->fdt, 0, 64); 쓰면 안됨
t->fdt[0] = STDIN_FILENO; // 있는 척하기
t->fdt[1] = STDOUT_FILENO; // 있는 척하기
t->next_fd = 2;
list_push_back(&thread_current()->child_list, &t->child_elem); // 바로 자식 리스트에 들어가버리기
// printf("%s는 %s의 자식이 되었다!\n", t->name, thread_current()->name);
#endif
check_priority_and_yield();
return tid;
}
일단 하긴 했는데
아래 거를 안 기다려줌
// int i = 0;
// while (i <= 1 << 29)
// i++;
원래 있던 이 while문 빼버리니까 정상적으로 wait까지 작동함.
FAIL tests/userprog/fork-once
Test output failed to match any acceptable form.
Acceptable output:
(fork-once) begin
(fork-once) child run
child: exit(81)
(fork-once) Parent: child exit status is 81
(fork-once) end
fork-once: exit(0)
Differences in `diff -u' format:
(fork-once) begin
- (fork-once) child run
- child: exit(81)
- (fork-once) Parent: child exit status is 81
+ (fork-once) Parent: child exit status is -1
(fork-once) end
fork-once: exit(0)
pintos -v -k -T 60 -m 20 --fs-disk=10 -p tests/userprog/fork-multiple:fork-multiple -- -q -f run fork-multiple < /dev/null 2> tests/userprog/fork-multiple.errors > tests/userprog/fork-multiple.output
perl -I../.. ../../tests/userprog/fork-multiple.ck tests/userprog/fork-multiple tests/userprog/fork-multiple.result
FAIL tests/userprog/fork-multiple
Test output failed to match any acceptable form.
(이하 생략)
앞에거 다 빠르게 pass 뜨고 뒤에건 뭔가가 나오기 시작했다.
앞에서도 wait 쓰긴 하니까 wait는 문제가 없다는 뜻. fork가 잘 안된다.
뭔가가 일단 된다는 거 자체가 감격스럽다.
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 = false;
/* 1. TODO: If the parent_page is kernel page, then return immediately. */
if (is_kern_pte(pte)) // 이거 주석처리하면 오류 생기는 걸로 봐서 맞게 한듯
return true;
결론적으로 va가 커널 영역인지 검사하는 코드에서 반환값이 false가 아닌 true가 되어야 할 것 같았다. 해당 조건이 만족할 때 바로 반환하라는 주석의 의도는 커널 주소가 인자로 들어오면 바로 fork에 실패하라는 것은 아닐 것이다. 애당초 pml4_for_each의 동작 원리를 생각하면 duplicate_pte의 va인자로 커널 주소는 반드시 들어가도록 되어 있다.
따라서 해당 주석의 의미는 아마도 커널 영역은 모든 프로세스를 통틀어 공통이기 때문에 복사할 필요가 없다는 것이다. 복사할 영역에 해당하는 페이지들은 유저 페이지로 한정되어 있다는 것이다. 따라서 인자로 커널 영역이 들어올 경우 true를 반환하도록 수정하면 문제가 해결될 것이다.
https://nullbyte.tistory.com/50
파일 시스템에도 락을 걸어줘야 한다고 한다
아마 깃북에서 봤거나 강의에서 봤을텐데 당시엔 구현에 급급해서 생략한듯
https://yskisking.tistory.com/245
이거 보고 하나씩 점검하는중