intOS | Project 2 | 시스템콜 구현 (halt, exit, exec, create, remove, open, filesize)
pintOS | Project 2 | 시스템콜 구현 (fork, wait, read, write, seek, tell, close)
지난 주에 이어서 마지막 Extend File Descriptor(Extra) 부분 도전!
extra 부분을 테스트하기 위해서는 uesrprg/Make.vars 라는 파일에서 몇 가지 수정을 해야 한다.
# -*- makefile -*-
os.dsk: DEFINES = -DUSERPROG -DFILESYS
KERNEL_SUBDIRS = threads tests/threads tests/threads/mlfqs
KERNEL_SUBDIRS += devices lib lib/kernel userprog filesys
TEST_SUBDIRS = tests/userprog tests/filesys/base tests/userprog/no-vm tests/threads
GRADING_FILE = $(SRCDIR)/tests/userprog/Grading.no-extra
# Uncomment the lines below to submit/test extra for project 2.
TDEFINE := -DEXTRA2
TEST_SUBDIRS += tests/userprog/dup2
GRADING_FILE = $(SRCDIR)/tests/userprog/Grading.extra
기존 파일은 가장 밑에 3줄의 코드의 앞자리에 #이 적혀 있다(주석처리 되어 있다). 그래서 저 세 줄의 앞에 #을 지워야 extra부분 테스트를 할 수 있다.
현재 pintos는 stdin과 stdout의 fd를 닫는 것이 구현되어 있지 않다. 즉 사용자가 stdin, stdout을 close할 수 있도록 구현해야 한다. 추가 설명을 하자면 stdin을 닫으면 프로세스가 입력을 읽어선 안 되고, stdout을 닫으면 프로세스가 출력되지 않아야 한다.
먼저 시스템 콜 핸들러에 dup2함수를 추가한다.
/* The main system call interface */
void syscall_handler (struct intr_frame *f) {
// TODO: Your implementation goes here.
char *fn_copy;
/*
x86-64 규약은 함수가 리턴하는 값을 rax 레지스터에 배치하는 것
값을 반환하는 시스템 콜은 intr_frame 구조체의 rax 멤버 수정으로 가능
*/
switch (f->R.rax) { // rax is the system call number
case SYS_HALT:
halt();
break;
case SYS_EXIT:
exit(f->R.rdi);
break;
case SYS_FORK:
f->R.rax = fork(f->R.rdi, f);
break;
case SYS_EXEC:
if (exec(f->R.rdi) == -1) {
exit(-1);
}
break;
case SYS_WAIT:
f->R.rax = process_wait(f->R.rdi);
break;
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;
case SYS_OPEN:
f->R.rax = open(f->R.rdi);
break;
case SYS_FILESIZE:
f->R.rax = filesize(f->R.rdi);
break;
case SYS_READ:
f->R.rax = read(f->R.rdi, f->R.rsi, f->R.rdx);
break;
case SYS_WRITE:
f->R.rax = write(f->R.rdi, f->R.rsi, f->R.rdx);
break;
case SYS_SEEK:
seek(f->R.rdi, f->R.rsi);
break;
case SYS_TELL:
f->R.rax = tell(f->R.rdi);
break;
case SYS_CLOSE:
close(f->R.rdi);
break;
case SYS_DUP2: // project2 - extra
f->R.rax = dup2(f->R.rdi, f->R.rsi);
break;
default:
exit(-1);
break;
}
}
그리고 dup2 함수를 다음과 같이 구현한다.
// ../userprog/syscall.c
// dup2함수: 식별자 테이블 엔트리의 이전 내용을 덮어써서 식별자 테이블 엔트리 oldfd를 newfd로 복사
int dup2(int oldfd, int newfd) {
struct file *file_fd = find_file_by_fd(oldfd);
if (file_fd == NULL) {
return -1;
}
if (oldfd == newfd) {
return newfd; // oldfd == newfd 이면 복제하지 않고 newfd 리턴
}
struct thread *cur = thread_current();
struct file **fdt = cur->fd_table;
if (file_fd == STDIN) {
cur->stdin_count++;
}
else if (file_fd == STDOUT) {
cur->stdout_count++;
}
else {
file_fd->dup_count++;
}
close(newfd);
fdt[newfd] = file_fd;
return newfd;
}
dup2() 함수는 기존의 파일 디스크립터 oldfd를 새로운 newfd로 복제하여 생성하는 함수다. 만약 newfd가 이전에 열렸다면, 재사용하기 전에 자동으로 닫힌다.
oldfd가 불분명하면 이 시스템 콜은 실패하며 -1을 리턴, newfd는 닫히지 않는다.
oldfd가 명확하고 newfd가 oldfd와 같은 값을 가진다면, dup2() 함수는 실행되지 않고 newfd값을 그대로 반환한다.
stdin_count와 stdout_count가 의미하는 것은 표준입/출력인 fd가 여러 개인 경우를 고려하여 만든 변수이다. 즉 입출력 fd가 0이 될 때까지 close를 닫으면 안 된다.
이를 위해서 먼저 선행되어야 하는 것이 있다. thread.c에서 thread_create()할 때, stdin_count, stdout_count의 디폴트값1을 설정해야 한다. 또한 이 변수들은 thread.h의 thread 구조체 안에 만든다.
// ..threads/thread.h
struct thread {
...
int stdin_count;
int stdout_count;
...
}
// ..threads/thread.c
tid_t thread_create (const char *name, int priority, thread_func *function, void *aux) {
...
t->stdin_count = 1;
t->stdout_count = 1;
...
}
추가로 fd table의 표준입력과 표준출력에 더미의 값을 넣는다. 이는 read, write, close, dup2 시스템 콜을 사용할 때, 표준 입력, 표준 출력을 구분하기 위한 장치로 쓰인다. 임의로 1, 2값을 추가하겠다.
// ..threads/thread.c
tid_t thread_create (const char *name, int priority, thread_func *function, void *aux) {
...
t->fd_table[0] = 1; // dummy values to distinguish fd 0 and 1 from NULL
t->fd_table[1] = 2;
t->stdin_count = 1;
t->stdout_count = 1;
...
}
표준입,출력 fd 테이블의 값과 같은 값을 가진 변수를 syscall.c에서 만들어준다.
// ../userprog/syscall.c
...
// for dup2
const int STDIN = 1;
const int STDOUT = 2;
...
그리고 표준 입,출력을 가리키는 fd가 아닌 경우도 있으니 dup_count변수를 만들어야 한다. 이는 file.h의 file 구조체에 추가하해 준다.
// ../include/filesys/file.h
/* An open file. */
struct file {
struct inode *inode; /* File's inode. */
off_t pos; /* Current position. */
bool deny_write; /* Has file_deny_write() been called? */
int dup_count; // 0일 때만 close()
};
dup_count를 0으로 디폴트한다.
// ../filesys/file.c
struct file *file_open (struct inode *inode) {
struct file *file = calloc (1, sizeof *file);
if (inode != NULL && file != NULL) {
...
file->dup_count = 0; // project2 - extra
return file;
}
...
}
이제 read() 함수부터 수정 ㄱㄱ
// ../userporg/syscall.c
// open된 파일의 사이즈를 읽는 함수
int read(int fd, void *buffer, unsigned size) {
check_address(buffer);
int read_result;
struct thread *cur = thread_current();
struct file *file_fd = find_file_by_fd(fd);
// Modified read func for dup2
if (file_fd == NULL) {
return -1;
}
if (file_fd == STDIN) {
if (cur->stdin_count == 0) { // stdin_count가 비정상적인 경우
NOT_REACHED();
remove_file_from_fdt(fd);
read_result = -1;
}
else {
int i;
unsigned char *buf = buffer;
for (i = 0; i < size; i++) {
char c = input_getc();
*buf++ = c;
if (c == '\0') {
break;
}
}
read_result = i;
}
}
else if (file_fd == STDOUT) { //raed에서 입출력fd일 경우 -1 리턴
read_result = -1;
}
else {
lock_acquire(&filesys_lock);
read_result = file_read(file_fd, buffer, size);
lock_release(&filesys_lock);
}
return read_result;
}
write() 수정
// ../userporg/syscall.c
// buffer로부터 사이즈 쓰기
int write(int fd, const void *buffer, unsigned size) {
check_address(buffer);
int write_result;
struct file *file_fd = find_file_by_fd(fd);
if (file_fd == NULL) {
return -1;
}
struct thread *cur = thread_current();
if (file_fd == STDOUT) {
if (cur->stdout_count == 0) { // stdout_count가 비정상적인 경우
NOT_REACHED();
remove_file_from_fdt(fd);
write_result = -1;
}
else {
putbuf(buffer, size);
write_result = size;
}
}
else if (file_fd == STDIN) { //write에서 표준입력fd인 경우 -1 리턴
write_result = -1;
}
else {
lock_acquire(&filesys_lock);
write_result = file_write(file_fd, buffer, size);
lock_release(&filesys_lock);
}
return write_result;
}
close() 수정
// ../userporg/syscall.c
// 열린 파일을 닫는 시스템 콜. 파일을 닫고 fd제거
void close(int fd) {
struct file *close_file = find_file_by_fd(fd);
// Modified write func for dup2
if (close_file == NULL) {
return;
}
struct thread *cur = thread_current();
if (fd == 0 || close_file == STDIN) { // fd가 표준입력인 경우
cur->stdin_count--;
}
else if (fd == 1 || close_file == STDOUT) { // fd가 표준출력인 경우
cur->stdout_count--;
}
remove_file_from_fdt(fd);
if (fd <= 1 || close_file <= 2) {
return;
}
if (close_file->dup_count == 0) {
file_close(close_file);
}
else {
close_file->dup_count--;
}
}
이제 다 구현이 됐다! 고 생각하고 테스트를 돌려봤는데, 'dup2-complex'라는 테스트파일이 계속 fail된다. 이를 해결하기 위해서는 process.c의 __do_fork() 함수를 건드려야 한다.
// ..userprog/process.c
// Project2-extra
struct MapElem
{
uintptr_t key;
uintptr_t value;
};
/* A thread function that copies parent's execution context.
* Hint) parent->tf does not hold the userland context of the process.
* That is, you are required to pass second argument of process_fork to
* this function. */
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_) */
struct intr_frame *parent_if;
bool succ = true;
parent_if = &parent->parent_if;
/* 1. Read the cpu context to local stack. */
memcpy (&if_, parent_if, sizeof (struct intr_frame));
if_.R.rax = 0; // fork return value for child
/* 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)) // to copy entire user memory space including corresponding pagetable structures
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.*/
// multi-oom) Failed to duplicate
if (parent->fd_idx == FDCOUNT_LIMIT)
goto error;
// Project2-extra) multiple fds sharing same file - use associative map (e.g. dict, hashmap) to duplicate these relationships
// other test-cases like multi-oom don't need this feature
const int MAPLEN = 10;
struct MapElem map[10]; // key - parent's struct file * , value - child's newly created struct file *
int dup_count = 0; // index for filling map
for (int i = 0; i < FDCOUNT_LIMIT; i++) {
struct file *file = parent->fd_table[i];
if (file == NULL)
continue;
// If 'file' is already duplicated in child, don't duplicate again but share it
bool found = false;
// Project2-extra) linear search on key-pair array
for (int j = 0; j < MAPLEN; j++) {
if (map[j].key == file) {
found = true;
current->fd_table[i] = map[j].value;
break;
}
}
if (!found) {
struct file *new_file;
if (file > 2)
new_file = file_duplicate(file);
else
new_file = file;
current->fd_table[i] = new_file;
// project2-extra
if (dup_count < MAPLEN) {
map[dup_count].key = file;
map[dup_count++].value = new_file;
}
}
}
current->fd_idx = parent->fd_idx;
// child loaded successfully, wake up parent in process_fork
sema_up(¤t->fork_sema);
/* 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);
}
위 코드에 대한 설명은 나중에 다시 쓰자!!
결과:
'개발자 도전기 > [OS] pintOS' 카테고리의 다른 글
pintOS | Project 3 | Introduction (0) | 2021.10.14 |
---|---|
pintOS | Project 2 회고 (2) | 2021.10.14 |
pintOS | Project 2 | 시스템콜 구현 (fork, wait, read, write, seek, tell, close) (3) | 2021.10.13 |
pintOS | Project 2 | 시스템콜 구현 (halt, exit, exec, create, remove, open, filesize) (0) | 2021.10.11 |
pintOS | Project 2 | 명령어 실행 기능 구현(Command Line Parsing) (0) | 2021.10.07 |
댓글