Добавить в корзинуПозвонить
Найти в Дзене
Триалогия

Пишем операционную систему Триалогия - Сетевые карты: E1000, Realtek, PCnet, кольца дескрипторов и DMA

В конце прошлой части остался один вопрос. Как пакет данных проходит путь от сетевого кабеля до NetworkManager? Ответ занимает не одну страницу, потому что начинается он у самого железа. Именно сетевая карта превращает электрические сигналы в поток байтов и выполняет обратную операцию. Однако одной микросхемы недостаточно. Операционная система должна понимать особенности конкретного чипа, а значит ей необходим драйвер. С него и начинается разговор о сетевой подсистеме. Система поддерживает четыре Ethernet-контроллера. Реально один и три начатых но не доведенных до ума. Почему? Об этом в честной части. Их внутренняя архитектура заметно отличается, хотя остальная часть ядра почти не замечает этой разницы. Поддержка охватывает Intel E1000, гигабитный Realtek 8169, более старый Realtek 8139 и AMD PCnet. Наиболее развитым среди них является E1000. Драйвер умеет определить модель контроллера, считывает аппаратный MAC-адрес из EEPROM и сразу подключает устройство к сетевому мосту. Остальные р
Оглавление

Сетевые карты - где пакет отправляется в путь

В конце прошлой части остался один вопрос. Как пакет данных проходит путь от сетевого кабеля до NetworkManager? Ответ занимает не одну страницу, потому что начинается он у самого железа. Именно сетевая карта превращает электрические сигналы в поток байтов и выполняет обратную операцию. Однако одной микросхемы недостаточно. Операционная система должна понимать особенности конкретного чипа, а значит ей необходим драйвер. С него и начинается разговор о сетевой подсистеме.

Четыре карты, один язык

Система поддерживает четыре Ethernet-контроллера. Реально один и три начатых но не доведенных до ума. Почему? Об этом в честной части. Их внутренняя архитектура заметно отличается, хотя остальная часть ядра почти не замечает этой разницы.

Четыре драйвера и их общий интерфейс
Четыре драйвера и их общий интерфейс

Поддержка охватывает Intel E1000, гигабитный Realtek 8169, более старый Realtek 8139 и AMD PCnet. Наиболее развитым среди них является E1000. Драйвер умеет определить модель контроллера, считывает аппаратный MAC-адрес из EEPROM и сразу подключает устройство к сетевому мосту. Остальные решения устроены проще. Особенно выделяется старый 8139: вместо полноценного кольца дескрипторов он использует обычный линейный буфер.

Несмотря на различия, каждый драйвер предоставляет одинаковый набор операций через интерфейс netcard_ops. Он содержит лишь то, что действительно требуется ядру: инициализацию, отправку пакета, проверку состояния линии и обработку прерываний.

typedef struct {

bool (*init)(netcard_device_t* dev, void* pci_info);

NetcardError (*send_packet)(netcard_device_t* dev, const void* data, uint32_t len);

bool (*get_link_status)(netcard_device_t* dev);

void (*irq_handler)(netcard_device_t* dev);

const char* name;

} netcard_ops_t;

Такой подход уже встречался в подсистеме хранения данных и в UDM. Каждый драйвер заполняет собственную таблицу ops, после чего проходит регистрацию. Код верхнего уровня работает лишь с абстрактной "сетевой картой №0" и совершенно не интересуется, какой контроллер скрывается под этим интерфейсом. Поэтому поддержка нового чипа обычно сводится к созданию ещё одной таблицы ops. Всё остальное продолжает работать без изменений.

Кольцо, за которым двое

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

Кольцо дескрипторов и как работает DMA
Кольцо дескрипторов и как работает DMA

Представь кольцо примерно из 256 элементов. Каждый дескриптор хранит адрес буфера в памяти, его размер и несколько битов состояния. Как только по кабелю приходит пакет, контроллер самостоятельно записывает данные в следующий свободный буфер. Процесс обходится без участия процессора, поскольку используется DMA - прямой доступ к памяти. Затем карта помечает дескриптор флагом DONE и вызывает прерывание.

После этого управление получает драйвер. Он проходит по кольцевому буферу и забирает все подготовленные пакеты.

while (desc[head].status & DONE) {
take_packet(desc[head].buf, desc[head].len);
return_slot(head); // следующий пакет может снова прийти сюда
head++;
}

Передача работает почти зеркально. Драйвер копирует пакет в буфер, подготавливает дескриптор и уведомляет контроллер записью в специальный регистр. Карта отправляет данные в сеть, освобождает место и вновь сообщает об этом через прерывание. Именно кольцо дескрипторов определяет весь interface между программой и оборудованием. Перед уведомлением контроллера обязательно выполняют барьер памяти memory fence. Благодаря этому аппаратная часть увидит дескриптор лишь после полной записи содержимого буфера.

