유저 프로그램을 실행하기 전에, 커널은 레지스터에다가 맨 처음 function의 argument를 저장해야 된다. process_exec()
은 유저가 입력한 명령어를 수행 할 수 있도록 process를 메모리에 적재하고 실행하는 함수다. 해당 프로그램은 f_name
에 문자열로 저장되어 있으나 현재 상태에서 process_exec()
은 새로운 프로세스에 대한 인자 전달을 제대로 해주지 않는다. process_exec()
에 코드를 추가해서 프로그램 파일 이름을 인자로 넣는것 대신에, 공백이 올 때 마다 단어를 파싱 하도록 만들어야 된다.
현재 실행중인 스레드의 context를 저장하고 context switching 하는 것이 process_exec()
의 역할이다. 우리가 입력하는 명령을 받기 전에 어떤 스레드가 실행중이었을 테니, process_exec()
에 context switching 역할도 같이 넣어줘야 한다.
인터럽트 프레임은 인터럽트가 들어왔을 때, 이전에 레지스터에 작업하던 context를 switch 하기 위해 정보를 담아놓는 구조체.
intr_frame
에 가 보면 멤버 구조체 gp_registers R
을 들고 있다. 이 R은 기존 스레드가 작업하고 있을 때의 레지스터 값을 인터럽트가 들어오면 switching 하기 위해 정보를 담는다.
load()
는 실행파일의 file name을 적재해 실행하는 함수다. load()
를 부른 caller인 process_exec()
에서 입력한 커맨드 전체가 file_name 인자로 들어오면 strtok_r()
을 이용해 파싱하고, arg_list에 넣어준다. strtok_r()
함수는 특정한 delimiter를 기준으로 문자열을 파싱해준다.
char * strtok_r (char *s, const char *delimiters, char **save_ptr) {
char *token;
ASSERT (delimiters != NULL);
ASSERT (save_ptr != NULL);
/* If S is nonnull, start from it.
If S is null, start from saved position. */
if (s == NULL)
s = *save_ptr;
ASSERT (s != NULL);
/* Skip any DELIMITERS at our current position. */
while (strchr (delimiters, *s) != NULL) {
/* strchr() will always return nonnull if we're searching
for a null byte, because every string contains a null
byte (at the end). */
if (*s == '\0') {
*save_ptr = s;
return NULL;
}
s++;
}
/* Skip any non-DELIMITERS up to the end of the string. */
token = s;
while (strchr (delimiters, *s) == NULL)
s++;
if (*s != '\0') {
*s = '\0';
*save_ptr = s + 1;
} else
*save_ptr = s;
return token;
}
인자값을 스택에 올려주는 함수를 구현해야 한다. 파싱한 문자들 배열(arg_list, argv)과 그들의 갯수값 (token_count, argc)와 인터럽트 프레임(_if)을 인자로 넘겨준다. 이 함수 자체에서 인터럽트 프레임을 스택에 올리는 게 아니고, 인터럽트 프레임 내 구조체 중 rsp
에 인자를 넣어준다.
void argument_stack(char **argv, int argc, struct intr_frame *if_){
char *arg_address[128];
// 1. 스택 공간 확보 및 복사
for(int i = argc - 1; i >= 0; i--){
int argv_len = strlen(argv[i]) + 1;
if_->rsp -= (argv_len);
memcpy(if_->rsp, argv[i], argv_len);
arg_address[i] = if_->rsp; // arg_address에 인자를 복사한 시작 주소값을 저장해준다
}
// 2. Stack Pointer의 word-alignment 단위는 8byte 단위이다.
while(if_->rsp % SAU != 0){
if_->rsp--;
memset(if_->rsp, 0, sizeof(uint8_t));
}
// 3. word-align 이후 ~argv[0]의 주소를 넣어준다
for(int j = argc; j >= 0; j--){
if_->rsp -= SAU;
if(j == argc){
memset(if_->rsp, 0, sizeof(char **));
} else{
memcpy(if_->rsp, &arg_address[j], sizeof(char **));
}
}
// 4. Fake return address
if_->rsp -= sizeof(void *);
memset(if_->rsp, 0, sizeof(void *));
// 5. rdi, rsi 설정
if_->R.rdi = argc;
if_->R.rsi = if_->rsp + SAU;
}
배열 arg_address[128]
는 for 문에서 스택에 담을 각 인자의 주소값을 저장하는 배열이다.
load()
에서 넘어온 arglist 인자들을 하나씩 꺼내서 if→rsp 에 넣어준다. if_→rsp
는 user stack 에서 현재 위치를 가리키는 스택 포인터이자 인터럽트 프레임 멤버다. 각 인자 크기를 읽는데 각 인자마다 실제로는 \n
이 포함되어있어 argv_len + 1
을 해 준다. 그리고 while문을 돌면서 패딩을 삽입해준다. SAU == Stack pointer Alignment Unit.arg_address[128]
배열에 있는 주소값을 for문을 돌면서 넣어준다.rdi
에 인자 count 값인 argc를, rsi
에는 fake address 바로 위인 arg_address의 맨 앞을 가리키는 주소값을 넣어준다.