Pull to refresh

Comments 13

Насчёт поллинга.

> Но, конечно, в целом это никуда не годная (кроме вышеприведённого случая:) модель.

А как быть с Linux NAPI? Там наоборот отказались от прерываний из-за того, что поллингом дешевле вычитывать большие объёмы трафика, чтобы не сжигать процессорное время на обработку прерываний от миллионов пакетов.
Вопрос соотношения времени обмена данными и простоя. И — цены свободного ядра процессора. Плюс несколько повышается реактивность. Прерывание тоже требует времени.

Был такой процессор — propeller, если не ошибаюсь — там восемь синхронных ядер и нет прерываний. Весь обмен — поллингом. Принципиально.

Кроме того, в жёстком риалтайме драйвера поллинговые — считается, что иначе невозможно гарантировать время реакции. (Хотя у меня лично есть смутные сомнения на этот счёт.)

В целом — согласен, есть в жизни место поллингу.
Но с миллионами пакетов мне как-то не очевидно: нормальный драйвер на одно прерывание читает все данные, которые есть в буфере железа, или пишет все, которые можно запихать в буфер за один раз. Совсем нормальный вообще делает железу в памяти scatter-gather list и железо само его обходит и делает ввод-вывод чисто аппаратно. В любом случае никто не делает прервание на байт, пакет или сектор — работают пачками.
Возможно, будет место удивлению, но к работе пачками в Линуксе (что в сетевой подсистеме, что в дисковой) пришли совсем недавно, причём, в дисковой — позже, по образу и подобию того, что было сделано в сетевой.
Может у нас с вами разное «недавно» или я не правильно понимаю термин «пачками», но еще когда я писал блочный драйвер для ядра 2.4.32 по LDD-2 (~2001 год выпуска), то уже там были рекомендации и примеры реордеринга очереди запросов (накопил, отсортировал и выплюнул в устройство), если конечно был асинхронный режим.
Чтобы не быть голословным: https://lwn.net/Articles/663879/
Недавно в одном аппаратно-программном проекте на базе как раз ОС РВ (Embox) для моментальной реакции на принятые сетевые пакеты нам пришлось настраивать EMAC в драйвере следующим образом: по-умолчанию прерывания приходили от каждого принятого пакета, по интенсивности приема на лету производилась корректировка контроллера прерываний, чтобы он реагировал не на каждый принятый пакет. Далее, по таймеру, производилась обратная перенастройка контроллера прерываний. Так же приходилось играться и с приоритетами обслуживающих потоков (клиентов).
нормальный драйвер на одно прерывание читает все данные, которые есть в буфере железа

В линуксе есть пара тонких моментов. Один момент в том, что в обработчике аппаратного прерывания никто старается не работать, потому что все прерывания в этот момент запрещены, и если делать что-то тяжёлое — будет заметно. Поэтому вся работа делается в softirq или в выделенном потоке, а между приходом прерывания и началом обработки возникает зазор. И вот тут уже гонка: кто быстрее — поток выгребает пакеты, или сеть набрасывает. Чтобы в этом месте не гоняться драйвер отключает прерывания от устройства, а по окончании обработки пакета проверяет, не пришёл ли ещё один.
Второй момент в том, что большАя часть работы сетевого стека по обработке входящих пакетов в NAPI выполняется синхронно. Т.е. драйвер, вызывая функцию netif_receive_skb может пропихнуть пришедший пакет до выходного интерфейса бриджа или маршрутизатора, если пакет не предназначен локальной машине, а это дополнительная задержка, за время которой может прийти новый пакет.
Ну в линуксе так в некотором смысле и есть. Код одного и того же драйвера может выполняться в несколько потоков:
— кто-то (может и не один) дернул из юзерспейса и мы бежим в контексте пользовательского потока
— одновременно обрабатываем в прерывание в ядерном потоке
— где-то вдруг сработал наш таймаут и второй процессор в другом ядерном потоке хендлит его
— третий процессор обрабатывает аппаратное прерывание от нашего устройства.

При чем, запросы на доступ к устройству (т.е. собственно input/output) могут поступать из любого из этих потоков, а сераилизировать их будет драйвер(а) шины.
запросы на доступ к устройству (т.е. собственно input/output) могут поступать из любого из этих потоков, а сераилизировать их будет драйвер(а) шины

Да нет, драйвер устройства должен сам их сериализовать — никто кроме него не знает семантики его регистров.
И потом, драйвер какой-нибудь PCI участвует только в мэппинге регистров, но не в обмене данными.
Ну в случае каких-то сложных команд — да. А если это, например, драйвер тупого расширителя GPIO, то можно целиком положиться на драйвер шины.
В этом смысле все драйвера multithreaded. Я о порождении нитей внутри драйвера и отработку порождённых драйвером событий внутрь остальной ОС.
Sign up to leave a comment.

Articles