当前位置: 首页 > news >正文

(学习日记)2024.03.31:UCOSIII第二十八节:消息队列常用函数

写在前面:
由于时间的不足与学习的碎片化,写博客变得有些奢侈。
但是对于记录学习(忘了以后能快速复习)的渴望一天天变得强烈。
既然如此
不如以天为单位,以时间为顺序,仅仅将博客当做一个知识学习的目录,记录笔者认为最通俗、最有帮助的资料,并尽量总结几句话指明本质,以便于日后搜索起来更加容易。


标题的结构如下:“类型”:“知识点”——“简短的解释”
部分内容由于保密协议无法上传。


点击此处进入学习日记的总目录

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;                       //返回,停止执行}
#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
}

相关文章:

(学习日记)2024.03.31:UCOSIII第二十八节:消息队列常用函数

写在前面&#xff1a; 由于时间的不足与学习的碎片化&#xff0c;写博客变得有些奢侈。 但是对于记录学习&#xff08;忘了以后能快速复习&#xff09;的渴望一天天变得强烈。 既然如此 不如以天为单位&#xff0c;以时间为顺序&#xff0c;仅仅将博客当做一个知识学习的目录&a…...

DLC原理解析及其优化思考

1. 引言 Discreet Log Contract (DLC) 是由麻省理工学院的Tadge Dryja在2018年提出的一套基于预言机的合约执行方案。DLC 允许两方根据预定义的条件进行有条件付款。各方确定可能的结果并进行预签名&#xff0c;并在预言机签署结果时使用这些预签名来执行支付。 因此&#xff…...

tigramite教程(七)使用TIGRAMITE 进行条件独立性测试

文章目录 概述1 连续数值变量1.1 ParCorr 偏相关&#xff08;ParCorr类&#xff09;1.2 鲁棒偏相关&#xff08;RobustParCorr&#xff09;非线性检验1.3 GPDC1.4 CMIknn 2a. 分类/符号时间序列2b. 混合分类/连续时间序列多变量X和Y的测试 概述 这个表格概述了 X ⊥ Y ∣ Z X\…...

【DevOps工具篇】使用Ansible部署Keycloak oauth2proxy 和 单点登录(SSO)设置

【DevOps工具篇】使用Ansible部署Keycloak oauth2proxy 和 单点登录(SSO)设置 目录 【DevOps工具篇】使用Ansible部署Keycloak oauth2proxy 和 单点登录(SSO)设置Ansible 基础知识部署 Keycloak创建 OIDC-客户端创建 oauth2proxy 部署顶级 Ansible PlaybookHost.iniplayboo…...

鸿蒙OS开发实例:【应用状态变量共享】

平时在开发的过程中&#xff0c;我们会在应用中共享数据&#xff0c;在不同的页面间共享信息。虽然常用的共享信息&#xff0c;也可以通过不同页面中组件间信息共享的方式&#xff0c;但有时使用应用级别的状态管理会让开发工作变得简单。 根据不同的使用场景&#xff0c;ArkT…...

C#清空窗体的背景图片

目录 一、涉及到的知识点 1.设置窗体的背景图 2.加载窗体背景图 3.清空窗体的背景图 二、 示例 一、涉及到的知识点 1.设置窗体的背景图 详见本文作者的其他文章&#xff1a;C#手动改变自制窗体的大小-CSDN博客 https://wenchm.blog.csdn.net/article/details/137027140…...

Qt 实现的万能采集库( 屏幕/相机/扬声器/麦克风采集)

【写在前面】 之前应公司需要&#xff0c;给公司写过一整套直播的库( 推拉流&#xff0c;编解码)&#xff0c;类似于 libobs。 结果后来因为没有相关项目&#xff0c;便停止开发&维护了。 不过里面很多有用的组件&#xff0c;然后也挺好用的&#xff0c;遂开源出来一部分。…...

将写好的打印机代码打包成jar包然后直接注册成windows服务,然后通过调用插件的接口地址将流传到接口实现解析并无需预览直接通过打印机直接打印PDF文件

