Chinaunix首页 | 论坛 | 博客
  • 博客访问: 14860
  • 博文数量: 5
  • 博客积分: 265
  • 博客等级: 二等列兵
  • 技术积分: 60
  • 用 户 组: 普通用户
  • 注册时间: 2008-06-14 20:10
文章分类
文章存档

2009年(3)

2008年(2)

我的朋友
最近访客

分类:

2009-10-06 12:01:29

IRQ

每个设备至少有一条IRQ设备线,每条线都会连接到该设备的PIC(中断控制器),是多对一的关系。

 

PIC的功能是:

1)  PIC监视该设备的IRQ线,如果IRQ有请求,则PIC选择请求队列中,最低编号的那个IRQ

2)  PIC将该IRQ线的请求信号组合成一个中断向量

3)  PIC将该向量存放在PICIO端口里,以便CPU检测到

4)  做好这些准备工作后,PICCPUINTR发消息,通知中断发生

5)  等待CPUPIC的某个IO端口写结束标志,然后将INTR信号清理

6)  返回到第一步的循环

 

对于特定的每条IRQ设备线,PIC可以选择是否启动它,如果该IRQ线被disable,则中断控制器PIC不会将此中断提交到CPU,但是会保留此中断,一旦IRQ线被enable,则PIC立刻将此中断提交到CPU处理。

 

使能enable和无效disable IRQ线,与通常意义上的屏蔽中断有些区别。对于X86来说,和屏蔽有关的寄存器为eflagsIF位,如果该位清零,则所有被PIC提交至CPU的中断都被忽略。对于powerpc e500来说,相应的寄存器为MSR

 

通常情况下,在单CPU系统中,PIC可以直接连接到CPUINTR pin。如果是多CPU系统,PIC应该怎么连接?

 

对于多CPU来说,PIC被替换成另外一种更高级的中断控制器,对X86系列,是APIC;对于Powerpc来说,是MPIC

每个CPU有一个自己的local APIClocal APIC连接到所有CPU公有的external APIC

每个local APIC具有2IRQ中断线LINT0LINT1,而external APIC24IRQ中断线,以及一个具有24entry的中断定向表(Interrupt Redirection Table),该定向表的作用是,当外部中断到达external APIC时,由中断表来确定发送到哪个local APIC

 

对于外部中断的分发,有两种方式:

1)  静态分发。IRQ信号根据定向表中的设定,往特定的CPU发放

2)  动态分发。IRQ信号根据每个CPU的运行的进程优先级来决定发送至哪个CPU,一般该CPU正在执行的进程,优先级越低的,会更容易被发送到。该CPU运行的进程优先级,是由该CPUlocal APIC里的TPR来保存的。这个优先级的控制,是由软件,也就是内核来设置的。假定某个时刻,两个CPU执行的进程优先级一样,则再比较该CPUarbitration priority register,选取优先级高的那个CPU,注意,这里按我的理解,应该是选取较高优先级的那个。另外,关于arbitration priority的计算,是按照round-robin来计算的,该机制是由硬件来实现。每个CPU被分配一个015的优先级,(可以看出,这个算法最多支持16核),当某个CPU被分配到去处理一个中断后,它的LOCAL APIC里的arbitration priority会被置0,然后其他的CPU相应加1,如果其他CPU已经是15了,则该CPUarbitration被置为上一次获得中断的CPUarbitration值加1-----说的这么复杂,其实就一句话,如果是15,就被置1。这样,一定不会有冲突。

Ps:核间中断也是通过local APICextrenal APIC发送的。

 

Interrupt Descriptor Table (IDT )即是存放中断向量的数据结构,理论上可以存放在内存的任意位置。每个IDTentry8字节组成,也最多可以有256entryEntry的类型有三种:

1)  TASK gate 

2)  Interrupt gate 处理中断

3)  Trap gate 处理异常

 

 

中断,异常的硬件处理过程

 

