APIC 심화 (Advanced Programmable Interrupt Controller)
x86 시스템의 인터럽트 라우팅 핵심인 APIC 아키텍처를 종합적으로 다룹니다.
8259A PIC에서 APIC으로의 진화, LAPIC 내부 레지스터, I/O APIC 리다이렉션 테이블,
x2APIC MSR 인터페이스, APIC Timer, MSI/MSI-X, Intel APICv와 AMD AVIC 가상화 지원,
커널 struct apic 서브시스템까지 전체 경로를 심층 분석합니다.
핵심 요약
- APIC은 x86의 인터럽트 컨트롤러로, 각 CPU마다 LAPIC(Local APIC)이 내장되고, 외부 장치는 I/O APIC을 통해 인터럽트를 전달합니다.
- xAPIC은 MMIO(
0xFEE00000), x2APIC은 MSR로 레지스터에 접근하며, x2APIC은 32비트 APIC ID와 단일 MSR ICR 쓰기를 지원합니다. - MSI/MSI-X는 I/O APIC을 우회하여 PCIe 장치가 메모리 쓰기로 직접 LAPIC에 인터럽트를 전달하는 현대적 방식입니다.
- Intel APICv와 AMD AVIC은 가상 머신의 인터럽트 처리를 하드웨어로 가속하여 VM Exit 오버헤드를 제거합니다.
- 커널은
struct apic드라이버 모델로 다양한 APIC 모드(flat, cluster, x2apic 등)를 추상화합니다.
단계별 이해
- 8259A → APIC 진화 (아래) — 왜 APIC이 필요한지 이해
- APIC 시스템 아키텍처 — 전체 그림: LAPIC, I/O APIC, 시스템 버스
- LAPIC 내부 구조 — 핵심 레지스터(IRR, ISR, TPR, LVT) 학습
- I/O APIC — 외부 장치 인터럽트가 CPU에 도달하는 경로
- 인터럽트 전달 흐름 — Device → I/O APIC → LAPIC → IDT 전체 과정
- x2APIC — 현대 시스템의 MSR 기반 고속 인터페이스
- 이후 APIC Timer, MSI/MSI-X, APICv/AVIC 등은 필요에 따라 학습
8259A → APIC 진화
초기 IBM PC/AT는 Intel 8259A PIC(Programmable Interrupt Controller) 2개를 캐스케이드하여 최대 15개의 IRQ 라인을 지원했습니다. 이 구조는 단일 프로세서 시스템에서는 충분했지만, 멀티프로세서 시스템에서는 근본적인 한계가 있었습니다.
| 특성 | 8259A PIC | APIC |
|---|---|---|
| IRQ 라인 수 | 15개 (마스터 8 + 슬레이브 7) | I/O APIC당 24+, MSI로 사실상 무제한 |
| CPU 지원 | 단일 CPU만 | 멀티프로세서 (APIC ID로 라우팅) |
| IPI 지원 | 불가 | ICR을 통한 프로세서 간 인터럽트 |
| 우선순위 | 고정 (IRQ 0 최고) | 벡터 기반 프로그래머블 우선순위 |
| 인터페이스 | I/O 포트 (0x20, 0xA0) | MMIO(xAPIC) 또는 MSR(x2APIC) |
| 타이머 | 별도 8254 PIT 필요 | LAPIC 내장 타이머 (TSC-Deadline 지원) |
| MSI 지원 | 불가 | 메모리 쓰기 기반 MSI/MSI-X |
8259A PIC 구조
8259A 초기화 코드 (커널 레거시 경로)
/* arch/x86/kernel/i8259.c - 8259A PIC 초기화 */
static void init_8259A(int auto_eoi)
{
/* ICW1: 초기화 시작, ICW4 필요 */
outb(0x11, PIC_MASTER_CMD);
outb(0x11, PIC_SLAVE_CMD);
/* ICW2: 벡터 오프셋 (IRQ 0→벡터 0x20, IRQ 8→벡터 0x28) */
outb(0x20, PIC_MASTER_IMR); /* 마스터: 벡터 32~39 */
outb(0x28, PIC_SLAVE_IMR); /* 슬레이브: 벡터 40~47 */
/* ICW3: 캐스케이드 설정 */
outb(0x04, PIC_MASTER_IMR); /* 마스터 IRQ2에 슬레이브 */
outb(0x02, PIC_SLAVE_IMR); /* 슬레이브 ID = 2 */
/* ICW4: 8086 모드, auto-EOI 옵션 */
if (auto_eoi)
outb(MASTER_ICW4_DEFAULT | PIC_ICW4_AEOI, PIC_MASTER_IMR);
else
outb(MASTER_ICW4_DEFAULT, PIC_MASTER_IMR);
outb(SLAVE_ICW4_DEFAULT, PIC_SLAVE_IMR);
/* 모든 IRQ 마스크 (APIC 전환 전) */
outb(0xff, PIC_MASTER_IMR);
outb(0xff, PIC_SLAVE_IMR);
}
APIC 전환 과정 (부트 시퀀스)
/* arch/x86/kernel/apic/apic.c - APIC 초기 설정 흐름
*
* 부트 순서:
* 1. BIOS가 8259A PIC 모드로 시작
* 2. 커널이 APIC 하드웨어 감지 (CPUID)
* 3. 8259A를 마스크하고 APIC으로 전환
* 4. x2APIC 지원 시 MSR 모드로 업그레이드
*/
void __init init_bsp_APIC(void)
{
unsigned int value;
/* APIC이 비활성화 상태면 건너뜀 */
if (!boot_cpu_has(X86_FEATURE_APIC))
return;
/* APIC 기본 주소 확인 (MSR 0x1B) */
value = apic_read(APIC_SPIV);
/* Spurious Vector Register: APIC 활성화 + 벡터 0xFF */
value &= ~APIC_VECTOR_MASK;
value |= APIC_SPIV_APIC_ENABLED;
value |= APIC_SPIV_FOCUS_DISABLED;
value |= SPURIOUS_APIC_VECTOR; /* 벡터 0xFF */
apic_write(APIC_SPIV, value);
}
APIC 감지 및 모드 결정
/* CPUID를 통한 APIC 기능 감지 */
static bool detect_apic_mode(void)
{
u32 eax, ebx, ecx, edx;
/* CPUID.01H: EDX[9] = APIC 존재 여부 */
cpuid(0x01, &eax, &ebx, &ecx, &edx);
if (!(edx & (1 << 9))) {
pr_err("APIC not present\n");
return false;
}
/* ECX[21] = x2APIC 지원 여부 */
if (ecx & (1 << 21))
pr_info("x2APIC supported\n");
/* IA32_APIC_BASE MSR (0x1B):
* [11] = APIC Global Enable
* [10] = x2APIC Enable
* [8] = BSP Flag
* [12:N] = APIC Base Address (기본 0xFEE00000)
*/
u64 msr = rdmsrl(MSR_IA32_APICBASE);
pr_info("APIC Base: %llx, BSP: %d, Enabled: %d, x2APIC: %d\n",
msr & 0xFFFFF000ULL,
!!(msr & (1 << 8)),
!!(msr & (1 << 11)),
!!(msr & (1 << 10)));
return true;
}
APIC 시스템 아키텍처
APIC 시스템은 LAPIC(Local APIC, 각 CPU 코어에 내장)과 I/O APIC(칩셋에 위치, 외부 장치 인터럽트 수집)으로 구성됩니다. 인터럽트는 시스템 버스(또는 ICC Bus)를 통해 I/O APIC에서 적절한 LAPIC으로 전달됩니다. 현대 시스템에서는 MSI/MSI-X를 통해 I/O APIC을 우회하는 경로도 있습니다.
APIC 기본 주소 (IA32_APIC_BASE MSR)
| 비트 | 필드 | 설명 |
|---|---|---|
| [7:0] | Reserved | 예약 |
| [8] | BSP | Bootstrap Processor 플래그 (읽기 전용) |
| [9] | Reserved | 예약 |
| [10] | EXTD | x2APIC 모드 활성화 (EN=1일 때만 유효) |
| [11] | EN | APIC 글로벌 활성화 |
| [N:12] | APIC Base | LAPIC 레지스터 기본 물리 주소 (기본: 0xFEE00000) |
/* IA32_APIC_BASE MSR 읽기/쓰기 */
static u64 read_apic_base(void)
{
return rdmsrl(MSR_IA32_APICBASE); /* MSR 0x1B */
}
/* xAPIC → x2APIC 전환:
* 1) EN=1, EXTD=0 (xAPIC)
* 2) EN=1, EXTD=1 (x2APIC)
* 주의: EN=0으로 비활성화 후 재활성화 시 RESET 필요 */
static void enable_x2apic_mode(void)
{
u64 msr = rdmsrl(MSR_IA32_APICBASE);
msr |= X2APIC_ENABLE; /* bit[10] = 1 */
wrmsrl(MSR_IA32_APICBASE, msr);
}
LAPIC 내부 구조
LAPIC(Local APIC)은 각 프로세서 코어에 내장된 인터럽트 컨트롤러입니다.
인터럽트 수신, 우선순위 판단, 벡터 전달, IPI 발신, 로컬 타이머 등 핵심 기능을 담당합니다.
xAPIC 모드에서는 4KB MMIO 공간(0xFEE00000)으로 매핑되고,
x2APIC 모드에서는 MSR로 접근합니다.
LAPIC 레지스터 맵 (주요 항목)
| 오프셋 | MSR (x2APIC) | 이름 | 읽기/쓰기 | 설명 |
|---|---|---|---|---|
| 0x020 | 0x802 | APIC ID | R/W | 프로세서 고유 ID (x2APIC에서 32비트) |
| 0x030 | 0x803 | Version | R | APIC 버전, Max LVT Entry |
| 0x080 | 0x808 | TPR | R/W | Task Priority Register |
| 0x090 | 0x809 | APR | R | Arbitration Priority Register |
| 0x0A0 | 0x80A | PPR | R | Processor Priority Register |
| 0x0B0 | 0x80B | EOI | W | End of Interrupt |
| 0x0C0 | 0x80C | RRD | R | Remote Read Register |
| 0x0D0 | 0x80D | LDR | R/W | Logical Destination Register |
| 0x0E0 | - | DFR | R/W | Destination Format Register (xAPIC 전용) |
| 0x0F0 | 0x80F | SVR | R/W | Spurious Interrupt Vector Register |
| 0x100~0x170 | 0x810~0x817 | ISR | R | In-Service Register (256-bit, 8x32) |
| 0x180~0x1F0 | 0x818~0x81F | TMR | R | Trigger Mode Register (256-bit) |
| 0x200~0x270 | 0x820~0x827 | IRR | R | Interrupt Request Register (256-bit) |
| 0x280 | 0x828 | ESR | R | Error Status Register |
| 0x300 | 0x830 | ICR (lo) | R/W | Interrupt Command Register Low |
| 0x310 | - | ICR (hi) | R/W | Interrupt Command Register High (xAPIC) |
| 0x320 | 0x832 | LVT Timer | R/W | 로컬 타이머 벡터/모드 |
| 0x330 | 0x833 | LVT Thermal | R/W | 온도 모니터 인터럽트 |
| 0x340 | 0x834 | LVT PMC | R/W | 성능 모니터 카운터 오버플로우 |
| 0x350 | 0x835 | LVT LINT0 | R/W | 로컬 인터럽트 0 (보통 ExtINT) |
| 0x360 | 0x836 | LVT LINT1 | R/W | 로컬 인터럽트 1 (보통 NMI) |
| 0x370 | 0x837 | LVT Error | R/W | APIC 내부 오류 인터럽트 |
| 0x380 | 0x838 | Timer ICR | R/W | 타이머 초기 카운트 |
| 0x390 | 0x839 | Timer CCR | R | 타이머 현재 카운트 |
| 0x3E0 | 0x83E | Timer DCR | R/W | 타이머 분주비 설정 |
| - | 0x83F | Self IPI | W | x2APIC Self IPI (벡터만 지정) |
IRR/ISR/TMR 256-bit 비트 벡터
/* IRR, ISR, TMR은 각각 256비트(8개 32비트 레지스터)로 구성
* 벡터 N에 대응하는 비트: 레지스터[N/32]의 비트[N%32]
*
* 벡터 범위:
* 0~15: 예약 (x86 예외)
* 16~31: 예약
* 32~255: 사용 가능 (리눅스: 32~0xEF 일반, 0xF0~0xFF 특수)
*/
/* IRR 읽기 예시 */
static u32 read_irr_vector_range(int reg_index)
{
/* reg_index: 0~7, 각 32비트 */
return apic_read(APIC_IRR + reg_index * 0x10);
}
/* 모든 펜딩 인터럽트 확인 */
static void dump_irr(void)
{
int i;
pr_info("IRR (Interrupt Request Register):\n");
for (i = 7; i >= 0; i--) {
u32 val = apic_read(APIC_IRR + i * 0x10);
if (val)
pr_info(" IRR[%d] (vec %d-%d): 0x%08x\n",
i, i * 32, i * 32 + 31, val);
}
}
TPR/PPR 우선순위 계산
/* TPR (Task Priority Register):
* [7:4] = Task Priority Class
* [3:0] = Task Priority Sub-Class
*
* PPR (Processor Priority Register):
* PPR = max(TPR, ISRV)
* 여기서 ISRV = ISR에서 가장 높은 비트의 벡터 값
*
* 인터럽트 수신 조건:
* 새 벡터의 [7:4] > PPR[7:4] 일 때만 수신
* 같은 우선순위 클래스 내에서는 차단됨
*/
/* TPR 설정으로 저우선순위 인터럽트 차단 */
static void set_task_priority(u8 priority_class)
{
/* priority_class: 0~15 (0이면 모든 인터럽트 수신) */
apic_write(APIC_TASKPRI, priority_class << 4);
}
/* cr8 레지스터: 64비트 모드에서 TPR[7:4]의 바로가기
* mov cr8, rax 는 TPR[7:4] = rax[3:0] 과 동일 */
static inline void write_cr8(u64 val)
{
asm volatile("mov %0, %%cr8" :: "r"(val));
}
LVT 엔트리 포맷
| 비트 | 필드 | 설명 |
|---|---|---|
| [7:0] | Vector | 인터럽트 벡터 (16~255) |
| [10:8] | Delivery Mode | 000=Fixed, 010=SMI, 100=NMI, 111=ExtINT |
| [12] | Delivery Status | 0=Idle, 1=Send Pending |
| [13] | Pin Polarity | 0=Active High, 1=Active Low (LINT만) |
| [14] | Remote IRR | Level 트리거 시 EOI 대기 상태 (읽기 전용) |
| [15] | Trigger Mode | 0=Edge, 1=Level (LINT만) |
| [16] | Mask | 1=마스크(비활성화) |
| [18:17] | Timer Mode | 00=One-shot, 01=Periodic, 10=TSC-Deadline |
/* LVT 설정 예시: LINT0을 ExtINT, LINT1을 NMI로 설정 */
static void setup_lapic_lvt(void)
{
/* LINT0: External Interrupt (8259A 호환) */
apic_write(APIC_LVT0,
APIC_DM_EXTINT | APIC_LVT_LEVEL_TRIGGER);
/* LINT1: NMI */
apic_write(APIC_LVT1,
APIC_DM_NMI);
/* Error: 벡터 0xFE로 에러 인터럽트 */
apic_write(APIC_LVTERR, ERROR_APIC_VECTOR);
/* Thermal Monitor: 벡터 지정 */
apic_write(APIC_LVTTHMR,
THERMAL_APIC_VECTOR | APIC_DM_FIXED);
/* Performance Counter: 벡터 지정 */
apic_write(APIC_LVTPC,
LOCAL_PERF_VECTOR | APIC_DM_FIXED);
}
EOI 처리 메커니즘
EOI(End of Interrupt)는 인터럽트 핸들러 완료를 LAPIC에 알리는 핵심 동작입니다. EOI 레지스터에 0을 쓰면 ISR에서 가장 높은 우선순위 비트가 클리어됩니다. Level 트리거 인터럽트의 경우 I/O APIC의 Remote IRR 비트도 클리어되어야 합니다.
EOI 흐름
| 단계 | 동작 | 설명 |
|---|---|---|
| 1 | EOI 레지스터 쓰기 | 인터럽트 핸들러 종료 시 apic_write(APIC_EOI, 0) |
| 2 | ISR 비트 클리어 | ISR에서 현재 서비스 중인 최고 우선순위 벡터 비트 클리어 |
| 3a | Edge 트리거 | 추가 작업 없음 (완료) |
| 3b | Level 트리거 | TMR 비트가 1이면 I/O APIC에 EOI 브로드캐스트 |
| 4 | I/O APIC Remote IRR 클리어 | 해당 RTE의 Remote IRR 비트 클리어 → 같은 핀에서 새 인터럽트 수신 가능 |
/* EOI 전송 - 리눅스 커널 내부 */
static inline void ack_APIC_irq(void)
{
/* EOI 레지스터에 0을 쓰면 ISR에서 최고 비트 클리어
* x2APIC에서는 MSR 0x80B에 wrmsr */
apic_write(APIC_EOI, 0);
}
/* Level 트리거 EOI: I/O APIC의 Remote IRR 클리어 필요
* Directed EOI (SVR[12]=1): 브로드캐스트 대신 특정 벡터만 EOI */
static void eoi_ioapic_pin(unsigned int apic, unsigned int pin)
{
/* I/O APIC EOI 레지스터에 벡터 번호 쓰기 (I/O APIC v2+) */
io_apic_eoi(apic, pin);
}
/* 커널의 irq_chip.eoi 콜백 */
static void apic_ack_edge(struct irq_data *data)
{
irq_complete_move(data);
irq_move_irq(data);
ack_APIC_irq(); /* Edge: LAPIC EOI만으로 충분 */
}
static void apic_ack_level(struct irq_data *data)
{
struct irq_cfg *cfg = irqd_cfg(data);
irq_complete_move(data);
irq_move_irq(data);
ack_APIC_irq(); /* ISR 클리어 */
/* Level: I/O APIC Remote IRR 확인 및 클리어 */
if (irqd_is_level_type(data))
eoi_ioapic_pin(cfg->apic, cfg->pin);
}
Suppress EOI Broadcast)를 설정하면, Level 트리거 EOI 시
시스템 버스를 통한 브로드캐스트 대신 소프트웨어가 직접 I/O APIC EOI 레지스터에 쓰게 됩니다.
이는 멀티 I/O APIC 시스템에서 불필요한 EOI 메시지를 줄여 성능을 향상시킵니다.
리눅스 커널은 이 기능을 기본으로 활성화합니다 (enable_directed_EOI()).
I/O APIC
I/O APIC(I/O Advanced Programmable Interrupt Controller)은 칩셋(보통 사우스브리지 또는 PCH)에 위치하며, 외부 장치의 인터럽트를 수집하여 시스템 버스를 통해 적절한 LAPIC으로 전달합니다. 주요 기능은 Redirection Table Entry(RTE)를 통한 인터럽트 라우팅입니다.
I/O APIC 레지스터 접근
I/O APIC은 MMIO 기반으로 접근하며, 기본 주소는 0xFEC00000입니다.
간접 주소 방식(Index/Data)을 사용합니다.
| MMIO 오프셋 | 이름 | 설명 |
|---|---|---|
| 0x00 | IOREGSEL | Index Register - 접근할 레지스터 번호 지정 |
| 0x10 | IOWIN | Data Register - 선택된 레지스터의 데이터 읽기/쓰기 |
| 0x40 | IOEOI | EOI Register (I/O APIC v2+) - Level 트리거 EOI |
| 인덱스 | 이름 | 설명 |
|---|---|---|
| 0x00 | IOAPICID | I/O APIC 식별자 |
| 0x01 | IOAPICVER | 버전 + 최대 RTE 수 |
| 0x02 | IOAPICARB | 중재 ID |
| 0x10~0x3F | IOREDTBL[0:23] | Redirection Table (64-bit 엔트리 x 24) |
I/O APIC 프로그래밍
/* I/O APIC 간접 접근 매크로 */
static inline u32 io_apic_read(unsigned int apic, unsigned int reg)
{
struct io_apic __iomem *io = io_apic_base(apic);
writel(reg, &io->index); /* IOREGSEL에 레지스터 번호 */
return readl(&io->data); /* IOWIN에서 데이터 읽기 */
}
static inline void io_apic_write(unsigned int apic, unsigned int reg, u32 val)
{
struct io_apic __iomem *io = io_apic_base(apic);
writel(reg, &io->index);
writel(val, &io->data);
}
/* RTE 읽기 (64비트: low + high) */
static void ioapic_read_entry(int apic, int pin,
struct IO_APIC_route_entry *e)
{
unsigned long flags;
raw_spin_lock_irqsave(&ioapic_lock, flags);
e->w1 = io_apic_read(apic, 0x10 + 2 * pin); /* Low 32-bit */
e->w2 = io_apic_read(apic, 0x10 + 2 * pin + 1); /* High 32-bit */
raw_spin_unlock_irqrestore(&ioapic_lock, flags);
}
/* RTE 쓰기 */
static void ioapic_write_entry(int apic, int pin,
struct IO_APIC_route_entry e)
{
unsigned long flags;
raw_spin_lock_irqsave(&ioapic_lock, flags);
/* 주의: High 먼저 쓰기 (Low 쓰기 시 인터럽트 트리거 가능) */
io_apic_write(apic, 0x10 + 2 * pin + 1, e.w2);
io_apic_write(apic, 0x10 + 2 * pin, e.w1);
raw_spin_unlock_irqrestore(&ioapic_lock, flags);
}
ACPI MADT를 통한 I/O APIC 감지
/* ACPI MADT (Multiple APIC Description Table)에서 I/O APIC 정보 파싱
*
* MADT Entry Type 1: I/O APIC
* - I/O APIC ID
* - I/O APIC Address (기본 0xFEC00000)
* - Global System Interrupt Base
*
* MADT Entry Type 2: Interrupt Source Override
* - ISA IRQ → GSI 매핑 (예: IRQ 0 → GSI 2 타이머 리다이렉트)
*/
static int __init acpi_parse_ioapic(
union acpi_subtable_headers *header,
const unsigned long end)
{
struct acpi_madt_io_apic *ioapic =
(struct acpi_madt_io_apic *)header;
pr_info("I/O APIC: id=%d addr=0x%x gsi_base=%d\n",
ioapic->id, ioapic->address,
ioapic->global_irq_base);
mp_register_ioapic(ioapic->id, ioapic->address,
ioapic->global_irq_base, NULL);
return 0;
}
I/O APIC 핀-GSI-벡터 매핑
I/O APIC의 핀 번호, GSI(Global System Interrupt), 리눅스 IRQ 번호, 그리고 실제 CPU 벡터 사이의 매핑을 이해하는 것이 인터럽트 라우팅 디버깅의 핵심입니다.
| 용어 | 범위 | 설명 |
|---|---|---|
| I/O APIC Pin | 0~23 (per I/O APIC) | 물리적 입력 핀 번호. RTE 인덱스와 동일 |
| GSI (Global System Interrupt) | 0~N (시스템 전체) | ACPI 표준 인터럽트 번호. I/O APIC의 base + pin으로 계산 |
| Linux IRQ | 가상 번호 | 커널 내부 irq_desc 배열 인덱스. IRQ 도메인이 GSI에서 매핑 |
| CPU Vector | 32~255 | 실제 IDT 엔트리. per-CPU 동적 할당 (같은 벡터가 다른 CPU에서 다른 IRQ 처리 가능) |
/* I/O APIC 핀 → GSI 변환 */
static unsigned int pin_to_gsi(int ioapic_idx, int pin)
{
return mp_ioapic_gsi_routing(ioapic_idx)->gsi_base + pin;
}
/* ISA IRQ → GSI 리다이렉트 (ACPI Interrupt Source Override)
* 일반적인 리다이렉트:
* ISA IRQ 0 (PIT Timer) → GSI 2 (I/O APIC pin 2)
* ISA IRQ 9 (ACPI SCI) → GSI 9 (Level, Low)
*/
static int __init acpi_parse_int_src_ovr(
union acpi_subtable_headers *header,
const unsigned long end)
{
struct acpi_madt_interrupt_override *ovr =
(struct acpi_madt_interrupt_override *)header;
pr_info("INT_SRC_OVR: bus=%d src_irq=%d gsi=%d flags=0x%x\n",
ovr->bus, ovr->source_irq, ovr->global_irq, ovr->inti_flags);
/* flags[1:0]: Polarity (00=Bus default, 01=Active High, 11=Active Low)
* flags[3:2]: Trigger (00=Bus default, 01=Edge, 11=Level) */
mp_override_legacy_irq(ovr->source_irq, ovr->inti_flags,
ovr->global_irq);
return 0;
}
I/O APIC RTE 설정 예시
/* 특정 핀에 대한 완전한 RTE 설정 예시 */
static void setup_ioapic_irq(int apic_id, int pin,
int vector, int dest_apicid)
{
struct IO_APIC_route_entry entry;
memset(&entry, 0, sizeof(entry));
/* Delivery Mode: Fixed (000) */
entry.delivery_mode = APIC_DELIVERY_MODE_FIXED;
/* Destination Mode: Physical (단일 CPU 지정) */
entry.dest_mode_logical = 0;
/* Trigger Mode: Edge (대부분의 ISA 장치) */
entry.is_level = 0;
/* Pin Polarity: Active High (ISA 기본) */
entry.active_low = 0;
/* Mask: 활성화 (0 = unmask) */
entry.masked = 0;
/* Vector 및 Destination 설정 */
entry.vector = vector;
entry.destid_0_7 = dest_apicid & 0xFF;
/* RTE 쓰기 (High word 먼저!) */
ioapic_write_entry(apic_id, pin, entry);
}
/* PCI 인터럽트는 일반적으로 Level-triggered, Active-Low
* INTA# → 핀은 ACPI _PRT (PCI Routing Table)에서 결정
* 또는 PIRQ 라우터 (레거시 시스템)로 매핑 */
인터럽트 전달 흐름
외부 장치에서 발생한 인터럽트가 CPU의 IDT 핸들러에 도달하기까지의 전체 과정입니다. I/O APIC 경로와 MSI 경로를 모두 포함하며, LAPIC의 우선순위 판단 로직이 핵심입니다.
벡터 우선순위 규칙
| 벡터 범위 | 우선순위 클래스 | 리눅스 용도 |
|---|---|---|
| 0x00~0x1F | 0~1 | x86 예외 (예약) |
| 0x20~0x2F | 2 | ISA IRQ 레거시 매핑 |
| 0x30~0xEF | 3~14 | 장치 인터럽트, I/O APIC, MSI |
| 0xF0 | 15 | APIC Timer |
| 0xF1~0xF9 | 15 | IPI (Reschedule, Call Function 등) |
| 0xFA | 15 | IRQ Work |
| 0xFB | 15 | Thermal |
| 0xFC | 15 | Threshold APIC |
| 0xFD | 15 | CMCI |
| 0xFE | 15 | Error APIC |
| 0xFF | 15 | Spurious APIC |
/* arch/x86/include/asm/irq_vectors.h - 리눅스 벡터 할당 */
#define FIRST_EXTERNAL_VECTOR 0x20
#define FIRST_SYSTEM_VECTOR 0xF0
/* 시스템 벡터 */
#define LOCAL_TIMER_VECTOR 0xF0
#define RESCHEDULE_VECTOR 0xF1
#define CALL_FUNCTION_VECTOR 0xF2
#define CALL_FUNCTION_SINGLE_VECTOR 0xF3
#define THERMAL_APIC_VECTOR 0xFB
#define THRESHOLD_APIC_VECTOR 0xFC
#define DEFERRED_ERROR_VECTOR 0xFD
#define ERROR_APIC_VECTOR 0xFE
#define SPURIOUS_APIC_VECTOR 0xFF
/* 벡터 할당 (동적): FIRST_EXTERNAL_VECTOR ~ FIRST_SYSTEM_VECTOR-1
* 리눅스는 per-CPU 벡터 할당으로 같은 벡터를 다른 CPU에서 다른 IRQ에 사용 가능 */
x2APIC
x2APIC은 xAPIC의 확장으로, Intel Nehalem 이후 프로세서에서 지원됩니다. MMIO 대신 MSR(Model Specific Register)을 통해 LAPIC 레지스터에 접근하며, APIC ID가 8비트에서 32비트로 확장되어 이론적으로 4G 이상의 프로세서를 지원합니다.
x2APIC MSR 매핑
| xAPIC MMIO 오프셋 | x2APIC MSR | 레지스터 | 비고 |
|---|---|---|---|
| 0x020 | 0x802 | APIC ID | 32비트로 확장 (읽기 전용) |
| 0x080 | 0x808 | TPR | 동일 |
| 0x0B0 | 0x80B | EOI | 동일 (쓰기 전용) |
| 0x0D0 | 0x80D | LDR | 읽기 전용 (하드웨어가 설정) |
| 0x0E0 | - | DFR | 제거됨 (Cluster 고정) |
| 0x300+0x310 | 0x830 | ICR | 64비트 단일 MSR로 통합 |
| - | 0x83F | Self IPI | 신규 (벡터만 지정) |
x2APIC 활성화 코드
/* arch/x86/kernel/apic/apic.c - x2APIC 활성화 */
void __init enable_x2apic(void)
{
u64 msr;
/* CPUID로 x2APIC 지원 확인 */
if (!boot_cpu_has(X86_FEATURE_X2APIC)) {
pr_info("x2APIC not supported\n");
return;
}
/* Interrupt Remapping (VT-d/IOMMU) 필수 확인
* x2APIC은 32-bit destination을 사용하므로
* IR 없이는 보안 위험 (임의 CPU에 인터럽트 주입 가능) */
if (!x2apic_supported_by_iommu()) {
pr_warn("x2APIC requires Interrupt Remapping\n");
return;
}
/* IA32_APIC_BASE MSR에서 x2APIC 활성화 */
msr = rdmsrl(MSR_IA32_APICBASE);
if (!(msr & X2APIC_ENABLE)) {
msr |= X2APIC_ENABLE; /* bit[10] = 1 */
wrmsrl(MSR_IA32_APICBASE, msr);
}
pr_info("x2APIC enabled, APIC ID: %u\n",
native_apic_msr_read(APIC_ID));
}
/* x2APIC Cluster 모드에서의 LDR 값
* LDR = (Cluster ID << 16) | (1 << (APIC_ID % 16))
* 하드웨어가 자동 설정 (소프트웨어 수정 불가) */
x2APIC Cluster 모드 상세
x2APIC의 Cluster 모드는 대규모 NUMA 시스템에서 효율적인 인터럽트 라우팅을 제공합니다. 32비트 APIC ID가 Cluster ID(상위 16비트)와 Logical ID(하위 16비트)로 분할됩니다.
| 구성요소 | 비트 | 설명 |
|---|---|---|
| Cluster ID | [31:16] | 클러스터 번호 (보통 NUMA 노드에 대응) |
| Logical ID | [15:0] | 클러스터 내 비트맵 (최대 16 CPU per cluster) |
/* x2APIC Cluster 모드: IPI 전송 최적화
*
* Cluster 모드에서 같은 클러스터 내 여러 CPU에 IPI를 보낼 때
* 단일 ICR 쓰기로 비트맵 OR 전송 가능 (브로드캐스트 최적화)
*
* 예: Cluster 0의 CPU 0,1,3에 IPI 전송
* Destination = (0 << 16) | (1<<0 | 1<<1 | 1<<3) = 0x0000000B
*/
static void x2apic_send_IPI_mask_cluster(
const struct cpumask *mask, int vector)
{
unsigned int cpu, this_cluster, dest;
unsigned long flags;
local_irq_save(flags);
/* 클러스터별로 묶어서 전송 */
for_each_cpu(cpu, mask) {
u32 apicid = per_cpu(x86_cpu_to_apicid, cpu);
this_cluster = apicid >> 16;
dest = apicid; /* Cluster ID + Logical ID */
/* 같은 클러스터의 다른 CPU도 비트맵에 추가 */
/* (실제 구현은 클러스터별 그룹핑 최적화 포함) */
__x2apic_send_IPI_dest(dest, vector,
APIC_DEST_LOGICAL);
}
local_irq_restore(flags);
}
/* xAPIC → x2APIC 전환 시 주의사항:
* 1. DFR 레지스터가 제거됨 (x2APIC에서는 존재하지 않음)
* 2. LDR은 읽기 전용 (하드웨어가 APIC ID 기반으로 자동 설정)
* 3. ICR이 64비트 단일 MSR로 통합 (두 번 쓰기 불필요)
* 4. Self IPI 전용 MSR 추가 (벡터만 지정, 최소 레이턴시)
* 5. xAPIC으로 역전환 불가 (RESET 필요)
*/
x2APIC 성능 이점
| 연산 | xAPIC (MMIO) | x2APIC (MSR) | 개선 |
|---|---|---|---|
| 레지스터 읽기 | ~100 cycles (uncacheable MMIO) | ~20 cycles (RDMSR) | ~5x 빠름 |
| 레지스터 쓰기 | ~100 cycles (uncacheable MMIO) | ~20 cycles (WRMSR) | ~5x 빠름 |
| IPI 전송 (ICR) | ~200 cycles (2x MMIO 쓰기) | ~30 cycles (1x WRMSR) | ~7x 빠름 |
| Self IPI | ~200 cycles (ICR shorthand) | ~15 cycles (전용 MSR) | ~13x 빠름 |
| EOI | ~100 cycles | ~20 cycles | ~5x 빠름 |
ack_APIC_irq()는 모든 인터럽트 핸들러에서 호출되므로,
초당 수십만 회의 인터럽트를 처리하는 시스템에서 x2APIC의 5x EOI 개선은
유의미한 CPU 사이클 절감을 제공합니다.
APIC Timer
LAPIC에는 프로세서별 독립 타이머가 내장되어 있습니다. 전통적인 8254 PIT이나 HPET을 대체하며, 각 CPU 코어가 독립적인 타이머 인터럽트를 받을 수 있어 멀티프로세서 환경에서 필수적입니다. 세 가지 모드를 지원합니다: One-shot, Periodic, TSC-Deadline.
APIC Timer 설정 코드
/* One-shot 모드 설정 */
static void lapic_timer_oneshot(u32 count)
{
/* LVT Timer: One-shot 모드 + 벡터 설정 */
apic_write(APIC_LVTT, LOCAL_TIMER_VECTOR); /* [18:17]=00 */
/* 분주비 설정 (16분주) */
apic_write(APIC_TDCR, APIC_TDR_DIV_16);
/* Initial Count 쓰기 → 카운트다운 시작 */
apic_write(APIC_TMICT, count);
}
/* Periodic 모드 설정 */
static void lapic_timer_periodic(u32 count)
{
apic_write(APIC_LVTT,
LOCAL_TIMER_VECTOR | APIC_LVT_TIMER_PERIODIC); /* [18:17]=01 */
apic_write(APIC_TDCR, APIC_TDR_DIV_16);
apic_write(APIC_TMICT, count);
}
/* TSC-Deadline 모드 설정 */
static void lapic_timer_tsc_deadline(u64 deadline)
{
/* LVT Timer: TSC-Deadline 모드 */
apic_write(APIC_LVTT,
LOCAL_TIMER_VECTOR | APIC_LVT_TIMER_TSCDEADL); /* [18:17]=10 */
/* IA32_TSC_DEADLINE MSR에 목표 TSC 값 쓰기
* TSC >= deadline 시 인터럽트 발생
* 인터럽트 후 자동으로 0 리셋 (재설정 필요) */
wrmsrl(MSR_IA32_TSC_DEADLINE, deadline);
}
/* 리눅스 clockevent 연동 */
static int lapic_next_deadline(unsigned long delta,
struct clock_event_device *evt)
{
u64 tsc = rdtsc();
wrmsrl(MSR_IA32_TSC_DEADLINE, tsc + ((u64)delta));
return 0;
}
APIC Timer 캘리브레이션
One-shot/Periodic 모드에서는 APIC Timer의 클럭 속도를 먼저 측정해야 정확한 시간 간격을 설정할 수 있습니다. 커널은 부팅 시 PIT 또는 TSC를 기준으로 APIC Timer 주파수를 캘리브레이션합니다.
/* arch/x86/kernel/apic/apic.c - APIC Timer 캘리브레이션 */
static void __init calibrate_APIC_clock(void)
{
struct clock_event_device *levt = this_cpu_ptr(&lapic_events);
u64 tsc_perj = 0, tsc_start = 0;
unsigned long deltaj;
long delta, deltatsc;
int pm_referenced = 0;
/* TSC-Deadline 모드 사용 가능하면 캘리브레이션 불필요 */
if (boot_cpu_has(X86_FEATURE_TSC_DEADLINE_TIMER)) {
levt->features &= ~CLOCK_EVT_FEAT_DUMMY;
levt->features |= CLOCK_EVT_FEAT_ONESHOT;
levt->set_next_event = lapic_next_deadline;
clockevents_config_and_register(levt,
tsc_khz * 1000, 0xF, ~0UL);
return;
}
/* APIC Timer를 최대 분주비로 설정하고 알려진 기간 대기 */
apic_write(APIC_TDCR, APIC_TDR_DIV_1);
apic_write(APIC_TMICT, ~0U);
/* jiffies 기반 10ms 대기 */
deltaj = jiffies_to_msecs(1);
mdelay(deltaj);
/* 카운트다운 양으로 주파수 계산 */
delta = ~0U - apic_read(APIC_TMCCT);
/* 주파수 = delta / (deltaj_ms / 1000) */
lapic_timer_period = (delta * 1000) / deltaj;
pr_info("APIC timer calibrated: %lu.%03lu MHz\n",
lapic_timer_period / 1000000,
(lapic_timer_period / 1000) % 1000);
}
clockevent 연동 구조
/* 리눅스 clockevent 장치로 등록 */
static struct clock_event_device lapic_clockevent = {
.name = "lapic",
.features = CLOCK_EVT_FEAT_PERIODIC |
CLOCK_EVT_FEAT_ONESHOT |
CLOCK_EVT_FEAT_DUMMY,
.shift = 32,
.set_state_periodic = lapic_timer_set_periodic,
.set_state_oneshot = lapic_timer_set_oneshot,
.set_state_shutdown = lapic_timer_shutdown,
.set_next_event = lapic_next_event,
.broadcast = lapic_timer_broadcast,
.rating = 100,
.irq = -1,
};
/* One-shot 모드 진입 */
static int lapic_timer_set_oneshot(struct clock_event_device *evt)
{
apic_write(APIC_LVTT, LOCAL_TIMER_VECTOR); /* [18:17]=00 */
apic_write(APIC_TMICT, 0); /* 카운터 정지 */
return 0;
}
/* 다음 이벤트 설정 (hrtimer가 호출) */
static int lapic_next_event(unsigned long delta,
struct clock_event_device *evt)
{
apic_write(APIC_TMICT, delta);
return 0;
}
/* TSC-Deadline vs One-shot 선택 기준:
* 1. CPUID.06H:EAX[2] = 1 → TSC-Deadline 지원
* 2. TSC가 invariant (constant_tsc, nonstop_tsc)
* 3. 두 조건 모두 만족 시 TSC-Deadline 우선 사용
* 4. 미지원 시 One-shot + PIT/HPET 캘리브레이션 폴백
*/
APIC Timer와 전원 관리 상호작용
| C-State | APIC Timer 동작 | TSC-Deadline 동작 | 영향 |
|---|---|---|---|
| C1 (HALT) | 정상 카운트다운 | 정상 비교 | 없음 |
| C1E | 정상 (대부분 CPU) | 정상 | 약간의 exit latency |
| C3 (Sleep) | 정지! (APIC 클럭 중단) | TSC invariant면 정상 | One-shot 기한 초과 가능 |
| C6 (Deep Sleep) | 정지 + 리셋 가능 | TSC nonstop_tsc면 정상 | LAPIC 레지스터 복원 필요 |
CLOCK_EVT_FEAT_C3STOP 플래그가 이 상황을 표시하며,
lapic_timer_broadcast를 통해 대체 타이머(HPET)가 보조합니다.
MSI/MSI-X
MSI(Message Signaled Interrupts)와 MSI-X는 PCIe 장치가 I/O APIC을 우회하여 메모리 쓰기(Memory Write Transaction)로 직접 LAPIC에 인터럽트를 전달하는 현대적 방식입니다. 물리적인 인터럽트 라인이 필요 없으며, 벡터 수도 크게 확장됩니다.
/* MSI-X Table Entry 구조 (BAR 공간, 16바이트/엔트리) */
struct msix_entry {
u32 msg_addr_lo; /* [31:0] Message Address Low */
u32 msg_addr_hi; /* [63:32] Message Address High (0 for 32-bit) */
u32 msg_data; /* [31:0] Message Data */
u32 vector_ctrl; /* [0] Mask bit (1=마스크) */
};
/* 리눅스 MSI/MSI-X 설정 API */
int pci_alloc_irq_vectors(struct pci_dev *dev,
unsigned int min_vecs,
unsigned int max_vecs,
unsigned int flags);
/* 사용 예시 */
int nvecs = pci_alloc_irq_vectors(pdev, 1, num_cpus,
PCI_IRQ_MSIX | PCI_IRQ_MSI | PCI_IRQ_LEGACY);
if (nvecs < 0)
return nvecs;
/* 벡터별 IRQ 번호 조회 */
int irq = pci_irq_vector(pdev, vector_index);
/* IRQ 핸들러 등록 */
request_irq(irq, my_handler, 0, "my_device", priv);
/* 해제 */
pci_free_irq_vectors(pdev);
Intel APICv (APIC Virtualization)
Intel APICv는 VT-x의 확장으로, 가상 머신의 APIC 접근을 하드웨어에서 직접 처리하여 VM Exit 오버헤드를 제거합니다. 주요 기능은 Virtual-APIC Page, TPR Shadow, Virtual Interrupt Delivery, Process Posted Interrupts입니다.
/* KVM의 APICv Posted Interrupt 처리 */
struct pi_desc {
u32 pir[8]; /* Posted Interrupt Requests (256-bit) */
union {
struct {
u16 on : 1; /* Outstanding Notification */
u16 sn : 1; /* Suppress Notification */
u16 rsvd : 14;
u8 nv; /* Notification Vector */
u8 rsvd2;
u32 ndst; /* Notification Destination */
};
u64 control;
};
} __attribute__((aligned(64)));
/* Posted Interrupt 주입 흐름:
* 1. 외부 장치 인터럽트 → KVM 핸들러
* 2. PIR[vector] 비트 atomic OR 세팅
* 3. ON 비트가 0이면 1로 세팅하고 notification IPI 발송
* 4. 타겟 CPU의 PI handler가 PIR → vIRR로 병합
* 5. Guest는 VM Exit 없이 인터럽트 수신
*/
static void vmx_deliver_posted_interrupt(struct kvm_vcpu *vcpu, int vector)
{
struct vcpu_vmx *vmx = to_vmx(vcpu);
struct pi_desc *pid = &vmx->pi_desc;
/* PIR 비트 세팅 */
if (pi_test_and_set_pir(vector, pid))
return; /* 이미 세팅됨 */
/* ON 비트 세팅 + notification IPI */
if (!pi_test_and_set_on(pid))
apic_send_IPI(vcpu_to_pcpu(vcpu), POSTED_INTR_VECTOR);
}
AMD AVIC (Advanced Virtual Interrupt Controller)
AMD AVIC은 AMD-V(SVM)의 확장으로, Intel APICv에 대응하는 APIC 가상화 기능입니다. Backing Page, Physical/Logical APIC ID Table, Doorbell Mechanism을 사용하여 Guest의 APIC 접근을 하드웨어에서 직접 처리합니다.
AVIC vs APICv 비교
| 기능 | Intel APICv | AMD AVIC |
|---|---|---|
| 가상 APIC 저장 | Virtual-APIC Page | Backing Page |
| TPR 가상화 | TPR Shadow | Backing Page TPR |
| 인터럽트 주입 | Posted Interrupts (PID) | Doorbell + GA Table |
| IPI 가상화 | ICR 쓰기 → HW 처리 | ICR → Doorbell |
| EOI 가상화 | Virtual Interrupt Delivery | EOI intercept 최적화 |
| Passthrough IR | VT-d Posted Interrupt | GA Table + IOMMU IR |
| VMCS/VMCB 필드 | PI Descriptor Address | Backing/Physical/Logical PA |
| Self IPI | x2APIC Self IPI 가속 | Backing Page 직접 업데이트 |
/* KVM의 AVIC 초기화 (arch/x86/kvm/svm/avic.c) */
static int avic_init_vcpu(struct vcpu_svm *svm)
{
struct kvm_vcpu *vcpu = &svm->vcpu;
struct page *page;
/* Backing Page 할당 (4KB per vCPU) */
page = alloc_page(GFP_KERNEL_ACCOUNT | __GFP_ZERO);
if (!page)
return -ENOMEM;
svm->avic_backing_page = page;
/* VMCB에 Backing Page 물리 주소 설정 */
svm->vmcb->control.avic_backing_page =
page_to_phys(page) | AVIC_HPA_MASK;
/* Physical APIC ID Table 엔트리 설정 */
avic_update_physical_id_entry(vcpu, vcpu->vcpu_id);
return 0;
}
/* AVIC Doorbell 전송 (IPI 가상화) */
static void avic_ring_doorbell(struct kvm_vcpu *vcpu)
{
int cpu = READ_ONCE(vcpu->cpu);
if (cpu != -1)
wrmsrl(MSR_AMD64_SVM_AVIC_DOORBELL, cpu_to_apicid(cpu));
}
KVM LAPIC 에뮬레이션
KVM은 게스트 VM에 가상 LAPIC을 제공합니다. APICv/AVIC 미지원 시 소프트웨어 에뮬레이션으로,
지원 시 하드웨어 가속과 소프트웨어 폴백을 혼합합니다.
struct kvm_lapic이 핵심 자료구조입니다.
/* arch/x86/kvm/lapic.h - KVM LAPIC 구조체 */
struct kvm_lapic {
unsigned long base_address; /* Guest가 보는 LAPIC 기본 주소 */
struct kvm_io_device dev; /* MMIO exit 처리용 */
struct kvm_timer lapic_timer; /* APIC 타이머 에뮬레이션 */
u32 divide_count; /* 타이머 분주비 */
struct kvm_vcpu *vcpu; /* 소유 vCPU */
bool sw_enabled; /* SVR[8] 소프트웨어 활성화 */
bool irr_pending; /* IRR에 pending 인터럽트 있음 */
bool lvt_lint0_watch;
/* 가상 APIC 레지스터 페이지 (4KB)
* APICv에서는 Virtual-APIC Page로 사용
* 소프트웨어 에뮬레이션에서는 이 버퍼에서 직접 R/W */
void *regs;
/* 최고 ISR 벡터 캐시 (PPR 계산 최적화) */
int highest_isr_cache;
};
/* Guest의 APIC MMIO 접근 처리 (APICv 미사용 시) */
static int apic_mmio_write(struct kvm_vcpu *vcpu,
struct kvm_io_device *dev,
gpa_t addr, int len, const void *val)
{
u32 offset = addr & 0xFF0;
u32 data = *(u32 *)val;
switch (offset) {
case APIC_EOI:
apic_set_eoi(vcpu->arch.apic);
break;
case APIC_TASKPRI:
apic_set_tpr(vcpu->arch.apic, data);
break;
case APIC_ICR:
apic_send_ipi(vcpu->arch.apic, data);
break;
case APIC_TMICT:
start_apic_timer(vcpu->arch.apic, data);
break;
/* ... 기타 레지스터 ... */
}
return 0;
}
/* 인터럽트 주입 가능 여부 확인 */
bool kvm_apic_has_interrupt(struct kvm_vcpu *vcpu)
{
struct kvm_lapic *apic = vcpu->arch.apic;
int highest_irr;
if (!kvm_apic_present(vcpu))
return false;
highest_irr = apic_find_highest_irr(apic);
if (highest_irr == -1)
return false;
/* PPR보다 높은 우선순위인지 확인 */
return (highest_irr >> 4) > (kvm_apic_get_reg(apic, APIC_PROCPRI) >> 4);
}
KVM APIC Timer 에뮬레이션
/* KVM의 APIC Timer 에뮬레이션 */
struct kvm_timer {
struct hrtimer timer; /* 호스트 hrtimer 사용 */
s64 period; /* 주기 (나노초) */
ktime_t target_expiration; /* 다음 만료 시각 */
u32 timer_mode; /* One-shot/Periodic/TSC-Deadline */
u32 timer_mode_mask;
u64 tscdeadline; /* TSC-Deadline 목표값 */
u64 expired_tscdeadline;
atomic_t pending; /* 미처리 타이머 인터럽트 수 */
bool hv_timer_in_use; /* HW preemption timer 사용 여부 */
};
/* Guest가 APIC Timer ICR에 쓸 때 호출 */
static void start_apic_timer(struct kvm_lapic *apic)
{
struct kvm_timer *ktimer = &apic->lapic_timer;
u64 ns;
if (ktimer->timer_mode == APIC_LVT_TIMER_TSCDEADL) {
/* TSC-Deadline: HW preemption timer 사용 시도 (VM Exit 없이 만료) */
if (kvm_can_use_hv_timer(apic->vcpu)) {
ktimer->hv_timer_in_use = true;
kvm_set_hv_timer(apic->vcpu, ktimer->tscdeadline);
return;
}
/* 폴백: TSC → 나노초 변환 후 hrtimer 사용 */
ns = kvm_scale_tsc(ktimer->tscdeadline - kvm_read_l1_tsc(apic->vcpu));
} else {
/* One-shot/Periodic: 카운트 × 분주비 → 나노초 */
ns = (u64)kvm_apic_get_reg(apic, APIC_TMICT) *
APIC_BUS_CYCLE_NS * apic->divide_count;
}
hrtimer_start(&ktimer->timer, ktime_add_ns(ktime_get(), ns),
HRTIMER_MODE_ABS_HARD);
}
가상화 레벨별 APIC 처리 비교
| Guest APIC 연산 | SW 에뮬레이션 | APICv/AVIC | VM Exit? |
|---|---|---|---|
| TPR 읽기/쓰기 | MMIO exit → KVM 처리 | Virtual-APIC Page 직접 | SW: Yes / HW: No |
| EOI 쓰기 | MMIO exit → vISR 클리어 | Virtual Interrupt Delivery | SW: Yes / HW: No |
| Self IPI | MMIO exit → vIRR 세팅 | x2APIC Self-IPI 가속 | SW: Yes / HW: No |
| IPI (동일 VM) | ICR exit → 대상 vCPU kick | ICR 가속 + Doorbell/PI | SW: Yes / HW: 일부 |
| LVT 설정 | MMIO exit → 레지스터 저장 | MMIO exit (보통) | SW: Yes / HW: Yes |
| Timer ICR 쓰기 | MMIO exit → hrtimer 시작 | MMIO exit + HW timer | SW: Yes / HW: Yes (설정 시) |
| MSI 수신 | KVM → vIRR 세팅 + kick | Posted Interrupt/Doorbell | SW: N/A / HW: No |
KVM APIC 관련 커널 모듈 파라미터
# APICv 활성화/비활성화 확인
cat /sys/module/kvm_intel/parameters/enable_apicv
# 1 = 활성화 (기본), 0 = 비활성화
# AVIC 활성화/비활성화 확인
cat /sys/module/kvm_amd/parameters/avic
# 1 = 활성화 (기본), 0 = 비활성화
# APICv 비활성화 (디버깅/비교 테스트용)
# /etc/modprobe.d/kvm.conf:
# options kvm_intel enable_apicv=0
# Guest에서 APIC 모드 확인
dmesg | grep -i "apic\|x2apic"
# KVM 통계로 APIC VM Exit 빈도 확인
perf kvm stat live
# 또는
cat /sys/kernel/debug/kvm/*/vcpu*/stats | grep -i apic
커널 struct apic 서브시스템
리눅스 커널은 다양한 APIC 모드(flat, physical, cluster, x2apic 등)를
struct apic 드라이버 모델로 추상화합니다. 부팅 시 하드웨어와 BIOS 설정에
따라 적절한 APIC 드라이버가 선택되며, IPI 전송, APIC ID 읽기 등의 연산을 드라이버별로 구현합니다.
/* arch/x86/include/asm/apic.h - struct apic 주요 콜백 */
struct apic {
/* 이름과 우선순위 */
const char *name;
/* 프로빙: 이 드라이버가 현재 HW에 적합한지 검사 */
int (*probe)(void);
/* IPI 전송 */
void (*send_IPI)(int cpu, int vector);
void (*send_IPI_mask)(const struct cpumask *mask, int vector);
void (*send_IPI_all)(int vector);
void (*send_IPI_allbutself)(int vector);
void (*send_IPI_self)(int vector);
/* APIC ID 관련 */
u32 (*read)(u32 reg);
void (*write)(u32 reg, u32 val);
u32 (*get_apic_id)(u32 id);
u32 (*set_apic_id)(u32 id);
/* 논리적 목적지 설정 */
void (*init_apic_ldr)(void);
u32 (*calc_dest_apicid)(unsigned int cpu);
/* IRQ 어피니티 */
int (*cpu_mask_to_apicid)(const struct cpumask *mask,
struct irq_data *irqdata,
unsigned int *dest);
/* 기타 */
int (*wakeup_secondary_cpu)(int apicid, unsigned long start_eip);
};
/* 전역 APIC 드라이버 포인터 */
extern struct apic *apic;
/* 사용 예: IPI 전송 */
apic->send_IPI(cpu, RESCHEDULE_VECTOR);
드라이버 선택 과정
/* arch/x86/kernel/apic/probe_64.c - 드라이버 선택 */
void __init default_setup_apic_routing(void)
{
/* x2APIC + Interrupt Remapping 사용 가능? */
if (x2apic_enabled()) {
if (x2apic_phys)
apic_install_driver(&apic_x2apic_phys);
else
apic_install_driver(&apic_x2apic_cluster);
return;
}
/* xAPIC: CPU 수에 따라 선택 */
if (num_possible_cpus() <= 8)
apic_install_driver(&apic_flat); /* Flat Logical */
else
apic_install_driver(&apic_physflat); /* Physical Flat */
}
/* 커널 명령줄 옵션:
* apic=debug - APIC 디버그 로그 활성화
* noapic - I/O APIC 비활성화 (8259A 사용)
* nolapic - LAPIC 비활성화
* x2apic_phys - x2APIC Physical 모드 강제
*/
APIC 드라이버별 특성 비교
| 드라이버 | 접근 방식 | APIC ID | Dest Mode | 최대 CPU | 사용 시나리오 |
|---|---|---|---|---|---|
apic_flat |
xAPIC (MMIO) | 8-bit | Logical Flat | 8 | 소규모 SMP 시스템 |
apic_physflat |
xAPIC (MMIO) | 8-bit | Physical | 255 | xAPIC + 8CPU 초과 |
x2apic_cluster |
x2APIC (MSR) | 32-bit | Logical Cluster | 이론상 4G | 대규모 서버 (기본) |
x2apic_phys |
x2APIC (MSR) | 32-bit | Physical | 이론상 4G | KVM Guest, 특수 환경 |
bigsmp |
xAPIC (MMIO) | 8-bit | Physical | 255 | 레거시 대규모 시스템 |
APIC 초기화 전체 흐름
부트 순서 (BSP):
1. BIOS: 8259A PIC 모드로 시작, ACPI MADT 테이블 준비
2. early_acpi_boot_init()
└─ acpi_parse_madt() → LAPIC/I/O APIC 정보 수집
3. init_apic_mappings()
└─ LAPIC MMIO 매핑 (0xFEE00000)
4. default_setup_apic_routing()
└─ struct apic 드라이버 선택 (flat/cluster/x2apic)
5. enable_x2apic() (지원 시)
└─ IA32_APIC_BASE[10] = 1, IR 확인
6. setup_local_APIC()
└─ SVR 활성화, LVT 설정, TPR 초기화
7. setup_IO_APIC()
└─ RTE 프로그래밍, ISA IRQ 매핑
8. 8259A PIC 마스크 (disable_8259A_irq)
9. AP 부트: INIT-SIPI-SIPI → 각 AP에서 setup_local_APIC()
dmesg 출력 예시:
[ 0.000000] ACPI: LAPIC (acpi_id[0x00] lapic_id[0x00] enabled)
[ 0.000000] ACPI: IOAPIC (id[0x02] address[0xfec00000] gsi_base[0])
[ 0.000000] Using ACPI (MADT) for SMP configuration
[ 0.004000] x2apic: IRQ remapping enabled
[ 0.004000] Switched APIC routing to: x2apic_cluster
디버깅
APIC 관련 문제는 인터럽트가 전달되지 않거나 잘못된 CPU로 라우팅되는 형태로 나타납니다.
/proc/interrupts, ESR(Error Status Register), 커널 트레이스포인트,
dmesg 로그를 활용하여 진단합니다.
/proc/interrupts 분석
# APIC 관련 인터럽트 확인
cat /proc/interrupts | head -5
# CPU0 CPU1 CPU2 CPU3
# 0: 22 0 0 0 IR-IO-APIC 2-edge timer
# 1: 0 0 3 0 IR-IO-APIC 1-edge i8042
# 8: 0 0 0 1 IR-IO-APIC 8-edge rtc0
# 9: 0 12 0 0 IR-IO-APIC 9-fasteoi acpi
# 키 식별자:
# IR-IO-APIC : Interrupt Remapping + I/O APIC 경로
# IR-PCI-MSI : Interrupt Remapping + MSI 경로
# IO-APIC : IR 없는 I/O APIC (레거시)
# PCI-MSI : IR 없는 MSI
# APIC 내부 인터럽트
cat /proc/interrupts | grep -E "LOC|RES|CAL|TLB|ERR|SPU"
# LOC: Local timer interrupts (APIC Timer)
# RES: Rescheduling interrupts (IPI)
# CAL: Function call interrupts (IPI)
# TLB: TLB shootdowns (IPI)
# ERR: APIC error count
# SPU: Spurious interrupts
# IRQ 어피니티 확인
for irq in /proc/irq/*/smp_affinity_list; do
echo "$(dirname $irq | xargs basename): $(cat $irq)"
done
# x2APIC 모드 확인
dmesg | grep -i "x2apic\|apic\|ioapic"
ESR (Error Status Register) 분석
| ESR 비트 | 이름 | 원인 | 대처 |
|---|---|---|---|
| [0] | Send CS Error | 전송 시 체크섬 오류 | 하드웨어 문제 의심 |
| [1] | Receive CS Error | 수신 시 체크섬 오류 | 하드웨어 문제 의심 |
| [2] | Send Accept Error | 전송한 메시지 거부됨 | 잘못된 APIC ID 확인 |
| [3] | Receive Accept Error | 수신한 메시지 거부됨 | APIC 비활성화 상태 확인 |
| [4] | Redirectable IPI | Lowest Priority에서 유효하지 않은 리다이렉션 | 라우팅 설정 확인 |
| [5] | Send Illegal Vector | 벡터 0~15 전송 시도 | 벡터 번호 확인 (16~255) |
| [6] | Receive Illegal Vector | 벡터 0~15 수신 | 원본 장치 설정 확인 |
| [7] | Illegal Register Access | 존재하지 않는 레지스터 접근 | 레지스터 오프셋 확인 |
/* ESR 읽기 (두 번 읽기 프로토콜) */
static u32 read_apic_esr(void)
{
/* ESR은 "쓰기 후 읽기" 방식:
* 1. ESR에 0을 쓰면 내부 에러 상태가 ESR로 래치
* 2. ESR을 읽으면 래치된 값 반환 */
apic_write(APIC_ESR, 0);
return apic_read(APIC_ESR);
}
/* APIC 에러 인터럽트 핸들러 */
static void smp_error_interrupt(struct pt_regs *regs)
{
u32 esr;
entering_irq();
esr = read_apic_esr();
pr_err("APIC error on CPU%d: ESR=0x%x (%s%s%s%s%s%s%s%s)\n",
smp_processor_id(), esr,
(esr & 0x01) ? "Send_CS " : "",
(esr & 0x02) ? "Recv_CS " : "",
(esr & 0x04) ? "Send_Accept " : "",
(esr & 0x08) ? "Recv_Accept " : "",
(esr & 0x10) ? "Redir_IPI " : "",
(esr & 0x20) ? "Send_Illegal " : "",
(esr & 0x40) ? "Recv_Illegal " : "",
(esr & 0x80) ? "Illegal_Reg " : "");
ack_APIC_irq();
exiting_irq();
}
커널 트레이스포인트
# APIC 관련 트레이스포인트 목록
ls /sys/kernel/debug/tracing/events/irq_vectors/
# local_timer_entry/exit - APIC Timer 인터럽트
# reschedule_entry/exit - Reschedule IPI
# call_function_entry/exit - Call Function IPI
# error_apic_entry/exit - APIC Error
# spurious_apic_entry/exit - Spurious 인터럽트
# thermal_apic_entry/exit - Thermal Monitor
# APIC Timer 인터럽트 트레이싱
echo 1 > /sys/kernel/debug/tracing/events/irq_vectors/local_timer_entry/enable
cat /sys/kernel/debug/tracing/trace_pipe | head -20
# ftrace로 APIC 함수 추적
echo function > /sys/kernel/debug/tracing/current_tracer
echo 'apic_*' > /sys/kernel/debug/tracing/set_ftrace_filter
cat /sys/kernel/debug/tracing/trace
# perf로 APIC 관련 오버헤드 측정
perf stat -e irq_vectors:local_timer_entry \
-e irq_vectors:reschedule_entry \
-e irq_vectors:call_function_entry \
-a sleep 5
bpftrace를 이용한 APIC 진단
# 레시피 1: APIC Timer 인터럽트 빈도 및 레이턴시 측정
bpftrace -e '
tracepoint:irq_vectors:local_timer_entry {
@timer_count = count();
@start[cpu] = nsecs;
}
tracepoint:irq_vectors:local_timer_exit / @start[cpu] / {
$lat = (nsecs - @start[cpu]) / 1000;
@timer_lat_us = hist($lat);
delete(@start[cpu]);
}
interval:s:5 {
printf("=== APIC Timer 통계 (5초) ===\n");
printf("총 인터럽트: "); print(@timer_count);
printf("처리 레이턴시 (μs):\n"); print(@timer_lat_us);
clear(@timer_count); clear(@timer_lat_us);
}'
# 레시피 2: IPI 종류별 빈도 측정
bpftrace -e '
tracepoint:irq_vectors:reschedule_entry { @ipi[\"reschedule\"] = count(); }
tracepoint:irq_vectors:call_function_entry { @ipi[\"call_func\"] = count(); }
tracepoint:irq_vectors:call_function_single_entry { @ipi[\"call_single\"] = count(); }
interval:s:5 {
printf("=== IPI 종류별 빈도 (5초) ===\n");
print(@ipi);
clear(@ipi);
}'
# 레시피 3: APIC Error 실시간 감시
bpftrace -e '
tracepoint:irq_vectors:error_apic_entry {
printf("APIC ERROR on CPU %d at %llu\n", cpu, nsecs);
@err_count = count();
}
tracepoint:irq_vectors:spurious_apic_entry {
printf("SPURIOUS APIC on CPU %d at %llu\n", cpu, nsecs);
@spur_count = count();
}'
# 레시피 4: EOI 레이턴시 히스토그램 (함수 프로브)
bpftrace -e '
kprobe:ack_APIC_irq {
@eoi_start[tid] = nsecs;
}
kretprobe:ack_APIC_irq / @eoi_start[tid] / {
@eoi_ns = hist(nsecs - @eoi_start[tid]);
delete(@eoi_start[tid]);
}
interval:s:10 { print(@eoi_ns); clear(@eoi_ns); }'
APIC 상태 진단 스크립트
#!/bin/bash
# apic_diag.sh: APIC 관련 진단 정보 수집
echo "=== 1. APIC 모드 확인 ==="
dmesg | grep -iE "apic|x2apic|ioapic" | tail -20
echo
echo "=== 2. /proc/interrupts (상위 20줄) ==="
head -22 /proc/interrupts
echo
echo "=== 3. APIC 관련 카운터 ==="
grep -E "LOC|RES|CAL|TLB|ERR|SPU|MIS|PMI|TRM" /proc/interrupts
echo
echo "=== 4. IRQ 어피니티 (장치 IRQ만) ==="
for d in /proc/irq/*/smp_affinity_list; do
irq=$(echo "$d" | cut -d/ -f4)
name=""
[ -f "/proc/irq/$irq/actions" ] && name=$(cat "/proc/irq/$irq/actions" 2>/dev/null | head -1)
[ -z "$name" ] && continue
echo " IRQ $irq ($name): CPUs $(cat $d)"
done
echo
echo "=== 5. MSI/MSI-X 장치 ==="
lspci -vvv 2>/dev/null | grep -B 10 "MSI-X\|MSI:" | grep -E "^[0-9a-f]|MSI"
echo
echo "=== 6. IOMMU / Interrupt Remapping 상태 ==="
dmesg | grep -iE "DMAR|IOMMU|interrupt remapping" | tail -10
echo
echo "=== 7. CPU별 APIC ID ==="
for cpu in /sys/devices/system/cpu/cpu[0-9]*; do
id=$(cat "$cpu/topology/core_id" 2>/dev/null)
pkg=$(cat "$cpu/topology/physical_package_id" 2>/dev/null)
echo " $(basename $cpu): package=$pkg core=$id"
done
echo
echo "=== 8. APIC 에러 확인 ==="
ERR=$(grep "ERR:" /proc/interrupts | awk '{sum=0; for(i=2;i<=NF-1;i++) sum+=$i; print sum}')
SPU=$(grep "SPU:" /proc/interrupts | awk '{sum=0; for(i=2;i<=NF-1;i++) sum+=$i; print sum}')
echo " Total APIC errors: $ERR"
echo " Total Spurious: $SPU"
[ "$ERR" -gt 0 ] && echo " ⚠ APIC 에러 발생 - dmesg 확인 필요"
일반적인 APIC 문제 진단 플로우
| 단계 | 확인 항목 | 명령 | 정상 기준 |
|---|---|---|---|
| 1 | APIC 모드 확인 | dmesg | grep x2apic |
"x2apic enabled" 또는 "switched to apic x2apic_*" |
| 2 | I/O APIC 감지 | dmesg | grep "IOAPIC" |
"IOAPIC[0]: apic_id N, address 0xFEC00000" |
| 3 | IR 활성화 | dmesg | grep "Interrupt remapping" |
"Enabled IRQ remapping in x2apic mode" |
| 4 | APIC 에러 | grep ERR /proc/interrupts |
ERR: 0 |
| 5 | Spurious 인터럽트 | grep SPU /proc/interrupts |
SPU: 0 또는 소수 |
| 6 | 인터럽트 분배 | cat /proc/interrupts |
장치 IRQ가 예상 CPU에 분배 |
| 7 | MSI-X 활성화 | lspci -vvv | grep MSI-X |
"MSI-X: Enable+" |
| 8 | 타이머 동작 | grep LOC /proc/interrupts |
모든 CPU에서 카운트 증가 |
일반적인 실수
| 실수 | 증상 | 해결 |
|---|---|---|
| EOI 전송 누락 | 같은 벡터의 인터럽트가 이후 전달되지 않음 (ISR 비트 고착) | 핸들러 종료 시 반드시 ack_APIC_irq() 호출 |
| 잘못된 ICR Destination | IPI가 엉뚱한 CPU로 전달되거나 분실 | APIC ID와 Destination Mode(Physical/Logical) 일치 확인 |
| x2APIC 전환 없이 MSR 접근 | #GP(General Protection) 예외 발생 | IA32_APIC_BASE[10]=1(EXTD) 확인 후 MSR 사용 |
| IR 없이 x2APIC 사용 | 보안 취약점 (MSI 인터럽트 주입 공격 가능) | VT-d/IOMMU Interrupt Remapping 필수 활성화 |
| APIC Timer 캘리브레이션 실패 | 타이머 주기가 비정상, 시간 드리프트 | TSC-Deadline 모드 사용 권장, PIT 기반 캘리브레이션 검증 |
| I/O APIC RTE에 마스크 해제 누락 | 장치 인터럽트가 전달되지 않음 | RTE[16](Mask bit)=0 확인 |
| Level 트리거에서 Remote IRR 미해제 | 해당 핀에서 인터럽트 재발생 불가 | I/O APIC EOI 레지스터 또는 RTE 재설정 |
| DFR/LDR 미설정 (xAPIC Logical 모드) | Logical Destination 기반 인터럽트 실패 | init_apic_ldr() 호출 확인, Flat vs Cluster 모드 일치 |
| SVR의 APIC Enable 비트 미설정 | LAPIC이 모든 인터럽트를 무시 | apic_write(APIC_SPIV, val | APIC_SPIV_APIC_ENABLED) |
| MSI-X Table의 Mask 비트 | 특정 MSI-X 벡터 인터럽트 미전달 | vector_ctrl[0]=0 (unmask) 확인 |
IA32_APIC_BASE[11]=0으로 APIC을 비활성화하면 RESET 없이는 재활성화가 불가능합니다.
커널 명령줄의 nolapic 옵션은 이 비트를 건드리지 않고 소프트웨어적으로 APIC 사용을 중단합니다.
BIOS에서 APIC이 비활성화된 경우 커널 부팅 시 "BIOS bug: APIC disabled" 메시지가 출력됩니다.
자주 묻는 질문 (FAQ)
Q: xAPIC과 x2APIC을 동시에 사용할 수 있는가?
아닙니다. 하나의 CPU에서 xAPIC과 x2APIC은 배타적 모드입니다.
IA32_APIC_BASE MSR의 EN(bit 11)과 EXTD(bit 10) 조합으로 결정됩니다:
- EN=0, EXTD=0: APIC 비활성화
- EN=1, EXTD=0: xAPIC 모드 (MMIO)
- EN=1, EXTD=1: x2APIC 모드 (MSR)
- EN=0, EXTD=1: 잘못된 상태 (#GP 발생)
x2APIC에서 xAPIC으로 역전환은 불가능합니다. RESET만이 xAPIC으로 복귀시킵니다. 단, 멀티 소켓 시스템에서 각 CPU가 독립적으로 모드를 결정하므로 (이론적으로) 다른 CPU가 다른 모드일 수 있으나, 실제로는 커널이 모든 CPU를 동일 모드로 설정합니다.
Q: Spurious Interrupt(벡터 0xFF)는 왜 발생하는가?
Spurious Interrupt는 LAPIC이 인터럽트 수신을 인지했으나, CPU가 ISR에 비트를 세팅하기 전에 인터럽트가 사라진 경우 발생합니다. 일반적인 원인:
- Level 트리거 인터럽트가 핸들러 진입 전에 디어서트(de-assert)됨
- I/O APIC과 LAPIC 사이의 레이스 컨디션
- 하드웨어 결함 (신호 글리치)
Spurious Interrupt에 대해서는 EOI를 보내지 않아야 합니다. ISR에 비트가 세팅되지 않았으므로 EOI는 다른 인서비스 인터럽트를 잘못 클리어할 수 있습니다. 리눅스 커널은 SVR의 벡터 0xFF로 설정하고, 이 벡터의 핸들러는 카운터만 증가시킵니다.
Q: I/O APIC 없이 MSI만으로 시스템을 운영할 수 있는가?
현대 시스템에서는 대부분의 장치가 MSI/MSI-X를 사용하지만, 레거시 장치(키보드 8042, UART, RTC 등)는 여전히 I/O APIC 핀에 연결됩니다. 따라서 완전히 I/O APIC 없이 운영하기는 어렵습니다.
그러나 커널 명령줄 pci=nomsi로 MSI를 비활성화하거나,
noapic으로 I/O APIC을 비활성화하여 한쪽만 사용할 수 있습니다.
noapic 사용 시 I/O APIC 대신 8259A PIC 호환 모드로 폴백합니다.
Q: APICv/AVIC이 비활성화되는 경우는?
다음과 같은 상황에서 KVM은 APICv/AVIC을 비활성화하고 소프트웨어 에뮬레이션으로 폴백합니다:
- Guest가 xAPIC Flat 이외의 모드 사용 (일부 구형 x2APIC 처리)
- Nested Virtualization (L2 Guest)
- AVIC에서 x2APIC 모드 Guest (AMD 구형 CPU)
enable_apicv=0모듈 파라미터- IRQ 윈도우 관련 특수 상황
- SEV/SEV-ES 암호화 VM (일부 AVIC 기능 제한)
KVM은 동적으로 APICv를 활성화/비활성화할 수 있으며,
kvm_request_apicv_update()로 런타임에 전환합니다.
비활성화 시 /sys/kernel/debug/kvm/*/vcpu*/apicv_inhibit에서 이유를 확인할 수 있습니다.
Q: APIC Timer와 TSC의 관계는?
APIC Timer의 One-shot/Periodic 모드는 APIC 버스 클럭(또는 코어 크리스탈 클럭)을 기반으로 카운트다운합니다. 이 클럭은 CPU 주파수 스케일링(P-state)에 영향을 받지 않지만, C3 이상의 C-state에서 중단될 수 있습니다.
반면 TSC-Deadline 모드는 TSC(Time Stamp Counter)를 기준으로 합니다. Invariant TSC(CPUID.80000007H:EDX[8]=1) 지원 시 TSC는 C-state/P-state에 관계없이 일정한 속도로 증가하므로, TSC-Deadline이 가장 안정적입니다.
커널은 constant_tsc와 nonstop_tsc CPU 기능을 확인하고,
TSC-Deadline 모드를 우선 사용합니다.
cat /proc/cpuinfo | grep -o "constant_tsc\|nonstop_tsc\|tsc_deadline_timer"로
지원 여부를 확인할 수 있습니다.
커널 명령줄 APIC 관련 옵션 정리
| 옵션 | 효과 | 용도 |
|---|---|---|
noapic |
I/O APIC 비활성화 (8259A PIC 사용) | I/O APIC 관련 부팅 문제 우회 |
nolapic |
LAPIC 비활성화 (PIC + PIT 폴백) | LAPIC 하드웨어 오류 시 부팅 |
apic=debug |
APIC 초기화/설정 디버그 로그 활성화 | APIC 설정 문제 진단 |
apic=verbose |
상세 APIC 정보 출력 | RTE/LVT 설정 확인 |
nox2apic |
x2APIC 모드 비활성화 (xAPIC 유지) | x2APIC 관련 문제 우회 |
x2apic_phys |
x2APIC Physical 모드 강제 | Cluster 모드 문제 시 대안 |
pci=nomsi |
MSI/MSI-X 비활성화 | MSI 관련 인터럽트 문제 진단 |
intremap=off |
Interrupt Remapping 비활성화 | IR 관련 부팅 문제 우회 (보안 약화) |
lapic_timer_c2_ok |
C2에서 LAPIC 타이머 사용 허용 | C2에서 타이머 정지하는 특정 BIOS 우회 |
clocksource=tsc |
TSC를 기본 클럭소스로 강제 | TSC-Deadline 타이머 안정화 |
관련 문서
- 인터럽트 (Interrupts) — IRQ 처리, Top/Bottom Half, softirq, GIC/APIC 아키텍처 개요
- IPI (프로세서 간 인터럽트) — x86 ICR/APIC 기반 IPI 벡터, TLB flush, call function
- IRQ 도메인 — 인터럽트 컨트롤러 추상화, IRQ 매핑, ACPI MADT
- NMI (마스크 불가 인터럽트) — NMI 소스, hardlockup watchdog, PMU
- 타이머 (Timers) — hrtimer, clocksource, clockevent, tickless 커널
- Softirq & Hardirq — Hard IRQ와 Soft IRQ 처리 메커니즘
- 가상화 — VT-x/AMD-V, VMCS/VMCB, APICv/AVIC 연동