Chinaunix首页 | 论坛 | 博客
  • 博客访问: 744925
  • 博文数量: 370
  • 博客积分: 2334
  • 博客等级: 大尉
  • 技术积分: 3222
  • 用 户 组: 普通用户
  • 注册时间: 2012-05-06 16:56
文章分类

全部博文(370)

文章存档

2013年(2)

2012年(368)

分类: LINUX

2012-05-27 18:59:38

1 IRQ


讲到中断的硬件环境,我们先从著名的IRQ信号谈起。每个能够发出中断请求的硬件设备控制器都有一条名为IRQ的输出线。所有现有的IRQ线都与一个名为可编程中断控制器(PIC)的硬件电路的输入引脚相连,可编程中断控制器执行下列动作:

1.    监视IRQ线,检查产生的信号。如果有条或两条以上的IRQ线上产生信号,就选择引脚编号较小的IRQ线。
2.    如果一个引发信号出现在IRQ线上:

 

a)    把接收到的引发信号转换成对应的向量号(参见上一篇博文)。
b)    把这个向量存放在中断控制器的一个I/O端口(0x20、0x21),从而允许CPU通过数据总线读此向量。
c)    把引发信号发送到处理器的INTR引脚,即产生一个中断。
d)    等待,直到CPU通过把这个中断信号写进可编程中断控制器的一个I/O端口来确认它;当这种情况发生时,清INTR线。

 

3.    返回第1步。

可以有选择地禁止某条IRQ线。因此,可以对PIC编程从而禁止IRQ,也就是说,可以告诉PIC停止对给定的IRQ线发布中断,或者激活它们。这个特点被大多数中断处理程序使用,因为这允许中断处理程序逐次地处理同一类型的IRQ。

这里要注意一下,有选择地激活/禁止IRQ线不同于上一篇博文中提到的可屏蔽中断的全局屏蔽/非屏蔽,一个是对PIC编程,一个是对CPU编程(汇编)。 当eflags寄存器的IF标志被清0时,由PIC发布的每个可屏蔽中断都由CPU暂时忽略。cli和sti汇编指令分别清除和设置该标志。

传统的PIC是由两片8259A风格的外部芯片以“级联”的方式连接在一起的。每个芯片可以处理多达8个不同的IRQ输入线。因为从PIC的INT输出线连接到主PIC的IRQ2引脚,因此,可用IRQ线的个数为15。

从P3开始,Intel引入了一种I/O高级可编程控制器(APIC),以替代老式8259A可编程中断控制器。首先,为了支持老式操作系统,新系统将两 个级联的老式PIC 8259A保留了下来。其次,新的80x86CPU的控制单元都含有一个本地的APIC。这不奇怪,为的就是支持多CPU或者多核的体系。每个本地 APIC都有32位寄存器,一个内部时钟,一个本地定时设备以及为本地APIC中断保留的两条额外IRQ线LINT0和LINT1。所有本地APIC都连 接到同一个外部I/O APIC,形成一个多APIC的系统。对于APIC,其本质跟传统8259A其实是一样的,只是多了一层在多CPU上分发外部中断请求的I/O APIC,这里不在话下。

2 中断描述符表


我们前边谈到,中断系统的主要目的就是改变程序的执行顺序。具体如何改变?也就是一个调用中断处理程序的过程,这里我们就要引出中断描述符表了。中断描述 符表(Interrupt Descriptor Table,IDT)是一个系统表,它与每一个中断或异常向量相联系,每一个向量在表中有相应的中断或异常处理程序的入口地址。内核在允许中断发生前,必 须适当地初始化IDT。

IDT是一个向量表,表中的每一项对应一个中断或异常向量,每个向量由8个字节组成。因此,最多需要256 × 8=2048字节来存放IDT,正好是半个页面。那么,这个IDT到底放在什么位置呢?有了idtr CPU寄存器,IDT就可以位于内存的任何地方。idtr寄存器指定IDT的线性基地址及其限制(最大长度)。在允许中断之前,必须用lidt汇编指令初 始化idtr。

IDT包含三种类型的描述符,下图显示了每种描述符中的64位(8个字节)的含义。尤其值得注意的是,在40~43位的Type字段的值表示三种描述符的类型。

 

中断描述符

这些描述符是:

