10. GDT, IDT 테이블, TSS 세그먼트를 추가해 인터럽트에 대비하자

들어가기

TSS 세그먼트와 IDT 테이블을 생성하는 방법에 대해 알아보고, 임시 핸들러를 등록해보자.

본론

10.1 인터럽트와 예외

10.1.1 인터럽트와 예외의 차이점

  • 인터럽트와 예외공통점은 코드 수행 도중에 발생하거나 프로세서에 의해 처리가 필요한 이벤트.
    • 인터럽트나 예외가 발생하면 준비한 특수 코드를 실행.
  • 인터럽트와 예외차이점은 이벤트를 발생시키는 주체가 다름.
    • 인터럽트외부 디바이스에 의해 프로세서에 전달되는 이벤트.
    • 예외는 프로세서가 코드를 수행하는 도중에 페이지 폴트나 잘못된 명령 같은 오류로 발생.
  • 이벤트별로 특수한 처리 함수가 필요 -> 인터럽트 또는 예외 핸들러.
    • 핸들러는 함수를 수행 후 발생한 시점으로 정상적으로 복귀.

10.1.2 IDT와 IDT 게이트 디스크립터

  • 프로세서는 인터럽트가 발생했을때 벡터 테이블의 인덱스에 해당하는 어드레스로 이동하여 처리 함수를 수행.
  • 벡터 테이블은 각 운영 모드마다 존재.
    • 리얼 모드의 벡터 테이블은 세그먼트 : 어드레스의 형태로 어드레스 0x0000 : 0x0000에 위치.
    • 보호 모드와 IA-32e 모드에서는 IDT라고 불리는 특수한 형태의 벡터 테이블 사용.
  • IDT는 IDT 게이트 디스크립터로 구성된 테이블, IDTR 레지스터를 통해 프로세서에 IDT 정보를 설정하며 최대 256의 엔트리.
  • IDT 게이트 디스크립터는 다른 디스크립터와 달리 세그먼트 셀렉터를 포함 -> 유저 레벨의 어플리케이션 코드를 실행 중 인터럽트 발생시 커널 레벨로 올라가야하기 때문.
  • IDT 게이트 디스크립터는 여러 가지 타입이 있음.
    • 태스크 게이트
    • 인터럽트 게이트
    • 트랩 게이트
  • 보호 모드만 세가지 다 지원하며 IA-32e 모드는 하위 2개의 게이트만 지원.
  • 인터럽트 게이트와 트랩 게이트의 차이는 핸들러 실행 중 추가 인터럽트를 발생 시키느냐의 여부.
    • 인터럽트 게이트를 사용해 예외 처리를 하는 동안 인터럽트가 더 이상 발생하지 않음.
  • IST는 IA-32e 모드에서 도입된 새로운 개념으로 인터럽트나 예외가 발생했을 때 별도의 스택 공간을 할당.

10.1.3 인터럽트와 예외의 종류

  • x86 프로세스는 IDT 테이블의 상위 32개 디스크립터를 예약, 20개는 실제로 사용되며 12개는 앞으로 사용을 위해 미리 예약한 영역.
  • 즉, 224개는 OS가 임의로 사용할 수 있는 부분.
  • 프로세서가 사용하는 20개의 예외는 3가지로 분류.
    • faults는 코드에 문제가 발생했으나 수정하면 정상적으로 실행 가능한 예외.
    • traps는 trap을 유발하는 명령어를 실행했을 때 발생.
    • aborts는 심각한 문제가 발생하여 더이상 코드를 실행할 수 없는 경우.

10.2 인터럽트와 예외, 스택과 태스크 상태 세그먼트

10.2.1 스택 스위칭과 IST

  • IDT 게이트 디스크립터에 설정된 코드 디스크립터의 권한이 현재 수행 중인 코드의 권한보다 높으면 새로운 스택으로 전환 -> 스택 스위칭.
  • 스택 스위칭을 하는 첫 번째 이유는 핸들러의 스택 공간 부족으로 오류가 발생하는 일을 방지.
  • 스택 스위칭을 하는 두 번째 이유는 권한이 높은 함수가 낮은 권한의 스택을 공유함으로써 발생할 수 있는 간섭을 최소화.
  • 보호 모드도 스택 스위칭을 지원하지만 권한이 변경되어야만 스택 스위칭이 발생.
  • IST는 인터럽트 처리를 위한 스택을 정의한 테이블로 최대 7개의 스택을 지정.
  • IST는 권한 변동 없이 무조건 스택 스위칭이 발생.
  • IST의 스택에서 수행 중이던 코드의 정보와 스택의 정보를 저장해 커널의 스택 데이터 유지.

