728x90

/bin/ls -l foo bar 와 같은 명령이 주어졌을 때, 인자들을 어떻게 다뤄야 하는지 생각해보자

  1. 명령을 단어들로 쪼갠다. /bin/ls, l, foo, bar 이렇게
  2. 이 단어들을 스택의 맨 처음 부분에 놓는다. 순서는 상관 없다. 왜냐면 포인터에 의해 참조될 예정이기 때문이다.
  3. 각 문자열의 주소 + 경계조건을 위한 널포인터를 스택에 오른쪽→왼쪽 순서로 푸시한다. **이들은 argv의 원소가 된다. 널포인터 경계는 argv[argc] 가 널포인터라는 사실을 보장해준다. C언어 표준의 요구사항에 맞춰서 말이다. 그리고 이 순서는 argv[0]이 가장 낮은 가상 주소를 가진다는 사실을 보장해준다. 또한 word 크기에 정렬된 접근이 정렬되지 않은 접근보다 빠르므로, 최고의 성능을 위해서는 스택에 첫 푸시가 발생하기 전에 스택포인터를 8의 배수로 반올림하여야 한다.
  4. %rsi 가 argv 주소(argv[0]의 주소)를 가리키게 하고, %rdi 를 argc 로 설정한다.
  5. 마지막으로 가짜 “리턴 어드레스”를 푸시한다 : entry 함수는 절대 리턴되지 않겠지만, 해당 스택 프레임은 다른 스택 프레임들과 같은 구조를 가져야 한다. 

 

Address Name Data Type

0x4747fffc argv[3][...] 'bar\0' char[4]
0x4747fff8 argv[2][...] 'foo\0' char[4]
0x4747fff5 argv[1][...] '-l\0' char[3]
0x4747ffed argv[0][...] '/bin/ls\0' char[8]
0x4747ffe8 word-align 0 uint8_t[]
0x4747ffe0 argv[4] 0 char *
0x4747ffd8 argv[3] 0x4747fffc char *
0x4747ffd0 argv[2] 0x4747fff8 char *
0x4747ffc8 argv[1] 0x4747fff5 char *
0x4747ffc0 argv[0] 0x4747ffed char *
0x4747ffb8 return address 0 void (*) ()

RDI: 4 | RSI: 0x4747ffc0

 

위 표는 스택과 관련 레지스터들이 유저 프로그램이 시작되기 직전에 어떤 상태인지를 보여준다. 스택은 아래로 자란다는 것을 잊지 말자.

 *      4 kB +---------------------------------+
 *           |          kernel stack           |
 *           |                |                |
 *           |                |                |
 *           |                V                |
 *           |         grows downward          |
 *           |                                 |
 *           |                                 |
 *           |                                 |
 *           |                                 |
 *           |                                 |
 *           |                                 |
 *           |                                 |
 *           |                                 |
 *           +---------------------------------+
 *           |              magic              |
 *           |            intr_frame           |
 *           |                :                |
 *           |                :                |
 *           |               name              |
 *           |              status             |
 *      0 kB +---------------------------------+

 

 

저 위의 표의 예시에서, 스택 포인터는 위에 보이는 것처럼 0x4747ffb8로 초기화될 것이고, 코드 include/threads/vaddr.h에 정의된 USER_STACK 값에서부터 스택을 시작시켜야 한다.

 

현재, process_exec() 함수는 새로운 프로세스들에 인자를 전달하는 것을 지원하지 않는다. process_exec() 함수를 확장 구현해서, 지금처럼 단순히 프로그램 파일 이름만을 인자로 받아오게 하는 대신 공백을 기준으로 여러 단어로 나누어지게 만들어야 한다.

