Чтобы разрешить выполнение инструкций SSE без генерации #UD, нам нужно изменить регистры CR0 и CR4 "Очистить бит CR0.EM (бит 2) [CR0 &= ~(1 << 2)]" "Установить бит CR0.MP (бит 1) [CR0 |= (1 << 1)]" "Установить бит CR4.OSFXSR (бит 9) [CR4 |= (1 << 9)]" "Установить бит CR4.OSXMMEXCPT (бит 10) [CR4 |= (1 << 10)]" Это правильный подход? Код (C++): VOID EnableSSE() { ULONG64 cr0, cr4; cr0 = __readcr0(); cr0 &= ~(1ull << 2); cr0 |= (1ull << 1); __writecr0(cr0); cr4 = __readcr4(); cr4 |= (3ull << 9); __writecr4(cr4); } Большое спасибо
Если речь о самописном ядре - всё норм, но учитывай, что код выше нужно вызывать на всех процессорах, т.к. у каждого логического процессора (ядра) свои собственные CR'ы. Если твой планировщик переключит поток на другое ядро, где SSE/AVX выключены - вылетит #UD. Но если речь о виндовом ядре, там SSE уже включен в ядре, и компилятор для 64х-битных ядер создаёт SSE'шный код в драйверах - ничего дополнительно включать не надо. AVX тоже можно вызывать, но только если предварительно сохранить AVX'овый контекст функцией KeSaveExtendedProcessorState, т.к. ядро предполагает, что AVX'овый контекст в ядре портиться не будет - и, если не сохранить его, поломаешь AVX'овый контекст в своём процессе при выходе из ядра. В линуксовом ядре тоже можно вызывать SSE/AVX, ничего дополнительно не включая, но там ядро не сохраняет для потока даже SSE'шный контекст, а код генерируется без использования SSE - поэтому перед ручным использованием SSE/AVX надо обернуть такой код в kernel_fpu_begin()/kernel_fpu_end() и убедиться, что данные выровнены на размер используемых регистров.
Код (C++): // Структура для хранения области FXSAVE __declspec(align(16)) typedef struct { unsigned char fxsave_area[512]; } FX_SAVE_AREA; Код (C++): void PerformSSEOperations() { FX_SAVE_AREA fxSaveArea; // Сохранение состояния с плавающей запятой с использованием _fxsave64 _fxsave64(&fxSaveArea); // Выполнение операций с плавающей запятой double floating = ourfloating(); нашеплавающее // Восстановление состояния с плавающей запятой с использованием _fxrstor64 _fxrstor64(&fxSaveArea); // Повторное включение SSE, если требуется EnableSSE(); } Это работает потрясающе на высоком уровне IRQL, так как максимальный уровень для KeSaveExtendedProcessorState - это DISPATCH_LEVEL
А тебе не надо сохранять состояние для SSE, оно уже сохранено. Можешь смело использовать флоаты и даблы на любом IRQL без предварительной подготовки. Сохранять состояние надо только в любом из следующих случаев: 1. Ты пишешь 32х-битный драйвер и используешь любые расширения для дробных чисел (FPU/MMX/SSE/AVX). 2. Ты пишешь 64х-битный драйвер и используешь расширения FPU или AVX. Для FPU нужно вызывать KeSaveFloatingPointState, для MMX, SSE и AVX - KeSaveExtendedProcessorState (или сохранять контекст вручную - например, как у тебя выше через FXSAVE). FPU при сборке 64х-битного кода не используется никогда. AVX включать нельзя (так как есть шанс, что компилятор сгенерирует AVX-инструкции до того, как ты сохранишь AVX-контекст). А MMX и SSE использовать можно всегда и в любое время на любом IRQL без сохранения контекста (справедливо только для x64). По умолчанию при сборке 64х-битных драйверов включена поддержка SSE: твой драйвер УЖЕ использует SSE во всяких memcpy/memset или для инициализации больших переменных/массивов, ничего не сохраняя - нет смысла впустую сжигать такты на ручное сохранение SSE-контекста, когда он и без того сохранён. Нет разницы, используешь ли ты SSE для дробных вычислений или для целочисленных - регистры используются одни и те же. Об этом написано и в документации:
на то есть опции компиля + [x]fence https://hadibrais.wordpress.com/2018/05/14/the-significance-of-the-x86-lfence-instruction/ хотя out-of-order execution всегда может явить сюрприз и фенсы не помогут
Дак ровно тем, что заставляют процессор выполнить чтения/записи до того как выполнять следующие после *FENCE чтения/записи. Если ты про то, что это не защищает память с WC из статьи, то это не совсем проблема - это специфика памяти помеченной таким образом. Она идеологически как бы несовместима с классическим кеширование и всеми сопутствующими методиками синхронизации обычной памяти и поэтому с ней такое "наплевательское" отношение со стороны шедулера мюопов в процессорах. Типичное применение WC - VRAM куда ЦП заливает текстуру и не собирается её читать - большой поток данных буферизируется (write combine) и как только так сразу отправляется полной порцией по шине. Там какие то свои методы должны быть для гарантии, что вся текстура залилась. За эту память не должны конкурировать более двух участников - заливающий текстуру драйвер и собственно видеочип.
фенсы никак не могут помочь 30е, пч отключают его частично либо напрочь. есть оптимизация 3ое чрез перестановку и/ль замену команд, но в глобальном случае такой подход бесполезен в силу большой нестабильности результата даже на той же самой машине.
Я даже догадался, что 3o это OoO - Out of Order, не распарсил только "пч". Фенсы нужны именно для того, чтобы OoO не мешал коду проводить прогнозируемые результаты в межпроцессорном взаимодействии, когда нужно точно быть уверенным в том в каком порядке что-то считывается или записывается. У интелей они довольно поздно введены потому что до них такое разруливалось через префикс LOCK - он тоже не позволяет OoO менять порядок всего что выше и ниже, так и жили. Но вот в какой то момент решили, что надо равняться на другие процессоры где они есть в явном виде.