- 中断信号的作用.
- 使CPU转而去运行正常控制流之外的代码.为了它.就要在内核态堆栈保存程序计数器的当前值(eip和cs寄存器).并把与中断类型相关的一个地址放在程序计数器.
- 中断处理与进程切换的差异:由中断或异常处理程序执行的代码不是一个进程,而是内核控制路径.代表中断发生时正在运行的进程执行.其比进程"轻".
- 中断和异常
- 中断:
- 可屏蔽中断(maskable): I/O设备发出的中断请求(irq)都属于.可处于两种状态:屏蔽的/非屏蔽的.
- 非屏蔽中断(nonmaskable): 只有几个危急事件才引起.总是由CPU辨认.
- 异常:
- 处理器探测异常:当CPU执行指令时探测到一个反常条件所产生的异常. 根据保存在eip寄存器中的值,分为3种; 1)故障(fault):通常可以被纠正.eip中保存的是引起故障的指令地址.纠正后会重新执行该条指令. ; 2)陷阱(trap):在陷阱指令执行后立刻报告.内核把控制器返回给程序后可以继续他的执行而不失连贯性. eip保存的是随后要执行的指令地址.只有当没有必要重新执行已终止的指令时(通常为了调试程序)时才触发陷阱. ; 3)异常中止(abort):不能在eip中保存引起异常的指令所在的确切位置.用于报告严重的错误.异常中止处理程序会强制受影响的进程终止.
- 编程异常: 在编程者发出请求时发生.将其作为陷阱来处理.也叫软中断.用途:1)执行系统调用.2)给调试程序通报一个特定的事件.
- 每个中断和异常由0~255之间的一个数来标示.称为向量(vector).只有可屏蔽中断的向量可以通过编程改变.其余都是固定的.
- IRQ和中断:能发出中断的设备都有一个IRQ的输出线.所有IRQ线都与一个可编程中断控制器(PIC)的硬件电路的输入引脚相连.可以有选择地禁止每条IRQ线.可对PIC编程从而禁止IRQ.禁止的中断不丢失.一旦激活,PIC就把他们发送到CPU.该特性运行中断处理程序逐次处理同一类型的IRQ.
- 为了发挥SMP体系的并行性,能够把中断传递给每个CPU很重要.所以引入了I/O高级可编程控制器(I/O APIC)的组件.所有CPU都含有一个本地APIC,通过APIC总线(在系统总线上)连接到外部的I/O APIC.还支持CPU产生处理器间中断(IPI),可以利用它来在CPU之间交换消息.
- 中断描述符表:IDT是一个系统表,它与每一个中断或异常向量相联系.每一个向量在表中有相应的中断或异常处理程序的入口地址.最多需要256*8=2048字节来存放IDT. idtr寄存器指定IDT的线性基地址及其最大长度,从而使IDT可以位于内存中的任何地方.分为3种类型: 1)任务门:信号发生时,必须取代当前进程的那个进程的TSS选择符存放在任务门中. ;2)中断门:包含段选择符和处理程序的段内偏移量.当CPU控制权转移到一个适当的断后,清除IF标志来关闭将来会发生的可屏蔽中断 ;3)陷阱门:与中断门相似,但控制权传递到一个适合的CPU时不修改IF标志. Linux利用中断门处理中断,利用陷阱门处理异常.注意: "Double fault"异常是唯一由任务们处理的异常.表示一种内核错误.
- 中断和异常处理程序的嵌套执行.
- 必须保证中断处理程序永不阻塞,即中断处理程序运行期间不能发生进程切换.因为嵌套的内核控制路径恢复执行时需要的数据都存放在当前线程的内核态堆栈上.
- 一个中断处理程序可以抢占其他的中断处理程序和异常处理程序.异常处理程序从不抢占中断处理程序.
- 初始化中断描述符表
- 过程: 1)在初始化系统时把IDT表的初始地址装入idtr寄存器,并初始化表中的每一项.; 2)int指令用于在用户态进程发出一个中断信号,为了防止模拟非法的中断,将门描述符的DPL=0.; 3)在用户态进程必须要能够发出一个编程异常时,将门的SPL=3。
- Linux中的分类: 中断门(DPL=0,所有LInux中断处理程序都通过中断门激活,并限制在内核态); 系统门(DPL=3,用来激活3个linux异常处理程序); 系统中断门(DPL=3,激活int3的异常处理程序); 陷阱门(DPL=0,激活大部分的异常处理程序); 任务门(DPL=0,"Double Fault的异常处理).
- idt的初始化分为两步:1)将256个表项用同一个中断门(即指向ignore_int()中断处理程序:其是一个空的处理程序)来填充.2)用有意义的陷阱和中断处理程序来代替空处理程序.
- 异常处理
- 大部分异常都解释成为出错条件.当异常发生时,内核向引起异常的进程发送一个信号向它通知一个反常条件.
- 特殊情况: 1)"Device not availeble" 2)"Page Fault"该异常推迟给进程分配新的页框,直到不能再推迟位置.
- 异常处理程序的标准结构: 1)在内核态堆栈中保存大多数寄存器的内容; 2)用C函数处理异常; 3)通过ret_from_exception函数从异常处理程序退出.
- 中断处理
- 由于一个进程被挂起好久后中断才到达,因此一个完全无关的进程可能正在运行.所以发送信号给当前进程是无用的.
- 中断处理依赖于中断类型:1)I/O中断(查询设备以确定适当的操作过程); 2)时钟中断(该中断告诉内核一个固定的时间间隔已经过去,作为I/O中断处理) ;3)处理器间中断.
- I/O中断处理:要能给多个设备同时提供服务.实现: 1)IRQ共享(每个ISR(中断服务例程)是一个与单独设备(共享IRQ线)相关的函数,因为无法预知那个特定的设备产生IRQ,所以,中断处理程序执行多个ISR,以验证它的设备是否需要关注,如果是,当设备产生中断时就执行所需的操作); 2)IRQ动态分配(一条IRQ线在可能的最后时刻才与一个设备相关联.这样,即使几个设备并不共享IRQ线,但同一IRQ向量也可以由这几个设备在不同时刻使用).
- 一个中断处理程序正在执行时,相应的IRQ线上发出的信号被暂时忽略; 中断处理程序所代表的进程必须是出于Task_Running态的; 其不能执行任何阻塞过程.
- 中断要执行的操作: 1)紧急的(在禁止可屏蔽中断下立即执行); 2)非紧急的(在开中断下立即执行); 3)非紧急可延迟的(由独立的函数执行).
- 步骤:1)在内核态堆栈中保存IRQ的值和寄存器的内容; 2)为正在给IRQ线服务的PIC发送一个应答来允许PIC进一步发出中断; 3)执行共享这个IRQ的所有设备的ISR; 4)跳到ret_from_intr()的地址后终止.
- IRQ数据结构: 1)意外中断:中断内核没有处理的中断.原因是与某个IRQ线相关的ISR不存在或者与某个中断线相关的所有例程都识别不出来.当一条IRQ线上的意外中断次数过多时,就禁用这条IRQ线. 2)
- IRQ在多CPU系统上的分发:对称多处理器模型(SMP).一般情况下,内核能够公平地在CPU间分发中断.但是在某些情况下,Linxu需要使用kirqd的内核线程来纠正IRQ的自动分配.其利用了CPU的IRQ亲和力:通过修改I/O APIC的中断重定向表表项,可以吧中断信号发送到某个特定的CPU上.
- CPU间中断处理:IPI不通过IRQ线传输,而是作为信号直接放在连接所有CPU本地APIC总线上. 类型: 1)Call_Function_Vector(发往不包含发送者的所有CPU,强制这些CPU运行发送者传递过来的函数). 2)Reschedule_Vector(从中断返回后,所有的重新调度都自动运行); 3)Invalidate_TLB_Vector(强制TLB无效,来刷新CPU的TLB).
- 软中断及tasklet:
- 可延迟中断可以在开中断的情况下执行.把可延迟中断从中断处理程序中抽出来有助于使内核保持较短的响应时间.Linux通过两种非紧迫,可中断内核函数:1)可延迟函数;2)通过工作队列来执行的函数.
- tasklet是在软中断之上实现的.软中断的分配是静态的,tasklet的分配和初始化是在运行时.软中断是可重入函数并使用自旋锁来保护数据,其实可以并行在多CPU上执行的,tasklet总是串行执行,但是不同类型的tasklet可以并发执行,其不必是可重入的.四种操作:1)初始化;2)激活;3)屏蔽;4)执行.激活和执行被绑定在一个CPU上,虽然可以更好地利用CPU的Cache,但是有潜在的危险性(一个CPU很忙,但其他的很闲).
- 软中断:使用下标(共6个)来表示优先级.softirq_action[32] softirq_vec数组.优先级是下标,所以只有前6元素个有效.另外,thread_info中有一个preempt_count字段来跟踪内核抢占和内核控制路径的嵌套.
- 每个CPu都有自己的ksoftirqd/n内核线程.其为了解决以下问题:软中断函数可以重新激活自己,软中断的连续高流量可能会产生问题.不然就要选择以下两种之一的策略:1)忽略do_softirq()运行时新出现的软中断,此种情况的等待是不可接受的;2)不断地重新检查挂起的软中断,这种情况下,do_softirq()函数就会一直不返回,用户态程序实际上停止执行.解决:do_softirq()函数确定哪些软中断是挂起的,并执行他们的韩素华.如果已经执行的软中断又被激活,则do_softirq()唤醒内核线程并终止.内核线程有较低的优先级,因此用户程序有就会运行.但是,如果机器空闲(没有用户态程序需要运行时),挂起的软中断就很快被执行.
- tasklet:是I/O驱动程序中实现可延迟函数的首选.
- 工作队列
- 用来代替任务队列.允许内核函数被激活,而且稍后由一种叫做工作者线程的特殊内核线程来执行.
- 可延迟函数运行在中断上下文中,而工作队列中的函数运行在进程上下文中.执行可阻塞函数的方式是在进程上下文中运行,因为在中断上下文中不可能发生进程切换.两者都不能访问进程的用户态地址空间.