IOCP 란?
마이크로소프트에서 제공된 시스템 레벨에서 제공되는 비동기 입출력을 담당 메커니즘 이다.
I/O Completion Port 약자로 완전한 I/O를 지원하기 위해 만들어 졌다.
기존에 I/O 비동기로 처리하기 위해서 보내는 스레드, 받는 스레드, 처리 워킹 스레드 등
다양한 부분을 관리하고 거기에 따른 이벤트 전달 및 처리, 데이터 흐름 대부분을 사용자가 구현해야 했다.
때문에, 스레드 관리 에서 문제가 주로 발생하고 이게 커널에도 부담이 되어
MS에서 제공하게 된 계기로 탄생 하게 되었다.
NT 계열의 서버 프로그래밍에 이용하기 위해 내부적으로 만들었으나 성능이 좋아 배포하게 된 케이스.
기본적으로 입출력에 대한 처리를 수행 시 N개의 스레드를 생성해 스레드 풀을 구성 하며,
스레드 풀에서 스레드를 가져와 입출력 처리를 담당 한다.
또한 대량의 입출력 시 스레드 풀에서 N개의 스레드를 가져와 사용하여 성능 향상과 확장성을 제공 한다.
CreateIOCompletionPort 통해서 IOCP가 생성된다.
해당 함수는 IOCP 커널 객체의 생성과 생성된 IOCP 커널 객체에 입출력 장치 핸들 연결을 할 수 있다.
// IOCP 커널 생성. 핸들러 연결.
// HANDLE WINAPI 반환 값 - 커널 객체 생성 조건이라면 IOCP 커널 객체 생성 핸들러.
HANDLE WINAPI CreateIoCompletionPort(
_In_ Handle FileHandle, // 객체 생성 유무. INVALID_HANDLE_VALUE로 선언시 IOCP 커널 객체 생성.
_In_opt_ HANDLE ExistingCompletionPort, // IOCP 핸들러 반환. IOCP 커널 객체 연결.
_In_ ULONG_PTR CompletionKey, // 식별자 지정. N개 장치 연결 시 식별자 지정.
// 커널 객체 반환시 의미 없음. 0값 설정.
_In_ DWORD NumberOfConcurrentThread // 스레드 수 지정. N개 장치 입출력 발생시 동시 수행 스레드 최대 수.
// 0값 설정시 CPU 수 만큼 지정. 커널 생성시 필요.
// 특정 장치 연결 시 0값 설정.
);
- 스레드 풀.
입출력 장치와 IOCP 연결 후 입출력에 대한 처리를 수행할 스레드 설정이 필요하다.
입력 발생 시 IOCP를 통해 스레드 풀에 대기 중인 스레드를 워킹 스레드로 활성화 하여 완료 처리를 한다.
그러므로 IOCP 워킹 스레드 및 스레드 풀을 구성해야 한다.
IOCP는 내부에서 받는 스레드 / 보내는 스레드 / 상태 체크 스레드 3가지가 활성화 되어 있고,
해당 스레드는 User가 아닌 커널 레이어에서 통제가 된다.
User는 로우 단계가 아닌 하이 단계에서 GetQueuedCompletionStatus라는 함수를 통해 자기 스레드로
내부 상태 모니터링을 하다가 내부 상태가 쓸 수 있게 반환이 되면 그때 해당 포트의 패킷을 꺼내온다.
워킹 스레드는 해당 함수로 All Suspended 상태로 대기.
IOCP의 장점은 스레드가 대기 상태로 들어가면 자원을 먹지 않는다는 점이다.
즉, 워킹 스레드 리스트를 만들어서 각 개별적으로 IOCP를 할당하고 그 통제도 IOCP가 하는데 보통은 대기 상태가 된다.
IOCP가 내부적인 큐에다가 통신을 마무리하고 패킷을 생성하면 그때 워킹 큐에 신호를 주고,
워킹 스레드들이 비로소 작동한 뒤 완료가 된다면 다시 대기 상태로 들어간다.
여기서 대기 여부를 판단하는 주체가 IOCP 이며 워킹 스레드를 제공하고 핸들을 소유한 주체는 User(프로그래머) 이다.
IOCP에서 사용 하는 스레드는 특정 객체가 아닌 커널 객체에 대기 한다.
워킹 스레드 함수는 GetQueuedCompletionStatus 를 이용한다.
IOCP에서 I/O패킷을 꺼내오기 시도를 위한 함수이며, 패킷 큐가 없다면 해당 함수는 패킷이 들어오기 전까지 대기한다.
// Queue를 위한 상태 체크.
// 워킹 스레드 활성화. I/O 처리 진행한다.
// 반환값이 TRUE 라면 스레드 풀에서 워킹 스레드가 활성화.
// 반환값이 FALSE 라면 GetLastError 로 오류 확인.
BOOL WINAPI GetQuedCompletionStatus(
_In_ HANDLE CompletionPort, // IOCP 커널 객체 핸들러.
_Out_ LPDWORD lpNumberOfBytes, // 입출력 장치로 받아올 데이터 크기.
_Out_ PULONG_PTR lpCompletionKey, // 입출력 장치와 IOCP 연결 시 받아온 식별자 값. 입출력 장치 판단.
_Out_ LPOVERLAPPED* lpOverlapped, // 파일을 읽거나 쓰기 위한 주소값. 쉽게 말해 버퍼 설정.
_In_ DWORD dwMilliseconds // 대기 시간 밀리초 단위 지정. 시간 경과 시 FALSE 반환 후 타임아웃.
// GetLastError은 WAIT_TIMEOUT (258L). INFINITE로 설정 시 무한 대기.
);
// Queue에 데이터를 보내기 위한 상태 체크.
// I/O 처리 정보 저장 대기큐. 직접 정보를 큐에 넣을 수 있음.
// 반환값이 TRUE 라면 대기큐 정보 저장.
// 반환값이 FALSE 라면 GetLastError 로 오류 확인.
BOOL WINAPI PostQueuedCompletionStatus(
_In_ HANDLE CompletionPort, // IOCP 커널 객체 핸들러
_In_ DWORD dwNumberOfBytesTransferred, // GetQueuedCompletionStatus::lpNumberOfBytes
// 반환될 데이터 크기.
_In_ ULONG_PTR dwCompletionKey, // GetQueuedCompletionStatus::lpCompletionKey 반환될 식별자 값.
_In_opt_ LPOVERLAPPED lpOverlapped // GetQueuedCompletionStatus::lpOverlapped 반환될 버퍼.
);
EX )
// [ 입출력 장치와 IOCP 연결. ]
HANDLE hIOCP;
unsinged long nKey = 100;
DWORD dwFlagsAndAttributes = FILE_FALG_OVERLAPPED; // 비동기 I/O 장치 열기.
HANDLE hDev = CreateFile(lpFileName, dwDesiredAccess, dwShareMode, dwFlagsAndAttributes, dwCreationDisposition, dwFlagsAndAttributes, hTemplateFile);
// 2번째 인자 값으로 IOCP 핸들값 반환.
// 생성된 장치 연결시 반환값을 받아올 IOCP 핸들러 필요 없음.
// 4번째 인자는 장치를 IOCP에 직접 연결 시 의미 없으므로 0 설정.
CreateIoCompletionPort(hDev. hIOCP, nKey, 0);
// [ 입출력 장치와 IOCP 해제. ]
// 핸들러 종료 시 커널과 핸들 연결 해지.
// 워킹 스레드는 ERROR_ABANDONED_WAIT_0 (735L) 오류 후 스레드 종료.
CloseHandle(hIOCP);
// [ IOCP 사용 예시 ]
struct IOCP_BUFFER { DWORD dwKey; byte buffer[512]; };
DWORD WINAPI ProcessThreads( LPVOID pPram )
{
CIOCPProcess* pIOCPProcess = static_cast( pPram );
pIOCPProcess->ThreadFunc();
return 0;
}
class CIOCPProcess
{
HANDLE m_hIOCP;
HANDLE* m_phThreadHandle;
int m_nThreadCount;
bool m_bProcessThreadFlag;
public:
CIOCPProcess() {}
~CIOCPProcess() { Release(); }
void Initialize(int nThreadCount)
{
m_bProcessThreadFlag = true;
m_hIOCP = CreateIoCompletionPort( INVALID_HANDLE_VALUE, NULL, NULL, 0 );
if (INVALID_HANDLE_VALUE == m_hIOCP) return;
m_nThreadCount = nThreadCount;
m_phThreadHandle = new HANDLE[nThreadCount];
for (DWORD i = 0; i < dwThreadCount; ++i)
m_phThreadHandle[i] = CreateThread(NULL, 0, ProcessThreads, this, THREAD_PRIORITY_NORMAL, NULL);
}
void Release()
{
m_bProcessThreadFlag = false;
}
void ThreadFunc()
{
byte* pBuffer = NULL;
DWORD dwKey = 0;
DWORD dwTransferredBytes = 0;
BOOL bSuccess = FALSE;
while (m_bProcessThreadFlag)
{
// 워킹 스레드 활성화. I/O 처리 진행한다.
bSuccess = GetQueuedCompletionStatus( m_hIOCP, &dwTransferredBytes,
(LPDWORD)&dwKey, (LPOVERLAPPED *)&pBuffer, INFINITE );
......
}
}
// I/O 처리 정보 저장 대기큐. 직접 정보를 큐에 넣을 수 있음.
bool pushBack( const IOCP_BUFFER* pBuffer )
{
DWORD dwSize = sizeof( IOCP_BUFFER );
// 메모리 풀 만들어 관리하는게 좋음.
//IOCP_BUFFER* _pBuffer = AllocMemory();
IOCP_BUFFER* _pBuffer = new IOCP_BUFFER();
if (nullptr == _pBuffer) return false;
int nResult = memcpy_s( _pBuffer, sizeof(IOCP_BUFFER), pBuffer, sizeof(IOCP_BUFFER) );
if (0 != nResult) reuturn false;
if (ERROR_SUCCESS == PostQueuedCompletionStatus(m_hIOCP, dwSize, (ULONG_PTR)_pBuffer->dwKey,
(LPOVERLAPPED)_pBuffer))
{
DWORD dwError = WSAGetLastError();
if (ERROR_IO_PENDING != dwError) return false;
}
return true;
}
};
참고 링크 :
docs.microsoft.com/en-us/windows/win32/api/ioapiset/nf-ioapiset-getqueuedcompletionstatus
m.blog.naver.com/PostView.nhn
ozt88.tistory.com/23
'[ Programing ] > C++' 카테고리의 다른 글
C++ Type Traits (0) | 2020.09.09 |
---|---|
C++ union (0) | 2020.06.05 |
char - wchar_t 변환 (0) | 2020.04.07 |
C++ 11이상 STL vector erase() 오류시 Exception 관련 (0) | 2020.03.05 |
C++ 현재 시간 구하기. (0) | 2019.12.04 |