Как использовать APIC для создания IPI, чтобы разбудить AP для SMP в сборке x86?

В среде после загрузки (без ОС), как использовать BSP (первое ядро ​​/ процессор) для создания IPI для точек доступа (всех других ядер / процессоров)? По сути, как можно разбудить и установить указатель команд для других ядер при запуске с одного?

1 ответ

ВНИМАНИЕ: я предположил, что 80x86 здесь. Если это не 80x86, то я не знаю:-)

Сначала необходимо выяснить, сколько существует других процессоров и каковы их идентификаторы APIC, и определить физический адрес локальных APIC. Для этого вы анализируете таблицы ACPI (см. MADT/APIC в спецификации ACPI). Если вы не можете найти действительные таблицы ACPI (например, компьютер слишком старый), есть более старая "Спецификация мультипроцессора", которая определяет свои собственные таблицы с той же информацией в нем. Обратите внимание, что "Спецификация мультипроцессора" теперь устарела (и есть некоторые компьютеры с фиктивными таблицами мультипроцессора), поэтому вам нужно сначала проверить таблицы ACPI.

Следующим шагом является определение типа локального APIC. Есть 3 случая - старые внешние локальные APIC "82489DX" (не встроенные в сам ЦП), xAPIC и x2APIC.

Начните с проверки CPUID, чтобы определить, является ли локальный APIC x2APIC. Если это у вас есть 2 варианта - вы можете использовать x2APIC, или вы можете использовать "режим совместимости с xAPIC". Для "режима совместимости с xAPIC" вы можете использовать только 8-битные идентификаторы APIC и не сможете поддерживать компьютеры с большим количеством процессоров (например, 255 или более процессоров). Я бы порекомендовал использовать x2APIC (даже если вы не заботитесь о компьютерах с большим количеством процессоров), так как это быстрее. Если вы используете режим x2APIC, вам нужно переключить локальный APIC в этот режим.

В противном случае, если это не x2APIC, прочитайте регистр версии локального APIC. Если версия локального APIC равна 0x10 или выше, то это xAPIC, а если 0x0F или ниже, то это внешняя локальная APIC "82489DX".

Старые внешние локальные APIC "82489DX" использовались в 80486 и более старых компьютерах, и они крайне редки (они были очень редки 20 лет назад, затем большинство умерло и / или с тех пор было заменено и выброшено). Поскольку для запуска других процессоров используется другая последовательность, а компьютеры, на которых установлены локальные APIC, встречаются крайне редко (например, вы, вероятно, никогда не сможете проверить свой код), имеет смысл не беспокоиться о поддержке этих компьютеров. Если вы поддерживаете эти старые компьютеры вообще; Я бы порекомендовал рассматривать их как "только один ЦП" и просто не запускать другие ЦП, если локальный APIC равен "82489DX". По этой причине я не буду описывать метод, используемый для их запуска здесь (он описан в "Спецификации многопроцессорности" Intel, если вам интересно).

Для xAPIC и x2APIC последовательность запуска другого ЦП по сути одинакова (только разные способы доступа к локальным APIC - MSR или отображенной памяти). Я бы рекомендовал использовать (например) указатели на функции, чтобы скрыть эти различия; чтобы более поздний код мог вызывать функцию "send IPI" через. указатель на функцию без заботы, если локальный APIC равен x2APIC или xAPIC.

Чтобы фактически запустить другой процессор, вам нужно отправить ему последовательность IPI (межпроцессорных прерываний). Метод Intel выглядит следующим образом:

Send an INIT IPI to the CPU you're starting
Wait for 10 ms
Send a STARTUP IPI to the CPU you're starting
Wait for 200 us
Send another STARTUP IPI to the CPU you're starting
Wait for 200 us
Wait for started CPU to set a flag (so you know it started)
    If flag was set by other CPU, other CPU was started successfully
    Else if time-out, other CPU failed to start

