1. 整体架构概览

SylixOS 中断子系统由三层构成,各层职责清晰分离:

BSP移植层

内核层

Arch适配层

硬件层

IRQ异常

向量号

查表

写入

初始化

控制

ARM CPU
异常向量地址 0x00-0x1C

SoC 中断控制器
具体由 BSP 移植实现

archIntEntry
汇编入口, 保存现场

archIntHandle
抢占/屏蔽管理

_K_idescTable
全局中断描述符表

API_InterVectorIsr
遍历链表调ISR

API_InterVectorConnect
注册ISR到表

bspIntInit
控制器硬件初始化

bspIntHandle
读向量号+EOI

bspIntVectorEnable/Disable
向量开关

核心设计思想: 以全局中断描述符表 _K_idescTable[] 为中心,Connect 写入映射,Isr 查表分发,BSP 负责硬件交互。内核与硬件完全解耦。

2. 异常向量表:硬件入口

2.1 ARM 异常向量地址是硬件固定的

ARM CPU 在异常/中断发生时,硬件自动完成:

  • 保存返回地址到 LR_<mode>,CPSR 到 SPSR_<mode>
  • 切换到对应特权模式(如 IRQ 模式)
  • 置位 CPSR 的 I 位(屏蔽全局 IRQ) – 关中断在这里就完成了
  • 把 PC 设到固定的向量地址

每种异常有固定偏移(低向量模式,V=0 时从 0x00000000 开始):

偏移异常类型说明
0x00Reset上电复位
0x04Undefined instruction未定义指令
0x08SWI (SVC)软件中断,系统调用
0x0CPrefetch abort取指中止
0x10Data abort数据中止
0x14Reserved保留
0x18IRQ普通中断
0x1CFIQ快速中断

2.2 向量表的内容:LDR PC 二次跳转

向量表在 startup.S 中定义:

SECTION(.vector)

FUNC_DEF(vector)
    LDR     PC, resetEntry          /* 0x00: Reset      */
    LDR     PC, undefineEntry       /* 0x04: Undefined  */
    LDR     PC, swiEntry            /* 0x08: SWI        */
    LDR     PC, prefetchEntry       /* 0x0C: Prefetch   */
    LDR     PC, abortEntry          /* 0x10: Data abort */
    LDR     PC, reserveEntry        /* 0x14: Reserved   */
    LDR     PC, irqEntry            /* 0x18: IRQ        */
    LDR     PC, fiqEntry            /* 0x1C: FIQ        */

FUNC_LABEL(irqEntry)
    .word   archIntEntry            /* IRQ -> archIntEntry */

每个向量槽是一条 LDR PC, label 指令,从紧跟其后的 literal pool 加载真正的处理函数地址写入 PC。

2.3 .vector** 如何落到 **0x00000000

链接脚本
.vector 放镜像最开头

镜像链接在
RAM基址

启动代码清CP15 V位
选用低向量0x0

SoC地址别名
0x0 映射到RAM基址

CPU异常时PC=0x0
取到LDR PC指令

三步配合:

  1. 链接脚本把 .vector 段放在 .text 最开头(镜像偏移 0)
  2. 启动代码清 CP15 控制寄存器的 V 位(bit 13),选用低向量模式(异常从 0x00000000 取指)
  3. SoC 硬件把地址 0x00000000 别名到 RAM 基址,使向量表在 0x0 可见

3. 中断初始化流程

3.1 完整初始化链路

从上电到中断系统就绪,经历以下阶段:

上电 reset
startup.S

CPU初始化
清V位/设各模式栈

.vector在0x0就位
异常入口可用

bspInit

API_KernelStart
KernelStart.c

_KernelPrimaryLowLevelInit
内核底层初始化

_InterVectInit
初始化软件向量表

bspIntInit
初始化中断控制器硬件

_KernelHighLevelInit
内核高级初始化

_KernelPrimaryEntry
启动调度器

驱动加载
module_init

