Введение Добрый день, сегодня мне бы хотелось затронуть довольно простую тему, которая неизвестна практически ни одному рядовому программисту, но каждый из вас, скорее всего, ею пользовался.
Мы поговорим о симметричной многопроцессорности (известной в народе как SMP) — архитектуре, которая встречается во всех многозадачных операционных системах и, конечно же, является их неотъемлемой частью.
Все знают, что чем больше ядер у процессора, тем он мощнее будет, да, это правда, но как ОС может использовать несколько ядер одновременно? Некоторые программисты не опускаются до этого уровня абстракции — им это просто не нужно, но, думаю, всем будет интересно, как работает SMP.
Многозадачность и ее реализация.
Те, кто когда-либо изучал архитектуру ВМ, знают, что сам процессор не может выполнять несколько задач одновременно; многозадачность нам обеспечивает только ОС, которая переключает эти задачи.
Существует несколько видов многозадачности, но наиболее адекватной, удобной и широко используемой является вытесняющая многозадачность (основные ее аспекты можно прочитать в Википедии).
Он основан на том, что каждый процесс (задача) имеет свой приоритет, от которого зависит, сколько процессорного времени ему будет выделено.
Каждой задаче дается один интервал времени, в течение которого процесс что-то делает; по истечении интервала времени ОС передает управление другой задаче.
Возникает вопрос – как распределить ресурсы компьютера, такие как память, устройства и т.д. между процессами? Все очень просто: Windows делает это сама, Linux использует систему семафоров.
Но одно ядро - это несерьезно, идем дальше.
Прерывания и PIC
Возможно для кого-то это будет новостью, а для кого-то нет, но архитектура i386 (я буду говорить конкретно об архитектуре x86, ARM не в счет, т.к.я эту архитектуру не изучал, и никогда с ней не сталкивался (даже на уровне написания какой-либо службы или резидентной программы)) использует прерывания (мы будем говорить только об аппаратных прерываниях, IRQ) для того, чтобы уведомить ОС или программу о том или ином событии.
Например, есть прерывание 0x8 (для защищенного и длинного режимов, например, 0x20, в зависимости от того, как вы настроите PIC, об этом позже), которое вызывается PIT, который, например, может генерировать прерывания с любым необходимым частота.
Тогда работа ОС по распределению временных интервалов сводится к 0; при вызове прерывания работа программы прекращается, а управление передается, например, ядру, которое в свою очередь сохраняет текущие данные программы (регистры, флаги и т. д.) и передает управление следующему процессу.
Как вы, наверное, понимаете, прерывания — это функции (или процедуры), вызываемые в какой-то момент времени оборудованием или самой программой.
Всего процессор поддерживает 16 прерываний на двух PIC. У процессора есть флаги, и один из них — флаг «I» — Управление прерываниями.
Установив этот флаг в 0, процессор не будет вызывать никаких аппаратных прерываний.
Но, еще хочу отметить, что существуют так называемые NMI — Non-Maskable Interrupts — эти прерывания все равно будут вызываться, даже если бит I установлен в 0. С помощью PIC-программирования можно отключить эти прерывания, но после возврата от любого прерывания, использующего IRET - они снова станут не запрещенными.
Отмечу, что из-под штатной программы вы не сможете отследить вызов прерывания - выполнение вашей программы остановится и возобновится только через некоторое время, ваша программа этого даже не заметит (да-да, вы можете проверить что было вызвано прерывание - но почему?
PIC — программируемый контроллер прерываний
Из Вики:Как правило, это электронное устройство, иногда выполненное в составе самого процессора или сложных микросхем его корпуса, входы которого электрически соединены с соответствующими выходами различных устройств.Как вы понимаете, это электронная схема, позволяющая устройствам отправлять запросы на прерывание, обычно их ровно 2. Теперь перейдем к теме самой статьи.Номер входа контроллера прерываний обозначается как «IRQ».
Этот номер необходимо отличать от приоритета прерывания, а также от номера записи таблицы векторов прерываний (INT).
Так, например, в IBM PC в реальном режиме работы (в этом режиме работает MS-DOS) процессора прерывание от стандартной клавиатуры использует IRQ 1 и INT 9. Исходная платформа IBM PC использовала очень простую схему прерываний.
Контроллер прерываний представляет собой простой счетчик, который либо последовательно перебирает сигналы от разных устройств, либо сбрасывается в начало при обнаружении нового прерывания.
В первом случае устройства имеют равный приоритет; во втором — устройства с меньшим (или большим при обратном отсчете) порядковым номером имеют более высокий приоритет.
СМП
Для реализации этого стандарта на материнские платы стали устанавливаться новые схемы: APIC и ACPI. Давайте поговорим о первом.APIC — усовершенствованный программируемый контроллер прерываний, улучшенная версия PIC. Он используется в многопроцессорных системах и является неотъемлемой частью всех последних (и совместимых) процессоров Intel. APIC используется для сложного перенаправления прерываний и отправки прерываний между процессорами.
Это было невозможно при использовании старой спецификации PIC.
Локальный APIC и APIC ввода-вывода
В системе на основе APIC каждый процессор состоит из «ядра» и «локального APIC».Локальный APIC отвечает за обработку конфигурации прерываний, специфичных для процессора.
Помимо прочего, он содержит таблицу локальных векторов (LVT), которая преобразует такие события, как «внутренние часы» и другие «локальные» источники прерываний, в вектор прерываний (например, вывод LocalINT1 может вызывать исключение NMI, сохраняя « 2" к соответствующему входу LVT).
Дополнительную информацию о локальном APIC можно найти в Руководстве по системному программированию для современных процессоров Intel. Существует также APIC IO (например, Intel 82093AA), который является частью набора микросхем и обеспечивает управление многопроцессорными прерываниями, включая статическое и динамическое симметричное распределение прерываний между всеми процессорами.
В системах с несколькими подсистемами ввода-вывода каждая подсистема может иметь свой собственный набор прерываний.
Каждый вывод прерывания индивидуально программируется «по срабатыванию по фронту или по уровню».
Вектор прерывания и информация управления прерыванием могут быть указаны для каждого прерывания.
Схема непрямого доступа к регистрам оптимизирует объем памяти, необходимый для доступа к внутренним регистрам ввода-вывода APIC. Чтобы повысить гибкость системы при назначении использования пространства памяти, пространство двух регистров ввода-вывода APIC является перемещаемым, но по умолчанию используется значение 0xFEC00000.
Инициализация «локального» APIC
Локальный APIC включается во время загрузки и может быть отключен путем сброса бита 11 IA32_APIC_BASE (MSR) (это работает только с процессорами семейства > 5, поскольку у Pentium нет такого MSR).Затем процессор получает прерывания непосредственно от PIC, совместимого с 8259. Однако в руководстве по разработке программного обеспечения Intel указано, что после отключения локального APIC через IA32_APIC_BASE вы не сможете включить его до полной перезагрузки.
IO APIC также можно настроить для работы в устаревшем режиме, чтобы он имитировал устройство 8259. Локальные регистры APIC сопоставлены с физической страницей FEE00xxx (см.
Таблицу 8-1 Intel P4 SPG).
Этот адрес одинаков для каждого локального APIC, существующего в конфигурации, что означает, что вы можете напрямую обращаться к регистрам локального APIC ядра, в котором в данный момент выполняется ваш код. Обратите внимание, что существует MSR, который определяет фактическую базу APIC (доступен только для процессоров семейства > 5).
MADT содержит локальную базу APIC, а в 64-битных системах может также содержать поле, определяющее переопределение 64-битного базового адреса, которое следует использовать вместо этого.
Вы можете оставить локальную базу APIC там, где вы ее найдете, или переместить ее куда захотите.
Примечание.
Я не думаю, что вы сможете переместить его дальше 4 ГБ ОЗУ.
Чтобы локальный APIC мог принимать прерывания, необходимо настроить «Регистр вектора ложного прерывания».
Правильным значением для этого поля является номер IRQ, который вы хотите сопоставить с ложными прерываниями, при этом младшие 8 битов и 8-й бит установлены в 1, чтобы фактически включить APIC (подробности см.
в спецификации).
Вы должны выбрать номер прерывания, младшие 4 бита которого установлены; Самый простой способ — использовать 0xFF. Это важно для некоторых старых процессоров, поскольку для этих значений младшие 4 бита должны быть установлены в 1. Отключите 8259 PIC правильно.
Это почти так же важно, как настройка APIC. Вы делаете это в два этапа: маскирование всех прерываний и переназначение IRQ. Маскирование всех прерываний отключает их в PIC. Переназначение прерываний — это то, что вы, вероятно, уже делали при использовании PIC: вы хотите, чтобы запросы на прерывания начинались с 32, а не с 0, чтобы избежать конфликтов с исключениями (в защищенном и длинном режимах процессора, т.к.
первые 32 прерывания — это исключения В этом случае вам следует избегать использования этих векторов прерываний для других целей.
Это необходимо, потому что даже если вы замаскировали все прерывания PIC, они все равно могут генерировать ложные прерывания, которые затем будут неправильно обработаны как исключения в вашем ядре.
Перейдем к СМП.
Симметричная многозадачность: инициализация
Последовательность запуска различна для разных процессоров.Руководство программиста Intel (раздел 7.5.4) содержит протокол инициализации для процессоров Intel Xeon и не распространяется на более старые процессоры.
Общий алгоритм «все типы процессоров» см.
в спецификации мультипроцессора Intel. Для 80486 (с внешним APIC 8249DX) вам следует использовать IPIT INIT, за которым следует IPI «отмена подтверждения уровня INIT» без какого-либо SIPI. Это означает, что вы не можете сказать им, с чего начать выполнение вашего кода (векторная часть SIPI), и они всегда начинают выполнять код BIOS. В этом случае вы устанавливаете значение сброса CMOS BIOS на «теплый старт с дальним переходом» (т. е.
устанавливаете позицию CMOS 0x0F на 10), чтобы BIOS выполнял jmp Far ~[0:0x0469]», а затем устанавливаете сегмент и смещение.
Точка входа AP по адресу 0x0469. IPI «отмена подтверждения уровня INIT» не поддерживается на новых процессорах (Pentium 4 и Intel Xeon), и AFAIK полностью игнорируется на этих процессорах.
Для новых процессоров (P6, Pentium 4) одного SIPI достаточно, но я не уверен, нужен ли второй SIPI старым процессорам Intel (Pentium) или процессорам других производителей.
Также возможно, что второй SIPI существует в случае сбоя доставки первого SIPI (шум в шине и т. д.).
Обычно я отправляю первый SIPI, а затем жду, увеличит ли точка доступа счетчик количества работающих процессоров.
Если счетчик не увеличится в течение нескольких миллисекунд, я отправлю второй SIPI. Это отличается от общего алгоритма Intel (который имеет задержку 200 микросекунд между SIPI), но попытаться найти источник времени, который может точно измерить задержку 200 микросекунд во время ранней загрузки, непросто.
Я также обнаружил, что на реальном оборудовании, если задержка между SIPI слишком велика (и вы не используете мой метод), основная точка доступа может дважды запустить код запуска ранней точки доступа для ОС (что в моем случае приведет к зависанию ОС).
думаю, что у нас в два раза больше процессоров, чем есть на самом деле).
Вы можете транслировать эти сигналы по шине, чтобы активировать каждое присутствующее устройство.
Однако при этом вы также можете включить процессоры, которые были отключены намеренно (поскольку они были «неисправны»).
Ищем информацию с помощью таблицы MT
Некоторая информация (которая может быть недоступна на новых машинах), предназначенная для многопроцессорной обработки.Сначала нам нужно найти структуру плавающего указателя MP. Он выровнен по границе 16 байт и содержит подпись в начале «_MP_» или 0x5F504D5F. ОС должна искать в EBDA, пространстве ПЗУ BIOS и последнем килобайте «базовой памяти»; Базовый размер памяти указывается как 2-байтовое значение по адресу 0x413 в килобайтах минус 1 КБ.
Вот как выглядит структура:
Вот как выглядит таблица конфигурации, если на нее указывает структура с плавающим указателем:struct mp_floating_pointer_structure { char signature[4]; uint32_t configuration_table; uint8_t length; // In 16 bytes (e.g. 1 = 16 bytes, 2 = 32 bytes) uint8_t mp_specification_revision; uint8_t checksum; // This value should make all bytes in the table equal 0 when added together uint8_t default_configuration; // If this is not zero then configuration_table should be // ignored and a default configuration should be loaded instead uint32_t features; // If bit 7 is then the IMCR is present and PIC mode is being used, otherwise // virtual wire mode is; all other bits are reserved }
struct mp_configuration_table {
char signature[4]; // "PCMP"
uint16_t length;
uint8_t mp_specification_revision;
uint8_t checksum; // Again, the byte should be all bytes in the table add up to 0
char oem_id[8];
char product_id[12];
uint32_t oem_table;
uint16_t oem_table_size;
uint16_t entry_count; // This value represents how many entries are following this table
uint32_t lapic_address; // This is the memory mapped address of the local APICs
uint16_t extended_table_length;
uint8_t extended_table_checksum;
uint8_t reserved;
}
После таблицы конфигурации идут записи enter_count, которые содержат дополнительную информацию о системе, за которой следует расширенная таблица.
Записи имеют размер либо 20 байт для обозначения процессора, либо 8 байт для представления чего-то другого.
Вот как выглядят записи ввода-вывода ЦП и APIC. struct entry_processor {
uint8_t type; // Always 0
uint8_t local_apic_id;
uint8_t local_apic_version;
uint8_t flags; // If bit 0 is clear then the processor must be ignored
// If bit 1 is set then the processor is the bootstrap processor
uint32_t signature;
uint32_t feature_flags;
uint64_t reserved;
}
Вот запись IO APIC. struct entry_io_apic {
uint8_t type; // Always 2
uint8_t id;
uint8_t version;
uint8_t flags; // If bit 0 is set then the entry should be ignored
uint32_t address; // The memory mapped address of the IO APIC is memory
}
Ищем информацию с помощью APIC
Таблицу MADT (APIC) можно найти в ACPI. В таблице приведен список локальных APIC, количество которых должно соответствовать количеству ядер вашего процессора.Деталей этой таблицы здесь нет, но вы можете найти их в Интернете.
Запустить точку доступа
После того, как вы собрали информацию, вам необходимо отключить PIC и подготовиться к вводу-выводу APIC. Вам также необходимо настроить BSP локального APIC. Затем запустите точку доступа с помощью SIPI. Код для запуска ядер: Обратите внимание, что вектор, который вы указываете при запуске, указывает начальный адрес: вектор 0x8 — это адрес 0x8000, вектор 0x9 — это адрес 0x9000 и т. д. // ------------------------------------------------------------------------------------------------
static u32 LocalApicIn(uint reg)
{
return MmioRead32(*g_localApicAddr + reg);
}
// ------------------------------------------------------------------------------------------------
static void LocalApicOut(uint reg, u32 data)
{
MmioWrite32(*g_localApicAddr + reg, data);
}
// ------------------------------------------------------------------------------------------------
void LocalApicInit()
{
// Clear task priority to enable all interrupts
LocalApicOut(LAPIC_TPR, 0);
// Logical Destination Mode
LocalApicOut(LAPIC_DFR, 0xffffffff); // Flat mode
LocalApicOut(LAPIC_LDR, 0x01000000); // All cpus use logical id 1
// Configure Spurious Interrupt Vector Register
LocalApicOut(LAPIC_SVR, 0x100 | 0xff);
}
// ------------------------------------------------------------------------------------------------
uint LocalApicGetId()
{
return LocalApicIn(LAPIC_ID) >> 24;
}
// ------------------------------------------------------------------------------------------------
void LocalApicSendInit(uint apic_id)
{
LocalApicOut(LAPIC_ICRHI, apic_id << ICR_DESTINATION_SHIFT);
LocalApicOut(LAPIC_ICRLO, ICR_INIT | ICR_PHYSICAL
| ICR_ASSERT | ICR_EDGE | ICR_NO_SHORTHAND);
while (LocalApicIn(LAPIC_ICRLO) & ICR_SEND_PENDING)
;
}
// ------------------------------------------------------------------------------------------------
void LocalApicSendStartup(uint apic_id, uint vector)
{
LocalApicOut(LAPIC_ICRHI, apic_id << ICR_DESTINATION_SHIFT);
LocalApicOut(LAPIC_ICRLO, vector | ICR_STARTUP
| ICR_PHYSICAL | ICR_ASSERT | ICR_EDGE | ICR_NO_SHORTHAND);
while (LocalApicIn(LAPIC_ICRLO) & ICR_SEND_PENDING)
;
}
void SmpInit()
{
Теги: #Компьютерное оборудование #История ИТ #Периферийные устройства #C++ #ИТ-стандарты #Аппаратное обеспечение #Системное программирование #многозадачность #Ассемблер #smp #apic #pic #osdev #pit #симметричная многопроцессорность #APCI
-
Что Такое Решения Цифрового Маркетинга
19 Oct, 24 -
Другой Способ Разложить Сигнал На Спектр
19 Oct, 24 -
Таблицы Google
19 Oct, 24