diff options
author | Andreas Klebinger <klebinger.andreas@gmx.at> | 2020-04-02 11:47:32 +0200 |
---|---|---|
committer | Andreas Klebinger <klebinger.andreas@gmx.at> | 2020-04-02 12:03:13 +0200 |
commit | 38c072c70de8272c009f7bb36a9cf6713afde94d (patch) | |
tree | 040bf612b22985ced9dee43d69405f1a127412ed | |
parent | 30a63e79c65b023497af4fe2347149382c71829d (diff) | |
download | haskell-wip/andreask/ticker.tar.gz |
Move windows RTS timers to a QueryPerformanceCounter based API.wip/andreask/ticker
This code was initially part of WINIO and the initial version
was written by Phyx (Tamar Christina).
-rw-r--r-- | rts/win32/Ticker.c | 227 |
1 files changed, 200 insertions, 27 deletions
diff --git a/rts/win32/Ticker.c b/rts/win32/Ticker.c index 27c9070a50..5b19e212f1 100644 --- a/rts/win32/Ticker.c +++ b/rts/win32/Ticker.c @@ -1,6 +1,9 @@ /* * RTS periodic timers. * + * http://undocumented.ntinternals.net/index.html?page=UserMode%2FUndocumented%20Functions%2FTime%2FNtSetTimerResolution.html + * https://docs.microsoft.com/en-us/windows/desktop/SysInfo/acquiring-high-resolution-time-stamps#hardware-timer-info + * */ #include "Rts.h" @@ -8,18 +11,121 @@ #include <windows.h> #include <stdio.h> #include <process.h> +#include <winnt.h> +#include <ntdef.h> +#include <ntstatus.h> +#include <stdbool.h> + +/* These functions are undocumented Windows APIs that are most commonly used by + driver code. They're the building blocks for the Win32 API functions and + haven't changed since David Cutler proposed them to the Portable System's + group in 1989. So they should be pretty safe. They also provise a + resolution of 100ns, which is much better than the 15ms of the normal Win32 + API calls. */ + +typedef struct _TIMER_BASIC_INFORMATION { + LARGE_INTEGER RemainingTime; + BOOLEAN TimerState; +} TIMER_BASIC_INFORMATION, *PTIMER_BASIC_INFORMATION; + +typedef enum _TIMER_INFORMATION_CLASS { + TimerBasicInformation +} TIMER_INFORMATION_CLASS, *PTIMER_INFORMATION_CLASS; +typedef void (WINAPI * PTIMER_APC_ROUTINE)(LPVOID TimerContext, DWORD TimerLowValue, DWORD TimerHighValue); + +typedef NTSTATUS (WINAPI *NtCreateTimerCB ) (PHANDLE, ACCESS_MASK, POBJECT_ATTRIBUTES, TIMER_TYPE); +typedef NTSTATUS (WINAPI *NtCancelTimerCB ) (HANDLE, PBOOLEAN); +typedef NTSTATUS (WINAPI *NtQueryTimerCB ) (HANDLE, TIMER_INFORMATION_CLASS, PVOID, ULONG, PULONG); +typedef NTSTATUS (WINAPI *NtSetTimerCB ) (HANDLE, PLARGE_INTEGER, PTIMER_APC_ROUTINE, PVOID, BOOLEAN, LONG, PBOOLEAN); +typedef NTSTATUS (WINAPI *NtOpenTimerCB ) (PHANDLE, ACCESS_MASK, POBJECT_ATTRIBUTES); +typedef ULONG (WINAPI *NtGetTickCountCB ) (VOID); +typedef NTSTATUS (WINAPI *NtQueryTimerResolutionCB) (PULONG, PULONG, PULONG); +typedef NTSTATUS (WINAPI *NtSetTimerResolutionCB ) (ULONG, BOOLEAN, PULONG); +typedef NTSTATUS (WINAPI *NtCloseCB ) (HANDLE); + +static NtCreateTimerCB NtCreateTimer; +static NtCancelTimerCB NtCancelTimer; +static NtQueryTimerCB NtQueryTimer; +static NtSetTimerCB NtSetTimer; +static NtOpenTimerCB NtOpenTimer; +static NtGetTickCountCB NtGetTickCount; +static NtQueryTimerResolutionCB NtQueryTimerResolution; +static NtSetTimerResolutionCB NtSetTimerResolution; +static NtCloseCB NtClose; +static HMODULE ntdll; +/* Determine if the kernel interface for the timer is usable. If so use it. */ +static volatile boolean use_kernel_timer = false; +static volatile boolean timers_running = false; + +/* Timer Queue fallback code. */ static TickProc tick_proc = NULL; static HANDLE timer_queue = NULL; static HANDLE timer = NULL; static Time tick_interval = 0; +static LARGE_INTEGER timeout; +static LARGE_INTEGER Frequency; +static LARGE_INTEGER StartingTime, EndingTime, ElapsedMicroseconds; + +static void WINAPI +kernel_callback ( + LPVOID TimerContext STG_UNUSED, + DWORD TimerLowValue STG_UNUSED, + DWORD TimerHighValue STG_UNUSED) +{ + QueryPerformanceCounter(&EndingTime); + tick_proc(0); + + ElapsedMicroseconds.QuadPart = EndingTime.QuadPart - StartingTime.QuadPart; + ElapsedMicroseconds.QuadPart *= 1000000; + ElapsedMicroseconds.QuadPart /= Frequency.QuadPart; + debugBelch ("%llu\n", ElapsedMicroseconds.QuadPart); +} + +static void WINAPI +kernel_callback_test ( + LPVOID TimerContext STG_UNUSED, + DWORD TimerLowValue STG_UNUSED, + DWORD TimerHighValue STG_UNUSED) +{ + use_kernel_timer = true; +} static VOID CALLBACK tick_callback( PVOID lpParameter STG_UNUSED, - BOOLEAN TimerOrWaitFired STG_UNUSED - ) + BOOLEAN TimerOrWaitFired STG_UNUSED) { - tick_proc(0); + tick_proc(0); +} + +static DWORD WINAPI timer_manager (LPVOID lpParameter STG_UNUSED) +{ + while (timers_running) { + /* Set thread into an alertable state. */ + DWORD res = WaitForSingleObjectEx (timer, INFINITE, true); + if (res == WAIT_OBJECT_0) + tick_proc (0); + + TIMER_BASIC_INFORMATION info; + /* Do we really need this call? May cause a drift.. probably checking return + for the wait is enough. */ + NtQueryTimer (timer, TimerBasicInformation, &info, sizeof(info), NULL); + + /* If signaled reset the timer, since the interval for automatic timer reset + is in the 100ms range and so it's useless. */ + if (info.TimerState) { + NTSTATUS result = NtSetTimer (timer, &timeout, &kernel_callback, NULL, + 0, 0, NULL); + QueryPerformanceCounter(&StartingTime); + /* Error handling here may be a bit harsh.. perhaps emit a non-fatal error + instead?. */ + if (result != STATUS_SUCCESS) { + sysErrorBelch("NtSetTimer"); + stg_exit(EXIT_FAILURE); + } + } + } + return 0; } // We use the CreateTimerQueue() API which has been around since @@ -28,54 +134,121 @@ static VOID CALLBACK tick_callback( // // Even with the improvements in Windows 7, this timer isn't going to // be very useful for profiling with a max usable resolution of -// 15ms. Unfortunately we don't have anything better. +// 15ms. Unfortunately we don't have anything better. <-- We do (Phyx). + void initTicker (Time interval, TickProc handle_tick) { - tick_interval = interval; - tick_proc = handle_tick; + tick_interval = interval; + tick_proc = handle_tick; + ntdll = LoadLibraryW (L"ntdll.dll"); + NtCreateTimer = (NtCreateTimerCB) GetProcAddress (ntdll, "NtCreateTimer"); + NtCancelTimer = (NtCancelTimerCB) GetProcAddress (ntdll, "NtCancelTimer"); + NtQueryTimer = (NtQueryTimerCB) GetProcAddress (ntdll, "NtQueryTimer"); + NtSetTimer = (NtSetTimerCB) GetProcAddress (ntdll, "NtSetTimer"); + NtOpenTimer = (NtOpenTimerCB) GetProcAddress (ntdll, "NtOpenTimer"); + NtGetTickCount = (NtGetTickCountCB) GetProcAddress (ntdll, "NtGetTickCount"); + NtQueryTimerResolution = (NtQueryTimerResolutionCB)GetProcAddress (ntdll, "NtQueryTimerResolution"); + NtSetTimerResolution = (NtSetTimerResolutionCB) GetProcAddress (ntdll, "NtSetTimerResolution"); + NtClose = (NtCloseCB) GetProcAddress (ntdll, "NtClose"); + + HANDLE detectTimer; + NTSTATUS result; + IF_DEBUG(scheduler, debugBelch ("* Detecting kernel support for high accuracy timers..\n")); + result = NtCreateTimer (&detectTimer, TIMER_ALL_ACCESS, NULL, SynchronizationEvent); + IF_DEBUG(scheduler, debugBelch ("* Detecting NtCreateTimer support..\n")); + if (result == STATUS_SUCCESS) { + timeout.QuadPart = -1000LL; + result = NtSetTimer (detectTimer, &timeout, &kernel_callback_test, NULL, false, 0, NULL); + IF_DEBUG(scheduler, debugBelch ("* Detecting NtSetTimer support..\n")); + if (result == STATUS_SUCCESS) { + DWORD res = WaitForSingleObjectEx (detectTimer, 100, true); + use_kernel_timer |= res == WAIT_OBJECT_0; + NtClose (detectTimer); + } + IF_DEBUG(scheduler, debugBelch ("* NtSetTimer signaled: %d.\n", use_kernel_timer)); + if (use_kernel_timer) { + timers_running = true; + timer_queue = CreateThread (NULL, 0, &timer_manager, NULL, CREATE_SUSPENDED, NULL); + if (!timer_queue) { + use_kernel_timer = false; + } + + if (use_kernel_timer) { + IF_DEBUG(scheduler, debugBelch ("* Kernel supports high accuracy timers. Enabling..\n")); + return; + } + }; + use_kernel_timer = false; // disable for now. + } + + if (!use_kernel_timer) { timer_queue = CreateTimerQueue(); if (timer_queue == NULL) { sysErrorBelch("CreateTimerQueue"); stg_exit(EXIT_FAILURE); } + } } void startTicker(void) { - BOOL r; - - r = CreateTimerQueueTimer(&timer, - timer_queue, - tick_callback, - 0, - 0, - TimeToUS(tick_interval) / 1000, // ms - WT_EXECUTEINTIMERTHREAD); - if (r == 0) { - sysErrorBelch("CreateTimerQueueTimer"); - stg_exit(EXIT_FAILURE); - } + char* errmsg; + //debugBelch ("* Starting ticker..\n"); + if (use_kernel_timer) { + //debugBelch ("* Starting timer for: %lluns\n", TimeToNS (tick_interval)) + NTSTATUS result = NtCreateTimer (&timer, TIMER_ALL_ACCESS, NULL, SynchronizationEvent); + if (result != STATUS_SUCCESS) { errmsg = "NtCreateTimer"; goto fail; } + + timeout.QuadPart = -(TimeToNS (tick_interval) / 100); + result = NtSetTimer (timer, &timeout, &kernel_callback, NULL, 0, 0, NULL); + if (result != STATUS_SUCCESS) { errmsg = "NtSetTimer"; goto fail; } + timers_running = true; + QueryPerformanceFrequency(&Frequency); + if (ResumeThread(timer_queue) == (DWORD)-1) { errmsg = "ResumeThread"; goto fail; } + QueryPerformanceCounter(&StartingTime); + //debugBelch ("* Timer started successfully.\n"); + } else { + BOOL r = CreateTimerQueueTimer(&timer, timer_queue, tick_callback, 0, 0, + TimeToMS(tick_interval), + WT_EXECUTEINTIMERTHREAD); + if (r == 0) { errmsg = "CreateTimerQueueTimer"; goto fail; } + } + return; + +fail: + sysErrorBelch(errmsg); + stg_exit(EXIT_FAILURE); } void stopTicker(void) { - if (timer_queue != NULL && timer != NULL) { - DeleteTimerQueueTimer(timer_queue, timer, NULL); - timer = NULL; + if (timer_queue != NULL && timer != NULL) { + if (use_kernel_timer) { + SuspendThread (timer_queue); + NtCancelTimer (timer, NULL); + CloseHandle (timer); + //debugBelch ("* Timer stopped.\n"); + } else { + DeleteTimerQueueTimer(timer_queue, timer, NULL); } + timer = NULL; + } } void exitTicker (bool wait) { - stopTicker(); - if (timer_queue != NULL) { - DeleteTimerQueueEx(timer_queue, wait ? INVALID_HANDLE_VALUE : NULL); - timer_queue = NULL; - } + stopTicker(); + if (use_kernel_timer) { + timers_running = false; + CloseHandle (timer_queue); + } else if (timer_queue != NULL) { + DeleteTimerQueueEx(timer_queue, wait ? INVALID_HANDLE_VALUE : NULL); + } + timer_queue = NULL; } |