[TIL] [WEEK9-10] Pintos Project(2) Argument Passing

woo__j·2024년 6월 2일
0

Pintos Project

목록 보기
4/14

3주간 진행했던 핀토스 프로젝트 1,2를 마치고 3이 시작됐다.
좀 더 미리 업로드 했어야 하는데 발제하는 날 새벽까지 과제를 붙잡고 있느라 회고가 늦어졌다.
사실 작성하게 된 현재 시점까지도 완벽히 이해했다고는 할 수 없긴 하다.. (〃⌒▽⌒〃)ゝ

프로젝트 2의 한마디 후기는... 정말 정말 어려웠고 힘들었다.
전체적인 그림이 그려지지 않아서 더 방황했던 것 같다. 길이 보이지 않는 느낌?

나중에 이 글을 보게 될 후배 기수분들이 호오옥시라도 계신다면... 조급해하지 말고 본인만의 속도를 가지셨으면 좋겠습니다.
남들의 속도를 따라가려고 하다보면 본인이 얻을 수 있는, 얻고 싶은 것들을 놓치게 되는 것 같아요. 사람마다 다르게 생각할 수 있겠지만요.

핀토스 프로젝트의 목적은 OS와 관련된 여러 핵심 기능/개념들을 실습해봄으로써 이해하는 것이 가장 큰 목표라고 생각하는데, 어느 순간부터는 '테스트 케이스 통과'에 집착하게 되는 것 같았다.
아무래도 숫자가 명시되어 있어 더 그런 것 같다. 줄어들고 늘어나는 숫자에 갇히게 된 느낌이었다.
그래서 이해를 바탕으로 된 구현을 하지 않게 되었고, 프로젝트가 끝난 후엔 내게 남은 게 없는 것 같아서 회의감이 들었다.

이 생각을 바탕으로 프로젝트 3에서는 팀원들과 소통을 많이 하면서 내가 무엇을 하는지, OS 기능의 흐름이 어떻게 되는지 전체적인 그림을 좀 그려가며 이해를 바탕으로 구현에 들어가고자 한다.

아무튼 잡담은 이만하고, 프로젝트 2 회고는

📍첫 번째 과제, Argument Passing (인자 전달)

현재의 PintOS는 명령어를 받아 프로그램을 실행할 때, 새로운 프로세스에 인자를 전달하는 것을 지원하지 않는다. 즉 프로그램 명과 인자를 나누지 않고 하나로 인식하고 있다.

이를 수정해 입력받은 문자열을 공백을 기준으로 나눠지게 한 후(parsing), 첫 번째 단어는 실행할 프로그램 이름, 나머지는 인자로 user stack에 저장하고 전달하도록 구현하는 것이 과제 목표다.

🛠️ Modify List

tid_t process_create_initd(const char *file_name)
: 프로그램을 실행할 프로세스를 생성하는 함수

  • 인자로 받은 file_name 문자열을 공백 기준으로 parsing해, 새 프로세스의 이름으로 첫 번째 토큰을 thread_create()로 전달하도록 수정

int process_exec(void *file_name)
: 프로그램을 메모리에 적재하고 응용 프로그램 실행하는 함수

  • 인자로 받은 file_name 문자열을 공백 기준으로 parsing한 후, 토큰들을 모두 담은 argvs[ ] 생성
  • load에는 실행파일명만 넘겨주고, 실행파일명+나머지 인자들은 user stack에 쌓아주도록(argument_stack()) 수정

void argument_stack(char argvs, int argc, struct intrframe *if)**
: 함수 호출 규약에 따라 user stack에 parsing된 토큰들을 저장하는 함수

  • argvs: process_exec에서 파싱해서 담았던 ‘프로그램 이름+인자들’이 들어있는 메모리 공간
  • argc: 인자의 총 개수
  • if_: 인터럽트 프레임

    [함수 호출 규약]
    : 함수 호출 시 인자 값은 ‘오른쪽’에서 ‘왼쪽’ 순서로 stack에 저장한다.
    return address는 함수를 호출하는 부분의 다음 수행 명령어의 주소, 호출 받은 함수의 반환값은 rax register에 저장

    스택의 상태 예시 -> /bin/ls -l foo bar 명령이 주어졌을 때)
    스택은 위에서 아래로 쌓이는 사실에 주의하자*
  • argv[i][...]: 인자값들을 오른쪽->왼쪽 순으로 stack에 쌓기
  • word-align: word 크기에 정렬된 접근이 정렬되지 않은 접근보다 빠르므로, 최고의 성능을 위해서는 스택에 첫 푸시가 발생하기 전에 스택포인터를 8의 배수로 반올림해 맞추기 위해 0을 쌓는다.
  • 널포인터 경계: argv[argc], 여기선 argv[4] = 이것은 인자가 끝났다는 경계선일까? 그렇게 생각하기로…
  • argv[i]: 처음에 삽입했던 인자들의 주소 쌓기
  • return address(fake address): 함수를 호출하는 부분의 다음 수행 명령어 주소를 저장하는 부분이다. 그런데 우리는 유저 프로그램을 실행하기 위한 준비 단계이므로 돌아올 곳을 표기하지 않는다. 그래서 fake로 0을 삽입한다.
  • 레지스터에 삽입: 마지막으로 %rsi를 argv가 시작하는 주소(argv[0]의 주소)로, %rdi는 argc(인자 개수)로 설정한다.
