/bin/ls -l foo bar 와 같은 명령이 주어졌을 때, 인자들을 어떻게 다뤄야 하는지 생각해보자
- 명령을 단어들로 쪼갠다. /bin/ls, l, foo, bar 이렇게
- 이 단어들을 스택의 맨 처음 부분에 놓는다. 순서는 상관 없다. 왜냐면 포인터에 의해 참조될 예정이기 때문이다.
- 각 문자열의 주소 + 경계조건을 위한 널포인터를 스택에 오른쪽→왼쪽 순서로 푸시한다. **이들은 argv의 원소가 된다. 널포인터 경계는 argv[argc] 가 널포인터라는 사실을 보장해준다. C언어 표준의 요구사항에 맞춰서 말이다. 그리고 이 순서는 argv[0]이 가장 낮은 가상 주소를 가진다는 사실을 보장해준다. 또한 word 크기에 정렬된 접근이 정렬되지 않은 접근보다 빠르므로, 최고의 성능을 위해서는 스택에 첫 푸시가 발생하기 전에 스택포인터를 8의 배수로 반올림하여야 한다.
- %rsi 가 argv 주소(argv[0]의 주소)를 가리키게 하고, %rdi 를 argc 로 설정한다.
- 마지막으로 가짜 “리턴 어드레스”를 푸시한다 : entry 함수는 절대 리턴되지 않겠지만, 해당 스택 프레임은 다른 스택 프레임들과 같은 구조를 가져야 한다.
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
위 표는 스택과 관련 레지스터들이 유저 프로그램이 시작되기 직전에 어떤 상태인지를 보여준다. 스택은 아래로 자란다는 것을 잊지 말자.
* 4 kB +---------------------------------+
* | kernel stack |
* | | |
* | | |
* | V |
* | grows downward |
* | |
* | |
* | |
* | |
* | |
* | |
* | |
* | |
* +---------------------------------+
* | magic |
* | intr_frame |
* | : |
* | : |
* | name |
* | status |
* 0 kB +---------------------------------+
저 위의 표의 예시에서, 스택 포인터는 위에 보이는 것처럼 0x4747ffb8로 초기화될 것이고, 코드 include/threads/vaddr.h에 정의된 USER_STACK 값에서부터 스택을 시작시켜야 한다.
현재, process_exec() 함수는 새로운 프로세스들에 인자를 전달하는 것을 지원하지 않는다. process_exec() 함수를 확장 구현해서, 지금처럼 단순히 프로그램 파일 이름만을 인자로 받아오게 하는 대신 공백을 기준으로 여러 단어로 나누어지게 만들어야 한다.
첫 번째 단어는 프로그램 이름이고, 두 번째 단어는 첫 번째 인자이며, 그런 식으로 계속 이어지게 만들면 된다. 따라서, 함수 process_exec("grep foo bar") 는 두 개의 인자 foo와 bar을 받아서 grep 프로그램을 실행시켜야 한다.
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 ();
int argc = 0;
char *argv[64]; //parssing한 인자를 담을 배열
char *token, *save_ptr;
for (token = strtok_r(file_name, " ", &save_ptr); token != NULL; token = strtok_r(NULL, " ", &save_ptr))
{
argv[argc++] = token;
}
/* And then load the binary */
success = load (file_name, &_if);
argument_stack(argv, argc, &_if);
/* If load failed, quit. */
palloc_free_page (file_name);
if (!success)
return -1;
/* Start switched process. */
do_iret (&_if);
NOT_REACHED ();
}
argument_stack 함수는 프로세스의 스택에 실행 인자를 준비하는 역할을 한다. 인자들을 스택에 복사하고, 64비트 시스템에서 요구하는 8바이트 정렬을 맞추며, 각 인자의 주소를 스택에 추가한다. 이 과정을 통해 프로그램 실행 시 필요한 인자들을 스택에 올바르게 준비한다.
void argument_stack(char **argv, int argc, struct intr_frame *if_)
{
char *arg_address[128];
for(int i = argc - 1; i >= 0; i--)
{
int arg_i_len = strlen(argv[i]) +1;
if_->rsp -= arg_i_len;
memcpy(if_->rsp, argv[i], arg_i_len);
arg_address[i] = (char *)if_->rsp;
}
if(if_->rsp % 8 != 0)
{
int padding = if_->rsp % 8;
if_->rsp -= padding;
memset(if_->rsp, 0, padding);
}
if_->rsp -= 8;
memset(if_->rsp, 0, 8);
for(int i = argc-1; i >= 0; i--)
{
if_->rsp -= 8;
memcpy(if_->rsp, &arg_address[i], 8);
}
if_->rsp -= 8;
memset(if_->rsp, 0, 8);
if_->R.rdi = argc;
if_->R.rsi = if_->rsp + 8;
}
커맨드라인에서, 여러개의 공백은 하나의 공백과 같게 처리해야 한다. 그러므로 process_exec("grep foo bar")는 저 위의 표(원본) 예시와 동일하게 동작해야 한다.
또한, 납득할만한 수준에서 커맨드라인 인자들의 길이 제한을 강요할 수 있다. 예를 들면, 인자들이 한 페이지 크기(4kB) 안에 들어가게끔 제한하는 것 이다. (그리고 직접적인 관계는 없지만, pintos 유틸리티가 커널에 전달할 수 있는 커맨드라인 인자 크기는 128바이트로 제한되어 있다.)
<stdio.h>에 선언된 비표준 함수인 hex_dump()는 인자 전달 코드를 디버깅 하는데에 유용할 것이다. argument passing을 잘 했는지 make tests/userprog/args-none.result로 체크를 하면 안된다. write가 구현되어 있지 않기 때문인데, 이 때문에, 파싱이 잘 됐는지 확인하기 위해서는 hex_dump를 써야한다.
int write (int fd, const void *buffer, unsigned length)
{
int byte = 0;
if(fd == 1)
{
putbuf(buffer, length);
byte = length;
}
return byte;
}
아니면 syscall.c 쪽에서 write를 임의로 구현해도 된다.
https://github.com/yunsejin/PintOS_RE/tree/project2
'CS > 운영체제' 카테고리의 다른 글
Pint OS_Project 3 구현 (0) | 2024.04.14 |
---|---|
Pint OS_Project 2 구현 - 2(system call) (0) | 2024.04.01 |
Page Replacement Policy (0) | 2024.03.27 |
Demand-zero page, Anonymous page, File-backed page (0) | 2024.03.27 |
하이퍼바이저, 애뮬레이션, QEMU (0) | 2024.03.26 |