IOCP(Input Ouput Completion Port)란?

한민우·2024년 10월 12일

IOCP란 Input Output Completion Port의 줄임말로 Windos 운영 체제에서 비동기 입출력 작업을 처리하기 위한 메커니즘이다.
IOCP의 핵심 아이디어는 여러 비동기 I/O 작업을 효율적으로 관리하고, 완료된 I/O 작업을 처리하기 위한 스레드 풀을 최적화 하는 것이다.
IOCP는 비동기(Asynchronous) + 스레드 풀링(Thread Pooling) + 논 블로킹(Non-Blocking) + 중첩 입출력(Overlapped I/O)과 같은 개념들을 이용해서 작동한다.


0. 왜 Port라는 이름이 붙었을까?

IOCP는 내가 아는 네트워크에서의 Port와는 다른 역할을 하는 것 같은데, 왜 Port라는 이름이 붙었을까?

포트(Port)의 개념

포트는 운영 체제 통신의 종단점(endpoint)이다. 이 용어는 하드웨어 장치에도 사용되지만, 소프트웨어에서는 네트워크 서비스나 특정 프로세스를 식별하는 논리 단위이다.

네트워크에서 포트는 특정한 종단점(endpoint)를 나타낸다. IP 주소와 함께 사용되어, IP 주소는 호스트를 식별하고, 포트를 통해 해당 호스트 내에서 실행 중인 특정 애플리케이션이나 서비스를 구분한다.

왜 Port일까?

네트워크에서 포트가 통신 지점 역할을 하듯이, 이름에서 "Port"가 사용된 이유는, 비동기 I/O 작업의 완료 결과를 수신하는 지점으로 비유되기 때문이다.
실제 네트워크 포트는 아니지만, 마치 네트워크 포트에서 데이터가 도착하는 것처럼, I/O 작업이 완료될 때 그 결과가 이 "Complete Port"로 수신된다는 의미를 담고 있다.


1. IOCP의 주요 특징

1.1 비동기 I/O 처리

  • 비동기적으로 I/O 작업을 처리한다.
  • I/O 요청을 시작한 후 즉시 제어권을 반환하여 다른 작업을 수행할 수 있다.
  • 시스템 리소스를 효율적으로 사용하고 서버의 반응성을 향상시킨다.

비동기 I/O 처리의 장점:

  1. 블로킹 방지: I/O 작업이 완료될 때 까지 기다리지 않고 다른 작업을 수행할 수 있다.
  2. 리소스 효율성: 하나의 스레드로 여러 I/O 작업을 관리할 수 있어서 메모리 사용량이 줄어든다.
  3. 성능 향상: 동시에 여러 I/O 작업을 처리할 수 있어 전체적인 시스템 처리량이 증가한다.

예시

OVERLAPPED overlapped = {0};
ReadFile(hFile, buffer, bufferSize, NULL, &overlapped);
// 파일 읽기가 진행되는 동안 다른 작업 수행 가능

1.2 스레드 풀링 (Thread Pooling)

  • IOCP는 내부적으로 스레드 풀을 관리한다.
  • 특정 작업을 수행할 때마다 새로운 스레드를 생성하지 않고, 미리 스레드를 생성해두고 필요할 때마다 일부 스레드를 사용한다.
  • 스레드 생성 및 제거에 따른 오버헤드를 줄이고, 시스템 리소스를 최적화한다.

스레드 풀 관리의 장점:

  1. 스레드 재사용: 생성된 스레드를 재사용하여 스레드 생성/제거 비용을 줄인다.
  2. 동시성 제어: 동시에 실행되는 스레드 수를 제한하여 시스템 리소스를 보호한다.
  3. 부하 분산: 작업을 여러 시스템에 균등하게 분배하여 효율적인 처리를 가능하게 한다.

IOCP 스레드 풀 생성 예:

HANDLE hCompletionPort = CreateIoCompletionPort(INVALID_HANDLE_VALUE, NULL, 0, 0);
// 시스템 코어 수에 맞춰 작업자 스레드 생성
for (int i = 0; i < numberOfCores; i++) {
    CreateThread(NULL, 0, WorkerThreadFunction, hCompletionPort, 0, NULL);
}

1.3 완료 큐 (completion queue)

  • I/0 작업이 완료되면 그 결과가 완료 큐에 추가된다.
  • 서버는 이 큐에서 완료된 작업을 가져와 처리한다.
  • 이 메커니즘을 통해 효율적인 I/0 완료 통지 처리가 가능하다.

