a.第一级8259A是主中断控制器,它的第二个中断请求输入端与第二级8259A的中断输出端INT相连。
b.与中断控制器相连的每条线叫做中断线。要使用中断线,就要进行中断线的申请,即IRQ。
那么这条线的名字是啥勒----》中断号。
IRQ线是从0开始顺序编号的,所以第一条IRQ线就是IRQ0。
C.那么该中断号于我们上面所说的中断向量有什么关系呢
中断向量=中断号+32。
从此等式可以看出,第一个中断线(IR0)所对应的中中断向量是32.
由此可以得出:
(1)异常和非屏蔽向量是CPU 内部引起的中断
(2)向量32-47对应的是外部中断。
d.并不是每个设备都可以向中断线上发中断信号,只有对某一条确定的中断线拥有了控制权后,才可以向这条中断线上发送信号。
e.8259A中还有一个很重要的寄存器-8位的中断屏蔽寄存器-这个寄存器的作用是屏蔽中断。
8位的中断屏蔽寄存每一位对应8259A中的一条中断线,如果要禁用某条中断线,则把中断屏蔽寄存器的相应位置1,要启用则置0。
(PS:看到这里就可以明白,需要用到中断控制器的中断都是外部中断,也就是可屏蔽中断。每个IRQ对应一个中断向量。但是并不是每一个中断向量都可以对应一个IRQ)
John哥说明:
屏蔽中断也可以从CPU的角度考虑,即清除eflag的中断标志位(IF),当IF位为0时禁止任何外部IO的中断请求,即关中断;
f.共享中断(一个很重要的概念,后面程序中会涉及到它)
由于计算机的外部设备越来越多,所以15条中断线已经不够用了。中断线是很宝贵的资源,为了更好的利用它,只有当设备需要中断的时候才申请占用一个IRQ,并且为了让更多的设备使用中断采取了在申请IRQ时采用共享中断的方式。
(PS:由于外部设备多,多个设备可以对应同一个IRQ,也就是对应同一个中断向量,一个中断向量对应一个中断处理程序,但是一个中断处理程序可能对应很多中断服务例程)
2高级可编程中断控制器(APIC)
先看图再说:
1.8259A 只适合单 CPU 的情况,为了充分挖掘 SMP 体系结构的并行性,能够把中断传递给系统中的每个 CPU 至关重要。基于此理由,Intel 引入了一种名为 I/O 高级可编程控制器的新组件,来替代老式的 8259A 可编程中断控制器。该组件包含两大组成部分:一是本地 APIC,主要负责传递中断信号到指定的处理器;举例来说,一台具有三个处理器的机器,则它必须相对的要有三个本地 APIC。另外一个重要的部分是 I/O APIC,主要是收集来自 I/O 装置的 Interrupt 信号且在当那些装置需要中断时发送信号到本地 APIC(相当于一个路由的功能),系统中最多可拥有 8 个 I/O APIC。
2.每个本地 APIC 都有 32 位的寄存器,一个内部时钟,一个本地定时设备以及为本地中断保留的两条额外的 IRQ 线 LINT0 和 LINT1。所有本地 APIC 都连接到 I/O APIC,形成一个多级 APIC 系统。
那么我们如何知道我们机子上使用的是那种中断控制器呢?
我们可以通过在终端出入命令:cat /proc/interrupts来查看
a.若看到列表中有IO-APIC,说明您的系统正在使用 APIC。
若看到 XT-PIC,意味着您的系统正在使用 8259A 芯片。
16位实地址模式的中断机制和32位保护模式的中断机制的最本质差别就是在保护模式下引入了中断描述表
在单处理器的系统中,第一列是中断号,第二列是CPU产生该中断的次数。最后一列是于这个中断相关的俄设备名字。这个名字是通过参数devname提供给函数request_irq()(下篇文章会对它讲解)
三.中断描述表
1.为什么引入
在实地址模式中,CPU把内存中从0开始的1kb空间作为一个中断向量表。表中的每个表项占四个字节,由两个字节的段地址和两个字节的偏移量组成,这样构成的地址就是相应中断处理程序的入口地址。
但是在保护模式下,由4个字节的表项构成的中断向量表已经不能满足要求了。在保护模式下,中断向量表中的表项由8个字节组成。此时他也有了新的名字----中断描述表(Interrupt Descriptor Table,IDT)(PS:总共有256个描述符,每个描述符8字节,256*8就是中断描述符占用的字节空间),其中的每个表项叫做一个门描述符(great descriptor)(在中断系列的其他文章中有很详细的介绍各种门描述符,系统所有的中断都需要经过这些门)
先来看图在说明:
(本文来源于图老师网站,更多请访问http://m.tulaoshi.com/fuwuqi/)1DPL:段描述符的特权级
2偏移量:入口函数地址的偏移量
3P:表示段是否在内存中的标志
4段选择符:入口函数所处代码段的选择符
5D:标志位,1表示32位,0标示16位
6xxx:3位门类型码
门类型符主要分为
a.中断门(interrupt gate):其类型码为110,中断门包含了一个中断或异常处理程序所在段的选择符和段内偏移量。
当控制权通过中断门进入中断处理程序时,处理器清IF标志即关中断这样就避免了中断嵌套的发生。
中断门中的DPL(请求特权级)为0,因此用户态中的进程不能访问中断门。所用的中断处理程序都由中断门激活,并全部限制在内核态。(进入中断就需要进入中断门)
b..陷阱门(tap gate)其类型码为111。它与中断门类似,唯一的区别是控制权通过陷阱门进入处理程序时保持IF标志位不变,即不关中断。
c.系统门(system gate):Linux内核特别设置的,用来让用户态的进程访问Intel的陷阱门。
系统门的DPL为3。系统调用就是通过系统门进入内核的。
2.在保护模式下,中断描述符表在内存的位置不再局限于从地址0开始的位置,而是可以放在内存的任何位置。
1为了实现这个功能---CPU中设计了一个中断描述符表寄存器IDTR,用来存放中断描述符表在内存的起始位置。
2中断描述表寄存器(IDTR)是一个48位的寄存器。它的低16为保存中断描述符表的大小,高32位保存中断描述表的基址。
3看下图:
中断向量是中断向量表(IDT)的索引,而中断向量表存在于内存的某个位置,由Intel的寄存器IDTR负责记录其基址(线性地址)和大小。IDT表中包含了操作系统中注册的外部IO中断的处理程序的入口地址,以及其他操作系统实现的架构相关的中断和异常的处理函数入口地址(这些地址又存放在所谓的gate destribtor中)。INTR和IDT的关系如下图所示:
我们知道了中断描述表的功能和基本设置后,那么系统是是在何时给它初始化以及是如何给它初始化的呢?
首先Linux内核在系统的初始化阶段对中断进行初始化,其中包括有:初始化可编程控制器8259A;将中断描述符表的起始地址装入IDTR中,并初始化表中的每一项。
(PS:首先通过IDTR找到中断描述符表,然后通过IDT再找中断处理程序的入口地址)
3.中断的初始化
1用户进程可以通过INT指令发出一个中断请求,其中断请求向量在0~255之间。
那么如何防止用户使用INT指令模拟非法的中断和异常?
此时DPL就起作用了-将DPL置为0就可以了。
2但是,有时候必须让用户进程能够使用内核所提供的功能(比如系统调用)也就是从用户态进入内核态,此时就可以通过把中断门或陷阱门的DPL置为3来实现。
3当计算机在实模式时,中断描述符表被初始化,并由BIOS使用。
but,在进入了Linux内核时,中断描述符表就被移到内存的另一个区域,并为进入保护模式进行预初始化:
用汇编指令LIDT对中断描述符表寄存器IDTR进行初始化,即把IDTR置为0,然后把中断描述符表IDT的起始地址装入IDTR。
4中断描述表的初始化
a.第一次初始化:用setup_idt()函数填充中断描述符表中的256个表项,填充时使用一个空的中断处理程序。因为现在还是在初始化阶段,还没有任何中断处理程序,因此,用这个空的中断处理程序来填充每个表项。
b.第二此初始化:内核在启用分页功能后对IDT进行第二次初始化。
此时,使用实际的陷阱和中断处理程序替换这个空的处理程序。一旦这个过程完成后,对于每个异常,IDT都包含一个专门的陷阱门或系统门,而对每个外部中断,IDT都包含专门的中断门。
上面提到了对IDT的初始化,那么我们就递归深入下来看看系统是如何对IDT表项进行设置的
4.IDT表项的设置
IDT表项的设置是通过_set_gate()函数来实现的。
1插入一个中断门
调用 set_intr_gate(n,addr)函数来实现
此函数的功能是在IDT的第n个表项插入一个中断门。门中的段选择符设置成内核代码的段选择符,偏移量设置为中断处理程序的地址addr,DPL字段设置为0.
分析下的形参:
n:表示在第几个表项中插入一个中断门。
addr:表示偏移量,此处偏移量设置为中断处理程序的地址addr.
现在我们迭代深入,看下它的内部是如何实现的
[cpp] view plaincopy
330static inline void set_intr_gate(unsigned int n, void *addr) 331{
332 BUG_ON((unsigned)n 0xFF); 333 _set_gate(n, GATE_INTERRUPT, addr, 0, 0, __KERNEL_CS);
334}[c-sharp] view plaincopy
19#if (_MIPS_ISA _MIPS_ISA_MIPS1) 20
21static inline void __BUG_ON(unsigned long condition) 22{
23 if (__builtin_constant_p(condition)) { 24 if (condition)
25 BUG(); 26 else
27 return; 28 }
29 __asm__ __volatile__("tne $0, %0, %1" 30 : : "r" (condition), "i" (BRK_BUG));
31} 32
33#define BUG_ON(C) __BUG_ON((unsigned long)(C)) 34
(1)我们可以看到BUG_ON()函数是一函数宏,系统最终调用的是__BUG_ON((unsigned long)(C)) 第五个形参表示IST(Interrupt Stack Table)共 3 位,表达 IST1 - IST7 共 7 个 Stack pointer[c-sharp] view plaincopy
371static inline void set_trap_gate(unsigned int n, void *addr) 372{
373 BUG_ON((unsigned)n 0xFF); 374 _set_gate(n, GATE_TRAP, addr, 0, 0, __KERNEL_CS);
375} [c-sharp] view plaincopy
365static inline void set_system_trap_gate(unsigned int n, void *addr) 366{
367 BUG_ON((unsigned)n 0xFF); 368 _set_gate(n, GATE_TRAP, addr, 0x3, 0, __KERNEL_CS);
369} 370