例程的主程序:
#include <std.h>
#include <log.h>
#include <mbx.h>
#include <tsk.h>
#include "mailboxcfg.h"
#define NUMMSGS 3 /* number of messages */
#define TIMEOUT 10
typedef struct MsgObj {
Int id; /* writer task id */
Char val; /* message value */
} MsgObj, *Msg;
Void reader(Void);
Void writer(Arg id_arg);
/*
* ======== main ========
*/
Void main()
{
/* Does nothing */
}
/*
* ======== reader ========
*/
Void reader(Void)
{
MsgObj msg;
Int i;
for (i=0; ;i++) {
/* wait for mailbox to be posted by writer() */
if (MBX_pend(&mbx, &msg, TIMEOUT) == 0) {
LOG_printf(&trace, "timeout expired for MBX_pend()");
break;
}
/* print value */
LOG_printf(&trace, "read '%c' from (%d).", msg.val, msg.id);
}
LOG_printf(&trace, "reader done.");
}
/*
* ======== writer ========
*/
Void writer(Arg id_arg)
{
MsgObj msg;
Int i;
Int id = ArgToInt (id_arg);
for (i=0; i < NUMMSGS; i++) {
/* fill in value */
msg.id = id;
msg.val = i % NUMMSGS + (Int)('a');
/* enqueue message */
MBX_post(&mbx, &msg, TIMEOUT);
LOG_printf(&trace, "(%d) writing '%c' ...", id, (Int)msg.val);
/* what happens if you call TSK_yield() here? */
// TSK_yield();
}
// LOG_printf(&trace, "writer (%d) done.", id);
}
程序建立了四个任务,一个 reader 任务,三个 writer 任务,三个 writer 任务调用的是同一个任务函数 writer(),但传递的函数参数是不同的,分别是0,1,2;四个任务的优先级是一样的,任务创建顺序是:reader->writer0->writer1->writer2。邮箱大小为8字节,2个字的长度。
程序运行结果如下
下面我们就来分析下这个demo是如何得到上述结果的:首先我们要清楚程序执行的顺序是怎样的,在这里我简单画一个程序的流程图。
程序在执行完main()函数后,最先执行的是reader()函数,在执行MBX_pend函数时发现邮箱内为空,故此任务被阻塞,只有等到系统调用MBX_post函数后才可以继续运行。这时候程序就执行到writer0任务了,writer0任务是往邮箱里写数据,由于邮箱的长度是2个字节,所以写入两个字节后,邮箱已满,任务切换至reader任务,就是打印出邮箱内的内容,同时清空邮箱内的数据。接着MBX_pend又被阻塞,继续执行writer0任务,writer0剩下的任务只能往邮箱里再写入一个字节,所以writer1任务被启动,此时邮箱又满了,故切换任务至reader读取数据。按照这种演示的步骤,推理出来的结果与真实输出的结果是不同的,那么问题出在哪里呢?
我在查阅了一些前辈的资料后终于明白mailbox有一个自己的任务队列,在这里借用前辈们一个生动的例子来说明下:
假设有ABC三队人在排队买票,每队3人,分别叫A1A2A3B1B2B3C1C2C3.他们在售票大厅外面的广场排队,售票大厅有一个门,要进大厅的人,必须在门前排成一队,然后才能进去.从各自的队伍排到门口的队伍,按照ABC的顺序,门口的队伍每队只能有一人.大厅里面有2个窗口在卖票,如果两个窗口都有人在买,那么后面的人只能排在门口,不能进大厅.只有当在两个窗口买票的人都离开后,排在后面的前两个人才能进大厅.进入大厅的人,如果自己的队伍还有人,必须通知下一个人过来排队.那么看一下买票和排队的顺序.一开始A1A2到门口,大厅空,两人进去,通知A3过来排队
A3到门口,里面满了,在门口排队,A队已经一人在门外,就轮到B队了B1到门口排队,排在A3后面,B组结束,轮到C队,C1排到B1后面,C队结束.现在开始第一轮卖票,A1A2买完走人,大门可以进人,A3进大厅,因为A队没人了,不用通知,接着B1进入大厅,顺便通知B队的B2过来排队,B2只能排在C1后面.
大厅又满,禁止进人,开始卖票,A3B1买完走人.大门开,C1进门,通知C2过来排队,C2排在B2后面,B2进门,通知B3来排队,B3排在C2后边.大厅满,开始卖票,C1B2买完走人.大厅空,门开,C2进,通知C3,C3排在B3后边,B3进大厅,B队没人,不用通知,大厅满,开始卖票,C2B3买完走人,大厅空,门开,C3进,后边没人,不用通知,由于所有队都排完,大厅虽然没满,也开始卖票,C3买完走人.卖票的人等了一会,发现没人来买了,就关门回家了.买票的顺序就是A1A2A3B1 C1B2 C2B3 C3,再和tsk和mbx类比,三个tsk就是ABC三条队伍,每次要post一个数据,就是来门口排队,而门口的队伍就是mbx的任务队列,只有mbx中的内容被读完,才会依次启动任务队列中的任务。
按照这个例子的意思我画了个示意图:
通过修改邮箱大小以及每个任务写入邮箱字节数的大小,做了一些测试,发现当邮箱数目小于任务的数目时,上述理论是正确的,但是一旦邮箱数目超过任务数目时,任务在排队时就会出现相同任务排在一个队伍中,就与上述理论发生了冲突,也就无法知晓此时的任务队列是怎样的了,MBX_pend 和 MBX_post函数都是TI提供的API接口函数,闭源的结果是我们并不清楚其函数内部是如何来进行任务队列设置的,所以这部分内容还是有待研究的。