中断系统的初始化

来源:互联网 发布:华晨培训网络教育 编辑:程序博客网 时间:2024/06/10 07:45
      linux系统中的中断系统就是对关于中断的汇编指令集的一个包装,将所有的中断功能进行集中处理,为各个中断建立相应的处理程序,本文主要目的是记录linux0.11下面的中断系统实现方式。
      中断初始化过程主要有:在内存中建立中断描述符表、中断项的初始化及中断描述项与对应的处理程序的关联。下面先说明中断描述符表的建立。从代码上看主要就是下面几段。所有的中断基本上都是基于代码-1和代码-2所建立的数据结构,代码-3是两个数据结构的初始化过程。
      linux系统运行在保护模式下时会依赖几个基本的硬件相关的数据结构,它们包括全局描述符表、局部描述符表及中断描述符表。其中全局描述符表的基地址由一个寄存器来保存即:GDTR(全局描述符表寄存器),中断描述符表的基地址由相应的寄存器来保存即:IDTR(中断描述符表寄存器),这两个寄存器的存取都由相应的汇编指令。下面只说明有关中断描述符表寄存器的指令: lidt(加载IDT)和中断寄存器结构。
       IDTR(中断寄存器)的结构。IDTR由两部分组成:指示中断描述符基地址的32位段基址和指出中断描述符长度的16位界限,共48位。代码-1就是这一结构所需要的数据结构。第92行就是加载中断寄存器的代码,head.s采用AT&T汇编码格式,所以lidt idt_descr中的标示符代表内存地址而非立即数据寻址。
       IDT(中断描述符表)结构。代码-1中_idt的标示符的基址表示中断描述符表所在的基地址,而在其相应内存中开辟空间的声明代码为:代码-2。填充结构为256项每一项为8个字节,并将所有的空间填充为0。
       下面对代码-3作详细说明。
/*       代码-1 中断描述表寄存器所需要的结构 (出自boot/head.s)                            */
220 .align 2
221 .word 0
222 idt_descr:
223         .word 256*8-1           # idt contains 256 entries
224         .long _idt


/*                 代码-2 中断描述表所占用的数据结构 (出自boot/head.s)                           */
232 _idt:   .fill 256,8,0           # idt is uninitialized

/*                  代码-3 中断数据结构的初始化和中断描述寄存器初始化(出自boot/head.s)                         */
67 /*
68 * setup_idt
69 *
70 * sets up a idt with 256 entries pointing to
71 * ignore_int, interrupt gates. It then loads
72 * idt. Everything that wants to install itself
73 * in the idt-table may do so themselves. Interrupts
74 * are enabled elsewhere, when we can be relatively
75 * sure everything is ok. This routine will be over-
76 * written by the page tables.
77 */
78 setup_idt:
79         lea ignore_int,%edx
80         movl $0x00080000,%eax
81         movw %dx,%ax                /* selector = 0x0008 = cs */
82         movw $0x8E00,%dx        /* interrupt gate - dpl=0, present */
83
84         lea _idt,%edi
85         mov $256,%ecx
86 rp_sidt:
87         movl %eax,(%edi)
88         movl %edx,4(%edi)
89         addl $8,%edi
90         dec %ecx
91         jne rp_sidt
92         lidt idt_descr
93         ret
94


先说明中断描述符表中每一项的结构。


+-------------------+-----+-------+---+----------+
|    OFFSET31--16   |P|DPL|01110  |000|(NOT USED)|
+-------------------+-----+-------+---+----------+
|    SELECTOR       |      OFFSET 15--0          |
+-------------------+----------------------------+
                                                                图-1
每一个中断描述符项都由上面的一个结构组成,由8个字节组成,其中OFFSET15--0和OFFSET31--16是用来标定某一个代码段上中断程序入口的偏移地址;SELECTOR表示GDT或LDT中的可执行代码段描述符。SELECTOR的结构为:
+-------------+--+-----+
| INDEX15---3 |TI| RPL | 
+-------------+--+-----+
                                   图-2

BIT15到BIT3为是指向某一描述符表中其中一项的索引。TI表示此SELECTOR指向GDT还是指向LDT,如果TI=0则表示此选择子指向一全局描述符表,否则指向局部描述符表。中断描述符表中每一项是指向某一段描述符的一项这主要由SELECTOR来实现,而其中的偏移量(32位偏移量)主要是表示在SELECTOR所选定的代码段项中断处理程序的入口偏移。

       中断执行过程如下图所示。

