在 VxWorks 实时操作系统(RTOS)环境下进行 C 语言编程,它本身不是 C 语言的一个新标准,而是 C 语言在特定嵌入式操作系统平台上的应用,有其独特的编程范式、库函数和开发流程。

(图片来源网络,侵删)
下面我将从几个核心方面来详细阐述。
VxWorks C 的核心特点
VxWorks C 之所以在嵌入式领域,特别是航空航天、国防、工业控制等高可靠性要求领域占据重要地位,主要源于以下几个特点:
a. 实时性
这是 VxWorks 的灵魂,C 语言代码通过调用 VxWorks 的 API 来实现任务的调度和管理。
- 抢占式多任务:VxWorks 是一个抢占式内核,高优先级的任务一旦就绪,可以立即抢占低优先级任务的 CPU 使用权,保证了关键任务的实时响应。
- 确定性的中断延迟:VxWorks 的内核设计精简,中断响应时间非常短且可预测,这对于需要快速响应外部事件的系统至关重要。
b. 高可靠性与稳定性
VxWorks 内核经过数十年的验证,以其稳定性和健壮性著称。

(图片来源网络,侵删)
- 内存保护:支持 MMU(内存管理单元),可以将不同任务运行在不同的内存保护域中,一个任务的崩溃不会影响整个系统。
- 错误检测机制:提供了丰富的机制来检测和报告错误,如断言、看门狗定时器等。
- 广泛的硬件支持:VxWorks 支持几乎所有主流的 32/64 位 CPU 架构,具有良好的可移植性。
c. 丰富的中间件和开发工具
VxWorks 不仅仅是一个内核,它提供了一个完整的软件平台。
- 中间件:包含 TCP/IP 协议栈、文件系统(如 NFS, dosFs, ramFs)、USB、CAN、串口通信等组件,开发者可以直接调用,无需从零开始。
- 开发工具 Workbench:这是一个强大的集成开发环境,集成了代码编辑、编译、调试、性能分析、版本控制等功能,极大地提高了开发效率。
关键编程概念与 API
在 VxWorks 中编程,你需要熟悉以下几个核心概念及其对应的 C API。
a. 任务
任务是 VxWorks 中调度的基本单位,可以看作是一个独立的、拥有自己栈空间的 C 函数。
创建任务:

