当前位置: 首页 > news >正文

SylixOS 下的中断嵌套

本文以 ARMv7 架构为例,讲解 SylixOS 操作系统下的中断嵌套。
本篇文章阅读的前提,需要读者对 ARM 架构有一定的了解

建议先阅读:ARM 学习笔记(一)

文章部分摘自:这可能是最通俗易懂的方式讲解ARM中断原理以及中断嵌套

1、概念

1.1 网络上的概念

  中断嵌套指中断系统正在执行一个中断服务 L 时,有另一个优先级更高的中断 H 触发,这时中断系统会暂时中止当前正在执行低优先级中断服务程序 L,而去处理高级别中断 H,待处理完毕,再返回被中断了的中断服务程序 L 继续执行。

  所谓中断嵌套,就是中断抢占机制,允许高优先级中断源抢占正在执行的低优先级中断。

上面这种大的宽的话,在互联网上很常见。只能模糊的理解,一旦找工作、面试被问到了相关问题,就是尴尬

1.2 更详细的概念

  我们还是以 ARM 架构为例,ARM 有七种模式,我们这里只讨论 SVC、IRQ 和 FIQ 模式。我们可以假设 ARM 核心有两根中断引脚(实际上是看不见的),一根叫 irq pin, 一根叫 fiq pin。在 ARM 的 cpsr 中,有一个 I 位和一个 F 位,分别用来禁止 IRQ 和 FIQ。

  先不说中断控制器,只说 ARM 核心。正常情况下,ARM 核都只是机械地随着 pc 的指示去做事情,当 CPSR 中的 I 和 F 位为 1 时,IRQ 和 FIQ 全部处于禁止状态。无论你在 irq pin 和 fiq pin上面发什么样的中断信号,ARM 不会理你,你根本不能打断它,因为它“耳聋”,"眼瞎"了。

  当 I 位和 F 位为 0 时,irq pin上有中断信号过来时,就会打断 ARM 的当前工作,并且切换到 IRQ 模式下,跳到相应的异常向量表(vector)位置去执行代码。这个过程是自动的,但是返回到被中断打断的地方就得您亲自动手。

  当你跳到异常向量表,处于 IRQ 的模式的时候,此时如果 irq pin上面又来中断信号,此时 ARM 是不会理你的,irq pin 就像秘书,ARM 核心就像老板,老板本来在做事,然后来了一个客户,秘书打断它,让客户进去。而此时再来一个客户,要么秘书不断去敲门问,要么客户走人。老板第一个客户没有会见完,不会理你。 但是有一种情况例外,当 ARM 处在 IRQ 模式,这个时候 fiq pin 来了一个中断信号,fiq pin 是什么?快速中断,好比公安局的来查刑事案件,才不管老板是不是在会见客户,直接打断,进入到fiq 模式,跳到相应的 fiq 的异常向量表处去执行代码。那如果当 ARM 处理 FIQ 模式,fiq pin 又来中断信号,也就是又一批公安来了,那没戏,都是执法人员,你打不断我。如果此时 irq pin 来了呢?来了也不理,正在办案,还敢来妨碍公务。 所以得出一个结论: IRQ 模式只能被 FIQ 模式打断,FIQ 模式下谁也打不断。 在打不断的情况下,irq pin 或 fiq pin 随便你怎么发中断信号,都是白发。 所以除了 fiq 能打断 irq 以外,根本没有所谓中断嵌套的情况。

  看到这里,你会不会有疑问,上面得出的结论,怎么和 1.1 章节有冲突?

  实际上,众多的 RTOS,为了达到中断嵌套的目的,通常会在软件上,进入 IRQ 中断处理后,手动将当前核的中断打开,也就是置 CPSR 的 I 位为 0。这时,就会存在 IRQ 打断 IRQ 的情况了

  其实对于 ARM CPU 核来说,它是不知道所谓的中断优先级、中断号的概念的。CPU 会做的事,只是 “irq pin上有中断信号过来时,就会打断 ARM 的当前工作,并且切换到 IRQ 模式下,跳到相应的异常向量表(vector)位置去执行代码”。对于中断号、中断优先级的概念,完全是由中断控制器决定的。关于中断控制器,后面会单独出一个章节详解。

1.3 中断嵌套的现状

  现在很多的操作系统,通常都不会使用中断嵌套(虽然支持,但是很少使用)。例如 Linux 内核,从某一个版本开始,就不再支持中断嵌套,给出的理由是,为了防止栈溢出。

