我是靠谱客的博主 含糊小伙,最近开发中收集的这篇文章主要介绍RTOS之UCOS(三)---任务间同步与通信一、临界区保护二、事件控制块三、任务间通信更多文章:,觉得挺不错的,现在分享给大家,希望可以做个参考。

概述

文章目录

  • 一、临界区保护
  • 二、事件控制块
    • 2.1 事件控制块的结构
    • 2.2 事件控制块的操作
  • 三、任务间通信
    • 3.1 信号量
    • 3.2 互斥量
    • 3.3 消息邮箱
    • 3.4 消息队列
    • 3.5 总结
  • 更多文章:

一、临界区保护

之前谈到C++多线程并发编程时就强调过线程同步的问题,现在操作系统进行多任务调度时自然也少不了任务同步的问题,前面谈线程同步时已经讲清楚了如何防止共享资源的并发访问,任务同步的情况与此类似,就不再赘述了。

线程同步中防止共享资源并发访问的方法主要是使用锁机制或原子操作,操作系统任务同步中主要也是加锁和原子操作,但多出了一个临界区的概念,所谓临界区(也称为临界段),就是访问和操作共享数据的代码段。为了避免在临界区中并发访问,编程者必须保证这些代码原子的执行,操作在执行结束前不可被打断,就如同整个临界区是一个不可分割的指令一样。理解了临界区,就明白要保证任务同步,需要对临界区进行加锁。任务的打断或切换主要是通过中断来实现的,所以在MCU中可以通过关闭/打开中断功能实现对临界区的加锁保护。

以UCOS为例,它是通过两个宏OS_ENTER_CRITICAL()和OS_EXIT_CRITICAL()实现对临界区的同步保护的,UCOS给出了三种实现方式,常用的是第三种,实现代码如下:

#if OS_CRITICAL_METHOD == 1
#define OS_ENTER_CRITICAL() __asm__("cli")
#define OS_EXIT_CRITICAL() __asm__("sti")
#endif
 
#if OS_CRITICAL_METHOD == 2
#define OS_ENTER_CRITICAL() __asm__("pushf nt cli")
#define OS_EXIT_CRITICAL() __asm__("popf")
#endif
 
#if OS_CRITICAL_METHOD == 3
#define OS_ENTER_CRITICAL() (cpu_sr = OSCPUSaveSR())
#define OS_EXIT_CRITICAL() (OSCPURestoreSR(cpu_sr))
#endif

第一种方式,OS_ENTER_CRITICAL()简单地关中断,OS_EXIT_CRITICAL()简单地开中断,这种方式虽然简单高效,但无法满足嵌套的情况。第二种方式,OS_ENTER_CRITICAL()会在关中断前保存之前的标志寄存器内容到堆栈中,OS_EXIT_CRITICAL()从堆栈中恢复之前保存的状态,这样就允许了临界区嵌套的情况,但有时候入栈的状态标志寄存器内容会被冲掉,导致恢复状态时出现异常错误。 第三种,在关中断前,使用局部变量保存中断状态,这也是几乎所有实时操作系统共有的选择。

临界区保护是需要关闭中断的,如果频繁关中断,势必会影响RTOS系统响应的实时性,有没有更好的方式实现任务同步和任务间通信呢?回想下C++线程同步中用到的两个工具:互斥量与条件变量,互斥量提供了一个全局互斥锁,可以方便管理多线程间对共享资源的访问同步,条件变量则引入了通知唤醒机制,避免了程序无谓的等待,且两者一般配合使用保证线程间的高效同步。那么操作系统比如UCOS中有没有类似的工具实现任务同步呢?

二、事件控制块

2.1 事件控制块的结构

简单的操作系统比如UCOS提供了任务控制块TCB来描述和管理任务,同时提供了一个事件控制块ECB来描述和管理事件,这里的事件指的是任务间传递的信号,包括对共享资源进行同步访问的控制信号比如信号量与互斥量,也包括多个任务间传递的数据信号比如消息邮箱和消息队列。下面先给出ECB(EVENT CONTROL BLOCK)的数据结构代码:

// MicriumSoftwareuCOS-IISourceucos_ii.h

