start_kernel接下来要做的事是初始化中断处理系统。整个内核的中断系统的核心就是我们在“初始化中断描述符表”里面设置的那个中断描述符表。而这个表的前19个表项我们已经在“初始化异常服务”中设置为了一些中断和异常的服务。内核接下来会如何设置其余的表项呢?
前面提到过高级可编程中断控制器APIC,这里简单地提一下它的体系结构,简单地说就是由两部分组成:本地高级中断控制器(Local APIC,LAPIC),位于每个CPU中,主要负责传递中断信号到指定的处理器中;I/O高级中断控制器(I/O APIC,)负责搜集来自I/O设备的中断信号并分发给LAPIC,形成一个多级APIC中断分发网络。
接下来的大部分初始化工作都将围绕着LAPIC和IOAPIC这两个东西进行。
回到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_read或apic_write调用全局变量apic的read和write方法。先说说这个全局变量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_read和native_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_irqs的108行是调用该芯片的初始化函数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_irqs,113行,刚才看见nr_legacy_irqs为 NR_IRQS_LEGACY,也就是16次循环,将全局irq_desc[irq]数组的16个irq_desc元素进行初始化;然后16次通过set_irq_chip_and_handler_name函数将每个irq_desc的chip字段通过set_irq_chip设置成全局变量i8259A_chip,以及将每个irq_desc的handle_irq和name字段通过__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()函数,32位x86体系中是一个空函数。后面206~229设置中断描述符表中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) |
看到上面的代码,从824到854行都属于数据段中,看到828~851之间的代码,虽然表面上看上去它们是分隔开的,但是实际上编译之后会在数据段中一块连续的区域中。我们看到从831行~850行,执行循环NR_VECTORS-FIRST_EXTERNAL_VECTOR+6次,也就是说是256-32+6=230次,那么代码展开后,这段数据的内容就类似于:
ENTRY(interrupt)
.long 1b
.long 1b
.long 1b
……
即构成225个项的interrupt[]数组,每个元素占64位,也就是8个字节。我们看到每个元素都是1b,即上面代码1标号后的代码片段:
pushl $(~vector+0x80)
CFI_ADJUST_CFA_OFFSET 4
.if ((vector-FIRST_EXTERNAL_VECTOR)%7) <> 6
jmp 2f
.endif
因此,都会执行代码2标号的那个common_interrupt,其参数为push进去的~vector+0x80。举个例子,比如中断向量32,即IRQ0,对应的中断处理程序interrupt[0]是
pushl $(~0 + 0x80)
CFI_ADJUST_CFA_OFFSET 4
jmp common_interrupt
至于common_interrupt随后如何执行,具体的细节请查阅博客“中断处理(续)” http://blog.csdn.net/yunsongice/archive/2010/03/07/5353896.aspx。
那么在native_init_IRQ设置好了中断描述符表的32~256项之后,也就是interrupt[]的0到224元素作为其处理函数之后,我们就可以设置它了。所以看到随后255、256行,在没有配置CONFIG_ACPI,也就是说,没有启动ACPI控制器的情况下,调用老式的setup_irq函数把2号IRQ设置成一个空函数。263、264行,调用setup_irq函数把13号IRQ设置成FPU容错处理函数:
int setup_irq(unsigned int irq, struct irqaction *act)
{
struct irq_desc *desc = irq_to_desc(irq);
return __setup_irq(irq, desc, act);
}
__setup_irq本质上就是通过irq号得到对应的irq_desc描述符。然后调用__setup_irq()函数初始化它和irqaction,并把这个描述符插入到合适的IRQ链表。具体的代码内容比较多,我就不一一分析了,对上述过程还想深入研究得可以去浏览一下博客“中断处理(续)” http://blog.csdn.net/yunsongice/archive/2010/03/07/5353896.aspx。
最后,由于我们没有配置CONFIG_4KSTACKS编译选项,所以native_init_IRQ的最后一个函数irq_ctx_init是空函数,那么native_init_IRQ结束后,整个init_IRQ函数就结束了,APIC和各本地PIC的相关的中断服务就算初始化完毕了。此时,内核全局变量irq_desc[]数组的16个元素的chip字段都是i8259A_chip;handle_irq字段是handle_level_irq;name字段为“XT”;同时,interrupt[]数组的每一个元素作为中断描述符表表项32~255的服务程序,且仅有interrupt[2]和interrupt[13]分别被初始化成了一个空的处理函数no_action和一个FPU容错函数math_error_irq,其余服务程序都还没有被初始化。