kernel/git/torvalds/linux.git - Linux kernel source tree

在这里插入图片描述
  我们在使用中断嵌套时,要考虑到中断嵌套实现的复杂度、多级中断上下文切换带来的时间开销、栈溢出问题等。

  很多 RTOS 实现的抢占式内核,也就是抢占式调度,已经可以满足实时性要求。例如:快速中断上下文 + 软中断(softirq)/tasklet 工作机制,完全可以替代中断嵌套带来的优势

  • 硬中断中只做最小处理(例如:确认、屏蔽、记录)
  • 剩下的耗时处理在软中断或工作队列中完成
  • 由优先级调度器来完成对高优先级任务的响应、处理

  以上这些,大大降低了操作系统对中断嵌套依赖。

2、实现

  下面以 SylixOS 操作系统为例,讲解中断嵌套的实现。完整的代码放在最后,本章节进行详细的代码拆解。

2.1 中断上下文保存

;/*********************************************************************************************************
;  中断入口
;*********************************************************************************************************/FUNC_DEF(archIntEntry)SUB     LR , LR, #4                                                 ;/*  调整用于中断返回的 PC 值    */STMFD   SP!, {LR}                                                   ;/*  保存返回地址                */STMFD   SP!, {R0-R12}                                               ;/*  保存寄存器                  */MOV     R1 , SPMSR     CPSR_c, #(DIS_INT | SYS32_MODE)                             ;/*  回到 SYS 模式               */STMFD   R1!, {SP}                                                   ;/*  保存 SP_sys                 */STMFD   R1 , {LR}                                                   ;/*  保存 LR_sys                 */MSR     CPSR_c, #(DIS_INT | IRQ32_MODE)                             ;/*  回到 IRQ 模式               */SUB     SP , SP , #(2 * 4)                                          ;/*  调整 SP_irq                 */MRS     R2 , SPSRSTMFD   SP!, {R2}                                                   ;/*  保存 CPSR_sys               */

  注意,这里为什么要保存 SP_sys 和 LR_sys? 首先有一个大的前提,要知道,SP 和 LR 这两个寄存器在每种模式下都是独立的寄存器,决定着当前模式正在使用的一个栈空间。

  因为中断处理函数通常在 SYS 模式运行。而在进行中断处理的过程中,势必会破坏之前的栈。2.3 章节中,详细的提到了,切换 SYS 模式栈的工作。所以这里提前保存,所有中断处理结束后,再去恢复。

  即便是中断嵌套的情况下,也是需要保存 SP_sys 和 LR_sys。因为当第一个中断正在处理的过程中,第二个中断来了,所以需要保存上一次中断处理的现场,包括栈信息。

以上代码执行结束,IRQ 模式栈情况如下:
在这里插入图片描述

2.2 中断上下文拷贝

    ;/*; * API_InterEnter(SP_irq), 如果是第一次中断, 会将 IRQ 模式栈空间的 ARCH_REG_CTX; * 拷贝到当前任务 TCB 的 ARCH_REG_CTX 里; */MOV     R0 , SPLDR     R1 , =API_InterEnterMOV     LR , PCBX      R1

  API_InterEnter 函数实现的主要功能是,将刚刚保存到 IQR 模式栈空间的这些中断上下文ARCH_REG_CTX,再重新保存一遍到当前任务 TCB 中。当所有中断处理结束时,需要恢复中断上下文时,使用的就是 TCB 中保存的中断上下文。

  需要注意的是,仅仅是第一次进入中断时,才把 IQR 模式栈空间的这些上下文,保存到当前任务 TCB 中!!!

VOID  archIntCtxSaveReg (PLW_CLASS_CPU  pcpu,ARCH_REG_T     reg0,ARCH_REG_T     reg1,ARCH_REG_T     reg2,ARCH_REG_T     reg3)
{if (pcpu->CPU_ulInterNesting == 1) {archTaskCtxCopy(&pcpu->CPU_ptcbTCBCur->TCB_archRegCtx, (ARCH_REG_CTX *)reg0);}
}LW_API
ULONG    API_InterEnter (ARCH_REG_T  reg0,ARCH_REG_T  reg1,ARCH_REG_T  reg2,ARCH_REG_T  reg3)
{PLW_CLASS_CPU  pcpu;pcpu = LW_CPU_GET_CUR();pcpu->CPU_ulInterNesting++;#if !defined(__SYLIXOS_ARM_ARCH_M__) || (LW_CFG_CORTEX_M_SVC_SWITCH > 0)archIntCtxSaveReg(pcpu, reg0, reg1, reg2, reg3);
#endif......return  (pcpu->CPU_ulInterNesting);
}

