VxWorks中断服务例程ISR编写与性能优化 - 第2节

2.1 什么是中断服务例程(ISR)

中断服务例程(Interrupt Service Routine, ISR)是嵌入式实时操作系统(RTOS)中响应硬件中断的核心机制。当外设或CPU内部产生中断事件时,系统暂停当前任务,转而执行预先注册的ISR,完成对中断事件的处理。

在VxWorks中,ISR通过intConnect()或intVecSet()函数与特定中断向量绑定,中断触发后由VxWorks中断管理层自动调用。

核心定义: ISR是一个在中断上下文中执行的、不可阻塞的、优先级高于所有任务的函数,用于快速响应硬件事件并完成最小必要处理。
ISR触发与执行流程 硬件中断源 (定时器/网卡/串口) 中断控制器 (INTC/GIC) VxWorks中断管理层 (中断向量表查找) ISR函数 (用户注册) 中断返回,恢复被中断的任务上下文 说明: ISR执行期间,当前任务被完全挂起;ISR返回后,VxWorks根据优先级决定是否恢复原任务或调度其他任务。

2.2 ISR与普通任务的核心区别

ISR与普通任务是VxWorks中两种完全不同的执行实体。理解它们的差异是正确编写ISR的前提。下表从多个维度进行对比:

对比维度 ISR(中断服务例程) 普通任务(Task)
执行上下文 中断上下文(Interrupt Context) 任务上下文(Task Context)
优先级 高于所有任务(不可被任务抢占) 按优先级调度(0~255)
阻塞能力 不可阻塞(不能调用阻塞型API) 可阻塞(semTake/msgQReceive等)
栈空间 使用中断栈(固定大小,通常4KB~16KB) 使用任务栈(可配置,通常8KB~64KB)
可调用API 仅限中断安全API(带INT后缀) 全部VxWorks API
执行时间 必须极短(微秒级) 可较长(毫秒级甚至更长)
上下文切换开销 小(仅保存/恢复少量寄存器) 大(完整任务上下文切换)
浮点运算 默认不支持(需特殊配置) 支持(任务级浮点上下文)
关键警告: 在ISR中调用非中断安全API(如printf()、malloc()、semTake())会导致系统崩溃或死锁。ISR只能调用以int或_开头的安全函数,例如semGive()在ISR中应使用semGive()(部分版本)或通过intSemGive()实现。
ISR与普通任务调度对比 时间 任务A(优先级100) 任务B(优先级50) ISR 中断触发 挂起 任务A恢复执行 继续 ISR抢占所有任务,执行完毕后返回被中断的任务;高优先级任务(B)不会抢占低优先级任务(A)的中断返回过程。

2.3 ISR的执行环境与上下文

ISR在VxWorks中运行于中断上下文,这是一个与所有任务上下文完全隔离的特殊执行环境。理解中断上下文的构成和限制,是编写可靠ISR的基础。

2.3.1 中断栈(Interrupt Stack)

VxWorks为所有ISR共享一个独立的中断栈,而非使用被中断任务的栈。中断栈的大小在系统启动时通过ISR_STACK_SIZE宏配置,典型值为4KB~16KB。

  • 优点: 避免ISR占用任务栈空间,防止栈溢出影响任务。
  • 风险: 中断栈空间有限,ISR中不能定义大型局部数组或进行深度函数调用。
  • 建议: ISR内部仅做标志置位、数据拷贝等轻量操作,复杂处理通过semGive()或msgQSend()委托给任务。

2.3.2 寄存器保存与恢复

中断发生时,VxWorks自动保存被中断任务的最小寄存器集合(通常包括PC、SR、通用寄存器等),ISR返回时恢复。浮点寄存器默认不保存,除非系统配置了INCLUDE_FP且ISR显式使用浮点指令。

性能提示: 由于ISR只保存最小寄存器集,其上下文切换开销远小于任务切换。这也是ISR适合高频中断处理的原因之一。但若在ISR中使用浮点运算,将额外增加数十微秒的保存/恢复开销。

2.3.3 中断优先级与嵌套

VxWorks支持中断优先级嵌套。高优先级中断可以抢占正在执行的低优先级ISR。中断嵌套增加了系统的响应能力,但也带来了可重入和死锁风险。

