Little Jay

[OS] System Call (POSIX API) 본문

Univ/Operating System(OS)

[OS] System Call (POSIX API)

Jay, Lee 2022. 7. 25. 14:21

System Calls

System Calls(이하 Syscall)이라고 하는 것은 앞에서도 계속 언급해왔다. 정말 이를 간단하게 말하자면 함수의 호출이다. 그러나 그 의미를 조금 더 자세히 살펴보자면 Process가 특정한 Kernel Service를 요청하는 것이다. 이를 통해 Application Level에서 OS와 communicate할 수 있는 것이다. Sys Call의 목적은 결국 System Resource를 보호하고자 사용하는 것이다. OS 입장에서는 Kernel Level에서 동작하는 코드들은 Trusted, 즉 신뢰할 수 있는 것으로 여겨진다. 반면에 User Program의 Level의 코드들은 OS입장에서 Untrusted하다. 이를 Reliability 측면에서 본다면 User Program들은 Buggy하기에 Resource Hog들이 존재한다. 이는 무한루프나, 메모리 초과 등 신뢰성이 부족할 수 있기 때문이다. 또한 Security 측면에서 바라보자면 User Program에는 Malicious한 코드들이 들어가있을 수 있다. OS의 허가없이 Program이 read/write를 수행한다면 이는 Kernel에서 작동해야한다는 OS의 기본 원칙을 무시한 채 에러가 날 수 있기 때문이다. 따라서 User Level Programming은 위험하기 때문에 Syscall을 통해 한 단계를 거쳐 안전하게 동작해야한다. 

 

POSIX에는 200개 이상의 Syscall들이 존재한다. 이는 Well-Defined된 코드들이며 Portable하고 Robust하다. 이 Syscall을 Compact한 부분만 제공해줌에 따라서 최소한의 부분을 최소한의 Interface에서 사용이 가능한 것이다. User Program에서는 이를 잘 wrapping된 API(Application Program Interface)로서 사용한다. 우리가 printf를 사용하지, write를 C코드를 짤때 사용하지 않는 것처럼 우리는 이를 Direct하게 사용하는 것이 아니라 printf를 사용함으로서 write라는 Kernel의 코드를 사용하는 것이다. 이는 OS에게 유연함을 제공하기 위해서인데, Legacy Code를 보면 실제 Kernel에 들어가 있는 Syscall들은 매우 복잡하다. 이를 API로 제공함으로서 사용하기 쉽지 않은 Kernel Level의 코드를 조금 더 유연하게 Library 형태로 제공해주는 것이다. OS마다 Syscall이 다른데 UNIX, LINUX, MAC에서는 POSIX API를 사용하고 Windows에서는 Win32 API를 사용하고 있다. 

Type of System Calls

Syscall은 크게 6가지의 카테고리로 분류할 수 있다. 이는 Process, Scheduling, Interrupt Communication, File System, Socket, Miscellaneous 이다. Process에는 execv, exit, getpid, setuid, ptrace 등이 있고, Scheduling에는 sched_getparam, sched_get_priority_max, sched_setscheduler 등이 있고, Interrupt Communication에는 msgrcv, semctl, semop, shmat, shmctl 등이 있고, File System에는 close, link, open, read, write, Socket에는 bind, connect, accept, send, gethostname, Miscellaneous에는 fsync, time, vhangup 등이 있다. 각 Syscall이 궁금하다면 검색을 하길 바라겠다. 지금까지 언급한 Syscall들은 POSIX 기반이기에 Win32와는 다르다. 아래 표를 참고하면 되겠다.

POSIX Win32
fork CreateProcess
wait WaitForSingleObject
exit ExitProcess
open CreateFile
close CloseHandle
read ReadFile
write WriteFile
lseek SetFilePointer
stat GetFileAttributesExt
mkdir CreateDirectory
rmdir RemoveDirectory
unlink DeleteFile
chdir SetCurrentDirectory

Example

그렇다면 read operation을 통한 Syscall이 어떻게 작동하는지 Flow를 알아보자. 먼저 User Program Level에서는 libc.a에 있는 read()를 호출하기 위한 System Call Function을 호출한다. 그런 다음 arguments들을 register에 넣거나 stack에 쌓아 올린다. 그런 다음 eax register에 syscall의 번호를 배치시킨다. 그 다음 int 0x80을 호출하여 실제 Syscall을 수행하는 Instructiond르 부른다. 이제 Kernel로 다시 넘어와서 Syscall Interrupt Handler를 실행한다. 그러면 이 Handler는 System Call Table을 참고하여 System Call을 사용하게 된다. 이 방식은 앞서서 배운 IRQ를 Handling하는 방식과 유사하다. 이제 Syscall의 결과를 지정된 레지스터에 저장 후, RETURN_FROM_INTERRUPT를 수행한다. 어째뜬 System Call도 Interrupt 기반으로 작동하는 것이기에 Interrupt 처리와 유사하게 동작한다. 

Syscall이 OS에게 Parameter를 보내는 방법에는 크게 세 가지가 존재한다. 먼저 Register로 보내는 방법이다. 이는 정말 간단한 방식이지만 종종 사용가능한 레지스터보다 더 많은 Parameter들이 필요한 경우가 있다. 그런 경우에는 Memory의 Block단위로 Parameter들을 보내는 방법도 존재한다. 이 방식은 LINUX와 Solaris에서 많이 사용하는 방식이다. 또한 Memory 단위로 넘기는 것이 아니라 Stack에 push하고 pop하는 방식으로 동작할 수도 있다. 중요한것은 Memory와 Stack을 통한 전달은 Parameter의 개수 제한 없이 보낼 수 있다. 하지만 결국 이는 Memory Bus를 타야한다는 관점에서는 Register에 접근하는 것보다는 느리다. 

오른쪽 표의 왼쪽은 수행순서, 오른쪽은 Ref 주소이다. 왼쪽은 Main Memory라고 보았을때 각 Process의 시작주소를 적어놓은 것이다. Dispatcher가 Process를 Scheduling해주는 System Call을 처리하는 영역이라고 보았을 때 위와 같은 Flow가 만들어지게 된다. 이러한 Flow를 잘 따라가보도록 하자. 

Reference

William Stallings. (2018). Operating Systems: Internals and Design Principles (8th Edition): Pearson.

Comments