等待多个内核对象

Posted 飞起的小田

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了等待多个内核对象相关的知识,希望对你有一定的参考价值。

  前面的等待内核对象,均是等待一个内核对象,这一章要讲解的是同时等待多个内核对象。这里的多个内核对象是指多值信号量和消息队列的任意组合。

  如果想要使用“等待多个内核对象”,就必须事先使能“等待多个内核对象”。“等待多个内核对象”的使能位于“os_cfg.h”。

#define OS_CFG_PEND_MULTI_EN            1u   //使能/禁用等待多个内核对象

  另外,值得注意,等待多个内核对象的内核对象指的是多值信号量或消息队列,要等待这两种对象,均须先使能它们,分别为 OS_CFG_SEM_EN OS_CFG_Q_EN,均位于“os_cfg.h

OSPendMulti ()
  OSPendMulti () 函数用于等待多个内核对象(多值信号量或消息队列)。OSPendMulti ()函数的信息如下表所示。

  OSPendMulti () 函数的定义位于“os_pend_multi.c”:

OS_OBJ_QTY  OSPendMulti (OS_PEND_DATA  *p_pend_data_tbl,  //等待对象(数组)
                         OS_OBJ_QTY     tbl_size,         //等待对象的数目
                         OS_TICK        timeout,          //超时(单位:时钟借配)
                         OS_OPT         opt,              //选项
                         OS_ERR        *p_err)            //返回错误类型
{
    CPU_BOOLEAN   valid;
    OS_OBJ_QTY    nbr_obj_rdy;
    CPU_SR_ALLOC(); //使用到临界段(在关/开中断时)时必需该宏,该宏声明和定义一个局部变
                    //量,用于保存关中断前的 CPU 状态寄存器 SR(临界段关中断只需保存SR)
                    //,开中断时将该值还原。 
    
#ifdef OS_SAFETY_CRITICAL               //如果使能(默认禁用)了安全检测
    if (p_err == (OS_ERR *)0) {         //如果错误类型实参为空
        OS_SAFETY_CRITICAL_EXCEPTION(); //执行安全检测异常函数
        return ((OS_OBJ_QTY)0);         //返回0(有错误),停止执行
    }
#endif

#if OS_CFG_CALLED_FROM_ISR_CHK_EN > 0u         //如果使能(默认使能)了中断中非法调用检测
    if (OSIntNestingCtr > (OS_NESTING_CTR)0) { //如果该函数是在中断中被调用
       *p_err = OS_ERR_PEND_ISR;               //错误类型为“在中断函数中定时”
        return ((OS_OBJ_QTY)0);                //返回0(有错误),停止执行
    }
#endif

#if OS_CFG_ARG_CHK_EN > 0u                       //如果使能(默认使能)了参数检测
    if (p_pend_data_tbl == (OS_PEND_DATA *)0) {  //如果参数 p_pend_data_tbl 为空
       *p_err = OS_ERR_PTR_INVALID;              //错误类型为“等待对象不可用”
        return ((OS_OBJ_QTY)0);                  //返回0(有错误),停止执行
    }
    if (tbl_size == (OS_OBJ_QTY)0) {             //如果 tbl_size 为0
       *p_err = OS_ERR_PTR_INVALID;              //错误类型为“等待对象不可用”
        return ((OS_OBJ_QTY)0);                  //返回0(有错误),停止执行
    }
    switch (opt) {                               //根据选项分类处理
        case OS_OPT_PEND_BLOCKING:               //如果选项在预期内
        case OS_OPT_PEND_NON_BLOCKING:
             break;                              //直接跳出

        default:                                 //如果选项超出预期
            *p_err = OS_ERR_OPT_INVALID;         //错误类型为“选项非法”
             return ((OS_OBJ_QTY)0);             //返回0(有错误),停止执行
    }
#endif
    /* 证实等待对象是否只有多值信号量或消息队列 */ 
    valid = OS_PendMultiValidate(p_pend_data_tbl,          
                                 tbl_size);
    if (valid == DEF_FALSE) {              //如果等待对象不是只有多值信号量或消息队列  
       *p_err = OS_ERR_OBJ_TYPE;           //错误类型为“对象类型有误”                     
        return ((OS_OBJ_QTY)0);            //返回0(有错误),停止执行
    }
    /* 如果等待对象确实只有多值信号量或消息队列 */ 
/*$PAGE*/
    CPU_CRITICAL_ENTER();                                 //关中断
    nbr_obj_rdy = OS_PendMultiGetRdy(p_pend_data_tbl,     //查看是否有对象被提交了
                                     tbl_size);
    if (nbr_obj_rdy > (OS_OBJ_QTY)0) {                    //如果有对象被发布
        CPU_CRITICAL_EXIT();                              //开中断
       *p_err = OS_ERR_NONE;                              //错误类型为“无错误”
        return ((OS_OBJ_QTY)nbr_obj_rdy);                 //返回被发布的等待对象的数目
    }
    /* 如果目前没有对象被发布 */
    if ((opt & OS_OPT_PEND_NON_BLOCKING) != (OS_OPT)0) {  //如果选择了不阻塞任务
        CPU_CRITICAL_EXIT();                              //开中断
       *p_err = OS_ERR_PEND_WOULD_BLOCK;                  //错误类型为“渴望阻塞”
        return ((OS_OBJ_QTY)0);                           //返回0(有错误),停止执行
    } else {                                              //如果选择了阻塞任务
        if (OSSchedLockNestingCtr > (OS_NESTING_CTR)0) {  //如果调度器被锁
            CPU_CRITICAL_EXIT();                          //开中断
           *p_err = OS_ERR_SCHED_LOCKED;                  //错误类型为“调度器被锁”
            return ((OS_OBJ_QTY)0);                       //返回0(有错误),停止执行
        }
    }
                                                            
    OS_CRITICAL_ENTER_CPU_EXIT();                         //锁调度器,重开中断
                                                           
    OS_PendMultiWait(p_pend_data_tbl,                     //挂起当前任务等到有对象被发布或超时
                     tbl_size,
                     timeout);

    OS_CRITICAL_EXIT_NO_SCHED();                          //解锁调度器(无调度)

    OSSched();                                            //调度任务
    /* 任务等到了(一个)对象后得以继续运行 */
    CPU_CRITICAL_ENTER();                                 //关中断
    switch (OSTCBCurPtr->PendStatus) {                    //根据当前任务的等待状态分类处理
        case OS_STATUS_PEND_OK:                           //如果任务已经等到了对象
            *p_err = OS_ERR_NONE;                         //错误类型为“无错误”
             break;                                       //跳出

        case OS_STATUS_PEND_ABORT:                        //如果任务的等待被中止
            *p_err = OS_ERR_PEND_ABORT;                   //错误类型为“等待对象被中止”
             break;                                       //跳出

        case OS_STATUS_PEND_TIMEOUT:                      //如果等待超时
            *p_err = OS_ERR_TIMEOUT;                      //错误类型为“等待超时”
             break;                                       //跳出

        case OS_STATUS_PEND_DEL:                          //如果任务等待的对象被删除
            *p_err = OS_ERR_OBJ_DEL;                      //错误类型为“等待对象被删”
            break;                                        //跳出

        default:                                          //如果任务的等待状态超出预期
            *p_err = OS_ERR_STATUS_INVALID;               //错误类型为“状态非法”
             break;                                       //跳出
    }

    OSTCBCurPtr->PendStatus = OS_STATUS_PEND_OK;         //复位任务的等待状态
    CPU_CRITICAL_EXIT();                                 //开中断

    return ((OS_OBJ_QTY)1);                              //返回被发布对象数目为1
}
OSPendMulti()

  OSPendMulti () 函数中,会调用 OS_PendMultiValidate () 函数验证等待对象是否均属于多值信号量或消息队列,如果不是,就返回,不继续执行等待。

  OS_PendMultiValidate () 函数的定义位于“os_pend_multi.c”:

CPU_BOOLEAN  OS_PendMultiValidate (OS_PEND_DATA  *p_pend_data_tbl, //等待对象
                                   OS_OBJ_QTY     tbl_size)        //等待对象数目
{
    OS_OBJ_QTY  i;
    OS_OBJ_QTY  ctr;
#if OS_CFG_SEM_EN  > 0u
    OS_SEM      *p_sem;
#endif
#if OS_CFG_Q_EN > 0u
    OS_Q        *p_q;
#endif


    for (i = 0u; i < tbl_size; i++) {                           //逐个判断等待对象
        if (p_pend_data_tbl->PendObjPtr == (OS_PEND_OBJ *)0) {  //如果等待对象不存在
            return (DEF_FALSE);                                 //返回,验证失败
        }

        ctr = 0u;                                               //置0 ctr
#if OS_CFG_SEM_EN  > 0u                                         //如果使能了多值信号量
        p_sem = (OS_SEM *)((void *)p_pend_data_tbl->PendObjPtr);//获取等待对象
        if (p_sem->Type == OS_OBJ_TYPE_SEM) {                   //如果该对象是多值信号量类型        
            ctr++;                                              //ctr 为1
        }
#endif

#if OS_CFG_Q_EN > 0u                                            //如果使能了消息队列
        p_q = (OS_Q *)((void *)p_pend_data_tbl->PendObjPtr);    //获取等待对象
        if (p_q->Type == OS_OBJ_TYPE_Q) {                       //如果该对象是消息队列类型 
            ctr++;                                              //ctr 为1
        }
#endif

        if (ctr == (OS_OBJ_QTY)0) {                             //如果 ctr = 0
            return (DEF_FALSE);                                 //返回,验证失败
        }
        p_pend_data_tbl++;                                      //验证下一个等待对象
    }
    return (DEF_TRUE);                                          //返回,验证成功
}
OS_PendMultiValidate()

  OSPendMulti () 函数中,还会调用 OS_PendMultiGetRdy () 函数查看是否有等待对象已被发布可供立即使用。

  OS_PendMultiGetRdy () 函数的定义位于“os_pend_multi.c”:

OS_OBJ_QTY  OS_PendMultiGetRdy (OS_PEND_DATA  *p_pend_data_tbl, //等待对象
                                OS_OBJ_QTY     tbl_size)        //等待对象数目
{
    OS_OBJ_QTY   i;
    OS_OBJ_QTY   nbr_obj_rdy;
#if OS_CFG_Q_EN > 0u
    OS_ERR       err;
    OS_MSG_SIZE  msg_size;
    OS_Q        *p_q;
    void        *p_void;
    CPU_TS       ts;
#endif
#if OS_CFG_SEM_EN  > 0u
    OS_SEM      *p_sem;
#endif



    nbr_obj_rdy = (OS_OBJ_QTY)0;
    for (i = 0u; i < tbl_size; i++) {                          //逐个检测等待对象
        p_pend_data_tbl->RdyObjPtr  = (OS_PEND_OBJ  *)0;       //清零等待对象的所有数据
        p_pend_data_tbl->RdyMsgPtr  = (void         *)0;
        p_pend_data_tbl->RdyMsgSize = (OS_MSG_SIZE   )0;
        p_pend_data_tbl->RdyTS      = (CPU_TS        )0;
        p_pend_data_tbl->NextPtr    = (OS_PEND_DATA *)0;
        p_pend_data_tbl->PrevPtr    = (OS_PEND_DATA *)0;
        p_pend_data_tbl->TCBPtr     = (OS_TCB       *)0;
#if OS_CFG_Q_EN > 0u                                           //如果使能了消息队列          
        p_q = (OS_Q *)((void *)p_pend_data_tbl->PendObjPtr);   //将该对象视为消息队列处理
        if (p_q->Type == OS_OBJ_TYPE_Q) {                      //如果该对象确为消息队列类型
            p_void = OS_MsgQGet(&p_q->MsgQ,                    //从该消息队列获取消息
                                &msg_size,
                                &ts,
                                &err);
            if (err == OS_ERR_NONE) {                          //如果获取消息成功
                p_pend_data_tbl->RdyObjPtr  = p_pend_data_tbl->PendObjPtr;
                p_pend_data_tbl->RdyMsgPtr  = p_void;          //保存接收到的消息
                p_pend_data_tbl->RdyMsgSize = msg_size;
                p_pend_data_tbl->RdyTS      = ts;
                nbr_obj_rdy++;
            }
        }
#endif

#if OS_CFG_SEM_EN > 0u                                           //如果使能了多值信号量
        p_sem = (OS_SEM *)((void *)p_pend_data_tbl->PendObjPtr); //将该对象视为多值信号量处理
        if (p_sem->Type == OS_OBJ_TYPE_SEM) {                    //如果该对象确为多值信号量类型
            if (p_sem->Ctr > 0u) {                               //如果该信号量可用
                p_sem->Ctr--;                                    //等待任务可以获得信号量
                p_pend_data_tbl->RdyObjPtr  = p_pend_data_tbl->PendObjPtr;
                p_pend_data_tbl->RdyTS      = p_sem->TS;
                nbr_obj_rdy++;
            }
        }
#endif

        p_pend_data_tbl++;                                       //检测下一个等待对象
    }
    return (nbr_obj_rdy);                                        //返回被发布的对象的数目
}
OS_PendMultiGetRdy()

  如果 OS_PendMultiGetRdy () 函数发现已有等待对象可用,OSPendMulti () 函数就会返回,继续运行任务。如果发现没有可用等待对象,就会继续调用 OS_PendMultiWait() 函数阻 塞 当 前 运 行 任 务 , 等 待 内 核 对 象 。

   OS_PendMultiWait() 函 数 的 定 义 也 位 于“os_pend_multi.c”:

void  OS_PendMultiWait (OS_PEND_DATA  *p_pend_data_tbl, //等待对象
                        OS_OBJ_QTY     tbl_size,        //等待对象数目
                        OS_TICK        timeout)         //超时(单位:时钟节拍)
{
    OS_OBJ_QTY      i;
    OS_PEND_LIST   *p_pend_list;

#if OS_CFG_Q_EN > 0u
    OS_Q           *p_q;
#endif

#if OS_CFG_SEM_EN > 0u
    OS_SEM         *p_sem;
#endif



    OSTCBCurPtr->PendOn             = OS_TASK_PEND_ON_MULTI;   //等待对象不可用,开始等待
    OSTCBCurPtr->PendStatus         = OS_STATUS_PEND_OK;
    OSTCBCurPtr->PendDataTblEntries = tbl_size;
    OSTCBCurPtr->PendDataTblPtr     = p_pend_data_tbl;

    OS_TaskBlock(OSTCBCurPtr,                                   //阻塞当前运行任务
                 timeout);                                    

    for (i = 0u; i < tbl_size; i++) {                           //逐个将等待对象插入等待列表
        p_pend_data_tbl->TCBPtr = OSTCBCurPtr;                  //所有的等待对象都绑定当前任务

#if OS_CFG_SEM_EN > 0u                                          //如果使能了多值信号量
        p_sem = (OS_SEM *)((void *)p_pend_data_tbl->PendObjPtr);//将对象视为多值信号量处理
        if (p_sem->Type == OS_OBJ_TYPE_SEM) {                   //如果该对象确为多值信号量
            p_pend_list = &p_sem->PendList;                     //获取该信号量的等待列表
            OS_PendListInsertPrio(p_pend_list,                  //将当前任务插入该等待列表
                                  p_pend_data_tbl);
        }
#endif

#if OS_CFG_Q_EN > 0u                                            //如果使能了消息队列
        p_q = (OS_Q *)((void *)p_pend_data_tbl->PendObjPtr);    //将对象视为消息队列处理
        if (p_q->Type == OS_OBJ_TYPE_Q) {                       //如果该对象确为消息队列
            p_pend_list = &p_q->PendList;                       //获取该消息队列的等待列表
            OS_PendListInsertPrio(p_pend_list,                  //将当前任务插入该等待列表
                                  p_pend_data_tbl);
        }
#endif

        p_pend_data_tbl++;                                      //处理下一个等待对象
    }
}
OS_PendMultiWait

 

以上是关于等待多个内核对象的主要内容,如果未能解决你的问题,请参考以下文章

UCOSIII事件标志组和同时等待多个内核对象

JUC并发编程 共享模式之工具 JUC CountdownLatch(倒计时锁) -- CountdownLatch应用(等待多个线程准备完毕( 可以覆盖上次的打印内)等待多个远程调用结束)(代码片段

OpenHarmony-内核对象事件之源码详解

Windows提高_2.3第三部分:内核区同步

等待多个 SwingWorker

ThreadX内核源码分析 - 事件