В комментариях к недавней статье оказалось что, во-первых, этот вопрос кому-то да и интересен, и, во-вторых, существует некоторое количество заблуждений на эту тему.
Вводные: нам нужен таймер, на Windows, с точностью порядка 1мс, драйвер при этом мы писать не хотим и решения при исполнении которых процессор попытается радикально ускорить глобальное потепление не приемлем.
Есть ли такое решение? Из коробки - нету, но при помощи нехитрых приспособлений наше досадное недоразумение превращается... в точный таймер, конечно же.
У нас есть некоторое количество досадных недоразумений системных API которые с каждой новой весией Windows всё сильнее ужимают с целью экономии батареи на ноутбуках, общий обзор можно посмотреть в статье по ссылке в самом начале, с графиками. В целом, можно сказать что сколько-нибудь удовлетворительный тайминг начинается примерно со 100мс, всё что ниже чем 15.6мс за гранью допустимого (по мнению ребят из Microsoft). Да и вообще, 640КБ ну точно хватит всем, правда?
Ну а временные промежутки меньше 1мс вообще немыслимы - большинство API даже не принимает таких значений, не говоря уже о корректной работе с ними.
Исходя из этого я буду строить своё решение вокруг трех недокументированных функций API Win32: NtQueryTimerResolution
, NtSetTimerResolution
, NtDelayExecution
.
Связка из первых двух позволяет добиться разрешения системного таймера меньше 1мс, а третья - воспользоваться этим дополнительным разрешением для сна с точностью менее 1мс.
Итак, начнем: я пишу преимущественно на C#, но на любом многих ЯП можно написать всё точно то же самое.
Шаг 0: поднимем разрешение до максимального. Начиная с Win10 2004 это разрешение больше не является глобальным так что можно ни в чём себе не отказывать (с другой стороны - если процесс не поднял себе разрешение то оно будет 15.6мс вне зависимости от того что там в "глобальном" параметре).
[DllImport("ntdll.dll", SetLastError = true)]
static extern int NtQueryTimerResolution(out int MinimumResolution, out int MaximumResolution, out int CurrentResolution);
[DllImport("ntdll.dll", SetLastError = true)]
static extern int NtSetTimerResolution(int DesiredResolution, bool SetResolution, out int CurrentResolution);
private static void AdjustTimerResolution()
{
var queryResult = NtQueryTimerResolution(out var min, out var max, out var current);
if (queryResult != 0) return;
_systemTimerResolution = TimeSpan.FromTicks(current);
if (NtSetTimerResolution(max, true, out _) == 0)
{
_systemTimerResolution = TimeSpan.FromTicks(max);
}
}
Шаг 1: создадим класс PreciseTimer
. Полный код я привести, увы, не могу но общая структура такова: поток с максимальным приоритетом который крутится в while(true)
цикле и следущие важные поля:
// Период срабатывания
private TimeSpan _period;
// Время прошедшее от последнего срабатывания
private readonly Stopwatch _sw = Stopwatch.StartNew();
// Время оставшееся до следующего срабатывания
public TimeSpan Remaining => _period - _sw.Elapsed;
// Таймер уничтожен и должен быть остановлен
private bool _disposed;
Приметка для людей которые не пишут на C#: Stopwatch это обертка над Win32 методамиQueryPerformanceFrequency
и QueryPerformanceCounter
, никакой дополнительной магии нету.
Шаг 2: выясним сколько же нам спать. И спим!
private static void TimerTick()
{
// Реализацию выбора следующего таймера оставим пытливым читателям
PreciseTimer nextTimer = GetNextTimer();
while (!nextTimer._disposed)
{
var remaining = nextTimer.Remaining;
if (remaining > _systemTimerResolution)
{
// Если разрешение системного таймера позволяет - спим
SleepPrecise(remaining);
continue;
}
// Когда разрешение уже не позволяет спать - спиним
while (nextTimer.Remaining > TimeSpan.Zero)
{
// YieldProcessor(), для X86 это инструкция REP NOP
Thread.SpinWait(1000);
}
// Дождались: тикаем!
nextTimer.Tick();
break;
}
}
// Функция unsafe потому что автор кода - ленивая жопа
// Перед броском гнилым помидором подумайте: хотелось бы вам выделять память вручную?
[DllImport("ntdll.dll", SetLastError = true)]
static unsafe extern int NtDelayExecution(bool alertable, long* delayInterval);
private static unsafe void SleepPrecise(TimeSpan timeToSleep)
{
// Посчитаем число целых периодов сна, округлим отбрасываем дробной части
var periods = (int)(timeToSleep.TotalMilliseconds / _systemTimerResolution.TotalMilliseconds);
if (periods == 0)
return;
// И спим!
var ticks = -(_systemTimerResolution.Ticks * periods);
NtDelayExecution(false, &ticks);
}
Шаг 3: посмотрим что из этого вышло: запустим таймер на 1 минуту и запишем полученные промежутки времени. Код обвязки был использован тоже из статьи по линку в начале, но, к сожалению, там нет кода чтобы построить те великолепные графики, поэтому... не стреляйте в программиста, он рисует как умеет.
Тесты запускались на Ryzen 9 5950X под управлением Win11 версии 22000.469
Среднее значение для таймера в 1мс: 1.022мс, stddev = 0.018
Для таймера в 10мс: 10.022мс, stddev = 0.017
Очевидно, что алгоритм можно слегка улучшить учитывая время лага и дожать среднее до целевого, но это тоже останется задачей для пытливых читателей.
С загрузкой процессора вопрос интереснее, в целом можно утверждать что обнаружению она не поддается: все утилиты радостно рапортируют о 0% загрузке. Установив вручную Affinity на конкретное ядро процессора ничего интересного тоже не обнаружено:
Должен признать что это best case когда циклы сна полностью совпали с таймером. Поскольку код оптимизирован под точность он иногда будет нагружать одно ядро примерно на 30-40%.
Тут стоит сразу же уточнить что эта нагрузка для процессоров у которых есть HyperThreading (или аналог) относительно безвредная: гарантируется что в это время второй поток сможет исполняться на том же ядре с минимальным ущербом для производительности. У процессоров без оного ситуация хуже, но тесты на ноутбуках показывают что в плане потребления энергии/тепла ситуация гораздо лучше чем у более грубых решений (while(true)
цикл без rep nop
, например).
Подводя итог: цель достигнута? Мне кажется что ответ "да".
Все сниппеты кода вдохновлены реальными событиями и использованы в продакшне.
Комментарии (52)
lieff
13.02.2022 16:25+1Не уверен, но возможно документировано это делает timeBeginPeriod(1).
Alexx999 Автор
14.02.2022 00:17Да, но разрешение системного таймера в 1мс сна для устойчивой работы таймера в 1мс - это много. Для таймеров в 3мс и более - вполне множно обойтись документированными функциями.
amarao
13.02.2022 17:42+6Смотреть на утилизацию процессора с при увеличении скорости системного таймера глупо. Возьмите ноут, выкиньте всё лишнее из процессов и сравните разряд батареи через N часов аптайма. Я ставлю на то, что на 20-30% больше энергии будет жрать, чем при дефолтных настройках таймера.
Alexx999 Автор
14.02.2022 00:19+1У меня дефолтная настройка таймера именно 0.5мс, спасибо WPF, спасибо хрому и (в моем случае) спасибо софту который мониторит напряжения на процессоре.
Номинально начиная с Win10 2004 это должно влиять гораздо меньше но конкретно в нашем продукте работа от батарей не рассматривается как класс, поэтому тему я не исследовал.
amarao
14.02.2022 12:252000 тиков в секунду? Жуть какая.
cat /boot/config-5.15.0-18-generic |grep HZ CONFIG_NO_HZ_COMMON=y CONFIG_NO_HZ_IDLE=y CONFIG_NO_HZ=y CONFIG_HZ_250=y CONFIG_HZ=250
alexeishch
13.02.2022 18:37+6Функция unsafe потому что автор кода - ленивая жопа Перед броском гнилым помидором подумайте: хотелось бы вам выделять память вручную?
Надо было банально прочитать инструкцию. Для таких случаев маршалинга используется ref long
shai_hulud
13.02.2022 21:29+1Для тех кто подумал "neat" пора переписывать всё с поинтеров на "ref X" хочу упомянуть, что это приводит к маршалингу т.е. будет сгенерена "ненужная" прокладка через которую будет вызван нативный метод. Не все сценарии требуют лучшего пефоманса, там 'ref' подходят.
Alexx999 Автор
14.02.2022 00:22Да, виноват, в рамках примера это наверное было бы лучше чем С-стайл.
Vindicar
13.02.2022 19:22+2Хм. Прикольно, но я затрудняюсь придумать применение.
Мне несколько лет назад требовалось таймить запросы к USB устройству (адаптер для энкодера), так там время выполнения запроса гуляло так, что в итоге пришлось плюнуть и вынести отслеживание энкодера и сбор данных на ардуинку. Боюсь, с другим вводом-выводом будет примерно то же самое.
Разве что какой-то специфический race condition ловить в чужом коде?Revertis
13.02.2022 19:29Ещё несколько лет назад писали тут на Хабре, что Chrome меняет разрешение системного таймера на 1мс, и после закрытия не меняет назад. Видимо для ускорения таймаутов в JS, анимаций и т.п.
Alexx999 Автор
14.02.2022 00:34+2Конкретно 1мс таймер? Это код который не имеет применения... Или имеет :)
У нас в коде используются промежутки 10 мс и более, проблема в том что с приемлемой точностью 10мс отмерить тоже нечем, мультимедиа таймеры точностью не отличились. Т.е. это в первую очередь точный таймер а не быстрый таймер. Просто так случилось что его точности хватает и для промежутков в 1мс и менее, а это уже отличный повод похвалиться успехами, не так ли?
third112
13.02.2022 20:22-2А asm команда rdtsc не подойдет? В ЯП Delphi-7 использовал таким образом:
type TCPUTime = record case Boolean of true: (w1,w2 : dword); false: (t: int64); end; var CPUTime : TCPUTime; procedure getCPUTime; asm rdtsc mov CPUTime.w1,eax mov CPUTime.w2,edx end;
Alexx999 Автор
14.02.2022 00:25+2Команда не спит, команда просто читает время. В случае с Windows такое доступно при помощи QueryPerformanceCounter
third112
14.02.2022 01:15-2Извините, не понял, чем мое решение хуже Вашего? (В случае с Windows asm доступен).
Alexx999 Автор
14.02.2022 01:58+3Тем что это решение не решает проблему вызова функции раз в 1 мс а просто получает текущее значение счетчика циклов процессора, поведение которого зависит от конкретной модели процессора.
А вот задача "измерить точное время" это совершенно иная история и по состоянию на сегодня она уже успешно решается стандартными библиотеками - в C# это
Stopwatch.StartNew()
, в случае с C++ этоstd::chrono::steady_clock::now()
, а для C (и других языков) даже без ASM в Win32 доступенQueryPerformanceCounter
progman78
14.02.2022 13:15-1Тем что это решение не решает проблему вызова функции раз в 1 мс а просто получает текущее значение счетчика циклов процессора, поведение которого зависит от конкретной модели процессора.
создали поток, в нем через RDTSC ( она кстати не на всех CPU у меня работала ) постоянно читаем время. Если прошло х мс - дергаем каллбэк функцию.
Минус - придется как то шаманить чтобы поток не занимал ядро на 100%. Сходу ничего придумать не могу пока. NOP`ами если только цикл забивать... но не думаю что сработат.
Alexx999 Автор
14.02.2022 14:20+4Это в статье по ссылке в начале есть - вариант "while цикл", и, собственно, вся сложность и состоит в том чтобы поток не занимал ядро на 100%
progman78
15.02.2022 06:43RDTSC - 10 тактов. если мы в цикле постоянно измеряем время и хотим загрузку проца <1% логично что нам нужна
задержкаcpu pause на 990 тактовREP NOP
NOP 3 такта. REP 1 такт. итого 4 такта. 250 инструкций вставляем после RDTSC. По идее дожно сработать.
Или использовать PAUSE инструкцию что по сути одно и то же.
progman78
15.02.2022 12:11У меня получилось "запаузить" cpu в варианте "while цикл" :
uint64_t clockCycleCountPerSecond = 0; uint64_t startTimerTime = 0; uint64_t rdtsc() { uint64_t c; __asm { rdtsc mov dword ptr[c + 0], eax mov dword ptr[c + 4], edx } return c; } void init() { uint64_t t1 = rdtsc(); Sleep(1000); uint64_t t2 = rdtsc(); clockCycleCountPerSecond = (t2 - t1) / 1000; } int main() { init(); new boost::thread( boost::bind( &timerThread ) ); printf("Enter character: "); getchar(); } void timerThread() { startTimerTime = rdtsc(); for (;;) { if ( rdtsc_2_ms() >= 1 ) { oneMsTimerFunc(); startTimerTime = rdtsc(); } cpu_sleep(); } return; } void oneMsTimerFunc() { std::cout << "oneMsTimerFunc\tms=" << rdtsc_2_ms()<< "\n"; }
функцию cpu_sleep() не привожу так как она занимает 16 килобайт кода забитые __asm NOP. При меньшем количестве nop загрузка CPU значительная. Ниже 12% загрузки CPU опустить не получилось.
как оно себя на одноядерной машине вести будет не знаю - у меня такого раритета нет. Скорее всего будет жутко лагать или винда будет все равно переключать потоки и 1мс тайминг добиться не получится совсем.
Alexx999 Автор
15.02.2022 15:17+1Собственно проблема в том что пока ядро исполняет нопы - оно не исполняет ничего полезного (а могло бы), поэтому уступить квант времени - априори оптимальнее.
Далее, если уж писать на плюсах то всю работу с
rdtsc
можно заменить на вызовыstd::chrono::steady_clock::now()
(номинальноstd::chrono::high_resolution_clock::now()
, но учитывая "нюансы с реализациями"steady_clock
будет более переносимым решением).Ну и, наверное, стоит уточнить что вся суть моей статьи - это как сделать таймер на 1мс не скатываясь до цикла с нопами. Цикл с нопами работает, и он у меня в коде тоже есть, но цель - крутить нопы только в случае крайней необходимости.
progman78
15.02.2022 15:37Собственно проблема в том что пока ядро исполняет нопы - оно не исполняет ничего полезного
ну когда у нас ядра растут как на дрожжах это уже не является проблемой. У меня половина ядер ничего полезного не исполняет вот совсем. Это проблема на машине с 2мя, 4мя ядрами. Когда ядер 16 24 или все 32 то это не проблема КМК.
Ну и, наверное, стоит уточнить что вся суть моей статьи - это как сделать таймер на 1мс не скатываясь до цикла с нопами.
ну использовать недокументироыванные функции это тоже не решение. Завтра выйдет обновление и досвидос.
цитирую вас:
Исходя из этого я буду строить своё решение вокруг трех недокументированных функций API Win32:
NtQueryTimerResolution
,NtSetTimerResolution
,NtDelayExecution
.поэтому правильное решение все таки только через драйвер идти.
Далее, если уж писать на плюсах то всю работу с
rdtsc
можно заменить на вызовыstd::chrono::steady_clock::now()
так то вы правы. Но что у меня под капотом в
rdtsc -
я знаю, и знаю что 10 тактов занимает вызов. а что там они нахимичилит вstd::chrono::steady_clock::now() -
не знаю ( вернее знаю и мне не нравится что там под капотом )Alexx999 Автор
15.02.2022 17:14ну когда у нас ядра растут как на дрожжах это уже не является проблемой
В этом и вся суть - то что для вас не является проблемой, для нас является. Сценарии есть разные, я же не заставляю везде использовать предложенный таймер :)
К примеру, наш продукт состоит кучи процессов-сервисов большинство из которых содержит таймер, обычно по несколько процессов на машине, в вырожденных случаях 10 и более. Ну а машины - не наши, есть и домашние пользователи с 4 ядрами и даже двухьядерники попадаются на вспомогательных машинах.
И как раз недавно в таймере был баг который приводил к тому что он крутил очень много нопов - в итоге машину только с кнопки можно было перезагрузить, глухое зависание
Предложенный таймер прекрасно справлялся с задачей на том же железе пока я туда не влез и не поломал, ни до ни после жалоб не было.ну использовать недокументироыванные функции это тоже не решение. Завтра выйдет обновление и досвидос.
Тут стоит понимать что "недокументированные функции" тоже бывают разными.
NtDelayExecution
существует с доисторических времен (не удивлюсь если аналог был ещё на OS/2), это KeDelayExecutionThread, другие две функции добавлены в Windows NT 3.5 в 94м году и уже тоже документированы на уровне ядра: ExQueryTimerResolution и ExSetTimerResolution. В целом, я готов поспорить что они никуда не собираются в ближайшем будущем.поэтому правильное решение все таки только через драйвер идти.
Да, это так. Зря что у меня нету сертификата которым можно этот драйвер подписать :)
и знаю что 10 тактов занимает вызов
Будем честны - не всё так просто
вернее знаю и мне не нравится что там под капотом
В случае с Windows и MSVC это будет
QueryPerformanceCounter
, который, в случае со свежими версиями Windows и процессором где есть Invariant TSC, будет использоватьrdtsc
(если хочется проверить лично - смотреть внутрь функцииRtlQueryPerformanceCounter
).
В случае с Linux логично предположить что будет использоватьсяclock_gettime
с параметромCLOCK_MONOTONIC
который, совершенно внезапно, точно так же используетrdtsc
в случае если процессор поддерживает Invariant TSC.progman78
16.02.2022 07:07К примеру, наш продукт состоит кучи процессов-сервисов большинство из которых содержит таймер, обычно по несколько процессов на машине, в вырожденных случаях 10 и более. Ну а машины - не наши, есть и домашние пользователи с 4 ядрами и даже двухьядерники попадаются на вспомогательных машинах.
драйвер делайте и не придется чесать левую пятку через правое плечо :).
Тут стоит понимать что "недокументированные функции" тоже бывают разными.
да. Но на то они и недокументированные что ничего не гарантируют. Она 30 лет может в системе быть а потом возьмут и в новой сборке ее уберут или параметры изменят.
В случае с Windows и MSVC это будет
QueryPerformanceCounter
так то да и если покопаться в исходниках это все нарыть можно. Но там обертка на обертке и есть преобразования. А с вызовом rdtsc напрмую все понятно - из проца читаются в EAX/EDX такты и все. Я понимаю что
QueryPerformanceCounter
по феншую вызывать но блин мне rdtsc больше по душе.
third112
14.02.2022 13:59-1Как написано в статье Performance measurements with RDTSC:
You should not convert your results to seconds. Report them in clock cycles.
From user's point of view, execution time in seconds makes more sense than clock cycles. But remember that:
time_in_seconds = number_of_clock_cycles / frequencyОтсюда тривиальное решение:
Задаем time_in_seconds = 0.001
и считаем сколько number_of_clock_cycles должно пройти,
как пройдет, так okА переводить каждый раз во время не надо.
Sap_ru
13.02.2022 20:36+2Мультимедиа таймеры, нет?
alex_dow
13.02.2022 23:46Согласен, как минимум миди кривое на винде, по сравнению с железными секвенсорами, стробит куда хочет то вперед, то назад, правда далеко не для всякой музыки нужен хороший миди тайминг
NTDLL
14.02.2022 01:09В случае с миди решающее влияние оказывают задержки при передаче управления в 16-разрядный код и обратно. Миди подсистема присутствует в современных виндах только потому, что она осталась со времён 16-разрядных винд.
Melanchall
14.02.2022 10:41А почему вы считаете, что подсистема MIDI должна отсутствовать в Windows или любой другой ОС? Она присутствует, потому что MIDI – протокол, активно используемый в музыкальном мире. Введённый в начале 80-х, он живее всех живых. Более того, 2 года назад был выкачен MIDI 2.0 (который крайне медленно набирает обороты, но это другая история).
MIDI API есть в Windows, в macOS, и очень хорошо, что он есть. В Windows есть новый API, но у него свои болячки.
alex_dow
14.02.2022 11:03Да, использование конвертера юсб-миди - это работает, но это не про хороший тайминг миди. Правда я не совсем понимаю, имеет ли отношение (и может ли иметь) тема статьи к миди?
Alexx999 Автор
14.02.2022 14:51+1Ну вот первоначальная статья про 1мс тайминг посвящена именно проблемам с MIDI - насколько я понял, в Windows при выводе MIDI нет нормальной буферизации и нужно софтварно реализовать тайминг.
NTDLL
14.02.2022 15:12Насколько я знаю, MIDI в винде был нужен, чтобы воспроизводить системные звуки. А сейчас он ни к чему, есть формат wav. Но, по непонятным мне причинам, MIDI подсистема осталась.
Melanchall
14.02.2022 18:09Вы путаете MIDI и WAV. MIDI никак не связан с продуцированием звука, это протокол общения между, например, компьютером и синтезатором. MIDI активно используется, как средство передачи партий в музыкальной среде. Например, можно записать партию ударных в MIDI, и вывести этот MIDI разными сэмплерами, получая уже как раз WAV. К системным звукам Windows протокол отношения не имеет. Сама подсистема в ОС нужна, и хорошо, что она существует.
Alexx999 Автор
14.02.2022 00:28+1Пробовали, у них большой разброс - иногда таймер опаздывает, иногда спешит, иногда вообще не работает. Собственно мне 1мс интервал не нужен (просто оказалось что мой таймер так может, в продакшне у нас не применяется), таймер был написан именно из-за проблем с мультимедиа таймером.
Dmitriy_Volkov
14.02.2022 02:44+2Лет 20 назад делал эксперименты и писал vxd драйвера. В частности программировал таймер и обрабатывал прерывания. Так вот, меняя делитель, настроить частоту проблемы нет. Для контроля менял состояние выходов LPT порта и проверял при помощи осцилографа. Частоту держало, но периодически (предполагаю при свопе на диск) всё плыло. Реже на такую же проблему наблюдал даже на QNX 4.25, если на десктопном варианте с Фотоном. Хотя система реального времени. На одноплатниках с чистым QNX проблем вроде не было.
Поэтому стоит разделять:
возможности самого таймера,
летенси обработки прерываний таймера и летенси добавляемый OS,
доступ к настройкам таймера в пользовательском API (на уровне драйверов он ограничен лишь самим аппаратным таймером)
Alexx999 Автор
14.02.2022 03:16+1В целом это решение на жесткое реальное время не претендует - в случае повышенной нагрузки на систему оно уплывёт. У меня есть вариант посложнее с комленсациями но всё-таки Windows - не RTOS, а C# так и подавно, приходит Garbage Collection и можно гасить свет.
vadimr
14.02.2022 13:44Вы продолжительность промежутка для контроля своего метода меряете тем же инструментом, которым этот промежуток формируете. Так не пойдёт. Просим осциллограф.
napa3um
14.02.2022 14:31Пришёл написать этот комментарий. Да, гладкость графиков - это показатель упорядоченности квантов исполнения, а не их реальной равномерности в физическом времени. Нужно замерять внешним инсртументом, и очень может оказаться, что ограничили точность таймеров разработчики винды вовсе не из жадности (и не из жадности всякие конторы торгуют реалтаймовыми ядрами осей) :).
Alexx999 Автор
14.02.2022 15:01Разница в том что реалтаймовое ядро дает гарантии а тут... "как получится".
Измерить извне я могу попробовать но мне банально некуда воткнуть щуп осцилографа - нужна железка, быстрая, без буфера, без регистрации и без смс. Разве что взять USB-COM адаптер и попробовать на нём сгенерировать 500Гц меандр и посмотреть что с этого получится
vadimr
14.02.2022 15:05Ну да, хотя бы меандром 500 Гц. Я практически уверен, что там будут пропущенные периоды в изрядном количестве.
Alexx999 Автор
14.02.2022 14:34+1Замечание верное, но, откровенно говоря, для промежутков в 1мс (или даже 0.1мс) у современных машин на Windows средства измерения с огромнейшим избытком - измерить (и проконтролировать) можно, а вот уснуть на малый промежуток - нечем. Собственно, к решению задачи изначально подход с предположением что хардварный счетчик который используется ОС имеет запас в несколько порядков, конкретно на моей машине Windows репортит точность в 100ns и дальше исходим из доверия к этому счетчику.
vadimr
14.02.2022 14:58+1Пока значение хардварного счётчика дойдёт до обработки в вашей программе, много времени может пройти. Вы никак этого не проверите и не учтёте при такой методике. Вы меряете интервал между двумя событиями в вашем потоке исполнения.
Xadok
14.02.2022 14:36Не совсем понял, будет ли это рабочим решением в win 7 или только сильно позднее?
Alexx999 Автор
14.02.2022 14:56У нас
вроде какработает уже пару лет, у клиентов есть Win7 машины. Но конкретно таймер в 1мс без гарантий - у нас этот же код крутит 10мс таймер повышенной точности.Xadok
14.02.2022 15:0810 и 15 не столь большая разница все-таки, а вот если выставить 1мс локально, а окажется, что оно глобально утекло…
segment
А если появился новый таймер, как правильно в таком случае сделать останов текущего таймера и перерасчет? Или какое применение у такого таймера?
Alexx999 Автор
Я постарался урезать код до минимума, как показывает практика читать длинные простыни не слишком интересно.
Для этого есть alertable wait states, при добавлении нового таймера можно будить поток. Плюс, можно ограничить максимальную длительность сна - даже если всегда спать 0.5мс это не сильно убьет нагрузку на процессор. Ну или ваш вариант :)
nckma
А будет ли работать такой "таймер":
1) открываем файл COM порт на скорости передачи 19200bps в режиме FILE_FLAG_OVERLAPPED
2) записываем в файл девайса порта 2 байта
3) ждем completition Event из структуры OVERLAPPED переданной в WriteFile(..)
При скорости 19200 один бит передается за 0,000052083 секунды, но там байт и еще старт бит и стоп бит, итого 2 байта передается 0,00052083*2=0,00104166 секунды.
Аппаратное прерывание последовательного порта через драйвер пробуждает поток, который ждет эвента. Все.
Alexx999 Автор
Далеко не у всех нынче есть COM порт - у меня нет :)
Но, кстати говоря, есть у меня один pet проект (даталоггер для блока управления двигателем) где используется COM через USB - когда в следующий раз буду ковыряться в нём с осцилографом то попробую.