SlideShare a Scribd company logo
1 of 49
10.동기 및 비동기 장치 I/O
제프리 리처의 Windows VIA C/C++
2022.01.25 함송연
1. 장치 열기와 닫기
2. 파일 장치 이용
3. 동기 장치 I/O 수행
4. 비동기 장치 I/O의 기본
5. I/O 요청에 대한 완료 통지의 수신
1
동기 및 비동기 장치 I/O
스레드들 간의 상호 통신
- 스레드가 동기적인 장치 I/O를 요청하면 I/O 작업이 완료될 때까지 일시적으로 스레드 수행이 블로킹
- 스레드들이 계속해서 수행되려면 스레드들 간에 수행할 작업에 대한 상호 통신이 필요
- 마이크로소프트가 결론낸 스레드 간의 통신 매커니즘  I/O 컴플리션 포트
2
01 장치 열기와 닫기
장치 : 통신 가능한 어떤 것
3
장치 관련 함수
CreateFile, CreateMailSlot, CreateNamedPipe,CreatePipe,
socket, accept, 또는 AcceptEx
CreateConsoleScreenBuffer 또는 GetStdHandle
 각각의 장치를 구분할 수 있는 고유의 핸들 값 반환
장치와 통신을 수행하는 함수에서 이 핸들 값을 인자로 사용
01 장치 열기와 닫기
장치 : 통신 가능한 어떤 것
4
예:
시리얼 포트의 전송 속도를 설정
- BOOL SetCommConfig
([in] HANDLE hCommDev, [in] LPCOMMCONFIG lpCC, [in]
DWORD dwSize);
데이터를 읽는 동안 대기 만료 시간을 설정
- BOOL SetMailslotInfo
([in] HANDLE hMailslot, [in] DWORD ReadTimeout);
사용 후 핸들 닫기
- BOOL CloseHandle(HANDLE hObject);
- int closesocket(SOCKET s) ;
장치의 타입을 확인하기
- GetFileType(HANDLE hDevice);
01.1 CreateFile에 대한 세부사항 검토
5
pszName
- 특정 장치의 인스턴스 값
- 장치의 타입을 구분할 수 있는 값
dwDesiredAccess
- 장치와 데이터를 어떻게 주고받을지를 결정
- 일부 장치는 접근 제어 플래그(access control flag) 필요
파일 생성, 기존 파일 열기 뿐만 아니라 파일이 아닌 다른 장치에
대해서도 열기 작업을 수행할 수 있다.
• 파일명 길이에는 제한이 있음에 주의
01.1 CreateFile에 대한 세부사항 검토
6
dwShareMode
- 장치의 공유 특성을 지정
- 장치에 대해 열기 작업을 이미 수행한 경우에(Close Handle을 호출하기
이전에) 동일 장치에 대해 추가적으로 CreateFile을 수행한 경우, 열기 작
업을 어떻게 수행할지를 제어
파일 생성, 기존 파일 열기 뿐만 아니라 파일이 아닌 다른 장치에
대해서도 열기 작업을 수행할 수 있다.
01.1 CreateFile에 대한 세부사항 검토
7
psa
- SECURITY ATIRIBUTES 구조체
- 보안 정보를 설정, CreateFile이 반환하는 핸들을 상속 가능하도록 구성
할 것인지…
- 이 구조체 내의 보안 디스크립터(security descriptor)는 NTFS와 같은 안
전한 파일시스템 상에 파일을 생성하는 경우에만 사용
- NULL 전달시 디폴트 값(기본 보안 특성, 상속 불가능한 핸들 반환)
dwCreationDisposition
- 파일 장치에 대해 사용시 가장 큰 의미를 가짐
파일 생성, 기존 파일 열기 뿐만 아니라 파일이 아닌 다른 장치에
대해서도 열기 작업을 수행할 수 있다.
01.1 CreateFile에 대한 세부사항 검토
8
dwFlagsAndAttributes
- 데이터 송수신시 세부적인 통신 플래그를 설정 : 사용자가 어떤 방식으로
장치에 접근할 것인지 의도를 설정하여, 캐시 알고리즘 최적화 가능
- 혹은 파일 특성을 설정
파일 생성, 기존 파일 열기 뿐만 아니라 파일이 아닌 다른 장치에
대해서도 열기 작업을 수행할 수 있다.
01.1 CreateFile에 대한 세부사항 검토
9
<CreateFile 캐시 플래그>
Flag 내용
FILE_FLAG_NO_BUFFERING 파일 접근시 어떠한 버퍼링도 수행하지 않음.
파일 시스템의 디바이스 드라이버는 함수를 통해 전달된 버퍼의 내용을 캐시 매니저가 관장하는 버퍼가 아닌, 파일에 직
접적으로 쓰게 됨.
- 파일에 접근할 때에는 항상 디스크 볼륨의 섹터 크기의 배수 단위로 I/O를 수행할 위치를 정해야 한다
(GetDiskFreeSpace로 섹터 크기 get)
- 한 번에 읽고 쓰는 바이트 수는 항상 섹터 크기의 배수로 지정
- 프로세스의 주소 공간 상의 버퍼의 시작 주소는 반드시 섹터의 크기로 나누어 떨어지는 위치로부터 시작
매우 큰 파일을 열 때 사용
- 파일이 커지면 캐시 매니저에게 더 큰 자료구조가 필요함.
- 캐시 매니저가 공간 확보에 실패하면 파일 열기가 실패할 수 있기 때문에 이럴 땐 FILE_FLAG_NO_BUFFERING을 사
용하자.
FILE_FLAG_SEQUENTIAL_SCAN,
FILE_FLAG_RANDOM_ACCESS
FILE_FLAG_SEQUENTIAL_SCAN : 순차적 접근 사용
- 파일 읽기를 시도하는 경우 실제로 필요한 크기보다 더 많은 데이터를 메모리로 읽어 들임  접근하는 횟수 줄이기
- 파일에 대한 접근 위치 이동시에 시간이 약간 더 걸림
- 접근하지 않은 데이터 캐싱에 메모리 공간을 일부 낭비
FILE_FLAG_RANDOM_ACCESS : 임의적 접근 사용
FILE_FLAG_WRITE_THROUGH 파일에 데이터를 쓸 때 데이터 손실의 가능성을 줄이기 위한 중간 캐싱 기능을 사용하지 않도록 한다.
(변경사항을 디스크에 직접 쓴다)
읽기는 여전히 내부적인 캐시를 사용한다.
네트워크 파일 서버에 있는 파일을 열 때 사용시 윈도우는 네트워크 파일 서버의 디스크 드라이브 상에 데이터를 완전히
쓸 때까지 함수를 반환하지 않는다.
01.1 CreateFile에 대한 세부사항 검토
10
<기타 CreateFile 플래그>
Flag 내용
FILE_FLAG_DELETE_ON_CLOSE 파일과 관련된 모든 핸들이 닫히면 파일이 삭제된다.
주로 FILE_ATTRIBUTE_TEMPORARY 플래그와 함께 사용
- 애플리케이션이 임시 파일을 생성, 쓰고, 읽고, 닫음
- 파일이 닫히면 시스템이 파일을 자동으로 삭제 (MS 오피스에서 많이 본듯…)
FILE_FLAG_BACKUP_SEMANTICS 백업/복원 시 파일 보안 확인 기능(프로세스에게 적절한 권한이 있는지)을 수행하지 않는다
- 시스템은 호출자의 접근 토큰이 파일과 폴더에 대해 백업/복원을 수행할 수 있는 권한을 가지고 있는지만 확인
FILE_FLAG_POSIX_SEMANTICS 파일을 찾을 때도 대소문자를 구분 (원래는 파일 찾을 때 구분하지 않음, 파일 저장시에는 구분)
- 파일을 생성할 때 이 플래그를 사용하면 다른 윈도우 애플리케이션에서 이 파일에 접근하지 못할 수 있음에 주의
FILE_FLAG_OPEN_REPARSE_POI
NT
(FILE_FLAG_IGNORE_REPARSE_
POINT가 더 낫지 않냐고 함)
시스템에게 파일의 리파스 Reparse 특성을 무시할 것을 요청
- 리파스 특성을 사용하면 파일시스템 필터가 열기, 읽기, 쓰기, 닫기 동작을 변경할 수 있음
FILE_FLAG_OPEN_NO_RECALL 오프라인 저장 장치(테이프 등…)로부터 온라인 저장 장치(하드 디스크 등…)로 파일을 복원하지 못하도록 한다.
FILE_FLAG_OVERLAPPED 시스템에게 장치에 비동기적으로 접근하길 원한다고 알림
- 비동기 I/O 작업시, 작업이 완료되기 전에 함수가 반환됨.
- 운영체제는 사용자 스레드를 대신하여 I/O 작업을 수행하고 요청한 작업이 완료되면 그 사실을 통보
01.1 CreateFile에 대한 세부사항 검토
11
<파일 특성 플래그>
- 새로운 파일을 만들거나, hFileTemplate 매개변수에 NULL을 주는 경우에만 의미를 가진다.
FILE_ATTRIBUTE_TEMPORARY,
FILE_FLAG_DELETE_ON_CLOSE를 함께 쓰면 시스템 성능
을 상당히 개선!
임시 파일 : CreateFile은 좀 더 빠르게 파일의 내용에 접근할
수 있도록 디스크 대신 메모리에 파일의 내용을 유지(하기 위
해 노력)
01.1 CreateFile에 대한 세부사항 검토
12
hFileTemplate
- 이미 열린 파일에 대한 핸들을 전달 
dwFlagsAndAttributes 매개변수 무시, 해당 파일의 특성 값을 사용
- 전달되는 핸들은 반드시 GENERIC_READ 플래그를 포함해야 함
- 단 CreateFile을 통해 이미 존재하는 파일을 여는 경우 hFileTemplate 매
개변수 무시
- 미사용시 NULL
반환값
- 에러시 INVALID_HANDLE_VALUE
파일 생성, 기존 파일 열기 뿐만 아니라 파일이 아닌 다른 장치에
대해서도 열기 작업을 수행할 수 있다.
02 파일 장치 이용
13
01. 파일 크기 얻기
BOOL GetFileSizeEx([in] HANDLE hFile, [out] PLARGE_INTEGER lpFileSize);
- 파일의 논리적 크기 반환
DWORD GetCompressedFileSizeA([in] LPCSTR lpFileName, [out, optional] LPDWORD
lpFileSizeHigh);
- 파일의 물리적 크기 반환(실제 디스크 점유)
- 파일의 핸들이 아닌 경로명을 전달
- lpFileSizeHigh : DWORD의 high-order. 파일이 4 기가바이트를 넘지 않는다면 필요 없음
LARGE_INTEGER 구조체
02.2 파일 포인터 위치 지정
14
파일 커널 오브젝트
- CreateFile 호출시 내부적으로 파일 커널 오브젝트를 생성. 내부적으로 파일 포인터를 가지고 있음.
파일 포인터
- 64비트 오프셋 값으로 동기적인 I/O를 수행할 위치 정보를 가지고 있음
- 파일 열기를 수행하면 파일 포인터가 0으로 초기화
- CreateFile 호출한 직후 ReadFile을 호출하면 파일의 가장 앞쪽 내용(오프셋 0)부터 데이터를 읽게 됨
 파일을 열어서 10 바이트씩 두 번 읽음
 각각의 커널 오브젝트는 별도의 파일 포인터를 가지고 있
어서 같은 파일이라 하더라도 서로 독립적으로 수행됨
02.2 파일 포인터 위치 지정
15
 DuplicateHandle은 같은 커널 오브젝트를 참조하기 때문에
동일한 파일 포인터를 사용하게 됨
02.2 파일 포인터 위치 지정
16
BOOL SetFilePointerEx
([in] HANDLE hFile, [in] LARGE_INTEGER liDistanceToMove,
[out, optional] PLARGE_INTEGER lpNewFilePointer, [in] DWORD
dwMoveMethod);
- 파일의 임의 위치에 접근하고 싶을 때 사용
- liDistanceToMove : 파일 포인터를 얼마나 이동하고자 하는지(바이트 단위)
- dwMoveMethod : DistanceToMove 매개변수에 대한 해석을 설정
- pliNewFilePointer : 갱신된 파일 포인터 값. 불필요시 NULL
주의
- 파일 포인터를 현재 파일 크기보다 더 크게 설정할 수 있음. 이 때 이동된 위치에 어떤 내용을 쓰거나 SetEndOfFile을 호출하면 파일 크기가 변경됨.
- FILE_FLAG_NO_BUFFERING을 지정하여 파일을 열었다면 SetFilePointerEx를 호출하여 섹터 크기로 정렬된 위치로만 파일 포인터를 이동할 수 있다.
- 윈도우는 GetFilePointerEx 함수를 제공하지 않는다. Get하는 방법은,
02.3 파일의 끝 설정
17
B00L SetEndOfFile(HANDLE hFile);
- 파일 포인터가 가리키는 현재 위치를 파일의 끝으로 설정
 파일 크기가 정확히 1024바이트가 됨
03 동기 장치 I/O 수행
18
장치 : 파일, 메일슬롯, 파이프, 소켓 등!!
이 때 FILE_FLAG_OVERLAPPED 플래그를 사용하지 않음(비동기니까)
pvBuffer
- 장치로부터 읽어온/장치에 쓸 데이터를 저장할 버퍼 포인터
nNumBytesToRead/nNumBytesToWrite
- 장치로부터 몇 바이트를 쓰거나 읽을지
pdwNumBytes
- 장치와 성공적으로 송수신한 데이터 크기(DWORD)를 받아올 변수 주소
pOverlapped
- NULL
03.1 장치로 데이터 플러시
19
장치 : 파일, 메일슬롯, 파이프, 소켓 등!!
BOOL FlushFileBuffers(HANDLE hFile);
- hFile이 가리키는 장치와 연관되어 있는 캐시된 데이터를 강제적으로 쓰도록 한다. (캐시된 데이터를 장치로 플러시)
- 이 작업을 수행하려면 반드시 GENERIC_WRITE 플래그를 포함하여 장치를 열여야 한다.
03.2 동기 I/O의 취소
20
애플리케이션 hang 증상
- 스레드가 동기 I/O의 완료를 기다리는 동안, 스레드의 메시지 큐에 삽입된 메시지가 정상적으로 수행되지 못함
- 스레드가 생성한 모든 윈도우가 같이 정지된다.
BOOL CancelSynchronousIo(HANDLE hThread);
hThread : 동기 I/O로 인해 정지되어 있는 스레드의 핸들
- 핸들 생성시 접근 권한이 THREAD_TERMINATE 로 되어 있어야 한다. 아닌 경우 실패. LastError : ERROR_ACCESS_DENIED
- 성공적으로 취소한다면, 스레드는 수행 재개. 함수는 TRUE 반환. LastError : ERROR_OPERATION_ABORTED
- 함수 수행 시점의 스레드 상태를 알 수 없음에 주의
- 해당 스레드가 실제로 동기 I/O를 수행중인지 알 수 없다 (I/O를 요청했지만 그보다 먼저 CancelSynchronousIo가 수행될 수 있음)
- 스레드가 장치로부터 응답을 받기 위해 대기하고 있을 수도 있고, 혹은 장치는 이미 응답을 하였으나 호출한 힘수가 반환되기 직전의 상태일 수도 있
다.
- 이럴 땐 FALSE 반환, LastError : ERROR_NOT_FOUND
- 취소할 동기 I/O 작업이 진행 중에 있는지 확인
- 추가적인 스레드 동기화 방법이 필요하지만 실제로는 그다지 필요하지 않다. 대부분 애플리케이션이 정지되었음을 사용자가 알 때 수행되기 때문.
04 비동기 장치 I/O의 기본
21
CreateFile
- dwFlagsAndAttributes 매개변수 : FILE_FLAG_OVERLAPPED
- 해당 플래그를 사용하여 CreateFile
ReadFile, WriteFile
- hFile이 가리키는 장치가 FILE_FLAG_OVERLAPPED 플래그를 이용하여 열렸다면 비동기 I/O를 수행
- 이때 pdwNumBytes 매개변수는 NULL 전달 가능 (함수 반환 시점에 의미가 없기 때문)
04.1 OVERLAPPED 구조체
22
Overlapped (중첩) : 동일 스레드가 다른 작업을 수행하는 동안에 또 다시 새로운 I/O 작업을 시작함을 의미
ReadFile/WriteFile 호출하기 이전에 초기화 필요
Internal과 InternalHigh : 디바이스 드라이버에 의해 설정. I/O 작업이 완료되었는지 여부
를 확인하기 위해 사용
04.1 OVERLAPPED 구조체
23
Offset, OffsetHigh
- 파일에 대한 I/O를 수행하는 경우 파일 내에서 I/O 작업을 시작할 위치를 64비트 오프셋 값 지
정
- 비동기 작업시 파일 커널 오브젝트 내의 파일 포인터를 사용할 수 없다.
- 파일 작업이 아닌 다른 장치에서도 무시되지 않기 때문에 주의 (0이 아닐 경우 LastError :
ERROR_INVALID_PARAMETER)
hEvent
- 416p “이벤트 커널 오브젝트의 시그널링”에서 상세
- 얼러터블 I/O 통지 방법을 사용하는 경우 사용자 임의로 이 멤버를 사용 (hEvent의 멤버들을
C++ 오브젝트들의 주소를 담기 위해 사용)
Internal
- 처리된 I/O의 에러 코드를 담기 위해 사용
- I/O 작업 수행중일 때의 코드 : STATUS_PENDING
비동기 I/O 요청 시도  디바이스 드라이버가 이 값으로 설정. 아직 작업이 완료되지 않았고,
어떠한 에러도 없다는 의미.
- HasOverlappedIoCompleted(pOverlapped) 매크로로 I/O 작업 완료 여부를 확인할 때
사용됨
InternalHigh
- 비동기 I/O 작업 완료되면 실제로 송수신된 바이트 수를 저장
OVERLAPPED 구조체에 추가적인 컨텍스트를 추가해서 서브클래스로
만들어 두면 편하게 쓸 수 있다는 이야기
04.2 비동기 I/O 사용 시 주의점
24
1. 디바이스 드라이버는 비동기 I/O 요청을 항상 선입선출 방식으로 처리하지 않는다.
2. 에러 확인을 수행하는 적당한 방법을 알고 있어야 한다.
ReadFile/WriteFile의 반환값
- 동기적으로 수행되는 경우 0이 아닌 값을 반환
- 비동기적으로 수행되는 경우나 에러가 발생하면 FALSE를 반환
-  FALSE일 경우, GetLastError()로 의미 확인
GetLastError()로 확인할 수 있는 에러 코드
ERROR_IO_PENDING : 요청 수행중
ERROR_INVALID_USER_BUFFER, ERROR_NOT_ENOUGH_MEMORY
: 논페이지 풀 nonpaged pool이 꽉 차게 된 경우 (각각의 디바이스 드라이버가 가지는 I/O 요청을
삽입하기 위한 고정 크기의 리스트)
ERROR_NOT_ENOUGH_QUOTA
: 프로세스가 잠글 수 있는 페이지의 크기를 초과하여 ReadFile,WriteFile 내부에서 사용자가 전달
한 버퍼를 잠글 수 없는 경우. 함수는 FALSE 반환, LastError : ERROR_NOT _ENOUGH_QUOTA
- 몇몇 장치들은 사용자가 전달해 주는 버퍼가 잠긴 상태의 페이지 내에 존재하여, I/O 요청을 기
다리는 동안 RAM 외부로 스와핑되지 않을 것을 요구. 파일 I/O시
FILE_FLAG_NO_BUFFERING 플래그를 사용하면 잠긴 페이지 내의 저장 공간을 필요로 한다.
- SetProcessWorkingSetSize를 호출하여 프로세스당 잠글 수 있는 페이지 크기를 변경할 수 있
다.
04.2 비동기 I/O 사용 시 주의점
25
3. 비동기 I/O 요청을 수행할 때 사용되는 데이터 버퍼와 OVERLAPPED 구조체는 요청이 완료될
때까지 옮겨지거나 삭제되지 않아야 한다.
- 매 요청시마다 OVERLAPPED 구조체를 새로 생성하고 초기화하여 전달하자
 잘못된 I/O 요청 코드 예시
함수가 즉시 반환되면서 함수 내부에서 만든 OVERLAPPED 구조체와 버퍼가 사라지지만,
디바이스 드라이버는 계속 이 두 개의 포인터를 사용하게 된다.
I/O 작업이 완료되면 디바이스 드라이버는 스레드 스택 내의 메모리를 수정하게 될 것이며, 메모리
손상의 원인이 된다.
비동기 작업이기 때문에 발견하기 어렵다.
04.3 요청된 장치 I/O의 취소
26
I/O 작업을 요청하기 전에 앞서 요청한 I/O 작업을 취소하고 싶은 경우
 다른 스레드가 삽입한 I/O 요청도 취소할 수 있다.
