基本原理
在要求信号量、信息队列、事件集、信号等事件时,如果当前线程失败,有时为了阻止线程被阻塞,将设置超时时间,这样即使一段时间后没有相应的事件资源,也会因为超时而退出阻塞。那么,该事件是如何加班的呢?
事件的超时功能也是通过添加节点等待唤醒链表(_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)