2. 파이버.
• UNIX 서버 애플리케이션들을 윈도우로 쉽게 포팅하기 위해 추가됨.
• UNIX 서버 애플리케이션은 단일 스레드 기반으로 수행되지만 자체 라이브러리를
이용하여 여러 개의 스택을 생성하고, 일부 cpu레지스터값을 저장하며, 클라이언트
요청에 응답하기 위해 정보들을 스위칭하는 방식으로 스레드와 유사한 기능을 제공한다.
• 기존의 UNIX 소프트웨어를 개발한 회사가 좀 더 빠르게 윈도우용으로 자사의
소프트웨어를 포팅할 수 있도록 마이크로소프트는 윈도우에 파이버를 추가하였다.
3. 파이버 사용하기.
• 윈도우의 스레드는 커널 내에 구현되어 있다.
• 파이버는 유저 모드에서 구현된 코드이다.
• 파이버 간의 스케줄링 알고리즘을 사용자가 정의해야 하며 때문에 파이버는 커널이
관여할 때까지 비선점 방식으로 파이버 간의 스케줄링을 수행한다.
• 단일의 스레드가 하나 혹은 여러 개의 파이버를 가질 수 있으나 스케줄링된 스레드는
특정 시점에 하나의 파이버만을 수행할 수 있다.
4. 파이버 사용하기.
• 파이버를 이용하기 위해 가장 먼저 해야 하는 작업은 기존의 스레드를 파이버로 변경하는
것이다. 이러한 작업을 수행하기 위해서는 ConvertThreadToFiber 함수를 호출하면 된다.
• 이 함수는 파이버가 수행되기 위해 반드시 필요한 컨텍스트 정보를 저장할 메모리를
할당한다
이 함수는 파이버가 수행되기 위해 반드시 필요한 컨텍스트 정보를 저장할 메모리를 할당한다.
이 컨텍스트 정보에는 다음과 같은 내용이 저장된다.
• ConvertThereadToFiber를 호출할 때 전달하는 매개변수 값.
• 구조적 예외 처리 체인의 가장 앞쪽 정보.
• 파이버가 사용할 스택의 최상위와 최하위 주소 정보.
(스레드를 파이버로 변경시에 기존의 스레드 스택과 동일한 스택이 파이버 스택으로 적용 됨.)
• 스택 포인터, 인스트럭션 포인터와 같은 다양한 CPU 레지스터 값
https://docs.microsoft.com/ko-kr/windows/win32/procthread/using-fibers
5. 파이버 사용하기.
• 기본적으로 x86 시스템에서 CPU 부동소수점 상태 정보는 파이버 단위로 구성되는
컨텍스트 정보 내에 포함되지 않는다.
• 파이버 내에서 부동소수점 연산을 수행하면 관련 레지스터의 값이 손상된다.
• 이러한 기본 동작을 변경하기 위해서는 ConvertThreadToFiberEx 함수를 호출하여
매개변수로 FIBER_FLAG_FLOAT_SWITCH값을 전달해야 한다.
6. 파이버 사용하기.
• 반환된 주소는 다른 용도로 사용되기 때문에 직접적으로 컨텍스트의 내용을 읽거나
변경해서는 안된다.
• 만약 컨텍스트의 정보를 읽거나 변경해야 한다면 파이버 관련 함수들을 이용해야 한다.
• 파이버가 반환되거나 ExitThread 함수를 호출하게 되면 파이버와 스레드는 동시에
종료된다.
8. 파이버 사용하기.
• 만일 매우 많은 수의 파이버를 사용해야 하는 경우라면 각 파이버 별로 스택을
유지하기위해 많은 메모리를 낭비하고 싶지 않은 경우에는 CreateFiber대신 아래의
CreateFiberEx 함수를 사용한다.
lpStartAddress 매개변수로는 파이버 함수를 가리키는 주소를 전달하면 되는데,
반드시 다음과 같은 원형을 가져야한다.
9. 파이버 사용하기.
• CreateFiber 함수는 ConvertThreadToFiber 함수와 마찬가지로 파이버의 수행 컨텍스트를
저장할 메모리의 주소를 반환한다.
• 하지만 ConvertThreadToFiber와는 다르게, 새롭게 생성된 파이버는 이미 다른파이버가
수행중이기 때문에 생성과 동시에 수행되지는 못한다.
• 특정 시간에 단일 스레드 상에서 수행될 수 있는 파이버는 하나뿐이기에 다른 파이버를
수행하기 위해서는 현재 수행중인 파이버에서 SwitchToFiber를 호출해 주어야 한다.
10. 파이버 사용하기.
• SwitchToFiber의 동작
1. 현재의 CPU 레지스터 중 인스트럭션 포인터 레지스터와 스택 포인터 레지스터를 포함한 몇몇
레지스터의 값을 자신의 파이버 수행 컨텍스트에 저장한다.
2. 수행을 재개할 파이버의 수행 컨텍스트 내에 저장되어 있는 레지스터 값을 CPU의 레지스터로
읽어 들인다. 이러한 레지스터 값들 중에는 스택 포인터도 포함되어 있으며, 이 값은 스레드의
수행을 재개하기 위해 사용된다.
3. 수행을 재개할 파이버의 수행 컨텍스트를 현재 스레드와 연계시킨다. 이를 통해 스레드가 이
파이버를 수행할 수 있게 된다.
4. 수행을 재개할 파이버의 컨텍스트 내에 저장된 인스트럭션 포인터 레지스터 값을 스레드의
인스트럭션 포인터 레지스터로 읽어 들인다. 이제 스레드(파이버)는 이 파이버가 이전에
마지막으로 수행하였던 위치부터 수행을 재개하게 된다.
11. 파이버 사용하기.
• 파이버를 파괴하기 위해서는 DeleteFiber를 호출한다.
• 주로 하나의 파이버가 다른 파이버를 삭제하고자 할 때 사용된다.
12. 파이버 사용하기.
편의를 위해 제공되는 파이버 함수들.
• 현재 수행중인 파이버의 수행 컨텍스트 주소를 알고 싶다면 GetCurrentFiber 함수를 사용.
• 혹은 GetFiberData 함수를 사용.
• 두 함수는 매우 빠르게 수행되며, 컴파일러가 인라인 함수로 생성하기 때문에 마치
내장함수인 것처럼 동작한다.
이 함수는 파이버가 수행되기 위해 반드시 필요한 컨텍스트 정보를 저장할 메모리를 할당한다(약 200 바이트). 이 컨텍스트 정보에는 다음과 같은 내용이 저장된다
파라미터는 데이터 스트럭쳐가 들어간다.
성공시 파이버의 주소값이 리턴된다.
이 함수를 호출하면 가장 먼저 dwStackSize 매개변수로 지정된 크기의 새로운 파이버 스택을 생성한다. 일반적으로 0을 전달하는데 이는 기본 크기의 스택을 만들며 1MB까지 저장이 가능한 스택을 생성한다.
하지만 이 중에서 단 두개의 페이지 공간만을 커밋한다.
만일 0이 아닌 값을 지정하면 지정된 크기만큼의 메모리를 예약하고 커밋까지 동시에 수행한다.
만일 매우 많은 수의 파이버를 사용해야 하는 경우라면 각 파이버 별로 스택을 유지하기위해 많
dwStackCommitSize 매개변수로 스택으로 사용할 메모리 공간 중 최초로 커밋할 크기를 지정하고
dwStackReverseSize 매개변수로는 예약할 가상 메모리의 크기를 지정하면 된다.
dwFlags 매개변수로 앞서 알아본 FIBER_FLAG_FLOAT_SWITCH 값을 전달하게 되면 파이버 컨텍스트 내에 부동소수점 상태를 저장하도록 할 수 있다.
다음으로, CreateFiber(Ex)는 새로운 파이버 수행 컨텍스트 구조체를 저장할 메모리 공간을 할당하고 그 내용을 초기화한다. 이후 사용자가 lpParameter 매개변수를 통해 전달한 값과 새로 생성된 스택의 최상위 및 최하위 주소, 파이버 함수를 가리키는 주소값(lpStartAddress 매개변수로 전달되는) 등을 수행 컨텍스트 구조체 내에 저장한다.
pfnStartAddress 매개변수로는 파이버 함수를 가리키는 주소를 전달하면 되는데, 반드시 다음과 같은 원형을 가져야한다.
파이버 Fìber 가 처음으로 스케줄되면 사용자가 정의한 파이버 함수를 호출하며, lpParam 매개변수로는 CreateFiber를 호출할 때 전달하였던 lpParam 값을 그대로 전달한다.
파이버 함수 내에서는 어떤 작업이라도수행할수 있다. 파이버 함수의 반환형이 VOID 인 이유는 반환값이 아무런 의미가 없기 때문이 아니라 이 함수를 절대로 반환하지 말아야 하기 때문이다. 만일 파이버 함수가 반환되면 이 파이버를수행하던 스레드와이 스레드에 의해 생성된 모든파이버가 그즉시 파괴되어버릴 것이다.
SwitchToFiber는 pvFiber라는 이름의 하나의 매개변수를 취히는데, 이 매개변수로는 앞서 ConvertThreadToFiber(Ex) 함수나 CreateFilber(Ex) 함수가 반환한 파이버 수행 컨텍스트의 주소를 전달하면 된다.
SwitchToFiber 함수를 통해서만 파이버에게 CPU 시간을 할당할 수 있다. 따라서 사용자 코드 내에 서 적당한 시간에 SwitchToFiber를 호출해야만 다른 파이버를 스케줄링할 수 있으며,
이는 사용자가 파이버 스케줄에 대한 완벽한 제어권을 확보하고 있는 것으로 볼 수 있다.
파이버 스케줄링은 스레드 스케줄링에 대해서는 아무런 영향도 미치지 않음을 기억해야 한다.
스레드가 스케줄되어야만 비로소 선택된 파이버가 수행되며, 수행 중인 파이버는 SwitchToFiber를 명시적으로 호출해야만 다른 파이버가 수행된다.
이 함수는 매개변수가 가리키는 파이버를 삭제하는데
이 값으로는 삭제 하고자 하는 파이버의 수행 컨텍스트를 가리키는 주소값을 전달하면된다.
이 함수는 파이버 스택으로 사용하던 메모리를 삭제하고 파이버 수행 컨텍스트를 파괴한다.
만일 현재 수행 중인 파이버의 수행 컨텍스트를 전달하게 되면 내부적으로 이 스레드가 생성한 모든 파이버를 파괴하고 ExitThread를 호출하여 스레드를 종료한다.
DeleteFiber는 하나의 파이버가 다른 파이버를 삭제하고자 할 때 주로 사용된다. 이 함수를 호출하면 삭제하고자 하는 파이버의 스택과 수행 컨텍스트를 삭제한다.
여기서 파이버와 스레드의 차이점에 대해 주목할 필요가 있다. 스레드는 보통 자신을 종료하기 위해 ExitThread를 호출한다 TerminateThread 함수를 호출하여 다른 스레드를 종료하는 것은 좋지 않은 방법이다.
만일 TerminateThread 함수를 호출하였다면 시스템은 종료되는 스레드가 사용하였던 스택을 삭제하지 못한다.
하지만 특정 파이이가 다른 파이버를 삭제하는 경우에는 깔끔하게 파이버를 삭제할수 있다.
모든 파이버가 삭제되면 ConvertThreadToFiber(Ex) 를 호출하여 파이버로 전환하였던 스레드를 다시 스레드로 돌려놓기 위해 ConvertFiberToThread 함수를 호출할 수 있으며
이를 통해 파이버가 사용하던 마지막 메모리까지 모두 삭제하게 된다