OVERLAPPED가 NULL이라면 hFile이 가리키는 장치에 대한 모든 I/O 요청을 취소
한다.
05 I/O 요청에 대한 완료 통지의 수신
27
05.1 디바이스 커널 오브젝트의 시그널링
28
디바이스 커널 오브젝트의 시그널/논시그널 상태를 이용한다.
개념을 설명하기 위한 코드이며 실제로는 이런 식으로 코드를 짜지 않는다.
05.2 이벤트 커널 오브젝트의 시그널링
29
이전과 같은 방법으로는 다수의 요청에 대한 처리를 수행할 수 없다.
파일로부터 10 바이트를 읽고, 동시에 10 바이트를 쓰는 작업을 수행해 보자.
WaitForSingleObject()를 사용하여 읽기/쓰기가 어떤 상태로 있는지 확인하기 어렵
다.
05.2 이벤트 커널 오브젝트의 시그널링
30
다소 억지스러우며 실사용을 위한 코드는 아니다.
hEvent
- CreateEvent를 호출하여 얻은 이벤트 커널 오브젝트에 대한 핸
들
- 비동기 I/O 요청이 완료되면 SetEvent 함수를 호출해 준다
(hEvent != NULL인 경우)
05.3 얼러터블 I/O
31
조금 복잡한 과거를 가진듯… (MS가 최상의 메커니즘인 것처럼 내놓았지만 개발자들은 공감하지 못했던 듯 하다)
비동기 프로시저 콜
- 스레드 생성시, 시스템은 각 스레드별로 비동기 프로시저 콜(APC) 큐라는 큐를 하나씩 생성한다.
- 비동기 I/O 요청을 전달하는 함수 호출시, (ReadFileEx, WriteFileEx)
디바이스 드라이버에게 I/O 작업 완료 통지를 스레드의 APC 큐에 삽입해 줄 것을 요청할 수 있다.
함수는 호출 즉시 반환됨.
ReadFile, WriteFile 차이점
- I/O 작업이 수행된 바이트 수를 돌려받을 DWORD 포인터 전달하지
않음. 이 정보는 콜백 함수를 통해 획득.
- 컴플리션 루틴이라고 불리는 콜백함수의 주소를 필요로 한다.
컴플리션 루틴은 반드시 다음과 같은 형태로 구현
05.3 얼러터블 I/O
32
스레드 APC 큐
W1 R2 R1
- 순서는 보장되지 않음
- Entry 각각은 컴플리션 루틴 주소, 컴플리션 루틴 전달값을 가짐
스레드 : *Ex() 호출하여 비동기 I/O 수행 요청, 컴플리션 루틴 주소 값 전달

디바이스 드라이버 : I/O 요청을 끝내고, 스레드의 APC 큐에 완료 통지 항목을 추가. 이 항목에 컴플리션 루틴의 주소와 I/O 요청시 사용된 OVERLAPPED
구조체 주소가 포함.

스레드 : 얼러터블 상태가 되면 시스템은 APC 큐의 내용을 확인하여 큐에 삽입된 모든 항목에 대해 컴플리션 루틴을 호출(I/O 에러코드, 송수신된 바이트
수, o 구조체 주소를 전달)
컴플리션 루틴 호출
- 스레드가 자신을 얼러터블 상태로 변경(어떠한 함수 호출)하여 인터럽트 가능한 상태가 되었음을 알림
- APC 큐에 항목이 있다면 스레드를 대기 상태로 전환하지 않고 하나씩 빼내어 지정된 콜백을 호출
- APC 큐가 완전히 비워지면, 커널 오브젝트가 시그널 상태가 될 때까지 기다리지 않고 얼러터블 상태로 변경하기 위해 호출하였던 함수(다음 페이지)가
반환됨
- APC 큐에 어떠한 항목도 없을 때 이 함수들을 호출하게 되면 스레드가 정지된다.
05.3 얼러터블 I/O
33
스레드를 얼러터블 상태로 변경할 수 있는 함수
마지막 인자 : 스레드를 얼러터블 상태로 변경할 것인지 여부를 나타내는 BOOL
내부적으로는 Ex가 없는 함수를 호출하면 내부적으로 bAlertable을 FALSE로 하여 *Ex를 호출한다.
반환값 : 이 함수가 반환된 이유
WAIT_IO_COMPLETION : (혹은 LastError가 WAIT_IO_COMPLETION) 적어도 하나 이상 APC 큐에 항목이
존재하였고 처리를 수행 완료
그 외 : 대기 시간 만료, 커널 오브젝트(혹은 단순 오브젝트)가 시그널 되었거나 뮤텍스가 버려졌음
 MWMO_ALERTABLE 플래그로 얼러터블
컴플리션 루틴 호출 (앞장과 동일한 내용)
- 스레드가 자신을 얼러터블 상태로 변경하여 인터럽트 가능한 상태가 되었음을 알림
- APC 큐에 항목이 있다면 스레드를 대기 상태로 전환하지 않고 하나씩 빼내어 지정된 콜백을 호출
- APC 큐가 완전히 비워지면 얼러터블 상태로 변경하기 위해 호출하였던 함수(다음 페이지)가 반환됨
- APC 큐에 어떠한 항목도 없을 때 이 함수들을 호출하게 되면 스레드가 정지된다.
05.3 얼러터블 I/O
34
사용하기 어려움
- 콜백 함수 : 코드를 이해하기 어렵게 함. 콜백 함수에서는 충분한 컨텍스트 정보를 유지하기 어렵기 때문에 전역변수가 많아질 수 있음.
- 쓰레딩 문제 : 가장 큰 문제점은 I/O 작업을 요청한 스레드가 반드시 완료 통지도 함께 처리해야 함.
여러 개의 I/O 요청  I/O 요청별로 발생하는 완료 통지를 자신이 모두 처리 (다른 스레드들이 놀고 있다고 하더라도…)
확장성 있는 애플리케이션을 만들기 어려워 짐.
APC 큐에 항목을 추가할 수 있는 함수
pfnAPC : 콜백 함수
콜백 함수의 원형
VOID WINAPI APCFunc(ULONG_PTR dwParam);
dwData : 콜백 함수로 전달할 값
반환값 : 성공 여부 BOOL 값(타입은 DWORD이지만…)
장점 : 다른 프로세스에 있는 스레드와도 효과적인 통신이 가능. 스레드를 대기 상태에서 강제로 빠져나오게 할 때에도
유용.
단점 : 단일의 값만 전달이 가능.
05.3 얼러터블 I/O
35
예시
 APC 큐의 모든 항목이 삭제되었으므로,
WaitForSingleObjectEx 함수는 WAIT_IO_COMPLETION 값을 반환
05.4 I/O 컴플리션 포트
36
서비스 애플리케이션의 아키텍처 모델
1) 시리얼 모델 serial model : 하나의 스레드가 사용자 요청을 대기. 사용자 요청이 들어오면 대기하던 스레드가 깨어나 클라이언트의 요청을 처
리.
2) 컨커런트 모델 concurrent model : 하나의 스레드는 사용자의 요청을 대기, 사용자의 요청을 처리하기 위해 새로운 스레드를 생성. 새로운 스레
드가 요청을 처리하는 동안 원래의 스레드는 다시 다른 사용자의 요청을 기다림. 사용자 요청을 처리하던 스레드는 작업이 완료되었을 때 종료.
멀티 프로세서의 장점을 십분 활용.
컨커런트 모델의 성능 저하
- 많은 수의 클라이언트 요청이 동시에 들어오면 시스템이 수많은 스레드를 만들고, 컨텍스트 스위칭에 너무 많은 시간을 허비. 각 스레드는 작업
에 필요한 시간을 충분히 할당받지도 못하게 됨.
- 이를 해결하기 위해 나온것이 IOCP
I/O 컴플리션 포트 생성
37
함수의 기능
1) I/O CP 생성
2) 장치와 I/O 컴플리션 포트를 연계
 저자는 두가지 기능을 별도의 함수로 만들어서 사용
I/O CP 생성
- 동일 시간에 수행할 수 있는 스레드의 최대 개수를 설정
- 0 입력시 머신에 설치된 CPU 개수를 사용
! 커널 오브젝트 생성 함수 중 유일하게 SECURITY_ATTRIBUTE 구조체 필요가 없음 (단일 프로세스 내에서만 수행될 수 있도록)
I/O CP 와 장치를 연계
- Next page…
I/O 컴플리션 포트 생성
38
I/O CP 생성시 만들어지는 5개의 구조체
I/O CP – (1) 장치 리스트
39
AssociateDeviceWithCompletionPort
- 내부적으로 관리되고 있는 장치 리스트에 새로운 항목을 추가
- I/O CP 핸들 : 앞서 생성한 IOCP 핸들
- 장치 핸들 : 파일, 소켓, 메일슬롯, 파이프 등…
- 컴플리션 키 : 이 값의 의미는 사용자가 임의로 전달할 수 있다. 운영체제는 이를 단순히 전달만 할 뿐이다.
I/O CP – (2) 컴플리션 큐
40
시스템 : 장치에 대한 비동기 I/O 요청 완료시, 장치와 연계된 I/O 컴플리션 포트가 있는지 확인
있다면 I/O 컴플리션 큐에 I/O 요청의 완료 통지를 나타내는 새로운 항목을 삽입
각 항목은 송수신된 바이트 수, I/O CP 연계시 지정한 컴플리션 키 값, 비동기 I/O 작업을 요청할 때 사용하였던 OVERLAPPED 구조체 포인터, 에러 코
드를 가짐
I/O CP를 이용한 아키텍처 설계
41
생성 팁
스레드 풀의 스레드는 CPU 개수 x 2가 가장 일반적
풀 내의 모든 스레드들은 동일한 스레드 함수를 수행하는 것이 좋다.
스레드 함수 : 초기화  루프 진입  애플리케이션 종료시 루프 탈출
루프 내에서 스레드는 대기 상태를 유지
: 비동기 장치 I/O 작업 완료시 I/O 컴플리션 포트를 통해 완료 통지가 될 때 이
를 곧바로 처리하도록.
GetQueuedCompletionStatus(…) 호출
이 함수는 호출 스레드를 I/O CP 내의 컴플리션 큐에 새로운 항목이 삽입될 때까
지 대기 상태를 유지. 타임아웃 값도 지정이 가능.
(대다수 앱은 하나의 I/O port만을 사
용)
I/O CP – (3) 대기 스레드 큐
42
각 스레드에서 GetQueuedCompletionStatus(…) 호출

스레드 ID가 대기 스레드 큐에 삽입.
I/O 커널 오브젝트는 어떤 스레드들이 I/O 요청에 대한 완료 통지를 처
리할 것인지 알 수 있음.
I/O 컴플리션 큐에 항목이 추가됨

I/O 컴플리션 포트는 대기 스레드 큐에 있는 스레드 중 하나를 깨움
이 스레드는 컴플리션 큐에 삽입된 항목을 가져오며,
GetQueuedCompletionStatus 함수의 매개변수를 통해 전달된다.
I/O CP – (3) 대기 스레드 큐
43
GetQueuedCompletionStatus(…) 함수가 왜 반환되었는지 파악하자
I/O 컴플리션 큐 : 선입선출
GetQueuedCompletionStatus 함수 호출 스레드 : 후입선출 방식으로 깨어남
하나의 I/O 완료 통지가 컴플리션 큐에 삽입

가장 마지막으로 GetQueuedCompletionStatus를 호출한 스레드가
컴플리션 큐의 항목을 처리하기 위해 깨어남

완료 통지에 대한 적절한 처리를 마친 후 다시 GetQueuedCompletionStatus() 호출

