System Calls 함수 설명
프로세스 관련 시스템 콜
halt()
👉 pintOS 자체를 종료
void halt(){
power_off();
}
exit()
👉 현재 프로세스 종료하는 시스템 콜
void exit(int status){
struct thread *curr = thread_current();
curr->exit_status = status;
printf("%s: exit(%d)\n", curr->name, status);
thread_exit();
}
- 종료 시 "프로세스 이름: exit(status)" 출력
- thread 구조체에 exit_status 필드 추가
- exit_status : 프로세스 종료 유무 확인
- 정상적으로 종료 시 status : 04
fork()
👉 부모 프로세스로 부터 자식 프로세스를 복제
pid_t fork(const char *thread_name){
check_address(thread_name);
return process_fork(thread_name, f);
}
- thread_name : 새로 생성될 자식 프로세스의 이름
- 자식 프로세스에 부모 프로세스의 레지스터 값(
%RBX ~ %R15
)을 복사
- f : 부모의 인터럽트 프레임
- 반환값 : 생성된 자식 프로세스의 pid
- 자식 프로세스는 부모 프로세스의 파일 디스크립터와 가상 메모리 공간을 복제해야한다
- process_fork 함수 source code 및 해석
tid_t
process_fork (const char *name, struct intr_frame *if_ UNUSED) {
struct thread *curr = thread_current();
memcpy(&curr->parent_if, if_, sizeof(struct intr_frame));
tid_t tid = thread_create(name, PRI_DEFAULT, __do_fork, curr);
if (tid == TID_ERROR)
return TID_ERROR;
struct thread *child = get_child_process(tid);
sema_down(&child->fork_sema);
if (child->exit_status == TID_ERROR)
return TID_ERROR;
return tid;
}
- __do_fork 함수 source code 및 해석
static void
__do_fork (void *aux) {
struct intr_frame if_;
struct thread *parent = (struct thread *) aux;
struct thread *current = thread_current ();
struct intr_frame *parent_if;
bool succ = true;
parent_if = &parent->parent_if;
memcpy (&if_, parent_if, sizeof (struct intr_frame));
if_.R.rax = 0;
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
if (parent->next_fd == FDCOUNT_LIMIT)
goto error;
for (int fd = 2; fd < FDCOUNT_LIMIT; fd++) {
struct file *file = parent->fdt[fd];
if (file == NULL)
continue;
current->fdt[fd] = file_duplicate (file);
}
current->next_fd = parent->next_fd;
sema_up(¤t->fork_sema);
if (succ)
do_iret (&if_);
error:
sema_up(&parent->fork_sema);
exit(TID_ERROR);
}
__do_fork
의 인자로 thread_create
를 하면서 4번째 인자로 넣은 curr
(aux) 가 들어가게 된다.
- 자식 프로세스에 부모의 인터럽트 프레임(실행 컨텍스트)를 복사해서 넣어준다.
- 부모 프로세스의 페이지 테이블을 자식 프로세스의 페이지 테이블로 복제한다.(
duplicate_pte
_”filesys/filesys.h”) 사용
- 부모의 fdt와 next_fd를 복사하여 자식 프로세스에게 설정해준다.
- 이후, 부모 프로세스에서 자식 프로세스로의 모든 복사 과정이 완료되었으므로
sema_up
을 수행한다.
- 반드시
process_fork
에서의 세마포어와 동일한 세마포어에 대해 Up 연산을 수행해주어야한다!
do_iret (&if_);
: sema_up
을 진행해서 부모 프로세스가 다시 Ready 상태가 되었으므로 컨텍스트 스위치를 수행한다.
exec()
👉 cmd_line으로 들어온 실행 파일을 실행하는 시스템 콜
int exec(const char *cmd_line){
check_address(cmd_line);
int size = strlen(cmd_line) + 1;
char *fn_copy = palloc_get_page(PAL_ZERO);
if(fn_copy == NULL)
exit(-1);
strlcpy(fn_copy, cmd_line, size);
if(process_exec(fn_copy) == -1)
return -1;
return 0;
}
- 현재 실행중인 프로세스를 cmd_line에 지정된 실행 파일로 변경하고 인수들을 전달
- process_exec 함수 source code 및 해석
int
process_exec (void *f_name) {
char *file_name = f_name;
bool success;
struct intr_frame _if;
_if.ds = _if.es = _if.ss = SEL_UDSEG;
_if.cs = SEL_UCSEG;
_if.eflags = FLAG_IF | FLAG_MBS;
int argc = 0;
char *argv[128];
process_cleanup ();
argument_parse(file_name, &argc, argv);
success = load (file_name, &_if);
if (!success){
palloc_free_page (file_name);
return -1;
}
argument_stack(argc, argv, &_if);
do_iret (&_if);
NOT_REACHED ();
}
wait()
👉
int wait(__pid_t pid){
process_wait(pid);
}
파일 관련 시스템 콜
create()
👉 `file`을 이름으로 하고 크기가 `initial_size`인 새로운 파일 생성
bool create(const char *file, unsigned initial_size){
check_address(file);
return filesys_create(file, initial_size);
}
file
: 생성할 파일의 이름 및 경로 정보
initial_size
: 생성할 파일의 크기
- 파일 생성은 파일 열기X
- 파일 열기는
open
시스템 콜이 수행
remove()
👉 `file` 이름을 가진 파일 삭제
bool remove(const char *file){
check_address(file);
return filesys_remove(file);
}
file
: 제거할 파일의 이름 및 경로 정보
- 파일은 열려있는지 닫혀있는지와 관계없이 삭제될 수 있다
open()
👉 파일을 열 때 사용하는 시스템 콜
int open(const char *file){
check_address(file);
struct file *target_file = filesys_open(file);
if(target_file == NULL)
return -1;
int fd = fdt_add_fd(target_file);
if(fd == -1){
file_close(target_file);
}
return fd;
}
file
: 파일의 이름 및 경로 정보
- 콘솔용으로 예약 되어있는 파일 디스크립터
- 0 : 표준 입력(STDIN_FILENO)
- 1 : 표준 출력(STDOUT_FILENO)
- 하나의 파일이 한번 이상 열릴 때, 각 open 프로세스는 새로운 파일 디스크립터를 반환
filesize()
👉 파일의 크기를 알려주는 시스템 콜
int filesize(int fd){
struct file *target_file = fdt_get_file(fd);
if(target_file == NULL)
return -1;
return file_length(target_file);
}
read()
👉 열린 파일의 데이터를 읽는 시스템 콜
int read(int fd, void *buffer, unsigned size){
int read_bytes = -1;
if(fd == STDIN_FILENO){
int i;
unsigned char *buf = buffer;
for(i = 0; i < size; i++){
char c = input_getc();
*buf++ = c;
if(c == '\0')
break;
}
return i;
}
else{
struct file *file = fdt_get_file(fd);
if(file != NULL && fd != STDOUT_FILENO){
lock_acquire(&filesys_lock);
read_bytes = file_read(file, buffer, size);
lock_release(&filesys_lock);
}
}
return read_bytes;
}
write()
👉 열린 파일에 데이터를 쓰는 시스템 콜
int write(int fd, const void *buffer, unsigned size){
check_address(buffer);
int write_bytes = -1;
if(fd == STDOUT_FILENO){
putbuf(buffer, size);
return size;
}
else{
struct file *file = fdt_get_file(fd);
if(file != NULL && fd != STDIN_FILENO){
lock_acquire(&filesys_lock);
write_bytes = file_write(file, buffer, size);
lock_release(&filesys_lock);
}
}
return write_bytes;
}
buffer
에서 열린 파일 fd
로 size
만큼의 바이트를 쓴다
- 실제로 쓴 바이트 수를 반환
- 일부의 바이트를 쓸 수 없는 경우에는 반환값이
size
보다 작을 수 있다
- 파일 끝까지 가능한 한 많은 바이트를 쓰고, 실제 바이트 수를 반환하거나 쓸 수 없는 경우 0을 반환
- fd 1은
putbuf()
를 이용해서 콘솔에 쓴다
seek()
👉 열린 파일의 위치를 알려주는 시스템 콜
void seek(int fd, unsigned position){
struct file *target_file = fdt_get_file(fd);
if(fd <= STDOUT_FILENO || target_file == NULL)
return;
file_seek(target_file, position);
}
tell()
👉 열린 파일의 위치를 알려주는 시스템 콜
unsigned tell(int fd){
struct file *target_file = fdt_get_file(fd);
if(fd <= STDOUT_FILENO || target_file == NULL)
return;
file_tell(target_file);
}
close()
👉 파일을 닫는 시스템 콜
void close(int fd){
struct file *target_file = fdt_get_file(fd);
if(fd <= STDOUT_FILENO || target_file == NULL || target_file <= 2)
return;
fdt_remove_fd(fd);
file_close(target_file);
}
추가 함수
fdt_add_fd()
👉 file descriptor table에 file descriptor 추가하는 함수
static int fdt_add_fd(struct file *f){
struct thread *curr = thread_current();
struct file **fdt = curr->fdt;
while(curr->next_fd < FDCOUNT_LIMIT && fdt[curr->next_fd]){
curr->next_fd++;
}
if(curr->next_fd >= FDCOUNT_LIMIT)
return -1;
fdt[curr->next_fd] = f;
return curr->next_fd;
}
- file descriptor가 제한 범위를 넘지 않으면서 fdt의 인덱스 위치와 일치할 때
까지 다음 인덱스로 이동하는 것을 반복
- fdt가 가득 차게되면
return -1
- fdt에 인자값으로 받은 fd 삽입하고 다음 fd 인덱스를 반환
fdt_get_file()
👉 file descriptor table에서 인자로 들어온 file descriptor를 검색 후 파일 객체를 반환
static struct file *fdt_get_file(int fd){
struct thread *curr = thread_current();
if(fd < STDIN_FILENO || fd >= FDCOUNT_LIMIT)
return NULL;
return curr->fdt[fd];
}
- fd가 0이하 또는 512이상이면 파일 찾기 실패
fdt_remove_fd()
👉 file descriptor table에서 인자로 들어온 file descriptor를 삭제
static void fdt_remove_fd(int fd){
struct thread * curr = thread_current();
if(fd < STDIN_FILENO || fd >= FDCOUNT_LIMIT)
return;
curr->fdt[fd] = NULL;
}
get_child_process()
👉 자식 리스트를 검색해서 해당 프로세스 디스크립터를 반환
struct thread *get_child_process(int pid){
struct thread *curr = thread_current();
struct list *child_list = &curr->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;
}
process_fork
를 통해 부모 프로세스에서 자식 프로세스를 복제하며 부모 프로세스의 child_list
에 자식 프로세스의 element 값을 넣어준다
- 이후,
process_wait
과 process_fork
의 load
과정 이전에 tid
로 받은 프로세스가 실제 부모의 자식 프로세스가 맞는지 검증하는 과정을 거친다
함수 수정 및 추가
👉 `thread_create()` 함수에 fdt 공간 할당 부분 추가
t->fdt = palloc_get_multiple(PAL_ZERO, FDT_PAGES);
if(t->fdt == NULL)
return TID_ERROR;
t->next_fd = 2;
t->fdt[0] = 1;
t->fdt[1] = 2;
palloc_get_multiple(enum palloc_flags flags, size_t page_cnt)
- source code (주석 포함)
void *
palloc_get_multiple (enum palloc_flags flags, size_t page_cnt) {
struct pool *pool = flags & PAL_USER ? &user_pool : &kernel_pool;
lock_acquire (&pool->lock);
size_t page_idx = bitmap_scan_and_flip (pool->used_map, 0, page_cnt, false);
lock_release (&pool->lock);
void *pages;
if (page_idx != BITMAP_ERROR)
pages = pool->base + PGSIZE * page_idx;
else
pages = NULL;
if (pages) {
if (flags & PAL_ZERO)
memset (pages, 0, PGSIZE * page_cnt);
} else {
if (flags & PAL_ASSERT)
PANIC ("palloc_get: out of pages");
}
return pages;
}
- 연속된 여러 개의 빈 페이지를 할당하고 반환하는 함수
- 인자값인 flags에 따라 사용자 영역이나 커널 영역에서 페이지를 할당하고, 필요에 따라 페이지를 0으로 초기화
STDIN(0)
, STDOUT(1)
은 미리 콘솔을 위해 예약된 fd이므로 next_fd
는 2부터 시작하도록 초기화한다.
fdt[0]
과 fdt[1]
에는 0(NULL)이 아닌 값으로 채워준다.
enum palloc_flags
👉 `process_exit()` 함수 수정
...
palloc_free_multiple(curr->fdt, FDT_PAGES);
file_close(curr->running);
process_cleanup();
sema_up(&curr->wait_sema);
sema_down(&curr->free_sema);
}
- 자식 프로세스의 종료 시, 부모 프로세스에게 자식 프로세스의 종료를 알릴 수 있게끔 세마포어 연산 수행
👉 `syscall_init()` 함수에 락 초기화 부분 추가
lock_init(&filesys_lock);
👉 `thread` 구조체 수정
#ifdef USERPROG
...
int exit_status;
struct file **fdt;
int next_fd;
struct file *running;
struct intr_frame parent_if;
struct list child_list;
struct list_elem child_elem;
struct semaphore fork_sema;
struct semaphore free_sema;
struct semaphore wait_sema;
...
#endif
👉 `init_thread` 함수 수정
...
t->exit_status = 0;
t->running = NULL;
list_init(&t->child_list);
sema_init(&t->wait_sema, 0);
sema_init(&t->fork_sema, 0);
sema_init(&t->free_sema, 0);
}
- 수정한
thread
구조체에 대한 초기화 작업 수행
- 이때 세마포어 값은
0
- 부모 프로세스에서 먼저 호출하기 때문에 부모 프로세스를 대기(wait 상태) 시키기 위해서
0
으로 만들어야함
👉 `load` 함수 수정
static bool
load (const char *file_name, struct intr_frame *if_) {
struct thread *t = thread_current ();
struct ELF ehdr;
struct file *file = NULL;
off_t file_ofs;
bool success = false;
int i;
...
t->running = file;
file_deny_write(file);
...
return success;
}
exec
를 통해 파일을 실행하게 되면 해당 프로세스에 해당하는 파일은 열린 상태로 있으므로 struct thread
의 running 필드에 해당 파일을 추가한다.
process_exit
에서 해당 파일에 대해 닫아주는 작업을 수행한다.
- 또, 현재 실행하기위해 오픈한 파일에 대해 접근하지 못하게 하기 위해
file_deny_write
를 이용해 접근 제한을 걸어준다.
아놔