这里实际上使用的是 IRQ 模式的栈。

;/*********************************************************************************************************
;  拷贝任务上下文
;  参数 R0 为目的 ARCH_REG_CTX 指针, R1 为源 ARCH_REG_CTX 指针
;*********************************************************************************************************/FUNC_DEF(archTaskCtxCopy)STMFD   SP! , {R4-R10}   //寄存器 r4~r10 共 7 个寄存器,存储到 SP 指向的栈中,相当于入栈LDMIA   R1! , {R2-R10}	 //从 R1 指向的内存地址开始,依次将内容加载到 R2–R10STMIA   R0! , {R2-R10}   //将刚才加载的寄存器值存储到 R0 指向的目标地址,并写回更新 R0LDMIA   R1! , {R2-R9}    //从 R1 当前地址(已偏移 36 字节)继续读取 8 个寄存器值(R2–R9)STMIA   R0! , {R2-R9}    //然后写入到 R0 当前地址(也偏移了36字节)LDMFD   SP! , {R4-R10}	 //SP 指向的栈中,恢复到 r4~r10 共 7 个寄存器,相当于出栈BX      LRFUNC_END()FILE_END()

2.3 清栈 & 调整 SYS 模式栈

    ;/*; * 如果不是第一次进入中断, 那么上一次中断(工作在 SYS 模式)已经设置 SP_sys, 只需要回到 SYS 模式; */CMP     R0 , #1BNE     1f;/*; * 第一次进入中断: 因为已经将 IRQ 模式栈空间的 ARCH_REG_CTX 拷贝到当前任务 TCB 的 ARCH_REG_CTX 里; * 调整 SP_irq; */ADD     SP , SP , #(ARCH_REG_CTX_SIZE);/*; * 第一次进入中断: 获得当前 CPU 中断堆栈栈顶, 并回到 SYS 模式, 并设置 SP_sys; */LDR     R0 , =API_InterStackBaseGetMOV     LR , PCBX      R0MSR     CPSR_c, #(DIS_INT | SYS32_MODE)                             ;/*  回到 SYS 模式               */MOV     SP , R0                                                     ;/*  设置 SP_sys                 */

  这里注意,在第一次进入中断的情况下,因为上面已经把 ARCH_REG_CTX 上下文保存到 TCB 中,所以 IRQ 模式栈中的上下文数据实际上已经不需要了,所以修改 IRQ 模式的 SP 指针(可以理解为清栈)。同时,为了后面的中断处理函数 bspIntHandle —— 这样一个 C 函数能够顺利执行,需要重新给 SYS 模式切换栈空间。

这里给 SYS 模式切换的栈,是操作系统分配的栈空间。为什么不直接使用 SYS 模式下的异常栈?
因为系统刚上电后执行的初始化操作,设置 ARM 异常栈时,通常不会设置的很大。但是这里需要调用一个 C 函数去做中断处理,可能会有多级调用、需要很大的栈空间

  在上面的操作过后, IRQ 模式栈空间情况如下:
在这里插入图片描述
  而对于非第一次进入中断、也就是中断嵌套的情况下,则无需切换 SYS 模式的栈空间。同时,也不能修改 IRQ 模式的 SP。因为中断嵌套情况下,是不会保存 IRQ 上下文到当前 TCB 中的。

2.4 中断处理

1:MSR     CPSR_c, #(DIS_INT | SYS32_MODE)                             ;/*  回到 SYS 模式(不是多余的)   */;/*; * bspIntHandle(),中断处理过程; */LDR     R1 , =bspIntHandleMOV     LR , PCBX      R1;/*; * API_InterExit(); * 如果没有发生中断嵌套, 则 API_InterExit 会调用 archIntCtxLoad 函数, SP_irq 在上面已经调整好; */LDR     R1 , =API_InterExitMOV     LR , PCBX      R1

  这里要注意,实际上中断处理的过程,是在 SYS 模式操作的,并不是 IRQ 模式。bspIntHandle 最终会调用到 archIntHandle

