九、异常处理(Exception Handling)

前言

异常是指任何需要核心停止正常执行,并转而执行与每种异常类型相关联的专用软件例程(称为异常句柄或异常入口)的情况。发生异常通常表示需要在特权模式下采取补救措施或更新系统的状态,以确保系统的平稳运行,这被称为处理异常。当异常处理完成后,特权软件需要为恢复到异常发生前的断点处继续运行做准备。其它体系结构可能将ARM所谓的异常称为陷阱或中断,但在ARM体系结构中,这些术语被保留用于特定类型的异常。

所有的微处理器都必须响应外部异步事件,例如按下按钮或时钟达到某个值。通常,有专门的硬件可以激活连接到核心的输入线,这会导致CPU核心暂时停止当前执行的代码序列,转而去执行特定的异常处理句柄。CPU核心响应此类异步事件的速度称为中断延迟(interrupt latency),是系统设计中的一个关键问题。在嵌入式系统里面,我们不会把系统所有的功能都做到中断里面,所以为这些功能分配优先级是设计的关键。系统不是通过CPU不断测试来自不同部分的标志来查看是否有要做的事情,而是通过生成中断来通知CPU核心有事情发生。复杂系统有非常多的中断源,它们具有不同的优先级并且可以嵌套处理中断,其中高优先级中断可以打断低优先级中断的处理。

在正常的程序执行中,程序计数器PC会随着程序的执行递增。程序中的显式跳转会修改指令的执行流程,例如函数调用、循环和条件判断。当异常发生时,预定的指令执行序列会被中断,并暂时切换到处理异常的代码。

除了响应外部中断之外,还有一些其它的事件可以让CPU核产生异常,包括外部的(例如复位、内存系统的外部中止)和内部的(例如MMU生成的中止或使用SVC指令的操作系统调用)事件。之前的文章中已经学习过了,在处理异常时,CPU核心会切换处理器模式并将一些寄存器复制到其它寄存器中。

异常类型( Types of exception)

之前已经学习过了,ARMv7-A和ARMv7-R支持好几种处理器模式。其中,FIQ、 IRQ、Supervisor、Abort、Undefined、System这六种模式为特权模式,User为非特权模式(用户模式);如果实现了虚拟扩展,则增加Hyp模式(虚拟机管理程序模式);如果实现了安全扩展,则增加Monitor模式(监视器模式)。当前模式可以在特权级软件里面改变或捕获异常时自动改变。

非特权的用户模式无法直接影响核心(处理器)的异常行为,但可以通过SVC调用产生SVC(Supervisor Call)异常来请求特权服务,用户的应用程序就是通过产生SVC异常来请求操作系统帮它完成某些任务的。

当异常发生时,核心(处理器)会保存当前的状态和返回地址,进入特定的模式,并可能禁用硬件中断。一个给定异常的处理是从一个固定的内存地址开始的,这个内存地址叫做该异常的异常向量。特权软件可以将一组异常向量的起始地址写到系统寄存器中,当某个异常发生时,会自动执行对应的异常向量。

