Пользовательский доступ к таймеру прерывания, например, через KeQueryInterruptTime (или аналогичный) - PullRequest
3 голосов
/ 21 ноября 2011

Есть ли "Nt" или аналогичная (то есть не-драйвер режима ядра) эквивалентная функция для KeQueryInterruptTime или что-то подобное? Кажется, что нет такой вещи, как NtQueryInterruptTime, по крайней мере, я ее не нашел.

Мне нужен какой-то достаточно точный и надежный, монотонный таймер (таким образом, , а не QPC), который достаточно эффективен и не имеет сюрпризов в виде переполненного 32-битного счетчика, и в нем нет необходимости » Интеллект », без часовых поясов или сложных структур.

Так что в идеале я хочу что-то вроде timeGetTime с 64-битным значением. Это даже не должен быть тот же таймер.
Существует GetTickCount64, начиная с Vista, что было бы приемлемо как таковое, но я бы не хотел прерывать поддержку XP только по такой глупой причине.

Чтение четырех слов в 0x7FFE0008, как указано здесь ... хорошо, работает ... и это доказывает, что на самом деле действительный внутренний счетчик равен 64 битам под XP (это и так быстро, как только возможно), но, черт ... давайте не будем говорить о том, что это за неприятный хак, чтобы прочитать какую-то неизвестную, жестко запрограммированную область памяти.

Должно быть что-то среднее между вызовом высокоуровневой API-функции (масштабирование 64-битного счетчика до 32-битного) и чтением необработанного адреса памяти?

Ответы [ 3 ]

3 голосов
/ 22 ноября 2011

Вот пример поточно-ориентированной оболочки для GetTickCount (), расширяющей значение счетчика тиков до 64 бит и эквивалентного GetTickCount64 ().

Чтобы избежать нежелательных переполнений счетчика, не забывайте вызывать эту функцию несколько раз каждые 49,7 дня. У вас даже может быть выделенный поток, единственной целью которого было бы вызвать эту функцию, а затем проспать около 20 дней в бесконечном цикле.

ULONGLONG MyGetTickCount64(void)
{
  static volatile LONGLONG Count = 0;
  LONGLONG curCount1, curCount2;
  LONGLONG tmp;

  curCount1 = InterlockedCompareExchange64(&Count, 0, 0);

  curCount2 = curCount1 & 0xFFFFFFFF00000000;
  curCount2 |= GetTickCount();

  if ((ULONG)curCount2 < (ULONG)curCount1)
  {
    curCount2 += 0x100000000;
  }

  tmp = InterlockedCompareExchange64(&Count, curCount2, curCount1);

  if (tmp == curCount1)
  {
    return curCount2;
  }
  else
  {
    return tmp;
  }
}

РЕДАКТИРОВАТЬ : И вот полное приложение, которое тестирует MyGetTickCount64 ().

// Compiled with Open Watcom C 1.9: wcl386.exe /we /wx /q gettick.c

#include <windows.h>
#include <stdio.h>
#include <stdarg.h>
#include <stdlib.h>

//
// The below code is an ugly implementation of InterlockedCompareExchange64()
// that is apparently missing in Open Watcom C 1.9.
// It must work with MSVC++ too, however.
//
UINT8 Cmpxchg8bData[] =
{
  0x55,             // push      ebp
  0x89, 0xE5,       // mov       ebp, esp
  0x57,             // push      edi
  0x51,             // push      ecx
  0x53,             // push      ebx
  0x8B, 0x7D, 0x10, // mov       edi, [ebp + 0x10]
  0x8B, 0x07,       // mov       eax, [edi]
  0x8B, 0x57, 0x04, // mov       edx, [edi + 0x4]
  0x8B, 0x7D, 0x0C, // mov       edi, [ebp + 0xc]
  0x8B, 0x1F,       // mov       ebx, [edi]
  0x8B, 0x4F, 0x04, // mov       ecx, [edi + 0x4]
  0x8B, 0x7D, 0x08, // mov       edi, [ebp + 0x8]
  0xF0,             // lock:
  0x0F, 0xC7, 0x0F, // cmpxchg8b [edi]
  0x5B,             // pop       ebx
  0x59,             // pop       ecx
  0x5F,             // pop       edi
  0x5D,             // pop       ebp
  0xC3              // ret
};

LONGLONG (__cdecl *Cmpxchg8b)(LONGLONG volatile* Dest, LONGLONG* Exch, LONGLONG* Comp) =
  (LONGLONG (__cdecl *)(LONGLONG volatile*, LONGLONG*, LONGLONG*))Cmpxchg8bData;

LONGLONG MyInterlockedCompareExchange64(LONGLONG volatile* Destination,
                                        LONGLONG Exchange,
                                        LONGLONG Comparand)
{
  return Cmpxchg8b(Destination, &Exchange, &Comparand);
}

#ifdef InterlockedCompareExchange64
#undef InterlockedCompareExchange64
#endif

#define InterlockedCompareExchange64(Destination, Exchange, Comparand) \
  MyInterlockedCompareExchange64(Destination, Exchange, Comparand)

//
// This stuff makes a thread-safe printf().
// We don't want characters output by one thread to be mixed
// with characters output by another. We want printf() to be
// "atomic".
// We use a critical section around vprintf() to achieve "atomicity".
//
static CRITICAL_SECTION PrintfCriticalSection;

int ts_printf(const char* Format, ...)
{
  int count;
  va_list ap;

  EnterCriticalSection(&PrintfCriticalSection);

  va_start(ap, Format);
  count = vprintf(Format, ap);
  va_end(ap);

  LeaveCriticalSection(&PrintfCriticalSection);

  return count;
}

#define TICK_COUNT_10MS_INCREMENT 0x800000

//
// This is the simulated tick counter.
// Its low 32 bits are going to be returned by
// our, simulated, GetTickCount().
//
// TICK_COUNT_10MS_INCREMENT is what the counter is
// incremented by every time. The value is so chosen
// that the counter quickly overflows in its
// low 32 bits.
//
static volatile LONGLONG SimulatedTickCount = 0;

//
// This is our simulated 32-bit GetTickCount()
// that returns a count that often overflows.
//
ULONG SimulatedGetTickCount(void)
{
  return (ULONG)SimulatedTickCount;
}

//
// This thread function will increment the simulated tick counter
// whose value's low 32 bits we'll be reading in SimulatedGetTickCount().
//
DWORD WINAPI SimulatedTickThread(LPVOID lpParameter)
{
  UNREFERENCED_PARAMETER(lpParameter);

  for (;;)
  {
    LONGLONG c;

    Sleep(10);

    // Get the counter value, add TICK_COUNT_10MS_INCREMENT to it and
    // store the result back.
    c = InterlockedCompareExchange64(&SimulatedTickCount, 0, 0);
    InterlockedCompareExchange64(&SimulatedTickCount, c + TICK_COUNT_10MS_INCREMENT, c) != c);
  }

  return 0;
}

volatile LONG CountOfObserved32bitOverflows = 0;
volatile LONG CountOfObservedUpdateRaces = 0;

//
// This prints statistics that includes the true 64-bit value of
// SimulatedTickCount that we can't get from SimulatedGetTickCount() as it
// returns only its lower 32 bits.
//
// The stats also include:
// - the number of times that MyGetTickCount64() observes an overflow of
//   SimulatedGetTickCount()
// - the number of times MyGetTickCount64() fails to update its internal
//   counter because of a concurrent update in another thread.
//
void PrintStats(void)
{
  LONGLONG true64bitCounter = InterlockedCompareExchange64(&SimulatedTickCount, 0, 0);

  ts_printf("  0x%08X`%08X <- true 64-bit count; ovfs: ~%d; races: %d\n",
            (ULONG)(true64bitCounter >> 32),
            (ULONG)true64bitCounter,
            CountOfObserved32bitOverflows,
            CountOfObservedUpdateRaces);
}

//
// This is our poor man's implementation of GetTickCount64()
// on top of GetTickCount().
//
// It's thread safe.
//
// When used with actual GetTickCount() instead of SimulatedGetTickCount()
// it must be called at least a few times in 49.7 days to ensure that
// it doesn't miss any overflows in GetTickCount()'s return value.
//
ULONGLONG MyGetTickCount64(void)
{
  static volatile LONGLONG Count = 0;
  LONGLONG curCount1, curCount2;
  LONGLONG tmp;

  curCount1 = InterlockedCompareExchange64(&Count, 0, 0);

  curCount2 = curCount1 & 0xFFFFFFFF00000000;
  curCount2 |= SimulatedGetTickCount();

  if ((ULONG)curCount2 < (ULONG)curCount1)
  {
    curCount2 += 0x100000000;

    InterlockedIncrement(&CountOfObserved32bitOverflows);
  }

  tmp = InterlockedCompareExchange64(&Count, curCount2, curCount1);

  if (tmp != curCount1)
  {
    curCount2 = tmp;

    InterlockedIncrement(&CountOfObservedUpdateRaces);
  }

  return curCount2;
}

//
// This is an error counter. If a thread that uses MyGetTickCount64() notices
// any problem with what MyGetTickCount64() returns, it bumps up this error
// counter and stops. If one of threads sees a non-zero value in this
// counter due to an error in another thread, it stops as well.
//
volatile LONG Error = 0;

//
// This is a thread function that will be using MyGetTickCount64(),
// validating its return value and printing some stats once in a while.
//
// This function is meant to execute concurrently in multiple threads
// to create race conditions inside of MyGetTickCount64() and test it.
//
DWORD WINAPI TickUserThread(LPVOID lpParameter)
{
  DWORD user = (DWORD)lpParameter; // thread number
  ULONGLONG ticks[4];

  ticks[3] = ticks[2] = ticks[1] = MyGetTickCount64();

  while (!Error)
  {
    ticks[0] = ticks[1];
    ticks[1] = MyGetTickCount64();

    // Every ~100 ms sleep a little (slightly lowers CPU load, to about 90%)
    if (ticks[1] > ticks[2] + TICK_COUNT_10MS_INCREMENT * 10L)
    {
      ticks[2] = ticks[1];
      Sleep(1 + rand() % 20);
    }

    // Every ~1000 ms print the last value from MyGetTickCount64().
    // Thread 1 also prints stats here.
    if (ticks[1] > ticks[3] + TICK_COUNT_10MS_INCREMENT * 100L)
    {
      ticks[3] = ticks[1];
      ts_printf("%u:0x%08X`%08X\n", user, (ULONG)(ticks[1] >> 32), (ULONG)ticks[1]);

      if (user == 1)
      {
        PrintStats();
      }
    }

    if (ticks[0] > ticks[1])
    {
      ts_printf("%u:Non-monotonic tick counts: 0x%016llX > 0x%016llX!\n",
                user,
                ticks[0],
                ticks[1]);
      PrintStats();
      InterlockedIncrement(&Error);
      return -1;
    }
    else if (ticks[0] + 0x100000000 <= ticks[1])
    {
      ts_printf("%u:Too big tick count jump: 0x%016llX -> 0x%016llX!\n",
                user,
                ticks[0],
                ticks[1]);
      PrintStats();
      InterlockedIncrement(&Error);
      return -1;
    }

    Sleep(0); // be nice, yield to other threads.
  }

  return 0;
}

//
// This prints stats upon Ctrl+C and terminates the program.
//
BOOL WINAPI ConsoleEventHandler(DWORD Event)
{
  if (Event == CTRL_C_EVENT)
  {
    PrintStats();
  }

  return FALSE;
}

