LabHub

컴퓨터 구조 · 입출력과 버스 · 이론

입출력과 버스 — 인터럽트, DMA, 그리고 PCIe

LabHub 에서 이어서 보기

한 줄 요약

장치와 대화하는 방법은 세 층으로 정리된다. 언제 알릴지(폴링 대 인터럽트), 누가 옮길지(CPU 대 DMA), 어디를 지나갈지(버스와 인터커넥트).

왜 이게 필요했나

CPU 는 나노초 단위로 움직이고 디스크나 네트워크 카드는 마이크로초에서 밀리초 단위로 움직인다. 이 속도 차이를 그대로 두면 CPU 가 장치를 기다리는 데 시간을 다 쓴다. 입출력 설계의 역사는 이 차이를 어떻게 감출 것인가에 대한 답의 축적이다.

어떻게 동작하나

폴링과 인터럽트. 폴링은 CPU 가 장치 상태 레지스터를 계속 읽어 보는 방식이다. 단순하고 지연이 짧지만 그동안 CPU 는 아무 일도 못 한다. 인터럽트는 장치가 준비되면 CPU 에 신호를 보내는 방식이다. CPU 는 그동안 다른 일을 하지만, 인터럽트마다 문맥을 저장하고 핸들러로 뛰는 비용이 든다. 그래서 초당 수십만 패킷이 오는 네트워크 카드에서는 인터럽트가 오히려 시스템을 마비시킬 수 있고(인터럽트 폭풍), 리눅스의 NAPI 처럼 처음엔 인터럽트, 바쁠 땐 폴링으로 전환하는 혼합 방식이 표준이 되었다.

DMA. 데이터를 CPU 레지스터를 거쳐 한 워드씩 옮기면 CPU 가 데이터 운반부에 묶인다. DMA 컨트롤러는 장치와 메모리 사이에서 직접 데이터를 옮기고 다 끝나면 인터럽트 한 번만 올린다. 현대 시스템에서 대용량 전송은 예외 없이 DMA 다.

MMIO 와 포트 I/O. 장치 레지스터를 메모리 주소 공간에 올려 두면 일반 로드/스토어로 접근할 수 있다(MMIO). 문법이 메모리와 같다는 점이 함정이다. 비용은 전혀 다르다. MMIO 읽기는 응답을 기다려야 하는 논포스티드 트랜잭션이라 PCIe 왕복 시간이 그대로 실행 시간이 되고, 캐시에 올릴 수도 없다. 드라이버 코드가 루프 안에서 장치 레지스터를 읽고 있다면 그 루프는 계산이 아니라 I/O 왕복의 반복이다.

PCIe. 대역폭은 레인 수와 세대로 정해진다. 세대가 올라갈 때마다 레인당 속도가 대략 두 배가 되고, x1/x4/x8/x16 처럼 레인을 묶어 폭을 늘린다. GPU 를 x16 슬롯이 아니라 x4 슬롯에 꽂으면 연산 성능은 그대로인데 데이터 공급이 4분의 1이 된다. GPU 여러 장을 묶어 학습할 때 NVLink 같은 전용 인터커넥트를 쓰는 이유도 같다. PCIe 를 통한 카드 간 통신이 병목이 되기 때문이다.

현장에서 만나는 모습

홈랩에서 GPU 노드를 구성할 때 가장 흔한 실수가 슬롯 대역폭이다. 메인보드의 두 번째, 세 번째 슬롯은 전기적으로 x4 인 경우가 많아서, 카드 세 장을 꽂으면 두 장이 조용히 4분의 1 대역폭으로 동작한다. 학습 자체는 되지만 스텝 시간이 설명되지 않게 길어진다. lspci -vv 로 협상된 링크 폭을 확인하는 습관이 시간을 아껴 준다.

이어지는 퀴즈에서 확인할 것

같은 mov 명령어가 왜 어떤 주소에서는 나노초, 어떤 주소에서는 밀리초인지 설명할 수 있는지 확인한다.