有如下几类异常:

  • Interrupts(中断)
    ARMv7-A的CPU核提供了两种类型的中断,FIQ interrupt(快速中断)和IRQ interrupt(中断)。
    FIQ中断的优先级高于IRQ中断。相比IRQ模式而言,FIQ模式下有更多的影子寄存器(这可以节省在异常handler中保存和恢复寄存器上下文的时钟周期),加上FIQ中断在异常向量表中的位置,故FIQ中断比IRQ中断更具速度优势。这两种类型的异常通常都与芯片的输入引脚相关——外部硬件通过输入引脚产生中断请求,对应的异常将在CPU执行完当前指令时产生(假设中断未被禁用)。
    FIQ和IRQ都是连接到CPU的物理信号,当CPU接收到信号时,会进行相应的异常处理。在几乎所有的系统中,各种中断源都会连接到一个中断控制器进,这个中断控制器负责根据优先级仲裁各种中断请求,然后发出一个序列化的单一信号,该信号随后被连接到CPU的FIQ或IRQ信号上,更多信息可以参考《The Generic Interrupt Controller》。
    由于IRQ和FIQ中断的发生与核心在任何给定时间执行的程序没有直接关系,因此它们被归类为异步异常。

  • Aborts(中止)
    中止可能由指令获取失败(预取中止)或数据访问失败(数据中止)而产生,它们来自外部存储系统对错误内存访问的响应(表明有可能指定的地址实际上是不可访问的)。另外,中止也可以由核心的内存管理单元(MMU)生成,操作系统可以使用MMU产生的中止来动态地为应用程序分配内存。
    当指令被预取时,可以在流水线中将其标记为中止。仅当核心尝试执行该指令时,才会发生预取中止异常。异常发生在指令执行之前,所以如果在中止的指令到达流水线执行阶段之前刷新了流水线,则不会发生中止异常。数据中止异常是在执行加载或存储指令的时候发生的,并且被认为是在尝试读取或写入数据之后发生的。
    如果中止是因为执行或尝试执行指令而产生的,则这个中止异常为同步异常(synchronous),该同步异常的返回地址将提供导致该中止的指令的详细信息(可通过异常发生时core dump打印的现场信息定位到发生异常的指令)。
    异步中止不是由执行指令产生的,异常的返回地址也并不总是能够提供导致中止的详细信息。
    ARMv7架构将异步中止进一步区分为精确的异步中止和不精确的异步中止,而由MMU生成的中止始终是同步的。该架构不要求特定的外部中止访问类别必须是同步的。
    例如,在特定实现上,遍历地址转换表时报告的外部中止可能被视为精确的异步中止,但并没有要求所有的CPU核都要这样。对于精确的异步中止,异常处理程序可以确定导致中止异常发生的指令,并且该指令之后没有执行其它的指令。这与不精确的异步中止相反,不精确的异步中止是外部存储系统报告不可识别访问错误的结果。
    在这种情况下,异常handler无法确定是哪个指令导致了问题,或者是否在造成中止异常的指令之后执行了其它指令。
    例如,如果带缓冲的写操作收到来自外部存储系统的错误响应,将会有额外的指令在存储操作后被执行。这意味着中止异常handler无法修复问题并返回到应用程序,它能做的仅仅是杀死导致问题的应用程序。因此,需要对设备探测做特殊处理,因为读取的存储区域不存在时(哪怕这个区域被标记为Strongly-ordered或Device),外部报告的中止也会生成不精确的同步中止。
    异步中止的检测由CPSR(当前程序状态寄存器)的A位控制。如果A位被设置,CPU核心将识别来自外部存储系统的异步中止,但不会生成中止异常。核心会保持中止挂起状态,直到A位被清除,并在那时产生异常。内核代码将使用屏障指令来确保挂起的异步中止是针对正确的应用程序进行识别的。如果因为不精确的中止而必须杀死一个线程,必须确保杀的是正确的线程。

  • Reset(复位)
    所有的CPU核心都有一个复位输入,它们在收到复位信号后会立即触发复位异常。这是优先级最高的异常,不能被屏蔽。这个异常用于在电源启动后,在核心上运行初始化代码来执行初始化操作。

  • Instruction Generated Excepitons(指令异常)
    执行某些指令可能会生成异常,这些指令通常用于请求在更高特权级别上运行的软件提供服务。
    “Supervisor Call”(SVC)指令允许用户模式的程序请求操作系统提供服务。
    如果实现了虚拟化扩展,则可以使用"Hypervisor Call"(HVC)指令,使来宾操作系统请求Hypervisor服务。
    如果实现了安全扩展,则可以使用"Secure Monitor Call"(SMC)指令,在正常世界里面请求安全世界的服务。
    任何尝试执行核心不识别的指令的操作都会生成一个UNDEFINED异常。

当发生异常时,核心会执行与该异常对应的处理程序。处理程序在内存中存储的位置被称为异常向量。在ARM架构中,所有的异常向量存储在一个表中,这个表称为异常向量表。因此,可以从异常向量表的开始,以固定的偏移量找到各个异常向量的位置。特权软件会将异常向量表的基地址编程到系统寄存器中,以便在发生异常时,核心可以从表中找到相应的处理程序。

可以为Secure PL1、Non-secure PL1、Secure monitor和Non-secure PL2特权级别配置单独的向量表。核心用于查找处理程序的向量表取决于当前的特权级别,以及即将进入的特权级别和安全状态。

您可以使用ARM或Thumb代码编写异常处理程序。CP15的SCTLR.TE位用于指定异常处理程序将使用ARM还是Thumb。在处理异常时,必须保存当前程序的特权模式、状态和内核寄存器,以便在异常处理完成后恢复程序的上下文。

异常优先级(Exception priorities)

