리눅스 터미널에서 프로그램을 실행할 때, 프로그램 이름과 함께 실행인자(argument)를 넘기는 경우가 있습니다. 이번에는 pintos에서 process를 실행할 때 실행인자를 넘기는 기능을 구현합니다.
pintos에서 프로세스(process)를 실행하면, 프로그램 이름과 실행인자(argument)가 하나의 문자열로 전달됩니다. 따라서 이 문자열을 프로그램 이름과 각 실행인자로 분리해야 합니다. pintos에서 문자열을 분리하는데 사용하는 함수를 제공하는데, strtok, strtok_r 함수가 있습니다.
char *strtok(char *str, const char *delim);
char *strtok_r(char *str, const char *delim, char **saveptr);
두 함수 모두 주어진 문자열(str)에 대해서 delim 값을 기준으로 분리를 해서 분리된 값을 반환하는 작업을 수행합니다. 이때 차이점은 strtok 함수는 내부적으로 분리된 위치를 저장하고 있고, strtok_r 함수는 saveptr 변수로 출력합니다.
strtok, strtok_r 함수를 사용하면 넘겨준 문자열에서 delim 값을 찾으면 그 값을 NUL(\0) 문자로 변경합니다. 만약 "AB CD" 문자열을 공백 문자를 기준으로 분리하게 되면 "AB\0CD"가 됩니다.


pintos에서 프로세스(process)의 메모리 구조입니다. 여기서 "user stack"에 전달받은 실행인자(argument)를 넣어줘야 합니다.
user stack에 넣을 때는 다음과 같은 순서가 있습니다.

그리고 중간에 word-align은 메모리 정렬을 위해 들어있는 값입니다. (4바이트 정렬) 또 복귀 주소는 사용되지는 않지만 user stack의 사용법을 일치시키기 위해서 넣습니다.
/* process.c */
#define USER_STACK (PHYS_BASE - 4)
static bool
setup_stack (void **esp)
{
...
if (kpage != NULL)
{
success = install_page (((uint8_t *) PHYS_BASE) - PGSIZE, kpage, true);
if (success)
*esp = USER_STACK;
else
palloc_free_page (kpage);
}
return success;
}
setup_stack 함수는 스택 포인터(esp)를 설정하는 함수입니다. 이제까지는 user stack을 사용하지 않았기 때문에 스택 포인터 값이 PHYS_BASE로 설정되어 있었습니다. 문제는 이 주소는 다른 용도로 사용하기 때문에 사용할 수가 없습니다. 따라서 스택의 시작 주소를 PHYS_BASE에서 4만큼 감소시킨 주소를 사용합니다. (pintos에서 메모리 주소를 사용할 때는 4의 배수로 사용해야 합니다.)
/* process.c */
tid_t
process_execute (const char *file_name)
{
...
char *args = NULL;
char *process_name = strtok_r ((char*)fn_copy, DELIMITER, &args);
...
}
우선 프로그램을 실행하기 전에 프로그램 이름과 실행인자(argument) 목록을 분리합니다.
/* process.c */
static void
start_process (void *aux)
{
...
int argc = 0;
char *argv[ARG_MAX];
memset (argv, 0, sizeof argv);
argv[argc++] = params->name;
char *token = strtok_r (NULL, DELIMITER, ¶ms->args);
while (token != NULL)
{
argv[argc++] = token;
token = strtok_r (NULL, DELIMITER, ¶ms->args);
}
...
}
그리고 프로세스(process)가 시작하는 과정에서 실행인자(argument) 목록을 분리합니다.
static void
start_process (void *aux)
{
...
/* Push argument value */
for (int i = argc - 1; i >= 0; --i)
{
push_stack (&if_.esp, argv[i], strlen (argv[i]) + 1);
argv[i] = if_.esp;
}
/* Push argument address */
for (int i = argc; i >= 0; --i)
push_stack (&if_.esp, argv + i, sizeof (char*));
/* Push argument vector address */
void *argument_vector = if_.esp;
push_stack (&if_.esp, &argument_vector, sizeof (void*));
/* Push argument count */
push_stack (&if_.esp, &argc, sizeof argc);
/* Push return address */
void *return_address = NULL;
push_stack (&if_.esp, &return_address, sizeof return_address);
...
}
위에서 정의된 순서대로 실행인자(argument)를 넣습니다. 이때 push_stack 함수를 사용합니다.
static void
push_stack (void **stack, const void *value, size_t size)
{
*stack -= size;
/* Aligned by 4 bytes */
*stack -= (uintptr_t)(*stack) % 4;
memcpy (*stack, value, size);
}
memcpy 함수를 사용해서 값을 복사합니다. memcpy 함수는 낮은 주소 -> 높은 주소 방향으로 메모리를 읽어가는데, user stack은 높은 주소 -> 낮은 주소로 메모리를 사용하기 때문에 user stack을 먼저 증가시킨 다음에 memcpy 함수를 사용해야 합니다.
https://thierrysans.me/CSCC69/projects/WWW/pintos_3.html#SEC32