티스토리 뷰
개요
입출력(I/O)은 컴퓨터 시스템이 외부환경과 데이터를 주고받는 유용한 장치로 기능하게 하는 핵심 과정이다. 외부로부터 데이터를 입력받고 처리 결과를 출력할 수 없다면 컴퓨터는 고립된 연산장치에 불과하기 때문이다.
그러나 CPU의 빠른 연산 속도와 I/O장치의 느린 처리 속도 사이에 큰 간극이 존재한다. 이 속도 차이를 효율적으로 관리하지 않으면 시스템 전체 성능이 저하되는 병목현상이 발생하게 된다. 이러한 문제를 해결하고 I/O작업을 효율적으로 관리하기 위해 인터럽트(Interrupt)와 직접 메모리 접근(DMA, Direct Memory Access)이 등장했다.
본 글에서는 두 방식이 어떠한 배경에서 등장했으며, 각각 어떠한 역할을 수행하는지 알아본다.
시스템 구조
먼저 고전적인 시스템 구조를 살펴보자. 가장 위에 존재하는 메모리 버스에 메인 메모리와 CPU가 연결되어 있다. 그 밑에 I/O버스를 통해 그래픽이나 고성능 장치가 연결되어 있고, 그 보다 아래 존재하는 SCSI, SATA, USB와 같은 장치들이 사용하는 Peripheral 버스가 존재한다.
이러한 계층적인 구조를 사용하는 이유는 성능과 비용 때문이다. 고속의 성능을 지원하는 버스는 비싸므로, CPU와 같이 빠른 통신이 필요한 장치는 가까운 고속 버스에 배치하고 상대적으로 느린 장치들은 저속 버스에 연결하여 시스템 전체의 비용 효율성을 높인다.

다음은 비교적 최신의 구조인 인텔 Z270칩셋의 구조이다. CPU는 메모리와 그리고 그래픽카드와 PCle로 연결되어있다. 그리고 DMI(Direct Media Interface)를 통해 CPU가 I/O칩과 연결되고 나머지 장치들이 I/O칩을 통해 연결된다. 이 구조는 CPU가 고속 장치와의 통신에 집중하게 하고, 나머지 I/O 제어는 I/O chip에 위임함으로써 시스템 구조를 단순화하고 효율을 높인다.

표준 장치
I/O장치와 운영체제와 통신하기 위해서는 정형화된 인터페이스가 필요하다. 대부분 I/O장치는 하드웨어적으로 다음과 같은 세가지 레지스터를 통해 운영체제와 상호작용을하는 표준 모델을 따른다.
상태(Status)레지스터는 장치의 현재상태를 읽을 수 있으며, 명령어(command)레지스터는 운영체제가 장치에게 특정 동작을 수행하도록 지시할 때, 데이터(Data)레지스터에는 장치에 데이터를 보내거나 받을 때 사용한다. 운영체제는 이 레지스터를 읽거나 쓰는 것을 통해 I/O장치의 동작을 제어할 수 있다.

운영체제와 I/O 디바이스가 다음과 같이 네 가지 단계로 통신한다.

- 장치 상태 확인(폴링): 운영체제는 장치의 상태(status) 레지스터를 반복적으로 읽어 장치가 명령을 받을 준비가 되었는지 확인
- 데이터 전달(PIO, programmed I/O): 운영체제는 데이터(data) 레지스터에 데이터를 전달한다. 이 과정에 CPU가 직접 관여하는 방식을 프로그래밍된 PIO라고한다.
- 명령 기록: 운영체제는 명령(Command)레지스터에 명령어를 기록하여 장치가 데이터 처리를 시작하도록 요청한다.
- 작업 완료 확인(폴링): 운영체제는 장치가 작업을 완료했는지 확인하기 위해 상태(status)레지스터를 반복적으로 폴링한다.
하지만 이러한 폴링 기반 방식은 구조가 단순하지만 심각한 비효율을 초래한다.
가장 큰 문제점은 CPU 시간을 낭비한다는 점이다. 운영체제는 장치의 상태를 확인하기 위해 루프(loop)를 돌며 상태 레지스터를 끊임없이 확인하는데, 이를 바쁜 대기(busy-waiting)라고 한다. 이 시간 동안 CPU는 다른 프로세스를 실행할 수 있음에도 불구하고, 오직 장치의 상태를 확인하는 데에만 묶여있게 된다. 이 문제를 해결하기 위해 등장한 것이 인터럽트(Interrupt) 방식이다.
인터럽트(Interrupt)
CPU가 디바이스의 상태를 지속적으로 확인하는 대신, 운영체제는 I/O 작업을 요청한 프로세스를 대기(block)상태로 만들고 CPU를 다른 프로세스에 할당한다. 이후 I/O 작업이 완료되면 I/O장치가 CPU에 하드웨어 인터럽트 신호를 보내고, CPU는 현재 작업을 잠시 중단한 뒤 운영체제 커널에 미리 정의된 인터럽트 서비스 루틴(ISR)을 실행하여 후속 처리를 진행한다.
두가지 타임라인을 비교하면 그 차이가 명확하다.
기존의 폴링을 사용했을 때의 타임라인이다. p는 폴링작업을 의미한다. CPU에서 프로세스 1이 일정시간동안 실행되다가 I/O장치가 작업을 완료할 때까지, 반복적으로 장치 상태를 폴링하게 된다.