(图片来源网络,侵删)
#include <taskLib.h>
/* 函数原型 */
STATUS taskSpawn (char *name, /* 任务名 */
int priority, /* 优先级 (0-255, 255最高) */
int option, /* 选项 (如 VX_FP_TASK, 表示使用浮点) */
int stackSize, /* 栈大小 */
FUNCPTR entry, /* 任务入口函数 */
int arg1, int arg2, ...); /* 传递给入口函数的参数 */
/* 示例:创建一个名为 "myTask", 优先级 100, 栈大小 20000 的任务 */
int taskId;
taskId = taskSpawn("myTask", 100, VX_FP_TASK, 20000, (FUNCPTR)myTaskFunction, 0, 0, 0, 0, 0, 0);
if (taskId == ERROR) {
printf("Failed to create task.\n");
}
任务控制:
taskSuspend(taskId): 挂起任务。taskResume(taskId): 恢复任务。taskDelete(taskId): 删除任务。
b. 信号量
信号量是任务间同步和互斥的主要工具。
创建二进制信号量 (用于互斥):
#include <semLib.h>
SEM_ID binSemId; /* 信号量 ID */
binSemId = semBCreate(SEM_Q_FIFO, SEM_EMPTY); /* 创建一个空的、FIFO 队列的信号量 */
if (binSemId == NULL) {
printf("Failed to create semaphore.\n");
}
使用信号量:
semTake(semId, WAIT_FOREVER): 获取信号量,如果信号量不可用,任务会阻塞,直到WAIT_FOREVER指定的超时时间。semGive(semId): 释放信号量。
c. 消息队列
消息队列用于任务间的异步通信,比共享内存更安全。
创建消息队列:
#include <msgQLib.h>
MSG_Q_ID msgQId;
int maxMsgs = 10;
int maxMsgSize = 100;
msgQId = msgQCreate(maxMsgs, maxMsgSize, MSG_Q_FIFO);
if (msgQId == MSG_Q_ID_NULL) {
printf("Failed to create message queue.\n");
}
使用消息队列:
msgQSend(msgQId, buffer, size, WAIT_FOREVER, MSG_PRI_NORMAL): 发送消息。msgQReceive(msgQId, buffer, size, WAIT_FOREVER): 接收消息。
d. 中断处理
VxWorks 提供了专门的中断处理机制。
中断服务程序:
#include <intLib.h>
#include <vxWorks.h>
/* 中断服务程序函数,必须符合特定格式 */
void myISR(int vector)
{
/* 1. 快速处理,清除中断源 */
...
/* 2. 释放信号量,唤醒处理任务 */
semGive(semIdForISR);
}
/* 连接中断 */
int connectMyISR()
{
int vector = INT_UART0; /* 假设是串口0的中断向量 */
/* FUNCPTR 是函数指针类型 */
if (intConnect((VOIDFUNCPTR)vector, (FUNCPTR)myISR, 0) == OK) {
return OK;
}
return ERROR;
}
开发流程与工具
开发 VxWorks C 程序通常遵循以下流程:
- 硬件平台选择:确定目标板(如基于 ARM, PowerPC, x86 的板卡)。
- 安装开发工具:安装 Wind River Workbench。
- 创建 BSP (Board Support Package):BSP 是连接 VxWorks 内核与特定硬件的桥梁,包含了硬件的驱动程序和初始化代码,通常由芯片或板卡供应商提供。
- 创建项目:在 Workbench 中创建一个 VxWorks 项目。
- 编写代码:编写 C 语言源文件,调用 VxWorks API。
- 编译与链接:Workbench 会使用工具链(如 GCC)将你的代码编译成目标文件,并与 VxWorks 内核链接,生成一个可执行映像(如
vxWorks)。 - 调试:
- 目标调试:通过 JTAG 或 以太网 将开发板与主机连接,在 Workbench 中进行源码级别的调试(设置断点、单步执行、查看变量等)。
- WindSh/Tornado Shell:这是一个在目标机上的命令行解释器,可以在程序运行时动态地执行命令、查看系统状态、调用函数,非常方便。
一个简单的示例
下面是一个经典的 "生产者-消费者" 模型示例,展示了任务、信号量和消息队列的使用。
#include <vxWorks.h>
#include <taskLib.h>
#include <semLib.h>
#include <msgQLib.h>
#include <stdio.h>
#define MAX_MSGS 10
#define MAX_MSG_SIZE 100
#define PRODUCER_PRIORITY 150
#define CONSUMER_PRIORITY 100
#define STACK_SIZE 20000
MSG_Q_ID msgQId; /* 消息队列 ID */
SEM_ID prodSemId; /* 生产者信号量 (控制生产速度) */
SEM_ID consSemId; /* 消费者信号量 (控制消费速度) */
/* 生产者任务 */
void producerTask(int arg)
{
int i;
char buffer[100];
for (i = 0; i < 20; i++) {
sprintf(buffer, "Message %d from Producer", i);
/* 获取生产者信号量 (模拟生产耗时) */
semTake(prodSemId, WAIT_FOREVER);
/* 发送消息到队列 */
msgQSend(msgQId, buffer, strlen(buffer) + 1, WAIT_FOREVER, MSG_PRI_NORMAL);
printf("Producer sent: %s\n", buffer);
}
}
/* 消费者任务 */
void consumerTask(int arg)
{
char buffer[MAX_MSG_SIZE];
while (1) {
/* 接收消息 */
int rcvSize = msgQReceive(msgQId, buffer, MAX_MSG_SIZE, WAIT_FOREVER);
if (rcvSize == ERROR) {
printf("Consumer received an error.\n");
break;
}
printf("Consumer received: %s\n", buffer);
/* 获取消费者信号量 (模拟消费耗时) */
semTake(consSemId, WAIT_FOREVER);
}
}
/* 主函数 - 在 VxWorks 启动时调用 */
void userStart()
{
/* 创建信号量 */
prodSemId = semBCreate(SEM_Q_FIFO, SEM_FULL); // 生产者一开始就可以生产
consSemId = semBCreate(SEM_Q_FIFO, SEM_FULL); // 消费者一开始可以消费
/* 创建消息队列 */
msgQId = msgQCreate(MAX_MSGS, MAX_MSG_SIZE, MSG_Q_FIFO);
if (msgQId == MSG_Q_ID_NULL) {
printf("Failed to create message queue.\n");
return;
}
/* 创建生产者和消费者任务 */
if (taskSpawn("producer", PRODUCER_PRIORITY, VX_FP_TASK, STACK_SIZE,
(FUNCPTR)producerTask, 0, 0, 0, 0, 0, 0) == ERROR) {
printf("Failed to create producer task.\n");
}
if (taskSpawn("consumer", CONSUMER_PRIORITY, VX_FP_TASK, STACK_SIZE,
(FUNCPTR)consumerTask, 0, 0, 0, 0, 0, 0) == ERROR) {
printf("Failed to create consumer task.\n");
}
}
与标准 C 的区别与注意事项
| 特性 | 标准 C (如 Linux/Windows) | VxWorks C |
|---|---|---|
| 入口点 | main() 函数 |
userStart() 函数(在 usrConfig.c 中配置),或者直接在内核启动代码中调用。 |
| 多任务 | 依赖操作系统提供的线程库(如 pthread) | VxWorks 的任务是其原生概念,API 直接集成在内核中,效率更高。 |
| 标准库 | 完整的 libc,包含 printf, malloc, free 等。 |
提供一个精简的 libc。malloc/free 存在,但需注意内存碎片问题,在关键系统中常使用内存池。 |
| 全局变量 | 全局变量在进程内共享。 | 全局变量在所有任务中共享。必须使用信号量等同步机制进行保护,否则会导致数据竞争。 |
| I/O 操作 | 使用 fopen, fprintf, read, write 等标准库函数,最终映射到文件描述符。 |
使用 VxWorks 的 I/O 系统,如 open(), read(), write(),它们直接操作设备驱动。open("/tyCo/0", ...) 打开串口。 |
| 调试 | GDB, LLDB 等调试器。 | Workbench 目标调试器,以及目标机上的命令行工具 WindSh。 |
VxWorks C 语言是 C 语言在高端嵌入式实时系统领域的专业实践,它要求开发者不仅要掌握 C 语言本身,更要深刻理解实时操作系统的核心概念(任务、同步、通信、中断),其强大的工具链、丰富的中间件和极高的可靠性,使其成为构建复杂、关键性嵌入式系统的首选平台,对于初学者来说,学习曲线相对陡峭,但一旦掌握,将具备开发顶级嵌入式系统的能力。
