-
Project2 : SystemCallOS/Pintos 2022. 1. 11. 00:05
Systemcall
시스템콜 ?
사용자 프로세스가 디스크 읽기와 같은 명령어를 실행하려면 어떻게 해야 할까? 이 때 필요한 것이 **시스템 콜(System call)**이다.
시스템 콜은 운영체제가 제공하는 서비스에 대한 프로그래밍 인터페이스로, 사용자 모드 프로그램이 커널 기능을 사용할 수 있도록 한다. 시스템 콜은 커널 모드에서 실행되며, 실행이 끝나면 다시 사용자 모드로 복귀된다.
Implement system call infrastructure.
Implement the system call handler in userprog/syscall.c.
어떠userprog/syscall.c에 시스템 콜 핸들러를 구현하자.
- 스켈레톤 코드에서는 프로세스를 종료하여 시스템 콜을 처리한다.
- 시스템 콜 번호를 얻고, 시스템 콜 인자를 얻어서 적절한 작업을 수행하자.
현재 userprog/syscall.c에 구현되어 있는 syscall_handler( ) 함수는 골격만 있다.
해당 함수를 수정하여 각각의 시스템 콜에 알맞은 처리 작업을 수행할 수 있도록 해야 한다.
/* userprog/syscall.c */ void syscall_handler (struct intr_frame *f UNUSED) { // TODO: Your implementation goes here. printf ("system call!\\n"); }
System Call Details
x86과는 다르게 x86_64 시스템에서는 syscall이라는 특별한 명령어를 제공한다. syscall 명령어는 시스템 콜 핸들러를 부를 수 있는 가장 빠른 방법이다.
사용자 프로그램은 syscall 명령어를 통해 시스템 콜을 보낼 수 있다. 이 때 다른 함수들을 호출할 때와 비슷하게 인자들과 리턴값이 레지스터에 저장되는데, 딱 두 가지가 다르다.
- %rax에는 시스템 콜 넘버가 저장된다(보통의 함수라면 이 자리에 callee의 반환값을 저장한다).
- 4번째 인자는 %rcx가 아닌 **%r10에** 저장된다(원래 보통의 함수라면 %rdi, %rsi, %rdx, %rcx, %r8 그리고 %r9에 저장된다).
- %rdi, %rsi, %rdx, %r10, %r8 , %r9
시스템 콜을 부른 사용자 함수의 레지스터 값은 struct intr_frame에서 확인할 수 있다.
Implement the following system calls
- 시스템 콜 번호는 include/lib/syscall-nr.h 에 정의되어있음
이제 본격적으로 시스템 콜 핸들러를 구현해보도록 하자. 시스템 콜이 호출되면 어떤 시스템 콜인지에 따라 다른 작업이 수행되어야 한다.
따라서 시스템 콜 핸들러의 인자로 전달된 inter_frame의 f->R.rax를 통해 확인할 수 있다. switch 문에 이를 넣어서 조건별로 알맞은
시스템 콜이 호출될 수 있도록 설정해주자. 가장 먼저 구현할 시스템 콜은 SYS_HALT이다.
halt
- src/include/threads/init.h
- 위의 경로에 정의되어있음.
- 거의 사용되지 않는다.
- 아래 요구 조건에 따라 halt( ) 함수를 구현하면 되는데 power_off( ) 함수를 호출할 수 있도록 작성하면 된다.
- power_off( ) 함수를 사용하기 위해서 threads/init.h를 추가해야 한다.
- Terminates Pintos by calling power_off( )(declared in src/include/threads/init.h).
-
/* userprog/syscall.c */ #include "threads/init.h" /*--------------- PROJECT2: User Programs ----------------*/ /* The main system call interface */ void syscall_handler(struct intr_frame *f UNUSED){ // TODO: Your implementation goes here. switch (f->R.rax){ case SYS_HALT: halt(); break; default: exit(-1); break; } } void halt(void){ power_off(); }
create
- 성공하면 true반환, 아니면 false반환
- 파일 생성은 파일을 열지는 않는다.
- 여는 것은 open system call에서 담당한다.
- file : 생성할 파일의 이름 및 경로 정보
- initial_size : 생성할 파일의 크기
그런데 create( )함수 인자로 넘어오는 주소가 올바른 값인지 확인할 필요가 있다.
이를 위해 check_address( )라는 함수를 추가로 구현해야 한다.
check_address( ) : 확인할 주소가 NULL 포인터이거나 유저 어드레스가 아니거나, 할당되지 않은 주소인 경우 프로세스를 종료하는 함수이다.
Q. create 와 remove의 인자가 몇개인지 어떻게 알지?
A. syscall.h에 몇개를 받는지 정의되어있음.
/* userprog/syscall.c */ // 중략 #include "filesys/filesys.h" /*--------------- PROJECT2: User Programs ----------------*/ void syscall_handler(struct intr_frame *f UNUSED){ // TODO: Your implementation goes here. switch (f->R.rax){ // 중략 case SYS_CREATE: f->R.rax = create(f->R.rdi, f->R.rsi); break; case SYS_REMOVE: f->R.rax = remove(f->R.rdi); break; default: exit(-1); break; } } void check_address(const uint64_t *uaddr){ struct thread *cur = thread_current(); if (uaddr == NULL || !(is_user_vaddr(uaddr)) || pml4_get_page(cur->pml4, uaddr) == NULL){ exit(-1); } } bool create(const char *file, unsigned initial_size){ check_address(file); return filesys_create(file, initial_size); }
remove
- 성공하면 true반환, 아니면 false반환
- 파일은 열리고 닫히는 여부와 관계없이 제거될 수 있고, 열린 파일을 제거해도 파일이 닫히지 않는다.
-
bool remove(const char *file){ check_address(file); return filesys_remove(file); }
exit
- exit( )함수는 내부에서 thread_exit( ) 함수를 호출하고 바로 다시 process_exit( ) 함수를 호출한다.
- process_exit( ) 함수에서는 palloc_free_multiple( ), file_close( ), process_cleanup( ) 등을 수행한다.
- 이를 통해 메모리 누수 방지, 사용하던 자원 반납 등을 진행함으로써 프로그램을 종료한다.
-
/* userprog/syscall.c */ /*--------------- PROJECT2: User Programs ----------------*/ void exit(int status){ struct thread *cur = thread_current(); cur->exit_status = status; printf("%s: exit(%d)\n", thread_name(), status); thread_exit(); } /* userprog/process.c */ void thread_exit (void) { ASSERT (!intr_context ()); #ifdef USERPROG process_exit (); #endif /*--------------- PROJECT2: User Programs ----------------*/ list_remove(&thread_current()->allelem); /* Just set our status to dying and schedule another process. We will be destroyed during the call to schedule_tail(). */ intr_disable (); do_schedule (THREAD_DYING); NOT_REACHED (); } void process_exit (void) { struct thread *curr = thread_current (); /* TODO: Your code goes here. * TODO: Implement process termination message (see * TODO: project2/process_termination.html). * TODO: We recommend you to implement process resource cleanup here. */ /*--------------- PROJECT2: User Programs ----------------*/ for (int i = 0; i < FDCOUNT_LIMIT; i++){ close(i); } palloc_free_multiple(curr->fdTable, FDT_PAGES); file_close(curr->running); process_cleanup (); sema_up(&curr->wait_sema); sema_down(&curr->free_sema); }
fork
fork( ) 함수는 내부에서 process_fork( ) 함수를 호출하며, 이때 인자로 thread_name과 Interrupt stack frame f를 전달한다.
process_fork( ) 함수는 내부에서 다양한 함수들을 호출하는데,
우선, memcpy( ) 함수를 호출하여 해당 함수의 두 번째 인자(if_)의 값을 첫 번째 인자(&cur->parent_if)로 복사한다.
fork( ) 함수가 자식 프로세스를 생성하는 과정으로 이해한다면,
이는 자식 프로세스 생성에 앞서 부모 프로세스의 내용을 memcpy 하는 과정으로 이해할 수 있다.
memcpy( ) 이후, thread_create( ) 함수를 진행하고, 해당 함수의 리턴 값을 tid에 저장한다.
thread_create( ) 함수의 인자로는 fork( ) 함수의 인자로 받았던 thread_name과, __do_fork( ) 함수, 현재 스레드 등이 들어간다.
__do_fork( ) 함수를 요약하자면 부모 프로세스의 내용을 자식 프로세스로 복사하는 과정이라고 할 수 있다.
이는 memcpy( ) 함수와 반복문, do_iret( ) 함수 등을 통해 이루어진다.
process_fork( ) 과정에서 semaphore가 등장한다는 것이다.
자세히 설명하자면, 부모 프로세스는 thread_create( ) 함수의 리턴으로 받은 tid를 갖고 자식 프로세스를 찾는다.
이후, 해당 자식의 fork_sema를 sema_down( ) 한다.
이러한 과정은 자식 프로세스의 정상적인 로드를 위한 것으로,
자식 프로세스는 __do_fork( ) 함수를 통해 부모 프로세스의 정보를 모두 복사한 이후 sema_up( )을 호출한다.
/* userprog/syscall.c */ /*--------------- PROJECT2: User Programs ----------------*/ tid_t fork(const char *thread_name, struct intr_frame *f){ return process_fork(thread_name, f); } /* userprog/process.c */ tid_t process_fork (const char *name, struct intr_frame *if_ UNUSED) { /* Clone current thread to new thread.*/ struct thread *cur = thread_current(); memcpy(&cur->parent_if, if_, sizeof(struct intr_frame)); // 부모 프로세스 memcpy tid_t tid = thread_create(name, PRI_DEFAULT, __do_fork, cur); // 전달받은 thread_name으로 __do_fork() 진행 if (tid == TID_ERROR) return TID_ERROR; struct thread *child = get_child_with_pid(tid); // get_child_with_pid 함수 실행 sema_down(&child->fork_sema); // child load 대기 if (child -> exit_status == -1) return TID_ERROR; return tid; } static void __do_fork (void *aux) { struct intr_frame if_; struct thread *parent = (struct thread *) aux; struct thread *current = thread_current (); /* TODO: somehow pass the parent_if. (i.e. process_fork()'s if_) */ /*--------------- PROJECT2: User Programs ----------------*/ struct intr_frame *parent_if; parent_if = &parent->parent_if; bool succ = true; /* 1. Read the cpu context to local stack. */ memcpy (&if_, parent_if, sizeof (struct intr_frame)); if_.R.rax = 0; /* 2. Duplicate PT */ current->pml4 = pml4_create(); if (current->pml4 == NULL) goto error; process_activate (current); #ifdef VM supplemental_page_table_init (¤t->spt); if (!supplemental_page_table_copy (¤t->spt, &parent->spt)) goto error; #else if (!pml4_for_each (parent->pml4, duplicate_pte, parent)) goto error; #endif /* TODO: Your code goes here. * TODO: Hint) To duplicate the file object, use `file_duplicate` * TODO: in include/filesys/file.h. Note that parent should not return * TODO: from the fork() until this function successfully duplicates * TODO: the resources of parent.*/ if (parent->fdIdx == FDCOUNT_LIMIT) goto error; /*--------------- PROJECT2: User Programs ----------------*/ for (int i = 0; i < FDCOUNT_LIMIT; i++) { struct file *file = parent->fdTable[i]; if (file == NULL) continue; // If 'file' is already duplicated in child, don't duplicate again but share it bool found = false; if (!found) { struct file *new_file; if (file > 2) new_file = file_duplicate(file); else new_file = file; current->fdTable[i] = new_file; } } current->fdIdx = parent->fdIdx; sema_up(¤t->fork_sema); // child load 완료 시 /* Finally, switch to the newly created process. */ if (succ) do_iret (&if_); error: current->exit_status = TID_ERROR; sema_up(¤t->fork_sema); exit(TID_ERROR); } /*--------------- PROJECT2: User Programs ----------------*/ struct thread *get_child_with_pid(int pid) { struct thread *cur = thread_current(); struct list *child_list = &cur->child_list; for (struct list_elem *e = list_begin(child_list); e != list_end(child_list); e = list_next(e)) { struct thread *t = list_entry(e, struct thread, child_elem); if (t->tid == pid) return t; } return NULL; }
exec
- exec(): 자식 프로세스를 생성하고 프로그램을 실행시키는 시스템 콜, 현재 프로세스를 명령어로 입력받은 실행가능 파일로 변경하는 함수이다.
- cmd_line으로 주어진 이름을 가진 실행 파일로 현재 프로세스를 바꾸고 지정된 인자를 전달한다.
- 성공하는 경우 반환하지 않는다.
- 프로그램이 모종의 이유로 로드되지 못하는 경우 -1의 상태로 프로세스를 종료한다.
- 이 함수는 exec를 호출한 스레드의 이름을 바꾸지 않는다.
- file descriptor는 Exec호출 도중에 열려 있음을 유의하자.
- 가장 먼저 인자로 받은 file_name 주소의 유효성을 확인한다
- 이후, palloc_get_page( ) 함수와 strlcpy( ) 함수를 이용하여 file_name을 fn_copy로 복사한다.
- 마지막으로 process_exec( ) 함수를 호출하여 해당 fn_copy를 이용하여 프로세스를 로드하고, 해당 정보를 스택에 쌓아준다.
/* userprog/syscall.c */ /*--------------- PROJECT2: User Programs ----------------*/ int exec(char *file_name){ check_address(file_name); int siz = strlen(file_name) + 1; char *fn_copy = palloc_get_page(PAL_ZERO); if (fn_copy == NULL) exit(-1); strlcpy(fn_copy, file_name, siz); if (process_exec(fn_copy) == -1) return -1; NOT_REACHED(); return 0; }
wait
- wait( ) 함수는 내부에서 process_wait( ) 함수를 호출한다.
- 이때 인자로 전달하는 것은 자식 프로세스의 ID이다.
- 해당 ID를 이용하여 process_wait( ) 함수 내부에서 get_child_with_pid( ) 함수를 호출하며, 이를 통해 자식 프로세스 구조체를 찾는다.
- 이후, 자식 프로세스의 wait_sema를 sema_down( )한다.
- 해당 semaphore는 자식 프로세스가 process_exit( )를 호출할 때 sema_up( )이 이루어진다. (즉, 자식 프로세스가 작업을 모두 마칠 때까지 기다린다는 의미라고 할 수 있다.)
- 작업을 마친 자식 프로세스를 자식 리스트에서 삭제 해주고, 자식 프로세스의 free_sema를 sema_up( ) 한다.
- 이 free_sema 역시, process_exit( ) 과정에서 자식 프로세스가 sema_down( ) 한 것을 변경해주는 것이다.
-
/* userprog/syscall.c */ /*--------------- PROJECT2: User Programs ----------------*/ int wait (tid_t pid){ process_wait(pid); }; /* userprog/process.c */ 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. */ struct thread *cur = thread_current(); struct thread *child = get_child_with_pid(child_tid); if (child == NULL) return -1; sema_down(&child->wait_sema); int exit_status = child->exit_status; list_remove(&child->child_elem); sema_up(&child->free_sema); return exit_status; }
open
- file이 가리키는 문자열 이름의 파일을 연다.
- 파일 디스크립터(fd)또는 -1을 반환한다.
- 파일 디스크립터 0과 1은 콘솔이 미리 예약해두었다.
- STDIN_FILENO(0)
- STDOUT_FILENO(1)
- 각각의 프로세스는 독립적인 파일 디스크립터들을 가진다.
- 파일 디스크립터는 자식에게 상속된다.
- 동일한 파일이 여러번 열리는 경우 각각은 새로운 파일 디스크립터를 갖는다.
- 한 파일에 대한 서로 다른 파일 디스크립터는 독립적으로 다루어져야 한다.
- filesys_open( ) 함수를 이용하여 파일 객체를 생성하도록 하면 된다.
- 파일 디스크립터 테이블이란 프로세스가 현재 사용 중인 파일을 관리하기 위한 테이블로, 프로세스마다 하나씩 가지고 있다.
- 그런데 이는 이전 프로젝트에서 사용하지 않았던 것이다. 따라서 thread 구조체에 새롭게 추가해주어야 한다
- thread 구조체에 fdTable과 해당 테이블을 인덱싱할 때 사용하기 위한 fdIdx를 선언하자.
- 마지막으로 thread를 생성할 때, 해당 파일 디스크립터 테이블과 각종 정보들을 초기화할 수 있도록 tread_create( ) 함수를 수정한다.
/* userprog/syscall.c */ /*--------------- PROJECT2: User Programs ----------------*/ int open(const char *file){ check_address(file); struct file *fileobj = filesys_open(file); if (fileobj == NULL) return -1; int fd = add_file_to_fdt(fileobj); if (fd == -1) file_close(fileobj); return fd; } int add_file_to_fdt(struct file *file){ struct thread *cur = thread_current(); struct file **fdt = cur->fdTable; // file descriptor table if (cur->fdIdx >= FDCOUNT_LIMIT) return -1; fdt[cur->fdIdx] = file; return cur->fdIdx; } /* threads/thread.h */ struct thread { // 중략 struct file **fdTable; int fdIdx; // 중략 }; /* threads/thread.c */ tid_t thread_create (const char *name, int priority,thread_func *function, void *aux) { // 중략 /*--------------- PROJECT2: User Programs ----------------*/ t->fdTable = palloc_get_multiple(PAL_ZERO, FDT_PAGES); if (t->fdTable == NULL) return TID_ERROR; t->fdIdx = 2; // 0: stdin, 1: stdout t->fdTable[0] = 1; // dummy values to distinguish fd 0 and 1 from NULL t->fdTable[1] = 2; t->stdin_count = 1; t->stdout_count = 1; // 중략 }
filesize
- 파일 사이즈를 알기 위해서는 fd가 아닌 파일이 필요하다.
- 입력받은 fd를 이용하여 파일을 찾는 함수 find_file_by_fd( )를 구현해보자.
- 해당 함수를 통해 파일을 구한 이후에는 file_length( ) 함수를 이용하여 사이즈를 리턴하면 된다.
-
/* userprog/syscall.c */ /*--------------- PROJECT2: User Programs ----------------*/ int filesize(int fd){ struct file *fileobj = find_file_by_fd(fd); if (fileobj == NULL) return -1; return file_length(fileobj); } static struct file *find_file_by_fd(int fd){ struct thread *cur = thread_current(); if (fd < 0 || fd >= FDCOUNT_LIMIT) return NULL; return cur->fdTable[fd]; // automatically returns NULL if empty }
read
- read( ) 함수는 인자로 fd, buffer, size를 받는다. buffer가 유효한 주소인지 check_address( ) 함수를 통해 확인한다. 이후, fd를 이용하여 파일 객체를 찾는다.
- 요구 조건에서 fd가 0인 경우, input_getc( ) 함수를 이용하여 키보드 입력을 읽어오라고 하였다.
- 따라서 해당 조건을 구현해주고, 그렇지 않은 경우 file_read( ) 함수를 이용하여 파일을 buffer에 size 크기만큼 읽어온다.
- 이때, 락(lock)을 이용하여 파일을 읽어올 때, 다른 접근을 막아야 한다. 그렇지 않으면 의도와 다른 결과가 발생할 수 있다. (락을 사용하기 위해 해당 구조체를 userprog/syscall.h에 선언해주어야 한다.)
- syscall_init( ) 함수에도 lock_init( ) 함수를 추가해주어야 한다.
-
/* userprog/syscall.c */ /*--------------- PROJECT2: User Programs ----------------*/ void syscall_init(void){ // 중략 lock_init(&file_rw_lock); } int read(int fd, void *buffer, unsigned size){ check_address(buffer); int read_count; struct thread *cur = thread_current(); struct file *fileobj = find_file_by_fd(fd); if (fileobj == NULL) return -1; if (fileobj == STDIN){ int i; unsigned char *buf = buffer; for (i = 0; i < size; i++) { char c = input_getc(); *buf++ = c; if (c == '\0') break; } read_count = i; } else if (fileobj == STDOUT){ read_count = -1; } else{ lock_acquire(&file_rw_lock); read_count = file_read(fileobj, buffer, size); lock_release(&file_rw_lock); } return read_count; } /* userprog/syscall.h */ struct lock file_rw_lock;
write
- 해당 함수는 인자로 fd, buffer, size를 받는다.
- read( ) 함수와 마찬가지로 buffer의 주소값을 확인해주고, fd를 이용하여 파일 객체를 찾는다.
- 파일 객체의 값이 STDOUT과 같으면 putbuf( ) 함수를 이용하여 버퍼에 있는 값을 사이즈 만큼 화면에 출력해준다.
- 그렇지 않다면, 파일 객체에 버퍼에 있는 값을 사이즈 만큼 적어준다.
- 이때도 락을 이용하여 파일에 대한 동시 접근을 제한해야 한다.
-
/* userprog/syscall.c */ /*--------------- PROJECT2: User Programs ----------------*/ int write(int fd, const void *buffer, unsigned size){ check_address(buffer); int read_count; struct file *fileobj = find_file_by_fd(fd); if (fileobj == NULL) return -1; struct thread *cur = thread_current(); if (fileobj == STDOUT){ putbuf(buffer, size); read_count = size; } else if (fileobj == STDIN){ read_count = -1; } else{ lock_acquire(&file_rw_lock); read_count = file_write(fileobj, buffer, size); lock_release(&file_rw_lock); } return read_count; }
seek
- seek( ) 함수의 경우, 인자로 fd와 position을 받는다.
- fd를 이용하여 파일을 찾고, 해당 파일 객체의 pos를 입력 받은 position으로 변경한다.
-
/* userprog/syscall.c */ /*--------------- PROJECT2: User Programs ----------------*/ void seek(int fd, unsigned position){ if (fd < 2) return; struct file *fileobj = find_file_by_fd(fd); if (fileobj == NULL) return; fileobj->pos = position; }
tell
- tell( ) 함수도 seek()함수와 유사하다. fd를 이용하여 파일을 찾고, file_tell( ) 함수에 해당 객체를 인자로 넣어 리턴한다.
-
/* userprog/syscall.c */ /*--------------- PROJECT2: User Programs ----------------*/ unsigned tell(int fd){ if (fd < 2) return; struct file *fileobj = find_file_by_fd(fd); if (fileobj == NULL) return; return file_tell(fileobj); }
close
- 파일을 닫는다는 것은 현재 프로세스의 파일 디스크립터 테이블에서 해당 fd를 제거한다는 의미이다.
- 이를 위해 close( ) 함수 내에 remove_file_from_fdt( ) 함수를 호출해주었다
- 해당 함수를 통해 현재 파일 디스크립터 테이블에서 fd를 인덱스로 하는 값을 NULL로 바꾸어주면 된다.
-
/* userprog/syscall.c */ /*--------------- PROJECT2: User Programs ----------------*/ void close(int fd){ struct file *fileobj = find_file_by_fd(fd); if (fileobj == NULL) return; struct thread *cur = thread_current(); if (fd == 0 || fileobj == STDIN){ cur->stdin_count--; } else if (fd == 1 || fileobj == STDOUT){ cur->stdout_count--; } remove_file_from_fdt(fd); if (fd < 2 || fileobj <= 2) return; } void remove_file_from_fdt(int fd){ struct thread *cur = thread_current(); if (fd < 0 || fd >= FDCOUNT_LIMIT) return; cur->fdTable[fd] = NULL;
참고블로그
https://always-be-wise.tistory.com/164?category=1027194
'OS > Pintos' 카테고리의 다른 글
Project 1,2 정리 (0) 2022.01.11 Pintos 프로젝트 시 도움받은 블로그들! (0) 2022.01.11 Project2 : Argument Passing (0) 2022.01.10 Project2: User Programs(introduction) (0) 2022.01.10 Project1 : Thread - Priority Scheduling(3) (0) 2021.12.30