您好、欢迎来到现金彩票网!
当前位置:2019跑狗图高清彩图 > 向量屏蔽 >

linux中断源码分析 - 中断发生(三)

发布时间:2019-07-25 09:38 来源:未知 编辑:admin

  上篇文章linux中断源码分析 - 初始化(二)已经描述了中断描述符表和中断描述符数组的初始化,由于在初始化期间系统关闭了中断(通过设置CPU的EFLAGS寄存器的IF标志位为0),当整个中断和异常的初始化完成后,系统会开启中断(设置CPU的EFLAGS寄存器的IF标志位为1),此时整个系统的中断已经开始可以使用了。本篇文章我们具体研究一次典型中断发生时的运行流程。

  首先我们需要了解,当系统处于中断上下文时,是禁止发生调度和抢占的。进程的thread_info中有个preempt_count成员变量,其作为一个变量,包含了3个计数器和一个标志位,如下:

  当进入到中断时,中断处理程序会调用irq_enter()函数禁止抢占和调度。当中断退出时,会通过irq_exit()减少其硬件计数器。我们需要清楚的就是,无论系统处于硬中断还是软中断,调度和抢占都是被禁止的。

  我们需要先明确一下,中断控制器与CPU相连的三种线:INTR、数据线、INTA。

  在硬件电路中,中断的产生发生一般只有两种,分别是:电平触发方式和边沿触发方式。当一个外部设备产生中断,中断信号会沿着中断线到达中断控制器。中断控制器接收到该外部设备的中断信号后首先会检测自己的中断屏蔽寄存器是否屏蔽该中断。如果没有,则设置中断请求寄存器中中断向量号对应的位,并将INTR拉高用于通知CPU,CPU每当执行完一条指令时都会去检查INTR引脚是否有信号(这是CPU自动进行的),如果有信号,CPU还会检查EFLAGS寄存器的IF标志位是否禁止了中断(IF = 0),如果CPU未禁止中断,CPU会自动通过INTA信号线应答中断控制器。CPU再次通过INTA信号线通知中断控制器,此时中断控制器会把中断向量号送到数据线上,CPU读取数据线获取中断向量号。到这里实际上中断向量号已经发送给CPU了,如果中断控制器是AEIO模式,则会自动清除中断向量号对应的中断请求寄存器的位,如果是EIO模式,则等待CPU发送的EIO信号后在清除中断向量号对应的中断请求寄存器的位。

  在SMP系统,也就是多核情况下,外部的中断控制器有可能会于多个CPU相连,这时候当一个中断产生时,中断控制器有两种方式将此中断送到CPU上,分别是静态分发和动态分发。区别就是静态分发设置了指定中断送往指定的一个或多个CPU上。动态分发则是由中断控制器控制中断应该发往哪个CPU或CPU组。

  CPU已经接收到了中断信号以及中断向量号。此时CPU会自动跳转到中断描述符表地址,以中断向量号作为一个偏移量,直接访问中断向量号对应的门描述符。在门描述符中,有个特权级(DPL),系统会先检查这个位,然后清除EFLAGS的IF标志位(这也说明了发发生中断时实际上CPU是禁止其他可屏蔽中断的),之后转到描述符中的中断处理程序中。在上一篇文章我们知道,所有的中断门描述符的中断处理程序都被初始化成了interrupt[i],它是一段汇编代码。

  我们先注意看一下中断描述符表,里面的每个中断描述符都有一个段选择符和一个偏移量以及一个DPL(特权级),而偏移量其实就是中断处理程序的入口地址,当中断或异常发生时:

  CPU首先会确定是中断或异常的号,然后根据这个号作为偏移量,通过读取idtr中保存的中断描述符表(IDT)的基地址获取相应的中断描述符。并从中断描述符中拿出其中的段选择符

  根据段选择符从GDT中获取这个段的段描述符(为什么只从GDT中获取?因为初始化所有中段描述符时使用的段选择符几乎都是__USER_CS,__KERNEL_CS,TSS,这几个段选择符对应的段描述符都保存在GDT中)。而这几个段描述符中的基地址都是0x00000000,所以偏移量就是中断处理程序入口地址。

  这时还没有进入到中断处理程序,CPU会先使用CS寄存器的当前特权级(CPL)与中断描述符中对应的段描述符的DPL进行比较,如果DPL的值 = CPL的值,则通过检查,而DPL的值 CPL的值时,会产生一个通用保护异常。这种情况发生的可能性很小,因为在上一篇初始化的文章中也可以看出来,中断初始化所用的段选择符都是__KERNEL_CS,而异常的段选择符几乎也都是__KERNEL_CS,只除了极特殊的几个除外。也就是大多数中断和异常的段选择符DPL都是0,CPL无论是在内核态(CPL = 0)或者是用户态(CPL = 3),都可以执行这些中断和异常。

  如果是用户程序的异常(非CPU内部产生的异常),这里还需要再进行多一步的检查(中断和CPU内部异常则不用进行这步检查), 我们回忆一下中断初始化的文章,里面介绍了门描述符,在门描述符中有一个DPL位,用户程序的异常还要对这个位进行检查,当前特权级CPL的值 DPL的值时,则通过检查,否则不能通过检查,而只有系统门和系统中断门的DPL是3,其他的异常门的DPL都为0。这样做的好处是避免了用户程序访问陷阱门、中断门和任务门。

  上面的检查结束后就可以转去执行中断或异常处理程序了,会将刚才获取到的段选择符加载到CS寄存器,段描述符加载到CS对应的非编程寄存器中,也就是CS寄存器保存的是__KERNEL_CS,CS寄存器的非编程寄存器中保存的是对应的段描述符。而根据段描述符中的段基址+段内偏移量(保存在门描述符中),则得到了处理程序入口地址,实际上我们知道段基址是0x00000000,段内偏移量实际上就是处理程序入口地址,这个门描述符的段内偏移量会被放到IP寄存器中。

  如果CS寄存器的特权级发生变化(陷入内核),则CPU会访问此CPU的TSS段(通过tr寄存器),在TSS段中读取当前进程的内核栈堆栈栈顶地址到ESP寄存器中,并且将__KERNEL_DS装载到SS寄存器中。之后把之前的SS寄存器和ESP寄存器的值保存到当前内核栈中。

  interrupt[i]的每个元素都相同,执行相同的汇编代码,这段汇编代码实际上很简单,它主要工作就是将中断向量号和被中断上下文(进程上下文或者中断上下文)保存到栈中,最后调用do_IRQ函数。

  在do_IRQ中,首先会添加硬中断计数器,此行为导致了中断期间禁止调度发送,此后会根据中断向量号从vector_irq[]数组中获取对应的中断号,并调用handle_irq()函数出来该中断号对应的中断出来例程。

  好的,最后执行中断描述符中的handle_irq指针所指函数,我们回忆一下,在初始化阶段,所有的中断描述符的handle_irq指针指向了handle_level_irq()函数,文章开头我们也说过,中断产生方式有两种:一种电平触发、一种是边沿触发。handle_level_irq()函数就是用于处理电平触发的情况,系统内建了一些handle_irq函数,具体定义在include/linux/irq.h文件中,我们罗列几种常用的:

  我们主要看看handle_level_irq()函数函数,有兴趣的朋友也可以看看其他的,因为触发方式不同,通知中断控制器、CPU屏蔽、中断状态设置的时机都不同,它们的代码都在kernel/irq/chip.c中。

  其实代码上很简单,我们需要注意几个屏蔽中断的方式:清除EFLAGS的IF标志、通知中断控制器屏蔽指定中断、设置中断描述符的状态为IRQD_IRQ_INPROGRESS。在上述代码中这三种状态都使用到了,我们具体解释一下:

  CPU禁止中断,当CPU进入到中断处理时自动会清除EFLAGS的IF标志,也就是进入中断处理时会自动禁止中断。在SMP系统中,就是单个CPU禁止中断。

  在中断控制器处就屏蔽中断,这样该中断产生后并不会发到CPU上。在SMP系统中,效果相当于所有CPU屏蔽了此中断。系统在执行此中断的中断处理函数才会要求中断控制器屏蔽该中断,所以没必要在此中断的处理过程中中断控制器再发一次中断信号给CPU。

  在SMP系统中,同一个中断信号有可能发往多个CPU,但是中断处理只应该处理一次,所以设置状态为IRQD_IRQ_INPROGRESS,其他CPU执行此中断时都会先检查此状态(可看handle_level_irq()函数)。

  所以在SMP系统下,对于handle_level_irq而言,一次典型的情况是:中断控制器接收到中断信号,发送给一个或多个CPU,收到的CPU会自动禁止中断,并执行中断处理函数,在中断处理函数中CPU会通知中断控制器屏蔽该中断,之后当执行中断服务例程时会设置该中断描述符的状态为IRQD_IRQ_INPROGRESS,表明其他CPU如果执行该中断就直接退出,因为本CPU已经在处理了。

http://bluecaleel.com/xiangliangpingbi/279.html
锟斤拷锟斤拷锟斤拷QQ微锟斤拷锟斤拷锟斤拷锟斤拷锟斤拷锟斤拷微锟斤拷
关于我们|联系我们|版权声明|网站地图|
Copyright © 2002-2019 现金彩票 版权所有