또 다른 완료 통지가 삽입되면 동일 스레드가 다시 깨어나서 완료 통지 항목을 처리
목적
- 가능한 앞서 작업을 유지했던 스레드를 다시 깨워서 작업
- 스케줄되지 않은 스레드들이 사용하는 메모리를 디스크로 내보낼 수 있고(swap
out), 프로세서 캐시를 비울 수 있다.
I/O CP – (3) 대기 스레드 큐
44
GetQueuedCompletionStatusEx(…)
- 여러 개의 완료 통지를 한 번에 가져올 수 있다.
pCompletionPortEntries : I/O 컴플리션 큐의 엔트리 배열을 받을 곳
ulCount : 몇 개의 항목을 복사해 올 것인지
pulNumEntriesRemoved : I/O 컴플리션 큐로부터 몇 개의 항목을 실제로 가지고 왔는지
bAlertable : FALSE라면 지정된 시간만큼 I/O 컴플리션 큐로 완료 통지가 삽입될 때까지 대기
TRUE라면 I/O 컴플리션 큐에 어떠한 완료 통지도 존재하지 않는 경우 스레드를 얼러터블 상태로 전
환
OVERLAPPED_ENTRY 구조체
Internal 필드는 미공개(사용하지 말것)
I/O CP로 비동기 작업을 요청했을 때,

동기적인 I/O 작업이 이루어지는 경우
…
I/O CP – 스레드 풀 관리 방법
45
I/O CP의 유용성
- 완료 통지 삽입  대기중인 스레드를 깨운다.
- 이 때 가동 가능한 스레드만큼 깨어있다면, 완료 통지 항목이 컴플리션 큐에 삽입되더라도 대
기 스레드를 깨우지 않는다.
이 때, 가동 가능한 스레드 수보다 많은 스레드를 스레드 풀로 관리하는 이유
I/O CP의 스레드 풀 관리
- 특정 스레드의 수행을 재개  릴리즈 스레드 리스트에 ID 기록
- 특정 스레드가 대기 상태로 진입  릴리즈 스레드에서 ID를 빼서 일시 정지 스레드 리스트로
이동
- 대기 상태에 있는 스레드 중 하나를 릴리즈 스레드로 옮겨온다.
- 이 때 대기 상태에 있던 스레드가 다시 수행을 재개하는 경우, 일시 정지 스레드 리스트에서
릴리즈로 항목을 옮겨온다.
- 이 때 릴리즈 리스트는 동시 수행 가능한 스레드 개수를 초과하는 개수의 항목을 가지게 된다.
- 이 후, 동시 수행중인 스레드 개수가 CPU 개수 이하로 떨어질 때까지 새로운 스레드를 깨우
지 않는다.
- 이러한 상황을 가능한 짧게 가져가기 위해 스레드가 GetQueuedCompletionStatus 를 재호출
하는 즉시 스레드를 대기 상태로 전환한다.
I/O CP – 스레드 풀 관리 방법
46
스레드 풀에 몇 개의 스레드를 유지?
고려할 것
1) 애플리케이션 초기화되는 시점엔 가능한 적은 수의 스레드만 생성
2) 가능한 한 스레드 최대 개수를 제한하고 싶다.
스레드 풀 관리 수도 코드
- 초기화 시에는 G_nThreadsMin 만큼 스레드 생성
- 모든 스레드들이 동일한 스레드 풀 함수를 사용
- 스레드 풀에는 하나 이상의 스레드가 존재하도록 해야 함
- GetCPUUsage는 직접 구현해야 함
I/O 완료 통지 흉내 내기
47
PostQueuedCompletionStatus
- 완료 통지를 I/O 컴플리션 큐에 삽입
- 이때 GetQueudCompletionStatus로 값을 가져오면 I/O 요청이 성공적으로 완료된 것처럼 TRUE 반환
- 스레드 풀에 존재하는 스레드들과 통신 수행시 유용
- 예: 스레드들이 I/O 컴플리션 포트 대기중인 경우에 애플리케이션을 종료하고 싶은 경우,
PostQueuedCompletionStatus()를 스레드 풀의 스레드 개수만큼 호출해주면 각 스레드들이 수행을 재개하게
되고 종료 절차를 진행
- 이 때, 어떠한 스레드가 종료 도중에 GetQueuedCompletionStatus를 호출하게 되면,
해당 스레드가 PostQueudComp etionStatu 를 두 번 호출할 수도 있다.
부가적인 스레드 동기화 방법이 필요하다.
 I/O CP 핸들을 CloseHandle 해버리면 일어나는 일
FileCopy 예제 애플리케이션
48
FileCopy.cpp 링크
IoCompletionPort.h 링크
I/O 컴플리션 포트를 사용하여 비동기 처리
49
끝

More Related Content

What's hot

Linux programming study
Linux programming studyLinux programming study
Linux programming studyYunseok Lee
 
도커 없이 컨테이너 만들기 2편
도커 없이 컨테이너 만들기 2편도커 없이 컨테이너 만들기 2편
도커 없이 컨테이너 만들기 2편Sam Kim
 
리눅스 커널 기초 태스크관리
리눅스 커널 기초 태스크관리리눅스 커널 기초 태스크관리
리눅스 커널 기초 태스크관리Seungyong Lee
 
도커없이 컨테이너 만들기 1편
도커없이 컨테이너 만들기 1편도커없이 컨테이너 만들기 1편
도커없이 컨테이너 만들기 1편Sam Kim
 
우분투에 시스템콜 추가하기
우분투에 시스템콜 추가하기우분투에 시스템콜 추가하기
우분투에 시스템콜 추가하기Hoyoung Jung
 
도커 없이 컨테이너 만들기 3편
도커 없이 컨테이너 만들기 3편도커 없이 컨테이너 만들기 3편
도커 없이 컨테이너 만들기 3편Sam Kim
 
Ch7,8. Configmaps, Secrets and API
Ch7,8. Configmaps, Secrets and APICh7,8. Configmaps, Secrets and API
Ch7,8. Configmaps, Secrets and APIHongmin Park
 
윈도우 커널 익스플로잇
윈도우 커널 익스플로잇윈도우 커널 익스플로잇
윈도우 커널 익스플로잇Seungyong Lee
 
Android ndk jni 설치및 연동
Android ndk jni 설치및 연동Android ndk jni 설치및 연동
Android ndk jni 설치및 연동Sangon Lee
 
ITs 2주차_기본명령어(발표)
ITs 2주차_기본명령어(발표)ITs 2주차_기본명령어(발표)
ITs 2주차_기본명령어(발표)Chulgyu Shin
 
2006 03 15_pe & api hook
2006 03 15_pe & api hook2006 03 15_pe & api hook
2006 03 15_pe & api hook용환 노
 
Linux Kernel Boot Process , SOSCON 2015, By Mario Cho
Linux Kernel Boot Process , SOSCON 2015, By Mario ChoLinux Kernel Boot Process , SOSCON 2015, By Mario Cho
Linux Kernel Boot Process , SOSCON 2015, By Mario ChoMario Cho
 
PE File Format and Packer - Inc0gnito 2016
PE File Format and Packer - Inc0gnito 2016PE File Format and Packer - Inc0gnito 2016
PE File Format and Packer - Inc0gnito 2016Hajin Jang
 
KGC 2016 오픈소스 네트워크 엔진 Super socket 사용하기
KGC 2016 오픈소스 네트워크 엔진 Super socket 사용하기KGC 2016 오픈소스 네트워크 엔진 Super socket 사용하기
KGC 2016 오픈소스 네트워크 엔진 Super socket 사용하기흥배 최
 
Windows via c/c++ ch03. 커널오브젝트
Windows via c/c++ ch03. 커널오브젝트Windows via c/c++ ch03. 커널오브젝트
Windows via c/c++ ch03. 커널오브젝트ssuser0c2478
 
Introduction to Linux #1
Introduction to Linux #1Introduction to Linux #1
Introduction to Linux #1UNIST
 
파일시스템 관련 명령어
파일시스템 관련 명령어파일시스템 관련 명령어
파일시스템 관련 명령어Chulgyu Shin
 

What's hot (20)

Linux programming study
Linux programming studyLinux programming study
Linux programming study
 
도커 없이 컨테이너 만들기 2편
도커 없이 컨테이너 만들기 2편도커 없이 컨테이너 만들기 2편
도커 없이 컨테이너 만들기 2편
 
리눅스 커널 기초 태스크관리
리눅스 커널 기초 태스크관리리눅스 커널 기초 태스크관리
리눅스 커널 기초 태스크관리
 
Linux tutorial
Linux tutorialLinux tutorial
Linux tutorial
 
도커없이 컨테이너 만들기 1편
도커없이 컨테이너 만들기 1편도커없이 컨테이너 만들기 1편
도커없이 컨테이너 만들기 1편
 
우분투에 시스템콜 추가하기
우분투에 시스템콜 추가하기우분투에 시스템콜 추가하기
우분투에 시스템콜 추가하기
 
도커 없이 컨테이너 만들기 3편
도커 없이 컨테이너 만들기 3편도커 없이 컨테이너 만들기 3편
도커 없이 컨테이너 만들기 3편
 
Ch7,8. Configmaps, Secrets and API
Ch7,8. Configmaps, Secrets and APICh7,8. Configmaps, Secrets and API
Ch7,8. Configmaps, Secrets and API
 
윈도우 커널 익스플로잇
윈도우 커널 익스플로잇윈도우 커널 익스플로잇
윈도우 커널 익스플로잇
 
Android ndk jni 설치및 연동
Android ndk jni 설치및 연동Android ndk jni 설치및 연동
Android ndk jni 설치및 연동
 
ITs 2주차_기본명령어(발표)
ITs 2주차_기본명령어(발표)ITs 2주차_기본명령어(발표)
ITs 2주차_기본명령어(발표)
 
1.intro to k8s
1.intro to k8s1.intro to k8s
1.intro to k8s
 
2006 03 15_pe & api hook
2006 03 15_pe & api hook2006 03 15_pe & api hook
2006 03 15_pe & api hook
 
Linux Kernel Boot Process , SOSCON 2015, By Mario Cho
Linux Kernel Boot Process , SOSCON 2015, By Mario ChoLinux Kernel Boot Process , SOSCON 2015, By Mario Cho
Linux Kernel Boot Process , SOSCON 2015, By Mario Cho
 
PE File Format and Packer - Inc0gnito 2016
PE File Format and Packer - Inc0gnito 2016PE File Format and Packer - Inc0gnito 2016
PE File Format and Packer - Inc0gnito 2016
 
KGC 2016 오픈소스 네트워크 엔진 Super socket 사용하기
KGC 2016 오픈소스 네트워크 엔진 Super socket 사용하기KGC 2016 오픈소스 네트워크 엔진 Super socket 사용하기
KGC 2016 오픈소스 네트워크 엔진 Super socket 사용하기
 
Windows via c/c++ ch03. 커널오브젝트
Windows via c/c++ ch03. 커널오브젝트Windows via c/c++ ch03. 커널오브젝트
Windows via c/c++ ch03. 커널오브젝트
 
Introduction to Linux #1
Introduction to Linux #1Introduction to Linux #1
Introduction to Linux #1
 
Nodejs_chapter3
Nodejs_chapter3Nodejs_chapter3
Nodejs_chapter3
 
