향상된 프로그램 가능한 인터럽트 컨트롤러

CPU가 하나라면 마스터 PIC의 출력선을 곧바로 CPU의 INTR핀으로 연결하면 된다. 그렇지만 2개 이상이 된다면 복잡한 PIC를 필요로 한다.

SMP 구조의 병렬성을 최대한 활용하려면 시스템에 있는 모든 CPU로 인터럽트를 전달해야 한다. 인텔은 펜티엄3 부터 APIC(Advanced Programableprogrammable interrupt controller)라는 구성 요소를 도입했다. 이칩은 8259A의 발전된 버전이다. 현재 출시되는 모든 80x86 프로세서에는 지역 APIC가 들어있다. 지역 APIC는 32비트 레지스터와 내부 클록, 지역 타이머 장치, 지역 APIC용으로 예약된 두 개의 부가적인 IRQ선 LINT0와 LINT1을 갖는다.

CPU내부에 있는 모든 지역 APIC를 외부에 있는 입출력 APIC로 연결하여 다중 APIC 시스템을 구성한다.

APIC버스는 앞단의 입출력 APIC와 지역 APIC를 연결한다. 장치로부터 나온 IRQ선을 입출력 APIC로 연결되며, 입출력 APIC는 지역 APIC 입장에서 보면 라우터역할을 한다. APIC 버스는 펜티업 3나 그 이전 프로세서를 탑재한 보드에서는 직렬로된 3개의 선으로 이루어진다. 펜티엄4 부터는 시스템 버스를 이용하여 APIC 버스를 구현한다.

입출력 APIC는 IRQ 2개의 선과 인터럽트 재정의 테이블(Interrupt Redirection Table) 엔트리 24개, 프로그램 가능한 레지스터, APIC 버스를 통하여 APIC 메시지를 보내고 받을 수 있는 메시지 유닛(Message Unit)으로 구성 된다. 8259A의 IRQ핀과는 달리 핀 번호와 인터럽트 우선순위는 아무런 관계가 없다. 재지정 테이블에 있는 항목을 프로그램하여 개별적으로 인터럽트 벡터와 우선순위 목적지 프로세서, 프로세서 선택 방법을 지정할 수 있다. 재지정 테이블에 있는 정보를 사용하여 각 외부 IRQ신호를 APIC 버스를 통해 지역 APIC(들)로 전달하는 메시지로 변환한다.

외부 하드웨어 장치에서 오는 인터럽트 요청을 사용가능한 CPU 사이에서 분배하는 방법은 2가지이다.

  • 정적 분배 : IRQ 신호를 해당 재지정 테이블을 항목에서 지정하고 있는 지역 APIC로 전달한다. 한 번에 지정한 CPU(들)로 인터럽트를 전달한다.

  • 동적 분배 : 인터럽트 신호를 우선순위가 가장 낮은 프로세스를 실행하는 프로세서의 지역 APIC로 전달한다.

다중 APIC 시스템은 프로세서 사이에서 인터럽트를 분배하는 것 외에도 CPU가 프로세서 간 인터럽트(IPI, Interprocessor Interrrupt)를 발생킬 수 있게 한다. 다른 CPU로 인터럽트를 보내고 싶을 때 지역 APIC에 있는 명령 인터럽트 레지스터(ICR, Interrupt Command Register)에 인터럽트 벡터와 대상 CPU에 있는 지역 APIC의 식별자를 기록한다.

그러면 APIC 버스를 통해 목적지인 지역 APIC로 메시지가 전달되어 인터럽트가 발생한다.

IPI는 SMP 아키텍처의 중요한 일부로 리눅스 CPU 사이에서 메시지를 교환할때 이를 활발히 사용한다.

많은 유니프로세서 시스템을 입출력 APIC칩을 내장하고 있으며 2가지의 방식을 설정할 수 있다.

  • 표준 입출력 APIC : 지역 APIC를 켜고 입출력 APIC를 통해 외부 인터럽트를 수신한다.
  • CPU에 연결된 표준 8259A 유형의 외부 PIC로 설정 : 지역 APIC를 끄고 두 개의 지역 IRQ선인 LINT0, LINT1을 각각 INTR과 NMI핀으로 설정한다.

예외

80x86 마이크로프로세서는 대약 20여개의 예외를 발생시킨다. 커널은 각 예외 유형에 따라 전용 예외 핸들러를 제공해야 한다.

