微信企业网站html5模板,建设网站最好的,谷歌网站提交,自己做网站建设写在前面#xff1a; 由于时间的不足与学习的碎片化#xff0c;写博客变得有些奢侈。 但是对于记录学习#xff08;忘了以后能快速复习#xff09;的渴望一天天变得强烈。 既然如此 不如以天为单位#xff0c;以时间为顺序#xff0c;仅仅将博客当做一个知识学习的目录 由于时间的不足与学习的碎片化写博客变得有些奢侈。 但是对于记录学习忘了以后能快速复习的渴望一天天变得强烈。 既然如此 不如以天为单位以时间为顺序仅仅将博客当做一个知识学习的目录记录笔者认为最通俗、最有帮助的资料并尽量总结几句话指明本质以便于日后搜索起来更加容易。 标题的结构如下“类型”“知识点”——“简短的解释” 部分内容由于保密协议无法上传。 点击此处进入学习日记的总目录 2024.03.31UCOSIII第二十八节消息队列常用函数 四十二、UCOSIII消息队列常用函数1、创建消息队列函数OSQCreate()2、消息队列删除函数OSQDel()3、消息队列发送函数OSQPost()1. OSQPost()函数2. OS_QPost()函数3. OS_MsgQPut()函数4. OS_Post()函数 4、消息队列获取函数OSQPend()1. OSQPend()函数2. OS_MsgQGet()函数3. OS_Pend()函数 四十二、UCOSIII消息队列常用函数
1、创建消息队列函数OSQCreate()
要使用 μC/OS的消息队列必须先声明和创建消息队列OSQCreate()用于创建一个新的队列。
队列就是一个数据结构 用于任务间的数据的传递。每创建一个新的队列都需要为其分配RAM在创建的时候我们需要自己定义一个消息队列结构体 其内存是由编译器自动分配的。
OSQCreate的源码具体如下
void OSQCreate (OS_Q *p_q, //(1) //消息队列指针CPU_CHAR *p_name, //(2) //消息队列名称OS_MSG_QTY max_qty, //(3) //消息队列大小不能为0OS_ERR *p_err) //(4) //返回错误类型{CPU_SR_ALLOC();//(5)//使用到临界段在关/开中断时时必须用到该宏该宏声明和//定义一个局部变量用于保存关中断前的 CPU 状态寄存器// SR临界段关中断只需保存SR开中断时将该值还原。#ifdef OS_SAFETY_CRITICAL//(6)//如果启用了安全检测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//(7)//如果启用了中断中非法调用检测if (OSIntNestingCtr (OS_NESTING_CTR)0) { //如果该函数是在中断中被调用*p_err OS_ERR_CREATE_ISR; //错误类型为“在中断中创建对象”return; //返回停止执行}
#endif#if OS_CFG_ARG_CHK_EN 0u//(8) //如果启用了参数检测if (p_q (OS_Q *)0) { //如果 p_q 为空*p_err OS_ERR_OBJ_PTR_NULL; //错误类型为“创建对象为空”return; //返回停止执行}if (max_qty (OS_MSG_QTY)0) { //(9)//如果 max_qty 0*p_err OS_ERR_Q_SIZE; //错误类型为“队列空间为0”return; //返回停止执行}
#endifOS_CRITICAL_ENTER(); //进入临界段p_q-Type OS_OBJ_TYPE_Q; //(10)//标记创建对象数据结构为消息队列p_q-NamePtr p_name; //(11)//标记消息队列的名称OS_MsgQInit(p_q-MsgQ, //初始化消息队列max_qty); //(12)OS_PendListInit(p_q-PendList); //(13) //初始化该消息队列的等待列表#if OS_CFG_DBG_EN 0u//如果启用了调试代码和变量OS_QDbgListAdd(p_q); //将该队列添加到消息队列双向调试链表
#endifOSQQty; //(14)//消息队列个数加1OS_CRITICAL_EXIT_NO_SCHED(); //退出临界段无调度*p_err OS_ERR_NONE; //错误类型为“无错误”
}(1)消息队列指针在创建之前我们要定义一个队列的数据结构然后将消息队列指针指向该队列。(2)消息队列的名称字符串形式这个名称一般与消息队列名称一致为了方便调试。(3)消息队列的大小也就是消息队列的可用消息个数最大为多少一旦确定无法修改。(4)用于保存返回的错误类型。(5)使用到临界段在关/开中断时时必须用到该宏该宏声明和定义一个局部变量 用于保存关中断前的 CPU 状态寄存器SR临界段关中断只需保存SR开中断时将该值还原。(6)如果启用了安全检测在编译时则会包含安全检测相关的代码如果错误类型实参为空 系统会执行安全检测异常函数然后返回停止执行。(7)如果启用了中断中非法调用检测在编译时则会包含中断非法调用检测相关的代码 如果该函数是在中断中被调用则是非法的返回错误类型为“在中断中创建对象”的错误代码并且退出不执行创建队列操作。(8)如果启用了参数检测在编译时则会包含参数检测相关的代码 如果 p_q 参数为空返回错误类型为“创建对象为空”的错误代码并且退出不执行创建队列操作。(9)如果 max_qty参数为 0表示不存在消息空间这也是错误的 返回错误类型为“队列空间为0”的错误代码并且退出不执行创建队列操作。(10)标记创建对象数据结构为消息队列。(11)初始化消息队列的名称。(12)调用OS_MsgQInit()函数初始化消息队列其实就是初始化消息队列结构的相关信息(13)初始化消息队列的阻塞列表消息队列的阻塞列表是用于记录阻塞在此消息队列上的任务。(14)OSQQty是系统中的一个全局变量 用于记录已经创建的消息队列个数现在创建队列完毕所以该变量要加一。
其中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; //队列的出队指针
}消息队列创建完成的示意图具体见图 在创建消息队列的时候是需要用户自己定义消息队列的句柄的。 但是需要注意的是定义了队列的句柄并不等于创建了队列。 创建队列必须是调用消息队列创建函数进行创建否则以后根据队列句柄使用消息队列的其他函数的时候会发生错误。 用户通过消息队列句柄就可使用消息队列进行发送与获取消息的操作用户可以根据返回的错误代码进行判断消息队列是否创建成功。
消息队列创建函数OSQCreate()使用实例具体
OS_Q queue; //声明消息队列OS_ERR err;/* 创建消息队列 queue */
OSQCreate ((OS_Q *)queue, //指向消息队列的指针(CPU_CHAR *)Queue For Test, //队列的名字(OS_MSG_QTY )20, //最多可存放消息的数目(OS_ERR *)err); //返回错误类型2、消息队列删除函数OSQDel()
队列删除函数是根据队列结构队列句柄直接删除的删除之后这个消息队列的所有信息都会被系统清空而且不能再次使用这个消息队列了。 需要注意的是如果某个消息队列没有被定义那也是无法被删除的。
想要使用消息队列删除函数就必须将OS_CFG_Q_DEL_EN宏定义配置为1 其函数源码具体如下
#if OS_CFG_Q_DEL_EN 0u//如果启用了 OSQDel() 函数
OS_OBJ_QTY OSQDel (OS_Q *p_q, //(1)//消息队列指针OS_OPT opt, //(2)//选项OS_ERR *p_err) //(3)//返回错误类型
{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 //(4)//如果启用默认禁用了安全检测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 //(5)//如果启用了中断中非法调用检测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 //(6)//如果启用了参数检测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: //(8)*p_err OS_ERR_OPT_INVALID; //如果选项超出预期return ((OS_OBJ_QTY)0u); //返回0有错误停止执行}
#endif#if OS_CFG_OBJ_TYPE_CHK_EN 0u //(9)//如果启用了对象类型检测if (p_q-Type ! OS_OBJ_TYPE_Q) { //如果 p_q 不是消息队列类型*p_err OS_ERR_OBJ_TYPE; //错误类型为“对象类型有误”return ((OS_OBJ_QTY)0); //返回0有错误停止执行}
#endifCPU_CRITICAL_ENTER(); //关中断p_pend_list p_q-PendList; //(10)//获取消息队列的等待列表cnt p_pend_list-NbrEntries; //(11)//获取等待该队列的任务数nbr_tasks cnt; //(12)//按照任务数目逐个处理switch (opt) { //(13)//根据选项分类处理case OS_OPT_DEL_NO_PEND: //(14)//如果只在没有任务等待的情况下删除队列if (nbr_tasks (OS_OBJ_QTY)0) {//(15)//如果没有任务在等待该队列
#if OS_CFG_DBG_EN 0u//如果启用了调试代码和变量OS_QDbgListRemove(p_q); //将该队列从消息队列调试列表移除
#endifOSQQty--; //(16)//消息队列数目减1OS_QClr(p_q); //(17)//清除该队列的内容CPU_CRITICAL_EXIT(); //开中断*p_err OS_ERR_NONE; //(18)//错误类型为“无错误”} else { //(19)//如果有任务在等待该队列CPU_CRITICAL_EXIT(); //开中断*p_err OS_ERR_TASK_WAITING; //错误类型为“有任务在等待该队列”}
break;case OS_OPT_DEL_ALWAYS: //(20)//如果必须删除信号量OS_CRITICAL_ENTER_CPU_EXIT(); //进入临界段重开中断ts OS_TS_GET(); //获取时间戳
while (cnt 0u) { //(21)//逐个移除该队列等待列表中的任务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--; //(22)}
#if OS_CFG_DBG_EN 0u//如果启用了调试代码和变量OS_QDbgListRemove(p_q); //将该队列从消息队列调试列表移除
#endifOSQQty--; //(23)//消息队列数目减1OS_QClr(p_q); //(24)//清除消息队列内容OS_CRITICAL_EXIT_NO_SCHED(); //退出临界段无调度OSSched(); //(25)//调度任务*p_err OS_ERR_NONE; //(26)//错误类型为“无错误”break; //跳出default://(27)//如果选项超出预期CPU_CRITICAL_EXIT(); //开中断*p_err OS_ERR_OPT_INVALID; //错误类型为“选项非法”break; //跳出}return (nbr_tasks); //返回删除队列前等待其的任务数
}
#endif(1)消息队列指针指向要删除的消息队列。(2)操作消息队列的选项具体在后面讲解。(3)用于保存返回的错误类型。(4)如果启用默认禁用了安全检测在编译时则会包含安全检测相关的代码如果错误类型实参为空 系统会执行安全检测异常函数然后返回停止执行。(5)如果启用了中断中非法调用检测在编译时则会包含中断非法调用检测相关的代码 如果该函数是在中断中被调用则是非法的返回错误类型为“在中断中删除”的错误代码并且退出不执行删除队列操作。(6)如果启用了参数检测在编译时则会包含参数检测相关的代码如果 p_q 参数为空 返回错误类型为“删除对象为空”的错误代码并且退出不执行删除队列操作。(7)根据选项分类处理如果选项在预期内直接跳出switch语句。(8)如果选项超出预期就退出不执行删除队列操作。(9)如果启用了对象类型检测在编译时则会包含对象类型检测相关代码如果 p_q 不是消息队列类型 那么返回错误类型为“对象类型有误”的错误代码并且退出不执行删除队列操作。(10)程序能执行到这里说明传入的参数都是正确的此时可以执行删除队列操作 系统首先获取消息队列中的等待列表通过p_pend_list变量进行消息队列等待列表的访问。(11)获取阻塞在该队列上的任务个数。(12)按照任务数目逐个处理。(13)根据选项分类处理。(14)如果如果删除选项是只在没有任务等待的情况下删除队列系统就会判断有没有任务阻塞在改队列上。(15)如果没有任务在等待该队列那就执行删除操作。(16)系统的消息队列数目减1。(17)清除该队列的内容。(18)返回错误类型为“无错误”的错误代码。(19)而如果有任务在等待该队列那么就没法进行删除操作返回错误类型为“有任务在等待该队列”的错误代码。(20)如果删除操作的选项是必须删除消息队列无论是否有任务阻塞在该消息队列上系统都会进行删除操作。(21)根据消息队列当前等待的任务个数逐个移除该队列等待列表中的任务。(22)调用OS_PendObjDel()函数将阻塞在内核对象如信号量上的任务从阻塞态恢复 此时系统在删除内核对象 删除之后这些等待事件的任务需要被恢复。每移除一个消息队列的任务个数就减一 当没有任务阻塞在该队列上就进行删除队列操作。(23)系统的消息队列数目减1。(24)清除消息队列内容。(25)发起一次调度任务。(26)返回错误类型为“无错误”的错误代码。(27)而如果选项超出预期返回错误类型为“选项非法”的错误代码然后退出。
OS_PendObjDel()源码如下
void OS_PendObjDel (OS_PEND_OBJ *p_obj, //(1) //被删除对象的类型OS_TCB *p_tcb, //(2) //任务控制块指针CPU_TS ts) //(3) //信号量被删除时的时间戳
{
switch (p_tcb-TaskState) //(4)//根据任务状态分类处理{case OS_TASK_STATE_RDY: //如果任务是就绪状态case OS_TASK_STATE_DLY: //如果任务是延时状态case OS_TASK_STATE_SUSPENDED: //如果任务是挂起状态case OS_TASK_STATE_DLY_SUSPENDED: //如果任务是在延时中被挂起break; //(5)//这些情况均与等待无关直接跳出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); //(6)}
#if (OS_MSG_EN 0u) //(7)//如果启用了任务队列或消息队列p_tcb-MsgPtr (void *)0; //清除复位任务的消息域p_tcb-MsgSize (OS_MSG_SIZE)0u;
#endifp_tcb-TS ts; //(8)//保存等待被中止时的时间戳到任务控制块OS_PendListRemove(p_tcb); //(9)//将任务从所有等待列表中移除OS_TaskRdy(p_tcb); //(10)//让任务进准备运行p_tcb-TaskState OS_TASK_STATE_RDY; //(11)//修改任务状态为就绪状态p_tcb-PendStatus OS_STATUS_PEND_DEL; //(12)//标记任务的等待对象被删除p_tcb-PendOn OS_TASK_PEND_ON_NOTHING; //(13)//标记任务目前没有等待任何对象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); //(14)}
#if (OS_MSG_EN 0u) //(15)//如果启用了任务队列或消息队列p_tcb-MsgPtr (void *)0; //(16)//清除复位任务的消息域p_tcb-MsgSize (OS_MSG_SIZE)0u;
#endifp_tcb-TS ts; //(17)//保存等待被中止时的时间戳到任务控制块OS_TickListRemove(p_tcb); //(18)//让任务脱离节拍列表OS_PendListRemove(p_tcb); //(19)//将任务从所有等待列表中移除p_tcb-TaskState OS_TASK_STATE_SUSPENDED; //(20)//修改任务状态为挂起状态p_tcb-PendStatus OS_STATUS_PEND_DEL; //(21)//标记任务的等待对象被删除p_tcb-PendOn OS_TASK_PEND_ON_NOTHING; //标记任务目前没有等待任何对象break; //跳出default: //(22)//如果任务状态超出预期break; //不需处理直接跳出}
}(1)被删除对象的类型如消息队列、信号量、互斥量、事件等。(2)任务控制块指针。(3)内核对象被删除时的时间戳。(4)根据任务状态分类处理。(5)如果任务是就绪状态、延时状态、挂起状态或者是在延时中被挂起 这些任务状态均与等待内核对象是无关的在内核对象被删除的时候无需进行任何操作。(6)如果任务是无期限等待状态或者是有期限等待状态 那么在内核对象被删除的时候需要将这些任务恢复。如果这些任务在等待多个内核对象信号量或消息队列等 那么就需要强制解除任务对某一对象的等待比如现在删除的是消息队列 那么就将该任务对消息队列的等待进行解除。(7)如果启用了任务队列或消息队列清除复位任务的消息指针任务等待的消息大小为0。(8)保存等待被中止时的时间戳到任务控制块。(9)调用OS_PendListRemove()函数将任务从所有等待列表中移除。(10)调用OS_TaskRdy()函数让任务进入就绪态参与系统调度准备运行。(11)修改任务状态为就绪状态。(12)标记任务的等待对象被删除。(13)标记任务目前没有等待任何对象。(14)如果任务在无期限等待中被挂起或者在有期限等待中被挂起 也是需要将这些等待内核对象的任务从等待中移除但是由于在等待中被挂起那么就不会将这些任务恢复为就绪态 仅仅是将任务从等待列表中移除。如果任务在等待多个信号量或消息队列同样也是讲任务从等待的对象中移除即可。(15)如果启用了任务队列或消息队列。(16)需要清除复位任务的消息指针任务等待的消息大小为0。(17)保存等待被中止时的时间戳到任务控制块。(18)调用OS_TickListRemove()函数让任务脱离节拍列表。(19)调用OS_PendListRemove()函数将任务从所有等待列表中移除。(20)修改任务状态为挂起状态因为在等待中被挂起此时即使任务不等的内核对象了它还是处于挂起态。(21)任务的等待对象被删除标记任务目前没有等待任何对象。(22)如果任务状态超出预期不需处理直接跳出。
消息队列删除函数OSQDel()的使用也是很简单的只需要传入要删除的消息队列的句柄与选项还有保存返回的错误类型即可。
调用函数时 系统将删除这个消息队列。 需要注意的是在调用删除消息队列函数前系统应存在已创建的消息队列。 如果删除消息队列时 有任务正在等待消息则不应该进行删除操作删除之后的消息队列就不可用了。
删除消息队列函数OSQDel()的使用实例具体如下
OS_Q queue; //声明消息队列OS_ERR err;/* 删除消息队列 queue */
OSQDel ((OS_Q *)queue, //指向消息队列的指针
OS_OPT_DEL_NO_PEND,
(OS_ERR *)err); //返回错误类型3、消息队列发送函数OSQPost()
1. OSQPost()函数
任务或者中断服务程序都可以给消息队列发送消息当发送消息时如果队列未满就说明运行信息入队。 μC/OS会从消息池中取出一个消息 挂载到消息队列的末尾FIFO发送方式。 如果是LIFO发送方式则将消息挂载到消息队列的头部 然后将消息中MsgPtr成员变量指向要发送的消息此处可以理解为添加要发送的信息到消息块中。 如果系统有任务阻塞在消息队列中那么在发送了消息队列的时候会将任务解除阻塞其源码具体如下
void OSQPost (OS_Q *p_q, //(1) //消息队列指针
void *p_void, //(2) //消息指针OS_MSG_SIZE msg_size,//(3) //消息大小单位字节OS_OPT opt, //(4) //选项OS_ERR *p_err) //(5) //返回错误类型
{CPU_TS ts;#ifdef OS_SAFETY_CRITICAL//(6)//如果启用默认禁用了安全检测if (p_err (OS_ERR *)0) { //如果错误类型实参为空OS_SAFETY_CRITICAL_EXCEPTION(); //执行安全检测异常函数return; //返回停止执行}
#endif#if OS_CFG_ARG_CHK_EN 0u//(7)//如果启用了参数检测if (p_q (OS_Q *)0) { //如果 p_q 为空*p_err OS_ERR_OBJ_PTR_NULL; //错误类型为“内核对象为空”return; //返回停止执行}switch (opt) { //(8)//根据选项分类处理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: //(9)//如果选项超出预期*p_err OS_ERR_OPT_INVALID; //错误类型为“选项非法”return; //返回停止执行}
#endif#if OS_CFG_OBJ_TYPE_CHK_EN 0u//(10)//如果启用了对象类型检测if (p_q-Type ! OS_OBJ_TYPE_Q) { //如果 p_q 不是消息队列类型*p_err OS_ERR_OBJ_TYPE; //错误类型为“对象类型错误”return; //返回停止执行}
#endifts OS_TS_GET(); //获取时间戳#if OS_CFG_ISR_POST_DEFERRED_EN 0u//(11)//如果启用了中断延迟发布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; //返回尚未发布停止执行}
#endifOS_QPost(p_q, //将消息按照普通方式p_void,msg_size,opt,ts,p_err); //(12)
}(1)消息队列指针指向要发送消息的队列。(2)消息指针指向任何类型的消息数据。(3)消息的大小单位字节。(4)发送消息的选项在os.h中定义
#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)/*发送消息但是不进行任务调度*/
(5)保存返回的错误类型用户可以根据此变量得知错误的原因。(6)如果启用默认禁用了安全检测在编译时则会包含安全检测相关的代码如果错误类型实参为空 系统会执行安全检测异常函数然后返回停止执行。(7)如果启用了参数检测在编译时则会包含参数检测相关的代码如果 p_q 参数为空 返回错误类型为“内核对象为空”的错误代码并且退出不执行发送消息操作。(8)根据opt选项进行分类处理如果选项在预期内直接退出其实在这里只是对选项的一个检查 看看传入的选项参数是否正确。(9)如果opt选项超出预期返回错误类型为“选项非法”的错误代码并且退出不执行发送消息操作。(10)如果启用了对象类型检测在编译时则会包含对象类型检测相关代码 如果 p_q 不是消息队列类型那么返回错误类型为“对象类型有误”的错误代码并且退出不执行发送消息操作。(11)如果启用了中断延迟发布并且发送消息的函数是在中断中被调用 此时就不该立即发送消息而是将消息的发送放在指定发布任务中此时系统就将消息发布到租单消息队列中 等待到中断发布任务唤醒再发送消息该函数会在中断管理章节详细讲解。(12)而如果不是在中断中调用OSQPost()函数或者未启用中断延迟发布 则直接调用OS_QPost()函数进行消息的发送
2. OS_QPost()函数
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) //(1)//如果没有任务在等待该队列{if ((opt OS_OPT_POST_LIFO) (OS_OPT)0) //把消息发布到队列的末端{post_type OS_OPT_POST_FIFO; //(2)}else//把消息发布到队列的前端{post_type OS_OPT_POST_LIFO; //(3)}OS_MsgQPut(p_q-MsgQ, //把消息放入消息队列p_void,msg_size,post_type,ts,p_err); //(4)OS_CRITICAL_EXIT(); //退出临界段return; //返回执行完毕}/* 如果有任务在等待该队列 */if ((opt OS_OPT_POST_ALL) ! (OS_OPT)0) //(5)//如果要把消息发布给所有等待任务{cnt p_pend_list-NbrEntries; //获取等待任务数目}else//如果要把消息发布给一个等待任务{cnt (OS_OBJ_QTY)1; //(6)//要处理的任务数目为1}p_pend_data p_pend_list-HeadPtr; //(7)//获取等待列表的头部任务while (cnt 0u) //(8)//根据要发布的任务数目逐个发布{p_tcb p_pend_data-TCBPtr; //(9)p_pend_data_next p_pend_data-NextPtr;OS_Post((OS_PEND_OBJ *)((void *)p_q), //把消息发布给任务p_tcb,p_void,msg_size,ts); //(10)p_pend_data p_pend_data_next;cnt--; //(11)}OS_CRITICAL_EXIT_NO_SCHED(); //退出临界段无调度if ((opt OS_OPT_POST_NO_SCHED) (OS_OPT)0) //如果没选择“发布完不调度任务”{OSSched(); //(12)//任务调度}*p_err OS_ERR_NONE; //错误类型为“无错误”
}(1)使用局部变量p_pend_list获取队列的等待列表 然后查看等待列表中是否有任务在等待分情况处理因为没有任务等待就直接将消息放入队列中即可 而有任务在等待则有可能需要唤醒该任务。(2)如果没有任务在等待系统就会看看用户发送消息的选项是什么 如果是发送到细细道来的末端队尾FIFO方式那么表示发送类型的post_type变量就被设置为OS_OPT_POST_FIFO。(3)否则就设置为OS_OPT_POST_LIFO 采用LIFO方式发送消息。将消息发送到队列的前端对头。(4)调用OS_MsgQPut()函数将消息放入队列中 执行完毕就退出(5)而如果有任务在等待消息会有两种情况 一种是将消息发送到所有等待任务广播消息另一种是只将消息发送到等待任务中最高优先级的任务。 根据opt选项选择其中一种方式进行发送消息如果要把消息发送给所有等待任务那就首先获取到等待任务个数 保存在要处理任务个数cnt变量中。(6)否则就是把消息发布给一个等待任务要处理任务个数cnt变量为1。(7)获取等待列表中的第一个任务。(8)根据要处理任务个数cnt逐个将消息发送出去。(9)获取任务的控制块。(10)调用OS_Post()函数把消息发送给任务(11)每处理完一个任务cnt变量就要减一等到为0的时候退出while循环。(12)如果没选择“发送完不调度任务”在发送消息完成的时候就要进行一次任务调度。
3. OS_MsgQPut()函数
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; //返回停止执行}
#endifif (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; //(1)//将消息控制块从消息池移除OSMsgPool.NextPtr p_msg-NextPtr; //(2)//指向下一个消息取走首个消息OSMsgPool.NbrFree--; //(3)//消息池可用消息数减1OSMsgPool.NbrUsed; //(4)//消息池被用消息数加1if (OSMsgPool.NbrUsedMax OSMsgPool.NbrUsed) //(5)//更新消息被用最大数目的历史记录{OSMsgPool.NbrUsedMax OSMsgPool.NbrUsed;}/* 将获取的消息插入消息队列 */if (p_msg_q-NbrEntries (OS_MSG_QTY)0) //(6)//如果消息队列目前没有消息{p_msg_q-InPtr p_msg; //将其入队指针指向该消息p_msg_q-OutPtr p_msg; //出队指针也指向该消息p_msg_q-NbrEntries (OS_MSG_QTY)1; //队列的消息数为1p_msg-NextPtr (OS_MSG *)0; //该消息的下一个消息为空}else//(7)//如果消息队列目前已有消息{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//(8)//如果用LIFO方式插入队列{p_msg-NextPtr p_msg_q-OutPtr; //将消息插入出队端出队p_msg_q-OutPtr p_msg; //指针指向该消息。}p_msg_q-NbrEntries; //(9)//消息队列的消息数目加1}if (p_msg_q-NbrEntriesMax p_msg_q-NbrEntries) //(10)//更新改消息队列的最大消息{p_msg_q-NbrEntriesMax p_msg_q-NbrEntries; //数目的历史记录。}p_msg-MsgPtr p_void; //(11)//给该消息填写消息内容p_msg-MsgSize msg_size; //(12)//给该消息填写消息大小p_msg-MsgTS ts; //(13)//填写发布该消息时的时间戳*p_err OS_ERR_NONE; // (14)//错误类型为“无错误”
}(1)从消息池获取一个消息暂存于 p_msg OSMsgPool是消息池它的NextPtr成员变量指向消息池中可用的消息。(2)更新消息池中NextPtr成员变量指向消息池中下一个可用的消息。(3)消息池可中用消息个数减1。(4)消息池已使用的消息个数加1。(5)更新消息被用最大数目的历史记录。(6)将获取的消息插入消息队列插入队列时分两种情况一种是队列中有消息情况 另一种是队列中没有消息情况。如果消息队列目前没有消息将队列中的入队指针指向该消息出队指针也指向该消息 因为现在消息放进来了只有一个消息无论是入队还是出队都是该消息更新队列的消息个数为1该消息的下一个消息为空。(7)而如果消息队列目前已有消息那么又分两种入队的选项 是先进先出排队呢还是后进先出排队呢如果采用FIFO方式插入队列那么就将消息插入入队端 消息队列的最后一个消息的NextPtr指针就指向该消息然后入队的消息成为队列中排队的最后一个消息 那么需要更新它的下一个消息为空。(8)而如果采用LIFO方式插入队列 将消息插入出队端队列中出队指针OutPtr指向该消息需要出队的时候就是 该消息首先出队这就是后进先出原则。(9)无论是采用哪种方式入队消息队列的消息数目都要加1。(10)更新改消息队列的最大消息。(11)既然消息已经入队了那肯定得添加我们自己的消息内容啊 需要给该消息填写消息内容消息中的MsgPtr指针指向我们的消息内容。(12)给该消息填写我们发送的消息大小。(13)填写发布该消息时的时间戳。(14)当程序执行到这里表面就是没有错误返回错误类型为“无错误”的错误代码。
4. OS_Post()函数
OS_Post()源码如下
void OS_Post (OS_PEND_OBJ *p_obj, //(1) //内核对象类型指针OS_TCB *p_tcb, //(2) //任务控制块void *p_void, //(3) //消息OS_MSG_SIZE msg_size, //(4) //消息大小CPU_TS ts) //(5) //时间戳
{switch (p_tcb-TaskState) //(6)//根据任务状态分类处理{case OS_TASK_STATE_RDY: //如果任务处于就绪状态case OS_TASK_STATE_DLY: //如果任务处于延时状态case OS_TASK_STATE_SUSPENDED: //如果任务处于挂起状态case OS_TASK_STATE_DLY_SUSPENDED://如果任务处于延时中被挂起状态break; //(7)//不用处理直接跳出case OS_TASK_STATE_PEND: //如果任务处于无期限等待状态case OS_TASK_STATE_PEND_TIMEOUT: //如果任务处于有期限等待状态if (p_tcb-PendOn OS_TASK_PEND_ON_MULTI) //(8)//如果任务在等待多个信号量或消息队列{OS_Post1(p_obj, //标记哪个内核对象被发布p_tcb,p_void,msg_size,ts); //(9)}else //(10)//如果任务不是在等待多个信号量或消息队列{
#if (OS_MSG_EN 0u)
//如果启用了任务队列或消息队列p_tcb-MsgPtr p_void; //(11)//保存消息到等待任务p_tcb-MsgSize msg_size;
#endifp_tcb-TS ts; //(12)//保存时间戳到等待任务}if (p_obj ! (OS_PEND_OBJ *)0) //如果内核对象不为空{OS_PendListRemove(p_tcb); //(13)//从等待列表移除该等待任务
#if OS_CFG_DBG_EN 0u//如果启用了调试代码和变量OS_PendDbgNameRemove(p_obj, //移除内核对象的调试名p_tcb);
#endif}OS_TaskRdy(p_tcb); //(14) //让该等待任务准备运行p_tcb-TaskState OS_TASK_STATE_RDY; //(15)//任务状态改为就绪状态p_tcb-PendStatus OS_STATUS_PEND_OK; //(16)//清除等待状态p_tcb-PendOn OS_TASK_PEND_ON_NOTHING; //(17)//标记不再等待
break;case OS_TASK_STATE_PEND_SUSPENDED://如果任务在无期限等待中被挂起case OS_TASK_STATE_PEND_TIMEOUT_SUSPENDED://如果任务在有期限等待中被挂起if (p_tcb-PendOn OS_TASK_PEND_ON_MULTI) //(18)//如果任务在等待多个信号量或消息队列{OS_Post1(p_obj, //标记哪个内核对象被发布p_tcb,p_void,msg_size,ts); //(19)}else //(20)//如果任务不在等待多个信号量或消息队列{
#if (OS_MSG_EN 0u)//如果启用了调试代码和变量p_tcb-MsgPtr p_void; //(21)//保存消息到等待任务p_tcb-MsgSize msg_size;
#endifp_tcb-TS ts; //保存时间戳到等待任务}OS_TickListRemove(p_tcb); //(22)//从节拍列表移除该等待任务if (p_obj ! (OS_PEND_OBJ *)0) //如果内核对象为空{OS_PendListRemove(p_tcb); //(23)//从等待列表移除该等待任务
#if OS_CFG_DBG_EN 0u//如果启用了调试代码和变量OS_PendDbgNameRemove(p_obj, //移除内核对象的调试名p_tcb);
#endif}p_tcb-TaskState OS_TASK_STATE_SUSPENDED; //(24)//任务状态改为被挂起状态p_tcb-PendStatus OS_STATUS_PEND_OK; //(25)//清除等待状态p_tcb-PendOn OS_TASK_PEND_ON_NOTHING; //(26)//标记不再等待break;default: //(27)//如果任务状态超出预期break; //直接跳出}
}(1)内核对象类型指针表示是哪个内核对象进行发布释放/发送操作。(2)任务控制块指针指向被操作的任务。(3)消息指针。(4)消息大小。(5)时间戳。(6)根据任务状态分类处理。(7)如果任务处于就绪状态、延时状态、挂起状态或者是延时中被挂起状态都不用处理 直接退出因为现在这个操作是内核对象进行发布释放操作而这些状态的任务是与内核对象无关的状态 也就是这些任务没在等待相关的内核对象如消息队列、信号量等。(8)如果任务处于无期限等待状态或者是有期限等待状态那么就需要处理了先看看任务是不是在等待多个内核对象。(9)如果任务在等待多个信号量或消息队列 就调用OS_Post1()函数标记一下是哪个内核对象进行发布释放操作。(10)如果任务不是在等待多个信号量或消息队列就直接操作即可。(11)如果启用了任务队列或消息队列启用了OS_MSG_EN宏定义 保存消息到等待任务控制块的MsgPtr成员变量中 将消息的大小保存到等待任务控制块的MsgSize成员变量中。(12)保存时间戳到等待任务控制块的TS成员变量中。(13)如果内核对象不为空调用OS_PendListRemove()函数从等待列表移除该等待任务。(14)调用OS_TaskRdy()函数让该等待任务准备运行。(15)任务状态改为就绪状态。(16)清除任务的等待状态。(17)标记任务不再等待。(18)如果任务在无期限等待中被挂起或者任务在有期限等待中被挂起反正任务就是在等待中被挂起了 也能进行内核对象发布释放操作同理先看看任务是不是在等待多个内核对象。(19)如果任务在等待多个信号量或消息队列 就调用OS_Post1()函数标记一下是哪个内核对象进行发布释放操作。(20)如果任务不在等待多个信号量或消息队列就直接操作即可。(21)如果启用了任务队列或消息队列启用了OS_MSG_EN宏定义 保存消息到等待任务控制块的MsgPtr成员变量中将消息的大小保存到等待任务控制块的MsgSize成员变量中。(22)调用OS_TickListRemove()函数将任务从节拍列表中移除。(23)从等待列表移除该等待任务。(24)任务状态改为被挂起状态。(25)清除任务的等待状态。(26)标记任务不再等待。(27)如果任务状态超出预期直接跳出。
从消息队列的入队操作发送消息我们可以看出 μC/OS支持向所有任务发送消息也支持只向一个任务发送消息 这样子系统的灵活性就会大大提高与此同时μC/OS还支持中断延迟发布不在中断中直接发送消息。
消息队列的发送函数OSQPost()使用实例具体如下
/* 发送消息到消息队列 queue */
OSQPost ((OS_Q *)queue, //消息变量指针(void *)Binghuo μC/OS-III,//要发送的数据的指针将内存块首地址通过队列“发送出去”(OS_MSG_SIZE )sizeof ( Binghuo μC/OS-III ), //数据字节大小(OS_OPT )OS_OPT_POST_FIFO | OS_OPT_POST_ALL,//先进先出和发布给全部任务的形式(OS_ERR *)err); 4、消息队列获取函数OSQPend()
当任务试图从队列中的获取消息时用户可以指定一个阻塞超时时间当且仅当消息队列中有消息的时候任务才能获取到消息。 在这段时间中如果队列为空该任务将保持阻塞状态以等待队列消息有效。 当其他任务或中断服务程序往其等待的队列中写入了数据 该任务将自动由阻塞态转为就绪态。 当任务等待的时间超过了用户指定的阻塞时间即使队列中尚无有效消息 任务也会自动从阻塞态转为就绪态。
1. OSQPend()函数
OSQPend()函数源码具体如下
void *OSQPend (OS_Q *p_q, //(1) //消息队列指针OS_TICK timeout, //(2) //等待期限单位时钟节拍OS_OPT opt, //(3) //选项OS_MSG_SIZE *p_msg_size,//(4) //返回消息大小单位字节CPU_TS *p_ts, //(5) //获取等到消息时的时间戳OS_ERR *p_err) //(6) //返回错误类型
{OS_PEND_DATA pend_data;void *p_void;CPU_SR_ALLOC(); //使用到临界段在关/开中断时时必须用到该宏该宏声明和//定义一个局部变量用于保存关中断前的 CPU 状态寄存器// SR临界段关中断只需保存SR开中断时将该值还原。#ifdef OS_SAFETY_CRITICAL//(7)//如果启用默认禁用了安全检测if (p_err (OS_ERR *)0) //如果错误类型实参为空{OS_SAFETY_CRITICAL_EXCEPTION(); //执行安全检测异常函数return ((void *)0); //返回0有错误停止执行}
#endif#if OS_CFG_CALLED_FROM_ISR_CHK_EN 0u//(8)//如果启用了中断中非法调用检测if (OSIntNestingCtr (OS_NESTING_CTR)0) //如果该函数在中断中被调用{*p_err OS_ERR_PEND_ISR; //错误类型为“在中断中中止等待”return ((void *)0); //返回0有错误停止执行}
#endif#if OS_CFG_ARG_CHK_EN 0u//(9)//如果启用了参数检测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) //(10)//根据选项分类处理{case OS_OPT_PEND_BLOCKING: //如果选项在预期内case OS_OPT_PEND_NON_BLOCKING:break; //直接跳出default: //(11)//如果选项超出预期*p_err OS_ERR_OPT_INVALID; //返回错误类型为“选项非法”return ((void *)0); //返回0有错误停止执行}
#endif#if OS_CFG_OBJ_TYPE_CHK_EN 0u//(12)//如果启用了对象类型检测if (p_q-Type ! OS_OBJ_TYPE_Q) //如果 p_q 不是消息队列类型{*p_err OS_ERR_OBJ_TYPE; //错误类型为“对象类型有误”return ((void *)0); //返回0有错误停止执行}
#endifif (p_ts ! (CPU_TS *)0) //(13) //如果 p_ts 非空{*p_ts (CPU_TS )0; //初始化清零p_ts待用于返回时间戳}CPU_CRITICAL_ENTER(); //关中断p_void OS_MsgQGet(p_q-MsgQ, //(14)//从消息队列获取一个消息p_msg_size,p_ts,p_err);if (*p_err OS_ERR_NONE) //(15)//如果获取消息成功{CPU_CRITICAL_EXIT(); //开中断return (p_void); //返回消息内容}/* 如果获取消息不成功 */ //(16)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//(17)//如果选择了阻塞任务{if (OSSchedLockNestingCtr (OS_NESTING_CTR)0)//(18)//如果调度器被锁{CPU_CRITICAL_EXIT(); //开中断*p_err OS_ERR_SCHED_LOCKED; //错误类型为“调度器被锁”return ((void *)0); //返回0有错误停止执行}}/* 如果调度器未被锁 */OS_CRITICAL_ENTER_CPU_EXIT(); //(19)//锁调度器重开中断OS_Pend(pend_data,//阻塞当前任务等待消息队列(OS_PEND_OBJ *)((void *)p_q), //将当前任务脱离就绪列表并OS_TASK_PEND_ON_Q, //插入节拍列表和等待列表。timeout); //(20)OS_CRITICAL_EXIT_NO_SCHED(); //开调度器但不进行调度OSSched(); //(21)//找到并调度最高优先级就绪任务/* 当前任务获得消息队列的消息得以继续运行 */CPU_CRITICAL_ENTER(); // (22)//关中断switch (OSTCBCurPtr-PendStatus) //(23)//根据当前运行任务的等待状态分类处理{case OS_STATUS_PEND_OK: //(24)//如果等待状态正常p_void OSTCBCurPtr-MsgPtr; // (25)//从发布时放于任务控制块提取消息*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: //(26)//如果等待被中止p_void (void *)0; //返回消息内容为空*p_msg_size (OS_MSG_SIZE)0; //返回消息大小为0if (p_ts ! (CPU_TS *)0) //如果 p_ts 非空{*p_ts OSTCBCurPtr-TS; //获取等待被中止时的时间戳}*p_err OS_ERR_PEND_ABORT; //错误类型为“等待被中止”break; //跳出case OS_STATUS_PEND_TIMEOUT: //(27)//如果等待超时p_void (void *)0; //返回消息内容为空*p_msg_size (OS_MSG_SIZE)0; //返回消息大小为0if (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: //(28)//如果等待的内核对象被删除p_void (void *)0; //返回消息内容为空*p_msg_size (OS_MSG_SIZE)0; //返回消息大小为0if (p_ts ! (CPU_TS *)0) //如果 p_ts 非空{*p_ts OSTCBCurPtr-TS; //获取对象被删时的时间戳}*p_err OS_ERR_OBJ_DEL; //错误类型为“等待对象被删”break; //跳出default: //(29)//如果等待状态超出预期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); //(30)//返回消息内容
}(1)消息队列指针指向要获取消息的队列。(2)指定阻塞时间单位时钟节拍。(3)获取消息的选项在os.h中有定义。(4)用于保存返回获取的消息大小单位字节。(5)用于保存返回等到消息时的时间戳。(6)用于保存返回的错误类型用户可以根据此变量得知错误的原因。(7)如果启用默认禁用了安全检测在编译时则会包含安全检测相关的代码 如果错误类型实参为空系统会执行安全检测异常函数然后返回停止执行。(8)如果启用了中断中非法调用检测并且如果该函数在中断中被调用 则返回错误类型为“在中断获取消息”的错误代码然后退出停止执行。(9)如果启用了参数检测在编译时则会包含参数检测相关的代码 如果 p_q 参数为空返回错误类型为“内核对象为空”的错误代码并且退出不执行获取消息操作。(10)根据opt选项进行分类处理如果选项在预期内直接退出 其实在这里只是对选项的一个检查看看传入的选项参数是否正确。(11)如果opt选项超出预期 返回错误类型为“选项非法”的错误代码并且退出不执行获取消息操作。(12)如果启用了对象类型检测在编译时则会包含对象类型检测相关代码 如果 p_q 不是消息队列类型那么返回错误类型为“对象类型有误”的错误代码并且退出不执行获取消息操作。(13)如果 p_ts 非空就初始化清零p_ts待用于返回时间戳。(14)调用OS_MsgQGet()函数从消息队列获取一个消息(15)如果获取消息成功就返回消息的内容。(16)如果获取消息不成功并且用户选择了不阻塞等待 则返回错误类型为“等待渴求阻塞OS_ERR_PEND_WOULD_BLOCK”的错误代码并且返回0表示没有获取到消息。(17)当获取消息不成功的时候用户选择了阻塞等待那么就会将任务状态变为阻塞态以等待消息。(18)判断一下调度器是否被锁如果被锁了则返回错误类型为“调度器被锁”的错误代码然后退出。(19)如果调度器未被锁就锁定调度器重新打开中断。 为什么刚刚调度器被锁就错误的呢?而现在又要锁定调度器 那是因为之前锁定的调度器不是由这个函数进行锁定的 这是不允许的因为现在要阻塞当前任务而调度器锁定了就表示无法进行任务调度这也是不允许的。 那为什么又要关闭调度器呢 因为接下来的操作是需要操作队列与任务的列表这个时间就不会很短系统不希望有其他任务来操作任务列表。这样可能引起其他任务解除阻塞 这可能会发生优先级翻转。 比如任务A的优先级低于当前任务但是在当前任务进入阻塞的过程中任务A却因为其他原因解除阻塞了 那系统肯定是会去运行任务A这显然是要绝对禁止的。 挂起调度器意味着任务不能切换并且不准调用可能引起任务切换的API函数 所以锁定调度器、打开中断这样的处理既不会影响中断的响应又避免了其他任务来操作队列与任务的列表。(20)调用OS_Pend()函数将当前任务脱离就绪列表 并根据用户指定的阻塞时间插入节拍列表和队列等待列表 然后打开调度器但不进行调度(21)在这里就进行一次任务的调度。(22)程序能执行到这里就说明大体上有两种情况要么是消息队列中有消息入队任务获取到消息了 任务还没获取到消息任务没获取到消息的情况有很多种无论是哪种情况都先把中断关掉再说。(23)根据当前运行任务的等待状态分类处理。(24)如果任务状态是OS_STATUS_PEND_OK则表示任务获取到消息了。(25)从任务控制块中提取消息这是因为在发送消息给任务的时候 会将消息放入任务控制块的MsgPtr成员变量中然后继续提取消息大小如果p_ts非空记录获取任务等到消息时的时间戳 返回错误类型为“无错误”的错误代码跳出switch语句。(26)如果任务在等待阻塞被中止则返回消息内容为空返回消息大小为0 如果p_ts非空获取等待被中止时的时间戳返回错误类型为“等待被中止”的错误代码跳出switch语句。(27)如果等待阻塞超时说明等待的时间过去了任务也没获取到消息 则返回消息内容为空返回消息大小为0如果p_ts非空将p_ts清零返回错误类型为“等待超时”的错误代码跳出switch语句。(28)如果等待的内核对象被删除则返回消息内容为空返回消息大小为0 如果p_ts非空获取对象被删时的时间戳返回错误类型为“等待对象被删”的错误代码跳出switch语句。(29)如果等待状态超出预期则返回消息内容为空返回消息大小为0 返回错误类型为“状态非法”的错误代码跳出switch语句。(30)打开中断返回消息内容。
2. OS_MsgQGet()函数
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); //返回空消息停止执行}
#endifif (p_msg_q-NbrEntries (OS_MSG_QTY)0) //(1)//如果消息队列没有消息{*p_msg_size (OS_MSG_SIZE)0; //返回消息长度为0if (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; //(2)//从队列的出口端提取消息p_void p_msg-MsgPtr; //(3)//提取消息内容*p_msg_size p_msg-MsgSize; //(4)//提取消息长度if (p_ts ! (CPU_TS *)0) //(5)//如果 p_ts 非空{*p_ts p_msg-MsgTS; //获取消息被发布时的时间戳}p_msg_q-OutPtr p_msg-NextPtr; //(6)//修改队列的出队指针if (p_msg_q-OutPtr (OS_MSG *)0) //(7)//如果队列没有消息了{p_msg_q-InPtr (OS_MSG *)0; //清零出队指针p_msg_q-NbrEntries (OS_MSG_QTY)0; //清零消息数}else//(8)//如果队列还有消息{p_msg_q-NbrEntries--; //队列的消息数减1}/* 从消息队列提取完消息信息后将消息释放回消息池供继续使用 */p_msg-NextPtr OSMsgPool.NextPtr; //(9)//消息插回消息池OSMsgPool.NextPtr p_msg;OSMsgPool.NbrFree; //(10)//消息池的可用消息数加1OSMsgPool.NbrUsed--; //(11)//消息池的已用消息数减1*p_err OS_ERR_NONE; //错误类型为“无错误”return (p_void); //(12)//返回消息内容
}(1)如果消息队列目前没有可用消息返回消息长度为0 并且返回错误类型为“队列没消息”的错误代码和空消息停止执行。(2)而如果队列中有消息则从队列的出口端提取消息。(3)提取消息内容。(4)提取消息长度。(5)如果p_ts非空获取消息入队时的时间戳。(6)修改队列的出队指针。(7)如果队列没有消息了就将出队指针与消息个数清零。(8)如果队列还有消息队列的消息个数减1。(9)消息插回消息池以便重复利用。(10)消息池的可用消息数加1。(11)消息池的已用消息数减1。(12)返回消息内容。
3. OS_Pend()函数
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_listp_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
}