KNP: мост, через который пакет покидает ядро
В прошлой части я показал, как сетевая карта записывает пакет в память через DMA, а драйвер получает готовые данные. На этом путь пакета не заканчивается. Превращением набора байтов в Ethernet, IP и TCP занимается уже не ядро. Эту работу выполняет стек протоколов внутри NetworkManager, который работает в ring 3. Между драйвером и сервисом проходит граница, и каждый пакет обязан пересечь её. Именно такую задачу решает KNP (Kernel Network Proxy) - сетевой прокси ядра.
Причина разделения совпадает с логикой оконного менеджера. Если стек протоколов завершит работу аварийно, ядро продолжит функционировать. Сам сервис легко перезапустить и отладить как обычное приложение. За такую архитектуру приходится платить дополнительным обменом между ядром и ring 3. При неудачной реализации именно этот участок способен превратиться в узкое место.
Одна память для трёх участников
Самый простой вариант выглядел бы так: драйвер копирует пакет в буфер ядра, затем данные переходят в ещё один буфер, после чего отправляются через границу в userspace. Каждый пакет потребовал бы три копирования. При высокой нагрузке процессор быстро потерял бы значительную часть времени только на перенос данных.
KNP использует другой подход. Копирование вообще отсутствует (zero-copy).
В основе лежит общий пул примерно из 256 буферов по два килобайта каждый. Память одновременно отображается в адресное пространство ядра и NetworkManager. Оба участника работают с одними и теми же физическими страницами.
Карта тоже использует этот пул. DMA записывает принятые пакеты непосредственно туда, без промежуточных областей памяти. Именно поэтому в предыдущей части драйвер E1000 переключал приёмный DMA сразу на пул KNP.
В результате пакет существует в памяти только в единственном экземпляре. Сетевая карта записывает данные, ядро контролирует состояние буферов, а NetworkManager читает содержимое напрямую. Вместо перемещения самих данных остаётся определить только владельца конкретного слота.
Четыре кольца управления
Если пакеты постоянно лежат в одном месте, между ядром и сервисом перемещаются уже не они, а небольшие сообщения. Для обмена служат четыре кольцевых буфера в общей памяти.
- Первое кольцо передаёт уведомления от ядра в NetworkManager. После завершения DMA ядро помещает запись вида "слот 7, длина 1500 байт" и будит сервис.
- Второе кольцо работает в обратном направлении. NetworkManager сообщает, что обработка завершена и слот можно вернуть сетевой карте.
- Через третье кольцо сервис передаёт команды на отправку. Ядро получает номер слота, выбирает нужный сетевой интерфейс и передаёт пакет NIC.
- Четвёртое кольцо обслуживает управляющий обмен. Через него проходят сообщения о подключении кабеля, аппаратном адресе интерфейса, изменении сетевых параметров и другие служебные события.
Такое разделение упрощает архитектуру. Крупные пакеты постоянно находятся внутри общего пула, тогда как между компонентами перемещаются лишь компактные записи с номером слота и дополнительной информацией. Для реализации колец используются те же lock-free структуры с порядковыми номерами, которые я уже разбирал в главе об IPC.
Кто владеет буфером
Zero-copy избавляет систему от копирования, однако требует строгого контроля над владением памятью. Без такого механизма карта способна начать новую запись, пока NetworkManager ещё читает предыдущий пакет. Обратная ситуация тоже недопустима.
Для решения этой задачи KNP использует небольшую машину состояний.
Каждый слот может находиться только в одном состоянии.
NIC_OWNED означает полный контроль со стороны сетевой карты. DMA имеет право записывать данные, остальные участники доступ не получают.
После завершения приёма аппаратные interrupts вызывают обработчик ядра. Затем слот переходит в состояние NM_READABLE. Управление получает NetworkManager, а карта прекращает любые операции с этим буфером.
Когда сервис завершает обработку и отправляет сообщение через кольцо возврата, слот получает состояние KERNEL_FREE. Ядро возвращает его сетевой карте, после чего цикл начинается заново.
В любой момент времени доступ принадлежит только одному участнику. Переход между состояниями проходит исключительно через кольца сообщений, поэтому мьютексы и другие блокировки вокруг буфера не нужны. Само состояние определяет владельца, а кольцо фиксирует передачу управления. Такой подход составляет цену zero-copy: копирование исчезает, зато учёт владельцев требует абсолютной точности.
Честное место
Здесь меня тоже пожидали коварные баги.....
После запуска системы всё выглядит просто. Пул создаётся, карта записывает пакеты, сервис читает данные. Сложности появляются во время переключения приёмного DMA со старого буфера на пул KNP. В этот момент аппаратная передача ещё может продолжать работу со старой областью памяти, хотя драйвер уже программирует новый адрес.
По этой причине после остановки приёма код выдерживает небольшую паузу. За это время аппаратные указатели успевают завершить переход. Такой участок остаётся одним из самых чувствительных мест, потому что аппаратный тайминг почти соприкасается с логикой программного обеспечения.
Существуют ещё две потенциальные проблемы (ха-ха пока никто не видит - больше!).
Первая возникает при переполнении кольца уведомлений. Если NetworkManager не успевает обрабатывать входящий поток, ядро сначала начинает мягко отбрасывать пакеты после достижения заданного порога, а затем полностью прекращает приём новых сообщений при полном заполнении кольца. Такой выбор вполне осознан: потеря части трафика гораздо безопаснее полной остановки системы.
Вторая связана с аварийным завершением NetworkManager. Ядро удаляет старые кольца, запускает новый цикл приёма и одновременно ограничивает частоту перезапусков. Благодаря такому механизму постоянно падающий сервис не способен превратить систему в бесконечную последовательность рестартов.
Что дальше
Теперь пакет добрался до NetworkManager в ring 3. Пока перед нами только последовательность байтов без какого-либо смысла. Распознать кадр Ethernet, запрос ARP или IP-пакет с TCP может лишь стек протоколов, который работает поверх KNP.
Следующая часть посвящена именно стеку TCP/IP в ring 3. Там набор байтов постепенно превращается в полноценные сетевые протоколы, слой за слоем.
Было бы интересно увидеть ваши комментарии и улучшить статьи.
◀ Предыдущая статья · Содержание · Следующая статья ▶
*Система не стоит на месте, поэтому в дальнейшем тексты могут не совпадать с реальным положением