어제 간략적으로 Argument Parsing 관련한 구현 요구사항 파악 및 관련 개념을 확인해 봤다면 이제 이 내용을 가지고 구현을 해보려고 한다.
우리가 해야하는 일은 (1) 사용자 커널로 부터 들어온 인자를 Parsing 후 (2) 스택 내 해당 레지스터로 Parsing하는 부분이다. (카이스트/한양대 자료에서는 쓰레드 이름 파싱도 추가 구현 내용인 것 같다.)
차근차근 내용을 이해하는 것을 목표로 진행해보겠다.
만약 기본적인 호출 개념에 대한 이해만 하고 실제 구현을 직접 해보고 싶다면 1번만 보는 것을 추천한다.
1. Pintos 호출 순서 (구현 전)
구현을 위한 대략적인 파일 구조를 확인해 보면 Pintos가 시작되면 /threads/init.c 내에 있는 main() 함수가 실행된다.
메모리, 쓰레드, page table 등을 초기화 해주는 작업이 진행되며 #ifdef를 통해 상황에 따라 초기화 함수를 실행하는 것과 안하는 것이 구분되어 있다.
함수 호출 순서를 간략히 적어보면 아래 순서와 같다.
- 함수 호출 순서
(1) read_command_line() 함수를 통해 사용자에게 인자를 전달 받는다.
(2) parse_options(argv) 을 통해 해당 명령어의 앞 뒤 공백 혹은 입력에 필요 없는 부분이 있다면 파싱을 진행해 argv에 선언한다. ex) ls -l /etc (이때, strtok_r 함수가 사용되는데 나중에 파싱할 때, 참고하면 된다.)
(3) 쓰레드를 사용하기 위해 필요한 함수들을 호출 후 초기화가 완료되면 run_actions(argv)을 수행한다.
(4) run_actions(argv) 내 actions[] 내에 있는 run_task 함수가 호출된다.
** 코드 분석 **
{"run", 2, run_task} : 2는 해당 명령어(run)가 인자와 함께 전달되어야 함을 의미한다.
즉, 인자 개수가 2개여야만 해당 명령어를 실행할 수 있으며 여기서 첫 번째 인자는 "run"이고, 두 번째 인자는 실행할 프로그램의 파일명이다.
(5) run_task 내에서 ifdef USERPROG가 활성화되어 process_create_initd (task) 함수가 호출되며
(6) 새로운 쓰레드에서 initd 함수를 호출하여 새로운 프로세스를 실행하기 위해 process_exec() 함수 내실행 파일을 파싱 후 load() 함수를 통해 User stack 메모리에 로드한다.
(7) load() 함수가 성공적으로 수행된다면(success=1) do_iret()과 NOT_REACHED() 함수를 통해 생성된 프로세스로 context_switching이 이루어지게 된다.
// threads/init.c
1. int main (void)
1-1. argv = read_command_line ();
1-2. argv = parse_options (argv);
1-3. run_actions (argv);
2. static const struct action actions[] = { {"run", 2, run_task}, ...
3. static void run_task (char **argv)
// userprg/process.c
3-1. process_wait (process_create_initd (task));
4. tid_t process_create_initd
// 추가 구현?) => Thread 파일명 파싱 하기 ex) AS-IS:arg-single onearg -> TO-BE:ars-single
4-1.tid = thread_create (file_name, PRI_DEFAULT, initd, fn_copy);
5. static void initd (void *f_name)
5-1. if (process_exec (f_name) < 0)
6. int process_exec (void *f_name)
6-1. // TO-DO 구현 => 입력 받은 인자 Parsing 진행
6-2. success = load (file_name, &_if);
6-3. // TO-DO 구현 => User stack에 입력 받은 인자 저장
6-4. do_iret (&_if);
6-5. NOT_REACHED ();
2. 코드 실행 방법
[실행]
pintos --fs-disk=10 -p tests/userprog/args-single:args-single -- -q -f run 'args-single onearg'
- --fs-disk = x : xMB 크기의 pintos 파일 시스템 파티션 하나를 포함하는 모의 디스크를 만든다.
- -p : 파일 시스템에 파일 push (-g : 파일 시스템에 파일 get)
- 파일명:별칭 : 파일을 별칭으로 이름으로 push 함(-p)
- -- : 옵션 사용
- -q : 시스템을 포맷팅하고
- -f : 핀토스 포맷팅이 끝나면 종료한다.
- run : 이어서 나오는 명령어 실행
- "~" : 파일명
[확인]
출력 결과를 모두 보고 싶다면 cmd 내 /pintos-kaist/userprg/build에서 make check를 통해 생성된 파일로 확인할 수 있다.
// 생성된 결과 파일
- 나의 출력 결과물
/userprg/build/tests/userprog/~.result
- 출력되어야 하는 결과
/tests/userprog/~.ck
나의 코드를 수행하면 나오는 결과와 미리 작성되어 있는 출력 결과를 /userprg/build/tests/userprog/~.result에 출력하게 된다.
‼ 확인 시 주의 사항
구현 과제 5가지 (+1 extra) 중 Argument Passing만 구현한다면 아직 시스템 콜이 구현되어 있지 않아 FAIL 만 출력되게 된다.
따라서, 구현 성공 여부를 확인하기 위해서는 실제 레지스터 내 Passing 한 데이터가 어떻게 들어갔는지 확인하는 작업이 필요한데 아래와 같은 명령어를 process.c 내 추가하면 된다.
// hex dump 출력을 위한 추가 (내용 수정)
int process_wait (tid_t child_tid UNUSED) {
/* XXX: Hint) The pintos exit if process_wait (initd), we recommend you
* XXX: to add infinite loop here before
* XXX: implementing the process_wait. */
while (1){}
return -1;
}
프로세스가 끝나기 전까지 대기를 한다는 의미이다.
int process_exec (void *f_name) {
...
/* If load failed, quit. */
palloc_free_page (file_name);
if (!success)
return -1;
/* Hex dump */
hex_dump(_if.rsp, _if.rsp, USER_STACK - _if.rsp, true);
/* Start switched process. */
do_iret (&_if);
NOT_REACHED ();
}
hex_dump() 함수를 추가함으로써 User Stack에 저장한 내용을 확인 할 수 있다.
이후 Argument Passing 코드 작성 후 실행을 하면 된다.
(맨 처음에 USER_STACK을 KERN_BASE로 했다가 fage-fault가 출력되어 고생했었다.... 우리가 인자를 저장한 곳은 user stack인 것을 헷갈리지 않도록 주의하자!!!)
'sw 사관학교 정글 > TIL' 카테고리의 다른 글
[정글 72일차] Project2 발표 및 Project3 발제 (0) | 2023.05.09 |
---|---|
[정글 63일차] Project2 : Argument Passing 구현 및 확인 (0) | 2023.04.30 |
[정글 60일차] Project2 발제 (0) | 2023.04.29 |
[정글 59일차] Project1 : Priority Schedule2 (0) | 2023.04.29 |
[정글 58일차] Project1 : Priority Schedule (0) | 2023.04.29 |