任务基础:任务控制块(TCB)、任务状态与状态转换、任务创建与删除

好,咱们正式开始聊任务。很多初学者觉得任务不就是个函数吗?嗯,这么说也对,但不够准确。在VxWorks里,任务是一个有“身份”的实体。这个身份,就记录在任务控制块(TCB)里。

1. 任务控制块(TCB)——任务的“身份证”

每个任务被创建时,内核都会给它分配一个TCB。你可以把TCB想象成一张档案卡,上面写着这个任务的所有关键信息。

TCB里到底存了什么?

  • 任务ID(TID):唯一标识,就像你的身份证号。我调试时经常用 taskIdSelf() 来获取当前任务的ID。
  • 优先级:决定了谁先抢到CPU。这个后面会细讲。
  • 栈指针和栈大小:每个任务都有自己的栈。栈给太小,任务容易崩;给太大,浪费内存。我曾经在一个项目中,因为栈只给了1024字节,结果任务一跑复杂逻辑就挂,查了两天才发现是栈溢出。
  • 程序计数器(PC):记录任务执行到哪一行代码了。
  • 任务状态:就绪、阻塞、挂起……下面会讲。
  • 错误状态:任务出错了,这里会记录错误码。

核心观点:TCB是内核管理任务的根本。你写的每个任务,内核都是通过TCB来感知它的存在。说白了,没有TCB,任务就是个孤魂野鬼。

2. 任务状态与状态转换——任务的一生

任务不是一直在跑的。它会在几个状态之间跳来跳去。理解这个,对后面做优先级设计特别重要。

VxWorks里主要有这几个状态:

状态 含义 我常遇到的情况
READY 就绪态。任务啥都准备好了,就等CPU。 高优先级任务一就绪,低优先级的就得让路。
PEND 阻塞态。任务在等某个资源(比如信号量、消息队列)。 我曾经调试一个死锁问题,发现两个任务都在PEND状态,谁也不让谁。
SUSPEND 挂起态。任务被强制暂停,不参与调度。 调试时我常用 taskSuspend 临时停掉一个任务,看看系统反应。
DELAY 延时态。任务调用 taskDelay() 后进入。 说白了就是任务主动说:“我先睡一会儿,到点叫我。”
RUNNING 运行态。任务正在使用CPU。 单核CPU下,同一时刻只有一个任务在RUNNING状态。

状态是怎么转换的?

举个例子:你创建了一个任务,它先进入READY态。调度器一看,嗯,你优先级最高,让你跑。它就变成RUNNING。跑着跑着,它要等一个信号量,但信号量被别人占着,它就乖乖进入PEND态。等信号量释放了,它又回到READY态,等着再次被调度。

我的小技巧:调试时用 i 命令(或 taskShow)查看所有任务的状态。如果发现某个任务长期卡在PEND,十有八九是资源没释放。我曾经靠这招抓出过好几个隐藏的bug。

3. 任务创建与删除——taskSpawn / taskDelete

创建任务,最常用的就是 taskSpawn。删除任务,用 taskDelete。这两个函数,你写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
    ...
);

创建任务时,我踩过的坑:

  • 栈大小给太小:任务一运行就崩。我建议至少给4096字节起步,复杂任务给8192以上。
  • 优先级给太高:系统任务(比如tExcTask)优先级是0,你把自己的任务也设成0,可能会把系统搞死。我个人习惯,应用任务优先级设在50-200之间。
  • options没设对:如果任务里用浮点运算,一定要加 VX_FP_TASK,否则结果全乱套。

taskDelete 怎么用?

STATUS taskDelete(int tid);  // 删除指定ID的任务

删除任务时,千万注意:

警告:不要随便删除一个正在使用共享资源的任务!比如它正拿着一个互斥信号量,你把它删了,信号量就永远没人释放了。其他等这个信号量的任务会永久阻塞。这就是典型的死锁隐患。

我曾经在一个项目中,为了快速清理异常任务,直接调了 taskDelete。结果系统跑着跑着就卡死了。查了半天,发现被删的任务正占着一个内存池的锁。从那以后,我删除任务前都会先确保它已经释放了所有资源。

避坑指南:如果你不确定任务是否安全,可以用 taskSafetaskUnsafe 来保护关键区域。但说实话,最好的办法是让任务自己退出(比如在入口函数里return),而不是外部强制删除。

好了,任务的基础就这些。你想想看,TCB是任务的身份证,状态是任务的当前处境,创建和删除是任务的生与死。把这些搞明白了,后面讲优先级和死锁,你才能听得更透。