实现文件流PDF不需要预览直接调用打印机打印实现方案就是&#xff0c;将写好的打印机代码打包成jar包然后直接注册成windows服务&#xff0c;然后通过调用插件的接口地址将流传到接口实现解析并无需预览直接通过打印机直接打印PDF文件。源码地址...

加密软件VMProtect教程:使用脚本-功能

VMProtect是新一代软件保护实用程序。VMProtect支持德尔菲、Borland C Builder、Visual C/C、Visual Basic&#xff08;本机&#xff09;、Virtual Pascal和XCode编译器。 同时&#xff0c;VMProtect有一个内置的反汇编程序&#xff0c;可以与Windows和Mac OS X可执行文件一起…...

51单片机入门_江协科技_21.1_开发板USB口连接建议

1. 目前我自己用的普中A2版本的开发板&#xff0c;操作失误导致在开发板连接电脑并通电的情况下误将跳线帽触碰到开发板的3.3V与GND&#xff0c;导致USB口浪涌&#xff0c;2个电脑上面的USB口烧毁&#xff0c;开发板暂时没有任何问题&#xff0c;电脑USB口现在只是接通后有电&a…...

基于Spring Boot 3 + Spring Security6 + JWT + Redis实现登录、token身份认证

基于Spring Boot3实现Spring Security6 JWT Redis实现登录、token身份认证。 用户从数据库中获取。使用RESTFul风格的APi进行登录。使用JWT生成token。使用Redis进行登录过期判断。所有的工具类和数据结构在源码中都有。 系列文章指路&#x1f449; 系列文章-基于SpringBoot3…...

Kubernetes(k8s):精通 Pod 操作的关键命令

Kubernetes&#xff08;k8s&#xff09;&#xff1a;精通 Pod 操作的关键命令 1、查看 Pod 列表2、 查看 Pod 的详细信息3、创建 Pod4、删除 Pod5、获取 Pod 日志6、进入 Pod 执行命令7、暂停和启动 Pod8、改变 Pod 副本数量9、查看当前部署中使用的镜像版本10、滚动更新 Pod11…...

【随笔】Git 高级篇 -- 相对引用2(十三)

&#x1f48c; 所属专栏&#xff1a;【Git】 &#x1f600; 作  者&#xff1a;我是夜阑的狗&#x1f436; &#x1f680; 个人简介&#xff1a;一个正在努力学技术的CV工程师&#xff0c;专注基础和实战分享 &#xff0c;欢迎咨询&#xff01; &#x1f496; 欢迎大…...

xilinx AXI CAN驱动开发

CAN收发方案有很多&#xff0c;常见的解决方案通过是采用CAN收发芯片&#xff0c;例如最常用的SJA1000,xilinx直接将CAN协议栈用纯逻辑实现&#xff0c;AXI CAN是其中一种&#xff1b; 通过这种方式硬件上只需外接一个PHY芯片即可 上图加了一个电平转换芯片 软件设计方面&…...

Python:百度AI开放平台——OCR图像文字识别应用

一、注册百度AI开放平台 使用百度AI服务的步骤为&#xff1a; 注册&#xff1a;注册成为百度AI开放平台开发者&#xff1b;创建AI应用&#xff1a;在百度API开放平台上创建相关类型的的AI应用&#xff0c;获得AppID、API Key和Secret Key&#xff1b;调用API&#xff1a;调用…...

OpenEuler/Centos制作离线软件源

需求背景&#xff1a; 一般线上服务器都是不能连接外网&#xff0c;服务器安装好系统之后就需要部署相关软件&#xff0c;此时因为无法联网导致无法下载软件&#xff0c;所以都会做一个本地的离线软件源&#xff0c;本文简单介绍如何快速利用已经下载好的rpm包&#xff0c;制作…...

论文笔记:基于多粒度信息融合的社交媒体多模态假新闻检测