/*********************************************************************************************************
** 函数名称: archIntHandle
** 功能描述: bspIntHandle 需要调用此函数处理中断 (关闭中断情况被调用)
** 输 入  : ulVector         中断向量
**           bPreemptive      中断是否可抢占
** 输 出  : NONE
** 全局变量: 
** 调用模块: 
** 注  意  : 此函数退出时必须为中断关闭状态.
*********************************************************************************************************/
LW_WEAK VOID  archIntHandle (ULONG  ulVector, BOOL  bPreemptive)
{
......if (bPreemptive) {VECTOR_OP_LOCK();__ARCH_INT_VECTOR_DISABLE(ulVector);                            /*  屏蔽 vector 中断            */VECTOR_OP_UNLOCK();KN_INT_ENABLE_FORCE();                                          /*  允许中断                    */}irqret = API_InterVectorIsr(ulVector);                              /*  调用中断服务程序            */
......
}

  由上面代码可以看出,为了达到中断嵌套的目的,在刚进 archIntHandle 函数时,就需要调用 KN_INT_ENABLE_FORCE 强制打开当前核的中断,否则当前核无法产生 IRQ 中断。

  而 API_InterExit 函数中,如果是第一次进入中断,则会进行中断上下文切换;如果不是,则直接返回;

  关于中断上下文切换,请看:SylixOS armv7 任务切换

LW_API
VOID    API_InterExit (VOID)
{
......if (pcpu->CPU_ulInterNesting) {                                     /*  查看系统是否在中断嵌套中    */     return;                                                         /*  LW_CFG_INTER_DSP > 0     */}......#if !defined(__SYLIXOS_ARM_ARCH_M__) || (LW_CFG_CORTEX_M_SVC_SWITCH > 0)archIntCtxLoad(pcpu);                                               /*  中断返回 (当前任务 CTX 加载)*/
#endif
}

2.5 中断嵌套的处理

    ;/*; * 来到这里, 说明发生了中断嵌套; */MSR     CPSR_c, #(DIS_INT | IRQ32_MODE)                             ;/*  回到 IRQ 模式               */MOV     R0 , SPLDMIA   R0!, {R2-R4}                                                ;/*  读取 CPSR LR SP             */ADD     SP , SP , #(ARCH_REG_CTX_SIZE)                              ;/*  调整 SP_irq                 */MSR     CPSR_c, #(DIS_INT | SYS32_MODE)                             ;/*  回到 SYS 模式               */MOV     SP , R4                                                     ;/*  恢复 SP_sys                 */MOV     LR , R3                                                     ;/*  恢复 LR_sys                 */MSR     CPSR_c, #(DIS_INT | IRQ32_MODE)                             ;/*  回到 IRQ 模式               */MSR     SPSR_cxsf , R2LDMIA   R0 , {R0-R12, PC}^                                          ;/*  恢复包括 PC 的所有寄存器,   */;/*  同时更新 CPSR               */FUNC_END()

  如果发生了中断嵌套,且当前中断已经处理完毕,则可以返回到上一层的中断现场。即恢复 IRQ 栈中保存的 ARCH_REG_CTX 上下文,这其中,包括 PC。

3、完整的代码片段

