SylixOS 翼辉操作系统中断子系统源码探究与移植详解
1. 整体架构概览
SylixOS 中断子系统由三层构成,各层职责清晰分离:
核心设计思想: 以全局中断描述符表 _K_idescTable[] 为中心,Connect 写入映射,Isr 查表分发,BSP 负责硬件交互。内核与硬件完全解耦。
2. 异常向量表:硬件入口
2.1 ARM 异常向量地址是硬件固定的
ARM CPU 在异常/中断发生时,硬件自动完成:
- 保存返回地址到
LR_<mode>,CPSR 到SPSR_<mode> - 切换到对应特权模式(如 IRQ 模式)
- 置位 CPSR 的 I 位(屏蔽全局 IRQ) – 关中断在这里就完成了
- 把 PC 设到固定的向量地址
每种异常有固定偏移(低向量模式,V=0 时从 0x00000000 开始):
| 偏移 | 异常类型 | 说明 |
|---|---|---|
| 0x00 | Reset | 上电复位 |
| 0x04 | Undefined instruction | 未定义指令 |
| 0x08 | SWI (SVC) | 软件中断,系统调用 |
| 0x0C | Prefetch abort | 取指中止 |
| 0x10 | Data abort | 数据中止 |
| 0x14 | Reserved | 保留 |
| 0x18 | IRQ | 普通中断 |
| 0x1C | FIQ | 快速中断 |
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段放在.text最开头(镜像偏移 0) - 启动代码清 CP15 控制寄存器的 V 位(bit 13),选用低向量模式(异常从 0x00000000 取指)
- SoC 硬件把地址 0x00000000 别名到 RAM 基址,使向量表在 0x0 可见
3. 中断初始化流程
3.1 完整初始化链路
从上电到中断系统就绪,经历以下阶段:
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 完整调用链
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 动作 |
|---|---|---|---|
| 0 | LW_IRQ_NONE | 不是本设备中断 | 继续调下一个(队列模式) |
| 1 | LW_IRQ_HANDLED | 已处理 | 正常恢复向量 |
| 2 | LW_IRQ_HANDLED_DISV | 已处理, 屏蔽本中断 | 保持/补做屏蔽 |
| 3 | LW_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 中断移植需要实现以下函数:
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.S | archIntEntry 汇编入口 |
libsylixos/SylixOS/arch/arm/common/armExc.c | archIntHandle C 分发逻辑 |
8.2 中断初始化
| 文件 | 关键内容 |
|---|---|
libsylixos/SylixOS/kernel/interface/KernelStart.c | API_KernelStart 调用链 |
libsylixos/SylixOS/kernel/core/_KernelLowLevelInit.c | _InterVectInit 调用点 |
SylixOSBSP_empty/SylixOS/bsp/bspLib.c | bspIntInit 空模板 |
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.c | bspIntInit /bspIntHandle /bspIntVectorEnable /bspIntVectorDisable /bspIntVectorIsEnable /bspIntVectorSetPriority /bspIntVectorGetPriority /bspIntVectorSetTarget /bspIntVectorGetTarget |
8.5 参考实现
| 文件 | 说明 |
|---|---|
SylixOSBSP/SylixOS/bsp/bspLib.c | 具体 SoC 中断控制器完整实现,可作为移植参考 |
如果对你有帮助,欢迎点赞收藏,有相关问题也可以评论讨论,后续将继续更新该系列。
openEuler 是由开放原子开源基金会孵化的全场景开源操作系统项目,面向数字基础设施四大核心场景(服务器、云计算、边缘计算、嵌入式),全面支持 ARM、x86、RISC-V、loongArch、PowerPC、SW-64 等多样性计算架构
更多推荐
所有评论(0)