typedef struct os_event {
    INT8U    OSEventType;                    /* Type of event control block (see OS_EVENT_TYPE_xxxx)    */
    void    *OSEventPtr;                     /* Pointer to message or queue structure                   */
    INT16U   OSEventCnt;                     /* Semaphore Count (not used if other EVENT type)          */
    OS_PRIO  OSEventGrp;                     /* Group corresponding to tasks waiting for event to occur */
    OS_PRIO  OSEventTbl[OS_EVENT_TBL_SIZE];  /* List of tasks waiting for event to occur                */

#if OS_EVENT_NAME_EN > 0u
    INT8U   *OSEventName;
#endif
} OS_EVENT;

#define  OS_EVENT_TYPE_UNUSED           0u
#define  OS_EVENT_TYPE_MBOX             1u
#define  OS_EVENT_TYPE_Q                2u
#define  OS_EVENT_TYPE_SEM              3u
#define  OS_EVENT_TYPE_MUTEX            4u
#define  OS_EVENT_TYPE_FLAG             5u

其中OSEventType表示事件类型,主要类型也在上面列出来了,就是前面提到的控制类型如信号量/互斥量和消息类型如消息邮箱/消息队列,当然还包括未使用类型。OSEventPtr指向消息类型结构的地址,OSEventCnt记录控制类型的信息如信号量计数器。OSEventGrp与OSEventTbl[OS_EVENT_TBL_SIZE]则是共同管理一个事件等待任务表,跟前面任务调度器中提到的OSRdyGrp与OSTCBTbl[OS_MAX_TASKS]共同管理一个任务就绪表类似,无非前者表示的是处于等待事件状态的任务及其优先级,后者表示的是处于就绪状态的任务及其优先级,对任务等待表的操作(某任务对应位置1、某任务对应位清0、查找到当前任务就绪表中优先级最高的任务优先级)也是跟对任务就绪表的操作一致,后面给出操作代码。二者还有一点不同之处是,整个操作系统由一个任务就绪表管理所有的任务,而每个事件都有一个任务等待表管理请求该事件的任务。下面是等待任务表图示:
任务等待表
任务间通信一般都是有方向性的,比如控制信息互斥量需要先等一个任务释放互斥量,另一个任务才能获得该互斥量,再比如数据信息消息邮箱需要先等一个任务写入消息,另一个任务才能读取消息。假如前一个任务还在占用某共享资源,后一个任务就需要等待直到前一个任务对共享资源访问完成。任务的切换和执行都是针对处于就绪态的任务而言的,如果一个任务正在等待某一资源或事件,则可以先将该任务置于等待状态,这样该任务就不会被执行。如果前一个任务已完成对某共享资源的访问,则将后一个处于等待状态的任务从等待状态转换到就绪状态,继续参与任务调度和运行。这不仅实现了互斥锁访问同步的功能,也实现了类似通知-唤醒机制的条件变量的功能,不得不赞叹操作系统设计者的巧思。

由于每个事件控制块都有一个任务等待表,每个任务等待表可以容纳所有的64个任务,所以事件控制块是支持多个任务同时等待一个事件的,至于哪一个任务优先获得该事件的访问权,还是跟优先级有关,处于等待状态最高优先级的任务优先拥有该事件的访问权。一个任务也可能同时等待多个事件,UCOS也是支持的,任务控制块里面有一个指向事件控制块的指针变量,同时也提供了一个指向事件控制块数组的指针变量,所以一个任务是可以同时等待多个事件的,这多个事件控制块以数组的形式管理。

2.2 事件控制块的操作

对事件控制块的操作主要是对任务等待表的操作,也可以说是对任务状态的切换,比如使一个任务进入等待状态或就绪状态等。任务控制块在使用前自然需要对其进行初始化操作,初始化只是将任务等待表清零。让一个任务进入就绪态或等待态都需要操作任务就绪表和任务等待表,由于之前对这两个表具体是如何操作的略去了,下面给出相关的代码供参考:

// MicriumSoftwareuCOS-IISourceos_core.c

