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

Linux内核中断系统笔记(一)

发布时间:2019-07-31 07:59 来源:未知 编辑:admin

  Linux内核要管理计算机上的硬件设备,首先要和他们通信。而处理器的速度跟外围硬件设备的速度往往不在一个数量级上,因此,如果内核采取让处理器向硬件发出一个请求,然后专门等待回应的办法,显然差强人意。既然硬件的响应这么慢,那么内核就应该在此期间处理其他事务,等到硬件真正完成了请求的操作之后,再回过头来对它进行处理。想要实现这种功能,轮询(polling)可能会是一种解决办法。可以让内核定期对设备的状态进行查询,然后做出相应的处理。不过这种方法很可能会让内核做不少无用功,因为无论硬件设备是正在忙碌着完成任务还是已经大功告成,轮询总会周期性地重复执行。更好的办法是由我们来提供一种机制,让硬件在需要的时候再向内核发出信号(变内核主动为硬件主动)。这就是中断机制。

  由中断或异常处理程序执行的代码不是一个进程,它是一个内核控制路径,代表中断发生时正在运行的进程执行,内核控制路径比一个进程要“轻“。

  中断(interrupt)通常被定为一个事件,该事件改变处理器执行的指令顺序。

  l同步中断是当指令执行时由CPU控制单元产生的,称为同步,是因为只有在一条指令执行完成后CPU 才会发生中断。

  同步中断又称为异常,异常是由程序的错误产生的(例如除0),或者是由内核必须处理的异常条件(例如缺页)产生的。前一种情况下,必须通知应用程序出现了异常,内核通过发送一个每个Unix程序员熟悉的信号来处理异常,后一种必须借助于内核才能修复,内核执行恢复异常需要的所有步骤。

  异常和异步中断的相同点:如果CPU当前不处于核心态,则发起从用户态到内核态的转变,接下来。内核中执行一个专门的中断服务例程(ISR interrupt service routine)。

  异常和异步中断的不同点:产生源的区别,产生原因的区别,及发生时间的区别。还有一方面,许多中断可以禁用,但有些不行。此后本文所说中断默认指的都是异步中断。

  (注意,这里是Intel划分的,并不是Linux,Linux有自己的划分方法)更进一步,Intel文档中又把中断和异常继续进行了细分:

  可屏蔽中断: I/O设备发出的所有的中断请求(IRQ)都产生可屏蔽中断。可屏蔽中断产生两种状态:屏蔽的(masked)或非屏蔽的(unmasked);当中断被屏蔽,则CPU控制单元就忽略它。

  通常可以纠正;一旦纠正,程序可以在不失连贯性的情况下重新开始。保存在eip中的值是引起故障的指令地址。因此,当异常处理程序停止时,那条指令会重新执行

  在陷阱指令执行后立即报告;内核把控制权返回给程序后就可以继续它的执行而不失连贯性。保存在eip中的值是一个随后要执行的指令地址。只有当没有必要重新执行已中止的指令时,才触发陷阱。陷阱的主要用途是为了调试程序。

  发生一个严重错误;CPU控制单元出了问题,不能在eip寄存器中保存引起异常指令所在的确切位置。这个异常中止处理程序除了强制中止受影响的进程中止外,没有别的选择。

  在编程者发出请求时发生。是由int或int3指令触发的。控制单元把编程异常作为陷阱来处理。编程异常也叫软中断。用途:执行系统调用及给调试程序通报一个特定的事件。

  综上所述,从广义上讲,中断可分为四类:中断、故障、陷阱、终止。这些类别之间的同点请参看表 1。

  在可能的情况下,内核试图避免禁用中断,因为禁用会损害系统性能(比如禁用键盘,就交互不友好了),但有些场合禁用中断是必要的,在处理第一个中断时,如果发生第二个中断,内核会发生严重的问题。如果内核在禁用中断的情况下,花费过多时间处理一个ISR(中断服务例程),会丢失一些系统正确运作不可必要的中断。并且因为中断随时可能发生,即中断处理程序随时可能执行,必须保证中断处理程序快速执行,这样才能保证尽可能快的恢复中断代码的执行,中断处理很重要,但对系统其它部分而言,中断处理程序尽可能短时间内处理完同样重要。为了解决这些问题,一般把中断处理且为两个部分。中断处理程序是上半部,接收到中断立即开始执行,但只做有严格时限的工作,例如对中断的应答,这些工作是在所有中断被禁止的情况下完成的,能够被允许稍后完成的会推迟到下半部去。此后,在合适的时机,下半部会被开中断执行。

  每个能发出中断请求的硬件设备控制器都有一条IRQ(Interrupt Request)输出线,它连接到可编程中断控制器(PIC),是PIC将中断请求转发到CPU的中断输入,外部设备不能直接发出中断,是通过PIC请求中断,所以中断更正确的叫法是IRQ,中断请求。IRQ线开始顺序编号,第一条IRQ线,与IRQn关联度额Intel的缺省向量是n+32,这是因为0~31号是Intel保留为异常使用的。X86计算机的CPU为中断只提供了两条外接引脚:NMI和INTR。其中NMI是不可屏蔽中断,它通常用于电源掉电和物理存储器奇偶校验;INTR是可屏蔽中断,可以通过设置中断屏蔽位来进行中断屏蔽,它主要用于接受外部硬件的中断信号,这些信号由中断控制器传递给 CPU。

  传统的PIC是由两片8259A风格的外部芯片以“级联”的方式连接在一起。每个芯片可处理多达8个不同的IRQ输入线。因为从PIC的INT输出线连接到主PIC的IRQ2引脚,所以可用IRQ线 。

  8259A只适合单CPU的情况,为了充分挖掘SMP体系结构的并行性,能够把中断传递给系统中的每个CPU至关重要。基于此理由,Intel引入了一种名位I/O高级可编程控制器的新组件,用以替代老式的8259A可编程中断控制器。此外,Intel当前所有的CPU都含有一个本地 APIC。每个本地APIC 都有32位的寄存器,一个内部时钟,一个本地定时设备及为本地中断保留的两条额外的IRQ线。所有本的APIC都连接到一个外部 I/O APIC,形成一个多APIC的系统。

  目前大部分单处理器系统都包含一个I/O APIC芯片,可以通过以下两种方式来对这种芯片进行配置:

  2)作为一种标准外部I/O APIC。本地APIC被激活,且所有的外部中断都通过I/O APIC接收。

  由于Intel公司保留 0~31 号中断向量用来处理异常事件。因此,硬中断必须设在 31 以后,Linux则在实模式下初始化时把8259A的IRQ0~IRQ15设在0x20~0x2f(INT32~INT47)。既然0~31号中断向量被保留,就剩下32~255共224个中断向量可用。这224个中断向量又是怎么分配的呢?除了0x80(SYSCALL_VECTOR)(INT128)用作系统调用总入口外,其他都用在外部硬件中断源上,如可编程中断控制器 8259A的15个IRQ。事实上,当没有定义CONFIG_X86_IO_APIC时,其他223个(除 0x80 外)中断向量,只利用了从32号开始的15个(与 8259A中的15个IRQ相对应),其他208个都空着,具体分布情况请参看下表:

  Intel在实现保护模式时,对CPU中断响应机制作了很大修改,中断向量表的表项变成了类似于入口地址加PSW(能够切换CPU运行模式及优先级)并且更复杂的项,称为“门”。只要想切换CPU的运行状态,即优先级别,就需要通过一道门,中断处理也是。X86 CPU中一共有四种门,即任务门、中断门、陷阱门以及调用门。其中调用门不与中断向量表相联系,即中断向量表上只有前三种门。

  用户态的进程可以访问的一个Intel陷阱门(门的DPL字段是3)。通过系统门激活三个Linux异常处理程序,向量分别是4,5,128(即int 0x80)。

  用户态进程不能访问的Intel中断门(门的DPL字段是0)。所有的Linux中断处理程序都通过中断门激活,并全部限制在内核态。

  不能被用户态进程访问的任务门(门的DPL字段是0)。Linux对”Double fault”异常处理程序是通过任务门激活的。

  在此,在2.6以后,Linux抽象出一个与平台无关的中断系统,代码放在kernel中。而与各平台相关的部分分散在各部分架构中。先来看一下抽象部分,kernel/irq/Makefile如下:

  handler:函数指针。指向设备的中断响应函数,它在系统初始化时被置入,当中断发生时,系统将自动调用该函数。注意,handler函数的原型是特定的—它接收三个参数,并有一个类型为irqreturn_t的返回值。

  dev_id: 中断设备id,主要用于共享中断线。当一条中断线是被多个中断设备共享的时候。在中断处理时,内核会按照dev_id逐个调用处理程序,并检查时不时本设备发出的中断。在中断处理程序释放时,dev_id提供唯一的标志信息,以便从共享中断线的诸多中断处理程序中删除指定的一个。

  irq:表示要分配的终端号,对某些设备,如传统PC设备上的系统时钟或键盘是预定死的。对于大多数设备来说,要么是通过探测(probe)获取,要不通过编程动态确定。

  第一个参数irq是中断线号,dev_id是一个通用指针,用来区分共享同一中断处理程序的多个设备,比如两个一样的硬盘。对于设备而言,设备结构是唯一的,通常把设备结构传递给dev_id。第三个参数regs是一个指向结构的指针,包含处理中断之前处理器的寄存器和状态。返回值的类型是reqreturn_t。中断处理程序可能返回两个特殊值:IRQ_NONE和IRQ_HANDLED。当中断处理程序检测到一个中断,但该中断对应的设备不是注册处理函数时指定的源时,返回IRQ_NONE,正确调用,返回IRQ_HANDLED。使用宏IRQ_RETVAL(x),x为非0,宏返回IRQ_HANDLED;否则,返回IRQ_NONE。利用这些值,内核可以知道设备发出的是否是一种虚假的中断。中断处理程序一般是static,因为它不被别的文件中代码直接调用,static表明只在本代码文件可用。

  name:用于标识硬件控制器,在IA-32系统上可能的值是“XTPIC”和“IO-APIC”。

  startup:指向一个函数,用于第一次初始化一个IRQ。大多情况下,初始化工作仅限于启用该IRQ,因而实际上就是将工作转给enable。

  ack:响应一个中断,与中断控制器硬件密切相关。在某些模型中,IRQ请求的到达必须显示的确认,后续的请求才能进行处理。

  eoi:在处理中断时需要一个到硬件的回调,由eoi提供。eoi表示end of interrupt,即中断结束

  end:调用end标记中断处理在电流层次的约束。如果一个中断在中断处理期间被禁用,那么该函数负责重新启用此类中断

  大多数控制方法都是重复的,基本上只要有中断响应、中断屏蔽、中断开启、中断触发类型设置等方法就可以满足要求了。其他各种方法基本上和这些相同。

  handle_irq:上层的通用中断处理函数指针,如果未设置则默认为__do_IRQ()。通常针对电平触发或者边沿触发有不同的处理函数。每个中断线可分别设置,该函数负责使用chip中提供的特定于控制器的方法,进行处理终端所必须的一些底层操作。

  action:指向 struct irqaction 结构组成的队列的头,正常情况下每个irq只有一个操作,因此链表的正常长度是1或0。但是,如果IRQ被两个或多个设备所共享,那么这个队列就有多个操作了

  depth:如果启用这条IRQ中断线;如果禁用这条IRQ中断线不止一次,则为一个正数。如果depth等于0,每当调用一次disable_irq( ),该函数就对这个域的值加1,同时该函数就禁用这条IRQ中断线。相反,每当调用enable_irq( )函数时,该函数就对这个域的值减1;如果depth变为0,该函数就启用这条IRQ中断线。

  irq_unhandled:对在IRQ线上无法处理的中断进行计数(仅在诊断时使用)。当100000次中断产生时,如果意外中断次数超过99900,内核禁用这条IRQ线。

  “____cacheline_aligned”表示这个数据结构的存放按32字节(高速缓存行的大小)进行对齐,以便于将来存放在高速缓存并容易存取

  在调用 start_kernel()函数进行内核初始化时,将会调用 trap_init()和 init_IRQ()函数对中断进行第二次初始化,其中 trap_init()只初始化系统将用到的 CPU 异常处理程序。这部分已经运行于保护模式下。

  为了设计方便,为了完成门描述符的设定,Linux提供了两层函数,底层调用_set_gate()来完成共有的操作,高层函数set_intr_gate()(中断门)、set_trap_gate()(陷阱门)、set_system_gate()(系统门)、set_task_gate()(任务门)和 set_system_intr_gate()(系统中断门)函数均调用_set_gate()来完成门描述符的设定。其中_set_gate()完成的动作与在保护模式下的 setup_idt汇编代码设定的任务完全相同。

  从以上代码可以看出,trap_init()函数只初始化了 0~19 号中断向量及 128(0x80)号中断(20~31 号是系统保留的),那么其他的中断是怎么被初始化的呢?答案就是 init_IRQ()。

  在 init_IRQ()函数中,将32~256 号的中断服务例程设置为 interrupt[i]的偏移地址,那么这个 interrupt[i]为何物,其实 interrupt[i]不是中断服务函数,而是所有中断的一个共同操作,具体的中断服务例程是由硬件的驱动程序所设定的。interrupt[i]是在 entry.S中所设定的,由汇编代码实现。它使得从31号以后的中断都跳到了 common_interrupt,到这里怎么区分具体的中断处理程序,难道都执行同一个吗?上面所讲述的 irqaction 三个结构体,与具体的中断有什么联系?请读者继续往下看

  在 IDT 表的初始化完成之初,每个中断服务队列(irqaction)都是空的。即使开了中断,并且产生了中断,也只不过是让它在 common_interrupt 中空跑一趟。所以,真正的中断服务要到具体硬件设备的初始化程序将其中断服务程序通过 request_irq()向系统“登记”,挂入某个中断服务队列(irqaction)以后才会发生。

  中断函数注册信息就保留在irq_desc_t结构中,系统所有的中断信息构成了一个由224个 irq_desc_t结构组成的全局描述符结构数组irq_desc[]。request_irq()的作用就是注册一个中断并启用。如下图:是注册中断处理程序的流程图:

  SA_INTERRUPT:此标志表明给定的中断处理程序是一个快速中断处理程序。在本地处理器上,快速中断处理程序在禁止所有终端的情况下运行。而默认情况(未设置)情况下,除了正在运行的中断处理程序对应的中断线被屏蔽外,其余所有中断都是激活的。

  SA_SHIRQ:此标志表明可以在多个中断处理程序之间共享中断线。(每个中断都有一个编号,若中断号n分配给一个网卡而不是SCSI控制器,那么内核可以区分两个设备。但是遗憾的是,由于特别设计,只有少数的编号可用于硬件中断,所以必须及格设备共享一个编号,在IA-32的处理器上,硬件中断的最大数目是15,这个叫中断共享)这个标志表明可以在多个处理程序之间共享中断线,在同一条线上注册的每个处理程序必须制定这个标志,否则,每条线上只有一个处理程序。

  l中断处理程序必须能够区分它的设备是否真的产生了中断,这既需要硬件的支持,也需要中断处理程序中有相应的处理逻辑。

  所有共享中断线的驱动程序都必须满足以上要求,只要有任何一个设备没有按规则进行共享,那么中断线就无法共享了。指定SA_SHIRQ标志以调用request_irq()时,只有以下两种情况才可能成功:中断线当前未被注册,或者在该线上的所有已注册处理程序都指定了SA_SHIRQ。

  IRQ_PENDING悬挂,其实就是有中断到达,已对PIC做出应答,但由于某种情况未处理,先标记上

  IRQ_REPLAYIRQ线已被禁用,但是前一个出现的IRQ还没对PIC做出应答

  IRQ_WAITING内核在执行硬件设备探测时使用IRQ线,此外,相应的中断还没有产生

  在request_irq()中调用函数setup_irq()来完成建立中断的具体操作。在全局中断描述符结构数组(irq_destc[])中,如果该中断号被使用,并且新旧中断都设置了共享,就把新中断加载就中断的后面;如果中断号没有被使用,就直接加在中断描述符结构数组中,并设置中断使能,具体函数分析如下:

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