Проблеми встановлення VxD драйверів та як їх вирішувати
Нотатки від першої особи про IT, новини та старості технологій.
Блог → Проблеми встановлення VxD драйверів та як їх вирішувати
Для початку з'ясуємо що таке VxD. Це Windows-драйвера системного рівня (тобто рівня ядра системи), що виконуються в нульовому кільці захисту захищеного режиму і мають усі мислимі та немислимі привілеї (зокрема - право доступу до пристроїв фізично). Дуже багато хто цікавиться тим, як створити та завантажити такий драйвер "штатними засобами". Що ж, питали – відповідаємо! У цій нотатці я торкнуся проблематики встановлення, завантаження, реєстрації VxD-драйвера в системі та деякі слабко документовані питання їх реалізації.
Насамперед - інструментарій. Як мінімум, нам знадобиться Microsoft Driver Development Kit (DDK). Він включає набір файлів заголовків (*.h), бібліотеки імпорту системних модулів (*.lib), вихідні тексти прикладів, файли довідок і кілька спеціалізованих утиліт. Крім того, знадобиться компілятор мови C з Microsoft Visual C/C++. Звичайно, ніхто не заважає використовувати компілятори інших виробників, але тоді вам доведеться самостійно створювати *.lib-файли і досить довго мучитися, щоб *.h-файли DDK транслювалися без лайок.
З чого почати? Потрібно почитати документацію з DDK і коротко усвідомити, як працює ядро Windows взагалі і драйвера (VxD) зокрема. Так, VxD поділяються на два основні класи: статичні (static loadable) і динамічно (dynamic loadable). Основна їх відмінність - у тому, що драйвера, що статично завантажуються, зчитуються в оперативну пам'ять при завантаженні системи і залишаються в ній до завершення роботи Windows (принаймні, я не знаю способу примусового вивантаження таких VxD). Динамічно завантаженідрайвера зчитуються в оперативну пам'ять у міру потреби, і за бажання можуть бути звідти видалені.
Тепер розберемося із класифікацією пристроїв. Вони можуть бути Plug and Play (PnP) і т.зв. legacy ("спадщина" від ранніх ранніх версій). PnP-пристрої автоматично розпізнаються та конфігуруються операційною системою при підключенні, тоді як legacy-пристрої потрібно встановлювати та конфігурувати вручну. PnP-пристроями є всі USB та SCSI-пристрої, модеми, миші, стандартні порти СОМ та LPT, а також практично всі сучасні ISA та PCI-плати. Ну а все інше, що не може бути автоматично визначено та налаштовано засобами Windows, відноситься до legacy-пристроїв (нестандартні COM-порти та навішане на них обладнання, старі ISA-плати, зовнішнє обладнання, що не підтримує специфікацію PnP, а також драйвера, не працюючі безпосередньо з фізичним обладнанням, наприклад – TCP/IP стек).
Ну і останнє зауваження щодо класифікації VxD: у Win9x визначено три типи функціональних драйверів: завантажувачі (loaders), перелічувачі (enumerators) та власне драйвери (drivers). Device loader - майже завжди статичний VxD, його призначення зрозуміло з назви, це завантаження драйверів зазначеного типу. Зазвичай програмісту немає необхідності писати свій завантажувач, можна скористатися одним із стандартних (*IOS - для драйверів файлової системи, *VCOMM -для послідовних та паралельних портів, *CONFIGMG - для PnP-пристроїв).
Enumerator - це штука, яка постійно сидить у пам'яті і дивиться, чи не з'явився новий відомий їй PnP-пристрій. Чи не поділося кудись існувало раніше. У цих ситуаціях enumerator (за допомогою *CONFIGMG - який, за сумісництвом, є ще й конфігуратором PnP-пристроїв)завантажує або вивантажує відповідний драйвер пристрою. Device driver – VxD, який і займається безпосередньо обслуговуванням обладнання. Драйвер пристрою може бути як статичним, так і динамічним (в останньому випадку він завантажується на запит від enumerator'a, прикладної програми або будь-якого іншого VxD).
З рештою теорії можна розібратися самостійно, прочитавши документацію з DDK. Тож переходимо до практики! Навряд чи вам доведеться писати драйвер для пристрою, що вставляється в штатний ISA або PCI-слот комп'ютера - зазвичай такі плати підтримуються виробниками устаткування досить пристойному рівні. Швидше за все, до вас в руки потрапить щось, що підключається до СОМ або LPT-порту, та ще й не підтримує специфікацію PnP. Тому основним завданням буде: - написання процедури коректної установки драйвера, з "менюшками", і "щоб було видно в менеджері пристроїв"; - визначення всіх наявних у системі портів СОМ і LPT (навіщо спантеличувати користувача зайвими питаннями при встановленні драйвера?); - коректне визначення наявності на вказаному порту саме вашого пристрою (уявляєте, як чудово, якщо в момент опитування портів повністю накривається миша або принтер перестає друкувати); - коректне захоплення та звільнення ресурсів, необхідних для функціонування вашого пристрою.
Крок перший – встановлення драйвера. Як ми вже з'ясували, драйвера можуть бути статичними та динамічними. Для завантаження статичних драйверів їх можна прописати в розділі [386Enh] файлу SYSTEM.INI. Але цей метод застарів і залишений, фактично, тільки для сумісності зі старими версіями Windows (єдиний виняток - драйвера, завантаження яких має проводитися ще до переведення процесора в захищений режим роботи - наприклад, длязвернення до реальних функцій BIOS). Другим (переважним) способом є записування інформації в registre, у гілку HKEY_LOCAL_MACHINE\System\CurrentControlSet\Services\VxD\ІМ'Я_ПРИСТРОЇ, де значення ІМ'Я_ПРИСТРОЇ може бути довільним (на ваш смак). У цій гілці має бути змінна типу REG_SZ з ім'ям StaticVxD і значенням, що містить ім'я вашого VxD-драйвера. Якщо файл знаходиться в директорії %SystemRoot%\SYSTEM, то можна вказати тільки його ім'я, інакше обов'язково прописати повний шлях. Крім того, в цій же гілці потрібно створити змінну Start типу REG_BINARY зі значенням 0. У принципі, змінна Start призначена тільки для сумісності з наступними версіями Windows.
Hint: для завантаження VxD, що використовують як Device loader *IOS, можна просто переписати ваш VxD до директорії %SystemRoot%\SYSTEM\IOSUBSYS і встановити розширення файлу *.VXD або *.MPD.
Але використання драйверів, що статично завантажуються, без особливої необхідності не виправдано, оскільки веде до підвищеної витрати оперативної пам'яті, якої ніколи не буває багато. Тому кращий другий варіант - використання VxD, що динамічно завантажуються. Якщо ваш пристрій не підтримує специфікацію PnP, він за визначенням є legacy. Для legacy-пристроїв ядро Windows надає якийсь емулятор enumerator'a, який завантажує всі драйвера, які описані в registry у гілці HKEY_LOCAL_MACHINE\Enum\Root\. Фактично, для того, щоб ваш не-PnP пристрій став аналогом PnP - потрібно завантажити написаний вами VxD-enumerator, який і, при необхідності, займатиметься завантаженням та вивантаженням безпосередньо драйвера фізичного пристрою. До речі, з цієї ж причини enumerator та драйвер виконують зазвичай у вигляді двох різних VxD(бо enumerator знаходиться в оперативній пам'яті постійно, а драйвер пристрою підвантажується при необхідності).
Отже, для встановлення написаного вами enumerator'a потрібно задати мінімальну інформацію про нього в registry - наприклад, у гілці HKEY LOCAL MACHINE\Enum\Root\ІМ'Я_ПРИСТРОЇ\0000, де як ІМ'Я_ПРИСТРІЙ може бути будь-який рядок (наприклад, ім'я вашого enumerator'a ), а 0000 означає, що це перший логічний пристрій (а навіщо потрібно більше?). З мінімально необхідної інформації необхідно в цю гілку записати такі змінні (всі значення мають тип REG_SZ):
Ось, у принципі, і все. Після перезавантаження системи з'явиться повідомлення Windows "виявлено новий пристрій і йде пошук програмного забезпечення для нього", далі запропонують вставити диск від виробника обладнання - і встановлення драйвера продовжиться у звичайному режимі. Постає питання: а чи можна обійтися без перезавантаження системи? Виявляється – можна! Хоча наш пристрій і є legacy, але сама Windows емулює його як PnP. В принципі, можна просто увійти в Device Manager і на закладці, де знаходиться device tree натиснути кнопку Refresh (Оновити), і наслідки будуть такі ж, як і після перезавантаження системи. А ще краще - попросити *CONFIGMG (саме він займається конфігуруванням PnP-пристроїв) перевірити всі пристрої (в т.ч. і знову з'явилися в системі). Для цього після запису інформації в Registry викличемо функцію ReenumerateDevices().
На жаль, у Win9x немає штатних засобів для визначення кількості та імен СОМ-портів, що є в системі. Нумерація портів не обов'язково буде наскрізною, у системі можуть бути, наприклад, "СОМ1", "СОМ7" та "СОМ18". Та й взагалі, послідовні порти мають називатися " COMx " , а паралельні " LPTx " . В принципі, імена портівможуть бути абсолютно довільними.
Для вирішення цього завдання скористаємося тим, що всі пристрої в системі впорядковані у вигляді дерева пристроїв (DevNode tree). Крім того, для будь-якого послідовного та паралельного порту штатним перечислювачем (enumerator) є драйвер *VCOMM, інтерфейс до якого документований DDK. Так як кількість портів у системі може бути різною, виділяти пам'ять для зберігання інформації про них у вигляді статичного масиву не варто - краще скористатися засобами, що надаються VxD ядром системи (т.зв. сервісні функції VMM).
Потім, коли ми вже маємо список усіх фізичних портів, відомих системі, нам залишається визначити, до якого підключено наше зовнішнє обладнання. І визначити, по-можливості, в "гарячому" режимі, оскільки користувач може підключити та вимкнути наше обладнання без вимкнення живлення комп'ютера та без перезавантаження системи.
Звичайно, як лобовий спосіб можна використовувати опитування по подіях таймера, забороняючи на час опитування переривання командою CLI і дозволяючи їх потім командою STI (бо VxD виконуються на рівні ядра - команди CLI/STI виконуються безпосередньо процесором, а не емулюються операційною системою ). Але така тактика може призвести до втрат переривань від інших пристроїв, та й швидкодія системи від цього не покращає.
З іншого боку, ми не повинні отримувати доступ до апаратних ресурсів в "будь-який момент часу, що нам сподобався" - не виключено, що з цим портом вже хтось працює, і ми можемо йому просто перешкодити. Для вирішення цієї проблеми VCOMM пропонує механізм, що називається contention handler. Його ідея полягає в тому, що якщо нам потрібні фізичні ресурси, керовані іншим драйвером (у даному випадку – драйвером)послідовного або паралельного порту), то ми звертаємося до точки входу contention handler із запитом на захоплення ресурсу. Ресурс нам може дати, а може й не дати. Якщо ресурс нам видали - ми його можемо використовувати абсолютно вільно, а після завершення використання знову викликати contention handler та звільнити його. Ну а якщо ресурс недоступний, то робити нічого, доведеться почекати і повторити спробу захоплення на наступному циклі перевірки.
До речі, якщо ми отримали ресурс "у тимчасове користування", інший драйвер також може спробувати його запросити. При цьому буде викликано нашу callback-функцію, в якій ми і вирішимо - віддати ресурс, або він нам ще потрібен для ексклюзивного використання. На жаль, у цій бочці меду є пара ложок дьогтю. По-перше, contention handler не підтримує більше двох запитів одночасно. Тобто. якщо наш драйвер виконав запит ресурсу, який уже кимось використовувався, цей ресурс у нас уже ніхто не зможе відібрати, поки ми не повернемо його добровільно.
Ну що, запрацював ваш пристрій? Задоволені? Ах, на "зелених" материнських платах ваш драйвер або взагалі не працює, або працює хвилини три, а потім обмін із вашим пристроєм припиняється без пояснення причин? В принципі, ця нісенітниця вперше з'явилася в Windows 98 (у Windows 95 підтримка ACPI реалізована в мінімальному обсязі і не заважає жити системному програмісту). Справа в тому, що при захопленні ресурсу за допомогою звернення до contention handler (і вже тим більше, якщо ви захоплюєте його "нелегально", між командами CLI/STI), ніхто і не думає переводити порт із "сплячого" режиму в "робочий". Коротше кажучи, на ньому просто немає напруги живлення! До речі, це стосується як послідовних, так і паралельних портів (особливо, якщо вони інтегровані на"зеленої" материнської плати).
Ви знаєте, як вмикати/вимикати живлення порту? Особисто я – не знаю. А ось відомий нам CONFIGMG - знає (цікаво, звідки?). Можна звернутися до нього за допомогою. В принципі, зберігати вихідний стан напруги живлення і відновлювати його після використання ресурсів абсолютно не обов'язково, це робиться виключно для забезпечення "коректності" по відношенню до іншого драйвера, що працює з цим же пристроєм. Як кажуть, "не роби іншому того, чого не бажаєш собі". Взагалі використання цього принципу при написанні драйверів дуже позитивно впливає на сумісність з іншим апаратним і програмним забезпеченням.
Ну от загалом і все, що я хотів розповісти. Озброюйтесь відладчиком, вивчайте ядро системи, дивіться приклади з DDK. І все одно, навіть будучи підготовленими морально, коли перейдете до написання драйверів під платформу Windows NT, плюватиметеся і згадуватимете недобрим тихим словом славну фірму Microsoft - за її реалізацію VxD під Windows 9х.