/*
*********************************************************************************************************
*                             MAKE TASK READY TO RUN BASED ON EVENT OCCURING
*
* Description: This function is called by other uC/OS-II services and is used to ready a task that was
*              waiting for an event to occur.
*
* Arguments  : pevent      is a pointer to the event control block corresponding to the event.
*
*              pmsg        is a pointer to a message.  This pointer is used by message oriented services
*                          such as MAILBOXEs and QUEUEs.  The pointer is not used when called by other
*                          service functions.
*
*              msk         is a mask that is used to clear the status byte of the TCB.  For example,
*                          OSSemPost() will pass OS_STAT_SEM, OSMboxPost() will pass OS_STAT_MBOX etc.
*
*              pend_stat   is used to indicate the readied task's pending status:
*
*                          OS_STAT_PEND_OK      Task ready due to a post (or delete), not a timeout or
*                                               an abort.
*                          OS_STAT_PEND_ABORT   Task ready due to an abort.
*
* Returns    : none
*
* Note       : This function is INTERNAL to uC/OS-II and your application should not call it.
*********************************************************************************************************
*/
#if (OS_EVENT_EN)
INT8U  OS_EventTaskRdy (OS_EVENT  *pevent,
                        void      *pmsg,
                        INT8U      msk,
                        INT8U      pend_stat)
{
    OS_TCB   *ptcb;
    INT8U     y;
    INT8U     x;
    INT8U     prio;
#if OS_LOWEST_PRIO > 63u
    OS_PRIO  *ptbl;
#endif


#if OS_LOWEST_PRIO <= 63u
    y    = OSUnMapTbl[pevent->OSEventGrp];              /* Find HPT waiting for message                */
    x    = OSUnMapTbl[pevent->OSEventTbl[y]];
    prio = (INT8U)((y << 3u) + x);                      /* Find priority of task getting the msg       */
#else
    if ((pevent->OSEventGrp & 0xFFu) != 0u) {           /* Find HPT waiting for message                */
        y = OSUnMapTbl[ pevent->OSEventGrp & 0xFFu];
    } else {
        y = OSUnMapTbl[(OS_PRIO)(pevent->OSEventGrp >> 8u) & 0xFFu] + 8u;
    }
    ptbl = &pevent->OSEventTbl[y];
    if ((*ptbl & 0xFFu) != 0u) {
        x = OSUnMapTbl[*ptbl & 0xFFu];
    } else {
        x = OSUnMapTbl[(OS_PRIO)(*ptbl >> 8u) & 0xFFu] + 8u;
    }
    prio = (INT8U)((y << 4u) + x);                      /* Find priority of task getting the msg       */
#endif

    ptcb                  =  OSTCBPrioTbl[prio];        /* Point to this task's OS_TCB                 */
    ptcb->OSTCBDly        =  0u;                        /* Prevent OSTimeTick() from readying task     */
#if ((OS_Q_EN > 0u) && (OS_MAX_QS > 0u)) || (OS_MBOX_EN > 0u)
    ptcb->OSTCBMsg        =  pmsg;                      /* Send message directly to waiting task       */
#else
    pmsg                  =  pmsg;                      /* Prevent compiler warning if not used        */
#endif
    ptcb->OSTCBStat      &= (INT8U)~msk;                /* Clear bit associated with event type        */
    ptcb->OSTCBStatPend   =  pend_stat;                 /* Set pend status of post or abort            */
                                                        /* See if task is ready (could be susp'd)      */
    if ((ptcb->OSTCBStat &   OS_STAT_SUSPEND) == OS_STAT_RDY) {
        OSRdyGrp         |=  ptcb->OSTCBBitY;           /* Put task in the ready to run list           */
        OSRdyTbl[y]      |=  ptcb->OSTCBBitX;
    }

    OS_EventTaskRemove(ptcb, pevent);                   /* Remove this task from event   wait list     */
#if (OS_EVENT_MULTI_EN > 0u)
    if (ptcb->OSTCBEventMultiPtr != (OS_EVENT **)0) {   /* Remove this task from events' wait lists    */
        OS_EventTaskRemoveMulti(ptcb, ptcb->OSTCBEventMultiPtr);
        ptcb->OSTCBEventPtr       = (OS_EVENT  *)pevent;/* Return event as first multi-pend event ready*/
    }
#endif

    return (prio);
}
#endif

