Category Archives: Linux

Прерывания или что такое MSI и MSI-X

Недавно довелось разбираться с проблемой производительности сетевой подсистемы Linux. Как и предполагалось проблема была связана с обработкой сетевых прерываний. Однако выяснилось что термин MSI-X знаком не всем и далеко не все понимают преимущества этой технологии и покупают карты где она не поддерживается.
Более того, при беглом осмотре выяснилось что далеко не все вендоры продают адаптеры с поддержкой MSI-X.
Но начну я как всегда издалека.

Обработка hardware прерываний всегда была “дорогой” операцией. “Дороговизна” связана как с большим количеством прерываний, так и со сложностями по распределению их обработки.
Проблема с количеством прерываний в Linux была частично решена с появлением NAPI: вместо прерывания по каждому событию, теперь драйвер сетевой карты самостоятельно забирает пришедшие пакеты из очереди карточки.Полноценная же возможность по распределению прерываний по cpu появилась лишь с приходом PCI 3.0 и MSI-X.Но сначала немного теории.

Обработка PCI прерываний
Изначально PCI шина была ограничена 4 линиями, выделенными под прерывания. Теоретически один PCI адаптер может использовать все 4 прерывания, но так-как эти прерывания являются общими для всех карт, то большинство PCI адаптеров использую лишь 1 прерывание.
Из-за того что PCI прерывания были общими для всех устройств на шине, для корректной обработки прерывания ОС была вынужденна запускать все обработчики данного прерывания.
Если же PCI устройство писало в память, то возникала проблема синхронизации и обработчик прерывания был вынужден проверять что данные действительно записаны в память. Это было необходимо т.к. прерывание посылалось по выделенным линиям и могло быть отправлено одновременно с операцией записи а значит и “прийти” на процессор до того как данные реально будут записаны в память. Проблема синхронизации решалась на уровне драйверов: необходимо считать регистр, тем самым убедиться что операция записи завершена. Это добавляло накладных расходов для обработчика прерываний и не делало его более производительным.
Все эти проблемы был призван решить MSI.

MSI
Если ранее за каждым устройством закреплялся свой номер запроса на прерывание (IRQ), то прерывание в случае MSI это запись по заранее определенному адресу. Благодаря этому отпадает необходимость в выделенных линиях для обработки прерываний и упрощается схема самого адаптера. Кроме того решается проблема с синхронизацией т.к. операция записи в память и прерывание теперь используют общий канал и более не могут прийти на процессор одновременно.
MSI позволяет использовать десятки прерываний. Точнее до 32. Однако более 16 прерываний использовались крайне редко.

MSI-X
В PCI 3.0 появилось расширение MSI : MSI-X позволило увеличить кол-во прерываний от одного устройства до 2048. Правда использование более 64 я не встречал. Но что более важно, MSI-X использует различные адреса записи для каждого прерывания, что позволило назначить обработку отдельный прерываний на определенный процессор.

APIC и распределение прерываний между CPU
APIC (Advanced Programmable Interrupt Controller) отвечает за распределение прерываний по процессорам. APIC предусматривает несколько различных политик распределения прерываний. Так первоначально все прерывания обрабатывались одним ( нулевым ) ядром.
За распределение прерываний по CPU отвечает чип IO-APIC. Этот чип может работать в 2 режимах :
fixed/physical mode: прерывания от каждого конкретного устройства обрабатываются конкретным CPU
logical mode: прерывания одного устройства могут обрабатываться несколькими CPU.
Применение logical mode в большинстве случаев не оправдано т.к. возникают проблемы cache miss’ов в случае работы с одной сущностью несколькими CPU ( например обработке прерываний несколькими CPU для одной tcp сессии ).
Попробую описать эту проблему детальнее. Итак у вас прерывания разбрасываются round-robin по нескольким CPU. Когда приходит первый пакет tcp сессии и генерируется прерывание, то оно обрабатывается процессором X. Второе прерывание о пакете, в рамках того же tcp соединения попало на процессор Y. Так как данных о данной сессии до этого на процессоре Y не было то никаких данных о ней CPU кеше нет Имеем cache miss на процессоре Y. При этом данные были в кеше процессора X и если бы прерывание попало на процессор X то обо было бы обработано быстрее. Теперь же данные о этой tcp сессии удаляются из кеша процессора X для обеспечения когерентности кешей. Однако следующее прерывание о новом пакете в рамках этой же tcp сессии снова попадает на процессор X и мы снова получаем cache miss…
Получается что с одной стороны выгодно распределять прерывания по всем CPU, чтобы CPU не было узким место и была равномерная загрузка CPU.
С другой стороны распределение прерываний по разным CPU безо всякой логики повышает кол-во cache miss. А это “дорогая” операция также оказывает влияние на производительность.

IO-APIC и MSI
На самом деле через IO-APIC будут распределяться либо прерывания “старого” типа (INT), либо MSI/MSI-X прерывания, для устройств, драйвера которых не реализовали поддержку MSI/MSI-X.
Как я описал выше MSI/MSI-X генерирует прерывания, посылая операцию записи с определенным адресом. Соответственно если драйвер не поддерживает MSI/MSI-X то будет использоваться некий стандартный адрес и прерывания будут попадать на IO-APIC и далее уже распределяться по LAPIC ( Local APIC ) разных CPU.
В случае же если в драйвере реализована поддержка MSI/MSI-X то при инициализации адрес может быть изменен, а значит прерывания будут попадать на определенный CPU в обход IO-APIC.

Так в чем же плюс MSI-X?
Помните предыдущий пример про tcp сессию,А что если бы сетевая карта “знала” о tcp сессиях и генерировала бы прерывания для того CPU, который и занимается обработкой данной tcp сессии? Это позволило бы решить и проблему с распределением прерываний по нескольким CPU при этом обеспечивая локальность обработки пакетов определенного типа.
Именно этого и позволяет добиться MSI-X.

Теперь о командах. lspci -t показывает список PCI устройств и мостов.
После чего в файле /sys/bus/pci/devices/*/msi_bus можно увидеть включена ли поддержка MSI: 1 – включена 0 – выключена.
Распределение прерываний по ядрам CPU вы можете посмотреть в файле /proc/interrupts . Там же можно увидеть используется ли IO-APIC для распределения прерываний, или же прерывания идут напрямую через MSI.
Для каждого прерывания есть директория в /proc/irq/ . В файле smp_affinity находится bitmap, который определяет каким ядром будет обрабатываться это прерывание. Меняя этот bitmap мы можем менять привязку прерываний к ядрам процессора.

О том как обработка MSI и MSI-X прерываний реализована в Solaris отлично написано здесь : в блоге Saurabh Mishra
и частично в блоге Charlie Chen здесь и здесь