UCOS你问我答系列之消息邮箱详解

邮箱是UCOS的一种通讯机制, 它可以使一个任务或者一个中断服务程序向另一个任务发送一个void *OSEventPtr指针变量,该指针指向一个特定的数据结构.即通过该指针传递消息.

本文使用的UCOS版本:V2.91.

事件控制块

说到消息邮箱, 首先需要讲解一下UCOS的事件控制块.UCOS的信号量,消息邮箱,消息队列.都是使用OS_EVENT结构体来标识的. 结构体成员变量OSEventType标识不同的事件类型(信号量,消息队列等).

OS_EVENT  *OSMboxCreate (void *pmsg)
{
    OS_EVENT  *pevent;
#if OS_CRITICAL_METHOD == 3u                     /* Allocate storage for CPU status register           */
    OS_CPU_SR  cpu_sr = 0u;
#endif

#ifdef OS_SAFETY_CRITICAL_IEC61508
    if (OSSafetyCriticalStartFlag == OS_TRUE) {
        OS_SAFETY_CRITICAL_EXCEPTION();
    }
#endif

    if (OSIntNesting > 0u) {                     /* See if called from ISR ...                         */
        return ((OS_EVENT *)0);                   /* ... can't CREATE from an ISR                       */
    }
    OS_ENTER_CRITICAL();
    /*
        在事件控制块链表上找到一个可用的事件控制块,(实质为 OSEventTbl[OS_MAX_EVENTS]数组中的一个可用项.)
        设置对应的pevent结构体成员变量的属性值. 最后调用OS_EventWaitListInit清除 OSEventGrp OSEventTbl值.
        OSMboxCreate执行完成后,我们得到了一个OS_EVENT*类型的消息邮箱.
    */
    pevent = OSEventFreeList;                     /* Get next free event control block                  */
    if (OSEventFreeList != (OS_EVENT *)0) {      /* See if pool of free ECB pool was empty             */
        OSEventFreeList = (OS_EVENT *)OSEventFreeList->OSEventPtr;
    }
    OS_EXIT_CRITICAL();
    if (pevent != (OS_EVENT *)0) {
        pevent->OSEventType    = OS_EVENT_TYPE_MBOX;
        pevent->OSEventCnt     = 0u;
        pevent->OSEventPtr     = pmsg;           /* Deposit message in event control block             */
#if OS_EVENT_NAME_EN > 0u
        pevent->OSEventName    = (INT8U *)(void *)"?";
#endif
        OS_EventWaitListInit(pevent);
    }
    return (pevent);                             /* Return pointer to event control block              */
}

消息邮箱创建

消息邮箱的创建主要是从事件控制块数组中取出一个空闲的事件控制块. 设置事件控制块的相关属性.

OS_EVENT  *OSMboxCreate (void *pmsg)
{
    OS_EVENT  *pevent;
#if OS_CRITICAL_METHOD == 3u                     /* Allocate storage for CPU status register           */
    OS_CPU_SR  cpu_sr = 0u;
#endif

#ifdef OS_SAFETY_CRITICAL_IEC61508
    if (OSSafetyCriticalStartFlag == OS_TRUE) {
        OS_SAFETY_CRITICAL_EXCEPTION();
    }
#endif

    if (OSIntNesting > 0u) {                     /* See if called from ISR ...                         */
        return ((OS_EVENT *)0);                   /* ... can't CREATE from an ISR                       */
    }
    OS_ENTER_CRITICAL();
    /*
        在事件控制块链表上找到一个可用的事件控制块,(实质为 OSEventTbl[OS_MAX_EVENTS]数组中的一个可用项.)
        设置对应的pevent结构体成员变量的属性值. 最后调用OS_EventWaitListInit清除 OSEventGrp OSEventTbl值.
        OSMboxCreate执行完成后,我们得到了一个OS_EVENT*类型的消息邮箱.
    */
    pevent = OSEventFreeList;                     /* Get next free event control block                  */
    if (OSEventFreeList != (OS_EVENT *)0) {      /* See if pool of free ECB pool was empty             */
        OSEventFreeList = (OS_EVENT *)OSEventFreeList->OSEventPtr;
    }
    OS_EXIT_CRITICAL();
    if (pevent != (OS_EVENT *)0) {
        pevent->OSEventType    = OS_EVENT_TYPE_MBOX;
        pevent->OSEventCnt     = 0u;
        pevent->OSEventPtr     = pmsg;           /* Deposit message in event control block             */
#if OS_EVENT_NAME_EN > 0u
        pevent->OSEventName    = (INT8U *)(void *)"?";
#endif
        OS_EventWaitListInit(pevent);
    }
    return (pevent);                             /* Return pointer to event control block              */
}

