设置APIC中断服务

2019-04-15 17:15发布

5.10 初始化中断处理系统

start_kernel接下来要做的事是初始化中断处理系统。整个内核的中断系统的核心就是我们在“初始化中断描述符表”里面设置的那个中断描述符表。而这个表的前19个表项我们已经在“初始化异常服务”中设置为了一些中断和异常的服务。内核接下来会如何设置其余的表项呢?   前面提到过高级可编程中断控制器APIC,这里简单地提一下它的体系结构,简单地说就是由两部分组成:本地高级中断控制器(Local APICLAPIC),位于每个CPU中,主要负责传递中断信号到指定的处理器中;I/O高级中断控制器(I/O APIC,)负责搜集来自I/O设备的中断信号并分发给LAPIC,形成一个多级APIC中断分发网络。   接下来的大部分初始化工作都将围绕着LAPICIOAPIC这两个东西进行。  

5.10.1 设置APIC中断服务

回到start_kernel,由于我们没有配置CONFIG_SPARSE_IRQ,所以605行的early_irq_init()函数是个空函数。606行,调用init_IRQ函数,其本质上是调用x86_init.irqs.intr_init函数,也就是前面“拷贝可用内存区信息”的那个x86_init中看到的那个native_init_IRQ函数,用来初始化LAPIC   235void __init native_init_IRQ(void)  236{  237        int i;  238  239        /* Execute any quirks before the call gates are initialised: */  240        x86_init.irqs.pre_vector_init();  241  242        apic_intr_init();  243  244        /*  245         * Cover the whole vector space, no vector can escape  246         * us. (some of these will be overridden and become  247         * 'special' SMP interrupts)  248         */  249        for (i = FIRST_EXTERNAL_VECTOR; i < NR_VECTORS; i++) {  250                /* IA32_SYSCALL_VECTOR could be used in trap_init already. */  251                if (!test_bit(i, used_vectors))  252                        set_intr_gate(i, interrupt[i-FIRST_EXTERNAL_VECTOR]);  253        }  254  255        if (!acpi_ioapic)  256                setup_irq(2, &irq2);  257  258#ifdef CONFIG_X86_32  259        /*  260         * External FPU? Set up irq13 if so, for  261         * original braindamaged IBM FERR coupling.  262         */  263        if (boot_cpu_data.hard_math && !cpu_has_fpu)  264                setup_irq(FPU_IRQ, &fpu_irq);  265  266        irq_ctx_init(smp_processor_id());  267#endif  268}   该函数初始化中断向量表中前面一些我们没有设置的表项,首先是240,调用x86_init.irqs.pre_vector_init函数,也就是init_ISA_irqs函数:   101void __init init_ISA_irqs(void)  102{  103        int i;  104  105#if defined(CONFIG_X86_64) || defined(CONFIG_X86_LOCAL_APIC)  106        init_bsp_APIC();  107#endif  108        legacy_pic->init(0);  109  110        /*  111         * 16 old-style INTA-cycle interrupts:  112         */  113        for (i = 0; i < legacy_pic->nr_legacy_irqs; i++) {  114                struct irq_desc *desc = irq_to_desc(i);  115  116                desc->status = IRQ_DISABLED;  117                desc->action = NULL;  118                desc->depth = 1;  119  120                set_irq_chip_and_handler_name(i, &i8259A_chip,  121                                              handle_level_irq, "XT");  122        }  123}   106行,由于我们配置了CONFIG_X86_LOCAL_APIC,所以首先调用init_bsp_APIC函数来配置本地LAPIC芯片。该函数调用apic_readapic_write调用全局变量apicreadwrite方法。先说说这个全局变量apic的数据结构,其代表一块LAPIC控制器芯片,于arch/x86/include/asm/apic.h文件中定义:   struct apic {        char *name;          int (*probe)(void);        int (*acpi_madt_oem_check)(char *oem_id, char *oem_table_id);        int (*apic_id_registered)(void);   ……          /* apic ops */        u32 (*read)(u32 reg);        void (*write)(u32 reg, u32 v);        u64 (*icr_read)(void);        void (*icr_write)(u32 low, u32 high);        void (*wait_icr_idle)(void);        u32 (*safe_wait_icr_idle)(void); };   而全局变量apic在文件arch/x86/kernel/apic/probe_32.c文件中被定义成: struct apic *apic = &apic_default; 于是乎,编译vmlinux的时候,apic_default就成为了该变量实际的值:   struct apic apic_default = {          .name                           = "default",        .probe                          = probe_default,        .acpi_madt_oem_check         = NULL,        .apic_id_registered        = default_apic_id_registered,   ……          .read                            = native_apic_mem_read,        .write                           = native_apic_mem_write,        .icr_read                = native_apic_icr_read,        .icr_write                     = native_apic_icr_write,        .wait_icr_idle                = native_apic_wait_icr_idle,        .safe_wait_icr_idle        = native_safe_apic_wait_icr_idle, };   我们看到,init_bsp_APIC函数实际上调用native_apic_mem_readnative_apic_mem_write等方法。回到init_ISA_irqs中,108行又是一个全局变量,legacy_pic struct legacy_pic *legacy_pic = &default_legacy_pic; 其值被初始化成了default_legacy_pic   struct legacy_pic default_legacy_pic = {        .nr_legacy_irqs = NR_IRQS_LEGACY,        .chip  = &i8259A_chip,        .mask_all  = mask_8259A,        .restore_mask = unmask_8259A,        .init = init_8259A,        .irq_pending = i8259A_irq_pending,        .make_irq = make_8259A_irq, };   struct legacy_pic *legacy_pic = &default_legacy_pic;   我们熟悉的8259A中断控制器芯片就是这个default_legacy_pic,所以init_ISA_irqs108行是调用该芯片的初始化函数init_8259A, 初始化了8259A的一些硬件状态,保证8259A能够正常工作:     static void init_8259A(int auto_eoi) {        unsigned long flags;          i8259A_auto_eoi = auto_eoi;          raw_spin_lock_irqsave(&i8259A_lock, flags);          outb(0xff, PIC_MASTER_IMR);  /* mask all of 8259A-1 */        outb(0xff, PIC_SLAVE_IMR);     /* mask all of 8259A-2 */          /*         * outb_pic - this has to work on a wide range of PC hardware.         */        outb_pic(0x11, PIC_MASTER_CMD); /* ICW1: select 8259A-1 init */          /* ICW2: 8259A-1 IR0-7 mapped to 0x30-0x37 on x86-64,           to 0x20-0x27 on i386 */        outb_pic(IRQ0_VECTOR, PIC_MASTER_IMR);          /* 8259A-1 (the master) has a slave on IR2 */        outb_pic(1U << PIC_CASCADE_IR, PIC_MASTER_IMR);          if (auto_eoi)   /* master does Auto EOI */               outb_pic(MASTER_ICW4_DEFAULT | PIC_ICW4_AEOI, PIC_MASTER_IMR);        else         /* master expects normal EOI */               outb_pic(MASTER_ICW4_DEFAULT, PIC_MASTER_IMR);          outb_pic(0x11, PIC_SLAVE_CMD);    /* ICW1: select 8259A-2 init */          /* ICW2: 8259A-2 IR0-7 mapped to IRQ8_VECTOR */        outb_pic(IRQ8_VECTOR, PIC_SLAVE_IMR);        /* 8259A-2 is a slave on master's IR2 */        outb_pic(PIC_CASCADE_IR, PIC_SLAVE_IMR);        /* (slave's support for AEOI in flat mode is to be investigated) */        outb_pic(SLAVE_ICW4_DEFAULT, PIC_SLAVE_IMR);          if (auto_eoi)               /*                * In AEOI mode we just have to mask the interrupt                * when acking.                */               i8259A_chip.mask_ack = disable_8259A_irq;        else               i8259A_chip.mask_ack = mask_and_ack_8259A;          udelay(100);          /* wait for 8259A to initialize */          outb(cached_master_mask, PIC_MASTER_IMR); /* restore master IRQ mask */        outb(cached_slave_mask, PIC_SLAVE_IMR);       /* restore slave IRQ mask */          raw_spin_unlock_irqrestore(&i8259A_lock, flags); }   函数具体内容我就不详细分析了,无非是些outb或者outb_pic通过这个芯片所占用的总线端口对它进行初始化。回到init_ISA_irqs113行,刚才看见nr_legacy_irqs NR_IRQS_LEGACY,也就是16次循环,将全局irq_desc[irq]数组的16irq_desc元素进行初始化;然后16次通过set_irq_chip_and_handler_name函数将每个irq_descchip字段通过set_irq_chip设置成全局变量i8259A_chip,以及将每个irq_deschandle_irqname字段通过__set_irq_handler分别设置为handle_level_irq” XT”   回到native_init_IRQ中,随后242行调用apic_intr_init()函数:   202static void __init apic_intr_init(void)  203{  204        smp_intr_init();  205  206#ifdef CONFIG_X86_THERMAL_VECTOR  207        alloc_intr_gate(THERMAL_APIC_VECTOR, thermal_interrupt);  208#endif  209#ifdef CONFIG_X86_MCE_THRESHOLD  210        alloc_intr_gate(THRESHOLD_APIC_VECTOR, threshold_interrupt);  211#endif  212#if defined(CONFIG_X86_MCE) && defined(CONFIG_X86_LOCAL_APIC)  213        alloc_intr_gate(MCE_SELF_VECTOR, mce_self_interrupt);  214#endif  215  216#if defined(CONFIG_X86_64) || defined(CONFIG_X86_LOCAL_APIC)  217        /* self generated IPI for local APIC timer */  218        alloc_intr_gate(LOCAL_TIMER_VECTOR, apic_timer_interrupt);  219  220        /* IPI for X86 platform specific use */  221        alloc_intr_gate(X86_PLATFORM_IPI_VECTOR, x86_platform_ipi);  222  223        /* IPI vectors for APIC spurious and error interrupts */  224        alloc_intr_gate(SPURIOUS_APIC_VECTOR, spurious_interrupt);  225        alloc_intr_gate(ERROR_APIC_VECTOR, error_interrupt);  226  227        /* Performance monitoring interrupts: */  228# ifdef CONFIG_PERF_EVENTS  229        alloc_intr_gate(LOCAL_PENDING_VECTOR, perf_pending_interrupt);  230# endif  231  232#endif  233}   smp_intr_init()函数,32x86体系中是一个空函数。后面206229设置中断描述符表中APIC相关的中断服务层序,也就是把位于32~255之间,除去系统调用外其他中断向量的中断处理程序设置为interrupt[i]interrupt共有NR_VECTORS-FIRST_EXTERNAL_VECTOR个表项: #define NR_VECTORS               256 #define FIRST_EXTERNAL_VECTOR              0x20  //32 并且interrupt数组在arch/x86/kernel/entry_32.S中定义并初始化:   823.section .init.rodata,"a"  824ENTRY(interrupt)  825.text  826        .p2align 5  827        .p2align CONFIG_X86_L1_CACHE_SHIFT  828ENTRY(irq_entries_start)  829        RING0_INT_FRAME  830vector=FIRST_EXTERNAL_VECTOR  831.rept (NR_VECTORS-FIRST_EXTERNAL_VECTOR+6)/7  832        .balign 32  833  .rept 7  834    .if vector < NR_VECTORS  835      .if vector <> FIRST_EXTERNAL_VECTOR  836        CFI_ADJUST_CFA_OFFSET -4  837      .endif  8381:      pushl $(~vector+0x80)   /* Note: always in signed byte range */  839        CFI_ADJUST_CFA_OFFSET 4  840      .if ((vector-FIRST_EXTERNAL_VECTOR)%7) <> 6  841        jmp 2f  842      .endif  843      .previous  844        .long 1b  845      .text  846vector=vector+1  847    .endif  848  .endr  8492:      jmp common_interrupt  850.endr  851END(irq_entries_start)  852  853.previous  854END(interrupt)   看到上面的代码,从824854行都属于数据段中,看到828~851之间的代码,虽然表面上看上去它们是分隔开的,但是实际上编译之后会在数据段中一块连续的区域中。我们看到从831~850行,执行循环NR_VECTORS-FIRST_EXTERNAL_VECTOR+6次,也就是说是256-32+6=230次,那么代码展开后,这段数据的内容就类似于: ENTRY(interrupt)       .long 1b       .long 1b       .long 1b …… 即构成225个项的interrupt[]数组,每个