资讯详情

SylixOS里的时间【12】--- 事件超时是如何实现的

基本原理

在要求信号量、信息队列、事件集、信号等事件时,如果当前线程失败,有时为了阻止线程被阻塞,将设置超时时间,这样即使一段时间后没有相应的事件资源,也会因为超时而退出阻塞。那么,该事件是如何加班的呢?

事件的超时功能也是通过添加节点等待唤醒链表(_K_wuDelay)实现的。基本原理是将当前线程添加到事件等待列表中,并添加到延迟等待唤醒链表中(_K_wuDelay)。若超时计时到前已获得资源,则将节点从延时等待唤醒链表中删除,则无超时响应。如果你一直等不及资源,你会在系统心跳中监控延迟等待唤醒链表对应的节点时间,节点会从延迟等待唤醒链表中删除并设置加班标志。如果事件被唤醒后发现加班标志有效,你会知道你因为加班被唤醒,然后以加班错误号返回。

延过延迟功能和超时功能_K_wuDelay链表实现时,如果时间到了,会设置加班标志,但加班功能还需要添加其他事件等待列表,是被事件唤醒还是加班。

详细说明

为了简单易懂,以二值信号量的加时功能为例,其他事件的加时功能与此类似。

/********************************************************************************************************* ** 函数名称: API_SemaphoreBPend ** 功能描述: 等待二进制型信号量,中断中不得调用 ** 输 入 : ** ulId 事件句柄 ** ulTimeout 等待时间 ** 输 出 : *********************************************************************************************************/ LW_API  ULONG  API_SemaphoreBPend (LW_OBJECT_HANDLE  ulId, ULONG  ulTimeout) { 
                      INTREG                iregInterLevel;              PLW_CLASS_TCB         ptcbCur;     REGISTER UINT16                usIndex;     REGISTER PLW_CLASS_EVENT       pevent;     REGISTER UINT8                 ucPriorityIndex;     REGISTER PLW_LIST_RING        *ppringList;              ULONG                 ulTimeSave;                          /* 记录系统事件 */              INT                   iSchedRet;                            ULONG                 ulEventOption;                       /* 创建事件选项 */                   usIndex = _ObjectGetIndex(ulId);          ///获得当前线程控制块     LW_TCB_GET_CUR_SAFE(ptcbCur);                                       /* 当前任务控制块 */      __wait_again:     //获取事件对象     pevent = &_K_eventBuffer[usIndex];          iregInterLevel = __KERNEL_ENTER_IRQ();                              /* 进入内核 */     ///如果信号计数是真实的,则获,假期后返回     if (pevent->EVENT_ulCounter) { 
                                              /* 事件有效 */         pevent->EVENT_ulCounter = LW_FALSE;         __KERNEL_EXIT_IRQ(iregInterLevel);                              /* 退出内核 */         return  (ERROR_NONE);     }     //如果信号计数是假的,则应进行阻塞调度     //如果参数不延迟,直接返回错误号     if (ulTimeout span class="token operator">== LW_OPTION_NOT_WAIT) { 
                                      /* 不等待 */
        __KERNEL_EXIT_IRQ(iregInterLevel);                              /* 退出内核 */
        _ErrorHandle(ERROR_THREAD_WAIT_TIMEOUT);                        /* 超时 */
        return  (ERROR_THREAD_WAIT_TIMEOUT);
    }
    //选择等待队列
    ptcbCur->TCB_iPendQ         = EVENT_SEM_Q;
    //设置等待信号量标志
    ptcbCur->TCB_usStatus      |= LW_THREAD_STATUS_SEM;                 /* 写状态位,开始等待 */
    ptcbCur->TCB_ucWaitTimeout  = LW_WAIT_TIME_CLEAR;                   /* 清空等待时间 */
    //判断是否为无穷等待,无穷等待则不需要设置超时
    if (ulTimeout == LW_OPTION_WAIT_INFINITE) { 
                                 /* 是否是无穷等待 */
        ptcbCur->TCB_ulDelay = 0ul;
    } else { 
        
        ptcbCur->TCB_ulDelay = ulTimeout;                               /* 设置超时时间 */
    }
    __KERNEL_TIME_GET_NO_SPINLOCK(ulTimeSave, ULONG);                   /* 记录系统时间 */
    //判断是按优先级等待还是FIFO等待,加入不同的等待队列 
    if (pevent->EVENT_ulOption & LW_OPTION_WAIT_PRIORITY) { 
                     /* 按优先级等待 */
        _EVENT_INDEX_Q_PRIORITY(ptcbCur->TCB_ucPriority, ucPriorityIndex);
        _EVENT_PRIORITY_Q_PTR(EVENT_SEM_Q, ppringList, ucPriorityIndex);
        ptcbCur->TCB_ppringPriorityQueue = ppringList;                  /* 记录等待队列位置 */
        _EventWaitPriority(pevent, ppringList);                         /* 加入优先级等待表 */
    
    } else { 
                                                                    /* 按 FIFO 等待 */
        _EVENT_FIFO_Q_PTR(EVENT_SEM_Q, ppringList);                     /* 确定 FIFO 队列的位置 */
        _EventWaitFifo(pevent, ppringList);                             /* 加入 FIFO 等待表 */
    }
    
    KN_INT_ENABLE(iregInterLevel);                                      /* 使能中断 */

    ulEventOption = pevent->EVENT_ulOption;
    //进行调度,此处会被阻塞让出CPU
    iSchedRet = __KERNEL_EXIT();                                       /* 调度器解 */
    //已被唤醒,判断被唤醒的原因
    //判断是否为信号唤醒
    if (iSchedRet) { 
        
        if ((iSchedRet == LW_SIGNAL_EINTR) && 
            (ulEventOption & LW_OPTION_SIGNAL_INTER)) { 
        
            _ErrorHandle(EINTR);
            return  (EINTR);
        }
        //非主动信号唤醒的话,需要重新计算超时时间,并重新等待
        ulTimeout = _sigTimeoutRecalc(ulTimeSave, ulTimeout);   
        if (ulTimeout == LW_OPTION_NOT_WAIT) { 
        
            _ErrorHandle(ERROR_THREAD_WAIT_TIMEOUT);
            return  (ERROR_THREAD_WAIT_TIMEOUT);
        }
        goto    __wait_again;
    }
    //判断超时标志是否有效,有效则返回超时错误号
    if (ptcbCur->TCB_ucWaitTimeout == LW_WAIT_TIME_OUT) { 
                       /* 等待超时 */
        _ErrorHandle(ERROR_THREAD_WAIT_TIMEOUT);                        /* 超时 */
        return  (ERROR_THREAD_WAIT_TIMEOUT);
        
    } else { 
        
        //事件可能已被删除,被删除则返回错误号,否则是正常唤醒
        if (ptcbCur->TCB_ucIsEventDelete == LW_EVENT_EXIST) { 
                   /* 事件是否存在 */
            return  (ERROR_NONE);
        
        } else { 
        
            _ErrorHandle(ERROR_EVENT_WAS_DELETED);                      /* 已经被删除 */
            return  (ERROR_EVENT_WAS_DELETED);
        }
    }
}