;/*********************************************************************************************************
;  中断入口
;*********************************************************************************************************/FUNC_DEF(archIntEntry)SUB     LR , LR, #4                                                 ;/*  调整用于中断返回的 PC 值    */STMFD   SP!, {LR}                                                   ;/*  保存返回地址                */STMFD   SP!, {R0-R12}                                               ;/*  保存寄存器                  */MOV     R1 , SPMSR     CPSR_c, #(DIS_INT | SYS32_MODE)                             ;/*  回到 SYS 模式               */STMFD   R1!, {SP}                                                   ;/*  保存 SP_sys                 */STMFD   R1 , {LR}                                                   ;/*  保存 LR_sys                 */MSR     CPSR_c, #(DIS_INT | IRQ32_MODE)                             ;/*  回到 IRQ 模式               */SUB     SP , SP , #(2 * 4)                                          ;/*  调整 SP_irq                 */MRS     R2 , SPSRSTMFD   SP!, {R2}                                                   ;/*  保存 CPSR_sys               */;/*; * API_InterEnter(SP_irq), 如果是第一次中断, 会将 IRQ 模式栈空间的 ARCH_REG_CTX; * 拷贝到当前任务 TCB 的 ARCH_REG_CTX 里; */MOV     R0 , SPLDR     R1 , =API_InterEnterMOV     LR , PCBX      R1;/*; * 如果不是第一次进入中断, 那么上一次中断(工作在 SYS 模式)已经设置 SP_sys, 只需要回到 SYS 模式; */CMP     R0 , #1BNE     1f;/*; * 第一次进入中断: 因为已经将 IRQ 模式栈空间的 ARCH_REG_CTX 拷贝到当前任务 TCB 的 ARCH_REG_CTX 里; * 调整 SP_irq; */ADD     SP , SP , #(ARCH_REG_CTX_SIZE);/*; * 第一次进入中断: 获得当前 CPU 中断堆栈栈顶, 并回到 SYS 模式, 并设置 SP_sys; */LDR     R0 , =API_InterStackBaseGetMOV     LR , PCBX      R0MSR     CPSR_c, #(DIS_INT | SYS32_MODE)                             ;/*  回到 SYS 模式               */MOV     SP , R0                                                     ;/*  设置 SP_sys                 */1:MSR     CPSR_c, #(DIS_INT | SYS32_MODE)                             ;/*  回到 SYS 模式(不是多余的)   */;/*; * bspIntHandle(); */LDR     R1 , =bspIntHandleMOV     LR , PCBX      R1;/*; * API_InterExit(); * 如果没有发生中断嵌套, 则 API_InterExit 会调用 archIntCtxLoad 函数, SP_irq 在上面已经调整好; */LDR     R1 , =API_InterExitMOV     LR , PCBX      R1;/*; * 来到这里, 说明发生了中断嵌套; */MSR     CPSR_c, #(DIS_INT | IRQ32_MODE)                             ;/*  回到 IRQ 模式               */MOV     R0 , SPLDMIA   R0!, {R2-R4}                                                ;/*  读取 CPSR LR SP             */ADD     SP , SP , #(ARCH_REG_CTX_SIZE)                              ;/*  调整 SP_irq                 */MSR     CPSR_c, #(DIS_INT | SYS32_MODE)                             ;/*  回到 SYS 模式               */MOV     SP , R4                                                     ;/*  恢复 SP_sys                 */MOV     LR , R3                                                     ;/*  恢复 LR_sys                 */MSR     CPSR_c, #(DIS_INT | IRQ32_MODE)                             ;/*  回到 IRQ 模式               */MSR     SPSR_cxsf , R2LDMIA   R0 , {R0-R12, PC}^                                          ;/*  恢复包括 PC 的所有寄存器,   */;/*  同时更新 CPSR               */FUNC_END()
http://www.xdnf.cn/news/1116937.html

相关文章:

  • Android自定义View的事件分发流程
  • python的平安驾校管理系统
  • html案例:编写一个用于发布CSDN文章时,生成有关缩略图
  • 嵌入式固件 .pkg 打包流程
  • 深度学习图像分类数据集—宠物四种表情识别分类
  • 学习python调用WebApi的基本用法(2)
  • k8s存储入门
  • 基于Leaflet调用天地图在线API的多层级地名检索实战
  • 深度学习16(对抗生成网络:GAN+自动编码器)
  • 跨网络连接不同机器上的虚拟机
  • UNet改进(22):融合CNN与Transformer的医学图像分割新架构
  • 15. JVM调优的参数设置
  • [Linux 入门] Linux 引导过程、系统管理与故障处理全解析
  • word设置多级标题
  • Cursor的使用
  • docker容器高级管理-dockerfile创建镜像
  • 树莓派5-ollama-linux-arm64.tgz 下载
  • OkHttp SSE 完整总结(最终版)
  • cuda编程笔记(7)--多GPU上的CUDA
  • 敦煌藻井配色:姜黄×钴蓝的东方色彩应用手册
  • CVE-2022-0609
  • 用信号量实现进程互斥,进程同步,进程前驱关系(操作系统os)
  • hercules zos 安裝 jdk 8
  • CTFSHOW pwn161 WP
  • 整流电路Multisim电路仿真实验汇总——硬件工程师笔记
  • 使用macvlan实现容器的跨主机通信
  • KL散度:信息差异的量化标尺 | 从概率分布对齐到模型优化的核心度量
  • C++高频知识点(十一)
  • ALB、NLB、CLB 负载均衡深度剖析
  • 开源工具DeepFilterNet:实时语音降噪