《第四章中断和异常精选文档.ppt》由会员分享,可在线阅读,更多相关《第四章中断和异常精选文档.ppt(42页珍藏版)》请在taowenge.com淘文阁网|工程机械CAD图纸|机械工程制图|CAD装配图下载|SolidWorks_CaTia_CAD_UG_PROE_设计图分享下载上搜索。
1、第四章中断和异常本讲稿第一页,共四十二页4.1 中断信号的作用4.2 中断和异常4.3 中断和异常处理程序的嵌套执行4.4 初始化中断描述符表4.5 异常处理4.6 中断处理4.7 软中断及tasklet4,8 工作队列4.9 从中断和异常返回本讲稿第二页,共四十二页中断中断:中断是一个能改变处理器执行顺序的事件。中断通常分为同步中断和异步中断。同步中断同步中断:当指令执行时由CPU控制单元产生的,只有在一条指令终止执行后CPU才会发出中断。异步中断异步中断:由其他硬件设备依照CPU时钟信号随机产生的。在Intel微处理器手册中,把同步和异步中断分别称为异常和中断。中断是由间隔定时器和I/O设
2、备产生的,而异常是由程序的错误产生的,或者是由内核必须处理的异常条件产生的。本讲稿第三页,共四十二页 中断信号的作用是提供一种特殊的方式使处理器转而去运行正常控制流之外的代码。当一个中断信号到达时,CPU必须停止它当前正在做的事情,并且切换到一个新的活动。为了做到这一点,就要在内核态堆栈保存程序计数器的当前值(即eip和cs寄存器的内容),并把与中断类型相关的一个地址放进程序计数器。中断处理与进程切换的区别中断处理与进程切换的区别:由中断或异常处理程序执行的代码不是一个进程。中断处理程序比一个进程要“light”(中断的上下文很少,建立或终止中断处理需要的时间很少)。本讲稿第四页,共四十二页中
3、断:1)可屏蔽中断I/O设备发出的所有中断请求(IRQ)都产生可屏蔽中断,可屏蔽中断可以处于两种状态:屏蔽的或非屏蔽的,一个屏蔽的中断只要还是屏蔽的,控制单元就忽略它。2)非屏蔽中断只有几个危急事件(如硬件故障)才引起非屏蔽中断。非屏蔽中断总是由CPU辨认。异常:1)处理器探测异常 当CPU执行指令时探测到的一个反常条件所产生的异常。可以分为3组:包括故障,陷阱和异常终止。这取决于CPU控制单元产生异常时候保存在内核态堆栈eip寄存器中的值。2)编程异常在编程者发出请求时发生。是由int或int3指令触发的;当into(检查溢出)和bound(检查地址出界)指令检查的条件不为真时,也引起编程异
4、常。控制单元把编程异常作为陷阱来处理,编程异常通常也叫做软中断。编程异常有两种用途:执行系统调用和给调试程序通报一个特定的事件。本讲稿第五页,共四十二页每个能够发出中断请求的硬件设备控制器都有一条IRQ输出线,所有现有的IRQ线都与一个可编程中断控制器的输入引脚相连,可编程控制器执行以下动作:1.监视IRQ线,检查产生的信号。如果有两条或两条以上的IRQ线上产生信号,就选择引脚编号较小的IRQ线。2.如果一个引发信号出现在IRQ线上:a)把接收到的引发信号转换成对应的向量。b)把这个向量存放在中断控制器的一个I/O端口,从而允许CPU通过数据总线读此向量。c)把引发信号发送到处理器的INTR引
5、脚,即产生一个中断。d)等待,直到CPU通过把这个中断信号写进可编程中断控制器的一个I/O端口来确认它,当这种情况发生时,清INTR线。3.返回到第一步。IRQ线是从0开始编号的,第一条IRQ线通常表示成IRQ0,与IRQn关联的Intel的缺省向量是n+32。可以有选择的禁止每条IRQ线,可以对PCI编程从而禁止IRQ。而有选择的激活/禁止IRQ线不同于可屏蔽中断的全局屏蔽/非屏蔽,当eflags寄存器的IF标志被清0时,由PIC发布的每个可屏蔽中断都由CPU暂时忽略,cli和sti汇编指令分别清除和设置该标志。本讲稿第六页,共四十二页目的目的:为了充分发挥多处理器体系结构的并行性,能够把中
6、断传递给系统中的每个CPU。结构结构:每个CPU都含有一个本地APIC,每个本地APIC都有32位的寄存器,一个内部时钟,一个本地定时设备以及为本地APIC中断保留的两条额外的IRQ线LINT0和LINT1。所有本地APIC都连接到一个外部I/O APIC,形成一个多APIC的系统。I/O APICI/O APIC的组成的组成:一组24条IRQ线,一张24项的中断重定向表,可编程寄存器,以及通过APIC总线发送和接收APIC信息的一个信息单元。中断重定向表中的信息用于把每个外部IRQ信号转换为一条消息,然后通过APIC总线把消息发送给一个或多个本地APIC单元。来自外部硬件设备的中断请求以下面
7、两种方式在可用CPU之间分发:1)静态分发IRQ信号传递给重定向表相应项中所列出的本地APIC。2)动态分发如果处理器正在执行最低优先级进程,IRQ信号就传递给这种处理器的本地APIC。如果两个或多个CPU共享最低优先级,就利用仲裁技术分配CPU。本讲稿第七页,共四十二页仲裁技术仲裁技术:在本地APIC的仲裁优先级寄存器中,给每一个CPU都分配一个0(最低)15(最高)范围内的值。每当中断传递给一个CPU时,其相应的仲裁优先级就自动置为0,而其他每个CPU的仲裁优先级都增加1,当仲裁优先级寄存器大于15时,就把它置为获胜CPU的前一个仲裁优先级加1。因此,中断以轮转的方式在CPU之间分发,且具
8、有相同的任务优先级。处理器间中断(处理器间中断(IPIIPI):除了在处理器之间分发中断外,多APIC系统还允许CPU产生处理器间中断。当一个CPU希望把中断发送给另一个CPU时,它就在自己本地APIC的中断指令寄存器(ICR)中存放这个中断向量和目标本地APIC的标识符。然后,通过APIC总线向目标本地APIC发送一条消息,从而向自己的CPU发出一条相应的中断。IPI被linux用来在CPU之间交换信息。本讲稿第八页,共四十二页80 x86处理器发布了大约20种异常,内核必须为每种异常提供一个专门的异常处理程序,对于某些异常,CPU控制单元在开始执行异常处理前会产生一个硬件出错码,并且压入内
9、核态堆栈。0 Divide error(故障)当一个程序试图执行整数被0除操作时产生1 Debug(陷阱或故障)产生于设置eflags的TF标志或一条指令或操作数的地址落在一个活动debug寄存器的范围之内时。2 未用为非屏蔽中断保留3 Breakpoint(陷阱)由int3(断点)指令引起4 Overflow(陷阱)当eflags的OF标志被设置时,into指令被执行。5 Bounds check(故障)对于有效地址范围之外的操作数,bound指令被执行。6 Invalid opcode(故障)CPU执行单元检测到一个无效操作码。7 Device not available(故障)随着cr0
10、的TS标志被设置,ESCAPE、MMX或XMM指令被执行。8 Double fault(异常中止)正常情况下当CPU正试图为前一个异常调用处理程序时,同时又检测到一个异常,两个异常能被串行的处理,然而在少数情况下,处理器不能串行的处理它们,因而产生这种异常。9 Coprocessor segment overrun(异常中止)因外部的数学协处理器引起的问题。10 Invalid TSS(故障)CPU试图让一个上下文切换到有无效TSS的进程。本讲稿第九页,共四十二页11Segment not present(故障)引用一个不存在的内存段。12Stack segment fault(故障)试图超过
11、栈段界限的指令,或者ss标识的段不在内存。13General protection(故障)违反了80 x86保护模式下的保护规则之一。14Page fault(故障)寻址的页不在内存,相应的页表项为空,或者违反了一种分页保护机制。15由Intel保留。16Floating point error(故障)集成到CPU芯片中的浮点单元用信号通知一个错误情形,如数字溢出或被0除。17Alignment check(故障)操作数的地址没有被正确的对齐18Machine check(异常中止)机器检查机制检测到一个CPU错误或总线错误。19SIMD floating point exception(故障
12、)集成到CPU芯片中的SSE或SSE2单元对浮点操作用信号通知一个错误情形。2031这些值由Intel留作将来开发。本讲稿第十页,共四十二页 中断描述符表是一个系统表,它与每一个中断或异常向量相联系,每一个向量在表中有相应的中断或异常处理程序的入口地址,内核在允许中断发生前,必须用lidt汇编指令初始化IDT。idtr CPU寄存器指定了IDT的线性基地址及其限制(最大长度),因此IDT可以位于内存的任何地方。IDT包含三种类型的描述符:1)任务门:当中断信号发生时,必须取代当前进程的那个进程的TSS选择符存放在任务门中。2)中断门:包含段选择符和中断或异常处理程序的段内偏移量。当控制权转移到
13、一个适当的段时,处理器清IF标志,从而关闭将来会发生的可屏蔽中断。3)陷阱门:与中断门相似,只是控制权传递到一个适当的段时处理器不修改IF标志。本讲稿第十一页,共四十二页当执行了一条指令后,cs和eip寄存器包含下一条将要执行指令的逻辑地址,在处理那条指令之前,控制单元会检查在运行前一条指令时是否已经发生了一个中断或异常,如果发生了一个中断或异常,控制单元执行以下操作:1.确定与中断或异常关联的向量i(0=i=255).2.读由idtr寄存器指向的IDT表中的第i项。3.从gdtr寄存器获得GDT的基地址,并在GDT中查找,以读取IDT表项中的选择符所标识的段描述符,这个描述符指定中断或异常处
14、理程序所在段的基地址。4.确信中断是由授权的中断发生源发出的。首先将当前特权级CPL与段描述符的描述符特权级DPL比较,如果CPL小于DPL,就产生一个“General protection”异常,因为中断处理程序的特权不能低于引起中断的程序的特权。对于编程异常则做进一步的安全检查:比较CPL与处于IDT中的门描述符的DPL,如果DPL小于CPL就产生一个“General protection”异常。这最后一个检查可以避免用户应用程序访问特殊的陷阱门或中断门。5.检查是否发生了特权级的变化,也就是说,CPL是否不同于所选择的段描述符的DPL,如果是,控制单元必须开始使用与新的特权级相关的栈。6
15、.如果故障已经发生,用引起异常的指令地址装载cs和eip寄存器,从而使得这条指令能再次被执行。本讲稿第十二页,共四十二页7.在栈中保存eflags、cs及eip的内容。8.如果异常产生了一个硬件出错码,则将它保存在栈中。9.装载cs和eip寄存器,其值分别是IDT表中第i项门描述符的段选择符和偏移量字段。这些值给出了中断或者异常处理程序的第一条指令的逻辑地址。控制单元所执行的最后一步就是跳转到中断或异常的处理程序,即被选中处理程序的第一条指令。中断或异常处理完后,相应的处理程序必须产生一条iret指令,把控制权交给被中断的进程,这时控制单元执行以下操作:1.用保存在栈中的值装载cs、eip或e
16、flags寄存器。如果一个硬件出错码曾被压入栈中,并且在eip内容的上面,那么执行iret指令前必须先弹出这个硬件出错码。2.检查处理程序的CPL是否等于cs中最低两位的值(这意味着被中断的进程与处理程序运行在同一特权级),如果是,iret终止执行,否则转入下一步。3.从栈中装载ss和esp寄存器,因此返回到与旧特权级相关的栈。4.检查ds、es、fs及gs段寄存器的内容,如果其中一个寄存器包含的选择符是一个段描述符,并且其DPL小于CPL,那么清相应的段寄存器。这是为了禁止用户态的程序(CPL=3)利用内核以前所用的段寄存器(DPL=0),如果不清这些寄存器,怀有恶意的用户态程序就可能利用它
17、们来访问内核地址空间。本讲稿第十三页,共四十二页 每个中断或异常都会引起一个内核控制路径,或者说代表当前进程在内核态执行单独的指令序列。内核控制路径可以任意嵌套,一个中断程序可以被另一个中断程序“中断”,因此引起内核控制路径的嵌套执行。其结果是:对中断进行处理的内核控制路径的最后一部分指令并不总能使当前进程返回到用户态:如果嵌套深度大于1,这些指令将执行上次被打断的内核控制路径,此时的CPU依然运行在内核态。允许内核控制路径嵌套执行必须要求中断处理程序永不阻塞,即中断处理程序运行期间不能发生进程切换。在内核没有bug的情况下,大多数异常在在CPU处于用户态时发生。异常要么是由编程错误引起,要么
18、是由调试程序触发,但是缺页异常发生在内核态。一个中断处理程序既可以抢占其他的中断处理程序,也可以抢占异常处理程序,相反,异常处理程序从不抢占中断处理程序。在内核态能触发的唯一异常就是缺页异常,但中断处理程序从不执行可以导致缺页的操作。在多处理器系统上,几个内核控制路径可以并发的执行,此外与异常相关的内核控制路径可以开始在一个CPU上执行,并且由于进程切换而移往另一个CPU上执行。本讲稿第十四页,共四十二页4.4 初始化中断描述符表 内核启用中断以前,必须把IDT表的初始地址装到idtr寄存器,并初始化表中的每一项。这项工作是在初始化系统的时候完成的。Linux中中断描述符表分类如下:中断门:用
19、户态进程不能访问的一个Intel中断门,门的DPL字段为0,所有的Linux中断处理程序都通过中断门激活,并全部限制在内核态。系统门:用户态进程可以访问的一个Intel陷阱门,门的DPL字段为3,通过系统门来激活三个Linux异常处理程序,它们的向量是4,5及128,因此在用户态下可以发布into、bound和int$0 x80三条汇编语言指令。系统中断门:能够被用户态进程访问的Intel中断门,DPL字段为3,与向量3相关的异常处理程序是由系统中断门激活的,因此在用户态可以使用汇编语言指令int3.陷阱门:用户态的进程不能访问的一个Intel陷阱门,DPL字段为0,大部分Linux异常处理程
20、序都通过陷阱门来激活。任务门:不能被用户态进程访问的一个Intel任务门,DPL字段为0,Linux对“double fault”异常的处理程序是由任务门激活的。本讲稿第十五页,共四十二页IDT的初步初始化 当计算机还运行在实模式时,IDT被初始化并由BIOS例程使用,一旦Linux接管,IDT就被移到RAM的另一个区域,并进行第二次初始化,因为Linux没有利用任何BIOS例程。IDT存放在idt_table表中,有256个表项,6字节的idt_descr变量指定了IDT的大小和它的地址。在内核初始化过程中,setup_idt()汇编语言函数用同一个中断门(即指向ignore_int()中断
21、处理程序)来填充所有这256个idt_table表项。Ignore_int()中断处理程序执行以下操作:1.在栈中保存一些寄存器的内容。2.调用printk()函数打印“Unknown interrupt”系统消息。3.从栈恢复寄存器的内容。4.执行iret指令以恢复被中断的程序。紧接着这个预初始化,内核将在IDT中进行第二遍初始化,用有意义的陷阱和中断处理程序替换这个空处理程序。本讲稿第十六页,共四十二页4.5异常处理异常处理程序的标准结构,由三部分组成:1.在内核堆栈中保存大多数寄存器的内容(汇编语言实现)。2.用高级的C函数处理异常。3.通过ret_from_exception()函数从
22、异常处理程序退出。本讲稿第十七页,共四十二页为异常处理程序保存寄存器的值用handler_name表示一个通用的异常处理程序的名字,则每一个异常处理程序都以下面的汇编指令开始:handler_name:pushl$0/*only for some exceptions*/pushl$do_handler_namejmp error_code其中error_code汇编语言代码执行以下步骤:1.把高级C函数可能用到的寄存器保存在栈中。2.产生一条cld指令来清eflags的方向标志DF,以确保调用字符串指令时会自动增加edi和esi寄存器的值。3.把栈中位于esp+36处的硬件出错码拷贝到edx
23、中,给栈中这一位置存上值-1,这个值用来把0 x80异常与其他异常隔离开。4.把保存在栈中的esp+32位置的do_handler_name()高级C函数的地址装入edi寄存器中,然后在栈的这个位置写入es值。本讲稿第十八页,共四十二页5.把内核栈的当前栈顶拷贝到eax寄存器。这个地址表示内存单元的地址,在这个单元中存放的是第一步所保存的最后一个寄存器的值。6.把用户数据段选择符拷贝到ds和es寄存器中。7.调用地址在edi中的高级C函数。进入和离开异常处理函数进入和离开异常处理函数 执行异常处理程序的C函数名总是由do_前缀和处理程序名组成。其中大部分函数把硬件出错码和异常向量保存在当前进程
24、的描述符中,然后向当前进程发送一个适当的信号。异常处理程序刚一终止,当前进程就关注这个信号,该信号要么在用户态由进程自己的信号处理程序来处理,要么由内核来处理。在后一种情况下,内核一般会杀死这个进程。异常处理程序总是检查异常时发生在用户态还是在内核态,在内核态时还要检查是否由系统调用的无效参数引起。出现在内核态的任何其他异常都是由于内核的bug引起的,在这种情况下异常处理程序认为是内核行为失常了。为了避免硬盘上的数据崩溃,处理程序调die()函数,该函数在控制台上打印出所有CPU寄存器内容,并调用do_exit()来终止当前进程。当执行异常处理的C函数终止时,程序执行一条jmp指令以跳转到re
25、t_from_exception()函数。本讲稿第十九页,共四十二页4.6 中断处理 中断处理依赖于中断类型,中断类型主要分三类:I/O中断、时钟中断和处理器间中断。I/OI/O中断处理:中断处理:一般情况下,I/O中断处理程序必须足够灵活以给多个设备同时提供服务。其灵活性是以两种不同的方式实现的:1)IRQ共享:中断处理程序执行多个中断服务例程(ISR),每个ISR是一个与单独设备(共享IRQ线)相关的函数,因为不可能预先知道哪个特定的设备产生IRQ,因此每个ISR都执行,以验证它的设备是否需要关注,如果是,当设备产生中断时就执行需要执行的所有操作。2)IRQ动态分配:一条IRQ线在可能的最
26、后时刻才与一个设备驱动程序相关联,这样,即使几个硬件设备并不共享IRQ线,同一个IRQ向量也可以由这几个设备在不同时刻使用。本讲稿第二十页,共四十二页所有I/O中断处理程序都执行四个相同的基本操作:1.在内核态堆栈中保存IRQ的值和寄存器的内容。2.为正在给IRQ线服务的PIC发送一个应答,这将允许PIC进一步发出中断。3.执行共享这个IRQ的所有设备的中断服务例程(ISR)。4.跳到ret_from_intr()的地址后终止。本讲稿第二十一页,共四十二页中断向量中断向量本讲稿第二十二页,共四十二页IRQIRQ数据结构数据结构 每个中断向量都有它自己的irq_desc_t描述符,其字段如下表,
27、所有的这些描述符组织在一起形成irq_desc数组。本讲稿第二十三页,共四十二页本讲稿第二十四页,共四十二页IRQIRQ在多处理器系统上的分发在多处理器系统上的分发 Linux遵循对称多处理模型(SMP),因此内核试图以轮转的方式把来自硬件设备的IRQ信号在所有的CPU之间分发,所有CPU服务于I/O中断的执行时间片几乎相同。当硬件设备产生了一个中断信号时,多APIC系统就选择其中的一个CPU,并把该信号传递给相应的本地APIC,本地APIC又一次中断它的CPU,这个事件不通报给其他所有的CPU。所有这些都由硬件完成,但在有些情况下,硬件不能以公平的方式在微处理器之间成功的分配中断,因此在必要
28、的时候,Linux2.6利用kirqd特殊内核线程来纠正对CPU进行的IRQ的自动分配。多种类型的内核栈多种类型的内核栈 每个进程的thread_info描述符和thread_union结构中的内核栈紧邻,而根据内核编译时的选项不同,thread_union结构可能占一个页框或两个页框。如果thread_union结构的大小为8KB,那么当前进程的内核栈被用于所有类型的内核控制路径:异常、中断和可延迟函数。如果thread_union结构的大小为4KB,内核就使用三种类型的内核栈:异常栈(处理异常)、硬中断请求栈(处理中断)和软中断请求栈(处理可延迟的函数)。本讲稿第二十五页,共四十二页为中断
29、处理程序保存寄存器的值为中断处理程序保存寄存器的值 保存寄存器是中断处理程序做的第一件事情,IRQn中断处理程序的地址开始存在interruptn中,然后复制到IDT相应表项的中断门中。通过文件arch/i386/kernel/entry.S中的几条汇编语言指令建立interrupt数组,数组中索引为n的元素中存放下面两条汇编语言指令的地址:pushl$n-256jmp common_interrupt 结果是把终端号减256的结果保存在栈中,内核用负数表示所有的中断(正数表示系统调用)。当引用这个数时,可以对所有的中断处理程序都执行相同的代码,这段代码开始于标签common_interrup
30、t处,包含的汇编指令和宏如下:common_interrupt:SAVE_ALLmovl%esp,%eaxcall do_IRQjmp ret_from_intr SAVE_ALL可以在栈中保存中断处理程序可能会使用的所有CPU寄存器,但eflags、cs、eip、ss和esp除外,因为这几个寄存器已经由控制单元自动保存了。然后这个宏把用户数据段的选择符装到ds和es寄存器。保存寄存器的值以后,栈顶的地址被存放到eax寄存器中,然后中断处理程序调用do_IRQ()函数,执行到ret指令时,控制转到ret_from_intr()。本讲稿第二十六页,共四十二页挽救丢失的中断挽救丢失的中断本讲稿第二
31、十七页,共四十二页中断服务例程中断服务例程 一个中断服务例程(ISR)实现一种特定设备的操作。当中断处理程序必须执行ISR时,它就调用handle_IRQ_event()函数,这个函数执行以下操作:本讲稿第二十八页,共四十二页所有的ISR都作用于相同的参数(分别通过eax、edx和ecx寄存器传递):irqIRQ号dev_id设备标识符regs指向内核(异常)栈的pt_regs结构的指针,栈中含有中断发生后随即保存的寄存器。第一个参数允许一个单独的ISR处理几条IRQ线,第二个参数允许一个单独的ISR照顾几个同类型的设备,第三个参数允许ISR访问被中断的内核控制路径的上下文。每个中断服务例程在
32、成功处理完中断后都返回1,否则返回0.IRQIRQ线的动态分配线的动态分配 在激活一个准备利用的IRQ线的设备之前,其相应的驱动程序调用request_irq()。这个函数建立一个新的irqaction描述符,并用参数值初始化它,然后调用setup_irq()函数把这个描述符插入到合适的IRQ链表,如果setup_irq()返回一个出错码,设备驱动程序中止操作,这意味着IRQ线已经已由另一个设备所使用,而这个设备部允许中断共享。当设备操作结束时,驱动程序调用free_irq()函数从IRQ链表中删除这个描述符,并释放相应的内存区。本讲稿第二十九页,共四十二页处理器间中断处理处理器间中断处理本讲
33、稿第三十页,共四十二页处理器间中断程序的汇编语言代码是由BUILD_INTERRUPT宏产生的:它保存寄存器,从栈顶压入向量号减256的值,然后调用高级C函数,其名字就是低级处理程序的名字加前缀smp_。每个该机处理程序应答本地APIC上的处理器间中断,然后执行由中断触发的特定操作。本讲稿第三十一页,共四十二页4.7 软中断和tasklet 软中断和tasklet有密切关系,tasklet是在软中断之上实现。事实上,出现在内核代码中的术语“软中断softirq”常常表示可延迟函数的所有种类。另外一种经常使用的术语是“中断上下文”;表示中断当前正在执行一个中断处理程序或者一个可延迟的函数。软中断
34、的分配是静态的(在编译时定义),而tasklet的分配和初始化可以在运行时进行。软中断(即便是同一种类型的中断)可以并发的运行多个CPU上。因此,软中断是可重入函数而且必须明确的使用自锁保护其数据结构。tasklet不必担心这些问题,因为内核对tasklet的执行有了更加严格的控制。相同的tasklet总是串行执行的。,换句话说,就是不能在cpu上同时运行两个相当同类型的tasklet。但是。类型不同的tasklet可以在几个cpu上并发执行。tasklet的串行化使tasklet函数不必是可重入的,因此简化了设备驱动程序开发者的工作。一般而言,在可延迟函数上可以执行四种操作:初始化:定义一个
35、新的可延迟函数,这个操作通常在内核自身初始化或者加载模块时进行。激活:标记一个可延迟函数为“挂起”(在可延迟函数的下一轮调度中执行。)激活可以在任何时候执行(即使正在处理中断)。屏蔽:有选择的屏蔽一个可延迟函数,这样,即使他被激活,内核也不执行它。执行:执行一个挂起的可执行函数和同类型的所有挂起的可延迟函数;执行是在特定的时间进行的。本讲稿第三十二页,共四十二页软中断软中断 一个软中断的下标决定了它的优先级:低下标意味着高优先级,因为软中断函数将从下表0开始执行。软中断使用的数据结构是softirq_vec数组,该数组包含类型为softirq_action的32个元素。一个软中断的优先级是相应
36、的soft_action元素在数组内的下标。Soft_action数据结构包括两个字段:指向软中断函数的一个action指针和指向软中断函数需要的通用数据结构的data指针。另外一个关键字段是32位的preempt_count字段,用它来跟踪内核抢占和内核控制路径的嵌套,该字段存放在每个进程描述符的thread_info字段中。本讲稿第三十三页,共四十二页 实现软中断的最后一个关键的数据结构是每个cpu都有的32位掩码,他存放在irq_cpustar_t数据结构的_ _softirq_pending字段中。为了获取或设置位掩码的值,内核使用宏local_softirq_pending(),它选
37、择本地cpu的软中断位掩码。处理软中断处理软中断 open_softirq()函数处理软中断的初始化。它使用三个参数:软中断下标,指向要执行的软中断函数指针及指向可能由软中断函数使用的数据结构的指针。Open_softirq()限制自己初始化softirq_vec数组中适当的的元素。raise_softirq()函数用来激活软中断,它接受软中断下标nr作为参数,执行下面操作。1.执行local_irq_save宏以保存eflags寄存器IF标志的状态值并禁用本地cpu上的中断。2.把软中断标记为挂起状态,这是通过设置本地的cpu的软中断掩码中与下标nr相关的位来实现。3.如果in_interr
38、upt()产生为1的值,则跳转到第5步。这种情况说明,要么已经在中断上下文中调用了raise_softirq(),要么当前禁用了软中断。4.否则,就在需要的时候去调用wakeup_softirq()以唤醒本地cpu的ksoftirqd内核线程。5.执行local_irq_restore宏,恢复在第1步保存的IF标志的状态值。本讲稿第三十四页,共四十二页 应该周期性的检查活动的软中断,检查是在内核代码的几个点上进行的。这在下列几种情况下进行,:1.当内核调用local_bh-enable()函数激活本地cpu的软中断时。2.当do_IRQ()完成了I/O中断的处理时或调用irq_exit()宏时
39、。3.如果系统使用I/O APIC,则当smp_apic_timer_interrupt()函数处理完本地的定时器中断时。4.在多处理器系统中,当cpu处理完被CALL_FUNCTION_VECTOR处理器间中断所触发的函数时。5.当一个特殊的ksoftirqd/n内核线程被唤醒时。tasklettasklet tasklet是I/O驱动程序中实现可延迟函数的首选方法。如前所述,tasklet建立在两个叫做HI_SOFTIRQ的软中断之上。几个tasklet可以与同一个软中断相关联,每个tasklet执行自己的函数。两个软中断之间没有真正的区别,只不过do-softirq()先执行HI_SOF
40、TIRQ的tasklet,后执行TASKLET_SOFTIRQ的tasklet。tasklet和高优先级的tasklet分别存放在tasklet_vec和tasklet_hi_vec数组中,二者都包含类型为tasklet_head的NR_CPUS个元素,每个元素都有一个指向tasklet描述符链表的指针组成。tasklet描述符是一个tasklet_struct类型的数据结构,其字段如下表:本讲稿第三十五页,共四十二页本讲稿第三十六页,共四十二页 调用tasklet_disable_nosync()或tasklet_disable()可以选择性的禁止tasklet。这两个函数都增加taskle
41、t描述符的count字段,但是最后一个函数只有在tasklet函数已经运行的实例结束后才返回。为了重新激活tasklet,调用tasklet_enable()函数。为了激活tasklet,应该根据tasklet需要的优先级,调用tasklet_schedule()函数或tasklet_hi_schedule()函数,这两个函数非常相似,其中每个都执行下列操作:1.检查TASKLET_STATE_SCHED()标志;如果设置则返回。2.调用local_irq_save保存IF标志的状态并禁用本地中断。3.在tasklet_vecn指向的链表的起始处增加tasklet描述符。4.调用raise_s
42、oftirq_irqoff()激活TASKLET_SOFTIRQ或HI_SOFTIRQ类型的软中断。5.调用local_irq_restore恢复IF标志的状态。软中断函数一旦被激活,do_softirq()函数执行。与HI_SOFTIRQ软中断相关的软中断函数叫做tasklet_hi_action()。这两个函数非常相似,他们都执行下列操作:1.禁用本地中断。2.获得本地cpu的逻辑号n。3.把tasklet_vecn或tasklet_hi_vecn指向的链表的地址存入局部变量list。本讲稿第三十七页,共四十二页4.把tasklet_vecn或asklet_hi_vecn的值赋为null,
43、因此,已调度的tasklet描述符的链表被清空。5.打开本地中断。6.对于list指向的链表中的每个tasklet描述符:a.在多处理系统上,检查tasklet的tasklet_STATE_RUN标志。如果该标志被设置,说明同类型的一个tasklet正在cpu上运行,因此,把任务描述符重新插入到由tasklet_vecn或tasklet_hi_vecn指向的链表中,并再次激活tasklet_softirq或HI_SOFTIRQ软中断。如果未被设置,tasklet就没有在其他cpu上运行,就需要设置这个标志,以便tasklet函数不能再其他cpu上执行。b.通过查看tasklet描述符的coun
44、t字段,检查tasklet字段是否被禁止。如果 是,就请TASKLET_STATE_RUN标志,并把任务描述符重新插入到由tasklet_vecn或tasklet_hi_vecn指向的链表中,然后函数再次激活tasklet_softirq或HI_SOFTIRQ软中断。c.如果tasklet被激活,清TASKLET_STATE_SCHED标志,并执行tasklet函数。本讲稿第三十八页,共四十二页4.8工作队列 在linux2.6中引入了工作队列,它与linux2.4中的任务队列具有相似的构造,用来代替任务队列。它们允许内核函数被激活,而且稍后有一种叫做工作者线程的特殊内核线程执行。工作队列的数
45、据结构工作队列的数据结构 主要是workqueue_struct描述符,它包括一个有NR_CPUS个元素的数组,NR_CPUS是系统中cpu的最大数量。每个元素都是cpu_workqueue_struct类型的描述符。工作队列函数工作队列函数 create_workqueue(“foo”)函数接受一个字符串作为参数,返回新创建工作队列的workqueue_struct描述符地址。该函数还创建n个工作者线程(n是当前系统中有效运行的cpu的个数),并根据传递给函数的字符串为工作者线程命名。create_singlethread_workqueue()函数与之相似,但不管系统中有多少个cpu,cr
46、eate_singlethread_workqueue()函数都只创建一个工作者线程。内核调用destroy_workqueue()函数撤销工作队列,它接受指向workqueue_struct数组的指针作为参数。本讲稿第三十九页,共四十二页预定义工作队列预定义工作队列 在绝大多数情况下,为了运行一个函数而创建整个工作者线程开销太大了。因此,引入叫做events的预定义工作队列,所有的内核开发者都可以随意使用它。预定义工作队列只是一个包括不同内核层函数和I/O驱动程序的标准工作队列,它的workqueue_struct描述符存放在keventd_wq数组中。当函数很少被调用时,预定义工作队列节省
47、了重要的系统资源,另一方面,不应该使在预定义工作队列中执行的函数长时间处于阻塞状态。因为工作队列链表中的挂起函数是在每个CPU上以串行的方式执行的,而太长的延迟对预定义工作队列的其他用户会产生不良影响。本讲稿第四十页,共四十二页4.9 从中断和异常中返回尽管终止阶段的目的很明确,就是恢复某个程序的执行,还是需要考虑几个问题:内核控制执行程序并发执行的数量如果只有一个,那么cpu必须切换到用户状态。挂起进程的切换请求如果有请求,内核必须进行调度,否则,把控制权还给当前进程。挂起信号一个信号发送到当前进程,就必须处理它。单步执行模式如果调试程序正在跟踪当前进程的执行,就必须在进程切换回到用户态之前恢复到单步执行。Virtual-8086模式如果cpu处于Virtual-8086模式,当前进程正在执行原来的实模式程序,因而必须以特殊方式处理这种情况。需要使用一些标志来记录挂起进程切换的请求、挂起信号和单步执行,这些标志被存放在thread_info的flags字段中,这个字段也存放其他与从中断和异常返回无关的标志。本讲稿第四十一页,共四十二页本讲稿第四十二页,共四十二页