[혼자 공부하는 컴퓨터 구조 + 운영체제] 8강 입출력장치
장치 컨트롤러와 장치 드라이버
컴퓨터에는 CPU와 메모리만 존재하지 않는다. 스피커, 모니터, 키보드, 마우스 등 다양한 입출력 장치가 있는데, 이런 외부 장치들이 어떻게 내부와 소통하는지 알아보자.
장치 컨트롤러
입출력장치는 CPU, 메모리보다 다루기 까다롭다. 이유는 크게 두 가지가 있다.
- 입출력장치의 종류가 많다
- 일반적으로 CPU와 메모리 데이터 전송률은 높지만 입출력장치의 데이터 전송률은 낮다
이런 이유로 입출력 장치는 장치 컨트롤러(device controller)라는 하드웨어를 통해 연결된다. 장치 컨트롤러는 입출력 제어기(I/O controller), 입출력 모듈(I/O module)등으로 다양하게 불리기도 하지면, 여기에선 장치 컨트롤러라고 부르겠다.
모든 입출력장치는 각자의 장치 컨트롤러를 통해 컴퓨터 내부와 정보를 주고받고, 장치 컨트롤러는 하나 이상의 입출력장치와 연결되어있다.
장치 컨트롤러는 대표적으로 다음과 같은 역할을 통해 앞에서 언급한 문제를 해결한다.
- CPU와 입출력장치 간의 통신 중개
- 오류 검출
- 데이터 버퍼링
장치 컨트롤러의 내부는 매우 복잡하기 때문에 모두 알기 어렵지만, 여기에서 중요하게 알아두어야 할 것은 다음과 같다.
- 데이터 레지스터
- 상태 레지스터
- 제어 레지스터
장치 드라이버
장치 드라이버는 장치 컨트롤러의 동작을 감지하고 제어함으로써 장치 컨트롤러가 컴퓨터 내부와 정보를 주고받을 수 있게 하는 프로그램이다. 프로그램이기 때문에 실행과정에서 메모리에 저장된다.
컴퓨터가 드라이버를 인식하고 실행할 수 있다면, 어떤 장치던 컴퓨터 내부와 정보를 주고받을 수 있다. 반대로 드라이버를 인식할 수 없다면, 정보를 주고받을 수 없다.
이때 장치 드라이버를 인식하고 실행하는 주체는 운영체제이다.
다양한 입출력 방법
프로그램 입출력
프로그램 입출력(programmed I/O)은 프로그램속 명령어로 입출력장치를 제어하는 방법이다. CPU가 명령어를 실행하는 과정에서 입출력 명령어를 만나면, 장치 컨트롤러와 상호작용하여 입출력 작업을 수행한다.
하드 디스크에 정보를 써야한다면, 다음과 같은 순서로 작업을 수행한다.
- CPU가 장치 컨트롤러의 제어 레지스터에 쓰기 명령을 보낸다.
- 장치 컨트롤러가 하드디스크를 체크하고, 쓰기를 수행할 준비가 된다면, 상태 레지스터에 준비가 되었음을 표시한다.
- CPU는 상태 레지스터를 읽고 준비가 완료되었음을 인식하게되면, 데이터 레지스터에 저장할 데이터를 기록한다. 쓰기가 끝나지 않았다면 1번부터 다시 수행하고, 쓰기를 완료했다면 작업을 종료한다.
이때 CPU는 어떻게 장치 컨트롤러의 레지스터에 접근할 수 있는걸까? CPU가 장치 컨트롤러의 레지스터에 접근하는 방식은 크게 두 가지가 있다
- 메모리 맵 입출력(Memory-mapped I/O)
- 고립형 입출력(isolated I/O)
인터럽트 기반 입출력
4장에서 배운 인터럽트에서는 ‘CPU가 입출력장치에 처리할 내용을 명령하면 입출력장치가 명령어 수행하는 동안 CPU가 다른 일을 할 수 있다’라고 했다. 실제로는 CPU가 입출력장치에 직접 명령을 하는게 아닌, 장치 컨트롤러에 명령을 한다.
장치 컨트롤러가 입출력 작업을 마치면, CPU에 인터럽트 신호를 보낸다. CPU는 하던일을 잠시 백업하고 인터럽트 서비스 루틴을 실행한다.
이렇게 인터럽트 기반으로 하는 입출력을 인터럽트 기반 입출력이라고 한다.
인터럽트와 자주 비교되는 개념으로 폴링(polling)이 있다. 인터럽트 방식은 인터럽트가 오는 시점에만 기능을 수행하는 방식이지만, 폴링은 매 순간마다 기능을 수행할 수 있는지 검사하고, 가능하다면 기능을 수행하는 방식이다.
인터럽트와 달리 주기적으로 검사를 하는 방식이기 때문에 부담이 크다는 특징이 있다.
일반적으로 컴퓨터는 여러개의 입출력장치가 연결되어 있는데, 만약 여러개의 입출력장치가 동시에 인터럽트를 발생시키면 어떻게 동작해야할까?
간단한 방식으로는 인터럽트가 발생한 순서대로 처리하는 것이다. 하지만 현실적으로 모든 인터럽트를 순차적으로 처리할 수는 없다. 더 먼저 처리되어야하는 인터럽트가 있기 때문이다. 즉, CPU는 인터럽트의 우선순위를 고려하여 우선순위가 높은 인터럽트 순으로 처리할 수 있다.
플래그 레지스터 속 인터럽트 비트가 비활성화 되어 있어도 무시할 수 없는 인터럽트인 NMI(Non-Maskable Interrupt)가 발생한 경우 CPU는 이렇게 우선순위가 높은 인터럽트 부터 처리한다.
우선순위를 반영하는 방법은 여러 가지가 있지만, 많은 컴퓨터에서는 프로그래머블 인터럽트 컨트롤러(PIC; Programmable Interrupt Controller)라는 하드웨어를 사용한다. PIC는 여러 장치 컨트롤러에 연결되어 장치 컨트롤러에서 보낸 하드웨어 인터럽트 요청들의 우선순위를 판별한 뒤 CPU에 지금 처리해야 할 하드웨어 인터럽트는 무엇인지 알려주는 장치이다.
PIC에는 여러 핀이 있는데, 각 핀은 하드웨어 인터럽트 요청을 보낼 수 있는 약속된 하드웨어가 연결되어 있다. 가령 첫 번째 핀은 타이머, 두 번째 핀은 키보드 처럼 연결되어있다.
PIC에 연결된 장치 컨트롤러들이 동시에 하드웨어 인터럽트 요청을 보내면 PIC는 이 들의 우선순위를 판단하여 CPU에 가장 먼저 처리할 인터럽트를 알려준다. 일반적으로 더 많고 복잡한 장치들의 인터럽트를 관리하기 위해 아래와 같이 PIC를 두 개 이상 계층으로 구성한다.
PIC의 다중 인터럽트 처리 과정은 다음과 같다.
- PIC가 장치 컨트롤러에서 인터럽트 요청 신호 (들)를 받아들인다.
- PIC는 인터럽트 우선순위를 판단한 뒤 CPU에 처리해야 할 인터럽트 요청 신호를 보낸다.
- CPU는 PIC에 인터럽트 확인 신호를 보낸다.
- PIC는 데이터 버스를 통해 CPU에 인터럽트 벡터를 보낸다.
- CPU는 인터럽트 벡터를 통해 인터럽트 요청의 주체를 알게 되고, 해당 장치의 인터럽트 서비스 루틴을 실행한다.
참고로 PIC는 무시할 수 없는 인터럽트인 NMI까지 우선순위를 판별하지 않는다. NMI는 우선순위가 가장 높아 우선순위 판별이 불필요하기 때문이다.
DMA 입출력
프로그램 기반 입출력과 인터럽트 기반 입출력에는 공통점이 있다. 바로 메모리 간의 데이터 이동은 CPU가 주도하고, 이동하는 데이터도 반드시 CPU를 거친다는 점이다.
예를들어 입출력장치의 데이터를 메모리에 저장하려면 다음과 같은 절차로 진행된다.
- CPU가 장치 컨트롤러에서 입출력 장치 데이터를 하나씩 읽어 레지스터에 적재한다.
- 레지스터에 적재된 데이터를 메모리에 저장한다.
반대로 메모리에 있는 데이터를 입출력장치에 보내는 과정도 동일하게 레지스터를 거쳐서 저장된다. 이는 CPU의 부담이 커지게 만든다.
그래서 입출력장치와 메모리가 CPU를 거치지 않고도 상호작용을 할 수 있는 입출력 방식인 DMA(Direct Memory Access)가 등장했다. DMA는 이름 그대로 직접 메모리에 접근할 수 있는 입출력 기능이다. 이를 사용하기 위해선 시스템 버스에 연결된 DMA 컨트롤러라는 하드웨어가 필요하다.
일반적으로 DMA 입출력은 다음과 같이 이루어진다.
- CPU는 DMA컨트롤러에 입출력장치의 주소, 수행할 연산(읽기/쓰기), 읽거나 쓸 메모리의 주소 등과 같은 정보로 입출력 작업을 명령한다.
- DMA 컨트롤러는 CPU대신 장치 컨트롤러와 상호작용하며 입출력 작업을 수행한다. 이때 DMA 컨트롤러는 필요한 경우 메모리에 직접 접근하여 정보를 읽거나 쓴다.
- 입출력 작업이 끝나면 DMA 컨트롤러는 CPU에 인터럽트를 걸어 작업이 끝났음을 알린다.
이렇게 입출력장치와 메모리 사이에 주고받을 데이터가 CPU를 거치지 않으면 작업부담을 훨씬 줄일 수 있게된다.
하지만 여기에서 생각해봐야 하는 문제가 있는데, 시스템 버스는 동시에 사용이 불가능하는 점이다. CPU가 시스템 버스를 사용할 때 DMA 컨트롤러는 시스템 버스를 사용할 수 없다. 그래서 DMA 컨트롤러는 CPU가 시스템 버스를 이용하지 않을 때마다 조금씩 시스템 버스를 이용하거나, CPU가 일시적으로 시스템 버스를 이용하지 않도록 허락을 구하고 시스템 버스를 집중적으로 이용한다.
CPU 입장에서는 마치 버스에 접근을 주기적으로 도둑맞는 기분이 들것이다. 그래서 이러한 DMA의 시스템 버스 이용을 사이클 스틸링(cycle stealing)이라고 부른다.
DMA 컨트롤러와 장치 컨트롤러의 연결 방식과 입출력 버스에 대해서 알아보자.
CPU, 메모리, DMA 컨트롤러, 장치 컨트롤러가 모두 같은 버스를 공유하는 구성에서는 DMA를 위해 한번 메모리에 접근할때마다 시스템 버스를 두 번 사용하게 되는 부작용이 있다.
예를들어 메모리에 있는 정보를 하드디스크에 저장하려고 하면, 메모리에 있는 데이터를 DMA 컨트롤러로 옮겨오면서 한 번 사용하고, DMA 컨트롤러에 있는 데이터를 하드디스크에 저장할때 한 번 사용한다.
이렇게 DMA를 위해 시스템 버스를 자주 사용하면 CPU가 그만큼 시스템 버스를 이용하지 못한다는 문제가 있다. 이런 문제는 장치 컨트롤러를 입출력 버스(input/output bus)라는 별도의 버스에 연결하여 해결할 수 있다. 장치 컨트롤러들이 시스템 버스가 아닌 입출력 버스로 DMA 컨트롤러에 연결된다면, DMA 컨트롤러와 장치 컨트롤러가 서로 데이터를 전송할때는 시스템 버스를 이용하지 않는다.
현대 대부분 컴퓨터에는 입출력 버스가 있다.
입출력 버스에는 PCI 버스, PCI Express(PCIe)버스 등 여러 종류가 있다. 현재 사용중인 대부분의 입출력장치들은 이렇게 입출력 버스와 연결되는 통로를 통해 시스템 버스를 타고 CPU와 정보를 주고받는다.
DMA를 사용하기 위해선 CPU가 입출력 명령어를 인출하고, 해석하고, 실행하는 연산을 해야한다. 그러나 최근에는 메모리에 직접 접근할 뿐 아니라 입출력 명령어를 직접 인출하고, 해석하고, 실행까지 하는 일종의 입출력 전용 CPU가 만들어졌는데, 이를 입출력 프로세서(IOP; Input/Output Processor) 혹은 입출력 채널(Input/Output Channel)이라고 부른다.
입출력 채널이 있는 컴퓨터는 CPU가 입출력 명령어를 실행하지 않는다. CPU가 입출력 채널에게 메모리에 저장된 특정 입출력 명령어를 수행하라고 지시하면, 입출력 채널이 처리를 한 뒤에 CPU에게 결과를 알린다.