이번 주차에서는 Argument Parsing, User Memory Access(Check Address), System Call 과제를 수행했다.
이 중에서 이번 글에서는 Argument Parsing에 대해서만 먼저 정리하겠다.
Argument Parsing
Argument Parsing 관련 테스트 코드를 실행시키면 process.c 파일에 있는 process_create_initd() 함수가 호출되는데, 이때 파라미터로 테스트용 명령어(프로그램 이름 + 인자들)이 들어오게 된다. 그러면 process_create_initd() 함수는 이 파라미터를 이용하여 thread_create()라는 함수를 호출하여 스레드를 생성한다. thread_create에는 파라미터로 initd라는 function가 전달되는데 그 함수의 인자로 쓸 전체 명령어 또한 같이 전달된다.

thread_create 함수를 보면 스레드를 생성하면서 trap frame(interrupt frame)의 %rip(다음에 수행할 instruction의 주소를 담고 있는 레지스터)에 kernel_thread를 저장하고 있다. 해당 스레드가 다음에 수행할 동작은 커널 모드에서 수행될 것임을 알 수 있다. 그리고 trap_frame의 R(general register) 중에서 %rdi(첫 번째 인자를 저장하는 레지스터)에는 파라미터로 전달받은 function, 우리의 경우에 initd를 저장하고 있다. %rsi(두 번째 인자를 저장하는 레지스터)에는 initd라는 function의 인자로 사용할 aux, 우리의 경우에 전체 명령어를 저장하고 있다. 이제 thread_create를 통해 생성된 스레드는 커널 모드에서 수행될 것이고, initd라는 함수를 수행할 것이다.
process.c 파일에 있는 initd 함수는 process_exec() 함수를 호출하고 파라미터로 자신이 파라미터로 전달받은 전체 명령어를 process_exec()에게 그대로 전달한다.
process_exec() 함수에서는
1. 명령어에서 프로그램 이름과 각 인자들을 모두 토큰화해서 배열에 저장한다.
2. 사용자 모드에서 사용할 interrupt frame에 ds, cs, eflags 정보를 설정한다.
3. process_cleanup() 함수를 통해 이전에 실행 중이던 스레드의 자원을 정리한다.
4. load() 함수를 호출하여 위에서 파싱한 프로그램 이름을 가진 프로그램을 메모리로 로드하고, 런타임 스택을 준비한다.
5. argument_stack() 함수를 호출하여 런타임 스택에 프로그램 이름, 인자들, 그리고 커널 모드에서의 작업이 끝나고 유저 모드의 작업으로 돌아갈 때 돌아갈 주소(return address)를 push한다.
6. 사용자 모드에서 사용할 interrupt frame의 rdi에 argc를, rsi에 런타임 스택에서 리턴 주소가 들어 있는 칸 바로 위를 가리키는 포인터를 저장해준다.
7. do_iret() 함수를 호출하여 커널 모드에서 유저 모드로 전환한다.
위 단계 중에서 5번째 단계, argument_stack() 부분을 구현하는 것이 주요 과제였다. 우리 조가 짠 코드는 다음과 같다.
void
argument_stack(char **parse, int count, void **rsp) {
char* argv_addr[30]; // array to save addresses of argument strings
int len; // integer to save string legth of each argument string
// 1. push program name
for (int i = count-1; i >= 0; i--) {
len = strlen(parse[i]) + 1; // get string length including NULL(\0)
*rsp -= len; // lower stack pointer
for (int j = 0; j < len; j++) { // copy each character to stack (can replaced by memcpy)
((char *)(*rsp))[j] = parse[i][j];
}
argv_addr[i] = (uintptr_t)(*rsp); // save the address of string to array
}
// 2. push padding for 8 bytes address alignment
size_t padding_size = (uintptr_t)(*rsp) & 0x7;
*rsp -= padding_size;
memset(*rsp, 0, padding_size);
// 3. push NULL pointer which separates argument strings with addresses of them
*rsp -= sizeof(uint8_t*);
*(char **)(*rsp) = 0;
// 4. push addresses of argument strings
for (int i = count - 1; i >= 0; i--) {
*rsp -= sizeof(char *);
*(char **)(*rsp) = (char *)argv_addr[i];
}
// 5. push fake return address
*rsp -= sizeof(void *);
*(void **)(*rsp) = 0;
}
이 코드를 짜면서 스택에 데이터가 정확하게 쌓이지 않는 오류가 발생했다. 오류가 발생했던 주요 원인은 argument_stack에서 **rsp를 void ** 타입으로 받고 있어서 포인터 타입 캐스팅을 정확하게 해주어야 했는데 잘못된 포인터 타입으로 캐스팅해주는 부분이 많았기 때문이었다.
'정글' 카테고리의 다른 글
Nginx (1) | 2024.12.18 |
---|---|
PINTOS 4주차 Stack Growth (0) | 2024.10.22 |
PINTOS 3주차 가상 메모리 관리 시스템 (0) | 2024.10.15 |
PINTOS 1주차 Blocked List, Scheduling (0) | 2024.10.01 |
[WEEK01] 첫 주를 시작하며 (0) | 2024.08.10 |