3.2 软件层初始化:_InterVectInit

在 _KernelLowLevelInit.c 中调用:

_InterVectInit();    /* 初始化中断向量表 */

作用:将内核全局中断描述符表 _K_idescTable[](k_globalvar.h)清零初始化。每个向量号对应的 LW_CLASS_INTDESC 结构体的 IDESC_plineAction 链表头、自旋锁、标志位等全部归零。
此时表是空的,所有向量都没有注册 ISR。

3.3 硬件层初始化:bspIntInit

紧接着在 KernelStart.c 中调用:

bspIntInit();    /* 初始化中断系统 */

作用:由 BSP 实现具体的中断控制器硬件初始化(屏蔽所有中断源、设置优先级、配置触发方式等)。

3.4 为什么"先软件后硬件"

_InterVectInit()  ->  软件表建好(空表, 不会误调ISR)
bspIntInit()      ->  硬件控制器初始化(中断全屏蔽)

保证在硬件有能力送中断之前,软件分发结构已就绪。即使有中断漏过来,API_InterVectorIsr 查空表也只会打印 “no service”,不会访问野指针。

3.5 后续:驱动注册与使能

内核启动后,驱动在 module_init 中完成中断注册:

/* 1. 注册:把 ISR 写入软件表 */
API_InterVectorConnect(ulVector, pfuncIsr, pvArg, "my_irq");

/* 2. 使能:在硬件层面打开该向量 */
API_InterVectorEnable(ulVector);

两步缺一不可:Connect 让内核"认识"这个中断,Enable 让硬件"放行"这个中断。

4. 中断向量注册流程(Connect)

4.1 调用示例

以 BSP 注册 tick 定时器中断为例:

API_InterVectorConnect(timer0.inum,                       /* ulVector: 中断向量号 (键)     */
                       (PINT_SVR_ROUTINE)__tickTimerIsr,  /* pfuncIsr:  ISR 函数指针       */
                       &timer0,                           /* pvArg:     传给 ISR 的参数    */
                       "tick_timer");                     /* pcName:    中断名称           */

4.2 内部逻辑:写入_K_idescTable

API_InterVectorConnect 的核心逻辑在 InterVectorConnect.c 中,将参数组装为 LW_CLASS_INTACT 节点,按两种模式挂入链表:

pidesc = LW_IVEC_GET_IDESC(ulVector);            /* 取向量号对应的描述符 */

if (pidesc->IDESC_ulFlag & LW_IRQ_FLAG_QUEUE) {  /* 队列模式(多服务) */
    for (plineTemp  = pidesc->IDESC_plineAction; /* 遍历查重 */
         plineTemp != LW_NULL;
         plineTemp  = _list_line_get_next(plineTemp)) {
        piactionOld = _LIST_ENTRY(plineTemp, LW_CLASS_INTACT, IACT_plineManage);
        if ((piactionOld->IACT_pfuncIsr == pfuncIsr) &&
            (piactionOld->IACT_pvArg    == pvArg)) {
            break;                                 /* 已存在, 不重复安装 */
        }
    }
    if (plineTemp) {
        bNeedFree = LW_TRUE;                       /* 重复, 释放新节点 */
    } else {
        _List_Line_Add_Ahead(&piaction->IACT_plineManage,
                             &pidesc->IDESC_plineAction);  /* 挂到链表头 */
        bNeedFree = LW_FALSE;
    }

} else {                                           /* 非队列模式(单服务) */
    if (pidesc->IDESC_plineAction) {               /* 已有节点 -> 覆盖 */
        piactionOld = _LIST_ENTRY(pidesc->IDESC_plineAction,
                                  LW_CLASS_INTACT, IACT_plineManage);
        piactionOld->IACT_pfuncIsr   = piaction->IACT_pfuncIsr;
        piactionOld->IACT_pfuncClear = piaction->IACT_pfuncClear;
        piactionOld->IACT_pvArg      = piaction->IACT_pvArg;
        lib_strcpy(piactionOld->IACT_cInterName, piaction->IACT_cInterName);
        bNeedFree = LW_TRUE;
    } else {                                       /* 无节点 -> 新建 */
        _List_Line_Add_Ahead(&piaction->IACT_plineManage,
                             &pidesc->IDESC_plineAction);
        bNeedFree = LW_FALSE;
    }
}

