Skip to content

Latest commit

 

History

History
180 lines (135 loc) · 8.74 KB

File metadata and controls

180 lines (135 loc) · 8.74 KB

IOCP의 완료 통지 방식

IOCP(Input/Output Completion Port)는 Windows 운영체제에서 제공하는 고성능 비동기 I/O 모델로, 완료 통지 방식을 통해 비동기 I/O 작업의 완료를 애플리케이션에 알립니다.

IOCP 완료 통지 방식의 핵심 요소

  1. 큐 기반 알림 메커니즘: IOCP는 내부적으로 완료 패킷을 큐에 저장하며, 애플리케이션은 이 큐에서 완료된 I/O 작업의 결과를 가져옵니다.

  2. 커널 대 유저 모드 전환 최소화: 다수의 I/O 완료를 한 번의 시스템 호출로 처리할 수 있어 성능이 향상됩니다.

  3. 스레드 풀과의 통합: IOCP는 작업자 스레드 풀과 함께 사용되어 효율적인 병렬 처리가 가능합니다.

완료 통지 작동 과정

  1. IOCP 객체 생성: CreateIoCompletionPort() 함수를 사용하여 IOCP 객체를 생성합니다.

  2. I/O 장치 연결: 소켓이나 파일 핸들을 IOCP에 연결합니다.

  3. 비동기 I/O 요청: WSASend(), WSARecv() 등의 함수로 비동기 I/O 요청을 합니다. 이때 각 요청에는 OVERLAPPED 구조체가 연결됩니다.

  4. I/O 완료 대기: 작업자 스레드는 GetQueuedCompletionStatus() 함수를 호출하여 완료된 I/O 작업을 기다립니다.

  5. 완료 통지 처리: I/O 작업이 완료되면 커널은 완료 패킷을 IOCP 큐에 추가하고, 대기 중인 작업자 스레드 중 하나를 깨워 완료 패킷을 처리하게 합니다.

완료 패킷의 구성 요소

완료 패킷은 다음 정보를 포함합니다:

  • 전송/수신된 바이트 수
  • 완료 키(Completion Key): 장치 등록 시 지정한 값
  • OVERLAPPED 구조체 포인터: I/O 요청과 연결된 컨텍스트 정보
  • 에러 코드: I/O 작업 실패 시 오류 정보

IOCP 완료 통지의 장점

  1. 높은 확장성: 많은 동시 연결을 효율적으로 처리할 수 있습니다.
  2. 자원 효율성: 스레드 수를 CPU 코어 수에 맞게 최적화할 수 있습니다.
  3. 부하 분산: 커널이 자동으로 작업자 스레드에 작업을 분배합니다.
  4. 우선순위 조정: 완료 패킷에 우선순위를 부여할 수 있습니다.

IOCP 완료 통지 사용 시 주의점

  1. 스레드 동기화: 완료 처리 시 적절한 동기화 메커니즘을 사용해야 합니다.
  2. 메모리 관리: OVERLAPPED 구조체와 연결된 버퍼의 수명 관리가 중요합니다.
  3. 스레드 풀 크기 설정: 최적의 성능을 위해 작업자 스레드 수를 적절히 조정해야 합니다.


IOCP의 핵심 메커니즘 상세 설명

1. 큐 기반 알림 메커니즘

IOCP(Input/Output Completion Port)의 중심에는 커널 모드에서 관리되는 완료 패킷 큐가 있습니다.

완료 패킷 큐의 구조와 동작

  • 커널 관리 큐: 완료 패킷 큐는 Windows 커널 내부에서 관리되는 FIFO(First In, First Out) 데이터 구조입니다.
  • 완료 패킷 구성: 각 완료 패킷은 다음 요소를 포함합니다:
    • dwNumberOfBytesTransferred: 전송/수신된 바이트 수
    • CompletionKey: I/O 장치 등록 시 지정한 사용자 정의 값
    • lpOverlapped: 비동기 I/O 요청 시 제공된 OVERLAPPED 구조체 포인터
    • 오류 코드: I/O 작업 결과

완료 패킷 등록 과정

  1. 비동기 I/O 작업(예: WSASend, ReadFile)이 시작됩니다.
  2. 작업이 즉시 완료되지 않으면 운영체제는 요청을 큐에 넣고 제어를 애플리케이션에 반환합니다.
  3. I/O 작업이 완료되면(하드웨어 인터럽트 발생):
    • 커널은 완료 패킷을 생성하여 IOCP 큐에 삽입합니다.
    • 대기 중인 스레드가 있다면 하나를 깨워 통지합니다.

완료 패킷 소비 과정

  1. 애플리케이션 스레드는 GetQueuedCompletionStatus() 함수를 호출하여 완료 패킷을 기다립니다.
  2. 큐에 완료 패킷이 있으면:
    • 커널은 큐에서 패킷을 제거하여 호출 스레드에 전달합니다.
    • 애플리케이션은 반환된 정보를 사용하여 완료된 I/O 작업을 처리합니다.
  3. 큐가 비어있으면 스레드는 대기 상태가 됩니다(또는 타임아웃 설정에 따라 반환).

2. 커널 대 유저 모드 전환 최소화

