μC/OSIII学习day7
介绍
消息队列
# 消息队列
# 消息队列的基本概念
- 队列又称消息队列,是一种常用于任务间通信的数据结构,队列可以在任务与任务间,中断和任务间传递信息,实现了任务接收来自其他任务或中断的不固定长度的消息,任务能够从队列里面读取消息,当队列中的消息是空时,读取消息的任务将被阻塞,用户还可以指定阻塞的任务时间timeout,在这段时间中,如果队列为空,该任务将保持阻塞状态以等待队列数据有效.当队列中有新消息时,被阻塞的任务会被唤醒并处理新消息;当等待的时间超过了指定的阻塞时间,即使队列中尚无有效数据,任务也会自动从阻塞态转为就绪态.
- 消息队列是一种异步的通信方式.
- 通过消息队列服务,任务或中断服务程序可以将消息放入消息队列中.同样,一个或多个任务可以从消息队列中获得消息.当有多个消息发送到消息队列时,通常是将先进入消息队列的消息先传给任务,也就是说,任务先得到的是最先进入消息队列的消息,即先进先出原则(FIFO),但是μC/OS也支持后进先出原则(LIFO).
# 消息队列的工作过程
- 在μC/OSIII中定义了一个数组
OSCfg_MsgPool[OS_CFG_MSG_POOL_SIZE]
,因为在使用消息队列的时候存取消息比较频繁,在系统初始化的时候就将这个大数组的各个元素串成单向链表,组成我们说的消息池,而这些元素我们称之为消息,为什么这里是单向链表而不是我们之前在各种列表中看到的双向链表?因为消息的存取并不需要从链表中间,只需在链表的首尾存取即可,单向链表即够用,使用双向链表反而更复杂.消息池的大小OS_CFG_MSG_POOL_SIZE
(os_cfg_app.h)由用户自己定义. - 消息队列的处理速度很快,同时共用了资源,系统中所有被创建的队列都可以从消息池中取出消息,挂载到自身的队列上,以表示消息队列拥有消息,当消息使用完毕,则又会被释放回到消息池中,其他队列也可以从中取出消息,这样子的消息资源是能被系统所有的消息队列反复使用.
# 消息池初始化
在运行到
OSInit()
系统初始化函数时,系统就会将消息池进行初始化,其中OS_MsgPoolInit()
(定义在os_msg.c中)就是消息池初始化函数.void OS_MsgPoolInit(OS_ERR *p_err){ OS_MSG *p_msg1; OS_MSG *p_msg2; OS_MSG_QTY i; OS_MSG_QTY loops; #ifdef OS_SAFETY_CRITICAL//如果启用(默认禁用)了安全检测 if (p_err == (OS_ERR *)0) { //如果错误类型实参为空 OS_SAFETY_CRITICAL_EXCEPTION(); //执行安全检测异常函数 return; //返回,停止执行 } #endif #if OS_CFG_ARG_CHK_EN > 0u//如果启用了参数检测 if (OSCfg_MsgPoolBasePtr == (OS_MSG *)0) {//如果消息池不存在 *p_err = OS_ERR_MSG_POOL_NULL_PTR; //错误类型为"消息池指针为空" return; //返回,停止执行 } if (OSCfg_MsgPoolSize == (OS_MSG_QTY)0) { //如果消息池不能存放消息 *p_err = OS_ERR_MSG_POOL_EMPTY; //错误类型为"消息池为空" return; //返回,停止执行 } #endif /* 将消息池里的消息逐条串成单向链表,方便管理 */ p_msg1 = OSCfg_MsgPoolBasePtr; p_msg2 = OSCfg_MsgPoolBasePtr; p_msg2++; loops = OSCfg_MsgPoolSize - 1u; for (i = 0u; i < loops; i++) { //初始化每一条消息 p_msg1->NextPtr = p_msg2; p_msg1->MsgPtr = (void *)0; p_msg1->MsgSize = (OS_MSG_SIZE)0u; p_msg1->MsgTS = (CPU_TS )0u; p_msg1++; p_msg2++; } p_msg1->NextPtr = (OS_MSG *)0; //最后一条消息 p_msg1->MsgPtr = (void *)0; p_msg1->MsgSize = (OS_MSG_SIZE)0u; p_msg1->MsgTS = (CPU_TS )0u; /* 初始化消息池数据 */ OSMsgPool.NextPtr = OSCfg_MsgPoolBasePtr; OSMsgPool.NbrFree = OSCfg_MsgPoolSize; OSMsgPool.NbrUsed = (OS_MSG_QTY)0; OSMsgPool.NbrUsedMax = (OS_MSG_QTY)0; *p_err = OS_ERR_NONE; //错误类型为"无错误" }
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48如果启用了安全检测
OS_SAFETY_CRITICAL
这个宏定义,那么在编译代码的时候就会包含安全检测,如果p_err的指针为空,系统会执行安全检测异常函数OS_SAFETY_CRITICAL_EXCEPTION()
,然后退出.如果启用了参数检测
OS_CFG_ARG_CHK_EN
这个宏定义,那么在编译代码的时候会包含参数检测,如果消息池不存在,系统会返回错误类型为"消息池指针为空"的错误代码,然后退出,不执行初始化操作;如果消息池不能存放消息,系统会返回错误类型为"消息池为空"的错误代码,然后退出,也不执行初始化操作.每个消息有四个元素
- NextPtr:指向下一个可用的消息.
- MsgPtr:指向实际的消息.
- MsgSize:记录消息的大小(以字节为单位).
- MsgTS:记录发送消息时的时间戳.
OSMsgPool是个全局变量,用来管理内存池的存取操作,它包含以下四个元素.
- NextPtr :指向下一个可用的消息.
- NbrFree :记录消息池中可用的消息个数.
- NbrUsed:记录已用的消息个数.
- NbrUsedMax:记录使用的消息峰值数量.
# 消息队列的运作机制
- μC/OS的消息队列控制块由多个元素组成,当消息队列被创建时,编译器会静态为消息队列分配对应的内存空间(因为我们需要自己定义一个消息队列控制块),用于保存消息队列的一些信息如队列的名字,队列可用的最大消息个数,入队指针,出队指针等.在创建成功的时候,这些内存就被占用了,创建队列的时候用户指定队列的最大消息个数,无法再次更改,每个消息空间可以存放任意类型的数据.
- 任务或者中断服务程序都可以给消息队列发送消息,当发送消息时,如果队列未满,μC/OS会将从消息池中取出一个消息,将消息挂载到队列的尾部,消息中的成员变量
MsgPtr
指向要发送的消息.如果队列已满,则返回错误代码,入队失败. - μC/OS还支持发送紧急消息,也就是我们所说的后进先出(LIFO排队,其过程与发送消息几乎一样,唯一的不同是,当发送紧急消息时,发送的消息会挂载到队列的队头而非队尾,这样,接收者就能够优先接收到紧急消息,从而及时进行消息处理.
- 当某个任务试图读一个队列时,可以指定一个阻塞超时时间.在这段时间中,如果队列为空,该任务将保持阻塞状态以等待队列数据有效.当其他任务或中断服务程序往其等待的队列中写入了数据,该任务将自动由阻塞态转移为就绪态.当等待的时间超过了指定的阻塞时间,即使队列中尚无有效数据,任务也会自动从阻塞态转移为就绪态.
- 当消息队列不再被使用时,可以对它进行删除操作,一旦删除操作完成,消息队列将被永久性的删除,所有关于队列的信息会被清空,知道再次创建才可使用.
# 消息队列的阻塞机制
- 我们使用的消息队列一般不是属于某个任务的队列,在很多时候,我们创建的队列,是每个任务都可以去对他进行读写操作的,但是为了保护每个任务对它进行读操作的过程(μC/OS队列的写操作是没有阻塞的),我们必须要有阻塞机制,在某个任务对它读操作的时候,必须保证该任务能正常完成读操作,而不受后来的任务干扰.
- μC/OS消息队列这种先来后到的机制,每个对消息队列读的函数,都有这种机制,我称之为阻塞机制.假设有一个任务A对某个队列进行读操作的时候(也就是我们所说的出队),发现它没有消息,那么此时任务A有3个选择:第一个选择,任务A扭头就走,既然队列没有消息,那我也不等了,干其他事情去,这样子任务A不会进入阻塞态;第二个选择,任务A还是在这里等等吧,可能过一会队列就有消息,此时任务A会进入阻塞状态,在等待着消息的道来,而任务A的等待时间就由我们自己定义,比如设置1000个系统时钟节拍tick的等待,在这1000个tick到来之前任务A都是处于阻塞态,当阻塞的这段时间任务A等到了队列的消息,那么任务A就会从阻塞态变成就绪态,如果此时任务A比当前运行的任务优先级还高,那么,任务A就会得到消息并且运行;假如 1000个tick都过去了,队列还没消息,那任务A就不等了,从阻塞态中唤醒,返回一个没等到消息的错误代码,然后继续执行任务A的其他代码;第三个选择,任务A死等,不等到消息就不走了,这样子任务A就会进入阻塞态,直到完成读取队列的消息.
- 假如有多个任务阻塞在一个消息队列中,那么这些阻塞的任务将按照任务优先级进行排序,优先级高的任务将优先获得队列的访问权.
- 如果发送消息的时候用户选择广播消息,那么在等待中的任务都会收到一样的消息.
# 消息队列的应用场景
- 消息队列可以应用于发送不定长消息的场合,包括任务与任务间的消息交换,队列是μC/OS中任务与任务间,中断与任务间主要的通讯方式,发送到队列的消息是通过引用方式实现的,这意味着队列存储的是数据的地址,我们可以通过这个地址将这个数据读取出来,这样子,无论数据量是多大,其操作时间都是一定的,只是一个指向数据地址指针.
# 消息队列的结构
μC/OS的消息队列由多个元素组成,在信号量被创建时,需要由我们自己定义消息队列(也可以称之为消息队列句柄),因为它是用于保存消息队列的一些信息的,其数据结构OS_Q除了队列必须的一些基本信息外,还有PendList链表与MsgQ,为的是方便系统来管理消息队列.
struct os_q{ /* ------------------ GENERIC MEMBERS ------------------ */ OS_OBJ_TYPE Type; CPU_CHAR *NamePtr; OS_PEND_LIST PendList; #if OS_CFG_DBG_EN > 0u OS_Q *DbgPrevPtr; OS_Q *DbgNextPtr; CPU_CHAR *DbgNamePtr; #endif /* ------------------ SPECIFIC MEMBERS ------------------ */ OS_MSG_Q MsgQ; };
1
2
3
4
5
6
7
8
9
10
11
12
13
14Type
:消息队列的类型.*NamePtr
:消息队列的名字.PendList
:等待消息队列的任务列表.MsgQ
:消息列表.
消息队列
OS_MSG_Q
的结构struct os_msg_q{ /* OS_MSG_Q */ OS_MSG *InPtr;/* 指向要插入队列的下一个 OS_MSG 的指针 */ OS_MSG *OutPtr;/* 指向要从队列中提取的下一个 OS_MSG 的指针 */ OS_MSG_QTY NbrEntriesSize;/* 队列中允许的最大消息个数 */ OS_MSG_QTY NbrEntries;/* 队列中当前的消息个数 */ OS_MSG_QTY NbrEntriesMax;/* 队列中的消息个数峰值 */ };
1
2
3
4
5
6
7
8
9*InPtr
,*OutPtr
:队列中消息也是用单向链表串联起来的,但存取消息不像消息池只是从固定的一端.队列存取消息有两种方式,一种是 FIFO模式,即先进先出,这个时候消息的存取是在单向链表的两端,一个头一个尾,存取位置可能不一样就产生了这两个输入指针和输出指针,具体见图FIFO模式.另一种是LIFO模式,后进先出,这个时候消息的存取都是在单向链表的一端,仅仅用OutPtr就足够指示存取的位置,具体见图LIFO模式.当队列中已经存在比较多的消息没有处理,这个时候有个紧急的消息需要马上传送到其他任务去的时候就可以在发布消息的时候选择LIFO模式.NbrEntries
:记录消息队列中当前的消息个数,每发送一个消息,若没有任务在等待该消息队列的消息,那么新发送的消息被插入此消息队列后此值加1,NbrEntries
的大小不能超过NbrEntriesSize
.
# 消息队列常用函数
函数名称 | 函数作用 |
---|---|
OSQCreate() | 创建消息队列 |
OS_MsgQInit() | 初始化消息队列 |
OSQDel() | 删除消息队列 |
OS_PendObjDel() | 将阻塞在内核对象(如信号量)上的任务从阻塞态恢复 |
OS_QDel() | 删除消息队列 |
OSQPost() | 给消息队列发送消息 |
OS_QPost() | 给消息队列发送消息 |
OS_MsgQPut() | 将消息放入队列中,执行完毕就退出 |
OS_Post() | 在发送消息完成的时候,进行一次任务调度 |
OSQPend() | 获取消息队列 |
OS_MsgQGet() | 从消息队列获取一个消息 |
OS_Pend() | 将当前任务脱离就序列表,并根据用户指定的阻塞时间插入节拍列表和队列等待列表,打开调度器,但不进行调度 |
创建消息队列函数OSQCreate()
使用消息队列必须先声明和创建消息队列,队列是一种数据结构,用于任务间的数据的传递.每创建一个新的队列都需要为其分配RAM,在创建的时候我们需要自己定义一个消息队列结构体,其内存是由编译器自动分配的.
void OSQCreate (OS_Q *p_q, //消息队列指针 CPU_CHAR *p_name, //消息队列名称 OS_MSG_QTY max_qty, //消息队列大小(不能为 0) OS_ERR *p_err){ //返回错误类型 CPU_SR_ALLOC();//使用到临界段(在关/开中断时)时必须用到该宏,该宏声明和 //定义一个局部变量,用于保存关中断前的 CPU 状态寄存器 // SR(临界段关中断只需保存 SR),开中断时将该值还原 #ifdef OS_SAFETY_CRITICAL//如果启用了安全检测 if (p_err == (OS_ERR *)0) { //如果错误类型实参为空 OS_SAFETY_CRITICAL_EXCEPTION(); //执行安全检测异常函数 return; //返回,停止执行 } #endif #ifdef OS_SAFETY_CRITICAL_IEC61508 //如果启用了安全关键 //如果在调用 OSSafetyCriticalStart() 后创建 if (OSSafetyCriticalStartFlag == DEF_TRUE) { *p_err = OS_ERR_ILLEGAL_CREATE_RUN_TIME; //错误类型为"非法创建内核对象" return; //返回,停止执行 } #endif #if OS_CFG_CALLED_FROM_ISR_CHK_EN > 0u//如果启用了中断中非法调用检测 if (OSIntNestingCtr > (OS_NESTING_CTR)0) { //如果该函数是在中断中被调用 *p_err = OS_ERR_CREATE_ISR; //错误类型为"在中断中创建对象" return; //返回,停止执行 } #endif #if OS_CFG_ARG_CHK_EN > 0u //如果启用了参数检测 if (p_q == (OS_Q *)0) { //如果 p_q 为空 *p_err = OS_ERR_OBJ_PTR_NULL; //错误类型为"创建对象为空" return; //返回,停止执行 } if (max_qty == (OS_MSG_QTY)0) { //如果 max_qty = 0 *p_err = OS_ERR_Q_SIZE; //错误类型为"队列空间为 0" return; //返回,停止执行 } #endif OS_CRITICAL_ENTER(); //进入临界段 p_q->Type = OS_OBJ_TYPE_Q; //标记创建对象数据结构为消息队列 p_q->NamePtr = p_name; //标记消息队列的名称 OS_MsgQInit(&p_q->MsgQ, //初始化消息队列 max_qty); OS_PendListInit(&p_q->PendList); //初始化该消息队列的等待列表 #if OS_CFG_DBG_EN > 0u//如果启用了调试代码和变量 OS_QDbgListAdd(p_q); //将该队列添加到消息队列双向调试链表 #endif OSQQty++; //消息队列个数加 1 OS_CRITICAL_EXIT_NO_SCHED(); //退出临界段(无调度) *p_err = OS_ERR_NONE; //错误类型为"无错误" }
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55ps:消息队列创建完成的示意图
在创建消息队列的时候,是需要用户自己定义消息队列的句柄的,但是注意了,定义了队列的句柄并不等于创建了队列,创建队列必须是调用消息队列创建函数进行创建,否则,以后根据队列句柄使用消息队列的其他函数的时候会发生错误,用户通过消息队列句柄就可使用消息队列进行发送与获取消息的操作,用户可以根据返回的错误代码进行判断消息队列是否创建成功,消息队列创建函数
OSQCreate()
使用实例具如下OS_Q queue; //声明消息队列 OS_ERR err; /* 创建消息队列 queue */ OSQCreate ((OS_Q *)&queue, //指向消息队列的指针 (CPU_CHAR *)"Queue For Test", //队列的名字 (OS_MSG_QTY )20, //最多可存放消息的数目 (OS_ERR *)&err); //返回错误类型
1
2
3
4
5
6
7
8
9初始化消息队列函数OS_MsgQInit()
void OS_MsgQInit(OS_MSG_Q *p_msg_q, //消息队列指针 OS_MSG_QTY size){ //消息队列空间 p_msg_q->NbrEntriesSize = (OS_MSG_QTY)size; //消息队列可存放消息数目 p_msg_q->NbrEntries = (OS_MSG_QTY)0; //消息队列目前可用消息数 p_msg_q->NbrEntriesMax = (OS_MSG_QTY)0; //可用消息数的最大历史记录 p_msg_q->InPtr = (OS_MSG *)0; //队列的入队指针 p_msg_q->OutPtr = (OS_MSG *)0; //队列的出队指针 }
1
2
3
4
5
6
7
8
9消息队列删除函数OSQDel()
队列删除函数是根据队列结构(队列句柄)直接删除的,删除之后这个消息队列的所有信息都会被系统清空,而且不能再次使用这个消息队列了,但是需要注意的是,如果某个消息队列没有被定义,那也是无法被删除的.想要使用消息队列删除函数就必须将
OS_CFG_Q_DEL_EN
宏定义配置为1.#if OS_CFG_Q_DEL_EN > 0u//如果启用了 OSQDel() 函数 OS_OBJ_QTY OSQDel(OS_Q *p_q,//消息队列指针 OS_OPT opt, //选项 OS_ERR *p_err){ //返回错误类型 OS_OBJ_QTY cnt; OS_OBJ_QTY nbr_tasks; OS_PEND_DATA *p_pend_data; OS_PEND_LIST *p_pend_list; OS_TCB *p_tcb; CPU_TS ts; CPU_SR_ALLOC(); //使用到临界段(在关/开中断时)时必须用到该宏,该宏声明和 //定义一个局部变量,用于保存关中断前的 CPU 状态寄存器 // SR(临界段关中断只需保存 SR),开中断时将该值还原. #ifdef OS_SAFETY_CRITICAL//如果启用(默认禁用)了安全检测 if (p_err == (OS_ERR *)0) { //如果错误类型实参为空 OS_SAFETY_CRITICAL_EXCEPTION(); //执行安全检测异常函数 return ((OS_OBJ_QTY)0); //返回 0(有错误),停止执行 } #endif #if OS_CFG_CALLED_FROM_ISR_CHK_EN > 0u //如果启用了中断中非法调用检测 if (OSIntNestingCtr > (OS_NESTING_CTR)0) { //如果该函数在中断中被调用 *p_err = OS_ERR_DEL_ISR; //错误类型为"在中断中中止等待" return ((OS_OBJ_QTY)0); //返回 0(有错误),停止执行 } #endif #if OS_CFG_ARG_CHK_EN > 0u //如果启用了参数检测 if (p_q == (OS_Q *)0) { //如果 p_q 为空 *p_err = OS_ERR_OBJ_PTR_NULL; //错误类型为"对象为空" return ((OS_OBJ_QTY)0u); //返回 0(有错误),停止执行 } switch (opt) { (7)//根据选项分类处理 case OS_OPT_DEL_NO_PEND: //如果选项在预期内 case OS_OPT_DEL_ALWAYS: break; //直接跳出 default: *p_err = OS_ERR_OPT_INVALID; //如果选项超出预期 return ((OS_OBJ_QTY)0u); //返回 0(有错误),停止执行 } #endif #if OS_CFG_OBJ_TYPE_CHK_EN > 0u //如果启用了对象类型检测 if (p_q->Type != OS_OBJ_TYPE_Q) { //如果 p_q 不是消息队列类型 *p_err = OS_ERR_OBJ_TYPE; //错误类型为"对象类型有误" return ((OS_OBJ_QTY)0); //返回 0(有错误),停止执行 } #endif CPU_CRITICAL_ENTER(); //关中断 p_pend_list = &p_q->PendList; //获取消息队列的等待列表 cnt = p_pend_list->NbrEntries; //获取等待该队列的任务数 nbr_tasks = cnt; //按照任务数目逐个处理 switch (opt) { //根据选项分类处理 case OS_OPT_DEL_NO_PEND: //如果只在没有任务等待的情况下删除队列 if (nbr_tasks == (OS_OBJ_QTY)0) { //如果没有任务在等待该队列 #if OS_CFG_DBG_EN > 0u //如果启用了调试代码和变量 OS_QDbgListRemove(p_q); //将该队列从消息队列调试列表移除 #endif OSQQty--; //消息队列数目减 1 OS_QClr(p_q); //清除该队列的内容 CPU_CRITICAL_EXIT(); //开中断 *p_err = OS_ERR_NONE; //错误类型为"无错误" } else { //如果有任务在等待该队列 CPU_CRITICAL_EXIT(); //开中断 *p_err = OS_ERR_TASK_WAITING; //错误类型为"有任务在等待该队列" } break; case OS_OPT_DEL_ALWAYS: //如果必须删除信号量 OS_CRITICAL_ENTER_CPU_EXIT(); //进入临界段,重开中断 ts = OS_TS_GET(); //获取时间戳 while (cnt > 0u) { //逐个移除该队列等待列表中的任务 p_pend_data = p_pend_list->HeadPtr; p_tcb = p_pend_data->TCBPtr; OS_PendObjDel((OS_PEND_OBJ *)((void *)p_q), p_tcb, ts); cnt--; } #if OS_CFG_DBG_EN > 0u//如果启用了调试代码和变量 OS_QDbgListRemove(p_q); //将该队列从消息队列调试列表移除 #endif OSQQty--; //消息队列数目减 1 OS_QClr(p_q); //清除消息队列内容 OS_CRITICAL_EXIT_NO_SCHED(); //退出临界段(无调度) OSSched(); //调度任务 *p_err = OS_ERR_NONE; //错误类型为"无错误" break; //跳出 default://如果选项超出预期 CPU_CRITICAL_EXIT(); //开中断 *p_err = OS_ERR_OPT_INVALID; //错误类型为"选项非法" break; //跳出 } return (nbr_tasks); //返回删除队列前等待其的任务数 } #endif
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103阻塞恢复函数OS_PendObjDel()
void OS_PendObjDel (OS_PEND_OBJ *p_obj, //被删除对象的类型 OS_TCB *p_tcb, //任务控制块指针 CPU_TS ts){ //信号量被删除时的时间戳 switch (p_tcb->TaskState) //根据任务状态分类处理 { case OS_TASK_STATE_RDY: //如果任务是就绪状态 case OS_TASK_STATE_DLY: //如果任务是延时状态 case OS_TASK_STATE_SUSPENDED: //如果任务是挂起状态 case OS_TASK_STATE_DLY_SUSPENDED: //如果任务是在延时中被挂起 break; //这些情况均与等待无关,直接跳出 case OS_TASK_STATE_PEND: //如果任务是无期限等待状态 case OS_TASK_STATE_PEND_TIMEOUT: //如果任务是有期限等待状态 if (p_tcb->PendOn == OS_TASK_PEND_ON_MULTI)//如果任务在等待多个信号量或消息队列 { OS_PendObjDel1(p_obj, //强制解除任务对某一对象的等待 p_tcb, ts); } #if (OS_MSG_EN > 0u)//如果启用了任务队列或消息队列 p_tcb->MsgPtr = (void *)0; //清除(复位)任务的消息域 p_tcb->MsgSize = (OS_MSG_SIZE)0u; #endif p_tcb->TS = ts; //保存等待被中止时的时间戳到任务控制块 OS_PendListRemove(p_tcb); //将任务从所有等待列表中移除 OS_TaskRdy(p_tcb); //让任务进准备运行 p_tcb->TaskState = OS_TASK_STATE_RDY; //修改任务状态为就绪状态 p_tcb->PendStatus = OS_STATUS_PEND_DEL;//标记任务的等待对象被删除 p_tcb->PendOn = OS_TASK_PEND_ON_NOTHING;//标记任务目前没有等待任何对象 break; //跳出 case OS_TASK_STATE_PEND_SUSPENDED: //如果任务在无期限等待中被挂起 case OS_TASK_STATE_PEND_TIMEOUT_SUSPENDED://如果任务在有期限等待中被挂起 if (p_tcb->PendOn == OS_TASK_PEND_ON_MULTI)//如果任务在等待多个信号量或消息队列 { OS_PendObjDel1(p_obj, //强制解除任务对某一对象的等待 p_tcb, ts); } #if (OS_MSG_EN > 0u) //如果启用了任务队列或消息队列 p_tcb->MsgPtr = (void *)0;//清除(复位)任务的消息域 p_tcb->MsgSize = (OS_MSG_SIZE)0u; #endif p_tcb->TS = ts; //保存等待被中止时的时间戳到任务控制块 OS_TickListRemove(p_tcb); //让任务脱离节拍列表 OS_PendListRemove(p_tcb); //将任务从所有等待列表中移除 p_tcb->TaskState = OS_TASK_STATE_SUSPENDED; //修改任务状态为挂起状态 p_tcb->PendStatus = OS_STATUS_PEND_DEL; //标记任务的等待对象被删除 p_tcb->PendOn = OS_TASK_PEND_ON_NOTHING; //标记任务目前没有等待任何对象 break; //跳出 default: //如果任务状态超出预期 break; //不需处理,直接跳出 } }
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58消息队列删除函数OSQDel()
系统只应删已创建的消息队列.如果删除消息队列时,有任务正在等待消息,则不应该进行删除操作,删除之后的消息队列就不可用了.实例如下
OS_Q queue; //声明消息队列 OS_ERR err; /* 删除消息队列 queue */ OSQDel ((OS_Q *)&queue, //指向消息队列的指针 OS_OPT_DEL_NO_PEND, (OS_ERR *)&err); //返回错误类型
1
2
3
4
5
6
7
8
9消息队列发送函数OSQPost()
任务或者中断服务程序都可以给消息队列发送消息,当发送消息时,如果队列未满,就说明运行信息入队.μC/OS会从消息池中取出一个消息,挂载到消息队列的末尾(FIFO发送方式),如果是LIFO发送方式,则将消息挂载到消息队列的头部,然后将消息中MsgPtr成员变量指向要发送的消息(此处可以理解为添加要发送的信息到消息(块)中),如果系统有任务阻塞在消息队列中,那么在发送了消息队列的时候,会将任务解除阻塞.
void OSQPost(OS_Q *p_q, //消息队列指针 void *p_void, //消息指针 OS_MSG_SIZE msg_size, //消息大小(单位:字节) OS_OPT opt, //选项 OS_ERR *p_err){ CPU_TS ts; #ifdef OS_SAFETY_CRITICAL //如果启用(默认禁用)了安全检测 if (p_err == (OS_ERR *)0) { //如果错误类型实参为空 OS_SAFETY_CRITICAL_EXCEPTION(); //执行安全检测异常函数 return; //返回,停止执行 } #endif #if OS_CFG_ARG_CHK_EN > 0u//如果启用了参数检测 if (p_q == (OS_Q *)0) { //如果 p_q 为空 *p_err = OS_ERR_OBJ_PTR_NULL; //错误类型为"内核对象为空" return; //返回,停止执行 } switch (opt) { //根据选项分类处理 case OS_OPT_POST_FIFO: //如果选项在预期内 case OS_OPT_POST_LIFO: case OS_OPT_POST_FIFO | OS_OPT_POST_ALL: case OS_OPT_POST_LIFO | OS_OPT_POST_ALL: case OS_OPT_POST_FIFO | OS_OPT_POST_NO_SCHED: case OS_OPT_POST_LIFO | OS_OPT_POST_NO_SCHED: case OS_OPT_POST_FIFO | OS_OPT_POST_ALL | OS_OPT_POST_NO_SCHED: case OS_OPT_POST_LIFO | OS_OPT_POST_ALL | OS_OPT_POST_NO_SCHED: break; //直接跳出 default: //如果选项超出预期 *p_err = OS_ERR_OPT_INVALID; //错误类型为"选项非法" return; //返回,停止执行 } #endif #if OS_CFG_OBJ_TYPE_CHK_EN > 0u //如果启用了对象类型检测 if (p_q->Type != OS_OBJ_TYPE_Q) { //如果 p_q 不是消息队列类型 *p_err = OS_ERR_OBJ_TYPE; //错误类型为"对象类型错误" return; //返回,停止执行 } #endif ts = OS_TS_GET(); //获取时间戳 #if OS_CFG_ISR_POST_DEFERRED_EN > 0u//如果启用了中断延迟发布 if (OSIntNestingCtr > (OS_NESTING_CTR)0) { //如果该函数在中断中被调用 OS_IntQPost((OS_OBJ_TYPE)OS_OBJ_TYPE_Q, //将该消息发布到中断消息队列 (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(p_q, //将消息按照普通方式 p_void, msg_size, opt, ts, p_err); }
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67ps:发送消息的选项
#define OS_OPT_POST_FIFO (OS_OPT)(0x0000u)/* 默认采用 FIFO 方式发送 */ #define OS_OPT_POST_LIFO (OS_OPT)(0x0010u)/* 采用 LIFO 方式发送消息 */ #define OS_OPT_POST_1 (OS_OPT)(0x0000u)/* 将消息发布到最高优先级的等待任务 */ #define OS_OPT_POST_ALL (OS_OPT)(0x0200u)/* 向所有等待的任务广播消息 */ #define OS_OPT_POST_NO_SCHED (OS_OPT)(0x8000u)/* 发送消息但是不进行任务调度 */
1
2
3
4
5
6OSQPend()使用实例
OS_Q queue; //声明消息队列 OS_ERR err; OS_MSG_SIZE msg_size; /* 获取消息队列 queue 的消息 */ pMsg = OSQPend ((OS_Q *)&queue, //消息变量指针 (OS_TICK )0, //等待时长为无限 (OS_OPT )OS_OPT_PEND_BLOCKING, //如果没有获取到信号量就等待 (OS_MSG_SIZE *)&msg_size, //获取消息的字节大小 (CPU_TS *)0, //获取任务发送时的时间戳 (OS_ERR *)&err); //返回错误
1
2
3
4
5
6
7
8
9
10
11
12消息发送函数OS_QPost()
如果不是在中断中调用
OSQPost()
函数,或者未启用中断延迟发布,则可直接调用OS_QPost()
函数进行消息的发送.void OS_QPost (OS_Q *p_q, //消息队列指针 void *p_void, //消息指针 OS_MSG_SIZE msg_size, //消息大小(单位:字节) OS_OPT opt, //选项 CPU_TS ts, //消息被发布时的时间戳 OS_ERR *p_err){ OS_OBJ_QTY cnt; OS_OPT post_type; OS_PEND_LIST *p_pend_list; OS_PEND_DATA *p_pend_data; OS_PEND_DATA *p_pend_data_next; OS_TCB *p_tcb; CPU_SR_ALLOC(); //使用到临界段(在关/开中断时)时必须用到该宏,该宏声明和 //定义一个局部变量,用于保存关中断前的 CPU 状态寄存器 // SR(临界段关中断只需保存 SR),开中断时将该值还原. OS_CRITICAL_ENTER(); //进入临界段 p_pend_list = &p_q->PendList; //取出该队列的等待列表 if (p_pend_list->NbrEntries == (OS_OBJ_QTY)0) //如果没有任务在等待该队列 { if ((opt & OS_OPT_POST_LIFO) == (OS_OPT)0)//把消息发布到队列的末端 { post_type = OS_OPT_POST_FIFO; } else {//把消息发布到队列的前端 post_type = OS_OPT_POST_LIFO; } OS_MsgQPut(&p_q->MsgQ, //把消息放入消息队列 p_void, msg_size, post_type, ts, p_err); OS_CRITICAL_EXIT(); //退出临界段 return; //返回,执行完毕 } /* 如果有任务在等待该队列 */ if ((opt & OS_OPT_POST_ALL) != (OS_OPT)0) //如果要把消息发布给所有等待任务 { cnt = p_pend_list->NbrEntries; //获取等待任务数目 } else {//如果要把消息发布给一个等待任务 cnt = (OS_OBJ_QTY)1; //要处理的任务数目为 1 } p_pend_data = p_pend_list->HeadPtr; //获取等待列表的头部(任务) while (cnt > 0u) //根据要发布的任务数目逐个发布 { p_tcb = p_pend_data->TCBPtr; p_pend_data_next = p_pend_data->NextPtr; OS_Post((OS_PEND_OBJ *)((void *)p_q), //把消息发布给任务 p_tcb, p_void, msg_size, ts); p_pend_data = p_pend_data_next; cnt--; } OS_CRITICAL_EXIT_NO_SCHED(); //退出临界段(无调度) if ((opt & OS_OPT_POST_NO_SCHED) == (OS_OPT)0)//如果没选择"发布完不调度任务" { OSSched(); //任务调度 } *p_err = OS_ERR_NONE; //错误类型为"无错误" }
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64消息入队函数OS_MsgQPut()
将消息放入队列中,执行完毕就退出
void OS_MsgQPut(OS_MSG_Q *p_msg_q, //消息队列指针 void *p_void, //消息指针 OS_MSG_SIZE msg_size, //消息大小(单位:字节) OS_OPT opt, //选项 CPU_TS ts, //消息被发布时的时间戳 OS_ERR *p_err){ OS_MSG *p_msg; OS_MSG *p_msg_in; #ifdef OS_SAFETY_CRITICAL//如果启用了安全检测 if (p_err == (OS_ERR *)0) //如果错误类型实参为空 { OS_SAFETY_CRITICAL_EXCEPTION(); //执行安全检测异常函数 return; //返回,停止执行 } #endif if (p_msg_q->NbrEntries >= p_msg_q->NbrEntriesSize) //如果消息队列已没有可用空间 { *p_err = OS_ERR_Q_MAX; //错误类型为"队列已满" return; //返回,停止执行 } if (OSMsgPool.NbrFree == (OS_MSG_QTY)0) //如果消息池没有可用消息 { *p_err = OS_ERR_MSG_POOL_EMPTY; //错误类型为"消息池没有消息" return; //返回,停止执行 } /* 从消息池获取一个消息(暂存于 p_msg )*/ p_msg = OSMsgPool.NextPtr; //将消息控制块从消息池移除 OSMsgPool.NextPtr = p_msg->NextPtr; //指向下一个消息(取走首个消息) OSMsgPool.NbrFree--; //消息池可用消息数减 1 OSMsgPool.NbrUsed++; //消息池被用消息数加 1 if (OSMsgPool.NbrUsedMax < OSMsgPool.NbrUsed) //更新消息被用最大数目的历史记录 { OSMsgPool.NbrUsedMax = OSMsgPool.NbrUsed; } /* 将获取的消息插入消息队列 */ if (p_msg_q->NbrEntries == (OS_MSG_QTY)0) //如果消息队列目前没有消息 { p_msg_q->InPtr = p_msg; //将其入队指针指向该消息 p_msg_q->OutPtr = p_msg; //出队指针也指向该消息 p_msg_q->NbrEntries = (OS_MSG_QTY)1; //队列的消息数为 1 p_msg->NextPtr = (OS_MSG *)0; //该消息的下一个消息为空 } else {//如果消息队列目前已有消息 if ((opt & OS_OPT_POST_LIFO) == OS_OPT_POST_FIFO) //如果用 FIFO 方式插入队列 { p_msg_in = p_msg_q->InPtr;//将消息插入入队端,入队 p_msg_in->NextPtr = p_msg; //指针指向该消息. p_msg_q->InPtr = p_msg; p_msg->NextPtr = (OS_MSG *)0; } else {//如果用 LIFO 方式插入队列, p_msg->NextPtr = p_msg_q->OutPtr; //将消息插入出队端,出队 p_msg_q->OutPtr = p_msg; //指针指向该消息. } p_msg_q->NbrEntries++; //消息队列的消息数目加 1 } if (p_msg_q->NbrEntriesMax < p_msg_q->NbrEntries) //更新改消息队列的最大消息 { p_msg_q->NbrEntriesMax = p_msg_q->NbrEntries; //数目的历史记录. } p_msg->MsgPtr = p_void; //给该消息填写消息内容 p_msg->MsgSize = msg_size; //给该消息填写消息大小 p_msg->MsgTS = ts; //填写发布该消息时的时间戳 *p_err = OS_ERR_NONE; //错误类型为"无错误" }
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66发送完消息的任务调度函数OS_Post()
在发送消息完成的时候,进行一次任务调度
void OS_Post(OS_PEND_OBJ *p_obj, //内核对象类型指针 OS_TCB *p_tcb, //任务控制块 void *p_void, //消息 OS_MSG_SIZE msg_size, //消息大小 CPU_TS ts){ //时间戳 switch (p_tcb->TaskState) //根据任务状态分类处理 { case OS_TASK_STATE_RDY: //如果任务处于就绪状态 case OS_TASK_STATE_DLY: //如果任务处于延时状态 case OS_TASK_STATE_SUSPENDED: //如果任务处于挂起状态 case OS_TASK_STATE_DLY_SUSPENDED: //如果任务处于延时中被挂起状态 break; //不用处理,直接跳出 case OS_TASK_STATE_PEND: //如果任务处于无期限等待状态 case OS_TASK_STATE_PEND_TIMEOUT: //如果任务处于有期限等待状态 if (p_tcb->PendOn == OS_TASK_PEND_ON_MULTI) //如果任务在等待多个信号量或消息队列 { OS_Post1(p_obj, //标记哪个内核对象被发布 p_tcb, p_void, msg_size, ts); } else {//如果任务不是在等待多个信号量或消息队列 #if (OS_MSG_EN > 0u) //如果启用了任务队列或消息队列 p_tcb->MsgPtr = p_void; //保存消息到等待任务 p_tcb->MsgSize = msg_size; #endif p_tcb->TS = ts; //保存时间戳到等待任务 } if (p_obj != (OS_PEND_OBJ *)0) //如果内核对象不为空 { OS_PendListRemove(p_tcb); //从等待列表移除该等待任务 #if OS_CFG_DBG_EN > 0u//如果启用了调试代码和变量 OS_PendDbgNameRemove(p_obj, //移除内核对象的调试名 p_tcb); #endif } OS_TaskRdy(p_tcb); //让该等待任务准备运行 p_tcb->TaskState = OS_TASK_STATE_RDY; //任务状态改为就绪状态 p_tcb->PendStatus = OS_STATUS_PEND_OK; //清除等待状态 p_tcb->PendOn = OS_TASK_PEND_ON_NOTHING; //标记不再等待 break; case OS_TASK_STATE_PEND_SUSPENDED: //如果任务在无期限等待中被挂起 case OS_TASK_STATE_PEND_TIMEOUT_SUSPENDED: //如果任务在有期限等待中被挂起 if (p_tcb->PendOn == OS_TASK_PEND_ON_MULTI) //如果任务在等待多个信号量或消息队列 { OS_Post1(p_obj, //标记哪个内核对象被发布 p_tcb, p_void, msg_size, ts); } else {//如果任务不在等待多个信号量或消息队列 #if (OS_MSG_EN > 0u)//如果启用了调试代码和变量 p_tcb->MsgPtr = p_void; //保存消息到等待任务 p_tcb->MsgSize = msg_size; #endif p_tcb->TS = ts; //保存时间戳到等待任务 } OS_TickListRemove(p_tcb); //从节拍列表移除该等待任务 if (p_obj != (OS_PEND_OBJ *)0) //如果内核对象为空 { OS_PendListRemove(p_tcb); //从等待列表移除该等待任务 #if OS_CFG_DBG_EN > 0u//如果启用了调试代码和变量 OS_PendDbgNameRemove(p_obj, //移除内核对象的调试名 p_tcb); #endif } p_tcb->TaskState = OS_TASK_STATE_SUSPENDED; //任务状态改为被挂起状态 p_tcb->PendStatus = OS_STATUS_PEND_OK; //清除等待状态 p_tcb->PendOn = OS_TASK_PEND_ON_NOTHING; //标记不再等待 break; default: //如果任务状态超出预期 break; //直接跳出 }
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79消息队列获取函数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(); //使用到临界段(在关/开中断时)时必须用到该宏,该宏声明和 //定义一个局部变量,用于保存关中断前的 CPU 状态寄存器 // SR(临界段关中断只需保存 SR),开中断时将该值还原. #ifdef OS_SAFETY_CRITICAL //如果启用(默认禁用)了安全检测 if (p_err == (OS_ERR *)0) //如果错误类型实参为空 { OS_SAFETY_CRITICAL_EXCEPTION(); //执行安全检测异常函数 return ((void *)0); //返回 0(有错误),停止执行 } #endif #if OS_CFG_CALLED_FROM_ISR_CHK_EN > 0u //如果启用了中断中非法调用检测 if (OSIntNestingCtr > (OS_NESTING_CTR)0) //如果该函数在中断中被调用 { *p_err = OS_ERR_PEND_ISR; //错误类型为"在中断中中止等待" return ((void *)0); //返回 0(有错误),停止执行 } #endif #if OS_CFG_ARG_CHK_EN > 0u //如果启用了参数检测 if (p_q == (OS_Q *)0) //如果 p_q 为空 { *p_err = OS_ERR_OBJ_PTR_NULL; //错误类型为"对象为空" return ((void *)0); //返回 0(有错误),停止执行 } if (p_msg_size == (OS_MSG_SIZE *)0) //如果 p_msg_size 为空 { *p_err = OS_ERR_PTR_INVALID; //错误类型为"指针不可用" return ((void *)0); //返回 0(有错误),停止执行 } switch (opt) //根据选项分类处理 { case OS_OPT_PEND_BLOCKING: //如果选项在预期内 case OS_OPT_PEND_NON_BLOCKING: break; //直接跳出 default: //如果选项超出预期 *p_err = OS_ERR_OPT_INVALID; //返回错误类型为"选项非法" return ((void *)0); //返回 0(有错误),停止执行 } #endif #if OS_CFG_OBJ_TYPE_CHK_EN > 0u //如果启用了对象类型检测 if (p_q->Type != OS_OBJ_TYPE_Q) //如果 p_q 不是消息队列类型 { *p_err = OS_ERR_OBJ_TYPE; //错误类型为"对象类型有误" return ((void *)0); //返回 0(有错误),停止执行 } #endif if (p_ts != (CPU_TS *)0) //如果 p_ts 非空 { *p_ts = (CPU_TS )0; //初始化(清零)p_ts,待用于返回时间 } CPU_CRITICAL_ENTER(); //关中断 p_void = OS_MsgQGet(&p_q->MsgQ, //从消息队列获取一个消息 p_msg_size, p_ts, p_err); if (*p_err == OS_ERR_NONE) //如果获取消息成功 { CPU_CRITICAL_EXIT(); //开中断 return (p_void); //返回消息内容 } /* 如果获取消息不成功 */ if ((opt & OS_OPT_PEND_NON_BLOCKING) != (OS_OPT)0) //如果选择了不阻塞任务 { CPU_CRITICAL_EXIT(); //开中断 *p_err = OS_ERR_PEND_WOULD_BLOCK; //错误类型为"等待渴求阻塞" return ((void *)0); //返回 0(有错误),停止执行 } else {//如果选择了阻塞任务 if (OSSchedLockNestingCtr > (OS_NESTING_CTR)0)//如果调度器被锁 { CPU_CRITICAL_EXIT(); //开中断 *p_err = OS_ERR_SCHED_LOCKED; //错误类型为"调度器被锁" return ((void *)0); //返回 0(有错误),停止执行 } } /* 如果调度器未被锁 */ OS_CRITICAL_ENTER_CPU_EXIT(); //锁调度器,重开中断 OS_Pend(&pend_data,//阻塞当前任务,等待消息队列, (OS_PEND_OBJ *)((void *)p_q), //将当前任务脱离就绪列表,并 OS_TASK_PEND_ON_Q, //插入节拍列表和等待列表. timeout); OS_CRITICAL_EXIT_NO_SCHED(); //开调度器,但不进行调度 OSSched(); //找到并调度最高优先级就绪任务 /* 当前任务(获得消息队列的消息)得以继续运行 */ CPU_CRITICAL_ENTER(); //关中断 switch (OSTCBCurPtr->PendStatus) //根据当前运行任务的等待状态分类处理 { case OS_STATUS_PEND_OK: //如果等待状态正常 p_void = OSTCBCurPtr->MsgPtr; //从(发布时放于)任务控制块提取消息 *p_msg_size = OSTCBCurPtr->MsgSize; //提取消息大小 if (p_ts != (CPU_TS *)0) //如果 p_ts 非空 { *p_ts = OSTCBCurPtr->TS; //获取任务等到消息时的时间戳 } *p_err = OS_ERR_NONE; //错误类型为"无错误" break; //跳出 case OS_STATUS_PEND_ABORT: //如果等待被中止 p_void = (void *)0; //返回消息内容为空 *p_msg_size = (OS_MSG_SIZE)0; //返回消息大小为 0 if (p_ts != (CPU_TS *)0) //如果 p_ts 非空 { *p_ts = OSTCBCurPtr->TS; //获取等待被中止时的时间戳 } *p_err = OS_ERR_PEND_ABORT; //错误类型为"等待被中止" break; //跳出 case OS_STATUS_PEND_TIMEOUT: //如果等待超时 p_void = (void *)0; //返回消息内容为空 *p_msg_size = (OS_MSG_SIZE)0; //返回消息大小为 0 if (p_ts != (CPU_TS *)0) //如果 p_ts 非空 { *p_ts = (CPU_TS )0; //清零 p_ts } *p_err = OS_ERR_TIMEOUT; //错误类型为"等待超时" break; //跳出 case OS_STATUS_PEND_DEL: //如果等待的内核对象被删除 p_void = (void *)0; //返回消息内容为空 *p_msg_size = (OS_MSG_SIZE)0; //返回消息大小为 0 if (p_ts != (CPU_TS *)0) //如果 p_ts 非空 { *p_ts = OSTCBCurPtr->TS; //获取对象被删时的时间戳 } *p_err = OS_ERR_OBJ_DEL; //错误类型为"等待对象被删" break; //跳出 default: //如果等待状态超出预期 p_void = (void *)0; //返回消息内容为空 *p_msg_size = (OS_MSG_SIZE)0; //返回消息大小为 0 *p_err = OS_ERR_STATUS_INVALID; //错误类型为"状态非法" break; //跳出 } CPU_CRITICAL_EXIT(); //开中断 return(p_void); //返回消息内容 }
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149消息获取函数OS_MsgQGet()
void *OS_MsgQGet (OS_MSG_Q *p_msg_q, //消息队列 OS_MSG_SIZE *p_msg_size, //返回消息大小 CPU_TS *p_ts, //返回某些操作的时间戳 OS_ERR *p_err){ //返回错误类型 OS_MSG *p_msg; void *p_void; #ifdef OS_SAFETY_CRITICAL //如果启用(默认禁用)了安全检测 if (p_err == (OS_ERR *)0) //如果错误类型实参为空 { OS_SAFETY_CRITICAL_EXCEPTION(); //执行安全检测异常函数 return ((void *)0); //返回空消息,停止执行 } #endif if (p_msg_q->NbrEntries == (OS_MSG_QTY)0) //如果消息队列没有消息 { *p_msg_size = (OS_MSG_SIZE)0; //返回消息长度为 0 if (p_ts != (CPU_TS *)0) //如果 p_ts 非空 { *p_ts = (CPU_TS )0; //清零 p_ts } *p_err = OS_ERR_Q_EMPTY; //错误类型为"队列没消息" return ((void *)0); //返回空消息,停止执行 } /* 如果消息队列有消息 */ p_msg = p_msg_q->OutPtr; //从队列的出口端提取消息 p_void = p_msg->MsgPtr; //提取消息内容 *p_msg_size = p_msg->MsgSize; //提取消息长度 if (p_ts != (CPU_TS *)0) //如果 p_ts 非空 { *p_ts = p_msg->MsgTS; //获取消息被发布时的时间戳 } p_msg_q->OutPtr = p_msg->NextPtr; //修改队列的出队指针 if (p_msg_q->OutPtr == (OS_MSG *)0) //如果队列没有消息了 { p_msg_q->InPtr = (OS_MSG *)0; //清零出队指针 p_msg_q->NbrEntries = (OS_MSG_QTY)0; //清零消息数 } else {//如果队列还有消息 p_msg_q->NbrEntries--; //队列的消息数减 1 } /* 从消息队列提取完消息信息后,将消息释放回消息池供继续使用 */ p_msg->NextPtr = OSMsgPool.NextPtr; //消息插回消息池 OSMsgPool.NextPtr = p_msg; OSMsgPool.NbrFree++; //消息池的可用消息数加 1 OSMsgPool.NbrUsed--; //消息池的已用消息数减 1 *p_err = OS_ERR_NONE; //错误类型为"无错误" return (p_void); //返回消息内容 }
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51任务脱离就序列表函数OS_Pend()
将当前任务脱离就绪列表,并根据用户指定的阻塞时间插入节拍列表和队列等待列表,然后打开调度器,但不进行调度.
void OS_Pend(OS_PEND_DATA *p_pend_data, //待插入等待列表的元素 OS_PEND_OBJ *p_obj, //等待的内核对象 OS_STATE pending_on, //等待哪种对象内核 OS_TICK timeout){ //等待期限 OS_PEND_LIST *p_pend_list; OSTCBCurPtr->PendOn = pending_on; //资源不可用,开始等待 OSTCBCurPtr->PendStatus = OS_STATUS_PEND_OK; //正常等待中 OS_TaskBlock(OSTCBCurPtr,timeout); //阻塞当前运行任务,如果 timeout 非0,把任务插入的节拍列表 if (p_obj != (OS_PEND_OBJ *)0) //如果等待对象非空 { p_pend_list = &p_obj->PendList; //获取对象的等待列表到 p_pend_list p_pend_data->PendObjPtr = p_obj; //保存要等待的对象 OS_PendDataInit((OS_TCB *)OSTCBCurPtr, //初始化 p_pend_data(待插入等待列表) (OS_PEND_DATA *)p_pend_data, (OS_OBJ_QTY )1); //按优先级将 p_pend_data 插入等待列表 OS_PendListInsertPrio(p_pend_list, p_pend_data); } else {//如果等待对象为空 OSTCBCurPtr->PendDataTblEntries = (OS_OBJ_QTY )0; //清零当前任务的等待域数据 OSTCBCurPtr->PendDataTblPtr = (OS_PEND_DATA *)0; } #if OS_CFG_DBG_EN > 0u //如果启用了调试代码和变量 OS_PendDbgNameAdd(p_obj, //更新信号量的 DbgNamePtr 元素为其等待 OSTCBCurPtr);//列表中优先级最高的任务的名称. #endif }
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
# 消息队列使用注意事项
在使用μC/OS提供的消息队列函数的时候,需要了解以下几点:
用
OSQPend()
,OSQPost()
等这些函数之前应先创建需消息队列,并根据队列句柄(队列控制块)进行操作.列读取采用的是先进先出(FIFO)模式,会先读取先存储在队列中的数据.当然μC/OS也支持后进先出(LIFO)模式,那么读取的时候就会读取到后进队列的数据.
无论是发送或者是接收消息都是以数据引用的方式进行.
队列是具有自己独立权限的内核对象,并不属于任何任务.所有任务都可以向同一队列写入和读出.一个队列由多任务或中断写入是经常的事,但由多个任务读出倒是用的比较少.
消息的传递实际上只是传递传送内容的指针和传送内容的字节大小.这在使用消息队列的时候就要注意了,获取消息之前不能释放存储在消息中的指针内容,比如中断定义了一个局部变量,然后将其地址放在消息中进行传递,中断退出之前消息并没有被其他任务获取,退出中断的时候CPU已经释放了中断中的这个局部变量,后面任务获取这个地址的内容就会出错.所以一定要保证在获取内容地址之前不能释放内容这个内存单元.
有三种方式可以避免这种情况:
- 将变量定义为静态变量,即在其前面加上static,这样内存单元就不会被释放.
- 将变量定义为全局变量.
- 将要传递的内容当做指针传递过去.比如地址0x12345678存放一个变量的值为5.常规是把0x12345678这个地址传递给接收消息的任务,任务接收到这个消息后,取出这个地址的内容5.但是如果我们把5当做"地址"传递给任务,最后接收消息的任务直接拿着这个"地址"当做内容去处理即可.不过这种方法不能传递结构体等比较复杂的数据结构,因为消息中存放地址的变量内存大小是有限的(一个指针大小)
# 消息队列实验
创建两个任务AppTaskPost()
和AppTaskPend()
,任务AppTaskPost()
用于发送消息,任务AppTaskPend()
用于接收消息,两个任务独立运行,把接收到的消息通过串口输出.
声明消息队列,并定义任务空间栈的大小以及任务栈数组,任务控制块和优先级.
定义任务函数
任务启动函数编写
结果现象