- 스레드 02 - 스레드 스케줄링 012024년 10월 08일
- 묭묭.cpp
- 작성자
- 2024.10.08.:07
스레드 02 - 스레드 스케줄링 01
윈도우 스케줄링의 개요
윈도우는 우선순위 기반의 선점형(Preemptive) 스케줄링 시스템
- 가장 우선 순위가 높은 실행 가능 스레드가 최소한 1개는 실행됨을 보장
- 선점의 의미 : 우선순위가 높은 스레드가 이미 수행되고 있는 스레드를 밀어내고 수행될 수 있다.
하나의 스레드가 실행되게 선택됐다면
- 스레드는 퀀텀이라는 시간 동안만 실행
퀀텀(Quantum)
- 같은 우선순위를 가진 스레드가 돌아가며 실행되기 전까지 이 스레드가 실행되게 허용된 시간 간격
퀀텀이 달라질 수 있는 이유
- 시스템 구성 설정
- 포그라운드 백그라운드 프로세스 상태
- 퀀텀을 변경하기 위한 잡 객체의 사용 여부
퀀텀을 모두 소진하지 못하는 경우
- 더 높은 우선순위의 스레드가 실행 준비가 되어 있으면
- 스레드가 블록 당하는 경우(I/O 요청 등에 의하여)
- 실제로 퀀텀을 모두 소진하는 경우는 드뭄
- 연산자로만 이루어진 경우 모두 소진할 수도 있음
윈도우 스케줄링 코드는 커널에 구현되어 있음
- 그러나 별도의 스케줄러 모듈이나 루틴은 없음
- 스케줄링 관련 이벤트가 발생하는 커널 전체에 퍼져있음
- 이런 작업을 수행하는 루틴들을 커널 디스패처라 부름
스레드 디스패칭을 필요로 하는 이벤트
- 스레드 실행 준비가 됨 - 신규 스레드 생성, 대기 상태에서 막 해제
- 프로세서가 실행하던 스레드가 실행 상태를 벗어남
- 스레드의 우선순위가 동적으로 변경된 경우
- 스레드의 친화성(affinity)가 변경된 경우
위와 같은 상황에서 스레드는 컨텍스트 스위칭을 함
- 컨텍스트 스위칭 : 휘발성 프로세서를 저장하고 새 스레드의 값을 가져와 실행을 시작하는 것
윈도우는 스레드 단위 스케줄링
- 프로세스 A, B를 50 : 50으로 분배하는 것이 아닌 스레드 기준으로 분배
우선순위 수준(Priority Level)
윈도우는 0~31까지 32개의 우선순위 수준을 가짐
- 16개의 실시간 수준(16-31)
- 16개의 가변 수준(0-15)
- 수준 0은 제로 페이지(Zero Page) 스레드
스레드 우선순위 수준은 두 가지 다른 관점에서 부여
윈도우 API가 프로세스가 생성될 때 부여하는 방법
- PROCESS_PRIORITY 우선순위 클래스에 따라 분류 - 숫자는 인덱스
- Real-time, 4
- High, 3
- Above normal, 6
- Normal, 2
- Below normal, 5
- Idle, 1
- SetPriorityClass()를 이용하여 우선순위 클래스 변경 가능
- 상대적인 우선순위
- Time-critical, 15
- Highest, 2
- Above-normal, 1
- Normal, 0
- Below-normal, -1
- Lowest, -2
- Idle, -15
- +15와 -15는 포화 값, 실제 오프셋이라기 보다는 적용되는 특정 수준
- 윈도우 API에서는 PROCESS_PRIORITY 클래스와 상대적인 우선순위에 따라 결정된 기본 우선순위를 가짐
커널의 관점에서의 우선순위
- PspPriorityTable 전역 배열과 PROCESS_PRIORITY_CLASS 인덱스를 사용하여 프로세스의 기본 우선 순위로 변환
- 이 값은 우선순위 4, 8, 13, 6, 10(이 값은 고정된 매핑으로 변경 불가)
- 이후 상대적인 스레드 우선순위가 기본 우선순위의 차별점으로 적용
- 프로세스 내의 스레드 끼리도 우선순위가 부여
타임 크리티컬과 유휴 스레드 우선순위는 고유의 값을 가짐
- 윈도우 API가 요청된 상대적 우선순위 16과 -16을 넘김으로 우선순위의 포화를 커널에게 요청하기 때문
- 이들의 값을 구하는 공식(HIGH_PRIORITY는 31)
- 타임 크리티컬(Time-Critical) : ((HIGH_PRIORITY + 1)) / 2
- 유휴(Idle) : -((HIGH_PRIORITY + 1)) / 2
- 이 값은 KTHREAD의 Saturation 필드에 설정됨
- 우선순위를 변경하는 요청이 와도 포화 값을 가진 스레드는 건너뜀
최종적으로 스케줄러는 최종적 결과(프로세스 우선순위와 스레드 우선순위의 조합)만 따짐
스레드는 두 가지 우선순위 값을 가짐
- 기본 우선순위(정적)
- 프로세스 기본 우선순위로부터 상속
- 보통 사용자 애플리케이션 Normal(8) 우선순위
- 시스템 어플리케이션은 8보다 높은 우선순위
- 현재 우선순위(동적)
- 우선순위 상승(Boosting)에 따라 우선순위가 바뀔 수 있음(추후 정리)
스레드 상태
레디(Ready)
- 실행을 기다리거나 대기를 완료하여 In-swapped 되기를 기다리는 상태
- 디스패처는 레디 상태의 스레드만들 고려하여 실행 대상을 찾음
지연된 레디(Deferred Ready)
- 프로세서 친화도에 따라 실행되지 못하고 있는 스레드를 위한 상태
- 이 상태로 인해 커널에서 스케줄링 데이터베이스 접근을 위한 프로세서 락의 소유 시간을 최소화할 수 있음
스탠바이(Standby)
- 특정 프로세서에서 다음번에 실행되게 선택된 것을 의미
- 올바른 조건이 이뤄지면 디스패처는 이 스레드로 컨텍스트 스위칭을 수행
- 각 프로세서 별 이 상태의 스레드는 1개만 존재
- 물론 더 우선순위가 높은 스레드가 나타나면 선점당할 수 있음
실행(Running)
- 디스패처가 컨텍스트 스위칭을 수행한 이후의 스레드
실행을 마무리하는 상황
- 퀀텀이 끝난 경우
- 우선순위가 높은 다른 스레드가 선점됨
- 스레드가 종료됨
- 실행을 양보함
- 자발적으로 대기 상태로 넘어감(I/O 등의 블락 상황)
트랜지션(Transition)
- 스레드가 실행 준비가 되었으나 커널 스택이 페이지 아웃 되어 있는 상태에 트랜지션으로 진입
- 커널 스택이 페이지 인 되면 스레드는 레디 상태로 들어감
종료(Terminated)
- 스레드가 실행을 끝낸 상태
- 익스큐티브 객체는 정책에 따라 할당 해제가 될 수도 아닐수도 있음
- 다른 스레드에 의해 강제 종료 되어도 이 상태가 될 수 있음
초기화(Initialized)
- 스레드가 생성되고 있는 동안 내부적으로 사용되는 상태
스레드의 상태 전이
디스패처 데이터베이스(Dispatcher Database)
스레드 스케줄링에 대한 결정을 내리기 위한 데이터 구조체 집합
- 스레드의 상태를 추적함
스레드 디스패칭 병렬성을 포함한 확장성을 향상시키기 위한 구조
- 프로세서별 디스패처 레디 큐(Ready Queue)와 공유 프로세서 그룹 큐를 가짐
- 각 CPU는 시스템 전체 레디 큐에 대한 락을 걸지 않고 자신의 레디 큐를 검사해 다음 스레드를 결정
윈도우8 이전 버전
- 프로세서별 레디 큐와 레디 요약 정보를 사용
- PRCB(프로세서 컨트롤 블록)의 일부분에 저장
윈도우8 이후
- 프로세서 그룹에 대해 공유 레디 큐와 레디 요약 정보가 사용됨
- CPU 별 레디 큐는 여전히 존재하고 친화성 제약을 가진 스레드에 사용됨
공유 레디 큐의 정보는 PRCB의 KSHARED_READY_QUEUE에 저장됨
- 이 정보는 각 프로세서 그룹의 첫 프로세서에만 사용됨
- 나머지 프로세서는 이를 공유
디스패처 레디 큐의 구조
- 실행을 위해 스케줄링이 되기를 기다리는 레디 상태의 스레드를 가짐
- 32가지의 우선순위 레벨마다 큐를 1개 씩 가짐
- 더욱 빠른 선택을 위해 32bit 비트마스크를 가지고 있음
- 각 비트는 32개의 우선순위 수준에 하나 이상의 스레드가 대기 중임을 나타냄
- 단일 비트에 대한 조사는 최상위 비트가 설정되어 있는지 호가인하는 네이티브 프로세서 명령으로 가능
- 이 동작은 항상 상수 시간
디스패처 데이터베이스의 동기화 방식
- IRQL을 DISPATCH_LEVEL(2)로 상승시킴으로 동기화
- IRQL을 상승시키면 다른 스레드가 스레드 디스패칭에 개입하는 것을 차단할 수 있음
- 대부분의 스레드가 IRQL 0 혹은 1에서 실행하기 때문
하지만 다른 프로세서가 동시에 IRQL을 올린다면?
- 이것만으로는 충분하지 않음
- 4장 후반부 '멀티 프로세서 시스템'에서 다룸
퀀텀(Quantum)
- 같은 우선순위의 다른 스레드가 실행되기를 기다리고 있는지를 확인할 때까지 한 스레드가 실행하게 주어진 시간을 의미
- 퀀텀을 모두 소진하였지만 같은 우선순위의 다른 스레드가 없으면 현재 스레드에게 한 번의 퀀텀을 부여함
스레드의 수행 시간
- 클라이언트 윈도우 : 2 클록 간격씩 동작
- 서버 시스템 : 12 클록 간격씩 동작
- 컨텍스트 스위칭 횟수를 최소화하기 위해 큰 시간을 할당
클록 간격의 길이
- 하드웨어 플랫폼에 따라 달라짐
- 커널이 아닌 HAL에 의해 결정
- 이 클록 주기는 KeMaximumIncrement에 수백 나노초로 저장
스레드는 클록 단위로 실행
- 그런데 시스템은 이를 이렇게 판단하지 않음
- 스레드 런타임 통계는 프로세서 사이클에 기반을 두기 때문
KiCyclesPerClockQuantum
- 초당 CPU 클록 사이클 * KeMaximumIncrement(클록 틱이 일어나는 데 걸린 초)
- 이 계산법에 따라 스레드는 퀀텀 타겟에 따라 실행함
퀀텀 타겟
- 스레드가 자신의 차례를 포기할 때까지 소비한 CPU 클록 사이클 수에 대한 추정치
- 내부적인 클록 타이머 틱의 개수와 같아야 함
- 퀀텀당 클록 사이클 계산은 내부적인 클록 타이머 주기(time interval)를 기본으로 함
퀀텀 계산
각 프로세스는 KPROCESS(프로세스 제어 블록)에 퀀텀 재설정 값(Reset value)를 가지고 있음
- 새로운 스레드를 생성하는 경우에 사용, KTHREAD에 복사됨
- 그리고 스레드에 새로운 퀀텀 타겟 값을 부여하는 데 사용
퀀텀 타겟 = 퀀텀 재설정 값 * 퀀텀당 클록 사이클 수
- 퀀텀 타겟은 클록 틱의 1/3으로 표현
- 즉, 클라이언트용 6퀀텀, 서버용 36퀀텀
퀀텀 중지 처리
- 타이머 인터럽트가 발생한 시점에 CPU 클록 사이클 수가 퀀텀 타겟에 도달하거나 초과하면 발생
- 레디 큐의 다음 스레드에게 컨텍스트 스위칭 발생
내 컴퓨터에서의 퀀텀의 클록 사이클 계산
- CPU 클록 사이클 : 3.70Ghz = 1초당 3700000000
- 타이머 인터럽트 주기 : 15.625ms = 0.015625s
- 위 두값을 곱하고 3을 나누면 19270833
- 1 퀀텀은 19270833 클록 사이클
왜 클록 틱의 일부분이 내부적으로 퀀텀으로 저장되게 되었나?
- 비스타 이전의 윈도우에서 대기 완료에 대한 일부 퀀텀 손실을 허용하기 위해서
- 실행 대기 실행 대기를 반복하다 대기인 상태에 타이머 인터럽트가 발생하면 퀀텀을 소진하지 않게 됨
- 여기서 고민해야할 것
- 언제 이런 상황이 발생하는가
- 대기 상태가 되면서 퀀텀을 반납하지 않아야 이런 상황이 발생함
- 우선순위가 높은 스레드가 선점당했을 때 반납하지 않음
- 동기 I/O를 수행할 때 블록 상태에 들어가는데 이때 퀀텀을 반납하는가?
- 뒷 내용에서 프로세서 사용을 자발적으로 포기한다는 문장에서 퀀텀을 반납한다고 보는 것이 맞을 것 같음
- 퀀텀을 언제 재설정 하는가?에서 실마리를 찾아보는 중
퀀텀 제어
모든 프로세스에 대한 스레드 퀀텀을 변경할 수 있음
- 하지만 두 가지 종류 중 한개만을 선택 가능
- 숏(short) : 클라이언트 머신의 기본 값인 2 타이머 인터럽트
- 롱(long) : 서버 시스템의 기본 값인 12 타이머 인터럽트
가변 퀀텀
우선순위 상승
윈도우 스케줄러는 우선순위 상승 메커니즘으로 스레드의 현재 우선순위를 동적으로 조정
- 대기 시간을 줄이고 반응 속도를 높임
- 또한 inversion과 starvation을 예방할 수 있음
몇 가지 상승 시나리오
- 스케줄러/디스패처 이벤트에 의한 상승(지연시간 감소)
- I/O 작업의 완료에 의한 상승(지연시간 감소)
- 사용자 인터페이스 입력에 의한 상승(지연시간 감소 및 반응속도 향상)
- 익스큐티브 리소스(ERESOURCE)를 너무 오래 기다린 스레드에 의한 상승(기아 회피)
- 실행할 준비가 된 스레드가 한동안 실행되지 못하고 있을 때의 상승(기아와 우선순위 전도의 회피)
그러나 이런 조정은 완벽하지 못하고 도움이 되지 않을 수 있음
실시간(16~31) 범위의 우선순위는 절대로 조정하지 않음
몇 가지 상승 케이스만 정리함
Unwait 상승
객체가 시그널 되어(레디 상태로) 깨어나는 스레드와 Unwait을 처리하기 위해(실행 상태로) 들어가는 스레드 사이의 지연을 줄이기 위한 시도
- 대기 상태에서 깨어난 스레드가 빨리 실행을 한다는 것은 바람직한 상황
- 유저 모드 API에는 공개되지 않은 인자로 _INCREMENT 정의를 사용함
- 기본 상승값을 사용
단일 프로세서 시스템에서는 효과가 약할 수 있음
- 우선 순위가 더 높은 스레드가 존재 가능
멀티 프로세서 시스템에서는 우선순위 스레드가 선택될 가능성이 더 높음
- 스틸링과 밸런싱 알고리즘이 존재하기 때문
락 소유권 상승
두 스레드가 반복하여 우선순위가 상승하는 락 호위(Lock convoy) 문제를 피하기 위한 방법
- 익스큐티브 리소스와 크리티컬 섹션 락은 디스패처 객체를 사용하므로 Unwait 상승을 유발
- 이 객체의 고수준 구현은 락 소유자를 추적 - 커널은 AdjustBoost 원인을 이용
- 우선순위를 낮추지 않으므로 락을 해제하는 스레드가 계속해서 수행되는 상황이 야기
- 해제하는 스레드를 보통 우선순위로 돌리기 위해 이벤트나 게이트 코드에서 KiRemoveBoostThread 함수가 호출됨
I/O 완료 후의 우선순위 상승
I/O가 완료되는 시점에 원하는 I/O를 기다리던 스레드에서 우선순위 상승을 적용시킴
- 좀 더 우수한 응답성을 요구하는 I/O 장치일 경우 더 높은 상승값 보유
- Unwait 상승에 의존적임
지금 중요한 점
- 커널이 APC(비동기)를 사용하거나 이벤트(동기)를 통해 IoCompleteRequest API 내부에 시그널링 코드를 구현했다는 것
- KeInsertQueueApc : APC 큐에 인큐될 때 우선순위 상승이 발생
익스큐티브 자원 대기 동안의 상승
CPU 기아를 방지하기 위한 방법
- 500ms가 넘도록 자원을 소유하고 있다면 리소스를 소유한 스레드들을 15로 상승시키고 퀀텀을 재설정
- 독점 소유자를 먼저 상승시키고 공유 소유자 모두를 상승시킴
CPU 기아 상태에 대한 우선순위 상승
우선순위 부스팅
- 벨런스 셋 매니저 스레드의 일부 동작으로 CPU 기아 해결 메커니즘임
- 1초에 1번 레디 큐를 스캔하여 레디 상태에서 4초 이상 머문 스레드를 찾음
- 찾았다면 우선순위를 15로 올리고 3 퀀텀 단위의 CPU 클록 사이클 개수만큼 설정함
- 퀀텀이 만료되면 즉시 기본 우선순위로 돌아감
- 한번에 16개의 스레드만 검사하고, 10개의 스레드만 상승시킬 수 있음
컨텍스트 스위칭(Context Switching)
일반적인 컨텍스트 스위칭이 하는 일
- 다음의 자료들을 저장하고 복원함
- 인스트럭션 포인터(Instruction Pointer)
- 커널 스택 포인터(Kernel Stack Pointer)
- 스레드가 실행하는 주소 공간의 포인터(페이지 테이블 디렉터리)
- 이런 정보들을 KTHREAD 블록에 저장함
- 새로운 스레드의 커널 스택으로 설정하고, 컨텍스트를 로드함
- 만약 다른 프로세스라면 PTD를 CR3 레지스터에 적재함
- 여기부터 해당 프로세스의 가상 주소 공간을 사용할 수 있게됨
만약 처리되어야 하는 커널 APC가 지연되고 있다면
- IRQL 1의 인터럽트 요청
아니라면 새로운 스레드의 인스트럭션 포인터로 제어가 넘어감
직접 전환(Direct Switch)
윈도우8 부터 도입된 최적화 기법
- 자신의 퀀텀을 양보하고 동일한 프로세서에서 즉시 스케줄되도록 다른 스레드를 상승시킴
스케줄러에게 직접 전환을 한다는 상황을 알려주기 위해 특수한 함수를 호출함
- KiExitDispatcher에 플래그를 사용하여 호출하면
- KiDirectSwitchThread를 호출하여 가능하면 직접 전환 작업을 수행함
- 우선순위 반납 플래그가 지정되면 다음 스레드로 우선순위를 반납됨
- 프로세서 선호도가 존재하면 실패할 수 있음
스케줄링 시나리오
우선순위 기반 선점형 멀티태스킹은 어떻게 동작하는가?
자발적인 전환
윈도우의 대기 함수를 호출하여 대기 상태로 진입하며 프로세서 사용을 포기함
- WaitForSingleObject 같은 함수
스레드가 대기 상태로 진입하고 새로운 스레드를 선택하는 과정
- 대기 상태를 호출한 스레드는 대기 상태로 전환되고 - 대기 큐로 이동
- 레디 큐에 있는 다음 스레드가 실행될 수 있게 함
선점
선점 당하는 경우
- 우선순위가 높은 스레드의 대기가 끝남(이벤트 대기)
- 스레드의 우선순위가 변화
스레드가 선점 당하면 자신이 실행했던 우선순위의 레디 큐의 맨 앞에 놓이게 됨
이 때 밀려난 스레드는 남은 퀀텀을 소진하게 됨
- 퀀텀 재할당 받지 않고 남은 것을 사용
퀀텀 종료
퀀텀을 소진한 경우 윈도우는 이 스레드의 우선순위가 감소해야 하는지, 다른 스레드가 프로세서에 스케줄 되어야 하는지를 판단함
- 우선순위가 감소되었다면 다른 적절한 스레드를 찾음(더 높은 우선순위의 스레드가 레디 큐에 있다면)
- 같은 우선순위의 레디 큐에 다른 스레드가 있다면 이것을 선택하고 이전 스레드를 레디 큐의 맨 뒤로 보냄
- 레디 큐에 다른 스레드가 없다면 지금 실행 중인 스레드가 퀀텀을 다시 받아 계속 실행하게 됨
위에서 논의한 타이머 인터럽트에 의존한 스케줄 모델에서 발생할 수 있는 상황
- 스레드 A와 스레드 B가 간격 사이에 준비 상태가 될 수 있음. - 타이머 인터럽트에서만 스케줄링 코드가 실행되는 것이 아님, 꽤 자주 발생
- 스레드 A가 실행됐지만 잠시 인터럽트 되면 그 처리에 소비된 시간은 그 스레드에 부가됨
- 인터럽트 처리가 끝나고 스레드 A가 다시 실행됐지만 바로 다음 타이머 인터럽트에 도달
- 바로 스레드 B로 전환이 일어남
- 스레드 B가 실행되고 모든 클록 간격을 다 사용할 기회를 가짐
- 선점이나 인터럽트 처리를 제외하고
스레드 A는 두 가지 관점에서 불리한 입장
- 자신의 인터럽트가 아닌데도 비용 부과
- 스레드 A의 스케줄 시작 전의 유휴 처리도 타이머 인터럽트 간격에서 발생
윈도우는 이제 스레드가 처리하는 작업에 대한 정확한 카운트를 유지하게 됨
- 인터럽트는 제외임
위 불공정한 상황은 이제 발생하지 않고 다음 상황이 발생함
- 스레드 A와 B는 간격 사이에 준비 상태가 됨
- 스레드 A가 실행되고 잠시 인터럽트, 그 비용은 부과하지 않음
- 인터럽트 처리가 끝나고 스레드 A가 다시 실행되었지만, 바로 다음 타이머 인터럽트에 도달
- 스케줄러는 부가된 CPU 클록 사이클을 보고 퀀텀 종료 시에 부가해야 하는 예상 사이클과 비교
- 바로 전의 수치가 예상보다 작아 스케줄러는 스레드 A가 타이머 인터럽트 중간에서 실행됐고 추가적인 인터럽트가 발생했다고 가정
- 다음 타이머 인터럽트에서 스레드 A는 퀀텀을 마치고 스레드 B가 실행할 기회를 가짐
종료
스레드가 실행을 마무리하면(ExitThread, TerminateThread)
- 실행 상태에서 종료 상태로 옮겨짐
- 해당 스레드 객체의 사용 카운트가 0이라면 프로세스의 스레드 목록에서 제거되고 스레드 객체 또한 할당 해제됨
유휴 스레드(Idle Thread)
프로세서에 실행 가능한 스레드가 없을 때 유휴 스레드를 프로세서에 배정하여 실행
- 각 프로세서는 자신만의 유휴 스레드를 가짐
유휴 스레드는 유휴 프로세스에 속함
- 다른 스레드 처럼 ETHREAD/KTHREAD 구조체로 나타나지만 스레드 객체는 아님
- 유휴 프로세스는 작업 관리자에서 확인 가능
- 시스템 유휴 시간 프로세스
- 프로세스 ID와 스레드 ID는 0
- PEB, TEB 등 많은 필드가 0의 값을 가짐
- 유저 모드 코드가 필요없기 때문
- 실제로 유저 모드 코드는 없음
유휴 프로세스/스레드 동작
- 초기 부팅 시 유휴 스레드와 유휴 프로세스 구조체가 정적으로 할당되어 프로세스 관리자와 객체 관리자가 초기화 되기 전 시스템 부팅을 위해 사용
- 그 이후의 유휴 스레드 구조체는 프로세서가 동작할 때 동적으로 할당
- 논페이지 풀에서 할당하여 객체 관리자에게 넘김
유휴 스레드의 우선순위
- 윈도우는 유휴 스레드 우선순위를 0으로 봄
- 그러나 유휴 스레드는 스레드가 전혀 없을 때만 디스패칭을 위해 선택됨
- 이 값은 사실 의미없는 값 - 절대로 다른 스레드와 비교되지 않음
- 우선순위가 0인 스레드는 제포 페이지 스레드(Zero Page Thread) 뿐
- 유휴 스레드는 어떠한 스레드 큐에도 들어가지 않음
유휴 스레드의 선점
- 유휴 스레드 루틴 KiIdleLoop는 다른 스레드에 의해 선점되는 것을 차단하는 명령을 수행
- 프로세서에 수행될 스레드가 하나도 없는 경우 PRCB에 유휴 표기가 됨
- 이후 수행될 스레드가 선택되면 PRCB의 NextThread 포인터에 저장
- 유휴 스레드는 자신의 루프에서 이 포인터 값을 검사
유휴 스레드의 기본 제어 흐름
- 펜딩 상태인 인터럽트를 전달할 수 있게 일시적으로 인터럽트를 활성화하고 다시 비활성화
- 유휴 스레드의 상당 부분이 인터럽트가 비활성화 상태로 수행하기 때문
- 펜딩 중인 DPC가 있다면 KiRetireDpcList를 호출하여 펜딩된 DPC를 전달
- 1단계에서 인터럽트를 비활성화 했기 때문에 KiRetireDpcList는 인터럽트가 비활성화된 채로 빠져나옴
- 퀀텀 종료 처리가 요청됐는지 검사 - KiQuantumEnd를 호출하여 처리
- 다음 실행 스레드가 있는지 검사 - 있으면 해당 스레드 배정
- 요청이 있다면 다른 프로세서에서 대기 중인 스레드가 있는지 검사하여 지역적으로 스케줄링
- 전원 관리 유휴 루틴 호출
스레드 일시 중지
SuspendThread와 ResumeThread API 함수로 중지, 재개 가능
- 각각 일시중지 카운트를 증가시키고 감소 시키며 0이되면 스케줄링 가능
동작 방식
- 커널 APC를 해당 스레드에 큐잉하며 동작
- 스레드가 실행을 위해 전환될 때 APC가 먼저 실행
- 이는 스레드가 시그널되는 이벤트를 대기하게 함
이런 메커니즘은 일시 중지 요청이 들어올 때 스레드가 대기 상태라면 엄청난 단점을 가짐
- 스레드가 일시 정지되기 위해서는 깨어나야함
- 이로 인해 커널 스택 인스왑이 일어날 수도 있음
- 페이지 아웃 되었다면
가벼운 일시 중지(Lightweight Suspend) 메커니즘
- APC가 아닌 메모리의 스레드 객체를 직접 조작하여 일시 정지 상태로 표시
- 윈도우 8.1, 서버 2012 R2 부터 도입
스레드 선정
논리 프로세서는 다음 스레드를 골라야 할 때 KiSelectNextThread 스케줄러 함수를 호출
위 함수가 호출되는 시나리오
- 현재 수행되거나 대기 중인 스레드가 고정 친화성(Hard Affinity)의 변화가 발생하여 선택된 논리 스레드에서 실행 자격을 잃은 경우
- 현재 실행 중인 스레드의 퀀텀이 끝나고 SMT 설정(해당 프로세서의 리소스)이 busy 상태로 변경(실행 준비 상태)되었지만 이상적인 노드(NUMA 노드) 내의 다른 SMT 설정이 유휴 상태(사용되지 않음)인 경우 스케줄러는 현재 스레드의 퀀텀 종료 이동을 수행하여 다른 스레드가 선택되게 함
- busy 상태 : 해당 프로세서에 자원이 할당되어 있지만 실제로는 당장 작업을 처리하지 않거나 대기 상태로 들어가는 것처럼 보이는 상태
- 대기 작업이 종료되고 대기 상태 레지스터에 펜딩된 스케줄링 작업이 있는 경우
- 우선순위와 친화성이 설정되어 있음
이런 상황에서 스케줄러 동작
- 다름 레디 스레드를 찾음
- KiSelectReadyThreadEx를 호출하여 검사
- 레디 스레드가 없다면 유휴 스케줄러가 활성화되고 유휴 스레드가 선택됨
- 레디 스레드가 있다면 로컬이나 공유 레디 큐에 해당 스레드를 스탠바이 상태로 인큐
논리 프로세서가 스케줄되어야 하는 스레드를 선택하고 아직 그 스레드가 수행되지 않았을 경우 KiSelectNextThread 작업이 수행
논리 프로세서가 다른 동작에 관심이 있는 경우
- 우선순위가 변경되어 더 높은 우선순위의 스레드가 선점되어야 하는 경우
- 스레드가 YieldProcessor 혹은 NtYieldExecution을 호출하여 양보하고 다른 스레드가 수행 준비가 된 경우
- 현재 스레드의 퀀텀이 끝나고 동일 우선순위의 다른 스레드 역시 수행될 필요가 있는 경우
- 스레드가 우선순위 상승을 잃은 경우
- 유휴 스케줄러 동작 도중 레디 스레드를 검사할 필요가 있는 경우
두 경우의 차이점
- KiSelectNextThread : 다른 스레드를 반드시 수행해야 함
- KiSelectReadyThread : 가능하다면 다른 스레드 수행
- 두 경우 모두 우선순위가 높은 스레드가 없거나 레디 상태의 스레드가 없다면 반환되는 스레드는 없음
유휴 스케줄러
유휴 스케줄링 기능이 활성화되어 있다면
- 유휴 스레드가 KiSearchForNewThread를 호출하여 다른 프로세서의 레디 큐를 스캔
- 이 작업은 유휴 스레드의 시간에 부과되지 않고 인터럽트와 DPC 시간으로 부여됨
- 즉, 시스템 시간
반응형'윈도우 인터널즈' 카테고리의 다른 글
메모리 관리 03 - 가상 주소 배치, 랜덤화 (0) 2024.10.14 메모리 관리 02 - 힙 (0) 2024.10.11 메모리 관리 01 (3) 2024.10.11 스레드 03 - 스레드 스케줄링 02 (0) 2024.10.10 스레드 01 (0) 2024.10.08 다음글이전글이전 글이 없습니다.댓글