Updated:

컴퓨터 하드웨어

 다양한 서비스를 얻기위해 사용하는 인터페이스 프로그램은 운영체제를 이용해 일을한다.
 레지스터 PSW(Program Status Word)에는 비교 연산들에 의해 설정되는 조건 코드 비트들, CPU의 우선 순위, 모드(사용자 또는 커널) 그리고 각종 제어 비트들이 있다. 사용자 프로그램들은 PSW 전체를 읽을 수는 있으나 몇몇 필드에만 쓰기를 할 수 있다. PSW는 시스템 호출이나 I/O에 중요한 역할을 한다.
 대부분의 CPU는 커널 모드와 사용자모드라는 두 개의 모드를 지원한다. 보통 PSW에 있는 한 비트를 통해 모드를 조정한다. 커널 모드(kernel mode)에서 실행하는 동안 CPU는 자기 명령 집합에 있는 모든 명령들을 다 실행할 수 있을 뿐만 아니라 하드웨어의 모든 기능도 사용할 수 있다. 운영체제가 커널 모드에서 실행하므로 하드웨어에 대한 완전한 접근이 가능하다.
 상대적으로 사용자 프로그램은 사용자 모드(user mode)에서 실행되며, 명령들의 일부만 실행할 수 있고 하드웨어 기능도 일부만 사용할 수 있다. 사용자 모드에서는 일반적으로 I/O와 메모리 보호와 관련된 명령들은 허락되지 않는다. 물론 커널 모드로 진입하기 위해 PSW의 비트를 조작하는 것도 허용되지 않는다.
 운영체제로부터 서비스를 받으려면 사용자 프로그램은 커널로 트랩을 걸어서 운영체제를 활용하도록 하는 시스템 호출(System calls)을 요청해야 한다. 트랩이라는 명령이 사용자 모드에서 커널 모드로 변환을 일으키며 운영체제를 시작한다. 일이 완료되면 제어권은 사용자 프로그램으로 돌아오며, 시스템 호출을 요청한 명령 바로 다음 명령이 수행된다. 컴퓨터에는 시스템 호출을 실행하기 위한 트랩 명령 외에도 다은 트랩 명령들이 있다. 다른 트랩 명령들의 대부분은 0으로 나누기나 부동소수점 연산의 언더플로우(underflow)현상과 같은 특별한 상황을 알리기 위해 하드웨어에서 일으키는 것들이다. 이 모든 경우에 운영체제가 제어권을 획득하고 트랩의 원인에 따라 어떤 일을 해야 할지를 결정하게 된다. 어떤 때는 오류와 함께 프로그램을 강제로 종료할 경우도 있다. 어떤 때는 오류를 그냥 무시하는 경우도 있다. 마지막으로, 프로그램이 어떤 조건들에 대해서는 자기가 처리하고 싶다는 의향을 밝힐 경우, 제어권이 프로그램에게 전달되어 프로그램이 원하는 작업을 할 수 있도록 할 수 있다.

시스템 호출

 실제 시스템 호출이 이루어지는 방법은 기계에 따라 상당히 다를 수 있고 보통 어셈플리 언어로 되어 있어 C 프로그램이나 다른 고급 언어로도 시스템 호출을 할 수 있도록 하기 위해 라이브러리 함수가 제공된다.
 다음 사실을 기억해 두면 좋다. 어떠한 단일 CPU 컴퓨터에서든 한 번에 한 개의 명령어만을 수행할 수 있다. 시스템 호출과정을 알아보자.

  • 어떤 프로세스가 사용자 모드에서 사용자 프로그램을 실행하다가 파일에 있는 데이터를 읽어야 하는 것과 같은 시스템 서비스가 필요하게 되면 그는 제어권을 운영체제로 넘기기 이해 트랩 명령을 사용한다.
  • 그러면 운영체제는 호출한 프로세스가 무엇을 해 달라고 하는 것인지를 알아내기 위해 인자들을 확인한다.
  • 그리고나서 시스템 호출을 실행하고 완료가 되면 제어권을 다시 사용자 프로그램으로 넘긴다.

