Введение


Доброго времени суток, сегодня хотелось бы затронуть достаточно простую тему, которая почти никем из обычных программистов неизвестна, но каждый из вас, скорее всего, ей пользовался.
Речь пойдет о симметричной мультипроцессорности(в народе — SMP) — архитектура, которая встречается во всех многозадачных операционных системах, и конечно же, является неотъемлемой их частью. Каждый знает, что чем больше ядер у процессора — тем мощнее будет процессор, да, это так, но как ОС может использовать несколько ядер одновременно? Некоторые программисты не спускаются до такого уровня абстракции — им это попросту не надо, но думаю, всем будет интересно то, как же SMP работает.

Многозадачность и её реализация


Те, кто когда-нибудь изучал архитектуру ЭВМ знает, что процессор сам по себе не умеет выполнять несколько задач сразу, многозадачность нам даёт только ОС, которая и переключает эти задачи. Есть несколько типов многозадачности, но самый адекватный, удобный и широко используемый — вытесняющая многозадачность(главные её аспекты Вы можете прочитать на википедии). Она основана на том, что у каждого процесса(задачи) есть свой приоритет, который влияет на то, сколько процессорного времени ему будет выделено. Каждой задаче даётся один квант времени, во время которого процесс что-либо делает, после истечение кванта времени ОС передает управление другой задаче. Возникает вопрос — а как распределить ресурсы компьютера, такие, как память, устройства и т.п. между процессами? Всё очень просто: Windows делает это сама, Linux же использует систему семафоров. Но одно ядро — несерьезно, идем дальше.

Прерывания и PIC


Возможно, для кого-то это окажется новостью, для кого-то — нет, но архитектура i386(буду говорить именно про архитектуры x86, ARM не в счет, т.к. данную архитектуру я не изучал, да и никогда с ней не сталкивался(даже на уровне написание какой-нибудь службы или резидентной программы)) использует прерывания(мы будем говорить только про прерывания оборудования, IRQ) для того, чтобы оповестить ОС или программу о том или ином событии. К примеру, существует прерывание 0x8(для защищенного и длинного режимов, к примеру, 0x20, смотря как настроить PIC, об этом дальше), которое вызывается PIT'ом, который, к примеру, может генерировать прерывания с какой-либо необходимой частотой. Тогда работа ОС для распределения квантов времени сводиться к 0, при вызове прерывания работа программы прекращается, и управление отдаётся, к примеру, ядру, которое в свою очередь сохраняет текущие данные программы(регистры, флаги и т.п.) и отдает управление следующему процессу.

Как вы наверное поняли, прерывания — это функции(или же процедуры), которые вызываются в какой-либо момент времени оборудованием, или же самой программой. Всего процессор поддерживает 16 прерываний на двух PIC'ах. Процессор имеет флаги, и один из них — флаг «I» — Interrupt Control. Путем установки данного флага в 0 процессор не будет вызывать никаких аппаратных прерываний. Но, так же хочу заметить, что есть так называемые NMI — Non-Maskable Interrupts — данные прерывания всё равно будут вызываться, даже если бит I установлен в 0. При помощи программирования PIC можно запретить данные прерывания, но после возврата из любого прерывания при помощи IRET — они вновь станут не запрещенными. Замечу, что из-под обычной программы вы не сможете отследить вызов прерывания — выполнение вашей программы остановиться, и только через некоторое время возобновиться, ваша программа этого даже не заметит(да-да, можно проверить то, что было вызвано прерывание — но зачем?

PIC — Programmable Interrupt Controller

Из Вики:
Как правило, представляет собой электронное устройство, иногда выполненное как часть самого процессора или же сложных микросхем его обрамления, входы которого присоединены электрически к соответствующим выходам различных устройств. Номер входа контроллера прерываний обозначается «IRQ». Следует отличать этот номер от приоритета прерывания, а также от номера входа в таблицу векторов прерываний (INT). Так, например, в IBM PC в реальном режиме работы (в этом режиме работает MS-DOS) процессора прерывание от стандартной клавиатуры использует IRQ 1 и INT 9.

