/****************************** Module Header ******************************\ * Module Name: taskman.c * * Copyright (c) 1985 - 1999, Microsoft Corporation * * This module contains the core functions of the input sub-system * * History: * 02-27-91 MikeHar Created. * 02-23-92 MattFe rewrote sleeptask * 09-07-93 DaveHart Per-process nonpreemptive scheduler for * multiple WOW VDM support. \***************************************************************************/ #include "precomp.h" #pragma hdrstop /***************************************************************************\ * WakeWowTask * * If needed the wowtask is woken by setting its event. It is assumed that if * any wow task is currently scheduled that it is a waste of time to wake the * specified wow task since rescheduling will occur when the currently * scheduled wow task enters xxxSleepTask. * * History: * ???? \***************************************************************************/ VOID WakeWowTask( PTHREADINFO pti) { PWOWPROCESSINFO pwpi; pwpi = pti->ppi->pwpi; if (pwpi && !pwpi->ptiScheduled) { KeSetEvent(pti->pEventQueueServer, EVENT_INCREMENT, FALSE); } } /***************************************************************************\ * InsertTask * * This function removes a task from its old location and inserts * in the proper prioritized location * * Find a place for this task such that it must be inserted * after any task with greater or equal priority and must be * before any task with higher priorty. The higher the priority * the less urgent the task. * * History: * 19-Nov-1993 mikeke Created \***************************************************************************/ VOID InsertTask( PPROCESSINFO ppi, PTDB ptdbNew) { PTDB *pptdb; PTDB ptdb; int nPriority; PWOWPROCESSINFO pwpi = ppi->pwpi; CheckCritIn(); UserAssert(pwpi != NULL); pptdb = &pwpi->ptdbHead; nPriority = ptdbNew->nPriority; while ((ptdb = *pptdb) != NULL) { /* * Remove it from it's old location */ if (ptdb == ptdbNew) { *pptdb = ptdbNew->ptdbNext; /* * continue to search for the place to insert it */ while ((ptdb = *pptdb) != NULL) { if (nPriority < ptdb->nPriority) { break; } pptdb = &(ptdb->ptdbNext); } break; } /* * if this is the place to insert continue to search for the * place to delete it from */ if (nPriority < ptdb->nPriority) { do { if (ptdb->ptdbNext == ptdbNew) { ptdb->ptdbNext = ptdbNew->ptdbNext; break; } ptdb = ptdb->ptdbNext; } while (ptdb != NULL); break; } pptdb = &(ptdb->ptdbNext); } /* * insert the new task */ ptdbNew->ptdbNext = *pptdb; *pptdb = ptdbNew; } /***************************************************************************\ * DestroyTask() * * History: * 02-27-91 MikeHar Created. \***************************************************************************/ VOID DestroyTask( PPROCESSINFO ppi, PTHREADINFO ptiToRemove) { PTDB ptdbToRemove = ptiToRemove->ptdb; PTDB ptdb; PTDB* pptdb; PWOWPROCESSINFO pwpi = ppi->pwpi; // try to catch #150446 CheckCritIn(); BEGINATOMICCHECK(); UserAssert(pwpi != NULL); if (ptdbToRemove != NULL) { if (ptdbToRemove->TDB_Flags & TDBF_SETUP) { /* * This means that the WoW app was a setup app (checked in * SetAppCompatFlags). If so, the shell needs to be notified so * it can clean up potential problems caused by bad calls to * DDE, etc. */ PDESKTOPINFO pdeskinfo = GETDESKINFO(ptiToRemove); if (pdeskinfo->spwndShell) { _PostMessage(pdeskinfo->spwndShell, DTM_SETUPAPPRAN, 0, 0); } } /* * Remove the WOW per thread info. */ if (ptdbToRemove->pwti) { PWOWTHREADINFO *ppwti = &gpwtiFirst; while (*ppwti != ptdbToRemove->pwti && (*ppwti)->pwtiNext != NULL) { ppwti = &((*ppwti)->pwtiNext); } if (*ppwti == ptdbToRemove->pwti) { *ppwti = ptdbToRemove->pwti->pwtiNext; } CLOSE_PSEUDO_EVENT(&ptdbToRemove->pwti->pIdleEvent); UserFreePool(ptdbToRemove->pwti); } gpsi->nEvents -= ptdbToRemove->nEvents; /* * remove it from any lists */ pptdb = &pwpi->ptdbHead; while ((ptdb = *pptdb) != NULL) { /* * Remove it from it's old location */ if (ptdb == ptdbToRemove) { *pptdb = ptdb->ptdbNext; UserAssert(ptiToRemove->ptdb == ptdbToRemove); UserFreePool(ptdbToRemove); ptiToRemove->ptdb = NULL; break; } pptdb = &(ptdb->ptdbNext); } UserAssert(ptdb == ptdbToRemove); } ENDATOMICCHECK(); /* * If the task being destroyed is the active task, make nobody active. * We will go through this code path for 32-bit threads that die while * Win16 threads are waiting for a SendMessage reply from them. */ if (pwpi->ptiScheduled == ptiToRemove) { pwpi->ptiScheduled = NULL; ExitWowCritSect(ptiToRemove, pwpi); /* * Wake next task with events, or wowexec to run the scheduler */ if (pwpi->ptdbHead != NULL) { for (ptdb = pwpi->ptdbHead; ptdb; ptdb = ptdb->ptdbNext) { if (ptdb->nEvents > 0) { KeSetEvent(ptdb->pti->pEventQueueServer, EVENT_INCREMENT, FALSE); break; } } if (!ptdb) { KeSetEvent(pwpi->pEventWowExec, EVENT_INCREMENT, FALSE); } } } UserAssert(ptiToRemove != pwpi->CSOwningThread); } /***************************************************************************\ * xxxSleepTask * * This function puts this task to sleep and wakes the next (if any) * deserving task. * * BOOL fInputIdle - app is going idle, may do idle hooks * HANDLE hEvent - if nonzero, WowExec's event (client side) for * virtual HW Interrupt HotPath. * History: * 02-27-91 MikeHar Created. * 02-23-91 MattFe rewrote * 12-17-93 Jonle add wowexec hotpath for VirtualInterrupts \***************************************************************************/ BOOL xxxSleepTask( BOOL fInputIdle, HANDLE hEvent) { PTDB ptdb; PTHREADINFO pti; PPROCESSINFO ppi; PWOWPROCESSINFO pwpi; PSMS psms; NTSTATUS Status; int nHandles; BOOLEAN bWaitedAtLeastOnce; /* * !!! * ClearSendMessages assumes that this function does NOT leave the * critical section when called with fInputIdle==FALSE and from a * 32bit thread! */ CheckCritIn(); pti = PtiCurrent(); ppi = pti->ppi; pwpi = ppi->pwpi; /* * If this task has received a message from outside of the current * wow scheduler and hasn't yet replied to the message, the scheduler * will deadlock because the send\receive lock counts are updated * in ReplyMessage and not in receive message. Check for this * condition and do the DirectedSchedukeTask that normally occurs * in ReplyMessage. 16-Feb-1995 Jonle */ psms = pti->psmsCurrent; if (psms && psms->ptiReceiver == pti && psms->ptiSender && !(psms->flags & SMF_REPLY) && psms->flags & (SMF_RECEIVERBUSY | SMF_RECEIVEDMESSAGE) && psms->ptiSender->TIF_flags & TIF_16BIT && (pwpi != psms->ptiSender->ppi->pwpi || !(pti->TIF_flags & TIF_16BIT)) ) { DirectedScheduleTask(psms->ptiReceiver, psms->ptiSender, FALSE, psms); } /* * Return immediately if we are not 16 bit (don't have a pwpi). */ if (!(pti->TIF_flags & TIF_16BIT)) { return FALSE; } /* * Deschedule the current task. */ if (pti == pwpi->ptiScheduled) { ExitWowCritSect(pti, pwpi); pwpi->ptiScheduled = NULL; } UserAssert(pti != pwpi->CSOwningThread); /* * If this is wowexec calling on WowWaitForMsgAndEvent * set up the WakeMask for all messages , and check for wake * bits set since the last time. Reinsert wowexec, at the end * of the list so other 16 bit tasks will be scheduled first. */ if (pwpi->hEventWowExecClient == hEvent) { InsertTask(ppi, pti->ptdb); pti->pcti->fsWakeMask = QS_ALLINPUT | QS_EVENT; if (pti->pcti->fsChangeBits & pti->pcti->fsWakeMask) { pti->ptdb->nEvents++; gpsi->nEvents++; } } bWaitedAtLeastOnce = FALSE; do { /* * If nobody is Active look for the highest priority task with * some events pending. if MsgWaitForMultiple call don't * reschedule self */ if (pwpi->ptiScheduled == NULL) { rescan: if (pwpi->nRecvLock >= pwpi->nSendLock) { for (ptdb = pwpi->ptdbHead; ptdb; ptdb = ptdb->ptdbNext) { if (ptdb->nEvents > 0 && !(hEvent == HEVENT_REMOVEME && ptdb->pti == pti)) { pwpi->ptiScheduled = ptdb->pti; break; } } if (bWaitedAtLeastOnce) { // // If not first entry into sleep task avoid waiting // more than needed, if the curr task is now scheduled. // if (pwpi->ptiScheduled == pti) { break; } } else { // // On the first entry into sleep task input is going // idle if no tasks are ready to run. Call the idle // hook if there is one. // if (fInputIdle && pwpi->ptiScheduled == NULL && IsHooked(pti, WHF_FOREGROUNDIDLE)) { /* * Make this the active task so that no other * task will become active while we're calling * the hook. */ pwpi->ptiScheduled = pti; xxxCallHook(HC_ACTION, 0, 0, WH_FOREGROUNDIDLE); /* * Reset state so that no tasks are active. We * then need to rescan the task list to see if * a task was scheduled during the call to the * hook. Clear the input idle flag to ensure * that the hook won't be called again if there * are no tasks ready to run. */ pwpi->ptiScheduled = NULL; fInputIdle = FALSE; goto rescan; } } } /* * If there is a task ready, wake it up. */ if (pwpi->ptiScheduled != NULL) { KeSetEvent(pwpi->ptiScheduled->pEventQueueServer, EVENT_INCREMENT, FALSE ); /* * There is no one to wake up, but we may have to wake * wowexec to service virtual hardware interrupts */ } else if (ppi->W32PF_Flags & W32PF_WAKEWOWEXEC) { if (pwpi->hEventWowExecClient == hEvent) { pwpi->ptiScheduled = pti; ppi->W32PF_Flags &= ~W32PF_WAKEWOWEXEC; InsertTask(ppi, pti->ptdb); EnterWowCritSect(pti, pwpi); UserAssert(pti == pwpi->ptiScheduled); return TRUE; } else { KeSetEvent(pwpi->pEventWowExec, EVENT_INCREMENT, FALSE); } } else if ((pti->TIF_flags & TIF_SHAREDWOW) && !bWaitedAtLeastOnce) { if (pwpi->hEventWowExecClient == hEvent) { /* * We have to call zzzWakeInputIdle only if this will * awake WowExec's thread and not other thread. Bug 44060. */ zzzWakeInputIdle(pti); // need to DeferWinEventNotify() ?? IANJA ?? } } } /* * Return if we are a 32 bit thread, or if we were called by * MsgWaitForMultiple to exit the wow scheduler. */ if (!(pti->TIF_flags & TIF_16BIT)) { return FALSE; } else if (hEvent == HEVENT_REMOVEME) { InsertTask(ppi, pti->ptdb); KeClearEvent(pti->pEventQueueServer); return FALSE; } if (pti->apEvent == NULL) { pti->apEvent = UserAllocPoolNonPaged(POLL_EVENT_CNT * sizeof(PKEVENT), TAG_EVENT); if (pti->apEvent == NULL) return FALSE; } /* * Wait for input to this thread. */ pti->apEvent[IEV_TASK] = pti->pEventQueueServer; /* * Add the WowExec, handle for virtual hw interrupts */ if (pwpi->hEventWowExecClient == hEvent) { pti->apEvent[IEV_WOWEXEC] = pwpi->pEventWowExec; nHandles = 2; } else { nHandles = 1; } if (pti->TIF_flags & TIF_MEOW) { xxxClientWOWTask16SchedNotify(WOWSCHNOTIFY_WAIT, 0); } LeaveCrit(); Status = KeWaitForMultipleObjects(nHandles, &pti->apEvent[IEV_TASK], WaitAny, WrUserRequest, UserMode, TRUE, NULL, NULL); EnterCrit(); if (pti->TIF_flags & TIF_MEOW) { xxxClientWOWTask16SchedNotify(WOWSCHNOTIFY_RUN, 0); } bWaitedAtLeastOnce = TRUE; // remember if we woke up for wowexec if (Status == STATUS_WAIT_1) { ppi->W32PF_Flags |= W32PF_WAKEWOWEXEC; } else if (Status == STATUS_USER_APC) { /* * ClientDeliverUserApc() delivers User-mode APCs by calling back * to the client and immediately returning without doing anything: * KeUserModeCallback will automatically deliver any pending APCs. */ ClientDeliverUserApc(); } } while (pwpi->ptiScheduled != pti); /* * We are the Active Task, reduce number of Events * Place ourselves at the far end of tasks in the same priority * so that next time we sleep someone else will run. */ pti->ptdb->nEvents--; gpsi->nEvents--; UserAssert(gpsi->nEvents >= 0); InsertTask(ppi, pti->ptdb); ppi->W32PF_Flags &= ~W32PF_WAKEWOWEXEC; EnterWowCritSect(pti, pwpi); UserAssert(pti == pwpi->ptiScheduled); return FALSE; } /***************************************************************************\ * xxxUserYield * * Does exactly what Win3.1 UserYield does. * * History: * 10-19-92 Scottlu Created. \***************************************************************************/ BOOL xxxUserYield( PTHREADINFO pti) { PPROCESSINFO ppi = pti->ppi; /* * Deal with any pending messages. Only call it this first time if * this is the current running 16 bit app. In the case when starting * up a 16 bit app, the starter calls UserYield() to yield to the new * task, but at this time ppi->ptiScheduled is set to the new task. * Receiving messages at this point would be bad! */ if (pti->TIF_flags & TIF_16BIT) { if (pti == ppi->pwpi->ptiScheduled) { xxxReceiveMessages(pti); } } else { xxxReceiveMessages(pti); } /* * If we are a 16 bit task * Mark our task so it comes back some time. Also, remove it and * re-add it to the list so that we are the last task of our priority * to run. */ if ((pti->TIF_flags & TIF_16BIT) && (pti->ptdb != NULL)) { if (pti->ptdb->nEvents == 0) { pti->ptdb->nEvents++; gpsi->nEvents++; } InsertTask(ppi, pti->ptdb); /* * Sleep. Return right away if there are no higher priority tasks * in need of running. */ xxxSleepTask(TRUE, NULL); /* * Deal with any that arrived since we weren't executing. */ xxxReceiveMessages(pti); } return TRUE; } /***************************************************************************\ * DirectedScheduleTask * * History: * 25-Jun-1992 mikeke Created. \***************************************************************************/ VOID DirectedScheduleTask( PTHREADINFO ptiOld, PTHREADINFO ptiNew, BOOL bSendMsg, PSMS psms) { PWOWPROCESSINFO pwpiOld; PWOWPROCESSINFO pwpiNew; CheckCritIn(); pwpiOld = ptiOld->ppi->pwpi; pwpiNew = ptiNew->ppi->pwpi; /* * If old task is 16 bit, reinsert the task in its wow scheduler list * so that it is lowest in priority. Note that ptiOld is always the * same as pwpiOld->ptiScheduled except when called from ReceiverDied. */ if (ptiOld->TIF_flags & TIF_16BIT) { if (pwpiOld->ptiScheduled == ptiOld) { ptiOld->ptdb->nEvents++; gpsi->nEvents++; InsertTask(ptiOld->ppi, ptiOld->ptdb); } // Update the Send\Recv counts for interprocess scheduling in SleepTask if (pwpiOld != pwpiNew || !(ptiNew->TIF_flags & TIF_16BIT)) { if (bSendMsg) { pwpiOld->nSendLock++; psms->flags |= SMF_WOWSEND; } else if (pwpiOld->nRecvLock && psms->flags & SMF_WOWRECEIVE) { pwpiOld->nRecvLock--; psms->flags &= ~SMF_WOWRECEIVE; } } } /* * If the new task is 16 bit, reinsert into the wow scheduler list * so that it will run, if its a sendmsg raise priority of the receiver. * If its a reply and the sender is waiting for this psms or the sender * has a message to reply to raise priority of the sender. */ if (ptiNew->TIF_flags & TIF_16BIT) { BOOL bRaisePriority; ptiNew->ptdb->nEvents++; gpsi->nEvents++; bRaisePriority = bSendMsg || psms == ptiNew->psmsSent; if (bRaisePriority) { ptiNew->ptdb->nPriority--; } InsertTask(ptiNew->ppi, ptiNew->ptdb); if (bRaisePriority) { ptiNew->ptdb->nPriority++; WakeWowTask(ptiNew); } // Update the Send\Recv counts for interprocess scheduling in SleepTask if (pwpiOld != pwpiNew || !(ptiOld->TIF_flags & TIF_16BIT)) { if (bSendMsg) { pwpiNew->nRecvLock++; psms->flags |= SMF_WOWRECEIVE; } else if (pwpiNew->nSendLock && psms->flags & SMF_WOWSEND) { pwpiNew->nSendLock--; psms->flags &= ~SMF_WOWSEND; } } } } /***************************************************************************\ * xxxDirectedYield * * History: * 09-17-92 JimA Created. \***************************************************************************/ VOID xxxDirectedYield( DWORD dwThreadId) { PTHREADINFO ptiOld; PTHREADINFO ptiNew; CheckCritIn(); ptiOld = PtiCurrent(); if (!(ptiOld->TIF_flags & TIF_16BIT) || !ptiOld->ppi->pwpi) { RIPMSG0(RIP_ERROR, "DirectedYield called from 32 bit thread!"); return; } /* * If the old task is 16 bit, reinsert the task in its wow * scheduler list so that it is lowest in priority. */ ptiOld->ptdb->nEvents++; gpsi->nEvents++; InsertTask(ptiOld->ppi, ptiOld->ptdb); /* * -1 supports Win 3.1 OldYield mechanics */ if (dwThreadId != DY_OLDYIELD) { ptiNew = PtiFromThreadId(dwThreadId); if ((ptiNew == NULL) || (ptiNew->ppi != ptiOld->ppi)) { RIPMSG0(RIP_ERROR, "DirectedYield called from different process or invalid thread id!"); return; } if (ptiNew->TIF_flags & TIF_16BIT) { ptiNew->ptdb->nEvents++; gpsi->nEvents++; ptiNew->ptdb->nPriority--; InsertTask(ptiNew->ppi, ptiNew->ptdb); ptiNew->ptdb->nPriority++; } } xxxSleepTask(TRUE, NULL); }