4.3 两种注册模式对比

模式标志行为适用场景
队列(多服务)LW_IRQ_FLAG_QUEUE多个 ISR 可挂同一向量,查重后追加到链表多设备共享同一中断线
非队列(单服务)默认只保留一个 ISR,重复注册则覆盖独占中断线

队列模式需在 bspIntInit 中提前设置:

API_InterVectorSetFlag(LW_IRQ_4, LW_IRQ_FLAG_QUEUE);

4.4 映射关系存储位置

_K_idescTable[ulVector]                    (全局中断描述符表, k_globalvar.h)
  └─ IDESC_plineAction (链表头)
       └─ LW_CLASS_INTACT 节点
            ├─ IACT_pfuncIsr    = ISR 函数指针
            ├─ IACT_pvArg       = 传给 ISR 的参数
            ├─ IACT_pfuncClear  = 中断清除函数
            ├─ IACT_cInterName  = 中断名称字符串
            └─ IACT_plineManage = 链表节点

5. 中断触发与分发流程

5.1 完整调用链

驱动 ISRAPI_InterVectorIsr(InterVectorIsr.c)archIntHandle(armExc.c)bspIntHandle(BSP)archIntEntry(armExcAsm.S)向量表 startup.SARM 硬件驱动 ISRAPI_InterVectorIsr(InterVectorIsr.c)archIntHandle(armExc.c)bspIntHandle(BSP)archIntEntry(armExcAsm.S)向量表 startup.SARM 硬件alt[队列模式(多服务)][非队列模式(单服务)]alt[irqret ==LW_IRQ_HAN-DLED_DISV][可抢占 +正常返回]外设中断信号到达保存LR/CPSR, 切IRQ模式,置CPSR.I(关中断), PC=0x18LDR PC, irqEntry ->> archIntEntry保存现场, 切中断栈,每次MSR带DIS_INT(保持关中断)调用 bspIntHandle()读中断控制器得到向量号archIntHandle(ulVector, LW_FALSE)校验向量号合法性(可抢占时) 屏蔽本向量+开全局中断API_InterVectorIsr(ulVector)LW_IVEC_GET_IDESC(ulVector) 取描述符遍历链表, 逐个调用返回 irqret只调第一个返回 irqret返回 irqretKN_INT_DISABLE(关回全局中断)屏蔽本向量(ISR要求)重新使能本向量返回EOI应答返回恢复现场LDMIA {R0-R12,PC}^ (恢复现场+PC+CPSR, 返回被中断处)

5.2 关键环节

5.2.1 关中断是硬件自动的

bspIntHandle 被调用时全局中断已关闭,不需要软件再关。关中断由两层完成:

  • ARM 硬件:进 IRQ 异常时自动置 CPSR.I 位
  • archIntEntry 汇编:每次切模式的 MSR CPSR_c 都带 DIS_INT

所以 armExc.c 的注释"关闭中断情况被调用"是描述调用前提条件,不是 bspIntHandle 的动作。

5.2.2 可抢占中断的嵌套机制

if (bPreemptive) {
    VECTOR_OP_LOCK();
    __ARCH_INT_VECTOR_DISABLE(ulVector);    /* 1. 先屏蔽当前向量, 防止自重入 */
    VECTOR_OP_UNLOCK();
    KN_INT_ENABLE_FORCE();                  /* 2. 再开全局中断, 允许高优先级抢占 */
}

irqret = API_InterVectorIsr(ulVector);      /* 3. 执行 ISR */

KN_INT_DISABLE();                           /* 4. 关回全局中断 */

