1. VxWorks异常概述

1.1 异常的定义

异常,说白了就是CPU在执行指令时遇到了「意外情况」。我习惯把它理解成——程序跑着跑着,突然遇到了自己处理不了的事,只好举手喊「报告!」。

在VxWorks里,异常是一个广义概念。它既包括CPU硬件检测到的非法操作,也包括软件主动触发的特殊事件。嗯,这里要注意:异常和中断不一样。中断是外部设备主动通知CPU,而异常是CPU自己执行指令时发现不对劲。

举个例子。我在调试一个电机控制程序时,遇到过指针越界。CPU执行到那条指令时,突然就跳到了异常向量表。当时我盯着示波器看了半天,才意识到是野指针惹的祸。从那以后,我写代码都会先检查指针有效性。

核心定义:异常是CPU在执行指令过程中检测到的非正常事件,需要操作系统介入处理。

1.2 异常的分类

VxWorks把异常分成两大类:硬件异常和软件异常。你想想看,这其实很好理解——一个是CPU硬件报错,一个是程序主动搞事情。

硬件异常

硬件异常是CPU内部检测到的。我列几个常见的:

  • 除零异常:整数除法时除数为0。PowerPC、ARM、x86都有这个异常。
  • 非法指令:CPU不认识这条指令。比如你给ARM核塞了一条x86指令。
  • 总线错误:访问了不存在的内存地址。我在项目中遇到过,DMA配置错了,往保留地址写数据,直接触发总线错误。
  • 对齐异常:访问未对齐的数据。比如从奇数地址读一个32位整数。
  • 页面错误:MMU找不到对应的页表项。VxWorks里如果启用了虚拟内存,这个很常见。
异常类型 触发条件 典型场景
除零异常 整数除法除数为0 数学计算错误
非法指令 CPU无法识别的指令码 代码段损坏
总线错误 访问无效物理地址 指针越界
对齐异常 未对齐的内存访问 结构体打包问题
页面错误 MMU缺页 虚拟内存管理

软件异常

软件异常是程序主动触发的。说白了就是程序自己觉得「不对劲,得让系统来处理」。VxWorks里主要有:

  • 系统调用:用户程序通过trap指令进入内核。比如taskDelay()底层就是触发一个软件异常。
  • 断言失败:assert()宏检测到条件不成立。我调试时特别喜欢用断言,能快速定位逻辑错误。
  • 看门狗超时:软件看门狗没及时喂。我曾经在一个通信项目里,因为任务优先级配错了,导致喂狗任务被饿死,系统反复重启。

个人经验:我建议在开发阶段把断言全部打开。等产品稳定了,再考虑关闭。别问我为什么——有一次我把断言关了,结果一个隐藏bug在生产环境跑了三个月才暴露出来,那叫一个惨。

1.3 异常处理的基本流程

VxWorks处理异常有一套固定的流程。我把它总结成四步走:

  1. 异常触发:CPU检测到异常,自动保存当前上下文(PC、SR、寄存器等)。
  2. 向量查找:CPU根据异常类型,从异常向量表中找到对应的处理函数入口。
  3. 内核接管:VxWorks的异常分发函数excEnt()被调用,它负责判断异常来源。
  4. 用户处理:如果用户注册了信号处理函数,就调用它;否则执行默认行为(通常是挂起任务)。

这里有个关键点:异常处理是在当前任务的上下文中执行的。也就是说,哪个任务触发了异常,就由哪个任务来处理。我刚开始学VxWorks时,以为异常处理是独立线程,后来发现不是——它用的是触发任务的栈空间。

避坑指南:我曾经在异常处理函数里分配大数组,结果栈溢出了。因为异常处理用的是任务栈,而任务栈通常只有几KB。所以异常处理函数里千万别搞大内存操作。

看一个简单的异常注册示例:

#include <vxWorks.h>
#include <excLib.h>
#include <signal.h>

/* 自定义异常处理函数 */
void myExcHandler(int signo, siginfo_t *pInfo, void *pContext)
{
    /* 打印异常信息 */
    printf("异常信号: %d\n", signo);
    printf("异常地址: 0x%x\n", pInfo->si_addr);
    
    /* 这里可以做日志记录 */
    /* 但别做复杂操作,小心栈溢出 */
}

/* 注册异常处理 */
void setupExceptionHandler(void)
{
    struct sigaction act;
    
    act.sa_handler = (void (*)())myExcHandler;
    sigemptyset(&act.sa_mask);
    act.sa_flags = SA_SIGINFO;
    
    /* 注册SIGSEGV(段错误)的处理函数 */
    sigaction(SIGSEGV, &act, NULL);
}

嗯,这里要注意:sigaction的sa_flags一定要设SA_SIGINFO。不然你拿不到异常地址,排查问题会非常痛苦。我踩过这个坑,调试了整整一个下午才发现。

1.4 异常处理的优先级

VxWorks里异常处理的优先级很有意思。它比任务优先级高,但比中断低。为什么?

你想想看,异常是CPU执行指令时触发的。如果异常处理时来了中断,CPU会先响应中断。因为中断通常有时效性要求,比如网卡收包不能等太久。但反过来,中断处理时如果触发了异常,那就麻烦了——这叫「异常嵌套」,处理不好会导致系统崩溃。

关键原则:异常处理函数中不要调用可能阻塞的系统调用。比如taskDelay()、msgQReceive()这些。因为异常处理时系统状态可能不一致,一阻塞就死锁了。

我记得有一次,同事在异常处理里调用了printf()。printf底层用了信号量保护,结果异常发生时信号量已经被当前任务持有了——死锁!从那以后,我写异常处理函数只用简单的日志记录,绝不碰复杂API。

1.5 小结

异常处理是VxWorks系统稳定性的基石。说白了,没有好的异常处理,系统就像走钢丝——随时可能崩。我个人习惯是:

  • 开发阶段打开所有异常检测
  • 异常处理函数保持精简
  • 关键路径注册信号处理函数
  • 异常日志一定要记录现场信息

下一章我会讲VxWorks的异常向量表配置,以及如何自定义异常处理。到时候会带一个实际项目的案例,敬请期待。