В текущем потоке установлен максимальный приоритет (SetThreadPriority(THREAD_PRIORITY_TIME_CRITICAL)). Я же правильно понимаю, что приоритет процесса в данном случае неважен, если он не REALTIME_PRIORITY_CLASS (а такой можно установить только из-под админа)? В определённый момент мне нужно сделать так, чтобы последующий код выполнялся некоторое непродолжительное время без переключения контекста. На ум приходит 2 варианта: SwitchToThread и Sleep(0). Что лучше и почему? В разных источниках вижу противоречивую инфу: где-то пишут, что SwitchToThread не переключает контекст на потоки с более низким приоритетом, в отличие от Sleep(0), где-то пишут, что наоборот. В MSDN инфа об этом есть только в описании Sleep в контексте конкретно XP. Есть ли ещё какая-либо разница между этими 2-мя функциями (Sleep интересует именно с 0)? И ещё вопрос: есть ли какие-либо ситуации (и какие), когда винда может переключить исполнение с текущего потока на поток с более низким приоритетом (на том же ядре), если текущий поток вообще не обращается к ядру или если его не остановили или не понизили его приоритет?
Sleep(0) и SwithToThread делают абсолютно одно и то же. Sleep сводится к NtDelayExecution, SwitchToThread - к NtYieldExecution, который, в свою очередь, сводится к KeYieldExecution(0). NtDelayExecution сводится к KeDelayExecutionThread, внутри которого есть проверка, задан ли нулевой интервал. Если интервал нулевой - вызывается KeYieldExecution(0). Итого, Sleep(0) и SwitchToThread сводятся к KeYieldExecution(0), только Sleep(0) приходит к ней чуть более длинным путём с доп. проверкой, поэтому оптимальнее вызывать SwitchToThread. NtYieldExecution: KeDelayExecutionThread: --- Сообщение объединено, 21 июн 2020 --- Планировщик переключает потоки, независимо от того, переходят они в ядро или нет, поэтому потоки с низким приоритетом всё равно будут выполняться (кроме случаев, когда у твоего процесса и потока реалтаймовый приоритет). Однако, твой поток по-прежнему могут прервать из ядра, т.к. с реалтаймовым приоритетом он продолжает работать на PASSIVE_LEVEL. --- Сообщение объединено, 21 июн 2020 --- Хотя начал подробнее смотреть и засомневался, попадём ли мы вообще в блок с KeYieldExecution(0)... Ведь, похоже, самое первое условие не выполнится - поток не Alertable, в Interval ноль, в WaitMode будет UserMode (1). Поторопился с ответом...
Jin X, Если установить реалтайм приоритет, получить число переключений контекста(SYSTEM_THREAD_INFORMATION.ContextSwitches), затем покрутить цикл в 10 секунд и вновь запросить число переключений - будет всего несколько, которые были при вызове сервиса до смены приоритета. Если в цикл вставить ZwYieldExecution возвращается ~73k, те 7k/sec переключений. При нормальном приоритете переключений ~100/sec. Тоесть сервис делает то что должен - принудительно переключает контекст, отдаёт время потока.
Нууу... если посмотреть на Sleep, то там как-то всё замороченнее. И судя по описанию, там вроде бы ещё должна быть проверка приоритета (правда, не на всех виндах). Как я понял из этого документа, потоки с более низким приоритетом выполняются, только если более приоритетные потоки ждут (к примеру, GetMessage, Sleep, WaitFor...), в противном случае, скажем, если у меня просто цикл каких-то вычислений, менее приоритетные будут тупо ожидать. Разве не так? Хорошо, а после возврата у меня же будет обновлённый квант, так ведь? Т.е. пару-тройку десятков миллисек я могу делать всё, что мне надо (если, конечно, не появится более приоритетный поток). Ну или... если более приоритетный поток не выйдет из спячки (те же WaitFor...).
Jin X, > Т.е. пару-тройку десятков миллисек я могу делать всё, что мне надо Принудительно отдать квант что бы получить начало следующего ? Зачем это нужно, какая задача ?
Jin X, Тоесть профайл в виде значения снять ? Обычно блокировки переключений не нужны, выполняется некоторая операция в цикле. Зная число итераций и время пустого цикла можно посчитать время операции. Обнаружить начало кванта просто в x64, планировщик фиксит gs.
А если будут переключения контекста? Расскажу подробнее. Решил я сделать тест для определения размера линии кэша. Но не через cpuid (это всё есть), а через rdtsc. Цель – будем считать, что по приколу. Понятное дело, что [почти] всегда должно выходить 64, но суть не в этом, а в том, что хочется сделать так, чтобы прога работала на разных процессорах. Будем проверять размеры 128, 64, 32 байт (меньше, думаю, смысла нет, больше тоже). Технология такая (делал в VS, конфиг Release x86): 1. Выделяю кусок памяти (1 Мб или 100 Мб, чтобы не попал в L3), создаю доп. массив с индексами/смещениями элементов с шагом 256 байт. Этот массив перемешиваю (чтоб затруднить процу предсказание обращений к памяти с предварительной загрузкой данных в кэш). Без этого (проверено) вообще не работает. 2. Выставляю потоку приоритет TIME_CRITICAL. 3. Далее два вложенный цикла: первый step = 128, 64, 32; второй – перебор элементов массива смещений (i). 4. В каждом цикле делаю: SwitchToThread, далее замеряю холостой цикл, замеряю чтение из куска памяти dword'а по смещению i, замеряю чтение из куска памяти dword'а по смещению (i+step-4). Вычитаю из 2-х последних холостой цикл. Замер через mfence/rdtsc/mfence. Выравнивание адреса i по step делается. 5. Суммирую все значения по всем внутренним итерациям. 6. Во внешнем цикле: если разница между первыми и вторыми чтениями хотя бы раза в 4, значит размер линии кэша = step, выходим, иначе goto 4. Результаты: • На Intel Sandy Bridge (десктоп) работает (например, показывает 239/206 тактов на цикл для step=128 (иногда, правда, раза в 1,5 различаются значени), 221/18 тактов на цикл для step=64). • На AMD Ryzen 5 2600U (ноут) показывает 128 байт. Причём, если расширить тест до step=1024, например, тест показывает размер 256 байт (причём, там значения 297/9, например). Почему так? Хотя, если выделять не 100 Мб, а 1 Мб, вторые значения на step=256 и 128 больше (267/27, к примеру), чем на step=64 (277/2). Бывает ещё и такое: • Если выделять 100 Мб, а не 1 Мб, то первые значения (первое чтение из 2-х) в половине тестов (step'ов) будут в 2-3 (а иногда и раз в 5) раза ниже, чем при других step'ах. На 1 Мб такого эффекта не наблюдается. • Вариант на асме (асм-вставки п.3-6, описанных выше) работает так же в конфиге Debug, но в Release скорость первого чтения для самого старшего step (1024 или 128 – неважно, первая внешняя итерация) больше раз в 10. Причём, как на Intel, так и на AMD. Но по этому поводу я ещё посмотрю, может, где-то баг. Но почему тогда в Debug бага нет? Но самый главный вопрос – почему на AMD чтение второго значения с шагом 252 байта такое быстрое, как будто линия кэша = 256 байтам?
Jin X, > А если будут переключения контекста? В твоём случае это важно. Тут были тесты melt&spectre, твой замер и есть эта атака. Так вот проблема не в самой ошибке вводимой планированием была, а в сбросе всех частей механизма трансляции, когда планировщик свопит поток, то загружает cr3, а это сбрасывает все кэши. Поэтому при включённом планировании ничего толком подобного(статистику по кэшам) на нт замерить не получится. Тогда наконец понятно для чего тебе отключать планировщик --- Сообщение объединено, 22 июн 2020 --- Jin X, Можно обнаружить планирование на 64, это связано с ошибками compat mode https://wasm.in/threads/access-violation-pri-vozvrate-iz-64-bitnogo-rezhima.33604/#post-415896 Механизм фикса сегментов неизменен XP%10: Код (Text): ; If the user segment selectors have been changed, then reload them with ; their cannonical values. ; ; N.B. The following code depends on the values defined in ntamd64.w that ; can be loaded in ds, es, fs, and gs. In particular an "and" operation ; is used for the below comparison. ; mov eax, ds ; compute sum of segment selectors mov ecx, es ; and eax, ecx ; mov ecx, gs ; and eax, ecx ; cmp ax, (KGDT64_R3_DATA or RPL_MASK) ; check if sum matches je short KiSC25 ; if e, sum matches expected value mov ecx, KGDT64_R3_DATA or RPL_MASK ; reload user segment selectors mov ds, ecx ; mov es, ecx ; ; ; N.B. The following reload of the GS selector destroys the system MSR_GS_BASE ; register. Thus this sequence must be done with interrupt off. ; mov eax, (PcSelf - PcPrcb)[rbx] ; get current PCR address mov edx, (PcSelf - PcPrcb + 4)[rbx] ; cli ; disable interrupts mov gs, ecx ; reload GS segment selector mov ecx, MSR_GS_BASE ; get GS base MSR number wrmsr ; write system PCR base address sti ; enable interrupts - wrk Хороший способ.