[0731 발표] fork 실패 시 예외 처리하기

방법이있지·2025년 7월 30일
post-thumbnail

fork가 실패하는 경우

핀토스의 process_fork 함수는 성공 시 자식 프로세스의 TID를 반환하고, 실패 시 TID_ERROR를 반환해야 합니다. 이때 실패할 수 있는 경우는 크게 두 가지로 나눌 수 있습니다.

// [부모] process_fork 함수
tid_t result = thread_create(name, PRI_DEFAULT, __do_fork, fa);

// 자식 쓰레드 생성에 실패하면 TID_ERROR 반환
if (result == TID_ERROR){
    free(fa);
    return TID_ERROR;
}

첫째, thread_create 함수가 자식 프로세스를 생성하지 못한 경우입니다. 이 경우 thread_createTID_ERROR를 반환하므로 실패 여부를 바로 확인할 수 있습니다.

반대로 thread_create를 통한 자식 프로세스 생성에 성공하면, 부모는 세마포어를 통해 자식이 __do_fork에서 부모의 자원을 모두 복제할 때까지 대기하게 됩니다.

둘째, 자식이 부모의 자원을 복제하는 도중에 실패하는 경우입니다. 이때 자식 프로세스는 thread_exit을 호출해 스스로 종료할 수 있습니다. 그러나 세마포어 대기에서 부모 프로세스가 깨어난 후, 자식이 복제에 성공했는지 여부를 알 수 없습니다. 이로 인해 자원 복제에 실패해, 이미 종료된 자식의 TID를 반환하는 오류가 발생할 수 있습니다.

구조체를 활용

struct fork_aux {
	struct thread *parent;
	struct intr_frame *parent_if;
	bool success;                       // 복제의 성공 여부 표시
};

struct thread *curr = thread_current();
	// [구현 7-2] 자식에게 부모의 인터럽트 프레임 전달하기.
	struct fork_aux *fa = malloc(sizeof(struct fork_aux));
	if (fa == NULL){
		return TID_ERROR;
	}
	fa -> parent = thread_current();
	fa -> parent_if = if_;
	fa -> success = false;          // false로 초기화

	// 자식 쓰레드 생성 및 __do_fork 호출
	tid_t result = thread_create(name, PRI_DEFAULT, __do_fork, fa);

이 문제를 해결하기 위해, __do_fork에 전달하는 구조체에 bool 타입의 success 멤버를 추가했습니다. 초기값은 false로 설정하고, PML4 테이블 및 파일 명시자 테이블 등 자원 복제가 모두 성공했을 때 true로 변경합니다.

// [자식] __do_fork 함수

current->pml4 = pml4_create();
if (current->pml4 == NULL)
    goto error;

if (!pml4_for_each(parent->pml4, duplicate_pte, parent))
    goto error;

struct file *copy = file_duplicate(parent_file);
if (copy == NULL)
    goto error;

// 성공한 경우 success 멤버를 true로 변경
((struct fork_aux *)(aux)) -> success = true;
do_iret (&if_);
// 생략

error:
    // 실패한 경우, 바로 error로 이동하므로 success는 false로 유지됨
	sema_up(&(parent -> f_sema));
	thread_exit ();     // 자식 쓰레드 종료

구체적인 실행 흐름은 다음과 같습니다.

  • 자식 프로세스에서 복제에 실패한 경우, error 레이블로 이동 -> successfalse 유지
    • 이후 복제에 실패한 자식 쓰레드를 thread_exit으로 종료시킴
  • 모든 복제에 성공한 경우만, 값을 true로 변경하고, do_iret으로 사용자모드로 복귀
// [부모] process_fork
sema_down(&(thread_current()->f_sema));

// fa는 __do_init에 전달한 구조체
if (!(fa -> success)){
    result = TID_ERROR;
}
free(fa);
return result;

부모와 자식이 이 구조체를 공유하므로, 부모는 자식의 복제 성공 여부를 정확히 확인할 수 있습니다. 즉 세마포어에서 대기하던 부모 process_fork가 깨어난 뒤, success 멤버를 확인하고, false일 시 실패로 처리해 TID_ERROR를 반환하게 구현할 수 있습니다.

회고

핀토스에선 부모, 자식 프로세스 간 복잡한 상호작용이 이루어집니다. 간단한 프로그램에선 에러가 발생할 시 즉시 종료시키면 그만이지만, 핀토스에서는 상호작용을 고려하여 예외 상황을 명확히 처리해야 합니다. 프로그램의 규모가 커질수록 예외 처리가 중요해짐을 몸소 느꼈습니다.

예외 처리를 무시하는 것은 마치 카드키 없이 숙소를 나와 밖에 갇혀
꼼짝 못하는 것과 같습니다. 카드키를 꼭 챙겨야 하듯, 예외 처리도 반드시 챙기시기 바랍니다. 감사합니다.

profile
뭔가 만드는 걸 좋아하는 개발자 지망생입니다. 프로야구단 LG 트윈스를 응원하고 있습니다.

0개의 댓글