VirtualBox

source: vbox/trunk/src/VBox/Runtime/r3/win/timer-win.cpp@ 5702

Last change on this file since 5702 was 5428, checked in by vboxsync, 17 years ago

*-win32.cpp -> *-win.cpp

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