/*
*********************************************************************************************************
*                                   MAKE TASK WAIT FOR EVENT TO OCCUR
*
* Description: This function is called by other uC/OS-II services to suspend a task because an event has
*              not occurred.
*
* Arguments  : pevent   is a pointer to the event control block for which the task will be waiting for.
*
* Returns    : none
*
* Note       : This function is INTERNAL to uC/OS-II and your application should not call it.
*********************************************************************************************************
*/
#if (OS_EVENT_EN)
void  OS_EventTaskWait (OS_EVENT *pevent)
{
    INT8U  y;


    OSTCBCur->OSTCBEventPtr               = pevent;                 /* Store ptr to ECB in TCB         */

    pevent->OSEventTbl[OSTCBCur->OSTCBY] |= OSTCBCur->OSTCBBitX;    /* Put task in waiting list        */
    pevent->OSEventGrp                   |= OSTCBCur->OSTCBBitY;

    y             =  OSTCBCur->OSTCBY;            /* Task no longer ready                              */
    OSRdyTbl[y]  &= (OS_PRIO)~OSTCBCur->OSTCBBitX;
    if (OSRdyTbl[y] == 0u) {                      /* Clear event grp bit if this was only task pending */
        OSRdyGrp &= (OS_PRIO)~OSTCBCur->OSTCBBitY;
    }
}
#endif

/*
*********************************************************************************************************
*                                   REMOVE TASK FROM EVENT WAIT LIST
*
* Description: Remove a task from an event's wait list.
*
* Arguments  : ptcb     is a pointer to the task to remove.
*
*              pevent   is a pointer to the event control block.
*
* Returns    : none
*
* Note       : This function is INTERNAL to uC/OS-II and your application should not call it.
*********************************************************************************************************
*/
#if (OS_EVENT_EN)
void  OS_EventTaskRemove (OS_TCB   *ptcb,
                          OS_EVENT *pevent)
{
    INT8U  y;


    y                       =  ptcb->OSTCBY;
    pevent->OSEventTbl[y]  &= (OS_PRIO)~ptcb->OSTCBBitX;    /* Remove task from wait list              */
    if (pevent->OSEventTbl[y] == 0u) {
        pevent->OSEventGrp &= (OS_PRIO)~ptcb->OSTCBBitY;
    }
}
#endif

从上面的代码可以看出,主要就是对任务就绪表和任务等待表的操作,如果读者对具体的位操作运算有兴趣,可以针对性查阅更详细的资料,这个过程不是本文重点,就不详细解释了。让一个任务进入等待状态较简单,是把该任务添加到任务等待表并将该任务从任务就绪表中移除。让一个任务进入就绪状态略复杂些,一般任务是从等待状态进入就绪状态的,等待同一事件的任务可能有多个,需要将处于该事件等待状态最高优先级的任务加入到任务就绪表中,并从该事件等待任务表中移除该任务。

三、任务间通信

最常用的任务间通信工具或者说事件类型主要有四种,分别是控制类型信号量与互斥量、数据类型消息邮箱与消息队列。由于任务间通信通常具有方向性,前一个任务发出事件,后一个任务才能访问该事件。如果一个任务请求访问一个还未发出的事件,则该任务将进入等待状态,直到该事件被发出且该任务拥有对该事件的最高访问优先级。所以用于任务间通信的事件都是以”发送—请求“模式(也即Post—Pend模式)发挥作用的,下面分别介绍这四种事件类型是作用机制。

3.1 信号量

回顾下事件控制块的数据结构,跟信号量相关的除了事件类型外,最主要的就是OSEventCnt信号量计数器和任务等待表,假如给定一个计数器值10,该信号量控制块结构如下图示:
信号量控制块
信号量主要支持的操作如下:
信号量操作
对应的操作函数如下:

