2009年(49)
分类: LINUX
2009-05-25 12:16:06
CHAPTER 4 INTERRUPTS AND EXCEPTIONS
一
designate
vt.
指明, 指出, 任命, 指派
v.
指定, 指派
Intel 把同步和异步中断叫做异常和中断。
operand
[计] 操作数
relinquish
v.
放弃
二 中断和异常
内核把中断响应分成两个部分:关键部分(上半部)和不重要部分(下半部)
中断:可屏蔽,不可屏蔽
异常:(1)处理器检测到的异常
CPU 在发生异常的时候,存到内核栈里面的eip有三种不同的值。
错误:通常情况是可以被修正的。所以eip存的地方是发生错误的地方
陷阱:eip的值通常是在trap过后需要到的地址(编程里面的debug用的到)
退出:严重的错误,不可回复,eip没有存储。
(2)可编程的异常
程序员可控,指令int or int3 , into ,bound 这个也可以叫做软中断(software interrupts),用来执行system call等
中断和异常被定义成0-255之间的数。Intel是8bit的vector,其中不可屏蔽中断和异常是确定的。
三 中断和中断请求(IRQ)
每隔硬件设备的控制器都有一个中断请求线,它被连到一个叫做Programmable interrupt controller(PIC)的地方。可编程中断控制器。
PIC可以disable IRQs,但是这样不会使中断丢失,当PIC enable 他们的时候,他们又会被发送到CPU去。这个跟屏蔽中断不一样。
老的PIC有15个IRQs(两个
四 异常
80x86有大概20个异常,每个异常有一个自己的VECTOR,每个vector对应到一个异常处理程序中(handler),通常会发一个UNIX signal来通知一个异常。
五 Interrupt Descriptor Table 中断描述符表
中断描述符表将每一个中断或者异常向量对应到一个中断或异常处理程序的地址上。
IDT很像LDT和GDT,每个入口对应着一个中断或者异常向量。里面有8BYTE的描述符。这样就一共有(256×8 = 2048)2kB的IDT。Idtr 寄存器允许IDT被放到内存的任何一个可用的位置上。LDT有三种描述符。(1 task gate descriptor 2 interrupt gate descriptor 3 trap gate descriptor)(在linux里面好像不是这门分的。其中没有1,其他的两个也被分成不同的。参看后面)
?其中中断门中要禁止可屏蔽中断 (IF)而在陷阱门里面确不用,这时为什么?
六 硬件是怎么处理中断和异常的
如果CPU检测到有中断或者异常发生,则:
1 检查引起的向量(vector)
2在idtr中读取IDT中对应到向量的入口。由此找到对应的描述符(里面可以找到对应的处理程序)
3 在IDT中,取出描述符里面的选择符(selector),选择GDT里面的base address。确定里面处理程序。
4 确定这个中断是被允许的。用CPL和GDT里面的DPL对比,若CPL
5 进一步查看DPL和CPL相等不,如果不相等,说明是在不同的MODE下面,这样需要切换到新的Privilege下面的STACK下。
在TSS中取出新MODE下面的ss和esp,这样将堆栈切换到新MODE下
在新栈下面保存先前的ss和esp。(已经切换了,怎么可能还能存储老的?)
6 这时候如果是异常错误(fault)发生的话,会将这个时候的cs和eip load。?
7 将eflags,cs,eip入栈
8 如果异常带来一个硬件错误码,入栈。
9 通过IDT中的segment selector 和offset,载入cs和eip,这就是中断异常处理程序的入口。
在中断或者异常返回的时候,则:
1 取出栈中的cs,eip,eflags。(如果有错误码,先出栈)
2 检查CPL和cs下面的2bit的DPL一不一样,如果一样,则执行IRET,如果不一样,则执行下面的过程
3 将栈中的ss 和esp 取出。这些代表着以前的privilege level 的堆栈地址。
4 检查ds,es,fs和gs这些段寄存器有没有被修改过。
七 中断和异常的嵌套
中断处理程序可以抢占其他的中断和异常,但是异常从不抢占中断的处理程序。
八 初始化IDT
Int 指令可以允许USER MODE 进程产生任何一个从0-255的中断信号。所以IDT必须要很仔细的初始化,以免USER MODE下的进程能够模拟不允许的中断和异常。(讲DPL设0)
Linux下面有几种不同的中断描述符,跟INTEL介绍的不太一样。1 Interrupt gate 2 system gate 3 system interrupt gate 4 trap gate 5 task gate(这些具体的应用不太清楚)
Set_intr_gate(n,addr)可以设置一个中断门到nth IDT的入口,addr表示offset。
最初的IDT初始化
IDT初始化的时候还是会在实模式下被BIOS的routines用到,当linux开始后,IDT会被搬到RAM的另外的地方,并且会被初始化第二次。在这个阶段,会移掉没有意义的中断和异常的入口。
defer
vi.
推迟, 延期, 听从, 服从
vt.
使推迟, 使延期
九 异常处理程序
包含三部:1.存储大部分寄存器里面的内容到kernel mode stack。
初略有以下几部:更多参见p150
a.将c function可能要使用的寄存器压栈。
b.将error cold 从esp+36中取出,附上-1回去,这样可以区分0x80(system call)和其他的异常。
c.将esp+32中的c function 取出到edi,write es
d.调用c function,地址在edi中
2.用C function来处理异常(通常需要发送signal到current process)。
C function调用do_trap() function 来存储错误代码和异常向量(vector)到当前的进程描述符中(task_struct)。然后给进程发送信号(signal)。
这个信号会被user mode 处理,如果设置了signal handler。或者在kernel mode被处理,通常为终止进程。(这个机制可以在LINUX C编程下面用到)
3.ret_from_exception()来退出异常处理程序
IDT中必须初始化异常的处理函数
例如 set_trap_gate(0,÷_error); (0向量对应这异常里面的divide_error函数)
十 中断处理程序
中断处理不用发信号,因为当信号被相应的时候当前进程已经被挂起了,没有意义。
中断处理程序分成三种:1.I/O 中断
2.定时器中断
3.处理器间中断
十一 I/O中断处理程序
I/O中断通常很灵活用来为不同的设备服务。许多设备共享同一IRQ line,所以中断向量并不能提供所有的信息。
中断处理程序有两种方法来提供这种灵活性:
1. IRQ 共享
中断处理程序会执行多种interrupt service routines(ISRs)(这些设备共享一个IRQ line)
2. IRQ 动态分配
当设备需要使用的时候才会被分配到IRQ line上面。(当然这样的话不同的设备就不能同时使用)
Linux 将中断分成三种不同的执行:
1 critical 在中断程序里面执行,屏蔽可屏蔽中断
2 noncritical 在中断程序里面执行,不屏蔽任何中断
3 noncritical deferrable 后面阐述
中断处理程序通常的4个过程:(对比异常处理程序的过程)
1. 存储IRQ value 和寄存器的内容到kernel mode stack
2. 给PIC发送确认,以允许处理下一个中断
3. 处理共享到IRQ上面的所有IRQs
4. ret_from_intr();
There are three ways to select a line for an IRQ-configurable device:
1. 跳线
2. 装载在设备上的程序来选
3. 在系统开始的时候由硬件协议来选。
十二 IRQ数据结构
具体的图参看p156
每个中断向量有一个irq_desc_t的描述符。所有的irq_desc_t被安排在一个irq_desc array上。
许多个设备可以共享一个IRQ,因此内核维护了一个irqaction的descriptor,来联系一个设备和它的中断。
Irq_desc à irq_desc_t à irqaction
十三 multiple kernel mode stack
如果内核栈有8kb,那么所有的内核控制路径都被用来为中断,异常这些来服务。
如果内核栈只有4kb,那么内核就要利用三种不同的内核栈:
1. 异常栈 2. 硬IRQ栈 3. 软IRQ栈
十四 具体的中断过程
1.如前所述,存储寄存器的功能是中断处理程序里面的第一个步骤,这个是在interrupt[n]里面执行的
Interrupt[n]:
SAVE_ALL
Movl %esp,%eax
Call do_IRQ
Jmp ret_from_intr
SAVE_ALL:
Cld
Push %es
Push %ds
Pushl %eax
Pushl %ebp
Pushl %edi
Pushl %esi
Pushl %edx
Pushl %ecx
Pushl %ebx
Movl $__USER_DS,%edx
Movl %edx,%ds
Movl %edx,%es
SAVE_ALL里面存储了所有的CPU的寄存器,除了eflags,cs,eip,ss 和esp,因为这些寄存器已经在控制单元里面被自动存储了,具体的情况参看前面的内容。
2. do_IRQ() 函数
这个函数的功能
a.用irq_enter() macro给preempt_count (在thread_info里面)加1,中断嵌套的时候有用。
b.如果内核栈是4kb,那么分配硬中断的堆栈。
C.调用__do_IRQ()函数
d.执行irq_exit()宏,来给interrupt count减一
e.ret_from_intr();
3.__do_IRQ()函数
处理的内容我没有细看:)有点看不懂
4.interrupt service routines IRQs
调用handle_IRQ_event()函数
这个函数用来执行必须执行的IRQs
Retval = 0;
do{
retval |= action ->handler(irq,action->dev_id, regs);
action = action->next; //使action指向下一个要处理的IRQs
}while (action);
前面说到,可以动态分配IRQ line给设备,这个的实现方法就是用1.request_irq()2.setup_irq()3.free_irq()来实现的。
1用来创建一个新的irqaction的描述符,并且初始化它。
2 用来将这个描述符插入到一个合适的IRQ表中。
3用来将一个描述符从IRQline上移除。并且release the memory