TIL

[TIL/혼공컴운] 2025/02/08

입출력장치 ✍️ 1. 입출력장치 ⚙️ 1-1. 장치 컨트롤러와 장치 드라이버 🌿 1-1-1. 장치 컨트롤러 💡 일단 공감이 필요하다. 입출력장치는 지난 글에서 다루었던 CPU, 메모리보다 다루기 훨씬 까다롭다. 크게 두 가지 이유가 있다. 입출력장치의 종류

2025년 2월 8일5min read

입출력장치 ✍️

1. 입출력장치 ⚙️

1-1. 장치 컨트롤러와 장치 드라이버 🌿

1-1-1. 장치 컨트롤러 💡

일단 공감이 필요하다. 입출력장치는 지난 글에서 다루었던 CPU, 메모리보다 다루기 훨씬 까다롭다. 크게 두 가지 이유가 있다.

1. 입출력장치의 종류가 너무 많다. 2. 일반적으로 CPU/메모리보다 입출력장치의 데이터 전송률이 낮다.

가장 먼저, 입출력장치의 종류가 너무 많다. 키보드, 모니터, 마우스, 프린터, 스피커, 마이크 등 매우 많다. 장치마다 속도, 데이터 전송 형식 등에 차이가 발생하기 때문에, 다양한 입출력장치와 정보를 주고받는 형식을 규격화하기 어렵다.

다음으로 ``데이터 전송률(transfer rate)``이란 데이터를 얼마큼 빨리 교환할 수 있는지를 나타내는 지표인데, CPU와 메모리의 전송률은 높지만, 입출력장치의 데이터 전송률은 상대적으로 낮다.

비유하자면, CPU/메모리는 1초에 1000마디를 내뱉는 사람이고, 입출력장치는 1초에 한 마디를 내뱉는 사람이라서 통신에 어려움이 생긴다.

이러한 이유로, 입출력장치는 컴퓨터에 직접 연결되지 않고 ``장치 컨트롤러(device controller)``라는 하드웨어를 통해 연결된다.

장치 컨트롤러는 세 가지 대표적인 역할을 통해 앞서 발생한 문제를 해결한다.

1. CPU와 입출력장치 간의 통신 중개 2. 오류 검출 3. 데이터 버퍼링

여기서 ``버퍼링(buffering)``이란, 전송률이 높은 장치와 그렇지 않은 장치 사이에 주고받는 데이터를 버퍼(buffer)라는 임시 저장 공간에 저장하여 전송률을 비슷하게 맞추는 방법을 뜻한다.

장치 컨트롤러 내부 구조의 경우, 실제로는 매우 복잡하다. 데이터 레지스터와 상태 레지스터, 그리고 제어 레지스터가 핵심 요소이기에 해당 개념에 대해 설명하고자 한다.

데이터 레지스터```는 버퍼 역할을 한다. 즉, CPU와 입출력장치 사이에 주고받을 데이터가 담기는 레지스터다.

마지막으로 ``제어 레지스터``는 입출력장치가 수행할 내용에 대한 제어 정보와 명령이 저장된다.

1-1-2. 장치 드라이버 💡

장치 드라이버```는 장치 컨트롤러라는 하드웨어를 제어하기 위한 프로그램이다.

프로그램이기에 실행 과정에서 메모리에 저장된다.