函数功能描述
OS_EVENT *OSSemCreate (INT16U cnt)创建一个信号量,并对信号量的初始计数值赋值,返回该信号量ECB的指针。
void OSSemPend (OS_EVENT *pevent,
INT32U timeout,
INT8U *perr)
请求一个信号量,如果该信号量值大于0,则将该值减1并返回;否则调用OS_EventTaskWait(pevent)将该任务置为等待状态,并调用OS_Sched()调度下一个就绪态最高优先级任务运行;
参数timeout用于超时等待,如果设置非零值,则超时时间耗尽不管该任务是否等到该事件都转换为就绪状态,如果该参数设置为零则相当于超时时间无限大。
INT16U OSSemAccept (OS_EVENT *pevent)无等待请求一个信号量,如果该信号量值大于0,则将该值减1并返回;否则直接返回。
INT8U OSSemPost (OS_EVENT *pevent)发送一个信号量,如果有任务在等待该信号量,则调用OS_EventTaskRdy(pevent, (void *)0, OS_STAT_SEM, OS_STAT_PEND_OK)将该任务等待表中最高优先级的任务转换为就绪态,并调用OS_Sched()调度下一个就绪态最高优先级任务运行;如果没有任务等待该信号量,则将该信号量值加1。
INT8U OSSemQuery (OS_EVENT *pevent,
OS_SEM_DATA *p_sem_data)
查询一个信号量的当前状态,实际上就是将该ECB跟信号量相关的数据拷贝到一个信号量结构体中,该结构体是事件控制块的子集,删除了跟信号量无关的成员变量。
OS_EVENT *OSSemDel (OS_EVENT *pevent,
INT8U opt,
INT8U *perr)
删除一个信号量,将该事件控制块重新初始化后放回到空闲事件链表上;
参数opt提供了两个删除选项:选项OS_DEL_NO_PEND表示只有该信号量任务等待表没有等待任务时才删除该信号量;选项OS_DEL_ALWAYS表示不管该信号量是否有等待任务都删除该信号量,如果任务等待表有等待任务,则将所有的等待任务转换为就绪状态后删除该信号量,再调用OS_Sched()调度下一个就绪态最高优先级任务运行。

3.2 互斥量

互斥量算是信号量的一种特殊情况,相当于将信号量计数器设置为1时的状态,信号量管理多个任务对多个相同的共享资源的同步访问,互斥量管理多个任务对某一个共享资源的同步访问。但在抢占式操作系统中,当任务以独占方式使用共享资源时,会出现低优先级先于高优先级任务运行的情况,这种现象叫任务优先级反转(如下图示)。
任务优先级反转示意图
从上图可以看出,使用互斥量的任务是否能够运行受任务优先级和互斥量两个条件约束,且互斥量的约束高于优先级的约束。如果系统中高低优先级任务使用同一个互斥量,如果低优先级任务获得互斥量会使高优先级任务处于等待状态,而中优先级任务因优先级高于低优先级任务,则其可以剥夺低优先级任务的CPU使用权而先于高优先级任务运行(等待互斥量状态)。这种任务优先级反转的情况会影响高优先级任务响应的实时性,违背了抢占式RTOS的设计原则,所以应该尽力避免。那么该怎么避免呢?将低优先级任务的优先级在该任务使用互斥量期间提升到高优先级任务的优先级,中优先级任务自然无法剥夺其CPU使用权,待该低任务优先级使用完该互斥量再回复其原来的优先级,就可以有效避免任务优先级反转的问题。

为解决任务优先级反转问题,互斥量的事件控制块复用了信号量计数器变量,将该变量分为低8位和高8位两个变量使用,低8位存放信号值(该值为0xFF时互斥量有效,否则互斥量无效),高8位存放为避免出现任务优先级反转现象而要提升的优先级别prio,互斥量的数据结构如下图示:
互斥量结构
互斥量支持的操作函数如下:

