目录
一、概念
任务间通信的机制:
1:全局变量:需要加锁或者信号量,去限制任务的获取。
2:消息队列:消息对列,被读走,就没了。也是避免了一个资源同时被多个任务获取的情况(广播情况除外)。
消息队列:用于任务之间的数据传递,通信。
发布数据采用的是指针传递而不是值传递,所以发布的消息不存在数据的拷贝。UCOSII有消息队列和消息邮箱。ucosiii中只有消息队列了。消息队列是用户创建的内核对象,数量不受限制。
消息邮箱:只能存放一个消息。用于通知某个事件发生,可以初始化为空邮箱;用于共享某些资源,可以初始化一条消息。
消息队列:可以包含多个消息。
中断服务程序(ISR)只能使用OSQPost()函数。消息队列的去读可以采用先进先出和后进先出(紧急的消息可以绕过队列中的消息直接传递给任务)。
下面是多任务等待:消息队列有一个列表(记录了所有正在等待获取消息的任务)。多个任务可以在一个消息队列中等待。消息被发布到队列中时,最高优先级的等待任务将获得此消息,发布方也可以向消息队列中的所有等待的任务广播这一消息。
1、创建消息队列
2、等待消息队列
3、向消息队列发送消息
二、OS_Q
【1.】
typedef struct os_q OS_Q;
【2.】
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 ------------------ */
//MsgQ记录了消息队列总大小NbrEntriesSize, 已使用的大小NbrEntries
OS_MSG_Q MsgQ; /* List of messages */
};
【3.】
typedef struct os_msg_q OS_MSG_Q;
【4.】
struct os_msg_q { /* OS_MSG_Q */
OS_MSG *InPtr; /* Pointer to next OS_MSG to be inserted in the queue */
OS_MSG *OutPtr; /* Pointer to next OS_MSG to be extracted from the queue */
OS_MSG_QTY NbrEntriesSize; /* Maximum allowable number of entries in the queue */
OS_MSG_QTY NbrEntries; /* Current number of entries in the queue */
OS_MSG_QTY NbrEntriesMax; /* Peak number of entries in the queue */
};
三、任务内建消息队列
1、OSTaskQPend 内建消息队列的 接收函数
void *OSTaskQPend (OS_TICK timeout,
OS_OPT opt,
OS_MSG_SIZE *p_msg_size,
CPU_TS *p_ts,
OS_ERR *p_err)
- opt:
- OS_OPT_PEND_BLOCKING:阻塞等待
- OS_OPT_PEND_NON_BLOCKING :没有消息就直接返回
- p_msg_size : 指向存放消息大小的变量
- p_ts : 指向一个时间戳,表明什么时候接收的这个消息的。不用可设为NULL。
2、OSTaskQPost 内建消息队列的 接收函数
#if OS_CFG_TASK_Q_EN > 0u //所以要求OS_CFG_TASK_Q_EN =1
void OSTaskQPost (OS_TCB *p_tcb,
void *p_void,
OS_MSG_SIZE msg_size,
OS_OPT opt,
OS_ERR *p_err)
- p_tcb:指向接收消息的任务的TCB,写NULL给自己发消息。
- p_void:指向将要发送的消息内容。
- msg_size:该消息内容的大小。
- opt:
- OS_OPT_POST_FIFO:消息 先进先出
- OS_OPT_POST_LIFO:消息 后进先出
- OS_OPT_POST_NO_SCHED:发送消息后不会进行任务调度,调用该函数的任务可以继续运行。
四、消息队列 示例
示例:3个任务,1个软件定时器,2个消息队列(其中一个模拟消息邮箱)。
任务mytask(优先级高):
创建2个消息队列,创建任务;每隔一段时间cli_Msg消息队列发送一个字符串。短时间内刷新data_Msg的总大小和剩余空间。(因为cli_Msg是模拟邮箱,只有一个消息的空间,监控他的空间大小意义不大)。
每隔一段时间向RaedAdTask的内建消息队列中发送一个消息。
任务disp_task(优先级中等):
每隔一段时间从data_Msg中等待一个消息。
任务cli_task(优先级最低):
每隔一段时间从data_Msg 和 cli_Msg中各等待一个消息。
软件定时器t1_callback(周期500ms):
每隔一段时间向data_Msg 和 cli_Msg中各发送一个消息。
RaedAdTask(优先级最高):
每隔一段时间从data_Msg 和 内建消息队列中各等待一个消息。
cli_Msg(模拟消息邮箱):
大小是1
data_Msg(消息队列):
大小是5
思路图如下:
主要代码如下:
注意:该代码仅供学习消息队列的机制,当t1定时器的频率很快,即t1_task回调的速度很快,各任务的延时时间不变。内存占用率就会增加很快,加到100就会死机。
现在代码改好了,内存占用率不会飞飙了。
(今天刚被老师傅拷打,老师傅修改原来的t1定时器的时钟频率,然后内存占用率飞速飙升,一会就死机了。老师傅告诫:慎用延时,警惕内存和指针。)
/*************my space***********************************************************/
/****** 显示的数据结构 ******/
typedef struct Disp{
uint8_t queue_remain_size;
uint8_t queue_all_size;
char str[20];
char t1_str[10];
}Disp_t;
Disp_t disp;
/****** 消息队列 ******/
#define CLI_MSG_Q_NUM 1 //命令消息队列的数量,消息队列的大小,用来模拟消息邮箱。
#define DATA_MSG_Q_NUM 5 //数据消息队列的数量
/****定义内核对象,消息队列***/
OS_Q cli_Msg;
OS_Q data_Msg;
/****** 定时器 ******/
uint8_t t1_cnt; //定时器计数
OS_TMR tmr1;
void t1_callback(void *p_tmr, void *p_arg);
/* MYTASK 任务 配置
* 包括: 任务优先级 任务栈大小 任务控制块 任务栈 任务函数
*/
#define MYTASK_PRIO (OS_CFG_PRIO_MAX - 15) /* 任务优先级 */
#define MYTASK_STK_SIZE 64 /* 任务栈大小 */
OS_TCB MyTask_TCB; /* 任务控制块 */
CPU_STK MyTask_STK[MYTASK_STK_SIZE]; /* 任务栈 */
void mytask(void *p_arg);
#define DISP_PRIO (OS_CFG_PRIO_MAX - 14) /* 任务优先级 */
#define DISP_STK_SIZE 64 /* 任务栈大小 */
OS_TCB DISP_TCB; /* 任务控制块 */
CPU_STK DISP_STK[DISP_STK_SIZE]; /* 任务栈 */
void disp_task(void *p_arg);
#define PROCESS_PRIO (OS_CFG_PRIO_MAX - 13) /* 任务优先级 */
#define PROCESS_STK_SIZE 64 /* 任务栈大小 */
OS_TCB PROCESS_TCB; /* 任务控制块 */
CPU_STK PROCESS_STK[PROCESS_STK_SIZE]; /* 任务栈 */
void cliprocess_task(void *p_arg);
#define INSIDE_TASK_Q_SIZE 5 //任务内建消息队列的大小,创建时需要用到
/**************************************************************************/
/************* 下面是start_task里面的 创建任务和软件定时器 ***********/
OSTaskCreate( (OS_TCB *)&RaedAdTaskTask_TCB,
#if (OS_CFG_DBG_EN == 0u)
(CPU_CHAR *)0,
#else
(CPU_CHAR *)"RaedAdTask",
#endif
(OS_TASK_PTR )RaedAdTask,
(void *)0,
(OS_PRIO )RaedAdTask_PRIO,
(CPU_STK *)RaedAdTaskTask_STK,
(CPU_STK_SIZE )RaedAdTask_STK_SIZE / 10,
(CPU_STK_SIZE )RaedAdTask_STK_SIZE,
(OS_MSG_QTY )INSIDE_TASK_Q_SIZE, //内建消息队列的大小
(OS_TICK )0,
(void *)0,
(OS_OPT )(OS_OPT_TASK_STK_CHK | (OS_OPT)(OS_OPT_TASK_STK_CLR | OS_OPT_TASK_NO_TLS)),
(OS_ERR *)&err);
OSTmrCreate( (OS_TMR *)&tmr1,
(CPU_CHAR *)"tmr1",
(OS_TICK )10, // 10*100=1000ms
(OS_TICK )5, //周期是5*100 ms
(OS_OPT )OS_OPT_TMR_PERIODIC,
(OS_TMR_CALLBACK_PTR)t1_callback,
(void *)0,
(OS_ERR *)&err
);
char tmr1_str[10]={0};
void t1_callback(void *p_tmr, void *p_arg){
OS_ERR err;
char *tmr1_str2 = mymalloc(10); //每个任务一个堆栈,
uint8_t *t1_cnt1 = mymalloc(1);
t1_cnt++;
*t1_cnt1 = t1_cnt;
sprintf(tmr1_str2, "t1 cnt:%d", t1_cnt);
memcpy(tmr1_str, tmr1_str2, 9);
//发送计数给 任务RaedAdTask
OSTaskQPost(
(OS_TCB *)&RaedAdTaskTask_TCB,
(void *)t1_cnt1,
(OS_MSG_SIZE)1,
(OS_OPT )OS_OPT_POST_FIFO,
(OS_ERR *)&err
);
if(err != OS_ERR_NONE)
{
myfree(t1_cnt1);
}
OSQPost(
(OS_Q *)&data_Msg,
(void *)tmr1_str2,
(OS_MSG_SIZE)10,
//(OS_OPT )OS_OPT_POST_FIFO+OS_OPT_POST_ALL,
(OS_OPT )OS_OPT_POST_FIFO,
(OS_ERR *)&err
);
if(err != OS_ERR_NONE)
{
myfree(tmr1_str2);
}
}
void mytask(void *p_arg){
OS_ERR err;
int i=0, cli=0;
//消息队列的创建
OSQCreate(
(OS_Q *)&cli_Msg,
(CPU_CHAR *)"cli Msg",
(OS_MSG_QTY)CLI_MSG_Q_NUM,
(OS_ERR *)&err
);
OSQCreate(
(OS_Q *)&data_Msg,
(CPU_CHAR *)"data Msg",
(OS_MSG_QTY)DATA_MSG_Q_NUM,
(OS_ERR *)&err
);
//创建命令处理任务
OSTaskCreate(
(OS_TCB *)&PROCESS_TCB,
(CPU_CHAR *)"cliprocess",
(OS_TASK_PTR )cliprocess_task,
(void *)0,
(OS_PRIO )PROCESS_PRIO,
(CPU_STK *)&PROCESS_STK[0],
(CPU_STK_SIZE)PROCESS_STK_SIZE / 10,
(CPU_STK_SIZE)PROCESS_STK_SIZE,
(OS_MSG_QTY )0,
(OS_TICK )0,
(void *)0,
(OS_OPT )OS_OPT_TASK_STK_CHK|OS_OPT_TASK_STK_CLR,
(OS_ERR *)&err
);
//创建数据显示任务
OSTaskCreate(
(OS_TCB *)&DISP_TCB,
(CPU_CHAR *)"disp_task",
(OS_TASK_PTR )disp_task,
(void *)0,
(OS_PRIO )DISP_PRIO,
(CPU_STK *)&DISP_STK[0],
(CPU_STK_SIZE)DISP_STK_SIZE / 10,
(CPU_STK_SIZE)DISP_STK_SIZE,
(OS_MSG_QTY )0,
(OS_TICK )0,
(void *)0,
(OS_OPT )OS_OPT_TASK_STK_CHK|OS_OPT_TASK_STK_CLR,
(OS_ERR *)&err
);
while(1){
i++;
if(i==11){ //2s发送一次消息
i=0;
cli++;
char *cli_str = mymalloc(10);
switch(cli){
case 1:
memcpy(cli_str,"cli is 1",10);
break;
case 2:
memcpy(cli_str,"cli is 2",10);
break;
case 3:
memcpy(cli_str,"cli is 3",10);
break;
case 4:
memcpy(cli_str,"cli is 4",10);
break;
}
// 发送命令
OSQPost(
(OS_Q *)&cli_Msg,
(void *)cli_str,
(OS_MSG_SIZE )10,
(OS_OPT )OS_OPT_POST_FIFO,
(OS_ERR *)&err
);
if(err != OS_ERR_NONE)
{
myfree(cli_str);
}
if(cli==4){
cli=0;
}
delay_ms(1000);
}
disp.queue_all_size = data_Msg.MsgQ.NbrEntriesSize;
disp.queue_remain_size = data_Msg.MsgQ.NbrEntriesSize - data_Msg.MsgQ.NbrEntries;
delay_ms(250);
}
}
//低优先级
char clipro_str[10];
void cliprocess_task(void *p_arg){
OS_ERR err;
char *str, recv_size;
memcpy(disp.str,"o-o",4);
while(1){
str = OSQPend(
(OS_Q *)&cli_Msg,
(OS_TICK )100,
(OS_OPT )OS_OPT_PEND_BLOCKING,
(OS_MSG_SIZE*)&recv_size,
(CPU_TS *)NULL,
(OS_ERR *)&err
);
if(err == OS_ERR_NONE)
{
memcpy(disp.str, str, recv_size);
myfree(str);
}
//memcpy(str,"is null",10);
//和 disp_task抢 t1_str的值
str = OSQPend(
(OS_Q *)&data_Msg,
(OS_TICK )100,
(OS_OPT )OS_OPT_PEND_BLOCKING,
(OS_MSG_SIZE*)&recv_size,
(CPU_TS *)NULL,
(OS_ERR *)&err
);
if(err == OS_ERR_NONE)
{
memcpy(clipro_str, str, recv_size);
memcpy(disp.t1_str, clipro_str, recv_size);
myfree(str);
}
//memcpy(str,"is null",10);
OSTimeDlyHMSM(0,0,0,200,OS_OPT_TIME_PERIODIC,&err); //延时1s
}
}
//高优先级
char disp_str[10];
void disp_task(void *p_arg){
OS_ERR err;
uint8_t i, recv_size;
char *str;
while(1){
str = OSQPend(
(OS_Q *)&data_Msg,
(OS_TICK )100,
(OS_OPT )OS_OPT_PEND_BLOCKING,
(OS_MSG_SIZE*)&recv_size,
(CPU_TS *)NULL,
(OS_ERR *)&err
);
if(err == OS_ERR_NONE)
{
memcpy(disp_str, str, recv_size);
memcpy(disp.t1_str, disp_str, recv_size);
myfree(str);
}
//memcpy(str,"is null",10);
OSTimeDlyHMSM(0,0,0,800,OS_OPT_TIME_PERIODIC,&err); //延时1s
}
}
char read_str[10];
void RaedAdTask(void *p_arg)
{
OS_ERR err;
uint8_t *read_cnt;
uint8_t recv_size=0;
char *str;
while(1)
{
//接收内建消息队列
read_cnt = OSTaskQPend(
(OS_TICK )100,
(OS_OPT )OS_OPT_PEND_BLOCKING,
(OS_MSG_SIZE*)&recv_size,
(CPU_TS *)NULL,
(OS_ERR *)&err
);
if(err == OS_ERR_NONE)
{
myfree(read_cnt);
}
//接收data_Msg 中 tmr1_str
str = OSQPend(
(OS_Q *)&data_Msg,
(OS_TICK )100,
(OS_OPT )OS_OPT_PEND_BLOCKING,
(OS_MSG_SIZE*)&recv_size,
(CPU_TS *)NULL,
(OS_ERR *)&err
);
if(err == OS_ERR_NONE)
{
memcpy(read_str, str, recv_size);
memcpy(disp.t1_str, read_str, recv_size);
myfree(str);
}
//memcpy(str,"is null",10);
OSTimeDlyHMSM(0,0,0,500,OS_OPT_TIME_PERIODIC,&err); //延时1s
}
}
运行结果:
1. 一开始我是 使用全局变量作为消息的载体,运行之后发现。怎么可能会有两个任务同时获取一个消息呢?消息队列就是为了解决资源竞争而实现的。所以哪里出了错误呢?消息队列传送的是发布消息的指针,全局变量那块内存一直存在,所以指针也一直存在。多个任务访问在极短的时间内(可能是还未及时标记该消息已被读取)访问同一块空间,当然出现这种情况。
然后我把所有xxxxPend函数获取消息后,把消息的内容修改为”is null“,运行后:disp.t1_str出现"is null x",这是我们不想要的结果。
所以,还是老老实实地用申请堆上的空间,来存放发送的消息吧,发送前申请空间,xxxxPend周就立刻释放掉空间,而不是使用全局变量。
我把xxxPost要发送的数据,放在了堆上,xxxxPend接收到后,都立即销毁。运行结果如下:是我向要的结果,没个任务只取一个消息,多任务获取消息不会重复。
还有一个小验证:
我把软件定时器t1的周期变为1s,即1s才能发送一次消息。可以理解为1s生产一次资源。然后我把优先级最高的任务RaedAdTask的延时变为500ms。我想要验证的是,当没生产一个消息,队列中都会有一个最高优先级的任务就绪,消息会不会被低优先级的任务拿走。即验证opt选项 OS_OPT_POST_FIFO是不是入截图所说的,会给等待队列中优先级最高的任务。根据运行结果来看 答案是肯定的。
然后我把 data_Msg消息队列的opt选项成:OS_OPT_POST_FIFO+OS_OPT_POST_ALL,出现以下结果:三个任务获取到同一个消息。 所以OS_OPT_POST_ALL表示的是广播,在本文有一小节(3、向消息队列发送消息)的一张截图写有”将消息发送给所有等待的任务“是OS_OPT_POST_ALL的功能。