
해당 그림은 Pintos 운영체제에서 프로세스 실행 흐름을 설명하는 다이어그램이다.
1. 명령어 입력 (Test Program Execution)
커맨드 라인에 run echo x 라는 명령어가 입력됩니다.
2. main 함수 (프로세스 생성 시작)
3. run_actions() 함수는 명령줄에서 지정된 작업들을 순차적으로 실행
run 명령어 action 발견하면 2번째 인자 - echo 를 검사하고 run_task
4. run_task() 명령을 실행하는 역할 명령어 배열(argv)의 두 번째 요소를 이용해 실행할 작업을 지정
process_wait(process_create_initd(task)); 호출
5. tid_t process_execute()
process_create_initd(task) > thread_create > initd > process_exec
6. process_exec(void *f_name)
현재 실행중인 프로세스를 주어진 실행 파일로 교체하여 새로운 프로그램을 실행하는 역할을 기존 프로세스의 메모리 공간을 정리하고, 새로운 실행파일을 메모리에 로드한 후, 필요한 인자들을 스택에 설정
7. do_iret()을 통해 유저 모드로 전환 스택에 저장된 인자들이 argc와 argv 형태로 main() 함수에 전달
process_exec()에서 프로세스가 완전 교체되었고, 로드된 프로그램의 진입점이 main함수로 지정되었기 때문에 새로운 프로그램은 main함수 실행 시 명령줄 인자를 사용해 원하는 동작
여기서 스택에 인자를 저장하는 과정이 없기 때문에 추가해줘야한다.
명령어를 받아서 스택에 넣어주는 방법은 다음과 같다.
명령줄에 입력된 파일 이름이랑 인자들을 파싱해서 각 단어를 분리해야한다.
strtok_r은 공백을 기준으로 파싱하고, 각 토큰은 parse 배열에 저장한다.
char *parse[64];
char *token, *save_ptr;
int count = 0;
for (token = strtok_r(file_name, " ", &save_ptr); token != NULL; token = strtok_r(NULL, " ", &save_ptr))
parse[count++] = token;
명령줄 인자를 유저 스택에 삽입하는 로직입니다.
스택은 아래와 같이 저장되어야한다.
rsp (스택포인터)는 아래로 확장한다.
따라서 rsp의 포인터를 감소시켜주고 데이터를 넣어줘야한다.
1. 처음에는 명령어 인자들을 넣어준다.
2. 다음은 명령어 인자들의 word-align을 위해 패딩을 넣어준다.
현재 64비트 운영체제이기 때문에 8바이트를 맞춰주는 로직이 필요하다,
3. 빈문자열을 삽입한다.
4. 각 인자들이 저장된 주소를 넣어준다.
5. return 값을 넣어준다.
| 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 ()() |
argument_stack(parse, count, &_if.rsp);
_if.R.rdi = count;
_if.R.rsi = (char *)_if.rsp + 8;
void argument_stack(char **parse, int count, void **rsp) // 주소를 전달받았으므로 이중 포인터 사용
{
// 프로그램 이름, 인자 문자열 push
for (int i = count - 1; i > -1; i--)
{
for (int j = strlen(parse[i]); j > -1; j--)
{
(*rsp)--; // 스택 주소 감소
**(char **)rsp = parse[i][j]; // 주소에 문자 저장
}
parse[i] = *(char **)rsp; // parse[i]에 현재 rsp의 값 저장해둠(지금 저장한 인자가 시작하는 주소값)
}
스택 포인터의 현재 주소가 8바이트 정렬이 되도록 패딩을 추가
64비트 환경이기 때문에 8바이트 정렬
// 정렬 패딩 push
int padding = (int)*rsp % 8;
for (int i = 0; i < padding; i++)
{
(*rsp)--;
**(uint8_t **)rsp = 0; // rsp 직전까지 값 채움
}
// 주소 넣어주기
// 인자 문자열 종료를 나타내는 0 push
(*rsp) -= 8;
**(char ***)rsp = 0; // char* 타입의 0 추가
// 각 인자 문자열의 주소 push
for (int i = count - 1; i > -1; i--)
{
(*rsp) -= 8; // 다음 주소로 이동
**(char ***)rsp = parse[i]; // char* 타입의 주소 추가
}
return 주소를 갱신
여기선 초기화이기 때문에 0으로 설정
// return address push
(*rsp) -= 8;
**(void ***)rsp = 0; // void* 타입의 0 추가
}