Что происходит с программными прерываниями в конвейере?
Прочитав это:
Когда происходит прерывание, что происходит с инструкциями в конвейере?
Существует не так много информации о том, что происходит с программными прерываниями, но мы узнаем следующее:
И наоборот, исключения, такие как ошибки страниц, помечают затронутые инструкции. Когда эта инструкция собирается принять, в этот момент все последующие инструкции после исключения сбрасываются, и выборка инструкций перенаправляется.
Мне было интересно, что произойдет с программными прерываниями (INT 0xX) в конвейере, во-первых, когда они будут обнаружены? Возможно, они обнаружены на этапе предварительного кодирования? В очереди инструкций? На этапе декодирования? Или они добираются до бэкэнда и сразу же завершают (не заходят на станцию бронирования), уходят в отставку по очереди, и этап выхода на пенсию обнаруживает, что это инструкция INT (кажется расточительной).
Допустим, что это происходит с помощью предварительного кода, должен существовать метод сигнализации IFU о прекращении выборки команд или, действительно, тактового генератора / включения питания, или, если он получен в очереди команд, способ сброса инструкций перед ним в очереди., Затем должен быть способ передачи сигналов некоторой логике ("блок управления"), например, для генерации мопов для программного прерывания (индексация в IDT, проверка DPL >=CPL >= RPL сегмента и т. Д. И т. Д.), Наивный предложение, но если кто-то знает об этом процессе лучше, отлично.
Мне также интересно, как он справляется с этим, когда этот процесс нарушается, то есть происходит аппаратное прерывание (учитывая, что ловушки не очищают IF в EFLAGS), и теперь должен начать совершенно новый процесс обработки прерываний и генерации мопов, как бы это произошло? вернуться к состоянию обработки программного прерывания впоследствии.
2 ответа
Эта цитата из Andy @Krazy Glew о синхронных исключениях, обнаруженных во время выполнения "нормальной" инструкции, например mov eax, [rdi]
повышение #PF, если выясняется, что RDI указывает на не отображенную страницу. 1 Вы ожидаете, что это не вина, поэтому вы откладываете что- либо делать до выхода на пенсию, в случае, если это было в тени неверного прогноза филиала или более раннего исключения.
Но да, его ответ не раскрывает детали того, как конвейер оптимизируется для синхронного int
инструкции trap, которые мы знаем при декодировании, всегда будут вызывать исключение. Инструкции ловушек также довольно редки в общем наборе команд, поэтому их оптимизация не экономит много сил; Стоит только делать то, что легко.
Как говорит Энди, текущие процессоры не переименовывают уровень привилегий и, следовательно, не могут спекулировать в обработчике прерываний / исключений, поэтому останавливает выборку / декодирование после просмотра int
или же syscall
это определенно разумная вещь. Я просто собираюсь написать int
или "инструкция ловушки", но то же самое относится и к syscall
/ sysenter
/ sysret
/ iret
и другие изменяющие привилегии "ветви" инструкции. И 1-байтовые версии int
лайк int3
(0xcc
) а также int1
(0xf1
). Условная ловушка при переполнении into
Интересно; для не ужасной производительности в случае без ловушек, вероятно, предполагается, что не ловушка. (И конечно есть vmcall
и прочее для расширений VMX и, возможно, SGX EENTER
и, возможно, другие вещи. Но что касается останова конвейера, я предполагаю, что все команды прерывания равны, за исключением условного into
)
Я бы предположил, что как lfence
ЦП не спекулирует после команды прерывания. Вы правы, не было бы никакого смысла в том, чтобы эти мопы были в конвейере, потому что после int
определенно покраснела.
IDK, если что-нибудь выберет из IVT (таблица векторов прерываний реального режима) или IDT (таблица дескрипторов прерываний), чтобы получить адрес int
обработчик перед int
инструкция становится не спекулятивной в фоновом режиме. Возможно. (Некоторые инструкции, как syscall
используйте MSR для установки адреса обработчика, так что начальная выборка кода оттуда, возможно, будет полезна, особенно если это вызывает раннее промах L1i. Это должно быть сопоставлено с возможностью увидеть int
и другие инструкции по ловушке на неправильном пути после пропуска ветки.)
Неправильное предположение о попадании в команду ловушек, вероятно, достаточно редкое, чтобы стоило начать загрузку из IDT или предварительную загрузку syscall
точка входа, как только внешний интерфейс увидит команду прерывания, если внешний интерфейс достаточно умен, чтобы справиться со всем этим. Но это, вероятно, нет. Оставляя модные вещи в микрокоде, имеет смысл ограничить сложность внешнего интерфейса. Ловушки редки, даже в syscall
тяжелые нагрузки. Пакетная работа для передачи большими кусками через барьер пользователя / ядра - это хорошо, потому что дешево syscall
очень очень тяжелый пост Призрак...
Таким образом, самое позднее, будет обнаружена ловушка при выпуске / переименовании (которая уже знает, как остановить (частично) сериализацию инструкций), и никакие дальнейшие мопы не будут выделяться в нерабочем бэкэнде до тех пор, пока int
был на пенсии, и исключение было принято.
Но обнаружение этого в декодировании кажется вероятным, и не расшифровывать дальше инструкции, которая определенно принимает исключение. (И где мы не знаем, где искать дальше.) Стадия декодера знает, как останавливаться, например, для ловушек с незаконными инструкциями.
Допустим, это взято в предкоде
Это, вероятно, не практично, вы не знаете, что это int
до полного декодирования. Предварительное декодирование - это просто определение длины инструкции на процессорах Intel. Я бы предположил, что коды операций для int
а также syscall
только два из многих, которые имеют одинаковую длину.
Встраивание в HW для более глубокого поиска инструкций по отлову будет стоить больше энергии, чем при предварительном декодировании. (Помните, что ловушки очень редки, и раннее обнаружение их в основном только экономит энергию, поэтому вы не можете тратить больше энергии на их поиск, чем экономите, останавливая предварительное декодирование после передачи ловушки декодерам.
Вам нужно расшифровать int
поэтому его микрокод может исполниться и запустить ЦП снова, запустив обработчик прерываний, но в теории, да, вы могли бы выполнить предварительное декодирование в цикле после его прохождения.
Например, в обычных декодерах пропускаются команды перехода, которые пропускают предсказание ветвления, поэтому для основной стадии декодирования гораздо больше смысла обрабатывать ловушки, не идя дальше.
Гиперпоточность
Вы не просто приводите в действие интерфейс, когда обнаруживаете стойло. Вы позволяете другому логическому потоку иметь все циклы.
Гиперпоточность делает для внешнего интерфейса менее ценным начинать выборку из памяти, на которую указывает IDT, без помощи внутреннего интерфейса. Если другой поток не остановлен и может извлечь выгоду из дополнительной полосы пропускания внешнего интерфейса, пока этот поток разбирает ловушку, процессор выполняет полезную работу.
Я, конечно, не исключил бы выборку кода из точки входа SYSCALL, потому что этот адрес находится в MSR, и это одна из немногих ловушек, которая имеет отношение к производительности в некоторых рабочих нагрузках.
Еще одна вещь, которая меня интересует, это то, насколько сильно влияние любого уровня производительности одного уровня привилегий на переключение логического ядра влияет на производительность другого ядра. Чтобы проверить это, вы должны сконструировать некоторую рабочую нагрузку, которая ограничивает ваш выбор пропускной способности внешнего интерфейса, внутреннего порта, внутренней задержки в цепи передачи или способности внутреннего интерфейса находить ILP на средних и больших расстояниях. (Размер RS или ROB). Или комбинация или что-то еще. Затем сравните циклы / итерации для этой тестовой рабочей нагрузки, работающей на ядре, с самим собой, разделяя ядро с жестким dec/jnz
нить 4x pause / dec/jnz
рабочая нагрузка, и syscall
рабочая нагрузка, которая делает системные вызовы ENOSYS под Linux. Может быть, также int 0x80
рабочая нагрузка для сравнения разных ловушек.
Сноска 1: Обработка исключений, например #PF при нормальной загрузке.
(Не по теме, re: невинно выглядящие инструкции, которые ошибаются, а не команды перехвата, которые могут быть обнаружены в декодерах как вызывающие исключения).
Вы ждете, пока не завершится коммит (выход на пенсию), потому что вы не хотите сразу запускать дорогостоящий конвейер, а обнаруживаете, что эта инструкция находилась в тени промаха ветки (или более ранней ошибочной инструкции) и не должна была выполняться (с этим плохим адресом) в первую очередь. Пусть механизм быстрого восстановления ветвей поймает его.
Эта стратегия ожидания выхода на пенсию (и опасный кэш L1d, который не сокращает значение нагрузки до 0 для попаданий L1d, когда TLB говорит, что он действителен, но не имеет разрешения на чтение), является ключом к тому, почему эксплойты Meltdown и L1TF работают на некоторых Intel ЦП. ( http://blog.stuffedcow.net/2018/05/meltdown-microarchitecture/). Понимание Meltdown очень полезно для понимания стратегий синхронной обработки исключений в высокопроизводительных процессорах: пометить инструкцию и делать что-либо только в том случае, если она выходит на пенсию, - хорошая дешевая стратегия, потому что исключения очень редки.
По-видимому, не стоит усложнять, чтобы исполнительные блоки передавали сигнал переднему интерфейсу, чтобы остановить выборку / декодирование / выпуск, если какой-либо моп на заднем конце обнаруживает ожидающий #PF
или другое исключение. (Предположительно, потому что это будет более тесно связывать части процессора, которые в противном случае довольно далеко друг от друга.)
А поскольку во время быстрого восстановления после пропадания ветки все еще могут выполняться команды с неправильного пути, и для обеспечения того, чтобы вы останавливали только внешний интерфейс для ожидаемых сбоев в том, что, по нашему мнению, является текущим правильным путем выполнения, потребуется дополнительное отслеживание. В какой-то момент любой моп в бэк-энде считался правильным путем, но к тому времени, когда он доберется до конца исполнительного модуля, его может уже не быть.
Если вы не выполняли быстрое восстановление, то, возможно, стоило бы, чтобы серверная часть посылала сигнал "что-то не так", чтобы остановить интерфейсную программу до тех пор, пока серверная сторона не примет исключение или не обнаружит правильный путь.,
При использовании SMT (гиперпоточность) это может оставить больше внешней полосы пропускания для других потоков, когда поток обнаружит, что он в настоящее время спекулирует (возможно, правильным) путем, который приводит к ошибке.
Так что, возможно, есть некоторая заслуга в этой идее; Интересно, какие-нибудь процессоры это делают?
Я согласен со всем, что Петр сказал в своем ответе. Хотя у них может быть много способов реализовать INTn
инструкции, реализация, скорее всего, будет обращена для простоты проектирования процессора, а не производительности. Самая ранняя точка, в которой не может быть спекулятивно определено, существует ли такая команда, находится в конце этапа декодирования конвейера. Можно было бы предсказать, могут ли извлеченные байты содержать инструкцию, которая может вызывать или не вызывает исключение, но я не смог найти ни одной исследовательской работы, которая изучает эту идею, так что, похоже, она того не стоит.
Исполнение INTn
включает в себя выборку указанной записи из IDT, выполнение множества проверок, вычисление адреса обработчика исключений, а затем указание блоку выборки начать оттуда предварительную выборку. Этот процесс зависит от режима работы процессора (реальный режим, 64-битный режим и т. Д.). Режим описывается несколькими флагами из CR0
, CR4
, а также Eflags
регистры. Следовательно, потребовалось бы много мопов для фактического вызова обработчика исключений. В Skylake есть 4 простых декодера и 1 сложный декодер. Простые декодеры могут излучать только один слитый моп. Сложный декодер может излучать до 4 слитых мопов. Никто из них не может справиться INTn
таким образом, для выполнения всей работы по вызову исключения должна быть вызвана конкретная помощь от MSROM. Обратите внимание, что INTn
Сама инструкция может вызвать исключение. На данный момент неизвестно, INTn
сам изменит управление на указанный обработчик исключений (независимо от его адреса) или другой обработчик исключений. Все, что известно наверняка, это то, что поток команд определенно закончится в INTn
и начать где-нибудь еще.
Возможно, что помощь INTn
можно получить на этапе декодирования. Мы уже знаем, что другие инструкции, такие как rdtsc
Работай так. Я могу думать о двух возможных реализациях:
- Вызвать общую процедуру помощи микрокода, которая сначала проверяет текущий режим работы процессора, а затем выбирает специализированную помощь, которая соответствует текущему режиму. Это звучит сложно, потому что у вас есть мопы, которые должны вызывать другие мопы из MSROM. Единственный встроенный способ сделать это состоит в том, чтобы сделать исключение uop fake исключением, чтобы ROB использовал тот же встроенный механизм для вызова выделенной специализированной помощи. Кроме того, предыдущие мопы, находящиеся в полете, могут вызывать исключения, поэтому при вызове ассистента RS будет содержать микс из вспомогательных мопов
INTn
и все остальные предыдущие мопы, ожидающие выполнения. Эти мопы могут конкурировать друг с другом за структурные ресурсы, тем самым снижая производительность. Это может привести к проблемам с корректностью, о которых я не знаю (учитывая, чтоINTn
на данный момент все еще спекулятивный, потому что предыдущий моп может вызвать исключение). @PeterCordes предложил в комментариях, чтоINTn
может быть реализован как инструкция сериализации. Распределитель может удерживатьINTn
и глохнет, пока РОБ не опустеет (аналогично тому, какlfence
работает на процессорах Intel), затем вызовите ассист в этот момент. - раскодировать
INTn
в один специальный моп, который несет с собой вектор прерывания. ROB уже должен иметь поле для хранения информации, которая описывает, вызвало ли соответствующая инструкция исключение и какой тип исключения. Это же поле можно использовать для хранения вектора прерывания. UOP просто проходит через этап выделения и может не нуждаться в планировании в одном из исполнительных блоков, потому что нет необходимости выполнять вычисления. Когда моп собирается уйти в отставку, ROB определяет, что этоINTn
, На данный момент, есть две возможные реализации:- ROB вызывает универсальный помощник микрокода, который определяет специализированный помощник для вызова. Это идет так, как обсуждалось выше. Это выглядит медленно.
- Сам блок ROB включает в себя логику для проверки текущего режима работы и выбирает соответствующую помощь. Он передает вспомогательный адрес логике, ответственной за возникновение событий, которая, в свою очередь, предписывает MSROM выдавать вспомогательную подпрограмму, сохраненную по этому адресу. Эта подпрограмма содержит мопы, которые извлекают запись IDT и выполняют остальную часть процесса вызова обработчика исключений. Это очень разумный способ реализации
INTn
,
Во время выполнения помощи может возникнуть исключение. Это будет обрабатываться как любая другая инструкция, которая вызывает исключение. Блок ROB извлекает описание исключения из ROB и вызывает помощника для его обработки.
Неверные коды операций могут обрабатываться аналогичным образом. На этапе предварительного кода единственное, что имеет значение, - это правильное определение длины инструкций, предшествующих недействительному коду операции. После этих действительных инструкций границы не имеют значения. Когда простой декодер получает недопустимый код операции, он испускает специальный моп, единственная цель которого - просто вызвать недопустимое исключение кода операции. Другие декодеры, отвечающие за инструкции, которые следуют за последней действительной инструкцией, могут выдавать специальный моп. Поскольку инструкции удаляются по порядку, гарантируется, что первый специальный моп вызовет исключение. Если, конечно, предыдущий моп не выдал исключение или произошло неправильное предсказание ветви или очистка порядка памяти.
Когда какой-либо из декодеров выдает этот специальный запрос, этапы выборки и декодирования могут останавливаться до тех пор, пока не будет определен адрес обработчика исключений макроинструкции. Это может быть либо исключение, указанное в uop, либо какое-то другое исключение. Для каждой стадии, которая обрабатывает этот специальный моп, стадия может просто останавливаться (выключение / стробирование тактовой частоты) сама. Это экономит энергию, и я думаю, что это будет легко осуществить.
Или, если другое логическое ядро активно, обработайте его как любую другую причину, по которой этот логический поток передает свои циклы переднего плана другой гиперпотоке. Циклы распределения обычно чередуются между гиперпотоками, но когда один из них останавливается (например, ROB заполнен или внешний интерфейс пуст), другой поток может распределяться в последовательных циклах. Это также может происходить в декодерах, но, возможно, это можно было бы проверить с помощью достаточно большого блока кода, чтобы остановить его выполнение из кэша UOP. (Или слишком плотный, чтобы войти в кэш UOP).