일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
1 | 2 | 3 | ||||
4 | 5 | 6 | 7 | 8 | 9 | 10 |
11 | 12 | 13 | 14 | 15 | 16 | 17 |
18 | 19 | 20 | 21 | 22 | 23 | 24 |
25 | 26 | 27 | 28 | 29 | 30 | 31 |
- DFS(Depth First Search)
- 백준 21606번
- 알고리즘 개념
- 백준 1948번
- 큐(Queue)
- 위상 정렬(Topology Sort)
- 위상 정렬(Topological Sort)
- 이분 탐색(Binary Search)
- 스택(Stack)
- 백준 2261번
- 동적 프로그래밍(Dynamic Programming)
- 분할 정복(Divide and Conquer)
- 다익스트라 알고리즘(Dijkstra Algorithm)
- BFS(Breadth First Search)
- DFS & BFS
- 그래프(Graph)
- 백준 9012번
- 백준 17608번
- 이분 그래프(Bipartite Graph)
- 트리(Tree)
- 백준 2812번
- 백준 2504번
- DFS
- 백준 10000번
- 백준 2493번
- 백준 18352번
- 그리디 알고리즘(Greedy Algorithm)
- 백준 1707번
- BFS
- 플로이드 워셜 알고리즘(Floyd-Warshall Algorithm)
- Today
- Total
Always Be Wise
Project_2 : User Programs - System Calls(1) 본문
이제 Pintos의 System Calls을 구현해보자. 현재 userprog/syscall.c에 구현되어 있는 syscall_handler( ) 함수는 골격만 있다.
해당 함수를 수정하여 각각의 시스템 콜에 알맞은 처리 작업을 수행할 수 있도록 해야 한다.
/* userprog/syscall.c */
void syscall_handler (struct intr_frame *f UNUSED) {
// TODO: Your implementation goes here.
printf ("system call!\n");
}
우선, 구현해야 할 시스템 콜 종류를 알아보자.
프로젝트 요구사항에 따르면 시스템 콜 번호는 % rax에 저장되고, 인자들은 % rdi, % rsi, % rdx, % r10, % r8, % r9 순서로 전달된다.
그리고 include/lib/user/syscall.h와 include/lib/syscall-nr에는 각각의시스템 콜 프로토타입과 시스템 콜 번호가 정의되어 있다.
/* include/lib/user/syscall.h */
/* Projects 2 and later. */
void halt (void) NO_RETURN;
void exit (int status) NO_RETURN;
pid_t fork (const char *thread_name);
int exec (const char *file);
int wait (pid_t);
bool create (const char *file, unsigned initial_size);
bool remove (const char *file);
int open (const char *file);
int filesize (int fd);
int read (int fd, void *buffer, unsigned length);
int write (int fd, const void *buffer, unsigned length);
void seek (int fd, unsigned position);
unsigned tell (int fd);
void close (int fd);
int dup2(int oldfd, int newfd);
/* include/lib/syscall-nr.h */
enum {
/* Projects 2 and later. */
SYS_HALT, /* Halt the operating system. */
SYS_EXIT, /* Terminate this process. */
SYS_FORK, /* Clone current process. */
SYS_EXEC, /* Switch current process. */
SYS_WAIT, /* Wait for a child process to die. */
SYS_CREATE, /* Create a file. */
SYS_REMOVE, /* Delete a file. */
SYS_OPEN, /* Open a file. */
SYS_FILESIZE, /* Obtain a file's size. */
SYS_READ, /* Read from a file. */
SYS_WRITE, /* Write to a file. */
SYS_SEEK, /* Change position in a file. */
SYS_TELL, /* Report current position in a file. */
SYS_CLOSE, /* Close a file. */
/* Project 3 and optionally project 4. */
SYS_MMAP, /* Map a file into memory. */
SYS_MUNMAP, /* Remove a memory mapping. */
/* Project 4 only. */
SYS_CHDIR, /* Change the current directory. */
SYS_MKDIR, /* Create a directory. */
SYS_READDIR, /* Reads a directory entry. */
SYS_ISDIR, /* Tests if a fd represents a directory. */
SYS_INUMBER, /* Returns the inode number for a fd. */
SYS_SYMLINK, /* Returns the inode number for a fd. */
/* Extra for Project 2 */
SYS_DUP2, /* Duplicate the file descriptor */
SYS_MOUNT,
SYS_UMOUNT,
};
이제 본격적으로 시스템 콜 핸들러를 구현해보도록 하자. 시스템 콜이 호출되면 어떤 시스템 콜인지에 따라 다른 작업이 수행되어야 한다.
따라서 시스템 콜 핸들러의 인자로 전달된 inter_frame의 f->R.rax를 통해 확인할 수 있다. switch 문에 이를 넣어서 조건별로 알맞은
시스템 콜이 호출될 수 있도록 설정해주자. 가장 먼저 구현할 시스템 콜은 SYS_HALT이다.
아래 요구 조건에 따라 halt( ) 함수를 구현하면 되는데 power_off( ) 함수를 호출할 수 있도록 작성하면 된다.
power_off( ) 함수를 사용하기 위해서 threads/init.h를 추가해야 한다.
Terminates Pintos by calling power_off( )(declared in src/include/threads/init.h).
/* userprog/syscall.c */
#include "threads/init.h"
/*--------------- PROJECT2: User Programs ----------------*/
/* The main system call interface */
void syscall_handler(struct intr_frame *f UNUSED){
// TODO: Your implementation goes here.
switch (f->R.rax){
case SYS_HALT:
halt();
break;
default:
exit(-1);
break;
}
}
void halt(void){
power_off();
}
다음으로 SYS_CREATE와 SYS_REMOVE를 구현해보자. 해당 시스템 콜은 파일을 생성하고 삭제하는 시스템 콜이다.
아래 요구 조건에 따라 Pintos에서 기본적으로 제공하는 File System 함수를 이용하여 create( ), remove( ) 함수를 구현하자.
File System 함수 filesys_create( ), filesys_remove( )를 사용하기 위해서 filesys/filesys.h를 추가해주어야 한다.
그런데 create( ), remove( ) 함수 인자로 넘어오는 주소가 올바른 값인지 확인할 필요가 있다.
이를 위해 check_address( )라는 함수를 추가로 구현해야 한다.
이는 확인할 주소가 NULL 포인터이거나 유저 어드레스가 아니거나, 할당되지 않은 주소인 경우 프로세스를 종료하는 함수이다.
Creates a new file called file initially initial_size bytes in size.
Deletes the file called file.
Both returns true if successful, false otherwise.
/* userprog/syscall.c */
// 중략
#include "filesys/filesys.h"
/*--------------- PROJECT2: User Programs ----------------*/
void syscall_handler(struct intr_frame *f UNUSED){
// TODO: Your implementation goes here.
switch (f->R.rax){
// 중략
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;
default:
exit(-1);
break;
}
}
void check_address(const uint64_t *uaddr){
struct thread *cur = thread_current();
if (uaddr == NULL || !(is_user_vaddr(uaddr)) || pml4_get_page(cur->pml4, uaddr) == NULL){
exit(-1);
}
}
bool create(const char *file, unsigned initial_size){
check_address(file);
return filesys_create(file, initial_size);
}
bool remove(const char *file){
check_address(file);
return filesys_remove(file);
}
파일을 만들고, 삭제하는 것을 구현해보았으니 다음으로 만든 파일을 열어보는 시스템 콜 SYS_OPEN을 구현해보자.
구현을 위해 open( ) 함수를 만들자. create( ), remove( )와 마찬가지로 파일 주소가 유효한지 먼저 확인해주고,
filesys_open( ) 함수를 이용하여 파일 객체를 생성하도록 하면 된다.
그리고 해당 객체를 파일 디스크립터 테이블(FDT, File Descriptor Table)에 추가해야 한다.
파일 디스크립터 테이블이란 프로세스가 현재 사용 중인 파일을 관리하기 위한 테이블로, 프로세스마다 하나씩 가지고 있다.
그런데 이는 이전 프로젝트에서 사용하지 않았던 것이다. 따라서 thread 구조체에 새롭게 추가해주어야 한다.
thread 구조체에 fdTable과 해당 테이블을 인덱싱할 때 사용하기 위한 fdIdx를 선언하자.
마지막으로 thread를 생성할 때, 해당 파일 디스크립터 테이블과 각종 정보들을 초기화할 수 있도록 tread_create( ) 함수를 수정한다.
Opens the file called file.
Returns a nonnegative integer handle called a "file descriptor" (fd),
or -1 if the file could not be opened.
File descriptors numbered 0 and 1 are reserved for the console:
fd 0 (STDIN_FILENO) is standard input, fd 1 (STDOUT_FILENO) is standard output.
/* userprog/syscall.c */
/*--------------- PROJECT2: User Programs ----------------*/
int open(const char *file){
check_address(file);
struct file *fileobj = filesys_open(file);
if (fileobj == NULL)
return -1;
int fd = add_file_to_fdt(fileobj);
if (fd == -1)
file_close(fileobj);
return fd;
}
int add_file_to_fdt(struct file *file){
struct thread *cur = thread_current();
struct file **fdt = cur->fdTable; // file descriptor table
if (cur->fdIdx >= FDCOUNT_LIMIT)
return -1;
fdt[cur->fdIdx] = file;
return cur->fdIdx;
}
/* threads/thread.h */
struct thread {
// 중략
struct file **fdTable;
int fdIdx;
// 중략
};
/* threads/thread.c */
tid_t thread_create (const char *name, int priority,thread_func *function, void *aux) {
// 중략
/*--------------- PROJECT2: User Programs ----------------*/
t->fdTable = palloc_get_multiple(PAL_ZERO, FDT_PAGES);
if (t->fdTable == NULL)
return TID_ERROR;
t->fdIdx = 2; // 0: stdin, 1: stdout
t->fdTable[0] = 1; // dummy values to distinguish fd 0 and 1 from NULL
t->fdTable[1] = 2;
t->stdin_count = 1;
t->stdout_count = 1;
// 중략
}
다음으로 파일 사이즈를 확인할 수 있는 시스템 콜 SYS_FILESIZE를 구현해보자.
우선, fd를 인자로 받는 filesize( ) 함수를 생성해보자. 그런데 파일 사이즈를 알기 위해서는 fd가 아닌 파일이 필요하다.
입력받은 fd를 이용하여 파일을 찾는 함수 find_file_by_fd( )를 구현해보자.
해당 함수를 통해 파일을 구한 이후에는 file_length( ) 함수를 이용하여 사이즈를 리턴하면 된다.
/* userprog/syscall.c */
/*--------------- PROJECT2: User Programs ----------------*/
int filesize(int fd){
struct file *fileobj = find_file_by_fd(fd);
if (fileobj == NULL)
return -1;
return file_length(fileobj);
}
static struct file *find_file_by_fd(int fd){
struct thread *cur = thread_current();
if (fd < 0 || fd >= FDCOUNT_LIMIT)
return NULL;
return cur->fdTable[fd]; // automatically returns NULL if empty
}
파일을 읽고 쓰는 시스템 콜을 구현해보자. 우선, read( ) 함수를 작성하여 SYS_READ를 처리해보자.
read( ) 함수는 인자로 fd, buffer, size를 받는다. buffer가 유효한 주소인지 check_address( ) 함수를 통해 확인한다.
이후, fd를 이용하여 파일 객체를 찾는다.
그런데 요구 조건에서 fd가 0인 경우, input_getc( ) 함수를 이용하여 키보드 입력을 읽어오라고 하였다.
따라서 해당 조건을 구현해주고, 그렇지 않은 경우 file_read( ) 함수를 이용하여 파일을 buffer에 size 크기만큼 읽어온다.
이때, 락(lock)을 이용하여 파일을 읽어올 때, 다른 접근을 막아야 한다. 그렇지 않으면 의도와 다른 결과가 발생할 수 있다.
락을 사용하기 위해 해당 구조체를 userprog/syscall.h에 선언해주어야 한다.
또한, syscall_init( ) 함수에도 lock_init( ) 함수를 추가해주어야 한다.
Reads size bytes from the file open as fd into buffer.
Returns the number of bytes actually read (0 at end of file),
or -1 if the file could not be read (due to a condition other than end of file).
fd 0 reads from the keyboard using input_getc().
/* userprog/syscall.c */
/*--------------- PROJECT2: User Programs ----------------*/
void syscall_init(void){
// 중략
lock_init(&file_rw_lock);
}
int read(int fd, void *buffer, unsigned size){
check_address(buffer);
int read_count;
struct thread *cur = thread_current();
struct file *fileobj = find_file_by_fd(fd);
if (fileobj == NULL)
return -1;
if (fileobj == STDIN){
int i;
unsigned char *buf = buffer;
for (i = 0; i < size; i++)
{
char c = input_getc();
*buf++ = c;
if (c == '\0')
break;
}
read_count = i;
}
else if (fileobj == STDOUT){
read_count = -1;
}
else{
lock_acquire(&file_rw_lock);
read_count = file_read(fileobj, buffer, size);
lock_release(&file_rw_lock);
}
return read_count;
}
/* userprog/syscall.h */
struct lock file_rw_lock;
SYS_WRITE를 위해 write( ) 함수를 구현해보자. 해당 함수는 인자로 fd, buffer, size를 받는다.
read( ) 함수와 마찬가지로 buffer의 주소값을 확인해주고, fd를 이용하여 파일 객체를 찾는다.
파일 객체의 값이 STDOUT과 같으면 putbuf( ) 함수를 이용하여 버퍼에 있는 값을 사이즈 만큼 화면에 출력해준다.
그렇지 않다면, 파일 객체에 버퍼에 있는 값을 사이즈 만큼 적어준다.
이때도 락을 이용하여 파일에 대한 동시 접근을 제한해야 한다.
Writes size bytes from buffer to the open file fd.
/* userprog/syscall.c */
/*--------------- PROJECT2: User Programs ----------------*/
int write(int fd, const void *buffer, unsigned size){
check_address(buffer);
int read_count;
struct file *fileobj = find_file_by_fd(fd);
if (fileobj == NULL)
return -1;
struct thread *cur = thread_current();
if (fileobj == STDOUT){
putbuf(buffer, size);
read_count = size;
}
else if (fileobj == STDIN){
read_count = -1;
}
else{
lock_acquire(&file_rw_lock);
read_count = file_write(fileobj, buffer, size);
lock_release(&file_rw_lock);
}
return read_count;
}
파일을 읽거나 쓸 때, 발생할 수 있는 SYS_SEEK, SYS_TELL을 처리하기 위해 seek( ) 함수와 tell( )함수를 구현해보자.
우선, seek( ) 함수의 경우, 인자로 fd와 position을 받는다.
fd를 이용하여 파일을 찾고, 해당 파일 객체의 pos를 입력 받은 position으로 변경한다.
tell( ) 함수도 유사하다. fd를 이용하여 파일을 찾고, file_tell( ) 함수에 해당 객체를 인자로 넣어 리턴한다.
seek : Changes the next byte to be read or written in open file fd to position.
Change position in a file.
tell : Returns the position of the next byte to be read or written in open file fd.
Report current position in a file.
/* userprog/syscall.c */
/*--------------- PROJECT2: User Programs ----------------*/
void seek(int fd, unsigned position){
if (fd < 2)
return;
struct file *fileobj = find_file_by_fd(fd);
if (fileobj == NULL)
return;
fileobj->pos = position;
}
unsigned tell(int fd){
if (fd < 2)
return;
struct file *fileobj = find_file_by_fd(fd);
if (fileobj == NULL)
return;
return file_tell(fileobj);
}
파일을 닫아달라는 시스템 콜 SYS_CLOSE를 처리하기 위해 close( ) 함수를 구현해보자.
파일을 닫는다는 것은 현재 프로세스의 파일 디스크립터 테이블에서 해당 fd를 제거한다는 의미이다.
이를 위해 close( ) 함수 내에 remove_file_from_fdt( ) 함수를 호출해주었다.
해당 함수를 통해 현재 파일 디스크립터 테이블에서 fd를 인덱스로 하는 값을 NULL로 바꾸어주면 된다.
Closes file descriptor fd.
Exiting or terminating a process implicitly closes all its open file descriptors,
as if by calling this function for each one.
/* userprog/syscall.c */
/*--------------- PROJECT2: User Programs ----------------*/
void close(int fd){
struct file *fileobj = find_file_by_fd(fd);
if (fileobj == NULL)
return;
struct thread *cur = thread_current();
if (fd == 0 || fileobj == STDIN){
cur->stdin_count--;
}
else if (fd == 1 || fileobj == STDOUT){
cur->stdout_count--;
}
remove_file_from_fdt(fd);
if (fd < 2 || fileobj <= 2)
return;
}
void remove_file_from_fdt(int fd){
struct thread *cur = thread_current();
if (fd < 0 || fd >= FDCOUNT_LIMIT)
return;
cur->fdTable[fd] = NULL;
지금까지 파일 디스크립터와 관련한 시스템 콜을 구현하였다.
다음 포스트에서는 프로세스 실행과 관련한 시스템 콜들을 정리하도록 하겠다.
'카이스트 정글 - 프로젝트 > Pintos' 카테고리의 다른 글
Project_2 : User Programs - Weekly I Learned (0) | 2022.01.10 |
---|---|
Project_2 : User Programs - System Calls(2) (0) | 2022.01.07 |
Project_2 : User Programs - Argument Passing (0) | 2021.12.30 |
Project_2 : User Programs - Introduction (0) | 2021.12.30 |
Project_1 : Threads - Weekly I Learned (0) | 2021.12.30 |