시스템 콜
시스템 콜은 유저 프로그램이 커널 기능을 사용할 수 있게 해주는 인터페이스
시스템 콜은 커널 모드에서 실행되며, 작업을 마치면 유저 모드로 전환된다.
유저모드 VS 커널 모드
유저모드
매우 제한된 환경에서 명령어 실행 가능, 메모리 영역도 제한적으로 사용 가능
커널 모드
어떤 명령어도 실행 가능하며, 시스템 내의 어떤 메모리 위치도 접근 가능
시스템 콜의 실행 과정 - PINTOS
1. 응용 프로그램이 시스템 콜이 필요한 시점에 도달
EX) 파일을 열기 위해 open() 함수 호출 // 여기서 open()은 래퍼함수
// 경로 - PINTOS-KAIST/lib/user/syscall.c
int open(const char *file) {
return syscall1(SYS_FILESIZE, fd);
}
2. 응용 프로그램이 open 래퍼 함수를 실행하면 syscall(특수 트랩 명령어)가 수행되면 커널 모드로 전환되고 커널 스택에 프로세스 상태 정보 저장
PINTOS에서 특수 트랩 명령어 위치
/* 경로 - PINTOS-KAIST/lib/user/syscall.c */
/*
시스템 콜 번호는 rax 레지스터에 저장되며,
매개변수들은 rdi, rsi, rdx, r10, r8, r9 등의 레지스터에 저장되어 커널에 전달됩
*/
__asm __volatile(
"mov %1, %%rax\n"
"mov %2, %%rdi\n"
"mov %3, %%rsi\n"
"mov %4, %%rdx\n"
"mov %5, %%r10\n"
"mov %6, %%r8\n"
"mov %7, %%r9\n"
"syscall\n" // <-- 시스템 콜 트리거(특수 트랩 명령어)
: "=a" (ret)
: "g" (num), "g" (a1), "g" (a2), "g" (a3), "g" (a4), "g" (a5), "g" (a6)
: "cc", "memory");
PINTOS에서 커널 스택에 저장되는 부분
/* 경로 - PINTOS-KAIST/userprog/syscall-entry.S */
syscall_entry:
movq %rbx, temp1(%rip)
movq %r12, temp2(%rip) /* callee saved registers */
movq %rsp, %rbx /* Store userland rsp */
movabs $tss, %r12
movq (%r12), %r12
movq 4(%r12), %rsp /* Read ring0 rsp from the tss */
/* 커널 스택에 저장 시작 */
push $(SEL_UDSEG) /* if->ss */
push %rbx /* if->rsp */
push %r11 /* if->eflags */
push $(SEL_UCSEG) /* if->cs */
push %rcx /* if->rip */
subq $16, %rsp /* skip error_code, vec_no */
push $(SEL_UDSEG) /* if->ds */
push $(SEL_UDSEG) /* if->es */
push %rax
movq temp1(%rip), %rbx
push %rbx
pushq $0
push %rdx
push %rbp
push %rdi
push %rsi
push %r8
push %r9
push %r10
pushq $0 /* skip r11 */
movq temp2(%rip), %r12
push %r12
push %r13
push %r14
push %r15
PINTOS에서 시스템 콜 핸들러 호출 부분
movabs $syscall_handler, %r12
call *%r12
3. 커널의 인터럽트 핸들러가 실행되고, 시스템 콜 번호에 해당하는 커널 함수를 호출하여 작업 수행, 필요한 경우 사용자 모드에서 전달된 매개변수들을 검증하고, 커널 영역으로 전송
// 시스템 콜 핸들러
syscall_handler (struct intr_frame *f UNUSED) {
int sys_number = f->R.rax;
switch (sys_number)
{
case SYS_HALT: // 운영체제 종료
halt();
break;
case SYS_EXIT: // 프로그램 종료 후 상태 반환
exit(f->R.rdi);
break;
...
}
...
}
// open() 시스템 콜(래퍼 아님)
int open(const char *file) {
struct thread *curr = thread_current();
check_address(file);
...
}
// 주소 체크
void check_address (void *addr) {
struct thread *t = thread_current();
// 포인터가 가리키는 주소가 유저 영역의 주소인지 확인
// 포인터가 가리키는 주소가 유저 영역 내에 있지만 페이지로 할당되지 않은 영역인지 확인
if (!is_user_vaddr(addr) || addr == NULL
|| pml4_get_page(t->pml4, addr) == NULL){
exit(-1);
}
}
4. 작업 완료 후, 결과는 적절한 레지스터나 메모리에 저장됨
// 시스템 콜 실행 결과가 rax 레지스터에 저장됨.
// rax 레지스터는 반환 값을 저장하는 레지스터
case SYS_OPEN:
f->R.rax = open(f->R.rdi) // 커널 내에 존재하는 open 시스템 콜 함수 호출
5. 시스템 콜 함수 종료시, 인터럽트 핸들러 핸들러로 제어가 돌아오고, 인터럽트 핸들러가 작업을 마치면, 커널 스택에 저장된 값들을 다시 복구한 뒤sysretq 특수 명령어가 실행되어 커널 모드에서 유저모드로 전환된다.
PINTOS에서 커널 스택에 저장된 값을 복구하고 사용자 모드로 전환되는 부분
movabs $syscall_handler, %r12
call *%r12 /* 시스템 콜 핸들러 호출 부분*/
popq %r15 /* 커널 스택에서 복구 시작*/
popq %r14
popq %r13
popq %r12
popq %r11
popq %r10
popq %r9
popq %r8
popq %rsi
popq %rdi
popq %rbp
popq %rdx
popq %rcx
popq %rbx
popq %rax
addq $32, %rsp
popq %rcx /* if->rip */
addq $8, %rsp
popq %r11 /* if->eflags */
popq %rsp /* if->rsp */
sysretq /* 사용자 모드로 전환하는 특수 명령어 */
기타
시스템 콜의 안전성: 사용자로부터 전달되는 모든 매개변수는 반드시 검증되어야 하며, 이를 통해 커널의 안정성과 보안을 유지할 수 있습니다.
커널 스택 사용 이유 : 사용자 스택은 사용자 모드에서의 메모리 공간이므로, 커널 모드에서는 커널 스택을 사용하여 안정성을 확보합니다.
syscall 명령어 : 커널 모드로 전환하기 위한 특수 트랩 명령어
sysretq 명령어 : 사용자 모드로 전환하기 위한 특수 명령어
시스템 콜 번호는 rax 레지스터에 저장되며, 매개변수들은 rdi, rsi, rdx, r10, r8, r9 등의 레지스터에 저장되어 커널에 전달됨