여기서부터는 system call을 구현할 예정이다
(halt, exit, wait, close, create, remove, seek, tell)
syscall을 구현하기 전에 pml4 에 빨간줄이 계속 생겨서 불편하다면 아래와 같은 방법으로 해결하면된다. (Ctrl + shift + P) C/C++: 구성편집 UI로 들어간다
--> 이후 정의 탭에 USERPROG를 쓰고 꺼주면된다 (자동저장)
수정할 파일 : syscall.c, sycall.h, thread.c, thread.h, exception.c
먼저 system_call handler 에서 받은 frame 정보를 구분해준다 (switch를 사용하기 위해)
이후 switch 문을 만들어서 들어오는 syscall_no을 통해서 함수를 태운다
switch (syscall_no) { // return 을 시켜주는 handler들은 f->R.rax로 return을 시켜줘야함 (다음 syscall_no을 동작시켜야함)
case SYS_HALT: // poawer off system call
halt_handler ();
break;
case SYS_EXIT: // exit system call
exit_handler (a1);
break;
case SYS_FORK: // fork system call - 자식 process를 만든다
f->R.rax = fork_handler (a1, f);
break;
case SYS_EXEC: // exec system call
f->R.rax = exec_handler (a1);
break;
case SYS_WAIT:
f->R.rax = wait_handler (a1);
break;
case SYS_CREATE:
f->R.rax = create_handler (a1, a2);
break;
case SYS_REMOVE:
f->R.rax = remove_handler (a1);
break;
case SYS_OPEN:
f->R.rax = open_handler (a1);
break;
case SYS_FILESIZE:
f->R.rax = file_size_handler (a1);
break;
case SYS_READ:
f->R.rax = read_handler (a1, a2, a3);
break;
case SYS_WRITE:
f->R.rax = write_handler (a1, a2, a3);
break;
case SYS_SEEK:
seek_handler (a1, a2);
break;
case SYS_TELL:
f->R.rax = tell_handler (a1);
break;
case SYS_CLOSE:
close_handler (a1);
break;
default:
exit_handler (-1);
break;
}
먼저 선언하는 두가지 함수는 지속적으로 handler 를 구현하면서 지속적으로 사용하게됨
1. check_add -- input 받은 address가 user 영역인지 확인하는 작업
==> gitbook의 User Memory Access 와 관련된 부분
void
check_add (void *add) {
struct thread *cur = thread_current ();
if (!is_user_vaddr (add) || add == NULL ||
pml4_get_page (cur->pml4, add) == NULL) {
exit_handler (-1);
}
}
static struct file *
find_file_using_fd (int fd) { // fd를 가지고 file을 찾기 위한 함수 file은
// process에 저장되어있으니 찾는거 가능
struct thread *cur = thread_current ();
if (fd < 0 || fd >= FD_COUNT_LIMT)
return NULL;
return cur->fd_table[fd]; // 해당 file을 return 함
}
// syscall.h
struct lock filesys_lock; // write 사용시에 나만 작성하기 위해서 lock을 사용
syscall_init() 안에서 lock을 init 해줌
lock_init (&filesys_lock); // write 시에 다른 접근이 오는걸 막기위해 lock을 사용
//thread.h - struct thread 안에
int exit_status; // 현재 파일의 status를 확인하기 위해서
struct file **fd_table; // 프로세서는 파일 디스크립터를 관리하는 테이블이 필요함
int fd_idx; // 그리고 그 파일 디스크립터 테이블에 들어가는 파일 디스크립터의 인덱스를 저장
struct list child_s; // 부모가 자식 process의 정보를 기억할 공간
struct list_elem child_elem; // child list elem
struct intr_frame parent_if; // 부모의 intr_frame 정보를 기억할 공간
struct semaphore fork_sema; // 자식이 fork가 완료 될때까지 기다리는 sema
struct semaphore wait_sema; // process 가 동작중일땐 wait에서 대기를 해야만함 --> exit가 들어오기 전까지는 계속 대기
struct semaphore exit_sema; // exit handler를 위한 sema --> 종료가 되기전에 wait와 fork 쪽이 먼저 정리가 되어야하니깐
struct file *running; // rox를 위해 사용할 공간
// thread.c --> init_thread() 함수 안에
/* for project -2 start */
// project 2 에서 선언된 애들을 init 해줌
list_init(&t->child_s);
sema_init(&t->fork_sema,0);
sema_init(&t->wait_sema,0);
sema_init(&t->exit_sema,0);
/* for project -2 end */
// thread.c --> tid_t thread_create() 안에
// 2중 포인터 부분은 init 함수를 사용하지 못하기 때문에 fd_table 아래와 같이 새로 할당 받음
t->fd_table = palloc_get_multiple(PAL_ZERO, FD_PAGES);
if (t->fd_table == NULL)
return TID_ERROR;
// 0, 1 두개의 공간은 정해진 역할을 해야함 따라서 index (fd의 개수) 는 2부터 시작함
t->fd_table[0]=1; // stdin 자리
t->fd_table[1]=2; // stdout 자리
t->fd_idx = 2;
struct thread *cur = thread_current();
list_push_back(&cur->child_s, &t->child_elem); // create 할 때 자기 자신을 자식 process list에 넣어줌
cur->exit_status =0; // 종료 status를 초기 0으로 초기화
gitbook 내용
0번 파일식별자와 1번 파일식별자는 이미 역할이 지정되어 있습니다. 0번은 표준 입력(STDIN_FILENO)을 의미하고 1번은 표준 출력(STDOUT_FILENO)을 의미합니다.
pintos 자체를 종료시키는 부분 (power_off() 는 사전에 구현된 함수)
//syscall.c
void
halt_handler (void) { // Pintos를 종료시킴
power_off ();
}
//syscall.c
int
wait_handler (tid_t pid) { // 자식 process를 wait 함 (exit status가 올때까지)
return process_wait (pid);
}
//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 *child = get_child (child_tid); // 해당 tid를 갖는 child를 찾고
if (child == NULL)
return -1;
sema_down (&child->wait_sema); // process 의 wait를 sema down함 --> process는 현재 동작 중이고 wait에서 대기중임
list_remove (&child->child_elem); // 얘를 삭제한다는 것은 process exit 신호가 들어왔음을 의미함 (process_exit에서 sema up을 시켜줌)
sema_up (&child->exit_sema); // 삭제 후 sema_up으로 child exit_sema도 풀어줌
return child->exit_status;
}
get_child는 fd를 이용하여 child를 찾고 해당 child를 thread 형태로 return 함
struct thread *
get_child (int pid) { // child process를 찾는 함수 -- child list를 관리하니깐
// 거기서 찾으면됨
struct thread *cur = thread_current ();
struct list *child_list = &cur->child_s; // 현재 process의 자식 process list
for (struct list_elem *temp = list_begin (child_list); temp != list_end (child_list); temp = list_next (temp)) { // 자식 process의 list를 순회하면서 자식는 부분
struct thread *temp_thread = list_entry (temp, struct thread, child_elem);
if (temp_thread->tid == pid) {
return temp_thread;
}
}
return NULL; // 만일 적합한 child를 찾지 못했다면 NULL 을 return함
}
file_close 는 사전에 구현되어 있는 함수
//syscall.c
void
close_handler (int fd) { // fd를 이용하여 열려 있는 file을 닫음
struct file *file_obj = find_file_using_fd (fd);
if (file_obj == NULL)
return;
if (fd < 0 || fd >= FD_COUNT_LIMT)
return;
thread_current ()->fd_table[fd] = NULL; // 열린 파일이 있던 위치를 NULL로 바꾸고
lock_acquire (&filesys_lock);
file_close (file_obj);
lock_release (&filesys_lock);
}
process를 종료시킨다 -- 하지만 자식 process가 살아있다면 자식 process가 종료될 때까지 기다려야함
//syscall.h
void
exit_handler (int status) { // 현재 동작중인 program을 종료함
struct thread *cur = thread_current ();
cur->exit_status = status; // exit status를 저장해주고 종료
printf ("%s: exit(%d)\n", cur->name, status);
thread_exit (); // thread exit 안에 process exit가 있음
}
//process.c
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. */
for (int i = 2; i < FD_COUNT_LIMT; i++) // process가 끝나는 신호가 들어오면 해당 process에서 열려있던 file들을 모두 닫아줌
close_handler (i);
palloc_free_multiple (curr->fd_table, FD_PAGES); // thread create때 할당 받은 공간을 다시 free해줌
file_close (curr->running); // ROX 를 위한 부분 (write deny가 된걸 file close 해주면 해당 함수 안에 allow로 해제 해주는 부분도 존재함)
process_cleanup (); // 현재 process의 자원도 해제해줘야함 --> 이게 sema down 밑으로 가면 sema _down에서 동작하지 않아서 multi-oom 이 실패함
sema_up (&curr->wait_sema); // exit 신호를 받았기 때문에 wait_sema를 올려주고 wait에 걸려있던 process가 종료하게됨
sema_up (&curr->fork_sema); // fork_sema 도 해제해줌 --> 다 종료해야하니깐 (sema가 걸려있으면 삭제도 종료도 안되니깐)
sema_down (&curr->exit_sema); // 이후 exit는 wait가 끝난 이후에 종료가 되어야하니깐 exit sema를 down해서 걸어놓음
}
filesys_create 는 사전에 구현되어 있는 함수
// syscall.c
bool // create 성공하면 true 실패하면 false, ++ open 이랑은 다른것, 혼란스러워하면 안됨
create_handler (const char *file, unsigned initial_size) {
check_add (file);
return filesys_create (file, initial_size);
}
filesys_remove 는 사전에 구현되어 있는 함수
//syscall.c
bool // remove 성공하면 true 실패하면 false, ++ file이 remove되는 것과 close는 별개임 (즉, 제거되더라도 open되어있을 수도 있다는 말)
remove_handler (const char *file) {
check_add (file);
return (filesys_remove (file));
}
file_seek 는 사전에 구현되어 있는 함수
//syscall.c
void
seek_handler (int fd, unsigned position) { // file 내부의 커서를 이동한다 --> 첫 시작은 position이 0이고 끝까지 가면 file의 크기와 같은 byte를 가짐
struct file *file_obj = find_file_using_fd (fd);
file_seek (file_obj, position); // 만약 position이 파일의 끝을 가리키고 있는데 더 position을
// 찾는걸 요청한다면 커서는 파일 끝을 가리키지만 byte는 읽은
// 바이트가 없기 때문에 0을 나타냄
}
file_tell 은 사전에 구현되어 있는 함수
//syscall.c
unsigned
tell_handler (int fd) { // file의 read or write를 하기 위한 위치를 반환함 - byte로
if (fd <= 2)
return;
struct file *file_obj = find_file_using_fd (fd);
check_add (file_obj);
if (file_obj == NULL)
return;
return file_tell (file_obj);
}