消息邮箱请求

OSMboxPend函数的详解在代码内以注释的形式体现.

void  *OSMboxPend (OS_EVENT  *pevent,
                   INT32U     timeout,
                   INT8U     *perr)
{
    void      *pmsg;
#if OS_CRITICAL_METHOD == 3u                          /* Allocate storage for CPU status register      */
    OS_CPU_SR  cpu_sr = 0u;
#endif

#ifdef OS_SAFETY_CRITICAL
    if (perr == (INT8U *)0) {
        OS_SAFETY_CRITICAL_EXCEPTION();
    }
#endif

#if OS_ARG_CHK_EN > 0u
    if (pevent == (OS_EVENT *)0) {                    /* Validate 'pevent'                             */
        *perr = OS_ERR_PEVENT_NULL;
        return ((void *)0);
    }
#endif
    if (pevent->OSEventType != OS_EVENT_TYPE_MBOX) {  /* Validate event block type                     */
        *perr = OS_ERR_EVENT_TYPE;
        return ((void *)0);
    }
    if (OSIntNesting > 0u) {                          /* See if called from ISR ...                    */
        *perr = OS_ERR_PEND_ISR;                      /* ... can't PEND from an ISR                    */
        return ((void *)0);
    }
    if (OSLockNesting > 0u) {                         /* See if called with scheduler locked ...       */
        *perr = OS_ERR_PEND_LOCKED;                   /* ... can't PEND when locked                    */
        return ((void *)0);
    }
    OS_ENTER_CRITICAL();
    /*
        如果有可用的消息,就返回消息指针, ucos的消息邮箱采用的是直接传递指针的形式,没有使用memcpy.
        如果在发送消息的时候 pevent->OSEventPtr指向的内存是使用malloc申请的.记得在接收处理完消息释放内存.
    */
    pmsg = pevent->OSEventPtr;
    if (pmsg != (void *)0) {                          /* See if there is already a message             */
        pevent->OSEventPtr = (void *)0;               /* Clear the mailbox                             */
        OS_EXIT_CRITICAL();
        *perr = OS_ERR_NONE;
        return (pmsg);                                /* Return the message received (or NULL)         */
    }
    /*
        如果还没有消息,会运行到这里. 设置当前任务控制块的相关状态属性.
        OS_EventTaskWait函数将当前任务的优先级放入此消息邮箱的结构体成员变量,
        并将此任务的优先级从就绪表中移除.然后触发一次任务调度.CPU转到其他任务运行.
        此任务即处于了挂起等待消息邮箱的状态.
    */
    OSTCBCur->OSTCBStat     |= OS_STAT_MBOX;          /* Message not available, task will pend         */
    OSTCBCur->OSTCBStatPend  = OS_STAT_PEND_OK;
    OSTCBCur->OSTCBDly       = timeout;               /* Load timeout in TCB                           */
    OS_EventTaskWait(pevent);                         /* Suspend task until event or timeout occurs    */
    OS_EXIT_CRITICAL();
    // 执行完OS_Sched();任务被切换.
    OS_Sched();                                       /* Find next highest priority task ready to run  */
    /*
        当收到消息或超时时间到导致任务被放入就绪表, 且被调度器调度时,会从这里开始接着运行.
        先检查pend到消息的原因, 如果是等待超时,就将此任务的优先级从信号量的等待事件中移除.
        最后设置任务的相关状态.
    */
    OS_ENTER_CRITICAL();
    switch (OSTCBCur->OSTCBStatPend) {                /* See if we timed-out or aborted                */
        case OS_STAT_PEND_OK:
             pmsg =  OSTCBCur->OSTCBMsg;
            *perr =  OS_ERR_NONE;
             break;

        case OS_STAT_PEND_ABORT:
             pmsg = (void *)0;
            *perr =  OS_ERR_PEND_ABORT;               /* Indicate that we aborted                      */
             break;

        case OS_STAT_PEND_TO:
        default:
             OS_EventTaskRemove(OSTCBCur, pevent);
             pmsg = (void *)0;
            *perr =  OS_ERR_TIMEOUT;                  /* Indicate that we didn't get event within TO   */
             break;
    }
    OSTCBCur->OSTCBStat          =  OS_STAT_RDY;      /* Set   task  status to ready                   */
    OSTCBCur->OSTCBStatPend      =  OS_STAT_PEND_OK;  /* Clear pend  status                            */
    OSTCBCur->OSTCBEventPtr      = (OS_EVENT  *)0;    /* Clear event pointers                          */
#if (OS_EVENT_MULTI_EN > 0u)
    OSTCBCur->OSTCBEventMultiPtr = (OS_EVENT **)0;
#endif
    OSTCBCur->OSTCBMsg           = (void      *)0;    /* Clear  received message                       */
    OS_EXIT_CRITICAL();
    return (pmsg);                                    /* Return received message                       */
}