任务门(task gate):当中断信号发生时,必须取代当前进程的那个进程的TSS选择符存放在任务门中。

中断门(interrupt gate):包含段选择符和中断或异常处理程序的段内偏移量。当控制权转移到一个适当的段时,处理器清IF标志,从而关闭将来会发生的可屏蔽中断。

陷阱门(Trap gate):与中断门相似,只是控制权传递到一个适当的段时处理器不修改IF标志。

这里再联系上一篇博文强调一下,Linux利用中断门处理中断,利用陷阱门处理异常

3 中断和异常的硬件处理


好了,有了前面的基本概念后,我们现在开始分析CPU控制单元如何处理中断和异常。我们假定内核已被初始化,因此,CPU在保护模式下运行。当执行了一条 指令后,CS和eip这对寄存器包含下一条将要执行的指令的逻辑地址。在处理那条指令之前,控制单元会检查在运行前一条指令时是否已经发生了一个中断或异 常。如果发生了一个中断或异常,那么控制单元执行下列操作:

1.    确定与中断或异常关联的向量i (0 ≤ i ≤ 255)。
2.    读由idtr寄存器指向的 IDT表中的第i项(在下面的分析中,我们假定IDT表项中包含的是一个中断门或一个陷阱门)。
3.    从gdtr寄存器获得GDT的基地址,并在GDT中查找,以读取IDT表项中的选择符所标识的段描述符。这个描述符指定中断或异常处理程序所在段的基地址。
4.    确信中断是由授权的(中断)发生源发出的。首先将当前特权级CPL(存放在cs寄存器的低两位)与段描述符(存放在GDT中)的描述符特权级DPL比较, 如果CPL小于DPL,就产生一个“General protection”异常,因为中断处理程序的特权不能低于引起中断的程序的特权。对于编程异常,则做进一步的安全检查:比较CPL与处于IDT中的门 描述符的DPL,如果DPL小于CPL,就产生一个“General protection”异常。这最后一个检查可以避免用户应用程序访问特殊的陷阱门或中断门。
5.    检查是否发生了特权级的变化,也就是说,CPL是否不同于所选择的段描述符的DPL。如果是,控制单元必须开始使用与新的特权级相关的栈。通过执行以下步骤来做到这点:


i.    读tr寄存器,以访问运行进程的TSS段。
ii.    用与新特权级相关的栈段和栈指针的正确值装载ss和esp寄存器。这些值可以在TSS中找到(参见第三章的“任务状态段”一节)
iii.    在新的栈中保存ss和esp以前的值,这些值定义了与旧特权级相关的栈的逻辑地址。


6.    如果故障已发生,用引起异常的指令地址装载CS和eip寄存器,从而使得这条指令能再次被执行。
7.    在栈中保存eflags、CS及eip的内容。
8.    如果异常产生了一个硬件出错码,则将它保存在栈中。
9.    装载cs和eip寄存器,其值分别是IDT表中第i项门描述符的段选择符和偏移量字段。这些值给出了中断或者异常处理程序的第一条指令的逻辑地址。

控制单元所执行的最后一步就是跳转到中断或者异常处理程序。换句话说,处理完中断信号后,控制单元所执行的指令就是被选中处理程序的第一条指令。

中断或异常被处理完后,相应的处理程序必须产生一条iret指令,把控制权转交给被中断的进程,这将迫使控制单元:

1.    用保存在栈中的值装载CS、eip或eflags寄存器。如果一个硬件出错码曾被压入栈中,并且在eip内容的上面,那么,执行iret指令前必须先弹出这个硬件出错码。
2.    检查处理程序的CPL是否等于CS中最低两位的值(这意味着被中断的进程与处理程序运行在同一特权级)。如果是,iret终止执行;否则,转入下一步。
3.    从栈中装载ss和esp寄存器,因此,返回到与旧特权级相关的栈。
4.    检查ds、es、fs及gs段寄存器的内容,如果其中一个寄存器包含的选择符是一个段描述符,并且其DPL值小于CPL,那么,清相应的段寄存器。控制单 元这么做是为了禁止用户态的程序(CPL=3)利用内核以前所用的段寄存器(DPL=0)。如果不清这些寄存器,怀有恶意的用户态程序就可能利用它们来访 问内核地址空间。

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

上一篇:中断描述符表

下一篇:中断的分类

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