어떤 면에서는 시스템 호출을 한다는 것은 특별한 형태의 함수 호출을 하는 것과 같은데 다만 시스템 호출은 커널에 진입하고 함수 호출은 그렇지 않다는 점이 다르다.
 시스템 호출 과정을 더 명확하게 설명하기 위해 read 시스템 호출하는 과정을 간단히 살펴 보자. 이 시스템 호출은 세 개의 인자를 갖는다. 첫째 인자는 파일을 명시하고 둘째는 포인터로 버퍼를 가리키고 셋째는 읽어야 할 바이트 수를 지정한다. C 프로그램에서의 호출은 다음과 같은 모양이다.

count = read(fd, buffer, nbytes);

시스템 호출은 실제로 읽은 바이트 수를 count를 통해 반환한다. 이 값은 보통 nbytes 값과 같겠지만, 예를 들어 읽는 중에 파일의 끝을 만나는 경우는 이 보다 작을 수도 있다. 만약 시스템 호출이 잘못된 인자나 디스크의 오류로 인해 실행될 수 없다면 count 값은 -1로 설정되고 오류 번호를 전역변수인 errno에 넣게 된다.
 시스템 호출은 여러 절차를 걸쳐 실행된다. 이 과정을 명확하게 하기 위해 다시 read 호출을 관찰해 보자.

 위 그림에서 단계 1 ~ 3에서와 같이 시스템 호출을 하는 프로그램은 read 시스템 호출을 실제로 하는 read 라이브러리 함수를 부르기 위한 준비로 인자들을 스택에 푸시해 둔다. C와 C++에서는 인자들을 역순으로 스택에 푸시한다. 첫째와 셋째 인자는 값 호출(called by value)이나 둘째 인자는 참조 호출(called by reference)이므로 buffer의 내용이 아닌 buffer 주소가(&로 표시) 전달된다. 그리고는 라이브러리 함수에 대한 실제 호출이 일어난다(단계 4). 이 명령은 모든 함수 호출 시 사용되는 일반 함수 호출 방법이다.
 어셈블리어로 작성되었을 가능성이 있는 라이브러리 함수는 통상 운영체제가 예상하는 위치에, 예를 들어 특정 레지스터에, 시스템 호출 번호를 넣어 둔다(단계 5). 그리고는 TRAP 명령을 실행해서 사용자 모드에서 커널 모드로 전환하고 커널의 특정 위치에서 실행을 시작시킨다(단계 6). 트랩 명령은 이어지는 바로 뒤 명령이 멀리 떨어져 있는 위치에 있고 반환 주소도 나중에 사용될 것을 대비해서 스택에 보관된다는 점에서 일반 함수 호출과 매우 흡사하다.
 TRAP에 이어지는 커널 코드가 시스템 호출 번호를 확인하고 이에 따라 적절한 시스템 호출 핸들러를 실행하게 되는데 이는 보통 시스템 호출 핸들러를 가리키는 포인터들을 담은 테이블을 시스템 호출 번호로 인덱스 해서 결정하게 된다(단계 7). 이 시점에서 시스템 호출 핸들러가 실행된다(단계 8). 시스템 호출 핸들러의 실행이 완료되면 사용자 영역 라이브러리 함수에서 트랩 명령 바로 뒤 명령으로 제어권이 넘어 갈수도 있다(단계 9). 이 라이브러리 함수는 보통 함수 호출이 반환되는 방식 그대로 사용자 프로그램에게로 반환한다(단계 10).
 일을 마무리하기 위해 사용자 프로그램은 어느 함수 호출에서 하듯 스택을 정리한다(단계 11). 스택이 보통 그러하듯 아래로 커진다고 가정을 한다면 컴파일된 코드는 푸시된 인자들을 지우고 스택을 read 호출 이전 상태로 되돌리기 위해 스택 포인터를 증가시키게 된다.
 시스템 호출을 하는 라이브러리 함수들 중에서 가장 많이 사용되는 것들을 살펴본다. POSIX는 약 100개의 함수 호출을 정의한다. 이들 중 가장 중요한 것들의 일부는 다음과 같이 편의상 네 개의 영역으로 나누어 나열해 두었다.

 대체로 보면 개인용 컴퓨터에서 자원 관리에 대한 부분이 워낙 미약하기 때문에 이들 호출들이 제공해 주는 서비스들이 운영체제가 하는 일의 대부분이라고 볼 수 있다. 여기서 제공되는 서비스는 프로세스를 생성하고 종료하고, 파일을 생성하고 지우고 읽고 쓰고, 디렉토리를 관리하고, I/O를 하는 것들이다.

댓글남기기