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 实战经验:中断连接的最佳实践
根据我多年的经验,总结几条实用建议:
- ISR 要短小精悍:intConnect() 只是绑定,真正干活的是 ISR。ISR 里不要做复杂操作,比如 malloc、printf 这些都不能用。我一般只做三件事:清除中断标志、读取数据、通知任务。
- 参数传递要小心:parameter 参数虽然方便,但别滥用。如果你传的是指针,要确保指针指向的内存是有效的,而且不会被其他任务修改。
- 错误处理不能省:intConnect() 和 intDisconnect() 都可能失败。我见过很多新手直接忽略返回值,结果中断没连上,系统跑飞了都不知道原因。
- 考虑中断嵌套:VxWorks 默认支持中断嵌套。如果你的 ISR 执行时间较长,可以考虑在 ISR 里开中断,允许更高优先级的中断进来。但要注意,这会增加复杂度。
小技巧:调试中断时,我习惯在 ISR 入口和出口加两个 logMsg,打印时间戳。这样能直观看到中断响应时间和执行时间。当然,正式发布时要删掉这些调试信息。
3.5 总结
intConnect() 和 intDisconnect() 是 VxWorks 中断管理的基石。用好了,你的系统响应快、稳定可靠;用不好,各种诡异问题层出不穷。
记住几个关键点:
- 向量号要用
VOIDFUNCPTR类型的变量 - ISR 要短,别在里面干重活
- 断开和连接要成对出现
- 错误处理不能偷懒
下一章,咱们聊聊中断锁和中断屏蔽,那是更高阶的话题了。