본문 바로가기
개발자 도전기/[OS] pintOS

pintOS | Project 2 | 시스템 콜 (extra)

by 답수 2021. 10. 14.
728x90
SMALL

 

 

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 (&current->spt);
	if (!supplemental_page_table_copy (&current->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(&current->fork_sema);

	/* Finally, switch to the newly created process. */
	if (succ)
		do_iret(&if_);

error:
	current->exit_status = TID_ERROR;
	sema_up(&current->fork_sema);
	exit(TID_ERROR);
}

 

위 코드에 대한 설명은 나중에 다시 쓰자!!

 

 

결과:

 

728x90
LIST

댓글