uCOS消息队列相关函数的理解

OSQCreate()

创建消息队列函数。 有四个入口参数:消息队列指针;消息队列名称;消息队列大小(不能为0);返回错误类型。

函数过程:①首先进行安全检查,非法调用检查以及参数检查等。参数检查一般是检查入口参数是否非法,比如创建队列为空,消息队列大小为0等等,如果存在,则return;返回并停止运行。②然后依次给消息队列结构体中元素赋初值。因为在OSQCreate()创建消息队列之前,仅仅是定义了一个消息队列的结构体,然后编译器为此结构体分配了一块内存,需要通过OSQCreate()进行一系列检查并赋值。首先标记创建的数据结构类型为消息队列;标记消息队列的名称;初始化消息列表;初始化等待列表;如果使能了调试代码和变量,将该队列添加到消息队列双向调试链表。③创建成功,消息队列个数加1,退出临界段,返回无错误。

类型标识符:标记创建对象数据结构为消息队列,即给消息队列的类型参数赋值。是通过 (OS_OBJ_TYPE)CPU_TYPE_CREATE('Q', 'U', 'E', 'U')进行的。CPU_TYPE_CREATE()类型函数有四个参数,都是8位的字符型变量,这个函数通过移位,比如第一个参数移0位,第二个参数移8位...以此类推,然后通过OS_OBJ_TYPE强制转换成32位整型变量,达到独一无二类型标识符的目的。

初始化消息列表结构体:OS_MsgQInit(&p_q->MsgQ, max_qty)。因为是新定义了一个消息队列,只分配了一个内存区域给它,在初始化时,只会赋予它名字、大小,数据结构标识符。其他内存区域因为没有消息,里面的内容应该为空或者初始化为0。所以在初始化消息列表结构体时,只会赋予消息队列的大小(队列中允许的最大消息个数),其余指向消息的指针因为没消息指向为NULL,当前消息个数和队列中的消息个数峰值都赋值为0。

初始化等待(阻塞)列表结构体:OS_PendListInit(&p_q->PendList)。一开始等待列表中时没有等待的任务的,所以指针赋值为NULL,变量赋值为0。(因为等待列表是包含在消息队列结构体中的,所以每个消息队列都独占一个等待列表)。

}

