init.c
init.c의 main 함수에서 read_command_line()
함수를 호출하여 명령어를 읽어온다. -> argv
명령어로 들어오는 인자의 형태는 명령어와 그 명령어의 대상이다.
예를 들어 인자가 1개만 들어오는 경우(args-single.ck)라면, argv는 run 'args-single onearg'
의 형태를 가진다.
호출된 명령어 parse_options를 통해 option에 따라 명령어를 적절히 parsing한다.
명령어는 run_action 함수의 인자로 전달된다. argv안의 명령어 문자열에 있는 명령어 run은 run_task 함수를 호출한다.
run_task 함수에서 task = argv[1]로 정해진다. 그 이유는 명령어로 들어오는 인자의 형태는 명령어와 그 명령어의 대상이다. 따라서 명령어의 대상에 해당되는 인자인 'args-single onearg'
가 process_create_initd
의 인자가 된다.
해당 인자는 프로그램 파일이름과 프로그램들의 인자들이 같이 위치한다. 따라서 프로그램 파일명과 인자들을 공백을 기준으로 parsing하여야 한다.
process.c
process_create_initd()
의 인자로 입력받은 명령어의 대상(task = argv[1])이 들어온다. 해당 문자열은 thread_create의 인자로 들어가서 해당 인자를 이름으로 한 kernel 스레드를 생성한다. 해당 스레드는 생성된 이후 running thread가 되는 시점에 인자로 들어간 함수 initd를 호출한다.
initd()
함수는 process_exec()
함수를 호출한다.
process_exec()
함수는 load()
함수를 호출한다.
load함수에서 입력받은 인자를 parsing하고 해당 커널 스레드의 인터럽트 프레임에 인자의 주소를 저장하고, 이후 실행할 명령어의 주소를 스택에 저장한다.(if->rip)
이때 filesys_open에 전달되는 file_name은 입력받은 인자를 공백으로 parsing할 때 등장하는 첫 번째 문자열이다.두 번째 문자열부터 인자가 된다.
파싱한 문자열을 스택에 쌓는다. 스택은 주소가 감소하면서 확장한다.(위->아래). 인자 -> 8바이트 정렬을 맞추기 위한 공백 공간 -> 파싱한 인자의 스택 주소 -> 가짜 반환 주소 순으로 저장한다.
과정 중 헷갈렸던 부분과 알게 된 점
userprog/process.c
의 tid_t process_create_initd (const char *file_name)
thread_create
함수에서 인자로 받은 함수가 언제 실행되는 것인가?thread_create(file_name, PRI_DEFAULT, initd, fn_copy)
를 호출하며 새로운 kernel 스레드를 생성한다. 이 kernel 스레드는 initd()
함수를 thread routine으로 가지는 스레드로, 생성 후 ready list에 들어간 뒤, running thread가 되는 시점에 initd()
를 실행한다.다만 해당 함수는 반환되지 않기 때문에 해당 return address로 이동하지 않는다. 하지만 다른 스택 프레임과 동일한 구조를 갖기 위해서 가짜 반환주소를 넣는다.
load()
(in userprog/process.c)내에서 구현한다.thread.h
내의 struct fild_fd 선언과 struct thread 내 멤버 추가struct file_fd
{
int fd; /* fd: 파일 식별자 */
struct file *file; /* file */
struct list_elem fd_elem; /* list 구조체의 구성원 */
};
struct thread
{
....
struct list fd_list; /* file_fd 구조체를 저장하는 Doubley Linked List */
int fd_count; /* fd를 확인하기 위한 count*/
int exit_status
struct semaphore fork_sema; /* 자식 프로세스의 fork가 완료될 때까지 기다리도록 하기 위한 세마포어 */
struct semaphore wait_sema;
struct semaphore exit_sema;
struct list child_list; /* 자식 스레드를 보관하는 리스트 */
struct list_elem child_elem; /* 자식 리스트 element */
struct file *now_file; /* 현재 프로세스가 실행 중인 파일을 저장하기 위한 변수 */
....
};
목표: 총 14개의 구현해야할 syscall
프로세스 관련 system call
halt()
, wait()
, fork()
, exit()
, exec()
파일 관련 system call
open()
, filesize()
, close()
, read()
, write()
, seek
(), tell()
, create()
, remove()
create()와 remove()를 제외한 파일 관련 system call들은 file descriptor를 반환하거나, file descriptor를 이용해서 file에 대한 작업을 수행한다.
kernel은 file descriptor와 실제 file 구조체를 매핑하여 관리하며 이를 위한 도구가 바로 fd table이다.
halt()
: pintos를 종료시키는 시스템 콜power_off()
함수를 사용하여 pintos를 종료시켰다.exit()
: 현재 프로세스를 종료시키는 시스템 콜exit()
함수를 호출했거나 다른 어떤 이유들로 유저 프로세스 종료 시 프로세스 이름과 exit code를 아래와 같이 지정된 형식으로 출력한다.printf("%s: exit(%d)\n", ....);
filesize()
: fd로 열려있는 파일 사이즈를 리턴해주는 시스템 콜fd_list
를 순회하면서 찾으려는 fd와 mapping된 file을 찾는다.file_length()
를 호출한다.seek()
: fd로 열려있는 파일의 (읽고 쓸 위치를 알려주는) 포인터의 위치를 변경해주는 시스템 콜fd_list
를 순회하면서 찾으려는 fd와 mapping된 file을 찾는다.file_seek()
를 호출한다.tell()
: fd에서 읽히거나 써질 다음 바이트의 위치를 반환fd_list
를 순회하면서 찾으려는 fd와 mapping된 file을 찾는다.file_tell()
를 호출한다.filesize()
, seek()
, tell()
은 시스템 콜이 잘 동작하는지 확인할 수 있는 test case 부제로 test를 돌릴 수 있는 시스템 콜에서 호출하는 방식으로 호출됨을 확인하였다.~~create()
: 파일을 생성하는 시스템 콜
filesys_create()
를 호출 한다.remove()
: 파일을 삭제하는 시스템 콜
filesys_remove()
를 호출한다.read()
: fd를 통해 열린 파일의 데이터를 읽는 시스템 콜.file_read()
함수를 호출함.write()
: fd를 통해 열린 파일의 데이터를 기록하는 시스템 콜.file_write()
함수를 호출함.file_deny_write()
: 파일을 open 할 때, 실행 파일에 대해 쓰기를 방지한다. 메모리에 파일을 load한 후에 수정하면 안 되기 때문이다. 이를 통해, file synchronization Issue를 해결할 수 있다.file_allow_write()
: 파일의 데이터가 변경되는 것을 허락하는 함수이다. file_close()
함수 호출 시 해당 함수가 호출된다.exec()
: 현재 프로세스를 인자로 주어진 이름을 갖는 실행 파일로 변경하는 시스템 콜.
process_exec()
호출한다.fork()
: 현재 프로세스의 복제본인 프로세스를 생성하는 시스템 콜
movq %rsp, %rbx
를 통해 rbx에 유저스택포인터가 저장되고 있으며, 10-12행의 movabs $tss, %r12
-> movq (%r12), %r12
-> movq 4(%r12), %rsp
를 통해 rsp에는 커널 스택 포인터가 저장된다.fork_data
를 선언하였다. fork_data
구조체의 멤버변수로 thread *parent
와 intr_frame *user_level_f
를 활용하여 부모 스레드와 유저레벨 인터럽트를 저장한 후 해당 구조체를 __do_fork
의 인자로 전달하였다.__do_fork
함수 내부에서 pml4_for_each()
함수를 통해 인자로 duplicate_pte()
함수를 실행하여 실행부모의 유저 메모리 공간을 복사하여 새로 생성한 자식의 페이지에 넣어준다.fd_list
의 요소들과 fd_count
를 복사하여준다.fork_sema
를 활용해서 자식 프로세스에 대한 복사가 완료될때까지 process_fork
함수가 끝나지 않도록 하였다.TID_ERROR
를 반환하도록 한다.wait()
: 자식 프로세스가 수행되고 종료될 때까지 부모 프로세스가 대기하는 시스템 콜process_exit()
에서 open되어있는 파일을 close 하지 않았던 문제 struct list *exit_list = &curr->fd_list;
struct list_elem *start = list_begin(exit_list);
while (!list_empty(exit_list))
{
struct file_fd *exit_fd = list_entry(start, struct file_fd, fd_elem);
file_close(exit_fd->file);
start = list_remove(&exit_fd->fd_elem);
free(exit_fd);
}
process_exit()
즉, 프로세스를 종료해야할 시점에서 열린 모든 파일을 close 해주어야한다.write()
와 read()
를 할 때 입력받는 인자 buff가 올바른 address 인지 체크하지 않았던 문제process_wait()
와 process_exit()
sema_down과 sema_up의 순서파악