В первоначальной платформе IBM PC используется очень простая схема прерываний. Контроллер прерываний представляет собой простой счётчик, который либо последовательно перебирает сигналы разных устройств, либо сбрасывается на начало при нахождении нового прерывания. В первом случае устройства имеют равный приоритет, во втором устройства с меньшим (или большим при обратном счёте) порядковым номером обладают большим приоритетом.

Как вы поняли, это электронная схема, которая позволяет устройствам отправлять запросы на прерывания, обычно их ровно 2.

Теперь же, давайте перейдем к самой теме статьи.

SMP


Для реализации данного стандарта на материнские платы начали ставить новые схемы: APIC и ACPI. Давайте поговорим о первом.

APIC — Advanced Programmable Interrupt Controller, улучшенная версия PIC. Он используется в многопроцессорных системах и является неотъемлемой частью всех последних процессоров Intel (и совместимых). APIC используется для сложного перенаправления прерываний и для отправки прерываний между процессорами. Эти вещи были невозможны с использованием более старой спецификации PIC.

Local APIC и IO APIC


В системе на базе APIC каждый процессор состоит из «ядра» и «локального APIC'а». Local APIC отвечает за обработку конфигурации прерываний, специфичных для процессора. Помимо всего прочего, он содержит локальную векторную таблицу (LVT), которая переводит события, такие как «внутренние часы(internal clock)» и другие «локальные» источники прерываний, в вектор прерывания (например, контакт LocalINT1 может поднимать исключение NMI, сохраняя «2» в соответствующий вход LVT).

Более подробную информацию о локальном APIC можно найти в «Руководстве по системному программированию» современных процессоров Intel.

Кроме того, имеется APIC IO (например, intel 82093AA), который является частью набора микросхем и обеспечивает многопроцессорное управление прерываниями, включающее как статическое, так и динамическое симметричное распределение прерываний для всех процессоров. В системах с несколькими подсистемами ввода / вывода каждая подсистема может иметь свой собственный набор прерываний.

Каждый вывод прерывания индивидуально программируется «as either edge or level triggered». Вектор прерывания и информация об управлении прерываниями могут быть указаны для каждого прерывания. Схема косвенного доступа к регистру оптимизирует пространство памяти, необходимое для доступа к внутренним регистрам ввода-вывода APIC. Чтобы повысить гибкость системы при назначении использования пространства памяти, пространство двух регистров ввода-вывода APIC является перемещаемым, но по умолчанию оно равно 0xFEC00000.

Инициализация «локального» APIC'а


Локальный APIC активируется во время загрузки и может быть отключен путем сброса бита 11 IA32_APIC_BASE (MSR) (это работает только с процессорами с семейством > 5, поскольку у Pentium нет такого MSR), Затем процессор получает свои прерывания непосредственно из совместимого с 8259 PIC'а. Однако в руководстве 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-й ГБайт RAM'а.

Чтобы включить локальный APIC для приема прерываний, необходимо настроить «Spurious Interrupt Vector Register». Правильное значение для этого поля — это номер IRQ, который вы хотите сопоставить ложным прерываниям с младшими 8 битами, и 8-й бит, установленный в 1, чтобы фактически включить APIC (подробнее см. спецификацию). Вы должны выбрать номер прерывания, у которого установлены младшие 4 бита; проще всего использовать 0xFF. Это важно для некоторых более старых процессоров, потому что для этих значений младших 4 бит должны быть установлены в 1.

Отключите 8259 PIC правильно. Это почти так же важно, как настройка APIC. Вы делаете это в два этапа: маскирование всех прерываний и переназначение IRQ. Маскировка всех прерываний отключает их в PIC. Ремаппинг прерываний — это то, что вы, вероятно, уже сделали, когда вы использовали PIC: вы хотите, чтобы запросы прерывания начинались с 32 вместо 0, чтобы избежать конфликтов с исключениями(в защищенном и длинном(Long) режимах процессора, т.к. первые 32 прерывания — исключения(exceptions)). Затем вы должны избегать использования этих векторов прерываний для других целей. Это необходимо, потому что, несмотря на то, что вы маскировали все прерывания PIC, он все равно мог выдавать ложные прерывания, которые затем будут неверно обрабатываться в вашем ядре в качестве исключений.
Перейдем к SMP.

