초기의 컴퓨터는 계산기였기 때문에 특별한 규칙이 필요 없었다. 그러나 메모리, CPU 등의 성능이 향상되고, 여러 작업을 동시에 할 수 있는 컴퓨팅 환경이 조성되면서 사용 규칙이 필요해졌다. 규칙이 없으면 기계가 망가질 수도 있기 때문이다.
이 때문에 등장한 것이 운영체제이다. 이렇게 등장한 운영체제는 성능 뿐만 아니라 자원 관리(Resource Management)라는 중요한 역할도 한다. 우선순위를 정해주고 자원과 CPU를 할당하는 등 많은 일을 한다.
요약하자면 운영체제는 사용자에게 인터페이스를 제공하고 응용 프로그램에게 컴퓨터 자원을 골고루 배분한다. 또한 적당한 시점에 응용 프로그램으로부터 자원을 회수하고, 악의적인 응용 프로그램으로부터 컴퓨터를 보호하기도 한다. 즉, 운영체제는 각각의 응용프로그램이 활동할 수 있는 환경을 제공하고, 응용 프로그램이 필요로 하는 컴퓨터 자원을 나누어주며, 응용 프로그램으로부터 컴퓨터 자원을 보호하는 강력한 통치자 역할을 한다.
운영체제 없어도 동작은 가능하다.
운영체제는 사용자에게 편리한 인터페이스 환경을 제공하고 컴퓨터 시스템의 자원을 효율적으로 관리하는 소프트웨어이다.
1. 자원 관리
우리가 개발하는 프로그램들은 일반적으로 유저 모드에서 실행되게 된다. 그리고 프로그램 실행 중에 인터럽트(interrupt)가 발생하거나 시스템 콜(system call)을 호출하게 되면 커널 모드로 전환하게 된다.
커널(Kernel)은 프로세스 관리, 메모리 관리, 저장장치 관리와 같은 운영체제의 핵심적인 기능을 모아놓은 것으로, 자동차에 비유하자면 엔진에 해당한다. 세단, 스포츠카, SUV 등 자동차의 종류는 다양하지만 성능은 엔진이 좌우하는데, 운영체제의 성능은 커널이 좌우한다.
안드로이드와 ios도 커널이 있는데 안드로이드는 커널 공개 ios는 비공개이다.
커널에 사용자의 명령을 전달하고 실행 결과를 사용자에게 알려주는 역할을 한다. 운영체제는 커널과 인터페이스를 분리하여, 같은 커널을 사용하더라도 다른 인터페이스를 가진 형태로 제작할 수 있다. 유닉스를 예로 들어보면, 유닉스의 사용자 인터페이스는 shell을 기반으로 명령어 기반이다. 하지만 Mac OS X도 유닉스 계열의 커널을 이용해서 만들었다. GUI가 탑재되고 인터페이스 쪽을 손 본것뿐이다.
System Call은 다음에 더욱 자세히 다룰 예정이다.
운영체제의 핵심 기능을 모아놓은 커널이 주로 하는 일은 프로세스 관리, 메모리 관리, 파일 시스템 관리, 입출력 관리, IPC 관리 등이다. 즉 시스템의 전반을 관리/감독하는 역할을 하며 하드웨어와 관련된 작업을 직접 수행한다.
커널이 인터럽트나 시스템 콜을 직접 처리한다. 즉, CPU에서 커널 코드가 실행된다. 처리가 완료되면 중단됐던 프로그램의 CPU 상태를 복원한다. 그리고 다시 통제권을 프로그램에게 반환하고 유저모드에서 프로그램이 이어서 실행된다.
컴퓨터는 필수장치로 CPU와 메모리, 그리고 주변장치로 입출력장치와 저장장치로 구성되며, 각 장치는 메인보드에 있는 버스로 연결된다. 다양한 주변장치는 데이터 전송 속도에 따라 저속 주변장치와 고속 주변장치로 구분할 수 있다.
이러한 여러 주변장치는 메인보드 내의 버스로 연결된다. 그런데 1개만 버스를 사용하면 병목 현상이 발생한다. 따라서 여러개의 버스를 묶어서 사용하는데, 이를 데이터가 지나다니는 하나의 통로, 채널이라고 한다. 채널은 다차선 도로와 같이 움직인다. 채널을 효율적으로 쓰는 방법은 비슷한 장치끼리 묶어 분리하여 사용하는 것이다. 예를들면 버스전용도로, 화물차전용도로, 승용차전용도로와 같이 나누는 것이다.
초기에는 모든 장치를 하나의 버스로 연결하여, CPU가 작업중 입출력 장치를 만나면 직접 입출력장치에서 데이터를 가져왔는데 이를 폴링(polling) 방식이라고 한다. 하지만 주변장치도 너무 많아지고 성능도 너무 많이 발전하였다. 폴링 방식으로는 관리하기가 너무 어려워 모든 입출력을 입출력 제어기(I/O controller)에 맡기는 구조로 바뀌었다. 입출력 제어기를 사용하면 CPU나 메모리가 지연되는 일이 줄어들어 작업 효율이 향상된다.
하지만 저속 주변장치와 고속 주변장치의 속도 차이가 심하여 둘이 버스를 공유하면 속도가 현저히 느려져 분리하여 운영하고 있다. 이 두 버스 사이의 데이터 전송은 채널 선택기(channel selector)가 관리한다. 예를 들어 고속 입출력 버스에서 10번 데이터를 받으면 저속 입출력 버스에서 1번 데이터를 받는 식으로 두 버스의 데이터 전송 속도를 조절하는 것이다.
그래픽카드는 입출력버스로는 감당하기 힘든 계산량이 오고가기 떄문에 따로 운영하고 있다. 입출력 제어기에서는 다루지 않는다.
메모리는 CPU의 명령에 따라 작동한다. 하지만 DMA는 CPU의 도움 없이도 메모리에 접근할 수 있도록 입출력 제어기에 부여된 권한으로, 입출력 제어기에는 직접 메모리에 접근하기 위한 DMA 제어기가 마련되어 있다.
오늘날의 입출력 시스템에서는 CPU가 작업하는 공간과 DMA 제어기가 데이터를 옮기는 공간을 분리하여 메인메모리를 운영하는데, 이를 메모리 맵 입출력(memory mapped I/O)라고 부른다. 메모리 맵 입출력에서는 메인메모리의 주소 공간 중 일부를 DMA 제어기에 할당하여 작업 공간이 겹치는 것을 막는다.
시스템에서 발생한 다양한 종류의 이벤트 혹은 그런 이벤트를 알리는 메커니즘 이다. 인터럽트의 예시는 기본적으로 전원에 문제 생겼을 때 I/O 작업이 완료됐을 때, 시간 만료, 0으로 나누기, 잘못된 메모리 공간 접근 시도 등이 있다. 인터럽트가 발생하면 CPU에서는 즉각적으로 인터럽트 처리를 위해 커널 코드를 커널 모드에서 실행하게 된다.
입출력 제어기와 DMA 제어기의 협업으로 작업이 완료되면 입출력 제어기는 CPU에 인터럽트를 보낸다. 인터럽트는 주변장치의 입출력 요구나 하드웨어의 이상 현상을 CPU에 알려주는 역할을 하는 신호이다. 예를들어 CPU가 요청한 작업을 완료했을 떄, 키보드로 데이터 입력을 받았을 떄, 네트워크 카드에 새로운 데이터가 도착했을 때, 하드웨어에 이상이 발생했을 떄 등 다양한 경우 인터럽트가 발생한다.
CPU는 어떤 인터럽트인지 확인하기가 어렵다. 따라서 각 장치에는 IRQ라는 고유의 인터럽트 번호가 부여되어있는데, 예를 들면 IRQ 1번은 키보드, 10번은 네트워크, 12번은 마우스이다. CPU는 IRQ를 통해 파악한다.
여태까지 나온 인터럽트는 모두 외부 인터럽트이다. 외부 인터럽트는 입출력장치 뿐만 아니라 전원 이상이나 기계적 오류까지 모두 포함하므로 하드웨어 인터럽트라고 한다.
반대로 숫자를 0으로 나누거나, 주소 공간을 벗어나서 작업하는 것과 같이 프로세스의 오류와 관련된 인터럽트는 내부 인터럽트이다. 내부 인터럽트는 프로세스의 잘못이나 예상치 못한 문제 때문에 발생하는 인터럽트이므로 예외 상황(Exception) 인터럽트라고 부른다.
이러한 인터럽트는 사용자의 의지와 상관없지만 사용자가 직접 발생시키는 인터럽트도 있다. Ctrl + C이나 kill 명령어들이 그 예시이다. 이러한 자발적 인터럽트를 시그널이라고 부른다.
시스템 내에는 보통 100개 이상의 인터럽트가 있다. 인터럽트는 한 번에 하나 혹은 여러 개가 발생한다. 인터럽트 벡터는 어떤 인터럽트가 발생했는지 파악하기 위해 사용하는 자료 구조이다. 1이면 발생 0이면 안발생이다. 인터럽트가 발생하면 인터럽트 핸들러를 호출하여 작업을 한다. 이 핸들러는 인터럽트가 발생했을 시 어떤 루틴을 실행하는 그런 매개체 같은 느낌이다. 정확히는 모르겠다. 혹은 중간 지점이라고도 할 수 있다. 어쨋든 핸들러는 인터럽트를 처리하기 위함 함수라고 생각하면 된다. 인터럽트 벡터에는 해당 인터럽트 핸들러를 호출할 수 있도록 인터럽트 핸들러가 저장된 메모리의 주소가 포인터 형태로 등록되어 있다.
시그널의 경우 자신이 만든 인터럽트 핸들러를 등록할 수도 있다. 예를들면 kill을 프로세스 종료가 아닌 저장으로 바꿀수도 있다.
이러한 입출력이나 데이터간의 통신에서 버퍼가 사용되는데 이는 어느정도 저장해두었다가 한 번에 이동하도록 하는 통로같은 것으로 속도가 다른 두 장치의 속도 차이를 완화하는 역할을 한다. 버퍼는 데이터가 꽉 차 있지 않으면 일정시간이 흐른 후 데이터를 전송한다.