整理了ICMR2023 Multi-modal Fake News Detection on Social Media via Multi-grained Information Fusion&#xff09;论文的阅读笔记 背景模型实验 背景 在假新闻检测领域&#xff0c;目前的方法主要集中在文本和视觉特征的集成上&#xff0c;但不能有效地利用细粒度和粗粒度…...

攻防世界 xff_referer 题目解析

xff_referer 一&#xff1a;了解xxf和Referer X-Forwarded-For:简称XFF头&#xff0c;它代表客户端&#xff0c;也就是HTTP的请求端真实的IP&#xff0c;只有在通过了HTTP 代理或者负载均衡服务器时才会添加该项。 一般的客户端发送HTTP请求没有X-Forwarded-For头的&#xff0…...

open-cd框架调试记录

源于论文Changer: Feature Interaction Is What You Need forChange Detection 源码位置&#xff1a;open-cd/README.md at main likyoo/open-cd (github.com) 同样是基于MMSegmentation框架的代码&#xff0c;不符合本人编程习惯所以一直也没有研究这东西&#xff0c;近期打…...

【算法刷题day17】Leetcode:110.平衡二叉树、257. 二叉树的所有路径、404.左叶子之和

文章目录 Leetcode 110.平衡二叉树解题思路代码总结 Leetcode 257. 二叉树的所有路径解题思路代码总结 Leetcode 404.左叶子之和解题思路代码总结 草稿图网站 java的Deque Leetcode 110.平衡二叉树 题目&#xff1a;** 110.平衡二叉树** 解析&#xff1a;代码随想录解析 解题思…...

Linux云计算之Linux基础2——Linux发行版本的安装

目录 一、彻底删除VMware 二、VMware-17虚拟机安装 三、MobaXterm 安装 四、Centos 发行版 7.9的安装 五、rockys 9.1的安装 六、ubuntu2204的安装 一、彻底删除VMware 在卸载VMware虚拟机之前&#xff0c;要先把与VMware相关的服务和进程终止 1. 在windows中按下【Windo…...

C++:赋值运算符(17)

赋值也就是将后面的值赋值给变量&#xff0c;这里最常用的就是 &#xff0c;a1那么a就是1&#xff0c;此外还包含以下的赋值运算 等于int a 1; a10 a10加等于int a 1; a1;a2-减等于int a 1; a-1;a0*乘等于int a 2; a*5;a10/除等于int a 10; a/2;a5%模等于int a 10; a%…...

Spring Boot | Spring Boot的“数据访问“、Spring Boot“整合MyBatis“

目录: 一、Spring Boot”数据访问概述“二、Spring Boot”整合MyBatis”1. 基础环境搭建 (引入对应的“依赖启动器” 配置数据库的“相关参数”)① 数据准备 (导入Sql文件)② 创建项目&#xff0c;引入相应的启动器&#xff0c;编写数据库对应的“实体类”③额外添加pom.xml文…...

ActiViz中的数据集vtkPolyData

文章目录 前言一、数据结构二、数据内容三、几何操作四、数据导入与导出五、数据可视化六、函数详解1、SetPoints(vtkPoints points):2、SetPolys(vtkCellArray polys):3、GetNumberOfPoints():4、GetNumberOfCells():5、GetPointData():6、GetCellData():7、Ge...

【测试篇】测试用例

文章目录 前言具体设计测试用例等价类边界值场景设计法判定表&#xff08;因果图&#xff09;正交排列&#xff08;用的非常少&#xff09;错误猜测法 前言 什么是测试用例&#xff1f;&#xff1f; 测试用例是针对软件系统或应用程序的特定功能或场景编写的一组步骤&#xf…...

Shell学习 - 2.24 Shell let命令:对整数进行数学运算

let 命令和双小括号 (( )) 的用法是类似的&#xff0c;它们都是用来对整数进行运算&#xff0c;读者已经学习了《Shell (())》&#xff0c;再学习 let 命令就相当简单了。 注意&#xff1a;和双小括号 (( )) 一样&#xff0c;let 命令也只能进行整数运算&#xff0c;不能对小数…...

