Always Be Wise

Project_2 : User Programs - System Calls(2) 본문

카이스트 정글 - 프로젝트/Pintos

Project_2 : User Programs - System Calls(2)

bewisesh91 2022. 1. 7. 23:36
728x90

지난 포스트에서는 파일과 관련한 시스템 콜을 구현해보았다.

이번에는 프로세스와 관련한 아래 네 가지 시스템 콜들을 구현해보고자 한다.

/* include/lib/user/syscall.h */

/* Projects 2 and later. */

	// 중략

void exit (int status) NO_RETURN;
pid_t fork (const char *thread_name);
int exec (const char *file);
int wait (pid_t);

	// 중략

/* include/lib/syscall-nr.h */

enum {
	/* Projects 2 and later. */

	// 중략
    
	SYS_EXIT,                   /* Terminate this process. */
	SYS_FORK,                   /* Clone current process. */
	SYS_EXEC,                   /* Switch current process. */
	SYS_WAIT,                   /* Wait for a child process to die. */

	// 중략
};

가장 먼저 SYS_EXEC 시스템 콜을 처리하는 exec( ) 함수를 구현해보고자 한다.

exec( ) 함수는 현재 프로세스를 명령어로 입력받은 실행가능 파일로 변경하는 함수이다.

가장 먼저 인자로 받은 file_name 주소의 유효성을 확인한다.

이후, palloc_get_page( ) 함수와 strlcpy( ) 함수를 이용하여 file_name을 fn_copy로 복사한다.

마지막으로 process_exec( ) 함수를 호출하여 해당 fn_copy를 이용하여 프로세스를 로드하고, 해당 정보를 스택에 쌓아준다.

Change current process to the executable whose name is given in cmd_line.
/* userprog/syscall.c */

/*--------------- PROJECT2: User Programs ----------------*/
int exec(char *file_name){
	check_address(file_name);

	int siz = strlen(file_name) + 1;
	char *fn_copy = palloc_get_page(PAL_ZERO);
	if (fn_copy == NULL)
		exit(-1);
	strlcpy(fn_copy, file_name, siz);

	if (process_exec(fn_copy) == -1)
		return -1;

	NOT_REACHED();

	return 0;
}

다음으로 SYS_FORK 시스템 콜을 처리하는 fork( ) 함수를 구현해보고자 한다.

fork( ) 함수는 내부에서 process_fork( ) 함수를 호출하며, 이때 인자로 thread_name과 Interrupt stack frame f를 전달한다.

process_fork( ) 함수는 내부에서 다양한 함수들을 호출하는데, 

우선, memcpy( ) 함수를 호출하여 해당 함수의 두 번째 인자(if_)의 값을 첫 번째 인자(&cur->parent_if)로 복사한다.

fork( ) 함수가 자식 프로세스를 생성하는 과정으로 이해한다면,

이는 자식 프로세스 생성에 앞서 부모 프로세스의 내용을 memcpy 하는 과정으로 이해할 수 있다.

 

memcpy( ) 이후, thread_create( ) 함수를 진행하고, 해당 함수의 리턴 값을 tid에 저장한다.

thread_create( ) 함수의 인자로는 fork( ) 함수의 인자로 받았던 thread_name과, __do_fork( ) 함수, 현재 스레드 등이 들어간다.

__do_fork( ) 함수를 요약하자면 부모 프로세스의 내용을 자식 프로세스로 복사하는 과정이라고 할 수 있다.

이는 memcpy( ) 함수와 반복문, do_iret( ) 함수 등을 통해 이루어진다.

 

흥미로운 점은 process_fork( ) 과정에서 semaphore가 등장한다는 것이다.

자세히 설명하자면, 부모 프로세스는 thread_create( ) 함수의 리턴으로 받은 tid를 갖고 자식 프로세스를 찾는다.

이후, 해당 자식의 fork_sema를 sema_down( ) 한다.

이러한 과정은 자식 프로세스의 정상적인 로드를 위한 것으로,

자식 프로세스는 __do_fork( ) 함수를 통해 부모 프로세스의 정보를 모두 복사한 이후 sema_up( )을 호출한다. 

Create new process which is the clone of current process with the name THREAD_NAME
/* userprog/syscall.c */

/*--------------- PROJECT2: User Programs ----------------*/
tid_t fork(const char *thread_name, struct intr_frame *f){
	return process_fork(thread_name, f);
}


/* userprog/process.c */

tid_t process_fork (const char *name, struct intr_frame *if_ UNUSED) {
	/* Clone current thread to new thread.*/
	struct thread *cur = thread_current();
	memcpy(&cur->parent_if, if_, sizeof(struct intr_frame)); // 부모 프로세스 memcpy 

	tid_t tid = thread_create(name, PRI_DEFAULT, __do_fork, cur); // 전달받은 thread_name으로 __do_fork() 진행 
	
	if (tid == TID_ERROR)
		return TID_ERROR;

	struct thread *child = get_child_with_pid(tid); // get_child_with_pid 함수 실행
	sema_down(&child->fork_sema); // child load 대기
	if (child -> exit_status == -1)
		return TID_ERROR;

	return tid;
}


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_) */
	
	/*--------------- PROJECT2: User Programs ----------------*/
	struct intr_frame *parent_if;
	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; 

	/* 2. Duplicate PT */
	current->pml4 = pml4_create();
	if (current->pml4 == NULL)
		goto error;

	process_activate (current); 

