/* tokenize the 'string' with a deliminator " ",
   returns how many arguments in 'string' */
int get_argument_count(const char * string)
{
  char * new_string = (char *)malloc(sizeof(char)*(strlen(string)+1));
  strlcpy(new_string, string, strlen(string)+1);

  char *token, *save_ptr;
  int i=0;
  for(token=strtok_r(new_string, " ", &save_ptr); token;
      token=strtok_r(NULL, " ", &save_ptr))
  {
    i++;
  }
  free(new_string);
  return i;
}

/* tokenize the 'parse' with a deliminator " ",
   each arguments will be saved into the 'esp'. */
void argument_stack(char ** parse, int count, void ** esp)
{
/* parse = sentence including 'process and arguments'
   count = argc(argument counts) through 'GetArgumentCount' func.
   esp = &if_.esp */

  char * command_line = *parse; // like 'echo x y z'

  char * fn_copy;
  fn_copy = palloc_get_page(PAL_USER);
  if(fn_copy == NULL)
    thread_exit();
  strlcpy(fn_copy, command_line, PGSIZE);

  char * token, * save_ptr;
  int i=0;
  int * variable_index = (int *)malloc(sizeof(int)*count);

  for(token = strtok_r(fn_copy, " ", &save_ptr); token;
      token = strtok_r(NULL, " ", &save_ptr))
  {
    printf("'%s'\n", token);
    variable_index[i++] = token - fn_copy;
  }

  int size = strlen((char*)(*parse));

  *esp -= size + 1;
  int command_position = (int)*esp; // stack pointer position for command line

  for(i=0; i<=size; i++)
  {
    *(char*)(*esp) = fn_copy[i];
    *esp += 1;
  }
  *esp = (int)command_position;

  *esp -= ((int)(*esp)%4<0? (int)(*esp)%4+4 : (int)(*esp)%4);
  *esp -= 4;
  *(int*)(*esp) = 0;

  // argv[1,2,3,...]: arguments
  for(i=count-1; i>=0; i--)
  {
    *esp -= 4;
    *(void**)(*esp) = command_position + variable_index[i];
  }

  // argv[0]: process_name
  *esp -= 4;
  *(char **)(*esp) = (*esp + 4);
  // argc: argument count
  *esp -= 4;
  *(int *)(*esp) = count;
  // return address - fake (0)
  *esp -= 4;
  *(int*)(*esp) = 0;

  free(variable_index);
}


pintos 는 현재, 프로그램이름과 인자를 구분하는 규칙이 존재하지 않는다.

ls -l // 핀토스는 'ls -l' 을 하나의 프로그램 명으로 인식하고 있다. 


첫 번째 과제의 목표는 프로그램의 이름과 인자를 구분하여 스택에 저장, 인자를 응용 프로그램에 전달하는 기능을 구현하는 것이다.


이를 위해 소규모 목표를 잡아 보면,

1. 응용프로그램의 실행 흐름을 추적하여 파싱 시점을 알아내야 한다.

2. 파싱을 어떻게 할 것인가?

3. 파싱한 인자들을 전달하는 인자 전달 메커니즘을 이해하고, 인자를 전달하는 인터페이스를 구현한다.




| 1. 응용프로그램의 실행 흐름을 추적하여 파싱 시점을 알아내야 한다.


< pintos 의 응용 프로그램 실행 메커니즘 >

운영체제의 main 함수에서 run_action 함수 호출

-> run_task 함수 호출

-> process_execute 함수 호출

-> thread_create 함수와 start_process 함수 호출

...


이러한 과정을 지나치며 응용 프로그램이 시작된다. 


run_task에서 start_process 함수까지 진행이 되는 동안 함수들의 인자는 char ** argv 가 전달되는데, 이는 커맨드 라인 전체를 포함하고 있다. (예를 들어, 'ls -a'를 pintos 시작 시 받은 경우, argv 를 'ls -a' 전체를 받는 것이다.)

따라서, 이를 파싱해야 할 시점을 찾을 수 있다. 바로, start_process 에서 load 함수를 호출하는 그 때, 문자열을 파싱하여 넘겨주어야 한다.





| 2. 파싱을 어떻게 할 것인가?


start_process 함수에서 load 함수를 호출하기 전에 문자열을 파싱해야 한다. 문자열 파싱 함수로는 strtok_r 을 쓰도록 하자.


char *strtok_r (char *s, const char *delimiters, char **save_ptr) /* string.h */ 


example)

char s[] = "String to tokenize.";
char *token, *save_ptr;
for (token = strtok_r (s, " ", &save_ptr); token != NULL;

     token = strtok_r (NULL, " ", &save_ptr))

     printf ("'%s'\n", token); 






| 3. 파싱한 인자들을 전달하는 인자 전달 메커니즘을 이해하고, 인자를 전달하는 인터페이스를 구현한다.


유저 스택에 파싱된 토큰들을 저장하는 함수를 구현해야 한다.


void argument_stack(char **parse ,int count ,void **esp);

parse: 프로그램 이름과 인자가 저장되어 있는 메모리 공간

count: 인자의 개수

esp: 스택 포인터를 가리키는 주소


이 함수는 parse에서 커맨드 라인을 읽어와 파싱하고, esp에 알맞게 저장하는 함수이다. 


이 함수가 start_process 함수에서 호출될 때는 다음과 같은 꼴로 사용해야 한다.


argument_stack(&parse, count, &if_.esp);


즉, 첫 번째와 세 번째 인자는 주소값으로 인자를 전달한다는 가정 하에 다음 설명을 진행하겠다.


pintos에서는 스택이 아래로 자라는 경향이 있다.

제일 처음에는 *esp 값이 0xc0000000(유저스택의 최고점, 3GB)일 것이다.


*esp -= 4; 


이렇게 하면, esp 값은 0xbffffffc가 될 것이다. 이 때,


**esp = 10;  // 정확하게는, *(int *)(* esp) = 10; (캐스팅을 제대로 해야 한다.)


이렇게 되면, 0xbffffffc 라는 유저 스택 위치에서, 10이라는 값이 저장되는 것이다. 

이러한 논리를 이용해서 프로젝트를 진행할 수 있을 것이다. 




| 결과 확인


첫 번째 프로젝트를 제대로 완료했다면, 다음 명령어를 입력해보자.


$ pintos -v -- run 'echo x y z'


참고로, 결과를 확인하기 전에는 반드시 핀토스 파일 시스템 안에 'echo'라는 응용 프로그램이 들어가 있어야 한다.


+ Recent posts