시스템 콜은 GUI, CLI와 같은 UI와 운영체제가 제공하는 서비스들 사이에 위치한다. 사용자가 운영체제에게 일을 요청하고 싶을 때 시스템 콜을 이용한다. 즉 응용프로그램에서 운영체제에게 어떠한 기능(시스템 자원)을 수행해달라고 하는 하나의 수단이다. 다르게 정리하면 커널 영역의 기능을 사용자 모드가 사용 가능하게 하는 하나의 수단이다 -> 프로세스가 하드웨어에 접근해서 필요한 기능을 사용할 수 있게 함
먼저 듀얼모드(커널 모드, 사용자 모드)에 대해서 알아보자
- 사용자와 운영체제는 시스템 자원을 공유
- 사용자에게 제한을 두어 사용자가 메모리 내의 주요 운영체제 자원에 직접적으로 접근할 수 없도록 함(보안)
- 운영체제에서 프로그램이 구동되는데 있어, 파일을 읽어오거나, 쓰거나 화면에 메시지를 출력하는 등 많은 부분이 커널모드를 사용
- 커널 모드
프로그램 카운터가 운영체제가 존재하는 부분을 가리키고 있다면, 현재 운영체제의 코드를 수행 중이며 CPU가 커널 모드에서 수행 중이라고 한다.
- 사용자 모드
프로그램 카운터가 사용자 프로그램이 존재하는 메모리 위치를 가리킬 경우, 사용자 프로그램을 수행 중이며 CPU가 사용자 모드에서 수행 중이라고 한다
- 일반 명령 : 메모리에서 자료를 읽어와서 CPU에서 계산하고 결과를 메모리에 쓰는 일련의 명령들. 모든 프로그램이 수행할 수 있음(사용자 모드)
- 특권 명령 : 보안이 필요한 명령으로 입출력 장치, 타이머 등 각종 장치를 접근하는 명령(커널 모드)
- CPU 내에 모드 비트를 두어 두 명령을 수행한다
- 사용자 프로그램이 디스크의 파일을 접근하거나, 화면에 결과를 출력하는 등의 작업이 필요한 경우가 있다. 하지만, 이러한 작업은 특권 명령의 수행을 필요로 한다
- 이와 같은 경우, 사용자 프로그램은 스스로 특권 명령을 수행할 수 없으므로 운영체제에게 특권 명령의 대행을 요청한다. 이러한 서비스 요청은 시스템 콜이라고 부른다.(즉 특권 명령의 대행을 요청하여 사용자 프로그램이 커널 영역의 기능을 수행하게 해준다.)
- 사용자 프로그램이 시스템 콜을 하게 되면 운영체제는 자신의 커널 영역에 정의된 시스템 콜 처리 코드를 수행한다
- CPU가 컨트롤 레지스터를 세팅해 디스크 컨트롤러에게 데이터를 읽어오라고 명령한다
- 디스크 컨트롤러는 디스크로부터 데이터를 읽어와서 자신의 로컬 버퍼에 저장한다
- 작업이 완료되면 디스크 컨트롤러가 CPU에게 인터럽트를 발생시켜 입출력 작업이 완료되었음을 통지한다
통상적으로 시스템 콜은 여러 종류의 기능으로 나뉘어져 있다. 각 시스템 콜에는 번호가 할당되고 시스템 콜 인터페이스는 이러한 번호에 따라 인덱스되는 테이블을 유지한다. 아래 그림은 open() 시스템 콜을 호출했을 때, 운영체제에서 어떻게 처리되는지를 보여준다

필요한 기능이나 시스템 환경에 따라 시스템 콜이 발생할 때, 좀 더 많은 정보가 필요할 수도 있다. 그러한 정보가 담긴 매개변수를 운영체제에 전달하기 위해서는 대략 3가지 정도의 방법이 있다
- 매개변수를 CPU 레지스터 내에 전달한다. 이 경우에 매개변수의 갯수가 CPU 내의 레지스터 갯수보다 많을 수 있다.
- 위와 같은 경우에 매개변수를 메모리에 저장하고 메모리의 주소가 레지스터에 전달된다(아래 그림 참고)
- 매개변수는 프로그램에 의해 스택으로 전달될 수도 있다