+---------------------------+
|           IDTR            |----+ INT5
+---------------------------+    |
#         图-3 中断描述符表寄存器          |  
#                                |
+---------------------------+ <--+  
|         中断描述符1            |
+---------------------------+
|         中断描述符2            |
+---------------------------+
|         中断描述符3            |
+---------------------------+
|         中断描述符4            |
+-------------+--+---+------+
| INDEX15---3 |TI|RPL|其它项   |----+ [TI=1 INDEX=3] 
+-------------+--+---+------+    |
|         中断描述符6            |    |
+---------------------------+    |
#         图-4中断描述符表              |
#                                |
+---------------------------+    |
|       局部代码段描述符1           |    |
+---------------------------+    |
|       局部代码段描述符2           |    |
+---------+-----------------+ <--+
|  基地址BASE|限长度LIMIT         |----+[LIMIT=0X20 ]
+---------+-----------------+    |[BASE=0X1234]
|       局部代码段描述符4           |    |
+---------------------------+    |
#         图-5局部代码段描述符表           |
#                                |
+---------------+                |  
|      线性内存     |                |
+---------------+---0X1234       | 
|      线性内存     | <--------------+
+---------------+
|      线性内存     |
+---------------+
|      线性内存     |
+---------------+
|      线性内存     |
+---------------+
|      线性内存     |
+---------------+
#   图-6 线性地址


      调用过程是:程序触发INT指令或CPU内部出现错误时触发中断指令,下面以程序用INT指令触发中断处理过程为例说明中断调用所涉及到的系统数据结构及系统寄存器及其引用过程(INT 5)。
      首先程序调用INT指令,CPU完成对现有运行环境入栈工作,然后根据中断描述符表寄存器中中断描述符表地址找到中断描述符表。再根据INT指令后的中断向量来确实中断向量表中的项(指向第五项)。再由中断描述符表第五项中的选择子[TI=1 INDEX=3]来确实局部描述符表中的项[3]再由局部描述符表项中的基地址来确实中断处理程序所在的线性内存中的地址,然后再由分页机制完成物理内存的定位。确实好中断处理程序后就将其加载入CPU执行,执行完成后返回中断以前执行的环境,继承中断以前的执行。
      下面针对上面所写的内容看linux0.11代码中是如何初始化各种中断数据结构的。先看代码-3的79--83行
79         lea ignore_int,%edx
80         movl $0x00080000,%eax
81         movw %dx,%ax                /* selector = 0x0008 = cs */
82         movw $0x8E00,%dx        /* interrupt gate - dpl=0, present */
79行中的ingnore_int是作者为中断描述符表设置的一个空的处理程序没有实现代码代码为下面的代码-4


/*                                       代码-4默认中断处理程序[出自boot/head.s]          */
146 /* This is the default interrupt "handler" :-) */
147 int_msg:
148         .asciz "Unknown interrupt\n\r"
149 .align 2
150 ignore_int:
151         pushl %eax
152         pushl %ecx
153         pushl %edx
154         push %ds
155         push %es
156         push %fs
157         movl $0x10,%eax
158         mov %ax,%ds
159         mov %ax,%es
160         mov %ax,%fs
161         pushl $int_msg
162         call _printk
163         popl %eax
164         pop %fs
165         pop %es
166         pop %ds
167         popl %edx
168         popl %ecx
169         popl %eax
170         iret
171

79         lea ignore_int, %edx 将中断处理函数地址加载到寄存器edx中,
80         movl $0x00080000,%eax 将数据放入寄存器eax中。
81         movw %dx,%ax  将edx的低16字节[dx]放入eax中的低16字节[ax]中,这样eax中的高16字节中就是0x0008,低16个字节就是ignore_int地址的低16字节,所以eax中就是图-1中的低32位数据。
82         movw $0x8E00,%dx 再将0x8E00放入edx的低16字节中,这样edx就形成了图-1中的高32位数据。

下面说明84--93,这部分代码主要用来初始化中断描述符表及中断描述符寄存器。
84         lea _idt,%edi 将中断描述符表基地址放入edi中,作为初始化的初始地址。
85         mov $256,%ecx 中断描述符表一共有256项,所以将十进制的256放入ecx寄存器
86 rp_sidt: 循环的开始位置。
87         movl %eax,(%edi)    将eax寄存器中的内容放入edi指定的内存中,
88         movl %edx,4(%edi)   将edx寄存器中的内容放入edi+4指定的内存中
89         addl $8,%edi     将指针edi增加8以指向下一个中断描述符表项起始地址。
90         dec %ecx     计数器减1
91         jne rp_sidt     判定然后跳转
92         lidt idt_descr     填充中断描述符寄存器。
93         ret
设置中断、陷阱门

      当linux系统在head.s中完成中断系统的初步的初始化以后,系统在/init/main.c中继续进行中断系统的初始化工作,这部分工作主要是对head.s中设置的ignore_int中断处理函数进行替换,换上真正处理中断请求的函数。
      这部分工作主要在main.c中完成。下面看main.c中是如何完成中断系统的进一步初始化的。

