第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社区的惯例。比如tNetTasktLogTask。这样一看就知道是任务,不是变量或函数。

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 → 调用taskDelaytaskSuspend、等待信号量等

你想想看,如果两个任务优先级一样,又没有时间片调度,会怎样?

嗯,第一个任务会一直跑,第二个任务永远没机会。这就是所谓的「饿死」。所以同优先级任务一定要配合taskDelay或时间片调度来用。

4.4 优先级反转——一个经典问题

说到优先级,不得不提一个经典问题:优先级反转

我在一个军工项目里遇到过。一个高优先级任务等一个信号量,而这个信号量被一个低优先级任务占着。结果中优先级任务跑起来了,它不抢信号量,但抢了CPU。低优先级任务跑不了,信号量释放不了,高优先级任务就一直等。整个系统性能急剧下降。

VxWorks提供了优先级继承机制来解决这个问题。你可以在创建信号量时加上SEM_INVERSION_SAFE选项:

SEM_ID sem = semMCreate(SEM_Q_PRIORITY | SEM_INVERSION_SAFE);

这样,当低优先级任务持有信号量时,它会临时继承等待该信号量的最高优先级任务的优先级。等它释放信号量后,再恢复原来的优先级。

我的建议: 只要用到互斥信号量,就加上SEM_INVERSION_SAFE。别省这个选项,省了就是给自己挖坑。

4.5 实战避坑指南

最后,我总结几个实战中容易踩的坑:

  1. 栈溢出:任务栈给太小,函数调用层级深一点就崩了。我一般先给4096,跑起来后用checkStack命令看看实际用了多少,再调整。
  2. 优先级设太高:把系统任务(比如tNetTask、tLogTask)的优先级设得比用户任务还低,会导致网络不通、日志丢失。系统任务的优先级一般在50-100之间,用户任务建议100以上。
  3. 忘记加VX_FP_TASK:任务里用了浮点运算,但创建时没加这个选项。结果浮点寄存器被破坏,数据全乱套。这个bug我查了整整两天。
  4. 任务删除不干净:删了任务但没释放它申请的内存,造成内存泄漏。建议用taskSafetaskUnsafe配合使用,或者干脆让任务自己退出。

好了,这一章的内容就到这里。任务管理是VxWorks的基石,你把它搞明白了,后面学消息队列、信号量、中断处理都会轻松很多。

下一章我们讲任务间的同步与通信,到时候会用到今天学的知识。记得多动手写代码,光看是学不会的。