μC/OS-Ⅱ源码学习(5)---消息队列

快速回顾

μC/OS-Ⅱ中的多任务

μC/OS-Ⅱ源码学习(1)---多任务系统的实现

μC/OS-Ⅱ源码学习(2)---多任务系统的实现(下)

μC/OS-Ⅱ源码学习(3)---事件模型

μC/OS-Ⅱ源码学习(4)---信号量

本文进一步解析事件模型中,消息队列类型的函数源码。

先回顾一下之前的通用事件控制块类型OS_EVENT

cpp 复制代码
//ucos_ii.h
typedef struct os_event {
    INT8U    OSEventType;      /* 事件类型,有六种(其中一种是UNUSED) */
    void    *OSEventPtr;       /* OSEventPtr是一个多用途的指针,当作为链表时,可以指向下一个控制块;当作为具体的事件控制块时,指向具体的事件结构,如OS_Q。信号量不使用该指针 */  
    INT16U   OSEventCnt;       /* 信号量计数器,其它事件类型不使用该成员 */
    OS_PRIO  OSEventGrp;       /* 等待信号的任务优先级组,和OSEventTbl共同组成"事件等待表" */
    OS_PRIO  OSEventTbl[OS_EVENT_TBL_SIZE];  /* 等待信号的组内优先级 */

#if OS_EVENT_NAME_EN > 0u
    INT8U   *OSEventName;      //事件名称
#endif
} OS_EVENT;

消息队列有自己的专属结构,该结构通过OSEventPtr指针挂载在OS_EVENT中。

cpp 复制代码
//ucos_ii.h

typedef struct os_q {  
    struct os_q   *OSQPtr;         /* 链接下一个消息队列控制块,一旦脱离链表,该指针就没用了 */
    void         **OSQStart;       /* 消息队列首条消息的存放地址 */
    void         **OSQEnd;         /* 消息队列最后一条消息的存放地址 */
    void         **OSQIn;          /* 写指针,即下一条消息的存放地址 */
    void         **OSQOut;         /* 读指针,即下一条消息的读取地址 */
    INT16U         OSQSize;        /* 消息队列的长度(能存放几条消息) */
    INT16U         OSQEntries;     /* 当前存储的消息数量 */
} OS_Q;

消息队列的创建

创建消息队列的函数为OSQCreate(void**start****,INT16Usize****),其中start表示传入的消息队列首地址,size表示队列的大小(可以存几条消息)。

cpp 复制代码
//os_q.c
OS_EVENT  *OSQCreate (void    **start,
                      INT16U    size)
{
    OS_EVENT  *pevent;
    OS_Q      *pq;
#if OS_CRITICAL_METHOD == 3u          /* 初始化临界区变量 */
    OS_CPU_SR  cpu_sr = 0u;
#endif

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

    if (OSIntNesting > 0u) {                     /* 不能在中断中调用 */
        return ((OS_EVENT *)0);
    }
    OS_ENTER_CRITICAL();
    pevent = OSEventFreeList;        /* 从链表头获取一个空白事件控制块 */
    if (OSEventFreeList != (OS_EVENT *)0) {      /* 如果非空,则将链表头指向下一个,完成头一个事件控制块的脱离 */
        OSEventFreeList = (OS_EVENT *)OSEventFreeList->OSEventPtr;
    }
    OS_EXIT_CRITICAL();
    if (pevent != (OS_EVENT *)0) {       /* 只有获取的事件控制块非空才继续创建 */
        OS_ENTER_CRITICAL();
        pq = OSQFreeList;        /* 从空白消息队列控制块链表头获取一个 */
        if (pq != (OS_Q *)0) {       /* 如果获取的消息队列控制块非空,则 */
            OSQFreeList            = OSQFreeList->OSQPtr;     /* 将链表头指向下一个,完成头一个的脱离 */
            OS_EXIT_CRITICAL();
            pq->OSQStart           = start;           /* 开始初始化消息队列 */
            pq->OSQEnd             = &start[size];
            pq->OSQIn              = start;
            pq->OSQOut             = start;
            pq->OSQSize            = size;
            pq->OSQEntries         = 0u;
            pevent->OSEventType    = OS_EVENT_TYPE_Q;   //事件控制块类型为消息队列
            pevent->OSEventCnt     = 0u;     //消息队列不需要该成员
            pevent->OSEventPtr     = pq;     //具体的事件结构指针指向QS_Q类型
#if OS_EVENT_NAME_EN > 0u
            pevent->OSEventName    = (INT8U *)(void *)"?";
#endif
            OS_EventWaitListInit(pevent);      /* 将事件等待表清空 */
        } else {    /* 如果获得的消息队列控制块为空,说明系统设置的余量不足,则将一切复原回创建之前的状态 */
            pevent->OSEventPtr = (void *)OSEventFreeList; 
            OSEventFreeList    = pevent;
            OS_EXIT_CRITICAL();
            pevent = (OS_EVENT *)0;
        }
    }
    return (pevent);    //返回创建好的事件控制块
}

