마지막 구현인 fork이다
systemcall 구현중 가장 복잡한 부분이다
fork를 구현할때 가장 중요한 부분은
- 부모의 모든 process를 복사해서 저장한다
- 부모의 process에서 열려있던 file에 대한 정보도 전부 상속 받는다
관련된 함수
(process_fork, __do_fork, duplicate_pte)
//syscall.c
tid_t
fork_handler (const char *thread_name, struct intr_frame *f) {
return process_fork (thread_name, f);
}
아래 함수에서는 임시적으로 malloc을 이용하여 공간을 할당해준다
parent 의 정보를 임시적으로 저장하기 위한 공간
// process.c
struct argv { // process fork를 할때 현재 Thread의 정보와 interrupt frame의
// 정보를 저장하기 위한 공간
struct thread *fork_thread;
struct intr_frame *fork_if;
};
//process.c
tid_t
process_fork (const char *name, struct intr_frame *if_) {
/* Clone current thread to new thread.*/
struct thread *parent = thread_current ();
struct argv *fork_argv = (struct argv *) malloc (sizeof (struct argv)); // thread와 intr_fram을 기억하기 위한 공간을 만들어줌
fork_argv->fork_if = if_; // 각 구조안에 현재 정보를 넣어줌
fork_argv->fork_thread = thread_current ();
tid_t tid = thread_create (name, PRI_DEFAULT, __do_fork, fork_argv); // thread가 create되면서 fork에 성공했다면 tid를 return 하지만 실패했다면 TID_ERROR를 return 함
if (tid == TID_ERROR)
return TID_ERROR;
struct thread *child = get_child (tid); // child list에서 tid가 일치하는 child를 찾아주고 (thread가 create 되면서 추가가 됐을거니깐)
sema_down (&child->fork_sema); // 해당 child의 fork를 sema_down 함 --> fork가 완료되기전에 process가 종료되지 않게 방지함
return tid;
}
__do_fork의 역할은
부모 process의 모든 정보를 current process로 복사해준다
//process.c
static void
__do_fork (void *aux) {
struct intr_frame if_;
// struct thread *parent = (struct thread *) aux;
struct argv *fork_argv = (struct argv *) aux;
struct thread *parent = fork_argv->fork_thread;
struct thread *current = thread_current ();
/* TODO: somehow pass the parent_if. (i.e. process_fork()'s if_) */
struct intr_frame *parent_if;
parent_if = fork_argv->fork_if;
bool succ = true;
/* 1. Read the cpu context to local stack. */
memcpy (&if_, parent_if, sizeof (struct intr_frame));
if_.R.rax = 0; // 자식 process는 항상 0을 return 하기 때문에 if_R.rax = 0을 넣어줌 --> git book 내용과 함께 작성
/* 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, fork_argv)) // pte_entry table에 duplicate_pte 함수를 적용함 --> 실패하면 error로 가고 아니면 진행
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.*/
if (parent->fd_idx >= FD_COUNT_LIMT) // 만일 할당한 fd_idx가 FD_COUNT_LIMIT 보다 크다면 error로 감 (공간 초과)
goto error;
// 부모 process가 가지고 있는 열린 파일의 정보는 자식 process에게 상속됨
current->fd_table[0] = parent->fd_table[0]; // 처음 0과 1은 정해져있으니 그 둘을 그대로 복사해옴
current->fd_table[1] = parent->fd_table[1];
for (int i = 2; i < FD_COUNT_LIMT; i++) { // 남은 부분들도 복사
struct file *temp_file = parent->fd_table[i];
if (temp_file == NULL)
continue;
current->fd_table[i] = file_duplicate (temp_file); // temp file의 속성을 포함한 개체를 복사하고 File과 동일한 inode에 대한 새 파일을 반환 --> 실패하면 NULL을 반환
}
current->fd_idx = parent->fd_idx; // 위 정보를 상속 받기 때문에 열린 파일의 개수도 같이 넘겨줌
sema_up (¤t->fork_sema); // 그리고 sema_up으로 fork_sema는 풀어줌
process_init (); // process를 초기화
/* Finally, switch to the newly created process. */
free (fork_argv); // fork_argv의 역할은 끝났으니깐 free 해줌
/*
do_iret을 통해서 interrupt frame에 있는 정보를 register로 보내줌
if_.rip 의 시작주소를 따로 저장해주는 작업이 없는 이유는 (process_exe의 load에서 하는 작업)
duplicate_pte 와 file_duplicate 등을 통해서 interrupt frame의 모든 정보를 자식 process에 복사를 해주었기 때문임
그래서 그냥 register에 올려주기만 하면됨
*/
if (succ)
do_iret (&if_);
error:
current->exit_status = TID_ERROR;
sema_up (¤t->fork_sema);
free (fork_argv);
exit_handler (TID_ERROR);
}
duplicate 의 주 역할
부모의 주소 공간을 복사(속성 및 메모리를 복사한다)
읽기형인지 읽기형 && 쓰기형인지 속성을 복사해서 자식 process에 적용해준다
/* 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 argv *argv_fork = (struct argv *) aux;
// struct thread *parent = (struct thread *) aux;
struct thread *parent = argv_fork->fork_thread;
void *parent_page;
void *newpage;
bool writable;
/* 1. TODO: If the parent_page is kernel page, then return immediately. */
/*만약 va가 kernel address 라면 --> true를 return */
if (is_kernel_vaddr (va))
return true;
/* 2. Resolve VA from the parent's page map level 4. */
parent_page = pml4_get_page (parent->pml4, va);
if (parent_page == NULL)
return false;
/* 3. TODO: Allocate new PAL_USER page for the child and set result to
* TODO: NEWPAGE. */
newpage = palloc_get_page (PAL_USER);
if (newpage == NULL) {
printf ("duplicate_pte page fault\n");
return false;
}
/* 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). */
/*input 으로 들어온 parent -> pml4 와 is_writable 함수를 이용하여 쓰기가 가능한지 아닌지 판단한다*/
memcpy (newpage, parent_page, PGSIZE);
writable = is_writable (pte);
/* 5. Add new page to child's page table at address VA with WRITABLE
* permission. */
/* fork 하려는 것이 쓰기가 가능하면 read/write가 다되는걸 return 하고
read만 가능하면 read 전용으로 return함
위 두가지 모두 true를 return 함
하지만 메모리를 할당하지 못했다면 false를 return 함
*/
if (!pml4_set_page (current->pml4, va, newpage, writable)) {
/* 6. TODO: if fail to insert page, do error handling. */
printf (" duplicate_pte pmlt_set_page fault \n");
return false;
}
return true;
}