/* 5. 按返回值决定向量去留 */
if (bPreemptive && irqret != LW_IRQ_HANDLED_DISV) {
    __ARCH_INT_VECTOR_ENABLE(ulVector);     /* 正常: 重新使能 */
} else if (irqret == LW_IRQ_HANDLED_DISV) {
    __ARCH_INT_VECTOR_DISABLE(ulVector);    /* ISR要求: 保持屏蔽 */
}

顺序绝不能反: 必须先屏蔽当前向量,再开全局中断,否则同一中断会立刻重入。

5.2.3 ISR 返回值约定

返回值宏含义OS 动作
0LW_IRQ_NONE不是本设备中断继续调下一个(队列模式)
1LW_IRQ_HANDLED已处理正常恢复向量
2LW_IRQ_HANDLED_DISV已处理, 屏蔽本中断保持/补做屏蔽
3LW_IRQ_HANDLED_CONT已处理, 继续循环继续遍历链表

5.2.4 队列 vs 非队列的分发差异

/* API_InterVectorIsr 内部 (InterVectorIsr.c) */
if (pidesc->IDESC_ulFlag & LW_IRQ_FLAG_QUEUE) {
    for (plineTemp = pidesc->IDESC_plineAction; ...; ...) {
        piaction = (PLW_CLASS_INTACT)plineTemp;
        INTER_VECTOR_SVC(break;);           /* 依次调用ISR */
    }
} else {
    piaction = (PLW_CLASS_INTACT)pidesc->IDESC_plineAction;
    if (piaction) {
        INTER_VECTOR_SVC(;);                /* 只调第一个 */
    } else {
        _DebugFormat(..., "no service.\r\n");
    }
}

5.2.5 EOI 应答

bspIntHandle 在 archIntHandle 返回后还需调用中断控制器的 EOI(End of Interrupt),告诉硬件"这个中断处理完了"。少了这步,很多中断控制器不会再发同一个中断。

6. BSP 中断模块移植工作

6.1 需要实现的函数清单

基于空模板 SyslixOSBSP_empty/bspLib.c,BSP 中断移植需要实现以下函数:

SMP专用

条件实现

LW_CFG_INTER_PRIO > 0

LW_CFG_INTER_PRIO > 0

LW_CFG_INTER_TARGET > 0

LW_CFG_INTER_TARGET > 0

建议实现

bspIntVectorIsEnable
查询使能状态

必须实现

bspIntInit
控制器初始化

bspIntHandle
读向量+分发+EOI

bspIntVectorEnable
使能向量

bspIntVectorDisable
禁能向量

bspIntVectorSetPriority
设置优先级

bspIntVectorGetPriority
查询优先级

bspIntVectorSetTarget
设置目标CPU

bspIntVectorGetTarget
查询目标CPU

条件编译

条件编译

6.2 各函数职责与空模板代码

6.2.1 bspIntInit – 中断控制器初始化(必须)

VOID  bspIntInit (VOID)
{
    /*
     * TODO: 加入你的处理代码
     *
     * 如果某中断为链式中断,请加入形如:
     * API_InterVectorSetFlag(LW_IRQ_4, LW_IRQ_FLAG_QUEUE);
     * 的代码.
     *
     * 如果某中断可用作初始化随机化种子,请加入形如:
     * API_InterVectorSetFlag(LW_IRQ_0, LW_IRQ_FLAG_SAMPLE_RAND);
     * 的代码.
     */
}

移植要点:

  • 调用 SoC 中断控制器的初始化函数,完成寄存器配置
  • 屏蔽所有中断源(防止未注册的中断触发)
  • 为共享中断线设置 LW_IRQ_FLAG_QUEUE 标志
  • 可选:为可用作随机种子的中断设置 LW_IRQ_FLAG_SAMPLE_RAND

6.2.2 bspIntHandle – 中断入口(必须)

