上篇博文对中断描述符表(IDT)中异常和非屏蔽中断部分的初始化做了说明,这篇文章将分析中断部分的初始化。
在上篇博文中,可以看到,内核在setup_once汇编片段中,对中断和异常部分做了初步的初始化,用early_idt_handlers函数的地址来初始化异常门描述符,用ignore_int函数地址来初始化剩下的中断门描述符。接着,内核在trap_init函数中对IDT做了进一步的初始化,用有效的异常处理程序来初始化中断向量号为0-31的描述符。细心的你应该可以发现,在这一步初始化过程中,仅仅对异常和非屏蔽中断做了初始化(也就是中断向量号前32个),并没有对后256-32=224个中断门描述符初始化,也就是说后244个中断门描述符依然指向的是ignore_int这个无用的函数。下面将分析中断门描述符的最终初始化。首先介绍下interrupt全局数组,该数组中装有了所有的中断处理程序,如下所示:(arch/x86/kernel/entrt_32.S)
1 .section .init.rodata,"a" 2 ENTRY(interrupt) 3 .section .entry.text, "ax" 4 .p2align 5 5 .p2align CONFIG_X86_L1_CACHE_SHIFT 6 ENTRY(irq_entries_start) 7 RING0_INT_FRAME 8 vector=FIRST_EXTERNAL_VECTOR 9 .rept (NR_VECTORS-FIRST_EXTERNAL_VECTOR+6)/7 10 .balign 32 11 .rept 7 12 .if vector < NR_VECTORS 13 .if vector <> FIRST_EXTERNAL_VECTOR 14 CFI_ADJUST_CFA_OFFSET -4 15 .endif 16 1: pushl_cfi $(~vector+0x80) /* Note: always in signed byte range */ 17 .if ((vector-FIRST_EXTERNAL_VECTOR)%7) <> 6 18 jmp 2f 19 .endif 20 .previous 21 .long 1b 22 .section .entry.text, "ax" 23 vector=vector+1 24 .endif 25 .endr 26 2: jmp common_interrupt 27 .endr 28 END(irq_entries_start) 29 30 .previous 31 END(interrupt) 32 .previous
这段代码定义了一个interrupt全局数组,如果不懂ATT汇编的话,这段代码看起来非常吃力。下面笔者粗略分析下这段代码,第1行声明了一个数据段,第2行给这个数据段起了个名字,叫做‘inerrupt’,第3行又声明了一个代码段,该代码段被包在了前边的数据段当中,从第6行可看出这个代码段名字叫做‘irq_entries_start’。接着4-5行说明了代码段对齐的方式,接下来第7行给vector进行赋值,vector=32,实际上,interrup这个数组中存放的全是外部中断,没有异常,异常初始化已经在trap_init函数中完成了,而外部中断的向量号从32开始,所以vector赋值32。接下来在第9,11行大家可以看到出现了伪指令.rept,这个伪指令是循环的意思,你可以把它当成for循环去理解,指令后边的数字是循环次数。这个伪指令实际上告诉编译器要把后边的内容在内存中复制若干次。第9行的(NR_VECTORS-FIRST_EXTERNAL_VECTOR+6)/7值为32,要求其后的内容被循环复制32次。因此第9行和第11行合起来,就相当于一个双重for循环,总共循环32*7=224次,这刚好就是外部中断向量号的数量,每执行一次内部循环,就将一个外部中断处理程序放入了一个数组元素中。接着第20行出现了.previous伪指令。该指令的意思是返回到上一个段中,在这里就是要返回到interrupt数据段中,第21行,在interrupt数据段中定义了一个long型数据,值为标号1,标号1实际上就是第16行代码的地址。接着第22行又回到了当前代码段中,让vector自加1,然后第25行进入内重循环的下一次循环。第26行,标号2的这个指令夹在了内外两重循环之间,说明每执行7次内循环就要将jmp common_interrupt复制一次。然后第27行进入外重循环的下一次循环。总共执行32*7次循环后,这段代码就结束了。通过使用.preivous伪指令,最终实际上就定义了两个数组,一个是interrput数组,该数组的每个元素均为.long 1b(第21行),另外一个数组是irq_entries_start,该数组每个元素中放入了若干条汇编指令(16-18行,26行)。这就是.previous的用处,每在irq_entries_start数组中初始化完一个元素,立马返回到interrupt数组中定义一个指向irq_entries_start数组中刚初始化过的元素的指针(.long 1b),作为interrupt数组的元素。最终interrput数组中存放了224个指针(每个中断处理程序的地址),分别指向了irq_entries_start数组中的对应元素。irq_entries_start数组每个元素存放的是几条汇编指令(这些汇编指令就是中断处理程序的开头公共部分)。而且,通过第26行,可以看到,irq_entries_start数组每个元素都包含jmp common_interrupt指令,跳入到一段公共的代码中。
上边的工作,内核只是把所有的外部中断处理程序用两个数组管理起来了,接下来,就要在IDT(中断描述符表)中初始化所有外部中断的门描述符。代码如下:(arch/x86/kernel/irqinit.c)
1 void __init native_init_IRQ(void) 2 { 3 int i; 4 5 /* Execute any quirks before the call gates are initialised: */ 6 x86_init.irqs.pre_vector_init(); 7 8 apic_intr_init(); 9 10 /* 11 * Cover the whole vector space, no vector can escape 12 * us. (some of these will be overridden and become 13 * ‘special‘ SMP interrupts) 14 */ 15 i = FIRST_EXTERNAL_VECTOR; 16 for_each_clear_bit_from(i, used_vectors, NR_VECTORS) { 17 /* IA32_SYSCALL_VECTOR could be used in trap_init already. */ 18 set_intr_gate(i, interrupt[i - FIRST_EXTERNAL_VECTOR]); 19 } 20 21 if (!acpi_ioapic && !of_ioapic) 22 setup_irq(2, &irq2); 23 24 #ifdef CONFIG_X86_32 25 irq_ctx_init(smp_processor_id()); 26 #endif 27 }
从16-19行,可以看出,用interrupt数组中存放的所有中断处理程序地址来初始化IDT的中断门描述符。set_intr_gate函数上篇博文已经分析过来,这里不再分析。
至此,所有IDT中的异常和中断门描述符就初始化完成了。
中断——中断描述符表的定义和初始化(二) (基于3.16-rc4),布布扣,bubuko.com