운영체제에서 커널 모드와 유저 모드 간의 전환은 상당한 오버헤드를 발생시킵니다. IOCP는 이 전환을 최소화하여 성능을 향상시킵니다.

모드 전환 오버헤드

  • 컨텍스트 스위칭 비용: 모드 전환 시 CPU 레지스터, 메모리 매핑 등의 상태를 저장하고 복원해야 합니다.
  • CPU 캐시 영향: 모드 전환은 CPU 캐시를 플러시하여 성능 저하를 가져올 수 있습니다.

IOCP의 모드 전환 최적화

  1. 배치 처리: 여러 I/O 완료를 한 번의 GetQueuedCompletionStatus() 호출로 처리할 수 있습니다.

    • GetQueuedCompletionStatusEx() 함수는 한 번의 호출로 여러 완료 패킷을 가져올 수 있습니다.
  2. 지연된 처리: 커널은 완료 패킷을 즉시 전달하지 않고 축적하여 일괄 처리할 수 있습니다.

  3. 시스템 호출 감소:

    • 전통적인 모델: 각 I/O 완료마다 별도의 시스템 호출 필요
    • IOCP 모델: 다수의 I/O 완료에 대해 단일 시스템 호출로 처리 가능

성능 향상 예시

일반적인 서버 애플리케이션에서:

  • 전통적인 Select/Poll 모델: 1000개 연결 → 최대 1000번의 모드 전환
  • IOCP 모델: 1000개 연결 → CPU 코어 수에 비례하는 모드 전환 횟수(예: 8코어 시스템에서 약 8번)

3. 스레드 풀과의 통합

IOCP는 작업자 스레드 풀과 긴밀하게 통합되어 효율적인 병렬 처리를 제공합니다.

스레드 풀 구성 및 관리

  1. 최적의 스레드 수: 일반적으로 (CPU 코어 수 * 2) 정도로 스레드 풀을 구성합니다.

    • 너무 적은 스레드: I/O 처리 지연 발생
    • 너무 많은 스레드: 컨텍스트 스위칭 오버헤드 증가
  2. 스레드 관리 정책:

    • LIFO(Last In, First Out) 스레드 스케줄링: 최근에 실행된 스레드가 먼저 깨어나 CPU 캐시 지역성(locality)을 활용
    • 동시성 제한: 동시에 실행되는 스레드 수를 제한하여 컨텍스트 스위칭 최소화

스레드 동기화 메커니즘

  1. 자동 스레드 관리: 커널이 적절한 작업자 스레드를 깨우고 완료 패킷을 분배합니다.

  2. 부하 분산: 작업량에 따라 자동으로 스레드를 활성화/비활성화합니다.

    • 부하 증가 → 더 많은 스레드 활성화
    • 부하 감소 → 일부 스레드를 대기 상태로 전환
  3. 스레드 풀 상태 관리:

    • 활성 스레드(Active Threads): 현재 I/O 작업을 처리 중인 스레드
    • 대기 스레드(Waiting Threads): IOCP 큐에서 완료 패킷을 기다리는 스레드
    • 유휴 스레드(Idle Threads): 작업이 없어 대기 중인 스레드

구현 예시

// IOCP 생성 및 스레드 풀 크기 설정
HANDLE hCompletionPort = CreateIoCompletionPort(
    INVALID_HANDLE_VALUE, // 파일 핸들 없음(IOCP만 생성)
    NULL,                 // 새 IOCP 생성
    0,                    // CompletionKey(사용하지 않음)
    0                     // 동시 스레드 수(0=시스템 기본값 사용)
);

// 작업자 스레드 생성
for (int i = 0; i < nThreads; i++) {
    HANDLE hThread = CreateThread(
        NULL, 
        0, 
        WorkerThreadFunction,  // 작업자 스레드 함수
        hCompletionPort,       // 스레드 파라미터(IOCP 핸들)
        0, 
        NULL
    );
    CloseHandle(hThread);      // 스레드 핸들 닫기(스레드는 계속 실행됨)
}

// 작업자 스레드 함수
DWORD WINAPI WorkerThreadFunction(LPVOID lpParam) {
    HANDLE hCompletionPort = (HANDLE)lpParam;
    DWORD dwBytesTransferred;
    ULONG_PTR CompletionKey;
    LPOVERLAPPED lpOverlapped;
    
    while (TRUE) {
        // IOCP 큐에서 완료 패킷 가져오기
        BOOL bSuccess = GetQueuedCompletionStatus(
            hCompletionPort,
            &dwBytesTransferred,
            &CompletionKey,
            &lpOverlapped,
            INFINITE  // 무한 대기
        );
        
        // 완료 패킷 처리
        if (bSuccess && lpOverlapped) {
            // I/O 작업 완료 처리
            // ...
        }
        else {
            // 오류 처리
            // ...
        }
    }
    return 0;
}

이 세 가지 메커니즘이 결합되어 IOCP는 고성능 서버 애플리케이션을 위한 강력한 기반을 제공합니다. 특히 대규모 동시 연결을 처리하는 네트워크 서버에서 뛰어난 확장성과 효율성을 발휘합니다.