当多个异常同时发生时,会依次处理每个异常,最后再返回到应用程序。不可能同时发生所有异常。例如,“未定义指令”(Undef)和“超级管理员调用”(SVC)异常是相互排斥的,因为它们都是由执行指令触发的。

ARM架构没有定义何时发生异步异常。因此,异步异常相对于其它异常(无论是同步还是异步)的优先级是由实现定义的。

所有的异常都会禁用IRQ,只有FIQ和Reset会禁用FIQ。这是由核心自动设置CPSR的I(IRQ)和F(FIQ)位来完成的。因此,除非在处理程序里面明确禁用FIQ,否则FIQ异常可以中断中止处理程序或IRQ异常。在数据中止和FIQ同时发生的情况下,首先会捕获数据中止(其优先级更高),此时核心记录了数据中止的返回地址。但由于数据中止不会禁用FIQ,核心又会立即捕获到FIQ异常。在FIQ结束时,您将返回到数据中止处理程序。

可能会同时产生多个异常,但某些组合是相互排斥的。预取中止将指令标记为无效,因此不能与未定义指令或SVC同时发生(当然,SVC指令也不能是未定义指令)。这些指令不会引起任何内存访问,因此不会引起数据中止。该架构没有定义必须在什么时候捕获异步异常、FIQ、IRQ或异步中止,但捕获IRQ或数据中止异常不会禁用FIQ异常的事实意味着FIQ执行的优先级高于IRQ或异步中止处理。

从这两段描述来看,异常优先级分为捕获优先级和处理优先级,当提到优先级的时候,需要根据上下文判断指的是捕获优先级还是处理优先级,通常我们说的优先级都是处理优先级。根据异常的定义,异常发生时会中断程序的运行转而去处理异常。异常随时都可能发生,那么被中断的程序可能是用户态的应用程序,也可能是异常处理程序。只要有异常发生并且它没有被禁用,那么一定会被立即捕获并跳转到其异常处理程序,不管当前是不是本来就在处理异常,也不管当前正在处理的异常是否有更高的优先级。只是当多个异常同时发生时,其捕获的顺序是按照捕获优先级来的,优先级高的异常先被捕获。但是处理优先级却刚好跟捕获优先级的顺序相反,因为最后捕获的异常反而最先得到处理,所以低捕获优先级的异常反而具有高处理优先级。但是,我们可以通过在异常处理程序里面手动启用或禁用某些异常来调整异常捕获的先后顺序,间接达到调整处理优先级的目的。因为对不同的应用场景,处理优先级都不是一概相同的,或多或少都需要做一些调整。

核心上的异常处理是通过使用称为向量表的内存区域来控制的。默认情况下,向量表位于内存映射的底部区域,以字对齐的地址从0x00到0x1C排布。大多数带缓存的核心都允许将向量表从0x0移动到0xFFFF0000。

对于具有安全扩展的核心,情况更为复杂。这里有三个向量表:非安全、安全和安全监视器。对于具有虚拟化扩展的核心,有四个向量表,增加了一个虚拟机管理程序向量表。对于具有MMU的核心,所有这些向量地址都是虚拟的。下图是各个异常在向量表中的位置:
向量表
向量表
从表中可以得知我们可以使用多达4个向量表,它们的虚拟地址都是从0x0或0xFFFF0000开始。前面讲MMU的时候已经学过了,不同的模式可以设置不同的地址转换表。因此不同模式下的同一个虚拟地址,可以转换为不同的物理地址,它们之间的内存实际上是隔离的,4个向量表是独立的。

异常模式总结(Exception mode summary)

捕获并处理异常的时候,处理器会进入对应的模式,下表是各个异常对应的处理器模式:
异常对应的处理器模式
向量表(The Vector table)

前面向量表图中的第一列给出了向量表中与特定类型的异常相关联的向量偏移。这是一个ARM核心在发生异常时跳转到的指令表。这些指令位于内存的特定位置,默认的向量基地址是0x00000000,但大多数ARM核心允许将向量基地址移动到0xFFFF0000(或HIVECS)。所有的Cortex-A系列处理器都允许这样做,并且它是Linux内核选择的默认地址。实现了安全扩展的核心还可以单独为安全状态和非安全状态设置向量基地址,使用CP15向量基地址寄存器。