函数功能描述
OS_EVENT *OSMutexCreate (INT8U prio,
INT8U *perr)
创建一个带升级优先级的互斥量,如果指定的优先级prio未被占用则设置OSTCBPrioTbl[prio] = OS_TCB_RESERVED即保留该优先级的TCB供后面升级优先级使用,上面提到OSEventCnt意义不同了,16位高8位为待升级到的优先级,低8位默认0xFF表示还没有任务占用该互斥信号量,不是0xFF表示占用该ECB的任务的优先级,返回该互斥量ECB的指针。
void OSMutexPend (OS_EVENT *pevent,
INT32U timeout,
INT8U *perr)
请求一个互斥信号量,如果该互斥量有效(即OSEventCnt低8位为0xFF),则将当前任务的优先级保存到OSEventCnt低8位,并将当前任务TCB首地址赋值给该ECB的OSEventPtr指针变量后返回;
如果该互斥量被占用,且占用该互斥量的任务优先级既低于待升级到的优先级又低于当前任务的优先级,则将占用该互斥量任务的优先级提升到创建互斥量时设置的待升级到的优先级prio(如果占用该互斥量的任务当前在任务就绪表或任务等待表中,则相应的任务就绪表或任务等待表也要同步变更),然后调用OS_EventTaskWait(pevent)将该任务置为等待状态,并调用OS_Sched()调度下一个就绪态最高优先级任务运行;
参数timeout用于超时等待,如果设置非零值,则超时时间耗尽不管该任务是否等到该事件都转换为就绪状态,如果该参数设置为零则相当于超时时间无限大。
BOOLEAN OSMutexAccept (OS_EVENT *pevent,
INT8U *perr)
无等待请求一个互斥量,如果该互斥量有效(即OSEventCnt低8位为0xFF),则将当前任务的优先级保存到OSEventCnt低8位,并将当前任务TCB首地址赋值给该ECB的OSEventPtr指针变量后返回;否则直接返回。
INT8U OSMutexPost (OS_EVENT *pevent)发送一个信号量,如果当前任务的优先级等于该互斥量待升级的优先级,则说明当前任务占用该互斥量时进行了优先级提升,使用完该互斥量需要将当前任务调整为原来的优先级(原优先级保存在OSEventCnt低8位);
如果有任务在等待该互斥量,则调用OS_EventTaskRdy(pevent, (void *)0, OS_STAT_MUTEX, OS_STAT_PEND_OK)将该任务等待表中最高优先级的任务转换为就绪态,保存新转换任务的优先级和任务控制块首地址到该互斥量控制块(分别保存在OSEventCnt低8位和OSEventPtr成员变量中),然后调用OS_Sched()调度下一个就绪态最高优先级任务运行;
如果没有任务等待该互斥量,则将该互斥量置为有效(OSEventCnt低8位置为0xFF)并清空OSEventPtr指针变量后返回。
INT8U OSMutexQuery (OS_EVENT *pevent,
OS_MUTEX_DATA *p_mutex_data)
查询一个互斥量的当前状态,实际上就是将该ECB跟互斥量相关的数据拷贝到一个互斥量结构体中,该结构体是事件控制块的子集,删除了跟互斥量无关的成员变量。
OS_EVENT *OSMutexDel (OS_EVENT *pevent,
INT8U opt,
INT8U *perr)
删除一个互斥量,将该事件控制块重新初始化后放回到空闲事件链表上,并释放为提升优先级而占用的任务控制块;
参数opt提供了两个删除选项:选项OS_DEL_NO_PEND表示只有该互斥量任务等待表没有等待任务时才删除该互斥量;选项OS_DEL_ALWAYS表示不管该互斥量是否有等待任务都删除该信号量,如果任务等待表有等待任务,则将所有的等待任务转换为就绪状态后删除该互斥量,再调用OS_Sched()调度下一个就绪态最高优先级任务运行。

3.3 消息邮箱

回顾下事件控制块的数据结构,跟消息相关的除了事件类型外,最主要的就是OSEventPtr事件指针和任务等待表,下面给出一个消息邮箱控制块的结构图示:
消息邮箱结构
消息邮箱支持的主要操作如下:
消息邮箱操作
对应的操作函数如下:

