kernel API를 구현한다. (create(), remove(), open(), close(), fileszize(), read(), write(), seek(), tell())
pintos1에서 구현한 read(), write() file descriptor에 대한 추가적인 수정이 필요하다.
filesys/file.c내의 kernel function을 사용하여 System Call을 구현한다.
실행중인 파일에 write를 deny해준다. 만약, file을 open 하고 write를 시도할 때, 이미 실행중인 파일이라면 write()를 deny해줘야한다.
N개의 process들이 shared data를 사용하려 할 때, 한 process만 해당 process의 critical section에서 실행될 수 있도록 한다. (다른 process들이 이 process의 critical section에 실행되지 못하게 한다.) open, read, write를 정상적으로 실행시키기 위하여 lock을 통해 synchronization을 사용한다. 또한, load가 실패할 경우 child thread를 wait을 한 후 종료한다.
이번 프로젝트의 Syscall function인 write, read 등을 실행시키기 위해서 current thread의 file을 open해야한다. 이 작업을 수행하기 위해 file descriptor을 필요로 한다. file descriptor를 user mode가 아닌 system call을 통한 kernel mode에서 실행시켜 안정성을 높일 수 있도록 구현해야한다.
file system에 관련된 system call인 create(), remove(), open(), close(), fileszize(), read(), write(), seek(), tell()를 구현해야한다. 이 함수들을 구현하여 file system I/O를 kernel mode에서 다룰 수 있도록 만들어준다. 핵심적인 부분은 다수의 process가 file에 open, write 등을 실행 할 수 있는데, 각각의 실행을 방해할 수 없도록 해야한다.
여러개의 process들이 shared data를 사용하는 critical section에 대하여 lock을 통해 오직 한 process만이 접근할 수 있도록 만들어줘야한다. 또한, file_deny_write을 사용하여 memory load 후 실행중인 file에 write하는 행위를 거부해야한다. 마지막으로, child thread가 생성되었을때 parent thread가 child thread의 load를 sema_down을 통해 기다려주다가 load가 fail하거나 성공한 경우 sema_up 해줌으로써 load가 실패한 경우 process_wait 후 정상적으로 종료될 수 있게 해준다.
pintos manual에 명시되어있는 maximum file 개수인 128개의 file* struct 배열을 선언했다.
2-1. create
이름을 const char *file이고, size는 initail_size를 갖는 file을 생성한다. 이 때 만들어지는 file은 open함수와의 상호작용이 없다.
2-2. remove
const char *file과 일치하는 이름의 file을 remove 한다. 이 때 remove되는 file은 open, close와 상호작용이 없다.
2-3. open
read, write와 같은 file system call을 실행하기 전에 이뤄지는 system call이다. fd number에 해당하는 file descriptor가 null일때 file struct를 file descriptor 배열에 넣어준다.
2-4. close
fd number에 해당하는 file descriptor의 file이 NULL이 아닐 때, 해당하는 file descriptor 배열을 close 한 후 NULL로 바꿔준다.
2-5. filesize
fd number에 해당하는 file descriptor의 file에 사이즈를 return 해준다.
2-6. read
fd number에 해당하는 file descriptor의 file에 buffer를 read한다. 0일때는 STDIN을 읽어주고, 1, 2일때는 error 처리, 3이상일때 file descriptor를 통해 읽어준다.
2-7. write
fd number에 해당하는 file descriptor의 file에 buffer를 write한다. 1일때는 STDOUT을 write 해준다. 0, 2일때는 error 처리, 3이상일때 file descriptor를 통해 write 해준다.
2-8. seek
fd number에 해당하는 file descriptor의 file에서 position 위치의 offset을 변경해준다.
2-9 tell
fd number에 해당하는 file descripter의 file에서 해당 position으로부터 next byte를 read, write 하기 위해서 next byte의 position을 return한다.
lock은 특정 thread가 해당 lock을 소유했을 때, 다른 thread는 그 lock을 획득할 수 없으며, 이미 lock을 소유하고 있는 thread가 다시 그 lock을 획득하려고 시도하면 오류가 발생한다. 즉, lock은 0과 1로 이루어진 semaphore의 종류 중 하나라고 볼 수 있다.
3-1. open, read, write (lock)
(open-write), (read-write)는 shared data를 사용할 때 1개의 process의 접근만 허가한다. 다시 말해, critical section에 대한 처리가 필요하다. read-open사이는 critical section을 따로 처리해주지 않아도 된다고 생각하였다. 따라서, open-write그리고 read-write 사이에 lock을 사용하였다. lock_acquire을 통해 semaphore 값을 1로 만들어주거나 만약 semaphore값이 0인 상태라면 기다려준다. 즉, 만약 processes들이 open을 할 때 open-write의 lock을 걸어주어 1개의 file에 대하여 1번씩만 open이 가능하게 만들어준다. (open critical section) 이후 file에 write를 할 때 역시 lock_acquire을 사용하여 한 파일에 write를 한 process만 할 수 있게 해주었다. (write ciritical section). 마지막으로, read와 write 사이에 lock을 걸어줌으로써 파일을 읽고 있을 때는 쓸 수 없고, 쓰고 있을 때는 읽을 수 없게 만들어주었다.
3-2. parent thread와 child thread 간의 load (semaphore)
parent thread는 child thread의 create을 기다려줘야하기 때문에 process_execute에서 semaphore lock_load를 통해 해결해주었다. lock_load를 sema_init(0) [lock이 걸린 상태]로 통해 초기화 해준 후, parent thread가 child thread를 create 했을 때 sema_up을 통해 semaphore값을 높여준다. 즉, child thread의 load를 대기하게 만들어준다. 다시 말해 start_process에서 load를 실행시켜줬을때 성공하면 sema_up을 해주고 실패한다면 thread.c 에서 선언했던 load_success true bool값을 0으로 바꿔준 후 sema_up 을 해준다. semaphore값이 1이 되었을 때 sema_down을 통해 parent thread가 실행될 수 있도록 해준다. 즉, parent thread가 실행될 수 있어질 때 child thread를 일일히 확인해가며 load를 실패한 child process 들을 process_wait을 통해 reaping 해준다.
10.10 ~ 10. 15 : File Descriptor 자료구조 구현(pintos manual 분석)
10.15 ~ 10. 20 : System call 구현
10.20 ~ 10. 26 : Synchronization 구현, 다양한 test case issue 처리
B-1. process_execute
child thread의 load를 parent thread가 기다릴 수 있도록 sema_down을 사용하였다. 또한, load가 실패하였을때 기존에는 thread_exit으로 종료시켜주었지만, 만약 child thread가 load를 실패하였을때 success_load boolean 값을 false로 바꿔주며 parent thread가 child thread들의 load가 끝난 후 reaping 할 수 있도록 수정하였다. 즉, parent thread가 child thread보다 먼저 종료되는 상황을 방지해주었다.
B-2. start_process
load가 성공하면 sema_up을 호출하고, 실패해도 success_load의 bool 값을 바꿔준 후 sema_up을 호출하게 만들어 줌으로써 parent가 child보다 먼저 종료되지 않게 해주었다.
B-3. exit
thread file을 종료하려할 때, close 해주도록 수정하였다.
B-4. read
기존의 STDIN에 대한 처리 뿐 만 아니라 fd가 3 이상일 경우에 file read가 가능하게 수정하였다.
B-5. write
기존의 STDOUT에 대한 처리 뿐 만 아니라 fd가 3 이상일 경우에 file read가 가능하게 수정하였다.
B-6. syscall_handler()
pintos2의 새로 추가된 system call을 다루기 위해서 새로운 syscall number에 대한 function call이 가능하도록 수정하였다.
B-7. syscall_init
file descripter의 critical section을 다뤄주기 위해서 lock_read_write, lock_open_write를 추가하였다.
B-8. init_thread
process_execute, start_process에서 child load를 다루기 위해서 lock_load를 sema_init()을 통해 0으로 초기화 하였다.
B-9. struct_thread
process_execute, start_process에서 child load를 lock하기 위해서 struct semaphore lock_load 추가했고, parent thread를 정의(struct thread parent)해 주었으며, child의 load의 성공 여부를 확인하는 bool success_load을 추가해주었다.
또한, strcut file file_d[128]을 추가해 줌으로써 open한 fd number에 해당하는 file을 point 가 가능하게 해주어 file system call들을 다룰 수 있게 만들어주었다.
create : file create가 가능하게 해주었다.
remove : file remove가 가능하게 해주었다.
open : file open이 가능하게 해주었다.
close : file close가 가능하게 해주었다.
filesize : file size를 return이 가능하게 해주었다.
seek : file position 변경이 가능하게 해주었다.
tell : file의 next byte position의 return이 가능하게 해주었다.

