Linux x86_64 APIC中斷路由機制分析


不同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;
}

 

中斷配置過程

  1. 動態分配irq,以IRQ_BITMAP_BITS (NR_IRQS + 8196) bitmap數據結構管理;
  2. 動態分配vector,為保證最大利用local APIC上vector優先級,減少具有相同vector優先級的中斷,以16為間隔依次分配vector;
  3. 配置vector與irq的映射表:vector_irq表;
  4. 初始化irq_desc;
  5. 配置APIC PIRQ[A..H] pin腳屬性;
  6. 配置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--


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM