3. 中断连接与断开:intConnect()、intDisconnect() 函数详解

好,咱们进入中断管理的核心操作——连接和断开中断服务程序。

说白了,中断连接就是告诉 CPU:「当这个中断来了,你跳到我指定的函数去执行」。而中断断开,就是把这条「指令」撤销掉。

这两个函数,我几乎每个 VxWorks 项目都会用到。今天咱们把它们彻底讲透。

3.1 intConnect():把中断和 ISR 绑在一起

先看原型:

STATUS intConnect
    (
    VOIDFUNCPTR * vector,   /* 中断向量号 */
    VOIDFUNCPTR routine,    /* ISR 函数指针 */
    int parameter           /* 传给 ISR 的参数 */
    )

这个函数的作用,就是把一个中断向量和一个 C 函数绑定起来。

参数很简单:

  • vector:中断向量号。注意,它是个指针,指向一个 VOIDFUNCPTR 类型的变量。这个变量里存的就是向量号。为什么这么设计?嗯,这是为了和 VxWorks 的异常处理机制统一,你按规范写就行。
  • routine:你的 ISR 函数地址。函数必须符合 VOIDFUNCPTR 类型,也就是返回 void,接受一个 int 参数。
  • parameter:传给 ISR 的参数。这个参数会被压入栈中,ISR 里可以直接用。

重要:intConnect() 返回 OK 表示成功,返回 ERROR 表示失败。失败原因通常是向量号无效或内存不足。

来看个实际例子。我在一个电机控制项目里,需要响应定时器中断:

/* 定义中断向量号变量 */
VOIDFUNCPTR myTimerVector = (VOIDFUNCPTR) 0x20;  /* 假设定时器中断向量是 0x20 */

/* ISR 函数 */
void timerIsr(int arg)
{
    /* 清除中断标志 */
    *((volatile UINT32 *) TIMER_STATUS_REG) = 0;
    
    /* 处理业务逻辑 */
    g_timerCount++;
    
    /* 通知任务 */
    semGive(g_timerSem);
}

/* 连接中断 */
if (intConnect(myTimerVector, timerIsr, (int) NULL) != OK)
{
    logMsg("intConnect failed!\n", 0,0,0,0,0);
    return ERROR;
}

这里有个细节:myTimerVector 必须是一个 VOIDFUNCPTR 类型的变量,不能直接传数字。我刚开始学的时候,直接传 (VOIDFUNCPTR) 0x20,结果编译报错。后来才明白,VxWorks 要求传变量的地址。

个人习惯:我一般把中断向量号定义成全局变量,放在一个专门的 intVectors.h 文件里。这样方便管理和修改。

3.2 intDisconnect():把中断和 ISR 拆开

有连接就有断开。原型如下:

STATUS intDisconnect
    (
    VOIDFUNCPTR * vector   /* 要断开的中断向量号 */
    )

这个函数更简单,只需要传一个向量号指针。它会把这个向量上之前绑定的 ISR 给解绑,恢复成默认的中断处理。

什么时候需要断开?

  • 驱动卸载时:比如你的设备被拔掉了,或者模块被卸载了,必须把中断断开,否则系统会崩溃。
  • 动态重配时:你想换一个 ISR,需要先断开旧的,再连接新的。
  • 系统休眠时:某些低功耗场景下,需要临时关闭中断。

看个断开例子:

/* 断开定时器中断 */
if (intDisconnect(myTimerVector) != OK)
{
    logMsg("intDisconnect failed!\n", 0,0,0,0,0);
    return ERROR;
}

警告:千万不要在 ISR 里调用 intDisconnect()!我曾经犯过这个错,结果系统直接死锁。因为 intDisconnect() 内部会操作中断控制器的寄存器,而 ISR 执行期间中断是关闭的,这会导致不可预知的行为。

3.3 避坑指南:我踩过的那些坑

讲几个实战中容易出问题的地方。

坑一:参数传递的陷阱

intConnect() 的 parameter 参数,只能传一个 int。如果你需要传多个参数,怎么办?

我的做法是:定义一个全局结构体,把参数都放进去,然后传结构体的指针。注意,这个指针要强制转换成 int:

typedef struct {
    int   deviceId;
    char *buffer;
    int   length;
} DeviceContext;

DeviceContext g_ctx;

void deviceIsr(int arg)
{
    DeviceContext *ctx = (DeviceContext *) arg;
    /* 现在可以访问 ctx->deviceId, ctx->buffer 等 */
}

/* 连接时 */
g_ctx.deviceId = 1;
g_ctx.buffer   = g_dataBuf;
g_ctx.length   = 256;
intConnect(myVector, deviceIsr, (int) &g_ctx);

坑二:中断向量号的合法性

不同的 CPU 架构,中断向量号的范围不一样。比如 PowerPC 的向量号是 0-255,ARM 的可能只有 0-31。如果你传了一个超出范围的向量号,intConnect() 会返回 ERROR。

我建议你在初始化时,先查一下 BSP 手册,确认向量号范围。或者用 sysIntLvlToVec() 这类函数做转换。

坑三:重复连接的问题

如果你对一个已经连接过的向量再次调用 intConnect(),VxWorks 会覆盖之前的 ISR。这通常不是你想要的。

我的习惯是:在连接之前,先调用 intDisconnect() 确保是干净的。或者维护一个状态变量,记录哪些向量已经被占用了。

3.4 实战经验:中断连接的最佳实践

根据我多年的经验,总结几条实用建议:

  1. ISR 要短小精悍:intConnect() 只是绑定,真正干活的是 ISR。ISR 里不要做复杂操作,比如 malloc、printf 这些都不能用。我一般只做三件事:清除中断标志、读取数据、通知任务。
  2. 参数传递要小心:parameter 参数虽然方便,但别滥用。如果你传的是指针,要确保指针指向的内存是有效的,而且不会被其他任务修改。
  3. 错误处理不能省:intConnect() 和 intDisconnect() 都可能失败。我见过很多新手直接忽略返回值,结果中断没连上,系统跑飞了都不知道原因。
  4. 考虑中断嵌套:VxWorks 默认支持中断嵌套。如果你的 ISR 执行时间较长,可以考虑在 ISR 里开中断,允许更高优先级的中断进来。但要注意,这会增加复杂度。

小技巧:调试中断时,我习惯在 ISR 入口和出口加两个 logMsg,打印时间戳。这样能直观看到中断响应时间和执行时间。当然,正式发布时要删掉这些调试信息。

3.5 总结

intConnect() 和 intDisconnect() 是 VxWorks 中断管理的基石。用好了,你的系统响应快、稳定可靠;用不好,各种诡异问题层出不穷。

记住几个关键点:

  • 向量号要用 VOIDFUNCPTR 类型的变量
  • ISR 要短,别在里面干重活
  • 断开和连接要成对出现
  • 错误处理不能偷懒

下一章,咱们聊聊中断锁和中断屏蔽,那是更高阶的话题了。