
fork 시스템 콜
사진 오타 - case SYS_FORK에서 첫 인자는rax가 아니라 rdi 레지스터에서 저장됨.
fork는 부모 / 자식 프로세스 양쪽에서 반환이 이루어짐
0을 반환함이때 (A) 자식 프로세스가 생성되고, (B)부모 프로세스의 복제가 완료될 때까지, 부모 프로세스에서 fork의 반환이 이루어지면 안 됨
lib/user/syscall.c (사용자쪽): thread_name에 fork된 자식 프로세스의 이름을 전달한다.// lib/user/syscall.c
pid_t fork (const char *thread_name){
return (pid_t) syscall1 (SYS_FORK, thread_name);
}
userprog/process.c의 process_fork 함수에서 프로세스 포크를 수행한다.userprog/syscall.c의 voic syscall_handler 내 case SYS_FORK 부분을 수정하면 된다.struct thread에 세마포어 추가하기include/threads/thread.h: struct thread에 fork 시스템 콜에 사용될 세마포어인 struct sempahore f_sema를 추가한다.#include "threads/synch.h" // 헤더 인클루드 잊지 말기
struct thread {
// 생략
struct semaphore f_sema; /* [구현 7-1] fork 시스템 콜 용도 */
}
userprog/process.c: 프로세스가 생성될 때마다 호출되는 함수 process_init에, 세마포어를 초기화하는 코드를 추가한다.// 앞으로 process.c에서 세마포어 쓸 일이 많을 것임. include하기.
#include "threads/synch.h"
void process_init (void) {
struct thread *current = thread_current ();
// [구현 7-1] 쓰레드 구조체의 세마포어 초기화
sema_init(&(current -> f_sema), 0);
}
process_fork에선 thread_create를 이용해 자식 프로세스를 생성thread_create의 3번째 인자는 자식 프로세스에서 실행할 함수, 즉, __do_forkthread_create의 4번째 인자는 __do_fork의 매개변수. 1개만 보낼 수 있음.struct thread 및 (B) 인터럽트 프레임 if_를 전달해야 함struct fork_aux 선언. 구조체 내 (A)를 전달하기 위한 struct thread형 멤버와, (B)를 전달하기 위한 struct intr_frame형 멤버 선언.// userprog/process.c
// [구현 7-2] 부모의 인터럽트 프레임 전달 위한 구조체 선언
struct fork_aux {
struct thread *parent;
struct intr_frame *parent_if;
};
tid_t process_fork (const char *name, struct intr_frame *if_ UNUSED) {
// [구현 7-2] 부모의 인터럽트 프레임 전달하기.
struct fork_aux *fa = malloc(sizeof(struct fork_aux));
fa -> parent = thread_current();
fa -> parent_if = if_;
/* Clone current thread to new thread.*/
tid_t result = thread_create(name, PRI_DEFAULT, __do_fork, fa);
return result;
}
__do_fork에서 각 멤버를 변수에 저장. 저장하고 나면 aux를 free해 줌.static void
__do_fork (void *aux) {
// [구현 7-2] 부모로부터 인터럽트 프레임 전달받기
struct thread *parent = ((struct fork_aux *)(aux)) -> parent;
struct intr_frame *parent_if = ((struct fork_aux *)(aux)) -> parent_if;
free(aux);
}
struct thread에 선언된 f_sena를 이용sema_down(&(thread_current()->f_sema));sema_up으로 1로 높여주면 그때 계속 실행됨.// userprog/process.c
tid_t process_fork (const char *name, struct intr_frame *if_ UNUSED) {
// [구현 7-2] 자식에게 부모의 인터럽트 프레임 전달하기.
struct fork_aux *fa = malloc(sizeof(struct fork_aux));
fa -> parent = thread_current();
fa -> parent_if = if_;
/* Clone current thread to new thread.*/
tid_t result = thread_create(name, PRI_DEFAULT, __do_fork, fa);
// [구현 7-3] 부모 세마포어 내리기
sema_down(&(thread_current()->f_sema));
return result;
__do_fork에선 pml4_for_each 함수를 이용해, 부모 프로세스의 페이지 테이블을 자식으로 복제함// userprog/process.c, __do_fork 내부
if (!pml4_for_each (parent->pml4, duplicate_pte, parent))
__do_fork에선 해 줘야 하는 게 없음. 저 코드도 이미 위에서 잘 선언되어 있을 것임pml4_for_each는 각 페이지 테이블 엔트리에 대해, duplicate_pte를 호출하여 복제를 진행duplicate_pte가 미완성된 상황이라, 이걸 완성시켜 주시면 됨// userprog/process.c
static bool duplicate_pte (uint64_t *pte, void *va, void *aux) {
// [구현 7-4] 부모의 페이지 테이블 복사를 위한, duplicate_pte 함수 완성하기
// pte: 현재 페이지 테이블 엔트리의 주소. 테이블 내 모든 pte에 대해 이 함수가 실행됨.
// va: 해당 pte에 매핑된 사용자 가상 주소
// aux: 부모프로세스의 struct thread 포인터.
struct thread *current = thread_current ();
struct thread *parent = (struct thread *) aux;
void *parent_page;
void *new_page;
bool writable;
/* 1. pte에 매핑된 부모 페이지가 커널에 위치한 경우, 복사하지 않고 다음 pte로 건너뛴다. */
/* false를 반환하면 전체 테이블에 대한 복사 과정이 중단됨. true를 반환해야 함 */
if (is_kern_pte(pte)){
return true;
}
/* 2. 부모의 pml4 테이블에서 va에 해당하는 페이지를 가져와, parent_page에 저장한다. */
parent_page = pml4_get_page (parent->pml4, va);
/* 3. 자식 프로세스를 위해 PAL_USER (사용자영역) 옵션이 설정된 새 페이지를 할당하고, new_page에 저장한다 */
new_page = palloc_get_page(PAL_USER);
/* 4. 부모의 페이지 내용을 new_page에 복사한 뒤, 쓰기가능 여부를 writable 변수에 저장한다. */
memcpy(new_page, parent_page, PGSIZE);
if (is_writable(pte)){
writable = 1;
} else {
writable = 0;
}
/* 5. 자식의 페이지 테이블에 VA 주소로 new_page를 매핑하고, writable 권한과 함께 등록한다. */
if (!pml4_set_page (current->pml4, va, new_page, writable)) {
/* 6. 페이지 삽입에 실패했을 경우 에러 처리를 수행한다. (일단 강제 종료되게 false를 반환) */
return false;
}
return true;
}
// userprog/process.c, __do_fork 내부
// [구현 7-5] 자식프로세스 반환값 0으로 설정
if_.R.rax = 0;
__do_fork에서는 struct intr_frame if_에 값들을 저장해 두고, 추후 do_iret에 인자 &_if를 보내 해당 값들을 복원함if_.의 rax 레지스터에 0을 저장// userprog/process.c, __do_fork 내부
// [구현 7-6] 자식프로세스 처리 종료 후 세마포어 올리기
if (succ){
sema_up(&(parent -> f_sema));
do_iret (&if_);
}
do_iret으로 레지스터 값들을 복원하기 전에 sema_up을 수행process_fork가 정상 작동하니, SYS_FORK 시스템 콜이 보내질 때 process_fork를 실행하고, 그 반환값을 잘 받아오면 됨userprog/syscall.c에서 valid_pointer로 %rax 레지스터(첫 인수 -> thread_name)에 올바른 포인터가 전달됐는지 검사thread_create가 자식 프로세스의 tid를 반환하면, 이 값을 process_fork가 반환함thread_create는 내부적으로 쓰레드 생성 실패시 -1을 반환함에 유의 %rax 레지스터에 저장하면, 시스템 콜의 반환값이 됨case SYS_FORK:
/* Clone current process. */
// [구현 7] 자식 프로세스 생성 및 복제
valid_pointer(f -> R.rdi);
f -> R.rax = process_fork((char *)(f -> R.rdi), f);
break;