消息队列的操作

OSQPend(OS_EVENT *pevent, INT32U timeout, INT8U *perr)

功能描述:等待消息队列中的消息,如果消息非空,则立即执行,否则阻塞等待新的消息到来。该函数还支持等待超时,当timeout大于0时,等待timeout事件后系统会自动将该任务就绪,如传入timeout为0,则会一直等待直到有消息到来。

cpp 复制代码
//os_q.c
void  *OSQPend (OS_EVENT  *pevent,
                INT32U     timeout,
                INT8U     *perr)
{
    void      *pmsg;
    OS_Q      *pq;
#if OS_CRITICAL_METHOD == 3u       /* 初始化临界区变量 */
    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) {      /* 事件不能为空 */
        *perr = OS_ERR_PEVENT_NULL;
        return ((void *)0);
    }
#endif
    if (pevent->OSEventType != OS_EVENT_TYPE_Q) {     /* 事件必须是消息队列类型 */
        *perr = OS_ERR_EVENT_TYPE;
        return ((void *)0);
    }
    if (OSIntNesting > 0u) {        /* 不能在中断中调用 */
        *perr = OS_ERR_PEND_ISR; 
        return ((void *)0);
    }
    if (OSLockNesting > 0u) {        /* 当调度器被上锁是不能调用 */
        *perr = OS_ERR_PEND_LOCKED;
        return ((void *)0);
    }
    OS_ENTER_CRITICAL();
    pq = (OS_Q *)pevent->OSEventPtr;        /* 获取消息队列控制块指针 */
    if (pq->OSQEntries > 0u) {         /* 查看是否有消息存储 */
        pmsg = *pq->OSQOut++;       /* 如果有,弹出最旧的消息 */
        pq->OSQEntries--;            /* 将消息存储数量减1 */
        if (pq->OSQOut == pq->OSQEnd) {      /* 如果读指针已经在最后一条,则将读指针指向首条 */
            pq->OSQOut = pq->OSQStart;
        }
        OS_EXIT_CRITICAL();
        *perr = OS_ERR_NONE;
        return (pmsg);         /* 返回读取的消息 */
    }
    OSTCBCur->OSTCBStat     |= OS_STAT_Q;        /* 任务状态变成"等待消息队列" */
    OSTCBCur->OSTCBStatPend  = OS_STAT_PEND_OK;
    OSTCBCur->OSTCBDly       = timeout;          /* 填上传入的超时时长 */
    OS_EventTaskWait(pevent);        /* 将任务和事件双向链接,详见信号量章节 */
    OS_EXIT_CRITICAL();
    OS_Sched();           /* 进行上下文切换,下面的代码只有在任务重新就绪后才有机会运行 */
    OS_ENTER_CRITICAL();     //再次回到这里执行,说明任务已经就绪,只有以下三种可能原因
    switch (OSTCBCur->OSTCBStatPend) {      /* 通过pend状态查看任务重新执行的原因 */
        case OS_STAT_PEND_OK:           /* 有新消息入队导致任务就绪 */
             pmsg =  OSTCBCur->OSTCBMsg;     //在等待期间,有信息传入时会传入一个临时空间,从该空间取出最新消息
            *perr =  OS_ERR_NONE;
             break;

        case OS_STAT_PEND_ABORT:      /* 别的任务执行了OSQPendAbort导致pend结束 */
             pmsg = (void *)0;    //Abort导致的就绪,消息只能填空
            *perr =  OS_ERR_PEND_ABORT;
             break;

        case OS_STAT_PEND_TO:     //等待超时导致的就绪
        default:
             OS_EventTaskRemove(OSTCBCur, pevent);   //取消事件对任务的绑定
             pmsg = (void *)0;    //超时导致的就绪,消息只能填空
            *perr =  OS_ERR_TIMEOUT;
             break;
    }
    OSTCBCur->OSTCBStat          =  OS_STAT_RDY;      /* 设置任务状态为就绪 */
    OSTCBCur->OSTCBStatPend      =  OS_STAT_PEND_OK;  /* 设置pend状态为OK */
    OSTCBCur->OSTCBEventPtr      = (OS_EVENT  *)0;    /* 清除任务对事件的绑定 */
#if (OS_EVENT_MULTI_EN > 0u)
    OSTCBCur->OSTCBEventMultiPtr = (OS_EVENT **)0;