中断嵌套执行场景 时间 低优先级ISR(IRQ5) 高优先级ISR(IRQ2) 低优先级ISR恢复 IRQ5触发 IRQ2触发(嵌套) 高优先级ISR执行完毕后,低优先级ISR继续执行;所有ISR完成后,恢复被中断的任务。

2.4 ISR的编程限制与安全规则

由于中断上下文的特殊性,ISR编程必须遵守一系列严格限制。违反这些限制将导致系统不稳定、死锁甚至崩溃。

2.4.1 可调用API清单

VxWorks提供一组中断安全API,这些API在实现上保证了不会阻塞或使用任务级资源。常用中断安全API包括:

API类别 安全函数示例 说明
信号量操作 semGive() 释放信号量(不可获取)
消息队列 msgQSend() 向队列发送消息(不可接收)
任务通知 taskNotify() 向任务发送事件通知
看门狗 wdStart() 启动看门狗定时器
时间戳 tickGet() 获取系统Tick计数
中断控制 intLock()/intUnlock() 中断锁定/解锁

2.4.2 禁止操作清单

  • 禁止阻塞: 不能调用semTake()、msgQReceive()、taskDelay()等阻塞型API。
  • 禁止动态内存分配: 不能调用malloc()、free()、calloc()等堆操作函数。
  • 禁止I/O操作: 不能调用printf()、fprintf()、write()等标准I/O函数。
  • 禁止浮点运算: 除非系统显式配置浮点中断支持,否则避免浮点指令。
  • 禁止长时间循环: ISR执行时间应控制在微秒级,避免影响系统实时性。
常见错误: 在ISR中使用printf()进行调试输出。printf内部可能使用互斥锁保护控制台,在中断上下文中获取互斥锁会导致死锁。应使用logMsg()(中断安全版本)替代。

2.5 ISR编写示例与模式

以下是一个符合VxWorks规范的ISR编写示例,展示了中断安全API的正确使用方式:

/* 全局变量:中断与任务之间的通信 */
SEM_ID g_irqSem;          /* 二值信号量,用于通知任务 */
volatile UINT32 g_irqCount; /* 中断计数,volatile防止编译器优化 */

/* ISR函数:定时器中断处理 */
void timerIsr(void)
{
    /* 1. 清除中断标志(硬件相关) */
    *((volatile UINT32 *)TIMER_ISR_REG) = 0x1;
    
    /* 2. 更新共享数据(原子操作) */
    g_irqCount++;
    
    /* 3. 释放信号量,通知等待的任务 */
    semGive(g_irqSem);
    
    /* 4. 可选:启动看门狗,防止任务处理超时 */
    wdStart(g_wdId, WDT_TIMEOUT, (FUNCPTR)wdHandler, 0);
}

/* 任务函数:等待中断并处理 */
void timerHandlerTask(void)
{
    while (1)
    {
        /* 等待ISR释放信号量 */
        semTake(g_irqSem, WAIT_FOREVER);
        
        /* 执行耗时处理(在任务上下文中安全) */
        processTimerEvent(g_irqCount);
    }
}
最佳实践: ISR应遵循"最小特权原则"——只做必须立即完成的操作(如清除中断标志、拷贝数据、释放信号量),将耗时处理委托给任务。这种"上半部-下半部"(Top-Half/Bottom-Half)模式是RTOS中断处理的经典设计。
ISR与任务协作模式(Top-Half / Bottom-Half) 中断上下文(Top-Half) ISR 清除中断标志 semGive() 任务上下文(Bottom-Half) 处理任务 semTake()等待 耗时处理 唤醒任务 任务可以安全调用阻塞API、进行I/O操作、 动态内存分配等ISR中禁止的操作。

2.6 本章小结

  • ISR是运行在中断上下文中的特殊函数,优先级高于所有任务。
  • ISR与普通任务在上下文、栈空间、可调用API、阻塞能力等方面存在本质区别。
  • ISR必须使用中断安全API,禁止阻塞、动态内存分配和I/O操作。
  • 推荐采用Top-Half/Bottom-Half模式,ISR仅做最小必要处理,复杂逻辑委托给任务。
  • 中断嵌套提高了响应能力,但增加了可重入设计的复杂度。