langchain Chroma 构建本地向量数据库

langchain Chroma 构建本地向量数据库 # import from langchain_community.document_loaders import TextLoader from langchain_community.embeddings.sentence_transformer import (SentenceTransformerEmbeddings, ) from langchain_community.embeddings import HuggingFa…...

Rust 中的字符串类型:`str` 和 `String`

Rust 中的字符串类型&#xff1a;&str 和 String 文章目录 Rust 中的字符串类型&#xff1a;&str 和 String1. &str&#xff1a;不可变的字符串引用2. String&#xff1a;可变的字符串3、字符串使用综合案例代码执行结果 在 Rust 编程语言中&#xff0c;有两种主要…...

Visual Studio(VS) 搭建 QT 开发环境

Visual Studio(VS) 搭建 QT 开发环境 在当今的软件开发领域,Visual Studio(VS)是一款备受欢迎的集成开发环境(IDE),而 QT 则是一个强大的跨平台应用程序框架。将两者结合使用,可以为开发人员提供高效、便捷的开发体验。本文将详细介绍如何在 VS2022 中搭建 QT 开发环…...

Qt模拟面试(超硬核)

1. 请简要介绍一下你的 Qt 开发经验。 建议&#xff1a;诚实地描述你的 Qt 经验&#xff0c;包括你使用过的 Qt 版本、开发过的项目类型、遇到的挑战以及如何解决它们。 假如你没有开发经验&#xff0c;可以提供一些关于 Qt 开发的一般信息和常见的经验分享。 Qt 是一个跨平…...

济南网站建设用途/重庆百度快速优化

OperaMasks-UI是一款基于jQuery并提供丰富组件的前端UI库&#xff0c;拥有丰富的业务组件、强大的扩展能力、高度的可靠性&#xff0c;满足大部分业务场景需求&#xff0c;带给你便捷的前端开发新体验。 官网地址&#xff1a; http://ui.operamasks.org/在线演示&#xff1a; h…...

微信开放平台登录入口/seo关键词排名优化如何

记录一下 今天使用Hexo GitHub搞个博客&#xff0c;在使用npm全局安装hexo的时候出现了这个问题,zsh:command not found: hexo npm install -g hexo hexo init myBlog 复制代码解决 两种方法 reinstall npm with a version manager 重新安装npmchange npms default directory m…...

网站加视频播放设计怎么做的/seo百度关键词优化

1、首先去(http://www.apachelounge.com/download/)下载一个合适的mod_fcgid 文件。 2、将解压后的文件改为mod_fcgid.dll 并复制到apache的module目录下面。3、打开httpd.conf文件&#xff0c;添加如下内容&#xff1a;1 LoadModule fcgid_module modules/mod_fcgid.so 2 &l…...

网站建设服务介绍/桂林市天气预报

​作者&#xff1a;王悦 爱可生研发团队成员&#xff0c;负责数据库管理平台相关项目的开发和故障排查&#xff0c;好奇 MySQL 技术原理及各类数据库实现方案。 本文来源&#xff1a;转载自公众号-图解 MySQL *爱可生开源社区出品&#xff0c;原创内容未经授权不得随意使用&…...

求人做网站/如何给公司做网络推广

Headless Browser(无头的浏览器)是什么鬼?简而言之&#xff0c;Headless Browser是没有图形用户界面(GUI)的web浏览器&#xff0c;通常是通过编程或命令行界面来控制的。Headless Browser的许多用处之一是自动化可用性测试或测试浏览器交互。如果您正在尝试检查页面在不同的浏…...

做网站公司需要什么资质/营销软件站

前言 DLL&#xff0c;即Dynamic-link library&#xff0c;动态链接库。关于其详细介绍&#xff0c;参阅&#xff1a;https://en.wikipedia.org/wiki/Dynamic-link_library 创建一个DLL 声明导出函数&#xff1a;extern “C” __declspec(dllexport) int add(int a, int b);其中…...