파일시스템 관련 명령어
파일시스템 관련 명령어파일시스템 관련 명령어
파일시스템 관련 명령어
 

Similar to 10 동기및비동기장치io

Chapter 17
Chapter 17Chapter 17
Chapter 17nacheon
 
Windows via C/C++ Chapter 10
Windows via C/C++ Chapter 10Windows via C/C++ Chapter 10
Windows via C/C++ Chapter 10iluvs
 
Windosw via c 스터디17장
Windosw via c 스터디17장Windosw via c 스터디17장
Windosw via c 스터디17장HolyTak
 
한국에서 제일 쉬운 centos7 설치 튜토리얼
한국에서 제일 쉬운 centos7 설치 튜토리얼한국에서 제일 쉬운 centos7 설치 튜토리얼
한국에서 제일 쉬운 centos7 설치 튜토리얼ChoDae
 
고급시스템프로그래밍
고급시스템프로그래밍고급시스템프로그래밍
고급시스템프로그래밍kimkiweon
 
[NEXT] Android 개발 경험 프로젝트 4일차 (Networking)
[NEXT] Android 개발 경험 프로젝트 4일차 (Networking)[NEXT] Android 개발 경험 프로젝트 4일차 (Networking)
[NEXT] Android 개발 경험 프로젝트 4일차 (Networking)YoungSu Son
 
Red hat ansible engine 2.7 release(Korean version)
Red hat ansible engine 2.7 release(Korean version)Red hat ansible engine 2.7 release(Korean version)
Red hat ansible engine 2.7 release(Korean version)Leo Song
 
[231]나는서버를썰터이니너는개발만하여라 양지욱
[231]나는서버를썰터이니너는개발만하여라 양지욱[231]나는서버를썰터이니너는개발만하여라 양지욱
[231]나는서버를썰터이니너는개발만하여라 양지욱NAVER D2
 
Hadoop distributed file system rev3
Hadoop distributed file system rev3Hadoop distributed file system rev3
Hadoop distributed file system rev3Sung-jae Park
 
실용주의 프로그래머를 위한 개발환경 만들기
실용주의 프로그래머를 위한 개발환경 만들기실용주의 프로그래머를 위한 개발환경 만들기
실용주의 프로그래머를 위한 개발환경 만들기Mickey SJ Lee
 
포렌식 세미나.pptx
포렌식 세미나.pptx포렌식 세미나.pptx
포렌식 세미나.pptxdalonn
 
시스템 프로그램 설계1 최종발표
시스템 프로그램 설계1 최종발표시스템 프로그램 설계1 최종발표
시스템 프로그램 설계1 최종발표Jeongmin Cha
 
PCF Installation Guide
PCF Installation GuidePCF Installation Guide
PCF Installation Guideseungdon Choi
 
Oracle linux8 solaris_new_features-suk kim
Oracle linux8 solaris_new_features-suk kimOracle linux8 solaris_new_features-suk kim
Oracle linux8 solaris_new_features-suk kimsuk kim
 
2014_서버용 자료유출방지 솔루션_시온
2014_서버용 자료유출방지 솔루션_시온2014_서버용 자료유출방지 솔루션_시온
2014_서버용 자료유출방지 솔루션_시온시온시큐리티
 

Similar to 10 동기및비동기장치io (20)

Chapter 17
Chapter 17Chapter 17
Chapter 17
 
Device driver
Device driverDevice driver
Device driver
 
Windows via C/C++ Chapter 10
Windows via C/C++ Chapter 10Windows via C/C++ Chapter 10
Windows via C/C++ Chapter 10
 
Windosw via c 스터디17장
Windosw via c 스터디17장Windosw via c 스터디17장
Windosw via c 스터디17장
 
한국에서 제일 쉬운 centos7 설치 튜토리얼
한국에서 제일 쉬운 centos7 설치 튜토리얼한국에서 제일 쉬운 centos7 설치 튜토리얼
한국에서 제일 쉬운 centos7 설치 튜토리얼
 
Pe+file+format
Pe+file+formatPe+file+format
Pe+file+format
 
고급시스템프로그래밍
고급시스템프로그래밍고급시스템프로그래밍
고급시스템프로그래밍
 
[NEXT] Android 개발 경험 프로젝트 4일차 (Networking)
[NEXT] Android 개발 경험 프로젝트 4일차 (Networking)[NEXT] Android 개발 경험 프로젝트 4일차 (Networking)
[NEXT] Android 개발 경험 프로젝트 4일차 (Networking)
 
Red hat ansible engine 2.7 release(Korean version)
Red hat ansible engine 2.7 release(Korean version)Red hat ansible engine 2.7 release(Korean version)
Red hat ansible engine 2.7 release(Korean version)
 
[231]나는서버를썰터이니너는개발만하여라 양지욱
[231]나는서버를썰터이니너는개발만하여라 양지욱[231]나는서버를썰터이니너는개발만하여라 양지욱
[231]나는서버를썰터이니너는개발만하여라 양지욱
 
Hadoop distributed file system rev3
Hadoop distributed file system rev3Hadoop distributed file system rev3
Hadoop distributed file system rev3
 
실용주의 프로그래머를 위한 개발환경 만들기
실용주의 프로그래머를 위한 개발환경 만들기실용주의 프로그래머를 위한 개발환경 만들기
실용주의 프로그래머를 위한 개발환경 만들기
 
shell and process
shell and processshell and process
shell and process
 
포렌식 세미나.pptx
포렌식 세미나.pptx포렌식 세미나.pptx
포렌식 세미나.pptx
 
System+os study 3
System+os study 3System+os study 3
System+os study 3
 
시스템 프로그램 설계1 최종발표
시스템 프로그램 설계1 최종발표시스템 프로그램 설계1 최종발표
시스템 프로그램 설계1 최종발표
 
Oracle History #8
Oracle History #8Oracle History #8
Oracle History #8
 
PCF Installation Guide
PCF Installation GuidePCF Installation Guide
PCF Installation Guide
 
Oracle linux8 solaris_new_features-suk kim
Oracle linux8 solaris_new_features-suk kimOracle linux8 solaris_new_features-suk kim
Oracle linux8 solaris_new_features-suk kim
 
2014_서버용 자료유출방지 솔루션_시온
2014_서버용 자료유출방지 솔루션_시온2014_서버용 자료유출방지 솔루션_시온
2014_서버용 자료유출방지 솔루션_시온
 