VOID  bspIntHandle (VOID)
{
    /*
     * TODO: 通过读取硬件寄存器, 得到当前产生的中断向量号, 并赋给 uiVector 变量
     */
    REGISTER UINT32   uiVector = 0;

    archIntHandle((ULONG)uiVector, LW_FALSE);
}

移植要点:

  • 读取中断控制器的向量寄存器,获得当前中断向量号
  • 调用 archIntHandle(ulVector, LW_FALSE) 交给内核分发
  • archIntHandle 返回后需调用 EOI
    完整实现示例:
VOID  bspIntHandle (VOID)
{
    REGISTER UINT32   uiVector = hw_intc_get_vector();   /* 读向量号 */

    archIntHandle((ULONG)uiVector, LW_FALSE);             /* 内核分发 */

    hw_intc_irq_service();                                /* EOI 应答 */
}

注:hw_intc_get_vector() 和 hw_intc_irq_service() 为示意函数名,实际由移植者根据 SoC 中断控制器实现。

6.2.3 bspIntVectorEnable – 使能向量(必须)

VOID  bspIntVectorEnable (ULONG  ulVector)
{
    /*
     * TODO: 通过设置硬件寄存器, 使能指定的中断向量
     */
}

移植要点:

  • 在中断控制器中解除指定向量的屏蔽
  • 内核调 API_InterVectorEnable() 时最终调用此函数
  • 如果此函数为空,驱动调 Enable 后硬件不会真正放行中断

6.2.4 bspIntVectorDisable – 禁能向量(必须)

VOID  bspIntVectorDisable (ULONG  ulVector)
{
    /*
     * TODO: 通过设置硬件寄存器, 禁能指定的中断向量
     */
}

移植要点:

  • 在中断控制器中屏蔽指定向量
  • archIntHandle 的 __ARCH_INT_VECTOR_DISABLE 最终调用此函数
  • 如果此函数为空,可抢占中断的屏蔽机制完全失效

6.2.5 bspIntVectorIsEnable – 查询使能状态(建议)

BOOL  bspIntVectorIsEnable (ULONG  ulVector)
{
    /*
     * TODO: 通过读取硬件寄存器, 检查指定的中断向量是否使能
     */
    return  ((0) ? LW_FALSE : LW_TRUE);
}

移植要点: 读取中断控制器状态寄存器,返回该向量的使能状态。

6.2.6 bspIntVectorSetPriority / GetPriority(条件:LW_CFG_INTER_PRIO > 0)

ULONG   bspIntVectorSetPriority (ULONG  ulVector, UINT  uiPrio)
{
    return  (ERROR_NONE);
}

ULONG   bspIntVectorGetPriority (ULONG  ulVector, UINT  *puiPrio)
{
    *puiPrio = 0;
    return  (ERROR_NONE);
}

移植要点: 如果 SoC 中断控制器支持优先级且配置了 LW_CFG_INTER_PRIO,需实现优先级设置/查询。

6.2.7 bspIntVectorSetTarget / GetTarget(SMP:LW_CFG_INTER_TARGET > 0)

ULONG   bspIntVectorSetTarget (ULONG  ulVector, size_t  stSize,
                               const PLW_CLASS_CPUSET  pcpuset)
{
    return  (ERROR_NONE);
}

移植要点: 仅 SMP 多核场景需要,设置中断路由到哪个 CPU 核。

6.3 移植检查清单

检查项验证方法
bspIntInit 初始化了控制器启动后无中断异常打印
bspIntHandle 正确读取向量号中断能进入正确的 ISR
bspIntHandle 调用了 EOI同一中断能重复触发
bspIntVectorEnable 能打开向量驱动调 Enable 后中断到来
bspIntVectorDisable 能关闭向量驱动调 Disable 后中断停止
异常向量表在 0x0 就位系统能正常启动并响应中断

7. 关键数据结构速查

7.1 全局中断描述符表

/* k_globalvar.h */
__KERNEL_EXT LW_CLASS_INTDESC  _K_idescTable[LW_CFG_MAX_INTER_SRC];

