Chinaunix首页 | 论坛 | 博客
  • 博客访问: 156589
  • 博文数量: 49
  • 博客积分: 2510
  • 博客等级: 少校
  • 技术积分: 445
  • 用 户 组: 普通用户
  • 注册时间: 2009-05-16 23:55
文章分类
文章存档

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之间的数。Intel8bitvector,其中不可屏蔽中断和异常是确定的。

 

中断和中断请求(IRQ

  每隔硬件设备的控制器都有一个中断请求线,它被连到一个叫做Programmable interrupt controller(PIC)的地方。可编程中断控制器。

  PIC可以disable IRQs但是这样不会使中断丢失,当PIC enable 他们的时候,他们又会被发送到CPU去。这个跟屏蔽中断不一样。

  老的PIC15IRQs(两个8259a),APIC24个。

 

异常

  80x86有大概20个异常,每个异常有一个自己的VECTOR,每个vector对应到一个异常处理程序中(handler),通常会发一个UNIX signal来通知一个异常。

 

 

 

 

Interrupt Descriptor Table 中断描述符表

  中断描述符表将每一个中断或者异常向量对应到一个中断或异常处理程序的地址上。

  IDT很像LDTGDT,每个入口对应着一个中断或者异常向量。里面有8BYTE的描述符。这样就一共有(256×8 = 20482kBIDTIdtr 寄存器允许IDT被放到内存的任何一个可用的位置上。LDT有三种描述符。(1 task gate descriptor 2 interrupt gate descriptor 3 trap gate descriptor)(在linux里面好像不是这门分的。其中没有1,其他的两个也被分成不同的。参看后面)

  其中中断门中要禁止可屏蔽中断 IF)而在陷阱门里面确不用,这时为什么?

硬件是怎么处理中断和异常的

  如果CPU检测到有中断或者异常发生,则:

  1 检查引起的向量(vector

2idtr中读取IDT中对应到向量的入口。由此找到对应的描述符(里面可以找到对应的处理程序)

3 IDT中,取出描述符里面的选择符(selector),选择GDT里面的base address。确定里面处理程序。

  4 确定这个中断是被允许的。用CPLGDT里面的DPL对比,若CPL则不允许。在可编程异常里面,要进一步将CPLIDT里面的DPL对比,避免用户进程进入指定的陷阱或中断门。

  5 进一步查看DPLCPL相等不,如果不相等,说明是在不同的MODE下面,这样需要切换到新的Privilege下面的STACK下。

    TSS中取出新MODE下面的ssesp,这样将堆栈切换到新MODE

在新栈下面保存先前的ssesp。(已经切换了,怎么可能还能存储老的?

  6 这时候如果是异常错误(fault)发生的话,会将这个时候的cseip   load。?

  7 eflagscseip入栈

  8 如果异常带来一个硬件错误码,入栈。

  9 通过IDT中的segment selector offset,载入cseip,这就是中断异常处理程序的入口。

 

  在中断或者异常返回的时候,则:

  1 取出栈中的cseipeflags。(如果有错误码,先出栈)

  2 检查CPLcs下面的2bitDPL一不一样,如果一样,则执行IRET,如果不一样,则执行下面的过程

  3 将栈中的ss esp 取出。这些代表着以前的privilege level 的堆栈地址。

  4 检查dsesfsgs这些段寄存器有没有被修改过。

 

中断和异常的嵌套

  中断处理程序可以抢占其他的中断和异常,但是异常从不抢占中断的处理程序。

 

初始化IDT

  Int 指令可以允许USER MODE 进程产生任何一个从0-255的中断信号。所以IDT必须要很仔细的初始化,以免USER MODE下的进程能够模拟不允许的中断和异常。(讲DPL0

  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初始化的时候还是会在实模式下被BIOSroutines用到,当linux开始后,IDT会被搬到RAM的另外的地方,并且会被初始化第二次。在这个阶段,会移掉没有意义的中断和异常的入口。

 

defer

vi.

推迟, 延期, 听从, 服从

vt.

使推迟, 使延期

异常处理程序

  包含三部:1.存储大部分寄存器里面的内容到kernel mode stack

              初略有以下几部:更多参见p150

              a.将c function可能要使用的寄存器压栈。

              b.将error cold esp+36中取出,附上-1回去,这样可以区分0x80system call)和其他的异常。

              c.将esp+32中的c function 取出到ediwrite es

              d.调用c function,地址在edi

2.C function来处理异常(通常需要发送signalcurrent 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_gate0÷_error);  0向量对应这异常里面的divide_error函数)

 

中断处理程序

  中断处理不用发信号,因为当信号被相应的时候当前进程已经被挂起了,没有意义。

  中断处理程序分成三种:1.I/O 中断

                        2.定时器中断

                        3.处理器间中断

 

十一 I/O中断处理程序

  I/O中断通常很灵活用来为不同的设备服务。许多设备共享同一IRQ line,所以中断向量并不能提供所有的信息。

  中断处理程序有两种方法来提供这种灵活性:

1.       IRQ 共享

中断处理程序会执行多种interrupt service routinesISRs)(这些设备共享一个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,因此内核维护了一个irqactiondescriptor,来联系一个设备和它的中断。

   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的寄存器,除了eflagscseipss esp,因为这些寄存器已经在控制单元里面被自动存储了,具体的情况参看前面的内容。

  2. do_IRQ() 函数

  这个函数的功能

     a.用irq_enter() macropreempt_count (在thread_info里面)加1,中断嵌套的时候有用。

     b.如果内核栈是4kb,那么分配硬中断的堆栈。

     C.调用__do_IRQ()函数

     d.执行irq_exit()宏,来给interrupt count减一

     eret_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

阅读(2429) | 评论(0) | 转发(0) |
给主人留下些什么吧!~~