Перша стаття про драйвери була вже зовсім ввідною, і мені подумалося, що її не можна не доповнити розповіддю про те, як влаштовані драйвери більш сучасних пристроїв.
Для початку введемо визначення 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 може вміти або не вміти рахувати і вставляти в пакети, що надсилаються, контрольну суму.
Неважко бачити, що стандарт virtio описує досить типовий узагальнений драйвер bus master пристрою: ми передаємо «пристрою» запит з адресою в пам'яті і параметрами запиту введення-виведення, решта відбувається асинхронно.
На тлі вищесказаного говорити про DPC вже не так актуально, але раз в коментарях виникла дискусія - дам короткий опис.
У деяких ОС (Фантом «змалював» це з NT, звідки вони змалювали - не знаю) існує штатна підтримка запуску коду всередині «легких» ниток - Deferred Procedure Call. Це дозволяє знизити час знаходження драйвера в перериванні: хендер переривання лише фіксує подію і, як максимум, зчитує з пристрою статус - один регістр. Решта робиться в DPC, яка швидко активується і доробляє почате.
Відверто сказати, сенсу в цьому не так вже й багато - простіше запустити в драйвері його власну нитку високопріоритетну і робити введення-висновок з неї. Однак, можуть бути варіанти. Ви можете вибрати групу пріоритетів DPC і гарантувати їм пріоритет завжди вищий, ніж у нитки. Можна забезпечити надшвидкий шедулінг і передавати таким ниткам процесор прямо відразу на виході саме з того переривання, яке цей DPC запросило, знизивши латентність.
Окремо відзначимо, що з DPC можна багато з того, що в перериванні не можна. Що саме - залежить від ОС. У Фантомі всередині DPC можна все, в тому числі і заснути на місяць. Гріх, але - можна. NT, ЕМНІП, все ж, якось обмежує права DPC (тобто, це не звичайні нитки), але деталей я не пам'ятаю.
На цьому я відчуваю, що виконав свій обов'язок щодо драйверів.:)
З 1 травня вас, хоч і із запізненням.:)
