-
[SW 정글 67일차] Pintos project 2 - Argument Passing 구현기타/SW 사관학교 정글 2021. 10. 8. 16:01
오늘부터 Argument Passing 구현을 위해 해석해야하는 기존의 코드를 이해하고 추가해야할 코드를 구현했다.
1. 코드 파헤치기
- int main(void) (init.c)
int main (void) { uint64_t mem_end; char **argv; /* Clear BSS and get machine's RAM size. */ bss_init (); /* Break command line into arguments and parse options. */ argv = read_command_line (); argv = parse_options (argv); /* Initialize ourselves as a thread so we can use locks, then enable console locking. */ thread_init (); console_init (); /* Initialize memory system. */ mem_end = palloc_init (); malloc_init (); paging_init (mem_end); #ifdef USERPROG tss_init (); gdt_init (); #endif /* Initialize interrupt handlers. */ intr_init (); timer_init (); kbd_init (); input_init (); #ifdef USERPROG exception_init (); syscall_init (); #endif /* Start thread scheduler and enable interrupts. */ thread_start (); serial_init_queue (); timer_calibrate (); #ifdef FILESYS /* Initialize file system. */ disk_init (); filesys_init (format_filesys); #endif #ifdef VM vm_init (); #endif printf ("Boot complete.\n"); /* Run actions specified on kernel command line. */ run_actions (argv); /* Finish up. */ if (power_off_when_done) power_off (); thread_exit (); }
Pintos의 main program이다.
즉, pintos가 시작이 되면 init.c의 main()함수가 실행이 된다고 보면 된다.
여기서는 메모리, 쓰레드, page table 등을 초기화 해주는 작업이 진행된다.
코드를 보면 알겠지만 #ifdef로 상황에 따라 초기화 함수를 실행하는 것과 안하는 것이 있다.
우리는 USERPROG이므로 USERPROG 블록 안에 있는 init함수는 봐주어야한다.
위 코드에서 각자의 init함수를 들어가서 해석하는 것도 좋지만 일단은 함수명을 보고 주석을 보며 무엇을 하는지 이해했다.
- run_actions(argv) (init.c)
위에서 본 main()함수를 보았듯이 초기화할 대상을 초기화 시킨 후에 kernel command line에 정의된 action을 실행하는데 argv를 인자로 받아간다.
여기서 argv는 read_command_line()으로 읽은 후 parse_options()로 해당 line을 parsing하여 어떠한 action을 할지를 argv에 담아 인자로 넘기는 것이다.
/* Executes all of the actions specified in ARGV[] up to the null pointer sentinel. */ static void run_actions (char **argv) { /* An action. */ struct action { char *name; /* Action name. */ int argc; /* # of args, including action name. */ void (*function) (char **argv); /* Function to execute action. */ }; /* Table of supported actions. */ static const struct action actions[] = { {"run", 2, run_task}, #ifdef FILESYS {"ls", 1, fsutil_ls}, {"cat", 2, fsutil_cat}, {"rm", 2, fsutil_rm}, {"put", 2, fsutil_put}, {"get", 2, fsutil_get}, #endif {NULL, 0, NULL}, }; while (*argv != NULL) { const struct action *a; int i; /* Find action name. */ for (a = actions; ; a++) if (a->name == NULL) PANIC ("unknown action `%s' (use -h for help)", *argv); else if (!strcmp (*argv, a->name)) break; /* Check for required arguments. */ for (i = 1; i < a->argc; i++) if (argv[i] == NULL) PANIC ("action `%s' requires %d argument(s)", *argv, a->argc - 1); /* Invoke action and advance. */ a->function (argv); argv += a->argc; } }
run_actions()함수 안에는 action이라는 구조체가 정의되어 있는데 action 이름, action을 포함하여 command line에 있는 argument의 개수, action에 대응하는 정의된 function을 가진다.
우리는 아직 "run"이라는 action만 신경쓰면 되고 그에 따라 대응하는 run_task라는 함수가 실행된다.
while문을 통해 action 구조체를 탐색하면서 인자로 받은 argv와 action 구조체에 정의된 action name을 비교하면서 똑같다면 정의된 function을 실행한다.
그러면 다음으로 run_task를 살펴보자.
- run_task (char **argv) (init.c)
/* Runs the task specified in ARGV[1]. */ static void run_task (char **argv) { const char *task = argv[1]; printf ("Executing '%s':\n", task); #ifdef USERPROG if (thread_tests){ run_test (task); } else { process_wait (process_create_initd (task)); } #else run_test (task); #endif printf ("Execution of '%s' complete.\n", task); }
thread_tests는 boolean 타입으로 main()함수에서 parse_options함수에서 argv를 parsing할 때 조건에 따라 true로 바뀌게 된다.
true이면 run_test로 들어가는데 run_test를 보면 project 1에서 test case가 들어있는 함수이다.
그렇다면 우리는 process_wait()함수와 process_create_initd함수를 볼 필요가 있다.
먼저, process_create_initd함수를 보자.
- process_create_initd (const char *file_name) (process.c)
/* Starts the first userland program, called "initd", loaded from FILE_NAME. * The new thread may be scheduled (and may even exit) * before process_create_initd() returns. Returns the initd's * thread id, or TID_ERROR if the thread cannot be created. * Notice that THIS SHOULD BE CALLED ONCE. */ 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); /* 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; }
process_create_initd은 본격적으로 command line에서 받은 arguments를 통해 실행하고자 하는 파일에 대한 process를 만드는 과정의 시작이다.
인자로 char형 포인터 file_name을 받는다고 되어 있는데 조금 명시적인 예를 들어보면 아래와 같다.
우리는 prarse_option을 통해 command line에 입력된 것에서 run부터해서 뒷부분을 parsing할 것이다.
그렇게 되면 arg[0]에는 run이 들어가고 arg[1]에 우리가 실행하고자 하는 file name과 그에 같이 붙는 arguments가 있는 string이 있을 것이다.
우리는 이 arg[1]을 file_name으로 process_create_initd에 넘겨주게 되는 것이고 이것을 parsing하고 user stack에 쌓아야하는 것이다.
page_get_page()를 통해 page를 할당받고 해당 page에 file_name을 저장해준다.
그리고 pintos에서는 단일 스레드만 고려하기에 thread_create()로 새로운 스레드를 생성해주고 tid를 return해준다.
여기서, thread_create() 함수의 인자들을 눈여겨 봐야한다.
thread_create (file_name, PRI_DEFAULT, initd, fn_copy)
thread_create의 역할을 다시 한 번 정리하면 file_name을 이름으로 하고 PRI_DEFAULT를 우선순위 값으로 가지는 새로운 스레드가 생성되고 tid를 반환한다.
그리고 스레드는 fn_copy를 인자로 받는 initd라는 함수를 실행시키는 것이다.
그러면 initd함수를 봐봐야한다.
- initd(void *f_name) (process.c)
initd (void *f_name) { #ifdef VM supplemental_page_table_init (&thread_current ()->spt); #endif process_init (); if (process_exec (f_name) < 0) PANIC("Fail to launch initd\n"); NOT_REACHED (); }
process_init()은 process를 initialize해주는 역할을 하고 여기서는 process_exec()의 역할을 봐주어야한다.
- process_exec (void *f_name) (process.c)
/* Switch the current execution context to the f_name. * Returns -1 on fail. */ 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 (); /* And then load the binary */ success = load (file_name, &_if); /* If load failed, quit. */ palloc_free_page (file_name); if (!success) return -1; /* Start switched process. */ do_iret (&_if); NOT_REACHED (); }
여기서 순서대로 보면 intr_frame 구조체는 레지스터나 스택 포인터 같은 context switching을 위한 정보를 담고 있는 것이다.
prcoess_cleaup() 함수는 현재 실행 중인 스레드의 page directory와 switch information을 내려주는 역할을 한다.
이유는 새로 생성되는 process를 실행하기 위해서는 CPU를 점유해야하고 지금은 kernel 모드로 돌아가고 있지만 CPU를 선점하기 전에 지금 실행 중인 스레드와 context switching하기 위한 준비를 하는 것이다.
그 후에 file을 load해주는 load()함수에 들어가게 된다.
load()함수에서 우리가 해야할 parsing작업을 추가해주어야한다.
load()가 성공적으로 된다면 do_iret()와 NOT_REACHED()를 통해 생성된 프로세스로 context switching해주면 된다.
이제 로직은 코드를 따라가며 이해했다.
우리가 추가해야할 parsing작업은 load()함수에 구현하면 되고 user stack에 쌓는 것은 load한 이후에 process_exec함수에서 해주면 된다.
2. 과제 구현
과제 구현을 위한 모든 작업은 process.c에서 이루어졌다.
일단은 process_create_initd (const char *file_name)에서 file_name을 이름으로 가지는 새로운 스레드를 만들어준다.
여기서 인자로 받은 file_name은 parsing이 안된 문자열이다.
즉, ehco hello world 이러한 상태이다.
여기서 진짜 file_name(위 예시에서는 echo)만 parsing해서 tid = thread_create(file_name, PRI_DEFAULT, initd, fn_copy); 여기에 file_name으로 넣어줘야한다.
이 작업을 위해 pintos에서 구현이 되어있는 char * strtok_r (char *s, const char *delimiters, char **save_ptr)를 이용하면 file_name을 얻을 수 있다.
그리고 본격적으로 해야할 parsing 후 user stack에 넣는 코드를 구현해주어야한다.
나는 int process_exec(void *f_name)에 구현을 했다.
일단은 parsing되지 않은 문자열을 parsing을 하여 배열에 담아주는 작업을 진행했다.
file이 load가 된 이후에 user_stack에 하나씩 담아주는 코드를 구현하고 이번 argument passing 과제를 마무리했다.
<결과>
pintos --fs-disk=10 -p tests/userprog/args-single:args-single -- -q -f run 'args-single onearg'
]
[오늘의 나는 어땠을까?]
오늘은 다시 컨디션이 돌아온 느낌이다.
어제부터 코딩테스트 스터디가 끝나면 점심먹기 전까지 네트워크공부를 하려고 다짐했는데 오늘은 주어진 과제에 시간을 좀 많이 투자해야한다는 생각에 pintos를 하기 시작했다.
많은 시간투자로 로직이해와 코드 구현을 했다.
물론 다른 오픈소스 코드를 참고했지만 이 방법으로 내 것으로 만드는 것도 나쁜 방법은 아니라고 생각한다.
요즘은 기분이 오락가락하는 것같다.
pintos를 보면 차분하고 pintos를 하고 있지 않으면 고삐풀린 망아지마냥 날뛴다.
pintos 구현도 중요하지만 운영체제라는 cs과목에 대한 기초지식을 쌓는 것이 더 좋다고 생각하는 사람 중 한명으로 지금 pintos 과제를 하나씩 해결해나가는 시간이 조금 힘들다.
양자 중 선택과 집중을 해야하는데 일단은 일요일까지 pintos 과제를 마무리 하고 책을 읽고 싶다.
집중해서 내 것으로 만들고 과제를 마무리 해보려고 노력해보자.
'기타 > SW 사관학교 정글' 카테고리의 다른 글
[SW 정글 69일차] 오늘은 Concurrency 되짚어 보기 (0) 2021.10.11 [SW 정글 68일차] Pintos project 2 - System Calls 구현 1일차 (0) 2021.10.10 [SW 정글 66일차] Pintos project 2 - Argument Passing (0) 2021.10.08 [SW 정글 65일차] Pintos project 2 입문 (0) 2021.10.07 [SW 정글 64일차] 프로세스의 제어 (0) 2021.10.06