#예외예외 핸들러시그널
0나누기 에러devide_error()SIGFPE
1디버그debug()SIGTRAP
2NMIni()없음
3중단점int3()SIGTRAP
4오버플로두overflow()SIGEGV
5범위 검사bounds()SIGSEGV
6잘못된 연산 코드invalid_op()SIGILL
7장치를 사용할 수 없음divice_not_available()없음
8이중 폴트doublefault_fn()없음
9보조 프로세서 세그먼트 범람coprocessor_segment_overrun()SIGFPE
10잘못된 TSSinvalid_TSS()SIGSEGV
11존재하지 않는 세그먼트segment_not_present()SIGBUS
12스택 세그먼트 폴트stack_segment()SIGBUS
13일반 보호general_protection()SIGSEGV
14페이지 폴트page_fault()SIGSEGV
15인텔이 예약해둠없음없음
16부동 소수점 에러coprecessor_error()SIGFPE
17정렬 검사alignment_check()SIGBUS
18기계 검사machine_check()없음
19SIMD 부동 소수점 예외simd_coprocessor_error()SIGFPE
  1. 나누기 에러(Devide Error) : 폴트
  • 0으로 나눌 때 발생
  1. 디버그 : 트랩 또는 폴트
  • eflags의 T 플래그를 설정한 때나 명령어 주소나 피연산자가 활성화된 디버그 레지스터에서 지정한 범위 안에 들어갈 때 발생한다.
  1. 사용하지 않음
  • 마스크 불가능한 인터럽트(NMI 핀을 사용하는 인터럽트)용으로 예약하고 있다.
  1. 중단점 : 트랩
  • int3(중단점) 명령어(보통 디버거가 삽입한다)에 의해 발생한다.
  1. 오버플로우 : 트랩
  • into(오버플로우 검사) 명령어를 실행할 때 eflags의 OF(오버플로우) 플래그가 설정되어 있는 경우에 발생한다.
  1. 범위 검사 : 폴트
  • bound(주소 범위 검사) 명령어를 실행할 때 지정한 피연산자가 유효한 주소 범위를 벗어나 있는 경우에 발생
  1. 잘못된 연산 코드 :폴트
  • CPU 실행 유닛이 잘못된 OP code를 감지한 경우 발생
  1. 장치를 사용할 수 없음 : 폴트
  • cr0의 TS 플래그를 설정한 상태에서 확장 명령어나 MMX, XMM 명령어를 실행한 경우
  1. 이중 폴트 : 중단
  • CPU가 이전에 발생한 예외를 처리하는 핸들러를 호출하는 과정에서 예외가 다시 발생한다. 보통은 예외 두 개를 연속해서 처리할 수 있지만 종종 연속적으로 처리할 수 없는 경우가 있는데 이 때 발생
  1. 보조 프로세서 세그먼트 범람 : 중단
  • 외부 수학 보조 프로세서에 문제가 발생한 경우(예전 80386 마이크로프로세서에만 해당)
  1. 잘못된 TSS : 폴트
  • CPU가 잘못된 작업 상태 세그먼트를 가진 프로세스로 전화하려 할 때 발생
  1. 존재하지 않는 세그먼트 : 폴트
  • 메모리에 존재하지 않는 세그먼트(세그먼트 디스크립터의 Segment-Present 플래그가 0인 세그먼트)를 참조하는 경우
  1. 스택 세그먼트 폴트 : 폴트
  • 명령어가 스택 세그먼트의 제한을 넘어서려고 했거나 ss가 가르키는 세그먼트가 메모리에 존재하지 않는 경우 발생
  1. 일반 보호 : 폴트
  • 80x86의 보호 모드에 있는 보호 규칙 중 하나를 위반한 경우
  1. 페이지 폴트 : 폴트
  • 주소로 참조한 페이지가 메모리에 존재하지 않거나 해당 페이지 테이블 엔트리가 비어 있거나 페이징 보호 메커니즘을 위반한 경우
  1. 인텔이 예약해둠
  2. 부동 소수점 에러 : 폴트
  • 부동 소수점 유닛에서 숫자 오버플로우나 0으로 나누기 같은 에러 상황
  1. 정렬 검사 : 폴트
  • 피연선자의 주소가 올바로 정렬되어 있지 않은 경우(ex. long 데이터 타입 정수의 주소가 4의 배수가 아닌 경우)
  1. 기계 검사 : 중단
  • 기계 검사 메커니즘이 CPU나 버스 에러를 감지한 경우이다
  1. SIMD 부동 소수점 예외
  • CPU칩에 들어있는 SSE나 SSE2 유닛에서 부동 소수점을 연산할 때 에러 상황이 발생하여 이를 알려준 경우

인터럽트 디스크립터 테이블(Interrupt Descriptor Table)

이 시스템 테이블은 각 인터럽트와 인터럽트 핸들러 주소, 예외 벡터와 예외 핸들러의 주소를 연결한다. 커널은 인터럽트를 허용하기 전에 IDT를 올바르게 초기화해야 한다.

IDT의 각 엔트리는 8바이트 이다.

idtr CPU 레지스터가 있어 IDT를 메모리의 어느 위치에나 둘 수 있다. idtr 레지스터에는 IDT의 기본 물리 주소와 범위가 들어 있다. 인터럽트를 허용하기 전에 lidt 어셈블리 명령어를 사용해 초기화 해야 한다.

IDT에는 세 종류의 디스크립터가 들어 갈 수 있다. Fig

40~43비트에 있는 Type 필드가 디스크립터의 종류를 구별한다.

  • 태스크 게이트(Task Gate)
  • 인터럽트 신호가 발생할 때 현재 프로세스를 대체할 프로세스의 TSS 셀렉터를 가진다. 리눅스에서는 사용하지 않는다.
  • 인터럽트 게이트(Interrupt Gate)
  • 인터럽트나 예외 핸들러의 세그먼트 셀렉터와 세그먼트 내 오프셋을 가진다. 해당 세그먼트로 제어를 넘길 때 프로세서는 IF 플래그를 0으로 만들어 마스크 가능한 인터럽트가 더는 발생하지 않게 한다
  • 트랩 게이트(Trap Gate)
  • 인터럽트 게이트와 비슷하지만 IF 플래그를 바꾸지 않는다.

리눅스는 인터럽틑 처리할 때는 인터럽트 게이트, 예외를 처리할 때는 트랩 게이트를 사용한다.