13강 / 20강

버스와 입출력 — CPU가 기다리지 않는 법

PCIe · 인터럽트 · DMA, 그리고 네트워크 카드

버스와 입출력 — CPU가 기다리지 않는 법
CPU는 짐을 직접 나르지 않습니다. DMA에게 시키고 다른 일을 해요.

지금까지 CPU·메모리·디스크를 봤습니다. 그런데 이들이 어떻게 서로 이야기할까요?

그리고 더 중요한 질문 — 디스크에서 1MB를 읽어 오는 동안(0.15ms 정도) CPU는 무엇을 하고 있을까요? 멍하니 기다린다면 엄청난 낭비입니다.

버스 — 부품들을 잇는 고속도로

요즘 서버의 주된 통로는 PCIe입니다. 차선 수(x1, x4, x16)와 세대(Gen3, Gen4, Gen5)로 속도가 정해져요.

PCIe 차선 수 × 세대 = 대역폭.
Python 브라우저에서 실행
NVMe는 01:00.0, 랜카드는 02:00.0과 03:00.0에 있습니다.
bash 브라우저에서 실행

CPU가 기다리지 않는 세 가지 방법

👀
폴링
"다 됐어?" 계속 물어본다
CPU를 통째로 낭비
🔔
인터럽트
"다 되면 불러 줘"
그동안 다른 일을 한다
🚚
DMA
"메모리에 직접 넣어 둬"
CPU는 아예 손도 안 댄다
입출력 실험실 — 폴링·인터럽트·DMA로 같은 전송을 시켜 보고 CPU가 얼마나 자유로운지 보세요.
불러오는 중…

인터럽트 — 손 들고 부르기

디스크가 데이터를 다 준비하면 CPU에게 전기 신호로 손을 듭니다. CPU는 하던 일을 잠깐 저장해 두고, 정해진 처리 함수로 뛰어갔다가 돌아와요.

이 "잠깐 저장하고 뛰어갔다 오기"가 공짜가 아닙니다. 문맥 교환이 일어나거든요 — 레지스터를 전부 저장하고, 캐시가 밀려나고, TLB가 무효화됩니다.

in = 인터럽트, cs = 문맥 교환. 요청이 많아지면 함께 올라갑니다.
bash 브라우저에서 실행

DMA — CPU를 아예 빼는 방법

인터럽트로 "다 됐다"는 걸 알아도, 데이터를 장치에서 메모리로 옮기는 일은 남습니다. 1MB를 CPU가 4바이트씩 옮기면 26만 번의 복사예요. 미친 짓이죠.

그래서 장치가 직접 메모리에 씁니다. 이게 DMA입니다. CPU는 "여기부터 여기까지 채워 놔"라고 시켜 놓고 다른 일을 하다가, 다 됐다는 인터럽트만 한 번 받으면 끝이에요.

DMA가 없으면 서버가 파일 하나 보내다 멈춥니다.
Python 브라우저에서 실행

네트워크 카드도 병목이 됩니다

회선이 병목일 때는 CPU를 늘려도 소용없습니다.
Python 브라우저에서 실행
🤔 확인 문제
nginx가 이미지 파일을 보낼 때 CPU를 거의 쓰지 않는 이유는?
🤔 확인 문제
트래픽이 폭증하자 top의 %si(soft interrupt)가 40%까지 올라갔습니다. 무엇을 의심해야 하나요?