[SWJungle][PintOS] Project 2 - User Programs

d·2023년 5월 3일
0

Argument Passing


Process_exec()

유저 프로그램을 실행하기 전에, 커널은 레지스터에다가 맨 처음 function의 argument를 저장해야 된다. process_exec()은 유저가 입력한 명령어를 수행 할 수 있도록 process를 메모리에 적재하고 실행하는 함수다. 해당 프로그램은 f_name에 문자열로 저장되어 있으나 현재 상태에서 process_exec()은 새로운 프로세스에 대한 인자 전달을 제대로 해주지 않는다. process_exec()에 코드를 추가해서 프로그램 파일 이름을 인자로 넣는것 대신에, 공백이 올 때 마다 단어를 파싱 하도록 만들어야 된다.
현재 실행중인 스레드의 context를 저장하고 context switching 하는 것이 process_exec()의 역할이다. 우리가 입력하는 명령을 받기 전에 어떤 스레드가 실행중이었을 테니, process_exec()에 context switching 역할도 같이 넣어줘야 한다.

struct intr_frame

인터럽트 프레임은 인터럽트가 들어왔을 때, 이전에 레지스터에 작업하던 context를 switch 하기 위해 정보를 담아놓는 구조체.
intr_frame 에 가 보면 멤버 구조체 gp_registers R 을 들고 있다. 이 R은 기존 스레드가 작업하고 있을 때의 레지스터 값을 인터럽트가 들어오면 switching 하기 위해 정보를 담는다.

load ()

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;
}

argument_stack()

인자값을 스택에 올려주는 함수를 구현해야 한다. 파싱한 문자들 배열(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 문에서 스택에 담을 각 인자의 주소값을 저장하는 배열이다.

  • for문을 돌면서 load() 에서 넘어온 arglist 인자들을 하나씩 꺼내서 if→rsp 에 넣어준다. if_→rsp 는 user stack 에서 현재 위치를 가리키는 스택 포인터이자 인터럽트 프레임 멤버다. 각 인자 크기를 읽는데 각 인자마다 실제로는 \n 이 포함되어있어 argv_len + 1 을 해 준다. 그리고 while문을 돌면서 패딩을 삽입해준다. SAU == Stack pointer Alignment Unit.
  • arg_address[128] 배열에 있는 주소값을 for문을 돌면서 넣어준다.
  • fake address를 넣어주고 해당 영역의 메모리는 0으로 초기화해준다.
  • 인터럽트 프레임 if_의 멤버로 있는 레지스터 구조체의 rdi 에 인자 count 값인 argc를, rsi에는 fake address 바로 위인 arg_address의 맨 앞을 가리키는 주소값을 넣어준다.

0개의 댓글