10 동기및비동기장치io

  • 1. 10.동기 및 비동기 장치 I/O 제프리 리처의 Windows VIA C/C++ 2022.01.25 함송연 1. 장치 열기와 닫기 2. 파일 장치 이용 3. 동기 장치 I/O 수행 4. 비동기 장치 I/O의 기본 5. I/O 요청에 대한 완료 통지의 수신 1
  • 2. 동기 및 비동기 장치 I/O 스레드들 간의 상호 통신 - 스레드가 동기적인 장치 I/O를 요청하면 I/O 작업이 완료될 때까지 일시적으로 스레드 수행이 블로킹 - 스레드들이 계속해서 수행되려면 스레드들 간에 수행할 작업에 대한 상호 통신이 필요 - 마이크로소프트가 결론낸 스레드 간의 통신 매커니즘  I/O 컴플리션 포트 2
  • 3. 01 장치 열기와 닫기 장치 : 통신 가능한 어떤 것 3 장치 관련 함수 CreateFile, CreateMailSlot, CreateNamedPipe,CreatePipe, socket, accept, 또는 AcceptEx CreateConsoleScreenBuffer 또는 GetStdHandle  각각의 장치를 구분할 수 있는 고유의 핸들 값 반환 장치와 통신을 수행하는 함수에서 이 핸들 값을 인자로 사용
  • 4. 01 장치 열기와 닫기 장치 : 통신 가능한 어떤 것 4 예: 시리얼 포트의 전송 속도를 설정 - BOOL SetCommConfig ([in] HANDLE hCommDev, [in] LPCOMMCONFIG lpCC, [in] DWORD dwSize); 데이터를 읽는 동안 대기 만료 시간을 설정 - BOOL SetMailslotInfo ([in] HANDLE hMailslot, [in] DWORD ReadTimeout); 사용 후 핸들 닫기 - BOOL CloseHandle(HANDLE hObject); - int closesocket(SOCKET s) ; 장치의 타입을 확인하기 - GetFileType(HANDLE hDevice);
  • 5. 01.1 CreateFile에 대한 세부사항 검토 5 pszName - 특정 장치의 인스턴스 값 - 장치의 타입을 구분할 수 있는 값 dwDesiredAccess - 장치와 데이터를 어떻게 주고받을지를 결정 - 일부 장치는 접근 제어 플래그(access control flag) 필요 파일 생성, 기존 파일 열기 뿐만 아니라 파일이 아닌 다른 장치에 대해서도 열기 작업을 수행할 수 있다. • 파일명 길이에는 제한이 있음에 주의
  • 6. 01.1 CreateFile에 대한 세부사항 검토 6 dwShareMode - 장치의 공유 특성을 지정 - 장치에 대해 열기 작업을 이미 수행한 경우에(Close Handle을 호출하기 이전에) 동일 장치에 대해 추가적으로 CreateFile을 수행한 경우, 열기 작 업을 어떻게 수행할지를 제어 파일 생성, 기존 파일 열기 뿐만 아니라 파일이 아닌 다른 장치에 대해서도 열기 작업을 수행할 수 있다.
  • 7. 01.1 CreateFile에 대한 세부사항 검토 7 psa - SECURITY ATIRIBUTES 구조체 - 보안 정보를 설정, CreateFile이 반환하는 핸들을 상속 가능하도록 구성 할 것인지… - 이 구조체 내의 보안 디스크립터(security descriptor)는 NTFS와 같은 안 전한 파일시스템 상에 파일을 생성하는 경우에만 사용 - NULL 전달시 디폴트 값(기본 보안 특성, 상속 불가능한 핸들 반환) dwCreationDisposition - 파일 장치에 대해 사용시 가장 큰 의미를 가짐 파일 생성, 기존 파일 열기 뿐만 아니라 파일이 아닌 다른 장치에 대해서도 열기 작업을 수행할 수 있다.
  • 8. 01.1 CreateFile에 대한 세부사항 검토 8 dwFlagsAndAttributes - 데이터 송수신시 세부적인 통신 플래그를 설정 : 사용자가 어떤 방식으로 장치에 접근할 것인지 의도를 설정하여, 캐시 알고리즘 최적화 가능 - 혹은 파일 특성을 설정 파일 생성, 기존 파일 열기 뿐만 아니라 파일이 아닌 다른 장치에 대해서도 열기 작업을 수행할 수 있다.
  • 9. 01.1 CreateFile에 대한 세부사항 검토 9 <CreateFile 캐시 플래그> Flag 내용 FILE_FLAG_NO_BUFFERING 파일 접근시 어떠한 버퍼링도 수행하지 않음. 파일 시스템의 디바이스 드라이버는 함수를 통해 전달된 버퍼의 내용을 캐시 매니저가 관장하는 버퍼가 아닌, 파일에 직 접적으로 쓰게 됨. - 파일에 접근할 때에는 항상 디스크 볼륨의 섹터 크기의 배수 단위로 I/O를 수행할 위치를 정해야 한다 (GetDiskFreeSpace로 섹터 크기 get) - 한 번에 읽고 쓰는 바이트 수는 항상 섹터 크기의 배수로 지정 - 프로세스의 주소 공간 상의 버퍼의 시작 주소는 반드시 섹터의 크기로 나누어 떨어지는 위치로부터 시작 매우 큰 파일을 열 때 사용 - 파일이 커지면 캐시 매니저에게 더 큰 자료구조가 필요함. - 캐시 매니저가 공간 확보에 실패하면 파일 열기가 실패할 수 있기 때문에 이럴 땐 FILE_FLAG_NO_BUFFERING을 사 용하자. FILE_FLAG_SEQUENTIAL_SCAN, FILE_FLAG_RANDOM_ACCESS FILE_FLAG_SEQUENTIAL_SCAN : 순차적 접근 사용 - 파일 읽기를 시도하는 경우 실제로 필요한 크기보다 더 많은 데이터를 메모리로 읽어 들임  접근하는 횟수 줄이기 - 파일에 대한 접근 위치 이동시에 시간이 약간 더 걸림 - 접근하지 않은 데이터 캐싱에 메모리 공간을 일부 낭비 FILE_FLAG_RANDOM_ACCESS : 임의적 접근 사용 FILE_FLAG_WRITE_THROUGH 파일에 데이터를 쓸 때 데이터 손실의 가능성을 줄이기 위한 중간 캐싱 기능을 사용하지 않도록 한다. (변경사항을 디스크에 직접 쓴다) 읽기는 여전히 내부적인 캐시를 사용한다. 네트워크 파일 서버에 있는 파일을 열 때 사용시 윈도우는 네트워크 파일 서버의 디스크 드라이브 상에 데이터를 완전히 쓸 때까지 함수를 반환하지 않는다.
  • 10. 01.1 CreateFile에 대한 세부사항 검토 10 <기타 CreateFile 플래그> Flag 내용 FILE_FLAG_DELETE_ON_CLOSE 파일과 관련된 모든 핸들이 닫히면 파일이 삭제된다. 주로 FILE_ATTRIBUTE_TEMPORARY 플래그와 함께 사용 - 애플리케이션이 임시 파일을 생성, 쓰고, 읽고, 닫음 - 파일이 닫히면 시스템이 파일을 자동으로 삭제 (MS 오피스에서 많이 본듯…) FILE_FLAG_BACKUP_SEMANTICS 백업/복원 시 파일 보안 확인 기능(프로세스에게 적절한 권한이 있는지)을 수행하지 않는다 - 시스템은 호출자의 접근 토큰이 파일과 폴더에 대해 백업/복원을 수행할 수 있는 권한을 가지고 있는지만 확인 FILE_FLAG_POSIX_SEMANTICS 파일을 찾을 때도 대소문자를 구분 (원래는 파일 찾을 때 구분하지 않음, 파일 저장시에는 구분) - 파일을 생성할 때 이 플래그를 사용하면 다른 윈도우 애플리케이션에서 이 파일에 접근하지 못할 수 있음에 주의 FILE_FLAG_OPEN_REPARSE_POI NT (FILE_FLAG_IGNORE_REPARSE_ POINT가 더 낫지 않냐고 함) 시스템에게 파일의 리파스 Reparse 특성을 무시할 것을 요청 - 리파스 특성을 사용하면 파일시스템 필터가 열기, 읽기, 쓰기, 닫기 동작을 변경할 수 있음 FILE_FLAG_OPEN_NO_RECALL 오프라인 저장 장치(테이프 등…)로부터 온라인 저장 장치(하드 디스크 등…)로 파일을 복원하지 못하도록 한다. FILE_FLAG_OVERLAPPED 시스템에게 장치에 비동기적으로 접근하길 원한다고 알림 - 비동기 I/O 작업시, 작업이 완료되기 전에 함수가 반환됨. - 운영체제는 사용자 스레드를 대신하여 I/O 작업을 수행하고 요청한 작업이 완료되면 그 사실을 통보
  • 11. 01.1 CreateFile에 대한 세부사항 검토 11 <파일 특성 플래그> - 새로운 파일을 만들거나, hFileTemplate 매개변수에 NULL을 주는 경우에만 의미를 가진다. FILE_ATTRIBUTE_TEMPORARY, FILE_FLAG_DELETE_ON_CLOSE를 함께 쓰면 시스템 성능 을 상당히 개선! 임시 파일 : CreateFile은 좀 더 빠르게 파일의 내용에 접근할 수 있도록 디스크 대신 메모리에 파일의 내용을 유지(하기 위 해 노력)
  • 12. 01.1 CreateFile에 대한 세부사항 검토 12 hFileTemplate - 이미 열린 파일에 대한 핸들을 전달  dwFlagsAndAttributes 매개변수 무시, 해당 파일의 특성 값을 사용 - 전달되는 핸들은 반드시 GENERIC_READ 플래그를 포함해야 함 - 단 CreateFile을 통해 이미 존재하는 파일을 여는 경우 hFileTemplate 매 개변수 무시 - 미사용시 NULL 반환값 - 에러시 INVALID_HANDLE_VALUE 파일 생성, 기존 파일 열기 뿐만 아니라 파일이 아닌 다른 장치에 대해서도 열기 작업을 수행할 수 있다.
  • 13. 02 파일 장치 이용 13 01. 파일 크기 얻기 BOOL GetFileSizeEx([in] HANDLE hFile, [out] PLARGE_INTEGER lpFileSize); - 파일의 논리적 크기 반환 DWORD GetCompressedFileSizeA([in] LPCSTR lpFileName, [out, optional] LPDWORD lpFileSizeHigh); - 파일의 물리적 크기 반환(실제 디스크 점유) - 파일의 핸들이 아닌 경로명을 전달 - lpFileSizeHigh : DWORD의 high-order. 파일이 4 기가바이트를 넘지 않는다면 필요 없음 LARGE_INTEGER 구조체
  • 14. 02.2 파일 포인터 위치 지정 14 파일 커널 오브젝트 - CreateFile 호출시 내부적으로 파일 커널 오브젝트를 생성. 내부적으로 파일 포인터를 가지고 있음. 파일 포인터 - 64비트 오프셋 값으로 동기적인 I/O를 수행할 위치 정보를 가지고 있음 - 파일 열기를 수행하면 파일 포인터가 0으로 초기화 - CreateFile 호출한 직후 ReadFile을 호출하면 파일의 가장 앞쪽 내용(오프셋 0)부터 데이터를 읽게 됨  파일을 열어서 10 바이트씩 두 번 읽음  각각의 커널 오브젝트는 별도의 파일 포인터를 가지고 있 어서 같은 파일이라 하더라도 서로 독립적으로 수행됨
  • 15. 02.2 파일 포인터 위치 지정 15  DuplicateHandle은 같은 커널 오브젝트를 참조하기 때문에 동일한 파일 포인터를 사용하게 됨
  • 16. 02.2 파일 포인터 위치 지정 16 BOOL SetFilePointerEx ([in] HANDLE hFile, [in] LARGE_INTEGER liDistanceToMove, [out, optional] PLARGE_INTEGER lpNewFilePointer, [in] DWORD dwMoveMethod); - 파일의 임의 위치에 접근하고 싶을 때 사용 - liDistanceToMove : 파일 포인터를 얼마나 이동하고자 하는지(바이트 단위) - dwMoveMethod : DistanceToMove 매개변수에 대한 해석을 설정 - pliNewFilePointer : 갱신된 파일 포인터 값. 불필요시 NULL 주의 - 파일 포인터를 현재 파일 크기보다 더 크게 설정할 수 있음. 이 때 이동된 위치에 어떤 내용을 쓰거나 SetEndOfFile을 호출하면 파일 크기가 변경됨. - FILE_FLAG_NO_BUFFERING을 지정하여 파일을 열었다면 SetFilePointerEx를 호출하여 섹터 크기로 정렬된 위치로만 파일 포인터를 이동할 수 있다. - 윈도우는 GetFilePointerEx 함수를 제공하지 않는다. Get하는 방법은,
  • 17. 02.3 파일의 끝 설정 17 B00L SetEndOfFile(HANDLE hFile); - 파일 포인터가 가리키는 현재 위치를 파일의 끝으로 설정  파일 크기가 정확히 1024바이트가 됨
  • 18. 03 동기 장치 I/O 수행 18 장치 : 파일, 메일슬롯, 파이프, 소켓 등!! 이 때 FILE_FLAG_OVERLAPPED 플래그를 사용하지 않음(비동기니까) pvBuffer - 장치로부터 읽어온/장치에 쓸 데이터를 저장할 버퍼 포인터 nNumBytesToRead/nNumBytesToWrite - 장치로부터 몇 바이트를 쓰거나 읽을지 pdwNumBytes - 장치와 성공적으로 송수신한 데이터 크기(DWORD)를 받아올 변수 주소 pOverlapped - NULL
  • 19. 03.1 장치로 데이터 플러시 19 장치 : 파일, 메일슬롯, 파이프, 소켓 등!! BOOL FlushFileBuffers(HANDLE hFile); - hFile이 가리키는 장치와 연관되어 있는 캐시된 데이터를 강제적으로 쓰도록 한다. (캐시된 데이터를 장치로 플러시) - 이 작업을 수행하려면 반드시 GENERIC_WRITE 플래그를 포함하여 장치를 열여야 한다.
  • 20. 03.2 동기 I/O의 취소 20 애플리케이션 hang 증상 - 스레드가 동기 I/O의 완료를 기다리는 동안, 스레드의 메시지 큐에 삽입된 메시지가 정상적으로 수행되지 못함 - 스레드가 생성한 모든 윈도우가 같이 정지된다. BOOL CancelSynchronousIo(HANDLE hThread); hThread : 동기 I/O로 인해 정지되어 있는 스레드의 핸들 - 핸들 생성시 접근 권한이 THREAD_TERMINATE 로 되어 있어야 한다. 아닌 경우 실패. LastError : ERROR_ACCESS_DENIED - 성공적으로 취소한다면, 스레드는 수행 재개. 함수는 TRUE 반환. LastError : ERROR_OPERATION_ABORTED - 함수 수행 시점의 스레드 상태를 알 수 없음에 주의 - 해당 스레드가 실제로 동기 I/O를 수행중인지 알 수 없다 (I/O를 요청했지만 그보다 먼저 CancelSynchronousIo가 수행될 수 있음) - 스레드가 장치로부터 응답을 받기 위해 대기하고 있을 수도 있고, 혹은 장치는 이미 응답을 하였으나 호출한 힘수가 반환되기 직전의 상태일 수도 있 다. - 이럴 땐 FALSE 반환, LastError : ERROR_NOT_FOUND - 취소할 동기 I/O 작업이 진행 중에 있는지 확인 - 추가적인 스레드 동기화 방법이 필요하지만 실제로는 그다지 필요하지 않다. 대부분 애플리케이션이 정지되었음을 사용자가 알 때 수행되기 때문.
  • 21. 04 비동기 장치 I/O의 기본 21 CreateFile - dwFlagsAndAttributes 매개변수 : FILE_FLAG_OVERLAPPED - 해당 플래그를 사용하여 CreateFile ReadFile, WriteFile - hFile이 가리키는 장치가 FILE_FLAG_OVERLAPPED 플래그를 이용하여 열렸다면 비동기 I/O를 수행 - 이때 pdwNumBytes 매개변수는 NULL 전달 가능 (함수 반환 시점에 의미가 없기 때문)
  • 22. 04.1 OVERLAPPED 구조체 22 Overlapped (중첩) : 동일 스레드가 다른 작업을 수행하는 동안에 또 다시 새로운 I/O 작업을 시작함을 의미 ReadFile/WriteFile 호출하기 이전에 초기화 필요 Internal과 InternalHigh : 디바이스 드라이버에 의해 설정. I/O 작업이 완료되었는지 여부 를 확인하기 위해 사용
  • 23. 04.1 OVERLAPPED 구조체 23 Offset, OffsetHigh - 파일에 대한 I/O를 수행하는 경우 파일 내에서 I/O 작업을 시작할 위치를 64비트 오프셋 값 지 정 - 비동기 작업시 파일 커널 오브젝트 내의 파일 포인터를 사용할 수 없다. - 파일 작업이 아닌 다른 장치에서도 무시되지 않기 때문에 주의 (0이 아닐 경우 LastError : ERROR_INVALID_PARAMETER) hEvent - 416p “이벤트 커널 오브젝트의 시그널링”에서 상세 - 얼러터블 I/O 통지 방법을 사용하는 경우 사용자 임의로 이 멤버를 사용 (hEvent의 멤버들을 C++ 오브젝트들의 주소를 담기 위해 사용) Internal - 처리된 I/O의 에러 코드를 담기 위해 사용 - I/O 작업 수행중일 때의 코드 : STATUS_PENDING 비동기 I/O 요청 시도  디바이스 드라이버가 이 값으로 설정. 아직 작업이 완료되지 않았고, 어떠한 에러도 없다는 의미. - HasOverlappedIoCompleted(pOverlapped) 매크로로 I/O 작업 완료 여부를 확인할 때 사용됨 InternalHigh - 비동기 I/O 작업 완료되면 실제로 송수신된 바이트 수를 저장 OVERLAPPED 구조체에 추가적인 컨텍스트를 추가해서 서브클래스로 만들어 두면 편하게 쓸 수 있다는 이야기
  • 24. 04.2 비동기 I/O 사용 시 주의점 24 1. 디바이스 드라이버는 비동기 I/O 요청을 항상 선입선출 방식으로 처리하지 않는다. 2. 에러 확인을 수행하는 적당한 방법을 알고 있어야 한다. ReadFile/WriteFile의 반환값 - 동기적으로 수행되는 경우 0이 아닌 값을 반환 - 비동기적으로 수행되는 경우나 에러가 발생하면 FALSE를 반환 -  FALSE일 경우, GetLastError()로 의미 확인 GetLastError()로 확인할 수 있는 에러 코드 ERROR_IO_PENDING : 요청 수행중 ERROR_INVALID_USER_BUFFER, ERROR_NOT_ENOUGH_MEMORY : 논페이지 풀 nonpaged pool이 꽉 차게 된 경우 (각각의 디바이스 드라이버가 가지는 I/O 요청을 삽입하기 위한 고정 크기의 리스트) ERROR_NOT_ENOUGH_QUOTA : 프로세스가 잠글 수 있는 페이지의 크기를 초과하여 ReadFile,WriteFile 내부에서 사용자가 전달 한 버퍼를 잠글 수 없는 경우. 함수는 FALSE 반환, LastError : ERROR_NOT _ENOUGH_QUOTA - 몇몇 장치들은 사용자가 전달해 주는 버퍼가 잠긴 상태의 페이지 내에 존재하여, I/O 요청을 기 다리는 동안 RAM 외부로 스와핑되지 않을 것을 요구. 파일 I/O시 FILE_FLAG_NO_BUFFERING 플래그를 사용하면 잠긴 페이지 내의 저장 공간을 필요로 한다. - SetProcessWorkingSetSize를 호출하여 프로세스당 잠글 수 있는 페이지 크기를 변경할 수 있 다.
  • 25. 04.2 비동기 I/O 사용 시 주의점 25 3. 비동기 I/O 요청을 수행할 때 사용되는 데이터 버퍼와 OVERLAPPED 구조체는 요청이 완료될 때까지 옮겨지거나 삭제되지 않아야 한다. - 매 요청시마다 OVERLAPPED 구조체를 새로 생성하고 초기화하여 전달하자  잘못된 I/O 요청 코드 예시 함수가 즉시 반환되면서 함수 내부에서 만든 OVERLAPPED 구조체와 버퍼가 사라지지만, 디바이스 드라이버는 계속 이 두 개의 포인터를 사용하게 된다. I/O 작업이 완료되면 디바이스 드라이버는 스레드 스택 내의 메모리를 수정하게 될 것이며, 메모리 손상의 원인이 된다. 비동기 작업이기 때문에 발견하기 어렵다.
  • 26. 04.3 요청된 장치 I/O의 취소 26 I/O 작업을 요청하기 전에 앞서 요청한 I/O 작업을 취소하고 싶은 경우  다른 스레드가 삽입한 I/O 요청도 취소할 수 있다. OVERLAPPED가 NULL이라면 hFile이 가리키는 장치에 대한 모든 I/O 요청을 취소 한다.
  • 27. 05 I/O 요청에 대한 완료 통지의 수신 27
  • 28. 05.1 디바이스 커널 오브젝트의 시그널링 28 디바이스 커널 오브젝트의 시그널/논시그널 상태를 이용한다. 개념을 설명하기 위한 코드이며 실제로는 이런 식으로 코드를 짜지 않는다.
  • 29. 05.2 이벤트 커널 오브젝트의 시그널링 29 이전과 같은 방법으로는 다수의 요청에 대한 처리를 수행할 수 없다. 파일로부터 10 바이트를 읽고, 동시에 10 바이트를 쓰는 작업을 수행해 보자. WaitForSingleObject()를 사용하여 읽기/쓰기가 어떤 상태로 있는지 확인하기 어렵 다.
  • 30. 05.2 이벤트 커널 오브젝트의 시그널링 30 다소 억지스러우며 실사용을 위한 코드는 아니다. hEvent - CreateEvent를 호출하여 얻은 이벤트 커널 오브젝트에 대한 핸 들 - 비동기 I/O 요청이 완료되면 SetEvent 함수를 호출해 준다 (hEvent != NULL인 경우)
  • 31. 05.3 얼러터블 I/O 31 조금 복잡한 과거를 가진듯… (MS가 최상의 메커니즘인 것처럼 내놓았지만 개발자들은 공감하지 못했던 듯 하다) 비동기 프로시저 콜 - 스레드 생성시, 시스템은 각 스레드별로 비동기 프로시저 콜(APC) 큐라는 큐를 하나씩 생성한다. - 비동기 I/O 요청을 전달하는 함수 호출시, (ReadFileEx, WriteFileEx) 디바이스 드라이버에게 I/O 작업 완료 통지를 스레드의 APC 큐에 삽입해 줄 것을 요청할 수 있다. 함수는 호출 즉시 반환됨. ReadFile, WriteFile 차이점 - I/O 작업이 수행된 바이트 수를 돌려받을 DWORD 포인터 전달하지 않음. 이 정보는 콜백 함수를 통해 획득. - 컴플리션 루틴이라고 불리는 콜백함수의 주소를 필요로 한다. 컴플리션 루틴은 반드시 다음과 같은 형태로 구현
  • 32. 05.3 얼러터블 I/O 32 스레드 APC 큐 W1 R2 R1 - 순서는 보장되지 않음 - Entry 각각은 컴플리션 루틴 주소, 컴플리션 루틴 전달값을 가짐 스레드 : *Ex() 호출하여 비동기 I/O 수행 요청, 컴플리션 루틴 주소 값 전달  디바이스 드라이버 : I/O 요청을 끝내고, 스레드의 APC 큐에 완료 통지 항목을 추가. 이 항목에 컴플리션 루틴의 주소와 I/O 요청시 사용된 OVERLAPPED 구조체 주소가 포함.  스레드 : 얼러터블 상태가 되면 시스템은 APC 큐의 내용을 확인하여 큐에 삽입된 모든 항목에 대해 컴플리션 루틴을 호출(I/O 에러코드, 송수신된 바이트 수, o 구조체 주소를 전달) 컴플리션 루틴 호출 - 스레드가 자신을 얼러터블 상태로 변경(어떠한 함수 호출)하여 인터럽트 가능한 상태가 되었음을 알림 - APC 큐에 항목이 있다면 스레드를 대기 상태로 전환하지 않고 하나씩 빼내어 지정된 콜백을 호출 - APC 큐가 완전히 비워지면, 커널 오브젝트가 시그널 상태가 될 때까지 기다리지 않고 얼러터블 상태로 변경하기 위해 호출하였던 함수(다음 페이지)가 반환됨 - APC 큐에 어떠한 항목도 없을 때 이 함수들을 호출하게 되면 스레드가 정지된다.
  • 33. 05.3 얼러터블 I/O 33 스레드를 얼러터블 상태로 변경할 수 있는 함수 마지막 인자 : 스레드를 얼러터블 상태로 변경할 것인지 여부를 나타내는 BOOL 내부적으로는 Ex가 없는 함수를 호출하면 내부적으로 bAlertable을 FALSE로 하여 *Ex를 호출한다. 반환값 : 이 함수가 반환된 이유 WAIT_IO_COMPLETION : (혹은 LastError가 WAIT_IO_COMPLETION) 적어도 하나 이상 APC 큐에 항목이 존재하였고 처리를 수행 완료 그 외 : 대기 시간 만료, 커널 오브젝트(혹은 단순 오브젝트)가 시그널 되었거나 뮤텍스가 버려졌음  MWMO_ALERTABLE 플래그로 얼러터블 컴플리션 루틴 호출 (앞장과 동일한 내용) - 스레드가 자신을 얼러터블 상태로 변경하여 인터럽트 가능한 상태가 되었음을 알림 - APC 큐에 항목이 있다면 스레드를 대기 상태로 전환하지 않고 하나씩 빼내어 지정된 콜백을 호출 - APC 큐가 완전히 비워지면 얼러터블 상태로 변경하기 위해 호출하였던 함수(다음 페이지)가 반환됨 - APC 큐에 어떠한 항목도 없을 때 이 함수들을 호출하게 되면 스레드가 정지된다.
  • 34. 05.3 얼러터블 I/O 34 사용하기 어려움 - 콜백 함수 : 코드를 이해하기 어렵게 함. 콜백 함수에서는 충분한 컨텍스트 정보를 유지하기 어렵기 때문에 전역변수가 많아질 수 있음. - 쓰레딩 문제 : 가장 큰 문제점은 I/O 작업을 요청한 스레드가 반드시 완료 통지도 함께 처리해야 함. 여러 개의 I/O 요청  I/O 요청별로 발생하는 완료 통지를 자신이 모두 처리 (다른 스레드들이 놀고 있다고 하더라도…) 확장성 있는 애플리케이션을 만들기 어려워 짐. APC 큐에 항목을 추가할 수 있는 함수 pfnAPC : 콜백 함수 콜백 함수의 원형 VOID WINAPI APCFunc(ULONG_PTR dwParam); dwData : 콜백 함수로 전달할 값 반환값 : 성공 여부 BOOL 값(타입은 DWORD이지만…) 장점 : 다른 프로세스에 있는 스레드와도 효과적인 통신이 가능. 스레드를 대기 상태에서 강제로 빠져나오게 할 때에도 유용. 단점 : 단일의 값만 전달이 가능.
  • 35. 05.3 얼러터블 I/O 35 예시  APC 큐의 모든 항목이 삭제되었으므로, WaitForSingleObjectEx 함수는 WAIT_IO_COMPLETION 값을 반환
  • 36. 05.4 I/O 컴플리션 포트 36 서비스 애플리케이션의 아키텍처 모델 1) 시리얼 모델 serial model : 하나의 스레드가 사용자 요청을 대기. 사용자 요청이 들어오면 대기하던 스레드가 깨어나 클라이언트의 요청을 처 리. 2) 컨커런트 모델 concurrent model : 하나의 스레드는 사용자의 요청을 대기, 사용자의 요청을 처리하기 위해 새로운 스레드를 생성. 새로운 스레 드가 요청을 처리하는 동안 원래의 스레드는 다시 다른 사용자의 요청을 기다림. 사용자 요청을 처리하던 스레드는 작업이 완료되었을 때 종료. 멀티 프로세서의 장점을 십분 활용. 컨커런트 모델의 성능 저하 - 많은 수의 클라이언트 요청이 동시에 들어오면 시스템이 수많은 스레드를 만들고, 컨텍스트 스위칭에 너무 많은 시간을 허비. 각 스레드는 작업 에 필요한 시간을 충분히 할당받지도 못하게 됨. - 이를 해결하기 위해 나온것이 IOCP
  • 37. I/O 컴플리션 포트 생성 37 함수의 기능 1) I/O CP 생성 2) 장치와 I/O 컴플리션 포트를 연계  저자는 두가지 기능을 별도의 함수로 만들어서 사용 I/O CP 생성 - 동일 시간에 수행할 수 있는 스레드의 최대 개수를 설정 - 0 입력시 머신에 설치된 CPU 개수를 사용 ! 커널 오브젝트 생성 함수 중 유일하게 SECURITY_ATTRIBUTE 구조체 필요가 없음 (단일 프로세스 내에서만 수행될 수 있도록) I/O CP 와 장치를 연계 - Next page…
  • 38. I/O 컴플리션 포트 생성 38 I/O CP 생성시 만들어지는 5개의 구조체
  • 39. I/O CP – (1) 장치 리스트 39 AssociateDeviceWithCompletionPort - 내부적으로 관리되고 있는 장치 리스트에 새로운 항목을 추가 - I/O CP 핸들 : 앞서 생성한 IOCP 핸들 - 장치 핸들 : 파일, 소켓, 메일슬롯, 파이프 등… - 컴플리션 키 : 이 값의 의미는 사용자가 임의로 전달할 수 있다. 운영체제는 이를 단순히 전달만 할 뿐이다.
  • 40. I/O CP – (2) 컴플리션 큐 40 시스템 : 장치에 대한 비동기 I/O 요청 완료시, 장치와 연계된 I/O 컴플리션 포트가 있는지 확인 있다면 I/O 컴플리션 큐에 I/O 요청의 완료 통지를 나타내는 새로운 항목을 삽입 각 항목은 송수신된 바이트 수, I/O CP 연계시 지정한 컴플리션 키 값, 비동기 I/O 작업을 요청할 때 사용하였던 OVERLAPPED 구조체 포인터, 에러 코 드를 가짐
  • 41. I/O CP를 이용한 아키텍처 설계 41 생성 팁 스레드 풀의 스레드는 CPU 개수 x 2가 가장 일반적 풀 내의 모든 스레드들은 동일한 스레드 함수를 수행하는 것이 좋다. 스레드 함수 : 초기화  루프 진입  애플리케이션 종료시 루프 탈출 루프 내에서 스레드는 대기 상태를 유지 : 비동기 장치 I/O 작업 완료시 I/O 컴플리션 포트를 통해 완료 통지가 될 때 이 를 곧바로 처리하도록. GetQueuedCompletionStatus(…) 호출 이 함수는 호출 스레드를 I/O CP 내의 컴플리션 큐에 새로운 항목이 삽입될 때까 지 대기 상태를 유지. 타임아웃 값도 지정이 가능. (대다수 앱은 하나의 I/O port만을 사 용)
  • 42. I/O CP – (3) 대기 스레드 큐 42 각 스레드에서 GetQueuedCompletionStatus(…) 호출  스레드 ID가 대기 스레드 큐에 삽입. I/O 커널 오브젝트는 어떤 스레드들이 I/O 요청에 대한 완료 통지를 처 리할 것인지 알 수 있음. I/O 컴플리션 큐에 항목이 추가됨  I/O 컴플리션 포트는 대기 스레드 큐에 있는 스레드 중 하나를 깨움 이 스레드는 컴플리션 큐에 삽입된 항목을 가져오며, GetQueuedCompletionStatus 함수의 매개변수를 통해 전달된다.
  • 43. I/O CP – (3) 대기 스레드 큐 43 GetQueuedCompletionStatus(…) 함수가 왜 반환되었는지 파악하자 I/O 컴플리션 큐 : 선입선출 GetQueuedCompletionStatus 함수 호출 스레드 : 후입선출 방식으로 깨어남 하나의 I/O 완료 통지가 컴플리션 큐에 삽입  가장 마지막으로 GetQueuedCompletionStatus를 호출한 스레드가 컴플리션 큐의 항목을 처리하기 위해 깨어남  완료 통지에 대한 적절한 처리를 마친 후 다시 GetQueuedCompletionStatus() 호출  또 다른 완료 통지가 삽입되면 동일 스레드가 다시 깨어나서 완료 통지 항목을 처리 목적 - 가능한 앞서 작업을 유지했던 스레드를 다시 깨워서 작업 - 스케줄되지 않은 스레드들이 사용하는 메모리를 디스크로 내보낼 수 있고(swap out), 프로세서 캐시를 비울 수 있다.
  • 44. I/O CP – (3) 대기 스레드 큐 44 GetQueuedCompletionStatusEx(…) - 여러 개의 완료 통지를 한 번에 가져올 수 있다. pCompletionPortEntries : I/O 컴플리션 큐의 엔트리 배열을 받을 곳 ulCount : 몇 개의 항목을 복사해 올 것인지 pulNumEntriesRemoved : I/O 컴플리션 큐로부터 몇 개의 항목을 실제로 가지고 왔는지 bAlertable : FALSE라면 지정된 시간만큼 I/O 컴플리션 큐로 완료 통지가 삽입될 때까지 대기 TRUE라면 I/O 컴플리션 큐에 어떠한 완료 통지도 존재하지 않는 경우 스레드를 얼러터블 상태로 전 환 OVERLAPPED_ENTRY 구조체 Internal 필드는 미공개(사용하지 말것) I/O CP로 비동기 작업을 요청했을 때,  동기적인 I/O 작업이 이루어지는 경우 …
  • 45. I/O CP – 스레드 풀 관리 방법 45 I/O CP의 유용성 - 완료 통지 삽입  대기중인 스레드를 깨운다. - 이 때 가동 가능한 스레드만큼 깨어있다면, 완료 통지 항목이 컴플리션 큐에 삽입되더라도 대 기 스레드를 깨우지 않는다. 이 때, 가동 가능한 스레드 수보다 많은 스레드를 스레드 풀로 관리하는 이유 I/O CP의 스레드 풀 관리 - 특정 스레드의 수행을 재개  릴리즈 스레드 리스트에 ID 기록 - 특정 스레드가 대기 상태로 진입  릴리즈 스레드에서 ID를 빼서 일시 정지 스레드 리스트로 이동 - 대기 상태에 있는 스레드 중 하나를 릴리즈 스레드로 옮겨온다. - 이 때 대기 상태에 있던 스레드가 다시 수행을 재개하는 경우, 일시 정지 스레드 리스트에서 릴리즈로 항목을 옮겨온다. - 이 때 릴리즈 리스트는 동시 수행 가능한 스레드 개수를 초과하는 개수의 항목을 가지게 된다. - 이 후, 동시 수행중인 스레드 개수가 CPU 개수 이하로 떨어질 때까지 새로운 스레드를 깨우 지 않는다. - 이러한 상황을 가능한 짧게 가져가기 위해 스레드가 GetQueuedCompletionStatus 를 재호출 하는 즉시 스레드를 대기 상태로 전환한다.
  • 46. I/O CP – 스레드 풀 관리 방법 46 스레드 풀에 몇 개의 스레드를 유지? 고려할 것 1) 애플리케이션 초기화되는 시점엔 가능한 적은 수의 스레드만 생성 2) 가능한 한 스레드 최대 개수를 제한하고 싶다. 스레드 풀 관리 수도 코드 - 초기화 시에는 G_nThreadsMin 만큼 스레드 생성 - 모든 스레드들이 동일한 스레드 풀 함수를 사용 - 스레드 풀에는 하나 이상의 스레드가 존재하도록 해야 함 - GetCPUUsage는 직접 구현해야 함
  • 47. I/O 완료 통지 흉내 내기 47 PostQueuedCompletionStatus - 완료 통지를 I/O 컴플리션 큐에 삽입 - 이때 GetQueudCompletionStatus로 값을 가져오면 I/O 요청이 성공적으로 완료된 것처럼 TRUE 반환 - 스레드 풀에 존재하는 스레드들과 통신 수행시 유용 - 예: 스레드들이 I/O 컴플리션 포트 대기중인 경우에 애플리케이션을 종료하고 싶은 경우, PostQueuedCompletionStatus()를 스레드 풀의 스레드 개수만큼 호출해주면 각 스레드들이 수행을 재개하게 되고 종료 절차를 진행 - 이 때, 어떠한 스레드가 종료 도중에 GetQueuedCompletionStatus를 호출하게 되면, 해당 스레드가 PostQueudComp etionStatu 를 두 번 호출할 수도 있다. 부가적인 스레드 동기화 방법이 필요하다.  I/O CP 핸들을 CloseHandle 해버리면 일어나는 일
  • 48. FileCopy 예제 애플리케이션 48 FileCopy.cpp 링크 IoCompletionPort.h 링크 I/O 컴플리션 포트를 사용하여 비동기 처리