위 과정들은 stack pointer(intr_frame->rsp)를 옮겨가며 해당 위치에 저장하면 된다.

Parsing을 어떻게 구현해야 될까? 🤔

이 고민은 우리가 하지 않아도 된다. Pintos에선 이미 Parsing하는 함수를 제공하고 있다.
이럴 때 보면 Pintos는 친절한 것 같으면서도... 불친절하다.
아무튼 lib/string.c를 보면 strtok_r()이 보인다.

char *
strtok_r (char *s, const char *delimiters, char **save_ptr) {
	char *token;

	ASSERT (delimiters != NULL);
	ASSERT (save_ptr != NULL);

	if (s == NULL)
		s = *save_ptr;
	ASSERT (s != NULL);

	while (strchr (delimiters, *s) != NULL) {
		if (*s == '\0') {
			*save_ptr = s;
			return NULL;
		}

		s++;
	}

	token = s;
	while (strchr (delimiters, *s) == NULL)
		s++;
	if (*s != '\0') {
		*s = '\0';
		*save_ptr = s + 1;
	} else
		*save_ptr = s;
	return token;
}

이 함수는 두 번째 인자(delimiters)를 기준으로 string을 쪼개준다.

나는 process_exec()에서 printf문으로 디버깅을 해보다가 strtok_r를 실행한 뒤, 원본 file_name에 실행명만 담기게 되는 사실을 발견했다.

예를 들어 file_name으로 ‘echo x y z’를 넣은 후 while 문으로 strtok_r를 계속 해도, 원본은 file_name은 처음 쪼개진 ‘echo’를 유지하게 된다.

그 이유는 strtok_r은 원본 문자열을 ‘변경’해서 delimiters를 '\0'(=sentinel)으로 대체해버린다.
c언어에서 문자열을 읽어올 때 sentinel을 만날 때까지 읽어오기 때문에, 원본이 첫 token 문자로 변경된다.
그래서 따로 첫 token을 담을 변수를 만들어 저장하지 않고 넘겨주어도 되었다.

⚙️ Test 수행 방법

이를 알기 위해선 Pintos의 실행 흐름을 먼저 알아야 한다.

그림을 보면 프로세스 생성을 시작하고 프로세스의 종료를 대기해야 한다.
우리가 구현한 argument passing은 프로세스의 생성 부분인데, 지금 대기하는 부분이 구현되어 있지 않아 테스트를 해볼 수 없다. 프로세스 종료를 대기하는 process_wait()을 살펴보면 return -1로 바로 반환되어 버리고 있다.
그러니 테스트를 위해 임시로 for문 또는 while문을 작성해놓자.
(맥북 vm 웨어 환경에서는 10억번 정도가 적당했다. 이는 성능에 따라 적당한 숫자를 넣어야하는 듯 하다)

또 process_exec()에 hex_dump()를 넣어주자.
이는 메모리 내용을 출력해주는 함수로, 인자 전달 코드를 디버깅 하는데에 유용할 것이라고 git-book에 명시되어 있다.

pintos --fs-disk=10 -p tests/userprog/args-single:args-single -- -q -f run 'args-single onearg'

앞서 말한 내용을 처리해준 후, 해당 명령어를 돌렸을 때 위와 같은 결과가 나온다면 올바르게 parsing 처리를 해준 것이다.

argument passing 과제를 하더라도 test-case 통과는 아직 아무것도 할 수 없다. 아직 시스템 콜을 구현하지 않았기 때문에 테스트 결과와 대조할 수 없을 것이다.
시스템 콜 핸들러와 파일 관련 시스템 콜 + 파일 디스크립터 부분까지 구현 완료해야 그 때부터 테스트 통과를 확인해볼 수 있었던 것 같다.

나중에 make check를 돌릴 때, 본인이 디버깅하기 위해 추가했던 printf들과 hex_dump를 지우는 걸 잊지 말자. 해당 부분을 지우지 않으면 fail이다.

0개의 댓글

관련 채용 정보