μ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()

相关推荐
西岸行者2 天前
学习笔记:SKILLS 能帮助更好的vibe coding
笔记·学习
悠哉悠哉愿意2 天前
【单片机学习笔记】串口、超声波、NE555的同时使用
笔记·单片机·学习
别催小唐敲代码2 天前
嵌入式学习路线
学习
毛小茛2 天前
计算机系统概论——校验码
学习
babe小鑫2 天前
大专经济信息管理专业学习数据分析的必要性
学习·数据挖掘·数据分析
winfreedoms2 天前
ROS2知识大白话
笔记·学习·ros2
在这habit之下2 天前
Linux Virtual Server(LVS)学习总结
linux·学习·lvs
我想我不够好。2 天前
2026.2.25监控学习
学习
im_AMBER2 天前
Leetcode 127 删除有序数组中的重复项 | 删除有序数组中的重复项 II
数据结构·学习·算法·leetcode
CodeJourney_J2 天前
从“Hello World“ 开始 C++
c语言·c++·学习