您可能会注意到,每种异常类型的向量都占用一个字地址。因此,对于每种异常,向量表中只能放置一条指令(尽管在理论上可以使用两个16位的Thumb指令)。因此,向量表条目几乎总是包含以下两种分支形式之一:

  • B <label>
    这条指令执行了一个相对于程序计数器PC的跳转,它适合用于跳转到与当前PC值足够接近的异常处理程序,其相对于PC的偏移可以用24位编码表示。

  • LDR PC, [PC, #offset]
    这条指令从一个内存位置读取要跳转到的指令地址(或者异常处理程序),这个内存位置的地址与该异常向量的地址(也即本条LDR指令的地址)偏移为offset。这允许将异常处理程序放置在完整的32位内存空间内的任意地址(但相对于简单的跳转需要花费一些额外的周期)。

当核心在Hyp模式下运行时,它使用Hyp模式对应的那张向量表(虚拟机管理程序向量表)。通过使用向量表中0x14处的Hyp trap entry向量来引发一个称为Hyp trap entry的特殊异常过程来进入Hypervisor模式。一个专用寄存器(Hyp Syndrome Register)向超级管理程序提供有关异常或其它进入超级管理程序的原因的信息(例如,被捕获的CP15操作)。

为什么核心已经在Hyp模式下运行了,还需要进入Hypervisor模式呢?以下是AI答复:
核心在Hyp模式下运行并不意味着它已经完全进入了Hypervisor模式。在ARM架构中,Hyp模式是专门为虚拟化设计的非安全特权模式,它允许Hypervisor运行并管理虚拟机(VMs)。然而,Hyp模式本身并不等同于Hypervisor模式。当核心在Hyp模式下运行时,它执行的是Hypervisor的代码,但仍然需要一种机制来触发Hypervisor的介入,以便处理与虚拟化相关的异常和事件。这些异常和事件可能包括客户操作系统尝试执行特权指令、访问受保护的资源或发生其他需要Hypervisor干预的情况。

快速中断和中断(FIQ and IRQ)

FIQ是为某个需要保证快速响应时间的单个高优先级中断源保留的,而IRQ则用于处理系统中所有的其它中断。

由于FIQ是向量表中的最后一个条目,因此FIQ处理程序可以直接放置在向量中FIQ的位置并从该地址直接执行。这样做可以避免使用跳转指令和与之相关的延迟,从而加快FIQ的响应时间。与其它模式相比,FIQ模式有更多可用的影子寄存器,从而节省保存和恢复内核寄存器的时间,提高执行速度。

IRQ和FIQ之间的另一个关键区别在于,FIQ处理程序预期不会产生任何其它异常。因此,FIQ被保留给特殊的与系统有关的设备,这些设备的内存都已映射,并且无需进行SVC调用以访问内核函数(因此,只有不需要使用内核API的代码才能使用FIQ)。

简而言之,FIQ是一种特殊的中断类型,专为那些不会生成其它异常的系统级设备设计。这些设备可以直接访问其所需的内存,而无需通过内核API进行SVC调用。这种设计使得FIQ处理程序能够更快、更高效地运行,特别适用于需要快速响应的实时系统或硬件故障处理。

Linux通常不使用FIQ。由于内核是独立于体系结构的,它没有多种中断形式的概念。一些运行Linux的系统仍然可以利用FIQ,但由于Linux内核从未禁用FIQ,因此它具有比系统中任何其它内容(无论是应用还是异常)更高的优先级,因此需要谨慎处理。

如下内容是AI对本部分知识的补充:

Linux内核的设计注重可移植性和通用性,因此它并不直接依赖于特定的硬件特性或中断机制。相反,Linux使用一种统一的中断处理机制来处理所有的中断请求,无论是IRQ还是FIQ。这意味着Linux内核本身并不区分IRQ和FIQ的不同,而是将它们都视为需要处理的中断事件。

尽管Linux内核不直接利用FIQ,但在某些特定的硬件平台和系统配置中,FIQ仍然可以被用于优化性能或处理特定的硬件事件。在这些情况下,系统开发者需要特别注意FIQ的优先级和中断处理行为,以确保它们与Linux内核的中断处理机制兼容,并且不会导致系统不稳定或意外行为。

总的来说,尽管FIQ在某些特定场景下可能有用,但Linux内核本身并不依赖于FIQ,而是使用一种更通用的中断处理机制来管理系统中断。

返回指令(The return instruction)

LR寄存器用来保存合适的返回地址,以便异常处理完成后能够正确返回到原来的程序流。根据异常的类型,在使用LR返回的时候,必须按照下表对LR的值进行修改和调整。
异常返回时调整LR的值
异常处理( Exception handling)

当发生一个异常的时候,ARM核心会自动做以下的一些事情:

  1. 拷贝CPSR寄存器到异常对应SPSR_<mode>寄存器中。
  2. 将返回地址保存到异常对应的LR寄存器中。
  3. 修改CPSR的模式位,将处理器模式设置为与异常关联的模式。
    3.1 其它的CPSR模式位被设置为由CP15系统控制寄存器中的位所确定的值。
    3.2 T位被设置为由CP15 TE位给出的值。
    3.3 J位被清除,E位(字节序)被设置为EE(异常字节序)位的值。
    这使得异常始终在ARM或Thumb状态下运行,并且以小端或大端方式运行,而不考虑核心在异常发生之前所处的状态。
    4.修改PC的值,指向异常向量表中对应的异常向量。

异常handler代码几乎总是需要在开始的时候将寄存器保存到栈里面,FIQ异常有比较多的影子寄存器,因此需要保存到栈的寄存器更少。

异常处理返回(Exit from an exception handler)

为了从异常处理返回,必须原子地执行两个单独的操作:

  1. 从SPSR_<mode>恢复CPSR寄存器。
  2. 使用返回指令设置PC,返回发生异常前的代码。

下图为异常处理和异常返回的流程示意图:

异常处理和返回
其它异常处理handlers( Other exception handlers)

这部分简要描述了中止、未定义指令和SVC异常的handler,并考虑了Linux内核如何处理中断。复位handler将在后面介绍。

  • 中止handler(Abort handler)
    中止处理程序代码在不同的系统之间可能会有很大的差异。在许多嵌入式系统中,中止表示出现了意外的错误,异常处理程序将记录任何诊断信息,报告错误,并让应用程序(或系统)优雅地退出。
    在使用MMU支持虚拟存储的系统中,终止处理程序可以将所需的虚拟页面加载到物理内存中。实际上,它试图修复造成中止异常的问题,然后返回到中止的指令并重新执行它。之前的关于内存管理单元的文章提供了更多关于Linux如何做到这一点的信息。
    CP15寄存器提供了导致中止的内存访问的地址(故障地址寄存器)和中止的原因(故障状态寄存器)。原因可能是缺乏访问权限、外部中止或地址转换错误。除此之外,LR链接寄存器(根据中止是由指令获取还是数据访问引起的,进行-8或-4的调整)提供了导致中止异常的指令地址。通过检查这些寄存器、最后执行的指令以及系统中可能的其它内容(例如转换表入口),中止处理程序可以确定应采取什么行动。

  • 未定义指令handler(Undefined instruction handling)
    如果核心尝试执行ARM体系结构规范中定义为UNDEFINED的操作码指令,或者当执行协处理器指令但没有协处理器将其识别为可以执行的指令时,将引发未定义指令异常。
    在某些系统中,代码里面可能包含关于协处理器(例如VFP协处理器)的指令,但系统中没有相应的VFP硬件。此外,VFP硬件可能无法处理特定的指令,并希望调用软件来模拟它。或者,VFP硬件可能被禁用,并且您引发异常以启用它,然后重新执行指令。
    此类模拟器通过未定义指令向量进行调用。它们检查导致异常的指令操作码,并确定要采取什么行动(例如,在软件中执行适当的浮点运算)。在某些情况下,此类处理程序可能需要串联在一起(例如,可能需要模拟多个协处理器)。
    如果没有使用未定义或协处理器指令的软件,则必须记录适当的调试信息,并终止因意外事件而失败的应用程序。
    在某些情况下,未定义指令异常的另一个用途是实现用户断点,有关断点的更多信息,请参阅《Cortex-A7编程手册》的第24章—调试。

  • SVC异常处理( SVC exception handling)
    SVC(Supervisor Call,管理员调用)通常用于使用户模式的代码能够访问操作系统的功能。例如,如果用户代码想要访问系统的特权部分(例如执行文件I/O),则通常会使用SVC指令来实现这一点。参数可以通过寄存器或(不太常见)使用操作码内的注释字段传递给SVC handler。
    异常处理程序可能需要确定当异常发生的时候,核心是处于ARM状态还是Thumb状态。特别是SVC handler可能需要读取指令集状态,这是通过检查SPSR的T位来完成的。在Thumb状态下,该位被设置;而在ARM状态下,该位被清除。
    ARM和THUMB指令集里面都有SVC指令,当在THUMB状态下调用SVC时,需要考虑下面几点问题:
    1.返回地址为LR-2,而不是LR-4
    2.指令是16位的,需要使用半字加载
    3.THUMB状态下,SVC号是8位的;ARM状态下则是24位的。

Linux内核里面利用SVC指令实现系统调用的方式如下:

_start:
	MOV R0, #1 @ STDOUT
	ADR R1, msgtext @ Address
	MOV R2, #13 @ Length
	MOV R7, #4 @ sys_write
	SVC #0
	....
.align 2
msgtxt:
	.asciz "Hello World\n"

SVC #0指令让ARM核发生SVC异常,藉此访问内核函数。R7寄存器里面的值定义了想要访问的内核函数(示例中是sys_write),其它的参数也通过寄存器传递(比如R0放要写入的目的地址,R1存放数据源地址,R2则指定数据长度。

Linux处理异常的流程( Linux exception program flow)

Linux使用一种跨平台的异常处理框架,在处理异常时不区分不同的核心特权模式。因此,ARM实现使用异常句柄存根来使Linux内核能够在SVC模式下处理所有异常。除了SVC和FIQ之外的所有异常都使用句柄存根切换到SVC模式,然后在SVC异常里面调用正确的异常处理程序。

  • 启动过程(Boot process)
    在Arm Linux启动过程中,内核会分配一个4KB的向量页。它被映射到异常向量的位置,即虚拟地址0xFFFF0000或0x00000000。这一操作是通过arch/arm/mm/mmu.c文件中的devicemaps_init()函数完成的,它在ARM系统启动的早期阶段就被调用。之后,在arch/arm/kernel/traps.c中trap_init()函数将异常向量表、异常存根和kuser辅助函数复制到向量页中。显然,异常向量表必须被复制到向量页的开头,异常存根被复制到地址0x200,kuser辅助函数则被复制到页面的顶部(即0x1000 - kuser_sz的位置)。这些操作通过一系列的memcpy()函数来完成,如下所示:
unsigned long vectors = CONFIG_VECTORS_BASE; 
memcpy((void *)vectors, __vectors_start, __vectors_end - __vectors_start);
memcpy((void *)vectors + 0x200, __stubs_start, __stubs_end - __stubs_start); 
memcpy((void *)vectors + 0x1000 - kuser_sz, __kuser_helper_start, kuser_sz);

当复制完成后,内核异常句柄(内核异常处理程序)便处于其运行时的动态状态,准备好处理异常。

  • 中断调度(Interrrupt dispatch)

存在两种不同的中断处理程序:__irq_usr 和 __irq_svc。它们都会保存所有核心寄存器,并使用宏 get_irqnr_and_base 来指示是否有待处理的中断。中断处理程序会围绕这段代码循环,直到没有剩余的中断为止。如果存在中断,代码会跳转到 do_IRQ 函数,该函数存在于 arch/arm/kernel/irq.c 文件中。

解释:
这里的 __irq_usr 和 __irq_svc 是用于处理不同类型中断的特定函数。它们分别对应在用户模式和SVC(Supervisor Call)模式下发生的中断。当发生中断时,这些处理程序会首先保存当前的状态(即寄存器内容),以便中断处理完毕后能正确恢复执行。然后,它们通过调用 get_irqnr_and_base 宏来检查是否有待处理的中断,并获取相关的中断号和中断基地址。
一旦确定了有中断需要处理,处理程序会调用 do_IRQ 函数。这个函数是ARM架构中处理中断的核心部分,它负责根据中断号找到相应的中断处理程序,并执行相应的中断服务程序(Interrupt Service Routine, ISR)。这样,系统就能对发生的中断做出响应,并执行相应的处理逻辑。
总之,__irq_usr 和 __irq_svc 是中断处理流程中的两个关键步骤,它们负责保存状态、检查中断并调用 do_IRQ 来处理实际的中断事件。

到这里,代码在所有架构中都是相同的,并且你会调用一个用C语言编写的适当处理程序。

然而,还有另一点需要考虑。当中断完成时,你通常需要检查处理程序是否执行了需要调用内核调度器的操作。如果调度器决定切换到另一个线程,那么最初被中断的线程会保持休眠状态,直到再次被选中运行。

  • 16
    点赞
  • 17
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值