第4章:任务管理——任务创建、删除与优先级抢占
各位同学,今天我们来聊聊VxWorks里最核心的东西——任务管理。
说白了,一个实时系统跑起来,就是一堆任务在抢CPU。你管好了任务,系统就稳如老狗;管不好,那就是各种死机、跑飞、优先级反转。我当年刚入行时,就因为在任务优先级上栽了个大跟头,差点让整个产线停摆。嗯,今天咱们就把这块彻底讲透。
4.1 任务创建:taskSpawn
在VxWorks里,创建一个任务非常简单。你只需要调用一个函数——taskSpawn。
它的原型长这样:
int taskSpawn
(
char *name, /* 任务名字,调试用 */
int priority, /* 优先级,0-255,0最高 */
int options, /* 任务选项,比如VX_FP_TASK */
int stackSize, /* 栈大小,单位字节 */
FUNCPTR entryPt, /* 任务入口函数 */
int arg1, /* 参数1 */
int arg2, /* 参数2 */
...
int arg10 /* 参数10,最多10个 */
);
你看,参数挺多,但常用的就前5个。我个人习惯是:名字起得有意义,优先级别乱设,栈大小给够。
关键点:
- 优先级:0最高,255最低。数值越小,优先级越高。
- 栈大小:给少了任务会栈溢出,给多了浪费内存。我一般给4096或8192起步。
- options:常用的是
VX_FP_TASK,如果你的任务用到了浮点运算,必须加上这个选项。
举个例子:
#include <vxWorks.h>
#include <taskLib.h>
void myTask(int arg1, int arg2)
{
while(1)
{
printf("Hello from myTask, arg1=%d, arg2=%d\n", arg1, arg2);
taskDelay(100); /* 延时100个tick */
}
}
void startMyTask(void)
{
int tid;
tid = taskSpawn("tMyTask", 100, VX_FP_TASK, 4096,
(FUNCPTR)myTask, 10, 20, 0,0,0,0,0,0,0);
if(tid == ERROR)
{
printf("taskSpawn failed!\n");
}
}
这段代码创建了一个叫tMyTask的任务,优先级100,带浮点支持,栈大小4KB。入口函数是myTask,传了两个参数10和20。
小技巧: 任务名字最好以t开头,这是VxWorks社区的惯例。比如tNetTask、tLogTask。这样一看就知道是任务,不是变量或函数。
4.2 任务删除:taskDelete
有创建就有删除。删除任务用taskDelete。
STATUS taskDelete
(
int tid /* 任务ID */
);
你可能会想:删除任务还不简单?直接调一下不就完了?
嗯,这里有个大坑。我曾经在一个项目中,直接在主任务里删除了一个正在持有互斥信号量的子任务。结果呢?整个系统死锁了。所有等待那个信号量的任务全部卡死。
警告: 删除任务前,一定要确保它没有持有任何系统资源!比如信号量、消息队列、内存块。否则会造成资源泄漏或死锁。
安全删除的姿势是这样的:
void safeDeleteTask(int tid)
{
/* 先通知任务自己退出 */
taskSuspend(tid); /* 暂停任务 */
taskDelete(tid); /* 再删除 */
}
但说实话,我更推荐的做法是:让任务自己退出。在任务的主循环里检查一个退出标志,如果为真,就调用taskDelete(0)删除自己。这样最安全。
int exitFlag = 0;
void myTask(void)
{
while(!exitFlag)
{
/* 正常工作 */
taskDelay(100);
}
taskDelete(0); /* 自己删除自己 */
}
4.3 任务优先级与抢占
这是实时系统的灵魂。VxWorks用的是基于优先级的抢占式调度。
什么意思呢?说白了就是:高优先级的任务随时可以打断低优先级的任务。只要高优先级任务就绪了,CPU立刻归它。
举个例子:
void highTask(void)
{
while(1)
{
printf("High priority task running\n");
taskDelay(10);
}
}
void lowTask(void)
{
while(1)
{
printf("Low priority task running\n");
taskDelay(10);
}
}
void startTasks(void)
{
taskSpawn("tHigh", 50, 0, 4096, (FUNCPTR)highTask, 0,0,0,0,0,0,0,0,0,0);
taskSpawn("tLow", 150, 0, 4096, (FUNCPTR)lowTask, 0,0,0,0,0,0,0,0,0,0);
}
这里tHigh优先级50,tLow优先级150。你猜谁先跑?
当然是tHigh。只要它不阻塞(比如taskDelay),tLow永远没机会跑。这就是抢占。
核心原则:
- 高优先级任务就绪 → 立即抢占低优先级任务
- 同优先级任务 → 时间片轮转(如果开启了时间片调度)
- 任务主动让出CPU → 调用
taskDelay、taskSuspend、等待信号量等
你想想看,如果两个任务优先级一样,又没有时间片调度,会怎样?
嗯,第一个任务会一直跑,第二个任务永远没机会。这就是所谓的「饿死」。所以同优先级任务一定要配合taskDelay或时间片调度来用。
4.4 优先级反转——一个经典问题
说到优先级,不得不提一个经典问题:优先级反转。
我在一个军工项目里遇到过。一个高优先级任务等一个信号量,而这个信号量被一个低优先级任务占着。结果中优先级任务跑起来了,它不抢信号量,但抢了CPU。低优先级任务跑不了,信号量释放不了,高优先级任务就一直等。整个系统性能急剧下降。
VxWorks提供了优先级继承机制来解决这个问题。你可以在创建信号量时加上SEM_INVERSION_SAFE选项:
SEM_ID sem = semMCreate(SEM_Q_PRIORITY | SEM_INVERSION_SAFE);
这样,当低优先级任务持有信号量时,它会临时继承等待该信号量的最高优先级任务的优先级。等它释放信号量后,再恢复原来的优先级。
我的建议: 只要用到互斥信号量,就加上SEM_INVERSION_SAFE。别省这个选项,省了就是给自己挖坑。
4.5 实战避坑指南
最后,我总结几个实战中容易踩的坑:
- 栈溢出:任务栈给太小,函数调用层级深一点就崩了。我一般先给4096,跑起来后用
checkStack命令看看实际用了多少,再调整。 - 优先级设太高:把系统任务(比如tNetTask、tLogTask)的优先级设得比用户任务还低,会导致网络不通、日志丢失。系统任务的优先级一般在50-100之间,用户任务建议100以上。
- 忘记加VX_FP_TASK:任务里用了浮点运算,但创建时没加这个选项。结果浮点寄存器被破坏,数据全乱套。这个bug我查了整整两天。
- 任务删除不干净:删了任务但没释放它申请的内存,造成内存泄漏。建议用
taskSafe和taskUnsafe配合使用,或者干脆让任务自己退出。
好了,这一章的内容就到这里。任务管理是VxWorks的基石,你把它搞明白了,后面学消息队列、信号量、中断处理都会轻松很多。
下一章我们讲任务间的同步与通信,到时候会用到今天学的知识。记得多动手写代码,光看是学不会的。