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

1.中断描述符表的定义(arch/x86/kernel/traps.c)

1 gate_desc debug_idt_table[NR_VECTORS] __page_aligned_bss;

定义的描述符表为一个结构体数组,数组元素类型为gate_desc,大小为8B。NR_VECTORS宏为256,即描述符表大小为256*8B。

2.idt_descr变量的定义(arch/x86/kernel/head_32.S)

1 idt_descr:
2     .word IDT_ENTRIES*8-1        # idt contains 256 entries
3     .long idt_table
4
5 # boot GDT descriptor (later on used by CPU#0):
6     .word 0                # 32 bit align gdt_desc.address

这是内核定义的一个全局变量,存放有中断描述符表的大小和首地址。该变量将存放在idtr寄存器中。

3.中断描述符初步的初始化(arch/x86/kernel/head_32.S)

 1 __INIT
 2 setup_once:
 3     /*
 4      * Set up a idt with 256 entries pointing to ignore_int,
 5      * interrupt gates. It doesn‘t actually load idt - that needs
 6      * to be done on each CPU. Interrupts are enabled elsewhere,
 7      * when we can be relatively sure everything is ok.
 8      */
 9
10     movl $idt_table,%edi
11     movl $early_idt_handlers,%eax12     movl $NUM_EXCEPTION_VECTORS,%ecx
13 1:
14     movl %eax,(%edi)
15     movl %eax,4(%edi)
16     /* interrupt gate, dpl=0, present */
17     movl $(0x8E000000 + __KERNEL_CS),2(%edi)
18     addl $9,%eax
19     addl $8,%edi
20     loop 1b
21
22     movl $256 - NUM_EXCEPTION_VECTORS,%ecx
23     movl $ignore_int,%edx
24     movl $(__KERNEL_CS << 16),%eax
25     movw %dx,%ax        /* selector = 0x0010 = cs */
26     movw $0x8E00,%dx    /* interrupt gate - dpl=0, present */
27 2:
28     movl %eax,(%edi)
29     movl %edx,4(%edi)
30     addl $8,%edi
31     loop 2b
32         ...
33         ...

这段代码是对中断描述符表的初步初始化,14-20行是对前32个中断描述符进行初始化,让所有描述符指向early_idt_handlers处理函数。22-31行是对后256-32=224个中断描述符进行初始化,使之指向ignore_int处理函数。省略号以后是对GDT描述符表的初始化,这里不予讨论。