#endif
    OSTCBCur->OSTCBMsg           = (void      *)0; 
    OS_EXIT_CRITICAL();
    return (pmsg);        /* 返回收到的消息 */
}

OSQPost(OS_EVENT *pevent, void *pmsg)

功能描述:传入一条新消息给对应的消息队列,当没有任务等待该消息队列时,直接将新消息存储到队列中。如果有任务正在等待,则直接传给最高优先级的等待任务,并设置该任务就绪,此时消息不会填装到队列里。

cpp 复制代码
//os_q.c
INT8U  OSQPost (OS_EVENT  *pevent,
                void      *pmsg)
{
    OS_Q      *pq;
#if OS_CRITICAL_METHOD == 3u       /* 初始化临界区变量 */
    OS_CPU_SR  cpu_sr = 0u;
#endif

#if OS_ARG_CHK_EN > 0u
    if (pevent == (OS_EVENT *)0) {      /* 事件不能为空 */
        return (OS_ERR_PEVENT_NULL);
    }
#endif
    if (pevent->OSEventType != OS_EVENT_TYPE_Q) {      /* 传入事件类型必须是消息队列 */
        return (OS_ERR_EVENT_TYPE);
    }
    OS_ENTER_CRITICAL();
    if (pevent->OSEventGrp != 0u) {                    /* 如果有任务在等待消息 */
        /* 使等待消息的优先级最级的任务进入就绪状态,并将该消息传入,详见信号量章节 */
        (void)OS_EventTaskRdy(pevent, pmsg, OS_STAT_Q, OS_STAT_PEND_OK);
        OS_EXIT_CRITICAL();
        OS_Sched();         /* 尝试上下文切换 */
        return (OS_ERR_NONE);
    }
    pq = (OS_Q *)pevent->OSEventPtr;       /* 暂时没有任务等待消息,则存储该消息到队列中 */
    if (pq->OSQEntries >= pq->OSQSize) {         /* 消息量不能超过队列长度 */
        OS_EXIT_CRITICAL();
        return (OS_ERR_Q_FULL);
    }
    *pq->OSQIn++ = pmsg;       /* 将消息填装进队列中 */
    pq->OSQEntries++;          /* 消息队列的消息数加1 */
    if (pq->OSQIn == pq->OSQEnd) {      /* 如果写指针超出队列末尾,则调整写指针指向队首 */
        pq->OSQIn = pq->OSQStart;
    }
    OS_EXIT_CRITICAL();
    return (OS_ERR_NONE);
}

OSQPostFront(OS_EVENT *pevent, void *pmsg)

功能描述:基本功能和OSQPost() 相同,但OSQPostFront() 总是传入消息到下一个读出的位置,具体是通过操作读指针OSQOut来达到这一目的的(前者是通过OSQIn来填装消息的)。

OSQPendAbort(OS_EVENT *pevent, INT8U opt, INT8U *perr)

功能描述:将正在pend等待的任务设置为就绪状态,详见信号量章节的**OSSemPendAbort()**函数,功能基本一样。

OSQAccept(OS_EVENT *pevent, INT8U *perr)

功能描述:与OSQPend() 类似,从消息队列中取一条消息,区别在于,OSQAccept()无论能否取到消息,都不会阻塞等待,而是立即返回执行(没有消息时返回0)。这也是一个比较常用的API,当一个任务要做的事情很多时,使用它防止被某一部分阻塞(多数情况下最好拆分称两个任务分开执行)。

OSQFlush(OS_EVENT *pevent)

功能描述:清空消息队列。

消息队列的删除

对应函数为OSQDel(),具体实现可以参考信号量章节的OSSemDel()

相关推荐
码到成龚27 分钟前
SQL server学习05-查询数据表中的数据(上)
数据库·sql·学习
青い月の魔女30 分钟前
数据结构初阶---二叉树---堆
c语言·数据结构·笔记·学习·算法
不想写代码的我1 小时前
基于ZYNQ-7000系列的FPGA学习笔记11——IP核之单端RAM读写
笔记·学习·fpga开发·嵌入式·zynq
遮天华月6 小时前
C++ 运算符重载详解
c++·学习·算法
纪伊路上盛名在6 小时前
生成式AI、大模型、多模态技术开发与应用学习清单
服务器·人工智能·笔记·学习·知识图谱·学习方法
『潺湲¢7 小时前
PCIe学习笔记
笔记·学习·fpga开发
吃着火锅x唱着歌8 小时前
PHP7内核剖析 学习笔记 第三章 数据类型
android·笔记·学习
keira6748 小时前
【21天学习AI底层概念】day5 机器学习的三大类型不能解决哪些问题?
人工智能·学习·机器学习
快乐飒男10 小时前
C语言基础13(内存管理)
c语言·开发语言·学习