写在前面:
由于时间的不足与学习的碎片化,写博客变得有些奢侈。
但是对于记录学习(忘了以后能快速复习)的渴望一天天变得强烈。
既然如此
不如以天为单位,以时间为顺序,仅仅将博客当做一个知识学习的目录,记录笔者认为最通俗、最有帮助的资料,并尽量总结几句话指明本质,以便于日后搜索起来更加容易。
标题的结构如下:“类型”:“知识点”——“简短的解释”
部分内容由于保密协议无法上传。
点击此处进入学习日记的总目录
2024.03.31:UCOSIII第二十八节:消息队列常用函数
- 四十二、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) //消息队列大小(不能为0)
OS_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; //返回,停止执行
}
#endif
OS_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); //将该队列添加到消息队列双向调试链表
#endif
OSQQty++; //(14)//消息队列个数加1
OS_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(有错误),停止执行
}
#endif
CPU_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); //将该队列从消息队列调试列表移除
#endif
OSQQty--; //(16)//消息队列数目减1
OS_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); //将该队列从消息队列调试列表移除
#endif
OSQQty--; //(23)//消息队列数目减1
OS_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;
#endif
p_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;
#endif
p_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; //返回,停止执行
}
#endif
ts = 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; //返回(尚未发布),停止执行
}
#endif
OS_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; //返回,停止执行
}
#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; //(1)//将消息控制块从消息池移除
OSMsgPool.NextPtr = p_msg->NextPtr; //(2)//指向下一个消息(取走首个消息)
OSMsgPool.NbrFree--; //(3)//消息池可用消息数减1
OSMsgPool.NbrUsed++; //(4)//消息池被用消息数加1
if (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; //队列的消息数为1
p_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;
#endif
p_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;
#endif
p_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(有错误),停止执行
}
#endif
if (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; //返回消息大小为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: //(27)//如果等待超时
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: //(28)//如果等待的内核对象被删除
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: //(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); //返回空消息,停止执行
}
#endif
if (p_msg_q->NbrEntries == (OS_MSG_QTY)0) //(1)//如果消息队列没有消息
{
*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; //(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)//消息池的可用消息数加1
OSMsgPool.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_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
}