프로그래밍/소켓 프로그래밍 입문

IOCP 모델과 가상 메모리 관리: 운영체제가 어떻게 고성능 네트워크 I/O를 가능하게 할까?

데일리 백수 2025. 2. 3. 21:54

TCP 서버를 작성할 때, IOCP(I/O Completion Port)라는 용어를 흔히 접하게 됩니다. IOCP는 대규모 동시 연결(수천~수만 소켓)을 효율적으로 처리하기 위해 윈도우 운영체제가 제공하는 고성능 비동기 I/O 모델인데요, 이 글에서는 IOCP가 가상 메모리(Virtual Memory)와 결합하여 어떻게 고성능을 달성하는지 살펴보겠습니다.

 

 

1. IOCP의 기본 개념

  1. IOCP(Completion Port)
    • 윈도우 커널 레벨에서 네트워크/파일 I/O 완료 시점을 통지하는 메커니즘이자 큐(Queue)입니다.
    • 소켓(혹은 파일 핸들)을 IOCP에 등록해두면, 해당 핸들에 대한 I/O가 비동기로 진행되고, I/O 완료 시 IOCP 큐에 “완료 알림 패킷”이 쌓입니다.
  2. 스레드 풀
    • 애플리케이션은 IOCP에 연결된 스레드들(Worker Threads)을 미리 생성합니다.
    • 이 스레드들이 GetQueuedCompletionStatus로 큐에서 “누가 I/O를 끝냈는지” 꺼내 후속 로직을 처리합니다.

결과적으로, CPU가 놀지 않고 여러 클라이언트를 동시에 처리할 수 있으면서, 불필요한 스레드를 무한정 만들지 않아도 됩니다.


2. 네트워크 I/O와 가상 메모리의 연결고리

2.1 가상 메모리(Virtual Memory)

  • 프로세스가 메모리를 사용하면, 실제로는 물리 메모리(RAM)의 일부분만 매핑하여 사용합니다.
  • 윈도우 OS는 가상 메모리 페이징(Paging)을 통해 “애플리케이션이 요청한 버퍼를 어느 물리 페이지에 둘지” 결정하고, 필요 시 락(Lock)도 걸어줍니다.

2.2 Overlapped I/O 요청 시 메모리 Lock

  • “이 소켓으로 데이터를 보낸다(WSASend)” 혹은 “파일에 쓴다(WriteFileOverlapped)”는 순간, OS는 해당 가상 주소 범위를 **물리 메모리에 고정(Lock)**합니다.
    • 왜? 실제 네트워크/디스크 I/O 시, 장치 드라이버가 DMA(Direct Memory Access) 등으로 해당 메모리에 직접 접근해야 하므로, OS가 이 버퍼 페이지가 중간에 스왑아웃되지 않도록 보호.
  • I/O가 완료되면 OS는 언락(Unlock)하여 페이징을 자유롭게 가능하게 합니다.

3. IOCP가 “빠른” 이유

  1. OS가 관리하는 쓰레드 풀
    • 수백 개 소켓이 있어도, 동시에 실제 I/O를 처리 중인 소켓이 10개라면 10개 워커 스레드만 운영.
    • 불필요한 컨텍스트 스위칭 최소화, CPU 사용률 효율 극대화.
  2. 가상 메모리 Lock/Unlock
    • OS가 DMA를 위해 “이 버퍼 페이지는 건드리지 말아야 한다”는 잠금(Lock)을 자동으로 처리.
    • I/O가 끝나면 Unlock. 이 과정에서 불필요 복사가 줄어듦.
  3. 비동기 모델
    • I/O가 끝날 때까지 스레드가 블로킹되지 않음.
    • CPU와 I/O를 병렬로 활용: CPU는 다른 로직을 계속 진행, I/O는 OS 내에서만 대기. I/O 완료 시점에만 스레드가 깨어남.
  4. 프로세스-커널 간 전환 최적화
    • I/O 요청 -> 커널 내부 큐 -> 완료 -> IOCP 큐.
    • 중간에 유저모드/커널모드 전환이 필요할 때, IOCP 방식으로 최소화해 놨다는 점이 성능의 큰 이점.

4. 요약 & 결론

  • 가상 메모리IOCP가 결합된 이유:
    • 네트워크로 데이터를 “주고받을” 때, OS는 해당 버퍼가 물리 메모리에 언제든 접근 가능해야 하므로, 페이지를 Lock.
    • IOCP는 “이 I/O가 끝났어요”라는 사실을 한 큐(Completion Port)에 모아 두고, 워커 스레드만큼만 처리하므로 스레드 폭발 방지.
  • 진정한 고성능:
    • 운영체제가 I/O 스케줄링, 스레드 관리, 메모리 페이지 관리까지 다 해주므로, 애플리케이션은 최소한의 자원만으로 대규모 동시 접속이나 다중 파일 I/O를 처리 가능.
  • 개발자에게 주어진 편의:
    • I/O 완료를 “감지”하는 로직을 OS가 맡아주고, 워커 스레드가 GetQueuedCompletionStatus 한 번으로 처리.
    • 그 뒤 가상메모리에 있는 버퍼를 프로세스 레벨에서 가공하면 됨.