4、事件等待与超时处理:阻塞机制、Ticks设置与错误处理
好,咱们接着聊事件等待。上一节讲了怎么发事件,这一节重点说说怎么等事件。
说白了,事件等待就是任务在那里「蹲着」,等某个条件满足。但蹲多久?蹲不到怎么办?这就是本节要解决的问题。
4.1 阻塞机制:任务是怎么「睡」过去的?
当一个任务调用 taskEventPend() 等待事件时,它会被挂起。这个挂起不是简单的死循环,而是有讲究的。
我打个比方:你派了个哨兵去等信号。哨兵不能一直瞪着眼睛不眨眼吧?VxWorks 的做法是——让这个任务进入阻塞态,把 CPU 让给其他任务。等事件来了,内核再把它唤醒。
具体来说,阻塞分两种:
- 无条件阻塞:等不到就一直等,直到事件发生
- 有条件阻塞:设置一个超时时间,时间到了还没等到,就醒过来
我个人习惯,除非是系统初始化这种必须死等的场景,否则都会加超时。为什么?你想想看,万一发事件的那个任务崩了,你这边的任务不就永远睡下去了?
核心机制:事件等待的阻塞,本质上是任务状态从 READY 变为 PEND。事件到达后,内核会检查等待队列,把匹配的任务重新置为 READY。
4.2 超时参数 Ticks:这个数字怎么设?
VxWorks 的超时单位是 tick(系统时钟节拍)。1 tick 对应多长时间?取决于你的系统配置。默认一般是 1ms 或 10ms。
代码里是这样写的:
/* 等待事件,超时 100 ticks */
STATUS status = taskEventPend(eventId,
EVENT_MASK_1,
EVENTS_WAIT_ANY,
100); /* 超时参数 */
这个 100 是什么意思?如果系统 tick 是 10ms,那就是等 1 秒。
这里有个坑——tick 不是精确的时间。我在项目中遇到过,某次把超时设成 50 ticks,以为等 500ms。结果系统 tick 被改成了 1ms,实际只等了 50ms。业务逻辑直接崩了。
所以我建议:
- 用
sysClkRateGet()获取当前 tick 频率 - 用宏定义把时间换算成 ticks,别写死数字
/* 推荐写法:用宏定义换算 */
#define TIMEOUT_MS 500 /* 500毫秒 */
#define TICKS_PER_MS (sysClkRateGet() / 1000)
#define TIMEOUT_TICKS (TIMEOUT_MS * TICKS_PER_MS)
STATUS status = taskEventPend(eventId,
EVENT_MASK_1,
EVENTS_WAIT_ANY,
TIMEOUT_TICKS);
小技巧:如果你想让任务永远等下去,超时参数传 WAIT_FOREVER(也就是 0)。但除非你有十足的把握,否则别这么干。我曾经见过一个产品,因为某个驱动初始化失败,结果三个任务都在 WAIT_FOREVER,系统直接「死机」。
4.3 超时后的错误处理:别让异常变成灾难
超时了怎么办?taskEventPend() 会返回 ERROR,然后你可以通过 errnoGet() 拿到具体的错误码。
常见的错误码就两个:
| 错误码 | 含义 | 典型场景 |
|---|---|---|
| S_objLib_TIMEOUT | 超时了,没等到事件 | 对方任务处理太慢,或者根本没发事件 |
| S_objLib_OBJ_UNAVAILABLE | 事件对象被删了 | 其他任务把 eventId 给删了 |
处理代码一般长这样:
STATUS status = taskEventPend(eventId,
EVENT_MASK_DATA_READY,
EVENTS_WAIT_ANY,
TIMEOUT_TICKS);
if (status == ERROR) {
int errCode = errnoGet();
if (errCode == S_objLib_TIMEOUT) {
/* 超时处理:重试?报错?降级? */
logMsg("事件等待超时,重试第%d次\n", retryCount++, 0,0,0,0,0);
if (retryCount < MAX_RETRY) {
/* 重试 */
goto retry;
} else {
/* 彻底失败,走错误恢复流程 */
handleFatalError();
}
}
else if (errCode == S_objLib_OBJ_UNAVAILABLE) {
/* 事件对象被删了,这属于严重错误 */
logMsg("严重错误:事件对象已被删除!\n", 0,0,0,0,0,0);
taskDelete(0); /* 自杀 */
}
}
警告:千万不要在超时后什么都不做就直接重试!我曾经接手过一个项目,代码里超时后直接 taskDelay(1) 然后重试,结果因为事件永远不会来,这个任务就在那里空转,CPU 占用率直接飙到 30%。
4.4 实战中的几个决策点
嗯,这里我要多说几句。超时处理不是简单的「等不到就报错」,你得根据业务场景来:
- 重试策略:是立即重试?还是等一会再重试?我一般用指数退避,第一次等 10 ticks,第二次等 20,第三次等 40……
- 降级处理:如果超时了,能不能用旧数据?能不能走另一条路径?
- 记录现场:超时的时候,把当时的任务栈、事件状态都打出来,方便事后分析
举个例子,我在做某个通信协议栈的时候,底层收包任务偶尔会超时。一开始我直接报错重启,后来发现其实是网络瞬间拥塞。改成重试 3 次后,系统稳定性从 99.9% 提升到了 99.99%。
所以你看,超时处理不是简单的 if-else,它关系到整个系统的健壮性。
4.5 小结
这一节的核心就三句话:
- 事件等待是阻塞的,但别让它变成死等
- 超时参数用宏定义换算,别写死 ticks 数
- 超时后一定要有处理逻辑,哪怕是打印一行日志
下一节我们聊聊事件通知的另一种方式——回调。那个更灵活,但也更容易踩坑。