[SW사관학교 정글]70일차 TIL- systemcall 구현 3(exec, wait, fork)

김승덕·2022년 11월 27일
0

SW사관학교 정글 5기

목록 보기
110/150
post-thumbnail

Systemcall 정리 - 3

pintos에서 우리가 구현해야할 system call

  1. void halt(void)
  2. void exit (int status);
  3. bool create (const char *file, unsigned initial_size);
  4. bool remove (const char *file);
  5. int open (const char *file);
  6. int filesize (int fd);
  7. int read (int fd, void *buffer, unsigned size);
  8. int write (int fd, const void *buffer, unsigned size);
  9. void close (int fd);
  10. void seek (int fd, unsigned position);
  11. unsigned tell (int fd);
  12. int exec (const char *cmd_line);
  13. int wait (pid_t pid);
  14. tid_t fork(const char *thread_name, struct intr_frame *f);

이제 3대장이라고 불리우는 exec, wait, fork를 구현해보자.

int exec (const char *cmd_line);

int exec(const char *cmd_line)
{
	check_address(cmd_line);
	char *file_name_copy = palloc_get_page(PAL_ZERO);

	if (file_name_copy == NULL)
		return -1;
	strlcpy(file_name_copy, cmd_line, PGSIZE);

	if (process_exec(file_name_copy) == -1)
		exit(-1);

	NOT_REACHED();
}

Change current process to the executable whose name is given in cmd_line, passing any given arguments. This never returns if successful. Otherwise the process terminates with exit state -1, if the program cannot load or run for any reason. This function does not change the name of the thread that called exec. Please note that file descriptors remain open across an exec call.

현재 프로세스를 주어진 인자와 함께 cmd_line으로 주어진 이름을 갖는 실행 파일로 바꿔라. 이 함수는 성공시 어떤값도 리턴하지 않는다. 만약 그 프로그램이 어떤 이유던 로드되지 못하거나 실행되지 못하면 exit(-1)로 종료되어야한다. 이 함수는 exec라고 불리는 스레드의 이름을 바꾸지 않는다. 파일 디스크립터는 exec call에도 open을 유지해야한다는 것을 명심해라.

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

가장 먼저 인자로 받은 file_name 주소의 유효성을 확인한다. 이후, palloc_get_page() 함수와 strlcpy() 함수를 이용하여 file_namefn_copy로 복사한다. 이는 caller 함수와 load() 사이 race condition을 방지하기 위하여 전달된 인자를 복사하는 것이며, 이어서 process_exec() 함수를 호출하여 해당 프로세스를 메모리에 load()하고 정보를 스택에 쌓는다. 오류 발생시 -1을 반환한다.

