
현재 process.c의 int process_exec() 함수에서는
인자로 f_name을 받는다. 하지만 이 f_name은 user가 입력한 command line 전체가 들어가 있는데 이를 프로그램 이름과 인자로 나누어서 함수로 보내야 한다.

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 ();
}
char *file_name = f_name;
위의 인자는void 형인데 처리해야 하는 command line은 문자열이니 문자열 포인터로 형을 변경한다.
struct intr_frame _if;
intr_frame 구조체 멤버에 필요한 정보를 담는다. 여기서 intr_frame은 인터럽트 스택 프레임이다.
process_cleanup ();
새로운 실행 파일을 현재 스레드에 담기 전에 먼저 현재 process에 담긴 context를 지워준다. 이때 지운다는 말은 현재 프로세스에 할당된 page directory를 지운다는 뜻이다.
success = load (file_name, &_if);
file_name, _if를 현재 프로세스에 load한다. success는 bool type이니 load에 성공하면 1, 실패하면 0을 return한다.
palloc_free_page (file_name);
file_name은 프로그램 파일을 받기 위해 만든 임시 변수이다. load가 끝나면 메모리를 반환한다.
page의 할당은 위의 load 함수 내의 setup_stack 함수 내부에서 이루어진다. 여기서 할당한 페이지를 해제하기 위한 함수이다.
우선 file_name을 parse 하기 전에 원본을 남겨놓는다.
int
process_exec (void *f_name) {
char *file_name = f_name;
bool success;
//project 2. argument passing
//original file_name copy
char file_name_copy[128];
//+1은 \n이 들어갈 자리
memcpy(file_name_copy, file_name, strlen(file_name) + 1);
//project 2. argument passing
...
pintos에서 커널에 입력할 수 있는 문자열의 길이가 128 바이트라고 자료에 나와있었으므로 원본을 복사할 배열의 크기를 128 바이트로 정하고 원본 문자열을 memcpy로 복사했다. strlen에 1을 더하는 이유는 널 문자(\0) 때문.
본격적인 parsing은 load 함수 내에서 실행된다.
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;
//project 2. argument passing
uint64_t len_include_args = strlen(file_name) + 1;
char *argv[128];
memset(argv, 0, sizeof(argv));
char *save_ptr = NULL;
//첫번째 인자 = 프로그램 이름
char *token = strtok_r(file_name, " ", &save_ptr);
uint64_t argc = 0;
while (token != NULL) {
argv[argc++] = token;
token = strtok_r (NULL, " ", &save_ptr);
}
ASSERT (argc > 0);
ASSERT (argv[argc] == NULL);
//project 2. argument passing
/* Allocate and activate page directory. */
t->pml4 = pml4_create ();
if (t->pml4 == NULL)
goto done;
process_activate (thread_current ());
문자열의 parsing에는 strtok_r함수를 사용하면 된다.
이 함수는 lib/string.c에 선언되어 있다.
함수 위에 (영어로 된) 주석이 상세하게 설명되어 있는데 내 나름대로 간단하게 이해한 것이 다음과 같다.
char *s로 입력받은 문자열을
const char *delimiters (여기서는 " " 공백)이 나오면
이를 NULL로 치환하고 save_ptr를 넘기고,
아닌게 나오면 char *token 이라는 포인터에 save_ptr을 옮긴다.
token에 들어갈 문자열을 찾았다면 다음 공백이 나올때 까지 save_ptr을 옮긴다.
이를 널문자(\0)가 나올 때 까지 반복한다.
이와 같은 과정을 통해서 strtok_r 함수를 반복하면 *token이라는 변수 안에 공백으로 구분되는 단어가 하나씩 들어가게 된다.
이후 인자를 몇개나 가지고 있는지를 파악하기 위해서 반복문을 돌려 argc의 값을 알아낸다.
테스트를 통과하기 위해선 argv[argc]값, 즉 배열 argv에 인자를 다 담고 난 후의 원소가 NULL이어야 하는데 (배열이 끝났음을 알아내는 방법) 이는 char *argv[128]; 로 argv 배열을 선언하고 memset(argv, 0, sizeof(argv));으로 해당 배열을 전부 0으로 초기화 해버렸기 때문에 자연스럽게 이루어진다.
/* TODO: Your code goes here.
* TODO: Implement argument passing (see project2/argument_passing.html). */
//project 2. argument passing
//USER_STACK = 0x47480000
// stack 공간확보 -> rsp를 아래로 이동 (uint64_t 이므로 -1당 1씩 빠진다)
if_->rsp = (uintptr_t)((uint64_t) if_->rsp - ROUND_UP(len_include_args, 8));
/* Put argv[i][...] into stack */
uint64_t write_point = if_->rsp;
for (int i = 0; i < argc; i++) {
uint64_t argv_len = strlen(argv[i]) + 1;
memcpy((void *) write_point, (void *) argv[i], argv_len);
argv[i] = write_point; // stack에서의 argv[i]가 저장된 주소를 argv[i]에 저장
// printf("argv[%d] place in 0x%p\n", i, argv[i]);
write_point += argv_len; // 다음 복사위치로 이동
}
/* Put argv[i] into stack */
// 현재 argv[i] 에는 stack에서의 인자가 저장된 주소를 가지고 있다. ex) %rsp + 11
for (int i = argc; i >= 0; i--) {
if_->rsp -= sizeof(uint64_t);
memcpy((void *) if_->rsp, (void *) &argv[i], sizeof(uint64_t));
// char *temp = argv[i];
// __asm __volatile(
// /* Fetch input once */
// "movq %0, %%rax\n"
// "movq %1, %%rcx\n"
// "movq %%rcx, (%%rax)\n"
// : : "g"(if_->rsp), "g"(temp) : "memory");
// printf("rsp: %p, ptr: %p, value: %p, string: %s\n", if_->rsp, &argv[i], argv[i], argv[i]);
}
/* Put return address (0) */
if_->rsp -= sizeof(uint64_t);
memset((void *) if_->rsp, 0, sizeof(uint64_t));
/* Put argc, argv into register */
// %rdi: argc %rsi: &argv[0]
if_->R.rdi = argc;
if_->R.rsi = (uint64_t) (if_->rsp + sizeof(uint64_t));
// hex_dump(if_->rsp, if_->rsp, USER_STACK - if_->rsp, true);
//project 2. argument passing
success = true;
이후 스택에 데이터를

이런 모양으로 쌓을 수 있도록 저장하면 된다.
위의 코드는 데이터와 패딩의 위치가 그림과 반대지만 어쨌든 바이트에 맞게 정렬은 된 것이다.
주의해야 할 점은 전부 다 쌓은 후 R -> rsi가 위의 표의 return address 위치가 아니라 argv[0]의 위치를 가리키게 해야 한다는 것이다.
데이터를 잘 쌓았는지 터미널로 확인하려면 hex_dump라는 함수를 통해서 확인할 수 있다.
잘 돌아가는지 테스트를 하기 위해선
root에서 source ./activate
userprog 디렉토리에서 make
buidl 디렉토리에서
# -v: no vga, -k: kill-on-failure, --fs-disk: 임시 디스크 생성, -p: put, -g: get // -f: format
pintos -v -k --fs-disk=10 -p tests/userprog/args-single:args-single -- -q -f run 'args-single onearg'
같이 명령어를 입력하면 되는데, 현재 핀토스의 프로세스 흐름상 뭐가 돌아가기도 전에 프로세스가 종료되게 된다. 이를 임시로 방지하기 위해선 process_wait함수 위에 주석이 달려있는 것 처럼 process_wait 함수가 끝나는 걸 막기 위해 무한루프를 걸어놓으면 된다.
이후 system call을 구현하는 과정에서 wait를 제대로 동작하게 짜면 이런 일은 발생하지 않을것이다.
/* Waits for thread TID to die and returns its exit status. If
* it was terminated by the kernel (i.e. killed due to an
* exception), returns -1. If TID is invalid or if it was not a
* child of the calling process, or if process_wait() has already
* been successfully called for the given TID, returns -1
* immediately, without waiting.
*
* This function will be implemented in problem 2-2. For now, it
* does nothing. */
int
process_wait (tid_t child_tid UNUSED) {
/* XXX: Hint) The pintos exit if process_wait (initd), we recommend you
* XXX: to add infinite loop here before
* XXX: implementing the process_wait. */
/* --- Project 2: Command_line_parsing ---*/
while (1) {
}
/* --- Project 2: Command_line_parsing ---*/
return -1;
}