это правильный подход? Код (C++): class TSingleProcessorMode { KDPC DpcTraps[MAXIMUM_PROCESSORS]; volatile LONGLONG Stall; unsigned __int64 cr9; KPRIORITY SavedPriority; int CpuCount; static void DpcRoutine(KDPC* pDpc, void* pContext, void* pArg1, void* pArg2); public: void Initialize(); void Enter(); void Exit(); }; void TSingleProcessorMode::Initialize() { RtlZeroMemory(this, sizeof(TSingleProcessorMode)); CpuCount = (int)KeQueryActiveProcessorCount(nullptr); if (CpuCount > 1) { for (int i = 0; i < CpuCount; i++) { KeInitializeDpc(&DpcTraps[i], DpcRoutine, this); KeSetImportanceDpc(&DpcTraps[i], LowImportance); KeSetTargetProcessorDpc(&DpcTraps[i], i); } } } void TSingleProcessorMode::DpcRoutine(KDPC* pDpc, void* pContext, void* pArg1, void* pArg2) { TSingleProcessorMode* pThis = (TSingleProcessorMode*)pContext; auto cr8 = __readcr8(); __writecr8(HIGH_LEVEL); _disable(); InterlockedDecrement64(&pThis->Stall); do _mm_pause(); while (pThis->Stall > 0); _enable(); __writecr8(cr8); } void TSingleProcessorMode::Enter() { cr9 = __readcr8(); if (CpuCount > 1) { SavedPriority = KeSetPriorityThread(KeGetCurrentThread(), HIGH_PRIORITY - 1); KAFFINITY ActiveProcessors = KeQueryActiveProcessors(); __writecr8(DISPATCH_LEVEL); ULONG CurrentProcessor = (ULONG)KeGetCurrentProcessorNumber(); Stall = 1; for (int i = CpuCount - 1; i >= 0; i--) { if (i != CurrentProcessor && (ActiveProcessors & (1ull << i)) != 0) { InterlockedIncrement64(&Stall); KeInsertQueueDpc(&DpcTraps[i], 0, 0); } } __writecr8(cr9); while (InterlockedAdd64(&Stall, -1) > 0) { _mm_pause(); } __writecr8(HIGH_LEVEL); _disable(); } else { __writecr8(HIGH_LEVEL); _disable(); } } void TSingleProcessorMode::Exit() { if (CpuCount > 1) { InterlockedExchange64(&Stall, -1); _enable(); __writecr8(cr9); KeSetPriorityThread(KeGetCurrentThread(), SavedPriority); } else { _enable(); __writecr8(cr9); } } Проблема в том, что чтение происходит на ядре 6, а не на ядре 0.
&DpcTraps[[index]] а не &DpcTraps ? линии 23-26. Иначе получается что атрибуты ставятся только на первый элемент массива
Да, я исправил ошибку. Теперь, как остановить все процессоры и поставить в очередь только на нулевом ядре
Можешь сделать KeSetSystemAffinityThreadEx своему потоку, назначив его на нужное тебе ядро (на нулевое), после этого всем другим ядрам отправляешь DPC со спинлоком. В этом случае текущий поток гарантированно будет выполняться на нужном тебе ядре, а все другие потоки будут ждать. И насчёт прерываний, поднимать IRQL до HIGH_LEVEL или отключать прерывания не требуется, т.к. на DISPATCH_LEVEL планировщик уже не работает. А если надо отключить все прерывания, то cli достаточно, поднимать в cr8 не требуется, т.к. cli равнозначен поднятию до HIGH_LEVEL, и прервать его могут только NMI и SMI.
Я нашел еще один хороший способ чтения в ядре 0 Замените 6 на ваше максимальное количество процессоров Код (C++): const ULONG numberOfProcessors = KeQueryActiveProcessorCountEx(ALL_PROCESSOR_GROUPS); PROCESSOR_NUMBER processorNumber; NTSTATUS Status = KeGetProcessorNumberFromIndex(numberOfProcessors - 6, &processorNumber); if (!NT_SUCCESS(Status)) { return; } processorNumber.Group; processorNumber.Number; Status = ZwSetInformationThread(ZwCurrentThread(), ThreadIdealProcessorEx, &processorNumber, sizeof(processorNumber)); if (!NT_SUCCESS(Status)) return; KAFFINITY ProcessAffinity = (KAFFINITY)(1ull << processorNumber.Number); Status = ZwSetInformationProcess(ZwCurrentProcess(), ProcessAffinityMask, &ProcessAffinity, sizeof(ProcessAffinity)); if (!NT_SUCCESS(Status)) return; KAFFINITY ThreadAffinity = (KAFFINITY)(1ull << processorNumber.Number); Status = ZwSetInformationThread(ZwCurrentThread(), ThreadAffinityMask, &ThreadAffinity, sizeof(ThreadAffinity)); if (!NT_SUCCESS(Status)) return;
А это то же самое: внутри ZwSetInformationThread(ThreadAffinityMask) сведётся к KeSetSystemAffinityThreadEx.
Извините за вопрос, может ли эта рутина также работать для чтения ядра 0? Спасибо Код (C++): volatile LONG Barrier = 1; ULONG_PTR BroadcastFunction(ULONG_PTR Context) { volatile LONG* barrier = (volatile LONG*)Context; ULONG currentCpu = KeGetCurrentProcessorNumber(); if (currentCpu == 0) { // CPU 0 выполняет свою работу DbgPrint("CPU %u выполняет свою работу\n", currentCpu); InterlockedExchange(barrier, 0); } else { while (InterlockedCompareExchange(barrier, 0, 0) != 0) { _mm_pause(); // Используйте _mm_pause для энергосберегающего ожидания } } return 0; } VOID CallIpiForCpu0() { InterlockedExchange(&Barrier, 1); KeIpiGenericCall(BroadcastFunction, (ULONG_PTR)&Barrier); }
Может, но учитывай, что ты будешь выполняться на очень высоком IRQL - на IPI_LEVEL, а значит, нельзя безопасно работать с Paged-памятью, поскольку она может быть выгружена в своп, а на IRQL >= DISPATCH_LEVEL подгрузка страниц из свопа не работает.
фиксированный Код (C++): volatile LONG64 g_lock = 1; ULONG_PTR broadcast_function(ULONG_PTR context) { UNREFERENCED_PARAMETER(Context); ULONG currentProcessor = KeGetCurrentProcessorNumber(); if (currentProcessor != 0) { DbgPrint("CPU %u ждет барьер\n", currentProcessor); // Ожидание, пока барьер не будет снят while (InterlockedCompareExchange64(&g_lock, 1, 1) == 1) { _mm_pause(); } DbgPrint("CPU %u освободился от барьера\n", currentProcessor); } else { DbgPrint("CPU %u выполняет свою работу\n", currentProcessor); // Выполнение необходимой операции LARGE_INTEGER currentTime; KeQuerySystemTime(¤tTime); DbgPrint("CPU %u текущее время: %lld\n", currentProcessor, currentTime.QuadPart); // Снятие барьера путем установки блокировки в 0 InterlockedExchange64(&g_lock, 0); DbgPrint("CPU %u установил барьер в 0\n", currentProcessor); } return 0; } Код (C++): VOID PerformBroadcast() { InterlockedExchange64(&g_lock, 1); KeIpiGenericCall(broadcast_function, 0); }
А зачем тебе это? Похоже, ты пытаешься сделать какую-то простую вещь очень странными способами. IPI, конечно, работает, но для чего тебе вообще понадобилось привязываться к конкретному ядру и почему не использовать KeSetSystemAffinityThreadEx или на крайний случай отправку DPC нужному ядру через KeInitializeDpc/KeSetImportanceDpc/KeSetTargetProcessorDpc/KeInsertQueueDpc? Останавливать все ядра, пока на одном из них выполняется какая-то работа (как и вообще делать работу на IPI_LEVEL) - очень сомнительное решение.
Использование KeSetSystemAffinityThreadEx можно только для установки аффинности текущего потока. Но я хочу установить аффинность произвольного потока и заблокировать большинство процессоров, чтобы оставить активным только один. Именно поэтому я использую KeIpiGenericCall. KeIpiGenericCall позволяет мне послать IPI (Inter-Processor Interrupt) всем процессорам и синхронизировать их, чтобы только один процессор выполнял нужную задачу, а остальные ждали. Это обеспечивает высокую точность и предотвращает контекстные переключения, которые могут повлиять на точность времени в моей задаче. Также это помогает избежать использования DPC на уровне IPI, который может быть не оптимален для моего случая.
Так а что ты делаешь-то? Если что, планировщик отключается на DISPATCH_LEVEL. Отправки DPC уже достаточно, чтобы эксклюзивно "забрать" нужное ядро, чтобы планировщик перестал работать, и никакой IPI уже не нужен. А насчёт точности, отправка IPI - это точно не про точность) Потому что они и вызываются в произвольном порядке, и это довольно тяжеловесная операция - дёрнуть APIC, он пошлёт прерывания, процессорам надо сохранить контекст, и пошло-поехало... В принципе, отправка DPC - это тоже прерывание, но хотя бы не затрагивает другие ядра. Вряд ли тебе действительно нужно их морозить.
проблема выни в теневых процессах - если заморозишь ядра, есть некоторая гарантия, что никакая керь не вылезет в фоне
подойти к Вопросу можно по-разному: тот же патч ядра вполне может решить проблему. но в большинстве случаев лучше юникерны использовать
Мы даже не знаем, к какому именно вопросу нам надо подходить, а уже думаем, как патчить ядро, переходить на линукс и морозить намертво процессоры) А потом окажется, что ТС что-то намудрил, и ему вообще не надо никаких привязок ни к процессорам, ни к ядрам)
Теперь я понимаю, я использую KeIpiGenericCall, и это работает великолепно. Я добавил KeMemoryBarrier для дополнительной безопасности в упорядочении памяти. Есть другой API, который можно использовать — IoConnectInterruptEx, но я слышал, что он предназначен только для PnP устройств