函数功能描述
OS_EVENT *OSMboxCreate (void *pmsg)创建一个消息邮箱,并将消息数据缓冲区的地址赋给OSEventPtr指针变量,返回该消息邮箱ECB的指针。
void *OSMboxPend (OS_EVENT *pevent,
INT32U timeout,
INT8U *perr)
请求一个消息邮箱,如果该消息有效(指向消息的指针非空),则获取该消息指针,清空该消息邮箱OSEventPtr的值,然后返回该消息的地址;否则调用OS_EventTaskWait(pevent)将该任务置为等待状态,并调用OS_Sched()调度下一个就绪态最高优先级任务运行,并返回该调度运行任务控制块OSTCBMsg成员指针(返回前清空该指针)指向的消息缓冲区地址;
参数timeout用于超时等待,如果设置非零值,则超时时间耗尽不管该任务是否等到该事件都转换为就绪状态,如果该参数设置为零则相当于超时时间无限大。
void *OSMboxAccept (OS_EVENT *pevent)无等待请求一个消息邮箱,如果该消息邮箱存在且类型正确,则获取消息指针,并清空OSEventPtr值,返回获取到的消息指针(若无消息则该值为空,否则指向消息所在地址)。
INT8U OSMboxPost (OS_EVENT *pevent,
void *pmsg)
发送一个消息邮箱,如果有任务在等待该消息邮箱,则调用OS_EventTaskRdy(pevent, pmsg, OS_STAT_MBOX, OS_STAT_PEND_OK)将该任务等待表中最高优先级的任务转换为就绪态,并将该消息缓冲区地址赋给转换为就绪态的任务TCB的OSTCBMsg成员指针,然后调用OS_Sched()调度下一个就绪态最高优先级任务运行;如果没有任务等待该消息邮箱,则将该消息缓冲区地址赋值给该消息邮箱的OSEventPtr成员指针。
INT8U OSMboxQuery (OS_EVENT *pevent,
OS_MBOX_DATA *p_mbox_data)
查询一个消息邮箱的当前信息,实际上就是将该ECB跟消息邮箱相关的数据拷贝到一个消息邮箱结构体中,该结构体是事件控制块的子集,删除了跟消息邮箱无关的成员变量。
OS_EVENT *OSMboxDel (OS_EVENT *pevent,
INT8U opt,
INT8U *perr)
删除一个消息邮箱,将该事件控制块重新初始化后放回到空闲事件链表上;
参数opt提供了两个删除选项:选项OS_DEL_NO_PEND表示只有该消息邮箱任务等待表没有等待任务时才删除该事件控制块;选项OS_DEL_ALWAYS表示不管该消息邮箱是否有等待任务都删除该事件控制块,如果任务等待表有等待任务,则将所有的等待任务转换为就绪状态后删除该消息邮箱,再调用OS_Sched()调度下一个就绪态最高优先级任务运行。

3.4 消息队列

消息队列跟消息邮箱类似,但传递的是多条消息,消息邮箱使用一个消息数据缓冲区管理,消息队列则需要一个专门的数据结构来管理多条消息,UCOS使用了一个环形缓冲队列作为管理多条消息的数据结构,消息队列控制块的数据结构如下图示:
消息队列结构
上图中的OS_Q是一个结构体,表示一个环形数据缓冲队列,该结构体的成员变量和图示如下:

// MicriumSoftwareuCOS-IISourceucos_ii.h

typedef struct os_q {                   /* QUEUE CONTROL BLOCK                                         */
    struct os_q   *OSQPtr;              /* Link to next queue control block in list of free blocks     */
    void         **OSQStart;            /* Pointer to start of queue data                              */
    void         **OSQEnd;              /* Pointer to end   of queue data                              */
    void         **OSQIn;               /* Pointer to where next message will be inserted  in   the Q  */
    void         **OSQOut;              /* Pointer to where next message will be extracted from the Q  */
    INT16U         OSQSize;             /* Size of queue (maximum number of entries)                   */
    INT16U         OSQEntries;          /* Current number of entries in the queue                      */
} OS_Q;

环形队列结构
消息队列支持的主要操作如下:
消息队列操作
对应的操作函数如下:

