Source code of Windows XP (NT5)
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

535 lines
14 KiB

  1. /*++
  2. Copyright (c) 1989 Microsoft Corporation
  3. Module Name:
  4. dpcsup.c
  5. Abstract:
  6. This module contains the support routines for the system DPC objects.
  7. Functions are provided to process quantum end, the power notification
  8. queue, and timer expiration.
  9. Author:
  10. David N. Cutler (davec) 22-Apr-1989
  11. Environment:
  12. Kernel mode only, IRQL DISPATCH_LEVEL.
  13. Revision History:
  14. --*/
  15. #include "ki.h"
  16. //
  17. // Define DPC entry structure and maximum DPC List size.
  18. //
  19. #define MAXIMUM_DPC_LIST_SIZE 16
  20. typedef struct _DPC_ENTRY {
  21. PRKDPC Dpc;
  22. PKDEFERRED_ROUTINE Routine;
  23. PVOID Context;
  24. } DPC_ENTRY, *PDPC_ENTRY;
  25. PRKTHREAD
  26. KiQuantumEnd (
  27. VOID
  28. )
  29. /*++
  30. Routine Description:
  31. This function is called when a quantum end event occurs on the current
  32. processor. Its function is to determine whether the thread priority should
  33. be decremented and whether a redispatch of the processor should occur.
  34. Arguments:
  35. None.
  36. Return Value:
  37. The next thread to be schedule on the current processor is returned as
  38. the function value. If this value is not NULL, then the return is with
  39. the dispatcher database locked. Otherwise, the dispatcher database is
  40. unlocked.
  41. --*/
  42. {
  43. KPRIORITY NewPriority;
  44. KIRQL OldIrql;
  45. PKPRCB Prcb;
  46. KPRIORITY Priority;
  47. PKPROCESS Process;
  48. PRKTHREAD Thread;
  49. PRKTHREAD NextThread;
  50. //
  51. // Acquire the dispatcher database lock.
  52. //
  53. Prcb = KeGetCurrentPrcb();
  54. Thread = KeGetCurrentThread();
  55. KiLockDispatcherDatabase(&OldIrql);
  56. //
  57. // If the quantum has expired for the current thread, then update its
  58. // quantum and priority.
  59. //
  60. if (Thread->Quantum <= 0) {
  61. //
  62. // If quantum runout is disabled for the thread's process and
  63. // the thread is running at a realtime priority, then set the
  64. // thread quantum to the highest value and do not round robin
  65. // at the thread's priority level. Otherwise, reset the thread
  66. // quantum and decay the thread's priority as appropriate.
  67. //
  68. Process = Thread->ApcState.Process;
  69. if ((Process->DisableQuantum != FALSE) &&
  70. (Thread->Priority >= LOW_REALTIME_PRIORITY)) {
  71. Thread->Quantum = MAXCHAR;
  72. } else {
  73. Thread->Quantum = Process->ThreadQuantum;
  74. //
  75. // Decrement the thread's current priority if the thread is not
  76. // running in a realtime priority class and check to determine
  77. // if the processor should be redispatched.
  78. //
  79. Priority = Thread->Priority;
  80. if (Priority < LOW_REALTIME_PRIORITY) {
  81. NewPriority = Priority - Thread->PriorityDecrement - 1;
  82. if (NewPriority < Thread->BasePriority) {
  83. NewPriority = Thread->BasePriority;
  84. }
  85. Thread->PriorityDecrement = 0;
  86. } else {
  87. NewPriority = Priority;
  88. }
  89. //
  90. // If the new thread priority is different that the current thread
  91. // priority, then the thread does not run at a realtime level and
  92. // its priority should be set. Otherwise, attempt to round robin
  93. // at the current level.
  94. //
  95. if (Priority != NewPriority) {
  96. KiSetPriorityThread(Thread, NewPriority);
  97. } else {
  98. if (Prcb->NextThread == NULL) {
  99. NextThread = KiFindReadyThread(Thread->NextProcessor, Priority);
  100. if (NextThread != NULL) {
  101. NextThread->State = Standby;
  102. Prcb->NextThread = NextThread;
  103. }
  104. } else {
  105. Thread->Preempted = FALSE;
  106. }
  107. }
  108. }
  109. }
  110. //
  111. // If a thread was scheduled for execution on the current processor,
  112. // then return the address of the thread with the dispatcher database
  113. // locked. Otherwise, return NULL with the dispatcher data unlocked.
  114. //
  115. NextThread = Prcb->NextThread;
  116. if (NextThread == NULL) {
  117. KiUnlockDispatcherDatabase(OldIrql);
  118. }
  119. return NextThread;
  120. }
  121. #if DBG
  122. VOID
  123. KiCheckTimerTable (
  124. IN ULARGE_INTEGER CurrentTime
  125. )
  126. {
  127. ULONG Index;
  128. PLIST_ENTRY ListHead;
  129. PLIST_ENTRY NextEntry;
  130. KIRQL OldIrql;
  131. PKTIMER Timer;
  132. //
  133. // Raise IRQL to highest level and scan timer table for timers that
  134. // have expired.
  135. //
  136. KeRaiseIrql(HIGH_LEVEL, &OldIrql);
  137. Index = 0;
  138. do {
  139. ListHead = &KiTimerTableListHead[Index];
  140. NextEntry = ListHead->Flink;
  141. while (NextEntry != ListHead) {
  142. Timer = CONTAINING_RECORD(NextEntry, KTIMER, TimerListEntry);
  143. NextEntry = NextEntry->Flink;
  144. if (Timer->DueTime.QuadPart <= CurrentTime.QuadPart) {
  145. DbgBreakPoint();
  146. }
  147. }
  148. Index += 1;
  149. } while(Index < TIMER_TABLE_SIZE);
  150. //
  151. // Lower IRQL to the previous level.
  152. //
  153. KeLowerIrql(OldIrql);
  154. return;
  155. }
  156. #endif
  157. VOID
  158. KiTimerExpiration (
  159. IN PKDPC TimerDpc,
  160. IN PVOID DeferredContext,
  161. IN PVOID SystemArgument1,
  162. IN PVOID SystemArgument2
  163. )
  164. /*++
  165. Routine Description:
  166. This function is called when the clock interupt routine discovers that
  167. a timer has expired.
  168. Arguments:
  169. TimerDpc - Not used.
  170. DeferredContext - Not used.
  171. SystemArgument1 - Supplies the starting timer table index value to
  172. use for the timer table scan.
  173. SystemArgument2 - Not used.
  174. Return Value:
  175. None.
  176. --*/
  177. {
  178. ULARGE_INTEGER CurrentTime;
  179. LIST_ENTRY ExpiredListHead;
  180. LONG HandLimit;
  181. LONG Index;
  182. PLIST_ENTRY ListHead;
  183. PLIST_ENTRY NextEntry;
  184. KIRQL OldIrql;
  185. PKTIMER Timer;
  186. //
  187. // Acquire the dispatcher database lock and read the current interrupt
  188. // time to determine which timers have expired.
  189. //
  190. KiLockDispatcherDatabase(&OldIrql);
  191. KiQueryInterruptTime((PLARGE_INTEGER)&CurrentTime);
  192. //
  193. // If the timer table has not wrapped, then start with the specified
  194. // timer table index value, and scan for timer entries that have expired.
  195. // Otherwise, start with the first entry in the timer table and scan the
  196. // entire table for timer entries that have expired.
  197. //
  198. // N.B. This later condition exists when DPC processing is blocked for a
  199. // period longer than one round trip throught the timer table.
  200. //
  201. HandLimit = (LONG)KiQueryLowTickCount();
  202. if (((ULONG)(HandLimit - PtrToLong(SystemArgument1))) >= TIMER_TABLE_SIZE) {
  203. Index = - 1;
  204. HandLimit = TIMER_TABLE_SIZE - 1;
  205. } else {
  206. Index = (PtrToLong(SystemArgument1) - 1) & (TIMER_TABLE_SIZE - 1);
  207. HandLimit &= (TIMER_TABLE_SIZE - 1);
  208. }
  209. InitializeListHead(&ExpiredListHead);
  210. do {
  211. Index = (Index + 1) & (TIMER_TABLE_SIZE - 1);
  212. ListHead = &KiTimerTableListHead[Index];
  213. NextEntry = ListHead->Flink;
  214. while (NextEntry != ListHead) {
  215. Timer = CONTAINING_RECORD(NextEntry, KTIMER, TimerListEntry);
  216. if (Timer->DueTime.QuadPart <= CurrentTime.QuadPart) {
  217. //
  218. // The next timer in the current timer list has expired.
  219. // Remove the entry from the timer list and insert the
  220. // timer in the expired list.
  221. //
  222. RemoveEntryList(&Timer->TimerListEntry);
  223. InsertTailList(&ExpiredListHead, &Timer->TimerListEntry);
  224. NextEntry = ListHead->Flink;
  225. } else {
  226. break;
  227. }
  228. }
  229. } while(Index != HandLimit);
  230. #if DBG
  231. if ((PtrToUlong(SystemArgument2) == 0) && (KeNumberProcessors == 1)) {
  232. KiCheckTimerTable(CurrentTime);
  233. }
  234. #endif
  235. //
  236. // Process the expired timer list.
  237. //
  238. // N.B. The following function returns with the dispatcher database
  239. // unlocked.
  240. //
  241. KiTimerListExpire(&ExpiredListHead, OldIrql);
  242. return;
  243. }
  244. VOID
  245. FASTCALL
  246. KiTimerListExpire (
  247. IN PLIST_ENTRY ExpiredListHead,
  248. IN KIRQL OldIrql
  249. )
  250. /*++
  251. Routine Description:
  252. This function is called to process a list of timers that have expired.
  253. N.B. This function is called with the dispatcher database locked and
  254. returns with the dispatcher database unlocked.
  255. Arguments:
  256. ExpiredListHead - Supplies a pointer to a list of timers that have
  257. expired.
  258. OldIrql - Supplies the previous IRQL.
  259. Return Value:
  260. None.
  261. --*/
  262. {
  263. LONG Count;
  264. PKDPC Dpc;
  265. DPC_ENTRY DpcList[MAXIMUM_DPC_LIST_SIZE];
  266. LONG Index;
  267. LARGE_INTEGER Interval;
  268. KIRQL OldIrql1;
  269. LARGE_INTEGER SystemTime;
  270. PKTIMER Timer;
  271. LONG Period;
  272. LOGICAL PerfLogging;
  273. LARGE_INTEGER TimeStamp;
  274. PERFINFO_DPC_INFORMATION DpcInfo;
  275. //
  276. // Capture the timer expiration time.
  277. //
  278. KiQuerySystemTime(&SystemTime);
  279. //
  280. // Remove the next timer from the expired timer list, set the state of
  281. // the timer to signaled, reinsert the timer in the timer tree if it is
  282. // periodic, and optionally call the DPC routine if one is specified.
  283. //
  284. RestartScan:
  285. Count = 0;
  286. while (ExpiredListHead->Flink != ExpiredListHead) {
  287. Timer = CONTAINING_RECORD(ExpiredListHead->Flink, KTIMER, TimerListEntry);
  288. KiRemoveTreeTimer(Timer);
  289. Timer->Header.SignalState = 1;
  290. //
  291. // Capture the DPC and Period fields from the timer object. Once we have
  292. // called KiWaitTest, we must not touch the KTIMER again (unless it is
  293. // periodic). A thread may allocate a KTIMER on its local stack and wait
  294. // on it. KiWaitTest will cause that thread to immediately start running.
  295. // If it returns, the KTIMER will be corrupted.
  296. //
  297. Dpc = Timer->Dpc;
  298. Period = Timer->Period;
  299. if (IsListEmpty(&Timer->Header.WaitListHead) == FALSE) {
  300. KiWaitTest(Timer, TIMER_EXPIRE_INCREMENT);
  301. }
  302. //
  303. // If the timer is periodic, then compute the next interval time
  304. // and reinsert the timer in the timer tree.
  305. //
  306. // N.B. Even though the timer insertion is relative, it can still
  307. // fail if the period of the timer elapses in between computing
  308. // the time and inserting the timer. If this happens, then the
  309. // insertion is retried.
  310. //
  311. if (Period != 0) {
  312. Interval.QuadPart = Int32x32To64(Period, - 10 * 1000);
  313. do {
  314. } while (KiInsertTreeTimer(Timer, Interval) == FALSE);
  315. }
  316. if (Dpc != NULL) {
  317. //
  318. // If the DPC is explicitly targeted to another processor, then
  319. // queue the DPC to the target processor. Otherwise, capture the
  320. // DPC parameters for execution on the current processor.
  321. //
  322. #if defined(NT_UP)
  323. DpcList[Count].Dpc = Dpc;
  324. DpcList[Count].Routine = Dpc->DeferredRoutine;
  325. DpcList[Count].Context = Dpc->DeferredContext;
  326. Count += 1;
  327. if (Count == MAXIMUM_DPC_LIST_SIZE) {
  328. break;
  329. }
  330. #else
  331. if ((Dpc->Number >= MAXIMUM_PROCESSORS) &&
  332. (((ULONG)Dpc->Number - MAXIMUM_PROCESSORS) != (ULONG)KeGetCurrentProcessorNumber())) {
  333. KeInsertQueueDpc(Dpc,
  334. ULongToPtr(SystemTime.LowPart),
  335. ULongToPtr(SystemTime.HighPart));
  336. } else {
  337. DpcList[Count].Dpc = Dpc;
  338. DpcList[Count].Routine = Dpc->DeferredRoutine;
  339. DpcList[Count].Context = Dpc->DeferredContext;
  340. Count += 1;
  341. if (Count == MAXIMUM_DPC_LIST_SIZE) {
  342. break;
  343. }
  344. }
  345. #endif
  346. }
  347. }
  348. //
  349. // Unlock the dispacher database and process DPC list entries.
  350. //
  351. if (Count != 0) {
  352. KiUnlockDispatcherDatabase(DISPATCH_LEVEL);
  353. if (PERFINFO_IS_GROUP_ON(PERF_DPC)) {
  354. PerfLogging = TRUE;
  355. PerfTimeStamp(TimeStamp);
  356. } else {
  357. PerfLogging = FALSE;
  358. }
  359. Index = 0;
  360. do {
  361. #if DBG && defined(i386)
  362. //
  363. // Reset the dpc tick count. If the tick count handler,
  364. // which increments this value, detects that it has crossed
  365. // a certain threshold, a breakpoint will be generated.
  366. //
  367. KeGetCurrentPrcb()->DebugDpcTime = 0;
  368. #endif
  369. (DpcList[Index].Routine)(DpcList[Index].Dpc,
  370. DpcList[Index].Context,
  371. ULongToPtr(SystemTime.LowPart),
  372. ULongToPtr(SystemTime.HighPart));
  373. if (PerfLogging != FALSE) {
  374. DpcInfo.InitialTime = TimeStamp.QuadPart;
  375. DpcInfo.DpcRoutine = DpcList[Index].Routine;
  376. PerfInfoLogBytes(PERFINFO_LOG_TYPE_TIMERDPC,
  377. (PVOID) &DpcInfo,
  378. sizeof(DpcInfo));
  379. //
  380. // Get time for next iteration
  381. //
  382. PerfTimeStamp(TimeStamp);
  383. }
  384. Index += 1;
  385. } while (Index < Count);
  386. //
  387. // If processing of the expired timer list was terminated because
  388. // the DPC List was full, then process any remaining entries.
  389. //
  390. if (Count == MAXIMUM_DPC_LIST_SIZE) {
  391. KiLockDispatcherDatabase(&OldIrql1);
  392. goto RestartScan;
  393. }
  394. KeLowerIrql(OldIrql);
  395. } else {
  396. KiUnlockDispatcherDatabase(OldIrql);
  397. }
  398. return;
  399. }