윈도우 인터널즈

시스템 메커니즘 05 - APC

묭묭.cpp 2024. 10. 23. 12:09

시스템 메커니즘 05 - APC

비동기 프로시저 호출(APC) 인터럽트

비동기 프로시저 호출(APC, Asynchronous Procedure Calls)

  • 유저 프로그램과 시스템 코드가 특정 유저 스레드 컨텍스트에서 실행되게 해줌
    • 즉, 특정 프로세스 주소 공간 내에서 실행됨
  • 스레드 스케줄링 규칙에 종속되고 DPC와 동일한 환경에서 동작하지 않음
  • 즉, DISPATCH_LEVEL에서 동작하지 않음
    • 높은 우선순위 스레드에게 선점될 수 있음
    • 블로킹 대기가 가능
    • 페이징 메모리 액세스 가능
  • APC_LEVEL(1) IRQL에서 동작
    • 일종의 소프트웨어 인터럽트

APC 큐

  • APC는 APC 객체라는 커널 컨트롤 객체에 의해 기술됨
  • APC는 커널이 관리하는 2개 중 하나의 APC 큐에 머뭄
    • APC 큐는 스레드 별 존재
    • 커널 APC 큐와 유저 APC 큐 총 2개가 존재함
  • APC를 큐에 추가하는 요청
    • 커널은 APC 모드를 확인(커널인지 유저인지)
    • APC 루틴을 수행해야 할 스레드의 적절한 큐에 삽입

2가지 APC 모드의 차이점

APC가 스레드에 추가됐을 때의 스레드 상황

  • 스레드가 동작 중임(현재 스레드임)
  • 스레드가 대기 중임
  • 스레드가 다른 동작 중임(레디 또는 대기 등)

스레드 스케줄링을 생각

  • 스레드는 대기 동작을 할 때마다 깨어날 수 있는 상태에 놓이게 됨
    • 커널 APC의 경우
      • APC가 완전히 비활성화되지 않았다면 이 상태를 무시
      • APC는 항상 대기를 취소함
    • 유저 APC의 경우
      • 스레드가 깨어날 수 있는 대기 상태이고 다른 유저 모드 구성 요소 대신 인스턴스화될 때 또는 다른 대기 중인 유저 APC가 대기를 끝내고 있는 경우에만 스레드에 개입 가능
      • 여러 개의 프로세서가 같은 스레드에 APC를 주입할 때 발생

유저 APC는 이미 유저 모드에서 동작 중인 스레드에 개입하지 않음

  • 유저 APC가 개입할 수 있는 상황
    • 깨어날 수 있는 대기를 수행
    • 링 전환
    • 유저 APC 큐를 다시 확인하는 컨텍스트 스위칭

커널 APC는 대상 스레드에 인터럽트 가능

  • IRQL을 APC_LEVEL로 높임
  • 프로세서가 커널 APC 큐를 볼 수 있게 함

2가지 모드 모두 APC를 실행시키려면

  • 스레드가 무언가 하고 있다면 동작 중 또는 대기 상태로 전이가 일어나야 함
  • 이 결과로 중지된 스레드는 큐에 있는 APC를 수행할 수 없음

커널과 드라이버 개발자가 선택할 수 있는 메커니즘

IRQL을 APC_LEVEL로 유지, 코드 수행 중에 그 이상으로 레벨을 높이는 것

  • 스레드가 수행 중이기 때문에 인터럽트는 전달될 수 있음
  • 각각 IRQL 규칙에 의해 프로세서가 이미 APC_LEVEL 이상에 있다면 인터럽트는 사라짐
  • 따라서 IRQL이 패시브 레벨로 떨어질 때만 APC 수행

KeEnterGuardedRegion API를 사용

  • 인터럽트 컨트롤러 상태 변화를 일으키지 않기 때문에 선호
  • KeLeaveGuardedRegion API와 짝을 이룸
  • 스레드가 APC 전달을 받게 복구
  • 재귀와 중첩 호출이 가능함
  • 스레드의 컨텍스트 스위칭에도 안전
    • 스레드 객체 구조체의 필드를 갱신하기 때문
    • SpecialApcDiable 필드 갱신

APC_LEVEL에 있는 동안에도 컨텍스트 스위칭이 발생

  • 디스패처는 IRQL을 KETHREAD의 WaitIrql 필드에 저장
  • 프로세서 IRQL을 새로운 스레드의 WaitIrql로 설정(패시브 레벨일 것)
    • 패시브 레벨의 스레드가 APC_LEVEL의 스레드를 선점하게 됨
    • 스케줄러의 고려가 IRQL의 고려보다 앞서는 것
      • 스레드 선점을 막는 것은 디스패치 레벨로 올려야 함
      • 이때의 IRQL은 스레드를 대신함
      • APC_LEVEL만 이 방식을 사용
    • 스레드 로컬 IRQL이라고 함

커널 개발자에 의해 APC가 어떻게 비활성화되는지 상관없이 하나의 법칙이 가장 우선순위

  • APC가 패시브 레벨보다 낮고 SpecialApcDisable이 0이 아니라면 코드가 유저 모드로 돌아오지 못함
    • 즉시 버그체크 발생
  • 이런 상황은 드라이버가 락을 해제했지만 가드 영역을 해제하지 않은 상황

APC 모드

8-12

유저 모드 APC를 삽입하는 일부 API

ReadFileEx, WriteFileEx, QueueUserAPC 등의 APC

  • I/O 동작이 끝났을 때 호출되는 완료 루틴을 지정 가능
  • I/O를 발생 시킨 스레드에 APC를 전달함으로 I/O 완료가 구현됨
  • APC가 삽입된 시점에 바로 콜백이 호출되지 않아도 됨
    • 깨어날 수 있는 대기 상태에서만 유저 모드 APC가 전달되기 때문
    • 대기 함수에 이런 옵션이 있음
  • 유저 모드 APC는 PASSIVE_LEVEL에서 동작 가능
반응형