첫 번째 단어는 프로그램 이름이고, 두 번째 단어는 첫 번째 인자이며, 그런 식으로 계속 이어지게 만들면 된다. 따라서, 함수 process_exec("grep foo bar") 는 두 개의 인자 foo와 bar을 받아서 grep 프로그램을 실행시켜야 한다.

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 ();
	int argc = 0;
    char *argv[64];		//parssing한 인자를 담을 배열
    char *token, *save_ptr;
    
	for (token = strtok_r(file_name, " ", &save_ptr); token != NULL; token = strtok_r(NULL, " ", &save_ptr))
    {   
		argv[argc++] = token;
	}
	/* And then load the binary */
	success = load (file_name, &_if);

	argument_stack(argv, argc, &_if);
	
	/* If load failed, quit. */
	palloc_free_page (file_name);
	if (!success)
		return -1;

	/* Start switched process. */
	do_iret (&_if);
	NOT_REACHED ();
}

 

argument_stack 함수는 프로세스의 스택에 실행 인자를 준비하는 역할을 한다. 인자들을 스택에 복사하고, 64비트 시스템에서 요구하는 8바이트 정렬을 맞추며, 각 인자의 주소를 스택에 추가한다. 이 과정을 통해 프로그램 실행 시 필요한 인자들을 스택에 올바르게 준비한다.

 

void argument_stack(char **argv, int argc, struct intr_frame *if_)
{
	char *arg_address[128];

	for(int i = argc - 1; i >= 0; i--)
	{
		int arg_i_len = strlen(argv[i]) +1;
		if_->rsp -= arg_i_len;
		memcpy(if_->rsp, argv[i], arg_i_len);
		arg_address[i] = (char *)if_->rsp;
	}

	if(if_->rsp % 8 != 0)
	{	
		int padding = if_->rsp % 8;
		if_->rsp -= padding;
		memset(if_->rsp, 0, padding);
	}

	if_->rsp -= 8; 	
	memset(if_->rsp, 0, 8);

	for(int i = argc-1; i >= 0; i--)
	{
		if_->rsp -= 8;
		memcpy(if_->rsp, &arg_address[i], 8);
	}

	if_->rsp -= 8;
	memset(if_->rsp, 0, 8);

	if_->R.rdi = argc;
	if_->R.rsi = if_->rsp + 8;
}

 

커맨드라인에서, 여러개의 공백은 하나의 공백과 같게 처리해야 한다. 그러므로 process_exec("grep    foo   bar")는 저 위의 표(원본) 예시와 동일하게 동작해야 한다.

또한, 납득할만한 수준에서 커맨드라인 인자들의 길이 제한을 강요할 수 있다. 예를 들면, 인자들이 한 페이지 크기(4kB) 안에 들어가게끔 제한하는 것 이다. (그리고 직접적인 관계는 없지만, pintos 유틸리티가 커널에 전달할 수 있는 커맨드라인 인자 크기는 128바이트로 제한되어 있다.)

 

<stdio.h>에 선언된 비표준 함수인 hex_dump()는 인자 전달 코드를 디버깅 하는데에 유용할 것이다. argument passing을 잘 했는지 make tests/userprog/args-none.result로 체크를 하면 안된다. write가 구현되어 있지 않기 때문인데, 이 때문에, 파싱이 잘 됐는지 확인하기 위해서는 hex_dump를 써야한다.

 

int write (int fd, const void *buffer, unsigned length)
{
	int byte = 0;
	if(fd == 1)
	{
		putbuf(buffer, length);
		byte = length;
	}
	return byte;
}

아니면 syscall.c 쪽에서 write를 임의로 구현해도 된다.

 

https://github.com/yunsejin/PintOS_RE/tree/project2

 

GitHub - yunsejin/PintOS_RE: PintOS_Project1,2,3

PintOS_Project1,2,3. Contribute to yunsejin/PintOS_RE development by creating an account on GitHub.

github.com

 

728x90

'CS > 운영체제' 카테고리의 다른 글

Pint OS_Project 3 구현  (0) 2024.04.14
Pint OS_Project 2 구현 - 2(system call)  (0) 2024.04.01
Page Replacement Policy  (0) 2024.03.27
Demand-zero page, Anonymous page, File-backed page  (0) 2024.03.27
하이퍼바이저, 애뮬레이션, QEMU  (0) 2024.03.26

+ Recent posts