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

Project_2 : User Programs - Argument Passing

bewisesh91 2021. 12. 30. 16:55
728x90

두 번째 Pintos 프로젝트는 사용자 프로그램과 관련한 것으로 아래 사항을 구현하면 된다.

 

1) Argument Passing

2) User Memory

3) System Calls

4) Process Termination Messages

5) Denying Writes to Executables,

6) Extend File Descriptor(Extra)

 

순서대로 Argument Passing 먼저 구현해보자.

현재 Pintos의 process_exec( ) 함수는 새로운 프로세스에 인자를 전달하지 못하는 구조이다.

따라서 입력받은 명령어를 공백을 기준으로 나누어야 한다.

요구 사항에 따르면 명령어의 첫 번째 단어가프로그램명, 두 번째 단어부터가 해당 프로그램에 전달할 인자가 될 수 있도록 수정해야 한다.

예를 들어, process_exec("grep foo bar")에서 grep이 실행할 프로그램명이고 foo와 bar가 전달할 인자인 것이다.

 

구현에 앞서 현재 process_exec( ) 함수가 어떻게 작성되어 있는지 확인해보자.

자세히 보면 인자로 받는 명령어 f_name을 문자열 file_name으로 받고 있으며,

이는 특별한 변환 없이 인터럽트 프레임 구조체인 _if와 함께 load( ) 함수의 인자로 사용된다.

/* userprog/process.c */

int process_exec (void *f_name) {
	char *file_name = f_name;
	bool success;
 
	/* We cannot use the intr_frame in the thread structure.
	 * This is because when current thread rescheduled,
	 * it stores the execution information to the member. */
	struct intr_frame _if;
	_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 ();
 
	/* And then load the binary */
	success = load (file_name, &_if);
 
	/* If load failed, quit. */
	palloc_free_page (file_name);
	if (!success)
		return -1;
 
	/* Start switched process. */
	do_iret (&_if);
	NOT_REACHED ();
}

 

이제 load( ) 함수를 살펴보자. 언급하였듯이 file_name은 사용자가 입력한 명령어이다.

해당 명령어를 parsing 하여 프로그램을 정상적으로 로드 및 스택에 저장해보도록 하겠다.

/* userprog/process.c */

static bool load (const char *file_name, struct intr_frame *if_) {
	struct thread *t = thread_current ();
	struct ELF ehdr;
	struct file *file = NULL;
	off_t file_ofs;
	bool success = false;
	int i;

	// 중략

	/* Set up stack. */
	if (!setup_stack (if_))
		goto done;

	/* Start address. */
	if_->rip = ehdr.e_entry;
	
	/* TODO: Your code goes here.
	 * TODO: Implement argument passing (see project2/argument_passing.html). */

	success = true;

done:
	/* We arrive here whether the load is successful or not. */
	file_close (file);
	return success;
}

 

우선, file_name을 parsing 하기 위해서 strtok_r( ) 함수를 이용한다.

함수의 첫 번째 인자는 분할하고자 하는 문자열, 두 번째 인자는 분할 기준이 되는 구분자, 세 번째 인자는 포인터이다. 

strtok_r( ) 함수는 미리 구현되어 있으며, 자세한 내용은 구현 코드를 직접 살펴보는 것이 이해에 도움이 된다.

 

아래와 같이 strtok_r( ) 함수와 while 문을 이용하여 file_name의 모든 내용을 parsing 하여 argv에 넣어주었다.

앞의 예시를 적용하여 설명하자면 argv에는 [grep\0, foo\0, bar\0]가 담겨있는 것이다.

이렇게argv를 만든 이유는 스택에 프로그램명과 각종 인자들을 넘겨주기 위함이다.

이는 load( ) 함수 마지막 부분 argument_stack( ) 함수 호출을 통해 이루어진다.

/* userprog/process.c */

static bool load (const char *file_name, struct intr_frame *if_) {

	// 중략

	/*--------------- PROJECT2: User Programs ----------------*/
	char *argv[128];
	int argc = 0;
	char *token, *save_ptr;
	token = strtok_r(file_name, " ", &save_ptr);
	while (token != NULL)
	{
		argv[argc] = token;
		token = strtok_r(NULL, " ", &save_ptr);
		argc++;
	}


	// 중략	


	/* Set up stack. */
	if (!setup_stack (if_))
		goto done;

	/* Start address. */
	if_->rip = ehdr.e_entry;

	/* TODO: Your code goes here.
	 * TODO: Implement argument passing (see project2/argument_passing.html). */

	/*--------------- PROJECT2: User Programs ----------------*/
	void **rspp = &if_->rsp;
	argument_stack(argv, argc, rspp);
	if_->R.rdi = argc;
	if_->R.rsi = (uint64_t)*rspp + sizeof(void *);

	success = true;

done:
	/* We arrive here whether the load is successful or not. */
	// file_close (file); 이거 주석 맞나..? 무지성 주석처리;
	return success;
}

 

argument_stack( ) 함수는 다음과 같다.

해당 함수는 우리가 입력한 명령어가 유저 스택에 쌓이도록 하는 하는 함수이다.

/* userprog/process.c */

/* PROJECT2: User Programs - Argument Passing */
/* [argument_stack]
	실행할 파일의 stack(esp)에 인자를 전달하는 함수
	- argv: 프로그램 이름과 인자가 저장되어 있는 메모리 공간
	- argc: 인자의 개수
	- if: 스택 포인터를 가리키는 주소 값
*/

void argument_stack(char **argv, int argc, void **rspp)
{
	// 1. Save argument strings (character by character)
	for (int i = argc - 1; i >= 0; i--)
	{
		int N = strlen(argv[i]);
		for (int j = N; j >= 0; j--)
		{
			char individual_character = argv[i][j];
			(*rspp)--;
			**(char **)rspp = individual_character; // 1 byte
		}
		argv[i] = *(char **)rspp; 
	}

	// 2. Word-align padding
	int pad = (int)*rspp % 8;
	for (int k = 0; k < pad; k++)
	{
		(*rspp)--;
		**(uint8_t **)rspp = (uint8_t)0; // 1 byte
	}

	// 3. Pointers to the argument strings
	size_t PTR_SIZE = sizeof(char *);

	(*rspp) -= PTR_SIZE;
	**(char ***)rspp = (char *)0;

	for (int i = argc - 1; i >= 0; i--)
	{
		(*rspp) -= PTR_SIZE;
		**(char ***)rspp = argv[i];
	}

	// 4. Return address
	(*rspp) -= PTR_SIZE;
	**(void ***)rspp = (void *)0;
}