включить SSE в ядре

Тема в разделе "WASM.NT.KERNEL", создана пользователем zky02, 29 июн 2024.

  1. zky02

    zky02 New Member

    Публикаций:
    0
    Регистрация:
    16 янв 2024
    Сообщения:
    20
    Чтобы разрешить выполнение инструкций SSE без генерации #UD, нам нужно изменить регистры CR0 и CR4
    1. "Очистить бит CR0.EM (бит 2) [CR0 &= ~(1 << 2)]"
    2. "Установить бит CR0.MP (бит 1) [CR0 |= (1 << 1)]"
    3. "Установить бит CR4.OSFXSR (бит 9) [CR4 |= (1 << 9)]"
    4. "Установить бит CR4.OSXMMEXCPT (бит 10) [CR4 |= (1 << 10)]"
    Это правильный подход?
    Код (C++):
    1. VOID EnableSSE()
    2. {
    3.     ULONG64 cr0, cr4;
    4.     cr0 = __readcr0();
    5.  
    6.     cr0 &= ~(1ull << 2);
    7.     cr0 |= (1ull << 1);
    8.  
    9.     __writecr0(cr0);
    10.     cr4 = __readcr4();
    11.  
    12.     cr4 |= (3ull << 9);
    13.     __writecr4(cr4);
    14. }
    Большое спасибо
     
  2. HoShiMin

    HoShiMin Well-Known Member

    Публикаций:
    5
    Регистрация:
    17 дек 2016
    Сообщения:
    1.454
    Адрес:
    Россия, Нижний Новгород
    Если речь о самописном ядре - всё норм, но учитывай, что код выше нужно вызывать на всех процессорах, т.к. у каждого логического процессора (ядра) свои собственные CR'ы. Если твой планировщик переключит поток на другое ядро, где SSE/AVX выключены - вылетит #UD.

    Но если речь о виндовом ядре, там SSE уже включен в ядре, и компилятор для 64х-битных ядер создаёт SSE'шный код в драйверах - ничего дополнительно включать не надо.
    AVX тоже можно вызывать, но только если предварительно сохранить AVX'овый контекст функцией KeSaveExtendedProcessorState, т.к. ядро предполагает, что AVX'овый контекст в ядре портиться не будет - и, если не сохранить его, поломаешь AVX'овый контекст в своём процессе при выходе из ядра.

    В линуксовом ядре тоже можно вызывать SSE/AVX, ничего дополнительно не включая, но там ядро не сохраняет для потока даже SSE'шный контекст, а код генерируется без использования SSE - поэтому перед ручным использованием SSE/AVX надо обернуть такой код в kernel_fpu_begin()/kernel_fpu_end() и убедиться, что данные выровнены на размер используемых регистров.
     
    Последнее редактирование: 30 июн 2024
    zky02 и Mikl___ нравится это.
  3. zky02

    zky02 New Member

    Публикаций:
    0
    Регистрация:
    16 янв 2024
    Сообщения:
    20
    Код (C++):
    1. // Структура для хранения области FXSAVE
    2. __declspec(align(16)) typedef struct {
    3.     unsigned char fxsave_area[512];
    4. } FX_SAVE_AREA;
    Код (C++):
    1. void PerformSSEOperations() {
    2.     FX_SAVE_AREA fxSaveArea;
    3.  
    4.     // Сохранение состояния с плавающей запятой с использованием _fxsave64
    5.     _fxsave64(&fxSaveArea);
    6.  
    7.     // Выполнение операций с плавающей запятой
    8.     double floating = ourfloating(); нашеплавающее
    9.  
    10.     // Восстановление состояния с плавающей запятой с использованием _fxrstor64
    11.     _fxrstor64(&fxSaveArea);
    12.  
    13.     // Повторное включение SSE, если требуется
    14.     EnableSSE();
    15. }
    Это работает потрясающе на высоком уровне IRQL, так как максимальный уровень для KeSaveExtendedProcessorState - это DISPATCH_LEVEL
     
  4. HoShiMin

    HoShiMin Well-Known Member

    Публикаций:
    5
    Регистрация:
    17 дек 2016
    Сообщения:
    1.454
    Адрес:
    Россия, Нижний Новгород
    А тебе не надо сохранять состояние для 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 для дробных вычислений или для целочисленных - регистры используются одни и те же.

    Об этом написано и в документации:
     
    zky02 нравится это.
  5. UbIvItS

    UbIvItS Well-Known Member

    Публикаций:
    0
    Регистрация:
    5 янв 2007
    Сообщения:
    6.242
    на то есть опции компиля + [x]fence https://hadibrais.wordpress.com/2018/05/14/the-significance-of-the-x86-lfence-instruction/ хотя out-of-order execution всегда может явить сюрприз и фенсы не помогут :)
     
  6. aa_dav

    aa_dav Active Member

    Публикаций:
    0
    Регистрация:
    24 дек 2008
    Сообщения:
    457
    Инструкции *FENCE придуманы ровно для того чтобы помогать с out-of-order.
     
  7. UbIvItS

    UbIvItS Well-Known Member

    Публикаций:
    0
    Регистрация:
    5 янв 2007
    Сообщения:
    6.242
    чем именно? :)
     
  8. aa_dav

    aa_dav Active Member

    Публикаций:
    0
    Регистрация:
    24 дек 2008
    Сообщения:
    457
    Дак ровно тем, что заставляют процессор выполнить чтения/записи до того как выполнять следующие после *FENCE чтения/записи.
    Если ты про то, что это не защищает память с WC из статьи, то это не совсем проблема - это специфика памяти помеченной таким образом. Она идеологически как бы несовместима с классическим кеширование и всеми сопутствующими методиками синхронизации обычной памяти и поэтому с ней такое "наплевательское" отношение со стороны шедулера мюопов в процессорах.
    Типичное применение WC - VRAM куда ЦП заливает текстуру и не собирается её читать - большой поток данных буферизируется (write combine) и как только так сразу отправляется полной порцией по шине. Там какие то свои методы должны быть для гарантии, что вся текстура залилась. За эту память не должны конкурировать более двух участников - заливающий текстуру драйвер и собственно видеочип.
     
    Последнее редактирование: 5 июл 2024
    TermoSINteZ и Mikl___ нравится это.
  9. UbIvItS

    UbIvItS Well-Known Member

    Публикаций:
    0
    Регистрация:
    5 янв 2007
    Сообщения:
    6.242
    фенсы никак не могут помочь 30е, пч отключают его частично либо напрочь. есть оптимизация 3ое чрез перестановку и/ль замену команд, но в глобальном случае такой подход бесполезен в силу большой нестабильности результата даже на той же самой машине.
     
  10. HoShiMin

    HoShiMin Well-Known Member

    Публикаций:
    5
    Регистрация:
    17 дек 2016
    Сообщения:
    1.454
    Адрес:
    Россия, Нижний Новгород
    А что за Зоя-то?
     
  11. aa_dav

    aa_dav Active Member

    Публикаций:
    0
    Регистрация:
    24 дек 2008
    Сообщения:
    457
    Я даже догадался, что 3o это OoO - Out of Order, не распарсил только "пч".
    Фенсы нужны именно для того, чтобы OoO не мешал коду проводить прогнозируемые результаты в межпроцессорном взаимодействии, когда нужно точно быть уверенным в том в каком порядке что-то считывается или записывается.
    У интелей они довольно поздно введены потому что до них такое разруливалось через префикс LOCK - он тоже не позволяет OoO менять порядок всего что выше и ниже, так и жили. Но вот в какой то момент решили, что надо равняться на другие процессоры где они есть в явном виде.
     
  12. UbIvItS

    UbIvItS Well-Known Member

    Публикаций:
    0
    Регистрация:
    5 янв 2007
    Сообщения:
    6.242
    прально :)
    если буква повторяется энн раз - сокращают n{буква}.. удобно очень :) пч == потому что.