syscall.c
pid_t fork(const char *thread_name) {
check_address(thread_name);
return process_fork(thread_name, NULL);
}
process.c
/* Clones the current process as `name`. Returns the new process's thread id, or
* TID_ERROR if the thread cannot be created. */
/** #Project 2: System Call - 현재 프로세스를 `name`으로 복제합니다. 새 프로세스의
* 스레드 ID를 반환하거나 스레드를 생성할 수 없는 경우 TID_ERROR를 반환합니다.
*/
tid_t
process_fork (const char *name, struct intr_frame *if_ UNUSED) {
/* Clone current thread to new thread.*/
thread_t *curr = thread_current();
struct intr_frame *f = (pg_round_up(rrsp()) - sizeof(struct intr_frame)); // 현재 쓰레드의 if_는 페이지 마지막에 붙어있다.
memcpy(&curr->parent_if, f, sizeof(struct intr_frame)); // 1. 부모를 찾기 위해서 2. do_fork에 전달해주기 위해서
/* 현재 스레드를 새 스레드로 복제합니다.*/
tid_t tid = thread_create(name, PRI_DEFAULT, __do_fork, curr);
if (tid == TID_ERROR)
return TID_ERROR;
thread_t *child = get_child_process(tid);
sema_down(&child->fork_sema); // 생성만 해놓고 자식 프로세스가 __do_fork에서 fork_sema를 sema_up 해줄 때까지 대기
if (child->exit_status == TID_ERROR)
return TID_ERROR;
return tid; // 부모 프로세스의 리턴값 : 생성한 자식 프로세스의 tid
}
process.c
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->parent_if;
bool succ = true;
/* 1. Read the cpu context to local stack. */
memcpy (&if_, parent_if, sizeof (struct intr_frame));
if_.R.rax = 0; // 자식 프로세스의 return값 (0)
/* 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
// Page Table 통째로 복제
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.
* TODO: Hint) 파일 객체를 복제하려면 include/filesys/file.h에서 `file_duplicate`를 사용하세요.
이 함수가 부모의 리소스를 성공적으로 복제할 때까지 부모는 fork()에서 반환되어서는 안 됩니다. */
if (parent->fd_idx >= FDCOUNT_LIMIT)
goto error;
/** #Project 2: Extend File Descriptor - fd 복제 */
struct dict_elem dup_file_dict[DICTLEN];
int dup_idx = 0;
current->fd_idx = parent->fd_idx; // fdt 및 idx 복제
struct file *file;
for (int fd = 0; fd < FDCOUNT_LIMIT; fd++) {
file = parent->fdt[fd];
if (file == NULL)
continue;
bool is_exist = false;
for (int i = 0; i <= dup_idx; i++) {
if (dup_file_dict[i].key == file) {
current->fdt[fd] = file_duplicate(file);
is_exist = true;
break;
}
}
if (is_exist)
continue;
if (file > STDERR)
current->fdt[fd] = file_duplicate(file);
else
current->fdt[fd] = file;
if (dup_idx < DICTLEN) {
dup_file_dict[dup_idx].key = file;
dup_file_dict[dup_idx++].value = current->fdt[fd];
}
/** -------------------------------------------------------------- */
}
sema_up(¤t->fork_sema); // fork 프로세스가 정상적으로 완료됐으므로 현재 fork용 sema unblock
process_init ();
/* Finally, switch to the newly created process. */
if (succ)
do_iret (&if_); // 정상 종료 시 자식 Process를 수행하러 감
error:
sema_up(¤t->fork_sema); // 복제에 실패했으므로 현재 fork용 sema unblock
exit(TID_ERROR);
}
process_fork와 __do_fork의 차이점process_forkfork 시스템 호출을 처리하기 위한 함수입니다.__do_fork를 호출하여 실제로 프로세스 복제를 수행합니다.process_fork는 주로 초기화와 데이터 전달의 역할을 담당합니다.__do_forkprocess_fork에 의해 호출되며, 실제 자식 프로세스의 생성 및 초기화 작업을 수행합니다.process_fork부모 프로세스의 상태를 기반으로 새로운 프로세스를 준비합니다.
자식 프로세스를 위한 스레드 생성 요청을 수행하고, 해당 스레드가 실행되도록 설정합니다.
부모 프로세스의 CPU 상태(struct intr_frame)를 __do_fork에 전달하여 자식 프로세스에서 이어 실행될 수 있도록 준비합니다.
주요 흐름:
c
코드 복사
tid_t process_fork(const char *name, struct intr_frame *if_) {
// 현재 스레드 정보를 읽고 초기화
struct thread *parent = thread_current();
// 새 스레드를 생성하고 __do_fork 호출
tid_t tid = thread_create(name, PRI_DEFAULT, __do_fork, parent);
// 실패 시 에러 반환
if (tid == TID_ERROR)
return TID_ERROR;
// 부모는 자식이 fork 작업을 완료할 때까지 대기
sema_down(&parent->fork_sema);
return tid;
}
__do_fork새 스레드에서 실행되는 함수로, 자식 프로세스를 위한 구체적인 복제 작업을 수행합니다.
부모의 메모리 구조(Page Table), 파일 디스크립터 테이블, CPU 상태 등을 복사하여 자식 프로세스를 초기화합니다.
자식 프로세스는 복제가 끝난 뒤 새로운 실행 경로로 진입합니다.
주요 흐름:
c
코드 복사
static void __do_fork(void *aux) {
struct thread *parent = (struct thread *)aux;
struct thread *current = thread_current();
// 부모의 CPU 상태 복제
memcpy(&if_, &parent->parent_if, sizeof(struct intr_frame));
if_.R.rax = 0; // 자식 프로세스의 반환 값 설정
// 부모의 페이지 테이블 복제
current->pml4 = pml4_create();
if (!pml4_for_each(parent->pml4, duplicate_pte, parent))
goto error;
// 파일 디스크립터 복제
for (int fd = 0; fd < FDCOUNT_LIMIT; fd++) {
if (parent->fdt[fd])
current->fdt[fd] = file_duplicate(parent->fdt[fd]);
}
// 프로세스 전환
do_iret(&if_);
error:
exit(TID_ERROR);
}
| 기능 | process_fork | __do_fork |
|---|---|---|
| 스레드 생성 | 새로운 스레드를 생성 | 새로운 스레드로 호출되어 실행 |
| 부모 데이터 전달 | 부모의 CPU 상태(struct intr_frame)와 스레드 구조 전달 | 전달받은 데이터를 기반으로 자식 프로세스 초기화 |
| 주소 공간 복제 | 직접 수행하지 않음 | 부모의 페이지 테이블 및 메모리 구조를 복제 |
| 파일 디스크립터 복제 | 직접 수행하지 않음 | 부모의 파일 디스크립터 테이블을 복제 |
| CPU 상태 초기화 | 초기화 작업 없음 | CPU 상태를 복원하고 자식 프로세스의 실행 경로 진입 |
| 부모-자식 동기화 | sema_down을 통해 부모가 대기 | 작업 완료 후 sema_up으로 부모에게 작업 완료 신호 전송 |
process_fork:__do_fork:do_fork를 분리하여 구현한 이유는 설계의 모듈화, 책임 분리, 그리고 코드의 가독성과 유지보수성을 높이기 위함입니다. 이러한 설계는 운영체제와 같은 복잡한 시스템에서 특히 중요합니다. 주요 이유를 다음과 같이 정리할 수 있습니다:
process_fork:__do_fork:process_fork는 부모 프로세스에서 호출하며 새로운 스레드 생성 요청을 합니다.__do_fork는 새 스레드에서 호출되며 자식 프로세스의 주소 공간과 상태를 복제합니다.이렇게 두 함수를 나눔으로써 각 함수가 단일 책임 원칙(Single Responsibility Principle)을 따를 수 있습니다.
process_fork는 부모 프로세스에서 실행되고, __do_fork는 자식 프로세스에서 실행됩니다.sema_down과 sema_up과 같은 동기화 메커니즘을 활용하여 부모-자식 간의 작업 완료 여부를 명확히 처리할 수 있습니다.process_fork는 부모가 자식이 준비될 때까지 대기하도록 설계됩니다.__do_fork를 별도로 분리함으로써:process_fork와 *__do_fork는 서로 독립적으로 재사용될 수 있습니다.__do_fork는 다른 시스템 호출에서도 사용 가능하며, 다른 방식으로도 호출될 수 있습니다.process_fork:__do_fork:운영체제의 다른 부분에서 process_fork를 호출할 때 내부의 복잡한 구현을 알 필요 없이, 자식 프로세스를 생성하는 인터페이스로만 사용할 수 있습니다.
__do_fork 내부만 수정하면 됩니다.process_fork)는 영향을 받지 않으므로 안정성을 유지할 수 있습니다.__do_fork는 독립적인 단위로 테스트할 수 있어, 프로세스 복제 로직에 문제가 없는지 검증하기 쉽습니다.process_fork와 __do_fork를 분리하면 다음과 같은 이점이 있습니다:
이러한 설계는 운영체제와 같은 복잡한 소프트웨어 시스템에서 매우 유용합니다.