Симметричная многозадачность: инициализация


Последовательность запуска различна для разных ЦП. Руководство программиста Intel (раздел 7.5.4) содержит протокол инициализации для процессоров Intel Xeon и не охватывает более старые процессоры. Для общего алгоритма «всех типов процессоров» см. «Многопроцессорная спецификация Intel».

Для 80486 (с внешним APIC 8249DX) вы должны использовать IPIT INIT, за которым следует IPI «INIT level de-assert» без каких-либо SIPI. Это означает, что вы не можете сказать им, где начать выполнение вашего кода (векторная часть SIPI), и они всегда начинают выполнять код BIOS. В этом случае вы устанавливаете значение сброса CMOS BIOS в «warm start with far jump» (т. е. Установите положение CMOS 0x0F в значение 10), чтобы BIOS выполнил jmp far ~ [0: 0x0469] », а затем установите сегмент и смещение точки входа AP в 0x0469.

«INIT level de-assert» IPI не поддерживается на новых процессорах (Pentium 4 и Intel Xeon), а AFAIK полностью игнорируется на этих процессорах.

Для более новых процессоров (P6, Pentium 4) достаточно одного SIPI, но я не уверен, что более старые процессоры Intel (Pentium) или процессоры других производителей нуждаются в втором SIPI. Также возможно, что второй SIPI существует в случае сбоя доставки для первого SIPI (шум шины и т. д.).

Обычно я отправляю первый SIPI, а затем жду, чтобы увидеть, увеличивает ли AP счетчик количества запущенных процессоров. Если он не увеличит этот счетчик в течение нескольких миллисекунд, я отправлю второй SIPI. Это отличается от общего алгоритма Intel (который имеет задержку в 200 микросекунд между SIPI), но попытка найти источник времени, способный точно измерять задержку в 200 микросекунд во время ранней загрузки, не так-то просто. Я также обнаружил, что на реальном оборудовании, если задержка между SIPI слишком длинная (и вы не используете мой метод), главный AP может запускать ранний код запуска AP для ОС дважды (что в моем случае приведет к тому, что ОС будет думать, что мы имеем в два раза больше процессоров, чем есть на самом деле).

Вы можете транслировать эти сигналы по шине для запуска каждого присутствующего устройства. Однако при этом вы также можете включить процессоры, которые были отключены специально (потому что они были «дефектными»).

Ищем информацию, используя MT-таблицу


Некоторая информация (которая может отсутствовать на более новых машинах), предназначенная для многопроцессорности. Сначала нужно найти структуру плавающего указателя MP. Он выровнен по 16-байтовой границе и содержит подпись в начале «_MP_» или 0x5F504D5F. ОС должна искать в EBDA, пространстве BIOS ROM и в последнем килобайте «базовой памяти»; размер базовой памяти указан в 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;
}

После таблицы конфигурации лежат записи entry_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, число которых должно соответствовать количеству ядер на вашем процессоре. Подробностей этой таблицы здесь нет, но вы можете найти их в интернете.

Запуск AP