인터럽트를 사용할 때의 타임라인이다. 프로세스 1이 디스크에 I/O 요청을 하고 디스크가 이를 처리하는 동안 그저 기다리는 것이 아닌 프로세스 2를 수행한다. 디스크 요청이 완료되면 디스크가 하드웨어 인터럽트를 발생시키며, 운영체제가 프로세스 1을 깨워 다시 준비상태로 전환한다.

그러나 인터럽트 방식이 항상 최적의 해법은 아니다. 인터럽트 처리에는 현재 실행 중인 프로세스의 상태 저장, 인터럽트 서비스 루틴으로의 전환, 그리고 I/O가 완료된 프로세스로 다시 돌아오는 과정에서 발생하는 컨텍스트 스위칭(Context Switch)비용이 수반된다. 이 오버헤드는 결코 가볍지 않다.
만약 I/O 작업이 매우 빨리 완료되는 고속 장치라면, 폴링으로 인한 짧은 대기 시간보다 인터럽트 처리에 드는 비용이 오히려 더 클 수 있다. 따라서 일반적으로 고속 장치에는 폴링이, 키보드나 하드 디스크처럼 상대적으로 느린 장치에는 인터럽트 방식이 CPU 효율성 측면에서 더 유리한 전략으로 채택된다.
DMA(Direct Memory Access)
앞서 설명한 표준 방식 4가지 단계에서 한가지 더 고려해야할 부분이 있다. 많은 양의 데이터를 디스크로 전달하기 위해서 PIO(CPU가 데이터입출력을 모두 관리하는 것)를 사용하면 CPU는 이 작업으로 인해 다른 프로세스를 처리하기 위해 사용할 수 있는 시간을 소비하게 된다.

이 문제의 해결 방법은 DMA라는 특수 장치를 두어 CPU가 메모리의 데이터를 디스크로 직접 옮기는 대신 DMA 장치가 이를 수행하도록 하는 것이다. 운영체제는 DMA 엔진에게 메모리 상의 데이터위치와 전송할 데이터 크기, 대상 장치를 알려주기만 하면 DMA가 이를 수행하고 CPU는 그동안 다른 프로세스의 작업을 수행할 수 있다. DMA 동작이 끝나면 DMA컨트롤러가 작업이 완료되었다고 인터럽트를 발생시킨다.

결론
컴퓨터 시스템의 성능을 극대화하기 위해 I/O관리 방식은 지속적을 발전해왔다. 초기의 폴링 방식은 CPU자원을 심각하게 낭비하는 문제가 있었고, 이를 해결하기 위해 인터럽트 방식이 도입되어 CPU가 I/O작업을 기다리는 동안 다른 프로세스 작업을 할 수 있게 되었다.
더 나아가, 데이터 전송시에 CPU가 직접 관여해야하는 부담을 줄이기 위해 DMA가 등장했다. DMA는 데이터 전송을 전담하여 CPU가 온전히 연산에만 집중할 수 있도록 함으로써 시스템 전체의 효율을 한 단계 끌어올렸다. 이처럼 각 기술들은 이전 방식의 한계를 극복하며 CPU의 효율성을 높이는 방향으로 발전해왔다는 것을 알 수 있다.
참고 자료
- Remzi H. Arpaci-Dusseau, 운영체제 아주 쉬운 세가지 이야기(ostep), 2020.09.10 홍릉, 456p-468p
- [OS]I/O Device 알아보기 - OS 공부 24, https://icksw.tistory.com/171
'Computer Science' 카테고리의 다른 글
| 쿠키, 세션, 토큰 기반 인증에 대해 (0) | 2025.08.20 |
|---|---|
| 멀티 스레드 환경에서의 락(Lock) 메커니즘 (0) | 2025.08.07 |
| Zero-Copy 실전 적용: 정적파일 전송 최적화 (1) | 2025.06.23 |
| 메모리 복사를 최소화하는 기술, Zero Copy (0) | 2025.06.18 |
| C10K 문제로 살펴보는 서버 아키텍처와 커널 I/O의 진화 (2) | 2025.05.05 |