pintos-kaist 프로젝트 : System call (2)

Leesoft·2023년 3월 1일
0

학교 과제 : PintOS

목록 보기
2/6
post-thumbnail

이 포스팅은 제가 친구와 PintOS 과제를 하면서 떠올린 생각이나 삽질을 하는 과정을 의식의 흐름대로 적은 글이며 글을 작성한 이후 원래 코드에서 일부 오타나 버그가 수정되었을 수 있습니다. 즉 이 포스팅은 정답을 알려주는 포스팅이 아님을 밝힙니다.

Process 관련 system call

exit()

아무래도 가장 어려운 부분일 것 같다! 우선 오늘도 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로 처리되는 중, namechar[16]으로 제한되어 있었기에 잘린 것으로 볼 수 있다.

앞 2-1번 문제에서 입력된 텍스트를 argv에 나누어 담는 것은 했으니, 같은 방법으로 하면 될 것 같다!

struct thread
{
	// ...
	char name[16];
    // ...
}

2-1에서 텍스트를 공백을 기준으로 자르는 부분은 process.cload() 함수에 구현되어 있었으니, 여기에서 이름에 해당하는 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()

이제 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가지 난관에 봉착했다.

  1. fork()는 자식 프로세스에서 0을 반환하고, 부모 프로세스에서는 자식의 pid를 반환한다. 그렇다면 syscall.c에서 어떻게 짜야 하지?

  2. 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(&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.*/
	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, &current->c_elem);
		current->parent_thread = parent;

		do_iret(&if_);
	}
error:
	sema_up(fork_sema); /* IMPLEMENTED IN PROJECT 2-3. */
	thread_exit();
}

그럼 오늘은 그만 짜고 다음에는 exec(), wait()만 짜면 될 것 같다! 힘든 하루...

profile
🧑‍💻 이제 막 시작한 빈 집 블로그...

0개의 댓글