4.中断描述符表最终的初始化(arch/x86/kernel/traps.c)

 1 void __init trap_init(void)
 2 {
 3     int i;
 4
 5 #ifdef CONFIG_EISA
 6     void __iomem *p = early_ioremap(0x0FFFD9, 4);
 7
 8     if (readl(p) == ‘E‘ + (‘I‘<<8) + (‘S‘<<16) + (‘A‘<<24))
 9         EISA_bus = 1;
10     early_iounmap(p, 4);
11 #endif
12
13     set_intr_gate(X86_TRAP_DE, divide_error);
14     set_intr_gate_ist(X86_TRAP_NMI, &nmi, NMI_STACK);
15     /* int4 can be called from all */
16     set_system_intr_gate(X86_TRAP_OF, &overflow);
17     set_intr_gate(X86_TRAP_BR, bounds);
18     set_intr_gate(X86_TRAP_UD, invalid_op);
19     set_intr_gate(X86_TRAP_NM, device_not_available);
20 #ifdef CONFIG_X86_32
21     set_task_gate(X86_TRAP_DF, GDT_ENTRY_DOUBLEFAULT_TSS);
22 #else
23     set_intr_gate_ist(X86_TRAP_DF, &double_fault, DOUBLEFAULT_STACK);
24 #endif
25     set_intr_gate(X86_TRAP_OLD_MF, coprocessor_segment_overrun);
26     set_intr_gate(X86_TRAP_TS, invalid_TSS);
27     set_intr_gate(X86_TRAP_NP, segment_not_present);
28     set_intr_gate_ist(X86_TRAP_SS, &stack_segment, STACKFAULT_STACK);
29     set_intr_gate(X86_TRAP_GP, general_protection);
30     set_intr_gate(X86_TRAP_SPURIOUS, spurious_interrupt_bug);
31     set_intr_gate(X86_TRAP_MF, coprocessor_error);
32     set_intr_gate(X86_TRAP_AC, alignment_check);
33 #ifdef CONFIG_X86_MCE
34     set_intr_gate_ist(X86_TRAP_MC, &machine_check, MCE_STACK);
35 #endif
36     set_intr_gate(X86_TRAP_XF, simd_coprocessor_error);
37
38     /* Reserve all the builtin and the syscall vector: */
39     for (i = 0; i < FIRST_EXTERNAL_VECTOR; i++)
40         set_bit(i, used_vectors);
41
42 #ifdef CONFIG_IA32_EMULATION
43     set_system_intr_gate(IA32_SYSCALL_VECTOR, ia32_syscall);
44     set_bit(IA32_SYSCALL_VECTOR, used_vectors);
45 #endif
46
47 #ifdef CONFIG_X86_32
48     set_system_trap_gate(SYSCALL_VECTOR, &system_call);FIRST_EXTERNAL_VECTOR
49     set_bit(SYSCALL_VECTOR, used_vectors);
50 #endif
51
52     /*
53      * Set the IDT descriptor to a fixed read-only location, so that the
54      * "sidt" instruction will not leak the location of the kernel, and
55      * to defend the IDT against arbitrary memory write vulnerabilities.
56      * It will be reloaded in cpu_init() */
57     __set_fixmap(FIX_RO_IDT, __pa_symbol(idt_table), PAGE_KERNEL_RO);
58     idt_descr.address = fix_to_virt(FIX_RO_IDT);
59
60     /*
61      * Should be a barrier for any external CPU state:
62      */
63     cpu_init();
64
65     x86_init.irqs.trap_init();
66
67 #ifdef CONFIG_X86_64
68     memcpy(&debug_idt_table, &idt_table, IDT_ENTRIES * 16);
69     set_nmi_gate(X86_TRAP_DB, &debug);
70     set_nmi_gate(X86_TRAP_BP, &int3);
71 #endif
72 }

该函数对中断描述表的进行了部分初始化,13-36行对系统已分配的异常和非屏蔽中断进行初始化,中断向量号为0-19。接着,39-40行在中断位图表中对已初始化的中断所对应的位进行标记。接着,43和48行又出始化了两个中断,一个是系统中断门,中断向量号为0x80,一个是系统陷阱门,中断向量号为2。

在该函数中,大家可以看出,对中断进行初始化的函数有如下几个:

1 set_intr_gate()
2 set_system_intr_gate()
3 set_system_trap_gate()4 set_task_gate()

这几个函数也在arch/x86/kernel/traps.c中定义。分别是对中断门,系统中断门,系统陷阱门,任务门描述符的初始化。进一步深入可发现,这几个函数都调用了如下的函数:

 1 static inline void _set_gate(int gate, unsigned type, void *addr,
 2                  unsigned dpl, unsigned ist, unsigned seg)
 3 {
 4     gate_desc s;
 5
 6     pack_gate(&s, type, (unsigned long)addr, dpl, ist, seg);
 7     /*
 8      * does not need to be atomic because it is only done once at
 9      * setup time
10      */
11     write_idt_entry(idt_table, gate, &s);
12     write_trace_idt_entry(gate, &s);
13 }

该函数定义在arch/x86/include/asm/desc.h文件中。在该函数中定义了一个gate_desc类型变量s,并将s的指针传递给pack_gate函数,把要初始化的描述符各个字段的值临时存放在s中。下边分析下pack_gate函数,在分析该函数之前,我们先看下gate_desc结构体。

 1 struct desc_struct {
 2     union {
 3         struct {
 4             unsigned int a;
 5             unsigned int b;
 6         };
 7         struct {
 8             u16 limit0;
 9             u16 base0;
10             unsigned base1: 8, type: 4, s: 1, dpl: 2, p: 1;
11             unsigned limit: 4, avl: 1, l: 1, d: 1, g: 1, base2: 8;
12         };
13     };
14 } __attribute__((packed));

