不同CPU體系間的中斷控制器工作原理有較大差異,本文是《Linux mips64r2 PCI中斷路由機制分析》的姊妹篇,主要分析Broadwell-DE X86_64 APIC中斷路由原理、中斷配置和處理過程,並嘗試回答如下問題:
- 為什么x86中斷路由使用IO-APIC/LAPIC框架,其有什么價值?
- pin/irq/vector的區別、作用,取值范圍和分配機制?
x86_64 APIC關鍵概念
Pin
此處的pin特指APIC的中斷輸入引腳,與內外部設備的中斷輸入信號相連。從上圖中可以看出,Pin的最大值受APIC管腳數限制,目前取值范圍是[0,23]。其中[0, 15]這16個pin,基於與PIC兼容等原因考慮,有固定用途;PIRQ[A..H]這8個引腳為PCI IRQ引腳,為PCI設備提供中斷路由,其中PIRQ[A..D]為純中斷引腳,PIRQ[E..H]可配置為中斷引腳或GPIO引腳。
內部設備中斷路由到哪個PIRQ,可以通過DxxIR(Device XX Interrupt Route Register)寄存器設置;外部設備使用哪個APIC引腳,在硬件PCB設計時即固定下來。
為什么設備中斷要經過APIC再與CPU相連,而不直接與CPU相連?原因有二:1)存在大量的外部設備,但CPU的中斷引腳等資源是很有限的,滿足不了所有的直連需求;2)如果設備中斷與CPU直接相連,連接關系隨硬件固化,這樣在MP系統中,中斷負載均衡等需求就無法實現了。
Vector
Vector是CPU的概念,以CPU核的角度看,其以vector標識中斷,詳見下節中斷路由原理介紹。
vector是IDT表(idt_table)的索引。
gate_desc idt_table[NR_VECTORS] __page_aligned_data = { { { { 0, 0 } } }, };
vector的個數由硬件決定,從上圖可知,Broadwell-DE X86_64支持最多256個vector。其中前32個為系統保留使用,其他由操作系統動態分配。
vector提供優先級和親和性綁定的支持,vector的高4位為優先級,0最低,15最高。CPU只處理優先級高於LAPIC TPR值的vector中斷。
為什么在irq之外,又增加個vector概念,兩者是否可以合二為一?不可以,原因主要是:vector是針對每個CPU核的,描述每個CPU核對上報中斷的優先級處理和親和性關系;而irq是全局的,它維護所有CPU核上的中斷處理相關信息。
IRQ
在PIC和單核時代,irq、vector、pin這個概念的確是合三為一的,irq就是PIC控制器的pin引腳,irq也暗示着中斷優先級,例如IRQ0比IRQ3有着更高的優先級。當進入MP多核時代,多核CPU下中斷處理帶來很多問題(如如何決定哪個中斷在哪個核上處理,如何保證各核上中斷負載均衡等),為了解決這些問題,vector、pin等概念都從irq中剝離出來,irq不再含有特定體系架構下中斷控制器的硬件屬性,只是內核中對中斷的一個通用的軟件抽象,與特定硬件解耦,增強其通用性。在內核中,irq號做為中斷的抽象表達,其功能包括:
- 對所有cpu核上的中斷進行統一編碼,確保不同cpu核上相同中斷的irq號不重復;
- 作為中斷相關信息的查詢碼,如通過irq號可以獲得中斷控制器對象struct mpic,可以獲得中斷描述符對象irq_desc,可以獲得硬件中斷號等等。
irq的總數由下面的方式計算得出,nr_irqs為所有cpu核支持的中斷總數,NR_IRQS為nr_irqs的初始值。
當irq很大時,靜態分配irq_desc表將不是個明智的決定,這時內核會使用radix tree來組織irq_desc,即irq_desc_tree
nr_irqs_gsi = 64 nr_cpu_ids = 16 X = The number of irq sources we can talk about = nr_irqs_gsi+8*nr_cpu_ids+nr_irqs_gsi*16 = 1216 Y = The number of irqs we can possibly service = NR_VECTORS*nr_cpu_ids = 4096 nr_irqs = min(X, Y) = min(1216,4096) = 1216 NR_VECTORS = 256 NR_CPUS = 64 CPU_VECTOR_LIMIT = 64 * NR_CPUS = 256 * nr_cpu_ids = 4096 MAX_IO_APICS = 128 IO_APIC_VECTOR_LIMIT = 32 * MAX_IO_APICS = 4096 NR_IRQS = NR_VECTORS + max(CPU_VECTOR_LIMIT,IO_APIC_VECTOR_LIMIT) = 4352
最后,總結一下,這幾個中斷概念的關系和作用描述,如下圖:
x86_64 PCI設備中斷路由原理
如上圖所示,local APIC通過 I/O APIC接受中斷,I/O APIC負責把中斷處理成中斷消息並按一定規則轉發給local APIC。
如上圖所示,local APIC提供the interrupt request register (IRR) 和 in-service register (ISR) 2個寄存器,在處理一個vector的同時,緩存一個相同的vector,vector通過2個256-bit的寄存器標識,256個bit代表256個可能的vector,置1表示上報了相應的vector請求處理或者正在處理中。
local APIC以vector值作為優先級順序來處理中斷。每個vector為8-bit,高4位作為中斷優先級,1最低,15最高。vector 0-31為Intel 64 and IA-32體系保留專用,所以可用的中斷優先級為2-15。
另外,local APIC提供Task-Priority Register (TPR),Processor-Priority Register (PPR)來設置local APIC的task優先級和cpu優先級,但I/O APIC轉發的中斷vector優先級小於local APIC TPR的設置時,此中斷不能打斷當前CPU核上運行的task;當中斷vector優先級小於local APIC PPR的設置時,此CPU核不處理此中斷。操作系統通過動態設置TPR和PPR,從而實現操作系統的實時性需求和中斷負載均衡需求。
中斷處理過程
Broadwell-DE X86_64的中斷處理過程基本和《Linux mips64r2 PCI中斷路由機制分析》的類似,不同點是x86_64從ax寄存器中獲取需要處理的中斷vector,而不是從APIC的特定寄存器中獲取。然后從vector_irq數組中根據vector查詢到對於的irq號。
unsigned int __irq_entry do_IRQ(struct pt_regs *regs) { struct pt_regs *old_regs = set_irq_regs(regs); /* high bit used in ret_from_ code */ unsigned vector = ~regs->orig_ax; unsigned irq; irq_enter(); exit_idle(); irq = __this_cpu_read(vector_irq[vector]); if (!handle_irq(irq, regs)) { ack_APIC_irq(); if (printk_ratelimit()) pr_emerg("%s: %d.%d No irq handler for vector (irq %d)\n", __func__, smp_processor_id(), vector, irq); } irq_exit(); set_irq_regs(old_regs); return 1; }
中斷配置過程
- 動態分配irq,以IRQ_BITMAP_BITS (NR_IRQS + 8196) bitmap數據結構管理;
- 動態分配vector,為保證最大利用local APIC上vector優先級,減少具有相同vector優先級的中斷,以16為間隔依次分配vector;
- 配置vector與irq的映射表:vector_irq表;
- 初始化irq_desc;
- 配置APIC PIRQ[A..H] pin腳屬性;
- 配置I/O APIC pin與vector 映射表:REDIR_TBL寄存器;共有24個REDIR_TBL條目,與24個pin腳對應。除了定義相應的vector以外,還支持定義中斷引腳具有如下功能:定義此引腳中斷通過I/O APIC上報給指定的local APIC或local APIC組;中斷觸發模式;中斷屏蔽等。詳見下表。
附:相關數據結構:
typedef struct gate_struct64 gate_desc; /* 16byte gate */ struct gate_struct64 { u16 offset_low; u16 segment; unsigned ist : 3, zero0 : 5, type : 5, dpl : 2, p : 1; u16 offset_middle; u32 offset_high; u32 zero1; } __attribute__((packed)); struct irq_desc { struct irq_data irq_data; unsigned int __percpu *kstat_irqs; irq_flow_handler_t handle_irq; #ifdef CONFIG_IRQ_PREFLOW_FASTEOI irq_preflow_handler_t preflow_handler; #endif struct irqaction *action; /* IRQ action list */ unsigned int status_use_accessors; unsigned int core_internal_state__do_not_mess_with_it; unsigned int depth; /* nested irq disables */ unsigned int wake_depth; /* nested wake enables */ unsigned int irq_count; /* For detecting broken IRQs */ unsigned long last_unhandled; /* Aging timer for unhandled count */ unsigned int irqs_unhandled; u64 random_ip; raw_spinlock_t lock; struct cpumask *percpu_enabled; #ifdef CONFIG_SMP const struct cpumask *affinity_hint; struct irq_affinity_notify *affinity_notify; #ifdef CONFIG_GENERIC_PENDING_IRQ cpumask_var_t pending_mask; #endif #endif unsigned long threads_oneshot; atomic_t threads_active; wait_queue_head_t wait_for_threads; #ifdef CONFIG_PROC_FS struct proc_dir_entry *dir; #endif int parent_irq; struct module *owner; const char *name; } ____cacheline_internodealigned_in_smp; #ifndef CONFIG_SPARSE_IRQ extern struct irq_desc irq_desc[NR_IRQS]; #endif static RADIX_TREE(irq_desc_tree, GFP_KERNEL);
--EOF--