System Call

Synchronization

fild descriptor 구현을 위해 128 size의 file struct 배열을 만들어 주었다.
thread, process들은 각자의 file descriptor를 가진다. thread.c 에서 file struct 배열을 fd 3부터 NULL로 초기화했다. (fd 0은 STDIN, fd 1은 STDOUT, fd 2는 STDERR이기 때문이다.)
2-1. create
넘겨 받은 file이 존재하지 않다면 exit(-1)을 호출한다. 만약, 파일이 존재한다면 filesys_create(kernel API)함수를 호출하여 root directory에 파일을 생성한다.
2-2. remove
넘겨 받은 file이 존재하지 않다면 exit(-1)을 호출한다. 만약, 파일이 존재한다면 filesys_remove(kernel API)함수를 호출하여 root directory에서 파일을 지운다.
2-3. open
넘겨 받은 파일이 존재하지 않다면 exit(-1)를 호출한다. filesys_open(Kernel API)를 호출할 때 open 과 write 사이에 사용되는 lock을 걸어준다. 0(STDIN), 1(STDOUT), 2(STDERR)을 제외하고 3부터 file descriptor의 최대 크기인 128까지 fd number를 1씩 더해가며 NULL인 곳을 찾는다. 만약, NULL이지만 실행되고 있는 파일이 있다면 접근할 수 없도록 file_deny_write를 호출해준다. 위 작업을 모두 마친 후 filesys_open을 통해 open된 file을 찾아낸 file descriptor와 연결해준다.
2-4. close
fd number에 해당하는 file descriptor가 NULL이라면 exit(-1)을 통해 오류임을 알린다. 만약, NULL이 아니라면 file_close(Kernel_API)를 호출하여 open file을 close 해준다. 마지막으로 close된 file descriptor에 다시 열릴 수 있도록 NULL을 할당한다.
2-5. filesize
fd number에 해당하는 file descripor가 NULL이라면 마찬가지로 exit(-1)을 호출한다. 만약, NULL이 아니라면 file_length(Kernel API)를 호출하여 file의 size를 return 해준다.
2-6. read
넘겨 받은 파일이 존재하지 않다면 exit(-1)를 호출하는 과정은 open에서 선행되기 때문에 해줄 필요가 없다. read와 write 사이에 critical section의 침범 오류를 방지하기 위해서 lock_read_write를 사용하였다. read가 시작되기 전에 lock_acquire로 lock 해준다.fd number가 0이라면 STDIN이기 때문에 buffer을 통해 read하고 lock_release로 lock을 풀어준다. 1, 2는 각각 STDOUT, STDERR이기 때문에 lock을 풀어주고 return 해준다. 하지만, fd number가 3이상일 때는 fd number에 해당하는 file descriptor를 가져와주고 해당 file descriptor의 fd 가 NULL이라면 read 없이 lock을 풀어주고 exit(-1) 해준다. read할 파일이 있다면 fild_read(Kernal API)를 사용하여 file을 읽어준다. 각각의 return이나 exit(-1) 앞에 lock_release(&lock_read_write)를 붙여줌으로써, read와 write는 critical section을 침범하지 않게 해주지만 open과는 동시에 일어날 수 있도록 해주었다.
2-7. write
넘겨 받은 파일이 존재하지 않다면 exit(-1)를 호출하는 과정은 open에서 선행되기 때문에 해줄 필요가 없다. (read와 write) 그리고 (open과 write) 사이에 critical section의 침범 오류를 방지하기 위해서 lock_read_write와 lock_open_write를 사용하였다. write가 시작되기 전에 lock_acquire로 read와 write 사이 그리고 open과 write 사이를 lock 해준다.fd number가 1이라면 STDOUT이기 때문에 buffer을 통해 write하고 lock_release로 lock을 모두 풀어준다. 0, 2는 각각 STDIN, STDERR이기 때문에 lock을 풀어주고 return 해준다. 하지만, fd number가 3이상일 때는 fd number에 해당하는 file descriptor를 가져와주고 해당 file descriptor의 fd 가 NULL이라면 write 없이 lock을 모두 풀어주고 exit(-1) 해준다. write할 파일이 있다면 file_write(Kernal API)를 사용하여 file을 읽어준다. 각각의 return이나 exit(-1) 앞에 lock_release(&lock_read_write), lock_release(&lock_open_write)를 붙여줌으로써, (read와 write) 그리고 (open과 write)는 critical section을 침범하지 않게 해주었다.
2-8. seek
fd number에 해당하는 file descripor가 NULL이라면 exit(-1)을 호출한다. 만약, NULL이 아니라면 file_seek(Kernel API)를 호출하여 pos를 position으로 만들어준다.
2-9. tell
fd number에 해당하는 file descripor가 NULL이라면 exit(-1)을 호출한다. 만약, NULL이 아니라면 file_tell(Kernel API)를 호출하여 pos의 next byte를 return 해준다.
3-1. open, read, write Synchronization(lock)
위에서 설명한 open과 write 그리고 read와 write에 synchronization을 활용하기 위해서 syscall_init안에서 lock_read_write와 lock_open_write를 lock_init을 통해 초기화 해주었다.
3-2. lock_load(semaphore)
parent thread는 child thread의 create을 기다려줘야하기 때문에 process_execute에서 semaphore lock_load를 통해 해결해주었다. thread.c에서 thread.h에 선언한 lock_load를 sema_init(0) [lock이 걸린 상태]로 통해 초기화 해준 후, parent thread가 child thread를 create하고 load할 때 sema_up을 통해 semaphore값을 높여준다. 즉, parent thread의 process_wait를 대기하게 만들어준다. 다시 말해 start_process에서 load를 실행시켜줬을때 성공하면 sema_up을 해주고 실패한다면 thread.c 에서 선언했던 load_success true bool값을 0으로 바꿔준 후 sema_up 을 해준다.(load fail한 child thread를 wait 해주기 위해) semaphore값이 1이 되었을 때 sema_down을 통해 parent thread가 실행될 수 있도록 해준다. 즉, parent thread가 실행될 수 있어질 때 child thread를 일일히 확인해가며 load를 실패한 child process 들을 process_wait을 통해 reaping 해준다.
- exception.c
bad-jump, bad-write, bad-read test fail을 해결하기 위해서 exception.c if 조건문에 not_present를 추가하였다. mapping이 안된 주소를 page fault handler를 통해 처리함으로써 위 test case를 통과할 수 있었다.