VirtualBox

source: vbox/trunk/src/VBox/Runtime/r3/win32/timer-win32.cpp@ 2475

Last change on this file since 2475 was 1807, checked in by vboxsync, 18 years ago

Adopted the posix timer code to support the three new timer APIs

  • Property svn:eol-style set to native
  • Property svn:keywords set to Id
File size: 14.7 KB
Line 
1/* $Id: timer-win32.cpp 1807 2007-03-29 17:24:21Z vboxsync $ */
2/** @file
3 * InnoTek Portable Runtime - Timer.
4 */
5
6/*
7 * Copyright (C) 2006 InnoTek Systemberatung GmbH
8 *
9 * This file is part of VirtualBox Open Source Edition (OSE), as
10 * available from http://www.virtualbox.org. This file is free software;
11 * you can redistribute it and/or modify it under the terms of the GNU
12 * General Public License as published by the Free Software Foundation,
13 * in version 2 as it comes in the "COPYING" file of the VirtualBox OSE
14 * distribution. VirtualBox OSE is distributed in the hope that it will
15 * be useful, but WITHOUT ANY WARRANTY of any kind.
16 *
17 * If you received this file as part of a commercial VirtualBox
18 * distribution, then only the terms of your commercial VirtualBox
19 * license agreement apply instead of the previous paragraph.
20 */
21
22
23/* Which code to use is determined here...
24 *
25 * The default is to use wait on NT timers directly with no APC since this
26 * is supposed to give the shortest kernel code paths.
27 *
28 * The USE_APC variation will do as above except that an APC routine is
29 * handling the callback action.
30 *
31 * The USE_WINMM version will use the NT timer wrappers in WinMM which may
32 * result in some 0.1% better correctness in number of delivered ticks. However,
33 * this codepath have more overhead (it uses APC among other things), and I'm not
34 * quite sure if it's actually any more correct.
35 *
36 * The USE_CATCH_UP will play catch up when the timer lags behind. However this
37 * requires a monotonous time source.
38 *
39 * The default mode which we are using is using relative periods of time and thus
40 * will never suffer from errors in the time source. Neither will it try catch up
41 * missed ticks. This suits our current purposes best I'd say.
42 */
43#undef USE_APC
44#undef USE_WINMM
45#undef USE_CATCH_UP
46
47
48/*******************************************************************************
49* Header Files *
50*******************************************************************************/
51#define LOG_GROUP RTLOGGROUP_TIMER
52#define _WIN32_WINNT 0x0500
53#include <Windows.h>
54
55#include <iprt/timer.h>
56#ifdef USE_CATCH_UP
57# include <iprt/time.h>
58#endif
59#include <iprt/alloc.h>
60#include <iprt/assert.h>
61#include <iprt/thread.h>
62#include <iprt/log.h>
63#include <iprt/asm.h>
64#include <iprt/semaphore.h>
65#include <iprt/err.h>
66#include "internal/magics.h"
67
68__BEGIN_DECLS
69/* from sysinternals. */
70NTSYSAPI LONG NTAPI NtSetTimerResolution(IN ULONG DesiredResolution, IN BOOLEAN SetResolution, OUT PULONG CurrentResolution);
71NTSYSAPI LONG NTAPI NtQueryTimerResolution(OUT PULONG MinimumResolution, OUT PULONG MaximumResolution, OUT PULONG CurrentResolution);
72__END_DECLS
73
74
75/*******************************************************************************
76* Structures and Typedefs *
77*******************************************************************************/
78/**
79 * The internal representation of a timer handle.
80 */
81typedef struct RTTIMER
82{
83 /** Magic.
84 * This is RTTIMER_MAGIC, but changes to something else before the timer
85 * is destroyed to indicate clearly that thread should exit. */
86 volatile uint32_t u32Magic;
87 /** User argument. */
88 void *pvUser;
89 /** Callback. */
90 PFNRTTIMER pfnTimer;
91 /** The interval. */
92 unsigned uMilliesInterval;
93#ifdef USE_WINMM
94 /** Win32 timer id. */
95 UINT TimerId;
96#else
97 /** Time handle. */
98 HANDLE hTimer;
99#ifdef USE_APC
100 /** Handle to wait on. */
101 HANDLE hevWait;
102#endif
103 /** USE_CATCH_UP: ns time of the next tick.
104 * !USE_CATCH_UP: -uMilliesInterval * 10000 */
105 LARGE_INTEGER llNext;
106 /** The thread handle of the timer thread. */
107 RTTHREAD Thread;
108 /** The error/status of the timer.
109 * Initially -1, set to 0 when the timer have been successfully started, and
110 * to errno on failure in starting the timer. */
111 volatile int iError;
112#endif
113} RTTIMER;
114
115
116
117#ifdef USE_WINMM
118/**
119 * Win32 callback wrapper.
120 */
121static void CALLBACK rttimerCallback(UINT uTimerID, UINT uMsg, DWORD_PTR dwUser, DWORD_PTR dw1, DWORD_PTR dw2)
122{
123 PRTTIMER pTimer = (PRTTIMER)(void *)dwUser;
124 Assert(pTimer->TimerId == uTimerID);
125 pTimer->pfnTimer(pTimer, pTimer->pvUser);
126 NOREF(uMsg); NOREF(dw1); NOREF(dw2); NOREF(uTimerID);
127}
128#else /* !USE_WINMM */
129
130#ifdef USE_APC
131/**
132 * Async callback.
133 *
134 * @param lpArgToCompletionRoutine Pointer to our timer structure.
135 */
136VOID CALLBACK rttimerAPCProc(LPVOID lpArgToCompletionRoutine, DWORD dwTimerLowValue, DWORD dwTimerHighValue)
137{
138 PRTTIMER pTimer = (PRTTIMER)lpArgToCompletionRoutine;
139
140 /*
141 * Check if we're begin destroyed.
142 */
143 if (pTimer->u32Magic != RTTIMER_MAGIC)
144 return;
145
146 /*
147 * Callback the handler.
148 */
149 pTimer->pfnTimer(pTimer, pTimer->pvUser);
150
151 /*
152 * Rearm the timer handler.
153 */
154#ifdef USE_CATCH_UP
155 pTimer->llNext.QuadPart += (int64_t)pTimer->uMilliesInterval * 10000;
156 LARGE_INTEGER ll;
157 ll.QuadPart = RTTimeNanoTS() - pTimer->llNext.QuadPart;
158 if (ll.QuadPart < -500000)
159 ll.QuadPart = ll.QuadPart / 100;
160 else
161 ll.QuadPart = -500000 / 100; /* need to catch up, do a minimum wait of 0.5ms. */
162#else
163 LARGE_INTEGER ll = pTimer->llNext;
164#endif
165 BOOL frc = SetWaitableTimer(pTimer->hTimer, &ll, 0, rttimerAPCProc, pTimer, FALSE);
166 AssertMsg(frc || pTimer->u32Magic != RTTIMER_MAGIC, ("last error %d\n", GetLastError()));
167}
168#endif /* USE_APC */
169
170/**
171 * Timer thread.
172 */
173static DECLCALLBACK(int) rttimerCallback(RTTHREAD Thread, void *pvArg)
174{
175 PRTTIMER pTimer = (PRTTIMER)(void *)pvArg;
176 Assert(pTimer->u32Magic == RTTIMER_MAGIC);
177
178 /*
179 * Bounce our priority up quite a bit.
180 */
181 if ( !SetThreadPriority(GetCurrentThread(), THREAD_PRIORITY_TIME_CRITICAL)
182 /*&& !SetThreadPriority(GetCurrentThread(), THREAD_PRIORITY_HIGHEST)*/)
183 {
184 int rc = GetLastError();
185 AssertMsgFailed(("Failed to set priority class lasterror %d.\n", rc));
186 pTimer->iError = RTErrConvertFromWin32(rc);
187 return rc;
188 }
189
190 /*
191 * Start the waitable timer.
192 */
193
194#ifdef USE_CATCH_UP
195 const int64_t NSInterval = (int64_t)pTimer->uMilliesInterval * 1000000;
196 pTimer->llNext.QuadPart = RTTimeNanoTS() + NSInterval;
197#else
198 pTimer->llNext.QuadPart = -(int64_t)pTimer->uMilliesInterval * 10000;
199#endif
200 LARGE_INTEGER ll;
201 ll.QuadPart = -(int64_t)pTimer->uMilliesInterval * 10000;
202#ifdef USE_APC
203 if (!SetWaitableTimer(pTimer->hTimer, &ll, 0, rttimerAPCProc, pTimer, FALSE))
204#else
205 if (!SetWaitableTimer(pTimer->hTimer, &ll, 0, NULL, NULL, FALSE))
206#endif
207 {
208 int rc = GetLastError();
209 AssertMsgFailed(("Failed to set timer, lasterr %d.\n", rc));
210 pTimer->iError = RTErrConvertFromWin32(rc);
211 RTThreadUserSignal(Thread);
212 return rc;
213 }
214
215 /*
216 * Wait for the semaphore to be posted.
217 */
218 RTThreadUserSignal(Thread);
219 for (;pTimer->u32Magic == RTTIMER_MAGIC;)
220 {
221#ifdef USE_APC
222 int rc = WaitForSingleObjectEx(pTimer->hevWait, INFINITE, TRUE);
223 if (rc != WAIT_OBJECT_0 && rc != WAIT_IO_COMPLETION)
224#else
225 int rc = WaitForSingleObjectEx(pTimer->hTimer, INFINITE, FALSE);
226 if (pTimer->u32Magic != RTTIMER_MAGIC)
227 break;
228 if (rc == WAIT_OBJECT_0)
229 {
230 /*
231 * Callback the handler.
232 */
233 pTimer->pfnTimer(pTimer, pTimer->pvUser);
234
235 /*
236 * Rearm the timer handler.
237 */
238#ifdef USE_CATCH_UP
239 pTimer->llNext.QuadPart += NSInterval;
240 LARGE_INTEGER ll;
241 ll.QuadPart = RTTimeNanoTS() - pTimer->llNext.QuadPart;
242 if (ll.QuadPart < -500000)
243 ll.QuadPart = ll.QuadPart / 100;
244 else
245 ll.QuadPart = -500000 / 100; /* need to catch up, do a minimum wait of 0.5ms. */
246#else
247 LARGE_INTEGER ll = pTimer->llNext;
248#endif
249 BOOL frc = SetWaitableTimer(pTimer->hTimer, &ll, 0, NULL, NULL, FALSE);
250 AssertMsg(frc || pTimer->u32Magic != RTTIMER_MAGIC, ("last error %d\n", GetLastError()));
251 }
252 else
253#endif
254 {
255 /*
256 * We failed during wait, so just signal the destructor and exit.
257 */
258 int rc2 = GetLastError();
259 RTThreadUserSignal(Thread);
260 AssertMsgFailed(("Wait on hTimer failed, rc=%d lasterr=%d\n", rc, rc2));
261 return -1;
262 }
263 }
264
265 /*
266 * Exit.
267 */
268 RTThreadUserSignal(Thread);
269 return 0;
270}
271#endif /* !USE_WINMM */
272
273
274/**
275 * Create a recurring timer.
276 *
277 * @returns iprt status code.
278 * @param ppTimer Where to store the timer handle.
279 * @param uMilliesInterval Milliseconds between the timer ticks.
280 * This is rounded up to the system granularity.
281 * @param pfnTimer Callback function which shall be scheduled for execution
282 * on every timer tick.
283 * @param pvUser User argument for the callback.
284 */
285RTR3DECL(int) RTTimerCreate(PRTTIMER *ppTimer, unsigned uMilliesInterval, PFNRTTIMER pfnTimer, void *pvUser)
286{
287#ifndef USE_WINMM
288 /*
289 * On windows we'll have to set the timer resolution before
290 * we start the timer.
291 */
292 ULONG Min = ~0;
293 ULONG Max = ~0;
294 ULONG Cur = ~0;
295 NtQueryTimerResolution(&Min, &Max, &Cur);
296 Log(("NtQueryTimerResolution -> Min=%lu Max=%lu Cur=%lu (100ns)\n", Min, Max, Cur));
297 if (Cur > Max && Cur > 10000 /* = 1ms */)
298 {
299 if (NtSetTimerResolution(10000, TRUE, &Cur) >= 0)
300 Log(("Changed timer resolution to 1ms.\n"));
301 else if (NtSetTimerResolution(20000, TRUE, &Cur) >= 0)
302 Log(("Changed timer resolution to 2ms.\n"));
303 else if (NtSetTimerResolution(40000, TRUE, &Cur) >= 0)
304 Log(("Changed timer resolution to 4ms.\n"));
305 else if (Max <= 50000 && NtSetTimerResolution(Max, TRUE, &Cur) >= 0)
306 Log(("Changed timer resolution to %lu *100ns.\n", Max));
307 else
308 {
309 AssertMsgFailed(("Failed to configure timer resolution!\n"));
310 return VERR_INTERNAL_ERROR;
311 }
312 }
313#endif /* !USE_WINN */
314
315 /*
316 * Create new timer.
317 */
318 int rc;
319 PRTTIMER pTimer = (PRTTIMER)RTMemAlloc(sizeof(*pTimer));
320 if (pTimer)
321 {
322 pTimer->u32Magic = RTTIMER_MAGIC;
323 pTimer->pvUser = pvUser;
324 pTimer->pfnTimer = pfnTimer;
325 pTimer->uMilliesInterval = uMilliesInterval;
326#ifdef USE_WINMM
327 /* sync kill doesn't work. */
328 pTimer->TimerId = timeSetEvent(uMilliesInterval, 0, rttimerCallback, (DWORD_PTR)pTimer, TIME_PERIODIC | TIME_CALLBACK_FUNCTION);
329 if (pTimer->TimerId)
330 {
331 ULONG Min = ~0;
332 ULONG Max = ~0;
333 ULONG Cur = ~0;
334 NtQueryTimerResolution(&Min, &Max, &Cur);
335 Log(("NtQueryTimerResolution -> Min=%lu Max=%lu Cur=%lu (100ns)\n", Min, Max, Cur));
336
337 *ppTimer = pTimer;
338 return VINF_SUCCESS;
339 }
340 rc = VERR_INVALID_PARAMETER;
341
342#else /* !USE_WINMM */
343
344 /*
345 * Create Win32 event semaphore.
346 */
347 pTimer->iError = 0;
348 pTimer->hTimer = CreateWaitableTimer(NULL, TRUE, NULL);
349 if (pTimer->hTimer)
350 {
351#ifdef USE_APC
352 /*
353 * Create wait semaphore.
354 */
355 pTimer->hevWait = CreateEvent(NULL, FALSE, FALSE, NULL);
356 if (pTimer->hevWait)
357#endif
358 {
359 /*
360 * Kick off the timer thread.
361 */
362 rc = RTThreadCreate(&pTimer->Thread, rttimerCallback, pTimer, 0, RTTHREADTYPE_TIMER, RTTHREADFLAGS_WAITABLE, "Timer");
363 if (RT_SUCCESS(rc))
364 {
365 /*
366 * Wait for the timer to successfully create the timer
367 * If we don't get a response in 10 secs, then we assume we're screwed.
368 */
369 rc = RTThreadUserWait(pTimer->Thread, 10000);
370 if (RT_SUCCESS(rc))
371 {
372 rc = pTimer->iError;
373 if (RT_SUCCESS(rc))
374 {
375 *ppTimer = pTimer;
376 return VINF_SUCCESS;
377 }
378 }
379 ASMAtomicXchgU32(&pTimer->u32Magic, RTTIMER_MAGIC + 1);
380 RTThreadWait(pTimer->Thread, 250, NULL);
381 CancelWaitableTimer(pTimer->hTimer);
382 }
383#ifdef USE_APC
384 CloseHandle(pTimer->hevWait);
385#endif
386 }
387 CloseHandle(pTimer->hTimer);
388 }
389#endif /* !USE_WINMM */
390
391 AssertMsgFailed(("Failed to create timer uMilliesInterval=%d. rc=%d\n", uMilliesInterval, rc));
392 RTMemFree(pTimer);
393 }
394 else
395 rc = VERR_NO_MEMORY;
396 return rc;
397}
398
399
400
401/**
402 * Stops and destroys a running timer.
403 *
404 * @returns iprt status code.
405 * @param pTimer Timer to stop and destroy.
406 */
407RTR3DECL(int) RTTimerDestroy(PRTTIMER pTimer)
408{
409 /* NULL is ok. */
410 if (!pTimer)
411 return VINF_SUCCESS;
412
413 /*
414 * Validate handle first.
415 */
416 int rc;
417 if ( VALID_PTR(pTimer)
418 && pTimer->u32Magic == RTTIMER_MAGIC)
419 {
420#ifdef USE_WINMM
421 /*
422 * Kill the timer and exit.
423 */
424 rc = timeKillEvent(pTimer->TimerId);
425 AssertMsg(rc == TIMERR_NOERROR, ("timeKillEvent -> %d\n", rc));
426 ASMAtomicXchgU32(&pTimer->u32Magic, RTTIMER_MAGIC + 1);
427 RTThreadSleep(1);
428
429#else /* !USE_WINMM */
430
431 /*
432 * Signal that we want the thread to exit.
433 */
434 ASMAtomicXchgU32(&pTimer->u32Magic, RTTIMER_MAGIC + 1);
435#ifdef USE_APC
436 SetEvent(pTimer->hevWait);
437 CloseHandle(pTimer->hevWait);
438 rc = CancelWaitableTimer(pTimer->hTimer);
439 AssertMsg(rc, ("CancelWaitableTimer lasterr=%d\n", GetLastError()));
440#else
441 LARGE_INTEGER ll = {0};
442 ll.LowPart = 100;
443 rc = SetWaitableTimer(pTimer->hTimer, &ll, 0, NULL, NULL, FALSE);
444 AssertMsg(rc, ("CancelWaitableTimer lasterr=%d\n", GetLastError()));
445#endif
446
447 /*
448 * Wait for the thread to exit.
449 * And if it don't wanna exit, we'll get kill it.
450 */
451 rc = RTThreadWait(pTimer->Thread, 1000, NULL);
452 if (RT_FAILURE(rc))
453 TerminateThread((HANDLE)RTThreadGetNative(pTimer->Thread), -1);
454
455 /*
456 * Free resource.
457 */
458 rc = CloseHandle(pTimer->hTimer);
459 AssertMsg(rc, ("CloseHandle lasterr=%d\n", GetLastError()));
460
461#endif /* !USE_WINMM */
462 RTMemFree(pTimer);
463 return rc;
464 }
465
466 rc = VERR_INVALID_HANDLE;
467 AssertMsgFailed(("Failed to destroy timer %p. rc=%d\n", pTimer, rc));
468 return rc;
469}
470
Note: See TracBrowser for help on using the repository browser.

© 2025 Oracle Support Privacy / Do Not Sell My Info Terms of Use Trademark Policy Automated Access Etiquette