본문 바로가기
TIL (Today I Learned)/컴퓨터 시스템(CS)

[CS] Kaist PintOS User Programs, Passing the arguments #4

by 둥굴프 2022. 12. 23.
이 문서는 카이스트 핀토스 원유집 교수님의 강의를 참고하여 작성하였습니다.
이 문서는 카이스트 핀토스 프로젝트 가이드라인을 정리한 글입니다.

 

Part 2 : User Program

현재 핀토스는 명령어 전체 한 줄을 통째로 읽는다.

 

'echo x y z'

thread name : 'echo x y z'

find program with file name : 'echo x y z'

arguments : 'echo', 'x', 'y', and 'z' are not passed

 

각각의 인자가 구분되도록 수정해야한다.

thread name : 'echo'

find program with file name : 'echo'

pushl the arguments(x, y, z) to user stack.

 

수정해야 하는 파일 : process.*

 

현재 PintOS의 process_create_initd함수에는 인수(arguments)들로 user stack을 초기화하는 메커니즘이 없다.

이번에 구현해야 할 숙제는 위의 메커니즘이다.

 

구현할 것은 다음과 같다.

1. 명령을 /bin/ls, -l, foo, bar와 같이 단어로 구분한다.

2. 단어를 스택 맨 위에 놓는다. 포인터를 통해 참조되기 때문에 순서는 중요하지 않다.

3. 오른쪽에서 왼쪽 순서로 스택에서 각 문자열과 널 포인터 센티널의 주소를 푸시한다. 이들은 argv의 요소다. null 포인터 센티널은 argv[argc]가 C 표준에서 요구하는 대로 null 포인터임을 확인한다. 이 순서는 argv[0]이 가장 낮은 가상 주소에 있도록 한다. 단어 정렬 액세스는 정렬되지 않은 액세스보다 빠르므로 최상의 성능을 위해 첫 번째 푸시 전에 스택 포인터를 8의 배수로 반올림한다. 👉 8의 배수를 만들기 위해서 아래 표에서 word-align이라는 패딩값을 넣어준다.

4. %rsi를 argv(argv[0]의 주소)로 가리키고 %rdi를 argc로 설정한다.

5. 마지막으로 가짜 "반환 주소"를 푸시한다. 진입 함수는 절대 반환되지 않지만 스택 프레임은 다른 것과 동일한 구조를 가져야 한다.

 

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 (*) ()
RDI: 4 | RSI: 0x4747ffc0

 

이 예에서 스택 포인터는 0x4747ffb8로 초기화된다. 위에 표시된 대로 코드는 include/threads/vaddr.h에 정의된 USER_STACK에서 스택을 시작해야 한다.

 

인수 전달 코드를 디버깅하는 데 유용한 <stdio.h>에 선언된 비표준 hex_dump() 함수를 찾을 수 있다.

 

원하는 방식으로 인수 문자열을 구문 분석할 수 있다. 구문 분석에 대한 힌트는 include/lib/string.h에서 프로토타입이 생성되고 lib/string.c에서 철저한 주석으로 구현된 strtok_r()을 확인하면 된다.

 

이전 글의 load 함수 주석에서 얘기한 *rsp와 strtok_r()을 활용하여 이번 프로젝트 첫 번째 챌린지를 완성하자.

2022.12.23 - [TIL (Today I Learned)/컴퓨터 시스템(CS)] - [CS] Kaist PintOS THREAD, User Programs #3

 

[CS] Kaist PintOS User Programs, 프로그램 구조 설명 #3

이 문서는 카이스트 핀토스 원유집 교수님의 강의를 참고하여 작성하였습니다. 이 문서는 카이스트 핀토스 프로젝트 가이드라인을 정리한 글입니다. Part 2 : User Program 1. 핀토스(다른 운영체제가

roll-over-program.tistory.com

 

#1 strtok_r (for passing the arguments)

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;
}
주석 해석
- 문자열을 DELIMITERS(구분 문자)로 구분된 토큰으로 나눈다. 이 함수가 처음 호출될 때 S는 토큰화할 문자열이어야 하며 후속 호출에서는 널 포인터여야 한다. SAVE_PTR은 토크나이저의 위치를 추적하는 데 사용되는 'char *' 변수의 주소다. 매번 반환 값은 문자열의 다음 토큰이거나 토큰이 남아 있지 않으면 null 포인터다.
- 이 함수는 인접한 여러 구분 기호를 단일 구분 기호로 취급합니다. 반환된 토큰은 길이가 0이 되지 않습니다.
DELIMITERS는 단일 문자열 내에서 한 호출에서 다음 호출로 바뀔 수 있습니다.
- strtok_r()은 구분 기호를 null 바이트로 변경하여 문자열 S를 수정한다. 따라서 S는 수정 가능한 문자열이어야 한다. 특히 문자열 리터럴은 이전 버전과의 호환성을 위해 'const'가 아니지만 C에서 수정할 수 *없다*.

 

 

사용 예

 

char s[] = "  String to  tokenize. ";
char *token, *save_ptr;

for (token = strtok_r (s, " ", &save_ptr); token != NULL; token = strtok_r (NULL, " ", &save_ptr))
	printf ("'%s'\n", token);
 
 /*
 outputs:

'String'
'to'
'tokenize.'
*/

 

#2 hex_dump (for check)

void
hex_dump (uintptr_t ofs, const void *buf_, size_t size, bool ascii) {
	const uint8_t *buf = buf_;
	const size_t per_line = 16; /* Maximum bytes per line. */

	while (size > 0) {
		size_t start, end, n;
		size_t i;

		/* Number of bytes on this line. */
		start = ofs % per_line;
		end = per_line;
		if (end - start > size)
			end = start + size;
		n = end - start;

		/* Print line. */
		printf ("%016llx  ", (uintmax_t) ROUND_DOWN (ofs, per_line));
		for (i = 0; i < start; i++)
			printf ("   ");
		for (; i < end; i++)
			printf ("%02hhx%c",
					buf[i - start], i == per_line / 2 - 1? '-' : ' ');
		if (ascii) {
			for (; i < per_line; i++)
				printf ("   ");
			printf ("|");
			for (i = 0; i < start; i++)
				printf (" ");
			for (; i < end; i++)
				printf ("%c",
						isprint (buf[i - start]) ? buf[i - start] : '.');
			for (; i < per_line; i++)
				printf (" ");
			printf ("|");
		}
		printf ("\n");

		ofs += n;
		buf += n;
		size -= n;
	}
}
주석 해석
- BUF의 SIZE 바이트를 줄당 16개로 정렬된 16진수 바이트로 콘솔에 덤프한다. BUF의 첫 번째 바이트에 대해 OFS에서 시작하는 숫자 오프셋도 포함된다. ASCII가 참이면 해당 ASCII 문자도 함께 렌더링된다.

 

사용 방법

process_exec함수에서 load에 성공하면 호출

댓글