pintOS는 프로그램과 인자를 구분하지 못하는 구조이다.
ex) ls -a (pintOS 는 'ls-a'를 하나의 프로그램명으로 인식)
프로그램 이름과 인자를 구분하여 스택에 저장, 인자를 응용 프로그램에 전달하는 기능을 구현하자.
유닉스 64비트 x86-64 구현에 있는 몇 가지 중요한 호출 규약들은 다음과 같다.
유저-레벨 어플리케이션은 %rdi, %rsi, %rdx, %rcx, %r8, %r9 시퀀스들을 전달하기 위해 정수 레지스터를 사용합니다.
호출자는 다음 인스트럭션의 주소(리턴 어드레스)를 스택에 푸시하고, 피호출자의 첫번째 인스트럭션으로 점프합니다. CALL 이라는 x86-64 인스트럭션 하나가 이 두 가지를 모두 수행합니다.
피호출자가 실행됩니다.
만약 피호출자가 리턴 값을 가지고 있다면, 리턴 값은 레지스터 RAX에 저장됩니다.
피호출자는 x86-64 인스트럭션인 RET 를 사용해서, 스택에 받았던 리턴 어드레스를 pop하고 그 주소가 가리키는 곳으로 점프함으로써 리턴됩니다.
우리는 userprog 디렉토리의 process.c 파일을 수정할 것이다.
커널은 유저 프로그램이 실행되기 전에, 레지스터에 올라가 있는 초기 함수를 위한 인자를 반드시 넣어줘야 한다. (이 인자들은 일반적인 호출 규약과 동일한 방식으로 전달)
👉 /bin/ls -l foo bar
와 같은 명령이 주어졌을 때, 인자들을 어떻게 다뤄야 하는지 생각해보자.
/bin/ls
, l
, foo
, bar
argv
의 원소가 된다.argv[argc]
가 널포인터라는 사실을 보장해준다.argv[0]
이 가장 낮은 가상 주소를 가진다는 사실을 보장해준다.// userprog/process.c/argument_stack()
...
int padding = (int)*rsp % 8;
for (int i = 0; i < padding; i++)
{
(*rsp)--;
**(uint8_t **)rsp = 0; // rsp 직전까지 값 채움
}
...
%rsi
가 argv
주소(argv[0]
의 주소)를 가리키게 하고, %rdi
를 argc
로 설정한다.구현 할 때 크게 두 부분으로 나눠서 구현한다
유저 프로그램을 실행시키는 전체적인 흐름은 다음과 같다
void argument_stack(char **parse ,int count ,void **esp)
init.c → int main(void) → run_actions(argv) → run_task(char **argv) → process_create_initd(task) → thread_create (file_name, PRI_DEFAULT, initd, fn_copy) → initd→process_exec → load, do_iret