int main(void)
{
  HANDLE simulatedTickThreadHandle;
  HANDLE tickUserThreadHandle;
  DWORD dummy;

  // This is for the missing InterlockedCompareExchange64() workaround.
  VirtualProtect(Cmpxchg8bData, sizeof(Cmpxchg8bData), PAGE_EXECUTE_READWRITE, &dummy);

  InitializeCriticalSection(&PrintfCriticalSection);

  if (!SetConsoleCtrlHandler(&ConsoleEventHandler, TRUE))
  {
    ts_printf("SetConsoleCtrlHandler(&ConsoleEventHandler) failed with error 0x%X\n", GetLastError());
    return -1;
  }

  // Start the tick simulator thread.

  simulatedTickThreadHandle = CreateThread(NULL, 0, &SimulatedTickThread, NULL, 0, NULL);

  if (simulatedTickThreadHandle == NULL)
  {
    ts_printf("CreateThread(&SimulatedTickThread) failed with error 0x%X\n", GetLastError());
    return -1;
  }

  // Start one thread that'll be using MyGetTickCount64().

  tickUserThreadHandle = CreateThread(NULL, 0, &TickUserThread, (LPVOID)2, 0, NULL);
  if (tickUserThreadHandle == NULL)
  {
    ts_printf("CreateThread(&TickUserThread) failed with error 0x%X\n", GetLastError());
    return -1;
  }

  // The other thread using MyGetTickCount64() will be the main thread.

  TickUserThread((LPVOID)1);

  //
  // The app terminates upon any error condition detected in TickUserThread()
  // in any of the threads or by Ctrl+C.
  //

  return 0;
}

В качестве теста я запускал это тестовое приложение под Windows XP в течение 5 с лишним часов на бездействующей машине с двумя ЦП (в простое, чтобы избежать возможного длительного времени голодания и, следовательно, во избежание переполнения счетчика, которое происходит каждые 5 секунд). ) и все еще хорошо.

Вот последний вывод с консоли:

2:0x00000E1B`C8800000
1:0x00000E1B`FA800000
  0x00000E1B`FA800000 <- true 64-bit count; ovfs: ~3824; races: 110858

Как видите, MyGetTickCount64() обнаружил 3824 32-разрядных переполнения и не смог обновить значение Count с его вторым InterlockedCompareExchange64() 110858 раз. Таким образом, переполнения действительно происходят, и последнее число означает, что переменная фактически обновляется двумя потоками.

Вы также можете видеть, что 64-битный тик считает, что два потока получают от MyGetTickCount64() в TickUserThread() ничего не пропущено в старших 32 битах и ​​довольно близок к фактическому 64-битному тику в SimulatedTickCount, чьи 32 младших бита возвращаются SimulatedGetTickCount(). 0x00000E1BC8800000 визуально отстает от 0x00000E1BFA800000 из-за планирования потоков и нечастых распечаток статистики, отстает ровно на 100 * TICK_COUNT_10MS_INCREMENT или на 1 секунду. Внутренне, конечно, разница намного меньше.

Теперь, при наличии InterlockedCompareExchange64() ... Немного странно, что он официально доступен начиная с Windows Vista и Windows Server 2003 . Сервер 2003 фактически собран из той же базы кода, что и Windows XP.

Но самое важное здесь то, что эта функция построена на основе инструкции Pentium CMPXCHG8B, доступной с 1998 года или ранее (1) , (2), И я могу видеть эту инструкцию в моих двоичных файлах Windows XP (SP3). Он находится в ntkrnlpa.exe / ntoskrnl.exe (ядро) и ntdll.dll (DLL, которая экспортирует функции ядра NtXxxx () , на которых все построено) , Найдите последовательность байтов 0xF0, 0x0F, 0xC7 и разберите код вокруг этого места, чтобы увидеть, что эти байты не совпадают случайно.

Вы можете проверить доступность этой инструкции с помощью инструкции CPUID (бит EDX 8 функции CPUID 0x00000001 и функции 0x80000001) и отказаться от запуска вместо сбоя, если инструкция отсутствует, но в наши дни вы вряд ли найти машину, которая не поддерживает эту инструкцию. Если вы это сделаете, это не будет хорошим компьютером для Windows XP и, вероятно, вашим приложением в любом случае.

1 голос
/ 28 марта 2012

Благодаря Google Книгам, которые любезно предложили соответствующую литературу бесплатно, я придумал простую и быструю реализацию GetTickCount64, которая прекрасно работает и на системах до Vista (и все же она несколько менее неприятна, чем чтениезначение из жесткого адреса памяти).

На самом деле это так же просто, как вызов прерывания 0x2A, которое отображается на KiGetTickCount.Во встроенной сборке GCC это дает:

static __inline__ __attribute__((always_inline)) unsigned long long get_tick_count64()
{
    unsigned long long ret;
    __asm__ __volatile__ ("int $0x2a" : "=A"(ret) : : );
    return ret;
}

Из-за того, как работает KiGetTickCount, функцию, вероятно, лучше назвать GetTickCount46, поскольку она выполняет сдвиг вправо на 18, возвращая 46 битов,не 64. Хотя то же самое верно и для исходной версии Vista.

Обратите внимание, что KiGetTickCount clobbers edx, это актуально, если вы планируете реализовать собственную более быструю реализацию 32-битной версии.(в этом случае необходимо добавить edx в список дубликатов!).

1 голос
/ 24 ноября 2011

Вот другой подход, вариант оболочки Алекса, но использующий только 32-битные блокировки.На самом деле он возвращает только 60-битное число, но это все еще хорошо в течение примерно тридцати шести миллионов лет.: -)

Его нужно вызывать чаще, не реже одного раза в три дня.Обычно это не должно быть серьезным недостатком.

ULONGLONG MyTickCount64(void)
{
    static volatile DWORD count = 0xFFFFFFFF;
    DWORD previous_count, current_tick32, previous_count_zone, current_tick32_zone;
    ULONGLONG current_tick64;

    previous_count = InterlockedCompareExchange(&count, 0, 0);
    current_tick32 = GetTickCount();

    if (previous_count == 0xFFFFFFFF)
    {
        // count has never been written
        DWORD initial_count;
        initial_count = current_tick32 >> 28;
        previous_count = InterlockedCompareExchange(&count, initial_count, 0xFFFFFFFF);

        if (previous_count == 0xFFFFFFFF)
        {   // This thread wrote the initial value for count
            previous_count = initial_count;
        }
        else if (previous_count != initial_count)
        {   // Another thread wrote the initial value for count,
            // and it differs from the one we calculated
            current_tick32 = GetTickCount();
        }
    }

    previous_count_zone = previous_count & 15;
    current_tick32_zone = current_tick32 >> 28;

    if (current_tick32_zone == previous_count_zone)
    {
        // The top four bits of the 32-bit tick count haven't changed since count was last written.
        current_tick64 = previous_count;
        current_tick64 <<= 28;
        current_tick64 += current_tick32 & 0x0FFFFFFF;
        return current_tick64;
    }

    if (current_tick32_zone == previous_count_zone + 1 || (current_tick32_zone == 0 && previous_count_zone == 15))
    {
        // The top four bits of the 32-bit tick count have been incremented since count was last written.
        InterlockedCompareExchange(&count, previous_count + 1, previous_count);
        current_tick64 = previous_count + 1;
        current_tick64 <<= 28;
        current_tick64 += current_tick32 & 0x0FFFFFFF;
        return current_tick64;
    }

    // Oops, we weren't called often enough, we're stuck
    return 0xFFFFFFFF;
}
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...