완료 큐의 작동 방식:

  1. I/0 작업 완료 시 운영 체제가 완료 패킷을 큐에 추가한다.
  2. 작업자 스레드가 큐에서 완료된 작업을 가져와 처리한다.
  3. 큐가 비어있을 경우, 스레드는 대기 상태로 전환되어 리소스 소비를 최소화한다.

1.4 컨텍스트 스위칭 (context switching) 최소화

  • 스레드에서 다른 스레드로 작업 전환을 할 때 컨텍스트 스위칭이 일어난다.
  • IOCP 모델은 컨텍스트 스위칭을 최소화 하여 시스템 리소스를 효율적으로 사용한다.

컨텍스트 스위칭 최소화의 이점

  1. 성능 향상: 스레드 전환에 따른 오버헤드를 줄여 성능이 향상된다.
  2. 자원 효율성 증가: 컨텍스트 스위칭을 줄여 CPU가 대기 상태에 있는 시간을 줄일 수 있다.
  3. 스케줄링 오버헤드 감소: 스레드 전환에 따른 운영 체제의 부담을 줄인다.

2. IOCP의 작동 원리

2.1 IOCP 객체 생성

  • CreateIoCompletionPort() 함수를 사용해 IOCP 객체를 생성한다.

IOCP 객체 생성 과정

  1. Complete Port 생성
    • CreateIoCompletionPort()를 호출하여 새로운 IOCP 객체를 생성한다. 첫 번째 인자로 INVALID_HANDLE_VALUE를 전달하면 새로운 Complete Port를 생성한다.
  2. 핸들(소켓, 파일 등)과 Complete Port 연결
    • 생성된 IOCP 객체에 소켓이나 파일 핸들을 연결한다. 비동기 작업을 완료했을 때 IOCP가 이를 알 수 있도록 한다.
  3. complete key 설정 (선택 사항)
    • 입출력이 끝나고난 후 어떤 작업이였는지 구분하기 위한 Key값. 핸들을 식별하는 데 유용하다.

IOCP 객체 생성 예제:

HANDLE hCompletionPort = CreateIoCompletionPort(INVALID_HANDLE_VALUE,NULL,0,0);

2.2 소켓 또는 파일 핸들 연결

  • 생성된 IOCP 객체에 소켓이나 파일 핸들을 연결한다.
  • 해당 핸들의 I/O 작업 완료를 IOCP를 통해 통지받을 수 있다.

핸들 연결 시 고려사항

  • 각 핸들은 하나의 IOCP에만 연결할 수 있다.
  • complete key를 설정하여 핸들을 식별할 수 있다.

소켓을 IOCP에 연결하는 예:

SOCKET Socket = accept(listenSocket, NULL, NULL);
CreateIoCompletionPort((HANDLE)Socket, hCompletionPort, (ULONG_PTR)completion, 0);

2.3 비동기 I/O 작업 시작

  • WSASend, WSARecv와 같은 비동기 I/O 함수를 사용하여 작업한다.
  • 이 함수들은 작업이 완료될 때까지 기다리지 않고 즉시 리턴된다.
    • 비동기 모델이기 때문에 I/O는 따로 진행된다.
  • 작업이 완료되면 IOCP를 통해 통지를 받는다.

비동기 I/O 작업 시작 시 주의사항

  1. 비동기 작업을 관리하기 위해 OVERLAPPED 구조체를 사용해야 한다.
  2. 비동기 작업이 완료될 때 까지 버퍼가 유효해야 한다.
  3. 에러를 적절히 처리해야 한다. (특히 WSA_IO_PENDING)
    • WSA_IO_PENDING: 작업이 아직 완료되지 않았음

2.4 I/O 완료 처리

  • GetQueuedCompletionStatus() 함수를 사용하여 완료된 작업을 처리한다.
  • 이 함수는 완료 큐에서 완료된 작업을 가져와 처리할 수 있도록 한다.

GetQueuedCompletionStatus()

  • 스레드 중에서 작업이 끝난 스레드에서 GetQueuedCompletionStatus() 함수를 호출하면 IOCP에서 완료된 내용을 꺼내 받을 수 있다.
  • GetQueuedCompletionStatus() 함수를 실행하고 있는 스레드는 Waiting Thread Queue에 쌓인다.

I/O 완료 처리 과정:

  1. 작업 완료 대기: GetQueuedCompletionStatus() 함수를 호출하여 I/O 작업이 완료되는 것을 기다린다.
  2. 작업 결과 확인: 함수가 성공적으로 반환되면, I/O 작업의 결과가 매개변수로 전달된다.
    • I/O 작업 중 전송된 바이트 수, 완료 키, 작업의 상태를 담고 있는 OVERLAPPED 구조체의 포인터가 반환된다.
  3. 후속 작업 수행: 완료된 I/O 작업의 결과를 확인한 후, 그에 따라 적절한 후속 작업을 수행한다.
    • 일반적으로 수신된 데이터 처리, 다음 I/O 작업 준비, 오류 처리의 작업을 수행한다.

