[운체] 오늘의 삽질 - 0723

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

fork 시스템 콜

  • 사진 오타 - case SYS_FORK에서 첫 인자는rax가 아니라 rdi 레지스터에서 저장됨.

  • fork부모 / 자식 프로세스 양쪽에서 반환이 이루어짐

    • 부모: 자식 프로세스의 쓰레드 id를 반환함
    • 자식: 0을 반환함
  • 이때 (A) 자식 프로세스가 생성되고, (B)부모 프로세스의 복제가 완료될 때까지, 부모 프로세스에서 fork의 반환이 이루어지면 안 됨

    • 따라서 세마포어를 이용해, 자식 프로세스의 작업이 완료될 때까지 부모 프로세스가 대기하도록 구현해야 함

사용자 쪽 코드

  • 사용자 쪽 코드엔 손을 댈 일이 없다. 대신 어떻게 동작하는 진 알아야 함.
  • lib/user/syscall.c (사용자쪽): thread_namefork된 자식 프로세스의 이름을 전달한다.
// lib/user/syscall.c
pid_t fork (const char *thread_name){
	return (pid_t) syscall1 (SYS_FORK, thread_name);
}

커널 쪽 코드

  • 커널에서는 userprog/process.cprocess_fork 함수에서 프로세스 포크를 수행한다.
  • 위 함수를 잘 구현한 뒤, userprog/syscall.cvoic syscall_handlercase SYS_FORK 부분을 수정하면 된다.
    전체 소스코드는 깃허브 참고 바람

[구현 7-1] struct thread에 세마포어 추가하기

  • include/threads/thread.h: struct threadfork 시스템 콜에 사용될 세마포어인 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);
}

[구현 7-2] 자식에게 부모의 인터럽트 프레임 전달하기

  • 부모 프로세스의 process_fork에선 thread_create를 이용해 자식 프로세스를 생성
    • thread_create의 3번째 인자는 자식 프로세스에서 실행할 함수, 즉, __do_fork
    • thread_create의 4번째 인자는 __do_fork의 매개변수. 1개만 보낼 수 있음.
  • 우리는 (A) 부모 프로세스의 struct thread(B) 인터럽트 프레임 if_를 전달해야 함
    • 기존 소스코드는 (A)만 보내고 (B)를 보내지 않는데, (B)도 보내야 함
    • 하지만 1개의 인자만 보낼 수 있으므로, 구조체로 묶어서 보내야만 함
    • 이를 위해 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에서 각 멤버를 변수에 저장. 저장하고 나면 auxfree해 줌.
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);
}

[구현 7-3] 부모 세마포어 내리기

  • 자식 프로세스가 생성되고 부모 프로세스의 내용 복제가 완료될 때까지, 부모 프로세스는 리턴해선 안됨
  • 이때 부모 프로세스의 struct thread에 선언된 f_sena를 이용
    • sema_down(&(thread_current()->f_sema));
    • 세마포어의 초기값은 0. 일단 무조건 대기. 자식 프로세스에서 작업 마치고 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;

[구현 7-4] 부모의 페이지 테이블 복제하기

  • __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가 미완성된 상황이라, 이걸 완성시켜 주시면 됨
    • 주석 내용을 간단히 한국어로 정리해 두었으니, 참고하기
  • 사실 pml4 테이블 관련해선 저도 좀 더 공부가 필요하긴 한데, 일단 함수 사용법만 잘 읽어보고 천천히 구현하심 됩니다. 깃북을 읽으세요 깃북.
// 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;
}
  • 그리고 파일 명시자(file descriptor)도 복사해 줘야 하는데, 아직 파일 관련 시스템 콜까진 안 들어가서 뒤늦게 구현할 듯함.

[구현 7-5] 자식프로세스의 반환값 0으로 설정하기

// userprog/process.c, __do_fork 내부
// [구현 7-5] 자식프로세스 반환값 0으로 설정
if_.R.rax = 0;
  • __do_fork에서는 struct intr_frame if_에 값들을 저장해 두고, 추후 do_iret에 인자 &_if를 보내 해당 값들을 복원함
  • 자식 프로세스의 값은 0이 되어야 하므로, if_.rax 레지스터에 0을 저장

[구현 7-6] 자식프로세스 처리 종료 후, 세마포어 올리기

// userprog/process.c, __do_fork 내부
// [구현 7-6] 자식프로세스 처리 종료 후 세마포어 올리기
if (succ){
	sema_up(&(parent -> f_sema));
	do_iret (&if_);
}
  • 자식 프로세스 만들고 모든 복사가 끝나면, do_iret으로 레지스터 값들을 복원하기 전에 sema_up을 수행
  • [구현 7-3]에서 부모 프로세스가 대기 중인 경우, 대기를 해제시킴

[구현 7-7] 부모 프로세스의 반환값 처리

  • 이제 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;
profile
뭔가 만드는 걸 좋아하는 개발자 지망생입니다. 프로야구단 LG 트윈스를 응원하고 있습니다.

0개의 댓글