请稍侯

MQX机制分析——任务管理机制

06 March 2014

  使用操作系统的目的之一就是为了多任务运行,那么任务到底是什么?这一节会简单介绍MQX中的任务。
  任务是指在任务模板中注册的完成一定功能的函数,但是并不是所有的函数都可以被称为任务,任务有自己的特点,MQX的任务由三个部分组成:任务函数、任务堆栈、任务描述符。任务函数就是任务要完成具体功能的程序。每个任务拥有自己独立的任务栈空间,用于保存任务在调度时的上下文信息、任务内部使用的局部变量以及调用普通函数时保存返回地址等参数。任务描述符关联了任务代码的程序控制块,记录任务的各个属性。

  在MQX中,一个函数要向成为任务,必须要在任务模板中注册,因为MQX创建任务是根据任务模板里面的参数来创建的,一个任务模板可以创建多个任务实例,多个任务实例所执行的代码是一样的,只不过任务的名称和堆栈等参数不一样。当任务被创建的时候,便会被加入到kernel data中的TD_LIST成员中,这个TD_LIST是一个双向链表,所有的任务都在这个链表中。但是如果使用这个链表进行调度的话便会显得效率很低,于是又使用了就绪队列这个结构来供调度使用(在《启动流程》中有介绍),每个任务被将入到TD_LIST之后,还会将任务放入就绪队列来进行调度。

  在这里我们所说的队列一般指的双向链表,在MQX中还有其他的队列,比如当调用timne_delay函数把一个任务延时5ms的时候会将任务放入延时队列,_time_delay函数将当前系统时刻与传入的延时时间参数相加,得到该任务延时的“延时时刻”,即唤醒任务的时刻,并把它记录到任务描述符节点(TD_STRUCT)的成员TIMEOUT中,然后将任务从就绪任务队列中移除,以唤醒的时间递增的顺序插入到延时任务队列中。它由队头节点和任务描述符节点组成链表结构,延时任务队列的头节点在MQX系统初始化时创建,并将首地址记录在内核数据区的TIMEOUT_QUEUE指针成员中,便于对延时队列的访问。队头节点的SIZE记录当前延时队列的成员节点数,MAX指明延时队列最大允许的节点数,若MAX的值为0,则队列的长度没有限制。在每个systick的中断终端服务例程中,会对延时任务队列进行遍历,当系统的时间与任务的唤醒时刻一致时,就将这些任务唤醒,使其进入就绪态,回到由任务描述符节点成员MY_QUEUE指定的就绪队列,等待系统的调度。还有当我们使用一些任务同步的方法(如信号量、互斥等)时,任务在等待资源的时候,会将任务加到该资源的等待队列中,这个等待队列中记录的是所有等待该资源的任务,当占有该资源的任务将其释放后,系统从该资源的等待队列中取出第一个并且将它就绪,并且执行调度。

  回到之前所说的TD_LIST和就绪队列,任务描述符结构体(TD_STRUCT)中的前两个成员TD_NEXT和TD_PREV,这两个成员是TD_STRUCT类型的指针,用于将任务描述符链接起来形成链表,我们之前说每个任务创建时被将入到TD_LIST之后,还会将任务放入就绪队列,这样会出现一个问题:TD_STRUCT只有这两个成员用来链接前后成员的,也就是说当任务被加入到就绪队列中(就绪队列中的任务也是靠TD_NEXT和TD_PREV来维护的)的时候,那么它就会从TD_LIST里面删除。这一点要注意,因为在任务同步中轻量级信号量的等待函数(_lwsem_wait)中,当一个任务等待信号量时,会将这个任务阻塞掉,也就是从就绪队列中移除,但是等待函数中使用的是_QUEUE_UNLINK这个函数来从就绪队列中移除,这个函数的作用是将参数从任意一个双向链表中移除,因为此时任务是处于激活态,也就意味着任务已经不在TD_LIST中维护,而是在就绪队列中维护,调用_QUEUE_UNLINK其实是将任务从就绪队列中删除。所以搞不清楚这一点会以为是将任务从TD_LIST删除而未从就绪队列中删除。