После того, как вы собрали информацию, вам необходимо отключить PIC и подготовиться к APIC I/O. Вам также необходимо настроить BSP локального APIC'а. Затем запустите AP с использованием 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()
{
	kprintf("Waking up all CPUs\n");

	*g_activeCpuCount = 1;
	uint localId = LocalApicGetId();

	// Send Init to all cpus except self
	for (uint i = 0; i < g_acpiCpuCount; ++i)
	{
		uint apicId = g_acpiCpuIds[i];
		if (apicId != localId)
		{
			LocalApicSendInit(apicId);
		}
	}

	// wait
	PitWait(200);

	// Send Startup to all cpus except self
	for (uint i = 0; i < g_acpiCpuCount; ++i)
	{
		uint apicId = g_acpiCpuIds[i];
		if (apicId != localId)
			LocalApicSendStartup(apicId, 0x8);
	}

	// Wait for all cpus to be active
	PitWait(10);
	while (*g_activeCpuCount != g_acpiCpuCount)
	{
		kprintf("Waiting... %d\n", *g_activeCpuCount);
		PitWait(10);
	}

	kprintf("All CPUs activated\n");
}

[org 0x8000]
	AP:
		jmp short bsp ; Если это первое ядро - прыгаем в BSP
		xor ax,ax
		mov ss,ax
		mov sp, 0x7c00
		xor ax,ax
		mov ds,ax
	        ; Mark CPU as active
        	lock
		inc byte [ds:g_activeCpuCount]
		;Переходим в защищенный режим, настраиваем стек
		jmp zop
	bsp:
	xor ax,ax
	mov ds,ax
	mov dword[ds:g_activeCpuCount],0
	mov dword[ds:g_activeCpuCount],0
	mov word [ds:0x8000], 0x9090 ; Заменяем JMP сюда на 2 NOP'а
	;Переходим в защищенный режим, настраиваем стек

Теперь, как вы понимаете, чтобы ОС использовать много ядер, надо настроить стек для каждого ядра, каждое ядро, его прерывания и т.п., но самое важное — при использовании симметричной мультипроцессорности все ресурсы у ядер одни и те же: одна память, один PCI и т.п., и ОС остаётся лишь распараллелить задачи между ядрами.

Надеюсь, что статья получилась не достаточно нудной, и достаточно информативной. В следующий раз, думаю, можно поговорить о том, как раньше рисовали на экране(и сейчас рисуют), без использования шейдеров и крутых видеокарт.

Удачи!

