pintOS | 명령어 실행 기능 구현(Command Line Parsing)
이전 포스팅에서 pintos의 커맨드라인에서 인자를 파싱하고 패싱할 수 있도록 구현을 했다. 하지만 아직 시스템콜 핸들러가 구현되어 있지 않기 때문에 시스템콜이 호출되지 않고, 응용프로그램을 실행할 수 없다.
그렇다면 시스템 콜이 뭘까? 운영체제에는 사용자 모드(User mode)와 커널 모드(Kernel mode)가 있다. 사용자 모드에서는 실행되는 코드가 제한된다. e.g. 프로세스가 사용자 모드에서 실행 중이면 I/O 요청을 할 수 없다. 요청을 하게 되면 프로세스가 인터럽트를 발생시킨다.
이와 반대로 커널모드는 컴퓨터의 모든 자원에 대한 접근 권한을 가진다. 이 모드에서 실행되는 코드는 모든 특수한 명령어를 포함하여 원하는 모든 작업을 수행할 수 있다.
그렇다면 사용자 프로세스가 디스크 읽기와 같은 명령어를 실행하려면 어떻게 해야 할까? 이 때 필요한 것이 시스템 콜(System call)이다.
시스템 콜은 운영체제가 제공하는 서비스에 대한 프로그래밍 인터페이스로, 사용자 모드 프로그램이 커널 기능을 사용할 수 있도록 한다. 시스템 콜은 커널 모드에서 실행되며, 실행이 끝나면 다시 사용자 모드로 복귀된다.
현재 pintos에서 시스템콜 핸들러는 구현되어 있지 않고 우리가 직접 구현해야 한다.
시스템콜 핸들러는 ../userprog/syscall.c 에서 확인할 수 있다. 현재 코드를 한 번 보자.
/* The main system call interface */
void
syscall_handler (struct intr_frame *f UNUSED) {
// TODO: Your implementation goes here.
printf ("system call!\n");
thread_exit ();
}
... 텅 비어 있다 ㅋ.. 하나씩 시스템 콜을 구현해보도록 하즈아
우선 ../include/lib/syscall-nr.h 에 들어가 보면 프로젝트2에서 구현해야 할 함수들이 열거되어 있다. 이 순서에 맞게 시스콜 핸들러에 구현하자.
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. */
...
};
다음과 같이 구현. 참고로 이 때 기존에 있던 printf()와 thread_exit()함수를 제거하지 않으면 'Test output failed to match any acceptable form'이라는 에러문구가 뜨면서 테스트케이스에 통과되지 않는다.
/* The main system call interface */
void syscall_handler (struct intr_frame *f) {
// TODO: Your implementation goes here.
// printf ("system call!\n");
char *fn_copy;
/*
x86-64 규약은 함수가 리턴하는 값을 rax 레지스터에 배치하는 것
값을 반환하는 시스템 콜은 intr_frame 구조체의 rax 멤버 수정으로 가능
*/
switch (f->R.rax) { // rax is the system call number
case SYS_HALT:
halt(); // pintos를 종료시키는 시스템 콜
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;
default:
exit(-1);
break;
}
// thread_exit ();
}
핸드러 밑에 시스템콜 함수들을 만들자. 만들면서 어려운 함수들은 아직 구현 못 했다. 당장 구현한 것들부터 정리할 것임
| halt()
halt 함수는 pintos를 종료시키는 시스템 콜 함수이다. power_off()를 통해 간단하게 구현 가능하다.
// pintos 종료 시스템 콜
void halt(void) {
power_off();
}
| exit()
exit는 프로세스를 종료시키는 시스템 콜이다.
// 프로세스 종료 시스템 콜
void exit(int status) {
struct thread *cur = thread_current();
cur->exit_status = status; // 프로그램이 정상적으로 종료되었는지 확인(정상적 종료 시 0)
printf("%s: exit(%d)\n", thread_name(), status); // 종료 시 Process Termination Message 출력
thread_exit(); // 스레드 종료
}
여기서 thread.h 파일의 thread 구조체에
int exit_status;
를 추가하여 준다.
| exec()
현재 프로세스를 cmd_line에서 지정된 인수를 전달하여 이름이 지정된 실행 파일로 변경
// 현재 프로세스를 cmd_line에서 지정된 인수를 전달하여 이름이 지정된 실행 파일로 변경
int exec(char *file_name) {
check_address(file_name);
int file_size = strlen(file_name)+1;
char *fn_copy = palloc_get_page(PAL_ZERO);
if (fn_copy == NULL) {
exit(-1);
}
strlcpy(fn_copy, file_name, file_size);
if (process_exec(fn_copy) == -1) {
return -1;
}
NOT_REACHED();
return 0;
}
함수 내의 처음 코드를 보면 check_address()라는 함수가 있다. 이것은 포인터가 기리키는 주소가 사용자 영역인지 확인하는 함수다. 역시 따로 구현해줘야 한다.
// 주소값이 유저 영역(0x8048000~0xc0000000)에서 사용하는 주소값인지 확인하는 함수
void check_address(const uint64_t *addr)
{
struct thread *cur = thread_current();
if (addr == NULL || !(is_user_vaddr(addr)) || pml4_get_page(cur->pml4, addr) == NULL) {
exit(-1);
}
}
| create()
파일 이름과 파일 사이즈를 인자 값으로 받아 파일을 생성하는 함수. filesys_create 함수는 파일 이름과 파일 사이즈를 인자값으로 받아 파일을 생성하는 함수다. 이 함수는 filesys/filesys.c 디렉토리에 있다.
// 파일 생성하는 시스템 콜
// 성공일 경우 true, 실패일 경우 false 리턴
bool create(const char *file, unsigned initial_size) { // file: 생성할 파일의 이름 및 경로 정보, initial_size: 생성할 파일의 크기
check_address(file);
return filesys_create(file, initial_size);
}
| remove()
파일을 삭제하는 시스템 콜로, file 인자는 제거할 파일의 이름 및 경로 정보이다. 성공일 시 true, 실패 시 false 리턴
filesys_remove() 함수 역시 filesys/filesys.c 파일에 있다.
// 파일 삭제하는 시스템 콜
// 성공일 경우 true, 실패일 경우 false 리턴
bool remove(const char *file) { // file: 제거할 파일의 이름 및 경로 정보
check_address(file);
return filesys_remove(file);
}
| open()
open은 파일을 열 때 사용하는 시스템 콜이다. 성공 시 fd를 생성하고 반환, 실패 시 -1을 반환한다.
// fd값 리턴, 실패 시 -1 리턴
int open(const char *file) {
check_address(file);
struct file *open_file = filesys_open(file);
if (open_file == NULL) {
return -1;
}
int fd = add_file_to_fdt(open_file);
// fd table 가득 찼다면
if (fd == -1) {
file_close(open_file);
}
return fd;
}
여기에서 fd를 설정하기 위해 다음과 같은 함수를 작성한다.
// 현재 프로세스의 fd테이블에 파일 추가
int add_file_to_fdt(struct file *file) {
struct thread *cur = thread_current();
struct file **fdt = cur->fd_table;
// fd의 위치가 제한 범위를 넘지 않고, fdtable의 인덱스 위치와 일치한다면
while (cur->fd_idx < FDCOUNT_LIMIT && fdt[cur->fd_idx]) {
cur->fd_idx++;
}
// fdt이 가득 찼다면
if (cur->fd_idx >= FDCOUNT_LIMIT)
return -1;
fdt[cur->fd_idx] = file;
return cur->fd_idx;
}
thread.h 에서 thread 구조체 안에
struct file **fd_table; // thread_create에서 할당
int fd_idx; // fd테이블에 open spot의 인덱스
두 변수를 추가하여 준다.
그리고 thread.h 파일 내에
// for system call
#define FDT_PAGES 3 // pages to allocate for file descriptor tables (thread_create, process_exit)
#define FDCOUNT_LIMIT FDT_PAGES *(1 << 9) // Limit fdIdx
이것들을 선언해주자
| filesize()
파일의 크기를 알려주는 시스템 콜
// fd인자를 받아 파일 크기 리턴
int filesize(int fd) {
struct file *open_file = find_file_by_fd(fd);
if (open_file == NULL) {
return -1;
}
return file_length(open_file);
find_file_by_fd() 함수는 아래처럼 구현하자.
// fd로 파일 찾는 함수
static struct file *find_file_by_fd(int fd) {
struct thread *cur = thread_current();
if (fd < 0 || fd >= FDCOUNT_LIMIT) {
return NULL;
}
return cur->fd_table[fd];
}
나머지 함수들은 다음 포스팅에...
'개발자 도전기 > [OS] pintOS' 카테고리의 다른 글
pintOS | Project 2 | 시스템 콜 (extra) (0) | 2021.10.14 |
---|---|
pintOS | Project 2 | 시스템콜 구현 (fork, wait, read, write, seek, tell, close) (3) | 2021.10.13 |
pintOS | Project 2 | 명령어 실행 기능 구현(Command Line Parsing) (0) | 2021.10.07 |
pintOS | list_entry(), doubly linked-list (0) | 2021.10.04 |
pintOS | Project1: Threads 구현 (0) | 2021.10.04 |
댓글