Interlocked*를 이용한 Atomic Operation

많은 분이 Thread-Safety한 Atomic Operation 하기 위해 커널 오브젝트를 이용한 Thread-Blocking 방법(뮤텍스, 세마포어, 이벤트 등)으로 공부합니다. 시중에 나온 책에서 예제로 많이 사용하기 때문이기도 합니다만, 뭐 사실 동기화에 대한 개념만 익힐 수 있으면 큰 문제는 없습니다. 요즘 컴퓨터 사양으로 유저 모드에서 퍼포먼스 문제를 체감할 수 있다는 건 쉬운 일이 아닙니다. 커널 모드도 Thread-Blocking 방법을 사용하지만 상황이 좀 다릅니다.

Thread가 커널 모드에서 Blocking되는 두 가지 상황
- Thread의 Time Quantum을 다 사용하는 경우
- Thread가 Pagable한 메모리 영역에 접근하는 경우

Thread의 Time Quantum을 모두 소진하는 경우 다음 우선순위의 Thread가 CPU를 선점하기 때문에 현재 Thread가 Blocking되는 것이고, 현재 코드를 실행 중인 Thread가 IRQL이 DISPATCH_LEVEL 이상일 때 Pagable한 메모리에 영역에 접근하면 Page-Fault I/O가 발생하기 때문에 Thread가 Blocking되는 것입니다. 또한, 이 경우 Page-Fault I/O가 절대 완료될 수 없기 때문에 OS가 사전에 감지하고 BSOD를 띄워버립니다. 그렇기 때문에 커널 모드에서는 Thread-Blocking되는 동기화를 아무렇게나 사용할 수가 없는겁니다. Interlocked* 함수는 Signed Long 값을 +1, -1 하거나 비교 후 +1 해서 이전 값을 반환하기도 합니다. 또한, CPU에서 명령어(lock instruction)로 멀티 쓰레드 환경에서 각각의 Thread가 공유 자원에 대해 Atomic Operation을 할 수 있도록 보장합니다. 
FORCEINLINE
unsigned long
InterlockedIncrement(
       _Inout_ _Interlocked_operand_ unsigned long volatile *Addend
    )
{
    return (unsigned long) _InterlockedIncrement((volatile long*) Addend);
}
00000001`3f04a2b0 48894c2408      mov     qword ptr [rsp+8],rcx
00000001`3f04a2b5 57              push    rdi
00000001`3f04a2b6 488b442410      mov     rax,qword ptr [rsp+10h]
00000001`3f04a2bb b901000000      mov     ecx,1
00000001`3f04a2c0 f00fc108        lock xadd dword ptr [rax],ecx
만약 내부에서 관리하는 오브젝트의 Reference-Counter를 증가시킨다면 왠만해서는 Singed Long 크기를 넘어가는 일은 없으므로 Interlocked* 계열의 함수를 사용하는 것이 좋습니다. volatile 키워드를 변수에 사용하면 다른 Thread에 의해 값이 변경될 수 있다는 것을 의미하고, lock Instruction은 해당 메모리에 Thread-Safety한 Atomic Operation을 할 수 있다는 것을 의미합니다.

댓글 없음:

댓글 쓰기

Microcurruption Addis Ababa

11 Microcurruption의 Addis Ababa다. 4482: 3f40 0a00 mov #0xa, r15 4486: b012 5045 call #0x4550 <putchar> 448a: 8193 0...