Комментарии (24)


  1. VBKesha
    15.10.2018 20:51

    Спасибо!


  1. alexanster
    16.10.2018 11:34

    Перестал читать после:

    Каждый знает, что чем больше ядер у процессора — тем мощнее будет процессор


    1. homm
      16.10.2018 14:05

      А как правильно? Чем больше ядер, тем немощнее?


      1. springimport
        16.10.2018 16:16

        Лучше не использовать абстрактное «мощнее». И все же для обычного пользователя гораздо важнее скорость ядра. Ядра 8700k под разгоном, например, на треть быстрее чем ядра у threadripperr.
        Для обычного пользователя очень сложно придумать задачу где не хватит ладно не 4, а 6 или 8 ядер + ht (smt).


      1. alexanster
        16.10.2018 17:24

        Правильно рассматривать связку процессор-ПО, иначе это будет сферический конь в вакууме. Если к процессору на 100500 говноядер прикрутить ещё столько же, то мощнее он будет только греться. В общем случае ПРИ ПРОЧИХ РАВНЫХ я лично предпочту Тактовая частота х2, чем Количество ядер х2.


        1. homm
          17.10.2018 00:10

          В общем случае ПРИ ПРОЧИХ РАВНЫХ я лично предпочту Тактовая частота х2, чем Количество ядер х2.

          Нет, подождите. Про частоту там ни слова. По вашему утверждению «перестал читать после „чем больше — тем мощнее процессор“» вы предпочтёте количество ядер х1, чем количество ядер х2.


          1. alexanster
            17.10.2018 10:49

            Там ещё про много чего нет ни слова. Так что «По вашему утверждению», при добавлении ядер ЛЮБОЙ софт будет лучше работать?


            1. homm
              17.10.2018 15:43

              Там ещё про много чего нет ни слова.

              Откуда вы знаете, вы же дальше не читали.


              Так что «По вашему утверждению», при добавлении ядер ЛЮБОЙ софт будет лучше работать?

              Как вы это придумали? Там же написано «тем мощнее будет процессор», а не «любой софт будет лучше работать». Софт и при увеличении частоты не всегла лучше работает, sleep 60 вообще пофиг на мощность ваших ядер.


              1. alexanster
                17.10.2018 16:47

                Откуда вы знаете, вы же дальше не читали.
                А почему я должен это знать, я, как нормальный человек, читаю последовательно и осознаю прочитанное сразу, а не по завершении чтения.
                sleep 60 вообще пофиг на мощность ваших ядер
                Гениальный пример притянутости за уши, снимаю шляпу. Может ещё расскажете, как на sleep 60 влияет увеличение количества ядер?


                1. homm
                  17.10.2018 18:01

                  Может ещё расскажете, как на sleep 60 влияет увеличение количества ядер?

                  Давайте расскажу. Никак. Но как это опровергает утверждение «чем больше ядер — тем мощнее процессор»?


                  1. alexanster
                    17.10.2018 18:04

                    Т.е. вы подтвердили, что более «мощный» процессор не будет быстрее выполнять программу. ЧТД.


  1. BiosUefi
    16.10.2018 12:03

    Я так понимаю это про Интел, у АМД в настройке мелких нюансов чтото будет отличаться?

    Интересно, спасибо.


    1. Extravert34
      16.10.2018 13:19

      Архитектура системы команд одна — x86, так что всё также


  1. amarao
    16.10.2018 14:10

    Симметричная многопроцессорность? Оглянулся вокруг, не нашёл. Сплошная NUMA.


    1. lorc
      16.10.2018 18:00

      В телефоне посмотрите :) Да и в компьютере тоже. С точки зрения софта — между core и processor — разница небольшая.


      1. amarao
        16.10.2018 18:17

        Смотрю в компьютере.


        [    0.000000] NUMA: Initialized distance table, cnt=2
        [    0.000000] NUMA: Node 0 [mem 0x00000000-0x7fffffff] + [mem 0x100000000-0x47fffffff] -> [mem 0x00000000-0x47fffffff]
        [    0.000000] NODE_DATA(0) allocated [mem 0x47ffd5000-0x47fffffff]
        [    0.000000] NODE_DATA(1) allocated [mem 0x87ffd4000-0x87fffefff]
        [    0.000000] Zone ranges:
        [    0.000000]   DMA      [mem 0x0000000000001000-0x0000000000ffffff]
        [    0.000000]   DMA32    [mem 0x0000000001000000-0x00000000ffffffff]
        [    0.000000]   Normal   [mem 0x0000000100000000-0x000000087fffffff]
        [    0.000000]   Device   empty
        [    0.000000] Movable zone start for each node
        [    0.000000] Early memory node ranges
        [    0.000000]   node   0: [mem 0x0000000000001000-0x000000000009bfff]
        [    0.000000]   node   0: [mem 0x0000000000100000-0x0000000059dfcfff]
        [    0.000000]   node   0: [mem 0x0000000061e05000-0x000000006c5cefff]
        [    0.000000]   node   0: [mem 0x000000006f7ff000-0x000000006f7fffff]
        [    0.000000]   node   0: [mem 0x0000000100000000-0x000000047fffffff]
        [    0.000000]   node   1: [mem 0x0000000480000000-0x000000087fffffff]
        ...
        [    0.000000]   node   1: [mem 0x0000000480000000-0x000000087fffffff]
        [    0.000000] Initmem setup node 0 [mem 0x0000000000001000-0x000000047fffffff]
        [    0.000000] On node 0 totalpages: 4080995
        [    0.000000] Initmem setup node 1 [mem 0x0000000480000000-0x000000087fffffff]
        [    0.000000] On node 1 totalpages: 4194304
        ...
        [    0.000000] mempolicy: Enabling automatic NUMA balancing. Configure with numa_balancing= or the kernel.numa_balancing sysctl
        [    1.388022] pci_bus 0000:00: on NUMA node 0
        [    1.520013] pci_bus 0000:17: on NUMA node 0
        [    1.652026] pci_bus 0000:3a: on NUMA node 0
        [    1.725399] pci_bus 0000:5d: on NUMA node 0
        [    1.792512] pci_bus 0000:80: on NUMA node 1
        [    1.888016] pci_bus 0000:85: on NUMA node 1
        [    2.020032] pci_bus 0000:ae: on NUMA node 1
        [    2.089461] pci_bus 0000:d7: on NUMA node 1

        Где вы тут SMP нашли? Я могу порыться в старых компьютерах, и в районе Core2 найти SMP, но с тех пор...


        1. lorc
          16.10.2018 18:25

          Ну тогда в телефоне :)

          Или вот, моя машина:

          [ 0.000000] ACPI: Local APIC address 0xfee00000
          [ 0.000000] Setting APIC routing to Xen PV.
          [ 0.000000] NUMA turned off


          lorc:rumprun-x86/ (master?) $ cat /proc/cpuinfo
          processor : 0
          vendor_id : GenuineIntel
          cpu family : 6
          model : 94
          model name : Intel(R) Core(TM) i7-6700 CPU @ 3.40GHz


  1. BiosUefi
    16.10.2018 14:19

    >> начали ставить новые схемы: APIC и ACPI. Давайте поговорим о первом.

    А о втором(ACPI) будет позже или как? Больно интересно каким боком ACPI имеет отношение к SMP.


    1. Extravert34
      16.10.2018 15:36

      Вы можете найти таблицу MADT (APIC) в ACPI

      Информация, требуемая для инициализации процессора хранится в ACPI таблицах. На данный момент это более предпочтительный способ для работы с многоядерными и многопроцессорными системами. Тот способ, который указан в статье (через нахождение _MP_ структуры в памяти) оставлен для совместимости. Спецификацию от него уже даже на сайте Intel не найти — только на сторонних сайтах.
      А в UEFI так сделать уже вообще не получится


      1. BiosUefi
        16.10.2018 16:28

        >>таблицу MADT (APIC) в ACPI

        Точно, есть там Multiple APIC Description Table, давненько туда не заходил.
        И судя по количеству присутствующих там записей, LocalAPIC есть не только у каждого железного ядра, но и на HT потоки, оные LocalAPIC имеются.

        ПС если вы так глубоко в теме, может знаете почему в Win7/8/10 менеджер устройств в прерываниях показывает такое дикое количество ISA IRQ десятки и даже сотни? А PCI имеют странность быть минусовыми (0xFFFFFF9A-0xFFFFFFFF) сотнями?


        1. mrlolthe1st Автор
          16.10.2018 16:34

          Скоре всего шинда реассигнит IRQ девайсов, из-за этого, возможно, показывает такое дикое количество.


  1. Breslavets
    16.10.2018 15:39

    Хорошо написано, но кто не в теме, тому может показаться, что многозадачность как-то связана с прерываниями… В то время как аппаратные прерывания призваны обрабатывать прерывания от оборудования, чтобы не опрашивать «железо» постоянно, оборудование само вызывает прерывание…
    Мало отзывов, видимо новое поколение не изучает глубоко железо.
    Сразу, наверное жава-быдлокодить учат.


  1. Extravert34
    16.10.2018 15:43

    А пример реализации SMP можно посмотреть например в операционке JOS, которая используется для обучения в MIT. Файлы mpconfig.c и mpentry.S github.com/lewischeng-ms/mit-jos/tree/master/kern


  1. lorc
    16.10.2018 18:12

    В ARM в общем, все то же самое. Только вместо APIC — GIC (Generic Interrupt Controller), который состоит из глобального Distrubutor'а и нескольких CPU Interfaces (по одному на ядро). Запуск secondary ядер — платформозависимая процедура, и чтобы программисты не мучились, во всех современных системах происходит единообразно — через обращение к firmware.

    Вообще, x86 выглядит очень запутанной и местами нелогичной из-за огромного legacy. ARM не стесняются периодически дропать поддержку старых механизмов, поэтому архитектура у них проще и стройнее.