x86-64 기반 Pint OS 프로젝트 2의 System Calls를 구현하기 위해 공부한 개념을 정리한다.
User Memory Access
시스템 콜을 구현하기 전에, 유저가 보낸 포인터가 유효한 주소를 가리키는 지 확인할 필요가 있다. 만약에 유저 프로그램이 커널 영역을 조작하려고 한다면, 프로세스를 종료함으로써 막을 필요가 있다. 이외에도 포인터가 NULL 포인터인지, 유효한 페이지를 가리키는 지를 확인해야 한다.
check_address()
void
check_address(void *addr) {
if (is_kernel_vaddr (addr) || addr == NULL || pml4_get_page(thread_current()->pml4, addr) == NULL) {
exit (-1);
}
}
syscall_handler()
유저 모드에서 유저 프로그램이 보낸 시스템 콜 요청을 커널 모드에서 받고, 커널 모드에서 시스템 콜 핸들러를 통해 시스템 콜을 처리한다. 주어진 스켈레톤 코드를 기반으로 구현하면 된다. 스켈레톤 코드는 시스템 콜 요청을 받으면, 프로세스를 종료하는 것으로 handling한다. 레지스터에 담긴 시스템 콜 번호와 인자를 읽고 적절하게 처리해주면 된다!
스켈레톤 코드
/* The main system call interface */
void
syscall_handler (struct intr_frame *f UNUSED) {
// TODO: Your implementation goes here.
printf ("system call!\n");
thread_exit ();
}
시스템 콜 번호
int syscall_n = f->R.rax;
유저 프로그램에게서 받은 시스템 콜 번호는 syscall_handler 의 인자인 f->R.rax에 담겨있다. 이를 받아와서 처리해준다.
구현
switch (syscall_n) {
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:
f->R.rax = exec(f->R.rdi);
break;
case SYS_WAIT:
f->R.rax = wait(f->R.rdi);
break;
}
유저 모드에서는 시스템 콜 번호를 통해 시스템 콜을 요청한다. 따라서 커널 모드에서 이를 처리할 때도 시스템 콜 번호에 따라 처리해주어야 한다. 번호는 syscall-nr.h
에 선언되어 있다. 단순히 switch 문으로 분기하면 된다.
그 외
- 스켈레톤 코드 안에 있던 printf 문과 thread_exit () 함수는 지워야 한다.
- 출력문이 다르면 fail되고, 시스템 콜을 한 번 처리하고 프로세스를 종료해서는 안 된다.
wait
유저 프로그램을 최초 실행할 때, Pint OS의 main() -> run_actions() -> run_task() -> process_wait(process_create_initd(task)) -> 유저 프로그램 실행 및 종료 대기 의 순서대로 실행한다. 유저 프로그램이 종료된 후에 Pint OS의 main() 을 종료하기 위해, process_wait() 을 실행한다. 그러나 프로세스 종료를 현재로선 알아내기 어렵고, 우선 반복문으로 아주 오래 기다려주기로 한다.
wait() 시스템 콜에서는 바로 process_wait() 을 호출하도록 한다.
wait()
int
wait (pid_t pid) {
return process_wait (pid);
}
process_wait()
int
process_wait(tid_t child_tid) {
/* XXX: Hint) The pintos exit if process_wait (initd), we recommend you
* XXX: to add infinite loop here before
* XXX: implementing the process_wait. */
for (int i = 0; i < 1000000000; i++);
return -1;
}
halt
Pint OS에서 제공하는 power_off()
함수를 사용해서 Pint OS를 강제 종료한다.
halt()
void
halt (void) {
power_off ();
}
exit
현재 실행 중인 유저 프로그램(프로세스)를 종료한다. 현재 프로세스의 부모 프로세스가 wait()을 호출했다면, 이 status가 리턴될 것이다.
exit()
void
exit (int status) {
struct thread *t = thread_current ();
printf ("%s: exit(%d)\n", t->name, status);
t->exit_status = status;
thread_exit ();
}
write
write() 시스템 콜의 헤더는 다음과 같다.
int write (int fd, const void *buffer, unsigned size);
인자로 받은 fd(file descriptor)에 size만큼의 buffer를 입력해야 한다. fd는 표준 출력(stdout)이 될 수도 있고, 파일 출력이 될 수도 있다. 따라서 바로 구현하기 꽤나 어려운 함수이다. 그러나 테스트케이스를 pass하기 위해서는 유저 프로그램이 표준 출력(stdout)에 write할 수 있어야 한다. 따라서 우리는 fd가 stdout(1)인 경우에, putbuf() 라는 함수를 사용해서 표준 출력하는 것을 먼저 구현해야 한다.
Pint OS에서 제공하는 putbuf(buffer, size)
함수는 size 만큼의 buffer를 바로 콘솔에 출력시켜 준다! 이것을 바로 사용하면 된다.
write()
write (int fd, const void *buffer, unsigned size) {
if (buffer == NULL)
exit (-1);
if (fd == 1) {
putbuf (buffer, size);
}
return size;
}
'프로젝트 > Pint OS' 카테고리의 다른 글
[Pint OS] System Calls (4) (0) | 2023.06.11 |
---|---|
[Pint OS] System Calls (3) (0) | 2023.06.11 |
[Pint OS] 에러: missing "begin" message (0) | 2023.06.09 |
[Pint OS] System Calls (1) (0) | 2023.06.07 |
[Pint OS] User Memory Access (0) | 2023.06.07 |