除了仅供MultiPend时使用的成员,前四个成员很正常,作用一目了然,双向链表,直接指向了等待的任务TCB,不多分析了。另外多说一句,OS_PEND_DATA是在任务调用OSQPend时自动定义的一个变量,这与MultiPend调用略有不同,在MultiPend中等待多内核对象时,OS_PEND_DATA是手动分配的。两种方式中OS_PEND_DATA占用的都是任务自已的堆栈,要注意防止栈溢出。
这样等待该消息队列的“任务挂起表”数据结构就分析完了,主线如下:
OS_Q->OS_PEND_LIST<->OS_PEND_DATA <-> 任务TCB
正是这样的一套数据结构,实现了队列Q和等待它的TCB之间的连接。
题外话,OS_PEND_DATA个人认为它的出现纯粹是uC/OSIII为了实现MultiPend统一入口的作用(因为MultiPend要求任务也可以同时等待信号量),不然直接把 TCB挂在OS_PEND_LIST下面,本是一件多么清爽的事情。
下面再看消息队列OS_Q成员中的另一大结构分支:OS_MSG_Q,它的作用是以队列的形式管理消息。这也正是消息队列名称的由来。既有任务等待列表,又有消息存储列表,这样才构成了完整的消息队列结构。
OS_MSG_Q的结构定义如下:
struct os_msg_q { /* OS_MSG_Q */
OS_MSG *InPtr; /* 将要存储进来的消息 */
OS_MSG *OutPtr; /* 下一个要被推送的消息 */
OS_MSG_QTY NbrEntriesSize; /*允许存储的消息数量*/
OS_MSG_QTY NbrEntries; /* 当前有多少条消息 */
OS_MSG_QTY NbrEntriesMax; /* 最多时达到过多少条消息 */
};
可以认为,OS_MSG_Q就是消息队列OS_Q的管家,掌管着消息队列中的全部消息的你来我往。这个管家有权利指派下一个消息被存储在哪里,以及哪个消息将要被推送出去,场景就像排队买火车票时那个售票员,它有权利让你插队。同时OS_MSG_Q会完全按照主人OS_Q中定义的消息最多数量进行消息队列管理,这又像排队买火车票时那个售票员会突然对你大喊“我要下班了,你们后面的都不要排队了”一样。
可见,对于消息而言,OS_MSG_Q是掌握其命运的,OS_MSG_Q结构里的OS_MSG结构就是代表的这些消息。OS_MSG结构作为消息就需要有实体变量的,这些实体变量是在uCOSIII系统初始化时被定义,并且被永久的定义在那里,默认值为50个,在ucosiii/source文件夹的os_app_cfg.h文件里:
#define OS_CFG_MSG_POOL_SIZE 50u
在初始化的50个OS_MSG变量,由OS_MSG_POOL OSMsgPool来管理,它也是个管家,专门管理“没过门的丫头”,过了门的交由各自OS_Q的OS_MSG_Q来管理了,用完后OS_MSG_Q会把她们再踢回给OS_MSG_POOL。
那消息的模样究竟如何?下面就看一下消息的结构OS_MSG:
struct os_msg { /* MESSAGE CONTROL BLOCK */
OS_MSG *NextPtr; /* 指向下一条消息 */
void *MsgPtr; /* 消息真身 */
OS_MSG_SIZE MsgSize; /* 消息真身的长度 */
CPU_TS MsgTS; /* 时间截 */
};
确切地说,OS_MSG真的只是消息的结构,它是消息的载体,不是真身。仔细观察OS_MSG成员,就能发现它里面这个“void *MsgPtr和MsgSize” 这两个才是消息真身,它通常是指向一个全局变量的数组或者其他什么变量,消息正是通过这个指针来进行传递的。如果说OS_MSG是一封书信,那void *MsgPtr和MsgSize才是信的内容,这个内容只是“说”了一些坐标点,而坐标所指向的变量本身才是真正要传递的“小秘密”,可能是某处宝藏吧,也说不定。
至此消息存储的数据结构也看完了,大概流程如下:
OS_Q->OS_MSG_Q ->OS_MSG -> void *MsgPtr和MsgSize->宝藏
结合之前那条任务挂起表的主线,就形成了以下这条主线:
宝藏<-OS_Q<->任务TCB (注意TCB也反向指着OS_Q)
以上数据结构要牢记。接下来,才可以打开消息队列传递的大门。
对消息队列的基本操作是void OSQPost(OS_Q *p_q...)和void *OSQPend (OS_Q *p_q...)
注意OSQPend 函数为了节省一个传入参数,使用函数返回值作为获得的消息指针。
先看一下OSQPost函数,它的作用是完成“宝藏<-OS_Q”的环节,把消息挂接到对应的消息队列OS_Q上,函数的基本内容如下:
void OSQPost (OS_Q *p_q, /*要post到的消息队列*/
void *p_void, /*指向要传递的消息数据的指针*/
OS_MSG_SIZE msg_size, /*消息数据的长度,与指针配合使用*/
OS_OPT opt, /*选项:用于控制传递到队列头或尾;
是否推送给全部等待的TCB;
是否进行调度*/
OS_ERR *p_err) /*错误指针*/
{
CPU_TS ts;
...(大段的参数检查代码,此处略。)
ts = OS_TS_GET(); /* Get timestamp */
#if OS_CFG_ISR_POST_DEFERRED_EN > 0u /*如果使用中断中延迟推送方案,调用 OS_IntQPost函数进行post*/
if (OSIntNestingCtr > (OS_NESTING_CTR)0) { /*这里判断是否在中断中,延迟推送方案是为中断量身定制的,用于防止关中断时间太长,在其他地方不需要使用*/
OS_IntQPost((OS_OBJ_TYPE)OS_OBJ_TYPE_Q, /* Post to ISR queue */
(void *)p_q,
(void *)p_void,
(OS_MSG_SIZE)msg_size,
(OS_FLAGS )0,
(OS_OPT )opt,
(CPU_TS )ts,
(OS_ERR *)p_err);
return;
}
#endif
/*如果没在中断中,或者没有定义延迟中断推送,就直接调用OS_QPost函数进行推送*/
OS_QPost(p_q,
p_void,
msg_size,
opt,
ts,
p_err);
} |