![](https://velog.velcdn.com/images/minkwan/post/094d5105-249f-472e-808a-bf730c37de56/image.png)

장치 드라이버를 인식하고 실행하는 주체는 windows, macOS와 같은 운영체제다. 장치 드라이버는 운영체제가 기본적으로 제공하는 것도 있지만, 장치 제작자가 따로 제공하기도 한다. 어쨌든, 장치 드라이버가 설치되어 있지 않다면 해당 입출력장치를 사용할 수 없다.

1-2. 다양한 입출력 방법 🌿

1-2-1. 프로그램 입출력 💡

프로그램 입출력(programmed I/O)```은 프로그램 속 명령어로 입출력장치를 제어하는 방법에 관한 논의다. 하드 디스크도 데이터를 입력하고 출력하는 기능을 수행하기에, 입출력장치(I/O Device)로 볼 수 있다. 메모리에 저장된 정보를 하드 디스크에 백업하는 상황을 가정해 보자.

![](https://velog.velcdn.com/images/minkwan/post/6521274f-56a6-43b4-8ed5-93f005c3548f/image.png)

가장 먼저, CPU는 하드 디스크 컨트롤러의 제어 레지스터에 '쓰기(write)' 명령을 보낸다.

하드 디스크 컨트롤러는 하드 디스크 상태를 확인한 뒤, 만약 하드 디스크가 준비된 상태라면, 하드 디스크 컨트롤러는 상태 레지스터에 준비되었다고 표시한다.

이후 CPU는 주기적으로 상태 레지스터를 확인하며 하드 디스크의 준비 여부를 확인한다. 하드 디스크가 준비되었다는 사실을 CPU가 인지하면, 백업할 메모리의 정보를 데이터 레지스터에 쓰게 된다.

그런데 CPU 입장에서는, 장치 컨트롤러의 레지스터들을 어떻게 아는 것일까?

관련해서, 메모리 맵 입출력과 고립형 입출력에 대해 살펴봐야 한다.

메모리 맵 입출력 방식```은, 메모리에 접근하기 위한 주소 공간과 입출력장치에 접근하기 위한 주소 공간을 하나의 주소 공간으로 간주하자는 방법이다.

중요한 것은 메모리의 주소들이나 장치 컨트롤러의 레지스터들이나 모두 동일하게 메모리 주소를 대하듯 하면 된다는 점이다. 따라서 메모리에 접근하는 명령어와 입출력장치에 접근하는 명령어가 굳이 다를 필요가 없다.


![](https://velog.velcdn.com/images/minkwan/post/f9ebf972-d21e-4623-bdd3-59d8333aa06c/image.png)

위 이미지처럼 제어 버스에 '메모리 읽기/쓰기' 선 외에 '입출력장치 읽기/쓰기' 선이 따로 있다면, 메모리를 위한 주소 공간과 입출력장치를 위한 주소 공간을 각각 1024개씩 확보할 수 있게 된다.

따라서 고립형 입출력 방식에서, CPU는 입출력장치에 접근하기 위해 메모리에 접근하는 명령어와는 다른 입출력 명령어를 사용하게 된다.

1-2-2. 인터럽트 기반 입출력 💡

인터럽트를 통해 CPU가 입출력장치에 처리할 내용을 명령하면, 입출력장치가 명령어를 수행하는 동안 CPU는 다른 일을 할 수 있다. ``입출력장치에 의한 하드웨어 인터럽트는 엄밀히 따지면, 입출력장치가 아니라 장치 컨트롤러에 의해 발생하게 된다.``

그런데 ``다중 인터럽트``, 그러니까 키보드와 모니터 그리고 스피커 등의 여러 입출력장치에서 인터럽트가 동시에 발생하는 경우에는, 이러한 인터럽트를 어떻게 처리할까?

우선 첫 번째 방법은, 인터럽트 ``발생 순서대로`` 처리하는 것이다.

플래그 레지스터 속 ``인터럽트 비트를 비활성화``한 채 인터럽트를 처리하면, 위 이미지와 같이 인터럽트 서비스 루틴을 순차적으로 처리하게 된다.

다만, 현실적으로 모든 인터럽트를 위와 같은 방식으로 처리할 수 없다. 그래서 우선순위를 고려하여 우선순위가 높은 인터럽트 서비스 루틴부터 처리하는 방식이 제안되었다. 인터럽트 서비스 루틴 B의 우선순위가 더 높다고 가정해 보자.

플래그 레지스터 속 ``인터럽트 비트가 활성화`되어 있는 경우 혹은 인터럽트 비트를 비활성화해도 무시할 수 없는 인터럽트인 `NMI(Non-Maskable Interrupt)가 발생`한 경우, 위와 같은 방식으로 프로세스를 처리한다. 이처럼 우선순위를 통해 다중 인터럽트를 처리할 때에는 `프로그래머블 인터럽트 컨트롤러(이하 PIC: Programmable Interrupt Controller)``라는 하드웨어를 활용한다.

PIC가 장치 컨트롤러에서 인터럽트를 받아들이고, 동시에 우선순위를 판별하여 CPU에 처리해야 할 인터럽트 요청 신호를 보낸다.

CPU는 PIC에 인터럽트 확인 신호를 보내고, PIC는 데이터 버스를 통해 CPU에 인터럽트 벡터를 보낸다.

CPU는 인터럽트 벡터를 통해 인터럽트 요청의 주체를 인지하게 되고, 장치에 맞는 인터럽트 서비스 루틴을 실행하게 된다.

추가적으로 NMI의 경우 우선순위가 가장 높아 우선순위 판별이 필요치 않기에, PIC에서 NMI에 대한 우선순위를 판별하지는 않는다.

1-2-3. DMA 입출력 💡

프로그램 입출력은 입출력장치에 대한 명령어가 어떻게 달라지는가에 관한 논의였고, 인터럽트 기반 입출력은 입출력 과정에서 인터럽트를 어떻게 처리하는가에 관한 논의였다.

공통점이 있다면, 입출력장치와 메모리 간의 데이터 이동을 CPU가 주도하고, 이동하는 데이터 역시 반드시 CPU를 거친다는 점이다.

모든 데이터가 반드시 CPU를 거쳐야 한다면 CPU에 대한 부담이 매우 커진다. ``DMA(Direct Memory Access)는 이름 그대로 직접 메모리에 접근할 수 있는 입출력 기능`이다. 이러한 기능을 가능케 하는 하드웨어가 바로 `DMA 컨트롤러``다.

우선 DMA 입출력 과정을 살펴보자.

DMA 컨트롤러는 CPU를 거치지 않고 메모리와 직접 상호작용하며 백업할 정보를 읽어오고, 해당 정보를 장치 컨트롤러로 내보낸다.

백업이 종료되면 DMA 컨트롤러는 CPU에게 인터럽트를 걸어 작업이 종료되었음을 알린다.