10.2.2 프로세서와 태스크 상태 세그먼트, 태스크 디스크립터

  • TSS는 104 바이트로 태스크의 상태를 저장하는 영역으로 프로세스의 상태를 저장하는 역할.
  • 보호 모드의 TSS는 특정 레지스터를 제외하고 프로세서의 모든 레지스터를 저장, 또한 권한별 스택 정보를 저장하는 역할과 I/O 포트에 접근하는 것을 제한.
  • IA-32e 모드로 옮기면서 TSS 위상 추락, 레지스터의 크기가 커지면서 고정된 TSS를 사용할 수 없게됨.
  • IST 정보를 저장하는 역할을 IA-32e 모드에서 함.
  • 프로세서에 TSS 세그먼트에 대한 정보를 알려주는 TSS 디스크립터LTR 어셈블리어 명령어.
  • TSS 디스크립터는 GDT 테이블에 있으며 16바이트를 차지.

10.3 GDT 테이블 교환과 TSS 세그먼트 디스크립터 추가

10.3.1 왜 GDT 테이블을 교환해야 하는가?

  • 어셈블리어로 작성된 보호 모드 GDT에 추가해도 되지만, 커널 엔트리 포인트 영역(512바이트)에 비해 104바이트의 TSS 세그멘트 디스크립터가 너무 큼.
  • 1MB 이상의 공간에 GDT 테이블을 생성.

10.3.2 GDT 테이블 생성과 TSS 세그먼트 디스크립터 추가

  • GDT 테이블과 GDT 정보를 나타내는 자료구조를 구조체로 작성.
  • GDTR 자료구조의 시작 어드레스를 0x142000으로 설정하는 이유는 0x100000 영역부터 264KB를 페이지 테이블로 사용.

10.3.3 TSS 세그먼트 초기화

void kInitializeTSSSegment(TSSEGMENT * pstTSS) {
    kMemSet(pstTSS, 0, sizeof(TSSDATA));
    pstTSS -> qwIST[0] = 0x800000;
    pstTSS -> wIOMapBASEAddress = 0xFFFF;
}
  • I/O 맵을 사용하지 않으려면 TSS 세그먼트 디스크립터에서 설정한 Limit 필드 값보다 크게 설정.
  • IST 필드의 0이 아닌 값으로 설정하고 핸들러가 사용할 스택 어드레스를 설정하면 사용 가능.

10.3.4 GDT 테이블 교체와 TSS 세그먼트 로드

 kLoadGDTR:
    lgdt [rdi]  ; GDTR 어드레스를 프로세서에 로드
    ret

 kLoadTR:
    ltr di      ; TSS 세그먼트 디스크립터의 오프셋을 프로세서에 설정해 TSS 세그먼트 로드
    ret
  • GDT는 LGDT를 사용해 갱신.
  • TSS 세그먼트는 LTR 명령어를 사용하여 GDT 테이블 내의 TSS 세그먼트 인덱스를 지정.

10.4 IDT 테이블 생성, 인터럽트, 예외 핸들러 등록

10.4.1 IDT 테이블 생성

for(i = 0; i < 100; i ++) {
    kSetIDTEntry( &(pstEntry[i]), kDummyHandler, 0x08, IDT_FLAGS_IST1, IDT_FLAGS_KERNEL, IDT_TYPE_INTERRUPT)
}

void kDummyHandler(void) {
    kPrintString(0, 0, "==================================");
    kPrintString(0, 1, "  Dummy Interrupt Handler Called  ");
    kPrintString(0, 2, "==================================");
}
  • 앞서 IDTR 자료구조의 시작 어드레스는 이 전에 생성한 TSS 세그먼트 이후에 위차하게 0x1420A0을 설정.
  • 인터럽트와 예외는 최대 100개까지 처리 가능하게 설정.

10.5 IDT, TSS 통합과 빌드

10.5.1 디스크립터 파일 추가

Descriptor.c 코드 보기
Descriptor.h 코드 보기

10.5.2 어셈블리어 유틸리티 파일 수정

수정된 AssemblyUtility.h 코드 보기
수정된 AssemblyUtility.asm 코드 보기

10.5.3 유틸리티 파일 추가

Utility.c 코드 보기
Utility.h 코드 보기

10.5.4 C 언어 커널 엔트리 포인트 파일 수정

수정된 main 코드 보기

10.5.5 빌드와 실행


마치며

내일 코드 한번 다시 봐야겠다..

Share