接下来由MultiPend的任务自己判断就绪的是队列还是信号量,然后提取出相应的消息内容指针,这个是任务处理中自己的家事,由写应用的程序员到时操心,这里就不再关心了。
消息推送过程到此结束。
这里还要再增加一些内容,就是uC/OSIII里有任务消息队列,这个消息队列与OS_Q的区别就是:不需要定义OS_Q,因为它是在任务TCB定义时,被直接定义在任务TCB里面了!伴随任务一生。uC/OSIII真的很舍得,看一下它的定义:
struct os_tcb {
......
#if OS_CFG_TASK_Q_EN > 0u
OS_MSG_Q MsgQ;
......
}
可见,在任务TCB中定义的是OS_MSG_Q,而不是OS_Q,为什么呢?前面说过OS_Q中包含两个重要的主线,这里再把它们列写如下:
OS_Q->OS_PEND_LIST<->OS_PEND_DATA <-> 任务TCB
OS_Q->OS_MSG_Q ->OS_MSG -> void *MsgPtr和MsgSize->宝藏
可见,任务TCB与宝藏相连的纽带就是OS_Q,那既然任务TCB自己都可以包含消息队列了,还要OS_Q干啥,是不是。前面又说过,OS_MSG_Q就是消息队列OS_Q的管家,所以任务TCB中直接定义OS_MSG_Q,找到宝藏就得了呗。
任务队列推送函数叫OSTaskQPost(),里面调用的是OS_TaskQPost(),该函数是被定义在ucosiii\source文件夹下的os_task.c中的,它与普通OS_QPost()函数是同样的过程,里面也是调用OS_MsgQPut()进行无任务等待时的推送,调用OS_Post()进行本任务等待时的推送。唯一不同的是,它的输入参数中不是*OS_Q类型,而*TCB,省去了通过队列再查找TCB的过程,所以它的推送是非常快的,是直接推送。这也是uC/OSIII建议使用的消息队列推送方式。
个人认为,uC/OSIII不惜浪费TCB空间打造任务信号量,任务队列,目的就是要减少使用普通信号量和普通队列,因为进程间通信通常都是点对点的,这将大幅度提高效率。而普通信号量和普通队列存在的唯一目的,就是多任务Post和MultiPend这两种特殊情况,而uC/OSIII又指出,这两种特殊情况都是可能会长时间关中断的,建议少用。
消息队列推送机制基本就这些了,还剩下点边边角角的不值得再继续深入。
下面就是另一个重要方向,消息等待。uC/OSIII中的消息等待又分为三部分:普通消息队列等待函数void *OSQPend();任务消息队列等待函数void *OSTaskQPend();多对象等待函数OS_OBJ_QTY OSMultiPend()。
这里重点看第一个,任务调用void *OSQPend()后即进入等待消息状态。
OSQPend()函数是一个比较长的函数(通常接收器都比发送器要复杂一点),但简单讲,它可以分为两大部分:
一、准备进入任务挂起状态,将TCB写入到对应的要等待的消息队列下面的任务挂起表中;
然后执行调试,当前任务阻塞,其他任务执行;
二、收到消息后,从pend状态返回来,继续执行,把收到的消息指针取出来。
注意这两大部分的执行通常都是时间上分开的,但在空间上却是在一起的,就是代码被写在同一个函数里,这也正是Pend()函数的特点。下面分开介绍:
状态一,准备进入任务挂起状态,将TCB写入到对应的要等待的消息队列下面的任务挂起表中 。在这个过程中,Pend()函数做了几下几方面工作:先检查要pend的消息队列中是否已经有之前被post过来的消息存储在里面,如果有,就省事了,直接返回,不pend;另外,如果在输入参数中指定了不pend,或者是在中断中执行的,都不能pend,必须立即返回;如果没有之前的消息被存储,也没有在中断中,也指定了要pend,则准备进入阻塞等待状态,将挂起表等数据结构都准备好,将TCB写入其中。
二、收到消息后,从pend状态返回来,继续执行,如果是正常post过来的消息,就把收到的消息指针取出来,这是正常返回的情况。也有可能是等待超时,或者是消息队列被删除了,或者是pend被人为的abort了,这些异常情况都要进行判断拦截,然后返回空指针,并返回一个错误。 |