От кабеля наверх к NetworkManager

Теперь остаётся проследить дальнейший путь принятого пакета. Именно здесь проявляется одно из самых важных архитектурных решений всей сетевой подсистемы.

Путь от прерывания до NetworkManager
Путь от прерывания до NetworkManager

Обработчик прерывания выполняет только самые необходимые действия. Он считывает и очищает статус контроллера, перебирает готовые дескрипторы, передаёт каждый пакет в очередь и завершает обработку. На этом его работа заканчивается. Разбор Ethernet-кадров отсутствует. IP, TCP и остальные протоколы тоже обрабатываются в другом месте, а точнее в NetworkManager.

Причина вполне практична. Пока процессор обслуживает прерывание, выполнение остальных задач полностью останавливается. Чем дольше продолжается такая работа, тем выше задержки во всей системе. Поэтому тяжёлую обработку специально переносят как можно дальше от аппаратного уровня. После прохождения через мост KNP пакет попадает в разделяемую память, а затем ядро будит NetworkManager, работающий в ring 3. Только там начинается полноценный разбор каждого сетевого уровня. Исходящий трафик проходит тот же маршрут в обратном направлении. Именно здесь проходит граница между двумя мирами: драйвер и контроллер остаются внутри ядра, тогда как весь стек сетевых протоколов работает уже в пользовательском пространстве.

Честное место, она же правда жизни

Разработка драйверов сетевых карт принесла немало неприятных сюрпризов. Именно здесь проявились самые упрямые аппаратные ошибки за всё время работы над системой.

Особенно долго пришлось искать конфликт, связанный с адресными регистрами PCI. Во время обнаружения устройств код временно записывает специальное значение в BAR, а затем считывает результат, чтобы определить размер области памяти. Такая процедура безопасна только для неактивного оборудования. Если выполнить её над контроллером, который уже передаёт данные через DMA, запись вмешается в его работу. Однажды именно так удалось случайно нарушить работу активной E1000. Долгое время подозрение падало на сетевой драйвер, хотя настоящая причина скрывалась в совершенно безобидном механизме поиска устройств.

Эмуляторы тоже добавляют собственные особенности. VirtualBox, например, удерживает управление блоком передачи ещё некоторое время после аппаратного сброса. Из-за этого драйвер вынужден сделать небольшую паузу перед чтением состояния линии. Старый Realtek 8139 приносит другую проблему. Линейный буфер требует очень аккуратного расчёта момента перехода в его начало. При длительном переполнении ошибка в этой логике приводит к потере пакетов.

В целом реализация получилась прагматичной. Аппаратное вычисление контрольных сумм отсутствует. Несколько очередей передачи тоже пока не поддерживаются. Каждый драйвер проходил серьёзную проверку главным образом на одном представителе своего семейства. Кроме того, в новой интеграции KNP ещё остаётся чувствительный участок, где код переключает буферы приёма, хотя контроллер продолжает выполнять операции DMA.

Стоит объяснить, почему сейчас полностью работает только E1000, хотя выше перечислены четыре драйвера. Разработка начиналась с самых простых контроллеров. Такой выбор позволил быстрее получить первый результат и проверить архитектуру всей сетевой подсистемы. Затем появились более сложные драйверы, и в итоге все четыре карты успешно передавали пакеты внутри ядра. После этого проект пережил один из крупнейших этапов перестройки. NetworkManager переехал в ring 3, системные вызовы уступили место механизму KCall, а вместе с ними изменились и соседние подсистемы. Эта переработка нарушила работу всех существующих сетевых драйверов. Восстанавливать пришлось почти всё заново. В первую очередь я вернул в строй E1000, поскольку именно этот контроллер получил самое широкое распространение и служит основной платформой для разработки. Остальные драйверы пока остались в нерабочем состоянии. Их аппаратная база давно устарела, поэтому тратить время на восстановление старых решений не вижу особого смысла. Если в будущем потребуется расширить список поддерживаемого оборудования, гораздо разумнее направить усилия на современные сетевые контроллеры с полноценной поддержкой DMA, чем возвращаться к устройствам предыдущих поколений.

Что дальше

В этой главе мост KNP упоминался уже несколько раз. Именно через него пакет покидает пространство ядра и переходит в пользовательскую часть системы. Он соединяет драйверы с сетевым стеком и одновременно старается сократить количество копирований до минимума. В следующей части я подробно покажу устройство этого механизма и разберу его работу шаг за шагом.

Было бы интересно увидеть ваши комментарии и улучшить статьи.

Предыдущая статья · Содержание · Следующая статья

*Система не стоит на месте, поэтому в дальнейшем тексты могут не совпадать с реальным положением