Драйвери розумного або віртуального заліза

Перша стаття про драйвери була вже зовсім вступної, і мені подумалося, що її не можна не доповнити розповіддю про те, як влаштовані драйвери більш сучасних пристроїв.

Для початку введемо визначення bus master: пристрій, здатне бути не тільки керованим, але і провідним на шині комп'ютера. Тобто — не тільки відповідати на транзакції вводу-виводу, ініційовані процесором, але і самостійно ініціювати їх — за власною ініціативою «ходити» в пам'ять.

Історія таких пристроїв йде корінням в поняття DMA: ще в часи прабатька мікропроцесорів, мікропроцесора 8080 (КР5080ИК80), з'явилося розуміння, що процесор добре б розвантажити від рутинної операції перетягування байтиков між пристроями в-в і пам'яттю.

Контролер DMA (Direct Memory Access) був зовнішньої по відношенню до пристрою введення-виведення, підсистему, яку потрібно було явно запрограмувати — встановити тип операції (пишемо в пам'ять, читаємо з пам'яті, копіюємо пам'ять-пам'ять), адресу(а) пам'яті, і пр. Власне, я абсолютно несправедливо пишу про це в минулому часі — все це цілком існує і зараз, наприклад, в мікроконтролерах.

Вже в режимі DMA робота драйвера виглядає сущственно інакше — від драйвера потрібно не виконувати введення-виведення, а підготувати налаштування для пристрою, активувати його, дочекатися переривання по закінченню вводу-виводу, і перевірити успішність операції. Все сказане в попередній статті вірно і для DMA пристроїв, але в доповнення до сказаного, драйвер повинен розуміти схему взаємодії пристрою і DMA контролера, а іноді і явно аллоцировать і налаштовувати контролер: якщо в старих пристроях прив'язка порту вводу-виводу до контролера DMA робилася фіксовано, то зараз у багатьох випадках можливий повний роутинг або вибір каналу DMA з 2-4 варіантів.