/*                           代码-1main.c中的中断初始化[出自/init/main.c]          */
126         mem_init(main_memory_start,memory_end);
127         trap_init();
128         blk_dev_init();
129         chr_dev_init();
130         tty_init();
131         time_init();
132         sched_init();
133         buffer_init(buffer_memory_end);
134         hd_init();
135         floppy_init();
136         sti();

      其中第127行是陷阱门的初始化代码,第132行是时钟中断门和系统调用中断门的初始化代码。下面先说明127行代码如下:

/*                                         代码-2trap_init函数定义[出自/kernel/traps.c]                              */
181 void trap_init(void)
182 {
183         int i;
184
185         set_trap_gate(0,&divide_error);
186         set_trap_gate(1,&debug);
187         set_trap_gate(2,&nmi);
188         set_system_gate(3,&int3);       /* int3-5 can be called from all */
189         set_system_gate(4,&overflow);
190         set_system_gate(5,&bounds);
191         set_trap_gate(6,&invalid_op);
192         set_trap_gate(7,&device_not_available);
193         set_trap_gate(8,&double_fault);
194         set_trap_gate(9,&coprocessor_segment_overrun);
195         set_trap_gate(10,&invalid_TSS);
196         set_trap_gate(11,&segment_not_present);
197         set_trap_gate(12,&stack_segment);
198         set_trap_gate(13,&general_protection);
199         set_trap_gate(14,&page_fault);
200         set_trap_gate(15,&reserved);
201         set_trap_gate(16,&coprocessor_error);
202         for (i=17;i<48;i++)
203                 set_trap_gate(i,&reserved);
204         set_trap_gate(45,&irq13);
205         outb_p(inb_p(0x21)&0xfb,0x21);
206         outb(inb_p(0xA1)&0xdf,0xA1);
207         set_trap_gate(39,&parallel_interrupt);
208 }

以第185行为例说明每一个中断的初始化过程。 set_trap_gate为一个设置中断描述符表的宏定义,其中的&divide_error为取得中断处理函数divide_error的地址。下面先说明各个中断设置宏的定义,再说明各个中断处理函数的定义格式。
      set_trap_gate及set_system_gate定义在/include/asm/system.h

/*                             代码-3set_trap_gate及set_system_gate宏定义         */
22 #define _set_gate(gate_addr,type,dpl,addr) \
23 __asm__ ("movw %%dx,%%ax\n\t" \
24         "movw %0,%%dx\n\t" \
25         "movl %%eax,%1\n\t" \
26         "movl %%edx,%2" \
27         : \
28         : "i" ((short) (0x8000+(dpl<<13)+(type<<8))), \
29         "o" (*((char *) (gate_addr))), \
30         "o" (*(4+(char *) (gate_addr))), \
31         "d" ((char *) (addr)),"a" (0x00080000))
32
33 #define set_intr_gate(n,addr) \
34         _set_gate(&idt[n],14,0,addr)
35
36 #define set_trap_gate(n,addr) \
37         _set_gate(&idt[n],15,0,addr)
38
39 #define set_system_gate(n,addr) \
40         _set_gate(&idt[n],15,3,addr)


下面说明代码 _set_gate宏定义。

/*                            代码-4 _set_gate宏定义[出自/include/asm/system.h]         */
22 #define _set_gate(gate_addr,type,dpl,addr) \
23 __asm__ ("movw %%dx,%%ax\n\t" \
24         "movw %0,%%dx\n\t" \
25         "movl %%eax,%1\n\t" \
26         "movl %%edx,%2" \
27         : \
28         : "i" ((short) (0x8000+(dpl<<13)+(type<<8))), \
29         "o" (*((char *) (gate_addr))), \
30         "o" (*(4+(char *) (gate_addr))), \
31         "d" ((char *) (addr)),"a" (0x00080000))

这段代码整体上是c语言的宏定义,宏定义内部嵌入了AT&T格式的汇编代码。AT&T和c语言的混合编程的语法在这里

