中断和异常
Posted pghzl-123
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了中断和异常相关的知识,希望对你有一定的参考价值。
中断和异常
中断通常被定义为改变处理器执行指令顺序的事件。这样的事件对应于CPU芯片内部和外部的硬件电路的信号。
中断可分为
1)外部中断(硬件中断
2)内部中断(软件中断)/异常:
处理器探测异常:由CPU执行指令时探测到一个反常条件时产生,如溢出、除0错等。
对于处理器探测异常,根据异常时保存在内核堆栈中的eip的值可以进一步分为
- 故障(fault):eip=引起故障的指令的地址,通常可以纠正,比如缺页异常
- 陷阱(trap):eip=随后要执行的指令的地址
编程异常:由编程者发出的特定请求产生,通常由int类指令触发,比如系统调用
1、为什么会有中断?
内核的一个重要功能就是处理外设。当外设在进行事件处理时,CPU可以执行别的任务,只有当外设真正完成了准备好了时CPU才转过来处理外设IO;
对外设的处理方式一般是:轮询、DMA等,效率不高;
中断机制提供了一个很好的解决方法。
2、中断信号的作用
中断信号提供了一种特殊的方式,使得CPU转去运行正常程序之外的代码;
当一个中断信号到达时,CPU必须停止它当前正在做的事,并且切换到一个新的活动;
此时必须要如下动作:
在进程的内核态堆栈保存程序计数器的当前值(即eip和cs寄存器)以便处理完中断的时候能正确返回到中断点(中断没有自己的上下文,它占用的是被中断进程的上下文,cs、eip保存在被中断进程的内核栈中);
并把与中断信号相关的一个地址放入进程序计数器,从而进入中断的处理;
3、中断信号的处理:
中断信号的处理原则就是快!
只有中断可以打断中断的执行,允许发生中断嵌套
中断过程中不发生进程抢占:因为linux内核的所有中断程序中都不插入进程调度程序,就不会发生进程调度了
进程调度的时机:1)进程被创建时;2)进程的时间片用完;3)系统调用;4)中断退出时
4、中断的产生:
每个能够发出中断请求的硬件设备控制器都有一条称为IRQ(Interrupt ReQuest)的输出线;
所有的IRQ线都与一个中断控制器的输入引脚相连;
中断控制器与CPU的INTR引脚相连。
中断控制器:监视IRQ线,如果一个引发信号出现在IRQ线上,则
将此信号转换为中断向量,CPU通过数据总线读取向量进行解析,产生中断,转去对中断进行处理,知道收到应答返回。
IRQ号
0-31:异常
32起分给外部中断
5、中断描述符表(IDT)
中断描述符表是一个系统表,它与每一个中断或者异常向量相联系
1)每个中断或异常向量在表中有相应的中断或者异常处理程序的入口地址。
2)每个描述符8个字节,共256项,占用空间2KB
3)内核在允许中断发生前,必须适当的初始化IDT
4)CPU的idtr寄存器指向IDT表的物理基地址
6、如何根据中断向量找到异常处理程序?
首先,任何中断 事件发生,CPU都会得到一个对应的中断向量
中断向量——>中断描述符表的对应项(中断描述符)——>获取中断处理程序的eip+cs(即逻辑地址)——>转去中断处理程序
7、CPU如何得到中断描述符表的位置?
首先我们知道中断号n,
在内核初始化时申请一段内存即IDT表的位置,由idtr寄存器保存这段内存的基地址,CPU通过(idtr)+n*8获取描述符地址(因为一个中断描述符是8个字节)
8、中断执行过程
1)确定与中断或者异常关联的向量i(0~255)
2)读idtr寄存器指向的IDT表中的第i项
3)从gdtr寄存器获得GDT的基地址,并在GDT中查找,以读取IDT表项中的段选择符所标识的段描述符
4)确定中断是由授权的发生源发出的。
- 中断:中断处理程序的特权不能低于引起中断的程
序的特权(对应GDT表项中的DPL vs CS寄存器中的CPL)
- 编程异常:还需比较CPL与对应IDT表项中的DPL
5) 检查是否发生了特权级的变化,一般指是否由用户态陷入了内核态。如果是由用户态陷入了内核态,控制单元必须开始使用与新的特权级相关的堆栈
- a,读tr寄存器,访问运行进程的tss段
- b,用与新特权级相关的栈段和栈指针装载ss和esp寄存器。这些值可以在进程的tss段中找到
- c,在新的栈中保存ss和esp以前的值,这些值指明了与旧特权级相关的栈的逻辑地址
6)若发生的是故障,用引起异常的指令地址修改cs和eip寄存器的值,以使得这条指令在异常处理结束后能被再次执行(这是因为cs,eip默认保存吓一跳指令的地址,但是这个故障还要再次执行当前指令,因此要进行修改)
7)在栈中保存eflags、cs和eip的内容(保存在被中断进程的内核栈中)
8)如果异常产生一个硬件出错码,则将它保存在栈中
9)装载cs和eip寄存器,其值分别是IDT表中第i项门描述符的段选择符和偏移量字段。这对寄存器值给出中断或者异常处理程序的第一条指定的逻辑地址
此时的进程内核态堆栈:
9、从中断/异常返回:
中断/异常处理完后,相应的处理程序会执行一条iret汇编指令,这条汇编指令让CPU控制单元做如下事情:
1)用保存在栈中的值装载cs、eip和eflags寄存器。如果一个硬件出错码曾被压入栈中,那么弹出这个硬件出错码
2)检查处理程序的特权级是否等于cs中最低两位的值(这意味着进程在被中断的时候是运行在内核态还是用户态)。若是,iret终止执行;否则,转入3
3)从栈中装载ss和esp寄存器。这步意味着返回到与旧特权级相关的栈
4)检查ds、es、fs和gs段寄存器的内容,防止恶意用户程序利用这些寄存器访问内核空间
10、初始化中断描述符
1)初步初始化:即汇编初始化,setup_idt 用ignore_int()中断处理程序去填充256项中断描述符表,这是一个空处理程序
2)Start_kernel中再次初始化,内核用有意义的陷阱和中断处理程序替换ignore_int()这个空程序:
- trap_init():对0-31项异常进行初始化,把一些异常处理函数插入到IDT表项中,由set_trap_gate、set_system_gate完成
- init_IRQ:32-255对外部中断进行初始化系统初始化时,调用init_IRQ()函数用新的中断门替换临时中断门来更新IDT
11、异常处理架构:
异常处理有一个标准的结构,由三部分组成
1)在内核态堆栈中保存大多数寄存器的内容
2)调用C语言的函数
3)通过ret_from_exception()从异常处理程序退出
返回指令:jmp ret_from_exception()
返回时判断是否在中断嵌套中(通过特权等级比较判断),如果是,直接恢复现场即可;如果不是中断嵌套,还要判断是否需要进程调度;
12、error_code主要功能
1)按照pt_regs结构定义的堆栈数据格式完成相应的入栈操作,进一步完成现场的保存
2)把堆栈地址中的do_handler_name()函数的地址装入edi寄存器中,并在这个位置写入fs值,使栈结构进一步与pt_regs结构完全一致
3)最后执行call *%edi指令
13、中断处理
中断跟异常不同,它并不是表示程序出错,而是硬件设备有所动作,所以不是简单地往当前进程发送一个信号就OK的
- I/O中断
- 时钟中断
14、I/O中断
给多个设备同时提供服务,通过以下两种方式:
- IRQ共享:中断处理程序执行多个中断服务例程。每个ISR是一个与单独设备(共享IRQ线)相关的函数
- IRQ动态分配:一条IRQ线可能在最后时刻才与一个设备相关联
15、I/O中断处理程序基本操作:
1)在内核态堆栈保存IRQ的值和寄存器的内容
2)为正在给IRQ线服务的PIC发送一个应答,这将允许PIC进一步发出中断
3)执行共享这个IRQ的所有设备的中断服务例程
4)跳到ret_from_intr()的地址后中断跳出
中断处理示意图:
16、中断程序入口操作
1)将中断向量入栈
2)保存所有其他寄存器 SAVE_ALL,按照pt_regs数据结构保存现场
3)调用do_IRQ,见下图
4)跳转到ret_from_intr
do_IRQ使用的数据结构
irqaction数据结构:
用来实现IRQ的共享,维护共享irq的特定设备和特定中断,所有共享一个irq的链接在一个action表中,由中断描述符中的action指针指向
17、中断小结:
在调用do_IRQ之前,要为中断处理程序保存寄存器
这里对所有的中断处理程序都执行相同的代码:
common_interrupt:
SAVE_ALL
movl %esp,%eax
call do_IRQ
jmp $ret_from_intr
18、do_IRQ执行时内核态的堆栈
19、中断服务例程
一个中断服务例程实现一种特定设备的操作, handle_IRQ_event()函数依次调用这些设备例程
以上是关于中断和异常的主要内容,如果未能解决你的问题,请参考以下文章