一、基本概念
1.
产生的位置 | 发生的时刻 | 时序 | |
中断 | CPU外部 | 随机 | 异步 |
异常 | CPU正在执行的程序 | 一条指令终止执行后 | 同步 |
2.由中断或异常执行的代码不是一个进程,而是一个内核控制路径,代表中断发生时正在运行的进程的执行
中断处理程序与正在运行的程序无关
引起异常处理程序的进程正是异常处理程序运行时的当前进程
二、特点
1.(1)尽可能快
(2)能以嵌套的方式执行,但是同种类型的中断不可以嵌套
(3)尽可能地限制临界区,因为在临界区中,中断被禁止
2.大部分异常发生在用户态,缺页异常是唯一发生于内核态能触发的异常
缺页异常意味着进程切换,因此中断处理程序从不执行可以导致缺页的操作
3.中断处理程序运行于内核态
中断发生于用户态时,要把进程的用户空间堆栈切换到进程的系统空间堆栈,刚切换时,内核堆栈是空的
中断发生于内核态时, 不需要堆栈空间的切换
三、分类
1.中断的分类:可屏蔽中断、不可屏蔽中断
2.异常的分类:
分类 | 解决异常的方法 | 举例 |
故障 | 那条指令会被重新执行 | 缺页异常处理程序 |
陷阱 | 会从下一条指令开始执行 | 调试程序 |
异常中止 | 强制受影响的进程终止 | 发生了一个严重的错误 |
四、IRQ
1.硬件设备控制器通过IRQ线向CPU发出中断,可以通过禁用某条IRQ线来屏蔽中断。
2.被禁止的中断不会丢失,激活IRQ后,中断还会被发到CPU
3.激活/禁止IRQ线 != 可屏蔽中断的 全局屏蔽/非屏蔽
五、中断描述符表IDT
1.基本概念
中断描述符表是一个系统表,它与每一个中断或异常向量相联系,每一个向量在表中有相应的中断或异常处理程序入口地址。
在允许发生中断以前,必须适当地初始化IDT
TSS只能位于GDT中,IDT能位于内存的任何的地方
2.中断描述符
硬件提供的中断描述符:
(1)任务门:中断信号发生时,必须取代当前进程的那个进程的TSS选择符存放在任务门中
(2)中断门:包含段选择符和中断处理程序的段内偏移
(3)陷阱门:与中断门的唯一区别是,通过中断门进入服务程序后,自动关中断,而通过陷阱门进入服务程序不自动关中断
Linux中使用的中断描述符:
中断描述符的类型 | 用户态能否访问 | 用户态的访问方式 | 能激活的程序 |
中断门 | 否 | 所有的Linux中断处理程序 | |
系统门 | 是 | into、bound、int $0x80 | 向量号为4,5,128的三个Linux异常处理程序 |
系统中断门 | 是 | int 3 | 与向量3相关的异常处理程序 |
陷阱门 | 否 | 大部分Linux异常处理程序 | |
任务门 | 否 | Linux对Double fault异常的处理程序 |
Linux利用中断门处理中断,利用陷阱门处理异常
Double fault是唯一用任务处理的异常
3.中断向量与中断和异常的关系
(1)每个中断和异常是由0-255之间的一个数来标识的,这个数就是1中的中断向量
(2)大约有20种异常,内核为每一个异常分配了一种中断/异常向量分别是0-19
(3)0x80是系统调用的中断向量
(4)32-255是内核为什么中断分配的中断向量。然而,224个中断向量显然不够,因此系统为每个中断向量设置一个队列,根据每个中断源所使用的中断向量,将其中断服务程序挂到相应的队列中。中断发生时,先执行与中断向量相对应的一段总服务程序,再根据具体的中断源设备号在其所属的队列找到特定的中断服务程序加以执行。
4.中断向量、与中断向量相对应的总服务程序、某个中断源的中断服务程序之间的关系如图所示:
(1)irq_desc是中断向量描述符队列(中断描述符是INT的一项,中断向量描述符是一个数据结构,用于描述与中断向量相关的服务程序)
(2)irq_desc_t是中断向量描述符的数据结构
(3)irqaction是挂在某个中断向量的具体的中断服务程序的描述符,组成一个队列
(4)hw_irq_controller是这个中断向量的总服务程序
六、IDT的初始化
1.两次初始化
运行模式 | 初始值 | 使用者 | |
第一次 | 实模式 | 空处理程序 | BIOS例程 |
第二次 | 保护模式 | 有意义的中断处理程序或异常处理程序 | Linux系统 |
2.在IDT表的初始化完成之初,每个中断处理队列都是空的,此时即使打开中断并且某个外设中断真的发生了,也得不到实际的服务,因为没有执行具体的中断处理程序。
真正的中断服务要到具体设备的初始化程序将其中断处理程序ISR挂入某个中断请求队列后才会发生
3.在允许发生中断以前,必须适当地初始化IDT
七、激活中断或异常(以下内容都是由硬件自动完成)
1.确定与中断或异常相关的中断向量号
中断:硬件设备控制器通过IRQ向CPU发出信号,中断管制器把接受到的信号转换为中断向量号i
异常:对于软件指令发出或产生的异常,CPU会差别归类错误的类别,这个类别号就是中断向量
2.IDT第i项 -----> 段选择符 ----->段描述符 -----> 段基址
3.IDT第i项 -----> 偏移量
4.段基址 + 偏移量 -----> 中断处理程序第一条指令的地址
5.在栈中保存EFLAGS、CS、EIP的内容
6.如果异常产生了一个出错码,把它保存在栈中
7.装载CS、EIP,其值分别是2-段选择符和4-偏移量,由这两个寄存器可得到中断或异常处理程序第一条指令的地址
八、找到中断或异常处理程序的第一条指令后,跳转这到这一指令的过程
1.中断
(1)在当前进程的内核堆栈中保存IRQ的值,为什么与系统调用号区分,保存的是-n
(2)在当前进程的内核堆栈中保存寄存器的值:SAVE_ALL
EFLAGS、CS、EIP、SS、ESP不包括在内,因为它们由控制单元自动保存(见七-7)
(3)把栈顶的地址存放到EAX中
(4)把用户段的选择符装到DS和ES中
(5)调用do_IRQ(),地址保存在CS、EIP中(见七-7)
(6)为正在给IRQ线服务的PIC(中断控制器)一个应答,这将允许PIC进一步发出中断
(7)执行共享这个IRQ的所有设备的ISR(总服务程序称为IRQ,某个设备的具体的服务程序称为ISR)
(8)跳到ret_from_intr()的地址后终止
(6)(7)(8)都是在(5)中被调用的,见十
2.异常
(1)如果异常发生时,控制单元没有自己把一个出错码压出栈中(见七-6),则压入一个空值。
这个“凑数”的出错码不在正常的出错码应该在的位置,以下-步是为了把它调整到它应该在的位置
(3)把异常处理程序的地址压入栈中
(4)把异常处理程度可能用到的寄存器保存到栈中
(5)把栈中位于ESP+36处的硬件出错码拷贝到EDX中,给栈中这一位置存上-1
(6)把保存在栈中ESP+32位置的异常处理程序的地址装入EDI中,给栈中的这一位置写入ES的值
(7)把栈当栈顶拷贝到EAX中
(8)把用户段的选择符装到DS和ES中
(9)调用地址在EDI中的异常处理程序
九、从中断或异常处理程序返回的过程
1.跳转到用于返回的代码的入口点
(1)中断ret_from_intr()
(2)异常:ret_from_exception()
2.把当前线程描述符的地址装载到EBP
3.根据栈中的CS和EFLAGS确定要返回到用户态还是内核态
4.如果有进程调度请求则调度
5.通过执行iret指令结束控制,被中断的程序重新开始执行
十、总的中服务务程序IRQ
1.为正在给IRQ线服务的PIC(中断控制器)一个应答,这将允许PIC进一步发出中断
2.发生以下任何一种情况,则返回
(1)相应的IRQ线被禁止
(2)另一个CPU正常处理这类中断
(3)没有相关的ISR
3.执行共享这个IRQ的所有设备的ISR
4.检查是否有可延迟函数在等待执行,如果有do_softirq()
5.ret_from_intr()
十一、软中断在另一篇博客中介绍