+-------------------+-----+-------+---+----------+
|    OFFSET31--16   |P|DPL|TYPE   |000|(NOT USED)|
+-------------------+-----+-------+---+----------+
|    SELECTOR       |      OFFSET 15--0          |
+-------------------+----------------------------+
#                  图-1中断描述符
      先说明图-1中的各个位意义。P代表SELECTOR所指向的段是否存在,如果为1表示存在其为一个BIT位,DPL表示描述符权限级别,分为四级0-3。由2BIT组成。TYPE由5BIT组成,表示此描述符是中断门还是陷阱门或是任务门。00101表示是任务门、01110表示中断门、01111表示陷阱门。[linux中没有用到任务门的概念]
      通过中断门的转移和通过陷阱门的转移之间的差别只是对IF标志的处理。对于中断门,在转移过程中把IF置为0,使得在处理程序执行期间屏蔽掉INTR中断 (当然,在中断处理程序中可以人为设置IF标志打开中断,以使得在处理程序执行期间允许响应可屏蔽中断);对于陷阱门,在转移过程中保持IF位不变,即如 果IF位原来是1,那么通过陷阱门转移到处理程序之后仍允许INTR中断。因此,中断门最适宜于处理中断,而陷阱门适宜于处理异常。
      下面解释四个输入参数。
参数1: "i" ((short) (0x8000+(dpl<<13)+(type<<8)))[在嵌入代码中表示为%0]。0x8000表示描述符中的P为1;dpl<<13将程序所输入的权限级别向左移动13位正好是DPL所在的位置;type<<8将程序所输入的类型值向左移动8位正好是描述符中TYPE所在的位置。其中参数值14代表中断门、15参数值代表陷阱门。所以些输入参数就是设置描述符的高四字节的低16位。
参数2:"o" (*((char *) (gate_addr))),表示描述符在内存中的低32位的起始地址。
参数3:"o" (*(4+(char *) (gate_addr))),表示描述符在内存中的高32位的起始地址。
参数4: "d" ((char *) (addr)),将中断处理函数的地址放入到寄存器edx中。
参数5:"a" (0x00080000),将立即数(0x00080000)放入寄存器eax中。
      下面说明汇编代码。
23 __asm__ ("movw %%dx,%%ax\n\t" \ 将edx寄存器的低16位放入eax的低16位中,也即:将中断处理函数地址的低16位放入eax的低16位中。这样eax就形成了描述符所需要的低32位数据
24         "movw %0,%%dx\n\t" \ 将第一个参数放入到edx寄存器的低16位中,这样edx就形成了描述符所需要的高32位数据。
25         "movl %%eax,%1\n\t" \ 存入描述符的低32位数据
26         "movl %%edx,%2" \  存入描述符的高32位数据

代码-3中的如下定义就不再说明了主要的功能都是由_set_gate宏来完成的。
33 #define set_intr_gate(n,addr) \
34         _set_gate(&idt[n],14,0,addr)
35
36 #define set_trap_gate(n,addr) \
37         _set_gate(&idt[n],15,0,addr)
38
39 #define set_system_gate(n,addr) \
40         _set_gate(&idt[n],15,3,addr)

下面看代码-2中的中断处理函数定义方式,下面以数据divide_error为例说明其定义结构。

/*                代码-5 中断处理函数的声明[出自/kernel/trap.c]                 */
43 void divide_error(void);
44 void debug(void);
45 void nmi(void);

/*               代码-6中断处理函数[第一阶段]定义 [出自/kernel/asm.s]                */

14 .globl _divide_error,_debug,_nmi,_int3,_overflow,_bounds,_invalid_op
15 .globl _double_fault,_coprocessor_segment_overrun
16 .globl _invalid_TSS,_segment_not_present,_stack_segment
17 .globl _general_protection,_coprocessor_error,_irq13,_reserved
18
19 _divide_error:
20         pushl $_do_divide_error
21 no_error_code:
22         xchgl %eax,(%esp)
23         pushl %ebx
24         pushl %ecx
25         pushl %edx
26         pushl %edi
27         pushl %esi
28         pushl %ebp
29         push %ds
30         push %es
31         push %fs
32         pushl $0                # "error code"
33         lea 44(%esp),%edx
34         pushl %edx
35         movl $0x10,%edx
36         mov %dx,%ds
37         mov %dx,%es
38         mov %dx,%fs
39         call *%eax
40         addl $8,%esp
41         pop %fs
42         pop %es
43         pop %ds
44         popl %ebp
45         popl %esi
46         popl %edi
47         popl %edx
48         popl %ecx
49         popl %ebx
50         popl %eax
51         iret

/*            代码-7中断处理函数[第二阶段]定义[出自/kernel/traps.c]    */               

97 void do_divide_error(long esp, long error_code)
98 {
99         die("divide error",esp,error_code);
100 }

调用过程是代码-6中的定义会调用代码-7中所定义的do_XXXX_XXXX函数。实际的工作由一系列的do_XXXX_XXXX来完成。

原创粉丝点击