windows via c/c++ chapter 10
TRANSCRIPT
동기 ?! 비동기 ?!
• 동기 (Synchronous) : 동시 발생하는
• 비동기 (Asynchronous) : 동시에 존재하지 않는
01
장치란 ?!
• 정의 : 통신 가능한 어떤 것
• 종류 : 파일 , 직렬포트 , 메일슬롯 , 소켓 , etc... (p.388 표 10-1)
• 특성 : 일부 장치를 제외하고는 인터페이스가 같다 . CreateMailSlot(), CreateNamedPipe()
HANDLE hHandle = CreateFile(...
SetCommConfig (HANDLE hCommDev, ...SetMailslotInfo (HANDLE hMailslot, ...
CloseHandle(HANDLE hObject)
생성
이용
종료
!! 단 장치가 Socket 이였다면 Closesocket(SOCKET s)
HANDLE CreateFile (PCTSTR pszName,DWORD dwDesireAccess,DWORD dwShareMode,PSECURITY_ATTRIBUTES psa,DWORD dwCreationDisposition,DWORD dwFlagsAndAttributes,HANDLE hFileTemplate);
• pszName : 특정 장치의 인스턴스 / 장치의 타입을 구분• dwDesireAccess : 장치와 데이터 주고받길 원하는 범위• dwShareMode : 장치의 공유특성• Psa : 보안 설정• dwCreationDisposition : 파일을 여는 방법• dwFlagsAndAttributes : 세부적 통신 Flag / 파일의 특성• hFileTemplate : 이미 열린 파일에 대한 핸들 or NULL
!! 파일이 아닌 다른 장치에 대해서는 반드시 OPEN_EXISTING
(p.391 ~ 398)
02
장치 이용하기
• 파일 크기 얻기
• 파일 포인터 위치 지정
• 파일의 끝 설정
BOOL GetFileSize (hFile, ...);
DWORD GetCompressedFileSize (pszFileName, ...);
ReadFile (hFile, ...);
SetFilePointEx (hFile, ... );
SetEndOfFile(hFile, ...);
논리적인 크기 반환
물리적인 크기 반환
64bit 오프셋동기적인 I/O 를 수행할 위치
커널오브젝트의 파일포인터 변경
03
동기 장치 I/O
• 동기 장치 I/O 수행
• 동기 장치 I/O 취소 ( 전적으로 드라이버에 달려있다 .)
BOOL ReadFile (
HANDLE hFile,
PVOID pvBuffer,
DWORD nNumByteToRead,
PDWORD pdwNumBytes,
OVERLAPPED* pOverlapped);
BOOL CancelSynchronousIo (HANDLE hThread);
04
BOOL ReadFile (
HANDLE hFile,
PVOID pvBuffer,
DWORD nNumByteToRead,
PDWORD pdwNumBytes,
OVERLAPPED* pOverlapped); FILE_FLAG_OVERLAPPED
컴퓨터가 수행하는 다른 작업들에 비해 장치 I/O 는 상대적으로
가장 느리고 , 예측할 수 없는 작업 중 하나다 .
비동기 I/O 요청은 고성능의 확장성 있는 애플리케이션을
개발하기 위하여 설계 되었다 .
1. 실제로 I/O 작업을 수행할 장치의 디바이스 드라이버로 전달
2. 디바이스 드라이버가 장치로 부터 응답 대기
3. 애플리케이션은 I/O 완료될때까지 다른 작업 수행
typedef struct _OVERLAPPED {
DWORD Internal;
DWORD InternalHigh;
DWORD Offset;
DWORD OffsetHigh;
HANDLE hEvent;
} OVERLAPPED, *LPOVERLAPPED;
• Internal : 처리된 I/O 의 에러 코드를 담는데 사용한다 . 비동기 I/O
요청을 시도하면 디바이스 드라이버는 Internal 멤버 값을 STA-
TUS_PENDING 으로 설정하여 아직 작업이 완료되지 않았으며
어떠한 에러도 발생하지 않았음을 나타낸다 .
• InternalHigh : 비동기 I/O 작업이 완료되면 이 멤버는 실제로
송수신된 바이트 수를 저장하게 된다 .
• Offset : 이 인자와 아래 OffsetHigh 를 통해 파일 내에서 I/O
작업을 시작할 위치를 64 비트 오프셋 값으로 지정할 수 있다 .
동일한 파일 커널 오브젝트에 대해 여러 번의 비동기 I/O 요청
시에는 반드시 OVERLAPPED 구조체 내에 I/O 작업을 시작할
위치를 지정해야 한다 .
• OffsetHigh : 위의 Offset 과 같은 역할을 하며 32 비트 상위
파일의 오프셍을 나타낸다 .
• hEvent : 이 멤버는 I/O 완료 통지를 수신하는 네 가지 방법 중
하나의 방법에서 사용된다 . 얼러터블 I/O 통지 방법을 사용하는
경우에는 사용자 임의로 이 멤버를 사용할 수도 있다 .
• 비동기 I/O 요청이 완료되면 I/O 요청 시 사용했던 OVER-
LAPPED 구조체의 주소를 돌려주게 된다 . 그러므로 이
구조체에 장치 핸들이나 유용한 컨텍스트 정보를 저장할 만한
멤버를 포함시켜서 작성하기도 한다 .
Enum IOType
{
OP_RECV,
OP_SEND
}
class OVERLAPPEDEX : public OVERLAPPED
{
IOType m_eIOType;
SOCKET m_hSocket;
};
비동기 장치 I/O 사용 시 주의점
1. 디바이스 드라이버는 비동기 I/O 요청을 항상 선입 선출로 처리 안함
2. 에러 확인을 수행하는 적당한 방법에 대해 알고 있어야 한다 .
1. 비동기 요청에도 시스템에 의해 동기적으로 처리될 때 있다 .
2. Read / Write File 은 I/O 요청이 동기적으로 수행되는 경우 0
이 아닌 값을 반환
3. I/O 요청이 비동기적으로 수행되는 경우나 에러 발생시 FALSE
반환 , 고로 GetLastError (ERROR_IO_PENDING)
3. 데이터 버퍼와 OVERLAPPED 구조체는 I/O 요청이 완료될 때까지
옮겨지거나 삭제되지 않아야 한다 .
요청된 장치 I/O 의 취소
• CancelIo 함수 호출
• 이 함수를 호출한 스레드가 삽입한 모든 I/O 요청 취소
• 장치에 대한 핸들 닫기
• 어떤 스레드가 I/O 요청을 삽입한지 고려하지 않고 모든 I/O 요청
취소
• 핸들이 I/O 컴플레이션 포트와 연계되어 있는 경우를 제외하면 ,
스레드가 종료될 때 종료된 스레드가 삽입하였던 모든 I/O 요청이 취소
• 특정 장치에 대해 하나의 I/O 요청만 취소할 때
• CancelIoEx 함수 호출
05
완료 통지 방법 ?
• 비동기 호출은 언제 완료될 지 알 수 없기때문에 I/O 작업이 완료되면
완료된 사실을 통지해 주어야 한다 .
완료 통지 방법 ?
방법 요약
디바이스 커널 오브젝트 시그널링 • 단일의 장치에 대해 다수의 I/O 요청 적합하지 않다 .
• 특정 스레드가 I/O 요청을 삽입하고 다른 스레드가 완료 통지를 수신할 수 있다 .
이벤트 커널 오브젝트의 시그널링 • 단일의 장치에 대해 다수의 I/O 요청을 수행할 수 있다 .
• 특정 스레드가 I/O 요청을 삽입하고 다른 스레드가 완료 통지를 수신할 수 있다 .
얼러터블 I/O 사용 • 단일의 장치에 대해 다수의 I/O 요청을 수행할 수 있다 .
• 항상 I/O 요청을 삽입한 스레드가 완료 통지를 수신한다 .
I/O 컴플리션 포트 사용 • 단일의 장치에 대해 다수의 I/O 요청을 수행할 수 있다 .
• 특정 스레드가 I/O 요청을 삽입하고 다른 스레드가 완료 통지를 수신할 수 있다 .
• 이 방법이 가장 확장성이 뛰어나고 유연성이 있다 .
디바이스 커널 오브젝트의 시그널링
• 디바이스 커널 오브젝트의 시그널과 논시그널 상태를 이용 .
HANDLE hFile = CreateFile(..., FILE_FLAG_OVERLAPPED, ...)BYTE bBuffer[100];OVERLAPPED o = { 0 };o.Offset = 345;
BOOL bReadDone = ReadFile(hFile, bBuffer, 100, NULL, &o);DWORD dwError = GetLastError();
If (!bReadDone && (dwError == ERROR_IO_PENDING)) {WaitForSingleObject(hFile, INFINITE);bReadDone = TRUE;
}
If (bReadDone) {...}
디바이스 커널 오브젝트의 시그널링
• 비동기 I/O 를 위해 디바이스를 열 때 반드시
FILE_FLAG_OVERLAPPED
• OVERLAPPED 구조체의 Offset, OffsetHigh, hEvent 초기화
• bReadDone 을 확인후 동기적으로 수행되었는지 확인하고 동기적이
아니라면 비동기적으로 I/O 작업이 요청되었는지 확인
• 읽기 작업 완료 시 bBuffer 에는 읽은 데이터가 , OVERLAPPED
구조체의 Internal 멤버에는 Error 코드가 , InternalHigh 멤버에는
읽은 데이터의 크기가 각각 저장된다 .
디바이스 커널 오브젝트의 시그널링의 문제 ?
HANDLE hFile = CreateFile(..., FILE_FLAG_OVERLAPPED, ...)BYTE bReadBuffer[100];OVERLAPPED oRead = { 0 };
ReadFile(hFile, bReadBuffer, 100, NULL, &oRead);
BYTE bWriteBuffer[10] = {0, 1, 2, 3, ... };OVERLAPPED oWrite = { 0 };oWrite.Offset = 10;
WirteFile(hFile, bWriteBuffer, _countof(bWriteBuffer), NULL, &oWrite);
...
WaitForSingleObject(hFile, INFINITE)
// ???
이벤트 커널 오브젝트의 시그널링
HANDLE hHandle = CreateFile(..., FILE_FLAG_OVERLAPPED, ...)BYTE bReadBuffer[100];OVERLAPPED oRead = { 0 };oRead.hEvent = CreateEvent(...);
ReadFile(hFile, bReadBuffer, 100, NULL, &oRead);
BYTE bWriteBuffer[10] = {0, 1, 2, 3, ... };OVERLAPPED oWrite = { 0 };oWirte.hEvent = CreateEvent(...);
WirteFile(hFile, bWriteBuffer, _countof(bWriteBuffer), NULL, &oWrite);
이벤트 커널 오브젝트의 시그널링
HANDLE h[2];h[0] = oRead.hEvent;h[1] = oWrite.hEvent;
DWORD dw = WaitForMultipleObjects(2, h, FALSE, INFINITE);Switch (dw – WAIT_OBJECT_0) {
case 0: // 읽기 작업 완료break;
case 1: // 쓰기 작업 완료break;
}
GetOverlappedResult(...)
하지만 현재는 Internal 과 InternalHigh 이 문서화
얼러터블 I/O
APC(Asynchronous Procedure Call)
스레드가 생성되면 APC 큐라 불리는 큐가 하나씩 생성
ReadFileEX / WriteFileEX
BOOL ReadFileEx (
HANDLE hFile,
PVOID pvBuffer,
DWORD nNumByteToRead,
OVERLAPPED* pOverlapped,
LPOVERLAPPED_COMPLETION_ROUTINE pfnRoutine);
VOID WINAPI CompletionRoutine (DWORD dwError,DWORD pwNumBytes,OVERLAPPED* po);
얼러터블 I/O
hFile = CreateFile(..., FILE_FLAG_OVERLAPPED, ...);
ReadFileEx(hFile, ...);
WriteFileEx(hFile, ...);
ReadFileEx(hFile, ...);
SomeFunc();
Write I/O Read I/O Read I/O
스레드를 인터럽트 가능한 상태가 되었음을 알려줘야한다 .
즉 , 스레드를 얼러터블 상태로 변경
얼러터블 I/O
얼러터블 상태로 변경할 수 있는 함수
• SleepEx / WaitForSingleObjectEx / WaitForMultipleObjectsEx /
SignalObjectAndWait / GetQueuedCompletionStatusEx / Msg-
WaitForMultipleObjectsEx
얼러터블 I/O 의 장단점
• 장점
• 비동기 호출을 가능하게 만들어준다 .. ㅠ . ㅠ
• 단일 스레드라서 동기화 처리를 할 필요가 없다 . ㅠ . ㅠ
• 단점
• 콜백함수
• 쓰레딩 문제
I/O 컴플리션 포트
• 컨커런트 모델
• 컨커런트 모델을 사용하는 서비스 애플리케이션을 윈도우에서
구현하였을 때 성능이 기대만큼 나오지 않자 개선해서 나온 결과로
태어난 것이 바로 I/O 컴플리션 포트 커널 오브젝트이다
• I/O 컴플리션 포트 생성
HANDLE CreateIoCompletionPort(
HANDLE hFile,
HANDLE hExistingCompletionPort,
ULONG_PTR CompletionKey,
DWORD dwNumberOfConcurrentThreads);
I/O 컴플리션 포트
HANDLE CreateNewCompletionPort (DWORD dwThreads)
{
return CreateIoCompletionPort(
INVALID_HANDLE_VALUE, NULL, 0,
dwThreads);
|
• dwThreads
• 동일 시간에 동시에 수행할 수 있는 스레드의 최대 개수
• 보통은 CPU 개수만큼을 입력
I/O 컴플리션 포트
• I/O 컴플리션 포트 연계
HANDLE CreateIoCompletionPort(
HANDLE hFile,
HANDLE hExistingCompletionPort,
ULONG_PTR CompletionKey,
DWORD dwNumberOfConcurrentThreads);
• hFile : 장치에 대한 핸들 ( 파일 , 소켓 , 메일슬롯 , 파이프등 )
• hExistingCompletionPort : 생성해 둔 I/O 컴플리션 포트 핸들
• CompletionKey : 컴플리션 키 ( 사용자가 임의로 결정할 수 있다 .)
I/O 컴플리션 포트
• I/O 컴플리션 포트 연계
BOOL AssociateDeviceWithCompletionPort(
HANDLE hDevice,
HANDLE hExistingCompletionPort,
ULONG_PTR CompletionKey,) {
HANDLE h = CreateIoCompletionPort(hDevice,
hCompletionPort, dwCompletionKey, 0);
return (h == hCompletionPort);
}
I/O 컴플리션 포트
• 장치 리스트
• I/O 컴플리션 큐
• 대기 스레드 큐
• 릴리즈 스레드 리스트
• 일시 정지 스레드 리스트
I/O 컴플리션 포트
Device List I/O Request ListI/O Completion
Queue
커널 모드
CreateCompletionPort
WSARecv()WSASend()
ReadFileEX()…
GetQueuedComple-tionStatus()
유저 모드
I/O 컴플리션 포트를 이용한 아키텍쳐 설계
• 클라이언트의 요청을 처리하는 스레드 풀
• 풀 내에 몇 개의 스레드를 생성해 두는 것이 좋은가 ? ( 다음발표자
분 )
• 완료 통지가 전달 되었을 때 처리할 수 있도록 스레드를 대기 상태로 !!BOOL GetQueuedCompletionStatus(
HANDLE hCompletionPort,
PDWORD pdwNumberOfBytesTransferred,
PULONG_PTR pCompletionKey,
OVERLAPPED** ppOverlapped,
DWORD dwMilliseconds);
I/O 컴플리션 포트를 이용한 아키텍쳐 설계
• 대기 스레드 큐
• 후입선출 (LIFO) 방식으로 스레드를 깨운다 .
• 릴리즈 스레드 리스트
• I/O 컴플리션 포트가 대기 스레드 큐에 있는 스레드를 깨우는 경우
• 일시 정지되었던 스레드가 다시 깨어났을 경우
• 일시 정지 스레드 리스트
• 수행 중이던 스레드가 스레드를 정지시키는 함수를 호출
• 정지 중이던 스레드가 깨어나면 릴리즈 스레드 리스트로 삽입
I/O 컴플리션 포트의 스레드 풀 관리방법
• I/O 컴플리션 포트 생성할 때 지정한 스레드 개수 이상을 초과할 수 없다
• 근데 왜 ?! 이보다 많은 스레드를 풀로 관리해야 하는가 ?
• 릴리즈 스레드 리스트
• 대기 스레드 리스트
I/O 컴플리션 사용 목적
CPU 가 계속해서 작업을 수행하도록 상태를 유지하는 것