CPUcontrol unit负责在一条指令结束后,判断是否在上一条指令执行时产生了中断或者异常,如果有,则进行如下操作:

1)  判断当前中断号(0255,前面说过,IDT最多256entry

2)  找到第ientry,根据这个entryselector字段,再到GDT里取出相应的段描述符

3)  检查中断的有效性,即,先从cs寄存器的CPL字段(Current Priviledge Level),是否小于等于GDTentry—segment descriptor里的DPL字段,如果不是,则发一个“保护”异常。注意,这段内容是在深入理解内核一书中摘抄来的,该书的表述有误,原文是DPL大于CPL会出异常。解释:此时,cs寄存器是代码段基地址,eip是偏移,eip指向下一条指令。CPL的值是“用户态”的特权级,而DPL是中断程序的特权级。

4)  如果是编程异常,还会检查CPLIDT中的DPL,看是否小于IDT中描述符的特权级。为什么要这么设计?

5)  检查是否发生特权级变化,就是说,特权级提升。如果是的话,要建立新的堆栈

a)       通过读tr寄存器,获得当前进程TSS段,TSS段的作用,个人理解是,在中断处理过程中,CPU通过该段得到内核堆栈的地址;

b)      用中断栈的地址替换当前ssesp指针;

c)       在新栈中压入之前的ssesp的值。

6)  保留eflagscs以及eip的值到栈里,(即应该执行的下一条用户指令地址)

7)  IDT表中的段选择符和偏移分别赋给cseip寄存器。注意,是用IDT的选择符来替换cs,因为代码寄存器中,存放的都是段选择符。

最后一步7,实际就是跳转到中断处理程序,处理完后,执行ret,该指令的作用是,用保存在栈上的原cs,eip,eflags替换现有寄存器(返回到用户态),如果用户态处理程序的优先级和当前中断处理一样,则ret结束,否则,从栈中再恢复ss,esp,返回到用户态处理程序栈,检查所有段寄存器,如果DPL小于CPL(用户态特权大于中断特权),则清零。这样做,是为了保证用户态程序不会提高优先级。

 

关于中断嵌套,书里讲的不多,只说了中断嵌套的好处:

a)       当设备给CPU发送中断后,就会阻塞,一直等到CPU回应。由于允许内核的中断嵌套,所以内核可以及时的发送回应给设备

b)      中断没有优先级。所有的中断一来,就打断上一个中断,这样设计可以保证内核的简单,然而可能会出现饿死等情况。

 

 

 

关于中断门的初始化:

 

X86setup_idt函数里,将IDT256entry全部置为ignore_int地址(代码段选择符eax+偏移ignore_init edx

 

setup_idt:

        lea ignore_int, %edx

        movl $(_ _KERNEL_CS << 16), %eax

        movw %dx, %ax       /* selector = 0x0010 = cs */

        movw $0x8e00, %dx   /* interrupt gate, dpl=0, present */

        lea idt_table, %edi

        mov $256, %ecx

    rp_sidt:

        movl %eax, (%edi)

        movl %edx, 4(%edi)

        addl $8, %edi

        dec %ecx

        jne rp_sidt

        ret

 

 

程序对异常的处理过程:

 

1)    保存当前寄存器到内核态堆栈

2)      调用高级C异常处理函数

3)      从处理程序返回

 

类似于:handler_name:

        pushl $0

        pushl $do_handler_name

        jmp error_code

 

其中,error_code函数,最终调用do_handler_name执行异常处理

do_handler_name里,会将当前进程控制块current的异常向量等字段,设置为当前向量(什么意思??)然后往当前进程发信号(什么信号?)

 

        current->thread.error_code = error_code;

    current->thread.trap_no = vector;

force_sig(sig_number, current);

 

最后do_handler_name调用ret_from_exception,从异常返回。

 

 

 

阅读(824) | 评论(0) | 转发(0) |
0

上一篇:软中断

下一篇:mips的关中断

给主人留下些什么吧!~~