13. 제로카피(zero-copy) I/O: read/write 의 복사 경로와 sendfile·mmap·splice·io_uring
난이도 중난이도: 중상
1. 전통 경로의 복사·컨텍스트 스위치
파일을 소켓으로 보내는 read()+write() 는 데이터를 네 번 복사하고 컨텍스트를 네 번 전이(시스템콜 2회 × 진입/복귀) 한다.
- 디스크 → 페이지 캐시(커널) : DMA 복사(CPU 무관).
- 페이지 캐시 → 유저 버퍼 :
read()가 CPU 로 복사(유저↔커널 전이 1쌍). - 유저 버퍼 → 소켓 송신 버퍼(커널) :
write()가 CPU 로 복사(전이 1쌍). - 소켓 버퍼 → NIC : DMA 복사(CPU 무관).
낭비는 2·3번 — 데이터가 커널에 이미 있는데 유저 공간을 왕복하며 CPU 가 두 번 복사한다. 데이터를 유저 공간에서 건드리지 않는다면(그냥 전달) 이 왕복은 순수 낭비다.
2. 제로카피의 의미와 sendfile
"zero-copy" 는 CPU 가 관여하는 유저 공간 왕복 복사(2·3번)를 제거한다는 뜻이지, 물리 복사가 0이라는 게 아니다(DMA 는 남는다).
sendfile(out_sock, in_file, ...) 은 커널 내부에서 페이지 캐시 → 소켓으로 바로 보낸다. 유저 버퍼가 없어 2·3번 복사가 사라지고 시스템콜도 1회로 준다. 초기 구현은 페이지 캐시 → 소켓 버퍼 복사가 한 번 남았지만, NIC 가 scatter-gather DMA 를 지원하면 소켓 버퍼로의 복사도 없애고 디스크립터(주소·길이)만 NIC 에 넘겨 DMA 가 페이지 캐시에서 직접 읽어간다 → CPU 복사 0. 대용량 정적 파일 서빙(웹서버 sendfile, 패치 배포)에서 CPU·대역폭 이득이 크다.
3. mmap 기반 전송
mmap(file) 은 파일 페이지를 유저 주소공간에 매핑한다. 이후 write(socket, mapped_ptr, len) 은 유저 버퍼로의 read 복사(2번)를 페이지 캐시 매핑으로 대체 — 매핑된 포인터가 곧 페이지 캐시라 별도 복사가 없다. 다만:
- 페이지 폴트·TLB: 첫 접근마다 폴트가 나 페이지를 채우고 TLB 엔트리를 쓴다(
os_memory/problem8). 랜덤·대용량이면 폴트·TLB 미스 비용. - SIGBUS: 매핑 중 다른 프로세스가 파일을 잘라(truncate) 매핑 범위가 사라지면 접근 시
SIGBUS. 방어가 필요. - 작은 전송엔 비효율: 매핑 설정/해제 비용이 고정적이라 작은 파일엔 손해.
sendfile 은 "그냥 파일→소켓 전달"에 더 단순·빠르고, mmap 은 전송 전에 유저 공간에서 데이터를 살짝 봐야/고쳐야 할 때(부분 편집, 임의 접근) 유용하다.
4. splice / vmsplice / io_uring
- splice(in, out) 는 둘 중 한쪽이 파이프여야 하며, 커널 내부에서 페이지 참조(디스크립터)를 옮겨 유저 공간을 거치지 않고 FD↔FD 이동을 한다. 파이프를 매개로 소켓→소켓, 파일→소켓 등
sendfile보다 넓은 조합을 커버한다.vmsplice는 유저 페이지를 파이프에 매핑(복사 없이 참조) 해 넣는다. - io_uring 은 개별 복사보다 시스템콜 오버헤드를 공략한다. 제출 큐(SQ)/완료 큐(CQ) 를 유저·커널이 공유 링버퍼로 두고, 여러 I/O 를 한 번에 제출·비동기 완료 수거해 시스템콜·컨텍스트 스위치를 크게 줄인다.
IORING_OP_SENDMSG/splice/고정 버퍼(registered buffers)와 결합하면 제로카피 + 저(低)스위치를 함께 얻는다.sendfile이 못 하는 소켓↔소켓 릴레이, 파이프라인에 변환 삽입, 대량 요청 배치가 가능.
5. 게임 서버에서의 적용과 한계
이득이 큰 경우 — (a) 정적 패치/맵/에셋 파일 배포, 리플레이 다운로드처럼 "파일을 손대지 않고 그대로 대량 전송": sendfile/io_uring+splice 로 CPU·복사 절감.
이득이 작거나 깨지는 경우 — (b) 실시간 스냅샷 브로드캐스트:
- 암호화/압축/직렬화가 끼면 데이터를 유저 공간에서 변형해야 하므로 순수 제로카피가 깨진다. TLS 는 특히 — 다만 kTLS(커널 TLS) 로 암호화를 커널로 내리면
sendfile제로카피를 일부 회복한다. - 작은 패킷 다수(틱당 수십 바이트 × 수천 세션)는 전송당 고정 오버헤드가 지배적이라 제로카피 이득이 작다. 오히려 배칭(여러 논리 메시지를 한 프레임/한
sendmmsg), GSO/GRO(세그멘테이션 오프로드),TCP_NODELAYvs Nagle 조율(network/problem7)이 더 효과적. - 절충안: 유저공간 버퍼 풀(할당·GC 압박 감소,
os_memory/problem6)로 재사용,writev/scatter-gather 로 헤더+페이로드를 복사 없이 한 번에,MSG_ZEROCOPY(리눅스) 로 큰 송신의 유저→커널 복사를 비동기 제거(단, 완료 통지·페이지 핀 비용이 있어 큰 버퍼에서만 이득).
정리: 제로카피는 "유저 공간에서 데이터를 건드리지 않는 대용량 전달" 에서 빛나고, 변형이 필요하거나 작은 메시지가 많은 실시간 경로에선 배칭·오프로드·버퍼 재사용이 우선한다.
면접 포인트
read+write의 4복사·4전이를 세고 어디가 낭비인지,sendfile+scatter-gather DMA 가 CPU 복사를 어떻게 0으로 만드는지.mmapvssendfilevssplicevsio_uring의 적용 조건과 대가(폴트/TLB/SIGBUS, 시스템콜 배치).- 암호화·압축·작은 패킷이 왜 순수 제로카피를 깨는지와 kTLS·배칭·
MSG_ZEROCOPY·writev절충.