UCOSIII任务之间通信
一个任务或者中断服务程序有时候需要和另一个任务交流信息,这个信息传递的过程称为任务间的通信。任务间的信息传递可以通过两种途径来实现:一是通过全局变量,二是通过发布消息。
使用全局变量的时候,每个任务或者中断服务程序都必须保证其对全局变量的独占访问(通常的解决办法:关中断、临界区、信号量)。消息也可以通过消息队列作为中介发布给任务。
消息
一则消息包括几个部分:指向数据的指针,表明数据长度的变量和记录消息发布时刻的时间戳。指针指向的可以是一块数据区或者一个函数。显然,发布方和接收方需要对数据的内容和含义达成约定。换句话说,消息的接收方需要知道所接收的消息的含义才能对其进行处理。
消息的内容必须一直保持可见性,因为发布数据采用的是引用传递,是指针传递而不是值传递。也就是说,发布的数据本身并不产生拷贝。
消息队列
消息队列是一种由用户程序分配的内核对象。用户可以分配任意数量的消息队列,唯一的限制就是可用的ARM区的容量。
在UCOSII中有消息邮箱和消息队列,但是在UCOSIII中只有消息队列。其实只能容纳一个消息的消息队列,可以看成是消息邮箱。消息队列是由用户创建的内核对[象,数量不限制,下图展示了用户可以对消息队列进行的操作:
从上图中可以看出,中断服务程序只能使用OSQPost()函数!
对消息队列的读取方式采用先进先出(fifo)的方式。在UCOSIII中,也可以采用后进先出(LIFO)的方式发布消息。当任务或者中断服务程序需要向一个任务发布一则紧急的消息时,后进先出的机制就非常有用了。使用后进先出的方式,发布的消息会绕过所有其他已经位于消息队列中的消息优先传递给任务。消息队列的容量可以在运行时进行配置。
消息队列的结构
UCOSIII中消息队列有关的结构体有三个,其关系如下:
OS_Q结构体
struct os_q { /* Message Queue */
/* ------------------ GENERIC MEMBERS ------------------ */
OS_OBJ_TYPE Type; /* Should be set to OS_OBJ_TYPE_Q */
CPU_CHAR *NamePtr; /* Pointer to Message Queue Name (NUL terminated ASCII) */
OS_PEND_LIST PendList; /* List of tasks waiting on message queue */
#if OS_CFG_DBG_EN > 0u
OS_Q *DbgPrevPtr;
OS_Q *DbgNextPtr;
CPU_CHAR *DbgNamePtr;
#endif
/* ------------------ SPECIFIC MEMBERS ------------------ */
OS_MSG_Q MsgQ; /* 消息队列 */
};
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结构体
struct os_msg { /* MESSAGE CONTROL BLOCK */
OS_MSG *NextPtr; /* 指向下一个消息的指针 */
void *MsgPtr; /* 消息内容 */
OS_MSG_SIZE MsgSize; /* 消息大小 */
CPU_TS MsgTS; /* 时间戳 */
};
这个结构体才算是消息队列中的消息的本体了。上文提到:消息一般包含:指向数据的指针,表明数据长度的变量和记录消息发布时刻的时间戳。
消息队列的API函数
如何创建消息队列
OSQCreate()函数用来创建一个消息队列,消息队列使得任务或者中断服务程序可以向一个或者多个任务发送消息。
void OSQCreate (OS_Q *p_q, //指向一个消息队列
CPU_CHAR *p_name, //消息队列的名字
OS_MSG_QTY max_qty, //指定消息队列的长度,必须大于0
OS_ERR *p_err)
{
CPU_SR_ALLOC();
OS_CRITICAL_ENTER();
p_q->Type = OS_OBJ_TYPE_Q; /* Mark the data structure as a message queue */
p_q->NamePtr = p_name;
OS_MsgQInit(&p_q->MsgQ, /* Initialize the queue */
max_qty);
OS_PendListInit(&p_q->PendList); /* Initialize the waiting list */
OSQQty++; /* One more queue created */
OS_CRITICAL_EXIT_NO_SCHED();
*p_err = OS_ERR_NONE;
}
max_qty:指定消息队列的长度,必须大于0。
等待消息队列
当一个任务想要从消息队列中接收一个消息的话就需要使用函数OSQPend()。
void *OSQPend (OS_Q *p_q, //指向一个消息队列
OS_TICK timeout, //等待消息的超时时间
OS_OPT opt, //用来选择是否使用阻塞模式
OS_MSG_SIZE *p_msg_size, //指向一个变量用来表示接收到的消息长度(字节数)
CPU_TS *p_ts, //指向一个时间戳
OS_ERR *p_err)
{
OS_PEND_DATA pend_data;
void *p_void;
CPU_SR_ALLOC();
if (p_ts != (CPU_TS *)0) {
*p_ts = (CPU_TS )0; /* Initialize the returned timestamp */
}
CPU_CRITICAL_ENTER();
p_void = OS_MsgQGet(&p_q->MsgQ, /* Any message waiting in the message queue? */
p_msg_size,
p_ts,
p_err);
if (*p_err == OS_ERR_NONE) {
CPU_CRITICAL_EXIT();
return (p_void); /* Yes, Return message received */
}
if ((opt & OS_OPT_PEND_NON_BLOCKING) != (OS_OPT)0) { /* Caller wants to block if not available? */
CPU_CRITICAL_EXIT();
*p_err = OS_ERR_PEND_WOULD_BLOCK; /* No */
return ((void *)0);
} else {
if (OSSchedLockNestingCtr > (OS_NESTING_CTR)0) { /* Can't pend when the scheduler is locked */
CPU_CRITICAL_EXIT();
*p_err = OS_ERR_SCHED_LOCKED;
return ((void *)0);
}
}
OS_CRITICAL_ENTER_CPU_CRITICAL_EXIT(); /* Lock the scheduler/re-enable interrupts */
OS_Pend(&pend_data, /* Block task pending on Message Queue */
(OS_PEND_OBJ *)((void *)p_q),
OS_TASK_PEND_ON_Q,
timeout);
OS_CRITICAL_EXIT_NO_SCHED();
OSSched(); /* Find the next highest priority task ready to run */
CPU_CRITICAL_ENTER();
switch (OSTCBCurPtr->PendStatus) {
case OS_STATUS_PEND_OK: /* Extract message from TCB (Put there by Post) */
p_void = OSTCBCurPtr->MsgPtr;
*p_msg_size = OSTCBCurPtr->MsgSize;
if (p_ts != (CPU_TS *)0) {
*p_ts = OSTCBCurPtr->TS;
}
*p_err = OS_ERR_NONE;
break;
case OS_STATUS_PEND_ABORT: /* Indicate that we aborted */
p_void = (void *)0;
*p_msg_size = (OS_MSG_SIZE)0;
if (p_ts != (CPU_TS *)0) {
*p_ts = OSTCBCurPtr->TS;
}
*p_err = OS_ERR_PEND_ABORT;
break;
case OS_STATUS_PEND_TIMEOUT: /* Indicate that we didn't get event within TO */
p_void = (void *)0;
*p_msg_size = (OS_MSG_SIZE)0;
if (p_ts != (CPU_TS *)0) {
*p_ts = (CPU_TS )0;
}
*p_err = OS_ERR_TIMEOUT;
break;
case OS_STATUS_PEND_DEL: /* Indicate that object pended on has been deleted */
p_void = (void *)0;
*p_msg_size = (OS_MSG_SIZE)0;
if (p_ts != (CPU_TS *)0) {
*p_ts = OSTCBCurPtr->TS;
}
*p_err = OS_ERR_OBJ_DEL;
break;
default:
p_void = (void *)0;
*p_msg_size = (OS_MSG_SIZE)0;
*p_err = OS_ERR_STATUS_INVALID;
break;
}
CPU_CRITICAL_EXIT();
return (p_void);
}
timeout:等待消息的超时时间,如果在指定的时间没有接收到消息的话,任务就会被唤醒,接着运行。这个参数也可以设置为0,表示任务将一直等待下去,直到接收到消息。
opt:用来选择是否使用阻塞模式。
OS_OPT_PEND_BLOCKING:如果没有任何消息存在的话就阻塞任务,一直等待,直到接收到消息。OS_OPT_PEND_NON_BLOCKING:如果消息队列没有任何消息的话任务就直接返回。
**p_ts:指向一个时间戳,**表明什么时候接收到消息。如果这个指针被赋值为NULL的话,说明用户没有要求时间戳。
向消息队列发送消息
可以通过函数OSQPost()向消息队列发送消息,如果消息队列是满的,则函数OSQPost()就会立刻返回,并且返回一个特定的错误代码。
void OSQPost (OS_Q *p_q, //指向一个消息队列
void *p_void, //指向实际发送的内容
OS_MSG_SIZE msg_size, //设定消息的大小,单位为字节数
OS_OPT opt, //用来选择消息发送操作的类型
OS_ERR *p_err)
{
CPU_TS ts;
ts = OS_TS_GET(); /* Get timestamp */
OS_QPost(p_q,
p_void,
msg_size,
opt,
ts,
p_err);
}
p_void:指向实际发送的内容,p_void是一个执行void类型的指针,其具体含义由用户程序的决定。