ucosiii 消息队列

目录

一、概念

二、OS_Q 

三、任务内建消息队列

1、OSTaskQPend 内建消息队列的 接收函数

2、OSTaskQPost 内建消息队列的 接收函数

四、消息队列 示例


一、概念

任务间通信的机制:

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的功能。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值