ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • [x86] 인터럽트
    프로젝트/운영체제 만들기 2023. 6. 5. 18:14

    글의 참고

    - 64-ia-32-architectures-software-developer-vol-3a-part-1-manual.pdf

    - https://en.wikipedia.org/wiki/Interrupt

    - https://en.wikipedia.org/wiki/Interrupt_handler

    - https://en.wikipedia.org/wiki/Advanced_Programmable_Interrupt_Controller

    - https://linux-kernel-labs.github.io/refs/heads/master/lectures/interrupts.html


    글의 전제

    - 내가 글을 쓰다가 궁금한 점은 파란색 볼드체로 표현했다.

    - 밑줄로 작성된 글은 좀 더 긴 설명이 필요해서 친 것이다.


    글의 내용

    - 인터럽트

    : 인터럽트도 CPU 입장에서는 POLL 방식이다. 즉, CPU가 명령어 사이클이 끝나면 CPU는 그 때마다, 인터럽트 컨트롤러에서 온 인터럽트가 있는지를 확인한다.

     

    - 종류

    " 크게 2가지 나뉜다.

    1" 하드웨어 인터럽트
    2" 소프트웨어 인터럽트

     

     

    - 하드웨어 인터럽트

    : 대개 이 용어는 디바이스에서 프로세서로 발생하는 인터럽트를 의미하는 경우가 많다. 예를 들어, 키보드를 누를 때 마다 키보드 컨트롤러는 CPU에게 인터럽트를 발생시킨다. 마우스 컨트롤러는 마우스를 움직일 때마다, 인터럽트를 발생시킨다. 디스크 컨트롤러는 디스크에 데이터를 모두 읽거나/쓰면 인터럽트를 발생시킨다. 이렇게 하드웨어 인터럽트는 디바이스가 프로세서에게 보내는 인터럽트라고 생각하면 쉽다.

     

     

    - 시그널

    : 하드웨어 인터럽트는 프로세서의 클럭과는 상관없이 비동적기적으로 프로세서에게 보내질 수 있다. 그러나, 결국 최종적으로는 인터럽트에 대한 처리는 CPU가 하는 것임으로써 CPU의 클럭에 동기화된다. 그리고 많은 사람들이 인터럽트는 마치 하는 일 하다가 외부에서 신호가 들어오면 처리하는 방식으로 생각하지만, CPU 입장에서는 인터럽트는 여전히 POLL 방식이다. 왜냐면, CPU는 명령어 하나가 끝날 때마다, 인터럽트가 들어왔는지 확인하기 때문이다.

     

    : 많은 시스템들에서 인터럽트의 빠른 처리를 위해 각 디바이스들에게 임의의 개수에 IRQ 시그널 라인을 제공한다. 이 IRQ 시그널 라인은 말 그대로 하드웨어 라인을 의미한다. 이렇게 각 디바이스들에게 특정 라인을 제공함으로서, 인터럽트를 빠르게 처리할 수 있도록 했다.

     

    : 그리고 인터럽트의 종류를 구분하기 위해 `인터럽트 테이블` 개념을 도입했다. 이 인터럽트 테이블에는 인터럽츠 처리에 대한 주소와 해당 인터럽트가 어떤 인터럽트 종류인지를 기록되어 있다. 

     

     

    - 마스크

    : 인터럽트에서 `마스크`라는 용어가 자주 쓰이는데, 이 말은 해당 인터럽트를 비활성화 한다는 말이다. 반대로 `언마스크`는 해당 인터럽트를 활성화한다는 소리다. 대부분의 인터럽트 컨트롤러는 내부적으로 `인터럽트 마스크 레지스터`를 가지고 있다. 이 레지스터를 통해서 특정 인터럽트를 활성화/비활성화 할 수 있다. 그리고 비활성화는 또 2가지로 나눠 볼 수 있다. 해당 인터럽트에 대한 처리를 뒤로 미루느냐(Defered), 무시하냐로 나뉜다. 무시하는 것은 정말 무시해버린다. 그런데, 뒤로 미뤄지는 인터럽트들은 대게 인터럽트가 다시 활성화되는 시점이나 해당 인터럽트를 클리어 해버리기 전까지는 대기 상태로 남아있게 된다.

     

    : 그런데, 마스크를 할 수 있는 인터럽트가 있고 마스크를 하면 안되는 인터럽트가 있다. 전자를 `Maskable interrupts`라고 부르고, 후자를 `Non-maskable interruts`(NMIs) 라고 부른다. 대게 NMI는 굉장히 높은 우선순위를 갖는 인터럽트를 의미한다. 예를 들면, 리셋이나 왓치독 타이머 같은 인터럽트가 있다.

     

    - Spurious 인터럽트

    : `spurious interrupt`는 말 그대로 가짜 인터럽트를 의미한다. 아래의 내용을 보자.

    Normally when an IRQ occurs, the PIC chip tells the CPU "I've got an interrupt for you", then CPU replies "What is it?" and the PIC tells the CPU which interrupt vector. The problem is that the interrupt can disappear (either because software sent EOI or did something else) `after the PIC chip tells the CPU it has an interrupt but before the PIC chip tells the CPU which interrupt vector.` In this case, the CPU is waiting for a reply from the PIC and the PIC chip has to tell the CPU an interrupt vector, so it tells the CPU "it's a spurious IRQ" because the original interrupt disappeared.

    - 참고 : https://forum.osdev.org/viewtopic.php?f=1&t=23291

     

    : 위의 내용을 요약하면 다음과 같다.

     

    0" IRQ가 발생했다. 그래서 PIC는 CPU에게 인터럽트가 발생했다고 알렸다. CPU는 PIC에게 `어떤 인터럽트인데?`라고 재질문을 한다.

    1" PIC는 이제 인터럽트 벡터를 알려줘야 한다. 그런데, 여기서 인터럽트가 갑자기 사라졌다. 정확한 이유는 알 수 없다. (위에 예문에서는 `EOI`를 전송했다고 말하고 있다. 즉, 아직 처리되지도 않은 인터럽트를 모두 끝났다고 알리는 것이다) 즉, 인터럽트가 왔다는 사실은 알렸는데, 아직 이게 무슨 인터럽트인지는 알리지 못한 상황인 것이다.

    2" CPU는 PIC로 부터 응답을 계속 기다린다. PIC는 뒤늦게 CPU에게 말한다. 방근 건 `가짜 인터럽트(spurious interrupt)` 였다고 말이다.

     

     

    - 인터럽트 서비스 루틴(ISR)

    : 특정 인터럽트가 발생하면 CPU는 OS가 제공하는 인터럽트 테이블에서 해당 인터럽트와 관련된 인터럽트 처리기를 찾는다. 만약, 테이블에 발생한 인터럽트에 대한 인터럽트 처리기가 존재한다면, 해당 처리기가 있는 코드로 이동해서 인터럽트를 처리한다. 여기서 `해당 인터럽트 처리기`를 흔히 ISR 이라고 부른다.

     

    : 인터럽트 서비스 루틴은 함수의 호출과는 다른 루틴이다. x86 기준 인터럽트가 발생해서, ISR이 호출되면 스택에 EFLAGS, CS, EIP 순으로 푸쉬한다. 일반 함수 호출은 CALL 명령어 사용 시, 복귀 주소 하나를 스택에 푸쉬하는 것과 많이 다르다. 그래서 ISR은 CALLEE쪽에서도 복귀할 때, `ret`이 아닌 `iret`을 사용한다. `iret`은 스택에 푸쉬한 EFLAGS, CS, EIP을 다시 레지스터에 원복시킨다. 

     

    : 만약 인터럽트의 종류가 예외라면, CPU는 더블 워드(4B) 사이즈의 에러 코드를 스택에 푸쉬한다.

     

     

    - 인터럽트 실행 환경

    : 나중에 프로세스를 구현하면서 알게 되겠지만, 인터럽트는 별도의 특별한 실행 컨텍스트를 갖지 않는다. 예를 들어, A라는 프로세스가 실행 중일 때 인터럽트가 발생하면 해당 인터럽트 핸들러에게 할당되는 메모리는 모두 A 프로세스의 메모리에서 가져오게 된다. 즉, A 프로세스와 메모리를 공유하게 되는 셈이다. 쉽게 말하면 남에 컨텍스트를 빌려쓰는 셈이다. 뒤에 나올 SLIH는 커널 스레드로 동작하기 때문에, 자신만의 실행 컨텍스트를 갖는다.

     

     

     

    - 인터럽트 후반부

    :현대의 운영체제는 인터럽트 핸들러를 2종류로 나눈다

    1" First-Level Interrupt Handler(FLIH)
    2" Second-Level Interrupt Handler(SLIH)

    : FLIH는 흔히 `hard interrupt handler`, `fast interrupt handler`라고 불리고 SLIH는 흔히 `slow/soft interrupt handler`라고 불린다.

     

    : FLIH는 아키텍처 의존적인 인터럽트 핸들러로 흔히 우리가 말하는 `인터럽트 서비스 루틴`이 바로 이 핸들러를 의미한다. 인터럽트가 발생하면 `컨텍스트 스위치`가 발생하고 인터럽트 코드들이 메모리에 로드된 뒤에 인터럽트 서비스 루틴이 실행된다.FLIH는 아키텍처 의존적인 데이터들을 불러와서 SLIH에게 던져주고, 해당 인터럽트를 활성화 한 뒤 종료한다. 

     

    : 인터럽트를 이렇게 2개로 나누는 이유는 결국 빨리 처리하기 위함이다. 왜 인터럽트를 빨리 처리해야 할까? 인터럽트를 처리중에는 다른 인터럽트들 또한 마스크된다. 즉, 다른 인터럽트들이 실행되지 못한다. 이 말은 결국 데이터의 손실과 같은 말이다. 실행되어야 할 작업이 실행되지 못했기 때문에 데이터가 손실된 것이다. 이러한 이유 때문에, FLIH는 빠르게 실행될 수 있어야 한다. 그래서 아키텍처 의존적인 코드로 작성한다. 그리고 아키텍처에 독립적인 코들은 SLIH로 넘겨진다. 

     

    FLIH에서는 최소한의 작업만 하고 인터럽트를 다시 활성화한 후에, SLIH로 실제 작업을 넘겨야 한다. 

     

    : 물론, 인터럽트가 아직 끝나지도 않았는데, 해당 인터럽트를 언마스크하는 인터럽트 핸들러도 있다. 이런 핸들러를 `reentrant interrupt hanlder`라고 한다. 이 핸들러는 동일한 인터럽트가 발생하면 계속해서 이전 인터럽트 핸들러를 선점하는 방식을 일을 처리한다. 이렇게 하면 당연히 스택 오버플로우가 발생하기 때문에, 일반적으로는 피해야 한다. 우선 순위 개념이 적용된 시스템에서 FLIH는 자기와 동등하거나 낮은 순위의 인터럽트는 마스크한다.

     

    : SLIH는 일반적인 프로세스와 동일하게 처리된다. SLIH는 커널 스레드로 동작하며, 런큐에서 대기하다가 프로세서에게 할당되면 그제서야 인터럽트를 처리한다. 

     

    : 리눅스에서 이 개념은 굉장히 중요한 개념이다. 흔히, 유닉스 계열의 운영 체제에서 이 개념을 `bottom half`라고 부른다

     

    - x86 리얼 모드 IVT

    : 인텔 문서 `9.7 SOFTWARE INITIALIZATION FOR REAL-ADDRESS MODE OPERATION`의 내용을 `리얼 주소 모드의 IDT`라는 내용이 있다. 리얼 주소 모드에서 존재하는 시스템 자료 구조는 IDT 밖에 없다고 언급하고 있다. 즉, 반드시 리얼 주소 모드에서는 IDT가 있어야 한다는 말과 같다. 참고로, 리얼 주소 모드의 IDT는 `IVT`라고 한다.

     

    : 디폴트로 IVT는 0x0000H 번지에 존재한다. 이 주소는 `LIDT` 명령어를 통해서 바꿀 수는 있긴 한데, 저 주소가 사실 나쁘지 않다. 시스템 소프트웨어, 즉, 운영체제 및 시스템 펌웨어는 인터럽트가 활성화되기전에 반드시 인터럽트 핸들러 및 익셉션 핸들러들을 IVT에 로드해놔야 한다. 이 말을 다시 정리하면, 핸들러들이 등록되지 않으면 절대 인터럽트를 활성화 시키면 안된다.

     

    : 리얼 주소 모드의 인터럽트 및 익셉션 핸들러는 1MB 안에 존재해야 한다. 당연한 얘기다. 리얼 주소 모드의 주소 지정 가능(addressable) 범위가 1MB 이하이기 때문이다. 

     

    : 리얼 모드 핸들러 등록

    " 32비트와 달리 리얼 모드의 핸들러 등록은 IVT 주소에 직접 등록하면 된다. 아래 코드는 NASM을 기준으로 작성된 코드다.

    ...
    ...

    mov word [0x34], __isr_gp ; #GP
    mov word [0x36], 0x00

    mov word [0x20], __isr_dp ; #DP
    mov word [0x22], 0x00

    mov word [0x08], __isr_nmi ; #NMI
    mov word [0x0A], 0x00

    mov word [0x14], __isr_bound ; #BOUND
    mov word [0x16], 0x00

    ...
    ...

    __isr_gp:                                                                                                      
        jmp $

    __isr_dp:                                                                                                      
        jmp $

    __isr_nmi:                                                                                                      
        jmp $

    __isr_bound:                                                                                                      
        jmp $

    ...

    : 한 엔트리당 4바이트를 차지한다. LSB에 세그먼트 주소를 넣고, MSB쪽에 핸들러 주소를 넣으면 된다. 아래 사진을 보면 위의 코드를 이해할 수 있을 것이다.

     

     

     

    - x86 EFLAGS의 IF 플래그

    : `cli` 명령어는 인터럽트 관련해서 만능이 아니다. `cli` 명령어로 익셉션과 NMI는 막을 수 없다.

    Interrupt enable (bit 9) — Controls the response of the processor to maskable hardware interrupt requests (see also: Section 6.3.2, “Maskable Hardware Interrupts”). The flag is set to respond to maskable hardware interrupts; cleared to inhibit maskable hardware interrupts. The IF flag does not affect the generation of exceptions or nonmaskable interrupts (NMI interrupts). The CPL, IOPL, and the state of the VME flag in control register CR4 determine whether the IF flag can be modified by the CLI, STI, POPF, POPFD, and IRET.

     

    - x86 보호 모드 IDT

    : x86 ISR 작성

    " 인텔 시스템 가이드 문서 3A에서 인터럽트 관련 내용은 `챕터 6`다. ISR 작성 방법은 2가지가 있다.

    1" 어셈블리어로 작성
    2" GCC 확장 기능 사용

    " GCC 확장 기능은 ARM에서 사용해보기로 하고, x86에서는 직접 어셈블리어로 작성해본다.

     

     

    - x86 예약 이벤트

    : `x86`에서는 인터럽트와 익셉션을 하나로 포괄해서 `이벤트`라고 부른다. 아래 표는 예약된 32개의 이벤트를 보여준다. 0 ~ 31번이 예약된 이벤트들이다. 이 친구들은 비활성화를 할 수가 없다. 우리그 `cli`, `sti` 명령어를 통해 비활성화하는 건 외부 인터럽트에 해당한다. 외부 인터럽트는 `32 ~ 255`에 할당된다. 물론, `2`번에 외부 인터럽트가 할당되긴 한다. 근데, 일반적인 외부 인터럽트와는 조금 다르다. 비활성화가 불가능한 외부 인터럽트라고 해서 `NMI(Non-maskable external interrupt)`라고 부른다.


     

     

    - IDTR(Interrupt Descriptor Table Register)

    : 인터럽트 디스크립터 테이블 레지스터는 IDT의 베이스 어드레스와 리밋에 대한 데이터를 가진 주소를 로드해야 한다. GDTR과 형태가 완전 동일하기 때문에, GDT를 로드해봤다면 수월하게 로드까지는 할 수 있을 것이다.

    : IDTR은 IDT의 선형 주소를 갖는다. 논리 주소 및 물리 주소가 아님을 주의하자. 아래에서 선형 주소가 `byte 0`을 기준으로 하는선형 주소라고 했는데, 그냥 선형 주소를 가져다 쓰면 된다. 그런데, `LIMIT`는 선형 주소가 `byte 0`부터 카운트해서 `-1`을 해줘야 한다. GDT와 동일한 개념이다.

    The IDTR register holds the base address (32 bits in protected mode; 64 bits in IA-32e mode) and 16-bit table limit for the IDT. The base address specifies the linear address of byte 0 of the IDT; the table limit specifies the number of bytes in the table. The LIDT and SIDT instructions load and store the IDTR register, respectively. On power up or reset of the processor, the base address is set to the default value of 0 and the limit is set to 0FFFFH. The base address and limit in the register can then be changed as part of the processor initialization process.
    ...
    - 참고 : 2.4.3 IDTR Interrupt Descriptor Table Register

    The interrupt descriptor table (IDT) associates each exception or interrupt vector with a gate descriptor for the procedure or task used to service the associated exception or interrupt. Like the GDT and LDTs, the IDT is an array of 8-byte descriptors (in protected mode). Unlike the GDT, the first entry of the IDT may contain a descriptor. To form an index into the IDT, the processor scales the exception or interrupt vector by eight (the number of bytes in a gate descriptor). Because there are only 256 interrupt or exception vectors, the IDT need not contain more than 256 descriptors. It can contain fewer than 256 descriptors, because descriptors are required only for the interrupt and exception vectors that may occur. All empty descriptor slots in the IDT should have the present flag for the descriptor set to 0.

    The base addresses of the IDT should be aligned on an 8-byte boundary to maximize performance of cache line fills. The limit value is expressed in bytes and is added to the base address to get the address of the last valid byte. A limit value of 0 results in exactly 1 valid byte. Because IDT entries are always eight bytes long, the limit should always be one less than an integral multiple of eight (that is, 8N – 1).
    ...

    - 참고 : 6.10 INTERRUPT DESCRIPTOR TABLE (IDT)

    : 몇 가지 특징 및 주의 사항이 명시되어 있다. GDT와 다르게 IDT는 첫 번째 엔트리부터 의미가 있는 디스크립터이며, 256개가 최대다. `empty descriptor`는 `present` 비트가 0인 디스크립터를 의미하는데, 이게 IDT의 사이즈까지 차지할 지는 모르겠다. IDT는 8바이트 정렬일 때, 캐수 라인에 최적화되어 있다. 그리고 GDTR의 `LIMIT`이 0일 때, 길이는 1이 된다고 한다. 즉, `LIMIT`은 길이라기 보다는 IDT의 마지막 바이트를 의미한다고 볼 수 있다.

     

    : 아래는 IDT의 사이즈를 보여준다. LIMIT이 16바이트이고, IDT 엔트리 하나당 8바이트임을 고려하면 `2^16 / 2^8 = 2^8` 즉, 256개가 최대임을 알 수 있다.

     

    - IDT 디스크립터

    : IDT 디스크립터는 3가지 게이트 디스크립터로 나뉜다. 아래에서 볼 수 있다 시피, 상위 4바이트 [12:8] 비트를 통해 어떤 게이트 디스크립터가 될지가 나뉜다.

    : 문서에서 태스크 게이트가 나오는데, IDT의 태스크 게이트와 GDT 및 LDT에 나오는 태스크 게이트는 포맷이 동일하다. 태스크 게이트는 TSS 디스크립터를 통해서 TSS에 접근할 수 있게 되어있다. TSS는 이 글을 참고하자.

    The IDT may contain any of three kinds of gate descriptors:

    • Task-gate descriptor
    • Interrupt-gate descriptor
    • Trap-gate descriptor

    Figure 6-2 shows the formats for the task-gate, interrupt-gate, and trap-gate descriptors. The format of a task gate used in an IDT is the same as that of a task gate used in the GDT or an LDT (see Section 7.2.5, “Task-Gate Descriptor”). The task gate contains the segment selector for a TSS for an exception and/or interrupt handler task.

    Interrupt and trap gates are very similar to call gates (see Section 5.8.3, “Call Gates”). They contain a far pointer (segment selector and offset) that the processor uses to transfer program execution to a handler procedure in an exception- or interrupt-handler code segment. These gates differ in the way the processor handles the IF flag in the EFLAGS register (see Section 6.12.1.2, “Flag Usage By Exception- or Interrupt-Handler Procedure”).

    - 참고 : 6.11 IDT DESCRIPTORS

    : 여기서 중요한 부분은 인터럽트 및 트랩 게이트의 `세그먼터 셀렉터`와 `오프셋`이다[CS:EIP]. 여기서 세그먼트 셀렉터는 CS를 의미한다. 대개 최신 OS들은 세그먼트 방식을 `플랫`으로 지정하므로, `0`을 넣어도 무방하다. 오프셋은 실제 인터럽트 및 익셉션의 핸들러의 주소를 기입하게 된다. 실제 핸들러들의 시작은 어셈블리 언어로 작성되어야 한다(C 언어로도 작성할 수 있지만, 결국 컴파일러에 의해 어셈블리어로 컨버팅되기 때문에, 어셈블리어로 생각). 그래서 어셈블리어로 작성된 인터럽트 핸들러 주소를 오프셋에 넣게된다. 이벤트가 발생하면, 등록된 핸들러가 호출되고, 내부적으로 다시 C언어 핸들러 함수를 호출해서 인터럽트 및 익셉션을 처리하는 구조가 일반적이다.

     

    : 인터럽트 게이트와 트랩 게이트의 차이는 다음과 같다. `IF 플래그`는 외부 디바이스로 부터의 인터럽트를 받지 못하게 하는 역할을 한다. 인터럽트 게이트는 핸들러 진입 시, `IF 플래그` 클리어한다. 그래서 인터럽트 및 익셉션 핸들러가 작업을 처리할 때, 완전히 끝날 때까지 다른 인터럽트를 신경쓰지 않아도 된다. 그러나, 트랩 게이트는 `IF 플래그`를 건들지 않는다. 그래서 작업도중에 외부에서 인터럽트가 발생하면 인터럽트 우선순위에 따라 선점이 될 수도 있다. 

    ...
    The only difference between an interrupt gate and a trap gate is the way the processor handles the IF flag in the EFLAGS register. When accessing an exception- or interrupt-handling procedure through an interrupt gate, the processor clears the IF flag to prevent other interrupts from interfering with the current interrupt handler. A subsequent IRET instruction restores the IF flag to its value in the saved contents of the EFLAGS register on the stack. Accessing a handler procedure through a trap gate does not affect the IF flag.

    - 참고 : 6.12.1.2 Flag Usage By Exception- or Interrupt-Handler Procedure

    : 익셉션 파트에서 설명했지만, `트랩`은 소프트웨어에 의해 발생한 이벤트다. 그래서 동기적인 흐름으로 이벤트가 발생한다. 즉, SW 내에서 발생한 이벤트이기 인터럽트 핸들러를 `능동적`으로 호출하게 된다. 그러나, 인터럽트는 외부 디바이스에서 발생한 이벤트이기 때문에, 비동기적이다. 즉, 언제 발생할 지 알 수 없을 뿐만 아니라, 인터럽트 핸들러가 `수동적`으로 호출되기를 기다리는 입장이다.

     

    : 의미적인 부분에서 `인터럽트 게이트`와 `트랩 게이트`의 차이는 소프트웨어적인 인터럽트들은 `트랩 게이트`로 설정한다. 예를 들어, 시스템 콜은 `트랩 게이트`로 설정한다. 그래서, 시스템 콜을 처리하던 도중에 하드웨어 인터럽트가 발생하면 선점이 될 수 있다. 모든 하드웨어 인터럽트들은 `인터럽트 게이트`로 설정한다.

     

    - 인터럽트 우선순위

    : x86에서 인터럽트 우선순위는 사실 선점의 개념이 아니다. 왜냐면, x86에서 IF 플래그가 SET이면, 하드웨어적으로 현재 실행중 인 인터럽트를 선점할 수 있는 방법은 없다. 그래서 리눅스에서 이 상태에서 인터럽트를 놓칠 수 있기 때문에, IF 플래그를 CLEAR 해놓고 SW적으로 버퍼를 생성해서 거기에 인터럽트를 저장해놓는다.

     

     

    - 인터럽트 태스크

    : 여기서는 IDT의 태스크 게이트에 대해서 다룬다. 그러나 TSS 에서도 언급했지만, 현재 TSS는 거의 사용하지 않는다. 너무 아키텍처 의존적인 기술인데다가 현재 프로세서의 상태를 모두 저장해버려서 속도마저 느리기 때문이다. 

    When an exception or interrupt handler is accessed through a task gate in the IDT, a task switch results. Handling an exception or interrupt with a separate task offers several advantages:

    The entire context of the interrupted program or task is saved automatically.
    • A new TSS permits the handler to use a new privilege level 0 stack when handling the exception or interrupt. If an exception or interrupt occurs when the current privilege level 0 stack is corrupted, accessing the handler through a task gate can prevent a system crash by providing the handler with a new privilege level 0 stack.
    • The handler can be further isolated from other tasks by giving it a separate address space. This is done by giving it a separate LDT.

    The disadvantage of handling an interrupt with a separate task is that the amount of machine state that must be saved on a task switch makes it slower than using an interrupt gate, resulting in increased interrupt latency.

    A task gate in the IDT references a TSS descriptor in the GDT (see Figure 6-5). A switch to the handler task is handled in the same manner as an ordinary task switch (see Section 7.3, “Task Switching”). The link back to the interrupted task is stored in the previous task link field of the handler task’s TSS. If an exception caused an error code to be generated, this error code is copied to the stack of the new task.

    When exception- or interrupt-handler tasks are used in an operating system, there are actually two mechanisms that can be used to dispatch tasks: the software scheduler (part of the operating system) and the hardware scheduler (part of the processor's interrupt mechanism). The software scheduler needs to accommodate interrupt tasks that may be dispatched when interrupts are enabled.

    - 참고 : 6.12.2 Interrupt Tasks

    : 앞에서 말했다 시피, TSS를 이용한 방식은 현재는 거의 사용하지 않는다. 그러나, 문서에서는 스택 스위칭을 통한 이점을 주로 설명하고 있어서, 단점에 강조 표시를 해놓았다. 

     

    : 그리고 `IA-32`의 구조는 인터럽트 태스크 관련하여 중첩을 허용하지 않는다. 그 이유는 아래 글에서도 볼 수 있다시피, TSS의 BUSY 비트가 하나의 태스크와만 대응되기 때문이다. 그러므로, 반드시 TSS를 이용한 인터럽트 및 익셉션 처리를 할 때는 진입전에 인터럽트를 비활성화 해야 한다. 

    Because IA-32 architecture tasks are not re-entrant, an interrupt-handler task must disable interrupts between the time it completes handling the interrupt and the time it executes the IRET instruction. This action prevents another interrupt from occurring while the interrupt task’s TSS is still marked busy, which would cause a general-protection (#GP) exception.

    - 참고 : 6.12.2 Interrupt Tasks

    : IDT의 태스크 게이트는 GDT의 TSS 디스크립터를 참조하고 있다. 결국 `IDT 태스크 게이트 -> TSS 디스크립터 -> TSS` 이런식으로 접근을 하게 된다.

     

     

     

    : 게이트 타입

    " 인터럽트 디스크립터에 작성하는 게이트에 트랩 게이트와 인터럽트 게이트가 있는데, 이 2개의 게이트를 나누는 기준은 2가지 있다. 첫 번째는 아래와 같다.

    " 코드가 실행되던 도중에 `예외`를 만나면 `트랩 게이트`, 현재 실행되는 코드와 연관이 없는 이벤트가 발생하면 `인터럽트 게이트`

    : 트랩 게이트는 예외가 발생하면 예외 처리 루틴을 타고 나서 복귀하는 지점이 `예외`가 발생했던 지점으로 복귀한다. 이걸 `트랩`이라고 한다. 이건 잘못 처리하면 무한 루프가 될 수도 있다.

     

    : 인터럽트 게이트는 해당 인터럽트를 처리하고 중단이 되었던 다음 지점으로 복귀하는 경우다. 이 경우는 `하드웨어 인터럽트`에 의해서 발생하거나 `INT` 명령어를 통해서 발생시킬 수 있다. 예를 들어, x86 보호 모드를 기준으로 `INT 50` 명령어를 실행하면 , CPU는 IDT 테이블에서 50번째 인터럽트 디스크립터를 찾아서 인터럽트 서비스 루틴을 실행시킨다. 

     

    : 트랩 게이트와 인터럽트 게이트를 나누는 두 번째 기준은 다음과 같다.

    " 인터럽트가 발생했을 때, 또 다른 인터럽트를 허용할 것이냐 안 할 것이냐

    : 트랩 게이트는 중첩 인터럽트를 허용한다. 그러나 인터럽트 게이트는 허용하지 않는다. 즉, 인터럽트 게이트는 인터럽트가 발생하면 자동으로 인터럽트가 비활성화되고 `IRET` 명령어를 통해서 재활성화가 되지만, 트랩 게이트는 그렇지 않다.

     

     

    '프로젝트 > 운영체제 만들기' 카테고리의 다른 글

    세그먼테이션  (0) 2023.06.07
    Higher Half Kernel  (0) 2023.06.06
    권한  (0) 2023.06.05
    C 런타임  (2) 2023.06.04
    크로스 컴파일[작성중]  (0) 2023.06.03
Designed by Tistory.