운영체제 2. 최고의 운영체제 시스템이 무엇일까
최고의 운영체제 시스템은 무엇인가?
이를 이해하기 위해, 운영체제가 어떻게 발전했는지 살펴볼 필요가 있다.
초창기 프로그래밍은 펀치카드에 구멍을 뚫어 기계어를 기록하고, 카드 리더기에 읽혀서 프로그램을 실행하는 방식이었다. 카드 리더기와 컴퓨터를 사용하기 위해선 전산실에 예약을 걸어두고, 그 시간대가 되면 사용할 수 있는 방법이었다. 이는 여러 문제점이 존재한다.
- 입력, 출력을 하는 동안 CPU가 일할 수 없음
- 낭비되는 시간이 존재함
실제 필요한 시간보다 더 긴 예약 시간
- 사용하기 위해서 오랫동안 기다려야 함
먼저 첫번째 문제를 해결하기 위해 주컴퓨터 뿐만 아니라 입출력을 담당하는 컴퓨터를 하나 추가한다. 입출력 담당 컴퓨터와 계산 담당 컴퓨터를 분리되어 위성컴퓨터에서 입력을 받는동안 주컴퓨터에서 연산을 이어갈 수 있게 되었다.
이후 2, 3번째 문제를 해결하기 위해 운영자(operator)를 고용한다. 사용자 입장에선 카드들(Batch)을 넘기면 운영자가 프로그램을 돌려서 프린트해준다. 사용자는 나중에 다시 방문해서 출력물을 가져가기만 하면 된다. 이것이 초기 일괄처리 시스템 형태다. 하지만 이 시스템 또한 운영자 고용, 하드웨어 도입에 대한 추가 비용 발생한다는 문제점이 존재한다.
운영자가 하던 자기 테이프를 옮기는 일을 자동화하고자 했다. 입출력 장치로부터 입력을 받으면 자동으로 메모리에 기록되고, CPU가 기록된 데이터를 읽어 연산을 하고, 연산이 끝내면 자동으로 출력되어야 한다. 따라서 다음과 같은 구조를 고안한다.
입출력 장치를 통해 프로그램을 입력받으면, 채널을 통해 메모리 내의 버퍼에 기록한다. 데이터를 모두 기록하면, CPU에게 인터럽트를 날린다. CPU는 신호를 받으면 버퍼에 있는 내용을 사용자 영역의 메모리에 복사 후, 복사한 내용을 가지고 연산을 수행한다. 이후 실행 결과를 버퍼에 기록 후 입출력 장치에 명령을 보낸다. 입출력 장치는 출력 명령을 받으면 버퍼에 있는 결과를 채널을 통해 가져와 출력하게 된다.
[!question] 왜 버퍼에 있는 내용을 그대로 사용하지 않고, 사용자 영역으로 복사하는가?{title} CPU가 연산하는 동안 입출력을 멈추지 않고 계속 받을 수 있고, 반대로 입출력을 받는동안 CPU가 작업할 수 있다는 컨셉은 계속 유지되어야 한다. CPU가 버퍼에 저장된 원본을 그대로 사용한다면, 연산 도중 입출력 장치가 버퍼에 내용을 덮어쓰게 된다면 오류가 발생할 것이다.
[!question] 왜 채널을 사용하는가?{title} 만약 채널을 사용하지 않으면 어떤 구조를 가질까? 입출력 장치에는 입력한 값 또는 출력할 값을 저장하는 작은 메모리가 존재한다. 이를 주변기기의 레지스터라고 한다. 정석적인 입출력 방법은 CPU가 주변기기 레지스터 값을 복사해서 메모리에 쓰거나, 메모리의 값을 주변기기 레지스터에 씀으로써 입출력이 가능해진다. 이러면 입출력을 하는동안 CPU는 아무것도 할 수 없게 된다.
입력 명령이 처리될 때까지 기다려야 하므로
따라서 기존의 위성 컴퓨터와 같이 입출력 기능만 따로 담당하는 컴퓨터의 역할을 하는것이 바로 채널이다. 채널은 DMA(Direct Memory Access)
를 사용해서 메모리에 직접 접근이 가능하다.
[!question] CPU와 입출력 장치가 같은 버퍼에 독립적으로 읽고 쓰는 것을 어떻게 보장해주는가?{title} 기본적으로 메모리 제어권은 CPU에게 있다. 채널의 DMA 컨트롤러는 메모리 제어권이 필요해지면 CPU에게 요청을 보낸다. CPU는 주기적으로 메모리를 사용하지 않는 순간이 존재한다. 그 한 주기를 DMA 컨트롤러에게 넘기고, 작업 하나가 끝나면 즉시 CPU가 제어권을 다시 가져간다. 이를 Cycle Stealing이라고 부른다.
배치를 입력하면 입출력 장치에서 채널을 통해 메모리 안에 있는 버퍼에 데이터를 기록한다. 데이터를 모두 기록하면, CPU에게 인터럽트를 날려 다 작성했다는 신호를 보낸다. CPU는 신호를 받으면 연산을 하고, 출력 장치에 연산이 끝났다는 신호를 보낸다. 출력 장치는 신호를 받으면 채널을 통해 연산 결과를 버퍼에서 가져와서 결과를 출력한다.
CPU는 그저 연산하는 하드웨어일 뿐이다. 이런 CPU에 인터럽트를 받으면 CPU선 그 인터럽트를 처리하는 프로그램이 있어야 인터럽트를 받을 수 있다. 그 프로그램을 ISR(메모리 특정 부분에 있는 함수, Interrupt Service Routine)이라고 하고, ISR 또한 프로그램이기 때문에 메모리 상에 존재한다.
ISR은 인터럽트를 받으면 먼저 버퍼에 있는 내용을 메모리 위로 복사한다. 그 이유는, 입출력 장치에 의해 싸이클 스틸이 일어나면 버퍼의 내용이 달라지고, 버퍼의 내용이 달라지면 기존의 프로그램이 훼손되기 때문이다. 따라서 복사본을 통해 온전한 프로그램을 실행하도록 보장한다.
인터럽트는 메모리에 프로그램을 다 작성했다는 인터럽트만 있는 것은 아니다. 예로, 입출력 장치가 고장나면 더이상 입출력을 받을 수 없다는 신호 또한 인터럽트를 통해 보낸다. 인터럽트 종류에 따라 그것을 처리하기 위한 ISR이 하나씩 존재한다.
ISR 뿐만이 아니라 메모리 상에서 계속 상주하면서 운영을 도와주는 프로그램들이 하나 둘 씩 늘어나기 시작했다. 이를 상주 모니터 (Resident Moniter)라고 불렀다. 이게 운영체제의 원형이다.
상주 모니터의 등장에 따라, 사용자 프로그램이 상주 모니터가 사용하는 메모리 범위를 침범하지 못하도록 보호할 필요가 생겼다. 따라서, 프로그램이 메모리를 직접 사용하게 하지 않고 인터페이스를 도입해서 상주 모니터 범위의 메모리를 사용하지 못하도록 체크한다. (시스템 콜 개념의 등장)
여기까지가 일괄 처리 시스템에 대한 설명이다. 일괄처리 시스템은 프로그램을 한개만 돌릴 수 있기 때문에 문제가 발생한다. 프로그램에서 입력이 있으면, 입력이 들어오는동안 아무 작업도 할 수 없다. 따라서, 하나가 아닌 여러개의 프로그램을 메모리 위에 올려두고, 실행중인 프로그램의 I/O 작업이 처리될 동안 다른 프로그램을 실행하도록 개선한다. 이를 Multiprogramming이라고 한다.
멀티프로그래밍 시스템도 몇가지 문제가 존재한다.
- 출력 문제
- 프로그램이 동시에 실행되므로, 출력도 동시에 일어나 섞일 수 있다. 각 프로그램이 독립적으로 출력될 수 있도록 보장해주어야 한다.
- 간섭 문제
- 다른 프로그램의 실행 시간이 현재 실행중인 프로그램의 I/O 빈도, 시간에 의해 결정된다. 즉 프로그램 사이의 의존성이 존재하게 된다.
- 메모리 문제
- 메모리 위에 올라가있는 여러 프로그램중 하나가 종료되면, 메모리 상에서 더이상 남아있을 이유가 없다. 따라서 지워버리는데, 지우면 공간이 생긴다. 이후 다른 프로그램을 새로 실행할 때 그 빈공간보다 크면 추가 공간이 생길 때까지 기다려야 한다.
- 작아도 문제가 되는데, 자투리 메모리가 발생한다. 이를 프래그먼트라고 하는데, 실행시간이 길어질수록 이 프래그먼트가 계속 생기는 문제가 발생한다.
출력 문제를 어떻게 해결하는가?
출력 데이터를 바로 입출력 장치로 보내지 않고, 디스크에 마련된 특수한 공간에 잠시 저장한다. 이 공간을 SPOOL (Simultaneous Peripherial Operations On-Line)이라고 한다. 프로그램이 스풀에 데이터를 모두 출력하면, 그때서야 출력 장치로 데이터를 보내서 출력한다.
Spool 내에 각 프로그램마다 할당된 큐가 존재하고, 그 큐에 출력 데이터가 쌓인다.
간섭 문제를 어떻게 해결하는가?
I/O중일 때 스케쥴링한다. 스케쥴링 정책이 이것밖에 없다면, 실행중인 프로그램이 I/O를 하지 않으면 다른 프로그램은 실행될 수 없다. 따라서, 한 프로그램이 너무 오래 실행되고 있다면 다른 프로그램을 실행하도록 하는 스케쥴링 정책을 추가한다. 오래 실행되고 있다는 기준은 얼마인가? 리눅스에선 10ms가 기본값이다. 그 기준을 time slice라고 하고, 여기까지의 시스템을 시분할 시스템이라고 부른다.
\[\text{현재 사용하는 대부분 OS가 시분할 시스템을 채택하고 있다.}\][!question] OS에 User 기능이 있는 이유?{title} 1990년 초반까진 컴퓨터가 너무 비싸니까, 한 컴퓨터에 시분할 시스템을 깔고 몇백개의 모니터를 하나의 컴퓨터에 연결해서 사용했음.