{

 OSQDel(): 

消息队列删除函数。队列删除函数是根据队列结构(队列句柄)直接删除的,删除之后这个消息队列的所有信息都会被系统清空,而且不能再次使用这个消息队列了。想要使用消息队列删除函数就必须将OS_CFG_Q_DEL_EN 宏定义配置为 1。

函数实现过程

OSQDel()函数的入口参数有三个:消息队列指针;选项;返回错误类型。返回值为等待列表中任务数。

① 函数首先进行安全检查、参数检查等,参数检查中不仅对指向操作对象(消息队列)的指针进行了检查,对选项这个参数也进行了检查,主要是判断选项是否超出预期。然后检查操作对象的数据类型是否为消息队列类型。② 然后根据选项分类操作:对待选项OS_OPT_DEL_NO_PEND,表示需要在没有任务等待该队列时删除队列,所以用if-else进行判断,如果没任务就调用函数OS_QClr()清除该队列内容,消息队列数量减1;如果有任务则让错误类型为“有任务在等待该队列”,退出。③ 对待选项OS_OPT_DEL_ALWAYS,表示不管有无任务在等待该消息队列,都删除。所以根据任务数,用一个循环,首先删除等待列表中任务(通过调用OS_PendObjDel()),然后调用函数OS_QClr()清除该队列内容。注意:如果使能了调试代码和变量,在删除消息队列前,需要将该消息队列从调试列表中移除。

注:一般是在没有任务在等待该消息列表是删除,即在OS_OPT_DEL_NO_PEND选项下删除消息队列

-调用OS_PendObjDel()

将阻塞在内核对象(如信号量)上的任务从阻塞态恢复。这个函数只是为删除等待列表中的一个任务而封装起来的。为了删除等待列表中全部任务,应该配合while循环

该函数有三个参数:指向任务控制块的指针;时间戳;指向os_pend_obj目标等待结构体的指针,在这个函数中,指删除对象的类型。因为被删除对象的类型有消息队列、信号量、互斥量、事件等,每一个对象的结构体元素都不尽相同,所以需要强制转换指针变量。指针变量是按照指针类型对所指向的内存进行解析,所以可以判断这些对象结构体元素的排版都差不多,只不过是最后多了或者少了些元素,不然指针强制转换后无法根据类型解析内存

函数实现过程:既然要从等待列表中删除任务,就要根据任务的状态分类操作,用多分支判断语句switch和case。与等待无关的状态,比如就绪、延时、挂起状态,则退出,不进行删除;这只是进行一个检查,害怕用户调用此函数删除不处于等待阻塞态的任务。等待的任务状态分为OS_TASK_STATE_PEND、OS_TASK_STATE_PEND_TIMEOUT,OS_TASK_STATE_PEND_SUSPENDED、OS_TASK_STATE_PEND_TIMEOUT_SUSPENDED。

前两个分别是任务无限期等待和任务超时等待,如果任务在等待多个信号量或消息队列,强制接触任务对某一对象的等待;复位任务的消息域(指向消息的指针)和消息大小;然后调用OS_PendListRemove()将任务从等待列表中移除,如果是等待超时状态,要把任务从时基列表中移除;然后添加进就绪列表;最后修改任务的状态和标记。

后两个状态是任务在等待中被挂起和任务在超时等待中被挂起。其实和上面两个操作步骤差不多,只是从等待列表中删除后,前两个任务的状态变为就绪态,后两个任务的状态变为挂起态。所以其实可以把公共部分提出来,优化代码。

}

