任务创建与删除API实战:从入门到避坑

各位同学,今天我们来聊聊VxWorks里最基础也最要命的话题——任务的创建与删除。说实话,我见过太多工程师在taskSpawn()和taskDelete()上栽跟头了。我自己刚入行那会儿,也因为这个踩过不少坑。今天我就把压箱底的经验都掏出来,咱们一个一个说清楚。

taskSpawn()参数详解:你真的会用吗?

taskSpawn()是VxWorks里创建任务最常用的API。它的原型长这样:

int taskSpawn(
    char *name,         // 任务名
    int priority,       // 优先级
    int options,        // 选项
    int stackSize,      // 栈大小
    FUNCPTR entryPt,    // 入口函数
    int arg1,           // 参数1
    int arg2,           // 参数2
    ...
);

每个参数都有讲究,我一个个说。

name(任务名):说白了就是个标签,方便你调试时识别。我个人习惯用"t"开头,比如"tRecvTask"、"tSendTask"。注意名字最长不能超过31个字符,否则会被截断。

priority(优先级):0到255,数值越小优先级越高。0是最高,留给系统任务用的。我建议用户任务从50开始往上排。为什么?因为系统任务通常占0-49,你设太低容易跟系统抢资源,搞不好系统就崩了。

options(选项):这里有个坑。常用的选项有:

  • VX_FP_TASK:任务使用浮点运算
  • VX_NO_STACK_FILL:不填充栈(调试时建议别用)
  • VX_PRIVATE_ENV:私有环境

我遇到过最典型的问题:任务里用了浮点运算,但没加VX_FP_TASK选项。结果呢?浮点寄存器被别的任务污染了,算出来的数据全是错的。排查了整整两天才找到原因。

stackSize(栈大小):这个参数我建议宁大勿小。默认是20000字节,但实际项目中我一般给40000起步。为什么?你想想看,任务里调个函数、嵌套几个调用,栈空间就蹭蹭往上涨。栈溢出不会报错,只会悄无声息地覆盖别的内存,然后系统莫名其妙地崩溃。

entryPt(入口函数):就是任务启动后执行的第一个函数。注意这个函数不能返回,否则任务会异常终止。我习惯在函数末尾加个while(1)循环,或者用taskDelay()挂起。

参数1到参数10:最多传10个整型参数。如果需要传更多数据,我建议用指针传结构体地址。

实战经验:我写taskSpawn()时,一定会检查返回值。返回ERROR就说明创建失败,常见原因有内存不足、任务数超限。别偷懒,一定要加错误处理。

taskInit()与taskActivate()组合使用:更灵活的控制

taskSpawn()是一步到位,但有时候我们需要更精细的控制。这时候就要用taskInit() + taskActivate()组合了。

先看taskInit():

STATUS taskInit(
    WIND_TCB *pTcb,     // TCB指针
    char *name,         // 任务名
    int priority,       // 优先级
    int options,        // 选项
    int stackSize,      // 栈大小
    FUNCPTR entryPt,    // 入口函数
    int arg1,           // 参数1
    ...
);

taskInit()只是初始化任务控制块(TCB),但任务还没开始运行。这时候你可以做点手脚,比如修改任务的某些属性。等准备好了,再调用taskActivate()让它跑起来。

taskActivate(tid);  // tid是taskInit返回的任务ID

为什么要这么麻烦?我举个例子。有一次我需要创建一个任务,但它的优先级要在运行时根据系统负载动态调整。用taskSpawn()的话,优先级一创建就固定了。但用taskInit(),我可以在初始化后、激活前修改优先级,然后再启动。

小技巧:taskInit()分配的内存需要你自己管理。记得在删除任务后释放TCB和栈空间,否则会内存泄漏。

taskDelete()的陷阱:删任务不是闹着玩的

taskDelete()看起来简单:

STATUS taskDelete(int tid);

但这里面的坑,我敢说十个工程师有八个踩过。

陷阱一:任务正在使用共享资源

假设任务A正在操作一个互斥信号量,你突然把它删了。信号量没释放,别的任务就永远等在那里了。这就是死锁。

陷阱二:任务持有内存锁

任务正在malloc分配内存,你把它删了。内存分配器的内部锁没释放,整个系统的内存分配就瘫痪了。

陷阱三:任务正在执行关键操作

比如正在写文件、正在操作硬件寄存器。突然删除,文件可能损坏,硬件可能处于未知状态。

我曾经踩过的坑:有一次在通信模块里,我直接删了一个正在发送数据的任务。结果呢?网卡DMA缓冲区没清干净,后续所有数据包都乱了。排查了整整一个下午,最后发现是taskDelete()惹的祸。

那正确的做法是什么?我建议用事件标志或消息队列来通知任务自己退出。任务收到退出信号后,先释放资源,再调用taskDelete()删除自己。

// 安全删除任务的示例
void myTask(void)
{
    while (1)
    {
        // 检查退出标志
        if (exitFlag == TRUE)
        {
            // 释放资源
            semGive(mySem);
            // 删除自己
            taskDelete(taskIdSelf());
        }
        // 正常任务逻辑
        ...
    }
}

另外,千万别在中断服务程序里调用taskDelete()。中断上下文不能执行任务删除操作,这是VxWorks的硬性规定。我见过有人这么干,结果系统直接挂掉。

总结一下

任务创建和删除看似简单,但每个细节都可能成为系统的隐患。我的建议是:

  • 创建任务时,栈大小给足,选项别漏
  • 需要灵活控制时,用taskInit()+taskActivate()
  • 删除任务前,确保资源已释放
  • 永远不要在中断里删任务

嗯,今天就聊到这儿。下节课我们讲任务优先级反转,那才是真正让人头疼的问题。到时候我给你们讲讲我当年是怎么被优先级反转折磨的。