#ifdef VM
	supplemental_page_table_init (&current->spt);
	if (!supplemental_page_table_copy (&current->spt, &parent->spt))
		goto error;
#else
	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.*/

	if (parent->fdIdx == FDCOUNT_LIMIT)
		goto error;

	/*--------------- PROJECT2: User Programs ----------------*/
	for (int i = 0; i < FDCOUNT_LIMIT; i++) {
		struct file *file = parent->fdTable[i];
		if (file == NULL)
			continue;

		// If 'file' is already duplicated in child, don't duplicate again but share it
		bool found = false;
		if (!found) {
			struct file *new_file;
			if (file > 2)
				new_file = file_duplicate(file);
			else
				new_file = file;

			current->fdTable[i] = new_file;
		}
	}

	current->fdIdx = parent->fdIdx;

	sema_up(&current->fork_sema); // child load 완료 시

	/* Finally, switch to the newly created process. */
	if (succ)
		do_iret (&if_);
error:
	current->exit_status = TID_ERROR;
	sema_up(&current->fork_sema);
	exit(TID_ERROR);
}


/*--------------- PROJECT2: User Programs ----------------*/
struct thread *get_child_with_pid(int pid) {
	struct thread *cur = thread_current();
	struct list *child_list = &cur->child_list;

	for (struct list_elem *e = list_begin(child_list); e != list_end(child_list); e = list_next(e))
	{
		struct thread *t = list_entry(e, struct thread, child_elem);
		if (t->tid == pid)
			return t;
	}	
	return NULL;
}

다음으로 SYS_WAIT 시스템 콜을 처리하기 위한 wait( ) 함수를 구현해보도록 하자.

wait( ) 함수는 내부에서 process_wait( ) 함수를 호출한다. 이때 인자로 전달하는 것은 자식 프로세스의 ID이다.

해당 ID를 이용하여 process_wait( ) 함수 내부에서 get_child_with_pid( ) 함수를 호출하며, 이를 통해 자식 프로세스 구조체를 찾는다.

이후, 자식 프로세스의 wait_sema를 sema_down( )한다.

해당 semaphore는 자식 프로세스가 process_exit( )를 호출할 때 sema_up( )이 이루어진다.

즉, 자식 프로세스가 작업을 모두 마칠 때까지 기다린다는 의미라고 할 수 있다.

이후, 작업을 마친 자식 프로세스를 자식 리스트에서 삭제 해주고, 자식 프로세스의 free_sema를 sema_up( ) 한다.

이 free_sema 역시, process_exit( ) 과정에서 자식 프로세스가 sema_down( ) 한 것을 변경해주는 것이다.

Waits for a child process pid and retrieves the child's exit status.
/* userprog/syscall.c */

/*--------------- PROJECT2: User Programs ----------------*/
int wait (tid_t pid){
	process_wait(pid);
};


/* userprog/process.c */

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 *cur = thread_current();
	struct thread *child = get_child_with_pid(child_tid);

	if (child == NULL)
		return -1;
	
	sema_down(&child->wait_sema); 
	int exit_status = child->exit_status;
	list_remove(&child->child_elem);
	sema_up(&child->free_sema);

	return exit_status;
}

마지막으로 SYS_EXIT을 처리하기 위한 exit( ) 함수를 구현해보자.

exit( )함수는 내부에서 thread_exit( ) 함수를 호출하고 바로 다시 process_exit( ) 함수를 호출한다.

process_exit( ) 함수에서는 palloc_free_multiple( ), file_close( ), process_cleanup( ) 등을 수행한다.

이를 통해 메모리 누수 방지, 사용하던 자원 반납 등을 진행함으로써 프로그램을 종료한다.

Terminates the current user program.
/* userprog/syscall.c */

/*--------------- PROJECT2: User Programs ----------------*/
void exit(int status){
	struct thread *cur = thread_current();
	cur->exit_status = status;

	printf("%s: exit(%d)\n", thread_name(), status);

	thread_exit();
}


/* userprog/process.c */

void thread_exit (void) {
	ASSERT (!intr_context ());

#ifdef USERPROG
	process_exit ();
#endif
	/*--------------- PROJECT2: User Programs ----------------*/
	list_remove(&thread_current()->allelem);

	/* Just set our status to dying and schedule another process.
	   We will be destroyed during the call to schedule_tail(). */
	intr_disable ();
	do_schedule (THREAD_DYING);
	NOT_REACHED ();
}


void process_exit (void) {
	struct thread *curr = thread_current ();
	/* TODO: Your code goes here.
	 * TODO: Implement process termination message (see
	 * TODO: project2/process_termination.html).
	 * TODO: We recommend you to implement process resource cleanup here. */

	/*--------------- PROJECT2: User Programs ----------------*/
	for (int i = 0; i < FDCOUNT_LIMIT; i++){
		close(i);
	}

	palloc_free_multiple(curr->fdTable, FDT_PAGES); 

	file_close(curr->running);

	process_cleanup ();

	sema_up(&curr->wait_sema);
	
	sema_down(&curr->free_sema);
}
Comments