2,3 번은 전달되는 매개변수의 갯수나 길이에 제한이 없기 때문에 몇몇 운영체제에서 선호하는 방식이다
5가지의 범주로 나눌 수 있다
- 프로세스 제어 : 프로세스 생성, 종료, 로드, 실행, 프로세스 속성 가져오기, 프로세스 속성 설정, wait이나 signal 이벤트, 메모리 할당 및 해제
- 파일 조작 : 파일을 생성하거나 삭제, 읽기, 쓰기, 열기, 닫기, Reposition 등
- 장치 관리 : 장치 요구 및 장치 해제, 읽기, 쓰기, 재배치 등
- 정보 유지 : 시간과 날짜의 설정과 획득, 시스템 자료의 설정과 획득
- 통신 : 통신 연결의 생성 및 제거, 메시지의 송수신, 상태 정보 전달 등
- 보호 : 파일 권한 얻기 및 설정
fork(), exec(), wait()와 같은 것들은 Process 생성과 제어를 위한 System Call이다
- fork, exec은 새로운 Process 생성과 관련이 되어 있다
- wait는 Process(Parent)가 만든 다른 Process(Child)가 끝날 때까지 기다리는 명령어임
- 새로운 Process를 생성할 때 사용
pid : 29146
parent of 29147 (pid : 29146)
child (pid : 29147)
을 출력한다 (parent와 child의 순서는 non-deterministic하다. 즉, 확신할 수가 없다. scheduler가 결정하는 일이다.
PID : 프로세스 식별자, UNIX 시스템에서는 PID는 프로세스에게 명령을 할 때 사용함
Fork()가 실행되는 순간 프로세스가 하나 더 생기는데, 이 때 생긴 프로세스(Child)는 fork를 만든 프로세스(Parent)와 거의 동일한 복사본을 갖게 된다. 이 때 OS는 위와 똑같은 2개의 프로그램이 동작한다고 생각하고, fork()가 return될 차례라고 생각한다. 그 때문에 새로 생성된 Process(Child)는 main에서 시작하지 않고, if문부터 시작하게 된다
그러나, 차이점이 있다. 바로 Child와 Parent의 fork() 값이 다르다는 점이다. 따라서 완전히 동일한 복사본이라 할 수 없다
Parent의 fork()값 => child의 pid값
Child의 fork()값 => 0
Parent와 Child의 fork값이 다르다는 점은 매우 유용한 방식이다 그러나 Scheduler가 부모를 먼저 수행할지 아닐지 확신할 수 없다. 따라서 아래와 같이 출력될 수 있다.
pid : 29146
child (pid : 29147)
parent of 29147 (pid : 29146)
- Child 프로세스가 종료될 때까지 기다리는 작업
pid : 29146
child (pid : 29147)
parent of 29147 (wc : 29147 / pid :29146)
wait를 통해서, Child의 실행이 끝날 때까지 기다려줌. Parent가 먼저 실행되더라도, wait()는 Child가 끝나기 전에는 return하지 않으므로, 반드시 child가 먼저 실행됨
- 단순 fork는 동일한 프로세스의 내용을 여러 번 동작할 때 사용한다
- Child에서는 Parent와 다른 동작을 하고 싶을 때는 exec을 사용할 수 있다
exec가 실행되면,
execvp(실행 파일, 전달 인자) 함수는, code segment 영역에 실행 파일의 코드를 읽어와서 덮어 씌운다
씌운 이후에는, heap, stack, 다른 메모링 영역이 초기화되고, OS는 그냥 실행한다. 즉 새로운 Process를 생성하지 않고, 현재 프로그램에 wc라는 파일을 실행한다. 그로 인해서, execvp() 이후의 부분은 실행되지 않는다