typedef struct desc_struct gate_desc

该结构体定义位于arch/x86/include/asm/desc_defs.h中。该结构体中包含了一个共用体,共用体中又包含了两个结构体。我们知道,共用体在分配内存单元时,并不为每个成员都分配,而是为最大的成员来分配。可以看出该共用体的两个结构体成员大小相等,都是8B,因此整个gate_desc结构体大小就为8B。我们可以使用共用体中的任意一个结构体成员来为这个gate_desc赋值,也就是说我们既可以将gate_desc看成是struct { unsigned int a;  unsigned int b; };也可以看成是struct { u16 limit0; u16 base0; .... };下面在分析pack_gate函数过程中将看到赋值过程,我们将gate_desc看作是struct { unsigned int a;  unsigned int b; };。

1 static inline void pack_gate(gate_desc *gate, unsigned char type,
2                  unsigned long base, unsigned dpl, unsigned flags,
3                  unsigned short seg)
4 {
5     gate->a = (seg << 16) | (base & 0xffff);
6     gate->b = (base & 0xffff0000) | (((0x80 | type | (dpl << 5)) & 0xff) << 8);
7 }

该函数也定义在arch/x86/include/asm/desc.h文件中。在该函数中为gate所指向的gate_desc描述符进行初始化。gate->a是描述符的0-31位,gate->b是描述符的32-63位。描述符的如下所示:

接着,我们分析_set_gate()中的11行,write_idt_entry()调用。

1 static inline void native_write_idt_entry(gate_desc *idt, int entry, const gate_desc *gate)
2 {
3     memcpy(&idt[entry], gate, sizeof(*gate));
4 }

#define write_idt_entry() native_write_idt_entry()   //粗略的写了下,大家能明白就行 

该函数定义在arch/x86/include/asm/desc.h中。在该函数中,使用memcpy()函数将gate中的字段复制到&idt[entry]所指向的各个字段中。很显然,idt[]数组就是内核中定义的中断描述符表,我们在文章开头给大家看过该定义。gate就是我们在_set_gate()中定义的临时变量s,在这里我们将s中的字段值赋给idt[]数组的对应元素,至此一个描述符的初始化工作就全部完成了,s变量的用途也就结束了,另外,entry变量中存放的是要初始化的中断向量号,用该号来定位idt数组的元素。

最后,再补充说明一点东西,回头看下第4点中的trap_init()函数,在该函数中对中断描述符表进行初始化,使用了很多初始化函数比如set_intr_gate()或set_system_intr_gate()等等,我们拿第一个初始化函数set_intr_gate(X86_TRAP_DE, divide_error)来做说明。X86_TRAP_DE是枚举类型参数,代表的是中断向量号,定义在arch/x86/include/asm/traps.h文件中。这种枚举类型其实有很多。

 1 /* Interrupts/Exceptions */
 2 enum {
 3     X86_TRAP_DE = 0,    /*  0, Divide-by-zero */
 4     X86_TRAP_DB,        /*  1, Debug */
 5     X86_TRAP_NMI,        /*  2, Non-maskable Interrupt */
 6     X86_TRAP_BP,        /*  3, Breakpoint */
 7     X86_TRAP_OF,        /*  4, Overflow */
 8     X86_TRAP_BR,        /*  5, Bound Range Exceeded */
 9     X86_TRAP_UD,        /*  6, Invalid Opcode */
10     X86_TRAP_NM,        /*  7, Device Not Available */
11     X86_TRAP_DF,        /*  8, Double Fault */
12     X86_TRAP_OLD_MF,    /*  9, Coprocessor Segment Overrun */
13     X86_TRAP_TS,        /* 10, Invalid TSS */
14     X86_TRAP_NP,        /* 11, Segment Not Present */
15     X86_TRAP_SS,        /* 12, Stack Segment Fault */
16     X86_TRAP_GP,        /* 13, General Protection Fault */
17     X86_TRAP_PF,        /* 14, Page Fault */
18     X86_TRAP_SPURIOUS,    /* 15, Spurious Interrupt */
19     X86_TRAP_MF,        /* 16, x87 Floating-Point Exception */
20     X86_TRAP_AC,        /* 17, Alignment Check */
21     X86_TRAP_MC,        /* 18, Machine Check */
22     X86_TRAP_XF,        /* 19, SIMD Floating-Point Exception */
23     X86_TRAP_IRET = 32,    /* 32, IRET Exception */
24 };