以向量号为下标,每个元素是一个 LW_CLASS_INTDESC,管理该向量号的 ISR 链表。

7.2 三个函数、一张表

_InterVectInit()          ──> _K_idescTable[] 清零建表 (空表)
API_InterVectorConnect()  ──> 往 _K_idescTable[vec] 挂 LW_CLASS_INTACT 节点 (注册)
API_InterVectorIsr()      ──> 遍历 _K_idescTable[vec].IDESC_plineAction 调 ISR (触发)
函数时机对表的操作
_InterVectInit启动时 (_KernelPrimaryLowLevelInit)建空表
API_InterVectorConnect驱动/BSP 注册中断时写入映射(向量号 -> ISR)
API_InterVectorIsr中断发生时 (archIntHandle 调用)查表分发

7.3 ISR 返回值

定义在 k_routine.h:

#define LW_IRQ_NONE         0   /* 不是本设备中断              */
#define LW_IRQ_HANDLED      1   /* 中断已被正确处理            */
#define LW_IRQ_HANDLED_DISV 2   /* 中断处理结束并且屏蔽本中断  */
#define LW_IRQ_HANDLED_CONT 3   /* 中断已被处理, 但需要继续循环*/

7.4 中断服务函数类型

/* k_routine.h */
typedef irqreturn_t (*PINT_SVR_ROUTINE)(PVOID pvArg, ULONG ulVector);
/* pvArg:    Connect 时传入的参数     */
/* ulVector: 中断向量号               */
/* 返回值:   irqreturn_t (见上表)     */

8. 附录:源码定位索引

8.1 异常向量表与入口

文件关键内容
SylixOSBSP_empty/SylixOS/bsp/startup.S向量表定义、reset 入口
libsylixos/SylixOS/arch/arm/common/armExcAsm.SarchIntEntry 汇编入口
libsylixos/SylixOS/arch/arm/common/armExc.carchIntHandle C 分发逻辑

8.2 中断初始化

文件关键内容
libsylixos/SylixOS/kernel/interface/KernelStart.cAPI_KernelStart 调用链
libsylixos/SylixOS/kernel/core/_KernelLowLevelInit.c_InterVectInit 调用点
SylixOSBSP_empty/SylixOS/bsp/bspLib.cbspIntInit 空模板

8.3 中断注册与分发

文件 关键内容
libsylixos/SylixOS/kernel/interface/InterVectorConnect.c API_InterVectorConnect 注册逻辑
libsylixos/SylixOS/kernel/interface/InterVectorIsr.c API_InterVectorIsr 分发逻辑
libsylixos/SylixOS/kernel/include/k_globalvar.h _K_idescTable 全局表定义
libsylixos/SylixOS/kernel/include/k_class.h LW_CLASS_INTDESC / LW_CLASS_INTACT 结构体
libsylixos/SylixOS/kernel/include/k_routine.h ISR 返回值定义、PINT_SVR_ROUTINE 类型

8.4 BSP 移植接口

文件需实现的函数
SylixOSBSP/SylixOS/bsp/bspLib.cbspIntInit /
bspIntHandle /
bspIntVectorEnable /
bspIntVectorDisable /
bspIntVectorIsEnable /
bspIntVectorSetPriority /
bspIntVectorGetPriority /
bspIntVectorSetTarget /
bspIntVectorGetTarget

8.5 参考实现

文件说明
SylixOSBSP/SylixOS/bsp/bspLib.c具体 SoC 中断控制器完整实现,可作为移植参考

如果对你有帮助,欢迎点赞收藏,有相关问题也可以评论讨论,后续将继续更新该系列。

Logo

openEuler 是由开放原子开源基金会孵化的全场景开源操作系统项目,面向数字基础设施四大核心场景(服务器、云计算、边缘计算、嵌入式),全面支持 ARM、x86、RISC-V、loongArch、PowerPC、SW-64 等多样性计算架构

更多推荐