热门标签 | HotTags
当前位置:  开发笔记 > 编程语言 > 正文

中断——中断描述符表的定义和初始化(二)(基于3.16-rc4)

上篇博文对中断描述符表(IDT)中异常和非屏蔽中断部分的初始化做了说明,这篇文章将分析外部中断部分的初始化。在上篇博文中,可以看到,内核在setup_once汇编片段中,对中断和异常部分做了初步的

上篇博文对中断描述符表(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 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中的异常和中断门描述符就初始化完成了。

 


推荐阅读
author-avatar
fyy1784502
这个家伙很懒,什么也没留下!
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有