IOCP
1 | 커널 객체로써, 입출력 완료 포트(I/O Completion Port)이다. |
참고로 스레드는 디폴트로 1M의 스레드 스택을 가진다. 또한 스레드 문맥을 비록하여 자체 정보를 담기 위해 메모리를 소비한다.
IOCP는 가장 큰 특징은 요구사항을 처리해주는 스레드의 수를 제한할수 있다.
IOCP는 스레드를 적극적으로 활용하기 위해 만들어졌다.
IOCP는 기본적으로 스레드 풀의 형성이고, 따라서 최소 몇 개의 스레드를 전제하고 있다.
동작원리
IOCP 생성
1 | CreateIoComepletionPort( |
IO장치와 IOCP 연결
1 | HANDLE hPort = CreateIoCompletionPort(INVALID_HANDLE_VALUE, NULL, 0, 0); // IOCP Handle |
두번째 CreateIoCompletionPort를 호출하면서 IOCP 장치리스트에 새로운 레코드를 추가한다.
삭제할 시점은 해당 장치의 핸들이 닫혔을때 이다.(위는 소켓을 썻으니 closesocket을 호출시켜야 된다.)
IO Completion Queue
1 | 이 큐는 IOCP와 연결한 Device의 IO 작업이 끝났음을 알려주는 큐이다. |
Page-Lock
1 | WSASend, WSARecv 의 경우 상황에 따라 제공된 버퍼가 페이징 되지 않도록 Lock을 걸게 된다. |
Non-Paged Pool
1 | Page-Locking이 일반 메모리를 페이징 안되도록 막는 개념이라면, Non-Paged Pool은 애초부터 물리 메모리에 상주하여 페이징이 안되도록 만들어놓은 영역이다. |
Non-Paged Pool의 사용되는 경우
- 드라이버와 같은 커널모드 컴포넌트에서 사용. WinSock, TCPIP.sys와 같은 프로토콜 드라이버도 이에 속함.
- 소켓을 생성할때마다 소켓의 상태 정보를 저장하기 위한 용도로도 작은 양의 Non-Paged Pool 소비한다.
- 소켓이 특정 주소로 바인딩되면, TCP/IP 스택은 로컬 주소 정보를 저장하기 위한 용도로도 Non-Paged Pool을 할당한다.
- Overlapped I/O 연산시 IRP(I/O Request Packet)을 발생시키면서 약 500 바이트의 Non-Paged Pool 할당한다.
Zero Byte Recv
1 | Page Locking 최소화하기 위해 하는 방법. |
SO_RCVBUF, SO_SNDBUF
1 | 송수신 버퍼 크기를 0으로 지정한다면, 커널 레벨에서의 송수신 버퍼를 이용하지 않고, App으로 바로 복사가 일어나므로 복사 횟수는 한번 줄어들고 속도도 굉장히 빨라진다. 물론 버퍼로의 직접 복사 때문에 Page-Locking은 일어난다. (이 방법의 문제는 Recv에서는 문제가 있다. 못받는 틈이 존재하게 됨.) |
Thread
1 | 멀티스레드 프로그래밍이란 간단히 말해서 동일한 애플리케이션 안에서 두 갱 ㅣ상의 기능이 병렬적으로 수행될수 있도록 소프트웨어를 작성한다. |
멀티스레드 프로그램을 사용하는 이유
1 | 병렬화의 증가, 빠른 처리, 향상된 안정성, 사용자에 대한 향상된 응답성, CPU 사용률의 증가 |
스레드를 사용하면 안되는 경우
1 | DeadLock, Stravation(기아현상)을 발생시킬수 있는 요인이 있다. |
확실한 이유를 가지고 있지 않은 경우에는 스레드를 사용하면 안된다.( 두 개의 스레드가 명확하게 독립적이지 않은 경우에는 하나의 작업을 둘로 나누면 안된다.)
스레드에 의한 작업의 빈도를 고려할 때, 운영체제가 스레드 스케쥴링을 하고 스레드를 다루는 데 발생하는 부하(Overhead)가 실제 스레드에 의해 수행된느 작업량보다 클 경우에는 스레드를 사용하면 안된다.
스케쥴러
1 | Windows는 모든 스레드를 Round Robin 형태로 스케쥴링한다. |
문맥교환
스케쥴러가 같은 프로세스 안에서 동작하는 두 개의 스레드의 문맥 교환을 하는 경우.
스케쥴러가 다른 프로세스에서 동작하는 두 개의 스레드의 문맥 교환을 하는 경우. 같은 프로세스 안에서 문맥교환 하는것보다 큰 비용이 소모된다.
스레드 상태
1 | 시스템의 모든 스레드는 기본적으로 세 가지 상태 중 한가지에 속한다. |
Running
1 | 수행 중인 스레드는 컴퓨터의 CPU에서 현재 스레드의 코드가 수행되고 있는 스레드를 의미한다. |
Ready
1 | 현재 수행 중인 스레드는 아니지만 운영체제가 CPU에게 시간을 할당하면 바로 수행 할수 있는 스레드를 준비상태에 있다고 한다. |
Block / Suspended
1 | 세마포어 같은 커널 객체가 Signaled 되거나 I/O 동작이 끝나기를 대기하는 스레드를 블록된 상태라고 한다.Sleep이나 SuspendThread 같은 시스템 콜을 사용하게 되면 OS에 의해 잠깐동안 보류될수 있다. |
최소 단위 오퍼레이션 (Atomic Operation)
- 기본적으로는 에섬코드 1줄이라고 봐도 무방하다. (완벽하진 않음.)
- 스레드 디버깅 = 어셈블리 코드를 보고 Sync가 깨질만한 요소를 찾아야 된다.
- 최대한 락을 안걸고 쓰기 위해서는 디스어셈블리를 봐야됨.
상호 배제
1 | 최소 수행 단위를 넘어서는 길이의 동작을 인터럽트 되지 않고 수행해야 할 필요가 발생한다. |
교착 상태
1 | DeadLock 상태. 서로 자기 자원을 가지고 있고 상대방 스레드에 있는 자원을 서로 요구하면 무한적 락이 걸린다. |
다수 자원에 대한 소유와 대기(Hold And Wait For Multiple Resource)
- 적어도 하나의 스레드는 자원에 대한 소유권을 갖고 있어야 하고, 다른 스레드에 의해 소유된 공유 자원을 획득하려고 해야 함.
상호 배체(Mutual Exclusion)
- 자원의 소유권은 반드시 한번에 하나의 스레드에게 만 허용되어야 함.
무선점(No Preemption)
- 운영체제가 다른 스레드에게 공유 자원에 대한 소유권을 주기 위해서 이미 공유 자원을 소유하고 있는 스레드의 소유권을 뺏을수 없다.
순환 대기(Circular Wait)
- 둘 이상의 스레드가 순환 대기를 발생하는 형태로 자원에 대해 대기하고 있어야 한다.
1 | 위의 4가지 조건이 동시에 모두 발생하지 않는다면, 교착상태는 발생하지 않는다. |
스레드 역학
1 | 생성 - CreateThread(스레드 핸들 리턴) |
1 | CreateThread(NULL, 0, 스레드 함수 주소, 함수 파라메터, 플래그, 스레드 ID(Out) ) |
스레드 동기화 방법
Interlocked 계열
1 | 보통 Lock이라고 하지 않음. 특정 변수에 대한 안전한 동기화 |
Critical Section
1 | 상호 배제를 구현하기 위해서 임계영역을 사용한다. 임계영역은 한번에 하나의 스레드만이 들어갈수 있다. |
Spin Lock
1 | 일반적으로 Lock 거는 곳에서 CPU 자원을 소비하면서 무한 루프를 돌려 락을 요청한다. |
WaitForSingleObject, WaitForMultipleObject
1 | 커널 동기화 객체들은 이 함수를 통해서 락을 건다. 모든 커널 객체는 Signaled, NonSignaled 상태로 나뉘어진다. |
1 | DWORD WaitForSingleObject(HANDLE, 대기시간)// => 우리는 이벤트로 많이 쓸것. 리턴이 WAIT_OBJECT_0 이면 정상 |
Mutex
1 | 가장 일반적으로 사용되는 동기화 커널 객체 |
동작원리
생성
1
2
3
4HANDLE CreateMutex(LPSECURITY_ATTRIBUTES, BOOL, LPCSTR); // 보안속성, 해당 함수 호출 스레드도 포함여부, 뮤텍스 객체 이름을 나타내는 문자열
// 성공 : 뮤텍스 핸들
// 실패 : NULL이미 생성된 뮤텍스의 핸들 확보 또는 뮤텍스가 존재하지 않은 경우 뮤텍스를 생성하지 않기를 원할때(검색)
1
2
3
4HANDLE OpenMutex(DWORD dwDesiredAccess, BOOL InheritHandle, LPCTSTR lpName);
// lpName이름의 뮤텍스를 가지려는 스레드의 접근 형태를 나타내는 비트 플래그
// 이 함수를 호출하는 프로세스가 CreateProcess 함수를 사용한 경우 핸들이 새로 생성된 프로세스에 상속 가능한지 나타냄.
// 열려고 하는 뮤텍스의 이름을 나타냄뮤텍스 해제
1
BOOL ReleaseMutex(HANDLE hMutex);
Event
스레드를 외부에서 제어(자동모드와 수동모드가 있다.)
Signaled 신호를 없앨때 이를 자동으로 할지 수동으로 할지 정하는것.
자동일 경우 작업을 끝낸뒤에 없애버린다. 또한 이경우 SetEvent, ResetEvent를 쓰지 않는게 좋다.
수동일 경우 SetEvent, ResetEvent를 무조껀 써야 한다.
-> 우리는 대부분 메뉴얼모드를 종료용으로 쓸것이다.
동작원리
생성
1
2
3
4HANDLE CreateEvent(보안기술, 메뉴얼리셋, 초기상태, 이름);
// 2번재 인자 : TRUE 수동리셋 이벤트, FALSE: 자동리셋 이벤트
// 3번째 인자 : 이벤트 객체의 초기 상태를 지정하는 플래그
// 4번째 인자 : 이벤트 객체이름검색
1
HANDLE OpenEvent(DWORD dwDesiredAccess, BOOL InHeritHandle, LPCSTR lpName);
이벤트 객체를 Signaled화
1
BOOL SetEvent(HANDLE hEvent);
이벤트 객체를 NonSingaled
1
BOOL ResetEvent(HANDLE hEvent);
이벤트 객체가 대기중이라면 시그널상태로 바꿔준다.\
1
BOOL PulseEvent(HANDLE hEvent);