第二个参数,是汇编函数的函数名(在这里作为函数指针来使用),该函数为内核原先就定义好的中断或异常处理程序。这种类型的函数有很多,都定义在arch/x86/kernel/entry_32.S文件中,下边我们列举几个给大家看看,有兴趣自己去查。

 1 ENTRY(segment_not_present)
 2     RING0_EC_FRAME
 3     ASM_CLAC
 4     pushl_cfi $do_segment_not_present
 5     jmp error_code
 6     CFI_ENDPROC
 7 END(segment_not_present)
 8
 9 ENTRY(stack_segment)
10     RING0_EC_FRAME
11     ASM_CLAC
12     pushl_cfi $do_stack_segment
13     jmp error_code
14     CFI_ENDPROC
15 END(stack_segment)
16
17 ENTRY(alignment_check)
18     RING0_EC_FRAME
19     ASM_CLAC
20     pushl_cfi $do_alignment_check
21     jmp error_code
22     CFI_ENDPROC
23 END(alignment_check)
24
25 ENTRY(divide_error)
26     RING0_INT_FRAME
27     ASM_CLAC
28     pushl_cfi $0            # no error code
29     pushl_cfi $do_divide_error
30     jmp error_code
31     CFI_ENDPROC
32 END(divide_error)

这些汇编代码只是异常处理程序的开头一部分,可以看到每一个汇编段中,都有一条pushl_cfi $do_***的指令,该$do_***才是真正的异常处理程序(函数名,也是函数指针),现将该函数名压入栈中,然后通过jmp error_code指令跳转到$do_***函数中。error_code其实也是一段汇编代码,在下篇博文中断(二)中,我们将分析该段代码。

至此,中断描述符的初始化工作就告一段落。文中有问题的地方希望大家指正。qq:1193533825

时间: 2024-08-03 04:50:43

中断(一)——中断描述符表的定义和初始化 (基于3.16-rc4)的相关文章

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

上篇博文对中断描述符表(IDT)中异常和非屏蔽中断部分的初始化做了说明,这篇文章将分析中断部分的初始化. 在上篇博文中,可以看到,内核在setup_once汇编片段中,对中断和异常部分做了初步的初始化,用early_idt_handlers函数的地址来初始化异常门描述符,用ignore_int函数地址来初始化剩下的中断门描述符.接着,内核在trap_init函数中对IDT做了进一步的初始化,用有效的异常处理程序来初始化中断向量号为0-31的描述符.细心的你应该可以发现,在这一步初始化过程中,仅仅

嵌入式单片机,外部中断,中断标志位介绍

