이 포스팅은 제가 친구와 PintOS 과제를 하면서 떠올린 생각이나 삽질을 하는 과정을 의식의 흐름대로 적은 글이며 글을 작성한 이후 원래 코드에서 일부 오타나 버그가 수정되었을 수 있습니다. 즉 이 포스팅은 정답을 알려주는 포스팅이 아님을 밝힙니다.
아무래도 가장 어려운 부분일 것 같다! 우선 오늘도 Chat GPT에게 어떤 순서로 짜는 게 좋을지 한 번 물어봤다.
void syscall_handler(struct intr_frame *f UNUSED)
{
// ...
case SYS_EXIT:
exit((int)f->R.rdi);
break;
// ...
}
// ...
/* Terminates the current user program, returning status to the kernel.
If the process's parent waits for it (see below), this is the status that will be returned.
Conventionally, a status of 0 indicates success and nonzero values indicate errors.
*/
void exit(int status)
{
thread_current()->exit_status = status;
printf("%s: exit(%d)\n", thread_name());
thread_exit();
}
일단 thread_exit()
이라는 함수가 있으니 가져다 쓰면 될 것 같긴 한데, 이걸 뜯어보면 process_exit()
으로 넘어가야 될 것 같은 느낌이 든다.
/* Exit the process. This function is called by thread_exit (). */
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. */
palloc_free_page((void *)curr->fd_table); /* IMPLEMETED IN PROJECT 2-3. */
process_cleanup();
}
process_exit()
는 이렇게 생겼는데, 저번에 fd_table
을 정리하는 것까지 한 것 같은데, close()
의 설명에서 프로세스가 종료될 때 열려 있는 모든 파일을 닫는다고 했으니 그 전에 close()
를 통해 파일을 닫아 주도록 하자. close()
는 syscall.c
에서 접근할 수 있으니 거기서 닫으면 어떨까?
/* Terminates the current user program, returning status to the kernel.
If the process's parent waits for it (see below), this is the status that will be returned.
Conventionally, a status of 0 indicates success and nonzero values indicate errors.
*/
void exit(int status)
{
/* Close all open files. */
struct thread *curr = thread_current();
for (int i = 2; i < MAX_FILE_NUM; i++)
{
if (curr->fd_table[i] != NULL)
close(i);
}
/* Call the thread_exit() function. */
curr->exit_status = status;
printf("%s: exit(%d)\n", thread_name());
thread_exit();
}
이렇게 코드를 짜고 실행했는데, fd_table
자체가 NULL
이 되어서 page fault가 발생했고, gdb를 이용하여 디버깅한 결과 이전에 thread_create()
에서 palloc_get_page()
를 이용해서 fd_table
을 할당했는데 이게 init_thread()
를 거치면서 다시 NULL
이 되는 것을 알게 되었고 페이지 할당 부분을 init_thread()
호출 이후로 옮겨서 문제를 해결했다.
tid_t thread_create(const char *name, int priority,
thread_func *function, void *aux)
{
struct thread *t;
tid_t tid;
ASSERT(function != NULL);
/* Allocate thread. */
t = palloc_get_page(PAL_ZERO);
if (t == NULL)
return TID_ERROR;
/* Initialize thread. */
init_thread(t, name, priority);
tid = t->tid = allocate_tid();
#ifdef USERPROG
/* IMPLELEMENTED IN PROJECT 2-3. Initializes data structures for file descriptor table. */
t->fd_table = (struct file **)palloc_get_page(PAL_ZERO);
if (t->fd_table == NULL)
{
palloc_free_page(t);
return TID_ERROR;
}
for (int i = 2; i < MAX_FILE_NUM; i++)
{
t->fd_table[i] = NULL; /* Initializes all pointers to NULL. */
}
#endif
// ...
return tid;
}
실행 결과 이렇게 exit()
가 호출되었음을 확인할 수 있게 되었고, 아직 wait()
를 안 짜서 그런지 종료되지 않는다.
여기서 잠시 동안 2-4번 문제인 Process Termination Message를 해결하고 가자! exit()
에 printf()
만 넣으면 되고 코드도 여기 있긴 한데, 그렇다면 위의 실행 예제에서 args-single
만 나와야 하지만 현재는 args-single one
까지 나왔다. 즉 thread_name()
이 프로그램 이름만 받는 게 아니라 인자까지 받아서 args-single onearg
로 처리되는 중, name
이 char[16]
으로 제한되어 있었기에 잘린 것으로 볼 수 있다.
앞 2-1번 문제에서 입력된 텍스트를 argv
에 나누어 담는 것은 했으니, 같은 방법으로 하면 될 것 같다!
struct thread
{
// ...
char name[16];
// ...
}
2-1에서 텍스트를 공백을 기준으로 자르는 부분은 process.c
의 load()
함수에 구현되어 있었으니, 여기에서 이름에 해당하는 argv[0]
의 문자열을 t->name
에 복사해 주면 해결!
/* Loads an ELF executable from FILE_NAME into the current thread.
* Stores the executable's entry point into *RIP
* and its initial stack pointer into *RSP.
* Returns true if successful, false otherwise. */
static bool
load(const char *file_name, struct intr_frame *if_)
{
// ...
/* IMPLEMENTED IN PROJECT 2-1.
Slice file_name, making argc and argv.
Since spaces are turned into null char, file_name will indicate only name of the file, not arguments.
echo x -> argv = ["echo", "x"], argc = 2 */
char *argv[128];
int argc = 0;
char *token, *save_ptr;
for (token = strtok_r(file_name, " ", &save_ptr); token != NULL; token = strtok_r(NULL, " ", &save_ptr))
argv[argc++] = token;
/* IMPLEMENTED IN PROJECT 2-4.
Thread name is file name. */
strlcpy(t->name, argv[0], strlen(argv[0]) + 1);
// ...
}
이렇게 exit()
를 다 짠 것 같긴 했는데, 사실 exit()
의 설명을 보면 부모 프로세스가 기다리는 경우에 대한 설명이 나오는데, 아직 프로세스 간의 부모-자식 관계를 만들지 않았으니 아직 미완성이라고 볼 수 있겠다.
Terminates the current user program, returning status to the kernel.
If the process's parent waits for it (see below), this is the status that will be returned.
Conventionally, a status of 0 indicates success and nonzero values indicate errors.
그렇다면 이제 할 일은 프로세스 간의 부모-자식 관계를 정의하는 것이니까 thread
구조체 안에 부모와 자식 프로세스를 넣으면 되지 않을까? 부모는 하나이고 자식은 여러 개이니, 자식 스레드는 리스트로 관리해야 할 것이고 따라서 thread
구조체 안에 자식 스레드를 리스트로 관리하기 위한 child_elem
필드를 추가해야 할 것 같다.
이제 fork()
를 짤 차례인데, 그 전 exit()
의 설명에 wait()
가 들어가 있어서 wait()
와 함께 스레드의 부모-자식 관계를 확실히 하고 fork()
를 짤 생각이다. 즉, wait()
를 짜든 fork()
를 짜든 결국 스레드의 부모-자식 관계를 정의해야 한다!
나는 추천해 준 대로 fork()
를 먼저 짜겠지만, 그럼에도 일단 exit()
에 할 일이 남았을 수도 있다는 것을 생각해야겠다!
일단 그렇게 되었으니, 앞에서 말한 것처럼 thread
구조체에 이 내용을 추가하자. 리스트가 추가되었으니, init_thread()
에 child_threads
리스트를 초기화하는 코드와 정리하는 코드를 넣어 주자.
struct thread *parent_thread; /* Parent thread. IMPLEMENTED IN PROJECT 2-3. */
struct list child_threads; /* List of child threads. IMPLEMENTED IN PROJECT 2-3. */
struct list_elem c_elem; /* Child thread element. IMPLEMENTED IN PROJECT 2-3. */
/* Exit the process. This function is called by thread_exit (). */
void process_exit(void)
{
struct thread *curr = thread_current();
palloc_free_page((void *)curr->fd_table); /* IMPLEMETED IN PROJECT 2-3. */
t->parent_thread = NULL; /* IMPLEMENTED IN PROJECT 2-3. */
/* IMPLEMENTED IN PROJECT 2-3. */
while (!list_empty(&t->child_threads))
list_pop_back(&t->child_);
process_cleanup();
}
이제 fork()
를 건드려보자. 시작부터 2가지 난관에 봉착했다.
fork()
는 자식 프로세스에서 0을 반환하고, 부모 프로세스에서는 자식의 pid
를 반환한다. 그렇다면 syscall.c
에서 어떻게 짜야 하지?
fork()
는 thread_name
만 받는데, process_fork()
는 struct intr_frame *_if
까지 2개의 인자를 받는다. 그렇다면 이것을 어떻게 해결해야 하지?
case SYS_FORK:
f->R.rax = fork((char *)f->R.rdi);
break;
pid_t fork(const char *thread_name)
{
return (pid_t)process_fork(thread_name, ???);
}
일단 그러면 process_fork()
를 뜯어보자.
/* Clones the current process as `name`. Returns the new process's thread id, or
* TID_ERROR if the thread cannot be created. */
tid_t process_fork(const char *name, struct intr_frame *if_ UNUSED)
{
/* Clone current thread to new thread.*/
return thread_create(name, PRI_DEFAULT, __do_fork, thread_current());
}
즉 현재 process_fork()
는 이름이 같은 스레드를 생성하고, 여기에서 __do_fork
라는 함수를 실행하게 되어 있다.
그러면 1번 의문이 해결된 것 같은 느낌!
그렇다면 일단 자식 프로세스에서 0을 반환한다고 했으니,
__do_fork
가 0을 반환한다면 되는 것이고syscall_handler()
에서 RAX 레지스터에 저장되는 값은 부모 프로세스의 반환값이니 그냥 일반적인int -> int
라고 생각하면 되겠다!
그런데 __do_fork
함수는 반환형이 void
니까... 반환값을 처리하려면 자식 프로세스의 RAX 레지스터에 저장해야 하고, 그러려면 부모 프로세스로부터 struct intr_frame *if_
를 받아와야 할 것이다. thread
구조체에서 intr_frame
구조체를 찾아보니, 여기에 있다!
struct thread
{
// ...
/* Owned by thread.c. */
struct intr_frame tf; /* Information for switching */
unsigned magic; /* Detects stack overflow. */
};
pid_t fork(const char *thread_name)
{
struct thread *curr = thread_current();
return (pid_t)process_fork(thread_name, &curr->tf);
}
그렇다면 이렇게 syscall.c
파일에서 fork()
를 완성할 수 있고, 이제 부모-자식 관계를 설정하고 부모로부터 넘겨받은 intr_frame
구조체의 RAX 레지스터에 0을 저장하면 될 것 같다!
tid_t process_fork(const char *name, struct intr_frame *if_ UNUSED)
{
/* Clone current thread to new thread.*/
return thread_create(name, PRI_DEFAULT, __do_fork, thread_current());
}
처음에는 여기에서 자식 프로세스의 thread
구조체 포인터를 저장하려 했는데 자식 프로세스의 구조체 포인터에 접근할 방법이 없으므로, 답은 자식 프로세스에서 수정하는 것이다. __do_fork()
함수는 thread_current()
구조체를 인자로 받기 때문에 부모 프로세스의 thread
구조체의 주소를 알고 있으며, __do_fork()
내부에서 thread_current()
를 실행하면 자식 프로세스의 thread
구조체의 주소를 알 수 있기 때문이다.
/* A thread function that copies parent's execution context.
* Hint) parent->tf does not hold the userland context of the process.
* That is, you are required to pass second argument of process_fork to
* this function. */
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;
bool succ = true;
// ...
그러고 보니 __do_fork
의 윗부분에 이런 말이 있다.
parent->tf does not hold the userland context of the process.
여기서 parent->tf
가 유저의 context를 담고 있지 않다고 했는데, 그 이유를 생각해 보면 system call이 호출되면 이전의 context는 syscall_handler()
의 인자 struct intr_frame *f
로 전달되고 부모의 tf
는 이제 system call을 처리해야 하기 때문에 RSP 레지스터의 값이 돌아가야 할 함수가 아니라 커널을 가리키는 것으로 이해했다.
프로세스를 복제한다는 것은 system call 실행 직전 부모의 tf
를 복제해야 하는데, 이는 system call 호출 후 parent->tf
가 아닌 syscall_handler()
의 인자를 가져와야 한다는 이야기이다.
따라서, 이를 전달하기 위해 fork()
의 프로토타입, switch
부분, 그리고 fork()
의 구현을 다음과 같이 수정해야 한다.
pid_t fork(const char *thread_name, struct intr_frame *f);
case SYS_FORK:
f->R.rax = fork((char *)f->R.rdi, f);
break;
pid_t fork(const char *thread_name, struct intr_frame *f)
{
return (pid_t)process_fork(thread_name, f);
}
그런데 여기서 빌드를 하면 문제가 발생하는데, 바로 built-in function fork()
와 충돌이 일어난다는 것이다.
warning: conflicting types for built-in function ‘fork’
생각해 보니, 메뉴얼에 주어진 fork()
도 thread_name
을 인자로 받았는데 지금까지는 fork()
함수를 쓰지 않아서 컴파일할 때 빠져버려서 문제가 안 된 것 같지만 이제는 문제를 마주해야 했다.
built-in fork()
는 인자를 받지 않는 함수이므로, 충돌이 일어나지 않게 하려면 인자를 없애야 하는데 그러면 process_fork()
에 넘겨줄 인자가 없으므로 불가능하다.
남은 방법은 fork()
의 이름을 _fork()
로 바꾸어서 진행하는 것인데, 그렇다면 유저가 fork()
를 호출했을 때 그것이 _fork()
를 뜻하는지 알 방법이 없을 것 같다.
그래서, 일단 유저 프로그램이 실행하는 fork()
가 어떻게 된 것인지 찾아봐야겠다.
test/userprog/fork-multiple.c
를 열어보니 다음과 같은 코드가 있다.
/* Forks and waits for multiple child processes. */
#include <syscall.h>
#include "tests/lib.h"
#include "tests/main.h"
void fork_and_wait (void);
int magic = 3;
void
fork_and_wait (void){
int pid;
magic++;
if ((pid = fork("child"))){
int status = wait (pid);
msg ("Parent: child exit status is %d", status);
} else {
msg ("child run");
exit(magic);
}
}
void
test_main (void)
{
fork_and_wait();
fork_and_wait();
fork_and_wait();
fork_and_wait();
}
이 코드는 syscall.h
를 참조하는데 이를 타고 가면 syscall_init()
과 syscall_handler()
가 있는 syscall.c
에 도착하고, 여기의 syscall.h
는 system call interface가 정의되어 있는 lib/user/syscall.h
와 다르다. 하지만, 여기에서도 fork()
를 thread_name
과 함께 사용하고 있는 것을 보면, PintOS에서 fork()
의 프로토타입은 기존에 정의된 (char *) -> int
에서 벗어나면 안 될 것 같다.
삽질한 결과 문제의 원인은 바로 lib/user/syscall.h
를 불러온 것이었는데, 나는 이 헤더 파일을 가져와서 syscall.c
를 짜면 된다고 생각했지만 사실은 그게 아니었던 것이다. lib/user/syscall.h
에 있는 함수들은 이미 구현이 다른 곳에 되어 있었고, 그 구현 내용은 syscall_handler()
를 호출하는 것이다. 즉, lib/user/syscall.h
에서 fork(char *thread_name)
은 이미 syscall_handler()
에서 case FORK
를 호출하는 것이었고, 내가 이것을 가져와서 다시 짜고 있으니 컴파일러 입장에서는 fork()
의 구현이 2개가 되고 타입도 안 맞는다는 것이었다. 따라서, syscall.c
에서 사용할 함수들의 인터페이스는 syscall.c
내에서 다시 선언해야 하고, 나는 이러한 혼란을 방지하고자 모든 함수 앞에 언더바(_)를 붙이기로 하였다. 이렇게 하고 빌드를 하니 정상적으로 빌드 및 작동이 되었는데, 아직 왜 built-in function fork()
와 충돌이 안 나는지는 모르겠다.
void _halt(void) NO_RETURN;
void _exit(int status) NO_RETURN;
tid_t _fork(const char *thread_name, struct intr_frame *f); // Modified to pass intr_frame
// ...
void _close(int fd);
int _dup2(int oldfd, int newfd);
이제 하나의 문제를 해결했으니 조금만 쉬었다가, _fork()
를 짜야겠다! __do_fork()
안의 TODO 커멘트를 보니 이런 내용이 있었지?
/* TODO: somehow pass the parent_if. (i.e. process_fork()'s if_) */
그리고 이건 아래와 같은 순서로 전달되는데, 여기에서 __do_fork()
에게 struct intr_frame *f
를 넘겨주면 된다! 그런데 문제는 __do_fork()
의 인자로 이미 thread_current()
가 있으니 함수의 인자로 struct intr_frame *f
를 줄 수 없는 문제가 발생한다.
void syscall_handler(struct intr_frame *f UNUSED) {
// ...
case SYS_FORK:
f->R.rax = _fork((char *)f->R.rdi, f);
break;
// ...
}
tid_t _fork(const char *thread_name, struct intr_frame *f)
{
return process_fork(thread_name, f);
}
tid_t process_fork(const char *name, struct intr_frame *if_ UNUSED)
{
/* Clone current thread to new thread.*/
return thread_create(name, PRI_DEFAULT, __do_fork, thread_current());
}
어떻게 넘겨줄까 고민을 해 보았는데, thread
구조체 안에 parent_if
를 정의하는 방법도 있겠지만 이번 함수 이외에는 쓸 일이 없을 것 같아 불필요하다고 생각하고 대신 process.c
안에 process_fork()
와 __do_fork()
가 모두 있다는 점을 보고 이 파일 안에 void
포인터 2개로 이루어진 struct pair
를 만들어 전달하면 좋겠다고 생각했다! process_fork()
안에서 struct pair
를 만들 때 포인터로 정의하지 않음으로써 함수 실행이 끝날 때 메모리 누출을 방지하고자 하였고, __do_fork()
함수는 포인터로 인자를 받기 때문에 포인터로 넘겨주었다.
/* IMPLEMENTED IN PROJECT 2-3. */
struct pair
{
void *p1;
void *p2;
};
tid_t process_fork(const char *name, struct intr_frame *if_ UNUSED)
{
/* IMPLEMENTED IN PROJECT 2-3. */
struct pair p;
p.p1 = (void *)thread_current();
p.p2 = (void *)if_;
/* IMPLEMENTED IN PROJECT 2-3. */
return thread_create(name, PRI_DEFAULT, __do_fork, &p);
}
static void
__do_fork(void *aux)
{
struct intr_frame if_;
/* DELETED IN PROJECT 2-3.
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;
*/
struct pair *p = (struct pair *)aux;
struct thread *parent = (struct thread *)p->p1;
struct thread *current = thread_current();
struct intr_frame *parent_if = (struct intr_frame *)p->p2;
bool succ = true;
// ...
}
이렇게 하면 되는 걸까? 이 코드에서는 한 가지 문제점이 있는 것 같다. 결국 thread_create()
는 새로운 스레드를 만들어서 실행하는 함수이고, 그때 부모 프로세스와 자식 프로세스 중 어느 것이 먼저 실행될지 모른다고 한다면 부모 프로세스에서 return
문이 실행되어 함수가 끝난다면 자식 프로세스에서 더 이상 p
에 접근할 수 없게 된다.
그러니, 동기화 방법인 세마포어를 이용하여 p
의 정보를 가져올 때까지, 아니면 프로세스의 복제가 끝날 때까지 부모 프로세스에서 process_fork()
가 return
되지 않도록 하자. 실제로 __do_fork()
안에 이런 말도 있으니까 프로세스의 복제가 끝날 때까지 기다리면 되겠다.
/* 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.*/
그렇다면 부모 프로세스에서 세마포어를 정의한 뒤 sema_down()
을 하고, 이를 자식 프로세스에게 넘겨준 뒤 자식 프로세스에서 sema_up()
을 하면 될 것 같다!
tid_t process_fork(const char *name, struct intr_frame *if_ UNUSED)
{
/* DELETED IN PROJECT 2-3.
Clone current thread to new thread.
return thread_create(name,
PRI_DEFAULT, __do_fork, thread_current());
/* IMPLEMENTED IN PROJECT 2-3. */
struct semaphore fork_sema;
struct triple tri;
tri.p1 = (void *)thread_current();
tri.p2 = (void *)if_;
tri.p3 = (void *)(&fork_sema);
sema_init(&fork_sema, 0);
tid_t result = thread_create(name, PRI_DEFAULT, __do_fork, &tri);
sema_down(&fork_sema);
return result;
}
여기서 세마포어 주소도 넘겨주어야 해서 pair
를 만들었는데, 그러고 보니 차라리 그냥 크기 3짜리 배열을 만드는 것이 나을 것 같았다! 또 크기 3이라는 것을 알고 있으니 굳이 배열의 크기를 넘겨줄 필요도 없을 것 같고... 해서 배열을 이용해 이렇게 수정해 보았다! 그리고 마지막, 자식 프로세스가 0을 반환해야 하기 때문에, 자식 프로세스의 RAX 레지스터에 0을 넣었다.
tid_t process_fork(const char *name, struct intr_frame *if_ UNUSED)
{
struct semaphore fork_sema;
void *bundle[3] = {(void *)thread_current(), (void *)if_, (void *)(&fork_sema)};
sema_init(&fork_sema, 0);
tid_t result = thread_create(name, PRI_DEFAULT, __do_fork, bundle);
sema_down(&fork_sema);
return result;
}
static void
__do_fork(void *aux)
{
struct intr_frame if_;
/* IMPLEMENTED IN PROJECT 2-3. */
void **bundle = (void **)aux;
struct thread *parent = (struct thread *)bundle[0];
struct thread *current = thread_current();
struct intr_frame *parent_if = (struct intr_frame *)bundle[1];
struct semaphore *fork_sema = (struct semaphore *)bundle[2];
bool succ = true;
// ...
if_.R.rax = 0; /* IMPLEMETED IN PROJECT 2-3. */
sema_up(fork_sema); /* IMPLEMENTED IN PROJECT 2-3. */
/* Finally, switch to the newly created process. */
if (succ)
do_iret(&if_);
error:
thread_exit();
}
다음으로, 프로세스 복제가 성공했을 때 부모 프로세스의 child_threads
필드에 자신을 추가하고, 자신 프로세스의 parent_thread
필드에 부모를 추가하는 부분을 짜고 fd_table
의 내용을 복제하는 코드를 짜면 된다! 또한 오류가 발생했을 때도 sema_up()
을 해야 하니 잊지 않도록 하자. fd_table
을 복제하는 함수는 커멘트로 알려줬기 때문에 어렵지 않게 짤 수 있다! 최종 __do_fork()
의 코드는 다음과 같다.
static void
__do_fork(void *aux)
{
struct intr_frame if_;
/* DELETED IN PROJECT 2-3.
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;
*/
/* IMPLEMENTED IN PROJECT 2-3. */
void **bundle = (void **)aux;
struct thread *parent = (struct thread *)bundle[0];
struct thread *current = thread_current();
struct intr_frame *parent_if = (struct intr_frame *)bundle[1];
struct semaphore *fork_sema = (struct semaphore *)bundle[2];
bool succ = true;
/* 1. Read the cpu context to local stack. */
memcpy(&if_, parent_if, sizeof(struct intr_frame));
/* 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.*/
current->fd_table[0] = parent->fd_table[0];
current->fd_table[1] = parent->fd_table[1];
for (int i = 2; i < MAX_FILE_NUM; i++)
{
struct file *f = parent->fd_table[i];
if (f != NULL)
current->fd_table[i] = file_duplicate(f);
}
process_init();
if_.R.rax = 0; /* IMPLEMETED IN PROJECT 2-3. */
sema_up(fork_sema); /* IMPLEMENTED IN PROJECT 2-3. */
/* Finally, switch to the newly created process. */
if (succ)
{
/* IMPLEMENTED IN PROJECT 2-3.
If success, specify the parent-child relationship. */
list_insert(&parent->child_threads, ¤t->c_elem);
current->parent_thread = parent;
do_iret(&if_);
}
error:
sema_up(fork_sema); /* IMPLEMENTED IN PROJECT 2-3. */
thread_exit();
}
그럼 오늘은 그만 짜고 다음에는 exec()
, wait()
만 짜면 될 것 같다! 힘든 하루...