Окремо слід зауважити, що сама ініціація черговий транзакції вводу-виводу може бути автоматичною (DMA лупить з максимально можливою швидкістю), з автоматичною настройкою темпу (щоб не з'їсти всю пропускну здатність шини) або по події.

При цьому подією в розвинених системах може бути переривання, просто зміна стану ніжки мікроконтролера, або ж джерелом події може бути інший пристрій. Наприклад, таймер. Це дозволяє сполучити воєдино ЦАП, DMA engine і таймер так, щоб подача чергового байта в ЦАП відбувалася із заданою (таймером) частотою. Є й інші варіанти агрегування пристроїв, наприклад, включення одного каналу DMA по закінченні роботи іншого. Без залучення уваги процесора.

Доречно також сказати, що контролери DMA іноді вміють явно сполучати кілька каналів, щоб забезпечувати безперервність потоку даних по закінченні роботи одного каналу запускається другий і генерується переривання, по якому процесор знову завантажує роботою перший канал — для того ж ЦАП це може бути життєво важливо.

Повернемося зі світу контролерів у «дорослі» машини. Більшість сучасних підсистем вводу-виводу вже не базуються на зовнішньому DMA, а мають його аналог прямо всередині.

Це пристрої з режимом «майстер шини», bus master.

Найпростіше представити їх саме як пристрої, що мають вбудований в себе, особистий і оптимально влаштований контролер DMA.

Зазвичай такі пристрої управляються через дерево дескрипторів в пам'яті: у пристрої є спеціальний регістр, в який процесор поміщає адресу структури в пам'яті, яка містить завдання для контролера. Або, частіше, масив або список структур з такими завданнями. Контролер самостійно читає з пам'яті завдання і виконує їх крок за кроком. Завдання, як правило, складається з ідентифікатора операції, адреси пам'яті, де брати дані і додаткових параметрів, необхідних для виконання операції. Наприклад: { запис на диск, адреса на диску, адреса буфера пам'яті }. Так влаштовані сучасні контролери всього: диска, USB, мережевого інтерфейсу.

Крім структури дескрипторів для такого пристрою потрібні ще інструменти для обміну подіями: процесор повинен вміти повідомити, що змінив чи доповнив дескриптори, а пристрій — що закінчило введення-виведення частково або повністю. Друге виконується, природно, через переривання, а для першого часто застосовується регістр (дверний дзвінок — doorbell), в який процесор «стукає», щоб звернути увагу пристрою на зміни.

При цьому є небезпека змінити саме те, що пристрій зараз обробляє, що накладає додаткові обмеження на структуру драйвера.

Окремо в цьому ряду стоять virtio пристрою. З'явилися вони як наслідок переможної ходи гіпервізора по світу. Традиційно гіпервізор пропонував гостьової ОС деякий набір віртуальних пристроїв, які копіювали те чи інше популярне фізичний пристрій. Відому мережеву карту або дисковий контролер. Але емулювати «залізне» пристрій важко, незручно і клопітно — найчастіше, доводиться підтримувати абсолютно не потрібні у віртуальному світі властивості, при цьому для цілей віртуалізації структура реального залізного пристрої, зазвичай, неоптимальна.

Це і навело авторів на те, щоб спроектувати завідомо віртуальні пристрої, які ніколи не будуть (never say never © 007) реалізовані в залозі і потрібні виключно для спілкування гіпервізора і гостьової ОС. Вони створені так, щоб для великої кількості різнотипних пристроїв можна було реалізувати однакову загальну інфраструктуру, як в ядрі гостьової ОС, так і в гипервизоре.

Подивитися реалізацію

По суті драйвер virtio — це транспорт пакетів із запитами та відповідями між гостьової ОС і гіпервізором. Вміст пакета специфічно для типу драйвера і його режиму. Наприклад, для мережевої карти це адреса пакету ethernet, а для диска — scatter-gather дескриптор із зазначенням типу дискової операції та адреси на диску.

У драйверах virtio ядро заповнює пакет і просить узагальнений драйвер vitio «передати» його пристрою. Тепер поки пристрій не «передасть» пакет назад, чіпати його не можна. І навпаки, якщо пристрій вміє робити введення з зовнішньої ініціативи, йому потрібно передати кілька порожніх (підготовлених для читання) пакетів — по мірі надходження даних (наприклад, входять мережевих повідомлень) пакети будуть заповнюватися і передаватися тому, ядру гостьової ОС.

Крім того, стандарт virtio підтримує можливість стандартним чином ядру і пристрою домовлятися про режим робіт і підтримуваних функцій. Наприклад, мережевий драйвер virtio може вміти або не вміти рахувати і вставляти у відправляються IP-пакети контрольну суму.

Неважко бачити, що стандарт virtio описує досить типовий узагальнений драйвер bus master пристрою: ми передаємо «пристрою» запит з адресою в пам'яті і параметрами запиту вводу-виводу, інше відбувається асинхронно.

На тлі вищесказаного говорити про DPC вже не так актуально, але раз в коментарях виникла дискусія — дам короткий опис.

У деяких ОС (Фантом «змалював» це з NT, звідки вони змалювали — не знаю) існує штатна підтримка запуску коду всередині «легких» ниток — Deferred Procedure Call. Це дозволяє зменшити час знаходження драйвера в перериванні: хендлер переривання лише фіксує подію і, як максимум, зчитує з пристрою статус — один регістр. Інше робиться в DPC, яка швидко активується і доробляє розпочате.

Відверто сказати, сенсу в цьому не так вже й багато — простіше запустити в драйвері його власну нитку высокоприоритетную і робити введення-виведення з неї. Однак, можуть бути варіанти. Можна виділити DPC групу пріоритетів і гарантувати їм пріоритет завжди більш високий, ніж у нитки. Можна забезпечити надшвидкий шедулинг і передавати таким ниткам процесор прямо відразу на виході саме з того переривання, що цей DPC запросило, знизивши латентність.

Окремо зазначимо, що з DPC можна багато чого з того, що в перериванні не можна. Що саме — залежить від ОС. В Фантоми всередині DPC можна все, в тому числі і заснути на місяць. Гріх, але — можна. NT, ЕМНІП, все ж, якось обмежує права DPC (тобто, це не звичайні нитки), але подробиць я не пам'ятаю.

На цьому я відчуваю, що виконав свій обов'язок по відношенню до драйверів. :)

З 1 травня вас, хоч і з запізненням. :)

Джерело: Хабрахабр

0 коментарів

Тільки зареєстровані та авторизовані користувачі можуть залишати коментарі.