(출처 : https://woonys.tistory.com/entry/PintOS-Project-2-User-Program-9-System-Call정글사관학교-72일차-TIL-프로세스-관련-system-call-구현exec-wait-1)

참고 : int process_exec(void *f_name)

/* Switch the current execution context to the f_name.
 * Returns -1 on fail. */
int process_exec(void *f_name) // 유저가 입력한 명령어를 수행하도록 프로그램을 메모리에 적재하고 실행하는 함수. 여기에 파일 네임 인자로 받아서 저장(문자열) => 근데 실행 프로그램 파일과 옵션이 분리되지 않은 상황.
{
	char *file_name = f_name; // f_name은 문자열인데 위에서 (void *)로 넘겨받음! -> 문자열로 인식하기 위해서 char * 로 변환해줘야.
	bool success;
	int i = 0;

	struct intr_frame _if;				  // intr_frame 내 구조체 멤버에 필요한 정보를 담는다.
	_if.ds = _if.es = _if.ss = SEL_UDSEG; //유저 데이타 셀렉터
	_if.cs = SEL_UCSEG;					  // 유저 코드 셀렉터
	_if.eflags = FLAG_IF | FLAG_MBS;

	/* We first kill the current context */
	process_cleanup();
	// 새로운 실행 파일을 현재 스레드에 담기 전에 먼저 현재 process에 담긴 context를 지워준다.
	// 지운다? => 현재 프로세스에 할당된 page directory를 지운다는 뜻.

	/* And then load the binary */

	success = load(file_name, &_if);

	// file_name, _if를 현재 프로세스에 load.
	// success는 bool type이니까 load에 성공하면 1, 실패하면 0 반환.
	// 이때 file_name: f_name의 첫 문자열을 parsing하여 넘겨줘야 한다!

	/* If load failed, quit. */
	palloc_free_page(file_name); // file_name: 프로그램 파일 받기 위해 만든 임시변수. 따라서 load 끝나면 메모리 반환.
	if (!success)
		return -1;

	do_iret(&_if);

	NOT_REACHED();
}

int wait (pid_t pid)

int wait(tid_t pid)
{

	return process_wait(pid);
}

wait 시스템 콜은 process_wait 함수를 실행해준다.

process_wait의 역할은 다음과 같다.

  1. 자식프로세스가 모두 종료될때까지 대기해야하고
  2. 자식 프로세스가 올바르게 종료됐는지 확인해야한다.

현재 process_wait은 -1을 리턴한다. 즉 유저 프로세스가 종료될때까지 대기하지 않고 pintos가 종료된다. 이것을 위의 역할을 수행하도록 수정해준다.

int process_wait(tid_t child_tid UNUSED)

int process_wait(tid_t child_tid UNUSED)
{

	struct thread *child = get_child(child_tid);

	if (child_tid < 0)
		return -1;

	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;
}

자식 프로세스가 수행되고 종료될때까지 부모 프로세스가 대기한다. 프로세스의 정보를 알기위해 프로세스 디스크립터를 검색하고, 자식 프로세스가 종료되지 않았으면 부모 프로세스를 대기시킨다. 그리고 유저 프로세스가 정상적으로 종료시 exit status 반환하고 아니면 -1을 반환한다.

tid_t fork(const char *thread_name, struct intr_frame *f)

tid_t fork(const char *thread_name, struct intr_frame *if_)
{
	return process_fork(thread_name, if_);
}

Create new process which is the clone of current process with the name THREAD_NAME. You don't need to clone the value of the registers except %RBX%RSP%RBP, and %R12 - %R15, which are callee-saved registers. Must return pid of the child process, otherwise shouldn't be a valid pid. In child process, the return value should be 0. The child should have DUPLICATED resources including file descriptor and virtual memory space. Parent process should never return from the fork until it knows whether the child process successfully cloned. That is, if the child process fail to duplicate the resource, the fork () call of parent should return the TID_ERROR.

The template utilizes the pml4_for_each() in threads/mmu.c to copy entire user memory space, including corresponding pagetable structures, but you need to fill missing parts of passed pte_for_each_func (See virtual address).

현재 프로세스의 복제본으로 새로운 프로세스를 생성하라.(해당 프로세스의 이름은 인자로 들어가는 THREAD_NAME) %RBX%RSP%RBP, and %R12 - %R15(callee-saved 레지스터) 레지스터의 값을 제외하고는 레지스터의 값을 clone할 필요는 없다. 반드시 자식 프로세스의 process id를 반환해라. 그렇지 않으면 유효한 pid가 아니다. 자식 프로세스에서, 리턴값은 반드시 0이어야 한다. 자식 프로세스는 반드시 부모 프로세스로부터 파일 디스크립터, 가상 메모리 공간 등을 포함한 자원을 복사해와야 한다. 부모 프로세스는 자식 프로세스가 성공적으로 복제된것을 알고 나서 fork로부터 리턴해야한다. 즉, 만약 자식 프로세스가 자원을 복제하는데 실패하면 부모 프로세스의 fork() 호출은 반드시 TID_ERROR를 반환해야 한다.
이 템플릿은 threads/mmu.c 내에 있는 pml4_for_each()를 사용해 전체 user memory 공간을 복제하는데, 대응하는 페이지 테이블 구조체를 포함한다. 하지만 빈 부분(pte_for_each_func에서)을 채워야한다.

💡 fork() 함수는 부모 프로세스로부터 자식 프로세스를 생성하는 함수이다.

자식 프로세스의 pid 반환, 자식 프로세스에서 리턴값은 반드시 0이어야한다. 부모 프로세스는 자식 프로세스가 성공적으로 복제된것을 알고난 후에 fork로부터 리턴해야한다. 자식 프로세스가 자원을 복제하는데 실패하면 TID_ERROR 반환한다.

tid_t process_fork(const char *name, struct intr_frame *if_ UNUSED)

tid_t process_fork(const char *name, struct intr_frame *if_ UNUSED)
{
	struct thread *t = thread_current();
	memcpy(&t->parent_if, if_, sizeof(struct intr_frame));

	/* Clone current thread to new thread.*/
	tid_t pid = thread_create(name,
							  PRI_DEFAULT, __do_fork, thread_current());

	if (pid == TID_ERROR)
		return TID_ERROR;

	struct thread *child = get_child(pid);
	sema_down(&child->fork_sema);

	if (child->exit_status == -1)
		return TID_ERROR;

	return pid;
}

static void __do_fork(void *aux)

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;

	parent_if = &parent->parent_if;

	/* 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

	if (parent->fd_number >= FDT_COUNT_LIMIT)
		goto error;

	current->file_descriptor_table[0] = parent->file_descriptor_table[0];
	current->file_descriptor_table[1] = parent->file_descriptor_table[1];
	int fd = 2;
	struct file *f;

	current->fd_number = parent->fd_number;

	for (fd; fd < FDT_COUNT_LIMIT; fd++)
	{
		f = parent->file_descriptor_table[fd];

		if (f == NULL)
			continue;

		current->file_descriptor_table[fd] = file_duplicate(f);
	}
	if_.R.rax = 0;

	sema_up(&current->fork_sema);
	// process_init();

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

static bool duplicate_pte(uint64_t *pte, void *va, void *aux)

static bool
duplicate_pte(uint64_t *pte, void *va, void *aux)
{
	struct thread *current = thread_current();
	struct thread *parent = (struct thread *)aux;
	void *parent_page;
	void *newpage;
	bool writable;

	/* 1. TODO: If the parent_page is kernel page, then return immediately. */
	// if (is_kernel_vaddr(parent_page))
	if (is_kernel_vaddr(va))
		return true;

	/* 2. Resolve VA from the parent's page map level 4. */
	parent_page = pml4_get_page(parent->pml4, va);
	if (parent_page == NULL)
		return false;

	/* 3. TODO: Allocate new PAL_USER page for the child and set result to
	 *    TODO: NEWPAGE. */
	newpage = palloc_get_page(PAL_USER);
	if (newpage == NULL)
		return false;

	/* 4. TODO: Duplicate parent's page to the new page and
	 *    TODO: check whether parent's page is writable or not (set WRITABLE
	 *    TODO: according to the result). */

	// newpage = file_duplicate(parent_page);
	memcpy(newpage, parent_page, PGSIZE);
	writable = is_writable(pte);
	/* 5. Add new page to child's page table at address VA with WRITABLE
	 *    permission. */
	if (!pml4_set_page(current->pml4, va, newpage, writable))
	{
		return false;
		/* 6. TODO: if fail to insert page, do error handling. */
	}
	return true;
}
profile
오히려 좋아 😎

0개의 댓글