Да, все "нормальные" ISA, включая 8080 и x86, гарантируют, что инструкции являются атомарными по отношению к прерываниям на одном и том же ядре. Либо инструкция полностью выполнена, и все ее архитектурные эффекты видны (в обработчике прерывания) , или ни одного из них. Любые отклонения от этого правила обычно тщательно документируются.
Например, Руководство Intel x86, том 3 (~ 1000 страниц в формате PDF) действительно подчеркивает следующее:
6.6 ПЕРЕЗАПУСК ПРОГРАММЫ ИЛИ ЗАДАЧИ
Чтобы разрешить перезапуск программы или задачи после обработки исключения или прерывания, все исключения (кроме прерываний) гарантированно сообщают об исключениях на границе инструкции. Все прерывания гарантированно выполняются на границе инструкции.
Старый абзац в В руководстве Intel том.1 говорится об одноядерных системах, использующих cmpxchg
без lock
префикса для атомарного чтения-изменения-записи (в отношении другого программного обеспечения, а не доступа к аппаратному DMA).
Инструкция CMPXCHG обычно используется для тестирования и изменения семафоров. Он проверяет, свободен ли семафор. Если семафор свободен, он помечается как выделенный; в противном случае он получает идентификатор текущего владельца. Все это выполняется за одну непрерывную операцию [потому что это одна инструкция]. В однопроцессорной системе инструкция CMPXCHG устраняет необходимость переключения на уровень защиты 0 (для отключения прерываний) перед выполнением нескольких инструкций для проверки и изменения семафора.
Для многопроцессорных систем CMPXCHG можно комбинировать с префиксом LOCK для атомарного выполнения операции сравнения и обмена. (См. «Заблокированные атомарные операции» в главе 8 «Управление несколькими процессорами» Руководства разработчика программного обеспечения для архитектур Intel® 64 и IA-32, том 3A, для получения дополнительной информации об атомарных операциях.)
(Подробнее о префиксе lock
и его реализации по сравнению с неблокированным add [mem], 1
см. Может ли num ++ быть атомарным вместо int num?)
Как указывает Intel в первом абзаце, один из способов добиться атомарности нескольких инструкций - отключить прерывания, а затем снова включить их, когда вы закончите. Это лучше, чем использование мьютекса для защиты более крупного целое число, особенно если вы говорите о данных, совместно используемых основной программой и обработчиком прерывания. Если прерывание происходит, когда основная программа удерживает блокировку, она не может ждать снятия блокировки; этого никогда не случится.
Отключение прерываний обычно обходится довольно дешево для простых конвейеров, работающих по порядку, или особенно для микроконтроллеров. (Иногда вам нужно сохранить предыдущее состояние прерывания вместо безоговорочного разрешения прерываний. Например, функция, которая может быть вызвана с уже отключенными прерываниями.)
В любом случае, отключение прерываний - это то, как вы можете атомарно что-то делать с 64-битным целым числом на 8080.
Некоторые долго выполняющиеся инструкции можно прервать в соответствии с правилами, задокументированными для этих инструкций.
например rep
-строковые инструкции x86, такие как rep movsb
(одинарная инструкция memcpy произвольного размера), архитектурно эквивалентны повторению базовой инструкции (movsb
) RCX раз, каждый раз уменьшая RCX и увеличивая или уменьшая входные указатели (RSI и RDI). Прерывание, поступающее во время копирования, может установить RCX starting_value - byte_copied
и (если RCX тогда ненулевое) оставить RIP, указывающий на инструкцию, поэтому при возобновлении после прерывания rep movsb
снова запустится и выполнит остальную часть копирования.
Другие примеры x86 включают сборочные нагрузки SIMD (AVX2 / AVX512) и хранилища разброса (AVX512). Например. vpgatherdd ymm0, [rdi + ymm1*4], ymm2
выполняет до 8 32-битных загрузок, в соответствии с которыми установлены элементы ymm2
. И результаты объединяются в ymm0.
В обычном случае (без прерываний, ошибок страниц или других синхронных исключений во время сбора) вы получаете данные в регистр назначения, а регистр маски обнуляется. Таким образом, регистр маски дает ЦП место для хранения прогресса.
Сбор и разброс выполняются медленно, и может потребоваться запуск нескольких ошибок страницы, поэтому для синхронных исключений это гарантирует продвижение вперед даже в патологических условиях, когда обработка сбоя страницы отменяет отображение всех других страниц. Но что более важно, это означает, что нужно избегать повторения промахов TLB в случае сбоя страницы среднего элемента и не отбрасывать работу, если приходит асинхронное прерывание.
Некоторые другие долго выполняющиеся инструкции (например, wbinvd
, который очищает все кеши данных во всех ядрах) не являются архитектурно прерываемыми или даже микро архитектурно прерываемыми (чтобы отменить частичную работу и перейти к обработке прерывание). Он имеет привилегии, поэтому пользовательское пространство не может выполнить его как атаку отказа в обслуживании, вызывающую высокую задержку прерывания.
Связанный пример документирования забавного поведения - это когда x86 popad
выходит за пределы стека (ограничение сегмента). Это исключение (не внешнее прерывание), задокументированное ранее в руководстве тома 3, в разделе 6.5 ИСКЛЮЧИТЕЛЬНЫЕ КЛАССИФИКАЦИИ (например, отказ / прерывание / прерывание, более подробную информацию см. В PDF).
ПРИМЕЧАНИЕ. Одно подмножество исключений, которое обычно регистрируется как сбой, не подлежит перезапуску. Такие исключения приводят к потере некоторого состояния процессора. Например, выполнение инструкции POPAD при пересечении кадра стека конец сегмента стека вызывает сообщение об ошибке. В этой ситуации обработчик исключений видит, что указатель инструкции (CS: EIP) был восстановлен, как если бы инструкция POPAD не была выполнена. Однако внутреннее состояние процессора (регистры общего назначения) будет изменено. Такие случаи считаются ошибками программирования. Приложение, вызывающее этот класс исключений, должно быть прекращено операционной системой.
Обратите внимание, что это происходит только в том случае, если popad
сам вызывает исключение, не по какой-либо другой причине. Внешнее прерывание не может разделять popad
так, как это может быть rep movsb
или vpgatherdd
(Я предполагаю, что для целей popad
сбоев он эффективно работает итеративно, вытаскивая по одному регистру за раз и логически изменяя RSP / ESP / SP, а также целевой регистр. Вместо проверки всего региона он будет загружаться для ограничения сегмента перед начиная, потому что это потребует дополнительного добавления, я думаю.)
Неисправные ЦП возвращаются в состояние вывода из эксплуатации по прерываниям.
Такие процессоры, как современные x86 с выполнением вне очереди и разделением сложных инструкций на несколько мопов, по-прежнему гарантируют, что это так. Когда приходит прерывание, ЦП должен выбрать точку между двумя инструкциями, которые он выполняет, в качестве места, где архитектурно происходит прерывание. Он должен отменить любую работу, которая уже была сделана по декодированию или началу выполнения любых последующих инструкций. Предполагая, что прерывание вернется, они будут повторно извлечены и снова начнут выполнение.
См. Когда происходит прерывание, что происходит с инструкциями в процессе разработки?.
Как говорит Энди Глю, текущие процессоры не переименовывают уровень привилегий, поэтому то, что происходит логически (обработчик прерываний / исключений выполняется после завершения предыдущих инструкций), соответствует тому, что на самом деле происходит.
Однако забавный факт: прерывания x86 не полностью сериализуются, по крайней мере, не гарантируется на бумаге. (В терминологии x86 такие инструкции, как cpuid
и iret
, определены как сериализация; истощите внутреннюю часть OoO и буфер хранения и все остальное, что может иметь значение. Это очень сильный барьер, и многие другие вещи не являются < / em>, например mfence
.)
На практике (поскольку процессоры на практике не переименовывают уровень привилегий), не будет никаких старых инструкций / операций пользовательского пространства в вышедшей из строя серверной части, которые все еще работают, когда запускается обработчик прерывания.
Асинхронные (внешние) прерывания также могут истощать буфер хранилища, в зависимости от того, как мы интерпретируем формулировку Intel SDM vol.3 11.10: * содержимое буфера хранилища всегда выгружаются в память в следующих ситуациях: "..." Когда исключение или прерывание сгенерировано ". Очевидно, что это применимо к исключениям (когда ядро ЦП само генерирует прерывание), а также может означает перед обслуживанием прерывания.
(Сохранение данных из устаревших инструкций хранилища не является умозрительным; это определенно произойдет, и ЦП уже сбросил состояние, в которое он должен был бы иметь возможность откатиться до этой инструкции сохранения. Таким образом, большое хранилище буфер, заполненный разрозненными хранилищами промахов кэша, может снизить задержку прерывания. Либо из-за ожидания его истощения до того, как какие-либо инструкции обработчика прерываний могут быть запущены вообще, либо, по крайней мере, до того, как любая _22 _ / _ 23_ или lock
ed инструкция в ISR может произойти, если она превратится из того, что буфер хранилища не истощен.)
Связанный: Sandpile (https://www.sandpile.org/x86/coherent.htm) есть таблица сериализуемых вещей. Прерываний и исключений нет. Но опять же, это не означает, что они не истощают буфер хранилища. Это можно проверить с помощью эксперимента: ищите переупорядочение StoreLoad между хранилищем в пользовательском пространстве и загрузкой (другой общей переменной) в ISR, как это наблюдается другим ядром.
Часть этого раздела на самом деле не относится к этому ответу и должна быть перемещена в другое место. Это здесь, потому что обсуждение в комментариях к Что происходит с ожидаемой семантикой памяти (например, чтение после записи), когда поток запланировано на другом ядре ЦП? назвал это источником, вероятно, неверного утверждения о том, что прерывания не истощают буфер хранилища, что я написал после неверной интерпретации «не сериализации».
person
Peter Cordes
schedule
15.03.2019