3. Overlapped IO

비동기 작업을 관리하기 위해 OVERLAPPED 구조체를 사용해야 한다고 했다.
OVERLAPPED 구조체가 뭘까?? 간단하게만 알아보겠습니다.

3.1 Overlapped IO란?

I/O 작업을 요청한 후 작업이 완료될 때까지 해당 스레드가 블로킹되지 않고 다른 작업을 수행할 수 있는 모델

본격적으로 Overlapped IO를 살펴보기 전에 먼저 Blocking과 Non-Blocking에 대해 알아보겠습니다.

3.2 블로킹(Blocking) vs 논 블로킹(Non-Blocking)

  • 블로킹: 자신의 작업을 진행하다가 다른 주체의 작업이 시작되면 다른 작업이 끝날 때까지 기다렸다가 작업을 진행함
  • 논 블로킹: 다른 주체의 작업에 상관없이 자신의 작업을 진행함

기본적으로 I/O 작업은 동기로 이루어진다. 예를 들어 ReadFile(파일을 읽어 들이는 메서드) 함수를 호출 시 읽기 작업이 끝나기 전까지 함수는 값을 반환하지 않고 처리를 기다리게 된다.

이렇게 해당 스레드에서 작업이 끝날 때까지 다른 작업을 수행하지 못하고 대기해야 하는 상태를 블로킹(Blocking) 모드라고 한다.
반대로 Non-Blocking 모드는 I/O 작업을 호출하고 바로 반환되기에 해당 스레드는 I/O 작업을 대기하지 않고 다른 작업을 수행할 수 있다.

3.3 중첩 입출력(Overlapped I/O)

  • Overlapped IO의 포커스는 IO가 아닌 입출력의 확인 방법에 있다.
  • select(동기 + 논블로킹)가 아닌 정확한 비동기는 I/O에 대한 명령을 커널로 전송 후 끝났을 때 signal을 자동으로 받아 특정 동작을 수행한다.

동작 방식

  1. I/O 작업을 요청한다.
  2. WSASend(..., lpOverlapped) 함수를 호출 (이 함수는 데이터를 전송하는 역할을 담당하며 Overlapped IO 구조체를 파라미터로 전달한다.)하고 바로 리턴된다.
  3. 비동기적으로, I/O 작업을 요청했다는 정보를 Device Driver에게 전송함. I/O 작업을 Device Driver에게 전송한 스레드는 더 이상 I/O 작업을 신경 쓰지 않는다.
  4. Device Driver는 I/O 작업이 끝날 때까지 감시한다. 그리고 작업이 완료되면 끝났음을 통보해 준다.

3.4 I/O 작업 완료 여부 확인

Overlapped I/O에서 I/O 작업의 완료 여부를 확인하는 방법은 주로 두 가지 방식이 있다.

1. 이벤트 객체(Event Object)

  1. 비동기 I/O 작업을 요청할 때, OVERLAPPED 구조체의 hEvent 필드에 이벤트 객체를 할당한다.
  2. I/O 작업이 비동기적으로 진행되고, 작업이 완료되면 해당 이벤트 객체를 signaled 상태로 변경한다.
  3. 스레드는 WaitForSingleObject 또는 WaitForMultipleObjects 등의 함수로 이벤트 객체의 신호 상태를 확인하여, I/O 작업이 완료되었는지 판단한다.

2. IOCP

  1. I/O 작업을 요청할 때, I/O Completion Port에 바인딩된 장치(예: 소켓)를 사용하여 비동기 작업을 요청한다.
  2. I/O 작업이 비동기적으로 처리되고, 완료되면 해당 작업의 완료 상태가 Completion Queue에 저장된다.
  3. 스레드가 GetQueuedCompletionStatus()를 호출하여 Completion Queue에서 작업 완료 여부와 결과를 확인한다.

정리

IOCP는 작업자 스레드가 완료포트의 알림을 받아 비동기 작업을 처리하는 방식으로 동작한다. 이러한 IOCP의 특징은 대규모 서버 애플리케이션에서 높은 성능과 효율성을 제공하며, 동시에 많은 클라이언트 요청을 처리하는 데 최적화된 환경을 제공한다.

profile
예비 게임서버 개발자

0개의 댓글