这里关键调用了优先级事件等待(_EventWaitPriority)或FIFO事件等待(_EventWaitFifo)这两个函数,如果不是无穷等待(0代表无穷等待),则添加节点到延时等待唤醒链表。

/********************************************************************************************************* ** 函数名称: _EventWaitPriority ** 功能描述: 将一个线程加入优先级事件等待队列,同时设置相关标志位 (进入内核且关中断状态下被调用) ** 输 入 : pevent 事件 ** ppringList 等待链表 ** 输 出 : NONE *********************************************************************************************************/
VOID  _EventWaitPriority (PLW_CLASS_EVENT  pevent, PLW_LIST_RING  *ppringList)
{ 
        
    REGISTER PLW_CLASS_PCB    ppcb;
             PLW_CLASS_TCB    ptcbCur;
    //获取当前线程控制块
    LW_TCB_GET_CUR(ptcbCur);                                            /* 当前任务控制块 */
    //设置等待事件指针
    ptcbCur->TCB_peventPtr = pevent;
    ppcb = _GetPcb(ptcbCur);
    __DEL_FROM_READY_RING(ptcbCur, ppcb);                               /* 从就绪队列中删除 */
    
    //加入事件等待队列 
    _AddTCBToEventPriority(ptcbCur, pevent, ppringList);                /* 加入等待队列 */
    //如果不是穷等待,则添加节点到延时等待唤醒链表
    if (ptcbCur->TCB_ulDelay) { 
        
        __ADD_TO_WAKEUP_LINE(ptcbCur);                                  /* 加入等待扫描链 */
    }
    
    ptcbCur->TCB_ucIsEventDelete = LW_EVENT_EXIST;                      /* 事件存在 */
}
/********************************************************************************************************* ** 函数名称: _EventWaitFifo ** 功能描述: 将一个线程加入 FIFO 事件等待队列,同时设置相关标志位 (进入内核且关中断状态下被调用) ** 输 入 : pevent 事件 ** ppringList 等待链表 ** 输 出 : NONE *********************************************************************************************************/
VOID  _EventWaitFifo (PLW_CLASS_EVENT  pevent, PLW_LIST_RING  *ppringList)
{ 
        
    REGISTER PLW_CLASS_PCB    ppcb;
             PLW_CLASS_TCB    ptcbCur;
    //获取当前线程控制块 
    LW_TCB_GET_CUR(ptcbCur);                                            /* 当前任务控制块 */
    //设置等待事件指针
    ptcbCur->TCB_peventPtr = pevent;
    ppcb = _GetPcb(ptcbCur);
    __DEL_FROM_READY_RING(ptcbCur, ppcb);                               /* 从就绪队列中删除 */
    
    //加入事件等待队列 
    _AddTCBToEventFifo(ptcbCur, pevent, ppringList);                    /* 加入等待队列 */
    //如果不是穷等待,则添加节点到延时等待唤醒链表
    if (ptcbCur->TCB_ulDelay) { 
        
        __ADD_TO_WAKEUP_LINE(ptcbCur);                                  /* 加入等待扫描链 */
    }
    
    ptcbCur->TCB_ucIsEventDelete = LW_EVENT_EXIST;                      /* 事件存在 */
}