{

OSQPost()消息队列发送函数

任务或者中断服务程序都可以给消息队列发送消息。当发送消息时,如果队列未满,就说明运行信息入队;uCOS 会从消息池中取出一个消息,挂载到消息队列的末尾(FIFO发送方式);如果是 LIFO 发送方式,则将消息挂载到消息队列的头部;然后将消息中MsgPtr 成员变量指向要发送的消息(因为消息池在初始化时并没有指定消息指向,使用指针不仅是为了指向不定长的消息,也是为了在运行过程中,灵活指向用户要发送的消息),如果系统有任务阻塞在消息队列中,那么在发送了消息队列的时候,会将任务解除阻塞。

函数入口参数:消息队列指针;消息指针;消息大小;选项;返回错误类型。(消息指针之所以为void*类型是因为消息可以是char*,也可以是int*等)

函数实现过程:首先进行安全检查和参数检查,参数检查中包括选项检查,主要检查选项是否符合超出预期,符合预期的才进行下面操作。

符合预期的选项有:OS_OPT_POST_FIFO /* (默认)采用 FIFO 方式发送 */

OS_OPT_POST_LIFO /*采用 LIFO 方式发送消息*/

OS_OPT_POST_1  /*将消息发布到最高优先级的等待任务*/ (这个选项没检查)

OS_OPT_POST_ALL  /*向所有等待的任务广播消息*/

OS_OPT_POST_NO_SCHED /*发送消息但是不进行任务调度*/

然后进行类型检查,检查对象是否为消息队列类型。然后进行消息的发送。这里要注意的是:中断服务程序发送消息。因为存在中断延迟发布的情况,使得原本在中断中发布的信息变成任务级发布,所以如果使能了中断延迟发布,并且发送消息的函数是在中断中被调用,此时就不该立即发送消息,而是将消息的发送放在指定发布任务中,此时系统就将消息发布到租单消息队列中,等待到中断发布任务唤醒再发送消息(调用OS_IntQPost())。如果不是中断延迟发布,就直接调用OS_QPost()函数进行消息的发送。

-调用OS_QPost()

此函数在OSQPost()中被调用,主要是封装来实现消息的发送功能的函数。此函数是uC/OS-III的内部函数,用户的应用程序不应该调用它。发送消息时我们调用OSQPost()即可。此函数有六个入口参数:消息队列指针;消息指针;消息大小;选项;消息被发布时的时间戳;返回错误类型。虽然该函数是void类型,没有返回。但是因为入口参数中有个指针变量,用来保存返回的错误信息,所以可以认为是函数的出口参数。

这里要熟悉消息队列的运作方式任务或者中断服务函数给消息队列发送消息,消息队列没满则FIFO或者LIFO入队,如果满则返回错误;但是如果有任务在等待任务或中断服务函数发送消息给消息队列,则不会将消息入队因为没必要入了队马上又出队,直接将消息发送给等待列表中的最高优先级的任务,如果选择了OS_OPT_POST_ALL,那么就要广播把消息发送给等待列表中的所有任务。(重要)

函数过程:该函数也确实按照消息队列的运作方式编写。首先获取该队列的等待列表,然后用if语句进行判断。① 如果没有任务在等待该队列的消息,把任务或中断服务程序发过来的消息按照LIFO,FIFO插入到消息队列中,调用函数OS_MsgQPut()。② 如果有任务在等待该队列的消息,那么根据选项和OS_OPT_POST_ALL相与,判断需不需要把消息发送给等待列表中的所有任务。如果需要,则获取等待列表中任务数,逐个发送;如果不需要,就获取等待列表中头部任务,因为等待列表是按照优先级排的任务,把消息发送给他;把消息发送给等待中的任务时调用函数OS_Post()。③ 最后判断选项有没有选择OS_OPT_POST_NO_SCHED,如果没选择“发布完不调度任务” ,则调用OSSched()进行任务切换。(这里是可选调度???不是说任务状态改变,就要调用此函数???)(解释,任务调用此函数把消息发送给消息队列时,任务自身状态没有变,并没有被挂起或者延时阻塞,任务执行并没有被打断,所以不需要调用切换函数。所以是可选项。相当于任务切换函数切换到该任务运行,只不过该任务中调用了发送消息给消息队列的函数,任务没有被打断,没进入阻塞状态,不需要切换任务,来提高CPU利用率。应该让该任务执行完或者进入阻塞状态再切换)(之所以可选,是因为,可能存在某个任务急需这个消息,(而且刚好任务在等待列表中获取到消息解除阻塞)为了减少任务等待时间,所以进行切换。特别地,在释放信号量中,该选项更重要。因为信号量如果作为同步或者保护资源来,立即切换,可以减少同步的延迟或者优先级反转的时间)。

--调用OS_MsgQPut():

这个函数是封装起来,用来将消息进入到消息队列的消息列表中。

六个入口参数:消息列表指针;消息指针;消息大小;选项;消息发布时的时间戳;返回错误类型。

函数过程:① 除了安全检查外,还需要通过检查消息列表中消息个数判断消息队列是否已满,再判断消息池中是否有可用消息。② 检查后,从消息池中获取一个消息。(从消息池获取一个消息就是取走一个消息,取走的是消息池的第一个消息,保存第一个消息后,使管理消息池的指着指向第二个消息;消息池可用消息数减 1;消息池被用消息数加 1;更新消息被用最大数目的历史记录,如果被用最大数目小于被用消息数,则NbrUsedMax = NbrUsed)③ 将获取的消息插入到消息队列的消息列表中(分为三种情况:如果是空队列,那么插入的结点既是队头指针也是队尾指针。如果不是,是FIFO模式,就是把结点插入到队尾,根据“结点指针有变化先保存”的原则,先保存消息列表中的队尾结点指针,然后修改指针指向。如果是LIFO模式,就把结点插入到队头,修改消息列表的OutPtr和消息指针)。④ 更新消息列表历史最大消息数。⑤ 取出来的消息是初始化的消息,需要给消息赋值,添加消息内容,消息大小,发送消息时的时间戳。最后返回无错误信息。

发现的结论uCOS的链表,都有一个管理结点的结构体用指针连接在一起,并且管理结构体中通常有链表结点个数的变量,有时还不止一个,包括最大数量,目前结点数量等。就比如消息池,消息池中的消息时结构体,通过指针连接为单链表,然后有个管理消息池的全局的结构体,该结构体有两个指针,一个指向消息池的头部消息,一个指向消息池的尾部消息,还有表示消息数量的变量

总结:消息池中的消息初始化后是为空的,只是串成链表形式,方便消息的管理和存取;在任务调用函数OSQPost()往消息队列发送消息时,从消息队列取出消息,在插入到消息队列的消息列表中时,需要给消息赋值(消息内容,消息大小,发送时间戳)。任务发送消息,并不是存储到消息池,再赋值给消息列表的;而是直接从消息池取出初始化为NULL(0)的消息,先插入到消息列表中,再给消息赋值

--调用OS_Post():

这个函数是封装起来,任务发送消息给消息队列,若消息队列中有任务在等待消息,则不进入队列,直接把消息发送给消息队列中等待列表中的任务。

五个入口参数:内核对象类型指针(这里就把消息队列指针强制转化为OS_PEND_OBJ);任务控制块(是指在消息队列中等待列表的任务);指向消息的指针;消息大小;时间戳。

函数过程:对于涉及任务的函数,一定要检查任务的状态,根据任务的状态分类操作。① 用switch--case多分支选择语句进行任务的判断,只对四个等待状态OS_TASK_STATE_PEND和OS_TASK_STATE_PEND_TIMEOUT,OS_TASK_STATE_PEND_SUSPENDED(无限期等待被挂起)和OS_TASK_STATE_PEND_TIMEOUT_SUSPENDED(超时等待被挂起)进行操作。② 任务处于无期限等待状态或者是有期限等待状态:如果任务在等待多个信号量或消息队列,就调用 OS_Post1()函数标记一下是哪个内核对象进行发布(释放)操作。如果不是,保存消息指针和消息大小和时间戳到任务控制块(相当于任务获得该消息!!!;把任务从等待列表中移除,调用函数OS_PendListRemove();如果使能了调试代码和变量,调用OS_PendDbgNameRemove()移除内核对象的调试名;注意,如果是超时等待,因为设置有延迟时间,会把任务送进时基列表中,这里也要一并移除;最后将任务插入就绪列表中,修改任务状态位就绪态,清除等待标志,标记不再等待。③ 对于无限期/有限期等待被挂起的状态,步骤与②相差不太,都需要把消息发送给任务,发送成功后把任务从等待列表中移除,如果有延时之类的,从时基列表中移除,只是最后修改任务的状态为挂起态

总结消息队列发送函数:任务要发送消息给消息队列,调用函数OSQPost()即可。只不过为了优化程序结构,该函数中封装了很多小函数用来一起完成功能,OSQPost()调用关系如下图。

  }

{

OSQPend()消息队列获取函数 

当任务试图从队列中的获取消息时,用户可以指定一个阻塞超时时间,当且仅当消息队列中有消息的时候,任务才能获取到消息。在这段时间中,如果队列为空,该任务将保持阻塞状态以等待队列消息有效。当其他任务或中断服务程序往其等待的队列中写入了数据,该任务将自动由阻塞态转为就绪态。当任务等待的时间超过了用户指定的阻塞时间,即使队列中尚无有效消息,任务也会自动从阻塞态转为就绪态。当然,可以不设置等待时间,让它一直等待,直到等到消息,设置timeout参数为0即可。

注意读取流程任务从消息队列中获得消息,如果消息队列中的消息列表中有消息,则任务不进入消息队列的等待列表,直接获得消息;如果消息列表中没有消息,则需要把任务插入到等待列表中等待消息

六个函数参数:消息队列指针(要读取哪个队列的消息);等待时间(单位是时钟节拍);选项;返回消息大小;获取等到消息时的时间戳;返回错误类型。

函数返回:指向消息的指针。

函数过程:① 安全检查、中断中非法调用检查、参数检查、对象类型检查。注意,安全检查和中断中非法调用检查是默认禁止的,其中安全检查只提供了函数入口,需要自己编写程序实现。参数检查包括消息队列是否为空,用于保存返回的消息大小指针是否为空(没定义)。这里的选项检查并不是要进行什么特殊操作,选项OS_OPT_PEND_BLOCKING,OS_OPT_PEND_NON_BLOCKING是是否要阻止队列为空的意思。② 调用OS_MsgQGet()从消息队列中获取消息,如果获取成功则直接返回,不需要将任务插入到等待列表。 ③ 如果消息列表中没有消息,任务就需要送入等待列表,任务就需要进入等待阻塞,如果此时用户选项为OS_OPT_PEND_NON_BLOCKING,则返回错误“等待渴望阻塞”,并return返回。④ 如果选择的是阻塞任务,判断调度器是否被其他函数锁住,调度器被锁意味着不允许其他函数对任务进行操作,所以需要返回错误“调度器被锁,并返回。如果没有,则该函数调用OS_CRITICAL_ENTER_CPU_EXIT()锁住调度器,因为接下来要费时间把任务添加进时基和等待列表,不允许在这段时间内其他任务获得CPU控制权,打断该过程的进行。然后调用函数OS_Pend(),将任务插入到等待和时基列表(看是否超时等待)。⑤ 调用OSSched(),进行任务切换,因为该任务等待阻塞了,相当于延时阻塞,为了高效率利用CPU,在等待消息的这段时间里,应该把控制权交给其他任务。⑥ 差点以为切换后,就应该执行其他任务了,后面就不应该写代码了???错误。想想任务阻塞延时,任务调用延时函数后,在延时阻塞函数中除了设置任务的延时时间外,还会切换任务,但是任务时间到了,会重新进入就绪列表,等轮到该优先级任务时,重新返回现场执行该任务。所以切换后,要考虑该任务是否还需要执行什么操作,因为会保护现场,当再一次切换到该优先级的任务下时,从之前切换断开的位置处继续执行。所以这里切换后还需要做什么操作吗想一想,当os再次切换到该任务,从这里执行时,是不是意味着,任务已经从时基列表和等待列表中移除,只有移除后,才能重新进入就绪列表,重新被调度;那么是不是意味着任务已经获得了消息,或者等待时间都过了还没有获得消息,自动转为的就绪态。那么是不是接下来应该根据任务状态判断看返回什么信息,而且,因为函数有返回值,切换后至少应该写一个return xx之类的,才不会报错。⑦ 如果控制块状态为正常:OS_STATUS_PEND_OK,就应该把消息队列中消息列表的消息给任务,包括返回消息大小和时间戳。状态为等待被终止:OS_STATUS_PEND_ABORT,那么返回消息应该为空,获取等待被终止时的时间戳和错误类型。状态为等待超时:OS_STATUS_PEND_TIMEOUT,返回内容为空,获取等待超时的时间戳和错误类型。状态为等待的内核对象被删除:OS_STATUS_PEND_DEL,返回内容为空,获取对象被删除的时间戳和错误类型。其他状态,不符合预期,返回内容为空,返回状态非法错误类型。

-调用OS_MsgQGet()

调用OS_MsgQGet()从消息队列中获取消息,如果获取成功则直接返回,不需要将任务插入到等待列表。

4个入口参数:消息列表的指针;返回消息大小的指针;返回时间戳指针;返回错误类型指针。

返回值:指向消息的指针。

函数实现过程:情况无非分为两种:一是消息队列的消息列表中没有消息,那么返回空消息;一种是有消息,那么就返回消息,更新消息队列的指针。这里需要注意的是,消息是从消息池中取出插入到消息队列的消息列表中的,那么任务从消息队列中读走消息后,消息应该重新返回消息池,以便重复利用(插入消息池的头)(这里消息返回消息池时不需要清空是因为在任务调用函数OSQPost()发送消息给消息队列,重新从消息池取消息时,是会赋值给消息的,不管以前消息是空还是有只,都会重新更新内容)。① 首先进行安全检查,这里不需要参数检查,是因为该函数是在OSQPend()被调用,在OSQPend()已经对这些参数进行了检查。② 如果消息队列中消息列表没有消息,则返回空消息及大小,错误类型为队列无消息,时间戳清0。③ 如果消息队列有消息,让消息列表中的消息出队,修改消息列表管理结构体的指向队头的指针(出队指针)。注意,不管是FIFO还是LIFO模式,出队的消息都是从队头删除。消息出队后,要注意队列中还有无消息,如果没有消息,需要清0消息列表管理结构体的指向队尾的指针(入队指针)以及清0消息数;如果还有消息,则把消息数减1即可。④ 把消息释放回消息池。任务从消息池取消息时,是从管理消息池结构体指向的第一个开始取的,把消息释放回消息池,也应该是把他插入到管理消息池结构的后面。取出和插入都是从头开始。所以这里按照这个思路修改指针,就是单链表插入操作,修改两个指针,注意指针修改的先后顺序。消息池可用消息数加1,已用消息数减1。

-调用OS_Pend()

如果消息列表中没有消息,调用函数OS_Pend(),将任务移出就绪列表并插入到等待列表和(时基列表(看是否超时等待))。

4个入口参数:指向OS_PEND_DATA结构体的指针;指向OS_PEND_OBJ内核对象的指针(这里把指向消息队列的指针强制类型转换);任务等待状态(这里赋值为任务在消息队列中等待);等待节拍数timeout。

函数过程:① TCB中包含两个等待的状态变量。所以赋值PendOn表示正在等待哪种内核对象;PendStatus等待的状态,包括是否正常,被终止,超时等。② 调用OS_TaskBlock()阻塞当前任务运行,就是从就绪列表中删除,如果参数timeout不为0,就把任务插入时基列表中;并修改任务状态为等待或者超时等待。③ 插入消息列表的等待列表,分为两种情况:一是等待的消息队列存在。那么就获取消息队列的等待列表,注意等待列表中的数据结构是OS_PEND_DATA类型,这就是一个结构体,在插入之前,需要先定义一个,所以在该函数的入口参数中才会有“指向OS_PEND_DATA结构体的指针”,系统需要该结构体只是模糊的知道需要,目前不知道具体需要这些元素干嘛????,该结构体中的元素主要包括有等待的任务TCB,指向任务内容和大小的指针,指向等待对象的指针,两个组成双链表的指针等等,所以不能简单将任务TCB插入进消息列表,需要定义一个这样的结构体。因为OS_Pend()是内部函数,这里被OSQPend()调用,在OSQPend()中已经定义了OS_PEND_DATA类型的局部变量pend_data,该变量作为OS_Pend()的入口参数,调用OS_PendDataInit()进行该结构体的初始化(一般元素数量多的结构体都编写一个函数进行结构体的初始化),然后调用OS_PendListInsertPrio()按照优先级将OS_PEND_DATA结构体插入等待列表中。④ 第二种情况是消息队列不存在,就要把TCB中用于指向等待列表中断元素指针PendDataTblPtr变为NULL((OS_PEND_DATA *)0),并清零任务的等待域数据PendDataTblEntries。

OS_Pend()应该在其他函数中也有调用,因为在消息队列获取函数OSQPend()调用该函数之前已经检查了消息队列是否为空,在不为空的条件下,且消息列表没有消息时,调用该函数将任务送入等待列表。

     

猜你喜欢

转载自blog.csdn.net/m0_43443861/article/details/125960684