Есть 2 проблемы с методом Intel. Часто второй ЦП запускается при первом IPI STARTUP, и в некоторых случаях это может привести к проблемам (например, если код запуска другого ЦП выполняет что-то вроде total_CPUs++; тогда каждый процессор может выполнить его дважды. Чтобы избежать этой проблемы, вы можете добавить дополнительную синхронизацию (например, другой процессор ожидает установки флага "Я знаю, что вы начали" первым процессором, прежде чем он продолжится). Вторая проблема с методом Intel заключается в измерении этих задержек. Обычно ОС запускает другие процессоры, затем выясняет, какие функции поддерживают процессоры и какое оборудование присутствует впоследствии, и не имеет точной настройки таймера / с для точного измерения этих 200-кратных задержек.

Чтобы избежать этих проблем; Я использую альтернативный метод, который выглядит следующим образом:

Send an INIT IPI to the CPU you're starting
Wait for 10 ms
Send a STARTUP IPI to the CPU you're starting
Wait for started CPU to set a flag (so you know it started) with a short timeout (e.g. 1 ms)
    If flag was set by other CPU, other CPU was started successfully
    Else if time-out
        Send another STARTUP IPI to the CPU you're starting
        Wait for started CPU to set a flag with a long timeout (e.g. 200 ms)
            If flag was set by other CPU, other CPU was started successfully
            Else if time-out, other CPU failed to start
If CPU started successfully
    Set flag to tell other CPU it can continue

Также обратите внимание, что вам нужно запускать процессоры индивидуально. Я видел, как люди запускали все процессоры одновременно, используя функцию "широковещательный IPI для всех, кроме себя" - это неправильно, неубедительно и хитроумно (не делайте этого, если вы не пишете прошивку). Проблема заключается в том, что некоторые процессоры могут быть неисправны (например, не удалось их BIST/ встроенная самопроверка), а некоторые процессоры могут быть отключены (например, гиперпоточность, когда в микропрограмме отключена гиперпоточность); и метод "широковещательный IPI для всех, кроме себя" может запускать процессоры, которые никогда не должны были запускаться.

Наконец, для компьютеров с большим количеством процессоров их запуск может занимать относительно много времени, если вы запускаете их по одному. Например, если для запуска каждого ЦП требуется 11 мс и имеется 128 ЦП, то это займет 1,4 секунды. Если вы хотите быстро загрузиться, есть способы избежать этого. Например, первый ЦП может запустить второй ЦП, затем 1-й и 2-й ЦП могут запустить 3-й и 4-й ЦП, затем эти четыре ЦП могут запустить следующие четыре ЦП и т. Д. Таким образом, вы можете запустить 128 ЦП за 77 мс вместо 1,4 секунды.

Примечание: я бы рекомендовал запускать процессоры по одному и убедиться, что они работают, прежде чем пытаться выполнить какой-либо "параллельный запуск" (это то, о чем вы можете беспокоиться после того, как узнаете, что все остальное работает).

Адрес, который начнут выполнять другие CPU/s, кодируется в поле "вектор" IPI STARTUP. Процессор начнет выполнять код (в реальном режиме) с CS = vector * 256 а также IP = 0, Векторное поле является 8-битным, поэтому максимальный начальный адрес, который вы можете использовать, составляет 0x000FF000 (0xFF00:0x0000 в реальном режиме). Однако это устаревшая область ПЗУ (на практике начальный адрес должен быть ниже). Обычно вы копируете небольшой фрагмент кода запуска на подходящий адрес; где код запуска обрабатывает синхронизацию (например, устанавливает флаг "Я начал", который видит другой процессор, и ждет, когда ему сообщат, что все нормально, чтобы продолжить), а затем выполняет такие вещи, как включение защищенного / длинного режима и настройка стека перед переходом к записи. указать в нормальном коде ОС. Этот маленький кусочек кода запуска называется "батут запуска CPU AP". Это также то, что делает "параллельный запуск" немного сложным; поскольку каждый запускаемый ЦП нуждается в своих / отдельных флагах синхронизации и стеке; и потому что эти вещи обычно реализуются с переменными в батуте (например, mov esp,[cs:stackTop]) это означает, что в конечном итоге с несколькими батутами.

Другие вопросы по тегам