입출력 장치는 앞서 학습한 CPU, 메모리보다 다루기가 더 까다롭다.
그 이유로는,
- 입출력 장치 종류가 너무 많다. -> 규격화가 어렵다
- CPU, 메모리의 데이터 전송률에 비해 입출력 장치의 데이터 전송률이 낮다. -> 병목 발생 위험
위와 같은 문제를 해결하기 위해 입출력 장치에는 장치 컨트롤러(하드웨어)가 존재한다.
- CPU와 입출력 장치 간의 통신중개 -> "일종의 번역가 역할"
- 오류 검출
- 데이터 버퍼링 -> "버퍼 라는 임시 저장공간에 미리 입출력 장치의 데이터를 저장하고 CPU 전송률을 비슷하게 맞추기 위함"
장치 컨트롤러는 버스와 연결되어 데이터를 주고 받는다.
이때 버스는 시스템 버스중에서도 데이터 버스, 제어 버스를 의미한다.
주고 받는 데이터는 세 가지로 데이터, 상태, 제어 데이터를 주고받으며 이들은 모두 각각의 레지스터에 저장된다.
CPU와 입출력 장치 사이에 주고받을 데이터가 담기는 레지스터라고 하며 위에서 설명한 버퍼이다.
최근에는 이 데이터 양이 많아짐에 따라 버퍼 레지스터를 지원할 장치 컨트롤러 내부에 RAM으로 보완하기도 한다.
예 : 키보드 입력이 들어오면 데이터 레지스터에 저장되고 CPU가 그것을 읽는다.
상태(입출력 장치의 준비여부, 작업완료 여부, 오류존재 여부)등의 상태값을 저장한다.
입출력 장치가 수행할 내용에 대한 제어 정보가 담긴다.
장치 컨트롤러의 동작을 감지하고 제어하는 프로그램이다.
장치 드라이버가 설치되어 있지 않다면 해당 입출력장치를 사용할 수 없다.
이 장치 드라이버는 운영체제가 인식할 수 있어야하며 장치 드라이버를 실행시키는 자는 운영체제이다.
- 프로그램 입출력
- 인터럽트 기반 입출력
- DMA 입출력
프로그램 속 명령어로 입출력 장치를 제어하는 방법이다.
예로, 메모리의 저장된 데이터를 하드디스크에 백업하는 프로세스를 생각해보자.
(= 하드 디스크에 새로운 정보 쓰기)
이 과정은 다음과 같다.
- CPU는 장치 컨트롤러의 제어 레지스터로 쓰기 제어신호를 보낸다.
- 하드디스크 컨트롤러는 하드 디스크의 상태를 확인하고, 컨트롤러의 상태 레지스터에
준비 완료표시를 한다. - CPU는 이 상태 레지스터의 준비 완료 신호를 읽고 저장할 데이터를 데이터 레지스터로 보낸다.
- 데이터 저장이 완료될 때 까지 1~3 과정을 반복한다.
이런 식으로 CPU가 장치 컨트롤러의 값을 읽고 씀으로써 이루어지는 것이 프로그램 입출력방식이다.
분명 입출력 장치는 종류가 너무 많아 규격화가 어렵다고 언급했다.
CPU는 어떻게 프로그램 입출력 과정의 1~3에서 특정 장치 컨트롤러를 인식하고 그에 맞는 명령어를 수행하는걸까?
입출력 장치들도 RAM처럼 특정 주소값들을 가지고 있다.
CPU는 메모리 주소에 읽고 쓰는 것과 같은 명령어를 동일하게 사용해서 입출력 장치를 제어할 수 있다.
CPU는 각 장치 컨트롤러가 메모리 맵 상 어느 주소에 있는지 이미 알고있다.(BIOS/OS가 설정)
다시 정리하면, LDR R0, [0xFF00]과 같은 명령어는 형태적으로는 메모리에 접근하는 명령어이다.
하지만 이 주소는 입출력장치의 레지스터 주소이다.
즉 "메모리를 쓰는 것 처럼" 보이는 것이지만 메모리를 쓰는 것이 아니다.
위의 의문점을 해결할 수 있다. CPU는 각 입출력장치의 레지스터들에 접근하기 위해 새로운 명령어를 쓰지 않고 메모리에 접근하듯이 접근할 수 있다. 다만 그 주소가 메모리의 주소가 아니라 레지스터로의 주소일 뿐이다.
대부분의 현대 시스템은 이 방식을 사용한다.
우선 메모리 접근 명령어로 하여금 모든 입출력 장치에 접근가능하다는 장점이 매우 강력하기 때문이다.
고립형 입출력 방식은 CPU가 따로 입출력 장치에 대한 명령어(ex.. IN, OUT)를 확보해야하며
그렇기에 주소가 메모리 주소와 겹쳐도 상관없다. 어차피 명령어단에서의 분기에 의해 I/O버스로 하여금 메모리 주소로는 접근이 불가능하기 때문이다.
그렇다면 메모리맵 방식은 I/O버스를 사용하지 않는다는 것이 된다.
실제로 그렇다. CPU의 모든 주소 접근은 주소 버스를 통하며 버스 시스템에 주소 디코더가 존재하여 만약 입출력 접근이라면 입출력 장치 레지스터로 보내게 되는 것이다.
하드웨어 인터럽트는 정확히는 입출력 장치의 장치 컨트롤러에 의해 발생한다.
우리가 컴퓨터를 사용할 때 흔히 입출력 장치로는 키보드, 마우스, 모니터, 스피커와 같은 장치들을 사용한다.
일반적으로 이용할 때에, 입출력 장치들은 동시다발적으로 인터럽트를 보내게 될 것이다.
인터럽트 중에서는 우선순위가 높은 인터럽트가 존재한다.
인터럽트A보다 B가 중요하고, A -> B 순으로 인터럽트가 들어온다면 A 인터럽트 서비스 루틴 도중 B 인터럽트 서비스 루틴이 실행된다.
동시다발적인 하드웨어 인터럽트를 우선순위에 맞게 처리하기 위해 여러 장치 컨트롤러에 연결되는 PIC 칩을 활용한다.
우선순위를 판단하고, CPU에게 이를 알려준다.
이 PIC는 개념적으로 CPU와 입출력장치 사이에 존재하며, 실제로는 예전 기준 외부 칩 형태로 존재했고 현재 CPU상품들에는 CPU칩셋 내부에 포함되어있다.
기본적으로는 입출력 장치와 메모리 간의 데이터 이동은 CPU가 주도한다.
즉 이동하는 데이터는 모두 CPU를 거치게 되어있다.
만약 하드디스크 백업과 같이 엄청난 양의 입출력 장치의 데이터를 다른 입출력 장치에 데이터를 쓰기 위해서는
엄청난 양의 데이터가 CPU를 거쳐가야한다. 이 데이터들은 RAM에 임시로 담기고 CPU는 다시 RAM의 데이터를
백업용 하드디스크에 쓴다.
이 경우 CPU의 부담은 매우 커진다. CPU의 다른 일도 바쁘기 때문이다.
DMA 컨트롤러는 CPU를 거치지 않고 메모리와 입출력장치간의 데이터를 주고받을 수 있게 한다.
- CPU는 DMA 컨트롤러에 입출력 작업을 명령한다.
- DMA 컨트롤러는 CPU 대신 장치 컨트롤러와 상호작용하며 입출력 작업을 수행한다.
- 입출력 작업이 끝나면 DMA 컨트롤러는 인터럽트를 통해 CPU에 작업이 끝났음을 알린다.
DMA 컨트롤러 또한 예전에는 메인보드 위에 별도의 칩으로 존재했지만, 현재에서는 CPU 칩셋에 내장되어있는 형태로 존재한다.
CPU는 DMA활용시 입출력 작업의 시작과 끝에만 관여하게 되는 것이다. -> CPU의 입출력 장치에 대한 부담이 줄어든다.
그렇지만 DMA가 모든 CPU의 입출력 작업을 대신하는 것은 아니다.
DMA 대량의 데이터 전송에 대해서만 효과적이다.
그렇기에 일반적으로 사용하는 마우스나 키보드 같은 입출력 장치 처리는 CPU가 수행한다.
DMA 컨트롤러는 CPU 없이도 메모리와 입출력 장치간의 데이터를 주고받기 위해 시스템 버스를 사용한다.
보통 CPU가 주도적으로 사용하는 시스템 버스를 DMA가 독자적으로 이용할 수 있어야 하기에 버스에 대한 접근 권한 조정이 필요하다.
그렇기에 DMA 컨트롤러는 시스템 버스에 접근하기 전에 접근요청 -> 버스 제어권 획득의 방식으로 버스 사용권을 획득하여 버스를 사용한다.
의문 : DMA는 대용량 입출력 장치 데이터 <-> 메모리를 처리해야하다보니 버스 제어권을 오랫동안 사용할 수 있을 것이다. 우리가 DMA를 도입한 이유는 CPU의 부담을 덜어주려고 했는데 DMA가 CPU가 하는 일을 다시 막게되는 것이다.
이 의문에 대해 사이클 스털링과 같은 해결책을 사용한다.
CPU가 버스를 쓰지 않는 짦은 틈(CPU idle cycle)때에 DMA가 버스를 점유해서 사용하는 것이다.
이 사이클 스털링 방식은 CPU의 행차에 최대한 발목을 잡지 않게끔 할 목적으로 대신 DMA가 완벽히 자신의 속도를 낼 수는 없다.
반면 다른 해결책인 버스트 모드와 같은 것도 존재한다.
CPU을 일시적으로 정지시켜도 전송속도가 더 중요할 때 그렇다. 이 경우 DMA 컨트롤러가 버스를 오랫동안 붙잡고 데이터를 대량 전송한다.
이러한 DMA가 버스를 사용하는 여러 방식을 OS 주도하에 혼합적으로 사용하여 최대한의 효율로 작업을 수행한다.