body, table{font-family: 微软雅黑; font-size: 13.5pt} table{border-collapse: collapse; border: solid gray; border-width: 2px 0 2px 0;} th{border: 1px solid gray; padding: 4px; background-color: #DDD;} td{border: 1px solid gray; padding: 4px;} tr:nth-chil

软中断与硬中断 &amp; 中断抢占 中断嵌套

参考了这篇文章:http://blog.csdn.net/zhangskd/article/details/21992933 从本质上来讲,中断是一种电信号,当设备有某种事件发生时,它就会产生中断,通过总线把电信号发送给中断控制器. 如果中断的线是激活的,中断控制器就把电信号发送给处理器的某个特定引脚.处理器于是立即停止自己正在做的事, 跳到中断处理程序的入口点,进行中断处理. (1) 硬中断 由与系统相连的外设(比如网卡.硬盘)自动产生的.主要是用来通知操作系统系统外设状态的变化.比如当网卡收

《Linux内核设计与实现》学习笔记——中断、中断处理程序

中断和中断处理程序 中断随时可能产生,打断CPU的执行,CPU转而处理中断. 不同的设备对应的中断不同,每个中断都通过一个唯一的数字标志. 这些中断值称为中断请求(IRQ)线,每个irq线关联一个数值. 中断处理程序 响应中断时,内核会执行一个函数,中断处理程序/中断服务例程ISR, 一个设备的中断处理程序是他的设备驱动的一部分. IO资源包括 : 中断,I/O端口,共享RAM,DMA.驱动程序需要管理注册释放这些资源. 上半部:接收到中断就立即执行,只做有严格时限的工作,如对中断应答或复位硬件

树莓派官方自带gpio中断驱动bcm2708_gpio.c原理分析 linux 中断架构 中断子系统

上一篇记录了树莓派自带的gpio驱动(http://www.cnblogs.com/umbrellary/p/5164148.html),在bcm2708_gpio.c实现gpio驱动的同时其实也实现了中断控制器的驱动,本文记录bcm2708_gpio.c中驱动的实现. 一·bcm2708_gpio_irq_init中断初始化函数建立gpio中断描述表 static void bcm2708_gpio_irq_init(struct bcm2708_gpio *ucb) { unsigned i

verilog数组定义及其初始化

这里的内存模型指的是内存的行为模型.Verilog中提供了两维数组来帮助我们建立内存的行为模型.具体来说,就是可以将内存宣称为一个reg类型的数组,这个数组中的任何一个单元都可以通过一个下标去访问.这样的数组的定义方式如下: reg [wordsize : 0] array_name [0 : arraysize]; 例如: reg [7:0] my_memory [0:255]; 其中 [7:0] 是内存的宽度,而[0:255]则是内存的深度(也就是有多少存储单元),其中宽度为8位,深度为25

&lt;24&gt;【掌握】二维数组指针定义、初始化+

[掌握]二维数组指针定义.初始化 数组指针: 定义一个指针变量,让这个指针变量指向一维数组的元素 二维数组指针 行指针,用来指向二维数组的每一行,存放的是行的首地址 定义格式: 数据类型 (*行指针变量名)[数组第二维的长度]; 二维数组指针的初始化 int a[2][3]; int b[2][2]; float f1[4][4]; //假设我要定义一个指向数组a的一个行指针 // a = &a[0] = &a[0][0] = a[0] int (*p)[3] = a; 二维数组指针的使用

你好,C++(7)第三部分 C++世界众生相 3.2.1 变量的定义与初始化

第3部分 C++世界众生相 在听过了HelloWorld.exe的自我介绍,完成了与C++世界的第一次亲密接触后,大家是不是都急不可待地想要一试身手,开始编写C++程序了呢?程序的两大任务是描述数据和处理数据.那么,接下来我们将面临的第一个问题就是:如何在C++中描述数据? 3.1  C++中的数据类型 编程就是使用程序设计语言来描述和表达现实世界.现实世界中有很多客观存在的事物,例如,电脑.人.汽车等.我们总是用各种数据来描述这些事物的不同属性,比如,我们用一个字符串“ChenLiangqia

数组的定义和初始化

一.定义 数组的维数必须用大于等于1的常量表达式来定义 整形字面值常量.枚举常量或者常量表达式初始化的整形const对象: 二.初始化 1.显示初始化数组元素 *在函数体外定义的内置数组,其元素均初始化为0: *在函数体内定义的内置数组,其元素无初始化: *不管数组在哪里定义,如果其元素为类类型,则自动调用该类的默认构造函数进行初始:如果该类没有默认构造函数,则必须为该数组的元素提供显示初始化 2.特殊的字符数组 3.不允许数组直接复制和赋值 // SHUZU.cpp : Defines the