函数功能描述
OS_EVENT *OSQCreate (void **start,
INT16U size)
创建一个消息队列,先使用两个参数(消息数组指针start与消息个数size)初始化一个环形消息队列结构体OS_Q,并将该环形队列结构体的地址赋给OSEventPtr指针变量,返回该消息队列ECB的指针。
void *OSQPend (OS_EVENT *pevent,
INT32U timeout,
INT8U *perr)
请求一个消息队列,如果该消息队列中有消息,则取出一条消息(相当于该消息从消息队列中移除了),并返回该消息的指针;否则调用OS_EventTaskWait(pevent)将该任务置为等待状态,并调用OS_Sched()调度下一个就绪态最高优先级任务运行,并返回该调度运行任务控制块OSTCBMsg成员指针(返回前清空该指针)指向的消息缓冲区地址;
参数timeout用于超时等待,如果设置非零值,则超时时间耗尽不管该任务是否等到该事件都转换为就绪状态,如果该参数设置为零则相当于超时时间无限大。
void *OSQAccept (OS_EVENT *pevent,
INT8U *perr)
无等待请求一个消息队列,如果该消息队列中有消息,则取出一条消息(相当于该消息从消息队列中移除了),并返回该消息的指针;如果该消息队列中没有消息则返回空指针。
INT8U OSQPost (OS_EVENT *pevent,
void *pmsg)
发送一个消息队列,如果有任务在等待该消息队列,则调用OS_EventTaskRdy(pevent, pmsg, OS_STAT_Q, OS_STAT_PEND_OK)将该任务等待表中最高优先级的任务转换为就绪态,并将该消息缓冲区地址赋给转换为就绪态的任务TCB的OSTCBMsg成员指针,然后调用OS_Sched()调度下一个就绪态最高优先级任务运行;如果没有任务等待该消息邮箱,则将该消息插入(按队列正常的先入先出顺序)到该消息队列((OS_Q *)pevent->OSEventPtr)中。
INT8U OSQPostFront (OS_EVENT *pevent,
void *pmsg)
发送一个消息队列,作用跟INT8U OSQPost (OS_EVENT *pevent, void *pmsg)函数类似,唯一的区别是在将消息插入消息队列时,不是插入到消息队列后面(pevent->OSEventPtr->OSQIn++),而是插入到消息队列的前面(pevent->OSEventPtr->OSQOut–),也就是该处插入的消息是队列下一个将要取出的消息,实现了后入先出的效果。
INT8U OSQQuery (OS_EVENT *pevent,
OS_Q_DATA *p_q_data)
查询一个消息队列的当前信息,实际上就是将该ECB跟消息队列相关的数据拷贝到一个消息队列结构体中,该结构体包含了消息队列控制块OS_EVENT与环形队列结构体OS_Q的部分重要信息。
INT8U OSQFlush (OS_EVENT *pevent)清空该消息队列指向的环形数据缓冲区队列(OSEventPtr成员指针指向的OS_Q结构体)中的消息。
OS_EVENT *OSQDel (OS_EVENT *pevent,
INT8U opt,
INT8U *perr)
删除一个消息队列,将该事件控制块重新初始化后放回到空闲事件链表OSEventFreeList上,同时将使用的环形队列OS_Q重新放回到空闲队列链表OSQFreeList上;
参数opt提供了两个删除选项:选项OS_DEL_NO_PEND表示只有该消息队列任务等待表没有等待任务时才删除该事件控制块;选项OS_DEL_ALWAYS表示不管该消息队列是否有等待任务都删除该事件控制块,如果任务等待表有等待任务,则将所有的等待任务转换为就绪状态后删除该消息队列,再调用OS_Sched()调度下一个就绪态最高优先级任务运行。

3.5 总结

实际上UCOS还提供了一个事件标志组(EVENT FLAGS CONTROL BLOCK)的结构体,用来管理事件组,其实现和应用比事件控制块ECB略复杂,且使用频率较低,这里就略去不谈了,读者有兴趣可以再针对性查阅更详细的资料。

前面提到,常用的四种事件类型主要都是通过”发送—请求(Post—Pend)“模型实现多任务间通信的,而且都是靠任务状态转换(等待状态—就绪状态)实现多任务并发访问同一事件的同步互斥的,下面给出更详细的任务状态转换图供加深理解:
任务状态转换表

更多文章:

  • 《RTOS之UCOS(二)—任务调度器》
  • 《RTOS之UCOS(四)—中断管理与软件定时器》
  • 《UCOS_STM32 Porting Source Code from GitHub》
  • 《30天自制操作系统》

最后

以上就是含糊小伙为你收集整理的RTOS之UCOS(三)---任务间同步与通信一、临界区保护二、事件控制块三、任务间通信更多文章:的全部内容,希望文章能够帮你解决RTOS之UCOS(三)---任务间同步与通信一、临界区保护二、事件控制块三、任务间通信更多文章:所遇到的程序开发问题。

如果觉得靠谱客网站的内容还不错,欢迎将靠谱客网站推荐给程序员好友。

本图文内容来源于网友提供,作为学习参考使用,或来自网络收集整理,版权属于原作者所有。
点赞(57)

评论列表共有 0 条评论

立即
投稿
返回
顶部