精华内容
下载资源
问答
  • 用于高速数据传输或通道处理,在触发快速中断请求(FIQ)时进入。 FIQ和IRQ(外部中断模式)之间有很大的区别。FIQ模式必须尽快处理,处理结束后离开这个模式;IRQ模式可以被FIQ模式中断,但IRQ不能中断FIQ模式;为使...
    快速中断请求(Fast Interrupt Request,FIQ)

    在ARM中,FIQ模式是特权模式中的一种,同时也属于异常模式一类。用于高速数据传输或通道处理,在触发快速中断请求(FIQ)时进入。

    FIQ和IRQ(外部中断模式)之间有很大的区别。FIQ模式必须尽快处理,处理结束后离开这个模式;IRQ模式可以被FIQ模式中断,但IRQ不能中断FIQ模式;为使FIQ模式响应更快,FIQ模式具有更多的影子(Shadow)寄存器。FIQ模式必须禁用中断;如果一个中断例程必须重新启用中断,应使用IRQ模式而不是FIQ模式。

    展开全文
  • 中断请求

    千次阅读 2008-10-28 12:05:00
    中断请求级Windows NT为每个硬件中断和少数软件事件赋予了一个优先级,即中断请求级(interrupt request level - IRQL)。IRQL为单CPU上的活动提供了同步方法,它基于下面规则:一旦某CPU执行在高于PASSIVE_LEVEL的...
     
    
    中断请求级

    Windows NT为每个硬件中断和少数软件事件赋予了一个优先级,即中断请求级(interrupt request level - IRQL)。IRQL为单CPU上的活动提供了同步方法,它基于下面规则:

    一旦某CPU执行在高于PASSIVE_LEVEL的IRQL上时,该CPU上的活动仅能被拥有更高IRQL的活动抢先。

    图4-1显示了x86平台上的IRQL值范围。(通常,这个IRQL数值要取决于你所面对的平台) 用户模式程序执行在PASSIVE_LEVEL上,可以被任何执行在高于该IRQL上的活动抢先。许多设备驱动程序例程也执行在PASSIVE_LEVEL上。第二章中讨论的DriverEntryAddDevice例程就属于这类,大部分IRP派遣例程也属于这类。

    某些公共驱动程序例程执行在DISPATCH_LEVEL上,而DISPATCH_LEVEL级要比PASSIVE_LEVEL级高。这些公共例程包括StartIo例程,DPC(推迟过程调用)例程,和其它一些例程。这些例程的共同特点是,它们都需要访问设备对象和设备扩展中的某些域,它们都不受派遣例程的干扰或互相干扰。当任何一个这样的例程运行时,上面陈述的规则可以保证它们不被任何驱动程序的派遣例程抢先,因为派遣例程本身执行在更低级的IRQL上。另外,它们也不会被同类例程抢先,因为那些例程运行的IRQL与它们自己的相同。只有拥有更高IRQL的活动才能抢先它们。

    注意
    派遣例程(Dispatch routine)和DISPATCH_LEVEL级名称类似。之所以称做派遣例程是因为I/O管理器向这些函数派遣I/O请求。而存在派遣级(DISPATCH_LEVEL)这个名称是因为内核线程派遣器运行在这个IRQL上,它决定下一次该执行哪个线程。(现在,线程调度程序通常运行在SYNCH_LEVEL级上)
    080220185821.jpg

    图4-1. 中断请求级

    在DISPATCH_LEVEL级和PROFILE_LEVEL级之间是各种硬件中断级。通常,每个有中断能力的设备都有一个IRQL,它定义了该设备的中断优先级别。WDM驱动程序只有在收到一个副功能码为IRP_MN_START_DEVICE的IRP_MJ_PNP请求后,才能确定其设备的IRQL。设备的配置信息作为参数传递给该请求,而设备的IRQL就包含在这个配置信息中。我们通常把设备的中断级称为设备IRQL,或DIRQL。

    其它IRQL级的含义有时需要依靠具体的CPU结构。这些IRQL通常仅被Windows NT内核内部使用,因此它们的含义与设备驱动程序的编写不是特别密切相关。例如,我将要在本章后面详细讨论的APC_LEVEL,当系统在该级上为某线程调度APC(异步过程调用)例程时不会被同一CPU上的其它线程所干扰。在HIGH_LEVEL级上系统可以执行一些特殊操作,如系统休眠前的内存快照、处理bug check、处理假中断,等等。

    IRQL的变化

    为了演示IRQL的重要性,参见图4-2,该图显示了发生在单CPU上的一系列事件。在时间序列的开始处,CPU执行在PASSIVE_LEVEL级上。在t1时刻,一个中断到达,它的服务例程执行在DIRQL1上,该级是在DISPATCH_LEVEL和PROFILE_LEVEL之间的某个DIRQL。在t2时刻,另一个中断到达,它的服务例程执行在DIRQL2上,比DIRQL1低一级。我们讨论过抢先规则,所以CPU将继续服务于第一个中断。当第一个中断服务例程在t3时刻完成时,该中断服务程序可能会请求一个DPC。而DPC例程是执行在DISPATCH_LEVEL上。所以当前存在的未执行的最高优先级的活动就是第二个中断的服务例程,所以系统接着执行第二个中断的服务例程。这个例程在t4时刻结束,假设这之后再没有其它中断发生,CPU将降到DISPATCH_LEVEL级上执行第一个中断的DPC例程。当DPC例程在t5时刻完成后,IRQL又落回到原来的PASSIVE_LEVEL级。

    080220185841.jpg

    图4-2. 变化中的中断优先级

    基本同步规则

    遵循下面规则,你可以利用IRQL的同步效果:

    所有对共享数据的访问都应该在同一(提升的)IRQL上进行。

    换句话说,不论何时何地,如果你的代码访问的数据对象被其它代码共享,那么你应该使你的代码执行在高于PASSIVE_LEVEL的级上。一旦越过PASSIVE_LEVEL级,操作系统将不允许同IRQL的活动相互抢先,从而防止了潜在的冲突。然而这个规则不足以保护多处理器机器上的数据,在多处理器机器中你还需要另外的防护措施——自旋锁(spin lock)。如果你仅关心单CPU上的操作,那么使用IRQL就可以解决所有同步问题。但事实上,所有WDM驱动程序都必须设计成能够运行在多处理器的系统上。

    IRQL与线程优先级

    线程优先级是与IRQL非常不同的概念。线程优先级控制着线程调度器的调度动作,决定何时抢先运行线程以及下一次运行什么线程。然而,当IRQL级高于或等于DISPATCH_LEVEL级时线程切换停止,无论当前活动的是什么线程都将保持活动状态直到IRQL降到DISPATCH_LEVEL级之下。而此时的“优先级”仅指IRQL本身,由它控制到底哪个活动该执行,而不是该切换到哪个线程的上下文。

    IRQL和分页

    执行在提升的IRQL级上的一个后果是,系统将不能处理页故障(系统在APC级处理页故障)。这意味着:

    执行在高于或等于DISPATCH_LEVEL级上的代码绝对不能造成页故障。

    这也意味着执行在高于或等于DISPATCH_LEVEL级上的代码必须存在于非分页内存中。此外,所有这些代码要访问的数据也必须存在于非分页内存中。最后,随着IRQL的提升,你能使用的内核模式支持例程将会越来越少。

    DDK文档中明确指出支持例程的IRQL限定。例如,KeWaitForSingleObject例程有两个限定:

    • 调用者必须运行在低于或等于DISPATCH_LEVEL级上。
    • 如果调用中指定了非0的超时,那么调用者必须严格地运行在低于DISPATCH_LEVEL的IRQL上。

    上面这两行想要说明的是:如果KeWaitForSingleObject真的被阻塞了指定长的时间(你指定的非0超时),那么你必定运行在低于DISPATCH_LEVEL的IRQL上,因为只有在这样的IRQL上线程阻塞才是允许的。如果你所做的一切就是为了检测事件是否进入信号态,则可以执行在DISPATCH_LEVEL级上。但你不能在ISR或其它运行在高于DISPATCH_LEVEL级上的例程中调用KeWaitForSingleObject例程。

    IRQL的隐含控制

    在大部分时间里,系统都是在正确的IRQL上调用驱动程序中的例程。虽然我们还没有详细地讨论过这些例程,但我希望举一个例子来表达这句话的含义。你首先遇到的I/O请求就是I/O管理器调用你的某个派遣例程来处理一个IRP。这个调用发生在PASSIVE_LEVEL级上,因为你需要阻塞调用者线程,还需要调用其它支持例程。当然,你不能在更高的IRQL级上阻塞一个线程,而PASSIVE_LEVEL也是唯一能让你无限制地调用任何支持例程的IRQL级。

    如果你的派遣例程通过调用IoStartPacket来排队IRP,那么你第一个遇到的请求将发生在I/O管理器调用你的StartIo例程时。这个调用发生在DISPATCH_LEVEL级,因为系统需要在没有其它例程(这些例程能在队列中插入或删除IRP)干扰的情况下访问I/O队列。回想一下前面提到的规则:所有对共享数据的访问都应该在同一(提升的)IRQL级上进行。因为每个能访问IRP队列的例程都执行在DISPATCH_LEVEL级上,所以任何例程在操作队列期间都不可能被打断(仅指在单CPU系统)。

    之后,设备可能生成一个中断,而该中断的服务例程(ISR)将在DIRQL级上被调用。设备上的某些寄存器也许不能被安全地共享。但是,如果你仅在DIRQL上访问那些寄存器,可以保证在单CPU计算机上没人能妨碍你的ISR执行。如果驱动程序的其它代码需要访问这些关键的硬件寄存器,你应该让这些代码仅执行在DIRQL级上。KeSynchronizeExecution服务函数可以帮助你强制执行这个规则,我将在第七章的“与中断处理连接”段中讨论这个函数。

    再往后,你应该安排一个DPC调用。DPC例程执行在DISPATCH_LEVEL级上,它们需要访问你的IRP队列,并取出队列中的下一个请求,然后把这个请求发送给StartIo例程。你可以调用IoStartNextPacket服务函数从队列中提取下一个请求,但必须在DISPATCH_LEVEL级上调用。该函数在返回前将调用你的StartIo例程。注意,这里的IRQL吻合得相当巧妙:队列访问,调用IoStartNextPacket,和调用StartIo都需要发生在DISPATCH_LEVEL级上,并且系统也是在这个IRQL级上调用DPC例程的。

    尽管明确地控制IRQL也是可能的,但几乎没有理由这样做,因为你需要的IRQL和系统调用你时使用的IRQL总是相应的。所以不必不时地提高IRQL,例程希望的IRQL和系统使用的IRQL几乎总是正确对应的。

    IRQL的明确控制

    如果必要,你还可以在当前处理器上临时提升IRQL,然后再降回到原来的IRQL,使用KeRaiseIrqlKeLowerIrql函数。下面代码运行在PASSIVE_LEVEL级上:

    KIRQL oldirql;        <--1
    ASSERT(KeGetCurrentIrql() <= DISPATCH_LEVEL);    <--2
    KeRaiseIrql(DISPATCH_LEVEL, &oldirql);     <--3
    ...
    KeLowerIrql(oldirql);       <--4
    1. KIRQL定义了用于保存IRQL值的数据类型。我们需要一个变量来保存当前IRQL。
    2. 这个ASSERT断定了调用KeRaiseIrql的必要条件:新IRQL必须大于或等于当前IRQL。如果这个关系不成立,KeRaiseIrql将导致bug check。(即用死亡蓝屏报告一个致命错误)
    3. KeRaiseIrql把当前的IRQL提升到第一个参数指定的IRQL级上。它同时还把当前的IRQL值保存到第二个参数指定的变量中。在这个例子中,我们把IRQL提升到DISPATCH_LEVEL级,并把原来的IRQL级保存到oldirql变量中。
    4. 执行完任何需要在提升的IRQL上执行的代码后,我们调用KeLowerIrql把IRQL降低到调用KeRaiseIrql时的级别。

    DDK文档中提到,你必须用与你最近的KeRaiseIrql调用所返回的值调用KeLowerIrql。这在大的方面是对的,因为你提升了IRQL就必须再降低它。然而,由于你调用的代码或者调用你的代码所做的各种假设会使后面的决定变得不正确。所以,文档中的这句话从严格意义上讲是不正确的。应用到KeLowerIrql函数的唯一的规则就是新IRQL必须低于或等于当前IRQL。

    当系统调用你的驱动程序例程时,你降低了IRQL(系统调用你的例程时使用的IRQL,或你的例程希望执行的IRQL),这是一个错误,而且是严重错误,尽管你在例程返回前又提升了IRQL。这种打破同步的结果是,某些活动可以抢先你的例程,并能访问你的调用者认为不能被共享的数据对象。

    有一个函数专用于把IRQL提升到DISPATCH_LEVEL级:

    KIRQL oldirql = KeRaiseIrqlToDpcLevel();
    ...
    KeLowerIrql(oldirql)

    注意:该函数仅在NTDDK.H中声明,WDM.H中并没有声明该函数,因此WDM驱动程序不应该使用该函数。

    展开全文
  • linux中断--中断嵌套&中断请求丢失

    千次阅读 2014-04-14 20:06:04
    在linux内核里,如果驱动在申请注册中断的时候没有特别的指定,do_irq在做中断响应的时候,是开启中断的,如果在驱动的中断处理函数正在执行的过程中,出现同一设备的中断或者不同设备的中断,这时候新的中断会被...

    关于中断嵌套:

    在linux内核里,如果驱动在申请注册中断的时候没有特别的指定,do_irq在做中断响应的时候,是开启中断的,如果在驱动的中断处理函数正在执行的过程中,出现同一设备的中断或者不同设备的中断,这时候新的中断会被立即处理,还是被pending,等当前中断处理完成后,再做处理。
    在2.4和2.6内核里,关于这一块是否有什么不同。

    一般申请中断的时候都允许开中断,即不使用SA_INTERRUPT标志。如果允许共享则加上 SA_SHIRQ,如果可以为内核熵池提供熵值(譬如你写的驱动是ide之类的驱动),则再加上 SA_SAMPLE_RANDOM标志。这是普通的中断请求过程。对于这种一般情况,只要发生中断,就可以抢占内核,即使内核正在执行其他中断函数。这里有两点说明:一是因为linux不支持 中断优先级,因此任何中断都可以抢占其他中断,但是同种类型的中断(即定义使用同一个 中断线的中断)不会发生抢占,他们会在执行本类型中断的时候依次被调用执行。二是所谓 “只要发生中断,就可以抢占内核”这句是有一定限制的,因为当中断发生的时候系统由中断门 进入时自动关中断(对于x86平台就是将eflags寄存器的if位置为0),只有当中断函数被执行 (handle_IRQ_event)的过程中开中断之后才能有抢占。 对于同种类型的中断,由于其使用同样的idt表项,通过其状态标志(IRQ_PENDING和 IRQ_INPROGRESS)可以防止同种类型的中断函数执行(注意:是防止handle_IRQ_event被重入, 而不是防止do_IRQ函数被重入),对于不同的中断,则可以自由的嵌套。因此,所谓中断嵌套, 对于不同的中断是可以自由嵌套的,而对于同种类型的中断,是不可以嵌套执行的。
    以下简单解释一下如何利用状态标志来防止同种类型中断的重入:
    当某种类型的中断第一次发生时,首先其idt表项的状态位上被赋予IRQ_PENDING标志,表示有待处理。 然后将中断处理函数action置为null,然后由于其状态没有IRQ_INPROGRESS标志(第一次),故将其状态置上IRQ_INPROGRESS并去处IRQ_PENDING标志,同时将action赋予相应的中断处理函数指针(这里是一个重点,linux很巧妙的用法,随后说明)。这样,后面就可以顺利执行handle_IRQ_event进行中断处理,当在handle_IRQ_event中开中断后,如果有同种类型的中断发生,则再次进入do_IRQ函数,然后其状态位上加上IRQ_PENDING标志,但是由于前一次中断处理中加上的IRQ_INPROGRESS没有被清除,因此这里无法清除IRQ_PENDING标志,因此action还是为null,这样就无法再次执行handle_IRQ_event函数。从而退出本次中断处理,返回上一次的中断处理函数中,即继续执行handle_IRQ_event函数。当handle_IRQ_event返回时检查IRQ_PENDING标志,发现存在这个标志,说明handle_IRQ_event执行过程中被中断过,存在未处理的同类中断,因此再次循环执行handle_IRQ_event函数。直到不存在IRQ_PENDING标志为止。

    2.4和2.6的差别,就我来看,主要是在2.6中一进入do_IRQ,多了一个关闭内核抢占的动作,同时在处理中多了一种对IRQ_PER_CPU类型的中断的处理,其他没有什么太大的改变。这类IRQ_PER_CPU的中断主要用在smp环境下将中断绑定在某一个指定的cpu上。例如arch/ppc/syslib/open_pic.c中的openpic_init中初始化ipi中断的时候。

           其实简单的说,中断可以嵌套,但是同种类型的中断是不可以嵌套的,因为在IRQ上发生中断,在中断响应的过程中,这个IRQ是屏蔽的,也就是这个IRQ的中断是不能被发现的。

           同时在内核的临界区内,中断是被禁止的

    关于do_IRQ可能会丢失中断请求:

    do_IRQ函数是通过在执行完handle_IRQ_event函数之后判断status是否被设置了IRQ_PENDING标志来判断是否还有没有被处理的同一通道的中断请求。 但是这种方法只能判断是否有,而不能知道有多少个未处理的统一通道中断请求。也就是说,假如在第一个中断请求执行handle_IRQ_event函数的过程中来了同一通道的两个或更多中断请求,而这些中断不会再来,那么仅仅通过判断status是否设置了IRQ_PENDING标志不知道到底有多少个未处理的中断,handle_IRQ_event只会被再执行一次。
    这算不算是个bug呢? 不算,只要知道有中断没有处理就OK了,知道1个和知道N个,本质上都是一样的。作为外设,应当能够处理自己中断未被处理的情况。
     
    不可能丢失的,在每一个中断描述符的结构体内,都有一个链表,链表中存放着服务例程序

    关于中断中使用的几个重要概念和关系:

    一、基本概念

    1.

      产生的位置 发生的时刻 时序
    中断 CPU外部 随机 异步
    异常 CPU正在执行的程序 一条指令终止执行后 同步

    2.由中断或异常执行的代码不是一个进程,而是一个内核控制路径,代表中断发生时正在运行的进程的执行

    中断处理程序与正在运行的程序无关

    引起异常处理程序的进程正是异常处理程序运行时的当前进程


    二、特点

    1.(1)尽可能快

    (2)能以嵌套的方式执行,但是同种类型的中断不可以嵌套

    (3)尽可能地限制临界区,因为在临界区中,中断被禁止


    2.大部分异常发生在用户态,缺页异常是唯一发生于内核态能触发的异常

    缺页异常意味着进程切换,因此中断处理程序从不执行可以导致缺页的操作


    3.中断处理程序运行于内核态

    中断发生于用户态时,要把进程的用户空间堆栈切换到进程的系统空间堆栈,刚切换时,内核堆栈是空的

    中断发生于内核态时, 不需要堆栈空间的切换


    三、分类

    1.中断的分类:可屏蔽中断、不可屏蔽中断

    2.异常的分类:

    分类 解决异常的方法 举例
    故障 那条指令会被重新执行 缺页异常处理程序
    陷阱 会从下一条指令开始执行 调试程序
    异常中止 强制受影响的进程终止 发生了一个严重的错误

    四、IRQ

    1.硬件设备控制器通过IRQ线向CPU发出中断,可以通过禁用某条IRQ线来屏蔽中断。

    2.被禁止的中断不会丢失,激活IRQ后,中断还会被发到CPU

    3.激活/禁止IRQ线 != 可屏蔽中断的 全局屏蔽/非屏蔽

    可以有选择地禁止每条IRQ线。因此,可以对PIC编程从而禁止IRQ,也就是说,可以告诉PIC停止对给定的IRQ线发布中断,或者激活它们。禁止的中断时丢失不了的,它们一旦被激活,PIC就又把它们发送到CPU。这个特点被大多数中断处理程序使用,因为这允许中断处理程序逐次地处理同一类型的IRQ

    假定CPU有一条激活的IRQ线。一个硬件设备出现在这条IRQ线程上,且多APIC系统选择我们的CPU处理中断。在CPU应答中断前,这条IRQ线被另一个CPU屏蔽掉;结果,IRQ_DISABLED标志被设置。随后,我们的CPU开始处理挂起的中断;因此,do_IRQ()函数应答这个中断,然后返回,但没有执行中断服务例程,因为它发现IRQ_DISABLED标志被设置了,因此,在IRQ线禁用之前出现的中断丢失了。

    为了应付这种局面,内核用来激活IRQ线的enable_irq()函数先检查是否发生了中断丢失,如果是,该函数就强迫硬件让丢失的中断再产生一次

    它们最大的不同是上半部分不可中断,而下半部分可中断


    五、中断描述符表IDT

    1.基本概念

    中断描述符表是一个系统表,它与每一个中断或异常向量相联系,每一个向量在表中有相应的中断或异常处理程序入口地址。

    在允许发生中断以前,必须适当地初始化IDT

    TSS只能位于GDT中,IDT能位于内存的任何的地方


    2.中断描述符

    硬件提供的中断描述符:

    (1)任务门:中断信号发生时,必须取代当前进程的那个进程的TSS选择符存放在任务门中

    (2)中断门:包含段选择符和中断处理程序的段内偏移

    (3)陷阱门:与中断门的唯一区别是,通过中断门进入服务程序后,自动关中断,而通过陷阱门进入服务程序不自动关中断

    Linux中使用的中断描述符:

    中断描述符的类型 用户态能否访问 用户态的访问方式 能激活的程序
    中断门   所有的Linux中断处理程序
    系统门 into、bound、int $0x80 向量号为4,5,128的三个Linux异常处理程序
    系统中断门 int 3 与向量3相关的异常处理程序
    陷阱门   大部分Linux异常处理程序
    任务门   Linux对Double fault异常的处理程序
    Linux中的系统门、系统中断门、陷阱门使用的都是硬件中的陷阱门

    Linux利用中断门处理中断,利用陷阱门处理异常

    Double fault是唯一用任务处理的异常


    3.中断向量与中断和异常的关系

    (1)每个中断和异常是由0-255之间的一个数来标识的,这个数就是1中的中断向量

    (2)大约有20种异常,内核为每一个异常分配了一种中断/异常向量分别是0-19

    (3)0x80是系统调用的中断向量

    (4)32-255是内核为什么中断分配的中断向量。然而,224个中断向量显然不够,因此系统为每个中断向量设置一个队列,根据每个中断源所使用的中断向量,将其中断服务程序挂到相应的队列中。中断发生时,先执行与中断向量相对应的一段总服务程序,再根据具体的中断源设备号在其所属的队列找到特定的中断服务程序加以执行。


    4.中断向量、与中断向量相对应的总服务程序、某个中断源的中断服务程序之间的关系如图所示:

    (1)irq_desc是中断向量描述符队列(中断描述符是INT的一项,中断向量描述符是一个数据结构,用于描述与中断向量相关的服务程序

    (2)irq_desc_t是中断向量描述符的数据结构

    (3)irqaction是挂在某个中断向量的具体的中断服务程序的描述符,组成一个队列

    (4)hw_irq_controller是这个中断向量的总服务程序



    六、IDT的初始化

    1.两次初始化

      运行模式 初始值 使用者
    第一次 实模式 空处理程序 BIOS例程
    第二次 保护模式 有意义的中断处理程序或异常处理程序 Linux系统


    2.在IDT表的初始化完成之初,每个中断处理队列都是空的,此时即使打开中断并且某个外设中断真的发生了,也得不到实际的服务,因为没有执行具体的中断处理程序。

    真正的中断服务要到具体设备的初始化程序将其中断处理程序ISR挂入某个中断请求队列后才会发生


    3.在允许发生中断以前,必须适当地初始化IDT


    七、激活中断或异常(以下内容都是由硬件自动完成)

    1.确定与中断或异常相关的中断向量号

    中断:硬件设备控制器通过IRQ向CPU发出信号,中断管制器把接受到的信号转换为中断向量号i

    异常:对于软件指令发出或产生的异常,CPU会差别归类错误的类别,这个类别号就是中断向量

    2.IDT第i项 -----> 段选择符 ----->段描述符 -----> 段基址

    3.IDT第i项 -----> 偏移量

    4.段基址 + 偏移量 -----> 中断处理程序第一条指令的地址

    5.在栈中保存EFLAGS、CS、EIP的内容

    6.如果异常产生了一个出错码,把它保存在栈中

    7.装载CS、EIP,其值分别是2-段选择符和4-偏移量,由这两个寄存器可得到中断或异常处理程序第一条指令的地址



    八、找到中断或异常处理程序的第一条指令后,跳转这到这一指令的过程

    1.中断

    (1)在当前进程的内核堆栈中保存IRQ的值,为什么与系统调用号区分,保存的是-n

    (2)在当前进程的内核堆栈中保存寄存器的值:SAVE_ALL

    EFLAGS、CS、EIP、SS、ESP不包括在内,因为它们由控制单元自动保存(见七-7)

    (3)把栈顶的地址存放到EAX中

    (4)把用户段的选择符装到DS和ES中

    (5)调用do_IRQ(),地址保存在CS、EIP中(见七-7)

    (6)为正在给IRQ线服务的PIC(中断控制器)一个应答,这将允许PIC进一步发出中断

    (7)执行共享这个IRQ的所有设备的ISR(总服务程序称为IRQ,某个设备的具体的服务程序称为ISR

    (8)跳到ret_from_intr()的地址后终止

    (6)(7)(8)都是在(5)中被调用的,见十

    2.异常

    (1)如果异常发生时,控制单元没有自己把一个出错码压出栈中(见七-6),则压入一个空值。

    这个“凑数”的出错码不在正常的出错码应该在的位置,以下-步是为了把它调整到它应该在的位置

    (3)把异常处理程序的地址压入栈中

    (4)把异常处理程度可能用到的寄存器保存到栈中

    (5)把栈中位于ESP+36处的硬件出错码拷贝到EDX中,给栈中这一位置存上-1

    (6)把保存在栈中ESP+32位置的异常处理程序的地址装入EDI中,给栈中的这一位置写入ES的值

    (7)把栈当栈顶拷贝到EAX中

    (8)把用户段的选择符装到DS和ES中

    (9)调用地址在EDI中的异常处理程序


    九、从中断或异常处理程序返回的过程

    1.跳转到用于返回的代码的入口点

    (1)中断ret_from_intr()

    (2)异常:ret_from_exception()

    2.把当前线程描述符的地址装载到EBP

    3.根据栈中的CS和EFLAGS确定要返回到用户态还是内核态

    4.如果有进程调度请求则调度

    5.通过执行iret指令结束控制,被中断的程序重新开始执行


    十、总的中服务务程序IRQ

    1.为正在给IRQ线服务的PIC(中断控制器)一个应答,这将允许PIC进一步发出中断

    2.发生以下任何一种情况,则返回

    (1)相应的IRQ线被禁止

    (2)另一个CPU正常处理这类中断

    (3)没有相关的ISR

    3.执行共享这个IRQ的所有设备的ISR

    4.检查是否有可延迟函数在等待执行,如果有do_softirq()

    5.ret_from_intr()

    十一、中断服务例程
    一个中断服务例程(ISR)实现一种特定设备的操作。当中断处理程序必须执行ISR时,它就调用hand_IRQ_event()函数。

    十二、IRQ线的动态分配
    在激活一个准备利用IRQ线的设备之前,其相应的驱动程序调用request_irq()。这个函数建立一个新的irqaction函数,并用参数值初始化它。然后调用setup_irq()函数把这个描述符插入到适合的IRQ链表。如果setup_irq()返回一个出错码,设备驱动程序中止操作,这意味着IRQ线已有另一个设备所使用,而这个设备不允许中断共享。当设备操作结束时,驱动程序调用free_irq()函数从IRQ链表中删除这个描述符,并释放相应的内存区。
    request_irq()
    free_irq()

    1.Linux把紧随中断要执行的操作分为三类

      特点 处理方法 举例
    第一类 紧急的 在禁止可屏蔽中断的情况下立即执行 修改设备和处理器同时访问的数据结构
    第二类 非紧急的 在开中断的情况下立即执行 修改那些只有处理器才会访问的数据结构(例如,按下一个键后读扫描码)
    第三类 非紧急可延迟的 由独立的函数来执行 把缓冲区的内核拷贝到某个进程的地址空间

    2.把可延迟中断从中断处理程序中抽出来,由独立的函数来执行,有助于使内核保持较短的响应时间


    3.Linux2.6使用可延迟函数和工作队列来处理可延迟中断,这两个都是内核函数。


    二、可延迟函数

    1.可延迟函数包括软中断和tasklet,tasklet是在软中断之上实现的

    tasklet是I/O驱动程序中实现可延迟函数的首选方法。

    tasklet建立在HI_SOFTIRQ和TASKLET_SOFTIRQ这两个软中断之上

    几个tasklet可以与同一软中断相关联,每个tasklet执行自己的函数

      分配方式 并发性 可重入性
    软中断 编译时静态分配 可以并发地在多个CPU上运行 必须是可重入的,并明确地使用自旋锁保护其数据结构
    tasklet 运行时动态分配 相同类型的tasklet总是被串行地执行。不同类型的tasklet可以在几个CPU上并发地执行 不必是可重入的

    2.由给定CPU激活的一个可延迟函数必须在同一个CPU上执行

    可延迟函数执行时不允许内核抢占,因为从一个CPU移到另一个CPU的过程需要将进程挂起


    3.中断与软中断所使用的数据结构比较

      中断 软中断
    中断向量号描述符 irq_desc[中断向量号] softirq_vec[软中断号]
    中断请求寄存器 中断请求寄存器 __softirq_active
    中断屏蔽寄存器 中断屏蔽寄存器 __soft_mask
    中断处理程序 do_handler_name bh[]:32个
    中断处理程序描述项 irqaction softirq_action
    中断机制的初始化 trap_init():异常初始化
    init_IRQ():中断初始化
    softirq_action
    中断请求队列初始化 init_ISA_irqs() open_softirq
    中断处理程序与中断请求队列相关联 request_irq() init_bh()
    执行中断 do_IRQ() do_softirq

    4.激活可延迟函数

    (1)激活软中断

    A.把软中断置为把挂起状态,并周期性地检查处于挂起状态的软中断。如果有,就调用do_softirq()

    B.一般是在以下几个点来检查挂起的软中断的a.调用local_bh_enable()激活本地软中断时b.do_IRQ()完成I/O中断的处理即将退出时c.用于周期性检查挂起状态软中断的内核线程ksoftirqd被唤醒时d.else,我觉得不太重要

    (2)激活tasklet

    A.把自己定义的描述符加入到tasklet_vec指向的链表中即可。调用tasklet_action()时,依次处理队列中的每个tasklet描述符,然后清空tasklet_vec指向的链表。

    B.tasklet的每次激活至多触发tasklet函数的一次执行,除非tasklet函数重新激活自己


    三、工作队列

    1.它们允许内核函数被激活,并稍后由一种叫做工作者线程的特殊内核线程来执行

    如果系统有n个CPU,就会创建n个工作者,但是只会创建一个工作队列


    2.工作队列与可延迟函数的区别:

    可延迟函数运行中断上下文中,而工作队列运行在进程中下文中。

    可延迟函数不能阻塞,工作队列可执行可阻塞函数

    可延迟函数被执行时不可能有任何正在运行的进程,工作队列中的函数由内核线程来执行,不访问用户态地址空间


    3.工作队列的激活

    把要执行的函数的描述符插入到工作队列中。

    工作者等待函数被插入队列,被唤醒后把函数描述符取下并执行。

    由于工作队列函数可以阻塞,工作者线程可以睡觉,或移动另一个CPU


    总而言之:

           当发生一个中断时,如果发现中断被屏蔽,这个中断好似不会丢失的,会被再次触发!!


    展开全文
  • 中断有两种,一种是由CPU外部产生的,对于执行中的软件来说,这种中断的发生完全是“异步”的,根本无法预料此类中断会在什么时候发生,一般由其他硬件设备产生(例如键盘中断);另一种是由CPU本身在执行程序的过程...

    一、中断

    中断通常被定义为一个事件,该事件改变处理器执行的指令顺序。中断有两种,一种是由CPU外部产生的,对于执行中的软件来说,这种中断的发生完全是“异步”的,根本无法预料此类中断会在什么时候发生,一般由其他硬件设备产生(例如键盘中断);另一种是由CPU本身在执行程序的过程中产生的,例如X86中的“INT n”。Intel手册中分别将这两种中断称为中断和异常。Intel文档中,中断又可分为可屏蔽中断和非屏蔽中断,异常分为:故障、陷阱、异常中止和编程异常。不管是哪种中断,CPU的相应过程基本上一致,即:在执行完当前指令以后,或者在执行当前指令中途,根据中断源提供的“中断向量”,在内存中找到相应的服务程序入口并调用该服务程序。外部中断的向量是由软件或硬件设置好了的,陷阱的向量是在“自陷”指令中发出的(INT n中的n),而各种异常的向量则是CPU的硬件结构中预先规定好的。系统调用一般是通过INT指令实现的,所以也与中断密切相关。

    Intel X86 CPU支持256个不同的中断向量,早期X86 CPU的中断响应机制是非常简单的,内存中从0开始的1K字节作为一个中断向量表,表中每个表项占四个字节,由两字节的段地址和两字节的位移组成,构成的地址便是相应中断服务程序的入口地址。由于中断发生时,有可能涉及用户态和内核态的切换,那种简单的表项无法实现这种运行模式的切换,所以在Intel实现保护模式时,中断向量表中的表项从单纯的入口地址改成了更为复杂的描述项,称为“门”,当中断发生时必须通过这些门,才能进入相应的服务程序。按不同的用途和目的,CPU中一共有四种门:任务门、中断门、陷阱门和系统门(除了系统门外,其它三种都是用户态进程无法访问的中断门)。其中除任务门外其它三种门的结构基本相同,先看任务门,其大小为64位,结构如下图:

    这里写图片描述

    当中断发生时,CPU在中断向量表中找到相应表项,如果是一个任务门,CPU就会将当前任务的运行现场保存在相应的TSS中,并将任务门所指向的TSS作为当前任务,将其内存装入CPU中的各个寄存器,从而完成一次任务切换;

    其它三种门大小也是64位,如下图:

    这里写图片描述

    三种门之间的区别为3位的类型码。与任务门相比,不同之处主要在于:任务门中不需要段内位移,因为任务门指向一个段;而其它门则指向一个子程序,所以必须结合使用段选择码和段内位移。

    前面讲过,内核通过门描述符来实现运行模式的切换,这是怎么实现的呢?答案就在描述符的DPL字段,CPU先根据中断向量找到一扇门描述项,然后,将这个门的DPL与CPU的CPL相比,CPL必须小于或等于DPL,也就是优先级别不低于DPL,才能穿过这扇门。不过,如果中断是由外部产生或是因CPU异常而产生的话,就免去这一层检验。所以通过将系统门的DPL设为3,而其它的门的DPL设为0,就可以使用户态进程只能访问系统门,从而防止用户通过int指令模拟非法的中断和异常。

    进入中断服务程序时,如果中断服务程序的运行级别,也就是目标代码段的DPL,与中断发生时的CPL不同,那就要引起堆栈的更换。Linux中是这样更换堆栈的:如果进程thread_union结构大小为8KB,则当前进程的内核栈被用于所有类型的内核控制路径;相反,如果thread_union结构大小为4KB,内核使用三种类型的内核栈:

    • 异常栈:用于处理异常(包括系统调用)。这个栈包括在每个进程的thread_union数据结构中。
    • 硬中断请求栈:用于处理中断。系统中每个CPU都有一个硬中断请求栈,而且每个栈占用一个单独的页框。
    • 软中断请求栈:用于处理可延迟函数(软中断或tasklet)。系统中每个CPU都有一个软中断请求栈,而且每个栈占用一个单独的页框。

    二、中断向量表的初始化

    Linux内核在初始化阶段完成了页式虚存管理的初始化以后,便调用trap_init()和init_IRQ两个函数进行中断机制的初始化。其中trap_init()中主要是对一些系统保留的中断向量的初始化,而init_IRQ()则主要是用于外设的中断,这两个函数源码如下:

    1. trap_init()是在arch/i386/kelnel/traps.c中定义的:

          void __init trap_init(void)
          {
          #ifdef CONFIG_EISA
              void __iomem *p = ioremap(0x0FFFD9, 4);
              if (readl(p) == 'E'+('I'<<8)+('S'<<16)+('A'<<24)) {
                  EISA_bus = 1;
              }
              iounmap(p);
          #endif
      
          /*
           * APIC为高级可编程中断控制器
           */
          #ifdef CONFIG_X86_LOCAL_APIC
              init_apic_mappings();
          #endif
      
              /*
               * 程序中先设置中断向量表开头的19个陷阱门,这些中断向量都是CPU保留用于
               * 异常处理的,例如中断向量14就是为页面异常保留的
               */
              set_trap_gate(0,&divide_error);
              set_intr_gate(1,&debug);
              set_intr_gate(2,&nmi);
              set_system_intr_gate(3, &int3); /* int3-5 can be called from all */
              set_system_gate(4,&overflow);
              set_system_gate(5,&bounds);
              set_trap_gate(6,&invalid_op);
              set_trap_gate(7,&device_not_available);
              set_task_gate(8,GDT_ENTRY_DOUBLEFAULT_TSS);
              set_trap_gate(9,&coprocessor_segment_overrun);
              set_trap_gate(10,&invalid_TSS);
              set_trap_gate(11,&segment_not_present);
              set_trap_gate(12,&stack_segment);
              set_trap_gate(13,&general_protection);
              set_intr_gate(14,&page_fault);
              set_trap_gate(15,&spurious_interrupt_bug);
              set_trap_gate(16,&coprocessor_error);
              set_trap_gate(17,&alignment_check);
          #ifdef CONFIG_X86_MCE
              set_trap_gate(18,&machine_check);
          #endif
              set_trap_gate(19,&simd_coprocessor_error);
      
              /*
               * 然后是对系统调用的初始化,常数SYSCALL_VECTOR定义为0x80
               */
              set_system_gate(SYSCALL_VECTOR,&system_call);
      
              /*
               * Should be a barrier for any external CPU state.
               */
              cpu_init();
      
              trap_init_hook();
          }

      从程序中可以看到,这里用了三个函数来进行这些表项的初始化,分别为set_trap_gate()、set_system_gate()和set_intr_gate(),在同文件中定义如下:

       /*
           * 陷阱门
           */
          static void __init set_trap_gate(unsigned int n, void *addr)
          {
              _set_gate(idt_table+n,15,0,addr,__KERNEL_CS);
          }
      
          /*
           * 系统门
           */
          static void __init set_system_gate(unsigned int n, void *addr)
          {
              _set_gate(idt_table+n,15,3,addr,__KERNEL_CS);
          }
      
          /*
           * 中断门
           */
          void set_intr_gate(unsigned int n, void *addr)
          {
              _set_gate(idt_table+n,14,0,addr,__KERNEL_CS);
          }
      
          /* 
           * _set_gate函数如下,代码的理解需要结合上面门描述符的格式
           * %i为输出输入的变量,依次编号,%0与参数gate_addr结合,%1与(gate_addr+1)结合二者为内存单元
           * %2与局部变量__d0结合,存放在寄存器%%eax中,%3与局部变量__d1结合,存放在寄存器%%edx中
           * 输入部中第一个变量为%4,后面的两个变量等价于输出部的%3和%2,即存放在edx和eax中
      
          #define _set_gate(gate_addr,type,dpl,addr,seg) \
          do { \
            int __d0, __d1; \
            __asm__ __volatile__ (
              /* 
               * 将edx设为addr,eax设为(__KERNEL_CS << 16),edx的低16位移入eax的低16位,这样,在
               * %%eax中就形成了所需要的中断门的第一个长整数,其高16位为__KERNEL_CS,低16位为addr的低16位
               */
              "movw %%dx,%%ax\n\t" \
      
              /*
               * 将(0x8000+(dpl<<13)+(type<<8)))装入%%edx的低16位,这样,%%edx中高16位为addr高16位,
               * 而低16位的P位为1,DPL位段为dpl(因为dpl<<13),D位加上类型位段为type,其余各位都为0,这就是中断门的第二个长整数
               */
              "movw %4,%%dx\n\t" \
      
              /* 
               * 将%%eax写入*gate_addr
               */
              "movl %%eax,%0\n\t" \
      
              /*
               * 将%%edx写入*(gate_addr+1)
               */
              "movl %%edx,%1" \
              :"=m" (*((long *) (gate_addr))), \
               "=m" (*(1+(long *) (gate_addr))), "=&a" (__d0), "=&d" (__d1) \
              :"i" ((short) (0x8000+(dpl<<13)+(type<<8))), \
               "3" ((char *) (addr)),"2" ((seg) << 16)); \
          } while (0)
    2. 系统初始化时,在trap_init()中设置了一些为CPU保留的专用的IDT表项以及系统调用所用的陷阱门以后,就要进入init_IRQ()设置大量用于外设的通用中断门了,函数init_IRQ()源码在arch/i386/kernel/i8259.c中:

      void __init init_IRQ(void)
          {
              int i;
      
              pre_intr_init_hook();
      
              for (i = 0; i < (NR_VECTORS - FIRST_EXTERNAL_VECTOR); i++) {
                  int vector = FIRST_EXTERNAL_VECTOR + i;
                  if (i >= NR_IRQS)
                      break;
                  if (vector != SYSCALL_VECTOR) 
                      set_intr_gate(vector, interrupt[i]);
              }
      
              /* setup after call gates are initialised (usually add in
               * the architecture specific gates)
               */
              intr_init_hook();
      
              /*
               * Set the clock to HZ Hz, we already have a valid
               * vector now:
               */
              setup_pit_timer();
      
              /*
               * External FPU? Set up irq13 if so, for
               * original braindamaged IBM FERR coupling.
               */
              if (boot_cpu_data.hard_math && !cpu_has_fpu)
                  setup_irq(FPU_IRQ, &fpu_irq);
      
              irq_ctx_init(smp_processor_id());
          }

      pre_intr_init_hook()函数相当于一下代码:

      
      #ifndef CONFIG_X86_VISWS_APIC
      
              init_ISA_irqs();
          #else
              init_VISWS_APIC_irqs();
          #endif
      
          void __init init_ISA_irqs (void)
          {
              int i;
      
          #ifdef CONFIG_X86_LOCAL_APIC
              init_bsp_APIC();
          #endif
              init_8259A(0);
      
              for (i = 0; i < NR_IRQS; i++) {
                  irq_desc[i].status = IRQ_DISABLED;
                  irq_desc[i].action = NULL;
                  irq_desc[i].depth = 1;
      
                  if (i < 16) {
                      /*
                       * 16 old-style INTA-cycle interrupts:
                       */
                      irq_desc[i].handler = &i8259A_irq_type;
                  } else {
                      /*
                       * 'high' PCI IRQs filled in on demand
                       */
                      irq_desc[i].handler = &no_irq_type;
                  }
              }
          }

      init_ISA_irqs()和init_VISWS_APIC_irqs()分别是对8259A中断控制器和高级可编程中断控制器的初始化,并且初始化一个结构数组irq_desc[]。为什么要有这么一个结构数组呢?i386系统结构支持256个中断向量,除去CPU本身保留的,很难说剩下的这些向量是否够用。而且,很多外部设备由于各种原因本来就不得不共用中断向量,所以,Linux通过IRQ共享和IRQ动态分配。因此,系统中为每个中断向量设置一个队列,根据每个中断源所使用的中断向量,将其中断服务程序挂到相应的队列中去,而irq_desc[]中的每个元素则是这样一个队列头部及控制结构。当中断发生时,首先执行与中断向量相对应的一段总服务程序,根据具体的中断源的设备号在其所属队列中找到特定的服务程序加以执行,该结构在下文再详细分析。
      回到init_IRQ()函数,接下来的循环从FIRST_EXTERNAL_VECTOR开始,设立NR_IRQS个中断向量的IDT表项常数FIRST_EXTERNAL_VECTOR为0x20,这里设置的服务程序入口地址都来自一个函数指针interrupt[],该函数指针数组定义如下(arch/i386/kernel/entry.S):

       .data
          ENTRY(interrupt)
          .text
      
          vector=0
          ENTRY(irq_entries_start)
          .rept NR_IRQS
              ALIGN
          1:  pushl $vector-256
              jmp common_interrupt
          .data
              .long 1b
          .text
          vector=vector+1
          .endr

      数组包括NR_IRQS个元素,这个宏产生的数为224或16,当内核支持I/O APIC芯片时,为224,当内核支持8259A时,为16。可以看出,数组中索引为n元素把中断号减256的结果保存在栈中(原因:内核用负数表示所有中断,正数用来表示系统调用,用汇编指令可以很容易判断正负)。由此可以看出,实际上由外设产生的中断处理全部进入一段公共的程序common_interrupt中,在同一文件中定义如下:

      common_interrupt:
              SAVE_ALL
              movl %esp,%eax
              call do_IRQ
              jmp ret_from_intr
      
          /* SAVE_ALL如下 */
          #define SAVE_ALL \
          cld; \
          pushl %es; \
          pushl %ds; \
          pushl %eax; \
          pushl %ebp; \
          pushl %edi; \
          pushl %esi; \
          pushl %edx; \
          pushl %ecx; \
          pushl %ebx; \
          movl $(__USER_DS), %edx; \
          movl %edx, %ds; \
          movl %edx, %es;

      SAVE_ALL在栈中保存中断处理程序可能会使用的所有CPU寄存器,但eflags、cs、eip、ss及esp除外,因为这几个寄存器已经由控制单元自动保存了,然后这个宏把用户数据段选择符装到ds和es寄存器中。保存寄存器值以后,栈顶地址被存放到eax寄存器中,然后中断处理程序调用do_IRQ()函数(这个函数在稍后的博客中将独立分析),函数结束时,控制转到ret_from_intr()(见后面博客)。


    三、中断请求队列的初始化

    从上面的分析可以看出,trap_init()是为特定的中断源初始化中断向量表项的,而init_IRQ()是为外设的通用中断门设置表项的,不过还没完成,也就是说,通用中断的服务程序为空,所以需要进一步的初始化,即本节分析的中断请求队列的初始化。由于通用中断是让多个中断源共用的,而且允许这种共用的结构在系统运行过程中动态地变化,所以在IDT的初始化阶段只是为每个中断向量(表项)准备下一个“中断请求队列”,从而形成一个中断请求队列数组,这就是irq_desc[],定义如下:

        /*
         * 主要是一些函数指针,用于该队列或者说该共用“中断通道”的控制(而并不是对具体中断源的服务)
         * 具体函数取决于所用的中断控制器。例如,enable和disable用来开启和关闭其所属通道,ack用于对中断控制器的响应
         * 而end则用于每次中断服务返回前夕,这些函数都是在init_IRQ()中调用pre_intr_init_hook()设置好的
         */
        struct hw_interrupt_type {
            const char * typename;
            unsigned int (*startup)(unsigned int irq);
            void (*shutdown)(unsigned int irq);
            void (*enable)(unsigned int irq);
            void (*disable)(unsigned int irq);
            void (*ack)(unsigned int irq);
            void (*end)(unsigned int irq);
            void (*set_affinity)(unsigned int irq, cpumask_t dest);
        };
    
        typedef struct hw_interrupt_type  hw_irq_controller;
    
        typedef struct irq_desc {
            /*
             * 指向PIC对象,服务于IRQ线
             */
            hw_irq_controller *handler;
            /*
             * PIC方法所使用的数据
             */
            void *handler_data;
            /*
             * 用来维持一个由中断服务程序描述项构成的单链队列
             */
            struct irqaction *action;   /* IRQ action list */
            unsigned int status;        /* IRQ status */
            unsigned int depth;     /* nested irq disables */
    
            /* 下面两项在诊断时使用 */
            unsigned int irq_count;     /* For detecting broken interrupts */
            unsigned int irqs_unhandled;
            spinlock_t lock;
        } ____cacheline_aligned irq_desc_t;
    
        extern irq_desc_t irq_desc [NR_IRQS];
    
        struct irqaction {
            /*
             * 最主要的就是函数指针handle,指向具体的中断服务程序
             */
            irqreturn_t (*handler)(int, void *, struct pt_regs *);
            unsigned long flags;
            cpumask_t mask;
    
            /* I/O设备名和私有字段 */
            const char *name;
            void *dev_id;
    
            struct irqaction *next;
            int irq;
            struct proc_dir_entry *dir;
        };

    真正的中断服务要到具体设备的初始化程序将其中断服务程序通过request_irq()向系统“登记”,挂入某个中断请求队列以后才会发生,该函数如下:

        /*
         * 参数irq为中断请求队列的序号,也就是人们通常说的“中断请求号”,对应于中断控制器中的一个通道,
         * 有时候要在接口卡上通过微型开关或跳线来设置,注意与中断向量的区别
         * 在内核中,设备驱动程序一般都要通过request_irq()向系统登记其中断服务程序
         */
        int request_irq(unsigned int irq, irqreturn_t (*handler)(int, void *, struct pt_regs *),
             unsigned long irq_flags, const char * devname, void *dev_id)
        {
            unsigned long retval;
            struct irqaction *action;
    
            if (irq >= NR_IRQS || !irq_desc[irq].valid || !handler ||
                (irq_flags & SA_SHIRQ && !dev_id))
                return -EINVAL;
    
            action = (struct irqaction *)kmalloc(sizeof(struct irqaction), GFP_KERNEL);
            if (!action)
                return -ENOMEM;
    
            action->handler = handler;
            action->flags = irq_flags;
            cpus_clear(action->mask);
            action->name = devname;
            action->next = NULL;
            action->dev_id = dev_id;
    
            retval = setup_irq(irq, action);
    
            if (retval)
                kfree(action);
            return retval;
        }
    

    在分配并设置了一个irqaction数据结构action后,便调用setup_irq(),将其链入相应的中断请求队列中,函数如下:

        int setup_irq(unsigned int irq, struct irqaction *new)
        {
            int shared = 0;
            struct irqaction *old, **p;
            unsigned long flags;
            struct irqdesc *desc;
    
            if (new->flags & SA_SAMPLE_RANDOM) {
                /*
                 * 为中断请求队列初始化一个数据结构,用来记录该中断的时序
                 */
                rand_initialize_irq(irq);
            }
    
            /*
             * The following block of code has to be executed atomically
             */
            desc = irq_desc + irq;
            spin_lock_irqsave(&irq_controller_lock, flags);
            p = &desc->action;
            if ((old = *p) != NULL) {
                /* 
                 * 检查是否允许共用一个中断通道,只有在新加入的结构以及队列中的第一个结构都允许共用时才将其链入队列的尾部
                 */
                if (!(old->flags & new->flags & SA_SHIRQ)) {
                    spin_unlock_irqrestore(&irq_controller_lock, flags);
                    return -EBUSY;
                }
    
                /* add new interrupt at end of irq queue */
                do {
                    p = &old->next;
                    old = *p;
                } while (old);
                shared = 1;
            }
    
            *p = new;
    
            if (!shared) {
                desc->probing = 0;
                desc->running = 0;
                desc->pending = 0;
                desc->disable_depth = 1;
                if (!desc->noautoenable) {
                    desc->disable_depth = 0;
                    desc->chip->unmask(irq);
                }
            }
    
            spin_unlock_irqrestore(&irq_controller_lock, flags);
            return 0;
        }
    
    展开全文
  • 硬件中断请求IRQ

    千次阅读 2010-03-22 17:38:00
    http://tech.sina.com.cn/c/2004-05-17/0931362528.shtml 文/蔡远翔  IRQ(Interrupt Request)的...比如我们要打印一份文件,在打印结束时就需要由系统对打印机提出相应的中断请求,来以此结束这个打印的操作。 产
  • 本文通过按键发起中断请求尝试学习PL请求中断的处理机制。 板子用的是zc702。 ZYNQ是中断类系统框图: 由上图可知,zynq的中断分为三种: 1.软件中断(SGI,Software generatedinterrupts,中断号0-15)...
  • 中断向量表
  • 中断之中断请求队列的初始化

    千次阅读 2011-11-11 17:28:10
    参考文章:http://bbs.chinaunix.net/thread-3566316-1-1.html 参考文章:... 在“初始化中断向量表”的博文中,我们留下了一个问题,就是 void __init native_init_IRQ(void) { int i; /* Ex
  • 服务端是无法感知的,只能通过发心跳包来检测,而显然我们的nginx是没有发心跳包的,所以,包括nginx,php-fpm都是不知道客户端已断开,而且web服务器也都没做这种中断机制,所以服务器依然会把客户端的请求走完,...
  • vue+axios中断正在发送的请求

    千次阅读 2021-02-25 15:00:53
    axios内部提供axios.CancelToken.source().cancel()方法用于取消正在发送的请求,我们可以利用这个去处理 简单的api封装,具体看公司项目的封装方式 import axios from 'axios' export function getDataList ...
  • 如何中断一个正在发出的请求

    千次阅读 2018-04-12 06:29:59
    在用户交互或者最新输入的时候,你常常需要在一个 web 应用中频繁发起请求。比如说在输入文字的时候的自动完成操作或者是在地图上放大缩小的操作。让我们花一点时间去思考一下这些例子。首先是自动完成,每一次我们...
  • linux设备驱动的中断与并发请求事件

    千次阅读 2009-06-10 21:12:00
    设备驱动的中断事件处理如下图所示,他与普通驱动的不同之处在于多了个数据缓冲区,驱动程序对上层提供的read/write方法并不直接完成硬件的数据操作,中断处理程序也是相对独立,他们通过缓冲区交换数据。...
  • TinyThread源码分析之中断 转载请注明来源:cuixiaolei的技术博客 ... TinyThread 是基于Cortex-... IPSR(中断程序状态寄存器),IPSR包含了当前正在执行的中断服务程序编号,用于识别当前中断。  Cortex-M0处理器...
  • 中断中断处理(一)

    千次阅读 2015-07-13 07:45:34
    (一):中断中断本质上是...这些中断值被称为中断请求线(IRQ)。中断是随时随地发生的,也就是说中断并不考虑与处理器的时钟同步。异常:异常的产生必须与处理器时钟同步,异常也被成为同步中断。在处理器执行到由于
  • 中断

    千次阅读 2017-02-11 16:29:34
    中断的概念及使用方法进行分析
  • 什么中断

    千次阅读 多人点赞 2019-07-03 14:40:35
    中断嘛,对于很多新手来讲,有点难以理解,举个容易的现实的例子吧。如果还不懂,骚年,考虑换行吧,或许这不是你的方向呢,诗和远方在前面岔路左拐。 比如小明和老丈人在喝酒,眼看老丈人就快招架不住了,正当此时...
  • DMA是l/O设备与主存之间由硬件组成的直接数据通路,主要用于高速I/O设备与主存之间的成组数据传送。(外设快、慢了没意义。然后数据多,少了也没多大...外设向cpu发出中断请求,cpu响应中断后进行数据传输。但是如...
  • 80c51单片机有两个外部中断,当实际应用系统有两个以上的外部中断源,而片内定时/计数器未使用时,可利用定时/计数器来扩展外部中断源。方法如下:将定时/计数器设置为计数模式,计数初值设定为满值,将待扩展的外部...
  • 这里写目录标题先验知识回顾控制寄存器回顾1、8086中断类型1、外部可屏蔽中断2、外部不可屏蔽中断3、除法错中断4、单步中断5、断点中断6、溢出中断7、软中断2、8086中断向量表3、8086中断响应1、外部可屏蔽中断响应2...
  • 【STM32】NVIC中断优先级管理(中断向量表)

    万次阅读 多人点赞 2018-04-08 19:55:22
    Cortex-M3内核支持256个中断,其中包含了16个内核中断(异常)和240个外部中断,并且具有256级的可编程中断设置。但是,STM32并没有使用CM3内核的全部东西,而是只用了它的一部分。STM32有84个中断,包括16个内核...
  • 中断系统

    2015-07-05 15:47:10
    中断系统的功能包括中断请求的保存和清除,优先级的确定,中断断点及现场的保存,对中断请求的分析和处理以及中断返回等。 在计算机中,中断可分为内部中断,外部中断,软件中断三类。 内部中断由CPU内的
  • 关于可屏蔽中断与不可屏蔽中断

    千次阅读 2014-05-07 19:10:38
    可屏蔽中断和不可屏蔽中断都属于外部中断,是由外部中断源引起的;但它们也有区别:可屏蔽中断是通过CPU的INTR引脚引入,当中断标志IF=1时允许中断,当IF=0时...CPU一般设置两根中断请求输入线:可屏蔽中断请求INTR
  • 外部中断和内部中断详解

    千次阅读 2017-09-06 17:59:40
    当一个经理正处理文件时,电话铃响了(中断请求),不得不在文件上做一个记号(返回地址),暂停工作,去接电话(中断),并指示“按第二方案办”(调中断服务程序),然后,再静下心来(恢复中断前状态),接着处理...
  • 中断详解

    千次阅读 2018-10-24 01:28:04
    程序中断是指在计算机执行现行程序的过场中,出现某些急需处理的异常情况或特殊请求,CPU暂停中断现行程序,而专区对这些异常情况或特殊情况进行处理,在处理完毕后CPU又自动返回到现行程序的断点处,继续执行原程序...
  • ...■看图理解:硬件中断与软件中断...1、8086/8088CPU可以处理256种不同类型的中断,每一种中断都给定一个编号(0~255),称为中断类型号,CPU根据中断类型号来识别不同的中断源; 2、中断类型号0~4已有固定对应
  • 中断屏蔽技术

    万次阅读 多人点赞 2016-12-20 18:12:14
    多重中断:(中断嵌套)当CPU正在执行某个中断服务程序时,另一个中断源又提出了新的中断请求,而CPU又响应了这个新的请求,暂时停止正在运行的服务程序,转去执行新的中断服务程序,这称为多重中断,又称中断嵌套。...

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 172,979
精华内容 69,191
关键字:

中断请求用于什么