消息邮箱发送

INT8U  OSMboxPost (OS_EVENT  *pevent,
                   void      *pmsg)
{
#if OS_CRITICAL_METHOD == 3u                          /* Allocate storage for CPU status register      */
    OS_CPU_SR  cpu_sr = 0u;
#endif



#if OS_ARG_CHK_EN > 0u
    if (pevent == (OS_EVENT *)0) {                    /* Validate 'pevent'                             */
        return (OS_ERR_PEVENT_NULL);
    }
    if (pmsg == (void *)0) {                          /* Make sure we are not posting a NULL pointer   */
        return (OS_ERR_POST_NULL_PTR);
    }
#endif
    if (pevent->OSEventType != OS_EVENT_TYPE_MBOX) {  /* Validate event block type                     */
        return (OS_ERR_EVENT_TYPE);
    }
    OS_ENTER_CRITICAL();
    /*
        OSEventGrp != 0 时表示有其他任务在等待此消息. OS_EventTaskRdy函数的作用是通过pevent的结构体成员
        找到等待此信号量的那个任务(假定为任务A),并将任务A的优先级放入就绪表,且从等待事件表移除.然后
        触发一次任务调度.   
    */
    if (pevent->OSEventGrp != 0u) {                   /* See if any task pending on mailbox            */
                                                      /* Ready HPT waiting on event                    */
        (void)OS_EventTaskRdy(pevent, pmsg, OS_STAT_MBOX, OS_STAT_PEND_OK);
        OS_EXIT_CRITICAL();
        OS_Sched();                                   /* Find highest priority task ready to run       */
        return (OS_ERR_NONE);
    }
    /*
        消息邮箱满,直接返回. 因为消息邮箱只能携带一条消息.pevent->OSEventPtr非空就表示有数据.消息邮箱满.
    */
    if (pevent->OSEventPtr != (void *)0) {            /* Make sure mailbox doesn't already have a msg  */
        OS_EXIT_CRITICAL();
        return (OS_ERR_MBOX_FULL);
    }
    //保存要发送的消息到消息邮箱事件控制块指针.
    pevent->OSEventPtr = pmsg;                        /* Place message in mailbox                      */
    OS_EXIT_CRITICAL();
    return (OS_ERR_NONE);
}

问答环节

1.问: UCOS中消息邮箱有哪些应用场景.
答: 消息邮箱一般用于任务与任务之间,或者是中断向任务传递消息.使用消息邮箱也间接性的做到了共享数据的互斥 比如任务A采集各个传感器数据,任务B上送各个传感器数据.直接在任务A,B使用全局变量的形式处理传感器数据.会出现 可能A采集了一半,跳转到了B运行,造成数据一致性缺失的问题. 如果是任务A发送消息给任务B,就不会有这种情况发生.

2.问: UCOS消息邮箱可以传递哪些类型的数据
答: UCOS消息邮箱可以传递任意类型的数据, 消息邮箱参数是一个void *类型的指针,它可以执行任意数据类型的数据. 如果只是传递一个简单的数据,可以使用基本数据类型,如果传递的数据较多,可以自定义一个结构体.接收方按照结构体约定 的数据格式解析即可.

  • 0
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值