과제 목표
구현
exec();
case SYS_EXEC:
exec(f->R.rdi);
break;
int exec (const char *file_name) {
check_address(file_name);
int file_size = strlen(file_name) + 1;
char *fn_copy = palloc_get_page(PAL_ZERO);
if (!fn_copy) {
exit(-1);
return -1;
}
strlcpy(fn_copy, file_name, file_size);
if (process_exec(fn_copy) == -1) {
exit(-1);
return -1;
}
}
인자로 받은 실행 파일을 실행시킨다. 현재 실행 중인 프로세스의 이미지를 이 실행 파일 프로세스의 이미지로 바꿔치기한다. 새로운 프로세스를 생성하는 것은 아니다. fork()가 자신의 복사본을 생성해 실행한다면, 자신의 복사본이 아닌 아예 다른 프로그램을 실행해야 하는 경우에 exec()을 사용한다.
file_name을 copy를 해야 하는가?
process_exec()
에서 process_cleanup()
을 할 때 해당 file_name
의 문자열의 물리적 메모리와의 매핑 정보를 담은 Page Table도 같이 지워지기 때문이다.strlcpy()
하여 복사된 파일 이름 문자열은 커널 스택에 저장될 것이다. 따라서 process_cleanup()
후에도 커널 스택과 연결된 페이지 테이블을 통해 물리적 메모리와 매핑되어 활용될 수 있으므로, 애초에 fn_copy
문자열을 process_exec()
의 인자로 넣어준다.process_cleanup()
후에 참조하려 시도하면 page fault가 뜬다.wait();
case SYS_WAIT:
f->R.rax = wait(f->R.rdi);
break;
int wait (tid_t pid) {
return process_wait(pid);
}
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. */
struct thread *child = get_child_process(child_tid);
if (child == NULL)
return -1;
sema_down(&child->sema_wait);
int exit_status = child->exit_status;
list_remove(&child->child_elem);
sema_up(&child->sema_exit);
return exit_status;
}
wait_sema
를 DOWN해주고 BLOCK된다. 이제 자식 프로세스가 sema_up()
으로 명시적으로 풀어주지 않는 이상 부모 프로세스는 계속 잠들어 있는다.exit_status
등.exit_status
를 얻어온다. 그리고 자신의 child_list
에서 자식 프로세스를 지운다.exit_status
를 반환한다.void process_exit (void) {
•••
sema_up(&curr->sema_wait);
sema_down(&curr->sema_exit);
palloc_free_page(table);
process_cleanup ();
}
process_exit()
함수에 세마포어를 추가해 자식 프로세스가 종료되었을 때, 부모를 깨울수 있도록 다음과 같이 코드를 추가해준다.fork();
case SYS_FORK:
memcpy(&thread_current()->ptf, f, sizeof(struct intr_frame));
f->R.rax = fork(f->R.rdi);
break;
int fork (const char *thread_name) {
check_address(thread_name);
return process_fork(thread_name, &thread_current()->ptf);
}
tid_t process_fork (const char *name, struct intr_frame *if_ UNUSED) {
/* Clone current thread to new thread.*/
struct thread *cur = thread_current();
tid_t ctid = thread_create (name, PRI_DEFAULT, __do_fork, cur);
if (ctid == TID_ERROR)
return TID_ERROR;
struct thread *child = get_child_process(ctid);
sema_down(&cur->sema_fork);
return ctid;
}
현재 실행되고 있는 부모 프로세스를 자식 프로세스에게 복제한다 __do_fork()
. 자식이 fork를 완료할 때까지 BLOCK해 있다가, 자식이 fork를 완료하면 새롭게 생성된 자식 프로세스의 pid
를 반환한다.
이 때 인자로 받는 if_
는 시스템 콜 핸들러 함수에 인자로 들어가는, 시스템 콜을 부른 부모 프로세스의 인터럽트 프레임이다.
어차피 부모 프로세스의 인터럽트 프레임이면 그냥 parent->tf
를 하면 되지 뭐하러 parent->parent_if
로 if
를 복사해서 쓰냐?
if_ = parent->parent_if
이다.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->ptf;
bool succ = true;
/* 1. Read the cpu context to local stack. */
memcpy (&if_, parent_if, sizeof (struct intr_frame));
if_.R.rax = 0;
•••
•••
int cnt = 2;
struct file **table = parent->fdt;
while (cnt < 128) {
if (table[cnt]) {
current->fdt[cnt] = file_duplicate(table[cnt]);
} else {
current->fdt[cnt] = NULL;
}
cnt++;
}
current->next_fd = parent->next_fd;
sema_up(&parent->sema_fork);
process_init ();
/* Finally, switch to the newly created process. */
if (succ)
do_iret (&if_);
error:
sema_up(&parent->sema_fork);
exit(TID_ERROR);
}
파일을 복사한다.
fdt
와 그에 매핑되는 파일도 모조리 똑같이 복사한다.자식이 fork가 끝날 때까지 잠들어있던 부모 프로세스를 깨운다.
if_의 값들을 모두 레지스터에 넣음으로서 자식 프로세스를 실행시킨다.
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_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)
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). */
memcpy(newpage, parent_page, PGSIZE);
writable = is_writable(pte);
/* 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. */
return false;
}
return true;
}
PintOS Project2 GIthub 주소 PintOS