일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
1 | 2 | 3 | ||||
4 | 5 | 6 | 7 | 8 | 9 | 10 |
11 | 12 | 13 | 14 | 15 | 16 | 17 |
18 | 19 | 20 | 21 | 22 | 23 | 24 |
25 | 26 | 27 | 28 | 29 | 30 | 31 |
- DFS
- BFS(Breadth First Search)
- 이분 탐색(Binary Search)
- 위상 정렬(Topology Sort)
- 다익스트라 알고리즘(Dijkstra Algorithm)
- 큐(Queue)
- 그리디 알고리즘(Greedy Algorithm)
- 백준 10000번
- 백준 2493번
- 알고리즘 개념
- 그래프(Graph)
- 동적 프로그래밍(Dynamic Programming)
- 플로이드 워셜 알고리즘(Floyd-Warshall Algorithm)
- 백준 18352번
- 백준 2261번
- 백준 21606번
- 위상 정렬(Topological Sort)
- 백준 1948번
- 트리(Tree)
- 백준 1707번
- 분할 정복(Divide and Conquer)
- DFS(Depth First Search)
- 스택(Stack)
- 이분 그래프(Bipartite Graph)
- 백준 9012번
- 백준 17608번
- 백준 2812번
- DFS & BFS
- 백준 2504번
- BFS
- Today
- Total
Always Be Wise
Project_2 : User Programs - System Calls(2) 본문
지난 포스트에서는 파일과 관련한 시스템 콜을 구현해보았다.
이번에는 프로세스와 관련한 아래 네 가지 시스템 콜들을 구현해보고자 한다.
/* 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 (¤t->spt);
if (!supplemental_page_table_copy (¤t->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(¤t->fork_sema); // child load 완료 시
/* Finally, switch to the newly created process. */
if (succ)
do_iret (&if_);
error:
current->exit_status = TID_ERROR;
sema_up(¤t->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);
}
'카이스트 정글 - 프로젝트 > Pintos' 카테고리의 다른 글
Project_3 : Virtual Memory - Introduction (0) | 2022.01.11 |
---|---|
Project_2 : User Programs - Weekly I Learned (0) | 2022.01.10 |
Project_2 : User Programs - System Calls(1) (0) | 2022.01.04 |
Project_2 : User Programs - Argument Passing (0) | 2021.12.30 |
Project_2 : User Programs - Introduction (0) | 2021.12.30 |