과제 목표
- Command line Parsing 기능 구현
과제 설명
- Pintos는 프로그램과 인자를 구분하지 못하는 구조로 되어있습니다.
- ex) /bin/ls -l foo bar 의 경우 null 포인터를 인식하지못하고 문자열 전체로 인식.
- 명령어를 널포인터 기준으로 단어를 분할해서 /bin/ls, -l, foo, bar로 나눠서 스택에 푸시한다.
- 스택에 저장된 인자를 응용 프로그램에 전달하는 기능을 구현합니다.
수정 부분
- process_create_initd() 함수에서 프로그램을 실행 할 프로세스를 생성합니다.
- process_exec() 함수에서 프로그램을 메모리에 탑재하고 응용 프로그램을 실행합니다.
- argument_stack() 함수에서 함수 호출 규약에 따라 유저 스택에 프로그램 이름과 인자들을 저장합니다.
💡 어셈블리어 레지스터 변수란?
- 레지스터는 CPU가 요청을 처리하는데 필요한 데이터(명령어의 종류, 연산결과, 복귀주소 등)을 일시적으로 저장하는 기억장치이다.
✔️ 범용 레지스터
- RAX(Accumulator) : 더하기, 빼기 등 산술/논리 연산을 수행하며 함수의 return값이 저장된다.
- 시스템콜 함수를 사용하려면 RAX에 함수의 syscall 번호를 넣어준다.
- RBX(Base) : 메모리 주소를 저장하기 위한 용도로 사용된다.
- RCX (Count) : 반복문에서 카운터로 사용되는 레지스터. 고급언어 for문의 i 와 같은 역할이지만, 다만 ECX는 미리 반복 값을 정해두고 명령어를 사용할 때마다 값이 하나씩 줄어든다는 점이 다르다.
- syscall을 호출했던 사용자프로그램의 return 주소를 가진다.
- RDX (Data) : 다른 레지스터를 서포트하는 여분의 레지스터. 큰 수의 곱셈이나 나눗셈 연산에서 EAX와 함께 사용된다.
✔️ 인덱스 레지스터
- RSI (Source Index) : 데이터를 복사할 때 src데이터, 즉 복사할 데이터의 주소가 저장된다.
- RDI (Destination Index) : 데이터를 복사할 때 복사된 dest데이터의 주소가 저장된다.
✔️ 포인터 레지스터
- RSP (Stack Point) : 스택프레임에서 스택의 끝 지점 주소(현재 스택 주소)가 저장된다.
- 즉, 데이터가 계속 쌓일 때 스택의 가장 높은 곳을 가리킨다.
- push, pop 명령을 통해 RSP 값이 위아래로 8바이트씩 이동하면서 스택프레임의 크기를 변경하게 된다.
- RBP (Base Point) : 함수가 호출되면 스택프레임이 형성 되는데 이 스택스레임의 시작 지점 주소(스택 복귀 주소)가 저장된다.
구현
1️⃣ tid_t process_create_initd(const char *file_name)
- file_name 문자열 파싱하기
- 인자로 들어오는 file_name을 널 포인터 기준으로 파싱해준다.
tid_t process_create_initd (const char *file_name) {
char *fn_copy;
tid_t tid;
/* Make a copy of FILE_NAME.
* Otherwise there's a race between the caller and load(). */
fn_copy = palloc_get_page (0);
if (fn_copy == NULL)
return TID_ERROR;
strlcpy (fn_copy, file_name, PGSIZE);
char *parsing;
/*file_name을 받아와서 null 기준으로 문자열 파싱*/
strtok_r(file_name," ", &parsing);
/* Create a new thread to execute FILE_NAME. */
tid = thread_create (file_name, PRI_DEFAULT, initd, fn_copy);
if (tid == TID_ERROR)
palloc_free_page (fn_copy);
return tid;
}
2️⃣ int process_exec(void *f_name)
- parsing한 이자를 담을 argu배열을 생성하고 배열의 길이는 pintos에서 command line 길이를 128바이트로 제한했기에 128바이트로 생성.
- strtok_r 함수를 활용해서 반복문을 만들어서 argu배열에 token을 저장.
- 스택에 마지막 추가한 fake address를 담기 직전의 주소가 argv의 시작 주소이므로, rsi에 현재 스택 포인터 rsp +8을 저장한다. (아래 사진에서는 argc 무시, 4바이트 기준일때 주소값입니다.)
- 결과 확인을 위한 메모리를 16진수 형태로 출력해주는 hex_dump()함수활용.
int
process_exec (void *f_name) {
char *file_name = f_name;
bool success;
/* We cannot use the intr_frame in the thread structure.
* This is because when current thread rescheduled,
* it stores the execution information to the member. */
struct intr_frame _if;
_if.ds = _if.es = _if.ss = SEL_UDSEG;
_if.cs = SEL_UCSEG;
_if.eflags = FLAG_IF | FLAG_MBS;
/* We first kill the current context */
process_cleanup ();
/*parsing한 인자를 담을 argu배열의 길이는 pintos제한 128바이트*/
char *argu[128];
char *token, *parsing;
int cnt = 0;
/* strtok_r 함수를 통해서 첫 번째로 얻은 값을 token에 저장, 나머지를 parsing에 저장 */
/* token이 NULL일때까지 반복 */
/* strtok_r 함수에 NULL값을 받아온다면 이전 호출 이후의 남은 문자열에서 토큰을 찾음, 따라서 token에는 다음 문자 저장*/
for(token=strtok_r(file_name," ",&parsing);token!=NULL; token=strtok_r(NULL," ",&parsing))
{
argu[cnt++]=token;
}
/* And then load the binary */
success = load (file_name, &_if);
argument_stack(argu,cnt,&_if.rsp);
/*if 구조체의 필드값 갱신*/
_if.R.rdi=cnt;
_if.R.rsi=(char*)_if.rsp+8;
/*_if.rsp를 시작 주소로하여 메모리 덤프를 생성. 메모리 덤프의 크기는 16진수로*/
hex_dump(_if.rsp, _if.rsp, USER_STACK - (uint64_t)_if.rsp, true);
/* If load failed, quit. */
palloc_free_page (file_name);
if (!success)
return -1;
/* Start switched process. */
do_iret (&_if);
NOT_REACHED ();
}
3️⃣ void argument_stack(char **parse ,int count ,void **esp)
- 프로그램 이름, 인자 push
- 주의해야할 점, stack의 bottom에 오른쪽 인자부터 쌓아 나간다. 즉 argu의 끝부터 rsp를 감소시키면서 stack에 저장한다.
- stack을 word정렬해주기 위해 패딩 해준다. -> rsp의 값이 8의 배수가 될 때까지 계속해서 stack에 0 추가.
- 문자열 종료를 나타내는 0 push
- 문자열의 주소 push
void argument_stack(char **argu, int count, void **rsp)
{
// 프로그램 이름, 인자 문자열 push
for (int i = count - 1; i > -1; i--)
{
for (int j = strlen(argu[i]); j > -1; j--)
{
(*rsp)--; // 스택 주소 감소
**(char **)rsp = argu[i][j]; // 주소에 문자 저장
}
argu[i] = *(char **)rsp;
}
/* 정렬 패딩 push
rsp의 값이 8의 배수가 될 때까지 스택에 0 넣어서 패딩맞춰주기*/
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 = argu[i]; // char* 타입의 주소 추가
}
// return address push
(*rsp) -= 8;
**(void ***)rsp = 0; // void* 타입의 0 추가
}
테스트 결과
- userprog/build에서 아래의 명령어 입력
pintos --fs-disk=10 -p tests/userprog/args-single:args-single -- -q -f run 'args-single onearg'
중간에 CG의 개수와 command_line이 출력된 부분을 확인할 수 있습니다.
위와 같은 출력물이 나오면 parsing부분은 테스트 성공입니다!
'운영체제' 카테고리의 다른 글
[Pintos-Kaist] Project2 - System Call(2) - System Call Flow (0) | 2023.06.07 |
---|---|
[Pintos-Kaist] Project2 - System Call(1) - (halt, exit, create, remove) (1) | 2023.06.06 |
[운영체제] 인터럽트 & 시스템 콜(Interrupt, System Call) (0) | 2023.06.02 |
[Pintos-Kaist] Project 1 - Threads 1주차 학습 (0) | 2023.06.02 |
[운영체제] 스핀락(Spinlock),뮤텍스(Mutex),세마포어(Semaphore) (0) | 2023.05.29 |