经过上面的操作,线程已经进入阻塞状态,分别在事件等待队列和延时等待唤醒链表中等待被唤醒,也可以被信号异步唤醒。被唤醒后要检查唤醒原因,如果超时标志有效则说明是超时唤醒。

系统心跳中断中会不断检查延时等待唤醒链表,如果节点计时已到会换新对应线程,并标记超时标记。

#define LW_WAIT_TIME_OUT 0x01 /* 超时 */
#define LW_WAIT_TIME_CLEAR 0x00 /* 清除超时标志位 */
/********************************************************************************************************* ** 函数名称: _ThreadTick ** 功能描述: 扫描等待唤醒链表, tick 处理函数 ** 输 入 : NONE ** 输 出 : NONE *********************************************************************************************************/
VOID  _ThreadTick (VOID)
{ 
        
    INTREG                 iregInterLevel;
    PLW_CLASS_TCB          ptcb;
    PLW_CLASS_PCB          ppcb;
    PLW_CLASS_WAKEUP_NODE  pwun;
    ULONG                  ulCounter = 1;
    LW_CLASS_WAKEUP        pwu = &_K_wuDelay;
    
    iregInterLevel = __KERNEL_ENTER_IRQ();      /* 进入内核同时关闭中断 */
    
    //获取队列首项,循环执行,直到队列为空
    pwu->WU_plineOp = pwu->WU_plineHeader;
    while (pwu->WU_plineOp) { 
            
        pwun = _LIST_ENTRY(pwu->WU_plineOp, LW_CLASS_WAKEUP_NODE, WUN_lineManage);  
        //检查首项等待时间是否已到,未到则更新等待时间,并退出循环检查
        if (pwun->WUN_ulCounter > ulCounter) { 
            
            pwun->WUN_ulCounter -= ulCounter; 
            break;  
        }
        //等待时间已到
        //更新等待时间
        ulCounter -= pwun->WUN_ulCounter; 
        pwun->WUN_ulCounter = 0;
        //获取线程控制块
        ptcb = _LIST_ENTRY(pwun, LW_CLASS_TCB, TCB_wunDelay);
        //将线程从等待链中删除
        ptcb->TCB_usStatus &= ~LW_THREAD_STATUS_DELAY;
        _WakeupDel(pwu, &ptcb->TCB_wunDelay, LW_FALSE);
        //检查是否在等待事件
        if (ptcb->TCB_usStatus & LW_THREAD_STATUS_PEND_ANY) { 
                       /* 检查是否在等待事件 */
            ptcb->TCB_usStatus &= (~LW_THREAD_STATUS_PEND_ANY);             /* 等待超时清除事件等待位 */
            //标记超时状态
            ptcb->TCB_ucWaitTimeout = LW_WAIT_TIME_OUT;                     /* 等待超时 */
            //将一个线程从事件等待队列中解锁
            if (ptcb->TCB_peventPtr) { 
        
                _EventUnQueue(ptcb);
            }  
        } else { 
        
            ptcb->TCB_ucWaitTimeout = LW_WAIT_TIME_CLEAR;                   /* 没有等待事件 */
        }
        //检查线程是否就绪,如果就绪则加入就绪表
        if (__LW_THREAD_IS_READY(ptcb)) { 
                                           /* 检查是否就绪 */
            ptcb->TCB_ucSchedActivate = LW_SCHED_ACT_OTHER;
            ppcb = _GetPcb(ptcb);                                           /* 获得优先级控制块 */
            __ADD_TO_READY_RING(ptcb, ppcb);                                /* 加入就绪环 */
        }
    
        //处理过程中是关中断的,但可能会循环多次,为了避免长时间影响高优先级中断响应,这里要开关一下中断
        KN_INT_ENABLE(iregInterLevel);                                      /* 这里允许响应中断 */
        iregInterLevel = KN_INT_DISABLE();
        //获取新的队列首项,循环执行,直到队列为空
        pwu->WU_plineOp = _list_line_get_next(pwu->WU_plineOp);
    }
    
    __KERNEL_EXIT_IRQ(iregInterLevel);                                  /* 退出内核同时打开中断 */
}

等待事件标记是个位集,通过它可以知道等待节点是那种原因的等待,宏定义如下:

#define LW_THREAD_STATUS_RDY 0x0000 /* 任务就绪 */
#define LW_THREAD_STATUS_DELAY 0x0001 /* 延迟 */
#define LW_THREAD_STATUS_SEM 0x0002 /* 等待信号量 */
#define LW_THREAD_STATUS_MSGQUEUE 0x0004 /* 等待数据队列 */
#define LW_THREAD_STATUS_EVENTSET 0x0008 /* 等待事件标志组 */
#define LW_THREAD_STATUS_SIGNAL 0x0010 /* 等待信号 */
#define LW_THREAD_STATUS_JOIN 0x0020 /* 等待另外的线程 (信号不唤醒) */

#define LW_THREAD_STATUS_PEND_ANY (LW_THREAD_STATUS_SEM | \ LW_THREAD_STATUS_MSGQUEUE | \ LW_THREAD_STATUS_EVENTSET | \ LW_THREAD_STATUS_SIGNAL)

标签: 24ppcb板公整套连接器

锐单商城拥有海量元器件数据手册IC替代型号,打造 电子元器件IC百科大全!

锐单商城 - 一站式电子元器件采购平台