Текст
                    ГМехЯ

В.П. Солдатов’

программные средства'

БИНОМ

\1У1пао1Л/5 98/2000/ХР/5ен/вг^003

Терминология

Ьедасу и \ЛЮМ модели
Инсталляция и запуск
Отладка

Обработка прерываний

Программирование
драйверов \Мпс1о\л/5

> I

©Солдатов В.П., 2004 Е-Воок СорупдЬШ Ьу Г Когут]

Соуег Айбхаша аапйа 1бааёпёхаёа 16 аабхба 1бааёпёхаёа хб пхпоааёбаёу уё. аабпёё Аёааа 1. Абаёаабй. Тайёа шудёу ё бабхёш Абаёаабй: ёбохшё хёак Упхх-Ьхпих, Ро8-АУхп6оау8 Йёхаабй басбааю^ёёа абаёааба АЬвйасбоп 81гисШге Цпхоп ОЬ{ес1 Кете! тобе Узег тобе СаПЬаск Соп1ех1 Кои1хпе 15К РрсРог18К РеГеггеб Ргосебиге Са11 ЮМапа§ег 1КР Ю 81аск 1оса!юп Рх8ра1с11 КоиНпез Ма]ог 1КР Собе ЮСТЬ Мхпог 1КР Собе
РпуегЕпТгу АУРМ Ьауеппц АсЦРеухсе Реутсе 1п81апсе Реутсе ОЬ^есЕ РРО, ЕРО Реутсе Ех1еп8топ 8ушЬо1тс Етпк Реутсе 81аск, Ргтуег 81аск МопоШЫс Ргтуег Техасу Рпуег., КТ 81у1е Рпуег 1КР Р1КрЬ РоШод УиШа! Мешогу 8у81еш Ра§т§ РПе У8ег 8расе Роо! Мешогу Ра§ес1 Метогу, Ра§ес1 Роо! Копра§ес1 Мешогу, Мопра§ес1 Роо! 8саИег/ОаЙ1ег РгоЫет РМА Ассе88 УЫайоп 8ЕН Ткгеас!, Ткгеас! ОЬ]ес1
Ргосезз, Ргосезз ОЬ{ес1 Айтйу ЗупсИгопхгайоп ОЬ{ес18 РпР Мапа§ег Епитегайоп Епитега1ог АСР1 АСР1 Рпуег Ех11ег Реухсе ОЬ{ес1 Ех11ег Рпуег НАЕ Ке§х81гу НагсКуаге ВгапсН Сипеп1Соп1го18е1 Еа81Кпо\упОоос1 Цтсобе РеухсеГО С1а88 Рпуег Рог! Рпуег МтИпуег ЕпоМёёё ё1д1б1абёё ЁНадша ёсаахёу (а ббппёп усйёа Есаахёу, ёютбйа ха айёё Табаааааш ха ббппёёё усйё Таоабёаёй ее хаёабха басбаахоёё абаёаабха обаойёб бёбх 1бхабашйа хбхабёбй хб Мхсгозой
Ахёбхахдабёу Мхсгозой РРК ОпПпе ахёбхахдабёу Мхсгозой Саёё|э^а1ёа Аёааа 2.Тбхабашйа пбаапдаа, хбёхаТуаша хбё басбаахдёа абаёаабха Тбхабашйа пбаапдаа хд Мхсгозой Тапдбхёёё хбхаёда а Ухзиа! 8йхс1хо 7 ХеТ Ешёёудёу ё пахбёа абаёааба ддёёёдхё ВшТй хаёада РРК Тбхабахха Ререпбз Тбхабахха КеВазе 1бхабахха ЕггЬоок 1бхабахха ОшаОеп (ИШРСтЕК) Тбхабапа баааёдёбхаахёу Йепдашах Раапдба Тбхабахха РеухсеТгее Тбхабапа РеуСоп Тбхабахха РеуСЙ Тбхабахш СккТпГ ё ОепТпГ Тбхабахха Тазк Мапа§ег (Аёшад^аб Сааа^) Йёпдапйё апёад Чбхёсахаёдаёштди" Ргхуег УегхТхег РгеЕазТ Тбхабашйа пбаапдаа ёс хаёадха басбаахдёё абаёаабха хд дбадйёб дёбх Тбхабахха Мопйог хд СошриАУаге СогрогаТхоп
1бхабапа дбатёубёё баёёа зоигсез а хбхаёд Ухзиа! 8йиНо 1бхабапа Мппеца 8уш1лпкз 1б1абапша пбаапдаа хд 1абёа Роппёпаё^а ё 8уз1п1егпа1з Тбхабапа Ке^Моп Тбхабапа АУтОЬ] Тбхабапа РеЬи§Ухе\у Тбхабапа РеЬи§Рпп1 Тбхабапа РеуУхелу Тбхабапа РооГГац 1б1абапа Тбтпдба даёёта Пйабапй Йааха 0баёааба Тбтабапа \у2к зус Тбтабапа \у2к зут Тбтабапа \у2к тет 1б1абапй 16 8пис1§еоп8ой 1б!абаНа РЕВголузе РгоГеззюпа! 1п1егасйуе 1б!абаНа ЫТРеухсез 1б!абаНа ?4ТОЬ{ес1з Тбтабапа 8уз1еш Мешогу Вголузе Тбтабапа РА Ехр1огег Аёсаппатаёаб ГОА Саёё^ахёа Аёааа 3.1бтдхё абаёааб "а-пдёёа-МТ": Ехашр1е.зуз 1бхбаа6ба РпуехЕпйу ё хбаааабёдаёиша хайуаёахёу
Обхёбёу Сошр1е1е!гр ВааНау хбхбааоба хабаахбёё рахбхша геабЛугке ВааНау хбхбааоба хабаахбёё рахбхша хоёбйбёу абаёааба ВааТ^ау хбхбааоба хабаахбёё рахбхпха саёбйоёу абаёааба ВааТ^ау хбхбааоба хабаахбёё ЮСТЬ рахбхша ВааТ^ау хбхбааоба айаббрёё абаёааба СаахёхаЖйё оаёё Вгхуег.И Ёшёёубёу ё пахбёа абаёааба Ехашр1е.8У8 Оаёё МакеШе Оаёё 8оигсе8 Ёшёёубёу ё пахбёа хбё пхшё боёёёбй ВшШ Ехпоаёёубёу ё раюпё абаёааба Ехашр1е.8у8 Ехпоаёёубёу ахапахёах сахепаё а Йёпбахшё Ваапоб Тхаёбёёабёу Йёпбашах Ваапбба АУшс1о\у8 98 Еаёбёёабёу Йёпбашах Ваапбба АУхпс1о\у8 2000, ХР, 8егуег2003 Саюпё абаёааба Ехпоаёёубёу п ёппёирхаахёах 1ЫР баёёа Ехпоаёёубёу п ёппёйрхаахёах хбхабахш Мопког Ехпоаёёубёу п ёппёйрхаахёах пабаёша 8СМ л ТО ГО •• О Х л Хахаажаба Тбёёхаеахёа аёу оапбёбхаахёу абаёааба Ехашр1е.8У8 Ваахба п абаёаабхх Ехашр1е.8У8 СаёёЬ^ахёа
Аёааа 4. Аббёоаёббба АУхпскпуз ЫТ 5. Ааааахёа Оаёё бадбаахбёё ОдТахё апабадшб Тбёаёёааёё а АУхпскпуз ЫТ 5 1абахтёххпой Вапоёбуахтбй Тбхёсахаёбаёйхтбй Еппёхёдаёшйа ёшпахдй Ехбаббаёп пёпбаххйб пёбаеа Шаааеаб (аётао^аб) хайаёоха Шаааеаб ёххбёаббёбхаахёу л ТО ЛО •• О X • О ы Шаааеаб хбхоапта Шаааеаб аёбобаёйпё хахубё Йбаапбаа ёхёаёшйб хбхбааббхйб айсхаха АёпТад^аб (Шаааеаб) аахаа/айахаа Вапоёбахёу аасхахё хТабабёшхё пёпбаш Иапёпбаха АУхп32 Аббаёа пбйапоаахша ёххшахой пабабёххпё пёпоаш Ёххпхахбй хапёбаеёаахёу пабабёё аахаа/айахаа, баахба|эйёа а бааеёха уаба Оаёё басбаахбёё папёпбахй аахаа/айахаа Оёш абаёаабха АУхпйолуз 1ЧТ5 №абёаёшйа абаёаабша аббёбаёбббй Iбёё^ёу хааеаб аабпёухё Саёё]э-Шёа Аёааа 5.1бёёапаупй ё апабаобба 1пххахйа паааахёу ха аххабаоххх хаапха-Шёё
Аабпабё^апёха бапххрхааахёа ё ёпбёаббёбхаахёа Рааёпббй бпббхёпба Атобх ё бааёпобах бпббхёпба 1бтобахпбах аахаа/айахаа Атобх ^абас аабапабё}э а хахудё Йёахаёй хбабйаахёё Тбёхбёбадй хбабйаахёё Ааёдхбй хбабйаахёу 1абааа^а пёахаёха хбабйаахёё Йбхапбах ё хбхбаптбб 1абахёсш Табааа^ё аахшб 1бхабаххёб6ашё ааха/айаха Тбу 11ё атобх ё хахубё РМА пабадёё п ётхёйсхаахёах пёпдахшб ёххббхёёабха Гхабабёё Ьи§ та§1ег РМА Шуби, хбааааххау бпббхёпбаб Рапббпй, ёппёйрбаша бпббхёпбахх 0ёш а ёшй|эбабхйб пёпбахаб 18А: 1пс1и8йу §1апс1агс1 АгсЬйесШге Е18А: Ех1епс1ес1 Тпскхзйу 81апс1агс1 АгсЬйесШге РС1: РегхрЬега! Сотропеп! 1п1егсоппес1 Атобх ё бааёпобах Табахёсш хбабйаахёё Ахспаехтбё РМА Шуби, хбааааххау бпббхёпбаах
Аабпабё^апёха баппсхааахёа ё ёпбёаббёбхаахёа 1ЕЕЕ 1394: Рхгелухге Виз Атобх ё бааёпобах 1абахёсш хбабйаахёё Ахспжппбё РМА Аабххабё^апёха башхсхааахёа ё ёпбёаббёбхаахёа Ц8В: Цшуегза! 8епа1 Виз Атобх ё бааёпобах 1абахёрш хбабйаахёё Ахспаеппбё РМА Аабпабё^апёха баппсхааахёа ё ёпбёаббёбхаахёа 0ёха РС Сагб (РСМС1А) Атобх ё бааёпобах 1абахёсш хбабйаахёё Ахспжппбё РМА Аабпабё^апёха баппсхааахёа ё ёпбёаббёбхаахёа Йхаабй п баахба п апабабббхё Аббёбаёббба оёш Рааёпббй бхбааёахёу Ёёб^ахёа ёхбхбхабёё х птбхухёё бпббхёпбаа ё ха хоёаёаб Ёаааахёа, паусаша п ёппёйсхаахёах хбабйаахёё 1абахёсш Табааа^ё аапйб Еппёйсбёба ёхбаёёаёб пахах бпббхёпбаа Оапбёбхаахёа апабабббй СаёёЬ^ахёа
Аёааа 6.Тапёбаеёаахёа аахаа/айахаа а бааеёха у аба Ёпдаёпд айпёхахёу хбхабаппах ёхаа Ёпдаёпд ёпёёр^ахёу ёёё ахддбапаах хбабйаахёу (Тгар) Ёпдаёпд хбабйаахёу Ёпдаёпд хбхабаппах пдхёа бааеёха уаба Тбёхбёдадй айпёхахёу хбхабаппах ёхаа Табаахдёа хбабйаахёё Тбабйаахёу, айраахша хбхабапп Тдёхаеапйа хбхбаадбша айрхай (РРС) дбхёбёпёбхаахёа РРС Тпхаашпдё хабахёрха РРС Атддх ё хаёапдух хахудё пёйрхаадаёйпёёб хбёёхаеахёё Йипхай атддха ё аддабшх хаёапдух Тайёё араёуа ха пдбдёддбд абаёааба бажёха уаба Тбюаадбй ёхёбёаёёрабёё абаёааба ё х^ёпдёё Тбхбаадба РгхуегЕпТгу Тбхбаадба ба-ёхёбёаёёрабёё Тбхбаадба айабдрёё Оп1оас1 Тбхбаадба ЗНиМоу/п Тбюаадба хабадпах айрша Ви^сНеск Раах^ёа хбхбаадбй хапёджёаахёу аахаа/айахаа Табаахд^ёёё рахбхша Ореп ё С1о§е Тбюаадбй Табааа^ё аапйб Тбхбаадба 81ах11о Тбхбаадба хапёдаеёаахёу хбабйаахёё
1бхбааббй РРС 1бхбааббй хабабпах айдхаа аёу пёхббххёрабёё атббха ё бапббпах 1бхбаабба Соп1го11егСоп1го1 Тбюаабба Айар1егСоп1го1 Тбюааббй 8упс11Сгй8есйоп Аббаёа хбхбааббй абаёааба Оаёхабша хбхбааббй 1бхбаабба 1оСошр1ейоп 1бхбаабба Сапсе1Кои1те Ппёаахаабаёштбй хапёбаеёаахёу сахбхта аахаа/ айахаа Тбаааабёдаёшау хабааюёа Аётад^абхх аахаа/айахаа Тбаааабёдаёшау хабааюёа а абаёааба Жабо пабабёё аахаа/айахаа Тбюаабба хапёбаеёаахёу хбабйаахёё 18К Ипб-хабаахбёа, айпёхуахау абаёаабхх Ипб-хабаахбёа, айпёхуахау Аётаб^абхх аахаа/айахаа Саёёр^ахёа Аёааа 7.1бёаш хбхабаххёбхаахёу а бааеёха уаба Кхаёаоахёу ха пепахёё аахшб ё ббхёбёё Ахххехеоаеихиа ххепаоаее оехха Еааёёбёёабхбй ГМ, ОУТ, ОРТЮЫАЬ Оёш ахсабайаашб сха^ахёё ббхёбёё Йхаёаоахёу ха ёхахаб ббхёбёё абаёааба ё пёпбахшб айсхаха
Гшбабёё п Тёааарйаё бНёхё Гшбабёё п хахубйЬ Айеши аёуайааёахёу ё тахахаеаахёу шёапбаё аёбббаёйпё Та!удё Ваахда п аптбёадёахшё тёпёахё Ваахда п МВЬ тёпёахё Одхёбёё аёаёёюаёё абахахё ашхёхахёу аёу баахдй п хахудйЬ бхбааёахёа баухайахёах ёхаа абаёааба а хахудё Гхбаааёахёа баухайахёу хбё ёххТёёубёё Аёхахё^апёш шбахайахёа ёхаа абаёааба а пдбахё^хдЬ хахудй Тбшёахй, ахсхёёа}эйёа хбё шбахайахёё ёхаа а пдба1ёш6|э хахудй Оёёпабёу пдбахё^шб паёбёё ёхаа ё аахшб а ххабадёаххё хахудё 1бшабёа ёхббаёдппдё айдхаха ёхаа, бархайахпах а пдбахё^пё хахудё Гшбабёё хаа пдбхёахё ЦК1СОВЕ 8ТК1ЫО Гшбабёё хаа пдбхёахё АМ81 пёхахёха Одхёбёё аёу баахдй п даёёахё Одхёбёё аёу баахдй п Кёпдаххш Ваапдбхх Одхёбёё атддха ё Йёпдахшд Ваапдбд, хбаатдааёуаша Аётад^абхх аахаа/айахаа Одхёбёё КИХхх хбупах атддха ё Йёпдахшд Ваапдбд Ваахда п Йёпдаххйх Ваапдбхх ^абас айсшй 2ауХхх
Обхёбёё аёу баахбй ш ппйёёахё (а хайаёбй О61ёоёё аёу баахбй п пёпбапш хбаапбааёахёах абахахё Саёё|э^а1ёа Аёааа 8.Тппаша хбхбааббй абаёааба Тбюаабба РгхуегЕпТгу Тбюаабба АскТРеухсе Тбюаабба Уп1оас1 РааНёа хбхбааббй абаёааба Таёабй ТКР Саахёхахё ТКР В^аёёё пбаёа аахаа/айахаа РааУёа хбхбааббй абаёааба Таахб бааУёб хбхбаабб Тшёаахаабаёйхшбй ааёпбаёё бааУёб хбхбаабб Яёб^аё Т: ТоёаУхау пёббабёу Йёб^аё 2: Сааабоахёа баахбй хаа ТКР рахбхшх Йёб^аё 3: Раахба ^абас Набааё ТКР хаёабха Аабапабёу ё атобх ё аахшх а ТКР хаёабаб ^бахёу/ дахепё Раах^ёа хбхбааббй хапёбжёаахёу ЮСТЬ сахбшха Тбё хабхаа МЕТНОР ВУГГЕКЕР Тбё хабхаа МЕТНОР Ш РТКЕСТ ё МЕТНОРОУТРТКЕСТ Тбё хабхаа МЕТНОР ЫЕТТНЕК Тапёбаеёаахёа хбабйаахёё
Тбхбааббй хбёхаеашах айсТаа хапёбаеёаахёу хбабйаахёё РрсРогЬг Айпёхахёу ёхаа хбхбааббй РрсРогТзг I6ёё|э^ахёа хб ёпбНхёёа хбабйаахёё Саёё^ахёа Аёааа 9. Абаёаабхау ххааёй ХУРМ Расаёбёа шабёбёёабёё Р1иц & Р1ау Тбхабашйа ёххшахбй Р1и§ апб Р1ау Рхёй Йёпбашах Раапбба Тхааёй ХУРМ ё хабхахёхаёу РпР АйсЮеухсе - ххаау хбхбаабба а абаёаабаб пааёё ХУРМ Рхёй абаёаабшб пёхаа а пааёё ХУРМ Тхайа бааТ^ёа хбхбааббй а АУРМ абаёаабаб Табахё-^ахёу, хаёёаайаааша ха АУРМ абаёаабй шабёбёёабёаё РпР Табааа^а РпР 1КР хаёабха хёаехёх абаёаабхш пёхух Реухсе ЕпишегаТхоп - апахайау хабахепй бпббхёпба Тпахпёхёша абаёаабй Ёхааа пёаабаб хбёхахубй шатёхёх6|э аббёбаёбббб? Ахахай "са" Таатбабёё хпатёхёпё аббёбаёбббй Рааюа п хёжхёхё пёхухё абаёаабпах пбаёа Йхдаахёа 1КР хаёабха айсхаахё 1оВихШ(А)8упс11гопои8р зйКедиез!
Йхдаахёа 1КР хаёабха айрхахх 1оВшШРеу1се1оСоп1го1Кедие81 Йхсаахёа 1КР ТаёадТа "п хбёу" Раахда п 1КР хаёадахё-бахеёёахдахё Оааёахёа 1КР хаёадха Саёё^ахёа Аёааа 10. Тбхабашйа пдхёё ё пёхббпёсабёу Кёпдахша хбхабашйа пдхёё Кепоаххиа баах^еа ххохее Йёхббпёдабёу а бааеёха уаба Ёхдабааёй хаеёаахёу аёу хдааёйпах пдхёа Расааёахёа абахахё ё аахшб п 18К хбхбааббхё Оаёхабй ё ёб ёшхёйдхаахёа Ибхёё ёаё хайаёдй пёхббпёсабёё 1айаёдй пхайдёу Йахабхбй 1и]Ьдаёпй №ех-аёхёёбхаёё Ёппёхёдаёипёёа бапобпй Абопа бохёбёё (Ех)1п1ег1оскес1Ххх Ёдхахахёа хбёхбёдадха ёаё пбаапбах пёхббпёсабёё РРС хбхбааббй ёаё пбаапбах пёхббпёсабёё Асаёпаёхёёбхаёё Саёё^ахёа Аёааа 11. 1абаахдёа аххабадшб хбабйаахёё Ипдапаёа уёшабёхахда
Оапбшш хбёпхшхаёахёа СЬескИ ЬоорЬаск Реухсе 1апббхёёа ххабабёхшё пёпоаш Ёппёйрбаша ётбббШбаёйша хбхабапй 1бтбаё0ёё абаёааб аёу баахбй п хбабйаахёухё СаахёхаЖйё баёё Ргхуег.Ь Ёппёхуашё ёха абаёааба Тбёёхаеахёа аёу бапбёбхаахёу абаёааба Ашёхёоаёйшё бапб ха пёхбтбй хабаппа Аабёахо 2. Йаёбёёабёу абаёааба аёу баахбй п хбабйаахёухё СаахёхаЖйё баёё Ргхуег.Ь Ёппёхуашё ёха абаёааба Паёбёёаоёу хбёёхаеахёу аёу бапбёбхаахёу абаёааба Саёё^ахёа Аёааа 12. Ётбаёёубёу абаёаабха Тбё пхшё ГМЕ баёёха Йоббёобба 1ЫР баёёа Йаёбёё тГ-баёёа ё тпаша хайёа хбааёёа аахаа дахепаё Йаёбёу ххепахёу аабпёё [Уег§юп] Яаёбёу пепахёу ппбаайёёа [МапиГасйдгег] Йаёоёу ххепахёу пааёаё анабаоббй [Мобек] Саха^ахёу п ааёхбёбхаахё}э ёхах Йаёоёё [РР1п81а11] Йаёбёу [РР1п81а11.8егухсе8] Йаёбёу [СоруРх1е8]
Аббаёа паёоёё, пбаааёуЬйёа ёпебхаахёа оаёёха Йаёбёу [8оигсеР1§к№те§] Йаёбёу [8оигсеРх8кРх1е8] Йаёбёу [Ре81та1хопРхг8] Тбёхабй пепахёу хбхбааббй ёпёбхаахёу оаёёха Яаёбёу [ АсШКе§] Сха^шёу НКК Йаёбёё [8егухсе1п81а11 ] Йаёбёу [С1а881п81а1132] Йаёбёё [РеГаи1Нп81а1132.Ххх] ё [РеГаи1Нп81а1132.Ххх.8егухсе81 1бхаабёа пёхбаёпёпа 1ЫР баёёа Етхёйсхаахёа IX Р оаёёха Тапбаб Опбаххаёё/бааёахёу пахе аххабабббй Опбапаёа РпР бпббхёпба Еаахбёбёёабхбй РпР бпобхёпоа РпР ёаахбёбёёабхбй РС1 бпббхёпоа РпР ёаахбёбёёабхбй 8С81 бпобхёпоа РпР ёаахбёбёёабхбй ГОЕ бпобхёпоа РпР ёаахбёбёёабхбй У8В бпббхёпба РпР ёаахбёбёёабхбй бпобхёпоа ШЕЕ-13 94 (ЕхгеАУхге) Саёё^ахёа Аёааа 13. Оапбёбхаахёа ё хбёааёа х6х пёаабаб хбхаабубй? Оёббхаха пахепахёа абаёааба
Абаёааб хбёарйаааопу баахбаои? Апабабша хбхаёаш 1бхабапша хбхаёаш боа^ёа бапббша бхбпжахёа хбхабашйб похёха Тбхаёаха хбёхбёоабха абахахё айпёхахёу Топёааеёаахёа шёахё Тоёаа^ёёё 1оёаа^ёё АУхпРЬ§ Аёбаёбхбёё ёаахбёбёёабхбха Аёбаёбхбёё ёпбхашб оаёпбха Саюпё ё хёхх^ахёа хоёааЖхё паппёё Тоёаа^ёё 8оШсе хбахёа сгазй-уёбахха Ахёбахё уёбах пхаббё (В8ОР) Ашёёс ёхбхбхабёё Сгазй Ришр баёёха 1айёа хбёаш хбёааёё бпбапаёа бёёпёбхаахшб бх^аё хбабйаахёу 1бпаае6дх-4йё айаха ха уёбах Йхббахахёа хдёааЖхах ёхаа а ёпбхапх даёпда абаёааба Табабааб хаёхббаёдшб бпёхаёё Епххёйдхаахёа аёаахтбё^апёёб саПЬаск-ббхёбёё Тахаббжахёа бба^аё хахубё бпбапаёа хабахаббха дааббрёё а баёёа Ьоо1лпх хапоша хбёаш атпоаххаёахёу пёпоаш
Саёё^а1ёа Тбёёхаеахёа А. Ёхай ТоёаНшб пёббабёё • • < т X Л • • X О Г У О А У ~ X Г ГУ У Х..ОХЛ..ЛХЛ Г X Г У ~ У О л X Тбеехаеахеа А. Саабосеа ххабаоеххххе пепоаш Ёааюшёа ё сааббсёа Та^аёшау пбааёу сааббсёё Йбааёу сааббсёё Рашхсхааахёа хахббахаахёу Айахб ёххбёаббабёё Сааббрёа уаба Ёхёбёаёёрабёу уаба Айаха ха уёбах ёхбхбхабёё х хбхбаппа саабодёё 1бёёхаеахёа А, Иёхбхбйа пбахааббша хабахаббй ххепахеу абаеааба а Кепоахххх Раапоба Табахабб Рх8р1ауКаше Табахаоб ЕггогСоп1го1 Табахабб 1ша§еРаЙ1 Табахабб 81ах1 Табахабб Роба Табахаббй пабасааёа \Епиш

Предисловие Когда кто-то приступает к большому делу, а для начала решает спросить у людей сведущих что-то вроде "Как съесть слона?", то самый правильный ответ, который он только может получить: "По частям!". Несерьезно? Но зато как верно! Данная книга — это попытка ввести Вас, Читатель, в не самое дружелюбное подпространство мира программ — разработку драйверов, а если быть совершенно точным — драйверов для операционных систем М1СгозоГ1: \Л/1Пс1о\л/5 1\1Т 5.x, представленных на сегодня версиями \Л/1Пс1о\л/5 2000, \Л/1Пс1о775 ХР и \Л/1Пс1о\л/5 Зегуег 2003. Идея книги была подсказана обескураживающей тишиной в этой области (разумеется, речь идет о России), когда лишь только 2002 год мог бы похвастаться заметным нарушением этого молчания. Предназначенная для студентов ВУЗ'ов и специалистов, чья профессиональная деятельность заставляет их обратиться к разработке собственных драйверов для \Л/|Пс1о\л/5 или просто к программированию в режиме ядра \Л/|пс1о\л/5, книга предполагает наличие у читателей достаточной подготовки. Прежде всего, разработчик драйвера должен владеть программированием на языке С (без расширений С++), поскольку описание синтаксиса и применения конструкций этого языка не рассматриваются в данной книге вовсе. Во-вторых, разработчик драйверов, пусть начинающий, должен иметь твердо сформировавшееся представление о программировании в многозадачной среде при интенсивном использовании многопоточности. Конечно же, указанные требования не столь объемны и могут
быть выполнены в результате короткого "самообразовательного штурма", но здесь придется корректировать свои планы на величину различия между этапами "я Это знаю" и "я умею Этим пользоваться". Необходимость знания Читателем языка программирования С, как было сказано выше, продиктовано тем обстоятельством, что излагаемый материал ориентирует Читателя на использование пакета М1сго5оЯ: ЭЭК (Эеу|се Эпуег КН — пакет программного обеспечения для разработки драйверов), хотя существуют коммерческие программные пакеты и от других фирм, которые базируются на использовании других языков программирования (подробнее эти вопросы будут рассмотрены далее, в главе 2). Наверное, следовало бы упомянуть и о таком требовании к потенциальному потребителю приведенной в книге информации, как "предрасположенность" или "дружественность" к аппаратуре, поскольку основное назначение драйвера все-таки — взаимодействие с аппаратным обеспечением. Однако во-первых, это подразумевается. Во-вторых, сведения из данной книги можно применять и для разработки таких модулей режима ядра, которые лишь формально являются драйверами, но ни с какими устройствами не связаны и используются лишь как агент доступа к богатому и полезному набору функций режима ядра. Как Вы, уважаемый Читатель, сможете неоднократно убедиться далее, в книге использован прием повтора некоторых важных положений и выводов, что призвано помочь в расстановке должных, с точки зрения автора, смысловых акцентов. (А вовсе не по причине его забывчивости и не по ошибке редактора!) Начинающему разработчику драйверов настоятельно рекомендуется не
пропускать первые главы книги, поскольку такое легкомыслие чревато серьезными проблемами в дальнейшем понимании материала. Обилие англоязычных синонимов к используемым терминам в тексте так же решает свою задачу. Рано или поздно (скоре всего, уже случилось!) Читателю придется обратиться к чтению СОК документации, поставляемой вместе с программами и библиотеками фирмой М1сго5оГЬ. По ряду причин это нельзя назвать простым делом. Поскольку чтение англоязычной документации — процесс, которого разработчику драйверов не избежать, чтобы облегчить вступление на этот нелегкий путь, в книге приводятся многочисленные наборы синонимичных терминов с развернутыми вариантами переводов. За пределами рассмотрения данной книги остались вопросы, которые можно назвать "сложным программированием" драйверов. Не рассматриваются принтерные, 5С51, видео и сетевые драйверы, поскольку этот емкий материал может легко заслонить приоритетные задачи — объяснение, какова внутренняя логика подсистемы ввода/вывода \Л/| пс!о\л/з и ознакомление с приемами программирования в режиме ядра. Книга ориентирована на разработчиков программного обеспечения, но некоторые ее части будут небесполезны и для разработчиков аппаратуры.

Предисловие от составителя эл. версии Во время создания этой эл. книги я старался избегать внесения своих ошибок (по крайней мере, делал это как можно тщательнее), при этом исправив значительное количество опечаток и ошибок оригинала (коих на самом деле немало).. Но я не буду гарантировать полное отсутствие ошибок - это было бы, по меньшей мере, глупо; а потому, если Вы все-таки обнаружите таковые, буду весьма признателен, если Вы сообщите мне об этом. Кроме того, если Вы интересуетесь драйверами, я бы мог посоветовать Вам, кроме источников, указанных в книге, цикл статей от Еоиг-Е "Драйверы режима ядра", который можно найти на превосходном сайте АааетЫег (ууаат.ги) Если вы поделитесь имеющимися у вас русскоязычными источниками информации о драйверах, моя благодарность не будет знать границ.. =))
Координаты С сообщениями об опечатках и ошибках в книге, а также с предложениями и информацией можно обращаться по этим координатам: таН: кд: 5953073 С уважением, [Коп//'п]
Глава 1
Драйверы. Общие понятия и термины Всякий уважающий себя курс лекций начинается, прежде всего, с обзора состояния предметной области и фиксации определений, которые и составят позже понятийный аппарат данной науки. И хотя данная книга не представляет собой курс лекций, не будем отступать от традиций.

Драйверы: крупный план. Утх- Ыпих, ОО8-\Л/1Пс1о\л/5 Строго говоря, драйвером считается фрагмент кода операционной системы, который позволяет ей обращаться к аппаратуре. Не вполне конкретный термин "аппаратура" обозначает здесь как неотъемлемые части компьютера (например, наборы микросхем на материнских платах современных персональных компьютеров), так и вполне автономные устройства (как, скажем, "древние" устройства считывания с перфокарт, редко размещавшиеся в одной комнате с процессорной стойкой). Концепция драйвера как отдельного сменного модуля оформилась не сразу. Некоторые версии 111М1Х и по сию пору практикуют полную перекомпиляцию ядра при замене какого-либо драйвера, что совершенно не похоже на обращение с драйверами в Ыпих, \Л/|пс1о\л/5 и М5 005. Кстати, именно М5 005 ввела в массовое обращение понятие драйвера, как легко сменяемой насадки, позволяющей моментально (сразу после очередной перезагрузки) улучшить качество жизни пользователя: шрифты на мониторе радуют глаз, емкость дискеты возросла вдвое, в принтере наконец-то поселилась кириллица и т.п. Касаясь характерных черт драйвера (работающего с полномочиями компонента ядра) для разных операционных систем - именно, \Л/| пс!о\л/з и Ыпих - остановимся на трех неслучайных совпадениях. Наблюдение 1. В операционных системах М5 005, \Л/|пс!о\л/5, 11п1х и всех клонах Ыпих принят способ работы с драйверами как с файлами. То есть при доступе к
драйверу используются функции либо совпадающие (лексически), либо весьма похожие на функции для работы с файлами (ореп, с1озе, геас1, шгНе, Сгеа1еН1е...). Данный порядок неудивителен для систем юниксоидного ряда, поскольку в них вся действительность воспринимается в виде файлов (что является изначальной концепцией данной ветви операционных систем). Например, директорию (каталог файлов) можно открыть как файл и считывать оттуда блоки данных, соответствующие информации о каждом хранящемся в этой директории файле. В директории /с!еу/ можно открыть файл, соответствующий мышке и считывать постепенно байты данных, появляющиеся в нем в точном соответствии с ее перемещениями. Как ни удивительно покажется это рядовому пользователю операционной системы \Л/1Пс1о\л/5, но и в ней предлагается точно такой же механизм. Для доступа к драйверу из своего приложение пользователь прибегает к помощи функции Сгеа1еН1е (это могла бы быть функция ОрепЕПе, но это название морально устарело, поскольку использовалась в старых 16- тиразрядных версиях У\Лпс1о\л/5). Правда, имя файла, который предполагается "открыть", выглядит странно, и на жестком диске такого файла отыскать невозможно - он существует лишь в недрах операционной системы и выглядит, например, как "\\\\.\\туОеу|се". (Операционная система понимает его как символьную ссылку для идентификации конкретного драйвера, привлекаемого к работе.) И хотя дальнейшие операции, сформулированные создателем пользовательского приложения как вызовы геас1()-ууп1е(), все-таки преобразуются операционной системой в специальные
запросы к драйверу, необходимо признать: формально процесс похож на работу с файлом. Наблюдение 2. Драйверы стали легко заменяемой запасной частью в операционной системе. Если раньше и были различия между продуктами МюгозоП: и юниксоидными системами (драйверы в операционных системах МюгозоП: изначально были "подвижно- сменными", но в 111М1Х и ранних версиях Шлих при их замене надо было заново выполнять перекомпиляцию ядра), то сейчас такие различия исчезли. При сохранении некоторых особенностей инсталляции, драйверы теперь повсеместно могут быть удалены/ добавлены в систему редактированием одной записи в специальных системных файлах. Более того, загрузка "по требованию" (по запросу пользовательской программы) становится практически общей чертой \Л/|Пс1о\л/5/ип1х/1_1пих. Даже операционные системы реального времени, например, (^ЫХ также используют методику сменных драйверов. Наблюдение 3. Концепция существования режима ядра (с большими функциональными возможностями и относительной бесконтрольности) и пользовательского режима (с жестким контролем со стороны системы) присутствует в У\Лпс1о775/ип1х/1_1пих с незапамятных времен. Если внимательно посмотреть на то, как в Ыпих реализуется драйвер, то увидим, что это всего лишь модуль ядра, который имеет некое (дополнительное) отражение в виде файла в директории /с1е7/. Если посмотреть теперь на драйвер (режима ядра) в операционной системе \Л/1Пс1о\л/5, то становится понятно: это не просто драйвер, это возможность войти в режим ядра со своим программным кодом. От судьбы не уйдешь. М1СгозоГ1: не предоставила явной возможности создавать модули ядра, однако закрыть эту брешь в виде
драйверов - невозможно! В великолепной книге Свена Шрайбера "Недокументированные возможности У\Лпс1о\л/5 2000" как раз эксплуатируется эта "черта личности" драйвера У\Лпс1о\л/5. Завершая мини-экскурс в сравнительный анализ драйверов разных популярных ОС, нельзя не упомянуть и об общем для всех систем механизме воздействия на драйвер при помощи ЮСТ1_ запросов (работа с ЮСТ1_ в \Л/|Пс1о\л/5 будет подробно рассмотрена в главе 8).
ГРгвУ1ои51 Г№х11
Словарь разработчика драйвера Впервые открывая документацию, поставляемую вместе с пакетом М1сго5оЛ: □□К, новичок неожиданно обнаруживает, что понять ее практически невозможно - периодически на форумах в Интернете возникают удивленные сообщения об этом феномене. Между тем, никакого феномена нет. Документация ООК - это весьма лаконичное "издание" справочного характера (с коварным множеством "любезных" переходов по ссылкам), и изучение предмета по нему во многом похоже на изучение медицины по энциклопедии. Для русскоязычного читателя-новичка, даже хорошо владеющего языком оригинала, положение усугубляется тем, что для многих терминов отсутствуют устоявшиеся отечественные аналоги. Особенно печально положение некоторых терминов, представленных в оригинале словосочетаниями. Порой, при самом добросовестном переводе так и не получается хорошего определения, поскольку добросовестный 'пословник' дает смысловые оттенки, как раз затрудняющие понимание внутренней логики предмета обсуждения (как, например, это происходит с термином 'сИзра^сИ гоийпе'). Словарь терминов, который приводится ниже, призван уравнять шансы читателя в борьбе со сложностью материала и дать стартовые сведения новичкам. Статьи этого мини-словаря расположены в порядке, при котором более заметны внутренние взаимосвязи терминов. Некоторые термины, как, например '1Р.Р', переведены, но далее в книге будут использоваться в оригинальном виде, который компактнее выглядит и обеспечивает привыкание к синтаксису будущего программного кода.
АЬз1гас11Оп Абстракция, представление сложного предмета искусственно созданным формальным описанием. Абстракция позволяет отойти от рассмотрения некоторых излишне конкретных вопросов реализации своего прототипа. Абстракции, вводимые М1сго5оГ1: ООК, возникли, главным образом из стремления облегчить переносимость кода на другие аппаратные платформы (обеспечение НА1_) и стремления уберечь разработчика драйвера от необходимости вникать в тонкости постоянно меняющихся версий аппаратного обеспечения на каждой конкретной платформы (например, объект адаптера позволяет абстрагироваться от реализаций контроллеров ОМА).
51 г и с1 иге Структура, тип данных языка С. Состоит из простых типов данных (сИаг, 1п1: и т.п.) и вложенных структур или объединений (ипюп), Например, тип данных 1_АкСЕ_11\1ТЕСЕк иногда (файл пЙеМп) определяется как структура, состоящая из одного поля: ТуресЛе^ зСгисС _ЪАКСЕ_1ЕТЕСЕК { ЬОЕСЬОЕС ОиадРагС; } ЪАКСЕ 1МТЕСЕК;
Оп1оп Объединение, тип данных языка С. Состоит из простых типов данных (сИаг, ]П1 и т.п.) и вложенных структур или объединений. Реализует доступ к одной и той же области памяти, как к данным разных типов. Например, тип данных 1_АРСЕ_11\1ТЕСЕР может быть определен (в файле п1х1еГ.И это выполняется при помощи условной компиляции) следующим образом: СурейеЕ ип1оп _ЬАКСЕ_1ЕТЕСЕК { зЕгисЕ { ПЬОМС ЬомРагС; ЪОЫС НхдЬРагР; } и; ЬОЕСЬОЕС ОиадРагС; } ЪАРСЕ ТЕТЕСЕР; Тип данных 1_АИСЕ_11\1ТЕСЕИ используется, например, в вызове КеЗеГПтег для установки таймера. Для того чтобы облегчить установку значений такого типа, можно применять вызов ВД1СогшегЫ_опдТо1-агде1п1едег, который скроет от разработчика, как конкретно реализован тип 1_АКСЕ_11\1ТЕСЕК. ЬАР6Е_ТЕТЕ6ЕР ТпЕег^а! = К1:1Со1ТУегбЪопдТоЪагде1пбедег (100*10) ; ф Здесь и далее обращения к системным функциям (типа ЯНХхх, КеХхх, 1оХхх, геас!, СгеаЪеРПе и т.п.) будут называться вызовами, а в тексте они будут обозначаться жирным шрифтом.
ОЬ]ес1 Объект. В программировании драйверов объект всегда является структурой или объединением, с которым связана одна из абстракций. Например, объект устройства - это всего лишь структура языка С. Однако она заполнена такими данными и на нее возложена такая логическая нагрузка, что все это позволяет говорить об этой структуре — почти что — как об устройстве. В режиме ядра имеются трудности с реализацией трюков С++ (оператора пе\л/, позднего связывания, идентификации типов во время выполнения и виртуальных методов), следовательно, и основных приемов объектно- ориентированного программирования (ООП). Соответственно, и объекты здесь "ненастоящие". Объекты режима ядра роднит с "настоящими" объектами (в смысле ООП) практически только одно обстоятельство: к каждому объекту прилагается набор функций, и фирма М1сгозоГ1: рекомендует работать с объектами ядра только при помощи этих специализированных функций. Этим М1сго5оГ1: достигает решения трех задач. Во-первых, скрывается внутренняя структура объектов (которая в будущем может модифицироваться разработчиком операционной системы или иначе реализовываться на разных платформах). Во-вторых, становится возможным ограничить пределы вмешательства программиста в жизнь ядра, что разработчик ОС считает потенциально опасным. В третьих, программист действительно делает меньше ошибок. В качестве объектов в ядре \Л/1пс1о\л/5 реализовано много концепций, например, существуют объекты процессов и потоков, объекты процедур отложенного вызова, объекты драйверов и устройств, объекты синхронизации и т.п.
Кегпе! тос1е Режим ядра. Привилегированный режим, в котором разрешено выполнять ответственные инструкции (команды процессора). Если приложение пользовательского режима в \Л/1пбо\л/5 98 попытается выполнить инструкции тс^ с!х, 037 8И оиС с!х,ах то не случится ничего страшного. Но после такого поступка в \Л/1пбо\л/5 ХР на экране монитора непременно появится сообщение, подобное следующему: Рис. 1.1 Исключение при выполнении привилегированных инструкций пользовательским приложением.. Что произошло? Просто \Л/1пбо\л/5 1\1Т позволяет выполнять ответственные операции только модулям режима ядра (к которым относятся драйверы режима ядра). В режиме ядра можно достоверно определять реально присутствующие в системе аппаратные ресурсы, непосредственно обращаться к ним, вызывать "могущественные" системные функции, влиять на прохождение данных (подсчитывать, кодировать/декодировать) и т.д. Программирование в режиме ядра имеет существенные особенности, прежде всего, это касается ответственности и необходимости обращать внимание на те вопросы, которые в пользовательском режиме не возникают. Например, на каком уровне приоритета 1к<21_ более оптимально выполнять отдельные рабочие операции и в какой тип области памяти размещать рабочий буфер? Образно говоря, программирование в режиме ядра отличается от программирования в пользовательском режиме так же, как жизнь на высокогорье отличается от жизни на равнине.
Узег тос1е Пользовательский режим. Непривилегированный режим, в котором выполняются обычные приложения, не имеющие возможности получать доступ к системным данным иначе, как обращаясь к вызовам функций подсистем (например, \ЛЛп32, СО1, Ро51х), которые, в свою очередь, прибегают к помощи системных вызовов. В пользовательском режиме все потоки, даже имеющие самый низкий приоритет, рано или поздно получают управления (возможность работы) в отличие от потоков режима ядра повышенных приоритетов, которые могут быть прерваны только потоками, получившими еще более высокий приоритет.
СаНЬаск, саНЬаск ГипсНоп Функция обратного вызова, саНЬаск функция. Прием программирования, когда один программный поток сообщает адрес известной ему функции другому программному потоку с той целью, чтобы второй выполнил вызов этой указанной функции в определенный момент (например, если поступил некий сигнал от аппаратного обеспечения или истек определенный интервал ожидания). Процесс передачи адреса вызываемой функции часто называется регистрацией.
Соп1ех1 Контекст. Этот термин употребляется программистами для обозначения двух существенно различающихся явлений. 1. Когда производится регистрация саНЬаск функции, производится определения параметра, который она получит в качестве аргумента при вызове. Как правило, это указатель на буфер с данными, которые саНЬаск функции необходимо знать, чтобы ориентироваться, зачем же ее вызвали. В драйверах чаще всего в роли контекста (контекстного указателя) выступает указатель на структуру описания устройства или структуру описания расширения устройства, например при регистрации 0рсРог15к (см. ниже). Именно так чаще всего и следует трактовать словосочетание "1п с1еу|се соШехС - в контексте устройства, иными словами: применительно к данному устройству. 2. Структура, отражающая состояние программного потока на данный момент. Создаваемая операционной системой \Л/1пс1о\л/з для каждого потока, служит, например, для запоминания состояния регистров и некоторой другой информации на момент окончания последнего по времени кванта времени, выделенного потоку. Структура контекста потока режима ядра несколько проще, чем контекста потока пользовательского режима. Для драйвера к контексту (по сути этого понятия) следует отнести также состояние объектов, которыми он владеет, 1кР пакеты (см. ниже) в очереди, к которым он может получить доступ, и даже состояние обслуживаемого устройства. Вольное, но все еще допустимое, значение слова "контекст" подразумевает некие признаки потока, вызвавшего одну из процедур драйвера (Опуег коийпе), которые перешли на вызванный код. С этой точки зрения, код драйвера режима ядра может выполняться в одном из трех контекстов: в контексте свойств пользовательского потока, который инициировал обращение к драйверу в контексте системного рабочего потока режима ядра как результат поступления сигнала прерывания от аппаратуры, то есть ни в каком из контекстов существующих потоков или процессов, которые были текущими в момент прерывания Приняв это определение, несложно понять, почему становится возможным благополучное разрешение следующей ситуации. Предположим, в некотором драйвере рабочая (Лзра^сИ) процедура, предназначенная для обработки 1ОСТ1. запросов от пользовательских приложений, получает при методе буферизации МЕТНОО_ЫЕ1ТНЕК виртуальный адрес буфера с пользовательскими данными (или для пользовательских данных). Этот адрес поступит в драйвер в том самом виде, как он был виден в пользовательском приложении (с адресом меньше 0x80000000, например, 0х00012А0). Однако этот виртуальный пользовательский адрес имеет смысл только в адресном пространстве вызывающего пользовательского приложения (такое же число в качестве
адреса в другом приложении пользовательского режима указывало бы на совершенно другую область памяти) - и это одно из неотъемлемых свойств виртуальной адресации. Должен произойти крах системы? Тем не менее, драйвер, к которому могут обратиться с подобными запросами разные пользовательские приложения (причем, почти одновременно), выполнит свою работу абсолютно корректно - данные попадут по назначению. Почему? Потому что рабочая процедура, обрабатывающая ЮСТЬ запросы от клиентов драйвера, работает в контексте вызвавших ее потоков, вследствие чего трактовка виртуальных адресов в подобных случаях не вызывает у операционной системы никаких затруднений. Драйвер получает доступ к нужной области памяти, и при этом никакой ошибки доступа к, на первый взгляд, "чужой" памяти не возникает.
ЯоиНпе Процедура. Строго говоря, под процедурой в программировании (начиная с "древнего" языка ЕоПтап) понимается модуль, получающий через заголовок параметры и ничего не возвращающий, в отличие от функции. В языке С таких традиционно понимаемых процедур нет - он привык обходиться одними функциями (правда, "старого типа" процедуру легко можно представить функцией типа уоИ). В результате "высвободилось" слово 'процедура'. Разработчики драйверов (как и многие программисты С и С++) стали позволять себе следующую вольность: вместо слова "функция" произвольно применяются и "функция", и "процедура" (впрочем, как и слово "вызов"). Поэтому, встречая в тексте книги слово "процедура", следует его понимать исключительно так: функция языка С. То же относится и ко всей документации на английском языке (относительно слова "гоийпе").
1п1еггир1 Зеплсе ЯоиНпе Процедура обслуживания прерываний. Функция, которую драйвер регистрирует для того, чтобы она получала управление в момент, когда аппаратура, обслуживаемая драйвером, передала сигнал прерывания. Задача этой функции выполнить некоторую самую минимальную работу и зарегистрировать саНЬаск функцию, называемую процедурой отложенного вызова для обслуживания прерывания (часто обозначается именем ОрсРог15к, однако автор драйвера может дать ей любое имя). Если учесть, что в операционной системе \Л/1пбо\л/5 на типовом компьютере ежесекундно "происходит" от 100 до 600 прерываний, то станет понятно, почему так вредно задерживаться на высоких приоритетных уровнях, которые имеют 15Н функции.
ОрсРогХЗЯ, ОеГеггес! Ргосес1иге Са11 Гог ХпГеггирГ Беплсе ВоиНпе Процедура отложенного вызова для обслуживания прерываний. Функция, которую драйвер регистрирует в момент работы 15Н-процедуры для выполнения основной работы при получения сигнала прерывания от устройства. Сама процедура 15К. работает при очень высоком приоритете, так что задержка внутри нее может привести к серьезной деградации системы, поэтому длительные операции следует "скидывать" процедуре ОрсРог15к (далее в тексте практически всегда будет использоваться именно это имя для данной функции, однако автор драйвера может присвоить ей любое название).
□еГеггес! Ргосес1иге Са11 Процедура отложенного вызова. Функция, которая будет вызвана позже (можно считать, "в более спокойной обстановке")- Из таких функций составляется очередь, чем ведает Менеджер (Диспетчер) ввода/вывода. Одно из применений этого типа функций уже упомянуто выше (ОрсРог15Н), о некоторых других будет рассказано чуть позже. Для учета ОРС процедур операционная система поддерживает ОРС объекты.
ЮМападег Менеджер ввода/вывода. Другой вариант перевода - Диспетчер ввода/вывода (ДВВ, Диспетчер ВВ). Является облаком кода (по аналогии с электронным облаком в физике), работающего в режиме ядра и относящегося к основным компонентам операционной системы. Ведает вопросами ввода/вывода, в частности, связывая приложения пользовательского режима с собственно драйверами. Все общение ДВВ с драйверами происходит исключительно при помощи вызова его процедур и передачи им (через заголовок) стандартизованной структуры данных 1кР, в которой сформулирована вся суть его обращения к драйверу. Помимо ДВВ в операционной системе выделяются и другие "сгустки" кода, которые обслуживают какую-нибудь часть ее ресурсов и называются Менеджер или Диспетчер, например, Менеджер Памяти, Диспетчер Объектов, Менеджер Энергопотребления (Ро\л/ег Мападег), РпР Менеджер, Менеджер Безопасности (ЗесигГСу Мападег) и т.п.
1КР, 1при1/ои1ри1 Яедиез!: Раске1, 1ЯР гедиезЕ, 1КР раскеЪ Пакет запроса на ввод/вывод. 1кР запрос, 1кР пакет. Равноценные переводы, поскольку обращение ДВВ к драйверу есть запрос посредством 1НР пакета, а 1кР пакет не имеет смысла, кроме как в контексте обращения с каким-то запросом к драйверу (либо от ДВВ, либо от другого драйвера, но, опять-таки, при посредничестве ДВВ). Сам по себе, пакет 1НР есть структура, состоящая из фиксированной части и изменяющейся части, носящей название стека 1кР или стека ввода/вывода (10 з1:аск). В случае, если драйверы поручают друг другу обработку запроса, то информация, которая меняется при "блуждании по инстанциям", сохраняется в этой изменяемой части 1кР.
Ю з1а с к 1оса11оп Ячейка стека ввода/вывода в пакете 1кР. Одна позиция в изменяемой части пакета 1РР, называемой стеком ввода/вывода ШР пакета. Ячейка стека сама является составной структурой. Если драйверы объединены в цепочку (называемую стеком устройств, Оеу|се З^аск^Х)), то, как правило, число ячеек стека ШР равно числу устройств в 0е71се 51:аск перед данным устройством (возможны варианты). Собственно, ячейки стека 1кР и предназначены для хранения "переменной" информации при хождении пакета ШР по стеку драйверов (или стеку устройств - на некотором этапе разработчики драйверов перестают делать различия между этими словосочетаниями). Однако хотя информация и "переменная", но имеет вполне определенный формат. При путешествии по процедурам драйвера, те извлекают из "своей" ячейки стека ввода/вывода полезную информацию. В некоторых случаях, передавая 1НР пакет вниз по стеку устройств (драйверов), драйверы могут и сохранять там, в пределах "своей" ячейки, некоторые текущие данные - если ожидают, что получат этот 1НР пакет при его обратном движении по стеку устройств.
Рабочие процедуры. Функции, которые регистрируется в вызываемой самой первой процедуре драйвера (ОпуегЕп^гу). Регистрация производится путем заполнения элементов массива МаргРипсйоп (указатель на начало этого массива ОНуегЕп1:гу получает косвенно через аргументы своего вызова). Индексом в этом массиве являются коды ШР_МЗ_Ххх, то есть описанные числами типы пакетов 1кР. Если драйвер считает необходимым обрабатывать ШР запросы какого-либо типа, то в соответствующем элементе массива МаргРипсйоп он регистрирует соответствующую функцию (записывает ее адрес). Диспетчер ввода/вывода, ориентируясь на заполнение этого массива, вызывает нужные функции драйвера - сПзра^сЬ гоийпез. Смысл данного словосочетания - "диспетчеризуемые" процедуры драйвера (а не диспетчерские!), что правильнее будет заменить на "рабочие процедуры" (функции) драйвера. Поскольку вне драйвера важны только адреса рабочих процедур (которые и регистрирует функция ОНуегЕп1ту), то все рабочие процедуры драйвера могут иметь совершенно произвольные имена. Редкие исключения составляют обязательные имена функций в некоторых специальных драйверах, например, видео.
Мадог 1ЯР Сос1е Основной код 1кР пакета. Число, которое обозначает назначение пакета 1кР, а значит и основной смысл данного обращения к драйверу. В пакете М1сго5оГ1: ООК каждое такое число имеет еще и символьное обозначение (установленное через #с1еГ|пе директиву). Для наглядности в литературе всегда используются присвоенные таким образом имена. Например, код 1НР_МЗ_ОЕ\/1СЕ_СО1\1ТЕЮ1_ имеет 1кР пакет, который поступил в драйвер (разумеется, из кода Диспетчера ввода/вывода) в результате того, что пользовательское приложение вызвало функцию Оеу1се1оСоп1го1 (см. пример драйвера в главе 3). Пакеты 1кР, соответствующие вызовам функций чтения или записи (геас1, \мгГСе) имеют коды 1РР_МЗ_РЕАО, 1РР_МЗ_\Л/Р1ТЕ.
гость 1/0 СопТгоЬ сос1е. Код управления вводом/выводом. Позволяет обращаться к драйверу с запросами, отличающимися от операций чтения и записи в устройство (хотя и они легко реализуются через 10СТ1_ запросы). Разработчик драйвера имеет возможность создавать свои собственные коды 10СТ1_. Данный код является одним из аргументов функции пользовательского режима Оеу1се1оСоп1го1 (в приложениях пользовательского режима). Поступающий в драйвер в результате работы этой пользовательской функции и Диспетчера ввода/вывода пакет 1кР будет иметь код 1кР_МЗ_0Е\/1СЕ_СО1\1ТкО1_, а одним из внутренних параметров данного 1НР пакета будет указанный в вызове функции ^еV^сеIоСоп^^оI код 10СТ1_.
Итог 1КР Сос1е Младший код 1кР пакета. Часто значение основного кода 1кР пакета обозначает слишком большой диапазон проблем. Для уточнения были придуманы младшие коды 1кР запросов, которым даны имена 1кР_М1\1_Ххх (бывают случаи применения и третьего уровня детализации). Например, запрос типа 1НР_МЗ_РМР, который обязаны обрабатывать все драйверы РпР устройств, слишком обширен, и его конкретизирует, например, младший код 1РР_М1\1_5ТОР_ОЕ\/1СЕ. Далее в тексте основной код 1НР запроса будем называть просто код 1РР, а младший код — суб-код 1кР.
ОпуегЕп1гу Процедура 0НуегЕп1ту. Функция драйвера, которая будет вызвана первой при его загрузке. Единственная функция драйвера, которую все разработчики предпочитают называть именно так (поскольку изменение стоит существенных и, в общем-то, бессмысленных усилий). Данная функция выполняется регистрацию основных процедур, в том числе рабочих (О1зра1:сИ Ноийпез, см. выше). Для \/\/0М и не-У\/ОМ драйверов задачи, которые должна решить эта функция, немного различаются. Функция ОНуегЕп1:гу выполняется на уровне 1Р<21_ равном РА551\/Е_1_Е\/Е1_ и даже может быть размещена в странично организованной памяти (путем специальных директив для редактора связей, линкера).
АЛЮМ, \ЛЛпс1оуу5 Опуег Мос1е1 Драйверная модель для \Л/1пбо\л/5. Ориентирована на устройства, поддерживающие спецификацию РпР (в особенности, самоидентификацию — сообщение своих идентификационных номеров — при подключении к соответствующей шине). В драйвер \ЛЮМ модели введены новые рабочие процедуры, которые отражают стадии подключения-обнаружения РпР устройства, события в изменении энергоснабжения и факт отключения устройства. "Правильное" РпР устройство обнаруживается шинным драйвером (той шины, к которой оно подключается), затем о нем узнает РпР Менеджер, после чего и загружается драйвер — в соответствии с полученными идентификаторами. Если \ЛЮМ драйвер "правильный", то он должным образом подключает себя к стеку драйверов на этой шине. После этого почти все в дальнейшей жизни драйвера зависит от обращения с 1НР запросами к драйверам, подключившимся к стеку ранее (начиная от запросов о конфигурации шины и заканчивая запросами к "своему" устройству). Драйвер модели ШМ использует только определения, доступные из файла \л/с1т.И, в результате чего не может использовать "полупартизанские" функции прежних ИТ-драйверов (функции типа На16е1Ви5Оа1а и т.п.). Драйверы модели \ЛЮМ зачастую полностью (вплоть до бинарной формы) совместимы для использования в \Л/1Пс1о\л/з 98, \Л/1Пс1о\л/5 Ме, \Л/1пбо\л/5 2000, \Л/1пс1о\л/з ХР и \ЛДпс1о\л/5 5еп/ег 2003, хотя бывают и исключения. Разумеется, можно не поддерживать обработку всех требуемых для РпР драйверов запросов, что вполне приемлемо для драйверов, которые нужны лишь в качестве окна в режим ядра. Однако для таких исследований проще использовать драйверы "в-стиле-ИТ" (ИТ з1зу1е или 1едасу драйверы, о чем рассказывается ниже).
Ьауеппд Многослойность. (Следовало бы перевести этот термин как "слоирование" — "насильственная многослойность", но такого слова нет в русском языке). Поддерживаемая моделью \/\/ОМ возможность реализовывать стековое соединение между драйверами. Находясь в стеке, верхний драйвер (подключившийся к стеку позднее) имеет возможность адресовать/ переадресовывать 1кР запросы нижним драйверам (находящимся в стеке до него). Абстракциями, которые выступают действующими лицами в обменах запросами, на самом деле являются не драйверы, а объекты устройств - именно они соединяются в стек и являются адресатами в получении и передаче пакетов 1НР. По мнению автора, главный выигрыш от такого подхода достается разработчикам драйверов устройств, подключаемых к шинам. Например, если взять шину 05В, то общение с внешним 05В устройством сводится к "правильному разговору" разрабатываемого драйвера (для нового внешнего устройства) с шинным драйвером 115В через специальные запросы. Альтернатива этому несложному подходу — работа по самостоятельному программированию всех существующих в мире вариантов 115В хост- контроллеров. (Разумеется, многослойность дает еще возможность мельчить "крупногабаритные" запросы ввода/вывода, вести подсчеты и частично изменять свойства нижних драйверов в глазах пользователей, "наблюдающих" сверху.)
Ас1с10еу|се Функция, которую должны поддерживать У\/ОМ драйверы, для того, чтобы в своем составе иметь обработчик, который будет вызван в момент обнаружения устройства. Регистрация Ас1сЮеу|се выполняется в момент работы ОпуегЕп^гу. Сама же процедура Ас1сЮе71се в драйверах, как правило, выполняет такую важную работу, как создание объекта устройства и подключение его к стеку устройств в момент вызова. (Справедливости ради, следует отметить, что если не-\ЛЮМ драйвер зарегистрирует процедуру Ас1сЮе71се, то ничего страшного не произойдет — и ее в соответствующий момент вызовет ДВВ, правда это событие уже не будет исполнено того смысла, как это имеет место в случае драйверов для РпР устройств.)
Оеу|се 1п81апсе Физический аппаратный компонент. Экземпляр устройства, возможно, один из нескольких других однотипных устройств, обслуживаемых данным драйвером. Это может быть как геометрически большой и автономный фрагмент (монитор или привод СО-НОМ), так и мелкий, физически неотделимый от других, несомненно солидных устройств внутри современного компьютера, компонентов, например, набор микросхем на материнской плате для обслуживания РС1 шины.
Оеу|се ОЬзес1, РОО, РОО Объект устройства, объект физического устройства (РИу51са1 Оеу!се ОЬ]ей), объект функционального устройства (Рипсйопа! Оеуке ОЬ)ес1:). Абстракции, созданные для представления дееспособных единиц в драйверной архитектуре. Объекты физических устройств (далее будем обозначать их аббревиатурой 'РОО') создаются шинными драйверами, когда те находят подключенные к шине реальные (физические) устройства - для отображения самого факта их существования. Именно к РОО объектам, принадлежащих шинному драйверу, будут подключаться объекты функциональных устройств (далее будем обозначать их как ТОО' либо 'объект-устройство') полноценных МОМ драйверов. Объект РОО создается собственно драйвером обнаруженного устройства (при помощи вызова IоС^еа^е^еV^се). Объект РОО может иметь свою "символьную ссылку" (зутЬоНс Ппк), по которой будет получать запросы от клиентов своего драйвера. Следует обратить внимание на то, что 1кР запросы получает не драйвер (хотя, и он тоже), а именно РОО объект, созданный в драйвере: в каждую рабочую процедуру (О15ра1:сИ коийпе) драйвера в качестве аргументов передается не только указатель на пакет 1НР запроса, но и указатель на РОО, которому адресован этот запрос. В результате подключения РОО-объекта (что внутри драйвера устройства) к РОО-объекту (что внутри шинного драйвера) создается впечатление, что один драйвер подключается к другому. Отсюда и берет начало жаргонизм "стек драйверов"! Те, кто не боится трудностей, может, например, создать в своем драйвере несколько РОО и получить для каждого символьные ссылки. Чего этим можно добиться? Р1апример, можно предоставлять разным клиентам драйвера функционально разные услуги по доступу к обслуживаемому данным драйвером устройству — в зависимости от того, какую символьную ссылку использовал новый клиент при открытии драйвера. Драйвер в-стиле-ЫТ, который не обслуживает реального устройства или обслуживает не-РпР устройство, не получает никаких РОО ни от каких шинных драйверов. Однако он все равно создает РОО объект, хотя бы только для того, чтобы иметь символьную ссылку (зутЬоНс Ппк) для связи с внешним миром. Возможны возражения, что существуют способы, как "добраться" до драйвера и без символьной ссылки — по зарегистрированному им при помощи вызова 1оНед181егОеу1се1п1егГасе интерфейсу (в большинстве случаев — путь для тех, кто коллекционирует трудности), однако, в данном случае для регистрации интерфейса опять-таки необходимо иметь указатель на РОО объект! Иными словами, трудно представить себе драйвер, которому не нужен был бы свой РОО объект, — этакий живущий сам по себе обрывок кода режима ядра.
Оеу|се Ех1еп81оп Расширение объекта устройства. Структура, конечный вид которой определяется автором драйвера (иными словами, он может делать в ней все что угодно). Данная структура создается в самый момент создания объекта устройства при помощи системного вызова IоС^еа^е^еV^се, одним из аргументов которого является ее размер — в момент создания объекта устройства система выделяет место (в нестраничном пуле памяти) и под эту "авторскую" структуру. В программировании драйверов плохим тоном считается создание переменных, глобальных для всего кода драйвера. Взамен, рекомендуется размещать эти претендующие на глобальность данные в структуре расширении устройства. Указатель на структуру расширения можно найти в объекте РОО сразу же после его создания по вызову 1оСгеа1еОеу1се. Существуют устоявшиеся традиции того, какие данные должны присутствовать обязательно в расширении устройства (это несложно увидеть из двух-трех примеров), внесение же дополнительных данных — по воле разработчика драйвера.
ЗутЬоНс Ыпк Символьная ссылка, символическая ссылка, строго говоря — символьная связь. В области разработки драйверов — файловый объект с особыми свойствами. Выполняя операцию по открытию такого файла (Сгеа1еЕНе, 2\л/Сгеа1еР|1е), клиент драйвера получает к нему доступ (в виде ненулевого дескриптора, НА^1_Е), причем все запросы клиента будут адресованы как раз тому ЕОО объекту, с которым соотнесена данная символьная ссылка (созданная по запросу драйвером в результате вызова 1оСгеа1е8утЬоНс1_тк).
Оеу|се 51а с к, Опуег 51а с к Стек устройств, стек драйверов. Страшилка для легковерного новичка, которому может показаться, что все объекты устройств (РОО и РОО) всех устройств, создаваемые в драйверах, включаются в один стек, в соответствии с многослойным подходом \ЛЮМ модели. Как там только не пропадают 1НР пакеты и еще остается какое-то быстродействие?! Между тем все устройства образуют довольно объемное дерево устройств (а вовсе не единый сквозной стек!) — и его можно рассмотреть, если обратиться в программе ^еV^сеТ^ее, поставляемой в составе пакета М1сгозоГ1: 00К. В этом дереве, например, из точки, которую можно назвать "РС1 шина" вырастают ветви шины 115В, шины Нге\Л/!ге, 15А шины, устройств 5С51 протокола. На самом деле, стеком следует считать относительно короткий отрезок соответствующей ветви, начиная от драйвера и его объекта устройства, куда вошел 1КР запрос (например, от пользовательского приложения), до места, где запрос окончательно был обработан. Можно считать, что в случае монолитного драйвера стека устройств нет вовсе, поскольку драйвер сам занимается обработкой всех своих 1НР. В случае же \/\/0М драйвера для устройства на шине 115В — стек от объекта устройства внутри этого драйвера "дотягивается" до объекта устройства внутри драйвера хост-контроллера 05В. Стек устройств формируется динамически по мере загрузки драйверов и подключения к нему устройств. Следовательно, подключение ЕОО нового драйвера к какому-нибудь драйверу в середине стека по окончании его формирования означает лишь одно — это будет боковой "сучок" и никакие запросы от верхних драйверов через него не пойдут (это замечание к популярной теме фильтр-драйверов).
МопоН1111С Опуег Монолитный драйвер. Драйвер, который не участвует ни в одном стеке устройств, получая и завершая обработку всех поступающих 1НР пакетов, самостоятельно обращаясь к своему обслуживаемому устройству. Несложно представить себе монолитный драйвер, который обслуживает старые устройства (не-РпР) или служит лишь в качестве средства доступа к функциям режима ядра (для исследовательских целей). Однако практически невозможно представить современный драйвер для устройств 5С51, 05В и Р1ге\ЛЛге, который был бы реализован как монолитный в прежнем смысле этого слова. В некоторых источниках информации по драйверам предпринята попытка модификации этого понятия. В них монолитным \ЛЮМ драйвером (!) называется драйвер, который сам получает свои 1кР запросы и доводит их до шинного драйвера, на чем его работа по общению с устройством завершается (остается лишь перехватить обратный отклик и его интерпретировать).
Ьедасу Опуег, ИТ 81у1е Огшег Унаследованный драйвер (устаревший драйвер), драйвер в-стиле-ЫТ. Драйвер, который не является \ЛЮМ драйвером, работает не с РпР устройством (если вообще работает с реальными устройствами) и не участвует в обмене данными по поводу изменений в энергоснабжении. При компиляции использует только определения из файла п!:с1с1к.Ь, отчего может в полной мере пользоваться устаревшими функциями На16е1Ви80а1а, На1СеНп1еггир1Уес1ог и т.п., но зато должен надеяться только на свои способности, поскольку у него нет могущественных прародителей в лице шинных драйверов, способных предоставить поддержку. "Правильный" драйвер в-стиле-ЫТ может быть запущен и остановлен при помощи программы МопКог (из состава пакета Ыитеда Кегпе1 Опуег) или процедурами 5СМ Менеджера. При наличии РпР вкраплений (в частности, из- за наличия зарегистрированной процедуры для обработки ШР_МЗ_РЫР запросов) эта возможность исчезает. Следует обратить внимание, что при компиляции кода с использованием пЙбк.Н, не только становятся недоступными отдельные функции, открытые раннее (для модели \АЮМ), но и изменяется назначение некоторых все еще доступных функций и полей внутри доступных структур. Тем не менее, подключение такого типа драйверов к другим драйверам по-прежнему возможно (при помощи вызовов IоАНасН^еV^сеТо^еV^се8^аск или IоАи:асI1^еV^се). Драйверы "в-стиле-ЫТ" совершенно не обязаны работать под \Л/1п98 (из-за использования специфических функций), и это иногда может стать существенной проблемой разработчика.
трь, 1п1еггир1 Ке(2ие811_еуе1 Уровень приоритета выполнения. Термин, исторически доставшийся от задач по обслуживанию прерываний (111(2), но теперь связанный не только с ними. Приоритеты, принятые для программного кода, работающего в режиме ядра. Планирование потоков с приоритетами 1Р.р1_ хотя бы на 1 выше минимального (РА551\/Е_1_Е\/Е1_) сильно отличается от планирования потоков в пользовательском режиме. В режиме ядра поток, работающий при некотором приоритете 1Нрь может быть прерван только для выполнения работы потоком с более высоким 1П<21_. Даже поток с равным приоритетом 1к(21_ должен дожидаться естественного окончания работы своего "равноправного коллеги". Поток может самостоятельно повысить свой Ш(21_, однако, величина повышения в некоторых версиях \Л/1пс1о\л/5 не произвольна. Например, работая на уровне РА551\/Е_1_Е\/Е1_ (0), поток может получить от операционной системы \Л/1пс1о\л/5 ХР согласие только на уровень 1к<21_ равный 10 (для сравнения, 015РАТСН_1_Е\/Е1_ имеет численное значение 3, а 11\1ТЕккиРТ_1_Е\/Е1_ численно равен 13). В отличие от действий по повышению собственного 1Н(21_, понижать приоритет потоку в режиме ядра категорически не рекомендуется, поскольку последствия могут быть непредсказуемыми. Приоритеты 1Р<21_ называются еще приоритетами диспетчеризации. Лишь внутри самого низкого приоритета 1Н(21_, равного РА551\/Е_1_Е\/Е1_ имеется градация потоков по приоритетам планирования (зсИедиНпд), часть из которых могут иметь потоки приложений пользовательского режима.
ПК), 1п1еггир1 Яеяие$1 Ыпе Аппаратная линия (проводник) от периферийного устройства, контроллера шины, другого процессора (в многопроцессорной системе), по которой они могут подавать сигналы о том, что им требуется обслуживание от данного процессора (микропроцессора).
Уровни аппаратных прерываний. Обработка сигналов прерываний (1КР), поступающих от реальных устройств, считается важным делом и должна проходить при повышенных уровнях приоритета режима ядра (1к(21_). Поэтому данным уровням дано особое название 0е71се 1КС21-, то есть О1Н(21_. Уровни приоритетов О1Р.С21. в системах на базе 1п1е1 занимают верхние 16 уровней в операционной системе.
Ро1Нпд Метод программирования работы с устройством, когда драйвер периодически опрашивает устройство на предмет изменения его состояния — в отличие от работы по сигналу прерывания, который может поступать от устройств, конструктивно имеющих такую способность. В частности, устройства Ы5В конструктивно не могут генерировать сигналы прерывания, но могут сообщать о подобных желаниях в момент их опроса хост-контроллером.
>/1г1иа1 Метогу Виртуальная память. Рассмотрим пример. Предположим, некая фирма имеет оплачиваемое пространство на складе своего оптового поставщика. Учет ее имущества производится хозяином склада в следующей форме. Каждая коробка имеет двузначный номер ПК (Полка/Коробка), где К — это действительно номер коробки 0..9 на соответствующей полке, а вот с номером полки дело обстоит сложнее. Для того чтобы узнать сквозной (физический) номер полки на складе, необходимо заглянуть в журнал выделения полок фирмам и получить из него настоящий номер полки. Правда, склад в нашем примере небольшой — всего десять полок! Неудивительной будет ситуация, если через несколько дней работы по предложенной системе коробка, относящаяся к рассматриваемой фирме, с виртуальным номером 19 будет находиться на полке 2, притом, что коробка номер 20 — уже на полке 8. Но нумерация коробок осталась непрерывной, что очень нравится владельцам коробок! Что дает такая система? Предположим, что очередному пользователю склада понадобилось 5 свободных полок, они имеются, но не подряд. Такая ситуация не внесет никакого разлада в систему учета, и понравившаяся клиентам склада непрерывная нумерация его пространства так и останется непрерывной. Аналогичная методика принята в современных операционных системах. Преимущества ее использования огромны. Приложениям не нужно ожидать получения непрерывных пространств памяти — достаточно наличия фрагментов стандартной длины. Более того, если какое-либо приложения имеет низкую активность, можно его виртуальную память "сбросить" в файл на жестком диске (этот процесс называется 5\л/арр!пд), а физическую память предоставить активным приложениям. (То есть — хранить коробки в подвале, но к моменту приезда клиентов — элегантно размещать их на полках склада. При определенной сноровке можно внушить каждому клиенту с большими запасами, что склад используется только для хранения его товара.) Помимо этого, можно относительно легко контролировать несанкционированный доступ приложений к памяти по адресам, которые им не были предоставлены. Столь объемное лирическое отступление было выполнено ради того, чтобы описать ситуацию, в которой проявляются весьма существенные проблемы.
Системный файл для хранения временно неиспользуемых областей странично организованной памяти (выгруженных из физической памяти).
Узег 8расе Пользовательское пространство памяти. Область виртуальной памяти, выделенная для работы пользовательских приложений. Обычно составляет 2 гигабайта и ограничена сверху адресом 0x79999999 (в серверной конфигурации возможны варианты, когда, в результате регулировки настроек операционной системы, приложения пользовательского режима получают возможность использовать 3 гигабайта виртуальной памяти, оставляя под системные нужды лишь 1 гигабайт). Все сказанное относится к 32-разрядным версиям \/\/1пс1о\л/5. В 64-разрядных версиях \Л/1Пс1о\л/5 ХР и \Л/1пс1о\л/з 5еп/ег 2003 ситуация более сложная, хотя для 32-разрядных приложений и там ничего не меняется. Подробнее вопросы адресации в 64-разрядных версиях \ЛДпс1оуу5 будут рассмотрены в главе 4.
Роо1 Метогу Память в пулах (страничном или нестраничном). Области в пространстве памяти ядра (адреса выше 0x80000000 для стандартной конфигурации системы — поскольку для сервера можно определить иначе), в которых можно динамически выделять (получать, аллокировать) и освобождать (деаллокировать) области памяти. Менеджер Памяти (Метогу Мападег) различает два типа пулов, к которым драйвер может получать доступ при помощи вызовов функций исполнительного блока Ех(есий7е): Радей роо1 — страничный пул, в котором каждый процесс имеет собственный набор РТЕ (Раде ТаЫе Еп^пез) — записей в таблице страниц. [Чопрадей роо! — нестраничный пул, в котором все процессы совместно используют один набор РТЕ — записей в таблице страниц. Выделение областей для физически непрерывных областей или областей некэшируемой памяти производится из ресурсов нестраничного пула.
Радей Метогу, Радей Роо1 Странично организованная память, страничный пул, страничная память. Виртуальная память, которая может быть перемещена системой на жесткий диск, в любой момент, когда она сочтет эту операцию целесообразной (например, при низкой активности приложения). Эта перемещенная область имеет название "радесТ, то есть "постранично сброшенная на диск". В том случае, если приложение, например, снова становится активным и обращается к отсутствующей в физической памяти области своей виртуальной памяти, то возникает исключение, хорошо известное под названием "раде Гайк". В результате его перехвата к работе приступает системный обработчик этого исключения и "подтягивает" в физическую память отсутствующую информацию. Однако при работе в режиме ядра кода, имеющего 1Р.01. уровень приоритета равный или выше О15РАТСН_1_Е\/Е1_, возникает катастрофическая ситуация. Если обстоятельства сложились так, что этот программный код должен получить доступ к виртуальной странице, сброшенной на диск, и эта страница могла бы быть размещена в физической памяти усилиями системного обработчика исключения типа "раде Гаи1Г", но... Но уровень обработчика ниже приоритета 015РАТСН_1_Е\/Е1_, и система не дает ему возможности приступить к работе, вместо этого прекращая всю работу системы! Обращаться к страничной памяти (если не предпринимать специальных мер) и производить получение новых областей категорически рекомендуется только из кода, работающего на 1Рр1_ уровнях РА551\/Е_1_Е\/Е1_ или АРС_1_Е\/Е1_. Можно, конечно, организовать собственный перехват исключения в сомнительном месте, как это сделано во фрагменте кода ниже. __Сгу { сЬаг х=*(сЪаг*)ОхОЬ; } __ехсерС(ЕХСЕРТ1ОЕ_ЕХЕСПТЕ_НАМОЪЕК) { РЬдРгтпЕ ("ЕхсерМоп деЕесЕед 1п ту с1г^ег"); }; Однако такое решение позволяет лишь уйти от конкретной ошибки (да и работает только на 1Рр1_ уровне РА551\/Е_1_Е\/Е1_). Получение доступа к нужным данным так и остается нерешенной задачей. Другое, более правильное решение предлагается ниже.
1Чопрадес1 Мешогу, Ыопрадес! Роо1 Нестранично организованная память, нестраничный пул, нестраничная память. Виртуальная память, которая никогда не может быть перемещена системой на жесткий диск и всегда остается в физической оперативной памяти. Ценный ресурс операционной системы, которым следует распоряжаться весьма осмотрительно. К памяти данного типа можно безопасно обращаться из потоков, работающих на любом уровне 1НС21_. Недостатки нестраничной памяти (помимо того, что ее не очень много) проявляются практически в одном случае — при работе с устройствами, поддерживающими режим прямого доступа к памяти (ОМА или, в русском варианте, ПДП). Будь то страничная виртуальная память, либо нестраничная — соответствующие области в физической памяти могут быть разрывными. Однако работа с ОМА устройствами требует физической непрерывности областей, из которых берутся или куда передаются данные при ОМА операциях. Выход, который могла бы предложить функция МтА11оса1еСоп1|диои5Метогу (она умеет выделять непрерывные области физической памяти), может лишь отсрочить момент краха, который неминуем, если все потоки в режиме ядра начнут практиковать исключительно такие методы. Объем памяти, которую система отводит под нестраничную память, ограничен. Даже при наличии достаточного объема физической оперативной памяти \ЛЛпс1о\л/5 2000 позволяла иметь до 660 Мбайт нестраничной памяти, а 32- разрядная версия \Л/1пс1о\л/5 ХР до 1,3 Гбайт. Использование физической памяти свыше 4 Гбайт в 32-разрядных версиях \Л/1пс1о\л/5 (даже если это позволяет аппаратура) производится через механизм физических адресов, что определяется параметром /РАЕ (разрешающим использование РНуз1са1 АсИгезз Ех1епзюп через загрузку другой под-версии ядра) в файле конфигурирования процесса загрузки Ьоо1.1п1, см. главу 4.
8са11ег/Са1Ьег РгоЫет Проблема сборки/разборки адресов. Возникла в момент определения методов работы аппаратуры ОМА в системах с виртуальной адресацией. В рамках общего подхода \Л/1пс1о\л/5, предлагается выполнять разборку непрерывной виртуальной области на локально непрерывные физические фрагменты. Результат помещается в МО1_ список, специально приспособленный для этого пакет данных. Драйвер получает МО1_ список и настраивает ссвоеЛ ОМА устройство, так, чтобы оно могло выполнить ОМА перенос в/из физически разрывной области клиентских данных (разумеется, используя информацию МО1_ списка) — как бы по фрагментам./р>
ОМА, О|гес1 Метогу Ассезз Метод обмена данными между устройством и оперативной памятью без участия центрального процессора. Процесс ОМА переноса данных может протекать либо под управлением самого устройства (Ьиз-таз^еппд ОМА или Лгз1:-раг1у ОМА) либо под управлением системного ОМА контроллера (з1ауе ОМА или 1:Ыгс1-раг1у ОМА).
Ассе$5 \Ло1а1юп Нарушение доступа. Попытка доступа к области памяти, которая вызвала исключение вследствие защиты доступа к страницам памяти (вследствие того, что данная страница относится к набору для другого процесса, а не из соображений безопасности). Типы нарушений доступа: Попытка доступа к памяти за пределами доступного текущему процессу адресного пространства (1епд1:Ь ую1айоп). Ошибочная операция, например, попытка записи на странице "только для чтения". Попытка доступа по запрещенному системой адресу, например, приложениям запрещено обращаться к нижним 64К адресного пространства, что упрощает обнаружение обращений по нулевому указателю. Доступ к странице, которая в данный момент предназначена для использования одним из системных компонентов (хотя и присутствует в физической оперативной памяти).
ЗЕН, §1гис1игес1 ехсерНоп ЬапсШпд Поддерживаемая операционной системой передача управления обработчику исключений, которые возникли во время работы (гип^те ехсерйопз).
ТНгеас1, ТЬгеас! ОЬ]ес1 Программный поток ("нитка"), объект потока. Поток является минимальной единицей исполнения и планирования в многозадачной операционной системе. Для учета потоков \ЛЛпс1оуу5 использует объекты потоков. В режиме ядра следует планировать работу с такими потоками, чтобы обеспечивать их естественное завершение (например, сигнализируя им при помощи объектов синхронизации). Особенностью многопроцессорных систем является возможность определения, на каком из конкретных процессоров будет исполняться код потока (параметра, известного как аГПпГСу). Состояние потока описывается его контекстом.
Ргосезз, Ргосе&8 ОЬ]ес1 Процесс, объект процесса. Процесс является объектом владения ресурсами. Чтобы процесс начал работать, необходимо, чтобы в нем был запущен хотя бы один поток (при создании процесса первый, первичный, поток создается автоматически). По окончании работы всех потоков процесса пользовательского режима операционная система освобождает все занятые им ресурсы, в частности, области динамически выделенной памяти, закрывает файлы, которые программист забыл закрыть. В режиме ядра такого сервиса со стороны системы просто нет. Разработчик драйвера должен тщательно следить за освобождением более не используемых ресурсов, чтобы к моменту выгрузки драйвера (если таковая предусматривается по логике его работы) что-нибудь не осталось забытым.
АГНпку Сродство к процессору. В многопроцессорной среде этот параметр определяет, на каком процессоре следует выполнять данный код. На симметричных многопроцессорных системах (5МР) — по умолчанию — потоки могут выполняться на любом. Соответственно, потоки, принадлежащие одному процессу, могут выполняться на разных процессорах одновременно. Это обстоятельство обязывает разработчика драйвера рассматривать вероятность того, что его продукт будет работать и на многопроцессорном компьютере, что возлагает дополнительные требования к синхронизации потоков (если драйвер многопоточный) и синхронизацию доступа к обслуживаемому устройству (правда, о последней проблеме следует помнить всегда). Особую заботу в многопроцессорных средах представляет обработка прерываний. Это связано с тем, что не исключена ситуация, когда, без должных предупредительных мер, при обработке прерывания на одном процессоре начинается обработка (вновь прибывшего) прерывания на другом.
§упсГ|гот2аНоп ОЬзес^з Объекты синхронизации. Эти объекты позволяют программным потокам корректировать последовательность своей работы или последовательность доступа к критическим данным, которые не могут быть доступны для чтения и записи разными потоками (то есть результат их модификации должен быть предсказуем в любой момент времени). В данную категорию входят События (Еуеп1:), Мьютексы (Ми1:ех, М1Лиа1 Ехсерйоп — взаимное исключение), Семафоры (ЗетарИоге), Спин-блокировки (5рт 1_оск) и даже объекты потоков. Не вполне объектами, но элементами синхронизации можно также считать аналог критических секций, что в режиме ядра реализуется при помощи вызовов КеЕп1егСгШса1Яед!оп и Ке1_еауеСп11са1Недюп. Следует отдавать себе отчет, что владение объектами синхронизации или возможность установки состояний объектов синхронизации не ведет непосредственно к остановке или запуску какого-либо из потоков. Имеется в виду тот факт, что объект синхронизации подобен светофору на перекрестке — только тот водитель, который соблюдает правила, останавливается. Лихач же, игнорирующий правила, может проехать, игнорируя любой сигнал. Для простой остановки выполнения кода потока (из него же самого) на некоторое время используются системные вызовы, например Ке81а11Ехеси1юпРгосе55Ог. Для организации запуска определенных процедур (функций) драйвера используются объекты таймеров, управление которыми выполняется при помощи соответствующих вызовов (КеЗеПЧтег и т.п.). Все объекты синхронизации являются объектами ядра, общими для использования и в режиме ядра, и в пользовательском режиме. Если передать в пользовательское приложение дескриптор, например, объекта события (Еуеп1: ОЬ]ес1:), который был создан в драйвере, то можно организовать пробуждение пользовательских потоков в нужное время вместо того, чтобы заставлять пользовательское приложение постоянно опрашивать драйвер.
РпР Мападег РпР Менеджер, один из ключевых компонентов операционной системы, конструктивно состоящий из двух частей, РпР Менеджера, работающего в режиме ядра, и РпР Менеджера пользовательского режима. Первый взаимодействует с остальными компонентами системы и драйверами в процессе загрузки программного обеспечения, необходимого для обслуживания имеющихся в системе устройств. Его часть, работающая в пользовательском режиме, отвечает за взаимодействие с пользователем в ситуациях, требующих установки новых драйверов или настройки рабочих параметров в существующих. Все драйверы должны обеспечивать РпР поддержку. В противном случае будут существенно ограничены РпР поддержка и управление энергопотреблением системы в целом. Если обратиться к процессу нумерации (епитегайоп) устройств и воспользоваться программой ОеукеТгее из состава пакета 00К, то станет очевидно, что во главе этого процесса стоит именно РпР Менеджер режима ядра (см. рисунки 2.6 и 2.7).
ЕпитегаНоп Процесс перечисления (нумерации). Обозначает последовательность действий со стороны системных компонентов по обнаружению устройств, реализованных в соответствии со спецификацией РпР. Возможен вследствие того, что устройства предоставляют о себе информацию в виде идентификационных кодов по протоколам, специфичным для каждого типа шины (РС1, 05В и т.д.), подробнее см. главу 11.
Епитега1ог Системный компонент, осуществляющий процесс перечисления (епитегайоп). Как правило, в этой роли выступают шинные драйверы, которые сообщают об обнаруженных устройствах РпР Менеджеру, после чего тот либо предпринимает шаги по загрузке и старту соответствующего драйвера (который он определяет по записям в Системном Реестре), либо начинает процесс установки драйвера (если подходящих записей не обнаружено). Нумерация некоторых устройств выполняется шинными фильтр-драйверами, например, АСР1 драйвером.
АСР1 Ас1уапсес1 СопЛдигайоп апс1 Ро\л/ег 1п1:егТасе. Абстрактный интерфейс, определяющий механизмы управления энергопотреблением и конфигурирования аппаратного обеспечения и компонентов операционной системы. Является частью так называемой "Инициативы Оп1\1о\л/".
АСР1 Опуег АСР1 драйвер. В системах с АСР1 В1О5 программное обеспечение НА1_ обеспечивает загрузку АСР1 драйвера во время старта операционной системы в корне дерева устройств. Драйвер АСР1 функционально прозрачен для других драйверов, которые не должны делать каких-либо допущений о наличии или отсутствии в своих стеках устройств фильтр-объектов от АСР1 драйвера. В обязанности АСР1 драйвера входит поддержка РпР возможностей системы и управления энергопотреблением, так что результаты работы АСР1 драйвера в виде многочисленных РОО или фильтр-объектов можно найти в ветвях дерева устройств (если воспользоваться программой ^еV^сеТ^ее), связанных с реальной аппаратурой.
Объект устройства, создаваемый фильтр-драйвером. Имеет следующее формальное отличие — это устройство остается безымянным (драйвер не указывает его имени при создании объекта) и не имеет символьной ссылки. Этот объект устройства, как правило, не предназначен для непосредственного доступа к нему, но после должного подключения его под или над устройством основного драйвера, это фильтр-устройство пропускает через себя все 1кР пакеты, на самом деле предназначенные основному драйверу.
ННег Опуег Фильтр-драйвер. Драйвер, предназначенный для выполнения дополнительных манипуляций над 1НР пакетами основного драйвера (вплоть до того, что самостоятельно отвергает их), к которому он подключается в стеке либо сверху, Оррег Нкег, либо снизу, 1_о\л/ег Нкег. У одного основного драйвера может быть несколько фильтр-драйверов, но они для основного драйвера как бы не существуют. Как правило, разбиение на основные и фильтр-драйвера делает сам разработчик основного драйвера, определяя порядок их установки и загрузки, обеспечивающий должную конфигурацию стека устройств в этом месте дерева устройств.
НАЬ, Нагс1шаге АЬ51гасНоп Ьауег Слой аппаратных абстракций. Слой программного обеспечения в \Л/1пс1о\л/5 1\1Т, который призван скрыть специфику аппаратной платформ (1п1:е132, 1п1:е1б4, А1рИа) от остальных компонентов операционной системы, обеспечивая малые затраты при переносе системы или элементов программного обеспечения. Уровень НА1_ предоставляет процедуры, которые позволяют абстрагироваться от аппаратных тонкостей, как, например, детали реализации шин ввода/ вывода, прерываний, ОМА операций и т.п. Для не-\ЛЮМ драйверов, например, уровень НА1_ предоставляет функции для получения информации о подключенных к шинам устройствах. Следует особо отметить процедуру НА1_ для выполнения отображения прерывания на конкретной аппаратной шине на общесистемный вектор прерывания с соответствующим уровнем приоритета 01к(21_. Персонально драйверам У\/ОМ остается лишь скромный набор макроопределений для доступа к портам ввода/вывода. Предполагается, что при дотошном использовании НА1_ инструментария проблемы по переносимости кода на другую аппаратуру будут минимальны.
Яед|$1гу Системный Реестр. База данных разнообразных настроечных и информационных параметров (вплоть до ключа, использованного при инсталляции данного экземпляра \ЛЛпбо\л/5), как для приложений пользовательского режима, так и конфигурационных параметров для всей системы. Файлы, составляющие эту базу, в 32-разрядных версиях операционной системы хранятся в директории %5уз1:етгоо1:%\5у51:ет32\СопПд\ и защищены системой от изменений. Несмотря на то, что программисты предпочитают обходить стороной это "страшное" место, тем не менее, запись конфигурационных параметров в Системный Реестр является нормальной и рядовой практикой. Такая программа, как 1п1:егпе1: Ехр1огег при простом движении курсора мышки по верхней части окна (где размещается меню и кнопки) делает до трех десятков операций над Реестром. Информация об обнаруженном РпР оборудовании и установленных драйверах также размещается в Системном Реестре. Составляющие разделов будем называть подразделами. В каждый подраздел могут быть вложены другие подразделы, но он может иметь и собственные данные, которые имеют имя и значение. Имена данных в подразделе будем далее называть параметрами (именами параметров), а собственно данные - значениями (значениями параметров.). Для редактирования и просмотра содержимого Системного Реестра предназначена программа редактирования Реестра, которая в \Л/1пс1о\л/з 2000 запускается командой гедесШ32, а в \Л/1пс1о\л/5 ХР и \Л/1пс1о\л/з 5еп/ег 2003 командой гедес1П:. Весь Реестр разделен на разделы. Самые крупные разделы носят названия Ыуе (улей). Различают разделы НКЕУ_1_ОСА1__МАСН1МЕ, НКЕУ_С1_А88Е8_КООТ, НКЕУ_СиККЕМТ_СОМЕ1(з, НКЕУ_С1)ВНЕМТ_1)8ЕН, НКЕУ_118ЕК8. (Здесь НКЕУ, очевидно, образовано от НуеКЕУ.) Наиболее употребительным для разработчика драйверов является раздел НКЕУ_1_ОСА1__МАСН1МЕ, который далее будет часто упоминаться в сокращенной форме как НК1_М. Он содержит общую информацию об аппаратном обеспечении и операционной системе (в том числе — установленных службах и драйверах). Инсталляция не-У\/ОМ драйвера может быть сведена к созданию подраздела в НК1_М\8у51ет\Сиггеп1Соп1го18е1\8еплсе5 с занесением туда трех-четырех параметров (и их значений) с последующей перезагрузкой, после чего драйвер появляется в системе. Работа с Системным Реестром через системные функции в \ЛЛпс1о\л/5 1\1Т требует использования кодировки 11тсос1е.
Нагс1шаге ЬгапсН Подраздел Системного Реестра НК1_М\Нагс1иуаге. Расширенное множество описывающее всю аппаратуру, когда-либо установленную на данном компьютере. Подмножество этого списка, называемое Нагс1ууаге 1гее, резидентно находящиеся в оперативной памяти, содержит только реально присутствующие в системе устройства.
Сиггеп1Соп1го18е1 Текущая работающая конфигурация. Подраздел Системного Реестра НКЬМ\8у51ет\Сиггеп1Соп1го18е1, описывающий текущую конфигурацию системы, на самом деле представляет собой информацию из одного из подразделов Системного Реестра, называющихся НК1_М\5у51ет\Сиггеп1Соп1го18е100Х, где X - число от 1 до 3. Информация какого конкретно фрагмента реестра используется в качестве текущей (то есть значение X), можно определить по значению параметра Сиггеп1: в разделе НК1_М\8у51ет\8е1ес1, который является указателем на один из подразделов НК1_М\8у51ет\Сиггеп1Соп1го18е100Х. (В самом деле, если что-то добавить или удалить в подразделе Сиггеп1Соп1го18е1, то точно такое же изменение произойдет и в том подразделе Сиггеп1Соп1го18е100Х, на который "указывает" параметр Сиггеп!:.)
1_а81Кпоууп(зООс1 Последняя работающая конфигурация. Подраздел Системного Реестра НКЬМ\5у51ет\Сиггеп1Соп1го18еЮ0Х, где X - число от 1 до 3, описывающий состояние системы, когда была выполнена полностью удачная загрузка. Какой же конкретно фрагмент реестра используется в качестве 1_аз1:Кпо\л/пбоос1 (то есть значение X), можно определить по значению параметра 1_аз1:Кпо\л/п6оос1 в разделе НК1_М\8у51ет\8е1ес1.
итсос!е Двухбайтная кодировка символов алфавитов. Делает возможной поддержку всех языков, имеющих буквенный или слоговый алфавит. Давно и широко применяется в \Л/1пс1о\л/з 1\1Т. В режиме ядра существует достаточный набор функций для работы со строками ип1сос1е.
Оеу|сеЮ Идентификатор устройства. Информация, идентифицирующая устройство. В ИГ-файле, используемом при инсталляции, а позже — в Системном Реестре информация об устройстве хранится в виде строки, формат которой может быть определен, например, как УЕМ_ХХХХ&ОЕУ_УУУУ&ЗОВЗУЗ_222222222&КЕУ_УУ Где ХХХХ — идентификатор производителя (\/ЕЫ0ОР. — поставщик), УХТУ — идентификатор устройства, 22222222 и \А/ уточняющие параметры. (Заметим, что эти числа хранятся во внутренних регистрах РпР устройства и становятся доступными при его подключении к шине.) Драйвер шины, к которой подключается устройство, передает идентификатор устройства в РпР Менеджер по запросу 1НР_М1\1_риЕНУ_Ю. РпР Менеджер использует эту информацию для того чтобы определить — какой драйвер следует использовать (если он уже был до этого установлен) или начинает процесс инсталляции драйвера, в результате чего будет создан и соответствующий подраздел в Реестре. Подробно форматы идентификационных записей будут рассмотрены в главе 12.
С1а$5 Опуег Классовый драйвер. Высокоуровневый драйвер, который представляет поддержку целого класса устройств (например, клавиатуры и мыши). При этом классовый драйвер абстрагируется от их аппаратных особенностей, поддержка которых возлагается на драйверы более низкого уровня, общение с которыми происходит при помощи ЮСТЬ запросов, саНЬаск функций или функций, экспортируемых этими драйверами нижнего уровня.
Рог1 Опуег Порт-драйвер. Драйвер самого низкого уровня, который отвечает на стандартные системные запросы и, возможно, дополнительно — на специальные 1ОСТ1. запросы соответствующего классового драйвера. Порт- драйвер изолирует классовые драйверы от специфики аппаратуры (возможно, при помощи мини-порт-драйвера) и синхронизирует их операции. Вообще говоря, любой драйвер устройства, являющегося "интеллектуальным контроллером" или шинным адаптером, может считаться порт-драйвером, если он общается с хотя бы одним классовым драйвером по определенному протоколу и синхронизирует доступ к контроллеру или шине. Среди примеров — 5С51 порт-драйвер, видео порт-драйвер, драйвер параллельного порта.
М1П1с1пуег Мини-драйвер. Представляет нечто меньшее, чем "полный" драйвер (являющийся для него оболочкой), и отражает аппаратную специфику обслуживаемого устройства. Реализуется, как правило, в виде динамически загружаемой библиотеки (ОЬЬ). Вероятно, самым известным примером мини- драйвера является 5С51 мини-порт-драйвер (мини-драйвер для 5С51 порт- драйвера). В данном термине в глоссарии М5 \Л/1пбо\л/5 Э0К наблюдается откровенный сбой, поскольку он трактуется как О1_1_, которая использует (?!) классовый драйвер для завершения своих запросов. (1) Это совершенно другое понятие, вовсе не стек ввода/вывода 1кР пакета!

Источники информации Разумеется, главным источником информации и истиной в последней инстанции относительно драйверов \Л/1пе1о\л/5 следует считать документацию М!егозой, поставляемую в составе пакетов разработки драйверов ООК (Оеу1се □пуег КН). Однако сначала о других источниках.

Печатные издания на русском языке 1. А.В. Фролов, Г.В. Фролов, Операционная система МВ- 005, том 1, - М., "Диалог-МИФИ", 1991 - 240 с. 2. Даниель Нортон, Драйвера \ЛЛпс1о\л/5, ввиду малого тиража на русском языке и давности момента издания невозможно установить выходные данные. Издана около 1995 года. 3. Свен Шрайбер, Недокументированные возможности \Л/1Пс1о\л/5 2000 (+СО), - СПб.: Питер, 2002 - 544 с. 4. Джеффри Рихтер, \Л/1Пс1о\л/5 для профессионалов: Создание эффективных У\Лп32-приложений с учетом специфики 64-разрядной версии У\Лпс1о775. Пер. с англ. - 4 изд. - СПб.:Питер: М.:Издательство торговый дом "Русская редакция", 2001 - 752 с. 5. С.И. Сорокина, А.Ю. Тихонов, А.Ю. Щербаков, Программирование драйверов и систем безопасности: Учебное пособие - СПб.:БХВ-Петербург, М.: Издатель Молгачева СВ., 2002 - 256 с. 6. Ольга Кокорева, Реестр \ЛЛпс1о\л/5 ХР, - СПб.:БХВ- Петербург: 2003 - 560 с.

Издания, которые не были переведены на русский язык В связи с тем, что приводимые ниже ссылки практически (по содержанию) незнакомы большинству русскоязычных читателей, то к каждой из них прилагается небольшой комментарий. 1. Кагеп НаггаГ), УУпйпд \Л/1пс1о\/У5 \/хОз & Оеу|се Опуегз; Ргодгатгтппд зесгеГз Гог VIгГиа1 Оеу!се Опуегз, 479 радез 2пс1 Вк&Ок есПГюп (МагсГ) 1, 1997) СМР Воокз; 15В1М: 0879304383. Книга целиком посвящена разработке \/хО драйверов под \Л/1пс1о\/У5 95, что позволяет говорить о ней, как о морально устаревшей. 2. А1еззапс1го КиЫп1, ЗопаГап СогЬеГ, Ыпих Оеу!се □пуегз, 2пс1 ЕсНГюп, 562 радез 2пс1 есНГюп (Зиле 2001) О'Ке|Ну & АззоааГез; 13В1М: 0596000081. Рассматриваются вопросы внутренней организации ядра Ыпих и создания драйверов как модулей ядра. Программисту в \Л/1пс1о\/У5 книга может быть интересна для проведения сравнительного анализа программирования драйверов в \Л/1пс1о\/У5 и Ыпих (11п1х). 3. У\/аИег Опеу, Ргодгатгтппд ГИе М1сгозоГ1 \Л/1Пс1о\л/5 □пуег Мос1е1, 624 радез Вк&Сс1-Р.от есНйоп (ЗерГетЬег 1999), М1сгозоГ1: Ргезз; 15В1М: 0735605882. Основательное издание, последовательно вводящее в программирование драйверов. На сопроводительном диске имеется отличный мастер инициации драйверных проектов на базе М3 \Лзиа1 ЗГисПо. Используются приемы программирования С++, особенно во втором (декабрь 2002 года) издании.
4. АН: Вакег, Зеггу 1_огапо, \Л/1Пс1о\л/з 2000 Оеуюе Эпуег Воок, ТИе: А (ЗиИе Гог Ргодгаттегз. 500 радез Вк&Сс1-кот есНГюп (ОесетЬег 15, 2000) Ргеп^се На11 РТк; 15В1М: 0130204315. Хорошо соответствует подзаголовку - руководство для программистов. Наиболее подробно из всех зарубежных изданий рассмотрено программирование ОМА операций, однако совершенно не рассматривается программирование 05В. 5. Ес1\л/агс1 И. Оеккег, ЗогерИ М. Ые\л/сотег, Оеуе1ор1пд \Л/1Пс1о\л/5 1\1Т Оеу|се Опуегз. А Ргодгаттег'з Напс1Ьоок, 1227 радез (МагсИ, 1999) АНсНзоп Мез1еу Ьопдтап, 1пс.; 15В1М: 0201695901. Замечательная и чрезвычайно объемная книга, посвященная программированию драйверов У\Лпс1о\л/5 2000 (хотя была завершена в момент выпуска ее бета-версии). Затронуто много вопросов, которые можно считать общесистемными. Дублирует много сведений из ООК, однако делает это с большим количеством комментариев. И хотя это та книга, которую должен иметь под рукой разработчик драйверов, ее нельзя считать книгой, которую новичку следует читать первой. Поскольку книга выпушена без СО-НОМ, это компенсируется размещением большого количества кода на интернет-сайте одного из авторов. 6. СИпз СапГ, Мпйпд \Л/1пс1о\л/5 \Л/с1т Оеу|се Опуегз: Соуегз 1\1Г 4, \ЛЛп 98, апс1 Мп 2000, 540 радез Вк&Сс1 Нот есПГюп (Зи1у 1999) СМР Воокз; 15В1М: 0879305657. Книга касается только драйверной модели \ЛЮМ и сосредоточена, в основном, на программировании ЬЗВ (хотя есть пример, связанный и с 1_РТ портом). Из-за своеобразного стиля изложения читатель-новичок, доверившийся этой книге, получит поверхностные знания, особенно если не уделит должного внимания примерам. Следует отметить, что некоторые примеры, связанные с НЮ
ЬЗВ устройствами плохо работают под \ЛЛпс1о\л/5 ХР. Тем не менее, несомненным плюсом данного издания является наличие на прилагаемом СО-НОМ прекрасного отладочного средства, известного под названием ОеЬидРпп!. 7. Ре1ег С. \7азсаго1а, \Л/. Алголу Мазоп, \Л/1Пс1о\л/5 1\1Т □еу|се Опуег Оеуе1ортеп1, 684 радез, (МоуетЬег 1998) МасМШал РиЬП5И|пд Сотралу; 15В1М: 1578700582. Книга написана ветеранами разработки кода под У\Ллс1о775. И хотя авторы аннотировали ее как "по! а соокЬоок" (не книга рецептов), что подтверждается малым количеством примеров кода и отсутствием СО-НОМ, эту книгу ни в коем случае нельзя сбрасывать со счетов, даже учитывая почти полное отсутствие в ней материала по У\ЮМ. Отдельные тонкости работы в режиме ядра 1\1Т предельно тщательно освещены только в этом издании. Наиболее подробно здесь (из всех упомянутых непереведенных изданий) рассмотрены ЫО15, 5С51 т1Л1рог! и видео драйверы. 8. Оау|с1 А. 5о1отоп, Магк кизз1ПОУ1сИ, 1пз1с1е МюгозоГ! \А/1Лс1оуу5 2000 (МюгозоГ! Ргодгатттд Зепез) МюгозоГ! Ргезз; Згс1 есПйол (5ер1етЬег 2000) 15В1М: 0735610215. Далеко небесполезная книга для разработчика драйверов, хотя имеет общеобразовательную направленность для программистов \Л/1Лс1о\/У5 1\1Т, 2000, ХР. 9. Сагу ЫеЬеН, \АЛлс1о\л/5 1МТ/2000 Ыайуе АР1 КеГегепсе, 528 радез; МТР; 15ВЫ 1578701996. Справочное руководство по набору У\Лпс1о\л/5 Ыайуе АР1 функций. Небольшое количество комментариев, немного примеров.

Материалы из пакетов разработки драйверов третьих фирм Некоторым читателям, возможно, понравится изложение знаний, как это сделано в документации к коммерческим пакетам разработки драйверов от третьих фирм (возможно, потому, что там необходимо убедительно доказать превосходство их программного обеспечения над М5 ООК). Перечислим наиболее крупных поставщиков такого программного обеспечения, из недавно весьма многочисленного состава которых на настоящий момент активно работают, практически, только двое: 1. Фирма Зипдо Ид. (бывшая \Л/ге1:с1п Иск), информацию о продуктах которой (\Л/1пОпуег и КегпеЮпуег) можно найти на интернет-сайте типдо.согг . 2. Фирма Сотри\л/аге Согрогайоп, распространяющая пакет разработки драйверов ОпуегЗ^идю. По интернет-адресу можно обнаружить много технической информации, касающейся программирования драйверов для \Л/|Пс1о\л/5 и Ыпих. Следует также помнить, что, загрузив оценочные версии продуктов от этих производителей и выполнив инсталляцию, можно ознакомиться с документацией, которая из-за объемности не выставлена на указанных интернет-сайтах.

Программные продукты от М!сго5оН Информация от Меговой:, полезная разработчику драйверов, может быть получена по нескольким каналам, которые имеют существенные различия. 1. Документация к М5 \Лзиа1 31:исНо, где описывается, хотя бы использование функций 5СМ Менеджера, что позволяет динамически загружать некоторые типы драйверов. 2. Р1аМ:огт ЗоГЬл/аге Оеуе1ортеп1: КИ: (Р5ОК), распространяемая как часть подписки М5О1М. Содержит весьма полезные программы (в частности, □ерепс15 и \Л/1пОЬ]). 3. Собственно пакет Оеу!се Эпуег КК (СОК). 4. Материалы ежегодной конференции У\ЛпНЕС по драйверам. Тактика распространения программ для разработки фирмой М1СгозоГ1: постоянно меняется. Хотя компиляторы \/15иа1 51:ис1ю изначально распространялись на коммерческой основе, однако, более специализированные средства разработки долгое время можно было бесплатно загрузить с интернет-сайта . Данная традиция прекратилась с выпуском ООК ХР, который поставлялся исключительно на СО кОМ, причем как бы бесплатно — необходимо только оплатить доставку курьерской службой. В настоящий момент этот прием применяется ко всем позднее выпущенным средствам для разработки и отладки драйверов (обновления Зеплсе Раскз, файлы с отладочными символами, тесты на совместимость с типовой аппаратурой персонального компьютера и т.п.).

Документация МкгозоН: ООК После инсталляции пакета ООК ХР (ЬинИ 2600) или ООК 5еп/ег 2003 (Ьш1с1 3790) среди вновь созданных директорий можно обнаружить каталог \Ие1р\, в котором размещены файлы-справки, имеющие расширение .сИт. При входе через "Пуск-Программы-..." чтение документации становится доступно в режиме, когда возможны переходы по ссылкам между разными файлами. (Таким образом, вся справочная информация выступает сплошным массивом, что вряд ли можно признать методически правильным в период начального ознакомления.) В противном случае, пользователь имеет дело с автономными файлами. Перечислим наиболее значительные из них. (Зз^ай.сНт Введение для начинающих (6ей1пд 31аг1ес1) Основное руководство по драйверной архитектуре \ЛЛпс1о\л/з, модели КтагсН.сНпл \Л/ОМ, деталям внутреннего устройства \ЛЛпс1о\л/з МТ, которые следует знать разработчику драйверов, справочник по функциям ЕхХхх, КеХхх, На1Хххх, 1оХхх, МтХхх, ОЬХхх, РзХхх, РЙХхх, ММ1 и 2ууХхх Особенности разработки драйверов устройств, подключаемых к Визез.сИт шинам ЯгеМге (1ЕЕЕ 1394), 113В (включая описание 11КВ), ЗС81, ЮЕ и РСМС1А 61озз.сНт Терминологический глоссарий 1пз1а11.сГ)т Освещены вопросы инсталляции драйвера и весьма подробно разобрана организация । пТ-файл о в (ЗгарНюз.Н Вопросы проектирования видео драйверов и драйверов для принтеров Вообще говоря, каждому, кто приступает к использованию пакета ООК, рекомендуется ознакомиться с содержанием всех файлов документации. Причина проста — некоторая информация встречается в не вполне ожидаемых местах, например, правила
присвоения РпР идентификаторов устройствам шины РС1 и другим устройствам, можно обнаружить в конце файла АррепсНх.сИт. Следует обратить внимание так же и на то, что пакет □□К поставляется с примерами драйверов и их исходными текстами. Некоторые из представленных таким образом примеров являются реально работающими в системе драйверами, как, скажем, драйвер параллельного порта рагрог^.зуз. Как своего рода документацию можно рассматривать и представленные в примерах весьма подробные комментарии.

ОпНпе документация МкгозоН: На интернет-сайте М1СгозоГ1, уже упомянутом выше, хранятся также статьи, посвященные частным случаям проблем, связанных с программированием в \Л/1Пс1о\л/5 при помощи продуктов МюгозоГ!, в частности, связанные с разработкой драйверов при помощи ООК. Статьи, зачастую, возникают как ответы на поступившие вопросы пользователей и имеют последовательно возрастающую нумерацию. Таким образом, если на одном из форумов некто написал, что некая волнующая собеседника проблема решается способом, описанным в <2115486 от МюгозоГ!, то это означает следующее: следует перейти по интернет адресу гтсго5о?*.сот и ввести упомянутый выше код в окне поиска, что даст возможность ознакомиться со статьей под заголовком "НО\Л/ТО: Соп1:го1 Оеуюе Опуег 1_оас1 Огс1ег (<2115486)" - "Как управлять порядком загрузки драйверов?". Более полную информацию о всех онлайн-ресурсах этого интернет-сайта, которая имеет прямое или косвенное отношение к драйверам, можно получить на странице с интернет адресом т1сго5ог*.сот/11уус1еу/с1пуег (хотя совершенно не исключено, что в будущем эта страница будет мигрировать).

Заключение В данной главе представлены начальные сведения по предметной области, называемой "Драйверы У\Лпс1о775".
Глава 2
Программные средства, применяемые при разработке драйверов В данной главе будет рассмотрен набор программных средств, который следует считать минимальным для использования при разработке драйверов \Л/1Пс1о\л/5. Часть из них успешно работает или имеет версии под \Л/|Пс1о\л/5 98, однако можно с уверенностью сказать, что лишь под 1\1Т разработка драйверов имеет основательную инструментальную поддержку.
ГРгеу|ои51 [Мех!],
Программные средства от МкгозоН Основным средством разработки является М1сго5оГ1 \Л/1Пс1о\л/5 00К, Оеу1се Опуег КК, — пакет разработки драйверов, включающий компилятор, редактор связей (линкер), заголовочные файлы, библиотеки, большой набор примеров (часть из которых является драйверами, реально работающими в операционной системе) и, разумеется, документацию. В состав пакета входит также отладчик \Л/1пОЬд, позволяющий проводить интерактивную отладку драйвера на двухкомпьютерной конфигурации и при наличии файлов отладочных идентификаторов операционной системы \Л/1пОЬд кроме того, позволяет просматривать файлы дампа (образа) памяти, полученного при фатальных сбоях операционной системы (так называемый сгазК с!итр Л1е). Следует особо отметить, что языком программирования, который используется в СЭК является язык С, разумеется, допускающий вставки на языке ассемблер, который в былые времена был основным и единственным языком программирования драйверов. В бесплатно распространяемом пакете ООК всегда отсутствовала интегрированная среда разработки. Поэтому программисты драйверов всегда были вынуждены подбирать для себя и средство редактирования исходного кода. Выбор был, практически, безальтернативен — пакет Х/1зиа1 С++ (теперь это \/15иа1 51ис1ю 7 Ые1). При должной настройке этой среды процесс выявлений синтаксических ошибок существенно облегчается — неотъемлемое преимущество интегрированных сред программирования. Компилятор и редактор связей \/15иа1 51ис1ю С++ создают нормальный бинарный код, вполне работоспособный при указании соответствующих опций (настроек) компиляции, однако эталоном следует считать бинарный код, получающийся при компиляции кода драйвера с использованием утилиты Вш1с1 из состава пакета ООК. Разумеется, встроенный интерактивный отладчик \/1зиа1 51ис1ю и прилагаемая документация становятся для разработки драйвера совершенно бесполезными, поскольку не предназначены для работы с программным обеспечением для режима ядра. Разработчику драйвера могут быть полезны некоторые вспомогательные программы, поставляемые теперь в составе Р1а1Гогт 50К, например утилита Оерепдз, подробнее о которой будет сказано ниже. ф Многие программные средства, упоминаемые в данной главе, являются коммерческими продуктами, их использование затрагивает вопросы приобретения и лицензирования, что выходит за рамки рассмотрения технических проблем, которым посвящена данная книга.
Настройки проекта в \Лзиа1 $1исНо 7 Настройка проекта для компиляции и сборки драйвера режима ядра существенно отличаются от настроек, которые используются для работы с приложениями и динамическими библиотеками пользовательского режима. Ниже приводится точный текст файл Ехатр1е.з1п ("з1п" является сокращением от "5о1ийоп"), который описывает проект драйвера Ехатр1е, рассматриваемого в следующей главе. МЕсгозоЕЕ У±зиа1 З^исИо Зо1иЕ±оп ЕНе, ЕогтаЕ Уегзтоп 7.00 Рго^есЕ("{8ВС9СЕВ8-8В4А-1Ю0-8В11-00А0С91ВС942} ") = "Ехатр1е", "Ехатр1е.Vср^о^", "{Е52 4ВАО 9-7993-4528-91А9-7Е27ЕАА35 65Е}" ЕпаРго^ есЕ С1оЬа1 С1оЬа13есЕ±оп(Зо1иЕ±опСопЕ±дигаЕ±оп) = ргеЗо!иЕ±оп СопЕЕдЕате.О = СНескес! Епс1С1оЬа13есЕ±оп С1оЬа13еск±оп (Рго^ есЕБерепс1епс±ез ) = розЕЗо1ик±оп Епс1С1оЬа13есЕ±оп С1оЬа13еск±оп(Рго^есЕСопЕ±дигак±оп) = розЕЗо1ик±оп {Е524ВА09-7 993-4528-91А9-7Е27ЕАА3565Е} . СЪескес!. Аск^еСЕд = Скескеа | Жп32 {Е524ВА09-7993-4528-91А9-7Е27ЕАА3565Е}.Скескеа.Ви±14.0 = Скескеа | Жп32 ЕпаС1оЬа13еск±оп С1оЬа13еск±оп (Ехкепз±Ы1±ЕуС1оЬа1з ) = розЕЗо1ик±оп ЕпаС1оЬа13еск±оп С1оЬа13еск±оп (ЕхЕепз±Ы1±ЕуАаа1пз ) = розЕЗо1ик±оп ЕпаС1оЬа13еск±оп ЕпаС1оЬа1 Значительно более важным в проекте Ехатр1е является файл Ехатр1е.усрго], который содержит конкретные значения настроек и описания используемых файлов. Точный текст Ехатр1е.усрго] (файла настроек для компиляции и сборки простейшего не-МОМ драйвера сЬескес1-версии в среде \/15иа1 5Еис1 ю 7 Ые1:) приводится ниже. <?хт! Vе^з^оп=”1.0” епсоа±пд = ”м±пасжз-1251”?> <У±зиа13Еиа±оРго^ еск Рго^ есЕТуре=”Утзиа! С++" \7егз±оп=”7.00" Еате=”Ехатр1е” ЗссРго^ есЕЕате=”" ЗссЬоса1Ракк=”"> <Р1аЕЕогтзХР1аЕЕогт Еате="Жп32 " /></Р1аЕЕогтз> <СопЕ±дигак±опз> <СопЕ±дигак±оп Еате=”Ре1еазе|Жп32" ОиЕриЕВ±гескогу=”.\скескеа" 1пЕегтеа±аЕеВ±гескогу=”.\скескеа" СопЕ±дигак±опТуре="2" ЕзеОЕМЕС=”0” АТЬМ±п±т±гезСРипТ±теЫЬгагуЕзаде=” ЕАЬЗЕ"
СкагаскегЗек=" 1 "> <Тоо1 Мате="УССЬСошрИегТоо1" Ас1с11Ыопа10рЫопз = "/2е1 -сЬзкгЬпд /С1Рск^- /С 0рЫт1гаЫоп="0" ЕпаЫе1пкг1пз1сЕипсЫопз = " ЕАЬЗЕ" 0т1ЕЕгатеРо1пкегз="ТВЬЕ" 0рЫт1геЕогРгосеззог="2" Ас1с11Ыопа11пс1ис1еЬ1гесЕог1ез = "С : \ЭД1пВБК\2 600\ С: \ЖпЬЬК\2600\1пс\м2к;С: \ЖпВБК\2 60 РгергосеззогЬеЫп1Ыопз="_Х8 6_=1;138 6=1; СОЕВ1Т1ОЕ_НАЕВЫЕС=1; ЫТ_СР=1 ; ЫТ_1ЕЗТ=0; ШШ2 = 10 0 ;_ЕТ1Х_=10 0 ; ШШТ _ШЕ32_Ш№Т=0х04 0 0 ; ШЕ32_ЬЕАЫ_АЕВ_МЕ БЕУЬ=1;БВС=1;ЕРО=0" 1дпогеЗЕапс1агс11пс1ис1еРаЕк="ТВЬЕ" ЗЬг1пдРоо11пд="ТВ1Ж" ЕхсерЫопНапс111пд="ТВ1}Е" ВипЫтеЫЬгагу="О" ЗЬгисЕМетЬегА11дптепЬ=" 4 " ВиЫегЗесиг1ЬуСкеск="ЕАЬЗЕ" ЕпаЬ1еЕипсЫоп^еVе1Ыпк^пд="ТВ1^Е" РгесотрИес1Неас1егЕ11е=" . \скескес!/Ехатр1е . рек" АззетЫегЫзЫпдЬосаЫоп=" . \скескес1/" 0Ь^ескЕ11е=" . \скескес1/" РгодгатВаЕаВазеЕИеЕате=" . \скескес1\Ехатр1е . рсЗ ЭДа^п^пд^еVе1="3" ЗирргеззЗЕагЕирВаппег="ТВ0Е" ВеЬид1пЬогтаЫопЕогтаЕ="1" Са1ИпдСог^епЫоп="2 " СотрИеАз="0" Еогсес11пс1ис1еЕ11ез = "магп1пд. к" / > <Тоо1 Еате="УССизкотВиИс1Тоо1" /> <Тоо1 Еате="УСЫпкегТоо1" Ас1с11Ыопа10рЫопз="" АсИ1Ь1опа1Верепс1епс1ез="ка1. ИЬ пкозкгп! . 11Ь тзVС^^.1^Ь " ОикрикЕ11е=" . \скескес!\Ехатр1е . зуз" Уегз1оп="5.О" Ыпк1псгетепка1="1" ЗирргеззЗЕагкирВаппег="ТВ0Е" Ас1с11Ыопа1ЫЬгагуВ1гесЕог1ез = "С: \ЭД1пВБК\2 600 1дпогеА11ВеЬаи1кЫЬгаг1ез = "ТВ1}Е" РгодгатВакаЬазеЕИе=" . \скескес!/Ехатр1е . рс!Ь" СепегакеМарЕИе="ТВОЕ" МарЕИеЕате="Ехатр1е .тар" ЗкаскВезе^е31ге="2 6214 4 " ЗкаскСотт1Е31ге="4096" ОрЫт1геВе1егепсез = "2 " ЕпаЫеСОМВАТЕо1с11пд="2 "
ЕпкгуРо1пЕ8утЬо1="Ег^егЕпкгу" 8еЕСкескзит="ТВ1}Е" ВазеАс1(1гезз = ” 0x100 00” 1трогкЫЬгагу=” " Мегде8есЕ1опз=" . гс1ака= . Еехк" ТагдеЕМас1а1пе=" 1" / > <Тоо1 Еате="УСМ1ВЪТоо1" МкТурЪ1ЬСотраЕ1Ые="ТВ1}Е" 8ирргезз8кагЕирВаппег=”ТК0Е” ТагдекЕгш1гоптепЕ="1" ТуреЫЬгагуЫате=” . \скескес1/Ехатр1е . к1Ь" /> <Тоо1 ^ате=”VСРозЕВи^1(1ЕVепЕТоо1” /> <Тоо1 ^ате=”VСР^еВи^1(1ЕVепЕТоо1”> <Тоо1 ^ате="VСР^е^^пкЕVеп^Тоо1" /> <Тоо1 Еате="УСНезоигсеСотр11егТоо1"/> <Тоо1 Еате="УСЭДеЬ8е^1сеРгохуСепегакогТоо1" / > <Тоо1 Еате="УСЭДеЬВер1оутепЕТоо1"/> </СопР1дигаЕ1оп> </СопР1дигаЕ1опз> <ЕИез> <ЕИкег Еате="Неас1ег ЕНез" ЕИкег=”. к"> <ЕНе Ве1аЕПеРак]а=" . \Ег1уег . к"> </ЕИе> </ЕИкег> <ЕИкег Ыате=”8оигсе ЕНез" ЕНкег=" . с; . срр"> <ЕНе Ве1аЕПеРаЕ1а=”1п1Е . срр"> </ЕНе> </ЕНкег> </ЕНез> <С1оЬа1з></С1оЬа1з> </У1зиа18кисНоРгсд еск> ф Приведенный выше текст переформатирован (для удобства чтения в формате книги), поэтому расположение слов несколько отличается от их размещения в оригинальных .чсрго] файлах, генерируемых средой \/15иа1 ЗЁисИо 7 ИеЁ. Значения строковых параметров Ас1сИИопа11пс1ис1еП1'гес1опе5 и РгергосеззогОеЯпПп'опз обязательно должны быть записаны в одну строку. Следует отметить, что особую важность для компиляции имеют значения пара метров 1дпоге51апс1агс11пс1ис1еРа1Ь (здесь он отменяет стандартные пути для обнаружения заголовочных файлов, которые явно заданы теперь в параметре Ас1сН1:1Опа11пс1ис1еО1гес1:ог1ез), Ас1сН1:10па10р1:10пз и Ргергосез5ОгОеГ|пШоп5 (значения которых рекомендуется повторить в точности), Са1НпдСопуеп1юп (здесь определяет ___51x1 са11). Из параметров сборки следует отметить параметры 1дпогеА1ЮеГаи1ШЬгапе5 (здесь он отменяет использование библиотек, назначаемых в \/1зиа1 51ис1 ю по умолчанию), Ас1сН1:1Опа11_|ЬгагуО1гес1:ог1ез и Ас1с1ШопаЮерепс1епс1е5 (они определяют используемые библиотеки — в данном случае для сборки не-МОМ драйвера под \Л/1Пс1оуу5 2000), ВазеАс1с1ге55 (обязательно следует указать равным 0x10000) и "неприметный" коварный параметр Зе^СЬескзит (должен быть "ТК11Е"). Все эти параметры можно настроить интерактивно и в самой интегрированной среде \/15иа1 51ис1ю, однако, затем рекомендуется сравнить содержимое файла .усрго] с
приведенным текстом, стараясь получить полное совпадение.
Компиляция и сборка драйвера утилитой Ви!1с1 пакета ООК В том варианте, как поставляется пакет ЭЭК, весьма просто использовать компилятор и редактор связей этого пакета. Для этого следует выбрать в меню запуска программ Пуск — Программы — ... запуск соответствующей среды (по ряду причин наиболее предпочтителен выбор среды \Л/1пс1о\л/ 2000, сКескес! или Ггее), в результате чего появится консольное окно, для которого уже (автоматически) будут должным образом установлены переменные окружения. В том случае, если у разработчика имеются файлы такеГПе, зоигсез (описывающие процесс сборки данного конкретного драйвера), а пакет ЭЭК установлен корректно, то необходимо лишь перейти в рабочую директорию проекта командой сс! (для драйвера, рассматриваемого в следующей главе, это — директория С:\Ехатр1е) и ввести команду ЬшИ (см. рисунок 2.1). Разумеется, что в случае ошибок компиляции или сборки вывод будет содержать и их диагностику. Рис. 2.1 Рабочее окно сборки драйвера под \Л/1пс1оуу5 2000 ЮЮК версии сЬескес!
Программа Оерепс15 Программа Оерепс15 предназначена для просмотра вызовов дополнительных библиотек. Программа Оерепдз была создана в 1996 году и ранее поставлялась в составе Х/15иа1 51ис1ю 6. Теперь она является частью Р1а1Гогт 50К. Скриншот (снимок экрана), представленный на рисунке 2.2, выполнен для просмотра дерева вызовов известного драйвера Сиуе1о.5у5. (Этот драйвер разблокирует доступ к портам ввода/вывода из приложений пользовательского режима при работе под \Л/|Пс1о\л/5 МТ, используя при этом недокументированные возможности \Л/1пс1о\л/5.) На приведенном рисунке видно, что драйвер обращается к функциям, экспортируемым МТО5КкЫ1_.ЕХЕ, причем видно, что первыми (в порядке алфавита) являются вызовы 1оСгеа1еОвУ1се, 1оСгеа1е8утЬоНсЬ1пк, 1оОе1е1еОеу1се, 1оОе1е1е8утЬоНсЬ1пк и др. Программа может быть использована для просмотра вызовов, выполняемых из драйверов, исполняемых файлов (.ехе файлов) и динамических библиотек. Программа работает и под \ЛЛпс1оу75 98. Рис. 2.2 Программа Оерепйз для драйвера С1уе!о
Программа кеВазе (консольное приложение) поставляется в составе \/1зиа1 51исИо и выполняет удаление отладочной информации из бинарных файлов, скомпилированных и собранных проектов, в нашем случае — из бинарных файлов драйверов (.зуз файлов). Даже после сборки окончательной (релизной, Ггее) версии драйвера при помощи программ ЭЭК в нем еще остается некоторая отладочная информация (например, внутренние имена функций), которую программа кеВазе может выделить и поместить в файл отладочных идентификаторов. Размер бинарного файла может уменьшиться при этом на четверть. Ниже приведены примеры командной строки для запуска программы кеВазе применительно к драйверу: геЬазе -В 0x10000 -X . ехатр1е.зуз геЬазе -ха ЬЬдсНг -Ь 0x10000 -1 ргоЬосо! ехатр1е.зуз Во втором примере ключ -ха (расширение ключа -х из первого примера) задает удаление всей отладочной информации (с перемещением ее в файл с расширением .с1Ьд). Директория, где будет размещен этот .с!Ьд файл в первом примере — текущая (поскольку указана точка после -х), во втором — вложенная поддиректория ДдЬдсПг. Ключ -Ь указывает базовый адрес (для драйверов режима ядра всегда указывается значение 0x10000), ключ -I (эль) указывает файл протокола (1од Л1е), во втором примере файл рго!осо1. Более подробно с командами кеВазе можно ознакомиться через сообщение, которое программа выведет по команде геЬазе -?
Программа ЕггЬоок Программа Егг1_оок, поставляемая и в составе ЭОК и в составе Х/15иа1 З^исНо, представляет собой декодер сообщений об ошибках. Полученный в приложениях пользовательского режима код ошибки (целое число) по вызову функции ОеИаз^Еггог несет мало информации для недостаточно квалифицированного пользователя. Получить по этому коду текстовое представления сообщения об ошибке можно с помощью специальных функций (при программировании приложения), либо подставив это число в программу Егг1_оок, как это показано на рисунке 2.3. рис. 2.з Программа ЕггЬоок ЁпогМн:вде |Т№-а№ыЪпо11ы41 | Цо | | ЦсЪ | Предположим, рабочая процедура драйвера возвратила управление Диспетчеру ввода/вывода, установив код завершения обработки пакета 1КР равным 5ТАТ05_ЭЕ\/1СЕ_РОУУЕкЕО_ОЕЕ. В результате, если пользовательское приложение обратилось к драйверу с вызовом Оеу1се1оСоп1го1, но драйвером ответил отказом такого рода, то Диспетчер ввода/вывода передает приложению код ошибочного завершения вызова функции (для ^еV^сеIоСоп^^оI это 0) и устанавливает код ошибки (известный в документации М50Ы под названием "зуз1ет еггог сос1е"). В свою очередь, пользовательское приложение может вызвать функцию (зе(Ьаз(Еггог и в результате получит число 21 (которое и определил Диспетчер вода/вывода), что соответствует ошибке ЕккСЖ_1\1ОТ_РЕАОУ — "ТКе деу!се 1з по! геас1у". В примере, представленном на рисунке 2.3, программа ЕггЬоок транслирует код 21 в текстовое сообщение, которое в русскоязычной версии УУ1пдо\л/5 выдается в нижнем окошке на русском языке. Следует обратить внимание, что, например, коду ошибки 21 (который выдается функцией 6е11_аз1Еггог пользовательского режима в пользовательских приложениях) соответствует сразу несколько кодов завершения обработки 1КР пакетов в драйвере: 5ТАТ1)5_ЭЕ\/1СЕ_М0Т_С0ММЕСТЕ0 5ТАТ1)5_0ЕУ1СЕ_М0Т_КЕА0У 5ТАТ1)5_ЭЕ\/1СЕ_ОЕЕ_1_1МЕ 5ТАТ1)5_ЭЕ\/1СЕ_РО\Л/ЕР_ЕА1ШРЕ 5ТАТ1)5_0ЕУ1СЕ_Р0\Л/ЕКЕ0_0ЕЕ Эту особенность неоднозначного соответствия кодов ошибок в пользовательском режиме (зуз1:ет еггог соде) и кодов завершения обработки запросов на ввод/вывод от Диспетчера ВВ (1РР запросов к драйверу) следует учитывать при выборе соответствующих значений 5ТАТ05_Ххх для достоверного информирования клиентов драйвера об ошибочных ситуациях. Транслировать код ошибки в текстовую форму программно можно при помощи несложной функции, текст которой приведен ниже. Функция получает код ошибки (зуз1ет еггог соде, например, 21) и выводит текст, расшифровывающий это значение. #1пс1иде <и!пЬазе.И> #1пс1иде <з1:с11о.к> уохс! РггЕпТЕггогМеззаде (ВИОР.Э егг)
ЬРТ8ТН тзд; МОКР гез = ::ЕогтабМеззаде(ЕОНМАТ_МЕ88АСЕ_ЕНОМ_8У8ТЕМ | ЕОКМАТ_МЕ88АСЕ_АЬЬОСАТЕ_ВОЕЕЕК, ЫЕЬЬ, еггг // код ошибки 0л // идентификатор языка по-умолчанию (ЬРТ8ТН) &тзд, О, ЫЕЬЬ); ФЕ(гез == 0) { /* неудача */ } е!зе { /* успешное завершение */ ргтпЕЕ("%з"лтзд); // вывод сообщения на экран ЬосаЕЕгее(тзд); // освобождение буфера с текстом } гебигп;
Программа (зшсКзеп (1Л1ЮСЕ1Ч) Программа ОиИСеп (1ЛЛ06ЕЫ — ее консольная версия) выполняет генерацию 128 разрядного уникального ключа (С11Ю — глобально уникальный идентификатор), который может использоваться для регистрации интерфейса драйвера, в процессе инсталляции и т.п. Вероятность повторения данного значения весьма и весьма низка (хотя и не равна нулю), так что программа СиИСеп является типовым инструментом для этих целей. Программа встречается во всех пакетах МюгобоЙ:. Как показано на рисунке 2.4, программа создала СОЮ в формате, удобном для включения в текст на языке С. Нажав на кнопку "Сору" можно перенести его текст в буфер обмена и вставить в нужное место в тексте программы или драйвера. Значения С1)Ю можно встретить и в программах, работающих по технологии СОМ, и в Системном Реестре, где они часто используются как идентификаторы классов устройств или входят в состав имен параметров, описывающих устройства. Рис. 2.4 Программа 6и1с16еп
Программа редактирования Системного Реестра Программа, без которой не обойтись ни одному разработчику драйвера — программа редактирования Системного Реестра, которая запускается командой Пуск - Выполнить - гедесШ: (в \Л/1пс1о\л/5 2000 — командой гедесП1:32). Целостность информации, находящейся в Системном Реестре весьма важна для операционной системы. Поэтому проводить эксперименты над Реестром следует весьма осторожно, поскольку даже резервное копирование Реестра (а также защита отдельных разделов от какого бы то ни было редактирования) может не спасти от необходимости переустановки всей системы. Без достаточной необходимости не следует оставлять изменения, сделанные в Системном Реестре (рекомендуется возвращать его состояние к начальному, имевшему место до экспериментов). ФНиже будет упомянута и еще одна полезная программа по изучению Системного Реестра — РедМоп (Ред/'зСгу МопНог)
В составе \Л/1Пс1о\л/5 00К поставляется программа Оеу1сеТгее (рисунок 2.5), абсолютно незаменимая при самостоятельном изучении УУЭМ модели, поскольку визуализирует представление о стеке драйверов (устройств) в операционной системе \ЛЛпс1оу75. Данная программа выполняет построение дерева устройств с двух точек зрения: с точки зрения принадлежности объектов устройств драйверам (режим О, рисунок 2.6) и с точки зрения взаимной подчиненности объектов устройств при выполнении операции перечисления устройств, епитегайоп ргосезз (режим Р, рисунок 2.7). Программа позволяет отслеживать подчиненность объектов устройств в локальных стеках драйверов, их принадлежность драйверам, выявлять существующие фильтр- драйверы, устанавливать (выяснять) коды 1РР пакетов, обслуживание которых объявил драйвер, и некоторую другую специфическую информацию. Рис. 2.5 Заставка программы Оеу1сеТгее На рисунке 2.6 показан фрагмент дерева устройств на участке шинного драйвера РС1. 11 объектов устройств (за исключением безымянного нижнего) представляют созданные этим шинным драйвером физические объекты устройств (так называемые РОО) для всех имеющихся в системе реальных РС1 устройств, включая мосты (РС1-РС1, РС1-15А), контроллеры 115В, АСР адаптер, аудио на материнской плате, РС1 адаптер ЕД1егпе1 и т.п. Рис. 2.6 Шинный драйвер РС1 и его объекты устройств Другой взгляд на этот же участок драйверного стека приведен на рисунке 2.7. Здесь показана взаимная подчиненность объектов устройств, возникающая в операционной системе при последовательно проводимой переписи устройств. Данное дерево отражает в своей структуре иерархию реальных устройств и очередность их обнаружения драйверами родительских устройств. Например, шинный драйвер РС1 обнаруживает подключенные к шине устройства, что приводит к загрузке их драйверов, что, в свою очередь, приводит к обнаружению новых устройств, подключенных к ним — как в случае с шиной 115В (ее контроллер является дочерним устройством шины РС1). Программа ОеуюеТгее является исследовательским инструментом, вносящим большое возмущение в работу системы, поэтому нередки случаи сбоя при некоторых ее операциях (наиболее часто - при выходе из программы). Рис.2.7 Драйверный стек от АСР1 драйвера до шинного драйвера РС1
Программа ОеуСоп Консольное приложение ЭеуСоп из состава вспомогательных утилит пакета СОК позволяет запускать, останавливать драйверы и собирать информацию об отдельных устройствах или их группах. Например, по команде с^еVсоп зкаск =изЬ >зкаск_пзЬ . кхк (собрать информацию обо всех устройствах в стеке 05В) данная утилита выводит в файл 51аск_и5ЬЛх1 следующую информацию (немного изменен формат): РС1\'УЕП_110 6&ОЕ7_3038&3[7ВЗУЗ_12340 925&ВЕ7_1А\3&61ААА01&0&ЗА Пате: У1А ВеV 5 133В Зекпр С1азз: {36ЕС9Е60-С465-11СЕ-8056-444553540000} ЕЗВ СопкгоШпд зе^тсе: изЬикст РС1\7ЕП_1Ю6&ОЕ7_3038&ЗЕВЗУЗ_12340 925&ВЕ7_1А\3&61ААА01&0&ЗВ Нате: \71А КеV 5 175В Зекир С1азз: {36ЕС9Е60-С465-11СЕ-8056-444553540000} ЕЗВ СопкгоШпд зе:гу1се: изЬикс! 175В\БЮОТ_Ш7В\4&1Е8Е7 657&0 Пате: Зекир С1азз: {36ЕС9Е60-С465-11СЕ-8056-444553540000} 173В СопкгоШпд зе:гу1се: изЫлиЬ Е5В\КООТ_НЕВ\4&ВВ5С5В2&0 Пате: Зекир С1азз: { 36ЕС9Е60-С4 65-11СЕ-8 05 6-4445535400 00 } 173В СопкгоШпд зе^тсе: изЫгиЬ 4 таксЫпд с1еV^се(з) коипск
Программа ОеуСЫ Консольное приложение ОеуС1:1 из состава вспомогательных утилит пакета ООК проводит тестирование драйвера путем применения к нему наиболее употребительных вызовов ввода/вывода пользовательского режима, например №Сгеа1еН1е или ^еV^сеIоСоп^^оI. В процессе тестирования могут быть выявлены серьезные упущения в программировании драйвера, например, некорректная обработка неожиданных (для тестируемого драйвера) ГОСТЬ запросов или запрос на получение данных, когда приложение указало заведомо малый размер буфера для получаемых данных. В качестве указателя на тестируемый драйвер программе ЭеуСИ следует передавать имя устройства (из числа тех, что видны в директории Оеу1се в рабочем окне программы \Л/1пОЬ], описание которой см. ниже). Более подробное описание программы ЭеуСИ можно найти в ЭЭК.
Программы СНкХпГ и 6еп1гИ Программа СКкТпГ (точнее, скрипт для интерпретатора Рег1) предназначена для проверки 1пГ файлов, необходимых для выполнения установки драйвера в операционной системе, и подробно будет рассмотрена в главе 11. Программа ОепТпГ предназначена для генерации 1пГ файлов в режиме вопросов и ответов (\Л/12агс1).
Программа Тазк Мападег (Диспетчер Задач) Не следует недооценивать значение использования стандартных системных средств в процессе получения нужных сведений о функционировании приложений, использующих драйверы. Всем известное системное программное средство Диспетчер Задач, вызываемый комбинацией клавиш С1т1-АК:-Ое1е1е, позволяет оперативно получать сведения о работающих процессах (рисунок 2.8). Необходимо лишь в пункте меню "Вид" выбрать интересующие параметры для отображения. Хотя относятся они к приложениям пользовательского режима, могут дать косвенное представление и о работе вызываемых драйверов. Закладка "Быстродействие" позволяет настроить отображение времени пребывания процесса в режиме ядра (в процентном отношении) — параметр "Вывод времени ядра" в пункте меню "Вид" для этой закладки. По этим показаниям можно оценить, какую часть времени работы приложения занимает обращение к драйверу. Рис.2.8 Рабочее окно системного Диспетчера Задач
Системный апплет "Производительность" Более глубоко лежащим средством мониторинга системы и отдельных процессов и служб является апплет "Производительность" системной панели "Администрирование", вызываемой запускающей последовательностью Пуск — Настройка — Панель управления — Администрирование — Производительность. Рабочее окно данного системного апплета показано на рисунке 2.9. По нажатию правой кнопки мышки появляется меню, допускающее добавление новых графиков в окно работающего апплета ("Добавить счетчики").Сочетание параметров, которые можно просматривать таким образом, настолько велико, что для их описания потребовалась бы отдельная глава. Для просмотра доступны параметры функционирования памяти, процессора, КЭШа, протоколов 1Р, ТСР, 1ЮР, отдельных процессов и сервисов, включая их отдельные потоки, — всего чуть менее полусотни информативных единиц. Для каждой такой единицы возможен просмотр достаточно большого набора счетчиков (как правило, не менее полутора десятков). Например, для процессора среди счетчиков, доступных для просмотра, можно назвать счетчики числа прерываний в секунду, процента времени бездействия, процента времени работы в пользовательском и привилегированном режимах, счетчика ОРС процедур, поставленных в очередь, в секунду. Краткие пояснения, касающиеся смысла указываемых счетчиков, можно получить при выборе счетчика по нажатию кнопки "Объяснение". Рис. 2.9 Рабочее окно системного Диспетчера Задач
Программное средство тестирования драйвера Опуег УепНег Программа Опуег УепЛег (последовательность старта Пуск — Программы — Оеуе1ортеп1 КИз — \Л/1пс1о\л/5 ООК — Тоо1з — Опуег Х/еНТчег) проверяет драйвер на правильность выполнения следующих тестов: Операции с пулами памяти. Корректность уровней 1К01_, на которых выполняется код драйвера. Обнаружение взаимоблокировок. Выполнение ОМА операций. Стресс-тест (нехватка ресурсов). Нетипичные запросы к драйверу. Проверка начинается после перезагрузки системы и при серьезных ошибках в драйвере может привести к необходимости переустановки системы. Поэтому не следует выполнять данные тесты на компьютере с ценными данными.
Программное средство проверки логики функционирования РгеРа8( Программа РгеРаз!:, появившаяся в составе пакета ООК Зегуег 2003, предназначена для выявления ошибочных паттернов (шаблонов) программного кода на уровне исходного текста. Она запускается в качестве наблюдателя за процессом сборки или компиляции какого-либо программного кода и по завершении этого процесса сообщает о дополнительно замеченных ошибочных фрагментах кода (в случае ошибки собственно сборки или собственно компиляции программа РгеЕазЬ в работу не вступает). Например, компиляция файла запускается под управлением РгеЕаз! следующей командной строкой: > ргеЕазб с1 /с туШе.срр > ргеЕазб V^еи Вторая строка при удачной компиляции покажет (если таковые имеются) логические ошибки в тексте, представленном в файле туГИе.срр. Каковы логические ошибки, пропущенные компилятором и выявляемые программой РгеРаз!:? Например, в следующем фрагменте *рбг1 = ша11ос(1000); ллохс! *рбг2 = ша11ос (1000) ; (р-Ьг2 == ИЮЛЬ) геЛигп ЕАЬЗЕ; // не выделена область памяти Ьгее(рбг1); Ьгее(рбг2); программа РгеРазЬ выявит потенциально возможную утечку памяти, поскольку возможна ситуация, когда при втором неудачном выделении памяти, не будет освобождена первая, выделенная ранее. То есть произойдет утечка памяти. На обнаружение такого типа логических ошибок, проявляющихся в пределах кода каждой отдельно взятой функции, и настроена программа РгеЕазТ Количество выявляемых ошибочных паттернов на настоящий момент составляет более трех десятков. Самым важным недостатком поставки РгеЕаз! в пакете ООК 5еп/ег 2003 сборки 3790 (на данный момент) является то, что поставляемые настройки позволяют работать только с кодом пользовательского режима (хотя и это бывает нужно разработчику драйвера), поскольку распознает только имена функций типа таПос, зрппЬГ и т.п. В режиме ядра, как будет показано позже, используются совершенно другие имена, которые, пока что, выходят за рамки настроек распознавания РгеЕазЬ.
ГРгеу1ои8~| ГИехМ
Программные средства из пакетов разработки драйверов от третьих фирм В настоящее время две фирмы, Зипдо Ш. и Сот ри Маге Согр., предлагают собственные коммерческие пакеты проектирования драйверов. Фирма Зипдо Ис1. предлагает разработчикам пакет М1п0пуег, позволяющий быстро создавать драйверы пользовательского режима (практически — динамические библиотеки), и пакет КегпеЮпуег для создания кода, работающего в режиме ядра (что более эффективно в смысле производительности драйвера). Оба пакета имеют удобные заготовки для программирования устройств, подключаемых к шинам РС1, Ь5В, 15А, и позволяют работать с ними программистам на Ое1рЫ и Вазю. Однако собственный базис функций представляет собой почти что новый язык программирования (в той степени, как это можно сказать, например, о наборе функций МЕС для программиста, ранее работавшего только с АР1 функциями У\Лпс1о775). Кроме того, для уверенной работы с данными пакетами крайне необходима постоянная лицензионная поддержка. Пакет Митеда Опуег 31:исНо (от Сотри\Л/аге Согр.) содержит в своем составе мощный отладчик ЗоШсе, ориентированный исключительно на платформу 1гПе1. Отладчик ЗоГНсе позволяет проводить отладку на одном компьютере (хотя опытные разработчики в категоричной форме рекомендуют не проводить отладку драйвера на компьютере с ценными данными и там, где установлены все программные средства разработки — время,
потраченное на восстановление системы квалифицированным специалистом, зачастую стоит дороже дополнительного компьютера). И хотя интерфейс с пользователем остается практически неизменным со времен М5 005, отладчик 5оШсе обладает мощными возможностями, по функциональности вряд ли уступающими возможностям отладчиков \/1зиа1 51:ис1ю для пользовательского режима. Несколько полезных программных средств от Сотри\Л/аге Согрогайоп рассматриваются ниже.
Программа МопКог от СотриУУаге Согрогайоп Программа МопНог от Митеда (теперь Сот ри Маге Согрогайоп) позволяет динамически загружать, запускать, останавливать и выгружать драйверы, выполненные в-стиле-1\1Т (не-МОМ), в большинстве случаев без перезапуска системы и без создания собственной программы загрузки драйвера при помощи 5СМ сервисов, а также без использования 1пГ файлов и системного Менеджера Устройств. Таким образом, достаточно подготовить лишь .зуз файл и затем воспользоваться программой МопИог. Вообще говоря, имеются и иные программы с данным сервисом, однако Мопког от Сот ри Маге Согрогайоп имеет наиболее завершенный вид (младшие версии работали еще с УхО драйверами) и удобный графический интерфейс. Первые четыре кнопки в панели инструментов, см. рисунок 2.10, посвящены загрузке (внесению записи о драйвере в Системный Реестр), запуску, останову, выгрузке драйвера и удалению записи о драйвере из Системного Реестра, соответственно. По мере выполнения этих действий в окне появляются диагностические сообщения о надлежащем выполнении операции или сообщения об ошибках. Недостаточно последовательно выполненные драйверы (имеющие в своем составе рабочие функции или вызовы системных функций, характерные для драйверов модели МОМ) могут не полностью обслуживаться данной программой (например, загружаются и запускаются, но не могут быть остановлены). Данное программное
средство удобно для проведения коротких тестов с несложными драйверами (как, например, драйвер Ехатр1е.5уз, подробно рассматриваемый в следующей главе). Рис. 2.10 Программа МопЛог
Программа трансляции файла воигсев в проект \Л5иа1 51исПо В составе пакета Опуег 51ис1ю имеется утилита, которая выполняет достаточно корректное создание файла описания проекта \/1зиа1 51ис1ю (.с!зр, файла описания проекта для МюгозоГ! \/15иа1 51ис1ю 6). Для программы ЗгсТоЭзр (рисунок 2.11) требуется в качестве входной информации файл зоигсез, управляющий обычно сборкой драйвера утилитой ВиПс! в пакете СОК. Среда программирование МюгозоГ! \Лзиа1 51ис1 ю 7 Ые1 также способна воспринимать .бзр файлы, однако при первой загрузке проекта она предпочитает перевести их в формат .усрго] (текстовый ХМ1_ формат, вполне читаемый и похожий на НТМ1_). Следует, тем не менее, критически относиться к результатам работы этой программы и не принимать все на веру, тщательно проверяя настройки проекта в \/1зиа1 51ис110. Кроме того, следует помнить, что чистовую сборку драйвера следует выполнять средствами пакета □□К (в котором настройки компиляции и сборки можно считать эталоном). Рис.2.11 Программа ЗгсТоОзр
Программа 1Читеда ЗутЫпкз В составе пакета Опуег 51ис1ю имеется утилита, которая позволяет просматривать все символьные ссылки, созданные реально функционирующими на данный момент драйверами. На рисунке 2.12 показаны некоторые из символьных ссылок, имеющихся в системе. В частности, в двух первых строчках указаны символьные имена НСОО и НСО1, соответствующие функциональным объектам устройств (с именами 115ВЕОО-0 и 115ВЕОО-1), которые обслуживаются драйвером 115В контроллера. То есть в системе физически присутствуют два 115В контроллера, к которым их клиенты могут обращаться с вызовом пользовательского режима Сгеа1еЕ|1е("\\\\.\\НСОО",...) или вызовом режима ядра 2шСгеа1еН1е (внутри параметров которого передается это же имя, правда, ритуал такой передачи несколько сложнее). Рис.2.12 Программа ЗутЫпкз Следует отметить, что программа 5утЫпкз существенно лаконичнее программ \ЛЛ пОЬ] и Оеу\Ле\л/, которые будут рассмотрены ниже.
ГРгеу1ои8~| ГИехМ
Программные средства от Марка Руссиновича и 5у51п1егпа15 Автор книги "1п51с1е \Л/1Пс1о\л/5 2000" Марк Руссинович является ветераном разработки программных средств для \Л/1пс1о\л/5, и некоторые из них будут весьма полезны разработчикам драйверов режима ядра. Ознакомиться с его программами и загрузить их зИагемаге версии можно на интернет сайте 5У51п*егпа15.сот.
Программа КедМоп Программа кедМоп (рисунок 2.13) перехватывает все обращения к Системному Реестру из приложений пользовательского режима и выдает сообщения о них в своем рабочем окне, что иногда бывает полезно, чтобы отследить, какие разделы этой общесистемной базы данных (Системного Реестра) использует конкретное приложение, и какие параметры его более всего интересуют. Рис. 2.13 Программа кедМоп Важный психологический эффект от знакомства новичков с этой программой состоит в том, что она со всей наглядностью демонстрирует: программы, созданные разными производителями, используют Системный Реестр очень интенсивно, некоторые даже чрезмерно (вспомним МкгозоГ! 1п1егпе1 Ехр1огег).
Программа АЛЛпОЬ] Программа ХЛЛпОЬэ является удобным средством просмотра директорий имен объектов операционной системы \Л/1Пс1о\л/5 1\1Т (включая 2000, ХР и 5еп/ег 2003). Для разработчика драйвера, естественно, наиболее интересными являются директории имен устройств и имен символьных ссылок (зутЬоПс Ппкз), Оеу!се и С1оЬа1?? соответственно, см. рисунок 2.14. Рис. 2.14 Программа \Л/1пОЬз В начале отладки драйвера непременно следует поинтересоваться в программе У\ЛпОЬ], созданы ли ожидаемые имена объектов устройств и соответствующие символьные ссылки, позволяющие обращаться к драйверу из клиентского кода (из приложения пользовательского режима или из другого драйвера режима ядра). Отсутствие ожидаемых имен сигнализирует о неполадках в драйвере. Зачастую, отсутствие этих имен в положенных местах сигнализирует о серьезных недочетах в процедурах инициализации драйвера, что не позволяет системе выполнить загрузку драйвера, а разработчику — увидеть хотя бы минимальные признаки жизни драйвера, хотя бы в виде диагностических сообщений для программ типа ОеЬид\/1е\л/ и ОеЬидРпп! МопНог, которые будут описаны ниже. Для просмотра некоторых из директорий имен объектов могут понадобиться привилегии администратора.

Программа ОеЬид\Ле\л/ Программа ОеЬид\/1е77 (рисунок 2.15) позволяет наблюдать в своем рабочем окне текст сообщений, которые во время своей работы выводит драйвер, если он использует специальные отладочные рпп!Г-подобные функции, такие как вызовы режима ядра ОЬдРпп1 в \Л/1Пс1о\л/5 1\1Т или ХЛ/1П32 вызов Ои1ри1ОеЬид§1ппд. Пример использования этих функций (а именно — ОЬдРпп!) в программном коде драйвера режима ядра можно увидеть в следующей главе (на примере драйвера Ехатр1е.5уз). Программа ОеЬидХЛем позволяет получать сообщения и с удаленных компьютеров по сетевым соединениям, включая Интернет, устанавливать фильтры (сообщения каких процессов следует выводить на экран), выводить сообщения в файл на жестком диске и просматривать сгазЬ битр файл. Некоторые сложности имеются лишь в получении сообщения от функций наблюдаемого драйвера, если они работают до момента запуска □еЬид\/1е\/у. В этом случае сообщения оказываются утерянными. Преодолеть эти затруднения можно при помощи программы ОеЬидРпп! МопНог, которая будет рассмотрена ниже. Рис. 2.15 Программа ОеЬидХЛеуу Для просмотра сообщений, которые выдают драйверы при работе инициализационных процедур (например, Ог/уегЕпЕгу или АбсЮеу/'се), но которые загружаются при запуске системы, можно использовать следующий прием. В случае, если драйвер был
инсталлирован при помощи Мастера Установки и т? файла, в результате чего драйвер виден в Панели Настроек в окне Диспетчера Устройств следует выполнить отключение устройства, после чего из системы выгрузится драйвер (в частности, отработает процедура ип1оас1). (Заметим, кстати, что между включением и отключением можно произвести замену файла драйвера на новую версию — если это необходимо). После этого необходимо выполнить там же, в Диспетчере Устройств, включение драйвера — в русскоязычной версии ]Л/'шс1о\л/5 это действие обозначено словом "задействовать ". В результате будет динамически загружен ранее отключенный драйвер, а его работа начнется с вызова процедуры ИпУегЕпбу и т.д., что позволит увидеть все диагностические сообщения для программы Пебид\/1е\д/ (разумеется, если разработчик их предусмотрел).
ГРгеу|ои51 [Мех!],
Программы Свена Шрайбера Программы, о которых здесь пойдет речь — лишь часть из программ-примеров, прилагаемых к уже упомянутой книге Свена Шрайбера "Недокументированные возможности \Л/1Пс1охл/5 2000", где можно ознакомиться с ними более подробно, включая исходные тексты и рабочие проекты, позволяющие выполнить компиляцию и сборку действительно работающих приложений. В настоящее время программы и их исходные тексты можно загрузить также с авторского Интернет-сайта Свена Шрайбера по адресу
Программа уу2к_зус Консольное приложение для \Л/1Пс1о\л/5 1\1Т, позволяющее просматривать установленные службы и драйверы. Например, по команде м2к_ЗVС /апу /а11 >1131:. 11x11 программа выводит список установленных системных служб и загруженных драйверов в текстовый файл 1151.1x1. Начало этого файла приводится ниже. // и2к_.з^с.ехе // 8В8 И1пс1оиз 2000 8е:гу1се Ызк VI. 00 // 08-27-2000 8Vеп В. ЗсйгетЬег // зЬз@огдоп.сот Еоипс! 2 61 йгз^егз апс! ргосеззез: 1. АЫозйзк................................. 2. аЬр4 8 0п5.............................. 3. Драйвер ................................ 4. АСР1ЕС ................................. АЫозйзк аЬр4 8 0п5 М1сгозо111 АСР1 АСР1 АСР1ЕС Записи отсортированы в алфавитном порядке по имени сервиса (имя, которым представлен драйвер, например, в Системном Реестре). В частности, драйвер мини- порта М1сго5оГ1:115В универсального хост-контроллера загружается из файла с:\\Л/1пс1о\л/5\5у51:ет32\ОР17ЕР5\и5ЬиИс1.5у5, имеет имя сервиса "изЬиЬа". Соответственно, в данном примере запись об этой службе идет под номером 238 (в соответствии с положением "изЬиЬа" среди имен остальных служб). Задать вывод информации только лишь о драйверах можно командой: м2к зус /йгз^егз /ас11уе >11зк.кхк
Консольное приложение для \Л/1Пс1о\л/5 1\1Т, позволяющее просматривать процессы и работающие драйверы режима ядра. Например, по команде м2к_зут /V /с! >Изк.кхк программа выводит список загруженных драйверов (см. фрагмент распечатки ниже) в текстовый файл 1151.1x1. В данном случае (для компьютера, на котором проводился тест) первые 27 записей абсолютно идентичны списку ОН и системных драйверов, выводимому при загрузке операционной системы, если в файл Ьоо1:.1П1 ввести ключ загрузки /боб (обеспечивающий вывод на экран списка загружаемых драйверов во время старта системы). 121 (Згз^егз Мопс1ау, 12-30-2002, 14:29:07 # АББКЕ88 812Е ЕАМЕ 1: 804Б0000 1Е4580 \ШЕВСЖ8\зузЕет32\пЕозкгп1. ехе 2 : 806В5000 1Е700 \эдтаЕОЭД8\зузкет32\11а1. с111 3: Е0998000 1В80 \ШЕБСЖ8\зузкет32\КБСОМ. БЬЬ 4 : Е08А8000 3000 \ШЕБСЖ8\зузЕет32\ВООТУ1Б . с!11 5: ЕБ44В000 2ВЕ00 АСР1.зуз б: ЕБ99А000 1100 \ШЕБСЖ8\8узЕет32\БК1УЕК8\ТСМ1ЫВ . 8У8 7 : Е0498000 Е500 рей.зуз 8 : Е04А8000 8000 йзарпр.зуз 9: ЕБ99С000 1100 V^а^с1е. зуз 10: ЕБ718000 5С80 \ШЕВСМ8\8узЕет32\ВВ1УЕВ8\РС11ВЕХ. 8У8 11: Е04В8000 9280 МоипЕМдг.зуз 12 : ЕО42С000 1ЕА00 УЕсИзк. зуз 13: ЕБ99Е000 1700 с!т1оас1. зуз 14 : ЕБ408000 23С80 атйо.зуз 15: Е0720000 4900 РагЕМдг.зуз 16: Е04С8000 сооо Уо18пар.зуз 17 : ЕБЗЕ2000 15280 акарй.зуз 18 : ЕО4Б8000 8380 сНзк. зуз 19: Е04Е8000 АЕ80 \ШЕВО^8\8узкет32\ВВ1УЕВ8\СЬА88РЕР. 8У8 20: ЕОЗЕ0000 11300 зг.зуз 21: ЕБЗВС000 23580 ЕазЕУак.зуз 22 : ЕБЗА8000 13780 К8есВБ.зуз 23: Е0380000 27700 ЕБ18.зуз 24 : Е0728000 6В00 V^аадр.зуз 25: ЕБ9А0000 1С80 птУИкег. зуз 26: ЕБ362000 Ю320 81^1(3.зуз 27 : Е0348000 19600 Мир.зуз
Интересно, что по командам и2к_зут /V /т - выводить список загруженных модулей и2к зут /V /р - выводить список работающих процессов информация о драйверах не выводится. Это косвенный, но уверенный намек на то, что драйверы не представляют полноценных процессов (впрочем, как и динамические библиотеки, ОН), а работают в контексте того кода, который к ним обратился.
Программа уу2к_тет Консольное приложение для \Л/1Пс1о\л/5 1\1Т, позволяющее просматривать дамп любых областей памяти, включая внутренние области ядра (системной памяти с адресами более 0x80000000), что недоступно для приложений пользовательского режима. Предположим, вы воспользовались командой и2к_тет #0x200 0х898Е9094 >сНзр1ау_тет1.РхЕ тогда в файле сИ5р1ау_тет 1.1x1 вы сможете увидеть дамп памяти длиной 512 байт (то есть 0x200) по шестнадцатеричному адресу 0хЕ98Е9094 — конечно же, только в том случае, если память по этому адресу в настоящий момент выделена какому-нибудь процессу. В противном случае, результат будет следующим: Е98Е9094..Е98Е9293: 0 Vа1^с^ ЬуЕез АсИгезз | 04 05 06 07-08 09 0А 0В 1 ОС ОО 0Е ОЕ-Ю 11 12 13 | 456789 1 Е98Е9094 1 1 Е98Е90А4 1 - - 1 Е98Е90В4 1 - - 1 Е98Е90С4 1 - - 1 Е98Е90Э4 1 - - 1 Е98Е90Е4 1 - - 1 Для успешно выполненной команды (в примере ниже по адресу 0х8СЮС5С88 существует занятая область памяти) м2к_тет #0x200 0х80ВС5С88 >сНзр1ау_тет2 . Тхк результат выглядит следующим образом: 80ОС5С88. Асйгезз .800С6087: 256 Vа1^с^ ЬуЬез 12 13-14 15 16 17 I 08 09 0А ов-ос ОО 0Е 0Е 10 11 80ОС5С88 1 | 03 00 С4 00-00 00 00 00 48 56 Е4 ЕЕ-00 00 00 00 80ОС5С98 | 00 00 00 00-00 00 00 00 00 00 00 00-40 00 00 00 80БС5СА8 I 00 00 00 00-00 00 00 00 40 50 ОС 80-22 00 00 00 80БС5СВ8 I 01 00 00 00-00 00 00 00 00 00 00 00-00 00 00 00 80ОС5СС8 | 00 00 00 00-00 00 00 00 00 00 00 00-00 00 00 00 80ОС5СО8 | 00 00 00 00-00 00 00 00 00 00 00 00-00 00 00 00 80БС5СЕ8 | 14 00 14 00-ЕС 5С ОС 80 ЕС 5С ОС 80-00 00 00 00 89АВСЕ Строго говоря, подобных результатов можно добиться и при помощи отладчиков например, ЗоГНсе. Тем не менее, у трех упомянутых программ имеется одно неоспоримое преимущество: методы, наработанные Свеном Шрайбером, могут быть
включены в нужное место программного кода ваших приложений (к чему призывает и сам автор этих программ). ф Наиболее очевидный способ получения стартовых адресов для путешествия по памяти режима ядра заключается в использовании вывода сообщений из отладочных версий драйверного кода, которые видны в рабочих окнах программ ЭеЬид Ией/ или ЭеЬид Рпп1 Мои Ног.

Программы от §т!с1деоп5оН Если программы Свена Шрайбера интересны для исследования У\Лпс1о\л/5 и допускают использования своего кода в новых разработках, представляя собой всего лишь консольные приложения, то программы фирмы 5гтпс1деоп5оГ1: являются завершенными программными продуктами с удобным графическим интерфейсом и реализующими практически все возможности из числа упомянутых ранее.. На интернет-сайте фирмы 5т!с1деоп5оГ1: по адресу можно загрузить последние версии этих весьма интересных программ, причем все полномасштабные версии распространяются бесплатно (на момент подготовки книги). Среди довольно большого набора следует отметить следующие программы.
Программа РЕВгошзеРгоГеззюпа! 1п1егасНуе Представляет собой программу просмотра "начинки" исполняемых ехе, с!11, зуз модулей (файлов РЕ-формата, Рог1:аЫе Ехеси1:аЫе Н1е Роптав) и дизассемблер (включая даже интерактивный отладчик программ пользовательского режима). Не самый удачный продукт 5гтнс1деоп5оГ1:, поскольку в первом качестве он существенно уступает программе РЕ Ехр1огег, а во втором — программе ЮА Рго. Тем не менее, данная программа мощнее и удобнее упомянутой ранее программы □ерепс15. Представляет интерес при изучении особенностей бинарных драйверных модулей (готовых драйверов). Кроме того, упрощенный вариант РЕВгомзе РгоГеззюпа! позволяет просматривать содержимое ПЬ- файлов.
Программа 1ЧТОеу1сез Программа МТОеуюез (рис. 2.20) представляет собой существенно более удобный в использовании аналог упомянутой ранее программы ОеуюеТгее, предназначенной для исследования стека драйверов в операционной системе. Позволяет также просматривать все символьные ссылки, зарегистрированные в системе, и список объектов устройств, имеющих подключенные (айасИес!) объекты других устройств. Рис.2.20 Программа 1\1ТОеу1се5
Программа 1ЧТОЬ]ес18 Более мощный аналог программы ХЛЛпОЬ]', позволяющий не только просматривать дерево директорий объектов операционной системы, но и оперативно получать дамп занимаемых ими областей памяти.
Программа 5у51ет Метогу Вгошзе Программа просмотра содержимого виртуальной памяти диапазона системного адресного пространства. Позволяет в традиционном режиме (то есть из программы Бузует Метогу Вгомзе, являющейся обычным приложением У\Лпс1о\л/5 1\1Т) получить доступ к такой информации, которая ранее была доступна разве только из отладчиков режима ядра (и, разумеется, упомянутой программы \л/2к_тет).

Заключение В данной главе не были рассмотрены программные средства, которые можно назвать средствами "второго эшелона". Среди них — программы верификации драйверов (которые тестируют драйверы, обращаясь к ним со всевозможными "глупыми" запросами — то есть такими обращениями, которые, как полагает разработчик, никогда не поступят в драйвер), а также модификация настроечных параметров запуска \Л/| пс!о\л/з (файл ЬооМпО, позволяющих, в частности, имитировать небольшой размер физической памяти. Кроме того, в составе утилит \Л/1Пс1о\л/5 ООК и на интернет сайтах упомянутых выше разработчиков можно обнаружить множество программ, которые могут оказаться полезными разработчику драйвера при решении отдельных специфических проблем.
Глава 3
Простой драйвер "в-стиле-1ЧТ": Ехатр1е.зу5 Все драйверы с точки зрения степени привилегированности их кода делятся на драйверы, функционирующие в пользовательском режиме и функционирующие в режиме ядра. Первые из них, драйверы пользовательского режима, представляют собой обычный программный код, как правило, оформленный в хорошо всем знакомые динамически загружаемые библиотеки (ЭЩ. Эти драйверы стеснены в обращении к системным ресурсам и опираются в своей работе на модули режима ядра, с которыми они тесно сотрудничают. Так устроен пакет проектирование драйверов У\ЛпОпуег от фирмы Зипдо □Д в котором клиентские приложения через функции пользовательского режима (библиотеку функций \Л/1пОпуег ОзегМобе ЫЬгагу, являющуюся, по сути, драйвером пользовательского режима) общаются с кодом режима ядра (модулем МпОпуег Кегпе1). В данной книге рассматриваются только лишь драйверы второго семейства — драйверы режима ядра. Программирование в режиме ядра имеет свои специфические особенности. В частности, в качестве библиотечных функций (типа привычных таНос и Ггее) в режиме ядра применяются системные вызовы (например, ЕхА11оса1еРоо1 и ЕхГгееРоо!). Более подробно вопросы программирования в режиме ядра будут рассмотрены в главе 7. Пока что кратко рассмотрим на примере простого драйвера Ехатр1е.зуз, как организованы драйверы режима ядра и как
происходит связь с ними из приложений пользовательского режима. Приведенный ниже код драйвера Ехатр1е.5уз является завершенным драйвером, который готов к компиляции и использованию в операционной системе в качестве тестового примера. По своей сути, приведенный код не может быть полноценным \Л/ОМ драйвером (в силу отсутствия в нем некоторых основных рабочих процедур драйверов РпР устройств), хотя и может быть успешно скомпилирован средствами \/15иа1 С или СОК с \Л/ОМ директивами. Драйвер Ехатр1е.5уз более подходит под описание "монолитный драйвер в-стиле-МТ", так что он вполне подойдет в качестве заготовки для экспериментов, которые будут над ним выполнены в последующих главах. Перед тем, как перейти непосредственно к рассмотрению примера, следует сделать одно важное замечание. Наиболее простое и одновременно весьма точное определение драйвера режима ядра гласит: драйвер — это ОН режима ядра. В самом деле, драйвер реализован как набор функций, каждая из которых предназначена для реализации отдельного типа обращений к драйверу со стороны Диспетчера ввода/вывода. Экспорт этих функций выполняется путем их регистрации в процедуре, стандартной для всех драйверов, — □пуегЕп1ту. Драйвер может быть загружен и выгружен, а для выполнения действий по инициализации или освобождению ресурсов драйвер должен зарегистрировать соответствующие рабочие функции. Как и всякие аналогии, сравнение драйвера режима ядра с /X/. Ф пользовательского режима имеет и некоторые изъяны. В частности, переменные, объявленные глобальными в динамических библиотеках, не являются общими для всех приложений, подключивших эту библиотеку. В драйвере же все наоборот.
Переменная, введенная как глобальная для драйвера, может быть установлена в определенное значение по указанию драйверной процедуре из одного приложения, и это же значение может получить из драйвера другое приложение.
ГРгеу|ои51 [Мех!],
Компиляция и сборка драйвера Ехатр1е.5у5 Компиляция и сборка отладочной версии драйвера в среде \/15иа1 51ис1ю 7 №1 требует выбора пункта меню Р.еЬи11с1 5о1ийоп, после чего будет выполнена компиляция и сборка драйвера, а результат (в соответствии с настройками Ехатр1е.51п и Ехатр1е.усрго]) будет размещен в поддиректории ДсКескес!. Для компиляции и сборки драйвера утилитой Ви11с1 пакета ЭЭК потребуется создать два файла описания проекта — МакеЛ1е и Зоигсез.
Файл МакеЛ1е Этот файл управляет работой программы ВшИ и в нашем случае имеет стандартный вид (его можно найти практически в любой директории примеров ООК), а именно: # Файл МакеВИе # # ВО ИОТ ЕБ1Т ТН13 Е1ЬЕ! ! ! ЕсНВ .\зоигсез. 1В уои иапВ -Со ас1с1 а пей з # Ше Во ВЫз сотропепВ. ТЫз ВИе шеге1у тпсНгесВз Во Вйе геа! таке # ВйаВ 13 зйагед. Ьу а11 Вйе с!гз^ег сотропепВз оВ Вйе ИВпдоиз МТ ВВК # ! ТМСЬТОЕ $ (ИТМАКЕЕМУ) \шакеВИе . йеВ
Файл Зои геев Файл зоигсез отражает индивидуальные настройки процесса компиляции и сборки. В нашем случае файл Зоигсез чрезвычайно прост и имеет вид: # Файл Зоигсез ТАКСЕТЫАМЕ=Ехатр1е ТАКСЕТТУРЕ=ЭК1УЕК #РК1УЕКТУРЕ=И0М ТАР.СЕТРАТН=оЬд ЗОСКСЕЗ=1п1'Ь. срр Данный файл задает имя выходного файла Ехатр1е. Поскольку проект (ТАВСЕТТУРЕ) имеет тип ОР1\/ЕР, то выходной файл будет иметь расширение .зуз. Промежуточные файлы будут размещены во вложенной директории ДоЬ]. Строка ЗО1ЖСЕ5 задает единственный файл с исходным текстом — это файл ИИ.срр. ф Если бы мы выполняли компиляцию и сборку УУИМ драйвера, то нужно было бы в тексте Оп'уег.Н использовать #тс1ис1е "\л/с1т.1'1" (взять определения из заголовочного файла "шс/т.Н" вместо ”п1с1с1к.Н''), а в данном файле Зоигсез — удалить символ (который вводит строку-комментарий) в первой позиции третьей строки. После этого строка ОП1УЕКТУРЕ= ШОМ стала бы указывать утилите ВиПс! на то, что выполняется компиляция и сборка \А/ИМ драйвера.
Компиляция и сборка при помощи утилиты Ви!1с1 Разместим для определенности все файлы (нам понадобятся файлы 1п11.срр, Опуег.К, МакеГИе и зоигсез) в директорию С:\Ехатр1е. После этого процесс компиляции и сборки сКескес! (отладочной) версии драйвера при помощи утилиты Ви11с1 пакета ЭЭК полностью описывается во 2 главе ("Компиляция и сборка драйвера утилитой Ви11с1 пакета СОК"). Результат сборки можно будет найти в поддиректории .\оЬ)сКк_уу2к\|386 (поскольку используются настройки переменных среды сборки под \Л/1Пс1охл/5 2000).
ГРгеу|ои51 [Мех!],
Инсталляция и запуск драйвера Ехатр1е.5у5 Существует несколько способов инсталляции и запуска данного драйвера. Следует отметить, что в других случаях выбор может быть не столь разнообразен, в частности, УУЭМ драйверы рекомендуется инсталлировать при помощи Мастера Установки нового оборудования и 1пГ файла.
Инсталляция внесением записей в Системный Реестр Метод инсталляции путем внесения записей в Системный Реестр (Ред151ту) не требует никаких вспомогательных программных средств — только редактор Системного Реестра. Редактирование Системного Реестра, предлагаемое ниже, не является критичным для работоспособности операционной системы. Модификация Системного Реестра \ЛЛпс1о\л/5 98 Как ни удивительно это будет узнать, но данный драйвер (собранный как версия сКескес! для \Л/1Пс1о\л/5 2000) устанавливается, запускается и работает под \Л/1пс1о\л/5 98 5Е. Для запуска драйвера следует переписать его бинарный файл Ехатр1е.5у5 в директорию С:\\Л/1Пс1о\л/5\5у51:ет32\Опуег5 и создать файл (назовем его Ехатр1е98.гед) со следующими записями: КЕСЕО1Т4 [НКЕУ_Ь0САЬ_МАСН1ЫЕ\8уз'Ьет\Сиггеп'ЬСоп'Ьго13е'Ь\8е^1сез\Ехатр1е] "ЕггогСоп1го1"=с1иогс1: 00000001 "Туре"=с1иогс1: 00000001 "8'Ьаг'Ь"=с1могс1: 00000002 " 1тадеРа'Ыл" = " \\Зуз'ЬетКоо'Ь\\8уз'Ьет32 \\Вг^егз\\Ехатр1е . зуз " После этого следует войти в редактор Системного Реестра (Пуск — Выполнить — гедедИ) и произвести импорт созданного файла. Импорт данного файла в Реестр \ЛЛпс1оал/5 98 можно выполнить, если дважды кликнуть мышкой на этом файле в стандартной программе Проводник (после этого последует предложение импортировать файл в реестр У\Лпс1оуу5 98, на которое следует ответить утвердительно). В результате импорта в Системном Реестре будет создан новый подраздел \Ехатр1е в ветви НК1.М\5у81ет\Сиггеп1Соп1го15е1\5еплсе5. В этот подраздел будут занесены параметры ЕггогСоп1го1, 1тадеРа1Ь, 51а г1 и Туре, значение которых обсуждается ниже. Параметр Туре определяет драйвер режима ядра (значение 1). Параметр 1тадеРа1Н определяет местонахождение файла загружаемого модуля (в нашем случае — С:\УУ1пс1о\л/5\5у51ет32\Опуег5\Ехатр1е.5у5). Параметр 51аг1 определяет момент загрузки сервиса — автостарт после загрузки системы (значение 2). Параметр ЕггогСопСго! определяет поведение системы при возникновении ошибок во время загрузки данного модуля. В данном случае (значение 1) означает следующее: в процессе загрузки ошибки игнорируются, но выводятся сообщения о них, при этом загрузка продолжается. (Другие значения использовать не рекомендуется.) ф Несмотря на простоту внесения изменений в Системный Реестр путем импорта заранее созданного текстового файла соответствующего формата, этот метод следует применять с большой осторожностью, поскольку новая информация легко переписывает предшествующую (если она была, разумеется). Возможно, следует дополнительно побеспокоиться о сохранении прежних данных, которые подвергаются модификации (например, путем предварительного экспорта модифицируемых разделов в файл на диске средствами штатного редактора Системного Реестра).
Модификация Системного Реестра \ЛЛпс1о\л/5 2000, ХР, 5еплег2003 Файлы импорта в Системный Реестр У\Лпс1о\л/5 1\1Т должны быть в формате 1ЛМ1С00Е, поэтому их невозможно создать простым текстовым редактором (например, 1\1о1:ерас1), как это можно было сделать в случае У\Лпс1оуу5 98. Для модификации Системного Реестра \Л/1Пс1о\л/5 2000, ХР, 5еп/ег 2003 необходимо выполнить модификацию Системного Реестра вручную непосредственно в Реестре. Для этого необходимо войти в редактор Системного Реестра (Пуск — Выполнить — гедес! 1132 для \ЛЛпс1оу75 2000 и Пуск — Выполнить — гедесП1 для \Л/1Пс1о\л/5 ХР, Зегуег 2003) и найти там подраздел НК1.М\5у81ет\Сиггеп1Соп1го15е1\5еплсе5. В этом подразделе следует создать вложенный подраздел \Ехатр1е и внести в него следующие параметры и их значения: с1ал/огс1 параметр ЕггогСоп1го1 со значением 1 с1ал/огс1 параметр Туре со значением 1 с1ал/огс1 параметр 51аг1 со значением 2 строковый параметр 1тадеРа1Н со значением "\\5у51ет Роо1\\5уз1:е т 3 2\\О п уе гз\\Еха т р 1е. зу з" После того как указанные данные будут внесены в Системный Реестр, можно выполнить экспорт подраздела НК1_М\5у91ет\Сиггеп1Соп1го15е1\5епИсе9\Ехатр1е во внешний файл (например, Ехатр1е1\1Т.гед) и в следующий раз (например, на другом компьютере) выполнять импорт этого файла в Системный Реестр вместо набора вручную. Смысл вносимых параметров тот же, что и в случае \Л/1пс1о\л/5 98. Запуск драйвера После того как выполнена описанная модификация Системного Реестра и файл Ехатр1е.зуз был размещен в директории С:\\Л/1пс1о\л/з\Е>уз1:ет32\Опуегз\, необходимо выполнить перезагрузку операционной системы (как в случае \ЛЛпс1оу75 98, так и в случае \Л/1пс1о\л/5 2000, ХР, 5еп/ег 2003) для того, чтобы драйвер был загружен и начал работу.
Инсталляция с использованием 11ЧЕ файла Для такого способа инсталляции драйвера потребуется создать текстовый файл (назовем его Ехатр1е.тГ), в котором будет представлена информация для работы Мастера Установки нового оборудования. В данном файле имеет значение даже то, куда поставлена запятая. Поэтому его следует повторить в точности. (Более подробно составление 1пГ-файлов обсуждается в документации ООК, файл справки 1П$1а11.сНт, и в главе 12.) ; Ехатр1е.1п1 - ФпзЕаН ФпРогтаЕгоп Й1е ; СгеаЕес! 2 РеЬ 2003 Ьу ЗУР [Уегзгоп] 81дпаЕиге=” $СЫсадо$ ” С1азз=0пкпомп Ргс^±с1ег=%8УРВоок% БгПегУег=02/22/2003,1.0.0.2 [МапиРаскигег] %ЗУРВоок%=ЗУР.Зсгепсе [ЗУР.Зсгепсе] %Ехатр1е%=Ехатр1е.1пзка11, *ЗVрВоок\Еxатр1е [ ЕезЕтпакгопЕггз ] Ехатр1е . ЕНез . ВгПег=10,3узкет32\БгПегз ; куда копировать для Жп98 Ехатр1е . ЕНез . БгПег . ЫТх8 6=10, ЗузЕет32 \БгПегз ; куда копировать для [ЗоигсеБгзкзЕатез] 1="Ехатр1е ЬиНс1 сИгескогу", , , ; первая цифра -- единица [ ЗоигсеБФзкзЕНез ] Ехатр1е . зуз = 1, с!^\м98 ; где находится новый драйвер для ЭД1п98 [ЗоигсеБтзкзЕНез . х8 6] Ехатр1е . зуз = 1, с1^\пк ; где находится новый драйвер для ЫТ ; Жпс1омз 98 [Ехатр1е.1пзка11] СоруЕНез=Ехатр1е . ЕНез . БгПег Ас1с1Нед=Ехатр1е . АсИНед [Ехатр1е . АсИНед] НКР, , ^еV^оас1е^, , *пккегп НКН, , ЕТМРЕгПег, , Ехатр1е . зуз [Ехатр1е . ЕНез . БгПег] Ехатр1е.зуз ; Жпс1омз 2000, ХР, Зе^ег 2003
[Ехатр1е. 1пзка11 .ЫТх86] СоруЕИез=Ехашр1е . ЕНез . Впуег . МТх8 6 [Ехатр1е . ЕНез . Вгхуег. ЫТх8 6 ] Ехатр1е.зуз,,,%С0РУЕЬС_Ы03К1Р% [Ехатр1е . 1пзка11 .МТх86 . Зетгутсез] АсИЗегугксе = Ехатр1е, %ЗР37С1ЫЗТ_АЗЗОСЗЕР71СЕ%, Ехатр1е . Зегухсе [Ехатр1е.Зегуасе] СтзрТауПате = %Ехатр1е.Зегу±сеПате% Зегу1сеТуре = %ЗЕР71СЕ_КЕРНЕЬ_0Р17ЕР% ЗкагкТуре = %ЗЕРУ1СЕ_АПТ0_ЗТАРТ% ЕггогСопкго! = %ЗЕКУ1СЕ_ЕККОК_ЬЮКМАЬ% 5егу1сеВ1пагу = %10%\Зузкеш32\Вг1уегз\Ехашр1е.зуз ; Зкг1пдз [ Зкгтпдз ] 57РВоок="1пкгос1иск1оп ко Бгауег Ргодгагшпапд" Ехатр1е="Ехатр1е с1г±уег: скескед. ЬиНс!" Ехатр1е . Зегу1сеЫате="Ехатр1е ЫТРРК с1г1уег (7.001)" 5Р57С1М5Т_АЗЗОС5ЕК71СЕ=ОхОО000002 СОРУЕЬС_ЫОЗК1Р=2 ; Во пок а11ои изег ко зк!р Н1е ЗЕР71СЕ_КЕРНЕЬ_0Р17ЕР=1 5ЕК71СЕ_АЕТО_ЗТАКТ=2 5ЕК71СЕ_ВЕМАМО_5ТАКТ=3 ; см. п. 11.1.10 ЗЕР71СЕ_ЕРР0Р_Н0РМАЬ=1 Для проведения инсталляции рекомендуется воспользоваться дискетой. По крайней мере, не следует проводить инсталляцию из директорий на жестком диске, имеющих в названии пробелы и символы кириллицы, например, "СДПример драйверах". В корневой каталог дискеты следует поместить данный файл, Ехатр1е.1пГ, а также создать директорию а:\с!гу со вложенными поддиректориями а:\с!гу\\л/98 и а:\с1гу\п1, куда следует поместить по одной копии файла драйвера Ехатр1е.зуз. (В том случае, если решено устанавливать драйвер из директории на жестком диске, то указанная структура информации должна быть также соблюдена). Теперь (когда уже создан ИГ-файл) можно приступать к установке драйвера при помощи Мастера Установки нового оборудования (Пуск — Настройка — ...). При его работе важно выполнить следующие действия: 1. Следует самостоятельно выбрать устанавливаемое устройство (а не пользоваться услугами автоматического обнаружения). 2. В \Л/1Пс1охл/з 98 следует указать тип устройства "? Другие устройства". 3. Выбрать установку драйвера с диска, после чего следует указать диск 'а:' (либо директорию на жестком диске, где находится Ехатр1елпГ, поддиректории \с1гу\пГ и \с1гу\уу98 и две копии Ехатр1е.5уз, как было указано выше).
После идентификации 1пГ файла Мастер Установки нового оборудования самостоятельно скопирует файл Ехатр1е.зуз из соответствующей директории с!п/\\л/98 или с1гу\п1 (в нашем случае эти файлы идентичны) в \5уз1ет32\0пуегз внутри системной директории. Мастер Установки произведет модификацию записей в Системном Реестре, в результате чего драйвер будет загружаться после загрузки системы (когда она произойдет в следующий раз). Для запуска данного драйвера сразу после установки Мастером Установки не требуется перезагрузки системы. (Но для других драйверов под \Л/1Пс1охл/з 98 это может потребоваться.) По завершении работы Мастера Установки драйвер готов к использованию и обращению к нему из консольного приложения, описанного ниже. Результаты работы Мастера Установки с записями Системного Реестра следует искать в разделе НК1_М\5у81ет\Сиггеп1Соп1:го15е1\5еплсе8\С1а88\ипкпо\л/п (для \ЛЛп98) и в разделе НКЬМ\5у5(ет\Сиггеп1Соп1го15е1\5егу1се5\11пкпо^п\Ехатр1е (для \Л/1Пс1о\л/5 2000/ХР/5егуег 2003). Следует отметить, что информацию о драйвере Ехатр1е.5у5 после установки можно увидеть в Настройках Системы (Система/Диспетчер Устройств в \Л/1Пс1о\л/з МТ, либо Система/Устройства в \Л/1пс1о\л/5 98), однако многие информационные поля там не будут определены (в случае \Л/1Пс1о\л/з МТ таких полей будет меньше). Это объясняется тем, что информация, для которой указано "неизвестна" должна поступать из файла драйвера, для чего в нем должны быть предусмотрены информационные ресурсы, обычно размещающиеся в .гс файле проекта. В данном проекте такого файла нет, поэтому не вся желаемая информация предоставляется системным службам. ф Возникает вопрос, почему драйвер, предназначенный для ИТ, запускается и работает под \Мпс!о\м5 98?! Ответ прост. В \Мпс!о\л/5 98 установлен модуль пСкегп.ухс/, который так выполняет свою работу, что драйверам НТ кажется, что они имеют дело с ИТ-системой, К сожалению, возможности его не безграничны, иначе \Л/!пс1о\л/5 98 была бы \Л/!пс1о\л/5 ИТ. Другой вопрос, который может возникнуть после описанной процедуры: почему мы смогли установить драйвер, в сущности, "никакого" устройства?! Ответ также несложен. Поскольку к системе могут подключаться устройства, не поддерживающие РпР (1едасу деуюез), которые не могут быть автоматически обнаружены и которые не могут быть подключены (загружены их драйверы) иначе, чем по указанию администратора системы, то фирма М1сго5оГ1 обязана предоставить способ установки драйверов "по желанию". Что и произошло в нашем случае. ф При установке драйвера под операционной системой \А/!пс1о\л/52000/ХР/5егуег 2003 может появиться сообщение следующего вида (рисунок 3.1). Данное сообщение, выдаваемое операционной системой, связано с тем, что фирма МюгозоН: для повышения ответственности разработчиков за качество своих драйверов ввела программу тестирования и подписания вновь присоединяемых к дистрибутиву \ЛЛпс1оал/5 драйверов. Для получения цифровой подписи драйвер должен пройти тестирование в специальной лаборатории М1сгозоГ1 (соответственно, она действует только на конкретный бинарный .зуз файл, при перекомпиляции цифровую подпись следует получать заново). Разумеется, "потренировать" свой драйвер перед такой процедурой вполне можно — для этого МюгозоЛ поставляет соответствующие программные средства. В данном случае для инсталляции драйвера Ехатр1е.зуз следует выбрать кнопку "Все равно продолжить".
Рис. 3.1 Предупреждение о том, что драйвер не подписан По завершении инсталляции в окне Диспетчера Устройств (свойства устройства) можно увидеть сообщения об установленном драйвере, в частности, в форме, представленной на рисунке 3.2 (в графе "Цифровая подпись" для данного драйвера указано, что она отсутствует). Рис. 3.2 Свойства драйвера в окне Диспетчера Устройств Следует также удостовериться при помощи перечисленных в главе 2 программ, поступила ли информация и драйвере (и в достаточном ли объеме) в операционную систему. Программа ОеуюеТгее предоставляет информацию, показанную на рисунке 3.3. На нем показаны коды 1КР_МЗ_Ххх, для которых драйвер зарегистрировал собственные процедуры обработки, а также более общая информация о драйвере, в частности, операционная система сама установила для него флаг 1_Е(ЗАСУ_ОР.1\/ЕР.. Рис. 3.3 Общие свойства драйвера в окне Оеу1сеТгее, поддерживаемые ШР_М1_Ххх Более подробно вопросы составления ИГ файлов для установки драйверов будут рассмотрены в главе 12. ф В процессе установки драйвера при помощи Мастера установки система выполняет резервное копирование файлов, отражающих ее состояние перед установкой драйвера. Результаты этой работы сохраняются в директории \5уз1ет ]/о1ите 1п/ЪгтаПоп\Нр1Уп на одном из логических дисков (Ип — это номер резервной точки). В том случае, если инсталляция драйвера приведет к нестабильной работе системы, можно восстановить ее состояние на момент сохранения данной резервной точки (гезегуе ро1п1) через запуск системной утилиты Пуск — Программы — Стандартные — Служебные — Восстановление Системы. Эта же утилита позволит администратору выполнить принудительное создание резервной копии, если в том имеется необходимость. Более подробно эти вопросы освещены в обстоятельной книге Ольги Кокоревой "Реестр Штс/омз ХР", рассматривающей многие аспекты организации Системного Реестра \А/'шс1оу/5 ИТ, весьма важные для разработчика драйверов. ф В большинстве случаев драйвер, установленный с помощью Мастера установки, можно отключить и включить (задействовать) снова, даже не выполняя при этом перезагрузку системы. Если между отключением и включением выполнить подмену бинарного файла драйвера (например, на новую версию), то в результате таких манипуляций в работу вступит новая версия драйвера. Разумеется, чтобы обеспечить такую возможность, драйвер должен иметь корректно написанные процедуры завершения работы и выгрузки. Подмену файла УУЭМ драйвера РпР устройства вполне успешно можно выполнять в то время, когда отключены все РпР устройства, обслуживаемые таким драйвером. Однако выполнение установки новых версий драйвера только с помощью Мастера установки дает следующее небольшое преимущество: средствами системы можно восстанавливать предыдущую версию драйвера (если она существовала). Для этого необходимо в окне — см. рисунок 3.2 — выбрать пункт "Откатить".
Инсталляция с использованием программы МопНог Как было сказано в главе 2, для запуска драйверов "в-стиле-МТ" под управлением \Л/1Пс1о\л/5 МТ предназначена программа МопКог, разработанная Сотри\Л/аге СогрогаИоп и входящая в состав пакета Эпуег 51ис1ю (в том числе, в 30-дневную 1па1 версию). Эта программа прекрасно подходит для загрузки, запуска, остановки и удаления драйвера Ехатр1е.5уз. Интуитивно понятный графический интерфейс этой программы практически не требует дополнительных пояснений. Для запуска драйвера не следует перезагружать систему и самостоятельно модифицировать Системный Реестр, что удобно для проведения быстрых автономных тестов. Если перед запуском драйвера из программы МопНог предварительно запустить программу ОеЬид\/1е\л/, то все отладочные сообщения, которые были введены даже в функции ОпуегЕп1гу, будут отображены в рабочем окне ОеЬид\/1е\л/ и могут быть сохранены в файле протокола (1_06-файле). Более подробно режимы ознакомительного тестирования драйвера Ехатр1е.5у5 при помощи собственного приложения пользовательского режима будут рассмотрены ниже.
Инсталляция с использованием сервисов 5СМ Менеджера В операционной системе \Л/1пс1о\л/5 1\1Т имеется компонент, называемый Зепдсе Соп1го1 Мападег (5СМ Менеджер). Удобство предоставляемых им услуг состоит в том, что, используя его функции в приложениях пользовательского режима, можно динамически запускать и выгружать драйверы, требующиеся только данному конкретному приложению, не прибегая к вызову Мастера Установки нового оборудования. Таким образом, приложение само определяет время присутствия драйвера в операционной системе. Достаточно подробное описание программирования приложений с использованием функций 5СМ имеется в документации М5ОЫ (практически, единственное место ее соприкосновения с потребностями собственно разработки драйверов), поставляемой отдельно или в составе пакетов Меговой: \/15иа1 51ис1ю. Работа с сервисами 5СМ менеджера начинается с вызова функции ОрепЗСМападег (это имя можно рассматривать как "точку входа" в документацию М50Ы по программированию с применением 5СМ функций) и завершается вызовом функции С1о5е5еплсеНапс11е. Пример работы с функциями 5СМ будет рассмотрен ниже, в примере консольного приложения для тестирования драйвера Ехатр1е.5у5. Следует отметить, что не все типы драйверов могут быть загружены и запущены средствами 5СМ функций.
ГРгеу|ои51 [Мех!],
Приложение для тестирования драйвера Ехатр1е.5у5 Перед тем, как приступить к тестированию драйвера путем вызова его сервисов из приложения, следует это приложение создать, хотя бы в минимальном виде, как это предлагается ниже. И хотя драйвер можно успешно запускать программой Моп^ог, воспользуемся функциями 5СМ, поскольку это будет существенно полезнее для будущей практики. //////////////////////////////////////////////////////////////////// // (Файл Ехатр1еТезб.срр) // Консольное приложение для тестирования драйвера Ехатр1е.зуз // 22-ЕеЬ-2003 1.0.0 8УР //////////////////////////////////////////////////////////////////// // Заголовочные файлы, которые необходимы в данном приложении: #1пс1ис1е #1пс1ис1е #1пс1ис1е #1пс1ис1е // Внимание! Файл 1осб1.1т должен быть получен из файла Вггчег.й // (см. комментрарии к Вггчег.й) и размещен в одной директории с // данным файлом (ТезбЕхат.срр). #1пс1ис1е " 1осб1. И" // Имя объекта драйвера и местоположение загружаемого файла МеПпе ВВ1УЕВКАМЕ _Т ( "Ехатр1е" ) //МеНпе ВВ1УЕВВ1КАВУ _Т ( "С : \\Ехатр1е\\Ехатр1е . зуз" ) //Мейпе ВВ1УЕВВ1КАВУ _Т("С:\\Ех\\оЬсйк_м2к\\138 6\\Ехатр1е . зуз" ) Мейпе ВВ1УЕВВ1КАВУ _Т ( "С : \\Ех\\кезкег\\Ехатр1е . зуз" ) // Функция установки драйвера на основе 8СМ вызовов ВООЬ 1пзка1Юг1чег ( 8С_НАИВЬЕ зет, ЬРСТЗТВ ВггчегИате, ЬРСТЗТВ бггче { 8С_НАИВЬЕ Зегчгсе = СгеабеЗегчгсе ( зет, // открытый дескрипто БггчегКате, // имя серви БггчегКате, // для вывод 8ЕВУ1СЕ_АЬЬ_АССЕ88, // жел 8ЕВУ1СЕ_КЕВКЕЬ_ВБЙУЕВ, // тип 8ЕВУ1СЕ_ВЕМАКВ_8ТАВТ, // тип 8ЕВУ1СЕ_ЕВВОВ_КОВМАЬ, // как бггчегЕхес, // пут // Остальные параметры не исп КИЬЬ, //Не определяем гру КИЬЬ, КИЬЬ, КИЬЬ, КИЬЬ); И (Зегчгсе == КИЬЬ) // неудача {
ШОВБ егг = СеЬЬазЬЕггог(); 1Р (егг == ЕККОК_8ЕКУ1СЕ_ЕХ18Т8) {/* уже установлен * // более серьезная ощибка: е!зе рггпЬЬ ("ЕКК: СапТЬ сгеабе зетсе. Егг=%б\п”,е // (АА Ётот код ошибки можно подставить в ЕггЬоок): гекигп ЕАЬ8Е; } С1озе8е^1сеНапс11е (Вегчгсе) ; гекигп ТЕБЕ; } // Функция удаления драйвера на основе 8СМ вызовов ВООЬ ВеточеВггчег (8С_НАЕБЬЕ зет, ЬРСТ8ТВ БггчегЕате) { 8С_НАЕБЬЕ 8е^±се = 0реп8е^±се (зет, БггчегЕате, 8ЕВУ1СЕ_АЬЬ_АСС (8е^±се == ЕЬЬЬ) гебигп ЕАЬ8Е; ВООЬ геЬ = Ве1еЬе8е^±се (8е^±се) ; ЬЬ (!геЬ) { /* неудача при удалении драйвера */ } С1озе8егч±сеНапб1е (Вегчгсе); геЬигп геЬ; } // Функция запуска драйвера на основе 8СМ вызовов ВООЬ ВЬагЬВггчег(8С_НАЕБЬЕ зет, ЬРСТ8ТВ БггчегЕате) { 8С_НАЕБЬЕ Вегчгсе = ОрепВегчгсе(зет, БггчегЕате, 8ЕВЛ71СЕ_АЬЬ_АССЕ 1Ь (Вегчгсе == ЫЬЬЬ) гебигп ЕАЬ8Е; /* ореп ЬаНеб */ ВООЬ геЬ = 8ЬагЬ8егч±се( 8егчЬсеЛ // дескриптор 0Л // число а ЫЬЬЬ ); // указате тЬ (!геЬ) // неудача { ВЭДОВВ егг = СеЬЬазЬЕггог(); (егг == ЕВВ0В_8ЕВУ1СЕ_АЬВЕАВХ_ВЖШЕС) геЬ = ТВОЕ; // 0КЛ драйвер уже работает! е!зе { /* другие проблемы */} } С1озе8егч±сеНапб1е (Вегчгсе); гебигп геб; } // Функция останова драйвера на основе 8СМ вызовов ВООЬ ВЬорВггчег(8С_НАЕБЬЕ зет, ЬРСТ8ТВ БггчегЕате) {
ЗС_НАПБЬЕ Зе^йсе = ОрепЗе^йсе (зет, Бг^егИате, ЗЕВУ1СЕ_АЬЬ_АСС ФЕ (Зе^йсе == ПБЬЬ) // Невозможно выполнить останов драйвер { БЭДОВБ егг = СеЕЬазЕЕггог(); геЕигп ЕАЬЗЕ; } ЗЕВУ1СЕ_ЗТАТБЗ зегчйсеЗЕаЕиз; ВООЬ геЕ = СопЕгоЬЗе^йсе (Зе^йсе, ЗЕВУ1СЕ_СОПТВОЬ_ЗТОР, йзе^йсеЗЕаЕиз) йЕ ( !геЕ) { БЭДОВБ егг = СеЕЬазЕЕггог(); // дополнительная диагностика } С1озеЗе^1сеНапс11е (Зегчйсе) ; геЕигп геЕ; } // Соберем вместе действия по установке, запуску, останову // и удалению драйвера (для обобщения сведений). // (Однако пользоваться этой функцией в данном примере не придется.) /* Закомментируем ее. чойб. ТезЕ_ЗСМ_1пзЕа11аЕ1оп (чойб) { ЗС_НАПБЬЕ зет = ОрепЗСМападег(ПБЬЬ,ПБЬЬ,ЗС_МАПАСЕВ_АЬЬ_АССЕЗЗ ФЕ(зет == ПБЬЬ) // неудача { // Получаем код ошибки и ее текстовый эквивалент ипзйдпеб 1опд егг = СеЕЬазЕЕггог(); РгйпЕЕггогМеззаде(егг); // см. п. 2.1.5 геЕигп; } ВООЬ гез; гез = 1пзЕа11Бгйчег(зет, БШУЕВПАМЕ, БЫУЕВВ1ПАВУ ); // Ошибка может оказаться не фатальной. Продолжаем: гез = ЗЕагЕБгйчег (зет, БЫУЕВПАМЕ ); 1Е(гез) { //Е Здесь следует разместить функции работы с драйвер гез = ЗЕорБгйчег (зет, БВПУЕВПАМЕ ); ЕЕ(гез) гез = ВеточеБгйчег (зет, БВПУЕВПАМЕ ); } С1озеЗегч1сеНапс11е (зет) ; геЕигп;
} */ #аеЕ±пе 8СМ_8ЕВУ1СЕ II аааааааааааааааа вводим эЛСМСНТ УСЛОВНОЙ КОМПИЛЯЦИИ, При ПОМОЩИ // которого можно отключать использование 8СМ установки драйвера // в тексте данного приложения. (Здесь Ц использование 8СМ включено.) // Основная функция тестирующего приложения. // Здесь минимум внимания уделен диагностике ошибочных ситуаций. // В действительно рабочих приложениях следует уделить этому // больше внимания! гпб __сбес! та±п(±пб агдс, сНаг* агдч[]) { ”” #±ВбеВ 8СМ_8ЕВУ1СЕ // Используем сервис 8СМ для запуска драйвера. ВООЬ гез; // Получаем доступ к 8СМ : 8С_НАПБЬЕ зет = Ореп8СМападег(ППЬЬ,ППЬЬ,8С_МАПАСЕВ_АЬЬ_АССЕ88 гЕ(зст == ИПЬЬ) гебигп -1; // неудача // Делаем попытку установки драйвера гез = 1пзба11Бг±чег(зет, БВТУЕВПАМЕ, ВВ1УЕВВ1ИАВУ ); 1У(!гез) // Неудача, но возможно, он уже инсталлирован рггпбЕ ("Саппоб гпзбаН зегчгсе”) ; гез = ЗбагбВггчег (зет, ВВ1УЕВИАМЕ ); ФЕ(!гез) { рггпбЕ("Саппоб збагб аггчег!"); гез = ВеточеВггчег (зет, ВВ1УЕВИАМЕ ); ФЕ(!гез) { рггпбЕ("Саппоб геточе аггчег!"); } С1озе8егч±сеНапа1е(зет); // Отключаемся от 8СМ гебигп -1; } #епа±В НАИБЬЕ ННапа1е = // Получаем доступ к драйверу СгеабеЕНе ( "\\\ \ . \\Ехатр1е" , СЕПЕВ1С_ВЕАВ | СЕПЕВ1 Е1ЬЕ_8НАВЕ_ВЕАВ | ЕН ППЬЬ, ОРЕП_ЕХ18Т1ПС, Е1ЬЕ_АТТВ1ВПТЕ_ПОВМА1 ППЬЬ );
(ЬНапс11е==1Н'7АЫ0_НАН0ЬЕ_7АЫ)Е) { рг1пбБ("ЕКК: сап по!: ассезз с1г:^ег Ехатр1е.зуз !\п"); ге'Ьигп (-1) ; } БИОКО ВуЬезКеБигпес!; // Переменная для хранения числа // переданных // Последовательно выполняем обращения к драйверу // с различными кодами ЮСТЪ: ипзтдпес! 1опд 1ос01Сос1е=10СТЬ_РК1МТ_0ЕВПС_МЕЗЗ ; ( ! 0еч1се1оСоп'Ьго1 ( ННапс11е, тосЫСойе, МПЬЬ, 0, // Тприб МПЬЬ, 0, // ОибриО &ВуЬезКе'Бигпес1, ЬГОЬЬ ) ) { рг1пВБ( "Еггог 1п 1ОСТЬ_РК1ПТ_ОЕВПС_МЕ55!" ); гебигп(-1); } 1осВ1Сос1е=10СТЬ_СНАМОЕ_1КОЬ; ( ! Оеч1се1оСоп'Ьго1 ( ННапс11е, тосЫСойе, МПЬЬ, 0, // Рприб МПЬЬ, 0, // ОибриО &ВуЬезКе'Бигпес1, ЬГОЬЬ ) ) { рг1пВБ( "Еггог 1п 10СТЬ_СНАПСЕ_1КОЬ!" ); гебигп(-1); } 1осВ1Сос1е=10СТЬ_Т0ПСН_Р0КТ_37 8Н; ( ! Оеч1се1оСоп'Ьго1 ( ННапс11е, 1осЫСо<1е, МПЬЬ, 0, // РприО МПЬЬ, 0, // ОиОриб &ВуЬезКе'Бигпес1, ЬШЬЬ ) ) { рг1пВБ( "Еггог 1п 1ОСТЬ_ТОПСН_РОКТ_378Н!" ); гебигп(-1); } // Следующий тест. Получаем 1 байт данных из драйвера. // По окончании данного вызова переменная хс1аба должна
// содержать значение 33: ипзйдпеа скаг хааЕа = 0x88; ±осЕ1Соае=10СТЕ_ЗЕМВ_ВУТЕ_Т0_03ЕВ; 1Е ( !БечЕсеЕоСопЕго!( ЕНапаТе, йосЕЮоае, МОЬЬ, 0, // 1приЕ &хааЕа, зйео^ (хааЕа) ,// ОиЕр &ВуЕезВеЕигпеа, ОТЬЬ ) ) { ргйпЕЕ( "Еггог 1п ЮСТЬ_ЗЕМБ_ВУТЕ_ТО_ОЗЕВ.!" ); геЕигп(-1); } // Вывод диагностического сообщения в консольном окне: рггпЕЕ ( " ЮСТЬ_ЗЕМБ_ВУТЕ_ТО_ОЗЕВ.: ВуЕезВеЕигпеа=%а хааЕа=%а", ВуЕезВеЕигпеа, хааЕа); // Выполнение следующего теста в Жпаомз МТ приведет к // фатальному сбою операционной системы (намеренно выполнение // падение ОС может быть полезно при изучении, например, // организации сгазй битр файла и работы с отладчиком). /* ЕосЕ1Соае=10СТЬ_МАКЕ_ЗУЗТЕМ_СВАЗН; ЕЕ ( !БечЕсеЕоСопЕго!( ННапаТе, йосЕТСоае, маьь, о, МОЬЬ, 0, // ОиЕриЕ &ВуЕезВеЕигпеа, МОЬЬ ) ) { ргйпЕЕ( "Еггог йп 1ОСТЬ_МАКЕ_ЗУЗТЕМ_СВАЗН!" ); геЕигп(-1); } */ // Закрываем дескриптор доступа к драйверу: СТозеНапаТе(ЕНапаТе); #±ЕаеЕ ЗСМ_ЗЕВУ1СЕ // Останавливаем и удаляем драйвер. Отключаемся от ЗСМ. гез = ЗЕорБгйчег (зет, БВ1УЕВМАМЕ ); гЕ(!гез) { ргйпЕЕ("СаппоЕ зЕор аггчег!"); СТозеЗегчйсеНапаТе(зет) ; геЕигп -1;
гез = Кетс^еБг^ег (зстл БК1УЕКМАМЕ ); (!гез) { рг±пЕЕ("СаппоЕ гетере с!г^ег!"); С1озеЗе^±сеНапс11е (зет) ; геЕигп -1; } С1озеЗе^±сеНапс11е (зет) ; #епсИЕ геЕигп 0; } ф Сообщения намеренно введены на английском языке. Использование кириллицы в консольных приложениях УУ'тйошз для правильного отображения на экране требует дополнительного преобразования с использованием функции СНагТоОет.
Работа с драйвером Ехатр1е.5у5 Как уже было сказано, из всех возможных способов инсталляции и запуска драйвера Ехатр1е.5у5, ниже будет использован способ тестирования с применением тестирующего консольного приложения, которое само будет выполнять инсталляцию и удаление драйвера (прибегая к вызовам 5СМ Менеджера). Для поэтапного ознакомления с процессом взаимодействия драйвера и обращающегося к нему приложения рекомендуется запустить программу Ехатр1еТе51 под отладчиком (например, \/1зиа1 51ис1ю) в пошаговом режиме. Перед запуском тестирующей программы Ехатр1еТе51 рекомендуется загрузить программу ОеЬид\/|е\л/, чтобы в ее рабочем окне наблюдать сообщения, поступающие непосредственно из кода драйвера Ехатр1е.5у5 (отладочной сборки). Однако прежде чем перейти к рассмотрению сообщений от драйвера, после выполнения установки и запуска драйвера (программным кодом консольного приложения Ехатр1еТе51: в пошаговом режиме), прежде следует обратиться к программам \ЛЛпОЬ) и ОеуюеТгее или аналогичным им программным средствам для того, чтобы удостовериться, присутствует ли в них информация об установленном драйвере (как, например, после инсталляции Мастером Установки нового оборудования.) Как уже было сказано в главе 2, протокол полученных программой ОеЬид\/|е\л/ отладочных сообщений драйвера можно сохранить в файле для последующего анализа. Ниже приведена информация из такого файла, отражающая события в драйвере Ехатр1е.5у5 с момента его загрузки и вызова процедуры ОпуегЕп1гу до момента выгрузки и вызова процедуры 0п1оасЖои11пе. Рассмотрим содержание этого файла подробнее. Сообщения, отправляемые драйвером из процедуры ОпуегЕп1гу (первое число — номер сообщения, второе — относительное время, не имеющие большого значения в данном случае), выглядят следующим образом: 00000000 0.00000000 =Ехатр1е= 1п Эг^егЕпЕгу. 00000001 0.00003743 =Ехатр1е= КедУзЕгуРаЕН = \РЕС13ТРУ\МАСН1ЫЕ\ЗУЗТЕ 00000002 0.00012823 =Ехатр1е= ЕОО ЕЕ919С68, ^еVЕxЕ=ЕЕ919^20. 00000003 0.00021176 =Ехатр1е= Ог^егЕпЕгу зиссеззЕиНу сотр1еЕес1. В переменной Кед151гуРа1Ь содержится поступающий от системы путь (в формате 11М1ССЮЕ) внутри Системного Реестра, где можно найти информацию о запускаемом драйвере. Видим также, что созданный драйвером функциональный объект устройства (ЕОО) имеет адрес 80Е57ВЕ0, а недалеко от него (а именно — внутри) находится структура расширения устройства, которая была определена в файле Опуег.Ь. Сообщения, отправляемые драйвером из функции Сгеа1е_ЕПе_1ИРргосе551пд, обработчика запросов Диспетчера ввода/вывода, которые тот делает к драйверу в результате обращения из тестирующего приложения с вызовом Сгеа1еЕ11е. 00000004 0.00172536 -Ехатр1е- СгеаЕе ЕНе 1з Далее тестирующее приложение делает серию вызовов функции Оеу1се1оСоп1го1, в результате чего драйвер реагирует на них сообщениями для ОеЬид\/|е\л/. Приводятся шестнадцатеричные значения 1ОСТ1. кодов.
Реакция на запрос ЮСТЬ_Рк1МТ_0ЕВЫ0_МЕ55: 00000005 0.00178850 -Ехатр1е- 00000006 0.00180554 -Ехатр1е- 00000007 0.00182230 -Ехатр1е- 00000008 0.00183515 -Ехатр1е- 00000009 0.00184940 -Ехатр1е- 1п ^еV^сеСопЬ^о1Р.оиЬ^пе (1с1о= ЕЕ919С68 ^еV^сеIоСопЬ^о1: ЮСТЬ 222004. РАЗЗIУЕ_ЬЕУЕЬ ^а1=0) ЮСТЬ_РК1ЫТ_ОЕВ1Ю_МЕЗЗ. ^еV^сеIоСопЬ^о1: 0 ЬуЬез мгЬЬЬеп. Реакция на запрос ЮСТЬ_СНАМОЕ_1ЕЩЬ: 00000010 0.00187594 -Ехатр1е- 1п ^еV^сеСоп^^о1Кои^^пе (Йо= ЕЕ919С68 00000011 00000012 00000013 00000014 00000015 0.00189074 0.00190331 0.00191477 0.00193125 0.00194466 -Ехатр1е- -Ехатр1е- -Ехатр1е- -Ехатр1е- -Ехатр1е- ^еV^сеIоСопЬ^о1: ЮСТЬ 222008. РАЗЗIУЕ_ЬЕУЕЬ ^а1=0) ЮСТЬ_СНАМ6Е_1К<2Ь. О13РАТСН_ЬЕУЕЬ Vа1ие =2 1В.<2Ьз аге о1с!=2 пем=10 00000016 0.00196226 -Ехатр1е- ^еV^сеIоСоп^^о1: 0 ЬуЕеа мгтЕЕеп. Интересно, что попытки установить в \ЛЛпс1о\л/з ХР текущее значение 1К.рь, равное 25 (в середине диапазона аппаратных прерывания) не дала результата: было позволено только значение 10, что ниже границы начала аппаратных прерываний. В то же время, в У\Лпс1оуу5 98 и У\Лпс1о\л/5 5еп/ег 2003 (для сравнения) в этом месте выводится значение 25. Реакция на запрос ЮСТЬ_ТОЫСН_РСЖТ_378Н выглядит следующим образом: 00000017 0.00198377 -Ехатр1е- 1п ^еV^сеСопЬ^о1Р.оиЬ^пе (1с1о= ЕЕ919С68 00000018 0.00199858 -Ехатр1е- ^еV^сеIоСопЬ^о1: ЮСТЬ 222010. 00000019 0.00201115 -Ехатр1е- РА531УЕ_ЬЕУЕЬ ^а1=0) 00000020 0.00202344 -Ехатр1е- ЮСТЬ_ТОЬСН_РОВТ_37 8Н. Реакция на запрос ЮСТЬ_5ЕЫ0_ВХТЕ_Т0_Ь)5ЕР: 00000021 0.00204104 -Ехатр1е- ^еV^сеIоСопЬ^о1: 0 ЬуЬез мгЬЬЬеп. 00000022 0.00206870 -Ехатр1е- 1п ^еV^сеСопЬ^о1Р.оиЬ^пе (1с1о= ЕЕ919С68 00000023 00000024 00000025 00000026 00000027 00000028 0.00208378 0.00209664 0.00210921 0.00212066 0.00213575 0.00214944 -Ехатр1е- -Ехатр1е- -Ехатр1е- -Ехатр1е- -Ехатр1е- -Ехатр1е- ^еV^сеIоСопЬ^о1: ЮСТЬ 222014. РАЗЗIУЕ_ЬЕУЕЬ ^а1=0) ВиЬЕег оиЫепдЫл 1 МеЫгос! : ВЫЕЕЕВЕО. ВиИег асМгезз тз ЕЕ978828 ^еV^сеIоСопЬ^о1: 1 ЬуЬез игтЬЬеп. Как видно из сообщений, здесь используется метод буферизации МЕТНОО_В1)ЕЕЕНЕО, что и было описано в файле Эпуег.К при создании данного кода ЮСТЬ. В соответствии с данным методом буферизации драйвер получает адрес буфера с явно системным значением (80Е87Е78 — адрес в системном пуле), что и должно было произойти при данном методе буферизации. Если бы в файле Опуег.К при описании данного ЮСТЬ кода был задан метод МЕТНОЭ_МЕ1ТНЕР, то драйвер получил бы точный адрес переменной хс1аЬа из приложения Ехатр1еТезЬ (с типичным для пользовательского приложения адресом, типа 0002ЕЕА0). Подробнее о том, как передаются данные при разных методах буферизации в ЮСТЬ запросах, будет рассказано позже, в главе 6.
Размер буфера для передачи данных пользовательскому приложению равен 1. Реально переданных данных 1 байт, в то время как при обработке остальных запросов никакой передачи данных не происходит. (Размер буферных областей и переданных данных всегда исчисляется в байтах.) Сообщения, отправляемые драйвером из функции С1о5е_Е11е_1РРргосе551пд, обработчика запросов Диспетчера ввода/вывода, которые тот делает к драйверу в результате обращения из тестирующего приложения с вызовом ОозеНапсИе : 00000029 0.00263888 -Ехатр1е- 1п С1озе ЬапсИег. Сообщения, отправляемые драйвером из функции Оп1оас1Рои11пе, обработчика запросов Диспетчера ввода/вывода, которые тот делает к драйверу при необходимости выполнить выгрузку драйвера: 00000030 0.00447794 -Ехатр1е- 1п Оп1оас1 КоиЕтпе. 00000031 0.00451985 -Ехатр1е- Ве1еЕес1 с^еV^се (0) : рогпЕег Ео ЕВО = 00000032 0.00454220 -Ехатр1е- Ве1еЕес1 зутИпк = \^оз^еV^сез\Еxатр1е. Выполнение ЮСТЬ запроса с кодом ЮСТ1__МАКЕ_5У5ТЕМ_СРА5Н, разумеется, здесь не приводится, потому что после такого действия система, например, \ЛЛпс1о\л/з ХР начинает перезагрузку (или получает управление отладчик ЗоГНсе, если он установлен и активирован). Однако под У\Лпс1о\л/5 98 данный вызов проходит как ни в чем не бывало, и драйвер выводит в рабочем окне ОеЬид\/1еуу сообщение о том, что ему удалось получить байт данных (равный, кстати, 0) по нулевому виртуальному адресу. Чтобы ввести элемент интриги, можно добавить, что при устранении небольшого затруднения, мешающего перехвату обращения по нулевому виртуальному адресу, в \Л/1Пс1о\л/5 МТ можно наблюдать, как это действительно срабатывает и переменная 'х' остается со своим значением ОхЕЕ. Подробнее об этом можно узнать в 10 главе.

Заключение В данной главе был рассмотрен простейший драйвер Ехатр1е.5у5, реализованный как драйвер "в-стиле-ЫТ" (тип драйвера, называемый еще 1_едасу Опуег). На его примере были представлены начальные сведения об основных этапах разработки простого драйвера: компиляция и сборка сИескес! версии в среде М1сго5оЛ: \/15иа1 51:ис1ю, в среде \Л/1Пс1о\л/5 ООК, реализация тестирующего приложения пользовательского режима, инсталляция и запуск. Изложение этого материала можно признать поверхностным и беглым, однако, предназначено оно для того, чтобы дать первый фактический материал начинающему разработчику для более продуктивного чтения последующих глав этой книги.
Глава 4
Архитектура У\Лпс1о\л/5 1ЧТ 5. Введение Различия базисов операционных систем \ЛЛпс1о\л/5 2000, \Л/1Пс1о\л/5 ХР и \Л/1Пс1о\л/5 5еп/ег 2003 настолько невелики, что позволяет в большинстве случаев говорить о них собирательно, используя термин \Л/| пс!о\л/з 1\1Т 5. Однако \Л/1Пс1о\л/5 2000 является первой из них, потому о ней особенная речь. Операционная система \Л/|пс1о\л/5 1\1Т 5.0 (то есть \Л/|пс1о\л/5 2000) представляет наиболее энергичную попытку создания гибкой и самоуправляемой операционной системы в компьютерной истории. Является очевидным фактом то, что драйверы устройств во всех операционных системах тесно связаны с основным кодом операционной системы, и это позволяет по праву называть их привилегированными программными единицами. Предваряя глубокое погружение в мир драйверов устройств, данная глава дает представление о психологии, насколько это понятие применимо к программному обеспечению, и общей организации архитектуры У\Лпс1о\л/5 1\1Т 5.

Исполнительные компоненты Так как Исполнительные компоненты представляют базисные сервисы операционной системы \ЛЛпс1о\л/5 1\1Т 5 (в дополнение к планированию потоков, осуществляемых ядром), их обязанности ясно очерчены. В таблице 4.1 представлены наименования основных исполнительных компонентов операционной системы. В первом столбце указаны сокращения, обычно являющиеся первыми символами имен функций, которые данный компонент предоставляют разработчику для использования при программировании в режиме ядра. Например, функция КНСоруМетогу, являющаяся аналогом известной функции пользовательского режима тетсру, предоставляется библиотекой времени выполнения Р11 (Рип Т1те ЫЬгагу). Таблица 4.1. Исполнительные компоненты \Л/'тс1о\л/8 1ЧТ5 Сс ОЬд Ех Диспетчер кэша Поддержка отладки Поддержка исполняющей подсистемы Ех(еси1|уе) ЕзРШ Библиотека времени выполнения для поддержки файловой системы (ЕИе Зуз^ет Нип-Т1те 1_!Ьгагу) На1 Диспетчер уровня аппаратных абстракций, 1Ье Нагс1ууаге АЬз^гасйоп 1_ауег (НА1_) 1пЬу Драйвер инициализации системы/загрузки \/СА 1п1Т 1п1ег1оскес1 1о ки Ке К! Инициализация системы Потокобезопасное оперирование переменными Диспетчер ввода/вывода 1о (1/0 Мападег) Поддержка Кегле! ОеЬиддег Подпрограммы ядра (Кегпе1) Обработка ядра
1_с1г 1_рс 1_за Мт 1\11з 1X11 ОЬ РГх Ро Рз КН 5е 7\л/ Загрузчик образа Локальный вызов процедур (1_оса1 Ргосебиге Са11) 1_оса1 5есип1у АиНпогКу Менеджер памяти (Менеджер Виртуальной памяти, \/ММ) Лингвистическая поддержка (МаНопа! Ьапдиаде 5иррог1) ИТ Ыайуе АР1 Менеджер объектов (ОЬ]ес1 Мападег) Обработка префиксов Менеджер электропитания Поддержка процессов (Ргосезз 51гис1иге) Библиотека времени выполнения (Кип-Т1те 1_1Ьгагу) Управление безопасностью и обеспечение привилегий Альтернативный интерфейс ЫаНуе АР1 другие Вспомогательные функции и библиотека времени выполнения С Наиболее важные для разработчика драйверов компоненты будут рассмотрены ниже подробнее.
Интерфейс системных служб Бузует 5еплсе 1п1:егГасе. Данный компонент обеспечивает точки перехода из пользовательского режима в код режима ядра, что позволяет пользовательским приложениям (потокам этих приложений) безопасно осуществлять вызовы системных сервисов (процедур режима ядра). В зависимости от платформы, переход из пользовательского режима к коду режима ядра может быть и простой процессорной инструкцией, и достаточно сложным переключателем контекста 5ауе или кез1юге.
Менеджер (диспетчер) объектов ОЬ]'ес± Мападег. Практически все услуги, предоставляемые операционной системой, оперируют с такой распространенной абстракцией, как объекты, хотя это и не стопроцентные объекты, по некоторым признакам, известным из объектно-ориентированного программирования. Например, программа, выполняемая в пользовательском режиме, которой необходимо синхронизировать несколько собственных потоков, может запросить у операционной системы объект синхронизации Еуеп1: (событие), а на самом деле — просто особым образом обслуживаемую структуру данных. Система предоставляет Еуеп!: в форме системного объекта, на который из программы пользовательского режима можно ссылаться только по дескриптору (ИапсПе). Файлы, процессы, потоки, события (Еуеп^з), секции памяти (Метогу Весйопз) и даже подразделы Системного Реестра (Нед151ту Кеуз) поддерживаются операционной системой как системные объекты. Все объекты создаются и уничтожаются централизованно — Менеджером Объектов. Это позволяет получить унификацию (единообразие) доступа к объектам, контроля над их временем жизни и обеспечивать безопасность и права доступа к ним. Всем исполнительным компонентам дозволено вступать в работу Ф только на определенном уровне приоритета — для обеспечения слаженности в их совместной работе. В результате, функции, предоставляемые этими компонентами, так же могут быть вызваны не с любого произвольного уровня 1К()1_. В частности, функции Менеджера объектов (например, ОЬОеге1егепсеОЬ)ес1:) следует вызывать с уровня не выше О15РАТСН_1_Е\/Е1_. Нарушение этого правила ставит систему в двусмысленное положение. В самом деле, поток с более высоким может ожидать окончания работы кода, который должен работать на более низком Ж()1_ (в силу своей медлительности или других внутренних вызовов). Чтобы не приводить систему к деградации,
соблюдение такого типа правил жестко контролируется операционной системой.
Менеджер конфигурирования СопЛдига^оп Мападег. Менеджер конфигурирования \Л/1Пс1о\л/5 1\1Т 5 конструирует модель всей доступной аппаратуры и всего инсталлированного программного обеспечения, которое имеется на компьютере. Для хранения образа этой модели используется база данных, хорошо известная под названием Системный Реестр. Драйверы устройств используют информацию Реестра для уточнения множества характеристик окружения, в котором им придется работать. С введением спецификации Р1ид апс1 Р1ау роль Системного Реестра для драйверов, реализованных по \ЛЮМ модели, существенно снизилась.
Менеджер процессов Ргосезз Мападег. Предоставляет функции, начинающиеся с префикса Рз (например, Рзбеи/егзюп — получить версию операционной системы). Функции РзХхх могут выполняться на разных уровнях что следует уточнять в документации ЭЭК. В чем заключается работа Менеджера процессов? Процесс является средой, в которой существует (выполняется) поток. Каждый процесс определяет собственное адресное пространство и содержит элементы идентификации для определения прав доступа (зесигГСу ИегиГСу). Важно отметить, что в У\Лпс1о\л/5 процесс не выполняется; выполняется поток, который является единицей выполнения, в то время, как процесс является "фигурой" собственности. Процесс владеет одним или несколькими потоками. Менеджер процессов \Л/|пс1о\л/5 2000/ХР/2003 является исполнительным компонентом, который управляет созданием процесса и предоставляет ему окружение, в котором работают программные потоки. Менеджер Процессов в своей работе опирается, главным образом, на другие исполнительные компоненты (например, Менеджера Объектов и Менеджера виртуальной памяти), так что можно сказать, что он представляет верхний уровень абстрагирования над другими системными сервисами более низкого уровня. Драйверы редко контактируют непосредственно с Менеджером процессов. Как правило, они опираются на другие системные сервисы для доступа к среде процесса (ргосезз епу|гоптеп1:). Например, драйвер хочет быть
уверен, что буферная область памяти в области адресов, находящихся во владении процесса, удерживается в течение всего времени передачи данных. Системные процедуры с префиксом Мт (относящиеся к Менеджеру памяти) предоставляют драйверу средства для такого удержания.
Менеджер виртуальной памяти \/1г1:иа1 Метогу Мападег, \/ММ. В операционной системе \Л/1Пс1о\л/5 2000/ХР/2003 (впрочем, как и в \Л/1Пс1о\л/5 9х) адресное пространство процесса является непрерывным и непрерывно адресуемым (Ла1:). Размер адресуемого таким образом пространства в 32 разрядной версии \Л/|пс1о775 составляет 4 ГБ (2 в степени 32), что соответствует использованию 32 разрядного указателя. При этом только нижние 2 ГБ доступны для использования кодом пользовательского режима. Программный код и данные пользовательских программ должны размещаться в этой нижней половине адресного пространства. В случае, если пользовательские программы используют совместно динамически подключаемые библиотеки (ОН), то этот библиотечный код также должен размещаться в первых двух гигабайтах адресного пространства (эта схема претерпевает некоторые изменения — в сторону увеличения пользовательского адресного пространства до 3 ГБ — лишь в Еп^егрпзе 5еп/ег при его соответствующей настройке). Верхние 2 ГБ адресного пространства каждого процесса содержат код и данные, доступ к которым возможен только из программного кода, выполняющегося на уровне ядра. Верхние 2 ГБ используются кодом уровня ядра совместно от процесса к процессу. Если вы выполните распечатку адресов каких-нибудь объектов, переменных или процедур в окне ОеЬид\/1е\л/ или □еЬидРпп!:, то сразу же увидите, что код драйвера отображается на адресное пространство выше 2 ГБ (адреса превышают 0x80000000).
Менеджер памяти (\/ММ) осуществляет управление памятью от имени всей операционной системы. Для обычной программы пользовательского режима это означает выделение памяти и управление адресным пространством и физической памятью ниже границы 2 ГБ. В том, достаточно обычном, случае, когда процессу пользовательского режима не хватает физической памяти, \/ММ создает иллюзию наличия памяти путем виртуализации запроса. Необходимая память выделяется страницами (то есть блоками соответствующего размера, для 1п1:е1 платформы — 4 КБ) на жестком диске (что называется — рад'шд}. По мере необходимости доступа к ней со стороны потоков процесса выделенные страницы перемещаются в физическую память. Таким образом, физическая память становится совместно используемым ресурсом всех процессов. Менеджер Виртуальной Памяти выступает также и в роли ответственного за распределение памяти в том смысле, что управляет "кучей" (Пеар агеа) для программного кода уровня ядра. Для своих нужд драйверы через соответствующие вызовы (например, вызов ЕхА11оса1еРоо1, в конечном счете, обрабатываемый \/ММ) могут запросить выделение областей памяти в страничной (то есть организованной странично и допускающей сброс на жесткий диск) или нестраничной памяти.
Средства локальных процедурных вызовов 1_оса1 Ргосес1иге Са11 (1_РС), локальный процедурный вызов, является механизмом вызовов между процессами на одном компьютере. Так как межпроцессный" вызов (т^егргосезз са11) может проходить между разными адресными пространствами, то существуют и соответствующий Исполнительный компонент, который делает это действие возможным и эффективным. Как правило, драйверному коду не требуется осуществление вызова другого процесса и, соответственно, 1_РС средства.
Диспетчер (менеджер) ввода/ вывода Диспетчер ввода/вывода (1/0 Мападег), ДВВ, является исполнительным компонентом, который реализован в виде множества процедур уровня ядра. ДВВ представляет для процессов пользовательского режима единообразный подход к операциям ввода/вывода. Предлагаемая модель не делает различий, получает ли пользовательский процесс доступ к клавиатуре, коммуникационному порту или файлу на диске — способ обращения оформлен одинаково. ДВВ представляет запросы от процессов пользовательского режима драйверным процедурам в форме пакета запроса на ввод/вывод, то есть пакета 1Р.Р. Пакет 1Р.Р является своего рода рабочим рецептом, созданным ДВВ, который передается в драйверные процедуры. Работа же драйвера состоит в том, чтобы должным образом этот запрос обработать. Большая часть данной книги как раз посвящена правильной организации драйверного кода, обрабатывающего 1РР пакеты. Фактически, Диспетчер ввода/вывода является интерфейсом между кодом пользовательского режима и драйверами устройств. Таким образом, он является первым и важнейшим системным компонентом, с которым взаимодействует драйвер. При посредничестве Диспетчера ввода/вывода с драйвером могут общаются и компоненты уровня ядра, например, могут обращаться другие драйверы (вспомним, хотя бы, случай с программой ОеЬид Рпп1: МопКог, глава 2).
Расширения базовой операционной системы Исполнительные компоненты \ЛЛпс1о\л/5 2000/ХР/2003 определяют и представляют основные сервисы операционной системы. Однако эти сервисы никогда не предоставляются программам пользовательского режима непосредственно. Вместо этого разработчики из МюгозоГ!: определили несколько интерфейсов прикладного программирования (АррПсайоп Ргодгатгтппд Тп^егГасез), при помощи которых код пользовательского режима может обращаться к абстракциям системных служб. Эти интерфейсы формируют различные среды (епу|гоптеп1:а1 зиЬзуз^етз), в которых и обитают прикладные программы. В настоящее время в \Л/1Пс1о\л/5 1\1Т 5 представлены: \Л/1п32 подсистема, являющаяся собственным (пайуе- тос1е) АР1 для 32-разрядных версий У\Лпс1о\л/5. Все остальные среды (епу|гоптеп1:а1 зиЬзуз^етз) используют эту подсистему для выполнения своей работы. Все новые приложения 32-разрядных \Л/1Пс1о\л/5 2000/ХР/2003 (а также и все перенесенные) полагаются на \ЛЛп32 как на среду своего функционирования. Из-за важности (и достаточно интересной реализации) эта подсистема будет рассмотрена далее более детально. Следует, однако, отметить, что в 64-разрядной версии \ЛЛпс1о\л/5 (версии ХР/Вегуег 2003) она сама становится клиентом \Л/О\Л/64 (см. ниже). \/1г1:иа1 005 МасЫпе (\ЮМ, виртуальная 005 машина) подсистема обеспечивает 16-разрядную М5 005 операционную среду для старых 005 приложений.
Несмотря на уверения в совместимости, множество существующих 005 программ в этой среде не работают надлежащим образом. Происходит это по той причине, что МюгозоГ!, проповедуя консервативный подход, предоставляет эмуляцию аппаратуры вместо возможности непосредственного обращения к ней. В результате, прямой доступ к аппаратуре приводит к ограничению со стороны операционной системы и, зачастую, отказу данного □05 приложения работать. Подсистема 'У\Лпс1о\л/5 оп VV^псIоVV5, (УУОУУ) поддерживает операционную среду для возможности работы старых 16-битных приложений \Л/1Пс1о\л/5 (например, \Л/| пс!о\л/з 3.x). В 64-разрядных клонах \Л/1Пс1о\л/5 ХР/5егуег 2003 подсистема МОМ 64 служит для запуска созданных ранее 32-разрядных приложений, перенесенных на новые аппаратные платформы. Подсистема Р051Х обеспечивает выполнение Шгнх- приложений, которые удовлетворяют стандарту Р051Х 1003.1. К сожалению, большинство перенесенных 11п1х-подобных систем приложений не работает должным образом в этой подсистеме. В данном случае большинство Оглх-приложений переносятся под \Л/1Пс1о\л/5 путем переписывания под ХЛ/1П32 подсистему или они изначально создаются с использованием специальных программных пакетов, типа Ма1пУ\Лп, МоИГ и ОрепМоИГ. Подсистема 05/2 создает среду выполнения для 16- разрядных программ операционной системы 05/2 — по крайней мере, тех из них, которые не используют в своей работе сервисов такого компонента 05/2, как Ргезеп^аИоп Мападег (РМ). На эту подсистему можно рассчитывать только в версии У\Лпс1о\л/5 для платформы 1п1е1 (х86).
Каждое приложение однозначно связано с одной средой выполнения. Приложения не могут осуществлять АР1 вызовы к другим исполнительным средам. Кроме того, подсистема У\Лп32 является основной в 32-разрядных версиях \Л/1Пс1о\л/5 1\1Т 5.x. Другие подсистемы эмулируют соответствующие свойства реализуемых сред через средства и методы У\Лп32. Соответственно, параметры выполнения программ в этих средах деградируют и существенно уступают аналогичным программам для \Л/1П32.
Подсистема У\Лп32 В качестве основного для У\Лпс1о775 2000/ХР/5еп/ег 2003 интерфейса АР1 (32-разрядных версий), подсистема ХЛ/1П32 ответственна за: графический пользовательский интерфейс (бгарЫса! 11зег 1п1:егГасе, 6111), который наблюдает пользователь системы. У\Лп32 отвечает за реализацию видимых окон, диалоговых элементов и элементов управления (кнопок, полос прокрутки и т.п.), то есть общий стиль оформления системы. Консольный ввод/вывод, включая клавиатуру, мышь и дисплей для всей операционной системы и других подсистем. Функционирование \Л/1п32 АР1, при помощи которого приложения и другие подсистемы взаимодействуют с исполнительными компонентами режима ядра. Так как подсистема \Л/1п32 имеет особый статус среди остальных подсистем, а вследствие этого к ней предъявляются повышенные требования, то и реализация этой подсистемы существенно отличается. В частности, подсистема \ЛЛп32 разделена на несколько компонентов, часть из которых работает пользовательском режиме, а другая в режиме ядра. Функции \Л/1п32 можно разделить на три категории: Функции, которые предоставляются пользователю для управления окнами, меню, диалогами и элементами контроля (кнопками, полосами прокрутки, переключателями, закладками и т.п.). Функции 601, реализующие прорисовку изображений на физических устройствах, экране, принтере, графопостроителе.
Функции, которые управляют неграфическими ресурсами, такими как процессы, программные потоки, файлы и объекты синхронизации. КЕкЫЕ1_- функции тесно связаны с системными службами исполнительных компонентов. Со времен 1\1Т 4.0 большая часть функций первых двух категорий из приведенной выше классификации была реализована в режиме ядра. Пользовательские процессы, которые запрашивают услуги С1Л, обращаются непосредственно к коду режима ядра при использовании 5уз1:ет 8еплсе 1п1:егГасе (Интерфейса Системных Служб). Код, представляющий эти функции и работающие в режиме ядра, локализован в модуле таЫ32К.5У5. Функции третьей категории при обработке запросов от пользовательских процессов опираются на стандартный серверный процесс С5Н55.ехе (СПеп^-Зегуег кипите 5иЬзу51:ет), который и обращается собственно к коду исполнительных компонентов для завершения обработки этих обращений.
Другие существенные компоненты операционной системы В дополнение к подсистемам, реализующим среду выполнения кодов 005, \Л/| пс!о\л/з, Р051Х и 05/2, имеется еще несколько ключевых системных компонентов, которые реализованы как процессы пользовательского режима. Среди них: 5есигКу 5иЬзу51:ет, подсистема безопасности, которая управляет локальной и удаленной безопасностью (защитой от неправомерного доступа) с использованием ряда процессов и динамических библиотек. Часть работы Ас^уе 0|гес1югу протекает как раз среди этой логической подсистемы. 5еплсе Соп1то1 Мападег (5СМ, функции которого использовались для запуска драйвера в Главе 3), Менеджер Управления Сервисами, управляет процессами-демонами (сервисами), и драйверами устройств. Процессы поддержки Р.РС вызовов (кетозе Ргосебиге Са11, вызов удаленной процедуры), которые оказывают поддержку приложениям, распространяемым по сети. Прибегая к использованию вызовов удаленных процедур, приложения могут выполнять свою работу с использованием множества сетевых компьютеров.
Компоненты обслуживания операций ввода/вывода, работающие в режиме ядра Цели разработки подсистемы ввода/вывода Подсистема ввода/вывода вносит корректировки в список задач новых систем У\Лпс1о\л/5, но особенно следует отметить: Конфигурируемость и в терминах аппаратуры, и в терминах программного обеспечения. Для драйверов \Л/|Пс1о\л/5 это означает полную поддержку РпР спецификации для шин и устройств. Приоритетность и прерываемость. Код, написанный для обслуживания ввода/вывода, никогда не должен блокироваться и должен содержать безопасные программные потоки. Безопасность при использовании на многопроцессорных платформах. Один и тот же драйверный код должен безошибочно работать и на однопроцессорных и на многопроцессорных компьютерах. Объектная ориентированность. Услуги, предоставляемые кодом, должны "формулироваться" в терминах вполне определенных структур данных, которые служат выполнению разрешенных операций. Пакетное управление. Запросы, сделанные к подсистеме ввода/вывода формулируются, передаются и отслеживаются с помощью четкого формата "рабочего рецепта", известного как 1Р.Р пакет (1/о Нериез!: Раске!:, пакет запроса на ввод/ вывод).
Поддержка асинхронного ввода/вывода. Подсистема ввода/ввода должна позволять коду программы, по обращению которой создан запрос 1КР, выполняться параллельно тому программному коду, который осуществляет обработку запроса. Также должен существовать механизм оповещения инициатора вызова о полном завершении обработки запроса. Помимо уже указанных задач, существует и насущная необходимость обеспечения повторного использования кода (так называемой реентерабельности, повторной входимости в код). Это означает необходимость не только сложного структурирования собственно кода, отвечающего за операции ввода/вывода в одном устройстве, но и другого взгляда на взаимодействие слоев всей подсистемы ввода/вывода. Например, код, отвечающий за управление шиной, должен быть реализован автономно от кода, отвечающего за конкретное устройство, подключенное к шине. Выполнение этого требования позволяет повторно использовать код шинного драйвера программными блоками (например, драйверами), отвечающими за другие устройства на этой шине, которые, кстати сказать, могут подключаться, и удаляться, и, весьма возможно, пока еще и вовсе не изготовлены.
Типы драйверов АЛЛпс1оуу5 1ЧТ5 В былые времена разработчик драйвера мог изучить новую аппаратуру, интерфейс общения операционной системы и драйверов, окинуть все это критическим взором, сосредоточиться и создать драйвер. Хорошо это или плохо, но пора монолитных драйверов практически прошла. Сегодня разработчик драйверов должен изучить архитектуру аппаратных шин, архитектуру многослойной подсистемы ввода/вывода для того лишь, чтобы определить мотивы выбора конструкции драйвера. Определение, какого типа драйвер следует написать, уже само по себе важное решение. Выбор, следует ли заново реализовать или можно повторно использовать какой-нибудь уже имеющийся в подсистеме ввода/ вывода слой, является еще одним важным решением. Если взять самый крупный план, то можно разделить драйверы, используемые \Л/1Пс1о\л/5 1\1Т 5, на две группы: драйверы пользовательского режима и драйверы режима ядра. Первые, как подразумевает их название, являются системным программным кодом, функционирующим в пользовательском режиме. В качестве примера можно назвать драйверы-симуляторы (виртуализаторы) для воображаемой аппаратуры или новых исполнительных подсистем (МОМ, РО51Х, 05/2). Так как М1пс1о\л/5 2000 не допускает непосредственной работы с аппаратурой для кода пользовательского режима, то такие драйверы должны полагаться в этой части на драйверы, работающие в режиме ядра. Предположим, в примере главы 3 (драйвер Ехатр1е.5уз) была бы создана 01_1_, которая работала бы в пользовательском режиме и общалась бы с драйвером на манер приложения Ехатр1еТез^. В свою очередь, если бы пользовательские приложения обращались бы к ней (а не к помощи
функций Сгеа1еН1е, Оеу|се1оСоп1го1 и т.п.) в те моменты, когда желали бы обратиться к драйверу, то тогда такая ОН и была бы типичным драйвером пользовательского режима. Драйверы режима ядра (кегпе1-тос1е йгнгегз) целиком состоят из кода системного уровня, выполняющегося в режиме ядра. Поскольку коду режима ядра разрешено работать непосредственно с аппаратурой (мы это видели в коде примера главы 3 при обработке ЮСТ1_ запроса ЮСТ1__ТО11СН_РОкТ_378Н), такие драйверы имеют прямой доступ к управлению устройствами, содержащимися в компьютере или подключенными к компьютеру. Разумеется, ничто не может помешать такому драйверу представлять вымышленную аппаратуру — воля разработчика, где этим заниматься — в пользовательском режиме или режиме ядра. Ограничившись категорией драйверов режима ядра, чему и посвящена данная книга, перемещаемся как раз в код режима ядра. На этом уровне можно выполнить деление драйверов еще на две категории: наследованные (1едасу, доставшиеся как наследство от \Л/1Пс1о\л/5 1\1Т 3.5, 4) и \ЛЮМ драйверы. Пример драйвера Ехатр1е.5уз, рассмотренный в предыдущей главе, как раз и является примером 1едасу драйвера. Он использует функции заголовочного файла пи1с1к.И, скомпилирован без директивы ОВ1УЕВТУРЕ=УУОМ, не зарегистрировал ни одной процедуры типового МОМ драйвера, не выполнил подключение объекта своего устройства к родительскому объекту и не реализует свои запросы путем обращения к стеку устройств. Драйверы типа 1едасу (если не говорить о задачах проникновения в режим ядра с задачами чистого программирования или исследования) предназначены для работы с теми устройствами, которые не поддерживают РпР
спецификацию, поскольку только разработчик драйвера знает, как различить его присутствие в системе, не говоря уже о приемах работы с ним. К счастью, практически все знания, касающиеся наследуемых драйверов 1\1Т, полностью применимы к модели \ЛЮМ, по которой можно построить драйверы, работающие в \Л/|пс1о\л/5 2000/ХР/5еп/ег 2003 (и \Л/1Пс1о\л/5 98, Ме). Правда, как мы видели на примере Ехатр1е.зуз, и некоторые экземпляры драйверов типа 1едасу ("в- стиле-МТ") могут работать во всех перечисленных ОС. Способность драйверов \ЛЮМ работать по РпР спецификации включает в себя: участие в управлении энергоснабжением системы, автоматическое конфигурирование устройства и возможность его "горячего" подключения. Корректно написанный \Л/ОМ драйвер может быть использован и под \Л/1Пс1о\л/5 1\1Т 5.x и под \Л/1Пс1о\л/5 98/Ме, хотя МюгозоЛ не гарантирует бинарной совместимости (простого переноса .зуз файла в другую ОС, как это получилось с Ехатр1е.зуз). В большинстве случаев все еще необходима перекомпиляция под \Л/1Пс1о\л/5 98 ООК. Наследуемые и МОМ драйверы можно разделить также и на другие три категории: высокоуровневые, средне и низкоуровневые драйверы. Как подразумевает эта классификация, высокоуровневые драйверы зависят от драйверов среднего и низкого уровня в выполнении своих задач. Драйверы среднего уровня (|гПегтесПа^е, промежуточные), соответственно, в своей работе зависят от функционирования драйверов низкого уровня (1о\л/- 1еуе1 ипуегз). К высокоуровневым драйверам, например, относятся драйверы файловых систем (Л1е зуз1:ет дпуегз, Е50з).
Такие драйверы предоставляют инициаторам запросов нефизическую абстракцию получателя, и уже эти запросы транслируется в специфические запросы к лежащим ниже драйверам. Необходимость в создании высокоуровневых драйверов возникает тогда, когда основные услуги аппаратуры уже реализованы драйверами нижнего уровня, и требуется только создать новую фигуру абстрагирования, которая необходима для предъявления инициатору запросов (клиенту драйвера). Фирма М!сго5о1Т поставляет комплект программного обеспечения 1пз1:а11аЫе Е!1е 5уз1:ет (1Е5) КК, который распространяется отдельно от М5ЭЫ или других продуктов. Пакет 1Е5 КИ: требует наличия пакета ИСК (и некоторых других средств) для того, чтобы заняться разработкой файловой системы всерьез. При этом существуют многочисленные ограничения на то, какие типы файловых систем могут быть получены при помощи этого пакета (1Е5 КН). Дополнительную информацию по пакету 1Е5 КК можно получить на интернет-сайте М|СГ050ГЬ. Драйверы среднего уровня могут быть проиллюстрированы такими примерами, как драйверы зеркальных дисков, классовые драйверы (с/азз с/гп/етз), мини-драйверы (т/л/ с/пуегз) и фильтр-драйверы (/Н1ег с/пуегз). Эти драйверы позиционируют себя между высокоуровневыми абстракциям высокоуровневых драйверов и средствами физической поддержки на нижних уровнях. Например, драйвер зеркальных дисков получает запрос от высокоуровневого драйвера файловой системы (Е5Э), транслирует этот запрос в два запроса к двум разным дисковым драйверам более низкого уровня. При этом нет никакой необходимости в том, чтобы кто-нибудь из драйверов верхнего или
нижнего уровней был в курсе, как на самом деле произошла "зеркализация" и была ли она вообще. Драйверы класса являются возможностью повторного использования кода в пределах драйверной модели. Так как много драйверов определенного типа могут иметь много общего, то программный код, описывающий общие места, может быть помещен в общий для данного типа классовый драйвер, отдельно от специфичного для обслуживания конкретных устройств кода. Например, драйверы НЮ (Питал 1п1:егГасе беу|се, устройства ввода по шине 115В) используют такие сходства. Драйверы специфических НЮ устройств могли бы быть, в такой ситуации, реализованы как мини-драйверы, взаимодействующие с классовыми драйверами. Мини- драйверы отличаются тем, что они, как правило, общаются только с другими драйверами верхнего уровня (представляющими для них оболочку), не выходя на "прямой контакт" с Диспетчером ввода/вывода. Мини- драйвер и его клиент наверху имеют заранее обусловленный протокол общения, и обычно мини- драйвер экспортирует набор функций своего интерфейса по запросу драйвера-оболочки, после чего возможно общение между драйверами минуя Диспетчер ввода/ вывода, что существенно ускоряет работу. Фильтр-драйверы (/Н1ег с1пуегв') являются драйверами среднего уровня, которые позиционирует себя во время загрузки над или под интересующим их драйвером и перехватывают запросы, идущие к нему или от него. Фильтр-драйверы, как правило, предназначены для модификации запроса к существующему драйверу или для реализации некоей дополнительной функции, изначально не заложенной в существующем драйвере (в простейшем случае это может быть подсчет пропущенных 1РР пакетов). В операционной системе
фильтр-драйверы с большой долей вероятности можно опознать по отсутствию имен у объектов устройств, созданных такими драйверами (при использовании программы □еу|сеТгее). Наконец, документация ООК упорно внедряет такую категорию (по отношению к \АЮМ драйверам) как функциональные драйверы (ГипсНопа! причем эти драйверы могут быть либо классовыми, либо мини-драйверами. Что имеет в виду документация ООК, когда вводит термин "функциональный"? Дело в том, что такие драйверы всегда работают как интерфейс между абстрактным запросом ввода/вывода и кодом низкоуровневого физического драйвера, "физического" — в том смысле, что он связан непосредственно с устройством и в его функционировании хорошо просматриваются особенности этого устройства. Характерна в данном случае следующая деталь. Когда типовой \АЮМ драйвер нормального РпР устройства собирается подключить себя к стеку устройств (это должно происходить в процедуре АбсЮеуюе), он выполняет подключение функционального объекта устройства (ЕЭО), созданного им самим, к физическому объекту устройства (РОО), предоставленному ему родительским драйвером. Как правило, родительским является драйвер шины, который первоначально обнаружил подключение данного устройства, инициировав затем обращение к рассматриваемому МОМ драйверу устройства. Вот здесь и проявляется функциональность последнего: он получает общие функциональные запросы, а превращает их в низкоуровневые (например, в 11Р.В запросы для устройств 115В), понятные шинному драйверу и устройству, олицетворяя при этом для своего клиента функции устройства, а не его конструкцию и внутреннюю логику.
Специальные драйверные архитектуры М!сго5оГ1: предлагает специфические драйверные архитектуры для нескольких типов или классов устройств, а именно: Видеодрайверы. Драйверы принтеров. Драйверы устройств мультимедиа. Сетевые драйверы. Рассмотрение этих архитектур выходит за рамки задач данной книги, поэтому ограничимся лишь данным перечислением.
ГРгеу|ои81 ГИехП
Отличия между версиями Отличия версий \Л/1пс1о\л/з 2000 (ЫТ 5.0), ХР (ЫТ 5.1) и 5еп/ег 2003 (ЫТ 5.2), разумеется, существуют. И касаются они не только пользовательского интерфейса и наполнения сервисными программами, но и наборов системных вызовов, предоставляемых в режиме ядра. Тем, кого интересует большее количество деталей, нежели будет представлено далее, можно порекомендовать три статьи, размещенные сегодня в Интернете: 1. статья Марка Руссиновича и Дэвида Соломона "\ЛЛпс1о\л/з ХР: Кегпе1 1тргоуетеп15 СгеаЬе а Моге КоЬиз!, Ро\л/егГи1, апс! 5са1аЫе 05", размещенная на сайте М1СГО5ОЛ по адресу •: ; 2. статья Марка Валла и Роберта Вильямса "У\/1Пс1оуу5 .ЫЕТ 51гис1иге апс! АгсЫ1ес1иге", размещенной в Интернет-журнале "Ме1\л/огк \Л/1пс1о\л/з & .ЫЕТ Мада21пе" по адресу ; 3. статья "Сотраге 1Ье ЕсПйопз о! \ЛЛпс1о\л/5 Зегуег 2003" (51апс1агс1, ЕпЬегрпзе, Оа!асеп1ег & УУеЬ ЕсНйоп), размещенная на Интернет-сайте МюгозоП по адресу 1П1Сго5оГ1.сот/уу|пс1оуу55егуег2003/еуа1иа11Оп/Геа1иге5/сотоагеес1|11ОП5. тзрх. Не вдаваясь в подробности, отметим наиболее важные для разработчика драйверов отличия. Прежде всего, следует различать 32 и 64-разрядные клоны. Если 64 разрядная \ЛЛпс1оуу5 2000 была собрана лишь один раз в тестовом режиме, то У\/1пс1оуу5 ХР (и уж тем более, 5еп/ег 2003) в 64-разрядной сборке представляет собой реальный коммерческий продукт. Организация адресного пространства памяти в 64 разрядной версии сильно отличается от организации виртуального пространства в 32-разрядном исполнении (таблица 7.1). Приложения, созданные как 32-разрядные программы, запускаются под управлением подсистемы \ЛЛп32 в рамках модели УУОУУ (\Л/1пс1о\л/з Оп \Л/1пс1о\л/з 64), аналогично тому, как 16-разрядные приложения работают в 32-разрядной среде. Драйвер в 64 разрядной версии компилируется как 64-разрядный код — в частности, с соответствующей длиной указателей. В данной ситуации драйверу актуально знать, от какого приложения он получил запрос — от нового 64- разрядного или старого 32-разрядного. Эта проблема не представляет большого затруднения, поскольку 64-разрядный драйвер может выяснить тип вызывающего процесса при помощи системной функции 1о1832Ы1Ргосе88, доступной для применения в 64-разрядных сборках драйверов. Пример приводится ниже. ( 1о1з32ЫЬРгосезз (Тгр) == ТРИЕ ) { ВВС ОЬдРгхп’Ь (" 1Р-Р гедиезВ 13 тас!е Ьу 32 ргосезз."); #епсН } е1зе { ВВС ВЬдРгтпЬ("ТРР гедиезЬ 13 таЬе МОТ Ьу 32 Ы1 ргосезз.");
#епсИ Г } В соответствии с ответом драйвер может скорректировать некоторые свои операции. С какой конкретно версией операционной системы драйвер имеет дело, можно легко определить по тому, какую версию модели \ЛЮМ поддерживает система. Это можно узнать с помощью системного вызова IоI5VV<^тVе^5^опАVа^IаЫе, например: 15 (IоIзИс^тVе^з^опАVа^1аЬ1е (1, 0x30)) { // И1пс1ом8 Зег^ег 2003 } е1зе 15 (ТоТзИйтУегзхогкАллахЗаЫе (1, 0x20)) { / / И1ПС1ОМ8 ХР } е1зе 1Г (IоIзИс^тVе^з^опАVа^1аЬ1е (1, 0x10)) { // И1пс1оиз 2000 } е1зе 15 (IоIзИс^тVе^з^опАVа^1аЫе (1, 0x05)) { / / И1пс1омз Ме } е1зе { // И1пс1оиз 98 } Тем не менее, пока что широкое использование в России дорогих 64-разрядных конфигураций не предвидится, что можно с некоторой степенью уверенности констатировать на основании неширокого применения в прошлом и настоящем относительно "продвинутых" процессоров ХЕОЫ. Сосредоточим внимание на 32-разрядных версиях. Среди них произошли следующие изменения. При переходе от \Л/тс1о\л/з 2000 к \Л/1пс1о\л/з ХР переписан загрузчик ЫТиЖ (см. Приложение Б), в результате чего процесс загрузки ускорился в 4-5 раз. Модуль МТО5КП.1М1..ЕХЕ \Л/1Пс1оуу5 2000 экспортировал 1129 функций (большинство из которых — системные вызовы, доступные из драйверов режима ядра), \Л/1пс1о\л/з ХР экспортирует 1464, а \Л/1пс1о\л/з 5еп/ег 2003 — уже 1525. Модуль НА1_.О1_1_ экспортирует, соответственно, 95, 92 и 92 вызова. Причем, по косвенным признакам заметно, что от версии 2000 к ХР его постигла значительная переработка, а от версии ХР к 5еп/ег 2003 — лишь процесс "шлифовки". Пополнение системных вызовов в 5еп/ег 2003 произошло, в основном, за счет вызовов с суффиксом Ех, то есть функций в чем-то расширяющих существующие системные вызовы \Л/тс1о\л/з ХР. Например, ХоСзяХпШаНгеЕх получился из вызова 1оС5я1пШаНхе. При переходе от У\Лпс1о\л/5 2000 к \ЛЛпс1о\л/5 ХР были смягчены многие ограничения на предельные размеры. Операционная система \Л/1пс1о\л/з 2000 ограничивала общий размер адресного пространства под драйверами 220 Мбайтами, в ХР этот предел отодвинут до 960. Таким же он остался и в 32 разрядной версии 5еп/ег 2003.
Предельный общий размер файлов Системного Реестра \Л/1пс1о\л/з ХР жестко не фиксирован, в то время как в \Л/1пс1о\л/з 2000 он не должен был превышать 376 Мбайт. Увеличен размер системных страничных таблиц, в результате чего системное виртуальное адресное пространство увеличилось до 1.3 Гбайт — против 660 Мбайт в \Л/1Пс1охла5 2000. При этом 960 Мбайт в системном виртуальном адресном пространстве ХР непрерывны, в других же его "местах" имеются разрывы. (Заметим, что 64 разрядная версия поддерживает размер виртуального системного адресного пространства равный 128 Гбайт.) Весьма вероятно, что такие же параметры остались и у \Л/тс1о\л/з 5еп/ег 2003 (точные сведения отсутствуют). Поскольку, страничные таблицы и большая часть Системного Реестра размещаются в физической памяти резидентно, очевидно, что цена такого "послабления" — новое повышение требований к минимальному размеру установленной на компьютере оперативной памяти — до 128 Мбайт. Увеличено число процессоров, которые может поддерживать операционная система в симметричной многопроцессорной конфигурации (5МР). В редакции Оа1асеп1ег У\Лпс1оуу8 5еп/ег 2003 может поддерживать до 64 процессоров (в 64 разрядной версии). 32-разрядная версия (как и Оа1асеп1ег \Л/тс1охлА5 2000) поддерживает по- прежнему до 32 процессоров. Разумеется, такая архитектура обязана быть должным протестирована, и МюгозоГ! анонсировала особый подход к продажам таких "тяжелых" серверов: операционная система будет поставляться только вместе с аппаратурой ОЕМ поставщиками. И, наконец, самые интересный вопрос: что же означают громадные цифры поддерживаемой оперативной памяти в 32-разрядных версиях: от 2 Гбайт (редакция УУеЬ \Л/1пс1о\л/з 5еп/ег 2003) до 64 Гбайт (редакция Оа1асеп1ег \Л/1пс1о\л/з 5еп/ег 2003)? Ответ кроется в двух аббревиатурах: АУУЕ и РАЕ, АсИгезз \Л/тс1охллпд ЕхЬепзюп и РИуз1са1 АсИгезз ЕхЬепзюп, соответственно. Операционная система, подчиняясь параметру /РАЕ, заданному в файле ЬооМп! (см. Приложение Б), загружается в модифицированной конфигурации, поддерживающей режим работы РАЕ. В таком режиме возможна манипуляция физическими адресами оперативной памяти (тип РНУ51СА1__АООК.Е55) за пределами 4 Гбайтного пространства функциями категории МтА11оса1еРаде5РогМ<11. Наиболее простое и находящееся "на поверхности" применение данного расширения — создание драйверов, реализующих РАМ диск. Правда, осложняет дело высокая цена оборудования. На сегодня доля материнских плат, поддерживающих размер оперативной памяти более 8 Гбайт, не превышает 1,5% рынка. В заключение отметим еще два факта. Во-первых, по-прежнему все рассмотренные версии продолжают поддерживать все три файловых системы: ГАТ, ЕАТ32, ЫТЕ5. Иными словами, \Л/1пс1о\л/з 5еп/ег 2003 Оа1асеп1ег ЕсНйоп вполне устанавливается на логическом диске ЕАТ32, занимая при этом чуть более 1,3 Гбайт. Во-вторых, МюгозоГ! окончательно (в У\Лпс1о\л/8 Бегуег 2003) отошла от поддержки подсистем РО51Х и 05/2. Впрочем, осталось неизменным самое важное — драйверная модель, заложенная в ХЛ/1Пс1о\/У5 2000, обеспечивает практически полную совместимость программного кода
(а иногда — и бинарного, как можно было убедиться на примере главы 3) во всех рассматриваемых версиях.

Заключение Операционные системы ряда \Л/1Пс1о\л/5 1\1Т 5.x (\Л/1Пс1о\л/5 2000/ХР/5егуег 2003) представляют богатые возможности для разработки приложений. Схема обработки операций ввода/вывода в \Л/1Пс1о\л/5 2000/ХР/5егуег 2003 сложна и требует достаточных усилий, чтобы охватить картину в целом, что необходимо для продуктивной работы. Главы 5 и 6 рассматривают эти вопросы более подробно.
Глава 5
Прикасаясь к аппаратуре Как было сказано ранее, термин "драйвер" в операционной системе \Л/1Пс1о\л/5 может описывать программные модули, которые ни одним битом данных не обмениваются с реальной аппаратурой. Но рано или поздно в своей практике разработчик драйверов все- таки сталкивается с необходимостью обслуживания реальных устройств. Традиционно первая версия драйвера создается разработчиком аппаратуры как тестовый код для нового устройства. Редко удается совмещать высокий профессионализм и в программировании и в разработке "железа", поэтому типичной является ситуация разделения труда по данному признаку. Такие образом, через некоторое время первоначальные заготовки поступают к разработчикам программного обеспечения, которым и предстоит довести начальный код до состояния законченного полнофункционального драйвера. Однако как ни парадоксально это выглядит, понимание аппаратных тонкостей на второй стадии требуется ничуть не меньшее.
ГРгеу|ои51 [Мех!],
Основные сведения об аппаратном обеспечении Несмотря на великое многообразие типов и областей применения устройств, потребность в подключении которых к компьютеру возникает на практике, можно выделить несколько общих черт, в курсе которых необходимо быть разработчику драйвера. Поддерживает ли устройство спецификацию РпР и как устройство может известить систему о своем существовании при подключении? Как происходит доступ к регистрам состояния и контроля устройства? Как устройство может генерировать сигнал прерывания? Как устройство участвует в передаче данных? Использует ли устройство какую-либо встроенную память? Как устройство конфигурируется и какэто можно сделать программно?
Автоматическое распознавание и конфигурирование За каждым аппаратным устройством, подключенным к компьютеру, закрепляются системные ресурсы, которые могут включать: диапазон адресов ввода/вывода (портов вода/вывода); диапазон отведенных адресов памяти; номер прерывания 1ЕЩ; ЭМА канал. Поскольку разные устройства были изготовлены в разное время разными поставщиками, то конфликты ресурсов совершенно неизбежны. Первые персональные компьютеры требовали немалой сообразительности от пользователя при конфигурировании подключаемых устройств, правильного, а подчас — единственно верного, выставления перемычек или О1Р переключателей, определяющих уникальную настройку ресурсов для данной системы. Инсталляция нового устройства требовала знания, какие ресурсы уже выделены существующим устройствам. Ошибки в таком ручном конфигурировании были частыми, а результатом были либо невозможность загрузить систему, либо непредсказуемые зависания системы и неработающие устройства. Для преодоления этих проблем были введены новые принципы построения шинных архитектур, которые бы поддерживали автоматическое распознавание и конфигурирования устройств. Автоматическое распознавание должно происходить в момент загрузки/перезагрузки системы или непосредственно в момент подключения устройства к компьютеру ("горячее" подключение). Возможности операционной системы, соответственно, должны быть таковы, чтобы соответствующее сопроводительное программное обеспечение вступало в работу без общей перезагрузки системы. Автоматическое конфигурирование позволяет программному обеспечению устанавливать приемлемые значения ресурсов для устройств, допускающих программную настройку. Эти усовершенствования позволяют отойти от использования перемычек на подключаемых устройствах, а конечному пользователю более нет нужды знать обо всех особенностях конфигурирования системы и нового устройства в ней.
Регистры устройств Драйверы взаимодействуют с подключаемыми устройствами путем чтения из регистров или записи в их внутренние регистры. Каждый внутренний регистр устройства обычно реализует одну из функций, перечисленных ниже: Регистр состояния. Обычно считывается драйвером, когда тому необходимо получить информацию о текущем состоянии устройства. Регистр команд. Биты этого регистра управляют устройством некоторым образом, например, начиная или прекращая передачу данных. Драйвер обычно производит запись в такие регистры. Регистры данных. Обычно такие регистры используются для передачи данных между устройством и драйвером. В выходные (ои1ри1) регистры, регистры вывода, драйвер производит запись, в то время как информация входных (1при1) регистров, регистров ввода, считывается драйвером. Доступ к регистрам устройства достигается в результате выполнения инструкций доступа к портам ввода/вывода (рог! ас1с1ге55) или обращения к определенным адресам в адресном пространстве оперативной памяти (тетогу-таррес1 ас1с1ге55), что и интерпретируются системой как доступ к аппаратным регистрам. Простые устройства (такие, как стандартный интерфейс параллельного порта, см. таблицу 5.1 — не путать с регистрами устройств, которые могут подключаться извне к параллельному порту!) имеют небольшое число ассоциированных регистров. В то же время, сложное аппаратное обеспечение (например, графические адаптеры) может иметь значительно больше регистров. Число и назначение регистров определяется разработчиками аппаратного обеспечения и должно быть полно и однозначно описано в документации. Однако зачастую такая однозначность так и остается недостижимой мечтой, а разработчику драйвера приходится определять реальное назначение нужных битов в устройствах используя случайно добытый тестовый программный пример неизвестного автора или метод собственных проб и собственных ошибок. Более того, часто выясняется, что биты, объявленные в документации как "зарезервированные" (гезеп/ес!), вовсе не являются тем безобидным предметом, о котором не следует и беспокоиться. Таблица 5.1. Регистры интерфейса стандартного параллельного порта (5РР) Смещение Доступ Регистр Описание 0 кеаи/МгИе □а!а (Юк) Байт данных, передаваемый через параллельный порт Кеас1 оп1у 51а1и8 (Бк) Текущее состояние порта Биты 0-1 Зарезервированы Бит 2 Р1К<2 0 — прерывание было запрошено портом (т.е. если сигнал АСК# вызвал прерывание) 1 Бит 3 ЕккОк# 0 — произошла ошибка Бит 4 БЕЬЕСТ 1 — принтер выбран (включен) Бит 5 ООТ_ОЕ_РАРЕк 1 — в принтере отсутствует бумага Бит 6 АСК# отображает состояния линии Аск# Бит 7 БОБУ# 0 — принтер занят (1 - разрешение на вывод очередного байта) кеас1/\/\/п1е Бит 0 Соп1го1 (Ск) БТкОВЕ# Команды, посылаемые в порт 1 — строб передачи данных в/из порта Бит 1 АОТО_ЕЕ 1 — автоматическая подача строки 2 Бит 2 11М1Т# 0 — инициализировать принтер Бит 3 5Е1_ЕСТ_1Ы# 1 — выбрать принтер Бит 4 ЕЫАВ1_Е_11\1Т 1 — разрешает прерывания по спаду сигнала на линии АСК# Биты 5-7 зарезервированы
Коварность отдельных устройств проявляется еще и в том (например, в случае с адаптерами ЕОА и \/ОА), что смысл ассоциированного регистра во второй операции доступа к нему определяется значением данных, отправленных в этот регистр во время первой операции доступа. Пусть у нас имеется X — указатель на регистр в "терминах" адресов памяти, и сначала мы сделали *Х=1 и после этого получили п=*Х — число конных матросов на лекции А.Блока. Но если мы сделали *Х=2, то можем получить п=*Х — температуру, при которой следует готовить сапоги всмятку, то есть совершенно другие по смыслу значения.
Доступ к регистрам устройств Когда функционирование устройства стало почти понятным, останется небольшая проблема: как программно получить доступ к регистрам устройства. Как правило, регистры следуют друг за другом в своем адресном пространстве. Следовательно, для начала, необходим адрес первого из них. К сожалению, значение термина 'адрес' сильно варьируется при использовании его относительно виртуальных адресных пространств на различных платформах. Вариант первый. Используем специфическую для данного процессора инструкцию ввода/вывода в порт, а значит, нужен адрес порта ввода/вывода. Вариант второй. Особые области в памяти совмещены с адресами доступа к устройству. Запись по адресу в памяти означает перенос данных в устройство. Самый распространенный прием для доступа к видеопамяти — доступ по специальным адресам с использованием стандартных обращений к памяти. Пример старой 005 программы (назовем ее АЬс), выполняющей вывод алфавита на экран в текстовом режиме, построен как раз на том, что видимая область первой текстовой страницы 005 экрана "совмещалась" с оперативной памятью и начиналась с адреса В800:0000 (что в переводе на современный... где-то... 0хВ8000). #1пс1ис1е <с1оз.И> 11111 та1п^о1с1) { ИпН 1; ипзНдпес! 11111 Наг *зсгееп; // <- адрес начала видимой 1-й стр // Собираем адрес из сегмента (0хВ800) и смещения: зсгееп = (ипзгдпес! ИпП *) МК_ГР(0хВ800, 0); Ног (1=0; 1<2б; 1++) зсгееп[1] = ОхОЕОО + ('а' + 1); // <- Вывод одного с геНигп 0; } В результате работы этого кода (его следует собрать ОО5 компилятором) в верхней строке экрана появится строка букв о 'а' до ’х'. (Здесь ОхОЕ — байт означающий, что символы будут белыми на черном фоне.) Если говорить в терминах ассемблера, то инструкции для доступа к пространству памяти — это МОУ (1оас1/81оге для других типов ассемблера), а для доступа к пространству ввода/вывода используются инструкции типа 1Ы или О11Т.
Пространство ввода/вывода В некоторых реализациях процессорных архитектур доступ к регистрам устройств осуществляется при помощи специальных команд процессора — инструкций ввода/ вывода. Они ссылаются на специальные наборы выводов процессора и определяют отдельное шинно-адресное пространство для устройств ввода/вывода. Адреса на этих шинах широко известны как порты (рог1в) и не имеют никакого отношения к адресации памяти. В архитектуре 1п1е1 х86 адресное пространство ввода/вывода имеет размер 64 КБ (16 разрядов), а в языке ассемблера определено две инструкции для чтения и записи в этом пространстве: '114' и 'О11Т' (точнее, две группы инструкций, внутри которых различие имеет место по разрядности считываемых/записываемых данных). Поскольку при создании драйвера следует избегать привязки к аппаратной платформе, М1сго5оГ1: рекомендует избегать и использования реальных инструкций 11Ч/О11Т. Вместо этого следует использовать макроопределения НА1_. Соответствие между традиционными инструкциям ОО5/\Л/1Пс1о\л/5 ассемблера и макроопределениями НА1_ приводится в таблице 5.2. Таблица 5.2. Макроопределения НА!_ для доступа к портам ввода/вывода Ассемблер х86 1Ы А1_,ОХ 1Ы А1_,рог1 Аналог НАЬ НЕАО_РОкТ_иСНАк Описание Чтение 1 байта из порта ввода/вывода 1Ы АХ,ОХ 1Ы АХ,рог! РЕАО_РОРТ_иЗНОкТ Чтение 16-ти разрядного слова из порта ввода/вывода 1Ы ЕАХ,ОХ 1Ы ЕАХ,рог1 1№В 1№\Л/ РЕАО_РОРТ_иЮЫС РЕАО_РОРТ_ВиЕЕЕР_иСНАР РЕАО_РОРТ_ВиЕЕЕР_и5НОРТ Чтение 32-х разрядного слова из порта ввода/вывода Чтение массива байт из порта ввода/вывода Чтение массива 16-ти разрядных слов из порта ввода/вывода 1№О ООТ ОХ,А1_ ООТ рог!,А1_ РЕ АО-РО РТ_Ви ЕЕЕ Р_и 1-01\1 с \Л/Р1ТЕ_РОРТ_ОСНАР Чтение массива 32-х разрядных слов из порта ввода/вывода Запись 1 байта в порт ввода/вывода оит ох,ах ООТ роП,АХ \Л/ Р1ТЕ_Р0 РТ_и 5 Н 0 РТ Запись 16-ти разрядного слова в порт ввода/вывода оит ОХ,ЕАХ оит роП,ЕАХ оитзв оитз\л/ оитво \Л/Р1ТЕ_Р0РТ_иЮЫС \Л/Р1ТЕ_Р0РТ_ВиЕЕЕР_иСНАР \Л/Р1ТЕ_Р0РТ_ВиЕЕЕР_и5Н0РТ \Л/ Р1ТЕ_Р0 РТ_Ви ЕЕЕ Р_01_01\1 С Запись 32-х разрядного слова в порт ввода/вывода Запись массива байт в порт ввода/вывода Запись массива 16-ти разрядных слов в порт ввода/вывода Запись массива 32-х разрядных слов в порт ввода/вывода
Доступ через адресацию в памяти Далеко не все создатели процессоров находили целесообразным организацию "портового" доступа к регистрам устройств через адресное пространство ввода/ вывода. В альтернативном подходе доступ к регистрам устройств осуществлялся путем обращения к определенным адресам в пространстве памяти (тегпогу ас1с1ге55 Брасе). В пример можно привести архитектуру РОР-11 Отбив, где вовсе не было портов ввода/ вывода и инструкций процессора для работы с ними: все регистры всех устройств имели свое место в общем пространстве адресации памяти и реагировали на обычную инструкцию доступа к памяти. В некоторых случаях (например, для видеоадаптеров в архитектуре 1пбе1 х86) допускаются оба способа доступа сразу: и через пространство ввода/вывода, и через адресное пространство Таблица 5.3. Макроопределения НА1_ для доступа к регистрам устройств через адресацию в памяти. Поля КЕАО_кЕС15ТЕк_ХХХ \Л/к1ТЕ_кЕС15ТЕк_ХХХ кЕАО_кЕС15ТЕк_ВиЕЕЕк_ХХХ \Л/к1ТЕ_кЕС15ТЕк_ВиЕЕЕк_ХХХ Описание Чтение одного значения из регистра ввода/вывода Запись одного значения в регистр ввода/вывода Чтение массива значений из последовательного набора регистров ввода/вывода Запись массива значений в набор из следующих друг за другом регистров ввода/ вывода Как и в предыдущем случае, определены макросы НА1_ для доступа к таким тетогу таррес! (то есть с доступом посредством адресации в памяти) регистрам, что описывается в таблице 5.3 (XXX принимает значения 11СНАК, 115НОКТ или 1Л_О1\1С). Так как эти макроопределения по содержанию отличаются от НА1_ макроопределений для операций с портами ввода/вывода, драйверный код должен быть разработан так, чтобы компиляция для разных платформ (с разными методами доступа к регистрам) проходила корректно. Хорошим приемом является составление таких макроопределений условной компиляции, которые указывали бы на один из нужных НА1_ макросов в зависимости от ключей компиляции.
ГРгеу1ои8~| ГИехМ
Как правило, устройства функционируют параллельно и асинхронно относительно действий центрального процессора. Поэтому, для ситуаций, когда им требуется внимание со стороны драйвера, код которого выполняется на центральном процессоре, была выработана тактика использования прерываний, когда устройства сигнализируют о необходимости обслуживания, то есть генерируют прерывания. Разные процессоры реализуют разные способы, как "привлечь" их, процессорное, внимание. Но имеется и общее место в эти способах: всегда задействован один (или несколько) из выводов процессора (или вспомогательной микросхемы). За этот вывод устройство может "подергать", когда потребуется участие процессора. В обязанности процессора, если он намерен перейти к обслуживанию по поступившему сигналу, входит сохранение состояния процессора, контекста выполняемого программного потока. Лишь после этого процессор(будет правильнее сказать — операционная система) может выполнить переход к программному коду Процедуры Обслуживания Прерывания (ТпСеггирС Зегу/се КоиИпе, которую регистрирует драйвер, взявший на себя работу по представлению данного устройства в операционной системе. Обычно устройства генерируют сигналы прерываний в ситуациях: Устройство завершило обработку предыдущего запроса (поступившего от драйвера) и теперь готово к обработке нового.
Буфер или очередь Е1ЕО устройства почти полны (во время ввода) или почти пусты (во время вывода). Такое прерывание разрешает драйверу получение из буфера устройства или вывод в буфер устройства данных для обеспечения его непрерывной работы. Устройство столкнулось с нештатной или ошибочной ситуацией во время выполнения операции, и прерывание может быть специально для того предназначенной формой завершения операции. Устройства, которые не способны генерировать прерывания, могут создать ситуацию серьезной деградации операционной системы. В связи с тем, что огромное количество выполняющихся потоков использует центральный процессор совместно, недопустимо позволять какому-либо драйверу такую роскошь, как ожидание полного завершения "его" текущей операции. При работе с подобными устройствами, не умеющими "разговаривать прерываниями", можно применять тактику опроса через определенные промежутки времени. В англоязычной литературе такой метод называется 'роШпд1. С усложнением аппаратного обеспечения возникли конфигурации, где шины стали подключаться к другим шинам через интерфейсные элементы, называемые мостами. В результате, источниками прерываний (а практически — устройствами) оказываются несколько аппаратных слоев, образовавшихся вокруг процессора, и методы определения приоритетов и передачи сигналов процессору претерпели изменения. Тем не менее, осталось и нечто общее.
Достаточно часто возникают ситуации, когда устройства требуют внимания к себе практически одновременно. Возникает вопрос: как определить очередность их обслуживания? По-видимому, самое важное устройство или устройство, которое меньше остальных может ожидать обслуживания, должно иметь максимальный приоритет. Если устройство может быть обслужено с задержкой, то ему присваивается самый низкий приоритет прерывания. В отдельных случаях можно назначить приоритет устройства при установке программного обеспечения для него. Для чего приоритет прерывания нужен? В момент, когда процессор занят выполнением программного кода в ответ на прерывание от низкоприоритетного устройства (например, выполняет его 15к-процедуру обработки прерываний), вполне может поступить сигнал прерывания от устройства с высоким приоритетом. В данном случае, центральный процессор получит два прерывания — второе над первым — и перейдет к обработке того, которое имеет больший приоритет, сохраняя, разумеется, контекст отложенного "в сторону" потока. И наоборот, обработка прерывания низкого уровня, поступившего при обслуживании высокоприоритетного, задерживается до момента, пока высокоприоритетное не будет обработано полностью и объявлено завершенным. Важно отметить, что в режиме ядра программный поток какого-либо приоритета не может быть прерван ради выполнения кода потока даже равного с ним приоритета — в отличие от пользовательского режима, когда даже самые низкоприоритетные потоки когда-нибудь
получают возможность поработать. Этим, в частности и объясняются многочисленные и "назойливые" рекомендации в литературе по программированию драйверов не задерживаться в коде процедур обработки прерываний (с высоким приоритетом), поскольку игнорирование этого правила может приводить к быстрой и необратимой деградации системы. Это требование становится особенно очевидным, если воспользоваться системным программным обеспечением, дающим информацию о количестве прерываний в секунду — оно меняется от полусотни до тысячи. Разумеется, если разработчик драйверов не будет придерживаться правила "меньше работай на уровнях Э1Р(21_", то система не сможет нормально работать, а драйвер никогда не получит цифровую подпись МЮГОЗОЛ.
Векторы прерывания Некоторые устройства и некоторые процессорные архитектуры допускают механизм автоматической диспетчеризации — переходов "по вектору", то есть адресу программных процедур, при поступлении сигналов о прерываниях. При другом методе векторизация не применяется, и для всех типов прерываний обслуживание предоставляется обобщенной процедурой. Такая процедура просматривает в порядке приоритетности весь список устройств, которые могли бы вызвать прерывание, и определяет, какое устройство требует обслуживания на самом деле. Очевидно, что в современных компьютерных системах, где генерируется от нескольких десятков до нескольких тысяч прерываний в секунду, существенно более эффективным оказывается векторный подход.
Передача сигналов прерываний При генерации прерываний используются две основные стратегии. Более старый и менее предпочтительный механизм носит название ес1де-{нддегес1 (управляемые перепадом, управляемые фронтом) или 1а1сЬес1 прерывания. Устройства, которые генерируют такие прерывания, сигнализируют о потребности в обслуживании осуществлением перехода на физически существующем проводе (например, на одном из проводников шины или соединительного кабеля) из одного логического состояния в другое, скажем, из 1 в 0. Как только этот переходной процесс прошел, устройство может отпустить линию (отпустить прерывание), восстановив параметры электрического сигнала, в данном случае, обозначающие логическую 1. Другими словами, линия прерывания такого устройства "пульсирует" под его руководством, а процессор должен обратить внимание на проходящие импульсы. Этот тип прерываний является потенциальным источником ошибочных срабатываний, поскольку шум на линии может выглядеть как сигнал прерывания. Кроме того, более сложная проблема возникает, когда два устройства с таким типом прерываний используют одну линию. В случае если два устройства "просигнализируют" одновременно, процессор различит лишь одно прерывание, и тот факт, что прерывания сгенерировали два устройства, будет потерян безвозвратно. Классическим примером оборудования, где случаются потери такого типа прерываний, являются последовательные СОМ порты. По традиции, СОМ1 и СОМЗ используют одно определяемое по фронту
прерывание, а именно 1ЕЩ4. В результате, невозможно одновременно подключить к этим двум портам оборудование, которое использует управляемое прерываниями программное обеспечение. Попытки включить мышь в СОМ1 и модем в СОМЗ рано или поздно, но — неминуемо приводят к остановке одного из драйверов, который ожидает "пропавший" сигнал о прерывании. Эти ограничения преодолеваются реализацией второго механизма, известного как 1еуе1-8еп5Ппге, чувствительный к уровню, или 1еуе1-{пддегес1г управляемый уровнем. Устройства удерживают общую аппаратную линию (совместно используемый проводник) в соответствующем состоянии до тех пор, пока потребность каждого не будет удовлетворена. Теперь для процессора нет проблем в том, чтобы зарегистрировать оба прерывания, так как линия удерживается в этом состоянии до момента завершения обслуживания и первого, и второго устройства — это позволяют делать специальные типы цифровых схем. В случае если два прерывания происходят одновременно, обслуживается устройство с более высоким приоритетом, а после и другое устройство, поскольку оно продолжает сигнализировать, удерживая линию в соответствующем состоянии и после окончания обработки запроса первого, высокоприоритетного, устройства.
Сродство к процессору В тех случаях, когда аппаратное обеспечение системы содержит более чем один процессор, возрастает значение вопроса: как прерывания будут обрабатываться? Как подключена линия прерывания устройства — к одному процессору или ко всем? Как правило, существуют некоторые компоненты аппаратного обеспечения, которые позволяют драйверу конфигурировать и распределять сигнал прерывания. Если отдельный процессор обслуживает прерывания, поступающие только от определенного устройства, то про такие прерывания говорят, что они имеют "сродство к процессору" (ргосеззог а Нт Ну). Назначение конкретного процессора для обработки конкретного прерывания может быть использовано для контроля сбалансированности загрузки отдельного устройства, если в системе присутствуют несколько процессоров и несколько управляемых устройств.
Механизмы передачи данных При всем многообразии компьютерной техники, существует три основных механизма, используя которые устройство может обмениваться с центральным процессором или, в широком смысле, с компьютером: Программируемый ввод/вывод. Прямой доступ к памяти (О|гес1: Метогу Ассезз, ОМА). Совместно используемые области памяти. При выборе разработчиком аппаратуры механизма передачи данных, используемого для связи с устройством, следует исходить из скорости, с которой требуется передавать данные, и средним размером передаваемого непрерывного блока данных.
Программируемый ввод/вывод Устройства, использующие программируемый ввод/ вывод (Ргодгаттес! 1/0, РЮ), осуществляют передачу данных непосредственно через регистры устройства. Драйвер должен использовать инструкцию ввода/вывода (1/0 тз^гисйоп) для чтения из регистра или записи в регистр устройства каждого байта данных. При больших объемах драйвер должен поддерживать адресацию буферной области и счетчик переданных данных. Поскольку реальная скорость передачи данных таких устройств много меньше, чем возможности процессора по чтению или записи в регистры данных, то устройства с таким подходом обычно генерирует прерывание для каждого байта (слова) передаваемых данных. Последовательный СОМ порт является одним из примеров РЮ устройств. Более совершенные устройства используют накопительные Е1Б0, что позволяет им в одно прерывание передавать 4 или 16 байт данных. И все же количество прерываний относительно переданного объема данных весьма велико, отчего этот метод пригоден лишь для медленных устройств.
Прямой доступ к памяти Прямой доступ к памяти (О1гес1: Метогу Ассезз, ОМА) использует выгоды от вовлечения в работу вторичного процессора, называемого контроллером ОМА (ОМА соп1то11ег, ОМАС, контроллер ПДП). Этот контроллер является вспомогательным процессором с ограниченным набором возможностей и обязанностей, но при этом с достаточным интеллектом и статусом, чтобы выполнять передачу данных между устройством и оперативной памятью. Контроллер ОМА работает параллельно с основным процессором (процессорами), и обычно его деятельность почти незаметна в общем функционировании системы. При инициации операции ввода/вывода с привлечением ОМА метода драйвер должен выполнить установку нужного состояния ОМА контроллера (запрограммировать его), определив адрес начала буферной области и количество передаваемых данных. По поступлению от драйвера указания начать работу ОМА контроллер приступает к выполнению передачи данных между устройством и системной оперативной памятью. Когда ОМА контроллер завершает передачу данных полностью, генерируется прерывание. Таким образом, драйверный код выполняется только в начале операции передачи данных и затем — лишь по ее завершении, освобождая центральный процессор для выполнения других задач. Следует обратить особое внимание на то, что в \ЛЛпс1о\л/5 1\1Т при ОМА операциях всегда используются логические адреса (трюк с памятью, схожий с виртуальной адресацией, но предназначенный специально для ОМА операций), позволяющие "обмануть" ОМА контроллер,
для которого логические адреса кажутся непрерывными, хотя и могут представлять физически разрывные области памяти. Высокоскоростные устройства, которые вынуждены регулярно заниматься передачей крупных блоков данных, являются первыми кандидатами на использования метода ОМА. По сравнению с операциями программируемого ввода/вывода, интенсивность использования сигналов прерываний и активность драйвера существенно уменьшаются. Жесткие диски, устройства мультимедиа, сетевые карты являются примерами устройств, использующих ОМА. Необходимо отметить, что реальная ОМА передача данных не является очевидно и безусловно выгодной. Вторичный процессор, каковым является ОМАС, конкурирует с центральным процессором в использовании пропускной способности системной оперативной памяти и шин. В случае если центральный процессор обращается к системной памяти часто, то кто- то из них — либо центральный процессор, либо ОМАС — вынуждены ждать, пока предыдущий цикл обращения к памяти не закончится. Можно лишь надеяться, что при больших размерах кэш-памяти современных процессоров, у последнего нечасто возникает крайняя необходимость использовании максимальной пропускной способности памяти. Но все же, система с большим количеством устройств, реализующих Ьиз таз^ег ОМА (одна из разновидностей операций прямого доступа к памяти), может обнаружить, что возможности одновременного доступа к памяти исчерпаны. Ниже рассмотрены два основных типа ОМА операций. ОМА операции с использованием системных контроллеров
Первоначальная спецификация персонального компьютера по 1ВМ (и последовавшие стандарты) включали основную плату (называемую ныне "материнской") с набором общих ОМА котроллеров. Каждый ОМА контроллер предоставляет ОМА каналы (□МА сИаппе1), и данное устройство может быть сконфигурировано так, чтобы использовать один (или более) доступных каналов. Изначально существовало четыре канала, которые были расширены до семи при введении спецификации АТ. Системный ОМА (зуз^ет □МА) известен еще как з1ауе ОМА. Преимущество использования системного ОМА состоит в том, что количество аппаратной логики для реализации □МА в устройстве уменьшается. К минусам следует отнести то, что в случае совместного использования канала данным устройством, оно получает возможность участия в ОМА передаче данных только один раз за определённый временной интервал. В каждый конкретный момент времени ОМА канал находится "в собственности" только одного устройства, остальные попытки использования этого канала со стороны других устройств откладываются до момента, когда первое устройство "откажется" от собственности на канал. Такое совместное использование канала дает плохие результаты при использовании его для двух высокоскоростных устройств. Контроллер флоппи дисководов в большинстве персональных компьютеров как раз является устройством, реализующим операции з1ауе ОМА. Операции Ьиз та$1ег ОМА Более сложные устройства, которые не желают быть зависимыми от системных ОМА контроллеров, содержат собственные аппаратные средства обеспечения ОМА. Так
как эти средства принадлежат собственно устройству, то передача происходит по "воле" устройства — если, разумеется, протокол шины поддерживает такие действия. Для выполнения своей операции устройству необходимо получить "контроль над шиной" в результате процедуры, которая описывается в протоколах соответствующих шин.
Память, отведенная устройству Третий из механизмов передачи данных состоит в том, что для доступа к устройству используются диапазоны адресов памяти, открытые для совместного использования, см. рисунок 5.1. Устройство Основная память Рис. 5.1 Доступ к памяти, имеющейся в устройстве Окно доступа к устройству Память устройства < Процессор В некоторых случаях эти диапазоны определены жестко, как в случае с диапазоном адресов ОхОАОООО-ОхОАЕЕРР буфера адаптера УСА. Память, отведенная устройству, является ресурсом, предоставляемым драйверу устройства операционной системой, и обычно с ним можно ознакомиться в апплете свойств драйвера в настройках системы (Пуск - Настройка - Панель Управления - Система - Устройства - Диспетчер Устройств - устройство - ресурсы).
Ресурсы, используемые устройством Правильно спроектированное устройство должно идентифицировать (проявить) себя и предоставить системе перечень ресурсов, которые оно потребляет. Это перечень, формулируемый в некоторых позициях собственно устройством, а в некоторых — его драйвером, должен включать: Идентификатор производителя (МапиГас1:игег Ю). Идентификатор типа устройства (Оеу!се 1:уре Ю). Требования к пространству ввода вывода (1/0 зрасе гери1гетеп1:5). Требования по использованию прерываний. Требования по использованию каналов ОМА. Требования относительно памяти, отведенной устройству. В случае РпР устройств, идентификаторы производителя и типа устройства являются критерием выбора драйвера при загрузке системы или же при подключении устройства (если оно было подключено после загрузки). Для обеспечения автоматической конфигурируемости, устройство должно разрешать авторизованному программному обеспечению динамически устанавливать и изменять установки порта ввода/вывода, прерывания и канала ЭМА, которые будут использованы при работе с данным устройством. Это позволит операционной системе разрешить конфликты ресурсов среди конкурирующих устройств.
ГРгеу1ои8~| ГИехМ
Шины в компьютерных системах Шина (Дш$) представляет из себя совокупность данных, адресов и линий (проводников на печатной плате или в кабеле или шлейфе) сигналов контроля, которые позволяют устройствам организовать сообщение между собой. Некоторые шины являются "широкими", обеспечивая одновременную (параллельную) передачу многих битов данных и битов контроля. Другие представляют собой всего лишь пару проводов, позволяющую устройствам передавать данные и управляющие сигналы последовательно. Некоторые шины позволяют связываться с любым другим устройством на шине. Другие требуют наличия "хозяина шины", таз^ег соп1то11ег (центральный процессор или контроллер ввода/вывода), выступающего в роли получателя или отправителя данных. От того, какими свойствами обладает главная шина компьютера зависит производительность всей системы (недостаточно корректно сказано, но все-таки можно ее определить, как шину, к которой подключается большинство системных устройств и которая ближе всего "подходит" к процессору). В середине 80-х, например, рабочая станция АроПо на базе процессора 68020 была ориентирована на 15А шину как на стандарт. В настоящее время персональные компьютеры с 1п1:е1- архитектурой эксплуатируют шину РС1, хотя и претерпевшую модификации, но очевидно главную — стоит лишь нарисовать блок-схему системы в целом. (Вообще говоря, РС1 шина применима на многих платформах, что и было изначально заложено в стандарт.)
Архитектура драйверов в операционной системе \Л/1Пс1о\л/5 1\1Т 5 эффективно поддерживает введение новых шин, а поддержка многих популярных шин встроена в стандартную поставку У\Лпс1о775.
Шина 15А была предложена фирмой 1ВМ для РС/АТ в начале 1980-х. Она поддерживала подключение и 8- разрядных, и 16-разрядных устройств. По той причине, что эта шина изобреталась два десятилетия назад, она не отличалась ни быстротой, ни простотой конструкции. Эта шина является динозавром компьютерных технологий, однако ее невозможно забыть по двум причинам. Во-первых, столь популярные и до сих пор обязательные устройства настольного компьютера, какими являются параллельный порт 1_РТ и последовательные СОМ порты, являются устройствами 15А шины. Эта 15А шина через мост подключается к шине РС1, и существует в компьютере даже тогда, когда на материнской плате нет ни одного 15А разъема. (Для фанатиков же 15А устройств некоторые фирмы, например РН11_1Р5, выпускает микросхемы для реализации подключения 15А плат к шине 115В.) Во-вторых, при усовершенствовании компьютеров, которые 15А шина поначалу вполне устраивала, выявилось столько неудобств и ограничений, что стало очевидным — к принятию шинных протоколов следует подходить более ответственно. Невысокая пропускная способность (максимум 8 МБ/сек), отсутствие стандарта на использование адресов ввода/вывода, невозможность совместного использования прерываний (их всего 16, большая часть которых уже занята системными устройствами, оставляя вновь подключаемым устройствам всего лишь 2 или 3) настойчиво указывали на необходимость новых решений. Например, старые 15А платы не могут использовать все 16 разрядов адресов
ввода/вывода, а декодируют только первые 10 разрядов адреса, в результате чего отзываются по адресам через 0x400 адресов. Устройство с адресом 0x300 отвечает также и по адресу 0x700. Появление таких устройств в системе приводит к откровенной бесполезности большей части 64 Кбайтного адресного пространства ввода/ вывода. И, наконец, шина 15А имеет возможность всего лишь 24 разрядной адресации, что определяет ограничение на выполнение переноса данных только в нижние 16 МБ физической памяти. Этот артефакт доставляет \Л/1Пс1о\л/5 особые проблемы при работе с 15А шиной, если возникает необходимость применить ОМА способ передачи данных. Как уже было сказано, устройства 15А шины не объявляли себя, они не предоставляли списка требуемых ресурсов, и они не нуждались в динамическом конфигурировании со стороны программного обеспечения. Конфигурирование этих устройств выполнялось вручную и обычно сводилось к требуемой установке перемычек 0итрегз) или переключателей Э1Р. Современные 15А устройства пытаются скорректировать эту ситуацию, предлагая Р1ид апс1 Р1ау расширение к стандарту 15А. Такие устройства значительно расширили свою популярность с введением операционной системы \Л/1Пс1о\л/5 95. Версии 1\1Т, существовавшие до \ЛЛпс1о\л/5 2000, реально не поддерживали спецификацию РпР, отчего этим новым 15А устройствам приходилось полностью полагаться на специальные программы инсталляции, которые обеспечивали их должное функционирование под 1\1Т, и услуги Системного Реестра. Однако с выходом \ЛЛпс1о\л/5 2000 поддержка таких устройств стала вполне корректной.
Е15А: Ех1епс1ес1 1пс1и51гу 51апс1агс1 АгсН!1ес1иге Спецификация шины Е15А является расширением архитектуры 15А. Расширение Е15А было попыткой устранить ограничения 15А и при этом избежать проблем несовместимости с существующими устройствами 15А (платами 15А), отказаться совсем от которых в тот момент было невозможно. Разумеется, требование на обеспечение совместимости ограничивало модернизацию шины по многим направлениям. Например, в то время, как разрядность передаваемых данных (в литературе — с1а!:а Ьиз \л^1с11:1п) была увеличены до 32 бит, тактовая частота осталась равной 8 МГц. Соответственно, максимальная скорость передачи данных по шине стала составлять около 32 Мбайт в секунду. Кроме того, поскольку разъемы Е15А на материнской плате должны были принимать еще и платы собственно 15А, то появились существенные сложности в устранении электрических шумов, обусловленных топологией разводки соединений 15А. Компьютерные пользователи постарше могут припомнить, что разъем Е15А на материнской плате состоял из 15А разъема и присоединенного к нему в продолжение разъема покороче и поизящнее, что вместе и составляло Е15А- слот. Устройства Е15А уже вполне обеспечивали возможности автоматической идентификации, хотя процедура эта была не слишком проста. Во всяком случае, спецификация Е15А сдала свои позиции протоколу РС1 достаточно легко, стоило лишь тому появиться. (А вот
прощание с 15А проходило гораздо тяжелее, можно, даже сказать — еще продолжается и сейчас!)
РС1: РепрЬега! Сотропеп1 1п1егсоппес1 Быстрые сетевые приложения, высококачественное видео, 16 и 20 разрядный звук и быстро развивающиеся музыкальные приложения, дисплеи с глубиной цвета 24 бит — все эти новшества потребовали введения нового стандарта шины с высокими скоростями передачи данных. Шина РС1 явилась попыткой удовлетворить все эти возросшие потребности. И хотя первоначальная конструкция зародилась в 1п1е1 (первая спецификация версии 1.0 появилась в июне 1992), шине РС1 оказана поддержка со стороны других компьютерных производителей. Она является относительно независимой от процессорных конфигураций и успешно используется теперь в компьютерах А1рИа(ОЕС) и РомегРС (Мо1ого1а). На рисунке 5.2 показана типовая конфигурация системы, содержащей РС1 шину. Рис. 5.2 Типовая конфигурация системы с шиной РС1
Повысив тактовую частоту до 33 МГц (позже и до 66 МГц) и применив множество технических уловок, разработчики РС1 добились того, что пропускная способность РС1 шины возросла до 132 Мбайт/сек при передаче данных по 32-разрядной шине. При передаче данных по 64-разрядной шине пропускная способность увеличивается вдвое. Некоторые из принципов, которые позволили обеспечить столь значительный прогресс, таковы: Протокол РС1 подразумевает, что каждая передача данных происходит как пакетно-монопольная операция (Ьигз! орегайоп), в результате чего для быстрых устройств, предающих большие объемы данных, действительно достигаются физически предельные показатели РС1. Протокол РС1 поддерживает режим многих "хозяев шины" (Ьиз таз1ег) и разрешает прямую передачу
данных устройство-устройство (без промежуточного использования памяти), что может быть использовано для повышения степени параллельности между операциями ввода/вывода и работой процессора. Центральный шинный арбитр (сеп1та1 Ьиз агЫ1:ег) совмещает операции арбитрирования и собственно перенос данных, чем уменьшается время задержек. Это позволяет следующей операции стартовать сразу же, как только текущая операция заканчивается. Мост РС1, наделенный существенными способностями, между главным процессором и собственно шиной представляет разнообразные возможности кэширования и выполнения операций упреждающего чтения данных (геас1аИеас1 Гипсйопз). Все это позволяет уменьшить время, которое процессор тратит на ожидание данных. Мост РС1 является устройством шины, позволяющим подключать вторичные шины РС1. В таком случае мост называется 'РС1-1Ю-РС1 (эгИде'. (В частности, ранее это было решением проблемы увеличения числа слотов РС1, если их требовалось более 4.) Архитектура РС1 позволяет подключить к шине до 32 физических единиц, называемых устройствами ((Зеуюез). Каждая из этих единиц может содержать до 8 отдельных функциональных единиц, называемых функциями (Гипсйопз). После отбрасывания одного адреса функции для генерации широковещательных сообщений (Ьгоас1са51: теззадез — сообщений, предназначенных всем устройствам), остается 255 адресуемых единиц- функций на одной шине РС1. (Учитывая, что РС1-РС1 мост является обычным устройством шины, возникает возможность подключения к основной шине до 255 дополнительных шин РС1).
Доступ к регистрам Шина РС1 позволяет использовать 32 разрядные адреса, но системное пространство ввода/вывода все еще ограничено адресами до 64К (16 разрядов). Соответственно, там должны пребывать и все регистры РС1. Более того, в системах, содержащих шины Е15А или 15А, разработчикам следует воздерживаться и от использования адресов, которые могли бы использовать устаревшие устройства, подключаемые к этим шинам, по причинам, указанным выше. Наряду с адресацией пространства ввода вывода и адресацией памяти, архитектура РС1 определяет диапазон адресов во внутренней памяти устройства, называемых конфигурационным пространством (сопНдигаНоп врасе). При обсуждении автоматического конфигурирования РС1 будет объяснено, какую структуру это конфигурационное пространство имеет и как используется в процессе идентификации устройств (плат) на шине РС1. Механизмы прерываний Шина РС1 использует четыре равно-приоритетных линии запросов на прерывание 11МТА-11МТО. Эти линии являются активными по низкому уровню (сигналом прерывания является низкий уровень в линии), переключаемые по уровню (информативным значением является логический уровень — в отличие от того случая, когда информативным является фронт сигнала, то есть переход от одного уровня к другому). Данные линии допускают возможность совместного использования (это, возможно, станет технически более понятным, если сказать, что такая линия прерываний реализуется по электронной схеме "орел с1га1п", М-МОП аналог "открытого
коллектора" для ТТЛ логики). Устройство, подключаемое к РС1 и представляющее одну функцию, должно использовать только линию 11МТА. Многофункциональные устройства могут использовать комбинации из четырех линий, начиная с 11МТА. Единственное ограничение состоит в том, что каждая из восьми функций (возможных в одном устройстве) может использовать только одну линию прерывания. Соответственно, устройство с внутренними восемью функциями может задействовать имеющиеся линии 11МТА, 11МТВ, 11МТС, 11МТО следующим образом: Все восемь функций подключены к 11МТА. Семь подключены к 11МТА, одна к 11МТВ. Две подключены к 11МТА, две к ИМТВ, две к ИМТС и две к 11МТО. Четыре подключены к ИМТА, четыре к ИМТВ. И т.п. Спецификация РС1 относительно безразлична к приоритетам прерываний. Приоритеты, в данном случае, зависят от внешнего контроллера, который переадресует запрос на прерывание РС1 устройства в соответствующую линию системных прерываний. Рекомендуемые схемы представления прерываний РС1 устройств с использованием программируемых редиректоров (гесНгесйэг или го1Лег) прерываний разной сложности можно найти в книге Тома Шанли и Дона Андерсона, РС1 5уз1:ет АгсЬНесвиге, 4 издание, стр. 227- 230, содержащей изложение спецификации РС1 с полезными комментариями. Например, на персональных компьютерах редиректор может преобразовать запрос функциональной единицы РС1 по линиям 11МТА-11МТО в запрос по одной из линий
1к<2О-И«215, схема 14-4 в указанном издании. Чтобы можно было сделать это, любая функция внутри РС1 устройства, которая генерирует прерывание, должна задействовать два конфигурационных регистра (в своем конфигурационном пространстве): Регистр вывода прерывания (1п{еггир{ рт гед151ег, обозначенный 1п1 Р1Ы в таблице 5.4). Является регистром "только для чтения", который идентифицирует линию прерывания РС1 (1МТА-11МТО), используемую данной функцией данного РС1 устройства. Регистр линии прерывания (1п1еггир1 Нпе гед/з^ег, обозначенный 1п1 Ыпе в таблице 5.4). Является регистром "только для записи", который указывает приоритет и вектор (номер системного прерывания), которые редиректор прерываний должен присвоить данной функции. В персональных компьютерах 1п1е1 х86 значения ОхОО-ОхОЕ соответствуют 1^0-1^15. Такая схема является достаточно гибкой, поскольку не навязывает никаких ограничений, обусловленных спецификой механизма прерываний в системе, конструктору устройств или системы, что делает возможным использование этой архитектуры в процессорных средах, отличных от 1п1е1 х86. Возможности ОМА Спецификация РС1 не включает понятия з1ауе ОМА. Вместо этого, собственные функции РС1 (функциональные единицы) являются либо "хозяевами шины" (Ьиз таз^егз), выполняющими свой собственный □МА перенос данных, либо они используют программируемый ввод/вывод. Единственным типом устройств, которые могут использовать ОМА перенос
данных с использованием системных контроллеров (з1ауе □МА), являются не-РС1 платы, подключенные через (Е)15А мост. В ОМА операциях собственно РС1 шины, участники называются агентами (адеп^з), и в каждой транзакции всегда участвуют два из них, а именно: Инициатор (1пШа(ог). Это "хозяин шины" (Ьиз таз1:ег), который выиграл право доступа к шине и хочет определить операцию переноса. Цель (Тагде1). Это РС1 функция (функциональная единица РС1 устройства), адресуемая в настоящий момент инициатором с целью выполнения передачи данных. Поскольку любой "хозяин шины" РС1 может быть инициатором, то возможно передача данных напрямую между двумя РС1 устройствами без промежуточной остановки данных в оперативной памяти. Эта мощная возможность просто предназначена для использования в высокоскоростных сетевых и видео приложениях. Необходимо также упомянуть, что спецификация РС1 не определяет стратегию, которая должна быть использована при арбитрировании доступа к шине. Определены только временные диаграммы арбитрирующих сигналов в шине. Метод по нахождению того, "кто будет следующим" в использовании шины, является определяемым конкретной системой, где реализованы шины РС1. Память, отведенная устройствам Отведенная память, используемая РС1 функциями (функциональными единицами РС1 устройств), может
быть размещена в любом месте 32 разрядного адресного пространства. Эта возможность может быть использована при работе в базисе "функция устройства" — "функция устройства". Интересная возможность спецификации РС1 состоит в том, что в конфигурационном пространстве предусмотрено место для указания ссылки на область расширения ПЗУ (Ех^епзюп К.ОМ Вазе Аббгезз, см. таблицу 5.4), встроенного в данное РС1 устройство. В этом ПЗУ могут быть записаны фрагменты кодов инициализации, предназначенные для инициализации устройства на разных аппаратных платформах, что дает возможность поставлять один и тот же продукт для разных компьютерных платформ. Спецификация РС1 определяет стандартный заголовочный формат для этих блоков ПЗУ, поэтому программное обеспечение, выполняющее инициализацию, может определить необходимый фрагмент, хранящийся в ПЗУ и загрузить именно его для выполнения инициализации на имеющейся платформе. Автоматическое распознавание и конфигурирование Спецификация РС1 обязывает каждую отдельную функцию (функциональную единицу устройства, а значит, и шины) иметь свою собственную область для хранения конфигурационных данных, размер которой равен 256 байт. Эта область известна как конфигурационное пространство функциональных единиц РС1 (РС1 ГипсНоп'з сопНдигаНоп зрасе). Первые 64 байта такого конфигурационного пространства (называемого заголовком) имеют предопределенную структуру (таблица 2.4), в то время
как остальные 192 байта могут быть использованы по усмотрению разработчика данной РС1 платы. Системное программное обеспечение может использовать этот заголовок для идентификации функциональной единицы РС1 и выделения ей ресурсов. Заголовок включает: Информацию о поставщике (\/епс1ог Ю), тип устройства (Оеу|се Ю) и версию устройства (Р.еу|зюп Ю). Два стандартных регистра состояния команд для того, чтобы имелась возможность работы с ошибками в устройстве (51а1из Р.ед!з1ег и Соттапс! Р.ед151ег). Список ресурсов, в котором описываются требования к памяти и пространству ввода/вывода. Регистр вывода прерывания (1п1 Р1п) и регистр линии прерывания (1п1 Ыпе), описанные выше. Указатель на расширение ПЗУ (Ехрапзюп Р.ОМ Вазе Ас1с1ге55), специфичное для данного устройства. Регистр С1азз Сос1е внутренне разбит на три поля (С1азз Сос1е, старший байт, 5иЬ-С1азз Сос1е и Ргод1Р, младший байт) и содержит информацию для использования классовыми системными драйверами (предназначенными для обслуживания целого класса родственных устройств, например, сетевых контроллеров, устройств мультимедиа, с1оск1пд-станций, шинных мостов, кодирующих/декодирующих контроллеров, интеллектуальных контроллеров ввода/вывода). Например, значение С1азз Сос1е, равное ОС (Иех), в спецификации РС1 2.2 определяет контроллеры последовательных шин. Тогда значение 5иЬ-С1азз Сос1е, равное 00, определяет 1ЕЕЕ 1394, 01 — АССЕЗЗ.Ьиз, 02 — 55А (5епа1 51огаде АгсЫ1ес1иге), 03 — 05В, 04 — ПЬге СИаппе!, 05 — ЗМВиз (5уз1ет Мападетеп! Виз). Для подкласса Ь5В значение РгодТЕ, равное 00, описывает Ь5В контроллер, соответствующий спецификации
ип|уег5а1 Иоз! Соп1го11ег. Значению 10 (Иех) соответствует контроллер спецификации Орел Ноз! Соп1го11ег, а значение ЕЕ (Иех) описывает 05В устройство (не являющееся хост-контроллером). Байты конфигурационного пространства Таблица 5.4. 3 2 10 Заголовок конфигурационного 00 пространства одной Оеу1се Ю \/епс1ог Ю 51а1из кед1з1:ег Соттапс! кед1з1:ег 01 функции устройства РС1 С1азз Сос1е кеу1зюп Ю 02 В15Т Неас1егТуре 1_а1 Т1тег С1_ Еяге 03 Вазе Ас1с1ге55 0 04 Вазе АсМгезз 1 05 Вазе АсМгезз 2 06 Вазе АсМгезз 3 07 Вазе АсМгезз 4 08 Вазе АсМгезз 6 09 Сагс1 Виз С15 Ро1п1ег ОА 5иЬзуз1:ет Ю 5иЬзуз1:ет \/епс1ог Ю ОВ Ехрапзюп кОМ Вазе АсМгезз ОС Зарезервировано Сар.Ро1п1ег 00 Зарезервировано ОЕ Мах_1_а1 Мт_6п1 Рт Ыпе 0? Регистр В15Т (Ви|11-1п-5е1Г-Тез1) отражает способность устройства к проведению операции самотестирования. Единица в старшем (седьмом) бите этого регистра означает, что данная функция устройства может выполнять операцию самотестирования. Шестой бит, установленный в 1, означает проведение тестирования, причем на эту операцию функции устройства дается не более 2-х секунд. В младших четырех битах содержится код ошибки либо 0, при удачном завершении. Регистры Вазе Ас1с1ге55 кед!з1ег 5..О содержат адреса портов ввода/вывода (или диапазонов отведенной памяти) для доступа к внутренней памяти данной РС1 функции.
Отображать все 256 адресов конфигурационного пространства в памяти или пространстве ввода/вывода достаточно расточительно, поэтому операционная система дает возможность доступа к конфигурационному пространству путем обращения к следующим двум регистрам (адреса которых устанавливаются операционной системой): Конфигурационный адресный регистр. Этот регистр определяет номер шины (Ьиз питЬег), устройство, функцию и адрес доступа к данным в конфигурационном пространстве. Конфигурационный регистр данных. Этот регистр действует как буфер между процессором и конфигурационным пространством. После установки значения в адресном регистре, чтение или запись в этот регистр данных приводит к собственно переносу информации из/в конфигурационное пространство. Операционная система предоставляет НА1_ функции для доступа к конфигурационным данным. Для этого предлагается использовать функции На1(зе(Ви5Оа1а, На1§е1Ви50а1а и На1А551дп$1о(Не5оигсе5. Правда, в \Л/ОМ модели они считаются устаревшими (даже их описание в ОЭК отсутствует) — рекомендовано работать через запросы к нижнему драйверу стека. Для идентификации драйвера, загружаемого операционной системой для обслуживания данного устройства, спецификация РС1 рекомендует разработчикам ОС использовать информацию из конфигурационных регистров \/епс1ог Ю, Оеу|се Ю, кеу15юп Ю, С1а55 Сос1е, 5иЬзу51:ет \/епс1ог Ю, ЗиЬзуз^ет Ю (последние два стали обязательными только в спецификации версии 2.2).
1ЕЕЕ 1394: Нге\ллге Виз Предложенная и реализованная Арр1е Сотри^егз, а впоследствии определенная институтом 1ЕЕЕ как высокоскоростная одноранговая (реег-1о-реег) последовательная шина, 1ЕЕЕ 1394 предназначена для приложений, когда 05В шина версии 1.1 не могла использоваться из-за низкой скорости передачи данных. Спецификация 1ЕЕЕ 1394 (на текущий момент — 1ЕЕЕ 1394а-2000) описывает три стандартных скорости передачи данных 100, 200 и 400 Мбит/сек. (Спецификация 1ЕЕЕ 1394Ь поддерживает большие скорости.) Но даже при таких скоростях, шина 1ЕЕЕ 1394 уже в состоянии переносить более 10 Мбайт в секунду, что превышает показатели 15А. Наименование Нгемге представляет собой торговую марку Арр1е Сотри1егз. Термин 1394 обычно используется для того, чтобы обозначить принадлежность этой спецификации к аппаратному обеспечению персональных компьютеров. Фирма Золу и другие производители видеокамер используют для обозначения своей модификации 1394 термин 1.Ыпк(ТМ). Каждое устройство 1ЕЕЕ 1394 может быть подключено к шине кабелем 4,5 м, содержащем 6 или 4 провода. К шине может быть подключено до 63 устройств шлейфовым образом (с1а15у-сИа1пес1) при общей длин соединения 72 м. Можно использовать шинные мосты и устройства разветвления, что позволяет подключить дополнительно до 62 устройств. При использовании шинных мостов можно задействовать до 1024 шин. В результате, число устройств, которые теоретически можно подключить к компьютеру в конфигурации 1394,
составляет 64К. При подключении устройства, ему присваивается 16-разрядный идентификатор (1\1ос1е Ю). 6-проводной кабель содержит 2 проводника для передачи данных, 2 для передачи сигналов синхронизации и 2 для энергоснабжения подключаемых устройств. Кабель экранирован и оканчивается разъемом, который получен модификацией разъема игровой приставки 1\11п1:епс1о СатеЬоу. Доступ к регистрам Спецификация 1ЕЕЕ 1394 удовлетворяет стандарту 1ЕЕЕ 1212 архитектуры Соп1го1 апб 51а1из кед151ег (С5к). Стандарт СВР. определяет фиксированную 64-разрядную схему адресации, включающую 10-разрядный шинный номер и 6-разрядный идентификатор узла (Мобе Ю) с остающимся 48-разрядным полем для использования по усмотрению устройства. Как в случае со спецификацией Ь5В, регистры предназначены для управления и хранения информации о состоянии устройства, а также для передачи небольших (до 64 байт) пакетов данных. Синхронная передача используется в тех случаях, когда необходимо выдержать указанную пропускную способность шины (например, при работе с видеокамерой), асинхронная передача данных используется при гарантированном поступлении данных (например, при считывании данных с жесткого диска.) Механизмы прерываний Шина 1394 симулирует прерывания устройств (впрочем, как и шина 05В). Устройство должно послать пакет данных для того, чтобы сообщить хост-контроллеру о
своем состоянии, когда требуется вмешательство операционной системы. Драйвер, отвечающий за данное устройство, должен отреагировать на такой пакет данных, который размещается 1ЕЕЕ 1394 интерфейсом в системном адресном пространстве. Семейство стандартов 1394 включает Орел Ноз! Соп1го11ег 1п1егГасе. Спецификация ОНС1 является наиболее значимым стандартом для разработчиков драйверов, работающих с устройствами шины 1394. Этот интерфейс обеспечивает общий механизм работы с прерываниями и ЭМА передачей данных. Ассоциация 1394 Тгас1е Аззоаайоп предоставляет информацию по спецификации ОНС1 и другие связанные с ним данные на интернет сайте 1394Ла.огд. Возможности ОМА Хост-контроллер 1ЕЕЕ 1394 использует ЭМА для передачи данных и команд в/из системной памяти. Устройства, подключенные к шине 1394, не могут иметь прямого доступа к системной памяти, поэтому адаптеры ОНС1 предоставляют диапазон адресов (в системной памяти),с которыми, собственно, и работают прикладные программы и контроллер ЭМА. Таким образом, возникает иллюзия возможности ЭМА передачи данных для каждого устройства, подключенного к шине. Автоматическое распознавание и конфигурирование Спецификация шины 1394 была разработана с непосредственной поддержкой спецификации РпР. Каждое устройство при подключении к шине сигнализирует о своем существовании при выполнении шинной операции Тезе!', что в сильной степени
напоминает способ обращения с шиной 05В и ее устройствами. Хост-компьютер (или другие узлы) выполняют операцию "перечисления" конфигурационных ПЗУ для того, чтобы обнаружить данное устройство и обеспечить запуск (а при необходимости — и инсталляцию) соответствующего драйвера.
118В: 11п|уег5а1 8епа1 Виз Спецификация 05В была разработана консорциумом компаний, включая 1п1е1 и М1сго5оЛ. Целью нового стандарта было обеспечение организации недорогой среднескоростной шины в таких областях применения, как передача цифровых изображений, компьютерная телефония и мультимедийные игры. Текущими версиями спецификации Ц5В является версия 1.1 и версия 2.0 (разумеется, во вторую заложены более высокие скоростные характеристики), и список компаний-членов, участвующих в консорциуме, постоянно растет. Предельная скорость передачи данных по шине 115В спецификации 1.1 составляет 12 Мбит/сек (Еи11 Зрееб). Медленные используют низкую скорость передачи 1,5 Мбит/сек (1_о\л/ 5реей). Стандарт 115В версии 2.0 поддерживает физическую скорость передачи 480 Мбит/ сек (Н|дИ 5реес1). Реально же максимальная скорость соединения компьютер - 115В устройство (в разрабатываемом автором приборе) достигала 32 Мбайт/ с. Данные передаются последовательно по паре проводников. Питание для некоторых устройств доступно по отдельным проводникам питания и заземления (для устройств с небольшим энергопотреблением). Устройства Ц5В могут быть подключены 5-метровым кабелем (а практически — и более длинным). Использовании Ц5В хаба (ИиЬ — сетевой концентратор), позволяет увеличить дальность размещения устройств от хост-компьютера и увеличить количество устройств, подключаемых к одной шине Ц5В. Последовательно можно включить до пяти устройств-хабов, обеспечив длину соединения 30 метров. Пример конфигурации
"сети", построенной с использованием ЦЗВ-хабов приводится на рисунке 5.4. Рис. 5.4 Пример конфигурации устройств шины 05В Следует заметить, что устройства ННдИ 5реес1 (для ЬЗВ 2.0) должны поддерживать функционирование и на скорости Ри11 5реес1. Более того, процесс "перечисления" проводится как раз на этой скорости. При конфигурировании устройств при использовании внешних 115В хабов следует помнить, что хаб, предназначенный для работы на некоторой скорости (например, Би11 Зрееб), ограничит скорость работы устройств, стоящих в цепочке устройств далее от хост- компьютера (например, Н|дИ Зрееб устройства), и последние не смогут работать на скорости, превышающую скорость этого медленного хаб- кон центратора. При отсутствии возможности работать на скорости Н1 Зрееб устройства Н! Зреес! работают на скорости Еи11 Зрееб. Работа программиста, создающего драйвер внешнего (не находящегося на материнской плате) ЦЗВ устройства сводится к тому, чтобы воспользоваться программным интерфейсом системных драйверов шины ЦЗВ, общение с которым происходит при помощи пакетов, называемых 1ЖВ (ЦЗВ Недиез!: В1оск) пакетами. Работа с регистрами
Ь5В контроллеров на материнской плате теперь стала уделом узкого круга специалистов — разработчиков материнских плат и операционных систем. Всем остальным разработчикам Ь5В устройств с управлением программ под \Л/1Пс1о\л/5 предлагается достаточно развитый программный интерфейс к системным \Л/ОМ драйверам, которые берут на себя все аппаратно- ориентированные операции. К хост-компьютеру можно подключить до 127 устройств, шинный адрес которых устанавливается динамически при подключении устройств. Доступ к регистрам Доступ к регистрам устройств (со стороны хост- контроллера и системных драйверов) осуществляется с применением специальных 115В команд, которые применяются для конфигурирования, управления энергопотреблением и переноса небольших объемов данных. Прохождение команд (предназначенных для конкретного устройства) в сильной степени определяется устройством, так что число и назначение команд определяется каждым устройством индивидуально путем заявления (передачи в хост-контроллер) конфигурационных данных по соответствующим запросам в начале работы. Механизм передачи данных является асинхронным и блочным. В каждом блоке передаваемых данных, называемом ОЗВ-фреймом, может передаваться до 1280 байт данных. Каждый фрейм занимает фиксированный временной интервал 1 мсек. Оперирование командами и блоками данных реализуется при помощи такой логической абстракции, как 115В р!ре-
канал. Р!ре-канал является каналом связи между хост- контроллером и конечной точкой (епс!ро1П1 — еще одна логическая абстракция спецификации Ь5В) внешнего устройства, которой обычно является буфер некоторой протяженности внутри внешнего устройства. Открытый р!ре-канал можно сравнить с открытым файлом. Кстати сказать, с ним ассоциирован дескриптор открытого канала. Для передачи команд (и данных, входящих в состав команд) используется р!ре-канал по умолчанию (деГаиН р/'ре), в то время как для передачи данных может быть использовано любое количество потоковых р!ре-каналов (з{геат р/рез) и р!ре-каналов сообщений (теззаде р/'рез). Концепция "конечных точек" (епс1ро1П15) 05В сильно напоминает концепцию функций устройств РС1, поскольку после конфигурирования устройства за конечными точками внутри него закрепляется и определенная функция (0 — только функции управления и контроля, остальные — по замыслу разработчика, но — на все время работы в системе). Механизмы прерываний Для шины Ь5В настоящего механизма прерываний, строго говоря, не определено. Вместо этого, 05В интерфейс хост-компьютера опрашивает подключенные устройства на предмет наличия данных о прерывании. Опрос происходит в фиксированные интервалы времени, обычно каждые 1-32 миллисекунды. Устройству разрешается посылать в момент опроса до 64 байт данных.
С точки зрения драйвера, возможности работы с прерываниями и ОМА передачей данных фактически определяются 05В хост-контроллером, который и обеспечивает поддержку физической реализации Ь5В интерфейса. Достаточно много усилий было предпринято для стандартизации интерфейса хост-адаптера, в результате чего появились два типа интерфейсов: Орел Ноз!: Соп1то11ег 1п1:егГасе (ОрепНС!) и ип1уег5а1 Ноз1: Соп1то11ег 1п1:егГасе (1ЛЧС1). Собственно хост-контроллер и поддерживает общепринятые механизмы прерываний и □МА передачи данных. Внешние пользовательские 05В устройства должны лишь пассивно и в соответствии с протоколом участвовать в обмене данными. Возможности ОМА Устройства 05В шины не имеют прямого доступа к системной памяти. Они изолированы от системных ресурсов 05В интерфейсом хост-компьютера и не поддерживают способа ОМА передачи данных в привычном смысле. Тем не менее, СЗВ интерфейс хост- компьютера обеспечивает "иллюзию" поддержки ОМА для логических р!ре-каналов, обеспечивающих доступ к конечным точкам (т.е. буферам) внутри подключенных к шине устройств. По мере получения данных от подключенных ЬЗВ устройств, интерфейс хост- контроллера использует ОМА доступ для того, чтобы поместить полученные из устройства данные в системную память. Таким образом, 05В интерфейс, состоящий из внешнего устройства, контроллера в хост- компьютере и системные драйверы (но не каждое 05В устройство по отдельности!), поддерживает возможность □МА передачи данных — или, строго говоря, ее иллюзию.
Автоматическое распознавание и конфигурирование Спецификация 115В была разработана с непосредственной поддержкой спецификации РпР (иногда кажется, что — с откровенной ориентацией на МОМ модель драйверов М1пс1о\/У5). Каждое 115В устройство при подключении к шине 115В сигнализирует о своем существовании и сообщает идентификатор производителя (уепбог Ю) и идентификатор устройства (беуюе Ю). Эти идентификаторы являются определяющей информацией в выборе загружаемого драйвера — соответствующая информация должна присутствовать в Системном Реестре (если ничего подходящего там не обнаруживается, то инициируется процесс установки нового драйвера).
Шина РС Саге! (РСМС1А) Около десяти лет назад, несколько компаний совместно разработали стандарт шинной архитектуры для мобильных устройств. Первоначально, внимание было сфокусировано на платах оперативной памяти, и эта группа получила название Регзопа! Сотри1ег Саге! 1п1егпа1юпа1 Аззоаайоп (РСМС1А). Мобильные устройства ограничены в размерах и энергоресурсах, и на этом сделан акцент стандартов данной группы. На сегодня более 300 компаний являются членами РСМС1А. Первоначально, стандарт РС Саге! определял 68- выводной интерфейс при одной из трех величин толщины печатной платы, типа I, типа II или типа III. Данный стандарт определяет скорость передачи данных практически такую же, что и для 15А шины. Но в данном случае более высокий приоритет был установлен для минимизации энергопотребления и размеров, а не для улучшения скоростных параметров шины. Термин РСМС1А зачастую используется вместо термина РС сагс!, и наоборот. Такое использование создает некое противоречие: РСМС1А является организацией, в то время, как РС Сагс1 определяет шинный интерфейс. В настоящее время РСМС1А определяет, по крайней мере, три стандарта: РС Сагс1, ОМА и Сагс1Ви5. Таким образом, когда используется термин 'РСМС1А сагсГ, следует насторожиться, поскольку, строго говоря, он не описывает точно, какой же конкретно тип устройств обсуждается. Первоначально, тактовая частота шины РС Саге! была установлена равной тактовой частоте шины 15А, то есть 8 МГц, что может быть использовано при работе с 8 и 16
разрядными устройствами. Соответственно, максимальная пропускная способность шины при работе 16 разрядными картами достигает 16 Мбайт в секунду. Архитектура СаМВиз позволяет использовать 32 разрядные устройства. Кроме того, тактовая частота увеличена до величины, определенной для шины РС1, то есть 33 МГц, что увеличивает пропускную способность Сагс1Ви5 до более чем 128 Мбайт/сек. Доступ к регистрам Стандарт РС Саге! определяет 26 разрядный адресный диапазон (64 Мбайт). С другой стороны, адресация происходит по схеме, сходной со схемой, используемой в 15А архитектуре. В архитектуре СаМВиз схема использования пространства, адресуемого 32 разрядным адресом, аналогична схеме, примененной в архитектуре РС1. Механизмы прерываний В стандартах РС Саге! и СаМВиз определен один проводник (линия) для прерываний — 1Р.ЕС2, или С11МТ. Эта линия прерываний управляется уровнем (1еуе1 5еп5111Уе), следовательно, может быть использована совместно несколькими картами на одной и той же шине. Однако при использовании многофункциональных РСМС1А карт (если линия прерываний используется совместно несколькими функциями карты) должно быть обеспечено арбитрирование средствами программного обеспечения. Возможности ОМА
Первоначально предложенный стандарт РС САНО не предполагал возможности ОМА передачи данных. Более поздний стандарт, принятый в 1995 году, ввел ОМА расширение в стандарт, названный 'ОМА'. Это расширение стандарта позволяет выполнять передачу 16 разрядных слов в манере, сходной со стандартом 15А. Этот стандарт подразумевает, что все устройства, подключенные к шине, выступают в роли 'Ьиз з1ауе' устройств, совместно использующих ЭМА контроллеры. Так же, как и в архитектуре 15А, трудно реализовать режим 'Ьиз таз!ег ОМА'. Стандарт СагЬВиз позволяет выполнять операции ЭМА передачи данный почти что таким же образом, как это выполняется в архитектуре РС1. Данное дополнение, позволяющее работать с устройствами в роли 'Ьиз таз!ег', является весьма важным. 16 и 32 разрядные операции ОМА передачи данных в стандарте СагЬВиз могут выполняться при частоте 33 МГц. Правда, фактор ограниченности геометрических размеров требует использования компонентов (ИС) более высокой степени интеграции. Автоматическое распознавание и конфигурирование Стандарт шины РС Сагс1 удовлетворяет спецификации РпР полностью, хотя еще продолжается внесение изменений, касающихся "горячего подключения" устройств и автоматического конфигурирования. Программное обеспечение для стандартов РС Саге! или СагЬВиз разделяется на два крупных фрагмента: Сервисы Сокетов, Боске! Беплсез (в данном случае, зоске! — гнездо, разъем), и Службы Карт (Сагб Беплсез). Первый представляет программное обеспечение уровня
ВЮ5, и оно управляет в системе одним или более разъемов. Это программное обеспечение отвечает за обнаружение и объявление факта подключения нового устройства. Вторая часть (Саге! Веплсе) управляет аппаратными ресурсами данной карты.
ГРгеу1ои8~| ГИехМ
Советы по работе с аппаратурой Перед началом разработки нового драйвера, постарайтесь узнать о новом устройстве как можно больше. Постарайтесь выделить информацию, касающуюся, по крайней мере, следующих вопросов: Используемая шинная архитектура. Регистры управления и способ доступа к ним из программного обеспечения. Способ сообщения о текущем состоянии устройства и ошибках. Поведение устройства в связи с использованием прерываний. Механизмы передачи данных. Память, реализованная в устройстве.
Архитектура шины Шинная архитектура, в которую должно вписаться новое устройство, будет иметь определяющее значение для конструкции нового драйвера — сможет ли он опираться на лежащие ниже в стеке драйверы или вынужден будет реализовывать весь физический интерфейс заново. Должны быть понятны все особенности, касающиеся автоматического распознавания и конфигурирования нового устройства. Следует рассчитывать на то, что новое устройство будет участвовать в общих механизмах РпР и обмена сообщениями об изменениях энергоснабжения, предлагаемых \Л/1Пс1о\л/5 2000/ХР/5еп/ег 2003.
Регистры управления Должна быть известна схема адресации, а также размер регистров устройства. Следует полностью проанализировать назначение каждого регистра управления, каждого регистра состояния и каждого регистра данных, так же как и возможные варианты их содержимого. Необходимо рассмотреть варианты возможного необычного поведения, например: Отдельные регистры могут быть регистрами только для чтения или только для записи. Один и тот же регистр может представлять одни функции при операциях чтения и совершенно иные при операциях записи. Регистры данных или состояния могут содержать неверные данные до истечения некоторого временного интервала после поступления команды- запроса. Доступ к регистру (регистрам) может проходить в специфической последовательности.
Получение информации о состоянии устройства и об ошибках Следует определить протокол, который устройство использует для сообщения о своем текущем состоянии и для сообщения о возникших ошибках. Необходимо обладать механизмом уверенного обнаружения сбоев в функционировании устройства.
Поведение, связанное с использованием прерываний Следует предельно достоверно выяснить, какие сигналы прерываний и при каких условиях генерирует рассматриваемое аппаратное обеспечение, и будет ли данное устройство использовать более одного вектора прерываний. При работе с контроллером, связанным со многими устройствами, прерывания могут поступать и от самого контроллера, так что следует выяснить механизм, как точно определить устройство-инициатор прерывания — на предмет, а к рассматриваемому ли устройству относится поступивший сигнал.
Механизмы передачи данных Драйверы для работы с устройствами, поддерживающими программируемый ввод/вывод, сильно отличаются от драйверов, реализующих методы □МА передачи данных. Некоторые устройства поддерживают оба механизма ввода/вывода. В случае □МА устройств, следует выяснить, требуется реализация □МА механизма, в котором устройство выступает в роли 'Ьиз таз^ег', или механизма, когда устройство выполняет действия по сценарию 'Ьиз 51ауе'. Следует определить, имеются ли ограничения на интервал адресов физического буфера памяти, который может быть задействован при этих операциях.
Используйте интеллект нового устройства Некоторые периферийные устройства содержат собственные процессоры, которые выполняют операции диагностики и управления. Эти процессоры могут работать под управлением встроенного программного кода (прошитого в ПЗУ), но возможно, что драйвер самостоятельно выполняет загрузку управляющего кода в оперативную память устройства в момент его инициализации (как это имеет место при работе с весьма интересными микросхемами С5В контроллеров фирмы Сургезз). Использование встроенных возможностей интеллектуальных устройств может дать существенно лучшие результаты в работе и в диагностике такой аппаратуры.
Тестирование аппаратуры Новая аппаратура нечасто может быть объявлена как "априори свободная от ошибок". По этой причине ее всегда следует подвергать предварительному тестированию. Помимо собственно обнаружения ошибок, этот этап является периодом, когда автор устройства узнает много нового о поведении своего детища. Базисные тесты. Прежде всего, следует удостовериться, что все устройства и все соединительные кабели являются совместимыми и их подключение выполнено правильно и надежно. Например, неэкранированный кабель 115В будет удовлетворительно работать с 115В 1.1 (1_о\л/ и Еи11 5реес1), но, возможно, будет причиной сбоев при работе на скорости Н1 5реес1 в Й5В 2.0. Что касается подключения внешних устройств к параллельному порту, то совершенно одинаковые внешне кабели 1_РТ могут создавать совершенно разные условия подключения, а при попытке повысить скорость передачи в режимах ЕРР или ЕСР некачественные кабели могут быть причиной экзотических сбоев. После сборки всей конфигурации следует выполнить простую перезагрузку операционной системы. Ее удачное завершение даст, в первом приближении, уверенность, что новое устройство не мешает работе других системных компонентов. Специальные тесты. Если это возможно, следует написать программный код, который тщательно тестирует новое устройство вместе с (возможно) содержащимся в нем встроенным или загружаемым программном обеспечении.
Наконец, следует испытать встроенные средства диагностики, введя устройство в ошибочное, но предусматриваемое диагностическими средствами состояние. Встроенное программное обеспечение должно обнаружить эту ошибку и выдать должное сообщение.

Заключение Данная глава представила самый общий взгляд на проблемы, связанные с аппаратным обеспечением. Драйвер, поддерживающий устройство, должен уметь находить его, определять системные ресурсы, необходимые для его правильного функционирования, резервировать их и обнаруживать ситуации ошибочного поведения обслуживаемого устройства.
Глава 6
Обслуживание ввода/вывода в режиме ядра

Контекст выполнения программного кода Важнейшим аспектом использования виртуальной адресации является вопрос, в каком контексте работает конкретный программный код. В общем случае, программный код исполняется в аппаратном и программном контексте. Контекст описывает состояние системы в процессе выполнения инструкций центрального процессора. Контекст включает описание состояния всех регистров процессора (включая стек), режим процессора (пользовательский или режим ядра) и, что очень важно, состояние таблицы страниц памяти. Этот последний компонент описывает, как виртуальная память выглядит с точки зрения выполняющегося кода и где в адресном пространстве размещаются эти используемые кодом фрагменты. (Подробное описание механизма виртуальной адресации и структуры таблиц страниц можно найти во многих изданиях, поэтому здесь он не рассматривается.) Практическим следствием контекста является то, как процессор будет интерпретировать виртуальные адреса, предлагаемые ему исполняемым кодом, иными словами, какой набор страничных таблиц будет принят для трансляции этих адресов. Очевидно, выполняемый код режима ядра должен предполагать, в каком контексте он выполняется. Программист, разрабатывающий драйверы должен знать, что возможны три контекста, в котором выполняется драйверный код режима ядра. Они описываются ниже.
Контекст исключения или внутреннего прерывания (1гар) Запрос программного кода пользовательского режима на обслуживание фрагментом кода режима ядра обслуживается через использование внутренних прерываний (Ч=гар', что переводится как 'ловушка', 'внутреннее прерывание'). Переход к выполнению процедуры режима ядра оформлен как вызванное этим приложением пользовательского режима программное исключение (ехсерйоп, или внутреннее прерывание, 1тар). В данном случае, контекст следует отнести, скорее, к коду режима ядра, нежели к коду пользовательского режима, вызвавшего исключение. Неуверенность толкования этой ситуации проистекает из того, что действие после генерации исключения разворачивается все-таки в режиме ядра. Однако память по адресам пользовательского приложения (каким либо образом попавшим в код драйвера) видится коду режима ядра точно так же, как она представлялась вызвавшему его потоку пользовательского режима. Когда поток, выполняющийся в пользовательском режиме, делает запрос Диспетчеру ввода/вывода, программный код последнего выполняется в пределах контекста инициатора запроса. В свою очередь, Диспетчер ввода/вывода может выполнить вызов одной из рабочих процедур драйвера, код которой так же будет выполняться в контексте пользовательского потока. Поэтому и возникают ситуации, когда при отладке драйвера, программист может наблюдать в отладочных сообщениях драйвера адреса буферных областей — те же самые, что пользовательский код передал при вызове процедур ввода/вывода.
Контекст прерывания В момент, когда аппаратура (или программное обеспечение) генерирует сигнал прерывания, то какой бы код более низкого приоритета не выполнялся в системе, его выполнения прекращается. Контекст его выполнения сохраняется, и сразу же управление передается сервисной процедуре соответствующего драйвера, предназначенной для обслуживания данного типа прерываний и своевременно им зарегистрированной. Очевидно, что в общем случае контекст выполняемого перед моментом прерывания программного кода, совершенно не относится к делу (к прерыванию), и его можно считать произвольным, или даже случайным. Соответственно, код режима ядра, привлеченный теперь для обслуживания прерывания, не может делать никаких допущений о состоянии страничной памяти и о том, при помощи какого набора страничных таблиц выполнять интерпретацию виртуальных адресов пользовательского диапазона виртуальной памяти (если что-то похожее имеется в его распоряжении). Буферные области пользовательского режима определенно должны рассматриваться как недоступные в данном контексте. Программный код, работающий в пределах контекста прерываний (который включает внутренние драйверные процедуры), не может делать никаких допущений относительно контекста процесса или потока, исполняемого в настоящий момент. Код обслуживания прерывания может использовать области памяти, указатели на которые он может получить через внутренние переменные и ссылки собственно в драйвере. Однако следует внимательно
следить за тем, чтобы используемые указатели или области памяти, на которые они указывают, не оказались в страничной памяти, поскольку это потенциально опасно и рано или поздно приведет к краху системы. В частности, следует аккуратно использовать директивы указаний компилятору типа #ргадта сос1е_5ед(,,РА6Е") и #ргадта а11ос_1ех1("РА6Е", МуГипсйопЫате). При сомнениях, относительно корректности размещения кода в страничной памяти можно применять макроопределение РА(эЕО_СООЕ(), которое выявит случаи входа в данный код с недопустимо высокими приоритетами (разумеется, только в отладочной версии драйвера).
Контекст программного потока режима ядра Последний возможный вариант контекста выполнения кода драйвера состоит в том, что фрагмент кода выполняется в контексте 'отдельного потока программного кода режима ядра'. Некоторые драйверы предпочитают создавать дополнительные программные потоки для выполнения специфических задач, связанных, например, с обслуживаем устройств, которые требуют последовательного опроса (роШпд), или при работе в условиях, которые продиктованы специфическими схемами организации ожидания. Эти потоки, выполняющиеся в режиме ядра, не так сильно отличаются от привычных потоков пользовательского режима, подробно описанных в книгах по У\Лп32 программированию. Они выполняются в соответствии с решениями планировщика ядра и в соответствии с присвоенными им приоритетами. Объясняется это "простое", несмотря на режим ядра, поведение тем, что создаются такие потоки вызовом Р8Сгеа1е§у81етТГ1геас1, который может быть сделан только из кода, работающего на уровне 1Р.01__РА581\/Е_1_Е\/Е1_. Соответственно, начинают работу они на этом же уровне 1Р.(21_ среди приоритетов пользовательского режима. Такой программный поток не имеет ни собственного ТЕВ (ТИгеас! епу|гоптеп1: Ыоск), ни контекста пользовательского режима, хотя вполне возможно, что "ответвился" от потока, у которого такой контекст имелся. Новый программный поток работает только в
режиме ядра. Он не может интерпретировать виртуальные адреса, полученные из приложений пользовательского режима — даже если он их как-то получит (например, через внутренние переменные драйвера). Как и в случае с контекстом прерываний, контекст потока, выполняющегося в режиме ядра, не может делать никаких допущений относительно выполняющихся в текущих момент процесса и потока. С позиции такого потока, таблица страничной памяти в значительной степени случайна.
ГРгеу1ои8~| ГИехМ
Приоритеты выполнения программного кода Поскольку разные процессорные архитектуры реализуют разные подходы к управлению аппаратурой при помощи прерываний, разделяемых по приоритетам, операционная система \Л/1Пс1о\л/5 1\1Т использует идеализированную схему, которая удовлетворяет особенностям всех аппаратных платформ. Практическая сторона данной схемы состоит в использовании процедур слоя аппаратный абстракций НА1_, которые и берут на себя специфические особенности аппаратуры, позволяя создавать платформенно-независимый драйверный код. Основой этой схемы абстрактных приоритетов прерываний является 1Р.(21_ (1п1еггир1 гедиез1 /еие/) — уровень запроса на прерывание. Уровень 1Н(21_ представляет собой число, определяющее приоритет. Программный код, выполняющийся на данном уровне не может быть прерван программным кодом, имеющим равный или более низкий 1Н(21_. В таблице 6.1 приводятся уровни 1Р.(21_, используемые в \Л/1пс1о\л,5 2000/ХР/5еп/ег 2003. Именно так уровни 1РС21_ видятся драйверу, независимо от того, на каком процессоре или в какой шинной архитектуре приходится драйверу работать. Важно также и то, что в любой конкретный момент времени каждая инструкция (оператор программного кода) выполняется на одном определенном уровне приоритета со специфическим значением 1Р.С21-. Уровень 1Н(21_ входит в состав контекста выполнения каждого потока, следовательно, в любой момент времени операционной системе достоверно известен его текущий уровень 1к(21_.
Таблица 6.1. Уровни 1ЯС?1_ Генерируется Наименование Назначение Аппаратным обеспечением । сч/^1 Проверка компьютера и шинные пЮп 1_с\/Ы_ - ошибки РОМЕР. 1_ЕУЕ1_ Прерывание по сбою в - энергоснабжении Прерывания межпроцессорного 1Р1_1_Е\/Е1_ взаимодействия для многопроцессорных систем С1_0СК_1_Е\/Е1_ Интервальный таймер РРОЕ11_Е_1_Е\/Е1_ Таймер профилирования Платформенно-зависимое число О1РРЬ уровней для прерываний устройств ввода/вывода Программным обеспечением Планирование потоков и выполнение 015РАТСН_1_Е\/Е1_ отложенных процедурных вызовов (ОРС) Выполнение асинхронных процедурных _ вызовов (1) плссп/р । р\/р| Уровень нормального исполнения - потоков (0) В приведенной схеме значения 1(2Н1_ аппаратных прерываний лежат в интервале выше О15РАТСН_1_Е\/Е1_ и ниже РНОЕ11_Е_1_Е\/Е1_. Эти уровни еще встречаются в литературе под названием 'с1е71се ТР-С^Ь', Э1РС21- — уровни 1РС21_ устройств. Уровни выше РА551\/Е_1_Е\/Е1_ называются повышенными (е1еуа1:ес1 Программные потоки, работающие на повышенных уровнях, могут быть вытеснены только потоками с более высоким уровнем 1РС21_. Такой способ работы с потоками называется в литературе сНзраСсЫпд, диспетчеризация. Потоки, работающие на уровне РА551\/Е_1_Е\/Е1_, попадают под управление планировщика заданий (зИес1и1ег). Приоритеты, которые различает планировщик заданий для потоков с уровнем РА551\/Е_1_Е\/Е1_,
принимают значения от 0 до 32 (МАХ1Мим_РР1ОР1ТУ) и называются в ряде источников 'приоритетом планирования' (зНес/и/ег ргюгНу, 'приоритет планировщика'). Между потоками РА551\/Е_1_Е\/Е1_, имеющими приоритеты планирования Р.еа1-Т1те и 1\1огта1 имеется существенное различие. Первые продолжают свою работу до тех пор, пока не появится поток с большим приоритетом, так что потоки низких приоритетов должны дожидаться, пока текущий поток Р.еа1Т1те не завершит работу естественным путем. Потоки с приоритетами 1\1огта1 планируются по другим правилам. Для работы им выделяется определенный квант времени, после чего управление передается другим потокам такого же приоритета. Время от времени планировщик может повышать приоритет отложенного потока в пределах диапазона 1\1огта1, в результате чего все программные потоки среди потоков этой группы, даже имеющие самые низкие приоритеты, рано или поздно получают управление. Таблица 6.2. Приоритеты планирования для потоков уровня РА551\/Е_1_Е\/Е1_ Ж<21_ Приоритеты Наименование Назначение Приоритеты системных Кеа1Т1те (Приоритеты реального времени) Н1СН_РКЮК1ТУ (31) программных потоков (программного кода режима ядра) 1_О\Л/_РЕА1_Т1 М Е_РКЮ К1ТУ (16) 1Чогта1 1\1огта1 тах!тит (15) Приоритеты потоков (Динамические пользовательских приложений приоритеты) Могта! 1с11е (1) 1_ОУУ_РкЮК1ТУ (0) Системный поток обнуления
страничной памяти Драйвер имеет возможность создавать системные программные потоки с приоритетами планирования, укладывающимися в диапазон Р.еа1Т1те и регулировать их приоритеты в этом диапазоне при помощи вызова Ке5е1РпогНуТЬгеас1 (рекомендуемым СОК документацией значением для этой операции является 1ОМ_Р.ЕА1_Т1МЕ_РР1ОР1ТХ, равное 16, см. заголовочные файлы \л/(Зт.Н или п!с!с1к.1п). Подробнее вопросы работы с программными потоками будут рассмотрены в главе 10. Получить текущее значение приоритета планирования известного потока можно при помощи вызова КериегуРпогНуТИгеас!, в то время как получить текущее значение ТРфЬ (при работе внутри самого потока режима ядра) можно при помощи вызова Ке(зе1СиггепМгд1, как это было сделано в коде драйвера Ехатр1е, см. главу 3. Значение приоритета системного потока сразу после его создания (без искусственного изменения) в \Л/1Пс1о\л/5 ХР равно 8. Что касается программных потоков пользовательского режима, то они могут иметь как приоритеты планирования из диапазона 1\1огта1, так и более низкие. Например, приоритет ТНР.ЕАО_ВА5Е_РР1ОР1ТХ_Ю1_Е имеет численное значение — 15.
Обработка прерываний В момент, когда сигнал прерывания достигает центрального процессора, процессор производит сравнение значение 1Р<21_ полученного запроса на прерывание со значением текущего ТЕЩЬ процессора. В случае если новое значение 1Р.С21- меньше или равно старому, запрос на прерывание временно игнорируется и остается в состоянии ожидания до тех пор, пока значение 1Р.С21- процессора не понизится. В том случае, если уровень 1ЕЩ1_ поступившего запроса превышает текущее значение 1Р.С21- процессора, последний выполняет следующие действия: Приостанавливает выполнение текущей последовательности процессорных команд. Сохраняет информацию о состоянии своей работы в стеке для того, чтобы возобновить работу отложенного программного потока позже. Повышает значение 1Н(21_ процессора до значения 1Р.(21_ поступившего запроса, предотвращая вмешательство в работу прерываний с более низкими уровнями 1к(21_. Передает управление надлежащей сервисной процедуре обслуживания прерывания (15Н). По окончании работы сервисная процедура выполняет специальную инструкцию (процессорную команду), означающую полное завершение обработки прерывания. По этой инструкции из стека восстанавливается информация о предыдущем состоянии процессора (включая предыдущее значение 1ЕЩ1-) и управление возвращается прерванному программному коду.
Следует особо отметить, что такая схема позволяет запросу с более высоким значением ТЕЩЬ прерывать работу процедур, обслуживающих прерывания с низким значением 1ЕЩ1-, то есть прерывать прерывания. Благодаря тому, что весь этот механизм для сохранения состояний использует стек, то каких-либо недоразумений не возникает. Однако при этом существенно повышается значение синхронизации выполнения потоков и обращения к совместно используемым данным. Прерывания, вызванные программно Нижние строки таблицы 6.1 описывают уровни Ш(21_, связанные с прерываниями, вызванными программно. Некоторые виды обработки прерываний инициируются программным кодом, работающим в режиме ядра, путем выполнения привилегированных инструкций (процессорных команд). Операционная система \Л/1Пс1о\л/5 1\1Т 5 использует эти программно вызываемые прерывания для расширения своей схемы приоритетов, и это позволяет ей лучше выполнять планирование потоков. Введение этих 1К01_ позволяет разрешить противоречия между соревнующимися потоками путем принудительного повышения приоритета одного из них по сравнению с другими, например, при выполнении критических операций, связанных с конкретным устройством. В примере кода драйвера в главе 3 такая операция выполнена при помощи вызова КеКа15е1г1д.
Доступ к областям памяти пользовательских приложений В момент, когда программный код приложений, выполняющийся в пользовательском режиме, делает запрос на ввод/вывод, то он передает адрес буферной области, которая содержит данные (или предназначена для их получения) и размещена в пользовательском адресном пространстве. Поскольку адреса пользовательского режима указывают в нижнюю часть (ниже 2 Гбайт для обычных версий ОС) страничных таблиц, драйвер должен рассматривать возможность того, что страничные таблицы могут (и будут) меняться до окончания обработки запроса. Это может произойти в случае, если рабочая ветвь кода драйвера выполняется в контексте прерывания или в контексте потока, выполняющегося в режиме ядра. Нижняя часть страничных таблиц меняется при каждом переключении процесса (ргосезз змКсИ). Таким образом, далеко не весь программный код драйвера может рассчитывать на то, что все адреса пользовательского режима будут верны и применимы в течение всего времени обработки запроса. Хуже того, пользовательский буфер может быть и вовсе перемещен из оперативной памяти на жесткий диск в системный 5\л/ар-файл. Области памяти, выделенные пользовательским приложениям, всегда рассматриваются операционной системой в качестве кандидатов для сброса на диск, если системе не хватает ресурсов оперативной памяти для других процессов. Общим правилом является то, что виртуальная память пользовательских приложений является страничной, а к такой памяти нельзя обращаться из программного кода,
выполнение которого происходит на 1К(^1_ уровне □15РАТСН_1_Е\/Е1_ или выше. Этому постулату следовать необходимо по той простой причине, что работа по возвращению страницы памяти из 5\л/ар-файла (в случае, если она туда попала) в оперативную память выполняется службами, которым для завершения этой операции могут потребоваться устройства, работающие при О1ЕЩ1. более низком, чем 1Н(21_ кода-инициатора обращения к этой "неудачной" странице виртуальной памяти. Способы доступа к буферным областям Диспетчер ввода/вывода предоставляет драйверам два разных метода для осуществления доступа к пользовательским буферным областям. Драйвер, при инициализации, должен сообщить Диспетчеру ввода/ вывода, какой из методов он планирует использовать. Выбор определяется логикой и скоростью работы устройства, которое обслуживает драйвер. Первая стратегия состоит в том, чтобы поручить Диспетчеру ввода/вывода копирование пользовательской буферной области в специальную область оперативной памяти, которая не является страничной и зафиксирована в физической памяти. Драйвер использует копию буфера для работы с устройством и осуществления операций ввода/вывода. По завершении работы, Диспетчер ввода/вывода обычно производит копирование данных из системного буфера в пользовательский. При запросе на операцию записи (то есть при переносе данных в обслуживаемое устройство), пользовательская область копируется в область в системном адресном пространстве до того, как последняя будет представлена драйверу. При запросе на чтение (получение данных из обслуживаемого устройства)
копирование системного буфера в пользовательский производится после того, как драйвер помечает запрос как завершенный. Стандартные запросы на чтение или запись не требуют выполнения двунаправленного копирования, хотя при обработке ЮСТ1_ запросов (выполненных в пользовательских приложениях при помощи функции Оеу1се1оСоп1го1) это может потребоваться. Описанный выше метод носит название Ьи/Тегес11/0 — буферизованного ввода/вывода. Он используется медленными устройствами, которые редко работают с большими объемами данных. Этот метод не сложен для реализации в логике драйвера, но требует дополнительных временных затрат на операции по копированию буферных областей. Вторая стратегия позволяет избежать операций копирования путем предоставления драйверу прямого доступа к пользовательской буферной области в оперативной физической памяти. В начале выполнения операции Диспетчер ввода/вывода фиксирует всю область пользовательского буфера в памяти, что предотвращает перемещение этого блока в 5\л/ар-файл и саму возможность возникновения ошибки отсутствующей страницы (раде ГаиК). Затем он создает список элементов страничной таблицы, которые отображаются на область памяти выше 2 Гбайт (на системную область), таким образом, устраняя повод для переключений контекста процесса. В сложившейся ситуации, когда память и элементы страничной таблицы зафиксированы на время обработки всего запроса ввода/вывода, драйверный код может без опаски работать с пользовательским буфером. Правда, свойства этого буфера существенно изменились: теперь эта область зафиксирована в памяти (фактически стала
нестраничной), а оригинальный пользовательский адрес транслирован в другой адрес, пригодный для использования только в коде, работающем в режиме ядра. Второй метод хорошо подходит для использования драйверами быстрых устройств, выполняющих перенос больших объемов данных. Этот метод известен как сНгесС 1/0 — прямой ввод/вывод. Устройства, имеющие способность к операциям ЭМА, практически всегда используют этот механизм.
ГРгеу1ои8~| ГИехМ
Отложенные процедурные вызовы (ОРС) В то время, когда выполняется программный код режима ядра, приоритет которого имеет наибольшее значение, никакой другой код на данном процессоре стартовать не может. Разумеется, если слишком большие объемы программного кода будут выполняться при слишком высоких значениях 1Н(21_, то это неминуемо приведет к общей деградации системы. Для разрешения такого типа проблем, программный код, предназначенный для работы в режиме ядра, должен быть сконструирован таким образом, чтобы избегать продолжительной работы при повышенных уровнях 1Р01_. Одним из самых важных компонентов этой стратегии являются ОеГеггес! РгосесЮге Са11з (ОРС) — отложенные процедурные вызовы.
Функционирование ОРС Схема применения отложенных процедурных вызовов позволяет построить процесс выполнения таким образом, что задача может быть запланирована кодом, работающим на высоком уровне Ш(21_, но она еще не выполняется. Такая отсрочка выполнения применима, если производится обслуживание прерывания в драйвере, и при этом нет никаких причин блокировать выполнение другого программного кода более низкого уровня 1Н(21_. Иными словами — когда обработка данной ситуации может быть безболезненно перенесена на более позднее время. Для учета заявок на вызов ОРС процедур операционная система поддерживает очередь объектов ОРС. Для начала, ограничимся рассмотрением более простого случая работы с ОРС процедурами, предназначенными для использования совместно с процедурами обработки прерываний. Данный тип ОРС процедур получил в литературе специальное название ОрсРогТзг. Объект ОРС для использования в процедурах обработки прерываний создается по вызову 1о1т11аН2еОрсКедие81, выполняемому обычно в стартовых процедурах драйвера. Данный вызов регистрирует предлагаемую драйвером ОрсЕогТзг процедуру и ассоциирует ее с создаваемым объектом — достаточно распространенная методика в \Л/1пс1о\/У5. Следует особо отметить, что ОРС объект, созданный данным вызовом так и останется в недрах операционной системы, недоступным разработчику драйвера. (Отличие ОрсЕогТзг от других ОРС процедур состоит только в том, что работа с последними проходит при помощи вызовов
Ке...Орс, а создаваемые для них ОРС объекты доступны разработчику драйвера.) Если драйвер зарегистрировал свою процедуру ОрсЕогТзг, то во время обработки прерывания 15Р. процедурой в системную очередь ОРС может быть помещен соответствующий ОРС объект (фактически, запрос на вызов этой ОрсЕогТзг процедуры позже) — при помощи вызова 1оЯедие51Орс. Процедура ОрсЕогТзг и завершит позже обработку полученного 15Р. процедурой запроса, что будет выполнено в менее критичных условиях и при низком уровне 1Р.(21_. В общих чертах, функционирование ОРС процедур (в данном случае, ОрсРогТзг) складывается из следующих операций: Когда некоторый фрагмент программного кода, работающий на высоком (аппаратном) уровне ТРфЬ желает запланировать выполнение части своей работы так, чтобы она была выполнена при низком значении 1Р.(21_, то он добавляет ОРС объект в системную очередь отложенных процедурных вызовов. Рано или поздно, значение 1Р.(21_ процессора падает ниже О15РАТСН_1_Е\/Е1_, и работа, которая была отложена прерыванием, обслуживается ОРС функцией. Диспетчер ОРС извлекает каждый ОРС объект из очереди и вызывает соответствующую функцию, указатель на которую хранится в этом объекте. Вызов этой функции выполняется в то время, когда процессор работает на уровне О15РАТСН 1_Е\/Е1_.
Особенности механизма ОРС Как правило, работа с отложенными процедурными вызовами не является сложной, поскольку операционные системы \Л/1Пс1о\л/5 2000/ХР/5еп/ег 2003 предлагают большой набор системных вызовов, скрывающих большую часть деталей этого процесса. Тем не менее, особо следует выделить два наиболее обманчивых момента в работе с ОРС. Во-первых, \Л/|пс!о\л/5 1\1Т 5 устанавливает ограничение, состоящее в том, что один экземпляр ОРС объекта может быть помещен в системную ОРС очередь в определенный временной промежуток. Попытки поместить в очередь ОРС объект, в точности совпадающий с уже там присутствующим, отвергаются. В результате, происходит только один вызов ОРС процедуры, даже если драйвер ожидает выполнение двух вызовов. Это может произойти, если два прерывания были вызваны обслуживаемым устройством, а обработка первого отложенного процедурного вызова еще не начиналась. Первый экземпляр ОРС объекта еще пребывает в очереди, в то время как драйвер уже приступил к обработке второго прерывания. Конструкция драйвера должна предусматривать такой ход работы ОРС механизма. Возможно, следует предусмотреть дополнительно счетчик ОРС запросов, либо драйвер может реализовать собственную реализацию очереди запросов. В момент выполнения реальной отложенной процедуры можно проверять счетчик и собственную очередь запросов для того, чтобы определить, какую конкретно работу следует выполнять.
Во-вторых, существуют значительные синхронизационные проблемы при работе на многопроцессорных платформах. Предположим, программный код, выполняемый на одном процессоре, выполняет обслуживание прерывания и планирует вызов □РС процедуры (размещая ОРС объект в системной очереди). Тем не менее, еще до момента полного завершения обработки прерывания, другой процессор может начать обработку ОРС объекта, помещенного в системную очередь. Таким образом, возникает ситуация, в которой программный код обслуживания прерываний выполняется параллельно и одновременно с кодом ОРС процедуры. По этой причине необходимо предусмотреть меры по надежной синхронизации доступа программного кода ОРС процедуры к ресурсам, используемым совместно с драйверной процедурой обслуживания прерывания. Если присмотреться к списку параметров вызовов 1о1пШаН2еОрсПедие81 и ТоВедиезЮрс (предназначенных для работы с ОрсЕогТзг процедурами), то нетрудно заметить, что ОРС объект "привязан" к объекту устройства. При размещении этого объекта в □РС очереди в момент работы 15Р. процедуры также указывается объект устройства. Этим и достигается определенность, вызов какой конкретно ОРС процедуры "заказан" 15Р. процедурой (соотнесение по объекту устройства). Это же говорит о том, что драйвер, который создал несколько объектов устройств (достаточно редкий случай), может эксплуатировать и несколько процедур □рсЕогТзг — по одной для каждого объекта устройства. Системный ЭРС механизм предотвращает возможность одновременной обработки ЭРС объектов из системной очереди, даже в условиях многопроцессорной конфигурации. Таким образом, если ресурсы
используются совместно несколькими отложенными процедурами, то нет необходимости заботиться о синхронизации доступа к ним. Выше было рассмотрено использование ОРС процедур для Ф завершения обработки прерываний, то есть ПрсРог1зг. Однако ОРС процедуры можно использовать и в другом ключе, например, совместно с таймерами для организации ожидания. Для этого создастся объект ОРС при помощи вызова КеТпШаИхеОРС, который связывает этот объект с ОРС процедурой, входящей в состав драйвера. После этого можно выполнять установку времени ожидания в предварительно инициализированном (используя Ке1пШаИхеТ1тег или КеТпШаИхеЕх) объекте таймера. Для установки интервала ожидания используется вызов КеЗеГПтег, которому в качестве одного из параметров необходимо передать указатель на инициализированный ЭРС объект. По истечении интервала ожидания ОРС объект будет внесен в системную ОРС очередь, и ОРС процедура, ассоциированная с ним, будет вызвана так скоро, насколько это будет возможно. Процедуры ИРС данного, второго, типа обозначены в документации ИИК термином 'Сизбэт ИРС. (Этот вариант использования ИРС процедур делает их весьма напоминающими АРС вызовы пользовательского режима.) Для размещения в системной ОРС очереди объектов, соответствующих второму типу ОРС процедур (не связанных с прерываниями), следует использовать вызов Ке1п5ег1(2иеиеОрс. Соответственно, код-инициатор вызова должен работать на уровне 1Р.(21_ не ниже О15РАТСН_1_Е\/Е1_. Для очистки системной ОРС очереди от Сиз1ют ОРС процедур, например, если драйвер должен срочно завершить работу, предназначен вызов КеКетоуеСЭиеиеОрс, который может быть вызван из кода любого уровня 1РС21_.
ГРгеу1ои8~| ГИехМ
Общий взгляд на структуру драйвера режима ядра Уточняя уже представленную ранее метафору, что драйвер есть О1_1_ режима ядра, можно сказать, что драйвер представляет собой всего лишь коллекцию процедур, которые вызываются системным программным обеспечением, как правило, Диспетчером ввода/вывода. Драйверные процедуры пассивно ожидают того момента, когда к ним обратится программный код Диспетчера ввода/вывода. В зависимости от назначения драйвера, Диспетчер ввода/вывода может вызывать процедуры драйвера в следующих ситуациях: При загрузке драйвера. При выгрузке драйвера и выполнении отката системы. В моменты, когда устройство, обслуживаемое драйвером, подключается или удаляется из системной конфигурации. Программы пользовательского режима выполняют вызовы системных служб для ввода/вывода. Совместно используемые аппаратные ресурсы становятся доступными для использования драйвером. В различные моменты во время реального функционирования обслуживаемого устройства (скажем, для обработки прерывания, поступившего от обслуживаемого устройства). В моменты,связанные с изменениями в энергоснабжении.
При опросе конфигурации устройства РпР Менеджером. Ниже приводится краткое описание основных категорий процедур, входящих в состав драйвера режима ядра.
ГРгеу1ои8~| ГИехМ
Процедуры инициализации драйвера и очистки В момент загрузки драйвера в систему необходимо решить определенные задачи по инициализации драйвера и его жизненно важных объектов. Соответственно, при выгрузке необходимо выполнить некоторые завершающие действия, которые носят название 'с1еапир' — действия по очистке (освобождению занятых у системы ресурсов). Существует несколько процедур, которые присутствуют в драйвере для выполнения подобной работы. Процедура ОпуегЕп1гу Диспетчер ввода/вывода вызывает данную процедуру при загрузке драйвера. Возможно, это произойдет в процессе загрузки операционной системы, но драйвер может быть загружен и динамически в любое время, как это было сделано с драйвером Ехатр1е.5уз в главе 3. Процедура ОпуегЕп1ту решает первоочередные задачи, в частности, регистрацию в специальном массиве адресов других драйверных процедур (для того, чтобы Диспетчер ввода/вывода имел возможность позже производить их вызов по адресу). В случае если драйвер не работает по модели \ЛЮМ (1едасу драйвер, драйвер "в-стиле-МТ"), то в этой же начальной процедуре можно провести локализацию оборудования, которое будет обслуживать драйвер, выделить или подтвердить использование аппаратных ресурсов (портов ввода/вывода, прерываний, каналов ОМА) и предоставить имя, видимое всей остальной системе, для каждого обнаруженного устройства. Для \Л/ОМ драйверов, поддерживающих спецификацию РпР, процесс определения аппаратных
ресурсов откладывается на более позднее время и выполняется драйверной процедурой Ас1с1Оеу|се. Строго говоря, определенное имя (то есть Игп/егЕпСгу) должно быть Ф только лишь у этой "входной" процедуры. Имена остальных драйверных процедур не столь жестко определены, поскольку они не будут никому известны, за исключением обладателя исходного текста или хакера-исследователя, работающего с отладочной версией. От остальных процедур в системе остаются только их адреса, и к ним предъявляется требование иметь определенный интерфейс вызова, согласованный по типу получаемых и возвращаемых параметров. Поэтому все приведенные в книге имена драйверных процедур можно рассматривать как "говорящие фамилии", кратко описывающие назначение этих процедур. При разработке собственных драйверов можно использовать любые названия. Процедура ре-инициализации Некоторые драйверы, по природе обслуживаемого ими устройства, иногда не могут завершить процесс инициализации во время работы ОпуегЕп1ту. Это может иметь место, если драйвер зависит от других драйверов или системных служб, которые еще не загружены к моменту вызова ЭпуегЕп^гу. Такого типа драйвера должны сделать запрос определенного вида, чтобы их инициализация была отложена. Отложенная инициализация будет выполнена вызовом процедуры ре- инициализации ке1пИ:1а1|2е, адрес которой необходимо сообщить во время работы ОпуегЕп1ту вызовом 1оКед151егОпуегКе1пШаНха(1Оп, см. таблицу 8.2. Процедура выгрузки 11п1оас1 Диспетчер ввода/вывода выполняет вызов драйверной процедуры С1п1оас1 в случае, если драйвер выгружается динамически, то есть не в связи с откатом (завершением работы) операционной системы. Эта процедура должна выполнить действия, обратные работе ЭпуегЕп^гу, так
чтобы не осталось "числящихся" за драйвером системных ресурсов. Эти действия включают удаление системного имени устройства и символьной ссылки, которые были предоставлены во время инициализации, объекта прерывания, если он был задействован, занятых областей памяти — поскольку за их удаление никто, кроме самого драйвера, в режиме ядра не отвечает. В драйверах \ЛЮМ удаление ресурсов устройств (включая имена) выполняется при удалении каждого устройства (если драйвер обслуживает несколько устройств) в процессе выполнения НетоуеЭеу|се. Возможно, 1едасу драйверам имеет смысл в пределах данной процедуры побеспокоиться о судьбе 1Р.Р пакетов, обработка которых не была завершена, и ЭРС объектов, которые еще находятся в системной ЭРС очереди. Процедура 5Ни1с1о\л/п Возможно, это прозвучит неожиданно, но процедура 11п1оас1 не вызывается при выполнении подготовки системы к завершению ее работы (откате). Поскольку система закончит свою работу в любом случае, то действия, которые следовало бы непременно выполнить в процедуре 11п1оас1, здесь никого не волнуют, и действия про очистки не требуются. Вместо этого, Диспетчер ввода/вывода привлекает к работе драйверную процедуру 5НиСс!о\л/п для того, чтобы предоставить драйверу возможность оставить устройство в приемлемом состоянии покоя после того, как операционная система перестанет функционировать. Процедура обратного вызова ВидсНеск В том случае, если драйверу необходимо получить управление при крахе системы, он должен реализовать
саПЬаск-процедуру ВидсИеск. Эта процедура, при должной ее регистрации, будет вызвана ядром операционной системы в соответствующем месте выполнения 'сгазИ'-процесса — сколь ни противоречиво звучит эта фраза.
ГРгеу1ои8~| ГИехМ
Рабочие процедуры обслуживания ввода/вывода В момент, когда Диспетчер ввода/вывода получает от приложения, работающего в пользовательском режиме, запрос на операцию ввода/вывода, происходит преобразование типа запроса (чтения, записи и т.п.) в код функции запроса. Диспетчер ввода/вывода идентифицирует надлежащий драйвер, которому следует адресовать запрос, после чего производит вызов одной из рабочих (с1|5ра1:сИ) процедур этого драйвера. Вызванная драйверная процедура проверяет запрос и либо его обрабатывает, либо, при необходимости, делает запрос к Диспетчеру ввода/вывода, чтобы он отложил запрос к устройству для последующей реальной работы с ним. Во втором случае, вызванная рабочая процедура возвращает управление Диспетчеру ввода/вывода, помечая поступивший запрос как незавершенный (репсНпд) — это случай простой задержки обработки 1Р.Р запроса (отличающийся от применения ОРС процедур для завершения обработки прерывания). Обработчики запросов Ореп и С1о$е Все драйверы, если только они не тестового назначения, должны иметь в своем составе процедуру Сгеа1:еО15ра1:сЬ (в примере Ехатр1е.5уз эта процедура называлась Сгеа1:е_Н1е_1кРргосе551пд), которая производит обработку пользовательского запроса Сгеа1еП1е. Помимо того, драйверы, которые должны выполнять действия по очистке в ответ на пользовательский запрос С1о8еНапс11е, обязаны иметь в своем распоряжении еще
и процедуру С1о5еО15ра1:сИ (в примере Ехатр1е.5уз — процедура С1о5е_Напс11е1кРргосе551пд). Процедуры передачи данных В зависимости от типа обслуживаемого устройства, драйвер может иметь отдельные рабочие процедуры для выполнения операций по переносу данных и операций управления устройством. Функции пользовательского режима Кеас1Н1е, УУгНеГПе и Оеу1се1оСоп1го1 "переключаются" на соответствующие процедуры, из числа процедур, предложенных драйвером Диспетчеру ввода/вывода. Драйверу необходимо всего лишь предложить свои процедуры для тех операций, которые он собирается обслуживать. В том случае, если пользовательская программа сделает запрос на выполнение операции ввода/вывода, для которой драйвер не предоставил соответствующую диспетчерскую процедуру, то эта программа получит сообщение об ошибке, утверждающее, что запрошенная функция данным устройством не поддерживается. Драйвер Ехатр1е.5уз для обслуживания указанных запросов зарегистрировал функции Неас1У\/п1:е_1кРИапс11ег и □еу|сеСоп1то1Нои1:1пе. Процедура 51а г11о Выполняя регистрацию рабочей процедуры 51:аН:1о, драйвер соглашается участвовать в процессе, называющемся 5уз1:ет (Зиешпд, то есть создание очередей необработанных запросов при помощи системных средств — в отличие от Эпуег (2иеи!пд— ведение учета необработанных запросов средствами собственно драйвера (Для создания и ведения
внутренних очередей драйверы используют вызовы Ке1т1аН2еОеу1се(2иеие и прочие Ке...(2иеие). В том случае, если какая-либо из рабочих процедур (например, обработки запросов Реас1-У\/п1е) не может завершить обработку запроса сразу, то она помечает текущий 1РР пакет как 'репсПпд' при помощи вызова, а на самом деле — макроопределения (см. файлы \л/с!т.Н или п!:с1с1к.Ь) 1оМагк1грРепсНпд, таблица 9.12. После этого она делает вызов 1о§1аг1Раске1 и возвращает управление Диспетчеру ввода/вывода с кодом 5ТАТ115_РЕШ1М6. В результате вызова 1о§1аг1Раске1 Диспетчер ввода/ вывода вызывает драйверную процедуру 51агНо или, если устройство занято (то есть уже существует очередь необработанных пакетов 1РР), помещает очередной пакет с состоянием 'репсНпд' в очередь необработанных запросов. Таким образом, Диспетчер ввода/вывода выполняет сериализацию 1КР запросов, вызывая рестарт ввода/вывода только по окончании обработки предыдущего запроса. Как правило, процедура 51агНо организует внутреннюю сортировку поступающих в нее 1Р.Р пакетов по кодам 1Р.Р_МЗ_Ххх (например, обычным оператором ’зуу^сН'). Завершается работа 51агНо тем, что текущий 1Р.Р пакет помечается как обработанный вызовом 1оСотр1е1еПедие51 (таблица 9.10), после чего выполняется вызов 1о§1аг1Ыех1Раске1, что побуждает Диспетчера ввода/вывода вызывать 51агНо снова — для следующего из оставшихся в очереди 1Р.Р пакетов. В том случае, если невозможность обработки запроса была обусловлена временной неработоспособностью устройства, то запуск обработки очереди накопившихся
1кР пакетов может быть выполнен следующим образом при участии 15Н процедуры. Устройство сигнализирует о способности к работе прерыванием, 15Н процедура обработки прерывания планирует запуск соответствующей ОРС процедуры, которая делает вызов 1о§1аг1№х1Раске1 (поскольку он может быть сделан только программным кодом уровня □15РАТСН_ЬЕ\/Е1). Этот вызов и запускает обработку помещенных в очередь 1РР пакетов, побуждая Диспетчера ввода/вывода вызывать процедуру 51:аН:1о. Аналогично, вместо автоматического запуска обработки следующего 1РР пакета в конце процедуры Э^агНо, как было указано выше, можно запускать следующую операцию ввода/ вывода также по сигналу прерывания от устройства. Размещение необработанных пакетов в очереди производится на принципах Е1ЕО, то есть в конец очереди. Однако можно изменить это правило, задавая определенные значения параметра Кеу в вызове 1о§1аг1Раске1. Изменить порядок извлечения очередного необработанного пакета из очереди можно, задавая определенные значения параметра Кеу в вызове 1о§1аг1№х1Раске1ВуКеу. Процедура ЗЪагЫо регистрируется во время работы ОпуегЕп1ту записью ее адреса в поле ОпуегЭ^агНо в структуре объекта драйвера (что похоже на регистрацию процедуры Ас1сЮеу|се). Процедура обслуживания прерываний Процедура обслуживания прерываний (1п1еггир1 Бегу/се коий'пе, 15%), входящая в набор процедур драйвера, вызывается диспетчером прерываний ядра (КегпеГз 1гПеггир1: сНзра<:с1пег) всякий раз, когда устройство генерирует сигнал прерывания. На этой процедуре лежит
обязанность полного обслуживания аппаратного прерывания. Собственно в 15Р. процедуре драйвера должна быть реализована самая минимальная обработка создавшейся ситуации. Если дополнительная, требующая больших временных затрат обработка прерывания требуется по логике работы устройства, то следует прибегнуть к использованию механизма ОРС (отложенных процедурных вызовов), то есть запланировать отложенный процедурный вызов в текущей процедуре обработки прерываний (15Н). После этого, остаток работы 15Р. процедуры можно завершить на уровне ниже уровня аппаратных прерываний (□1к(}1_), понизив приоритет выполняемого кода данных комплексом мер. Процедуры ОРС Драйвер может предоставлять любое число ОРС процедур, которые выполняют полностью или завершают работу (начатую в других процедурах драйвера) по взаимодействию с обслуживаемым им устройством. В их функции может входить освобождение системных ресурсов, сообщения об ошибках, пометка запроса на ввод/вывод как завершенного, запуск (старт) новой операции устройства.
ГРгеу1ои8~| ГИехМ
Процедуры обратного вызова для синхронизации доступа к ресурсам Программный код режима ядра должен обладать свойством повторной входимости (гееп1тапсе, реентерабельность). Драйверный код и сама его конструкция должны предусматривать, что возможна ситуация, когда многочисленные потоки в одном или нескольких процессах могут сделать запросы на ввод/ вывод одновременно. Конфликты между двумя одновременными запросами должны обрабатываться корректно и безопасно для системы реентерабельным драйверным кодом. Диспетчер ввода/вывода предоставляет несколько функций, выполняющих синхронизацию доступа к совместно используемым ресурсам при одновременных запросах. Способ работы этих процедур отличается от способа, принятого в обычных \Л/!п32 программах. В частности, модель \Л/1п32 предполагает, что если ресурс недоступен из-за использования его другим потоком, то совершенно нормальным решением является, например, блокировка выполнения "опоздавшего" потока. В режиме ядра блокировка выполнения потока, в общем-то, по случайному выбору неприемлема. Должен быть гарантирован быстрый ответ инициатору вызова, даже если он состоит в том, что его запрос помещен в очередь для обработки в более поздний период. Один из методов, применяемых для синхронизации в пределах программного кода режима ядра, состоит в том, что предоставляется адрес саПЬаск процедуры (процедуры обратного вызова), которую следует использовать для синхронизации доступа к
определенным ресурсам. Когда у драйвера появляется необходимость в доступе к совместно используемым ресурсам, то при содействии Диспетчера ввода/вывода он помещает свой запрос в очередь на доступ к такому ресурсу. Когда ресурс становится доступным, Диспетчер ввода/вывода вызывает предоставленную драйвером процедуру обратного вызова, ассоциированную с запросом. Разумеется, это означает, что все поступившие запросы от всех потоков выстраиваются последовательно, и в каждый конкретный момент ресурсом "владеет" только один из таких потоков. Существует три типа саНЬаск процедур, поддерживаемых Диспетчером ввода/вывода, о чем ниже. Процедура Соп1го11егСоп1го1 Существуют устройства, которые поддерживает более одной функции, но в отдельный момент времени может быть задействована только одна из них. При некотором воображении можно представить себе один контроллер, который реализует операции ввода/вывода для нескольких подключенных к нему дополнительных устройств, реализующих функции. Соответственно, необходимо создать абстракцию синхронизации доступа к этому котроллеру со стороны подключенных устройств- функций. В \Л/| пс!о\л/з 1\1Т эта абстракция реализована в виде объектов контроллера, которые создается вызовом 1оА11оса1еСоп1го11ег. Как правило, процедура, запускающая операцию ввода/вывода, выполняет запрос "владения" объектом контроллера при помощи вызова 1оА11оса1еСоп(го11ег, одновременно устанавливая процедуру, которая получит управление, как только запрос на владение будет удовлетворен. Эта саНЬаск
процедура известна в литературе под именем Соп1то11егСоп1то1. По завершении обработки запроса ввода/вывода драйвер освобождает контроллер вызовом 1оРгееСоп1го11ег. Следует обратить внимание, что использовать абстракцию объекта контроллера полезно не столько для возможности запуска разных процедур Соп1то11егСоп1то1 (она может быть единственной), сколько для разделения доступа из них к общим для данного устройства ресурсам (как драйверным, так и аппаратным), а также для разделения этих обращений во времени. Объекты контроллера в модели \Л/ОМ не поддерживаются, то есть разработчики ООК ограничили применение этой абстракции только драйверами "в- стиле-МТ" (1едасу драйверами). Процедура Ас1ар1егСоп1го1 Аппаратное обеспечение ОМА (прямого доступа к памяти) является другим совместно используемым (разделяемым) ресурсом, который должен передаваться от драйвера к драйверу. Перед выполнением ОМА операции драйвер делает запрос на исключительное владение необходимым аппаратным обеспечением (что реализовано через абстракцию объекта адаптера), обычно, это — ОМА канал. Когда доступ подтверждается, выполняется процедура обратного вызова Ас1ар1:егСоп1то1. Следует отметить, что в \Л/1Пс1о\л/5 ЫТ абстракция объекта адаптера реализует не только механизм эксклюзивного доступа со стороны потоков драйверного кода, но используется для того, чтобы обобщить опыт работы с аппаратным обеспечением ОМА на разных процессорных
платформах и освободить разработчика драйвера от необходимости знать все тонкости этой аппаратуры. Процедуры 5упсЬСп15ес11оп Обслуживание прерывания происходит на одном из уровней аппаратных (что зависит от типа устройства), в то время как весь остальной код драйвера работает на уровне приоритетов не выше □15РАТСН_1_Е\/Е1_. В случае, если когда-либо этому относительно низкоприоритетному коду понадобится поработать с ресурсами, которые использует и 15к (процедура обслуживания прерываний) драйвера, то эти действия должны выполняться только внутри саНЬаск процедуры 5упсИСгП:5есЬоп. Тот программный код, которому хочется корректно обратиться к ресурсам, что, возможно, затребует неожиданно вступившая в права высокоприоритетная процедура обслуживания прерываний, должен воспользоваться посредническими услугами саНЬаск процедуры 5упсИСгП:5есЬоп. Запустить эту саНЬаск процедуру необходимо вызовом КеЗупсНготгеЕхесиНоп из программного кода, который работает на любом из уровней не выше прерывания. Сама процедура 5упсИСгП:5есЬоп будет работать с приоритетом прерывания, поэтому пребывание внутри ее кода следует минимизировать (постоянное требование для кода высоких уровней 1КРЬ). Таким образом, фрагмент кода с изначально низким уровнем ТРфЬ через процедуру обратного вызова получает возможность сделать работу при уровне □1РС21_ устройства, не опасаясь, что в это время управление будет передано в 15Р. процедуру. По окончании ЗупсЬСгкЗесЬоп прежнее значение 1Р.(21_ восста на вл и вается.
ГРгеу1ои8~| ГИехМ
Другие процедуры драйвера В дополнение к только что описанному базисному набору процедур, драйвер может содержать несколько вспомогательных функций. Таймерные процедуры Драйвер, которому нужно выполнять точный отсчет временных интервалов, должен использовать либо ОРС процедуру 1оТ|тег (которая, если она зарегистрирована должным образом, всегда вызывается один раз в секунду), либо Сиз1:от ОРС процедуру для таймера (кратко была рассмотрена выше). В литературе последняя упоминается как Сиз1ютТ|тег0рс. Процедура 1оСотр1е11оп Драйвер модели \Л/ОМ, работающий внутри многослойной драйверной структуры, может испытывать потребность в том, чтобы его уведомляли, когда будет завершена обработка 1РР запроса, посланного драйверу нижнего уровня. Для этой цели драйвер может зарегистрировать саНЬаск процедуру 1оСотр1еЬоп (так она называется в документации ООК), при помощи которой он может выполнять весьма впечатляющие трюки. В частности, драйвер может разбивать крупные операции ввода/ вывода на более мелкие. Механизм достаточно прост: по окончании частичного переноса данных, управление оказывается в процедуре 1оСотр1еЬоп, которая тут же начинает следующий перенос данных. Регистрация этой саНЬаск процедуры выполняется при помощи вызова 1о§е1Сотр1е(1ОпНои11пе.
Процедура Сапсе1Яои1те Драйвер должен учитывать возможность того, что какой- либо из 1Р.Р запросов (текущий или находящийся в состоянии ожидания обработки) может быть отменен. Простейшая ситуация, которая к этому ведет, возникает, когда пользовательское приложение, не дождавшись отклика от устройства и драйвера, попросту снимается при помощи диспетчера задач. Тем временем драйвер, будто ничего не случилось, пытается завершить порученные ему задачи. Диспетчер ввода/вывода выполняет удаление 1РР запросов, находящихся в ожидании обработки, и мог бы сообщить об этом драйверу вызовом саПЬаск процедуры Сапсе1коийпе (под таким именем она фигурирует в документации СОК), если бы тот зарегистрировал ее при помощи вызова 1о§е1Сапсе1КоиНпе. Особенность применения этих процедур состоит в том, что при помощи вызова 1о§е1Сапсе1КоиНпе можно назначать разные Сапсе1Р.ои1:1пе процедуры для отслеживания разных экземпляров 1Р.Р пакетов. Более того, даже на разных уровнях работы драйвера можно заново определять процедуру Сапсе1Р.оийпе (необходимо только хранить указатели на интересующие 1Р.Р пакеты), а то и вовсе отменить отслеживание уничтожения 1Р.Р пакетов — указав 1\1111_1_ вместо адреса Сапсе1коийпе при вызове 1о§е1Сапсе1ПоиНпе. Другая ситуация удаления пакетов 1КР, ожидающих обработки, возникает в случае, если пользовательское приложение применило вызов АР1 функции СапсеПо (см. заголовочный файл мпЬазе.И или документацию М5О1М). Правда, применение этого вызова требует, чтобы доступ к драйверу был получен с использованием флага Е11_Е_Е1_А(3_О\/Ек1_АРРЕО, а этот прием программисты
применяют крайне редко. Подробнее о Сапсе1коийпе см. в 9 главе.
ГРгеу1ои8~| ГИехМ
Последовательность обслуживания запросов ввода/вывода Весьма важным для разработчика драйвера является понимание жизненного цикла 1КР запроса. Ниже рассматривается продвижение запроса — от программного кода пользовательского режима, через код Диспетчера ввода/вывода к драйверу устройства. Все запросы на ввод/вывод проходят следующие основные стадии: Предварительная обработка Диспетчером ввода/ вывода. Предварительная обработка драйвером устройства. Старт устройства и обслуживание прерывания. Пост-обработка драйвером. Пост-обработка Диспетчером ввода/вывода.
Предварительная обработка Диспетчером ввода/вывода На данном этапе производится не связанная с устройством подготовка запроса и его предварительная верификация. 1. Подсистема \Л/!п32 (ограничимся этой подсистемой) преобразует запрос в системный сервисный вызов (пайуе зуз^ет зеплсе са11). Диспетчер системного сервиса переходит в режим ядра и управление передается Диспетчеру ввода/вывода. 2. Диспетчер ввода/вывода выделяет память под структуру данных, известную под именем 1/0 Р^иезСРаскеС (1РР), пакет запроса на в вод/вы вод (пакет 1Р.Р). В следующей главе эта структура описывается детальнее, но в первом приближении, вполне можно считать пакет 1Р.Р рабочим рецептом (предписанием), выдаваемым драйверу и, соответственно, устройству, которое тот обслуживает. Структура данных 1Р.Р заполняется необходимой информацией, включая код, которым обозначается тип запроса на ввод/вывод. 3. Диспетчер ввода/вывода осуществляет некоторую проверку правильности аргументов, переданных из запроса (инициированного из кода пользовательского режима). Проверка включает верификацию дескриптора файла (имеется в виду, что доступ к устройству из программного кода пользовательского режима осуществляется как доступ к файлу — по файловому дескриптору, ассоциированному с нужным устройством, например, посредством вызова Яеас1ЕПе). Кроме того, выполняется проверка прав доступа к этому
файловому объекту, проверка, предоставил ли драйвер процедуру для обслуживания такого типа запроса, и проверка адресов пользовательских буферных областей памяти, необходимых для обслуживания запроса. 4. В случае, если запрос является операцией буферизованного ввода/вывода (ЬиНегес! 1/0}, Диспетчер ввода/вывода получает область памяти в области нестранично организованной памяти (нестраничном пуле) под создание буфера, после чего копирует данные из области пользовательского буфера в этот системный буфер. В случае, если запрос к устройству требует прямого ввода/вывода (сНгесС 1/0}, производится блокирование пользовательского буфера в физической памяти и создается список дескрипторов страниц (МО1_ список), через которые драйвер имеет возможность доступа к этому пространству физической памяти. 5. Диспетчер ввода/вывода производит вызов необходимых рабочих процедур драйвера (сНзра^сИ гоийпез).
Предварительная обработка в драйвере Как было сказано ранее, каждый драйвер в своей процедуре ОпуегЕп^гу производит заполнение специального массива, указатель на который передается в эту процедуру из Диспетчера ввода/вывода. Таким образом, драйвер выполняет регистрацию своих процедур. После заполнения массива адресами точек входа в процедуры драйвера, в распоряжении Диспетчера ввода/вывода оказывается таблица процедур для обслуживания запросов с поддерживаемыми функциональными кодами (индексом в этой таблице как раз и являются коды обрабатываемых запросов 1кР_МЗ_Ххх). Используя функциональные коды запросов, Диспетчер ввода/вывода ориентируется в этой таблице и привлекает к работе необходимые процедуры драйвера. Вызванная таким образом процедура решает (должна решать) следующие задачи: 1. Выполняет дополнительную проверку параметров входящего запроса. Драйвер может устанавливать зависящие от устройства ограничения, которые не могут быть известны Диспетчеру ввода/вывода. Например, пользователь может затребовать передачу большого объема данных, который превышает возможности обслуживаемого устройства (или драйвера). 2. В случае, если запрос не требует работы с физическим устройством (например, считывание О байт данных), то вызываемая драйверная процедура может завершить обработку и "отправить" 1Р.Р пакет обратно Диспетчеру ввода/вывода, пометив его как обработанный.
3. В случае, если запрос требует для завершения обработки реального обращения к обслуживаемому физическому устройству, рабочая процедура может пометить 1РР пакет как незавершенный (репсПпд). Таким образом, Диспетчеру ввода/вывода сообщается, что необходимо поместить в очередь запрос на вызов процедуры В^агНо как только устройство освободится — возможно, что устройство еще обрабатывает предыдущий запрос. Вообще говоря, этап общения с устройством может быть реализован весьма экзотическими приемами, тут как раз место, где фантазия разработчиков разыгрывается не на шутку.
Старт операции ввода/вывода В том случае, если рабочая процедура решит задействовать механизм Бузует (Зиеыпд и послать Диспетчеру ввода/вывода запрос на вызов процедуры В^агНо, то тот выполнит проверку, не занято ли устройство. Диспетчер ввода/вывода может это установить путем проверки, завершены или нет 1КР для данного устройства. Если предыдущий запрос еще не завершен, новый запрос на старт операции ввода/ вывода помещается в очередь. В противном случае, процедура 51:аН:1о вызывается непосредственно. Ожидается, что процедура старта ввода/вывода, реализация которой возлагается исключительно на разработчика драйвера, выполняет следующие задачи (или часть из них): 1. Проверяет код 1РР функции (чтение, запись и т.п.) и выполняет установочные действия для данного типа операций. 2. В случае, если устройство (то есть объект устройства, которому адресован 1КР пакет) по логике работы олицетворяет только одну из функций сложного реального устройства, запрашивает исключительный доступ к заранее созданному объекту контроллера, планируя при этом вызов соответствующей процедуры Соп1то11егСоп1то1. 3. В случае, если запрос требует ОМА операций, выполняет надлежащие операции над объектом адаптера и планирует вызов процедуры Ас1ар1:егСоп1то1 (которая будет вызвана, сразу же, как только условия исключительного доступа к соответствующему ОМА каналу будут удовлетворены).
4. Использует процедуру 5упсИСп15ес1юп для выполнения безопасного доступа к тем ресурсам, которые могут потребоваться процедуре обработки прерываний. 5. Возвращает управление Диспетчеру ввода/вывода в ожидании сигналов прерывания от устройства. Пример драйвера с использованием механизма 5у51ет (Зиеслпд подробно рассматривается в главе 11.
Процедура обслуживания прерываний 15П При возникновении прерываний, диспетчер прерываний (из состава кода ядра) производит вызов драйверной процедуры. Процедура 15Р. обычно выполняет следующие действия: 1. Проверяет, ожидалось ли прерывание (относится ли поступившее прерывание к обслуживающему устройству). 2. Освобождает (завершает) прерывание. 3. В случае, если ранее была начата операция программируемого ввода/вывода (не ОМА), но передача данных еще не завершена окончательно, процедура 15Р. могла бы начать операцию передачи следующей порции данных и завершить свою работу, пока она не будет вызвана по поводу следующего прерывания. 4. В случае, если ранее была начата операция ОМА и остались еще не переданные данные, то 15Р. могла бы запланировать вызов ОРС процедуры для настройки ОМА аппаратуры и передачи следующей порции данных. 5. В случае, если произошла ошибка или передача данных не была завершена, то 15Р. могла бы запланировать вызов ОРС процедуры для того, чтобы выполнить пост-обработку при более низком уровне 1НР1_.
Пост-обработка, выполняемая драйвером Диспетчер ОРС выполняет вызовы ОРС процедур драйвера для того, чтобы решать задачи пост-обработки, а именно: 1. В случае, если выполнялся набор операции по переносу данных и некоторая часть данных еще не была передана, ОРС процедура производит установку аппаратуры, производит "старт" устройства и, в ожидании нового прерывания, возвращает управление Диспетчеру ввода/вывода. При этом 1Р.Р пакет остается пока в состоянии 'репсНпд' (о его завершении будет объявлено позже — по окончании переноса). 2. В случае, если произошла ошибка или превышено время ожидание отклика (таймаут), ОРС процедура может записать это событие во внутреннюю очередь, поддерживаемую для данного объекта устройства, и затем либо сделать повторную попытку, либо прервать обработку запроса на ввод/вывод. Очереди 1Р.Р пакетов для устройств, Оеу!се (^иеие, могут поддерживаться драйверами как альтернатива системным очередям (5уз1:ет (^иеитд). 3. ОРС процедура освобождает необходимые для переноса ресурсы, удерживаемые драйвером (среди которых могут быть ОМА ресурсы). 4. ОРС процедура помещает размер переданных данных и информацию о финальном состоянии в 1Р.Р пакет. 5. Наконец, если обработка 1Р.Р пакета действительно завершена, ОРС процедура сообщает Диспетчеру ввода/вывода об окончании обработки текущего 1Р.Р запроса тем, что помечает его как завершенный
(вместо 'репсНпд' — ожидающий обработки) и делает вызов 1о§1аг1№х1Раске1. Это указывает Диспетчеру ввода/вывода на то, что следует переходить к вызову процедуры 51:аг1:1о для следующего 1КР пакета, если таковой ожидает обработки.
Пост-обработка, выполняемая Диспетчером ввода/вывода После того как ОРС процедура или одна из рабочих процедур помечают 1РР пакет как завершенный, Диспетчер ввода/вывода (разумеется, когда получит управление) осуществляет завершающие действия, которые в литературе и документации ООК собирательно называются "очисткой" (с1еапир). Это означает: 1. В том случае, если выполнялась операция записи при буферизованном вводе/выводе (ЬиГГегес! 1/0), Диспетчер ввода/вывода освобождает буферную область, использованную во время только что завершенного переноса данных. 2. В случае, если выполнялась процедура прямого ввода/вывода (сНгес1:1/0), Диспетчер ввода/вывода отменяет фиксацию (1оск) в оперативной памяти тех страниц, в которых размещался пользовательский буфер. 3. Диспетчер ввода/вывода помещает в очередь запрос к программному потоку, инициатору первоначального запроса, на выполнение асинхронного процедурного вызова (азупсЬгопоиз ргосес1иге саН, АРС), работающего в режиме ядра. Этот АРС вызов будет выполнять программный код Диспетчера ввода/ вывода в контексте потока-инициатора запроса на операцию ввода/вывода. 4. Выполняющийся в режиме ядра программный АРС- поток производит перенос информации о состоянии и размере переданных данных в пространство пользовательского приложения. 5. В случае, если выполнялась процедура чтения категории буферизованного ввода/вывода (ЬиГГегес!
1/0), процедура АРС производит копирование содержимого буферной области, размещенной в нестраничном пуле памяти, в область памяти, предоставленную пользовательским приложением в качестве рабочего буфера. Затем освобождается системный буфер в нестраничном пуле. 6. В случае, если исходный запрос был сделан для выполнения асинхронного ввода/вывода, процедура АРС устанавливает ассоциированное событие (еуеп!:) и/или файловый объект в состояние, сигнализирующее пользователю об окончании обработки запроса. (Если читатель не знаком с асинхронным вводом/выводом, то может получить доступ к его описанию в документации М50Ы по ключевым словам ОУЕкЬАРРЕЭ, СапсеПо, Неас1Г|1еЕх и т.п.) 7. В случае, если исходный запрос содержал процедуру завершения (как, например, при использовании функций пользовательского режима Кеас1Н1еЕх/УУп1еН1еЕх в пользовательском приложении), АРС процедура производит планирование вызова в дальнейшем АРС процедуры пользовательского режима, которая выполнит вызов процедуры завершения, указанных в параметрах вызовов Кеас1Н1еЕх/УУп1еН1еЕх.

Заключение Краткое введение в подсистему ввода/вывода операционной системы \Л/1Пс1о\л/5 1\1Т 5 завершено. Полностью, хотя и весьма кратко, описана структура драйвера \Л/1Пс1о\л/5 2000/ХР/5еп/ег 2003, который предназначен для работы в режиме ядра. Прежде, чем перейти к деталям реализации драйверных процедур, остановимся, в следующей главе, на некоторых практических приемах программирования в режиме ядра, а именно — на работе с памятью, строками 11п1сос1е, объектами и т.п.
Глава 7
Приемы программирования в режиме ядра Приемы программирования в режиме ядра носят характерный отпечаток: здесь имеются свои ограничения и тактика, применяется свой набор служебных функций, не совпадающий с АР1 набором пользовательского режима. Наконец, на использование системных функций в режиме ядра влияет приоритет программного кода, из которого предполагается их вызов: некоторые функции не могут быть вызваны при слишком высоких уровнях 1ЕЩ1-, некоторые, наоборот, не должны применяться на низких. Вспомним, что в пользовательском режиме приоритет потока никак не влиял на набор библиотечных функций — имеющиеся функции можно было вызывать без ограничений.
ГРгеуюц51 ГИехН
Соглашения об описании данных и функций Дополнительные описатели типов В документации и примерах 00К можно видеть относительно новые способы задания типов данных, которые внедряются там примерно с тем же энтузиазмом и размахом, как ранее это происходило с так называемой венгерской нотацией. Помимо типов данных, представляющих сложные данные (структуры и объединения, типа ОН1\/ЕН_ОВЗЕСТ), в драйверных исходных текстах широко применяются новые обозначения известных стандартных типов данных. Заголовочный файл п1:с1еГ.И содержит множество определений, которые придают новый вид старым и хорошо известным описателям типов языка С. Например, описатели беззнаковых целых типов и указателей на переменные такого типа принимают вид: СуресАеР ипзтдпесА сНаг ИСНАР; СуресАеТ ипзтдпесА зНогС ИЗНОЕТ; СуресАеА: ипзтдпесА 1опд ИЬОИС; РуресАеТ ИСНАЕ *РИСНАЕ; СуресАеР ИЗНОЕТ *РИЗНОЕТ; ЬуресАеТ ИЬОИС *РИЬОИС; СуресАеТ ипзтдпес! _1ПР64 ИЬОИСЬОМС; ЬуресАеТ ИЬОИСЬОИС *РИЪОМСЪОМС; Для чего это затеяна эта игра? В основном — для унификации стиля, когда в обращение вводятся совершенно новые типы данных, которые не являются базисными для языка С, скажем типа МСНАК. (двухбайтный Ктсос1е символ), или типа данных (а на самом деле — объединения) 1_АН6Е_1НТЕСЕН, наиболее часто используемого при программировании таймерных объектов, например: СуресАеТ ипгоп _ЪАЕСЕ_1МТЕСЕЕ { зИгисР { ИЬОИС ЬоиРагР; ИОИС НгдИРагР; }; зСгисС { ИЬОИС ЬоиРагР; ИОИС НгдИРагС; } и; ЪОМСЪОМС ОиасАРагС; } ЬАЕСЕ 1ИТЕСЕЕ;
Другая цель этих дополнений — достичь универсальности исходных текстов при переходе с 32-разрядных платформ на 64-разрядные.
Квалификаторы 114, ООТ, ОРТЮЫАЬ Еще одним небесполезным элементом украшения программного кода, активно используемого в ООК, являются макроопределения 11М, О11Т и другие. Как можно увидеть в файле п1:с1еГ.И, они не обозначают ровным счетом ничего, но зато повышают наглядность программного кода при его чтении, поскольку сообщают о назначении переменных, рядом с которыми используются. (Поскольку они ничего не значат, что является нетипичным для программирования, то позволим себе применить к ним нетипичный термин "квалификатор", который тоже ничего особенного не обозначает.) #с!е^1пе #с!еЛпе ОПТ #с1еНпе ОРТЮЫАЬ #с1еНпе СК1Т1САБ Если обратиться к использованию этих элементов, то можно рассмотреть определение функции Н112егоМетогу, которая может быть использована для обнуления некоторой области виртуальной памяти (обнуляет страничную память на уровнях 1Р.С)1_ только ниже О15РАТСН_1_Е\/Е1_, нестраничную — на любых), а именно: УОШ К-ЫХегоМетогу (1М УОЮ 1ЖАШСМЕ0 *Вез-Ыпа-Ыоп, 1М ЪепдСЬ) ; В этом описании квалификатор 1Ы сообщает, что указатель Оезйпайоп и длина буфера ЬепдЧп являются параметрами, передаваемыми внутрь вызова Н112егоМетогу. В иных случаях бывают даже сочетания 1Ы О11Т, что сигнализирует: через этот параметр вызова информация передается внутрь данного вызова и возвращается обратно. Квалификатор ОРТЮМАЬ обозначает необязательные параметры.
Типы возвращаемых значений функций Все функции, описываемые в языке С, либо возвращают значение определенного типа, либо не возвращают ничего (в последнем случае они описываются типом уоИ). В переводе на новые термины, предлагаемые ОКК, функции возвращают значения типа СНАН, 11СНАН, 5НСЖТ, 115НОНТ и т.п., либо описываются как УОГО функции. В дополнение к этим, знакомым и понятным типам функций в ООК добавлен тип 1МТ5ТАТ115. Что означает этот новый тип? Тип МТ5ТАТ115 несет код завершения функции и, на самом деле, является простым переопределением типа 1п1:едег 1опд, что можно увидеть, например, в файле пМеГ.И, а именно: буребе:Е ЪОМС МТЗТАТ113; ТуребеЕ ПТЗТАТОЗ *РМТЗТАТИЗ; Неотрицательные значения переменных этого типа (попросту говоря, неотрицательные целые значения) относятся к кодам удачного завершения и предупреждениям, отрицательные — сигнализируют об ошибке. Когда Диспетчер ввода/вывода вызывает одну из процедур драйвера, а она возвращает ему управление, передавая при этом код ЫТ5ТАТ115, то сигнализирует ему об условиях окончания работы. Файл ЫТ5ТАТ115.И содержит символьные имена для всех возможных значений кодов 1МТ5ТАТ115. Код, сообщающий об удачном завершении, носит имя 5ТАТ115_511ССЕ55 (кстати, равный 0). В случае, если работа завершена с ошибкой, код неудачного завершения, например, 5ТАТ115_11МЕХРЕСТЕО_Ю_ЕННОП. (равный 0хС00000Е9), транслируется системой в системный код ошибки и передается пользовательскому приложению, для которого работала данная процедура драйвера. Для работы с кодами завершения МТ5ТАТ115 существуют несколько полезных макроопределений (описанных в файле п!:с1еГ.И), среди которых самым употребительным является макроопределение ЫТ_511ССЕ55(). При употреблении в логических выражениях, оно позволяет проверять, означает ли анализируемый код удачное завершение работы, например: МТЗТАТОЗ Збабиз = ТоСоппесбТпбеггирб(); И( !МТ_ЗОССЕЗЗ(Збабиз) ) { Собработка ошибки> }
Соглашения об именах функций драйвера и системных вызовов Не последнюю роль в разработке программного продукта играют правила составления идентификаторов и размещения исходного текста в файлах. Если хорошие правила вырабатываются годами участия в крупных проектах, то плохие каждый может сформулировать без труда: называйте функции и переменные случайным образом, а еще лучше — х1, х2, Гипс1зоп133 и т.п. Поскольку МкгозоН: следует определенным правилам составления имен своих вызовов, то системные функции отличить в тексте драйвера несложно: они имеют префикс из числа представленных в таблице 4.1, например, На1(зеНп1еггир(Уес(ог, вызов, относящийся к множеству аппаратных абстракций, что обозначено префиксом На1. Правда, МкгозоГ! все-таки не до конца последовательно проводит эту линию в жизнь. Например, в именах функций, обслуживающих объект адаптера (структура ОМА_ОРЕНАТЮ№), нет уже никаких префиксов такого типа, как, скажем, в именах ЕгееАс1ар(егС11аппе1, Са1си1а1е5саЧег0аЧ1егЬ15(: и АИоса^еСоттопВиГГег. В пакете ООК все имена типов данных и все макроопределения вводятся прописными (большими) буквами, например, РУОГО, РНУ51СА1__АООНЕ55, НЕАО_РОНТ_11СНАН. Упоминание о так называемой венгерской нотации в ООК практически не встречается, однако, МкгозоГ! рекомендует разработчикам драйверов использовать собственные короткие префиксы для обозначения собственных функций и, возможно, идентификаторов переменных.
ГРгеуюив! [Нех1]
Операции с плавающей точкой Редко, но все-таки случаются ситуации, когда необходимо использовать операции с плавающей точкой и, соответственно, задействовать сопроцессор (ЕР11). Особенность этой ситуации состоит в том, что в режиме ядра программный поток, намеревающийся использовать сопроцессор, обязан сохранить текущее состояние всех регистров сопроцессора (5Т0-5Т7, ММХ0-ММХ7, ХММ0-ХММ7). Для сохранения этой информации в наборе системных вызовов режима ядра предусмотрены функции Ке8ауеНоа1тдРот181а1е и КеКе81огеР1оайпдРот181а1е, первая из которых сохраняет состояние регистров сопроцессора в предоставляемом буфере, описываемом типом КЕ1_ОАТ11\1С_5А\/Е, а вторая выполняет восстановление состояния сопроцессора по окончании работы с ним. Пример кода для работы с сопроцессором в режиме ядра приводится ниже: йоиЫе ХЫоаС; МТЗТАТОЗ зСаСиз; КЕЪОАТ1МС_ЗАУЕ заVес^ЕР^^аСа ; зСаСиз = КеЗаVеЕ1оаЬ^пдРо^пЬЗЬаЬе (&заVес1ЕР^^аЬа) ; 1Г (МТ_311ССЕЗЗ (зСаЕиз) ) {// Работа с сопроцессором ХЕ1оаС = 0.; // Восстановление состояния сопроцессора из заVес1ЕР^^аЬа : КеКез'ЬогеЕРоабтпдРотп'ЬЗ'Ьабе (&заVес1ЕР^^аЬа) ; } Следует обратить внимание на то, что работа с сопроцессором выполняется только в случае, если удачно завершен вызов функции Ке8ауеНоа1тдРот181а1е, которая должна вызываться на уровне 1Р.р1_ не выше О15РАТС1-1_1_Е\/Е1_. Вызов функции КеКе51огеНоа1тдРо1п181а1е для восстановления состояния сопроцессора должен выполняться на том же уровне, что было выполнено сохранение.
Операции с памятью Операционная система \Л/1пс1о\л/з оперирует тремя типами адресов: Виртуальные адреса, которые транслируются в физические адреса перед доступом к области памяти. Физические адреса, которые реально указывают в область физической памяти. Следует отметить, что по этим адресам содержимое памяти всегда появляется на шине доступа к памяти, независимо от того, реально ли она присутствует в ОЗУ, или содержится внутри обслуживаемых драйвером устройств. Логические адреса. Этот тип описывает специальные адреса, используемые уровнем НА1_ при общении с устройствами. Соответственно, уровень НА1_ и отвечает за операции с этими адресами. Работа по программированию в режиме ядра всегда связана с тонкостями работы с памятью. В каком контексте работает программный поток, какого типа памятью он манипулирует (пользовательской или режима ядра), какого типа память (страничная или нестраничная) используется, если идет работа с памятью режима ядра, наконец, приемлем ли текущий уровень приоритета 1П.р1_ для доступа к данному типу памяти? Разумеется, все эти вопросы возникают, только лишь, если разрабатывается код режима ядра. Адреса 4 гигабайтного виртуального пространства памяти 32-разрядных версий операционной системы У\Лпс1о\л/5 1МТ 5 (об отличиях для 64-разрядных версий было сказано в главе 4) делятся на 2 нижних гигабайта памяти пользовательского виртуального пространства, имеющего смысл только в контексте пользовательского приложения (процесса), которому оно выделено, и 2 верхних гигабайт системного виртуального пространства режима ядра. Системное адресное пространство доступно всем программным потокам режима ядра. (Иначе, как смогло бы работать программное обеспечение режима ядра собственно операционной системы?!) Все 4-х гигабайтное адресное пространство можно представить в виде книги с одной обложкой. Толстая обложка — это системное адресное пространство, тонкие бумажные листы — это виртуальные и автономные пользовательские адресные пространства. Системное виртуальное пространство памяти режима ядра делится на диапазоны (обычная архитектура х86), представленные в таблице 7.1. Адреса с ОхСООООООО по 0хС0800000 используются для хранения данных Менеджера памяти, который поддерживает механизм виртуальной памяти. Диапазон с ОхЕЕВЕОООО по ОхЕЕСООООО используются для хранения информации о страничном файле (файле подкачки), которая используется для сброса содержимого физической памяти в этот файл. (Методология сгазЬ дампа предусматривает создание сгазЬ бигпр файла из этого файла подкачки при следующей загрузке системы.) Адреса с ОхЕЮООООО по ОхЕЕВЕОООО занимают области странично и нестранично организованной памяти, что вместе составляет менее 500 мегабайт. Какие трюки можно проделывать с виртуальной памятью? На этот не слишком конкретный вопрос существует ответ в виде вопроса: а для достижения чего
именно? Рассмотрим довольно незамысловатую ситуацию, предпосылки которой рассматривались ранее. Предположим, драйвер создал программный поток (вызовом Р5Сгеа1е5у8(етТ11геас1), который в некоторой ситуации должен выполнять некоторую работу, например, по сигналу функции обработки ЮСТЬ запросов — выполнить перенос данных в буфер, предоставленный пользовательским приложением. Предположим, что разработчик драйвера так задал ЮСТЬ код (используя метод буферизации ЫЕ1ТНЕП.), что в драйвер поступает пользовательский адрес буфера (значение меньше 0x80000000). Разработчик драйвера через внутренние переменные передает этот адрес программному потоку, который должен выполнить перенос, и... Наступает сбой системы. Что случилось? Чтобы объяснить сложившуюся ситуацию и решить проблему, необходимо, прежде всего, согласиться с тем, что простые приемы пользовательского режима следует оставить пользовательскому режиму. Когда драйверная функция обработки ЮСТЬ запросов пользовательского режима (по вызову Оеу1се1оСоп1го1) получает адрес пользовательского буфера по методу буферизации ЫЕ1ТНЕ8., то этот пользовательский виртуальный адрес имеет смысл в этой функции, поскольку она работает в контексте пользовательского программного потока и интерпретация пользовательского виртуального адреса не вызовет проблем. Другое дело, если этот адрес окажется в распоряжении программного потока, созданного драйвером по вызову Р8Сгеа(е8у81етТЬгеас1, где интерпретация данного адреса вызовет ошибку, поскольку, как указано в документации ООК, этот системный программный поток не имеет никакого пользовательского контекста. Это одна из проблем. Таблица 7.1. Диапазоны памяти системного адресного пространства \Л/1пс1ош8 2000 ОхЕЕЕЕЕЕЕЕ НА1_ 0 Информация СКА5Н Э11МР 0 Нестраничный пул Страничный пул Файловый кэш Пространство файлового кэш-менеджера 'хЕЕСООООО 'хЕЕВЕОООО ЭхЕЮООООО ЭхСЮООООО ЭхСОСООООО Не используется Зарезервировано ЭхС0800000 ЭхС0400000 Элементы директории страниц (РЭЕ) ЭхСОЗООООО Элементы таблицы страниц (РТЕ) ЭхСООООООО Не используется Метогу Маррее! ГНез ЭхАЗОООООО Копия операционной системы 0x80000000
Вторая возможная проблема состоит в том, что к моменту обращения созданного программного потока к пользовательскому буферу, рассматриваемое пользовательское пространство вполне может оказаться в страничном файле на жестком диске, и (если поток повысил свой приоритет) неминуемо случится сбой системы, поскольку операционная система запрещает обработку таких ситуаций на высоких уровнях 1Р.С)1_. Чтобы решить задачу в данной постановке необходимо, во-первых, создать список МО1_ {структуру, хранящую отображение блока виртуальной памяти на физическую память), зафиксировать пользовательский буфер в физической оперативной памяти и передать МО1_ список (или соответствующий виртуальный адрес системного адресного пространства) рабочему потоку. По выполнении работы, следует разблокировать страницы пользовательского буфера и освободить структуру МО1_ списка. Соответственно, при этом делаются вызовы системных функций: 1оА11оса1еМс11, МтРгоЬеАпсИ-ОскРадев, Мт<зе15у81етАс1с1ге88РогМс11, Мт11п1оскРаде8 и 1оРгееМс11. Вызов 1оА11оса1еМс11 создает структуру МО1_ списка для указанного виртуального адреса (пользовательского или системного адресного пространства) с указанной длиной. Вызов МтРгоЬеАпсИ-оскРадез проверяет присутствие страниц в физической памяти, подгружает (для страничной памяти), если они отсутствуют, и производит их фиксацию (после чего данные страницы не могут быть сброшены в страничный файл на жестком диске). Для корректного вызова МтРгоЬеАпсИ-оскРадез по поводу МОЕ, составленного для страничной памяти, программный поток должен работать на уровне 1П.р1_ ниже О15РАТСН_1_Е\/Е1_ — чтобы позволить отработать системному коду, если страница окажется в страничном файле. (В случае, если исходный буфер находился бы в нестраничной памяти, то данный вызов можно было бы сделать с уровня 1П.р1_ равном О15РАТСН_1_Е\/Е1_ или ниже.) Функция Мт(зе18у81етАс1с1ге88РогМс11 возвращает виртуальный адрес, вычисленный из МОЕ списка, так, будто рассматриваемая область памяти находится в системном адресном пространстве, а именно — в нестраничном пуле. Этот адрес можно использовать в любом месте кода драйвера на любых уровнях 1Р.р|_, даже в процедуре обработки прерываний. Контекст выполнения для этого адреса не имеет никакого значения. (Справедливости ради, следует отметить, что перевод в системный адрес не является обязательной операцией, можно и далее использовать МОЕ список, что позволяют делать, в частности, вызовы нижних драйверов в стеке.) Вызов (точнее, макроопределение) Мт<зе15у81етАс1с1ге88РогМс11 является устаревшим, и его следует использовать только в \Л/ОМ драйверах, предназначенных для работы еще и в У\Лпс1о\/У5 98. Использование макроопределения Мт<зе15у81етАс1с1ге88РогМс118аГе является предпочтительным. Оба эти макроопределения могут быть вызваны из кода, работающего на уровне 1Р.С)Е не выше О15РАТН_1_Е\/Е1_. Вызов Мт11п1оскРаде8 отменяет фиксацию страниц в оперативной памяти, а вызов 1оРгееМс11 уничтожает структуру МОЕ списка. Чтобы отследить ошибки, связанные с фиксацией блока виртуальной памяти в памяти оперативной, рекомендуется выполнять вызов МтРгоЬеАпсИ-оскРадез внутри Ьгу-ехсерЬ блока, например:
__кгу { МтРгоЬеАпсШоскРадез ( рМс11, ЕзегМоде, РоМосНРуАссезз) ; } __ехсерк ( ЕХСЕРТ10М_ЕХЕС11ТЕ_НАШЬЕК) { р1гр->1оЗкакиз.Зкакиз = ЗТАТОЗ_АССЕЗЗ_У1ОЪАТ1ОМ; р1гр->1оЗкакиз.1пРогтак1оп = 0; 1оСотр1екеКедиезк (р!гр, 1О_ЫО_1ПСКЕМЕПТ) ; гекигп ЗТАТиЗ_СОМГЫСТ1МС_АООКЕЗЗЕЗ; } На рассмотренном выше примере видно, что операционная система предоставляет достаточно инструментов для преобразования адресов пользовательского пространства в адреса системного пространства и физические адреса. Важно только корректно отслеживать, когда и что следует использовать. Ниже приводятся более полные сведения о системных функциях, которые полезны для работы с виртуальной и физической памятью.
Вызовы для выделения и освобождения областей виртуальной памяти Для манипуляций с областями памяти (выделение и освобождение) в режиме ядра используются специальные системные вызовы, отличающиеся от АР1 вызовов режима ядра: ЕхА11оса1еРоо1, ЕхА11оса1еРоо1УУШТГад, ЕхРгееРоо!. В режиме ядра возможно выделение и освобождение области физически непрерывной памяти, что выполняется при помощи вызовов МтАНосаЕеСопйдиоизМетогу и, соответственно, МтРгееСопИдиоизМетогу. Эти вызовы рассмотрены ниже. Таблица 7.2. Прототип вызова ЕхА11оса1еРоо1 РА/ОЮ ЕхА11оса1еРоо1 < О15РАТСН_ЬЕУЕЬ Параметры Выполняет выделение области памяти 1Ы РОО1__ТУРЕ РооГГуре Тип виртуальной памяти, в которой следует выделять область. Наиболее употребительны значения: • Радес1Роо1 — страничная • ЫопРадейРоо! — нестраничная, тогда данную функцию можно вызывать при любом уровне ТРС^Ь 1Ы 1Л_О1\1С МитЬегОГВу^ез Размер запрашиваемой области Возвращаемое значение Указатель на выделенную область либо 1\1СН_1_ (в случае, если память выделить невозможно). Выделенная область всегда выравнивается на 8 байт. В случае, если запрашиваемый размер превышает РАСЕ_517Е, то выделяемая область выравнивается на размер страницы. Таблица 7.3. Прототип вызова ЕхАПоса1еРоо1]Л/ННТад РУОЮ ЕхА11оса1еРоо1\ЛЛ1НТад 11М21. < О15РАТСН_ЬЕУЕЬ Параметры Выполняет выделение области памяти 1Ы РОО1__ТУРЕ РооГГуре См. описание ЕхА11оса1еРоо1 выше. 1Ы 1Л_О1\1С МитЬегОГВу^ез Размер запрашиваемой области 1Ы 1Л_О1\1С Тад Метка (тег) для данной области, можно задавать как 4 символа, например, 'АВСЮ'. Удобно для отладки. Возвращаемое значение См. описание ЕхА11оса1еРоо1 выше. Таблица 7.4. Прототип вызова ЕхРгееРоо/ УОЮ ЕхРгееРоо! 11М21. < О15РАТСН_ЬЕУЕЬ Параметры Выполняет освобождение области памяти 1Ы РУОЮ рВиГГег Указатель на освобождаемую область памяти, выделенную вызовами ЕхА11оса1еРоо1 или ЕхАНоса1еРоо1У\Л1НТад. Если освобождается нестраничная память, вызов может быть сделан из кода на уровне □15РАТСН_1_ЕУЕ1_ Возвращаемое значение УО1С1 Таблица 7.5. Прототип вызова МтАПосаЪеСопНдиоизМетогу РУОЮ МтА11оса1еСопНдиои8Метогу == РА551УЕ_ЬЕУЕЬ Параметры Выполняет выделение физически непрерывной области памяти 1Ы 1Л_О1\1С МитЬегОГВу^ез Размер запрашиваемой области 1Ы РНУ51СА1__А00КЕ55 Верхний предел адресов для запрашиваемой области. Поле НН д Ь Ра И:=0,
тахАссер1аЫеАс1с1ге55 поле 1_о\л/РагС принимает значения, например: • ОхОООЕЕЕЕЕ (до 1 МБ) • ОхООЕЕЕЕЕЕ (до 16 МБ) • ОхЕЕЕЕЕЕЕЕ(до 4ГБ) Виртуальный адрес или 1\1111_1_ (при неудаче). (Для повышения вероятности успешного завершения рекомендуется р щ выполнять вызов в Ог1УегЕп1гу, поскольку память при работе системы быстро становится сильно дефрагментированной) Таблица 7.6. Прототип вызова МтРгееСопНдиоивМетогу У,О1Р .. 1КО1. == РА551Х/Е ЬЕУЕЬ МтЕгееСопйдиоизМетогу — Параметры Выполняет освобождение области памяти 1Ы Р\/ОЮ ВиГГег Указатель на область памяти, выделенную ранее с использованием р системного вызова МтАНоса1еСоп1|диои5Метогу Возвращаемое значение ВОО^ЕА^ Мт15Ас1с1ге55УаНс1 УО1С1 Таблица 7.7. Прототип вызова МшТвАсЫгеззУаИс! 1Я<21. <= О13РАТСН_1_ЕУЕ1. Параметры 1Ы РУОЮ У|г1иа1Ас1с1ге55 Выполняет проверку виртуального адреса Виртуальный адрес, который следует проверить Возвращаемое значение Т1ШЕ — если присутствует в оперативной памяти ЕАЬБЕ — если вызовет прерывание РАСЕ ЕА111_Т Замечание. Если адрес не находится в нестраничной памяти (или не зафиксирован в оперативной памяти), возврат ТР.11Е не гарантирует отсутствие проблем при работе на повышенных уровнях 1П<21_ (поскольку к моменту использования данного виртуального адреса ситуация может измениться). РНУ51СА1-_АООКЕ55 МтСе1РНу51са1Ас1с1ге55 Таблица 7.8. Прототип вызова МтСе1РНу81са1Ас1с1ге88 1Вр1_ — любой Параметры Определяет физический адрес, соответствующий данному виртуальному 1Ы РУОЮ У|г1иа1Ас1с1ге55 Возвращаемое значение Анализируемый виртуальный адрес Физический адрес. Перед данным вызовом следует воспользоваться Мт15Ас1с1ге55УаНс1
Работа с ассоциативными списками Иногда выделение памяти при помощи системных вызовов, описанных выше, становится неоптимальным. Например, частое выделение и освобождение мелких блоков при помощи системных вызовов оказывается причиной существенного падения быстродействия. Между тем, если известно заранее, что манипуляция будет производиться блоками определенного и постоянного размера, то имеет смысл организовать "локальную кучу", которая управлялась бы более производительными функциями. Такую возможность предоставляет механизм ассоциативных списков (1ооказ1с1е Нз!), который реализован в системных вызовах, представленных ниже. Поначалу, ассоциативный список — это всего лишь заранее созданный заголовок, который должен хранить информацию о состоянии списка, и не содержит никаких выделенных блоков памяти. По мере выполнения вызовов ЕхА11оса1еРгот(1Ч)Радес11.оока51с1е1-181 (таблицы 7.9 и 7.10) такие блоки создаются — либо системным вызовом ЕхАНоса1еРоо1УУШтТад, либо внутри предоставленной драйвером функции (указанной драйвером параметром рАПосЕипсйоп). По мере создания и, возможно, последующего освобождения выделенных ранее блоков (системным вызовом ЕхРгееРоо!, либо предоставленной драйвером функцией), ассоциативный список может оказаться держателем некоторого количества блоков фиксированного размера в страничной либо нестраничной памяти (в зависимости от способа инициализации). Таблица 7.9. Прототип вызова Ех1гнНаП2еРадес11-оока51с1е1-15{ РУОЮ Ех1тНаНхеРадес11-оока51с1е1-151 Параметры 1Ы РРАСЕО_1_ООКА5ЮЕ_1_15Т р1_оока51с1е1_151Неас1ег 1К<21- < О15РАТСН_ЬЕУЕЬ Создание ассоциативного списка блоков страничной памяти Указатель на предварительно выделенную драйвером область размером 512еоГ(РАСЕО_ЬООКА5ЮЕ_Ы5Т) Остальные параметры совпадают с параметрами вызова Ех1пШаНхеЫРадес11-Оока51с1е1-151, см. ниже таблицу 7.10 Возвращаемое значение УО1с1 В настоящий момент, \Л/1пс1о\л/5 ХР и 5еп/ег 2003 самостоятельно и динамически определяет максимальное число элементов в ассоциативном списке. Для \Л/1пс1о\л/з 2000 в качестве такого параметра был анонсирован параметр ОерИп. (К сожалению, в документации способ определения значения этого параметра умалчивается, начиная с версии ООК У\Лп98.) Следует отметить, что до достижения данного максимума освобождаемые драйвером блоки не возвращаются в системную память, оставаясь в составе списка. Если в списке имеются ранее освобожденные блоки, и поступил запрос на новый блок, то предоставляется указатель на один из них. Ситуация кардинально меняется если максимум достигнут. При запросе нового блока (и при этом ранее освобожденных блоков в списке нет) память под него берется непосредственно из системной памяти и при освобождении сразу же возвращается в соответствующий пул системной памяти — иными словами, исчезают преимущества использования ассоциативного списка. Таблица 7.10. Прототип вызова Ех1пН1аИ2еЫРадес11-оока51'с1е1-151: УОЮ ЕхТпШаНгеНРадесИ-ооказИеЫб»: 1Крь <= О15РАТСН_1_ЕУЕ1_
Параметры 1Ы РНРАСЕО_1_ООКА5ЮЕ_1_15Т р1_оока51с1е1_151:Неас1ег Создание ассоциативного списка блоков нестраничной памяти Указатель на предварительно выделенную драйвером область размером 512еоГ(НРАСЕ0_1_00КА5ЮЕ_1_15Т) 1Ы ОРТЮЫАЬ РА1_1_ОСАТЕ_ рАПосЕипсйоп _Р1Л\1СТЮЫ 1\1СП_1_ или указатель на предоставляемую драйвером функцию, которая будет заниматься выделением блоков из массива системной нестраничной памяти (если 1\1СП_1_ — будет использован системный вызов ЕхАНоса1еРоо1У\Л1НТад) 1Ы ОРТЮЫАЬ РРКЕЕ_Ри1\1СТЮЫ рРгееРипсйоп или указатель на предоставляемую драйвером функцию, которая будет заниматься освобождением блоков (если 1\1111_1_ — будет использован вызов ЕхЕгееРоо!) 1Ы 1Л_О1\1С Р1адз Зарезервировано. Указывать 0 1Ы 1Л_О1\1С Ву1:е5|2е Размер отдельных блоков, поддерживаемых данным списком 1Ы 1Л_О1\1С Тад Метка (тег) для создаваемых блоков, можно задавать как 4 символа, например, 'АВСЮ' 1Ы ЬЗНСЖТ Юер1:Г| Зарезервировано. Указывать 0 Возвращаемое значение УО1С1 Перед вызовом процедуры инициализации, для хранения заголовка ассоциативного списка драйвер должен получить область в нестраничной памяти размером 812еоГ(РАСЕ0_1_00КА5ЮЕ_1_15Т) или 812еоГ(МРАСЕ0_1_00КА5ЮЕ_1_15Т) — в зависимости оттого, какой список инициализируется. Для этих целей можно использовать описанные выше системные вызовы ЕхА11оса1еРоо1 или ЕхА11оса(еРоо1УУ!(1ТГад. В конце работы со списком обязательно следует выполнить вызов ЕхОе1е(е(1Ч)Радес11-оока51с1е1-!8(. Функции, на которые указывает рАНосЕипсйоп, имеют прототип: РУО1Б МуАИосаЪеГипсЫоп ( 1М_РООЪ_ТУРЕ РооРТуре, // РадесЗРоо! или МопРадейРоо! иьОРКЗ МитЬегОГВубез, // размер 1Ы ПЬОРГС Тад // тег Функции, на которые указывает рЕгееЕипсйоп, имеют прототип: РУО1Б МуГгееЕипс'Ыоп (РУО1Р рВиРРег) ; Таблица 7.11. Прототип вызова ЕхА11оса1еРгот№адес11.оока8к1е1.181 РУОЮ ЕхА11оса1еЕгогт^ Радей Ьоока51с1е1-151 <= О15РАТСН_ЬЕУЕЬ Параметры Выполняет выделение блока памяти из нестраничного списка 1Ы РЫРАСЕ0_1_ООКА5ЮЕ_1_15Т р1_ооказ1с1е1_151: Указатель на инициализированный ассоциативный список Возвращаемое значение Указатель на блок фиксированного размера или 1\1С11_1_ (если функция выделения памяти не смогла получить очередной блок) Таблица 7.12. Прототип вызова ЕхАПоса1еРготРадес11.оока81с1е1.1"81 РУОЮ ЕхАНоса1еЕготРадес11-оока51с1е1-151 Параметры < О15РАТСН_ЬЕУЕЬ Выполняет выделение блока памяти из страничного
списка 1Ы РРАСЕ0_1_00КА5ЮЕ_1_15Т р1_оока51с1е1_151: Указатель на инициализированный ассоциативный список Возвращаемое значение Указатель на блок фиксированного размера или 1\1С11_1_ (если функция выделения памяти не смогла получить очередной блок) Таблица 7.13. Прототип вызова ЕxЕ^ееТо^Радес^^оока8^с^е^^8^: УОГО ЕхЕгееТо№адес11-оока51с1е1-151 1Кр1_ <= О15РАТСН_ЬЕУЕЬ Параметры 1Ы РНРАСЕО_1_ООКА5ЮЕ_1_15Т р1_ооказ1с1е1_151: Возвращает блок в нестраничный ассоциативный список Указатель на инициализированный ассоциативный список 1Ы РУОЮ рЕп^гу Возвращаемое значение Указатель на ранее полученный из списка блок фиксированного размера УО1С1 Таблица 7.14. Прототип вызова ЕхРгееТоРадесИ-Оока81(1еиз1 Х/ОЮ ЕхЕгееТоРадес11-оока51с1е1-151 1К<21- < О15РАТСН_ЬЕУЕЬ Параметры 1Ы РРАСЕО_1_ООКА5ЮЕ_1_15Т р1_ооказ1с1е1_151: Возвращает блок в страничный ассоциативный список Указатель на инициализированный ассоциативный список 1Ы РУОЮ рЕп^гу Возвращаемое значение Указатель на ранее полученный из списка блок фиксированного размера УО1С1 Таблица 7.15. Прототип вызова Еx^еIе^е^Радес^^оока5^сIе^^5^: Х/ОЮ ЕхОе1е1е№адес11-оока51с1е1-151 1Кр1_ <= О15РАТСН_ЬЕУЕЬ Параметры Выполняет удаление нестраничного ассоциативного списка 1Ы РНРАСЕО_1_ООКА5ЮЕ_1_15Т р1_ооказ1с1е1_151: Возвращаемое значение Указатель на ассоциативный список УО1С1 Таблица 7.16. Прототип вызова ЕхОе1е{еРадес1Еоока81с1еи81 Х/ОЮ ЕхОе1е1еРадес11-оока51с1е1-151 1К<21- < О15РАТСН_ЬЕУЕЬ Параметры 1Ы РРАСЕО_1_ООКА5ЮЕ_1_15Т р1_ооказ1с1е1_151: Выполняет удаление страничного ассоциативного списка Указатель на ассоциативный список Возвращаемое значение УО1С1
Работа с МОЬ списками Как было сказано выше, когда поступает ЮСТЕ запрос от пользовательского приложения, в котором метод буферизации указан МЕТНОО_11\1_О1К.ЕСТ либо МЕТНОО_О11Т_О1кЕСТ, Диспетчер ввода/вывода выполняет построение МОЕ списка для выходного буфера (5-й параметр в пользовательском вызове Оеу1се1оСоп(го1). Указатель на этот МОЕ список передается в драйвер внутри структуры 1Р.Р пакета. Драйвер может зафиксировать (1оск) область этого буфера в оперативной памяти вызовом МтРгоЬеАпсИ-оскРадез, после чего с этим буфером можно работать в разных контекстах, в том числе на высоких уровнях 1Р.С2Е (О15РАТСН_ЕЕУЕЕ и выше). Драйвер не обязан ограничиваться использованием только лишь получаемых от Диспетчера ввода/вывода МОЕ списков. Он может создавать собственные МОЕ списки, что бывает совершенно необходимо при работе с устройствами, поддерживающими ОМА операции. Ниже приводятся сведения о наиболее часто употребляемых функциях для работы с МОЕ списками. Таблица 7.17. Прототип вызова Мт812еО?Мс11 1Л-О1Ч6 Мт51хеОГМс11 Параметры 1Рр1_ — любой Определяет размер структуры для МОЬ списка, который будет описывать область данного размера 1Ы РУОЮ Вазе 1Ы 1Л_О1\1С Ьепд1:Г| Возвращаемое значение РУОЮ МтСгеа1еМс11 Виртуальный адрес области, для которой следует определить размер предполагаемого М01_ списка Размер описываемой области в байтах Размер, необходимый для хранения МЮ1_ списка Таблица 7.18. Прототип вызова МтСгеаЪеМсН 11М21. < О15РАТСН_1_ЕУЕ1. Параметры 1Ы РМ01_ ОРТЮЫАЬ рМетогуОезспр1юг1_151: 1Ы РУОЮ Вазе Создает МОЬ список и инициализирует его (в документации ООК ХР объявлена устаревшей функцией и вместо нее рекомендуется использовать вызов 1оА11оса1еМс11) Указатель на область размером не менее 512еоГ(М01_) или 1\1С11_1_. Во втором случае вызов самостоятельно выделяет память в нестраничной памяти. Виртуальный адрес области, для которой следует построить МЮ1_ 1Ы 1Л_О1\1С 1_епд1:Г| Возвращаемое значение Размер буфера в байтах Указатель на М01_ список или 1\1СП_1_ (при ошибке) РУОЮ 1оА11оса1еМс11 Параметры Таблица 7.19. Прототип вызова 1оА11оса1еМс11 шрь <= О15РАТСН_ЬЕУЕЬ Создает МОЬ список и инициализирует его. При этом можно выполнить его привязку к нужному 1КР пакету 1Ы РУОЮ УАс1с1ге55 1Ы 1Л_О1\1С 1_епд1:Г| 1Ы ВООЬЕАЫ 5есопс1агуВиГГег Виртуальный адрес области, для которой будет построен МЮ1_ список Размер виртуальной области, для которой следует построить МЮ1_ Если р1гр равен Г\1С11_1_, см. ниже, то и параметр 5есопс1агуВиГГег должен быть равен 1\1111_1_. Если параметр р1гр не равен 1\1111_1_, тогда при: ЕАЬБЕ — указатель на созданную структуру МЮ1_ списка будет занесен в поле р1гр-> Мс11Ас1с1 геэз
1Ы ВООЬЕАЫ СЬагде(2ио1:а 1Ы О1ГГ Р1Р.Р р1гр ТГШЕ — поле Ыех1: созданной МЮ1_ структуры будет указывать на МО1_ список, который прописан в поле р1гр->Мс11Ас1с1ге55 Если ТЯЦЕ — вычесть из квоты текущего программного потока на создание МО1_ списков. Обычно применяется ЕА1_5Е 14111-1- — не работаем с каким-либо 1Р.Р пакетом. Иначе — см. описание 5есопс1агуВиГГег выше Возвращаемое значение Указатель на МО1_ список. Замечание. Инициализацию МО1_ списка можно считать завершенной только после вызова МтРгоЬеАпс1коскРаде5, см. ниже Поэтапный способ самостоятельного создания МО1_ списка состоит в следующем. Получив в результате вызова Мт5!хеОГМс11 размер будущего МО1_ списка, драйвер должен выполнить выделение области памяти в нестраничном пуле, после чего можно переходить к инициализации МО1_ списка при помощи системного вызова Мт1пШаНхеМс11, описание которого приводится ниже. Таблица 7.20. Прототип вызова МтТпШаПгеМсН УОЮ МтХтНаНхеМсН 1крь <= О15РАТСН_ЬЕУЕЬ Параметры Выполняет инициализацию МОЬ списка 1Ы РМЮ1_ ОРТЮЫАЬ рМсП Указатель на буфер для хранения МЮ1_ списка. 1Ы РХ/ОЮ Вазе Виртуальный адрес области, для которой следует построить МЮ1_ 1Ы СП_О1\1(3 1_епд1:И Размер буфера в байтах УО1С1 Возвращаемое значение Замечание. Инициализацию МО1_ списка можно считать завершенной только после вызова МтРгоЬеАпс1коскРаде5, см. ниже Следует обратить внимание на то, как происходит завершение работы с МО1_ списками. Действия, выполненные функцией МтРгоЬеАпсИ-оскРадез, отменяются вызовом Мтип1оскРадез (см. ниже), а инициализированный МО1_ список следует "деактивировать" вызовом 1оРгееМс11. Затем, если драйвер перед инициализацией самостоятельно выделял память под структуру МО1_ списка, то ее также следует явно освободить соответствующим системным вызовом (например, ЕхРгееРоо!). Таблица 7.21. Прототип вызова 1оРгееМс11 УОЮ 1оЕгееМс11 Параметры 1Ы РМО1_ рМсП Возвращаемое значение <= О15РАТСН_ЬЕУЕЬ Выполняет очистку МОЬ списка Указатель на структуру, описывающую МО1_ список УО1С1 Таблица 7.22. Прототип вызова МтРгоЬеАпс11.оскРаде5 УОЮ МтРгоЬеАпсИ-ОСкРадеб 1крь < О15РАТСН_ЬЕУЕЬ Параметры Выполняет фиксацию страниц, описанных в МО1_ списке, в физической памяти 1Ы О1ГГ РМО1_ рМсП Указатель на МО1_ список 1Ы КРКОСЕ55ОК.МООЕ Ассез5Мос1е Режим, в котором будет проверяться доступ к анализируемым страницам: Кегпе1Мос1е изегМойе 1Ы 1_ОСК_ОРЕРАТЮН Орегайоп Права доступа после того, как страницы будут зафиксированы. Одно из значений: ТоРеас^Ассезз
1о УУгНеАссезз ХоМосН/уА ссезз УО1С1 Возвращаемое значение Замечание. Часть МЮ1_ списка, описывающая физические страницы, после данного вызова, скорее всего, будет обновлена. Таблица 7.23. Прототип вызова Мтип/оскРадез УОЮ МтигПоскРадез 1Я<21. <= О13РАТСН_ЬЕУЕЬ Параметры Отменяет фиксацию страниц страничной памяти в оперативной памяти 1Ы РМ01_ рМсП Указатель на структуру, описывающую М01_ список, соответствующий набору зафиксированных в оперативной памяти страниц Возвращаемое значение УО1С1 После того как выполнена фиксация страниц в физической памяти, описываемых МОЬ списком (то есть описываемая область стала практически частью нестранично организованной памяти), имеет смысл поинтересоваться: какой же виртуальный адрес теперь у этой области памяти в терминах системного адресного пространства (адреса — более 0x8000000)? На этот вопрос может ответить системный вызов (макроопределение) Мт(зе18у81етАс1с1ге88РогМс11, описываемый ниже. Таблица 7.24. Прототип вызова МтОе15у81етАс/с1ге88РогМс11 РУОЮ Мт6е15у51етАс1с1ге55РогМс11 <= О15РАТСН_ЬЕУЕЬ Параметры Показывает системный виртуальный адрес начала буфера, описываемого МОЬ списком 1Ы РМ01_ рМсП Возвращаемое значение Указатель на структуру МЮ1_ списка Адрес диапазона системных адресов Непосредственный доступ к полям структуры МО1_ списка не приветствуется разработчиком У\Лпс1о\/У5 — фирмой М1сго5оГС. (Хотя эта структура детально описана в заголовочных файлах \л/с1т.Ь и лЫсИсЬ, представленных в пакете ООК). Вместо этого предлагается использовать следующие функции. Таблица 7.25. Прототип вызова Мт6е1:Мс/1Ву1:еСоип1: 1Л-О1Ч(з МтСе1Мс11Ву1еСоип1 Параметры 1Ы РМ01_ рМсП Возвращаемое значение 1Я<21. <= О13РАТСН_ЬЕУЕЬ Показывает размер буфера, описываемого МОЬ списком Указатель на структуру МЮ1_ списка Размер области, описываемой МЮ1_ списком Таблица 7.26. Прототип вызова Мт6е1МсНВу1еОН5е1 1Л-О1Ч(з Мт6е1Мс11Ву1еО№е1 1К<21- <= О15РАТСН_ЬЕУЕЬ Параметры Показывает смещение начала буфера на первой странице буферной области, описываемой данным МОЬ списком 1Ы РМ01_ рМсП Возвращаемое значение Указатель на структуру МЮ1_ списка Смещение Таблица 7.27. Прототип вызова Мт6е{Мс11У1г{иа1Ас1с1ге55 РУОЮ 1В<21_ — любой
Мт(зе1Мс1Мг1иа1Ас1с1ге55 Параметры 1Ы РМЮ1_ рМсП Возвращаемое значение Показывает виртуальный адрес начала буфера, описываемого 1401- списком Указатель на структуру МЮ1_ списка Исходный виртуальный адрес описываемой данным МЮ1_ списком области памяти. Замечание. В том случае, если область была предоставлена пользовательским приложением, здесь можно увидеть виртуальный адрес из диапазона пользовательского адресного пространства (для интерпретации которого требуется контекст этого приложения). Наконец, как поступить, если некоторая структура МОЕ стала ненужной, но для работы необходима новая? Если просмотреть набор описанных выше функций, то может показаться, что следует разрушить старый МОЕ список до основания, затем создать новую (выделив соответствующую область памяти) и приступать к ее инициализации. На самом деле, в составе пакета ООК имеется вызов, который позволит избежать многих бесполезных этапов в подготовке новой МОЕ структуры, если имеется другая, бывшая в употреблении ранее. Соответствующая функция называется МтРгерагеМсПРогЯеизе, см. описание ниже. Таблица 7.28. Прототип вызова МтРгерагеМсПРогЯеизе УОЮ МтРгерагеМсПЕогКеизе трь <= О18РАТСН_ЬЕУЕЬ Параметры Подготавливает МОЬ список к повторному использованию 1Ы РМЮ1_ рМсП Указатель на структуру МЮ1_ списка Возвращаемое значение УО1с1
Функции библиотеки времени выполнения для работы с памятью Операционная система \ЛЛпс1о\№ предоставляет набор ЯП (библиотека времени выполнения) функций для работы с памятью, которые в режиме ядра заменяют столь привычные программистам пользовательских приложений вызовы тетсру, тетзе! и т.п. Некоторые наиболее употребительные вызовы описаны ниже. Таблица 7.29. Прототип вызова КПРШМетогу УОЮ кИР|НМетогу 1Вр1_ — любой (если это допускает тип памяти заполняемого буфера) Параметрам Заполняет область памяти значением РаНет 1Ы УОЮ иЫАЫСЫЕЮ *Оезйпайоп Указатель на буфер-приемник (область без выравнивания) 1Ы 1Л_О1\1С Ьепд1:Г| Размер заполняемой области в байтах 1Ы 11СНАР. РаКегп Значение, которым будет заполнена указанная область (байт) Возвращаемое значение УО1С1 Таблица 7.30. Прототип вызова ЯН2егоМетогу УОЮ ЯЫ2егоМетогу 1Рр1_ — любой (если это допускает тип памяти обнуляемого буфера) Параметры Обнуляет область памяти 1Ы УОЮ СЫАЫСЫЕО *Оезйпайоп Указатель на буфер-приемник (область без выравнивания) 1Ы 1Л_О1\1С 1_епд1:Г| Размер обнуляемой области в байтах Возвращаемое значение УО1С1 Таблица 7.31. Прототип вызова КПСоруМетогу УОЮ ЯЫСоруМетогу — любой (если это допускают типы памяти копируемых буферов) Параметры Копирует содержимое одного буфера в другой 1Ы УОЮ СЫАЫСЫЕО *Оезйпайоп Указатель на буфер-приемник (область без выравнивания) 1Ы СО1М5Т УОЮ СЫАЫСЫЕО *5оигсе Указатель на буфер-источник (область без выравнивания) 1Ы 1Л_О1\1С ЬепдМп Размер копируемой области в байтах Возвращаемое значение УО1С1 Замечание. Области источника и приемника не должны перекрываться. Таблица 7.32. Прототип вызова ПЫМоуеМетогу УОЮ ПЩи^еМетогу — любой (если это допускают типы памяти копируемых буферов) Параметры Копирует содержимое одного буфера в другой 1Ы УОЮ СЫАЫСЫЕО *Оезйпайоп Указатель на буфер-приемник (область без выравнивания) 1Ы СО1М5Т УОЮ СЫАЫСЫЕО *5оигсе Указатель на буфер-источник (область без выравнивания) 1Ы 1Л_О1\1С ЬепдМп Размер копируемой области в байтах Возвращаемое значение УО1С1 Замечание. Допускается перекрытие областей источника и приемника.
Существует также вызов М1СоруВу1е5, совершенно идентичный приведенному выше вызову ЯНМоуеМетогу.
ГРгеу1ои51 ГИехМ
Управление размещением кода драйвера в памяти Разработчик драйвера, придерживающийся правил хорошего тона, обязан экономно расходовать системные ресурсы, в частности память. Наиболее уязвимым ресурсом является нестраничная память системного адресного пространства, которую можно экономить не только путем ее рачительного использования и корректного освобождения, но и путем соответствующего размещения драйверного кода.
Определение размещения при компиляции Для того чтобы драйверные процедуры оказались после загрузки в памяти определенного типа, можно использовать директивы указания компилятору #ргадта. #ргадта сос!е_зед (” 1П1Т") Программный текст> #ргадша сос!е_зед() #ргадша сос!е_зед ("РАСЕ” ) Программный текст> #ргадша сос!е_зед() Здесь первая строка вводит программный код категории 1М1Т. Этот код, подобно сгоревшей ступени ракеты, растворится в небытии сразу по окончании инициализации драйвера (работы драйверной процедуры ЭпуегЕп^гу). Традиции такого кода восходят еще ко временам операционной системы ЭО5, когда малый размер драйвера был его важнейшим достоинством. Директива '#ргадта собе-ЗедО' восстанавливает правила по умолчанию. Директива '#ргадта сос1е_зед("РАСЕ")' обеспечивает размещение кода в областях странично организованной памяти. Все остальные процедуры драйвера размещаются в областях нестранично организованной памяти (действие по умолчанию).
Аналогичным образом директивы компилятора применимы и к сегментам (секциям) данных, см. два примера ниже. #ргадша с!а'Ьа_зед (” 1П1Т") <описание переменных> #ргадша с!а'Ьа_зед() #ргадша с1а‘Ьа_зед ("РАСЕ” ) <описание переменных> #ргадша с1а‘Ьа_зед() Другой способ добиться того же самого для отдельных функций, применяя другую форму синтаксиса, представлен ниже. ШсМ АЪЪОС_РКАСМА #ргадша а11ос_1;ех1; ( Вг^егЕп'Ьгу ) #ргадша а11ос_Ьех-Ь ( "РАСЕ”, Му0п1оас1Ргосес1иге ) #епсИЕ В приведенном выше фрагменте процедура ОпуегЕп^гу будет отнесена к категории 1М1Т, а процедура Му11п1оас1Ргосес1иге будет размещена в станичной памяти.
Динамическое перемещение кода драйвера в страничную память Наконец, третий способ перемещения кода драйвера в страничную память является динамическим и происходит под управлением самого драйвера. Весь программный код драйвера, обычно размещенный в области кода операционной системы (начинающейся с адреса 0x80000000), можно пометить как "страничный" и переместить в странично организованную память системным вызовом МтРадеЕпНгеОпуег, которому в качестве параметра передается любой адрес или указатель на любую процедуру в составе кода драйвера, например, Ас1с10еу|се (если эта процедура присутствует). Обратное действие выполняет вызов МтВезеЕОпуегРадтд, который восстанавливает статус всех секций кода драйвера, данный им при компиляции, и все секции, которые были созданы как нестраничные, фиксируются в оперативной памяти. Вызовы МтРадеЕпНгеОпуег и МтКе5еШпуегРад1пд следует выполнять только в коде, работающем на уровне 1к01_ равном РА551УЕ_1_ЕУЕ1_.
Проблемы, возникающие при перемещении кода в страничную память В стремлении оказать системе хорошую услугу важно остановиться в нужном месте. В случае, если драйвер обслуживает прерывание, а оно поступило как раз в тот момент, когда 15К процедура, предназначенная для него, оказалась на жестком диске, среди другого кода категории РАСЕ, то крах системы неминуем. Соответственно, если пользователь прекращает работу с драйвером вызовом С1о5еНапс11е (в результате чего вызывается драйверная процедура, обслуживающая запрос 1РР_МЗ_СЬО5Е), то драйвер может выполнить перевод всего своего кода в область страничной памяти, после чего он, возможно, окажется сброшенным на диск. Разумеется, имеет смысл проверить предварительно, сколько раз был получен доступ к драйверу (возможно, имеются еще пользовательские приложения, имеющие открытый дескриптор). Если драйвер подключен к прерыванию, следует отключиться от него. Обратные действия (восстановления прежнего состояния драйверных процедур) можно проделать в процедуре, обслуживающей 1РР_МЗ_СРЕАТЕ, то есть когда пользовательское приложение пытается получить доступ к драйверу и получить соответствующий дескриптор вызовом пользовательского режима Сгеа(еН1е. Контроль за количеством обращений к драйверу можно вести при помощи системных вызовов 1п1ег1оскес11псгетеп( и 1п1ег1оскесЮесгетеп1, обеспечивающих безопасное обращение к счетчику, который можно разместить, например, в расширении
структуры устройства (с1е71се ех^епзюп), состав которой полностью зависит от воли разработчика драйвера. Следует также помнить, что после старта драйвера секции, отнесенные при компиляции к типу 11М1Т перестают существовать, поэтому использовать их в операциях по перемещению из/в страничную память нельзя.
Фиксация страничных секций кода и данных в оперативной памяти В некоторых случаях имеет смысл применить обратные манипуляции, то есть зафиксировать в оперативной памяти фрагменты данных или кода, которые изначально (при компиляции) были размещены в странично организованной памяти. Это может дать положительный эффект, выражающийся в повышении быстродействия драйвера или снижения времени реакции на события (например, запрос пользователя). Например, при неблагоприятных условиях задержка вызова странично размещенной процедуры обработки ЮСТ1_ запросов (по вызову Оеу|се1оСоп(го1 пользовательского режима) может достигать десятых долей секунды, независимо от быстродействия самого процессора. Фиксация секций страничной памяти данных и кода выполняется при помощи вызовов Мт1_оскРадаЬ1еОа(а5есНоп и МтЬоскРадаЫеСос1е5ес11Оп соответственно. В качестве параметра этим функциям передается адрес, указывающий внутрь интересующей области — адрес элемента данных или адрес интересующей процедуры. Возвращаемые этими вызовами дескрипторы следует сохранить — они потребуются для последующего системного вызова Мтип1оскРадаЫе1таде5ес11ОП, используемого для восстановления прежнего статуса соответствующих областей виртуальной памяти (уменьшения числа ссылок на такие области). Применение вызовов МткоскРадаЫеОа1а§ес11оп/МтЬоскРадаЫеСос1е§ес11 оп повторно к одной и той же области является расточительным приемом. Поэтому, если драйвер
вынужден использовать эти вызовы повторно, то следует использовать функцию Мт1-оскРадаЫе§ес11опВуНапс11е, используя первоначально полученный дескриптор. В противном случае, рекомендуется вести учет вызовов функций МткоскРадаЫеОа1а§ес11оп/МтЬоскРадаЬ1еСос1е§ес11 оп, чтобы не выполнять лишней работы. Для того чтобы программно контролировать отдельный набор данных, можно поместить эти данные автономно, как показано ниже: #ргадша с!а‘Ьа_зед (”МТ_ВАТА” ) <описание переменных> #ргадша с!а'Ьа_зед() После этого контроль можно осуществлять по адресу любой из описанных в этом сегменте переменных. Применять приведенные функции для фиксации в оперативной памяти буферных областей, поступивших в пакете 1Р.Р, нельзя — для этого следует использовать специально для того предназначенный вызов МтРгоЬеАпсИ-оскРадез. Все упомянутые выше вызовы следует выполнять только в коде, работающем на уровне равном РА551\/Е_1_Е\/Е1_.
Проверка корректности вызовов кода, размещенного в страничной памяти Если программный код размещен в страничной памяти, то в отладочной версии драйвера можно организовать проверку, всегда ли он вызывается на должном уровне привилегий. Для странично размещенного кода этот уровень 1НС21_ должен быть ниже □15РАТСН_1_Е\/Е1_. Для выполнения этой работы сконструировано специальное макроопределение 'РАСЕО_СООЕ();', которое проверяет текущий уровень 1ИС21_ (не превышает ли он АРС_1_Е\/Е1_) и генерирует отладочное сообщение при помощи вызовов Кс1Рпп1 и М1А55ег(, которое можно наблюдать, например, в окне программы ОеЬид\/1е\л/, если она к тому моменту запущена. Кстати, сам факт существования макроопределения РАСЕО_СООЕ говорит о том, что использование станичной памяти на высоких уровнях 1НС21_ является некорректным не само по себе (иногда оно может "сойти с рук"). Причина в том, что данное стечение обстоятельств является потенциальной угрозой правилам системы: она откажется обслуживать ситуацию, когда данная страничная память окажется на диске, а уровень 1Р.(21_ будет столь же высок. В том случае, если выявлено диагностическое сообщение от макроопределения РАСЕО_СООЕ, следует изменить тактику использования страничной памяти в указанном месте программного кода (в сообщении указан файл и строка, где возникла некорректная ситуация), возможно, отказаться от нее.
ГРгеу|ои51 [Мех!],
Операции над строками 1ЛЧ1СООЕ_5ТП11Ч(з Операционная система \Л/1пс1о\л/5 1МТ издавна ориентирована на использование так называемых "широких" символов (занимающих два байта), что, в отличие от символов А5СП, о которых будет сказано ниже, без особых затруднений обеспечивает поддержку всех типов алфавитов, включая поддержку языков Юго-Восточной Азии. Собственно тип данных 1Л\11С00Е_5Тк11\1С описывается в пакете ЭЭК следующим образом (см. заголовочный файл пМеГ.К): ЕурейеЕ збгисб _ЕМ1СОЭЕ_5ТК1МС { ЕЗНОКТ Ъепд'ЕИ; // Длина строки (в двухбайтных символах ЕЗНОКТ МахФтитЬепд'Ьй; // Максимально возможная длина строки РИЗТК ВиГГег; // указатель на буфер с двухбайтными си } ЕМ1СОЕЕ_5ТК1МС, *РЕМ1СОЕЕ_ЗТК1МС; Иногда 11М1С00Е_5ТкИ\1С определяется выражением "соип!:ес1 51ппд", что довольно точно передает сущность этого типа данных, то есть — "счетная строка", строка, где поддерживается учет действующих символов. Нетрудно догадаться, что у программистов, долгое время привыкавших к простым А5СП кодировкам и столь же несложным функциям, оперирующим А5СП2 строками, переход к использованию кодировки Юникод не вызовет энтузиазма. Тем не менее, при работе в режиме ядра \Л/1пс1о\л/5 1МТ это совершенно необходимый инструмент. (Правда, функции отладочной диагностики типа ОЬдРпп1 позволяют пользоваться строками в прежней манере.) Работа с типом 11М1ССЮЕ_5ТР1МС покажется менее сложной, если смириться с тем, что не следует "трогать его руками" (примерно, как С51ппд в МЕС), и научиться использовать набор системных функций, предназначенный для работы с ним. Прежде всего, простая буква ’С, примененная перед строкой символов, дает указание препроцессору, трактовать эту строку как строку "широких" символов (строку УУСНАк, но еще — не 1И\11СООЕ_5ТК11\1С). Соответственно, перейти от обычного текста, набираемого на клавиатуре компьютера, к строке 1)Ы1СООЕ_5ТК1ЫС можно так: ЕМ1СОЭЕ_5ТК1МС туЕеиЗЗбгхпд; Кб11п1ЕЕп1сос1еЗбг1пд ( &туЕеиЕЗРг1пд, Ь"Му Ептсобе Еехб ехатр!е." ); В следующем примере посредником при инициализации 1Л\11С00Е_5Тк1М0 выступает тип данных А№1_5Т1Ч1МС (счетная строка однобайтных символов): АЕ51_ЗТК1МС туЕеиАЕ515Ег1пд ; ЕЫ1СООЕ_ЗТК1ЫС туЫеиОЗРг1пд; Р.'ЫТпФбАпзФЗбгФпд ( &туЫемЕЗбг1пд, "Му зесопс! бехб ехатр1е." ); Кб1Апз15бг1пдТоЕп1сос1еЗбг1пд ( &туЕеиЕЗЕг1пд, &туЕеиАЫ313Ег1пд, ТКЕЕ) ; Третий параметр, указанный как ТЯ11Е, заставляет данную функцию выделять память под буфер двухбайтных символов. Соответственно, по окончании работы с 11М1СООЕ_5ТК11\1Сэ (а в данном случае — при выходе из текущей функции, поскольку туМе\л/1)51ппд определена как локальная переменная) следует выполнить
освобождение буфера двухбайтных символов вызовом Я11Егее11тсо(1е51ппд. То же необходимо проделать и в первом примере. Более того, аналогичное требование и к типу данных АЫ51_5ТК1М0, для которого следует использовать Я11ЕгееАп5151гтд. Сам тип АМ51_5Тк11\1С определяется следующим образом (см. п1с1еГ.К): "Сурейе^ З’Ьгис!: _5ТК1МС { 173НОР.Т ЪепдТН; 173НОКТ МахгтитЬепдТИ; РСНАК ВиГГег; // Здесь 'РСНАК' - просто 'скаг' указатель } 5ТК1ИС, *РЗТК1М6; Турес1е^ ЗТР.1ЫС АЫ31_ЗТР1ЫС; Очевидно, второй способ более трудоемкий, и им редко кто пользуется. После того как экземпляр 1Л\11С00Е_5ТК1М0 получен, над ним можно выполнять разнообразные операции. Предназначенные для этого системные функции описываются ниже. Таблица 7.33. Прототип вызова Я11Аррепс/игнсос1е81ппдТо81ппд 1ЧТ5ТАТ05 Н11Аррепс1итсос1е51ппдТо51ппд 1Кр1_ — любой (если это допускает тип памяти буферов двухбайтных символов) Параметры Объединяет строки 1ЛЧ1СООЕ_ВТВПЧб 1Ы ООТ Р1Л\11С00Е_5Тк1М6 Оез^паИюп Указатель на строку-получатель 1Ы ООТ Р1Л\11СООЕ_5ТР1МС Аррепс151:ппд Указатель на присоединяемую строку Возвращаемое значение 5ТАТи5_5иССЕ55 — строка присоединена и длина строки получателя обновлена 5ТАТи5_ВиЕЕЕк_ТОО_5МА1_1_ — слишком мал размер буфера двухбайтных символов строки-получателя При анализе приведенного выше описания возникает правомерный вопрос: что предпринимать, если объединение строк завершилось неудачей по причине недостаточно большого буфера принимающей строки 1Л\11С00Е_5Тк11\1С? Достойного ответа в пакете БОК на этот вопрос просто нет. Если требуемый размер буфера не составит труда вычислить как (Вез'Ыпа'Ыоп->Ьепд'ЬИ + Аррепс13'Ьг1пд->Ьепд'Ыа) * згхео^(ИСНАР) то расширение буфера строки-получателя — задача несколько более сложная. Хотя логично было бы иметь соответствующую системную функцию, однако в документации ЭЭК о подобном вызове нет никакого упоминания. Таблица 7.34. Прототип вызова М1Сотрагеип1собе5(ппд 1_О1Ч(з В11Сотрагеитсос1е51ппд Параметры 1Ы Р1Л\11ССЮЕ_5ТР1МС р51ппд1 1Кр1_ == РА551УЕ_ЬЕУЕЬ Выполняет сравнение строк 1ЛЧ1СООЕ_ВТКПМО Указатель на первую строку 1Ы Р1Л\11ССЮЕ_5ТР1МС р51ппд2 Указатель на вторую строку ВООЬЕАЫ СазеТпЗепз^уе ТРОЕ — игнорировать регистр (верхний/нижний) Возвращаемое значение 0 — строки идентичны < 0 — строка 1 меньше строки 2 Таблица 7.35. Прототип вызова М1ЕдиаШп1собе5(ппд ВООЬЕАИ В11Едиа1итсос1е51ппд 1Н<21. == РА551УЕ_ЬЕУЕЬ
Параметры Выполняет сравнение строк 1ЛЧ1СООЕ_5ТКПЧ(з 1Ы Ри1\11ССЮЕ_5ТК1МС р51ппд1 Указатель на первую строку 1Ы РиМ1ССЮЕ_5ТР1МС р51ппд2 Указатель на вторую строку ВООЬЕАЫ Сазе1п5епз11|уе ТК11Е — игнорировать регистр (верхний/нижний) т ТИПЕ — строки идентичны Возвращаемое значение рдьзе _ с^роки различаются Таблица 7.36. Прототип вызова МИп164Тоитсос!е31гтд 1ЧТ5ТАТ115 КШп164Тоип1Соае51г1пд 1К<21_ == РА851УЕ_ЬЕУЕЬ Параметры Преобразует число т164 в иИ1СООЕ_5ТК1ЫС 1Ы и1_ОМС1_ОМС \/а1ие Исходное число пмг к Формат: 16 — шестнадцатеричный, 8 — октавный, 2 — двоичный, 0 а5е или 10 — десятичный. 1Ы ОиТ РиМ1СООЕ_5ТР11\1С 8 Строка иМ1СООЕ_5Тк11\1С Возвращаемое значение 5ТАТи5_5иССЕ55 5ТАТи5_виЕЕЕк_О\/ЕкЕ1_О\/\/ — слишком мал размер буфера иМ1СООЕ_5ТР11\1С 5ТАТи5_11\1\/А1_Ю_РА РА МЕТЕР. — ошибочен параметр Вазе Аналогичный прототип вызова имеют также функции преобразования целых чисел без знака и указателей в строку 1Л\11ССЮЕ_5ТК1МС — КШп1едегТоип1СОс1е51г1пд и ЯШп1Р1гТо11п1СОс1е8(Г1пд, соответственно. Таблица 7.37. Прототип вызова ЯПирса5еитсос1е5{ппд ГМ Т5 ТАТ II5 КНОрсазеОтсоаеЗ^ппд Параметры 1Ы оит ОРТЮЫАЬ РиГ\11СООЕ_5ТК1МС с1е51: оит РиМ1СООЕ_8ТР11\1С зге 1Ы ВООЬЕАЫ АПоса1еОз151ппдВи1Т Возвращаемое значение 1Кр1_ == РА551УЕ_ЬЕУЕЬ Преобразует все символы строки зге в символы верхнего регистра Указатель на строку с буфером, подготовленным для приема преобразованной строки или 1\1и1_1_ (в последнем случае преобразование происходит по месту) Исходная строка Если ТЕШЕ — выделить буфер под результат преобразования (в этом случае его следует освободить вызовом К11Егееип1СОс1е51г1пд по окончании работы с этой строкой) 5ТАТи5_5иССЕ55 5ТАТи5_ВиЕЕЕк_О\/ЕРТ1_О\/\/ — слишком мал размер буфера иМ1СООЕ_5Тк1МС 5ТАТи5_11\1\/А1_1О_РАкАМЕТЕк — ошибочен параметр Вазе
Операции над строками А1Ч81 символов Работа со строками символов А№1 (8-битными) более привычна для программистов. Такие средства тоже присутствуют в наборе встроенных функций пакета и в наборе системных вызовов НИХхх. Строками простых 8-битных символов, в которых окончание обозначено нулем (так называемые строки 52, 51ппд, 2его епс1), можно манипулировать при помощи уцелевших встроенных функций 5(г1еп, тетсру, зСгсру, зСгпсру, 51гса1 и даже 5(гз1г. Набор функций, которыми можно попытаться воспользоваться, приведен в файле ЭЭК ХР в заголовочном файле тс\сг1\81ппд.Н, хотя в официальной документации ООН они не упоминаются вовсе, так что к ним следует применять правила использования по-умолчанию. Например, не обращаться к строкам, размещенным в страничной памяти, на высоких уровнях 1Р<21_ и т.п. Другие функции, например, мощную зрппЬГ или хотя бы а1о1, компилятор ЭОК не предоставляет. Вариант счетной строки обычных 8-битных символов вводится в пакете ЭЭК как тип данных АЫ51_5ТК11\1Сэ. Это аналогичная 1Л\11СООЕ_5ТР11\1Сэ структура данных, в которой собственно строка хранится по адресу, указанному в поле В и (Те г, как уже было показано выше. Длину строки можно взять из поля кепд^Ь. Хотя в большинстве случаев вполне успешно проходит обращение со строкой по адресу ВиГГег, как если бы она была строкой 57 (заканчивающейся нулем), однако, строго говоря, надеяться на это не следует, поскольку документация СОК не указывает, что на это можно вполне рассчитывать. Можно ли перейти от строки 1Л\11С00Е_5ТК1М0 к обычной строке 8-битных символов? Можно, но при этом произойдет потеря информации о языковой локализации. Ниже показан несложный способ, как перейти к счетной строке АЫ51, а затем и к обычной строке 52. // Полагаем, что уже существует счетная строка ПЫ1СОВЕ_ЗТВ.1ЫС // по адресу рбгТоПМ1С00Е_ЗТВ.1МС; АП51_ЗТВ1МС апзФЗбг; // стековая переменная (не инициализирована) сЬаг збг[100];// стековая переменная МТ5ТАТП5 зкаЕ = // инициализируем строку АИ51 ВбТПпФсобеЗкгФпдТоАпзФЗбгФпд( &апз!ЗЕг, ркгТоПМ1СОЭЕ_ЗТК1МС, ТИПЕ ); ФТ(ЫТ_ЗПССЕЗЗ(зкак)) { // Теперь поле апзФЗбгФпд.ВиТТег указывает на выделенный буфер, // куда помещена строка АЫ31 символов, полученная из строки // ПП1СОВЕ_ЗТВ1Ы(5, изначально находившейся по адресу, куда // указывает переменная рбгТоПМ1СООЕ_ЗТВ1МС. // Поле апзФЗбг.ЬепдбЬ уже содержит информацию о длине строки. Фпб 1еп= (апзФЗбг.ЬеидТН > 99? 99: апзФЗбг.ЬепдбН); збгпсру(збг, апзФЗбг . ВиТТег, 1еп) ; збг[1еп+1]=0;
ОЬдРгФпб ( "32 збгФпд = %з (1еп=%с1) . " збг, 1еп) ; // Освобождаем буфер, выделенный под хранение А№1 строки: Р.Р1ГгееАпз13бг1пд (&апз13Рг) ; } Заметим, что переменная 51г будет хранить строку 8-битных символов до выхода из данного программного модуля, поскольку з1г — локальная переменная. Прототип вызова функции Я1111тсо(1е51ппдТоАп5151ппд описан в таблице ниже. В том случае, если третий параметр указан как ТЯ11Е, то будет выполнено выделение памяти для хранения строки, полученной из строки 1Л\11С00Е_5Тк1М0. Соответственно, по окончании использования строки А№1_5ТР1М0 следует освободить числящуюся за драйвером область памяти, что выполняется вызовом К11ЕгееАп5151ппд. Таблица 7.38. Прототип вызова ЯНигнсос1е51нпдТоАпз15(ппд ГЧТ5ТАТ115 К1:111тсос1е51:ппдТоАп5|51ппд Параметры 1Ы ООТ РАМ51_5ТР1МС агыЗИппдРИг 1Ы Р1Л\11ССЮЕ_5ТР1МС Зоигсе ВООЬЕАЫ АНосНад Возвращаемое значение 1Кр1_ == РА551УЕ_ЬЕУЕЬ Преобразует информацию строки 1ЛЧ1СООЕ_ЗТКПЧб в строку АН51_5ТК11Ч(э Указатель на строку АМ51_5ТР11\1С, которая должна быть получена в результате вызова Указатель на исходную строку 1ЛЧ1СООЕ_ЗТКПЧб ТРОЕ — выделить память под буфер строки АЫ31_ЗТк11\1С ЗТАТиЗ_ЗиССЕЗЗ или код ошибки Работа с текстом в драйверах случается нечасто, но, тем не менее, следует быть к ней готовым. Например, при работе с файлами.
ГРгеу|ои51 [Мех!],
Функции для работы с файлами Невозможно представить полноценное средство разработки кода, если в нем нет способов работы с файлами. Для чего файлы могут понадобиться в драйвере? Два примера, первыми приходящие на ум: необходимость считывания кода прошивок внешних устройств (которые могут быть размером до десятков килобайт) и ведение протоколирования в файле на диске. Для открытия файла из драйвера режима ядра используется универсальная функция 2\л/Сгеа1еЕ11е. Универсальность этого вызова состоит в том, что с его помощью производится и открытие существующих файлов, и создание новых. Специфика системной функции 2\л/Сгеа1еЕПе состоит в том, что она имеет протокольных параметров даже больше, чем пользовательский вызов СгеаЕеЕНе. Существенная часть входной информации об открываемом объекте поступает внутри структуры ОВЗЕСТ_АТТК1В11ТЕ5, которую следует предварительно создать и заполнить соответствующим корректными данными. Для ведения учетной информации открытого объекта используется структура данных Ю_5ТАТ1)5_В10СК, которую следует предоставить при вызове (инициализировать ее не следует). Поскольку вызов 2\л/Сгеа1еР!1е имеет большое разнообразие флаговых значений практически для каждого из входных параметров, ограничимся кратким общим обзором, представленным в таблице ниже. Более полную информацию обо всех возможных значения параметров вызова 2\л/Сгеа1еЕПе следует получить из документации пакета СЭК. функции 7 страниц!) МТ5ТАТЦЗ 2\л/Сгеа1еРИе Параметры (Джозеф Ньюкамер уделил в своей книге описанию этой Таблица 7.39. Прототип вызова 2тСгеа1еРПе 1КРЬ == РА351УЕ_ЬЕУЕЬ Предоставляет доступ к системным ресурсам (в том числе файлам) в режиме ядра оит РНАЫОЬЕ рНапсПе Указатель на переменную, куда следует поместить дескриптор открытого объекта (файла, подраздела реестра и т.п.) 1Ы АССЕ55_МА5К 0е51гес1Ассе55 1Ы РОВЗЕСТ_АТТК1ВиТЕ5 рОЬ]АПпЬи1е8 оит рю_5ТАти5_в1_оск рЮ81а1из 1Ы Р1_АР.СЕ_11\1ТЕСЕР. А11оса1юп5|2е ОРТЮЫАЬ 1Ы и1_О1\1С РНеАИпЬШез Характеристика доступа к объекту. Для файлов вполне приемлемы значения СЕЫЕР1С_Р.ЕАО или СЕЫЕК1С_\Л/К1ТЕ, которые представляют сложные комбинации из более простых масок доступа (типа Е11_Е_АРРЕЫО_ОАТА и т.п.) Указатель на заполненную вызывающим кодом структуру данных, которая описывает имя, местоположение и некоторые другие характеристики открываемого объекта (см. ниже) Указатель на буфер, в котором будет размещена информация об открытом объекте в формате структуры Ю_5ТАТи5_В1_ОСК Начальный размер файла в байтах. Ненулевое значение принимается во внимание только при создании и перезаписи файла Атрибуты открываемого файла. Типовым является значение Е11_Е_АТТк1ВиТЕ_1\1СЖМА1_ 1Ы и1_О1\1С 8Ьагес1Ассе55Е1ад5 Описывает, разрешен ли совместный доступ, например, Е11_Е_5НАКЕ_кЕАО — для чтения 1Ы и1_О1\1С Сгеа1еО18ро8ИюпЕ1ад8 1Ы и1_О1\1С Сгеа1еОр1:юп5 Способ открытия файла, например, Е11_Е_ОРЕ1\1_1Е — если не существует, создать Комбинация флагов создания, например, Е11_Е_5УМСНКОМОи5_Ю_МОНА1_ЕкТ — все операции над файлом выполняются как синхронные (0е51гес1Ассе55 должен включать флаг 5УМСНКОМ17Е) 1Ы РУОЮ ЕаВиГГег ОРТЮЫАЬ 1Ы и1_О1\1С ЕаЬепдип Возвращаемое значение Для драйверов устройств и драйверов средних слоев следует указывать 1\1и1_1_ Для драйверов устройств и драйверов средних слоев следует указывать 0 5ТАТи5_5иССЕ55 или код ошибки (несколько более подробную информацию можно найти в структуре Ю_5ТАТи5_В1_ОСК)
Поле р!О51:а1:и5->1пГогта1:юп (структуры Ю_5ТАТ115_В1_ОСК) может после вызова иметь одно из следующих значений: ЕП_Е_СКЕАТЕО, ЕП_Е_ОРЕЫЕО, Р11_Е_ОХ/ЕРА/УК1ТТЕ1\1Л Е11_Е_511РЕН5ЕОЕО, ЕП_Е_ЕХ15Т5 или Е11_Е_ООЕ5_1\ЮТ_ЕХ15Т. Ниже приводится пример создания (или открытия — при повторных обращениях) файла С:\Ехатр1е\1:е51:Г|1еЛх1: и запись в него строки " з1:ппд : 1ез1 \л/гН:е!\" ИТ8ТАТП8 зЕаЕиз; □ШС0БЕ_8ТК1Ж ЬПШеИате ; НАПБЬЕ УЫеНапсИе ; 1О_8ТАТП8_ВЬОСК ФозЕаЕиз; ОВЙЕСТ_АТТВ1ВПТЕ8 оа; ВБ11п±Б0п±сос1е8Бг±пд ( &Уи11Е±1еПател Ь"\\??\\С: \\Ехатр1е\\Еез-ЬБ±1е . ЕхЕ" ) ; ТпИзФаИгеОкд есБАББгФЬиБез ( &оа, &Уи11Е±1еПате, ОВЙ_СА8Е_1П8ЕП81Т1УЕ | ОВЛ ЫПЪЪ, ТОБЬ ) ; зЕаЕиз = ЕмСгеаЕеЕИе ( йРИеНапсИе г СЕПЕР1С_^Р1ТЕ | 8УПСНВОШ2Е, &оа, &±озЕаЕиз г О, // аИос з±ге = попе Е1ЬЕ_АТТВ1ВПТЕ_ПОВМАЬ, Е1ЬЕ_8НАВЕ_1/Ж1ТЕ, Е1ЬЕ_ОРЕП_1Е, Е1ЬЕ_8УПСНВОПОП8_1О_ЕЮПАЬЕВТЛ ЫПЪЪ, 0) ; // Здесь: // СЕПЕР1С_^Р1ТЕ равно 8ТАЕВАВБ(0x40000000Ь) // // Е1ЬЕ_СЕЕЕВ1С_^В1ТЕ равно 8ТАЕВАВВ_В1СНТ8_^В1ТЕ|Е1ЬЕ_^В1ТЕ_БАТА | // Е1ЬЕ_йЖ1ТЕ_АТТВ1ВЕТЕ8 | Е1ЬЕ_йЖ1ТЕ_ЕА | Е1ЬЕ_АРРЕЕВ_БАТА | // 8УЕСНВОШ2Е, что можно увидеть в заголовочном файле мгппЕ.й ЕТ_8ЮССЕ88(зЕаЕиз) ) { // Строка для записи в файл сйаг туВЕггпд[100]=”зЕггпд : ЕезЕ мггЕе!\г\п"; // Структура, которая поможет определить длину файла: Е1ЬЕ_8ТАЕВАВВ_1ЕЕОВМАТЮЕ УНеТпУо; зЕаЕиз // Получаем информацию о файле
2^0иегу1пЕогта-ЫопЕИе ( ЕИеНапс11е, бстозкакиз , &ЕИе1пЕо, зтгеоЕ (Е1ЬЕ_8ТА№АКБ_таЕ0КМАТ10Ю , Е11е8Еапс1агс11пЕогтаЕ±оп □ЬОМС 1еп = зкг1еп(туЗЕгтпд); 1Е( ЫТ_8ОССЕ88(зкакиз) ) { ЬАКСЕ_ЮТЕСЕК ВуЕеОЕЕзеЕ = ЕИе1пЕо . ЕпсЮЕЕИе ; зЕаЕиз = ЕмЭДгИзеЕИе (ЕНеНапсПег ОТЪЪ, ОТЪЪ, ОТЪЪ, &±озЕа'Ьиз г туЗЕгтпд, 1еп, // Записываемая строка &ВуЕеОЕЕзеЕл // а если ЫОЬЬ? см. ниже ОТЬЬ) ; 1Е( !ЫТ_8ОССЕ88(зЕаЕиз) || тозЕаЕиз.ТпЕогтаЕтоп != 1е { ВЬдРгтпЕ("Еггог оп мгтЕтпд. 8ЕаЕиз = %х.", зЕ } } 2мС1озе ( ЕИеНапс11е) ; Ьгеак; } Следует заметить, что без магической комбинации "\\??\\" в начале имени файла, вызов 2ууСгеа1еН1е непременно возвратит ошибку. Этот префикс является обязательным для файлов на диске. Таблица 7.40. Прототип вызова ХшУУгНеРПе 1ЧТ5ТАТ05 2мД/УгИеН1е Параметры 1Ы НАЫОЬЕ РПеНапсПе 1Нр1_ == РА551УЕ_ЬЕУЕЬ Производит модификацию объекта (файла), указанного открытым дескриптором Дескриптор открытого для модификации файлового объекта 1Ы НАЫОЬЕ ЕуепЛ ОРТЮЫАЬ Драйверы устройств и драйверы средних слоев должны установить этот параметр равным 1\11Л_1_ 1Ы РЮ_АРС_КОиТ1ЫЕ рАрскоийпе ОРТЮЫАЬ 1Ы РУОЮ АрсСогИех! ОРТЮЫАЬ оит рю_5ТАтиз_в1_оск р1о51аШ8В1оск Драйверы устройств и драйверы средних слоев должны установить этот параметр равным 1\11Л_1_ Драйверы устройств и драйверы средних слоев должны установить этот параметр равным 1\11Л_1_ В поле р1о51аШ8В1оск->1пГогта1:юп по завершении вызова находится число реально записанных байт 1Ы РУОЮ ВиГГег Буфер с данными для записи 1Ы 1Л_01\1С 1_епд1Г| Размер записываемой порции данных 1Ы Р1_АР.СЕ_11\1ТЕСЕР. рВуНеОГГзе! ОРТЮЫАЬ 1Ы Р1Л_О1\1С Кеу ОРТЮЫАЬ Указатель на переменную, где содержится смещение в файле (от начала), по которому следует производить запись данных Драйверы устройств и драйверы средних слоев должны установить этот
параметр равным 1\11Л_1_ Возвращаемое значение 5ТАТи5_5иССЕ55 или код ошибки При помощи 2\л/Сгеа1еР!1е можно получать и доступ к драйверам, как это делается из вызова Сгеа1еН1е пользовательского режима. Таким образом, в частности, предлагается получать доступ к драйверу отладочной печати ОеЬидРпп1. Разумеется, вид имени для использования будет несколько другим. Для драйвера ОеЬидРпп!: это будет имя 1_"\\Оеу|се\\РНООеЬидРпп1". По данному имени через 2\л/Сгеа1еРПе можно получить доступ к драйверу из другого драйвера, несмотря на то, что символьная ссылка этим драйвером не создается. Аналогично, к нашему драйверу Ехатр1е можно получить доступ из другого драйвера по имени 1_"\\Оеу1се\\Ехатр1е", но ничего не получится, если пытаться использовать имя 1_"\\\\.\\Ехатр1е", как в вызове СгеаСеРИе. Рассмотрим подробнее некоторые функции, использованные в приведенном выше примере. Макроопределение 1т11аН2еОЬ]ес1АипЬи1е8 используется для заполнения полей структуры ОВЗЕСТ_АТТР1В11ТЕ5, что делает эту операцию компактнее. Это макроопределение вводится в заголовочном файле пМеГ. 11 пакета ЭЭК. Там же описана и внутренняя организация ОВЗЕСТ_АТТИ1ВОТЕ5. Собственно запись в файл в приведенном выше примере выполняется системной функцией 2\л/\Л/п1еРПе. Способ вызова 21мЯеа<1Р|1е во многом повторяет прототип для 21лМЛ/п1еРНе, приведенный в таблице 7.40. Если параметр рВуЬеОГГзеЬ указан равным 1\1111_1_, то в большинстве случаев это воспринимается как нулевое смещение, и запись была бы выполнена (скажем, в приведенном примере) с начала файла. Однако на это допущение лучше не полагаться. Например, если бы мы пытались произвести запись в упомянутое выше устройство РНООеЬидРпп1, то нас преследовали бы ошибки, пока значение *рВу1еОГГ5е1 не было бы задано явно, то есть: рВуТеО^^зеТ->0иас!РагТ = 0164; // 0 для ЪАРСЕ_1ЫТЕСЕР. ф Упоминания в документации ООК ХР, по поводу того, что при наличии в параметре Ое5/'гес1Ассе55 (при вызове ХууСгеа1еРПе для получения доступа к файлу) флага РИЕ_АРРЕЫО_ОАТА запись всегда производится в конец файла, мягко говоря, не совсем справедливы. При наличии этого флага (среди параметров вызова ХиуСгеа1еРПе) значение ВуЕеОНзеЕ играет по-прежнему ту же роль в вызове ХшУУгНеРПе, что и без использования этого флага при открытии файла. В примере, представленном выше, для получения информации о размере файла был использован системный вызов 2\л/<2иегу1гИогта1юпРПе. Данный вызов достаточно универсален, и возвращаемая им информация зависит от его последнего параметра, который может принимать следующие значения: РПеВаз1с1п?огтаНоп — для получения общих параметров открываемого файла, времени создания, времени последнего обращения, аргументов. РПе5(апс1агс11пГогтаНоп — для получения размера файла и признака того, что данный объект может быть директорией и т.п. РПеРозШопТп/огтаИоп — для получения текущей позиции в файле. РПеАПдптепНпГогтаНоп — для получения информации о способе выравнивания буфера для работы с данным объектом. РПе№те1п?огтаНоп — для получения системной информации об имени файла.
Разумеется, для каждого типа вызова должна быть предоставлена своя структура, для чего предусмотрен предпоследний параметр, где при вызове указывается ее размер. То есть больше указанного таким образом размера вызов 2уу<2иегу1гИогта1юпЕПе использовать не может. Недостаток места для записи всей информации может быть причиной неудачного завершения.
Функции для работы со ссылками на объекты В некоторых ситуациях необходимо получать доступ к объектам режима ядра. В операционной системе \ЛЛпс1оу75 объектами представлены многие компоненты: программные потоки, объекты синхронизации, файловые объекты, драйверы, устройства, объекты ОРС процедур, прерываний и т.п. Правильнее говорить, что эти явления представлены структурами данных для удобства ведения их учета. Структуры эти в полной мере (например, в смысле ООП) объектами не являются. У них нет конструкторов, деструкторов, виртуальных методов в привычном смысле С+ + . Тем не менее, понятие "объект" широко используется и, в некотором смысле, справедливо — оперировать этими структурами предпочтительнее специально для того предназначенными системными вызовами, которые и можно считать методами для данных объектов. Рассмотрим пример, в котором объектом синхронизации является программный поток. Программные потоки могут своим окончанием сигнализировать другим программным потокам о том, что пора приступать к работе. В частности, программный поток может быть запущен вызовом Р5Сгеа1е5у81етТ11геас1 (см. главу 10). Другой поток может дожидаться его окончания, организовав ожидание при помощи одного из вызовов КеУУаНЕогХхх. Возникает небольшое затруднение. Системный вызов Р8Сгеа(е5у5(етТ11геас1, создавая поток, возвращает его дескриптор (НАЫЭЬЕ), а функции КеУУаНЕогХхх принимают в качестве входного аргумента указатель на объект, по состоянию которого организуется ожидание. Выход состоит в использовании вызова ОЬЯеГегепсеОЬ]ес1ВуНапс11е, который в состоянии по корректному дескриптору возвратить указатель на соответствующий ему объект. Прототип ее вызова описан ниже. Таблица 7.41. Прототип вызова ОЬЯеГегепсеОЬ]'ес1ВуНапсПе 1ЧТ5ТАТ05 ОЬКеГегепсеОЬ]ес1ВуНапс11е ИНН == РА551УЕ_ЬЕУЕЬ Параметры Предоставляет указатель на объект режима ядра по открытому дескриптору 1Ы НАЫОЬЕ Напс11е Исходный дескриптор объекта 1Ы АССЕ55.МА5К □ез1гес1Ассе55 Маска доступа, интерпретация которой зависит от типа рассматриваемого объекта 1Ы РОВЗЕСТ.ТУРЕ ОЬ]есГГуре ОРТЮЫАЬ Можно установить 1\11Л_1_ (если параметр Ассе55Мос1е равен Кете/Мос/е) или одно из значений 1оРПеОЬд'ес1Туре ог ЕхЕуепЮЬдесГГуре 1Ы КРР.ОСЕ55СЖ_МСЮЕ Ассез5Мос1е • Кегпе1Мос/е — для работы в режиме ядра • изегМос/е ООТ РУОЮ *ррОЬ]ес1 Указатель на переменную типа Р\/0Ю, в которой будет возвращен указатель, соответствующий исходному дескриптору оит РОВЗЕСТ_НАМО1_Е_1МРОкМАТ1ОМ Напс11е1пГо ОРТЮЫАЬ Указатель на структуру, в которой будет представлена дополнительная информация об объекте и правам доступа к нему Возвращаемое значение • 5ТАТи5_5иССЕ55 • 5ТАТи5_ОВЗЕСТ_ТУРЕ_М15МАТСН • 5ТАТи5_АССЕ55_ОЕ1\11ЕО • 5ТАТи5_1МУА1_Ю_НАЫО1_Е Следует отметить, что в результате выполнения системного вызова ОЬКеГегепсеОЬ]ес1ВуНапс11е, увеличивается на единицу счетчик ссылок на этот объект, который поддерживается операционной системой.
Общим правилом операционной системы является то, что счетчик ссылок поддерживается для каждого объекта режима ядра, и когда он уменьшается до нуля, происходит уничтожение объекта. В некоторых ситуациях, как раз необходимо произвести увеличение счетчика ссылок на объект, чтобы продлить ему жизнь. В режиме ядра более распространен способ работы с объектами через указатели на них. Поэтому, если необходимо увеличить счетчик ссылок на объект, но в распоряжении имеется только указатель на него — не беда. На этот случай существует системный вызов ОЬПеГегепсеОЬ]ес1ВуРо1п1ег, который увеличивает ссылку на объект, используя в качестве исходных данных указатель, а не дескриптор. Таблица 7.42. Прототип вызова ОЬЯеГегепсеОЬ]'ес1ВуРот1ег 1ЧТ5ТАТ05 ОЬКеГегепсеОЬ]ес1ВуРо1п1ег Параметры 1Ы РУОЮ рОЬ]ес1 1Ы АССЕ55.МА5К 0е51гес1Ассе55 1Ы РОВЗЕСТ.ТУРЕ ОЬ]есГГуре ОРТЮЫАЬ == РА551УЕ_ЬЕУЕЬ Предоставляет указатель на объект режима ядра по открытому дескриптору Указатель на объект Маска доступа, интерпретация которой зависит от типа рассматриваемого объекта Можно установить 1\11Л_1_ (если параметр Ассе55Мос1е равен Кете/Мос/е) или одно из значений 1оРПеОЬд'ес1Туре ог ЕхЕуепЮЬдесГГуре 1Ы КРР.ОСЕ55СЖ_МСЮЕ Ассез5Мос1е • Кегпе1Мос/е — для работы в режиме ядра • изегМос/е Возвращаемое значение • 5ТАТи5_5иССЕ55 • 5ТАТи5_ОВЗЕСТ_ТУРЕ_М15МАТСН Вернемся к случаю с ожиданием, организованным по объекту программного потока одним из вызовов КеУУаИЕогХхх. Когда поток завершился, ожидание прекращено. При этом объект потока еще не уничтожен, поскольку в результате вызова ОЬЯеГегепсеОЬ]ес1ВуНапс11е получилось так, что число ссылок на него не равно нулю. Это положение можно изменить, если уменьшить число ссылок при помощи вызова ОЬОегеГегепсеОЬ]ес1, что, вероятно, приведет и к удалению объекта потока из системы. Таблица 7.43. Прототип вызова ОЬОегеГегепсеОЬдесЬ УОЮ ОЬОегеГегепсеОЬ]ес1 1Кр1_ == РА551УЕ_ЬЕУЕЬ Параметры Уменьшает счетчик ссылок на объект 1Ы РУОЮ рОЬ]ес1 Указатель на объект Возвращаемое значение УО1С1 Заметим, что при выполнении вызова 2шС1о8е происходит автоматическое уменьшение счетчика ссылок на объект и проверка, не пора ли его удалить из системы.
Функции для работы с системным представлением времени Внутреннее время в \ЛЛпс1оу75 2000/ХР/5еп/ег 2003 хранится как число 100- наносекундных отсчетов с 1 января 1601 года. Это очень большое число, и для его хранения используется 64-разрядный тип данных в структуре, обозначенной как тип данных 1_АРСЕ_1МТЕСЕк. В таблице 7.44 перечислены функции, предназначенные для работы с такими данными. Таблица 7.44. Функции для работы с системным представлением времени Функции Ке(2иегу5у51етТ|те Описание Возвращает 64-разрядное значение абсолютного системного времени КериегуТ1скСоип1 Возвращает число прерываний системного таймера (часов) с момента последней загрузки системы КериегуТ|те1псгетеп1 Возвращает число 100-нс интервалов, добавляемых к системному времени при каждом прерывании, поступающем от системного таймера (часов) 1К1Т! т еТоТ! т е Н е 1 с15 Разбивает 64-разрядное системное время на поля даты и времени III1Т! т е Н е 1 с15Т0Т1 т е Преобразует дату и время в 64-разрядное значение абсолютного системного времени В11Со1™ег11_опдТо1_агде1п1едег В11Со1™ег11И_опдТо1_агде1п1едег Создает знаковое 1_АкСЕ_11\1ТЕСЕР. Создает положительное 1_АР.СЕ_11\1ТЕСЕР. Различные арифметические и логические операции над типом данных В111_агде1п1едегХхх 1_АР.СЕ_11\1ТЕСЕР. (вместо этих функций в пакете ООК рекомендовано использовать встроенную поддержку компилятора для 64-разрядных операций) Все приведенные функции могут вызваться на любом уровне 1РС21-. Прототип одной из них, вызова Ке<2иегу5у81етТ|те, приводится ниже. Таблица 7.45. Прототип вызова Ке(2иегу8уз{е1тТПте У/ОЮ Кериегу5у51етТ|те 1Вр1_ == любой Параметры Возвращает текущее время ООТ РЬАкСЕ 11МТЕСЕР. „ . пп . СиггепГПте- Время в 100 нс интервалах с 1 января 1601 года Возвращаемое значение уо!с1
ГРгеу|ои51 [Мех!],
Функции для работы с Системным Реестром Системный Реестр является системной базой данных для хранения разнообразных параметров, используемых приложениями пользовательского режима и программными модулями, работающими в режиме ядра. В операционных системах от \Л/1пс1о\л/5 98 до \Л/|Пс1о\л/5 Зегуег 2003 существует набор системных вызовов, которыми можно воспользоваться в режиме ядра для доступа к существующим записям в Системном Реестре, для их чтения, редактирования, удаления, а также — для создания новых. Перед тем, как перейти к рассмотрению собственно функций доступа к Системному Реестру из кода режима ядра, еще раз повторим, что Реестр разбит на основные разделы, среди которых основным является раздел НКЕУ_1_ОСА1__МАСН1МЕ (в литературе часто используется сокращение НК1_М). Разделы могут быть разбиты на подразделы произвольной глубины вложения. В англоязычной литературе подразделы могут называться словом "ра1К", однако, чаще употребляется "кеу" или "гед151гу кеу". В подразделе (даже если он содержит вложенные подразделы) могут содержаться параметры (уа1иез), которые имеют имя (обычно, в литературе называемые \/а1иеЫате) и значение. Наиболее точно способ доступа к параметрам отражает следующее представление. Каждый подраздел можно считать файлом (в начале работы его следует открыть и получить соответствующий дескриптор), после чего можно работать с вложенной в него информацией — собственными параметрами, вложенными подразделами и их параметрами.
Функции доступа к Системному Реестру, предоставляемые Диспетчером ввода/вывода Функции доступа к Реестру, предоставляемые Диспетчером ввода/вывода, предоставляют доступ по указателю на объект устройства, либо по имени зарегистрированного интерфейса (аналог символьной ссылки). На практике, программисты выбирают в качестве имени интерфейса глобальный идентификатор С11Ю в строковом представлении (генерируется программой СиИСеп). Документация ООК рекомендует применять функции, предоставляемые для доступа к Реестру Диспетчером ввода/вывода, вместо функций прямого доступа — для облегчения переноса на другие процессорные платформы и в качестве защиты от изменений в структуре Реестра в будущем. IоЯед^8^е^^еV^сеIп^е^Гасе регистрирует интерфейс устройства (аналог регистрации символьной ссылки), что делает возможным доступ к устройству из приложений пользовательского режима и других системных компонентов. Диспетчер ввода/вывода создает подраздел Реестра для зарегистрированного интерфейса. Драйвер может хранить в этом подразделе собственные параметры, получая доступ к нему вызовом 1оОрепОеу1се1п1егГасеКед181гуКеу. 1о6еЮеу|сеРгорег1у запрашивает из Системного Реестра установочную информацию об устройстве. 1оОрепОеу1се1п1егГасеКед181гуКеу возвращает дескриптор доступа к подразделу реестра для зарегистрированного интерфейса устройства (способ регистрации устройства в системе при помощи вызова 1оЯед181егОеу!се1п1егГасе, вместо символьной ссылки). Полученный таким образом дескриптор должен по окончании использования быть закрыт вызовом 2\л/С1озе. 1оОрепОеу!сеКед!81гуКеу возвращает дескриптор доступа к подразделу Системного Реестра для драйвера или устройства по указателю на объект устройства. Полученный таким образом дескриптор должен по окончании использования быть закрыт вызовом 2шС1о8е. 1о5е1Оеу1се1п1егГасе51а1е разрешает или запрещает доступ к зарегистрированному ранее интерфейсу устройства. Приложения пользовательского режима и другие компоненты системы могут получать доступ только к незапрещенным интерфейсам.
Функции НИХхх прямого доступа к Системному Реестру М1СНескПед151гуКеу возвращает 5ТАТ05_50ССЕ55 в том случае, если указанный вложенный подраздел существует внутри подраздела, описанного первым параметром вызова. Первый параметр указывается как одно из значений ИТ1__РЕС15ТРУ_Ххх. Например, раздел \Ред151ту\МасК|пе\5у51ет\Сиггеп1Соп1го15е1:\5еплсе5 описывается значением РТ1__РЕО15ТИУ_5ЕР\/1СЕ5, а раздел \Ред151:гу\1)5ег\Сиггеп1115ег описывается значением РТ1__РЕО15ТИУ_115ЕИ (см. заголовочные файлы пЬЗдк.К или \л/с1т.Ь). Н11Сгеа1ейед181гуКеу создает подраздел внутри подраздела Реестра, указанного вторым параметром, который работает так же, как и в описанной выше функции №1СНескКед181гуКеу. НИриегуКед151гу\/а1ие5 позволяет одним вызовом получить значения нескольких параметров из всего поддерева указанного подраздела Реестра. При обнаружении требуемого параметра происходит вызов саНЬаск процедуры «ЗиегуРоиЬпе, предоставляемой драйвером. Тем не менее, во всем многообразии примеров ООК нет ни одного случая использования этого вызова с саНЬаск процедурой риегуРоиЬпе. На практике, вызов й11риегуКед151гу\/а1ие5 выполняется с первым параметром, имеющим в своем составе флаг РТ1_Р1)ЕРУ_РЕС15ТРУ_О1РЕСТ, при котором саНЬаск процедура не используется. Я11У7п1еКед1з1гуУа1ие производит запись значения параметра Системного Реестра. Попутно может быть создан один вложенный подраздел, см. пример ниже. ЕЫ1СОВЕ_ЗТВ1ЫС пемРагате'ЬегПп1сос1еТех'Ь7а1ие; Р.'Ы1п1'Ы}п1сос1е8'Ьг1пд ( &пемРагате'ЬегПп1сос1еТех'Ь7а1ие, Ь"Ехашр1е ЕехЕ рагатеЕег 1"); ЫТЗТАТИЗ зЕаЕиз = К'ЫИг1'ЬеКед1з'Ьгу7а1ие ( РТЬ_ВЕС13ТВУ_ЗЕР71СЕЗ, Ь"Ехашр1е1\\1ппегКеу", Ь"ПемРагашеЬег", // можно даже Ь"Вложенный Раздел" Р.ЕС_32, пемРагате'ЬегПп1сос1еТех'Ь7а1ие. ВиЕЕег, зигеоЕ (ИСНАК) * ( исзЕеп (пеиРагатеЕегВпЕсос1еТехЕУа1ие . ВиЕЕег) +1 1Е ( !ЫТ_ЗЕССЕЗЗ( зЕаЕиз ) ) { #1Е ВВС БЬдРгЕпЕ("КЕ1Иг1ЕеКед1зЕгуУа1ие са!1 1з ипзиссеззЕи!."); #епсНЕ } Первый параметр РТ1__РЕ615ТИУ_5ЕР\/1СЕ5 указывает на то, что действия будут выполняться в разделе \Пед!81гу\МасНте\5у81ет\Сиггеп1Соп1го15е1\5еплсе8, или, в общепринятых терминах, НК1_М\5У5ТЕМ\Сиггеп1:Соп1го15е1\5еплсе5. В нем должен существовать вложенный подраздел \Ехатр1е1. Тогда возможны два варианта: в подраздел \Ехатр1е1 вложен существующий подраздел \1ппегКеу, либо раздел \1ппегКеу не существует (и тогда он будет создан). В результате вызова получится подраздел НК1_М\5У5ТЕМ\Сиггеп1Соп1то15е1:\5еплсе5\Ехатр1е1\1ппегКеу, в
котором будет присутствовать строковый параметр Ме\л/Рагате1ег со значением "Ехатр1е 1ех1 рагате!:ег 1". В том случае, если не существует не только подраздел \1ппегКеу, но и подраздел \Ехатр1е1, вызов Н11\Л/п1еНед181гу\/а1ие завершится неудачей. В том случае, если все указанные подразделы присутствуют, и параметр Ме\л/Рагате1:ег уже имеет какое-то значение, то в результате последовательности действий, как в описанном выше примере, в Системный Реестр будет записано новое значение, то есть "Ехатр1е 1ех1 рагате1:ег 1" . №Юе1е1еПед151гуУа1ие удаляет параметр из указанного подраздела, например: з'Ьа'Ьиз = Ш110е1е11еКед1з11гу7а1ие ( КТЬ_КЕС13ТКУ_ЗЕК71СЕЗ, Ь"Ехатр1е1\\1ппегКеу ", Ь"ЫеиРагатеЪег" ); !ЫТ_ЗЕССЕЗЗ (х_з'Ьа'Ьиз ) ) { эвс СЬдРгтп'Ь ("КбЮе1е'ЬеКед1з'Ьгу7а1ие са!1 тз ипзиссеззГи1. " ) ; #епс11^ } В результате, в НК1М\5У5ТЕМ\Сиггеп1Соп1то15е1:\5еплсе5\Ехатр1е1\1ппегКеу исчезнет параметр Ме\л/Рагате1:ег.
Работа с Системным Реестром через вызовы 7ууХхх Все-таки наиболее богатым и основательным является набор функций для работы с Системным Реестром 2шХхх. 2\л/Сгеа1еКеу открывает доступ к существующему подразделу Системного Реестра или создает новый. Возвращает дескриптор открытого объекта. 2шОрепКеу открывает доступ к существующему разделу Системного Реестра и возвращает дескриптор открытого объекта (см.пример ниже). 2ш<2иегуКеу возвращает информацию о подразделе — его класс, число и размер вложенных подразделов. Инициатор вызова должен предоставить достаточного размера буфер для принимаемой информации, иначе вызов будет завершен с кодом ошибки 5ТАТ1)5_В1)ЕЕЕР_ТОО_5МА1_1_ или 5ТАТ1)5_В1)ЕЕЕк_ОУЕкЕ1_ОУУ. 2\л/Епитега1еКеу возвращает информацию о вложенных подразделах предварительно открытого подраздела Системного Реестра. 2шЕпитега1еУа1иеКеу возвращает информацию о параметрах и их значениях для предварительно открытого подраздела Системного Реестра. 2ш<2иегуУа1иеКеу возвращает информацию о значении параметра, присутствующего в данном предварительно открытом разделе Системного Реестра. Характер возвращаемой информации определяет третий аргумент вызова, который принимает одно из значений КеуУа1иеВаз1с1пГогтаНоп, КеуУа1иеРиП1пГогтаНоп или КеуУа1иеРагНа11пГогтаНоп. Пример применения данной функции приводится ниже. 2\л/5е1Уа1иеКеу создает или изменяет значение параметра в открытом подразделе Системного Реестра. Для возможности применения этой функции, дескриптор подраздела при открытии должен быть получен с применением маски Эе51гес1Ассе55, содержащей флаг КЕУ_5ЕТ_\/А1_11Е 2шЕ1и9НКеу форсирует фиксацию изменений, сделанных в открытом подразделе вызовами 2\л/Сгеа1еКеу или 2\л/8е1Уа1иеКеу, на диске. 2\л/Ое1е1еКеу удаляет открытый подраздел из Системного Реестра. 2\л/С1озе закрывает дескриптор открытого ранее подраздела Системного Реестра, фиксирует произведенные изменения на жестком диске. Ниже приводится пример программного кода, выполняющий операции по получении значения параметра ЕггогСоп1го1 из раздела Системного Реестра, который был создан для описания драйвера и поступил в процедуру ОпуегЕп1гу в аргументе Кед151гуРа1Ь. Для случая драйвера Ехатр1е.5у5 этот подраздел называется НК1_М\5У5ТЕМ\Соп1го15е1001\5еплсе8\Ехатр1е, а собственно строка Р.ед!з1:гуРа1:Ь хранит значение: Ъ"\\КЕС13ТКУ\\МАСН1МЕ\\ЗУЗТЕМ\\СопбгоРЗеб00Р\\Зе^Рсез\\ЕхатрРе" Вся работа с Системным Реестром вынесена в отдельную функцию СеШедУа1иеО\л/огс1. // Сначала объявляем прототип СебКедУаРиеРиогс!: Рпб СеРКедУаРиеРиогб(РСИ5ТК КедРаРЬ,РСИ5ТК УаРиеИате,РЕЬОМС рУаРие);
ехбегп "С" ИТ8ТАТБ8 Бг^егЕпбгу ( та РБВ1УЕВ_0ВЙЕСТ БгтчегОЬ] есб, та РБШСОБЕ_8ТВтаС РедбзбгуРабб ) { 1Р ( ! СебВедУаУиеБмога ( Вед±збгуРабЬ->Виббег г Ь"ЕггогСопбго1" г &и!Уа!ие)) { #16 ВВС ВЬдРгтпб("Еггог 1п СебВедУаУиеБмога.") ; #епсИР } е1зе { #16 ВВС ВЬдРгтпб("Вед±збгуРабЬ\\ЕггогСопбго1 = %х.” г и!Уа!ие) ; #епсИР } тпб СебРедУаХиеВмога(РСП8ТР ВедРабЬ, РС1А18ТВ УаТиеИате, РБЬОИС рУаХие) { тпб ВебигпУаХие = 0; ИТ8ТАТБ8 збабиз; ОВОЕСТ_АТТВ1ВБТЕ8 ОЬ] есбАббгХЬибез ; НАМБЬЕ КеуНапс11е; КЕУ_УАЬБЕ_РАВТ1АЬ_таЕОВМАТ1ОМ *р1пРогта1з±оп ; БЬОЫС и1пУогтаЪ±оп8±ге; БШСОБЕ_8ТВтаС БпасоаеВедРа^Н ; БШСОБЕ_8ТВтаС Бп±соаеУа1иеМате ; // Инициализация БШСОБЕ_8ТВтаС полученными // не-счетными строками двухбайтных символов Вб11п±бБп±соае8бг±пд ( &Бп±соаеВедРаб1аЛ ВедРабй) ; Вб11п±бБп±соае8бг±пд(&Бп±соаеУа1иеИате г Уа1иеИате) ; // Описание атрибутов, в частности, полного имени подраздела ТпгбгаИгеОЬ^ есбАббггЬибез ( &ОЬ^ есбАббггЬибез, &Бп±соаеВедРаб1а, 0, // Е1адз ИБЬЬ, // Вооб аггесбогу ИБЬЬ); // 8есиггбу аезсггрбог збабиз = 2мОрепКеу( &КеуНапа1е, КЕУ_ОБЕВУ_УАЬБЕ, &ОЬ^есбАббггЬибез ); гб( !ИТ_8БССЕ88(збабиз) ) // Если не получен доступ к подразделу:
{ #1Е ВВС ВЬдРггпЕ(”=Ехатр1е= Сап поЕ ореп гед раЕН %^з . ВпЕсобеВедРаЕН . ВиЕЕег) ; ВЬдРгйпЕ(”=Ехатр1е= 8ЕаЕиз = %х.",зЕаЕиз); ФЕ (8ЕаЕиз==8ТАТВ8_1МУАШВ_НАМВЬЕ) ВЬдРгйпЕ ( "=Ехатр1е= 8ТАТВ8_1МУАЫВ_НАМВЬЕ . " ) ; ФЕ (8ЕаЕиз==8ТАТВ8_АССЕ88_ВЕШЕВ) ВЬдРгФпЕ ("=ЕхатрФе= 8ТАТВ8_АССЕ88_ВЕШЕВ . " ) ; #епс!ФЕ геЕигп 0; } // Вычисляем размер буфера для получения информации о параметре: иФпЕогтаЕФопВФге = зйгеоЕ (КЕУ_УАЬВЕ_РАВТ1АЬ_1МЕОВМАТФОМ) + зйгеоЕ (ВЬОМС) // Выделение области в страничной памяти. // Область будет помечена тегом ’ЕХаМ1 рФпЕогтаЕФоп = (КЕУ_УАЬВЕ_РАВ.ТIАЬ_1ЫЕОВМАТФОМ*) ЕхАИосаЕеРооФЭДФЕНТад (РадебРоо! г иФпЕогтаЕФопВФге г ' ЕХаМ1 ) ; ФЕ( рФпЕогтаЕФоп == МВЬЬ ) //Не выделена память { ЕмСФозе(КеуНапсПе); геЕигп 0; } // Получить описание типа КеуУаУиеРагЕйаНпЕогтаЕйоп: // зЕаЕиз = 2м(2иегуУа1иеКеу (КеуНапсПе, &ПпйсобеУа1иеМате, КеуУаУиеРагЕйаНпЕогтаЕйоп, р1пЕогтаЕйоп, и1пЕогтаЕйоп8йге, &и1пЕогтаЕ±оп8йге ); йЕ( !МТ_8ПССЕ88(зЕаЕиз) ) { #йЕ ВВС ВЬдРгйпЕ ( "=Ехатр1е= 2м(2иегуУа1иеКеу поЕ зиссеззЕи! . " ) ; #епсПЕ } е!зе { йЕ ( р1пЕогтаЕйоп->Туре == ВЕС_В1/\ЮВВ && р1пЕогтаЕйоп->ВаЕаЬепдЕН == зйгеоЕ(ВЬОМС) )
{ КЕЮоруМетогу (рУа!иел р1пЕогтаЕйоп->Еабал айгеоЕ (ОЬОМ КебигпУаЕие = 1; } } // Завершение работы ЕхЕгееРоо!(1пЕогтабйоп); ЕмСЕоае(КеуНапсПе) ; геЕигп НебигпУаЕие; } ф Практически все функции для работы с Системным Реестром должны вызываться из кода, работающего на уровне 1РС}1. равном РА551\/Е_1_Е\/Е1.

Заключение Краткий обзор структур данных и функций общего назначения и первой необходимости при программировании в режиме ядра завершен. Более детальную информацию и сведения о нерассмотренных здесь системных вызовах можно почерпнуть в документации ООК. Автор надеется, что приведенные в этой главе сведения существенно в этом помогут. Позже, в главе 10, будет рассмотрена еще одна достаточно важная группа функций, посвященная работе с программными потоками и ведающая вопросами синхронизации.
Глава 8
Основные процедуры драйвера В примере драйвера Ехатр1е.5у5, представленном в главе 3, были кратко представлены основные процедуры, наличие которых обязательно для драйвера. Общий взгляд на структуру драйвера (6 глава) дал более полное представление о наборе и назначении этих функций. Теперь рассмотрим эти процедуры с технической точки зрения — как они вызываются, какие параметры получают, какие значения должны возвращать вызвавшему их Диспетчеру ввода/вывода.
ГРгеу|ои51 [Мех!],
Процедура ОпуегЕп^гу Процедура 0пуегЕп1ту присутствует в любом драйвере и имеет данное стандартное имя. Драйверы "в-стиле-МТ" выполняют в ОпуегЕп1ту большую работу, нежели У\ЮМ драйвера — последние откладывают часть работы по инициализации устройства до момента обнаружения устройства системой, когда будет вызвана процедура АдсЮеуюе. Таблица 8.1. Параметры вызова функции ОпуегЕп1гу 1ЧТ5ТАТ05 Ог^егЕп1гу Параметры 1Ы РОР1\/ЕР._ОВЗЕСТ рОпуегОЬ]ес! 1Ы Р1Л\11ССЮЕ_5ТР1МС ркед151гуРа1:1п 1КРЬ == РА351УЕ_ЬЕУЕЬ Описание Адрес объекта драйвера Путь в регистре к подразделу драйвера Возвращаемое значение • 5ТАТи5_5иССЕ55 • 5ТАТП5_ХХХ — код ошибки Получив от Диспетчера ввода/вывода указатель на структуру ОК1\/Ек_ОВЗЕСТ (см. заголовочные файлы ООК п1с!с1к.К или \л/с1т.И), драйвер должен заполнить в ней определенные поля, а именно: Поле рОпуегОЬ]ес!:->Опуег11п1оас1 — для регистрации собственной функции Оп1оас1, которая вызывается перед выгрузкой драйвера. Поле рОг1уегОЬ]ес1:->Ог1Уег51аг11о — для регистрации собственной функции ЗЬагНо, которая необходима для организации обработки очереди необработанных запросов Бузует ОиеЫпд. Поле р0пуег0Ь]ес!:->0пуегЕх1:еп5ЮП->Ас1с10еу|се — в структуре расширения объекта драйвера 0Н1\/ЕК_ЕХТЕЫ510Ы (см. п1с1с1к.11 или уус!т.К), в котором \А/ОМ драйвер регистрирует собственную процедуру Ас1с1Оеу|се. В массиве рОпуегОЬ]ес1->Ма]ОгЕипс1:юп[1КР_МЗ_Ххх] драйвер регистрирует точки входа в собственные рабочие процедуры. Регистрация рабочих процедур происходит обычно в виде: Сгз^егОЬд есб->Мад огЕипсЫоп [ 1КР_М0_КЕАС] = Кеас1Иг1Ее_1КР11апс11ег ; Сгз^егОЬдесб->МадогЕипсЫоп [ 1КР_М0_ИК1ТЕ] =Кеас1Иг1'Ье_1КР1тапс11ег ; ^^^Vе^ОЬ^ есЬ->МаогЕипсЫоп [ 1КР_МЗ_ЕЕУ1СЕ_ССЖТКОЪ] = ^еV^сеСопЫо1КоиЫпе; Однако для фильтр-драйверов модели УУОМ, которым интересен только определенный тип запросов, возможен следующий тип регистрации: тпЕ 1; Еог( 1=0; 1<1КР_М^МАХ1МиМ_ЕиМСТ1ОМ; 1++) { Ег:ЫегОк^есЬ->1У^огЕипсЫоп [1] = туРаззТгрРоип; } Сгз^егОЬдесб->МадогЕипсЫоп [ 1КР_МО_ОЕУ1СЕ_СОМТКОЬ] = ^еV^сеСопЫо1РоиЫпе;
Здесь все запросы будут приводить к вызову функции туРаззТгрОоууп, которая занимается только лишь переадресацией запросов нижним драйверам в стеке УУЭМ драйверов. Исключение составит функция-обработчик ЮСТЬ запросов, которые будут поступать в функцию Оеу|сеСоп1го1кои111пе фильтр-драйвера. Помимо регистрации функций, процедура ОпуегЕп1гу драйвера "в-стиле-ЫТ" может выполнять следующую работу: 1. ОпуегЕп1ту определяет аппаратное обеспечение, которое драйвер будет контролировать. Это аппаратное обеспечение выделяется драйверу, то есть помечается как находящееся под управлением данного драйвера. 2. Если драйвер управляет многокомпонентным (тиИшпИ) или многофункциональным контроллером, используется 1оСгеа1еСоп1го11ег для создания объекта контроллера, после чего инициализируется структура расширения контроллера. 3. Выполняет вызов 1оСгеа1еОеу!се для создания объекта устройства для каждого физического или логического устройства под управлением данного драйвера, в процессе которого инициализируется структура расширения устройства для каждого созданного объекта устройства. Рекомендуется сразу же после этого вызова явно установить флаги (поле Е1адз в объекте устройства), описывающие способ буферизации, используемый данным устройством. 4. Созданные устройства затем делаются видимыми для приложений пользовательского режима путем выполнения вызова 1оСгеа1е5утЬоНсЬ1пк. 5. Устройство подключается к объекту прерываний. В случае, если 15И процедура требуют использования объекта ОРС (отложенного процедурного вызова), то он создается и инициализируется на этом этапе. 6. Шаги с 3 по 5 повторяются для каждого физического или логического устройства, работающего под управлением данного драйвера. 7. В случае успешного завершения, функция ОпуегЕп1ту должна возвратить Диспетчеру ввода/вывода значение 5ТАТ1)5_51)ССЕ55. В случае, если процедура ОпуегЕп1гу обнаружила, что некоторые действия, которые она должна выполнить как часть подготовки к запуску драйвера, не могут быть выполнены сейчас, но в будущем (после загрузки некоторых других драйверов, например) это станет возможным, то ОпуегЕп1гу должна зарегистрировать собственную процедуру, которая завершит инициализацию чуть позже. Регистрация такой процедуры выполняется системным вызовом 1оКед151егОпуегКе1т11аНна11ОП (см. таблицу 8.2). Таблица 8.2. Параметры системного вызова 1оКед1зЬегОнуегКетШаП2аИоп УОЮ 1оНед151егОгшег1ге1т1!аН2аНоп Параметры 1Ы РОК1УЕК_ОВЗЕСТ рОпуегОЬ]ес! 1Ы Р0к1УЕк_кЕ11\11Т1А1_12Е □пуегке1П1йаН2айопР.о1Л1пе 1Ы РУОЮ СогИех! Возвращаемое значение 1Кр1_ == РА551УЕ_ЬЕУЕЬ Регистрирует функцию драйвера для отложенной инициализации Указатель на объект драйвера Указатель на процедуру реинициализации, предоставляемую драйвером (см. таблицу 8.3 ниже). Контекстный указатель, который получит регистрируемая функция при вызове УО1С1 Таблица 8.3. Описание параметров вызова туКетШаНхеРипсИоп УОЮ туКе1пШаНгеРипс11ОП 1ВрЬ == РА351УЕ_ЬЕУЕЬ _ Функция драйвера, регистрируемая для выполнения отложенной параметры г г инициализации
1Ы РОК1УЕК_ОВЗЕСТ рОпуегОЬ]ес! 1Ы РУОЮ СогИех! 1Ы 1Л_О1\1С Возвращаемое значение Указатель на объект драйвера Контекстный блок, указанный при регистрации Количество вызовов процедуры ре-инициализации (отсчет от 0) УО1С1 Как было указано в главе 7, процедуру ОпуегЕп1ту можно разместить в "отстреливающемся" сегменте кода 11М1Т.
Процедура Ас1с1Оеу|се Процедура Ас1с1Оеу|се, выполняющая в У\ЮМ драйверах работу по подготовке устройства к работе будет подробно рассмотрена в следующей главе в контексте вопросов, связанных с РпР методологией и использованием стека \Л/ОМ драйверов.
Процедура ип1оас1 Как правило, однажды загруженный драйвер остается в системе до перезагрузки. Для того чтобы сделать драйвер выгружаемым, необходимо написать и зарегистрировать процедуру выгрузки Оп1оай. Диспетчер ввода/вывода затем производит вызов этой процедуры в момент ручной либо автоматической выгрузки драйвера — как раз перед удалением драйвера из памяти. Таблица 8.4. Описание прототипа функции Чп/оас! УОЮ Цп1оаа == РА351УЕ_ЬЕУЕЬ Параметры Выполняет завершающие действия 1Ы РОк1\/Ек_ОВЗЕСТ рОпуегОЬ]ес1 Указатель на объект драйвера Возвращаемое значение уо!с1 Хотя действия процедуры 11п1оас1 могут меняться от драйвера к драйверу, общими являются следующие шаги, характерные более для драйверов "в-стиле-МТ". 1. Для некоторых типов аппаратуры необходимо сохранить ее состояние в Системном Реестре. При последующей загрузке драйвера эти данные могут быть использованы в процедуре ОпуегЕпЬгу. Скажем, драйвер принтера может сохранить последнее значение разрешения печати. 2. Если прерывания разрешены для обслуживаемого устройства, то процедура выгрузки должна запретить их и произвести отключение от объекта прерываний. Ситуация, когда устройство будет порождать запросы на прерывание в то время, как объект прерываний не существует, неминуемо приведет к краху системы. 3. Символьная ссылка должна быть удалена из пространства имен, видимого пользовательскими приложениями. Это выполняется при помощи вызова 1оОе1е1е5утЬоНсипк. 4. Объект устройства должен быть удален вызовом 1оОе1е1еОеу1се. 5. В случае, если драйвер управляет многокомпонентным (ти111ип|1) контроллером, необходимо повторить шаги 3 и 4 для каждого устройства, подключенного к контроллеру, а затем необходимо удалить сам объект контроллера при помощи вызова 1оОе1е1еСоп1го11ег. 6. Следует выполнить освобождение памяти, выделенной драйверу, во всех типах оперативной памяти. Драйверы УУЭМ модели выполняют практически все из описанных выше действий в обработчике 1КР_МЗ_РЫР запросов с субкодом 1РР_М1\1_РЕМО\/Е (то есть посвященном удалению устройства из системы). Поскольку процедура 11п1оас1 выполняется на уровне РА551\/Е_1_Е\/Е1_ 1Р<21-, то это означает возможность безопасного доступа к ресурсам страничной памяти. ф Процедура выгрузки драйвера Пп/оас/ не вызывается в момент отката системы, и если существует необходимость выполнять какую-либо работу при откате системы, то это следует сделать в специально предназначенной на то процедуре драйвера, зарегистрированной для обработки 1РР пакетов с кодом 1РР_МЗ_5НС1ТОО\Л/П. Объект устройства должен быть с помощью вызова 1оРед151ег5Ни1с/о^п^1ШсаИоп занесен в очередь объектов, получающих уведомление о перезагрузке, — только при этом условии будет вызвана процедура, зарегистрированная для обработки пакетов с кодом 1РР_МЗ_5НС/ТОО\Л/1\1.
Адресация и доступ к данным в 1ЯР пакетах чтения/ записи Сразу же после создания объекта устройства (будет ли это сделано в процедуре ЭпуегЕпТту, как это было указано выше для драйвера "в-стиле-ЫТ", или в процедуре Ас1сЮеу|се для УУЭМ драйверов) рекомендуется явно описать способ, каким новый объект устройства готов воспринимать поля пакета ТКР, описывающие адреса областей памяти, через которые будет происходить обмен между драйвером и его клиентами. Например: РБЕУ1СЕ_ОВ^СТ рЕеиОеуЕсеОЬ) есЕ ; IоС^еаЕе^еV^се(. . . , &рЫеиВе^71сеОЬ ) есЕ) ; рЫеиВе^АсеОЬ) есЕ->Е1адз |= ВО_ВЕЕЕЕКЕВ_1О; либо: рЫеиВе^АсеОЬ) есЕ->Е1адз |= ВО_В1Р.ЕСТ_1О; По умолчанию подразумевается ’рМе\л/0еу1се0Ь)ес1->Е1ад5 |= О' — метод МЕТТНЕК (ни один из первых двух). В том случае, если новый объект устройства ориентирован на работу с нижним драйвером в стеке драйверов, следует скопировать эти флаги из объекта устройства, к которому подключен данный (одним из вызовов ТоАНасНХхх, см. главу 9), например: рЫеиВе^АсеОЬ ) есЕ->Е1адз | = (р^пс^е^1у^пд^еV0Ь^есЕ->Е1адз) & (ОО_ВОЕЕЕКЕО_1О | ОО_О1КЕСТ_1 По традиции, главными считаются запросы в форме пакетов ТКР с основным кодом ТКР_МЗ_КЕАО (в результате вызова ПеайЕНе) либо ТКР_МЗ_У\/К1ТЕ (в результате вызова УУгНеЕПе). Диспетчер ввода/вывода, если он замечает в описании устройства установленный флаг ОО_ОТКЕСТ_ТО, непременно проверяет возможность доступа к буферу, который клиент драйвера указывает в своем запросе как буфер с данными (\А/КТТЕ) или для данных (КЕАЭ), подготавливает МОЕ список для него и фиксирует страницы в оперативной памяти. Адрес подготовленного таким образом МЭЕ списка вносится в поле ТКР пакета под названием Мс11Ас1с1ге55. Если попробовать получить виртуальный адрес отданного МЭЕ списка вызовом Мт(зе1Мс1Мг1иа1Ас1с1ге88, то получится именно виртуальный адрес (пользовательского адресного пространства), который предоставило пользовательское приложение в качестве адреса буфера с выводимыми данными. Адрес в терминах системного адресного пространства для той же области данных можно получить, если вызвать Мт(зе15у81етАс1с1ге88ЕогМс11. Когда обработка запроса ввода/вывода полностью завершена, клиентский буфер де- блокируется и удаляется из схемы распределения системной области памяти. В том случае, если объект устройства, которому адресован ТКР пакет, описан флагом ОО_ВЕ1ЕЕЕКЕО_Ю, то драйвер должен взять адрес буферной области из поля ТКР пакета Аззоаа^есПгр.Зуз^етВиГГег. Данный адрес будет адресом в системном адресном пространстве. Диспетчер ввода/вывода выделяет этот буфер в нестраничной памяти после проверки на доступность предоставленного клиентом буфера. При запросе
ввода данных (ИЕАО-запрос) Диспетчер ввода/вывода по окончании операции переносит данные из системного буфера в клиентский, а адрес клиентского буфера запоминается в поле ТИР пакета ОзегВиГГег. При запросе вывода данных (УУРТТЕ- запрос) в системный буфер переносятся данные из клиентского буфера (поле ОзегВиГГег равно МОП). В обоих описанных выше случаях, предоставленные в ТИР пакете адреса Аззос1а1:ес11гр.5у51етВиГГег можно использовать в произвольном контексте в потоках режима ядра. В том случае, если устройство не объявило признаков ОО_ОТИЕСТ_ТО или ОО_В11ЕЕЕИЕО_ТО, то Диспетчер ввода/вывода не делает никаких особых действий, а просто помещает адрес буферной области, переданной ему инициатором запроса в поле ТИР пакета ОзегВиГГег. Оба поля Аззос1а1:е.5у51етВиП:ег и МЛАс1с1ге55 устанавливаются равными МОП. В последнем случае помещенный в поле ОзегВиГГег виртуальный адрес является "нехорошим" адресом. В большинстве случаев инициатором обращения к драйверу является программный поток пользовательского режима. Соответственно, предоставленный им виртуальный адрес требует для своей корректной интерпретации контекст соответствующего пользовательского потока. Что, разумеется, исключает возможность использования такого адреса в произвольных контекстах режима ядра без предварительной подготовки (подготовки МОЕ списка и т.п.). Поэтому метод ЫЕТТНЕН можно рекомендовать только для драйверов самых верхних драйверных слоев. Длина буфера во всех случаях передается в полях Рагате1:ег5.УУп1:е.1_епд1:11 (при УУНТТЕ-запросах) или Рагате1:ег5.Неас1.1_епд1:11 (при ИЕАЭ-запросах).
ГРгеу1ои8~| ГИехМ
Рабочие процедуры драйвера Все функции, опубликованные в процедуре ОпуегЕп^гу путем заполнения массива □пуегОЬ)ес1>>Ма]огЕипс1:юп [...], вызываются Диспетчером ввода/вывода для обработки соответствующих запросов от клиентов драйвера (приложений пользовательского режима или от модулей режима ядра). Запросы эти всегда оформлены в виде специальных структур данных — пакетов 1РР.
ГРгеу1ои8~| ГИехМ
Пакеты 1ЙР Практически весь процесс ввода/вывода, имеющий место в У\Лпс1о\л/5, является пакетно-управляемым. Отдельная транзакция ввода/вывода описывается рабочим рецептом, предписывающим драйверу, что делать. При помощи 1КР прослеживается также обработка запроса в подсистеме ввода/вывода. Этот рабочий рецепт имеет форму структуры данных, называемой 1/о Кедиез! Раске! (1РР) — пакет запроса на ввод/вывод. При каждом запросе из программного кода клиента драйвера на выполнение операции ввода/вывода, включая ЮСТЬ запросы (управляющие воздействия на аппаратуру), Диспетчер ввода/вывода выделяет под 1Р.Р область нестраничной памяти. Определив по дескриптору открытого файла, к какому драйверу и объекту устройства адресовано обращение, и по запрошенному коду операции ввода/вывода (1РР_МЗ_Ххх), Диспетчер передает сформированный пакет 1Р.Р в соответствующую рабочую (см.ниже) процедуру драйвера. (Следует отметить, что для доступа из программного кода клиента применяется "файловая абстракция" процесса взаимодействия с драйвером — открытие, чтение, запись, дескрипторы и т.п.) Пакеты 1РР являются структурами данных переменной длины, и состоят из стандартного заголовка, содержащего общую учетную информацию, и одного или нескольких блоков параметров, называемых 1/0 з!аск /осаИоп — ячейкой стека ввода/вывода. Рис. 8.1 Структура пакета 1кР

Заголовок 1КР Ниже перечислены поля заголовка, к которым можно обращаться из программного кода драйвера. К другим полям заголовка обращаться не рекомендуется. Таблица 8.5. Заголовок пакета 1КР Поля Ю_5ТАТ115_В1_ОСК ТоЗЬаШз Описание Код состояния (статус) запроса РУОЮ Аз5ОС1а1ес11гр.5уз1:етВиГГег Указатель на системный буфер для случая, если устройство поддерживает буферизированный ввод/вывод РМОЬ Мс11Ас1с1ге53 Указатель на МО1_ список в случае, если устройство поддерживает прямой ввод/вывод РУОЮ ОзегВиГГег Адрес пользовательского буфера для ввода/ вывода ВООЬЕАЫ Сапсе! Индикатор того, что пакет 1КР должен быть аннулирован Фрагмент структуры 1Р.Р под названием 1о5^из фиксирует окончательное состояние данной операции ввода/вывода. Когда драйвер готов завершить обработку пакета 1Р.Р, он устанавливает в поле 1о5^из.8^из значение 5ТАТ115_ХХХ. В поле ТоЗ^изЛпГогтайоп этого блока записывается 0 (если произошла ошибка) или другое определенное операцией ввода/вывода значение, чаще всего — количество переданных/полученных байт данных (которое может быть и равно нулю). Вопросы адресации и доступа к буферам данных, описываемых пакетом 1РР, будут рассмотрены позже.
Ячейки стека ввода/вывода Основное назначение ячеек стека ввода/вывода (1/0 з!:аск 1оса1:юп) состоит в том, чтобы хранить функциональный код и параметры запроса на ввод/ вывод (последние могут претерпевать изменения при путешествии пакета по стеку драйверов). Ниже приводятся поля ячеек стека ввода/вывода, к которым драйвер может обращаться непосредственно по указателю (чего не рекомендуется делать для остальных полей). Таблица 8.6. Некоторые элементы ячейки стека ввода/ вывода 1О_8ТАСК_1_ОСАТ1ОМ, * РIО_5ТАСК_^ОСАТIО^ Поля Описание 11СНАК Ма]ОгРипсйоп Код 1КР_МЗ_ХХХ, описывающий назначение операции ЬСНАР. М|погрипсйоп Суб-код операции РОЕУ1СЕ_ОВЗЕСТ □еу1сеОЬ]ес1 РЕИ-Е-ОВЗЕСТ Н1еОЬ]ес1 Указатель на объект устройства, которому был адресован данный запрос 1Р.Р Файловый объект для данного запроса, если он задан итоп Рагате1ег5 (трактовка определяется значением МадогЕипсйоп): зЪгис! Кеас! з1гис1 УУгКе Параметры для 1КР типа 1КР_МЗ_КЕА0: • 111_01\1С 1_епдИ1 • 111_01\1С Кеу • 1_АКСЕ_11\1ТЕСЕк Ву^еОГГзе! Параметры для 1РР типа 1кР_МЗ_У\/Р.1ТЕ: • 111_01\1С 1_епд1±1 • 111_01\1С Кеу • 1_АР.СЕ_11\1ТЕСЕР. ВуЬеОйъеЬ зЪгис! Оеу|се1оСоп1то1 Параметры для 1КР типа 1КР_МЗ_0ЕУ1СЕ_СО1\1ТКО1_: • 1Л.01МС Ои^риШиГГеН-епдЫ • 1Л_01\1С 1при1ВиГГегЬепдЫ
• 1Л_01\1С 1оСоп1го1Сос1е • РХ/ОЮ Туре31при1ВиГГег Для запроса, который адресован драйверу самого нижнего уровня, соответствующий 1Р.Р пакет имеет только одну ячейку стека. Для запроса, который послан драйверу верхнего уровня, Диспетчер ввода/вывода создает пакет 1Р.Р с несколькими стековыми ячейками — по одной на каждый драйверный слой. Другими словами, размер стека в пакете 1Р.Р численно равен количеству драйверных слоев, участвующих в обработке запроса на данную операцию. Любому драйверу в иерархии разрешен доступ только к его собственной — "числящейся" за ним — ячейке стека пакета 1Р.Р (хотя никто не контролирует и иные "противоправные" действия). В случае если текущий драйвер решит обратиться к драйверу более низкого уровня с новым пакетом 1КР собственного производства, то он должен гарантировать, что в новом 1РР пакете имеется достаточное количество стековых ячеек. Когда драйвер передает 1Р.Р пакет нижнему драйверному уровню, Диспетчер ввода/вывода автоматически изменяет указатель стека ввода/вывода 1Р.Р пакета таким образом, что он указывает на стековую ячейку, предназначенную для драйвера очередного нижнего уровня. Когда обработка пакета драйвером нижнего уровня завершена, и он "отпускает" пакет 1К.Р, указатель стека снова возвращается в исходное положение и указывает на ячейку стека для лежащего выше драйвера. Разумеется, для получения указателя на текущую ячейку стека существует специальный системный вызов 1о(зе1Сиггеп1§1аск1-оса1юп.
ГРгеу|ои51 [Мех!],
Рабочие процедуры драйвера Рабочие процедуры драйвера (в англоязычной литературе по драйверам они всегда называются сПзраЬсН гоиНпез') регистрируются драйвером во время работы ОпуегЕп1ту. Фактически, таким образом драйвер сообщает Диспетчеру ввода/вывода о своих намерениях, какого типа запросы (то есть 1ИР_МЗ_Ххх коды) он собирается поддерживать. В тот момент, когда инициализируется запрос ввода/вывода, Диспетчер ввода/вывода в первую очередь создает пакет ТИР, чтобы зафиксировать след запроса, факт его прохождения. Наряду с другой информацией, в этом ТИР пакете (в ячейках стека ТИР пакета) в поле Ма)огЕипсТюп сохраняется код ТНР_МЗ_Ххх для того, чтобы однозначно идентифицировать тип этого запроса. Код 1ИР_МЗ_Ххх используется Диспетчером ввода/вывода для того, чтобы извлечь из массива Ма)огЕипс1юп объекта драйвера указатель на нужную для обработки запроса процедуру драйвера. В том случае, если драйвер не поддерживает запрошенную операцию, то соответствующий элемент Ма)огЕипс1юп указывает на код внутри Диспетчера ввода/вывода (поскольку драйвер предоставил в таком случае Диспетчеру ввода/вывода право распорядиться по собственному усмотрению), а именно — на функцию _IоIпVаI^с^^еV^сепе^ие9^, который возвращает клиенту драйвера сообщение об ошибке. Таким образом, инициатива обеспечения каждого нужного кода ТИР_МЗ_Ххх собственной обрабатывающей процедурой принадлежит автору драйвера. Метод объявления процедур ввода/вывода позволяет также объявить одну процедуру (функцию драйвера) для обслуживания нескольких запросов ввода/вывода. Для этого нужно лишь поместить адрес такой универсальной (если она действительно для этого предназначена) функции в нескольких ячейках массива Ма)огЕипсТюп, как это было показано выше для ОпуегЕп1ту фильтр-драйвера. Так как пакет ТИР содержит код ТИР_МЗ_Ххх, то с его помощью всегда можно восстановить точный код требующегося действия. Не следует выполнять внутри ОпуегЕпТгу операции над позициями в массиве МазогЕипсйоп, соответствующих функциям ввода/вывода, не поддерживаемым драйвером. Диспетчер ввода/вывода заполняет пропущенные места в массиве МазогЕипсйоп значением указателя на функцию _1о1гшаНсЮеу|сеПеяие51 прежде, чем обращается к процедуре ОпуегЕпТгу. Все функции драйвера, участвующие в обработке запросов и подлежащие занесению в таблицу (массив) Ма)огЕипс1юп используют один и тот же протокол вызовов, что включает число и тип передаваемых им параметров и тип вызова. Рабочие процедуры выполняются на РА55Т\/Е_1_Е\/Е1_ уровне ТНО1-, что означает, что они могут обращаться к ресурсам страничной памяти. (Разумеется, возможны ситуации искусственного изменения этого правила, как это было показано в примере Ехатр1е.5у5 главы 3.) Таблица 8.7а. Прототип, описывающий рабочую (сИвраЁсй) процедуру драйвера 1ЧТ5ТАТ05 О15ра1сНКои11пе 1Кр1_ == РА551УЕ_ЬЕУЕЬ Параметры Описание 1Ы РОЕУ1СЕ_ОВЗЕСТ рОеуОЬ]ес1 1Ы РР1Р р!гр Указатель на объект устройства, для которого предназначается 1РР запрос Указатель на пакет 1Р.Р, описывающий этот запрос Возвращаемое значение о 5ТАТи5_5иССЕ55 — запрос обработан о 5ТАТи5_РЕЫО11\1С — ожидается обработка запроса о 5ТАТ05_ХХХ — код ошибки
Набор рабочих процедур Все драйверы обязаны поддерживать функцию, которая отвечает за обработку запроса с кодом 1кР_МЗ_СКЕАТЕ, так как этот код генерируется в ответ на вызов пользовательского режима СгеаСеЕЛе. Без поддержки этого запроса пользовательское приложение не будет иметь никакой возможности получить дескриптор (Ьапс11е) для доступа к драйверу устройства, а значит, и к самому устройству. Разумеется, должна существовать функция, обрабатывающая запрос с кодом 1РР_МЗ_С1_О5Е, который генерируется по обращению приложения к драйверу при помощи функции АР1 пользовательского режима ОозеНапсПе. Кстати заметить, вызов ОозеНапсИе выполняется системой автоматически в момент завершения приложения пользовательского режима для всех дескрипторов ресурсов, оставленных эти приложением открытыми. Набор остальных поддерживаемых функций зависит от особенностей устройства, которое находится "под покровительством" драйвера. Таблица 8.76 показывает взаимосвязь между кодами запросов ввода/вывода и вызовами АР1 пользовательского режима, которые приводят к их генерации. При написании многослойных драйверов следует помнить, что вышестоящий драйвер обязан поддерживать подмножество запросов нижестоящего драйвера (драйверов), так как запрос пользовательского приложения проникает в нижние слои только через вышестоящий драйвер. Таблица 8.76. Коды запросов ТКР и функции АР1 пользовательского режима 1НР коды 1кР_МЗ_СНЕАТЕ Вызовы АР1 пользовательского режима Сгеа1еН1е 1кР_ _МЗ_ .СЬЕАМОР Очистка ожидающих обработки пакетов 1РР при закрытии дескриптора при отработке вызова С1о5еНапс11е 1РР_ _МЗ_ _С1_О5Е С1о5еНапс11е 1РР_ _МЗ_ _РЕАО Реас1Р|1е 1РР_ _МЗ_ _\Л/Р1ТЕ УУгИеЕИе 1РР_ _МЗ_ _0Е71СЕ_С0МТР01_ ^еV^сеIоСоп^^оI 1РР_ _МЗ_ ДЫТЕРЫАЬ_0Е71СЕ_С01\1ТР01_ Действия по управлению устройством, доступные только для клиентов, работающих в режиме ядра (недоступно для вызовов пользовательского режима) 1РР_ _МЗ_ ^иЕРУ_1ЫЕСЖМАТ1ОЫ Передача длины файла в ответ на вызов <эе№1е§12е 1РР_ _МЗ_ _5ЕТ_1ЫЕОРМАТЮЫ Установка длины файла по вызову 5е1ЕПе5|2е 1РР_ _МЗ_ _РШ5Н_ВиРРЕк5 Запись или очистка служебных буферов при отработке вызовов (например): • Е1и5НЕ|1еВиГГег5 • Е1и5НСоп5о1е1при1ВиГГег • РигдеСотт 1РР_ _МЗ_ .знитоодоы Действия, которые нужно выполнить драйверу в процессе подготовки системы к завершению работы Диспетчер ввода/вывода вызывает процедуры диспетчеризации в ответ на поступающие запросы от пользовательских приложений или запросов от клиентов, работающих в режиме ядра. Перед вызовом Диспетчер ввода/вывода строит пакет 1КР и заполняет его необходимыми данными, включая указатель на пользовательский буфер. Этот пользовательский буфер проверяется Диспетчером ввода/вывода с той целью, чтобы обеспечить доступность (для чтения или записи) страничных адресов буфера (если таковые имеются в области страничной памяти) в контексте поступающего запроса. В том случае, если поступил запрос на буферированную операцию ввода/вывода, Диспетчер сначала выделяет буфер в нестраничной памяти
и, в случае запроса на запись, копирует данные из пользовательского буфера в созданный. В случае запроса на прямой ввод/вывод, Диспетчер "вызывает" целиком пользовательский буфер в физическую память и фиксирует его (не допуская его перемещения на жесткий диск, как это может быть с областями страничной памяти). Рабочая процедура обычно может отслеживать состояние обработки запроса только с использованием пакета 1РР. В том случае, если рабочая процедура использует какие- либо данные за пределами пакета 1РР, драйвер должен обеспечить достаточно надежные меры по синхронизации доступа к этим данным. Это означает, что можно было бы использовать спин-блокировку для координации действий с другими процедурами, выполняющимися на уровне 1ЕЩ1_ не выше 015РАТСН_1_Е\/Е1_. Для синхронизации доступа из процедур, выполняющихся на уровне кода обслуживания прерываний, следует использовать КеЗупсготгеЕхесийоп. Пакеты 1КР являются данными коллективного пользования, хотя и с последовательным доступом. В частности, Диспетчер ввода/вывода использует поля объединения Рагате1егз для того, чтобы завершить обработку запроса. Например, по окончании обработки буферизованного ввода/вывода, ему необходимо выполнить копирование данных из области памяти нестраничного пула в буфер, заданный пользовательским приложением. По завершении копирования Диспетчер ввода/ вывода должен освободить буфер в нестраничном пуле. Одно из полей объединения (ипюп) Рагате1егз как раз указывает на этот буфер, и изменение кодом драйвера данного указателя приведет к нарушению работы системы.
Последовательность действий рабочих процедур Конкретное поведение каждой из рабочих процедур драйвера будет зависеть от функций, которые ей будет поручено поддерживать. Тем не менее, общие обязанности этих процедур включают следующие моменты: 1. Вызов 1о(зе1Сиггеп11гр51аскЬоса11ОП для того, чтобы получить указатель на ячейку стека ТИР пакета, относящуюся к ведению данного драйвера. 2. Дополнительную проверку параметров, специфичную для данного типа запроса и устройства. 3. Продолжение обработки ТИР до момента успешного завершения или возникновения ошибочной ситуации, препятствующей дальнейшей обработке. Когда рабочая процедура драйвера обрабатывает пакет 1РР, существует только три возможных варианта окончания ее работы. Параметры запроса не проходят проверку на полноту и правильность, и запрос отклоняется. Запрос может быть обработан в пределах данной рабочей процедуры драйвера, без вовлечения физического устройства, например, чтение/запись данных нулевой длины. Происходит обращение к физическому устройству с целью получить данные или выполнить действия над устройством, необходимые для завершения запроса. Случай 1: Ошибочная ситуация В случае, если процедура диспетчеризации не может разрешить проблем, возникших при обработке запроса, ей необходимо отклонить запрос и сообщить об этом вызывающей стороне. Следующие шаги необходимо предпринять при отражении "запроса". 1. Соответствующий код ошибки сохраняется в поле 51а1из в блоке То51:а1и5 пакета ТИР и производится обнуление поля ТпГогтайоп. 2. Производится вызов 1оСотр1е1еЯечие81 для того, чтобы завершить обработку пакета ТИР (без повышения приоритета). 3. Рабочая процедура, возвращая управление, должна возвратить тот же код ошибки, что был помещен в поле То51:а1и5.51а1:и5 пакета ТИР. МТ5ТАТН5 ИгдбеРедиезбНапсПег ( 1Ы РОЕУ1СЕ_ОВ^СТ р^еVОЬ^, 1Ы Р1Р.Р р!гр) { // Запрос не поддерживается данным устройством (например): р1гр->1оЗбабиз.Збабиз = ЗТАТНЗ_ЫОТ_ЗНРРОРТЕО; р!гр->1оЗбабиз.Тпбогтабдоп =0; // Ни одного байта не перед IоСотр1ебеКе^иезб (р1гр, 1О_МО_1МСКЕМЕМТ) ; // без изменения п гебигп 5ТАТНЗ_ЕЮТ_5НРРОКТЕЕ; } Вызов 1оСотр1е1еЯечие81 будет подробно рассмотрен в следующей главе, но сейчас следует отметить, что после него область памяти, занятая под собственно пакет ТИР может оказаться свободной. Поэтому категорически нельзя экономить и писать
операторы типа "гекигп р1гр->1о51а1:и5.51:а1и5;", впрочем, как и обращаться по адресу р1гр в каких бы то ни было целях после вызова 1оСотр1е1еКеяие51. Случай 2: Завершение работы над 1КР запросом Некоторые запросы могут быть полностью обработаны без обращения к физическому устройству, за которое отвечает драйвер, например, получение дескриптора устройства или настройки режима работы самого драйвера. В этом случае рабочая процедура должна выполнить следующие действия: 1. Поместить код успешного завершения в поле 51а1:и5 в блоке 1о51:а1и5 пакета 1НР и указать приемлемое значение в поле ТпГогтайоп. 2. Выполнить вызов 1оСотр1е1еЯеяие51, чтобы освободить пакет 1РР без повышения приоритета. 3. Возвратить управление с кодом 5ТАТ05_50ССЕ55. МТ5ТАТЕ5 С1озеРедиез'ЕНапс11ег ( 1Ы РОЕУ1СЕ_ОВ^СТ р^еVОЬ^, 1Ы Р1РР р!гр) { р1гр->1о5'ЬаЕиз . З'Ьа'Ьиз = 5ТАТЕЗ_5ЕССЕЗЗ ; р1гр->1оЗ'Ьа'Еиз . Тп^огта'Ыоп = 0; IоСотр1еЬеР.е^иезЬ ( р!гр, 1О_ЫО_1ЫСРЕМЕЫТ ) ; ге’Ьигп ЗТАТУЗ ЗЕССЕЗЗ; } Случай 3: Работа через очереди 1КР пакетов Разумеется, простейший драйвер может инициализировать свое устройство в процедуре ОпуегЕп1ту, в обработчике запросов 1РР_МЗ_РЕАЭ сразу же считывать данные из устройства (например, 1_РТ порта). Тем не менее, полномасштабный ритуал работы с подсистемой ввода/вывода \Л/1Пс1охл/5 (то есть Диспетчером ввода/вывода) диктует иную последовательность действия. Рабочая процедура должна поместить пакет 1РР в очередь для последующей обработки процедурой ЗЬагНО и сразу же возвратить Диспетчеру ввода/вывода сообщение о том, что обработка 1РР не завершена, а именно: 1. Выполнить вызов 1оМагк1грРепсНпд — чтобы информировать Диспетчера ввода/ вывода о том, что пакет 1Р.Р поставлен в очередь на обработку. 2. Выполнить вызов 1о51аг1Раске1, чтобы поместить пакет 1Р.Р в системную очередь для последующей его обработки процедурой З^агНО. Драйвер может реализовывать и свои очереди 1КР пакетов. 3. Возвратить управление из рабочей процедуры с кодом завершения 5ТАТ115_РЕМО1МС. Фрагмент кода, приведенный ниже, демонстрирует, как рабочая процедура размещает 1КР запрос в очереди на обработку. ЫТЗТАТИЗ ВеайВедиезЕНапсИег ( 1Ы РОЕУ1СЕ_ОВ^СТ р^еV^сеОЬ^ есЕ, Р1КР р1гр ) {
// ТКР "в работе", но работа с ним будет происходить // через очередь пакетов (в данном случае - системную): 1оМагк1грРепсНпд( р!гр ); // Четвертый параметр позволяет указывать процедуру удаления // СапсеТКоибтпе, что подробно обсуждается в конце главы 9. 1оЗбагбРаскеб( р^еV^сеОЬ^еск, р1гр, О, РГОЬЬ ); гебигп ЗТАТЧЗ_РЕЫО1ЫС; } Некоторые источники указывают, что Диспетчер ввода/вывода автоматически завершает запросы, не помеченные кодом 5ТАТ1)5_РЕМОТЫС, сразу же после получения управления из рабочей процедуры. Возможно, данный автоматически запускающийся механизм когда-то и работал именно так. Однако в доступных на сегодня версиях У\Лпс1оуу5 это не подтверждается, то есть для нормального завершения обработки ТКР пакета необходимо, чтобы текущий драйвер производил вызов 1оСотр1е1еПеяие91 в конце обработки ТКР пакета в своей рабочей процедуре (если только он не помечен как отложенный в системную очередь, 5ТАТ115_РЕЫО11\16). К тому же, при этом условии будут запущены все процедуры завершения в вышестоящих драйверах, если таковые, конечно, имеются. Подробнее эти вопросы рассмотрены в главе 9.
ГРгеу|ои51 [Мех!],
Рабочие процедуры обслуживания ЮСТЬ запросов Запросы данного типа формулируются в рамках двух более гибких типов запросов на ввод/вывод (1НР пакетах). Значение основного кода 1КР_МЗ_Ххх драйвер может найти в соответствующей ему ячейке стека 1РР пакета, а ЮСТЬ код размещен в Рагате1ег5.Оеу|се1оСоп1го1.1оСоп1:го1Сос1е той же ячейки стека 1РР пакета. 1ИР_МЗ_ОЕ\/1СЕ_СОМТРО1_ позволяет получать расширенные запросы от клиентов пользовательского режима посредством их действий через АР1 вызов Оеу1се1оСоп1го1. 1ИР_МЗ_1МТЕИМА1__ОЕ\/1СЕ_СОМТРО1_ позволяет получать расширенные запросы от кода (клиента), функционирующего на уровне режиме ядра. Доступ к этим операциям из кода пользовательского режима не разрешается. Эта возможность используется, главным образом, другими драйверами в многоуровневом драйверном стеке для передачи специальных запросов. С другой стороны, версия для внутреннего пользования идентична стандартной версии. Значение 1оСоп1то1Сос1е помещается в 1НР пакет инициатором запроса. Следует отметить, что реализация процедуры разборки таких 1К.Р запросов в драйвере требует вторичной диспетчеризации — в соответствии со значением 1оСоп1го1Соде. Это значение известно еще со времен М5 ЭО5 под именем ЮСТЬ — 1при1/0и1:ри1 СопТгоЬ соде. Значения ЮСТЬ, передаваемые в драйвер, могут быть определены разработчиком драйвера и имеют фиксированную внутреннюю организацию. Рисунок 8.2 демонстрирует поля 32-битной структуры ЮСТЬ кода. Пакет ООК имеет в своем составе макроопределение СТ1__С00Е, которое обеспечивает приемлемый механизм генерации значений ЮСТЬ, уже использованный в главе 3. Таблица 8.8 описывает аргументы этого макроопределения. Тип устройства Способ доступа Управляющей код Тип передачи Рис. 8.2. ОеиюеТуре ПедшгебАссевв Соп*го1Собе ТгапйГегТуре Структура блока данных ЮСТЬ | 31-16 | 15-14 | 13-2 1-0 | Таблица 8.8. Аргументы макроопределения СТЬ_СООЕ Параметры Описание □еу1сеТуре Е11_Е_0Е\/1СЕ_ХХХ значения передаваемые в 1оСгеа1еОеу|се • 0x0000 — 0х7ЕЕЕ — зарезервировано М1сго8оГ1 • 0x8000 — ОхЕЕЕЕ — определяется пользователем Соп1го1Сос1е Определяемые драйвером ЮСТЬ значения • 0x000 — 0х7ЕЕ — зарезервировано М1сго8оГ1 (рыЬНс) • 0x800 — ОхЕЕЕ — определяется пользователем ТгапзГегТуре Способ получения доступа к буферу • МЕТНОО_ВОЕЕЕкЕО • МЕТНОО_11\1_О1кЕСТ • МЕТНОО_О0Т_О1кЕСТ • МЕТНОО_ЫЕ1ТНЕР. кед и 1гес1 Асееве Требования инициатора относительно типа доступа • Е11_Е_А1\1Х_АССЕ55 • Е11_Е_кЕАО_ОАТА • Е11_Е_\Л/к1ТЕ_ОАТА • Е11_Е_кЕАО_ОАТА | Е11_Е_\Л/к1ТЕ_ОАТА
Операции драйвера, которые работают с ГОСТЬ запросами, часто требуют задания буферной области для размещения входных либо выходных данных, то есть поступающих от пользовательского приложения в драйвер либо в обратном направлении, соответственно. Возможно, что в одном запросе используются сразу оба буфера. В самом деле, вызов функции пользовательского режима Оеу1се1оСоп1го1 среди прочих входных параметров имеет два указателя на две буферные области, одну — для входных данных, другую — для выходных. Механизм переноса данных, обеспечиваемый Диспетчером ввода/вывода, определяется как раз в ГОСТЬ. Это может быть либо буферизованный, либо прямой ввод-вывод, либо метод ЫЕ1ТНЕИ. Как было сказано ранее относительно запросов чтения/записи, при буферизованном способе работы сданными, Диспетчер ввода/вывода копирует данные пользовательского буфера в/из промежуточного буфера, размещенного в нестраничном пуле, при работе с которым драйвер не будет испытывать сложностей. При прямом способе ввода/ вывода драйвер получает прямой доступ к определенной пользователем буферной области памяти, которая предварительно зафиксирована в оперативной памяти. В данном случае флаги, определяющие тип буферизации в объекте устройства (рОеу1сеОЬ]есЬ->Е1ад5), не имеют значения при работе с ГОСТЬ запросами. Механизм буферизированного обмена определяется при каждом задании значения ГОСТЬ в специально предназначенном для этого фрагменте этой структуры данных. Данный подход обеспечивает максимальную гибкость при работе с вызовом пользовательского режима Оеу1се1оСоп1го1. Поле ТгапзГегТуре (таблица 8.8) представляет собой два бита, которые определяют один из следующих типов буферизации: МЕТНОО_ВЬ)ЕЕЕИЕО. Диспетчер ввода/вывода копирует пользовательский буфер в/ из вспомогательного буфера, который он размещает в нестранично организованной памяти. МЕТНОО_1М_О1РЕСТ. Диспетчер ввода/вывода предоставляет список страниц, которые представляют пользовательский буфер. Драйвер использует этот список для того, чтобы осуществить прямой ввод/вывод (используя ЭМА или программируемый ввод/вывод) от устройства к пользовательскому буферу, в \Л/1п32 АР1 вызове ^еV^сеIоСоп^^оI описанному 5-м (!!) параметром. МЕТНОО_ОЫТ_О1РЕСТ. Диспетчер ввода/вывод предоставляет список страниц, которые представляют пользовательский буфер. Драйвер использует этот список для того, чтобы осуществить прямой ввод/вывод (используя ЭМА или программируемый ввод/вывод) от пользовательского буфера к устройству (буфер вводится в \Л/1п32 АР1 вызове Оеу1се1оСоп1го1 также 5-м параметром). МЕТНОО_ЫЕ1ТНЕИ. Диспетчер ввода/вывода не предлагает буферизированной передачи данных. Пользовательский буфер (скорее всего, в странично организованной памяти) предоставляется драйверу. Так как поле ТгапзГегТуре встроено в значение ГОСТЬ, то документированные МюгозоГЬ программные компоненты определяют и механизм буферизации. Для значений ГОСТЬ, определенных в коде драйвера, могут быть заданы любые требуемые значения для описания механизма переноса данных. Для переноса небольших объемов данных и при небольшой скорости обмена вполне приемлем буферизованный ввод/вывод. Для переноса больших объемов и быстрой работы более подойдет прямой ввод/вывод. Как только у драйвера появляется объявленная им рабочая процедура для обслуживания 1РР пакетов с кодами 1РР_МЗ_11\1ТЕРЫАЬ_ОЕ\/1СЕ_СОМТРОЬ либо 1РР_МЗ_ОЕ\/1СЕ_СОМТРОЬ, Диспетчер ввода/вывода начинает пропускать соответствующие пакеты 1РР внутрь драйвера. Интерпретация поступающих кодов
ЮСТЬ управления устройством становится обязанностью и ответственностью драйвера, включая проверку значений полей внутри кода ЮСТ1_. Любое 32 разрядное число, посланное инициатором запроса в качестве ЮСТ1_ кода, поступит в соответствующую рабочую процедуру драйвер, поскольку Диспетчер ввода/вывода не выполняет проверки корректности ЮСТ1_ кодов. Типовая конфигурация рабочей процедуры по обслуживанию ЮСТ1_ запросов должна быть большим оператором §у\л1сН, например: НТЗТАТОЗ 1оСопЕго1СоЬеНапЫег (1Ы РБЕУ1СЕ_ОВТЕСТ рВечОЬ], ТО Р1ВР р1гр { НТЗТАТЬЗ зЕабиз = ЗТАТТЗ_ЗТССЕЗЗ; РМУБЕУ1СЕ_ЕХТЕНЗ 1ОН рБечЕхб; ТЬОНС йосЫСобе, йпЗйге, оиЕЗйге; // Находим нужную ячейку стека 1НР пакета Р1О_ЗТАСК_ЬОСАТ1ОН р1грЗЕаск = 1оСеЕСиггепЫгрЗЕаскЬосаЫоп (р // Находим код 1ОСТЬ запроса йосЫСобе = р1грЗЕаск ->Рагатекегз.Веч±се1оСопкго1.1оСопкго1С // и требуемого размера передаваемых данных йпЗйге = р1грЗЕаск->Рагатекегз.Веч±се1оСопкго1.1приЕВиУУегЬеп оикзйге = р1грЗЕаск->Рагатекегз.Веч±се1оСопкго1.ОиЕриЕВиУУегР змйксй(сопЕгоТСоТе) { // Вторичная диспетчеризация сазе 1ОСТЬ_СОВЕ_1: { // Всегда следует проверять входные параметры ±У(йпЗ±ге > 0 || оиЕЗйге > 0) { ЗЕакиз = ЗТАТНЗ_1НУАЫВ_РАВАМЕТЕВ; Ьгеак; } } ЬеЬаиТЕ: // Драйвер получил непредусмотренные коды 1ОСТЬ зЕакиз = ЗТАТТЗ_1НУАЬ1В_ВЕУ1СЕ_ВЕ(2ТЕЗТ ; Ьгеак; } р1гр->1оЗЕакиз.ЗЕакиз = зЕакиз; р1гр->1оЗЕакиз . 1пЬогтаЫоп = 0; // нет данных для передачи 1оСотр1екеВедиезЕ( р1гр, 10____ЕЮ_1НСВЕМЕНТ ); геЕигп зЕакиз; } Доступ к буферным областям, содержащим данные или предназначенным для данных, описывается таблицей 8.9. Таблица 8.9. Передача адресов буферов данных в 1РР пакетах, описывающих ЮСТ!_ запросы МЕТНОО_11Ч_ОтЕСТ МЕТНОО_ВиЕЕЕКЕО или МЕТНОО_МЕ1ТНЕН МЕТНОО_ОиТ_О1НЕСТ
1при1 Буфер с данными Использует буферизацию (системный буфер) Адрес буфера в системном адресном пространстве указан в р1гр- >А55ОС1а1ес11гр.5у51етВиГГег Клиентский виртуальный адрес в Ра^ате^е^5.^еV^сеIоСоп^^оI. Туре31при1ВиГГег Длина указана в Ра^ате^е^5.^еV^сеIоСоп^^оI.Iпри^ВиГГе^^епд^^I Ои1ри1 Буфер для данных Использует буферизацию (системный буфер) Адрес буфера в системном адресном пространстве указан в р1гр-> А55ОС1а1ес11гр.5у51етВиГГег Использует прямой доступ, клиентский буфер преобразован в МО1_ список, указатель на который размещен в Р1гр->Мс11Ас1с1ге55 Клиентский виртуальный адрес в р1гр->05егВиГГег Длина указана в Ра^ате^е^5.^еV^сеIоСоп^^оI.Ои^ри^ВиГГе^^епд^^I ф Названия 1при1 и Ои1ри1 здесь и в литературе трактуются с точки зрения драйвера. Буфер ЧприГ содержит данные, поступающие от клиента, скорее всего, предназначенные для вывода в устройство. Буфер 'ОиСриС' указывает на то место, куда следует поместить данные, ожидаемые клиентом, скорее всего, прочитанные из устройства. Кстати сказать, в описании вызова ОеУ1се1оСоп1го1 в документации МББН никакого разночтения с данной трактовкой названий не наблюдается: буфер с данными для выполнения операции (3-й параметр вызова) называется 1р1при1:Ви1Тег, а буфер для получаемых данных (5-й параметр вызова) называется 1рОи1ри1Ви^ег. Следует также обратить внимание на то, что при методе буферизации МЕТНОО_ВОЕЕЕКЕО Диспетчер ввода/вывода в качестве системного буфера для драйвера получает одну область системного адресного пространства, достаточно большую для того, чтобы вместить наибольший из входного/выходного буферов клиента. В эту область Диспетчер ввода/вывода перед вызовом рабочей процедуры, обслуживающей запросы данного типа, копирует данные, передаваемые драйверу. В эту же область драйвер выводит свой данные, предназначенные для получающего буфера клиента. Следует внимательно относиться к операциям записи в этот буфер вводимых данных, поскольку есть опасность испортить, возможно, еще находящиеся там данные, предназначенные к выводу в устройство. Разумеется, если клиент драйвера предлагает в одном запросе действия и ввода, и вывода. Может показаться странным, что для метода МЕТНОО_11\1_О1КЕСТ строится МО1_ список для выходного (ои1ри1:) буфера, а для входного как бы используется менее мощный метод МЕТНОО_В1)ЕЕЕКЕО. Тем не менее, это так. Желающие могут обойти это самостоятельно, просто переставив в своих запросах Оеу!се1оСоп1го1 адрес 1при1 буфера на место ои1ри1: буфера (или учитывая это в драйвере, как это сделала фирма Сургезз в драйвере ЕгизЬ.зуз). Следует отметить, что имеющаяся в документации ООК пометка относительно построения МО1_ списка для поставляемых клиентом данных (для 1при1 буфера с размещением в поле 1КР пакета Мс11Ас1с1ге85) на практике и в остальной литературе по данному вопросу подтверждения не находит. Повторим сведения о размещении областей с данными/для данных при подготовке 1РР пакета, описывающего ЮСТЬ запрос к драйверу. При методе МЕТНОО_ВЦЕЕЕКЕО Диспетчер ввода/вывода выделяет единственный буфер в нестраничной памяти, достаточно большой, чтобы вместить входной или выходной буфер инициатора вызова. Адрес этой области размещается в пакете 1РР в поле Аззоаа^есПгр.Зуз^етВиГГег. Затем производится копирование входного (1при1) буфера с данными инициатора запроса в эту область. В поле ОзегВиГГег пакета 1РР заносится оригинальный адрес буфера для получения данных инициатора запроса. По завершении обработки ЮСТЬ запроса, Диспетчер ввода/вывода копирует содержимое системного буфера, размещенного в нестраничной памяти, в собственно буфер инициатора. Следует обратить внимание, что только один внутренний буфер
предоставляется драйверу, даже если пользователь указал два независимых буфера в качестве входного и выходного. При методе МЕТНОО_1М_О1НЕСТ и МЕТНОО_ОиТ_О1КЕСТ Диспетчер ввода/вывода проверяет приемлемость выходного (оифиГ) буфера инициатора вызова и производит его фиксацию (1оск) в физической памяти. Затем производит построение списка МЭ1_ (Метогу ОезспрГог ИзГ) для выходного буфера и сохраняет указатель на МЭ1_ в поле МсПАдскезз пакета 1Р.Р. Кроме того, Диспетчер ввода/вывода выделяет временную область в нестраничном пуле и сохраняет этот адрес в поле Аззос1аГес11гр.5у51етВиГГег пакета 1Р.Р. Производится копирование содержимого входного (1приГ) буфера инициатора вызова в выделенный системный буфер, а в поле ОзегВиГГег производится запись значения 1\Ю1_1_. После этого ТИР пакет поступает в вызываемую рабочую процедуру драйвера. При методе МЕТНООЦЧЕ1ТНЕЯ Диспетчер ввода/вывода помещает адрес входного (1приГ) буфера инициатора вызова в поле РагатеГегз.Оеу|се1оСопГго1.Туре31приГВиГГег в текущей ячейке стека пакета 1РР текущей операции ввода/вывода. В поле ОзегВиГГег производится запись адреса выходного (оиГриГ) буфера инициатора вызова, где инициатор вызова ожидает получить результаты выполнения операции. Оба этих адреса указывают в область памяти инициатора вызова.
ГРгеу|ои51 [Мех!],
Обслуживание прерываний В операционной системе \Л/1пс1о\л/5 1\1Т 5 все прерывания изначально обрабатываются ядром. Это сделано для облегчения переносимости операционной системы на разные аппаратные платформы. Ядро обеспечивает диспетчеризацию прерываний между драйверами путем создания и последующего подключения объектов прерывания по вызову 1оСоппес11п1еггир1 (см. таблицу 8.10). Этот вызов получает адрес процедуры для обслуживания прерываний (15К, 1п1еггир1: Зеплсе НоиНпе) драйвера, и таким образом операционная система связывает указанное аппаратное прерывание с определенным драйвером и принадлежащей ему 15К функцией. Вызов 1оСоппес11п1еггир1 возвращает указатель на объект прерывания (через первый аргумент). Возвращенный указатель должен быть сохранен в структуре расширения объекта устройства, так как он понадобится в дальнейшей работе, в частности, при отключении от источника прерываний. Когда операционная система получает сигнал прерывания от устройства, она использует свой список объектов прерывания для локализации 15К процедуры, в ведении которой находится обслуживание данного события. Она "пробегает" по всем объектам прерывания, подключенным к ОШОЬ этого прерывания, и вызывает 15К процедуры до тех пор, пока одна из них не заявит о своих правах на него. Диспетчер прерываний режима ядра вызывает 15К процедуру на уровне синхронизации 5упсКгоп12е1гц1, указанном в вызове 1оСоппес11п1еггир1. Обычно, это один из 01 Р.01 уровней. Кроме того, диспетчер прерываний получает владение над объектом спин-блокировки р5р1п1_оск и удерживает ее во время выполнения 15Р. процедуры, что предохраняет от выполнения 15Р процедуры на других процессорах. При выполнении на столь высоком уровне 1Р01_ существует некоторые вещи, которые процедура 15к не может себе позволить. В дополнении к обычным предостережениям избегать манипуляций со страничной памятью, 15К процедура не должна пытаться получать или освобождать какие-либо системные ресурсы, даже нестраничную память. Если разработчик предполагает сделать системный вызов из 15К процедуры, следует обязательно обратить внимание на уровень, на котором тот может выполняться. Вполне вероятно, что такие системные вызовы придется перепоручить ЭРС процедуре, запуск которой вполне может запланировать данная функция обслуживания прерываний. Таблица 8.10. Прототип функции 1оСоппесНп1еггир1 1ЧТ5ТАТ115 1оСоппес11п1еггир1 1Я<21_ == РА551УЕ_ЬЕУЕЬ Регистрирует процедуру обслуживания прерывания, Параметры предоставляемую драйвером, и "подключает" ее к источнику прерываний оит ркичтЕкнирт *р1п1еггирЮЬ]ес1 Адрес указателя, в котором будет возращен указатель на объект прерывания 1Ы РК5ЕР71СЕ_Р0иТ1ЫЕ ЗеплсеРоийпе Процедура (функция) драйвера, которая теперь будет обслуживать прерывание 1Ы РХ/ОЮ р5егу|сеСоп1ех1 1Ы РК5Р11\1_1_0СК р5р1п1_оск 1Ы 01-0140 7ес1ог Аргумент, передаваемый в процедуру 15Р, обычно рекомендуется приводить здесь указатель на структуру расширения объекта устройства Инициализированный объект спин-блокировки Транслированное значение вектора прерывания 1Ы К1РС21- 1гр1 1Ы К1Р<21_ 5упсЬгоп12е1гр1 Значение О1Р<21_ для данного устройства Обычно равно значению 1гц1 1Ы К11\1ТЕРРиРТ_М00Е Аппаратный тип прерывания. Одно из значений:
1п1еггир1Мос1е • 1_еуе15еп5111Уе • Еа1:сЬес1 1Ы ВООЬЕАЫ 155ЬагаЫе\/еси)г Если ТИПЕ — данный вектор прерывания является совместно используемым (разделяемым) 1Ы КАЕЕ1Ы1ТУ РгосеззогЕпаЫеМазк Установить набор процессоров, которые могут получать сигналы прерывания 1Ы ВООЬЕАЫ с1оЕ1оайпд5ауе Если ТИПЕ — сохранять состояние регистров сопроцессора (ЕРО). Обычно используется ГАЬЗЕ • 5ТАТи5_5иССЕ55 Возвращаемое значение • 5ТАТи5_11\1УА1_Ю_РАНАМЕТЕк • 5ТАТи5_и№иЕЕиС1ЕЫТ_кЕ5ОикСЕ5 Таблица 8.11. Прототип функции драйвера для обслуживания прерываний ВООЬЕАН 15К 1Нр1_ == ОТКрЬ Параметры Процедура драйвера, предоставляемая им для обслуживания прерывания 1Ы РКИЧТЕкЕШРТ *р1п1еггирЮЬ]ес1 1Ы Х/ОЮ р5егу|сеСоп1ех1 Объект прерывания, "генерирующий" прерывания Контекстный аргумент, указанный при регистрации в 1оСоппес11п1еггир1 Возвращаемое значение • ТКОЕ — прерывание было обслужено 15Р. • ЕАЬВЕ — прерывание не обслуживается Параметр 1п1:еггир1Мос1е вызова 1оСоппес(1п1еггир1 интерпретируется операционной системой следующим образом. Когда драйверы подключили свои 15К процедуры к прерыванию, считая его 1_еуе18епзШуе, операционная система вызывает все подключенные таким образом 15К функции до тех пор, пока одна из них не возвратит ТИКЕ. В противном случае, то есть если указано значение Ьа1сНес1 для параметра 1п1:еггир!:Мос1е, то операционная система вызывает все из подключенных таким образом 15к процедур, и эти вызовы повторяются до тех пор, пока все 15к процедуры не возвратят значение ЕА1.5Е. В рамках общего подхода У\Лпс1оуу5 любой программный код должен минимизировать свое пребывание на высоких приоритетах. Всегда следует оптимизировать код 15К процедуры для достижения максимальной скорости его выполнения. Действия, которые нельзя отнести к абсолютно необходимым именно в 15К процедуре, следует вынести в процедуру отложенного вызова (ОРС). Особенно важно, чтобы 15И процедура сразу же определилась, будет ли она обрабатывать поступившее прерывание. Возможно, многие 15И процедуры ожидают этого прерывания, а малозначительный код данной 15И процедуры блокирует их работу. Использование объектов прерываний дело достаточно хлопотное. Во-первых, если 15Р. процедура обслуживает более чем одно прерывание или драйвер имеет более чем одну 15Р процедуру, должна использоваться спин-блокировка для того, чтобы не возникло недоразумений в использовании контекстного аргумента р5еплсеСоп1ех1 процедур(ы) 15к. Во-вторых, в случае, если процедура 15К управляет более чем одним прерыванием (вектором прерывания), следует позаботиться о том, чтобы значение, указанное в качестве 5упсКгоп12е1гц1 было наибольшим значение О1Р.р1_ из всех обслуживаемых прерываний. Наконец, процедура 15К драйвера должна быть готова к работе с момента выполнения вызова 1оСоппес11п1еггир1. Даже в том случае, если выполнены еще не все действия по инициализации драйвера.
В общих чертах, процедура обслуживания прерываний должна придерживаться следующего плана: 1. Определить, относится ли поступившее прерывание к данному драйверу. Если не относится — немедленно возвратить ЕА1.5Е. 2. Выполнить все операции над устройством, необходимые для того, чтобы подтвердить устройству получение прерывания. 3. Определить, существует ли необходимость в передаче данных и дополнительных действиях, которые могут быть выполнены на низких уровнях ТИр1_. Если такая работа имеется, то следует запланировать вызов ЭРС процедуры (предоставляемой драйвером), то есть поставить в очередь ЭРС-запрос вызовом ТоКедиезЮрс. 4. Возвратить значение ТИКЕ. Таблица 8.12. Прототип вызова 1оЯедиезЮрс У/ОЮ ТоНедиезЮрс Параметры 1Ы РОЕ71СЕ_ОВЗЕСТ рОеуОЬ]ес1 1Ы Р1К.Р р!гр 1Ы \/ОЮ р5егу|сеСоп1ех1 Возвращаемое значение трь == оиин Помещает ОРС вызов в очередь Объект устройства, для которого зарегистрирована ОРС процедура Указатель на интересующий 1РР пакет Контекстный аргумент УО1С1 Если сравнить прототип Т5И процедуры и прототип вызова ТоКедиезЮрс, который делается из Т5Н процедуры для планирования последующего вызова ОРС процедуры (для завершения работы над прерыванием), то становится очевидной проблема. Обычно рабочие процедуры драйвера получают указатель на объект устройства и указатель на адресованный этому объекту ТИР пакет через заголовок при вызове. Но прототип Т5Н процедур этого не предусматривает. Как же тогда сделать из нее вызов ХоЯеяиезЮрс, чтобы запланировать ОРС процедуру? Данное затруднение решается, если при регистрации Т5И процедуры вызовом 1оСоппес11п1еггир1 в качестве контекстного аргумента рБеплсеСопТехТ: ввести указатель на структуру расширения структуры (извините за неблагозвучные повторы) данного объекта устройства, то есть указатель на ОЕ\/1СЕ_ЕХТЕМ51ОМ. При условии заблаговременного сохранения там указателя на объект устройства (например, как это было сделано в ОпуегЕпТгу примера главы 3) процедура Т5И драйвера не будет испытывать затруднения с тем, откуда ей взять данный указатель. Остается вопрос, что такое р1гр и где его найти? Возможны два варианта. Во-первых, для обработки прерывания действительно требуется текущий обрабатываемый драйвером ТИР пакет, и тогда его можно найти, как рОеу1сеОЬ)ес1:->Сиггепигр, но только для пакета из системной очереди, 5уз1:етриеи1пд. Во-вторых, ТИР пакет может и не потребоваться (по логике прерывания и работы драйвера), тогда можно просто указать 1\11Л_1_. Более того, во многих случаях ТИР пакет указать невозможно или нежелательно. Поскольку Диспетчер ввода/вывода не анализирует этот параметр, то в нем можно передавать нужные (по логике работы) данные так же, как и в указателе рБеплсеСопТехТ. В результате, Т5Н процедура может выглядеть следующим образом: ВООЬЕАИ 0п1пЕеггирР. ( РКЮТЕККУРТ рТпбеггирбОЬдесЬ, РУО1В рЗезгуд.сеСоп'Ьех'Ь )
{ РМУБЕУТСЕ_ЕХТЕМ8ТОМ рБечЕхЕ=(РМУБЕУТСЕ_ЕХТЕМ8ТОМ) р8е^1сеСоп // Считываем нужный регистр нашего устройства чтобы // определить, действительно ли оно посылало сигнал прерывания ПЬОМС ТпЕг8ЕаЕиз = НЕАБ_РОНТ_ПЬОМС ( (РПЬОМС) (рБечЕхЕ->1пЕеггирЕ8ЕаЕеНед) ) ; ТЕ ( йпЕг8ЕаЕиз!=. . . ) геЕигп ЕАЬ8Е; // Это чужое прерывание // Некоторые действия, например, изменение состояния // регистров устройства, чтобы оно знало: о нем помнят. // Планируем вызов БРО процедуры, например, без 1НР пакета: ТоНедиезЕБрс( рБечЕхЕ->Беч±сеОЬ^есЕ, МПЬЬ, рБечЕхЕ); геЕигп ТРОЕ; // Прерывание обработано } Детальное рассмотрение вопросов обработки прерываний и применения ОРС процедур выполнено в главе 11, "Обработка аппаратных прерываний".
Процедуры отложенного вызова обслуживания прерываний ОрсРогТзг Вызов процедуры ЭрсЕогТзг может быть запланирован из собственно 15к процедуры вызовом ТокедиезЮрс. Прототип этой функции драйвера описан в таблице 8.13. Таблица 8.13. Прототип функции для процедуры ОрсРогТзг 5/ОЮ ОрсРогТзг ТКр1_ == ОТ5РАТСН_ЬЕУЕЬ Параметры Завершает обработку прерывания, начатую в Т5К процедуре драйвера 1Ы РКОРС рОрс □РС-объект 1Ы РОЕУ1СЕ_ОВЗЕСТ рОеуОЬ] Указатель на объект устройства, для которого зарегистрирована данная ОРС процедура 1Ы Р1К.Р р!гр Интересующий пакет 1РР 1Ы УОЮ рСоп1ех! Контекстный указатель, переданный вызовом ТоКециезЮрс Возвращаемое значение УО1С1 При анализе прототипа вызова ХоПедиезЮрс и примера кода перед таблицей 8.13, возникает правомерный вопрос: вызов какой функции планирует 15к процедура, выполняя вызов ХойедиезЮрс? Ведь ЭрсЕогТзг функция не фигурирует ни при регистрации 15к процедуры вызовом 1оСоппес11п1еггир1, ни в тексте Оп1п1еггир1. Ответ состоит в том, что ЭрсЕогТзг регистрируется для конкретного объекта устройства вызовом 1о1т11аН2еОрсЯеяие81 (см. таблицу 8.14). Если обратить внимание на структуру ОЕ\/1СЕ_ОВЗЕСТ, описанную в заголовочных файлах \л/с1т.1п и п1с1с1к.11 (см. пакет ЭОК), то несложно заметить поле 'КОРС Орс', предназначенное как раз для хранения ОРС объекта. Соответственно, выполняя вызов ХоПедиезЮрс с указателем на объект устройства в качестве первого аргумента, 15К процедура драйвера однозначно определяет, какая именно ОрсРогТзг функция будет вызвана позже — хранящаяся в объекте устройства. У/ОЮ ТоТтНаНгеОрсКециезг Таблица 8.14. Прототип вызова 1о1пШаПгеОрсЯециея1 1Н<21. == РА551УЕ_ЬЕУЕЬ Параметры Регистрирует ОрсРогТзг процедуру для данного объекта устройства, создает и инициализирует ОРС объект 1Ы РОЕУ1СЕ_ОВЗЕСТ рОеуОЬ] Указатель на объект устройства, для которого регистрируется данная ОРС процедура 1Ы УОЮ (*ОрсЕог1зг) Адрес ОрсЕогТзг процедуры, которая должна иметь прототип, описанный в таблице 8.13 Возвращаемое значение УО1С1 Регистрацию ОрсРогТзг процедуры (и ее связывание с объектом устройства) для РпР драйверов рекомендуется делать в процедуре АдсЮеуюе драйвера. ф Первый параметр рИрс в описании прототипа процедуры ИрсГоНзг (таблица 8.13) не должен пугать разработчика. Если используется вызов ТоТт'ПаНгеОрсПедиез!, то именно он создает данный объект, а в вызов ПрсЕог1зг поступит указатель на уже готовый ИРС объект.
Выполнения кода процедуры ОрсЕог1&г В ответ на выполненный 15к процедурой вызов ТойедиезЮрс, принадлежащая данному драйверу процедура ОрсЕогТзг добавляется в очередь ОРС объектов (ОРС с1|5ра1:сК циеие). Когда значение процессорного 1ЕЩ1_ падает ниже 015РАТСН_1_Е\/Е1_, диспетчер процедур обработки отложенных вызовов выполняет вызов ОрсЕогТзг данного драйвера. Процедура ОрсЕогТзг выполняется на уровне 015РАТСН_1_Е\/Е1_, что означает: она не должна работать с адресами страничной памяти. Диспетчер ввода/вывода игнорирует множественные вызовы ХокедиезЮрс для данного объекта устройства до начала выполнения ЭрсЕогТзг. Попытки повторно поместить объект ОРС в очередь отклоняются. Это является нормальным отношением ко всем ОРС объектам. В случае если логика работы драйвера такова, что он может выдавать перекрывающиеся ОРС запросы для одного и того же устройства, и их следует учитывать, то такой драйвер должен реализовать собственную очередь ОРС запросов (объектов). Перечень обязанностей типовой процедуры ИрсЕогТзг может включать: Установку данных в блоке 1о51а1:и5 данного пакета 1РР, то есть размещение нужного кода 5ТАТ115_ХХХ в поле 51:а1и5 и числа действительно переданных байт данных в поле ТпГогтаИоп. Выполнение вызова 1оСотр1е1еЯеяие81 для завершения обработки пакета 1НР с соответствующим повышением приоритета. Будучи однажды вызван, пакет 1РР не должен обрабатываться снова. Выполнение вызова 1о51аг1Мех1Раске1 — чтобы инициировать поступление следующего 1кР пакета в процедуру Б^агНО. УО1Э РрсЕог!зг( РКРРС Эре, РОЕУ1СЕ_ОВЗЕСТ рРечОЬ)еск, Р1КР )ипк, РБЕУТСЕ ЕХТЕИЗЮИ р^еVЕxкепз^оп) { // Неким образом получаем текущий 1КР (например, из очереди // ^иеиеКеас^И^^^е, которую поддерживает сам драйвер) : Р1КР р!гр = СебСиггепбТгр ( &р^еVЕx^епз^оп->^иеиеКеас^И^^^е) ; // Инициируем поступление 1КР из внутренней очереди в // в процедуру ЗбагбЮО: ТойоЗбагбИехбРаскеб (&р^еVЕxкепз^оп->с^^Кеас1И^^ке, р^еVОЬ^еск) // Даем возможность отработать процедурам завершения всех // вышестоящий драйверов, если они есть: 1оСошр1екеКедиезк(р!гр, Ю_И0_1ИСКЕМЕИТ); }
Отключение от источника прерываний В случае, если драйвер должен обладать возможностью быть выгружаемым, то имеется насущная необходимость его отключения от источника прерываний. При этом он удаляется из внутреннего списка кода ядра, где он обозначен как обработчик прерываний. Разумеется, это необходимо выполнить до того, как драйвер будет удален из оперативной памяти. В противном случае код ядра операционной системы в ответ на прерывание, сгенерированной устройством, осуществит вызов процедуры по адресу нестраничном пуле, где раньше "обитала" процедура 15Р. Это неминуемо приведет к краху системы. Отключение от источника прерываний является двухступенчатой процедурой. Во- первых, следует использовать КеЗупсЬготгеЕхесийоп и процедуру ЗупсКСгИЗесИоп для того, чтобы обеспечить такое состояние устройства, когда он не будет производить генерацию сигналов на прерывание. Во-вторых, следует произвести удаление 15Р процедуры из системного списка обработчиков прерываний путем осуществления вызова 1оО15соппес11п(еггир( (с передачей этому вызову в качестве аргумента указателя на полученный ранее объект прерывания для данного устройства). Таблица 8.15. Прототип вызова 1оО15СОппес11п1еггир1 УОЮ 1оО|5СОПпес11п1еггир1 1Н<21. == РА551УЕ_ЬЕУЕЬ Параметры Регистрирует ОрсРогТзг процедуру для данного объекта устройства 1Ы РК1ЫТЕКРШРТ о1п1еггиоЮЫесС Указатель на объект прерывания, полученный ранее в результате вызова р р -1 1оСоппес11п1еггир1 Возвращаемое значение уо!с1 ф Нерассмотренным остается один весьма деликатный момент. При регистрации 15Р процедуры для обслуживания конкретного прерывания используется вызов 1оСоппесЫп1еггир{, подробно описанный в таблице 8.10. Наиболее важным и трудным в обращении является параметр \/есСог, представляющий транслированное прерывание, к которому и производится подключение регистрируемой 15Я процедуры. Данная процедура имеет свою специфику для каждого типа не-У\/ОМ драйверов (в зависимости от того, к шине какого типа подключено устройство, обслуживаемое драйвером, например, 15А или РС1). Однако для \Л/ОМ драйверов эта процедура универсальна. В данном случае, когда РпР Менеджер делает запрос с кодом 1РР_МЗ_РНР и субкодом 1РР_М1\1_5ТАРТ_ОЕ\/1СЕ, то в каждом таком запросе он передает и список присвоенных драйверу ресурсов. Драйвер УУЭМ, например, из пакета разработки устройств РС1 шины фирмы Р1_Х ТесЬпо1оду, выходит из положения следующим образом. Прежде всего, данный драйвер, получив 1КР пакет с указанными признаками (1КР_МЗ_РМР, субкодом 1КР_МЫ_5ТАкТ_ОЕ\/1СЕ), дает возможность сначала обработать его нижележащим драйверам, перехватывая его на обратном пути. После этого он производит анализ полученных ресурсов, в частности, подключает собственную функцию обработки прерываний Р1хОп1п1еггир1 к прерыванию, как оно было предопределено РпР Менеджером, передавшим выделенные ресурсы в ячейке стека пакета 1Р.Р. 11111 1, соип'Ь; ВООЬЕАИ ЬпЬеггирЬРгезепЬес! = ЕАЬЗЕ; Р1О_ЗТАСК_ЬОСАТ1ОЕ зЕаск = РоСеЬСиггепЫгрЗЕаскЬосаЫоп ( р!гр ); РСМ_РАРТ1АЬ_РЕЗОЕКСЕ_ЫЗТ рР.амР.езоигсе = &зЬаск->Рагате'Еегз . 3Еа^Ь^еV^се . А11оса'Еес1Р.езоигсез-> ЫзЕ[0] . РагЫа1КезоигсеЫзЕ;
соипб = рКамКезоигсе->Соипб ; ^ог (1 = 0; 1 < соипб; г++л рКамКезоигсе++) { //Просмотр всех выделенных драйверу ресурсов змтбсй (рКамКезоигсе ->Туре) { сазе СтКезоигсеТуре1пЕеггирЕ: // Прерывание гпЕеггирЕРгезепЕеб = ТРОЕ; 1грЬ = (К1Р(2Ь) Резоигсе->и.ТпбеггирЕ.Ьече!; чесбог = Резоигсе->и . 1пЕеггирЕ . УесЕог ; аРРтптЕу = Резоигсе->и.1пЕеггирЕ.АРРгпгбу; (РезоигсеРам->Е1адз == СМ_РЕ8ОПРСЕ_1ПТЕРРПРТ_ЬАТСН тобе = ЬаЕсЬес!; е!зе тос!е = Пече18епз±Е±че; Ьгеак; } } 1Р (тпЕеггирЕРгезепкес!) { // Здесь следует запретить прерывания от ведомого устройства // Создание объекта прерывания и подключение к нему збабиз = 1оСоппесб1пбеггирб( &рБеч Р1хОп рБечЕ ППЬЬ, чесбо 1гдЬЛ 1гдЬЛ тобе, ТРОЕ, аРРгп ЕАЬ8Е ) ; ( !ИТ_ЗиССЕЗЗ(збабиз) ) { // обработка ошибки } е1зе { // разрешить прерывания от устройства } } Здесь рЭеуЕхЬепзюп указывает на структуру расширения объекта устройства (соответственно, эта структура должна предусмотреть в своем составе место для хранения указателя на создаваемый объект прерывания, здесь р!п1:еггирЮЬ]ес1), а
Р1хОп1п1:еггир1 передает адрес предоставляемой данным драйвером 15к процедуры, созданной в соответствии с прототипом, описанным в таблице 8.11.

Заключение Рабочие процедуры составляют основу интерфейса между драйвером и инициатором запроса. В данной главе были рассмотрены основные особенности конструкции этих функций и обсуждались детали получения доступа к буферным областям, задаваемым инициатором запроса, и к другим параметрам, передаваемым в драйвер клиентским приложением. Были затронуты также вопросы, связанные с обработкой прерываний. Практические примеры, посвященные отработке приемов обслуживания прерываний будут рассмотрены в главе 11, "Обработка аппаратных прерываний". В следующей главе будут рассмотрены дополнения, привнесенные драйверной моделью \Л/ОМ.
Глава 9
Драйверная модель УУОМ

Развитие спецификации Р1ид & Р1ау Два десятилетия бурного развития вычислительной техники, в течение которого доступ к компьютерам стал действительно массовым, завершились вполне закономерно. Функционально насыщенная аппаратура выполняет не только работу, для которой она приобретается (управление механизмами, ведение финансовой отчетности, проектирование, игры и т.п.), но и в высокой степени самостоятельно решает задачи второго плана — собственное конфигурирование и настройку. В начале девяностых годов пользователь персонального компьютера должен был уметь настраивать свой ПК, экономно при этом расходуя запас "незанятых" прерываний, переставляя перемычки и меняя положение □1Р-переключателей на дополнительный картах в поисках оптимального быстродействия. Предварительная подготовка касалась не только необходимого знания портов ввода/вывода, настроек режима прямого доступа к памяти. Необходимо было знать, насколько хорошо сочетаются программы (которые предполагается установить) как с имеющимся "железом", так и между собой. И так далее, и так далее, включая "разгон" процессоров и преодоление проблем перевода ОС и принтеров на родной язык... Разумеется, о таких "пустяках", как возможность подсоединения новых устройств без выключения компьютера, не было речи вовсе! Максимум сервиса предлагали игровые программы, которые могли предложить "поиграть" настройками, спрашивая в конце каждой итерации "Слышите ли Вы звук в колонках?"
Пик беспорядка в вопросах конфигурирования устройств, составляющих компьютер, пришелся на то время, когда массовый пользователь вместе со своими любимыми программами вырос из рамок возможностей шины 15А. Разумеется, проблемы конфигурирования не давали покоя специалистам и раньше (скажем, со времен ОпИэиз периферии для РЭР-11), но только в данной временной точке распространенность вычислительной техники сделали преодоление этой проблемы делом почти что первостепенным. Решение пришло в виде разработки спецификации Р1ид апс! Р1ау, согласно которой устройства должны выдерживать определенные механические и электрические нормы. Основное же требование Р1ид апс! Р1ау состоит в том, что устройства должны уметь предоставлять идентификационную информацию о себе в формате, определенном для данного типа (РС1, 115В, ЯгеМге, СаМВиз) подключения. С выходом \Л/1пс1о\л/5 95 (и появлением некоторых сдвигов в подходах к разработке аппаратной части) усилия были сконцентрированы на автоматизации конфигурирования системы при добавлении и удалении устройств. Эти попытки усилили тенденции перехода пользователей на \Л/1Пс1о\л/5 95, что в свою очередь ускорило миграцию на 32-разрядные операционные системы МюгозоГ!:, в частности на \Л/1Пс1о\л/5 1\1Т. Наконец, с выпуском \Л/1пс1о\л/5 2000 М1СгозоГ1: реализовала законченную архитектуру Р1ид апс! Р1ау для подсистем ввода/вывода. В настоящее время подключение устройств по шинам 115В, СаМВиз (модифицированная РСМС1А) и Яге\Л/1ге (1ЕЕЕ-1394) при работающем основном компьютере является безопасным (и даже штатным) режимом работы. Драйверная архитектура \Л/1пс1о\/У5 2000/ХР/2003
полностью поддерживает эти события "появления" в системе новых устройств, позволяя конфигурировать и делать их доступными для использования без выключения питания компьютера и перезагрузки операционной системы. Главным плюсом использования методологии Р1ид апс! Р1ау является обеспечение автоматической поддержки инсталляции и удаления системных устройств. Чтобы добиться этого, необходимо выполнить несколько условий. Устройства должны быть ориентированы на выполнение программного конфигурирования. Должна существовать возможность установки портов ввода/вывода, задействованных прерываний и ресурсов ОМА (параметров прямого доступа к памяти) из управляющего программного обеспечения, исключая механическое конфигурирование при помощи переставляемых перемычек и О1Р-переключателей на платах. Система должна обеспечивать надежное автоматическое обнаружение нового устройства или факт удаления существующего. Устройство и шина, к которой устройство подключено (было подключено), должны информировать управляющее программное обеспечение о том, что аппаратная конфигурация претерпела частичные изменения. Необходимые драйверы для новых устройств должны загружаться автоматически, по мере обнаружения этих устройств операционной системой. (Вмешательство пользователя все-таки требуется, но только лишь для установки драйверов для никогда ранее не присутствовавших в системе нестандартных устройств.)
В тех случаях, когда устройство и его интерфейсная шина позволяют, операционная система должна поддерживать и так называемое "горячее" (при включенном питании) присоединение аппаратуры. То есть должна быть возможность подключения/ отключения устройства непосредственно в "живую" систему без создания для нее стрессовых ситуаций.
Программные компоненты Р1ид апд Р1ау В сообщество участников поддержки РпР в операционной системе входят: РпР Менеджер, который состоит из двух частей — работающей в режиме ядра и работающей в пользовательском режиме. Часть из режима ядра взаимодействует с аппаратурой и другими программными компонентами, функционирующими в режиме ядра, обеспечивая управление правильным определением и конфигурированием аппаратуры. Часть из пользовательского режима взаимодействует с компонентами пользовательского интерфейса, позволяя интерактивной программе делать запросы и изменять конфигурацию инсталлированного РпР программного обеспечения. Менеджер Управления Энергопитанием (Ро\л/ег Мападег), который определяет и обрабатывает события энергообеспечения. Системный Реестр \Л/|пс1о\л/5, являющийся базой данных установленного аппаратного и программного обеспечения, поддерживающего спецификацию РпР. Содержимое реестра помогает драйверам и другим компонентам при определении ресурсов, используемых любым конкретным устройством. 1пГ-файлы. Каждое устройство должно быть полностью описано файлом, который используется при инсталляции управляющего им драйвера. 1пГ- файл является рецептом, как и какую информацию об устройстве заносить, в частности, в Системный Реестр.
Драйверы для РпР устройств, которые можно разделить на две категории: \Л/ОМ и 1\1Т драйвера. Последние являются "унаследованными" от 1\1Т драйверами, которые опираются на некоторые аспекты РпР архитектуры, но, с другой стороны, не полностью удовлетворяют модели \Л/ОМ. Например, они могут использовать сервисы РпР Менеджера, чтобы получить информацию о конфигурации, но при этом не обрабатывают 1РР пакеты сообщения с кодом 1РР_МЗ_РЫР. Драйверы \Л/ОМ модели, по определению, полностью соответствуют требованиям взаимодействия по правилам РпР. Реестр и 1МГ файлы Менеджер питания РпР Менеджер Рис. 9.1 Программные РпР компоненты \Л/1пс1оу75 2000/ХР Пользовательский режим Режим ядра Диспетчер ввода/вывода Исполнительные компоненты | НТ РпР драйверы"! I Драйверы УУРМ | Слой аппаратных абстракций (НД1_)
Роль Системного Реестра Совсем еще недавно, общепринятым образом поведения для аппаратного обеспечения было оставаться в состоянии молчания до тех пор, пока программное обеспечение неким магическим образом не узнает о его существовании и не примется стимулировать эту аппаратуру. Методы, которыми действовали драйверы или операционная система ЫТ, можно было разделить на три группы: Драйвер получал список потенциально возможных ресурсов аппаратуры (адреса портов ввода/выводов, используемых каналов прямого доступа к памяти, □МА, и номера прерываний) для каждого устройства, с которым возможно взаимодействие. Путем "подергивания" за каждый потенциально возможный ресурс во время исполнения ОпуегЕп^гу, драйвер создавал приемлемый объект устройства (вызовом IоС^еа^е^еV^се). Драйвер полагался на инсталляционную программу, которая методом проб и ошибок или при активном участии пользователя производила приемлемое описание ресурсов и устройств, которыми мог бы управлять драйвер впоследствии. Список таких устройств и их ресурсов сохранялся в Системном Реестре. Операционная система ЫТ выполняла как часть загрузочного процесса (см. Приложение Б) испытание стандартных устройств и ресурсов. Например, параллельные порты представлены обычно по адресам 0x378 или 0x278, что и проверяется в процессе загрузки. Все обнаруженное заносилось в Системный Реестр.
Разработчики компьютерных систем признали правомерной потребность в более упорядоченном процессе конфигурирования аппаратного обеспечения. Новые шины и протоколы проектируются так, чтобы автоматически сообщать о появлении или удалении устройств. Все типы шин поддерживают теперь такую форму автоматического определения. Промежуточным решением для получения автоматически распознаваемых шин и аппаратуры было в ранних версиях 1\1Т расширение загрузочного процесса, во время которого в Системный Реестр включается информация об обнаруженном оборудовании. Таким образом, проходящий инициализацию драйвер в ходе работы □пуегЕп1ту получал возможность увидеть список автоматически обнаруженных на данный момент устройств и создать подходящие объекты устройств. Записи, появившиеся в результате в Системном Реестре, позволяли драйверу загрузиться (в момент загрузки системы или позже), чтобы тот мог затем заняться конфигурированием устройства. Первична в таком подходе загрузка драйвера, выполненная хотя бы один раз. Вполне естественно, что такой подход называется иногда "драйверо-центричным". С выходом \Л/|пс!о\л/5 95, а затем и \Л/1пс1о\/У5 98, и \Л/1пс1о\л,5 2000, данная модель была преобразована в обратную. Устройства объявляли о себе сами, либо во время загрузки, либо во время "горячего" подключения (Ьо1: р1ид), таким образом, настаивая на установке соответствующего регистрируемого драйвера. Такой метод получил название "аппаратно-центричного". Следует отметить, что в настоящий момент пользователь может самостоятельно инициировать установку драйвера
для устройств, не поддерживающих РпР (или даже — "как бы" устройств "как бы" не поддерживающих, что было в примере драйвера Ехатр1е.5уз несуществующего устройства, глава 3). Соответствующая информация будет сохранена в Системном Реестре для последующих загрузок — иными словами, старый "драйверо- центричный" механизм сохранен. Более того, информация о РпР устройствах, однажды обнаруженных системой полностью из Системного Реестра не удаляется, даже если устройство не будет подключено при следующей загрузке системы — система "помнит" обо всех ранее произведенных подключениях и установленных драйверах, что позволяет ей экономить время, если вдруг, после длительного перерыва, пользователь решит использовать это устройство снова (см. раздел НК1_М\§у81ет\Сиггеп1Соп1го1§е1\Епит). Изначально предназначенная для \Л/| пс!о\л/з 95, \Л/ОМ модель поддерживала методологию РпР, что существенно отличало ее от драйверной модели \Л/| пс!о\л/з 1\1Т. Компания М1СГО5ОЯ: настойчиво продвигалась к достижению совместимости драйверных сред, и в результате 1\1Т модель была дополнена поддержкой РпР. Родился обобщенный подход к драйверной среде для \Л/1Пс1о\л/5 98 и \Л/1Пс1о\л/5 1\1Т 5, а новая общая модель получила наименование У\Лпс1оуу5 Опуег Мос1е1 (УУОМ).
ГРгеу1ои8~| ГИехМ
Модель УУОМ и методология РпР
ГРгеу|ои51 [Мех!],
Ас1сЮеу1се — новая процедура в драйверах модели \ЛЮМ В драйверах модели \Л/ЭМ функция ОпуегЕп1ту все еще служит в качестве начальной точки соприкосновения операционной системы с драйвером. Однако теперь обязанности ее сократились. В частности, роль ОпуегЕп1гу теперь состоит только в том, чтобы "опубликовать" (передать Диспетчеру ввода/вывода соответствующие адреса для вызова функций по адресу) остальные процедуры драйвера. Теперь ОпуегЕп1ту не создает объект устройства (Оеу1се ОЬ)ес1) для подконтрольного аппаратного обеспечения. Обязанности по созданию объекта устройства возложены на новую функцию драйвера Ас1с1Оеу1се, которая теперь публикуется (обратите внимание!) в структуре расширения драйвера (Опуег Ех1еп5юп) во время работы ОпуегЕп1гу. Структура расширения драйвера является строго определенной структурой — не следует путать ее с определяемой разработчиком драйвера структурой расширения устройства (Оеуюе Ех^епзюп). Пример публикации Ас1с1ОеУ1се был закомментирован в теле функции ОпуегЕп1гу в главе 3. Повторим еще раз: ^^^Vе^ОЬ^ есЕ->^^^Vе^ЕxЕепз^оп->Ас1с1^еV^се = МуАс^с^^еV^сеКоиЫпе; Таблица 9.1. Прототип функции Ас/сЮеУ1се ГЧТ5ТАТ115 Ас1сЮеу!се Параметры ИНН == РА551УЕ_ЬЕУЕЬ Описание 1Ы РОк1\/Ек_ОВЗЕСТ рОпуегОЬ]ес1 Указатель на объект данного драйвера 1Ы РОЕУ1СЕ_ОВЗЕСТ рРОО Возвращаемое значение Указатель на объект физического устройства, созданного родительским (шинным) драйвером 5ТАТи5_5иССЕ55 или код ошибки Основной обязанностью МуАс1с10еу1сеРои11пе является создание объекта устройства (теперь уже — функционального) с использованием вызова системного 1оСгеа1еОеу!се и, скорее всего, подключение его к объекту физического устройства (вызовом 1оАНасНОеу!се), указатель на который поступает в параметре рРОО. Откуда взялся объект физического устройства, указатель на который получает процедура Ас1с1Оеу1се? Его создает шинный драйвер, когда обнаруживает подключенное РпР устройство. Зачем нужно подключаться к шинному драйверу?
Роль драйверных слоев в модели УУОМ Драйверная модель \Л/ОМ построена на организации и манипуляции слоями Объектов Физических устройств (РКу51са1 Оеу1се ОЬ]ес1, РОО) и Объектов Функциональных устройств (Еипс^опа! Оеу1се ОЬ]ес1, ЕОО). Объект РОО создается для каждого физически идентифицируемого элемента аппаратуры, подключенного к шине данных, и подразумевает ответственность за низкоуровневый контроль, достаточно общий для набора функций, реализуемых этим аппаратным элементом. Объект ЕОО предлагает "олицетворение" каждой логической функции, которую "видит" в устройстве программное обеспечение верхних уровней. В качестве примера рассмотрим привод жесткого диска и его драйвер. Привод диска может быть представлен объектом РОО, который реализует функции шинного адаптера (присоединяет ЮЕ диск к шине РС1). Как только возникает РОО объект, можно реализовывать объект ЕОО, который примет на себя выполнение функциональных операций над собственно диском. Обращаясь к ЕОО, можно будет сделать конкретный функциональный запрос к диску, например, чтение или запись сектора. Однако ЕОО может выбрать и передачу без модификации конкретного запроса своим партнерам по обслуживанию данного устройства (например, сообщение о снижении напряжения питания). В действительности, роль РОО объектов быстро усложняется и становится рекурсивной. Например, 115В хост-контроллер начинает жизнь как физическое устройство, подключенное к шине РС1. Но вскоре этот хост-контроллер сам начинает выступать в роли шинного драйвера и, по мере обнаружения устройств, подключенных к 115В шине, создает свою коллекцию РОО объектов, каждый из которых контролирует собственный ЕОО объект. Эта методология в дальнейшем усложняется еще более, поскольку Функциональным Объектам устройств (ЕОО) разрешается окружать себя Объектами-Фильтрами (ПИег с1еу|се оЬ)ес1:5, ЕЮО). Соответственно, каждому ЕЮО объекту сопоставлен драйвер, выполняющий определенную работу (иначе — зачем их создавать?). Эти фильтрующие объекты верхнего и нижнего уровня могут существовать в любом количестве. Назначение их в том, чтобы модифицировать или обогатить процесс обработки запросов ввода/вывода возможностью использования всего результирующего стека объектов устройств. Следует отметить, что ЕОО и ЕЮО объекты отличаются только в смысловом отношении — ЕОО объект и его драйвер являются главной персоной, ЕЮО объекты и их драйверы являются вспомогательными (вплоть до того, что предпочитают не иметь собственных имен). Для того чтобы сделать различие между ЕОО объектами, которые представляют аппаратные шины, и ЕОО объектами, которые аппаратные шины не представляют, в документации ООК используются термины шинные РОО (Ьиз РОО) и не-шинные РОО (попЬиз РОО). Первые реализуют обязанности драйвера по перечислению (епитегайпд) всех устройств, подключенных к шине. Такой шинный ЕОО объект затем создает новые РОО объекты для каждого из подключенных к шине устройств. Добавляет проблем тот факт, что существует лишь небольшая смысловая разница между не-шинным ЕОО и фильтрующим объектом устройства (Нкег с1еу1се оЬ)ес1:). С точки зрения Менеджера РпР, все объекты устройств позиционируют себя в стеке устройств (с1еу|се з1аск), а тот факт, что некоторые устройства считают себя более чем просто объектами-фильтрами, кажется ему малозначительным.
Последовательность в стеке устройств показана на рисунке 9.2. Различия между шинными и не-шинными РОО отражены на рисунке 9.3. Фильтрующий 00 Фильтрующий 00 Фильтрующий 00 РОО (создан шинным драйвером) Дс1с10еиюе0 вышестоящего фи л ьтр-драйвера ДсШОешсеО функционального драйвера АЛЛОешсеО нижестоящего фи л ьтр-драйвера Рис. 9.2 Стек устройств Понимание концепции стека устройств важно для того, чтобы правильно описать, когда вызывается функция Ас1с10еу1се конкретного устройства. Общий алгоритм, используемый для загрузки драйверов и вовлечения в работу функции МуАс1с1Оеу1сеКои1:1пе, описывается следующей последовательностью: 1. Во время инсталляции операционной системы, операционная система обнаруживает и составляет список (епитега1:е) всех шин в Системном Реестре (ЗузЬет Кед1з1гу). Кроме того, детектируется и регистрируется топология и межсоединения этих шин. 2. Во время процесса загрузки производится загрузка шинного драйвера для каждой известной системе шины. Как правило, М1сго5оП поставляет все шинные драйверы, однако могут быть установлены и специализированные драйвера для патентованных шин данных. 3. Одна из первоочередных задач шинного драйвера состоит в том, чтобы составить перечень (епитегаЬе) всех устройств, подключенных к шине. Объект РОО создается для каждого обнаруженного устройства. 4. Для каждого обнаруженного устройства в Системном Реестре определен класс устройств (с1азз оГс1еу|се), который определяет верхний и нижний фильтры, если таковые имеются, так же, как и драйвер для РОО. 5. В случае если фильтрующий драйвер или РОО драйвер еще не загружены, система выполняет загрузку и вызывает ОпуегЕпЬгу. 6. Функция Ас1с10еу|се вызывается для каждого РОО, которая, в свою очередь, вызывает 1оСгеа(еОеу1се и 1оАНасНОеу|сеТоОеу|се51:аск, обеспечивая построение стека устройств (с1еу1се з1аск). Функция IоА^^асI1^еV^сеТо5^аск вызывается из Ас1с10еу1се для того, чтобы разместить РОО в вершине (на текущий момент) стека устройств. Прототип функции 1оАНасНОеу1сеТо81:аск описывается в таблице 9.2. Таблица 9.2. Прототип функции 1оАНас11ОеУ1сеТоОеУ1се81аск
РОЕУ1СЕ_ОВЗЕСТ IоАКасН^еV^сеТо^еV^се§^аск Параметры == РА551УЕ_ЬЕУЕЬ Выполняет подключение вновь созданного объекта устройства, р^еVV^еV^се, к стеку устройств 1Ы РОЕУ1СЕ_ОВЗЕСТ рЫе\лЮеУ1се 1Ы РОЕУ1СЕ_ОВЗЕСТ рО1сЮеУюе Указатель на подключаемый к стеку объект (созданный в данном драйвере) Указатель на объект устройства, к которому подключается новое устройство Возвращаемое значение • Указатель на устройство, бывшее на вершине стека до данного вызова • 1\11Л_1_ (в случае ошибки, например, если драйвер целевого устройства еще не загружен) Возвращаемый вызовом IоАНасН^еV^сеТо^еV^се5^аск указатель может отличаться от переданного значения рО1сЮеу1се, например, по той причине, что над объектом устройства рО1сЮеу1се уже размещены объекты устройств, предоставленные фильтр- драйверами. Как видно из таблицы 9.2, для подключения данного объекта устройства (по указателю рЫе\лЮеу1се) необходимо владеть указателем на целевой объект устройства (рО1сЮеу1се). Прекрасна ситуация, когда драйвер подключает свой объект устройства к родительскому объекту устройства (шинного драйвера), указатель на который поступает в процедуру Ас1с1Оеу|се при вызове через заголовок (рРЭО, см. таблицу 9.1). Но что делать, если имеется желание подключить новый объект устройства к объекту устройства другого драйвера, отличающегося от рРОО? (Заметим, что подключение к стеку устройств не есть исключительное право процедуры Ас1сЮеу|се драйверов \А/ЭМ модели — это могут делать и драйверы "в-стиле-МТ", правда, к результатам такой операции следует относиться критически — по причинам, о которых ниже.) При подключении драйвера к произвольному объекту устройства можно поступить двумя способами. Во-первых, если известно имя нужного устройства, можно получить указатель на искомый объект устройства, воспользовавшись предварительно вызовом Iо<зе^^еV^сеОЬ^ес^Ро^п^е^ (см. таблицу 9.3). Полученный указатель на искомый объект устройства (возвращаемый по адресу ррОеуОЬ)), можно применить в вызове 1оАНасНОеу1сеТоОеу1се81аск, описанном выше. Таблица 9.3. Прототип функции 1оСе{ОеУ1сеОЬ]'ес1Ро1п1ег 1ЧТ5ТАТ05 IоСе^^еV^сеОЬ^ес^Ро^п^е^ Параметры 1Ы Р1Л\11ССЮЕ_5ТР.1МС ОеУюеЫате 1Ы АССЕ55.МА5К Ассезз ООТ РЕ11_Е_ОВЗЕСТ *ррЕНеОЬ] == РА551УЕ_ЬЕУЕЬ Получает указатель на объект устройства по имени устройства Имя устройства Маска доступа: Е11_Е_Р.ЕАО_ОАТА, Е11_Е_\А/кГГЕ_ОАТА или Е11_Е_А1_1__АССЕ55 Указатель на файловый объект, которым представлен искомый объект устройства для кода пользовательского режима ООТ РОЕУ1СЕ_ОВЗЕСТ *ррОеуОЬ] Указатель на искомый объект устройства Возвращаемое значение • 5ТАТи5_5иССЕ55 • 5ТАТ05_Ххх — код ошибки Вызов Iо(зе^^еV^сеОЬ^ес^Ро^п^е^ может быть интересен и тем, что драйвер мог бы адресовать 1КР запросы непосредственно искомому объекту устройства (при помощи 1оСа1Югшег), создавая ТИР пакеты с размером стека 51аск51ге+1, где значение 51аск5|ге получено из найденного объекта устройства. Вообще говоря, Диспетчер ввода/вывода автоматически устанавливает необходимое значение 51аск512е (то есть 51аск512е нижнего объекта плюс 1) в подключаемых к
стеку объектах устройств, если это делается при помощи вызовов 1оАНасНОеу1сеТоОеу1се51аск или 1оАНасЬОеу1се. Но в том случае, если драйвер пытается обойтись без этих вызовов, то должен установить 51:аск512е своего объекта явным образом. Таблица 9.4. Прототип функции 1оАНасНОеу1се МТ5ТАТП5 IоАпасН^еV^се Параметры 1Кр1_ == РА551УЕ_ЬЕУЕЬ Выполняет подключение вновь созданного объекта устройства, р^еVV^еV^се 1Ы РОЕУ1СЕ_ОВЗЕСТ рЫе\лЮеУ1се Указатель на подключаемый объект устройства 1Ы Р1Л\11С00Е_5Тк1М6 ТадОеуЫате ООТ РОЕУ1СЕ_ОВЗЕСТ *ррТадОеу1се Возвращаемое значение Имя целевого устройства Указатель на объект устройства, к которому подключается новое устройство (точнее, указатель на место для указателя) • 5ТАТи5_5иССЕ55 • 5ТАТи5_Ххх — код ошибки Второй способ подключения к стеку устройств через объект устройства с известным именем осуществляется при помощи вызова IоАНасI1^еV^се, прототип которого представлен в таблице 9.4. В результате вызовов 1оАНасЬОеу1сеТоОеу1се51:аск или 1оАНасНОеу1се будет найден объект устройства, находящийся на вершине стека над указанным целевым объектом (по имени или по указателю). К нему и будет подключен новый объект устройства. Соответственно, разработчик, подключающий свой объект устройства к устройству в "середине" стека и надеющийся, что таким образом через его драйвер будут "протекать" 1КР запросы от вышестоящих драйверов к нижестоящим, глубоко заблуждается. На самом деле, для достижения этой цели необходимо не просто выполнить подключение к нужному объекту устройства, но и сделать это в строго определенный момент загрузки — ранее, чем будет выполнена загрузка вышестоящих драйверов, чьи запросы предполагается перехватывать. Однако рассмотрение данной проблемы выходит за рамки данной книги. Полученный указатель на объект устройства, к которому произведено подключение, следует сохранить, поскольку он может понадобиться, например, в обработчике запросов 1НР_МЗ_РМР, см. ниже. Это можно сделать в структуре расширения объекта устройства. Заключительной задачей функции Ас1с1Оеу1се драйверов модели ТОМ является создание символьного имени-ссылки (зутЬоНс Нпк пате), если это необходимо, для вновь созданных и доступных устройств. Для этого используется вызов 1оСгеа1е5утЬоНсЬ!пк, применение которого было продемонстрировано ранее в ОпуегЕп1гу, глава 3.
Новые рабочие процедуры в МОМ драйверах Процедура Ас1с1Оеу|се, вызываемая РпР Менеджером, только лишь производит инициализацию объекта устройства и, если необходимо, структуры данных расширения объекта устройства. В процедуре АдсЮеуюе, по правилам хорошего тона УУЭМ модели, действия над собственно аппаратурой не должны совершаться. Но тогда остаются нерешенными две важные задачи: резервирование и конфигурирование аппаратных ресурсов обслуживаемого физического устройства; инициализация и подготовка аппаратной части к использованию. Все это должен сделать драйвер по получении 1КР пакета с кодом 1КР_МЗ_РЫР. Такие ТИР пакеты посылается РпР Менеджером, когда происходят события включения или выключения устройства, либо возникают вопросы по конфигурированию устройства. Категория 1РР_МЗ_РЫР пакетов включает запросы широкого спектра, которые детализируются суб-кодами 1ИР_ММ_Ххх. Поскольку они пропускается через единственную рабочую процедуру, то ее обязанностью является вторичная диспетчеризация по этим суб-кодам, содержащимся в ТИР пакете и описывающим специфические действия, осуществления которых ожидает РпР Менеджер. Регистрация новой для УУЭМ модели рабочей процедуры, которой будет поручено обрабатывать запросы 1РР_МЗ_РЫР со всеми подтипами 1ИР_М1\1_Ххх, производится традиционным образом в процедуре ОпуегЕп1гу: рБгтчегОЬ]->Ма]огЕипсбтоп[1КР_МЗ_РПР] = МуРпР_Напс11ег; Пример программного кода для осуществления вторичной диспетчеризации на основе суб-кодов 1ИР_М1\1_Ххх приводится ниже. МТ5ТАТП5 МуРпР_Напб1ег ( ТО РЭЕУ1СЕ_ОВЗЕСТ рОечОЬ], Р1КР р!гр ) { // Получить указатель на текущую ячейку стека 1КР пакета Р1О_ЗТАСК_ЬОСАТ1ОЕ р!грЗбаскЬосабтоп = 1оСебСиггепб1гр5баскЬо змгксЬ (р1грЗкаскЬосак1оп ->М1погЕипс'Ыоп) { сазе 1РР_МЫ_ЗТАПТ_0ЕУ1СЕ: . . . // Внимание. Все ветви оператора зи1Ес1т должны возвратить // результаты обработки бе^аи1к: // если не поддерживается здесь, то передать запрос вниз: 1о5к1рСиггепб1гр5баскЬосаб1оп(р1гр); гебигп 1оСа11Вг1чег(. . ., р!гр); } } Первым параметром вызова 1оСа1Юпуег (см. таблицу 9.5), разумеется, является указатель на объект устройства, к которому было произведено подключение в процедуре Ас1с1Оеу1се.
Вызов 1о5к1рСиггеп11гр51аскЬоса11оп (см. таблицу 9.6) сообщает Диспетчеру ввода/ вывода, что драйвер отказывается от дальнейшего участия в судьбе данного 1кР пакета. В том случае, если драйвер желает получить управление над ТКР пакетом в момент, когда его обработка нижними слоями драйверов будет завершена, то он должен воспользоваться системным вызовом 1оСоруСиггеп11гр51аскЬоса11опТоМех1 (см. таблицу 9.7) и зарегистрировать процедуру Сотр1е1:юпкои1:1пе. Она будет вызвана в соответствующий момент. Таблица 9.5. Прототип функции 1оСа1Юпуег МТ5ТАТП5 1оСа1Югшег ШСН <= О15РАТСН_ЬЕУЕЬ Параметры Обращается к другому драйверу с запросом, сформулированным в пакете 1НР (запросы типа 1КР—М3_РОА/УЕК следует выполнять при помощи вызова РоСаНОгшег) 1Ы РОЕУ1СЕ_ОВЗЕСТ рОеуОЬ] Указатель на объект устройства, которому адресован 1РР запрос 1Ы Р1К.Р р!гр Указатель на отправляемый 1РР пакет Возвращаемое значение • 5ТАТи5_5иССЕ55 • 5ТАТи5_РЕЫО11\1С — в случае, если пакет требует дополнительной обработки • 5ТАТ05_Ххх — в случае ошибки Таблица 9.6. Прототип функции 1о8к1рСиггепИгр81аскЬосаИоп Х/ОЮ 1о5к|рСиггеп11гр51аск1_оса11ОП Параметры 11иН <= О15РАТСН_ЬЕУЕЬ Изменяет указатель стека 1КР так, что нижестоящий драйвер будет считать текущую ячейку стека ТКР своей 1Ы Р1кР р!гр Возвращаемое значение Указатель на модифицируемый 1КР пакет УО1С1 Таблица 9.7. Прототип функции 1оСоруСиггепИгр81аскЬосаИопТоМех1 УОЮ 1оСоруСиггеп11гр51аск1_оса11ОпТо№х1 11иН <= О15РАТСН_ЬЕУЕЬ Параметры Копирует содержимое ячейки стека ТКР для текущего драйвера в ячейку стека для нижестоящего драйвера 1Ы Р1кР р!гр Указатель на модифицируемый 1РР пакет Возвращаемое значение УО1С1 Процедура завершения ввода/вывода Сотр1ейопКои11пе есть обратный вызов от Диспетчера ввода/вывода, который позволяет перехватить ТКР пакет после того, как низкоуровневый драйвер завершит его обработку. Процедура завершения ввода/ вывода регистрируется вызовом 1о5е1Сотр1еНопПои1те (см. таблицу 9.8). Таблица 9.8. Прототип макроопределения 1о8е1Сотр1еПопКоиПпе Х/ОЮ 1о5е1Сотр1е11Опкои11пе 11иН <= О15РАТСН_ЬЕУЕЬ Параметры Выполняет регистрацию саНЬаск-функции завершения обработки ТКР пакета 1Ы Р1РР р!гр Указатель на отслеживаемый 1РР пакет 1Ы РЮ_СОМР1_ЕТЕ_кОиТ1ЫЕ Функция, которая должна получить управление, когда обработка 1РР будет Сотр1е1юпРоийпе завершена 1Ы РХ/ОЮ рСоп^ех! Параметр, который получит регистрируемая саНЬаск функция Сотр1ейопРои11пе 1Ы ВООЬЕАЫ йоСаНОпВиссезз Вызывать Сотр1ейопРоийпе в случае успешного завершения обработки данного 1РР пакета
1Ы ВООЬЕАЫ аоСаНОпЕггог 1Ы ВООЬЕАЫ с1оСа1ЮпСапсе1 Возвращаемое значение Вызывать Сотр1ейопкоийпе в случае завершения обработки данного 1Р.Р с ошибкой Вызывать Сотр1ейопко1Л1пе в случае прерванной обработки данного 1НР пакета УО1С1 ф Подключать собственные процедуры Сотр/еИопРоиНпе для детектирования окончания обработки 1РР пакетов можно не только к пакетам с кодом 1ПР_1Ю_РПР, но также ко всем остальным, отправляемым драйверам нижних слоев. Вызов 1о5е1Сотр1е1юпПои11пе помещает регистрируемую функцию в ячейке стека 1НР пакета, соответствующую нижнему драйверу. Поэтому, за исключением драйвера, находящегося в самом низу стека, каждый драйвер в иерархии может подключить свою собственную процедуру окончания ввода/вывода к обработке данного 1КР пакета. Процедуры завершения выполняются в порядке помещения драйверных объектов в стек, то есть снизу к вершине стека. Таблица 9.9. Прототип функции завершения ввода/вывода Сотр1еНопПоиНпе 1ЧТ5ТАТ05 Сотр1е11ОпКои11пе 1Кр1_ == см. текст ниже Параметры Перехватывает пакет 1НР после завершения работы нижнего драйверного слоя 1Ы РОЕУ1СЕ_ОВЗЕСТ рОеуОЬ] Объект устройства (в составе данного драйвера), которому был ранее адресован данный 1Р.Р пакет 1Ы Р1К.Р р1гр Указатель на 1Р.Р пакет, обработка которого только что завершена 1Ы Р\/ОЮ рСоп^ех! Аргумент, указанный в 1о5е1Сотр1е1еКои1те Возвращаемое значение 5ТАТи5_МОкЕ_РкОСЕ5511\16_кЕС21ЛкЕО 5ТАТи5_5иССЕ55 Однозначно предсказать, на каком уровне 1И<21_ выполняется процедура завершения, невозможно. В том случае, если нижележащий драйвер, вызывает 1оСотр1е1еКеяие81 (подробности — ниже) с уровня 1Р<21_ равного РА551\/Е_1_Е\/Е1_, то процедура завершения находящегося выше драйвера выполняется на уровне РА551\/Е_1_Е\/Е1_. В случае, если лежащий ниже драйвер завершает обработку 1НР пакета на уровне О1РАТСН_1_Е\/Е1_ (например, из ОРС процедуры), то и процедура завершения лежащего выше драйвера выполняется на уровне 015РАТСН_1_Е\/Е1_. Выполнение программного кода на уровне О15РАТСН_1_Е\/Е1_ ограничивается системными вызовами, которые работают на этом уровне 1Р<21_. Разумеется, следует особо позаботиться, чтобы здесь не производилась работа со страничной памятью. Таблица 9.10. Прототип функции 1оСотр1е1еЯедиез1 У/ОЮ 1оСотр1е1еКедие51 <= О15РАТСН_ЬЕУЕЬ Параметры Вызывается, когда драйвер желает полностью завершить обработку данного 1КР пакета. Обеспечивает вызов процедур завершения всех драйверов, имеющихся над данным (см. ниже) 1Ы Р1К.Р р1гр Указатель на текущий 1КР пакет, обработка которого только что завершена 1Ы ССНАР РпогВооз! Величина, на которую следует изменить приоритет потока, выполняющего обработку данного 1КР пакета. Величина Ю_МО_1МСРЕМЕМТ используется, если никаких изменений делать не нужно. Возвращаемое значение УО1С1 Чтобы устранить упомянутую выше неоднозначность уровня 1Р<21_ работы процедуры завершения, можно прибегнуть к следующей уловке. Предположим, что мы имеем
программный код рабочей процедуры. Известно также, что РпР Менеджер (как, впрочем, и Диспетчер ввода/вывода) всегда выполняет вызов рабочей процедуры драйвера на уровне РА551\/Е_1_Е\/Е1_. Тогда, отправляя пакет 1РР нижним слоям драйвера, организуем ожидание (средствами объекта события режима ядра) не выходя из кода данной рабочей процедуры, пока отосланный нижним слоям 1РР пакет не возвратится в зарегистрированную функцию Сотр1ейопКоийпе. Как только это произойдет, объект события кодом функции Сотр1ейопКоийпе будет переведен в сигнальное состояние, и стадия ожидания в основном потоке завершится. Таким образом, мы получим сигнал о завершении обработки пакета 1КР на вполне определенном уровне 1К(21_, равном именно РА551\/Е_1_Е\/Е1_. Полностью данный метод описывается в примере ниже: // код рабочей процедуры, выполняющийся на уровне РА881УЕ_ЬЕУЕЬ 1оСоруСиггеп‘Ыгр8‘ЬаскЬоса‘ЫопТоЕехЕ (р1гр) ; // Резервируем место под объект события: КЕУЕЫТ туЕчепб; // Инициализируем его, состояние не сигнальное: КеТигЕгаИгеЕчепб ( &туЕчепб, ИобгРгсабгопЕчепб, ЕАЬ8Е ); // Регистрируем свою процедуру завершения обработки 1НР пакета. // Указатель на объект туЕчепб передаем как дополнительный параметр. 1о8ебСотр1еб±опНои-Ыпе ( р1гр, МуСотрТебеНоиЕТпе, (РУО1Б)&туЕчепб, ТРОЕ, ТРОЕ, ТРОЕ); // Предположим, что указатель на объект устройства, // к которому был подключен текущий объект устройства, был ранее // сохранен в структуре расширения текущего объекта устройства. РЕЕУ1СЕ_ЕХТЕИ81ОИ рВечгсеЕхбепзгоп = (РБЕУЮЕ ЕХТЕИ81ОИ) рБечгсеОЬ^ес РБЕУ1СЕ_ОВЙЕСТ рПпбегТугпдБечОЬ= рЕечгсеЕхбепзгоп-УрЬо^егЕечгсе; // Отправляем 1РР пакет на обработку нижними драйверными слоями 1оСа11Бг±чег( рПпбегТугпдБечОЬ^, р1гр ); // Организуем ожидание, пока не закончится работа на нижних уровнях КеЭДа±ЕЕог8±пд1еОЬ^есб( &туЕчепб, Ехесибе, КегпеТМобе, ЕАЬ8Е, ППЬЬ); // Теперь завершаем обработку 1РР пакета. // Его адрес не изменился - р1гр. // По "возвращении" из "ожидания" уровень 1К<2Ь остался прежним для // данного потока.
// Поскольку Диспетчер ввода/вывода и РпР Менеджер вызывают // рабочие процедуры драйвера на уровне РА551\7Е_ЬЕУЕЬ, то таким // он в данном потоке и остался. } МТЗТАТПЗ МуСошрТебеКоибтпе( РВЕУ1СЕ_ОВЙЕСТ р^еVОЬ^, Р1КР р!гр, 1Ы РУОРР рСопбехбАгдитепб ) { // Вычисляем указатель на Объект События: РЕУЕМТ рЕVепб = (РЕУЕМТ) рСопбехбАгдишепб; // Устанавливаем его в сигнальное состояние КеЗебЕчепб ( рЕчепб, О, ЕАЬЗЕ ); // 1Р<2Ь <=О13РАТСН_ЬЕУЕЬ // Пакет 1КР получен. Завершение работы здесь. Но не окончательно, гебигп ЗТАТПЗ_МОРЕ_РРОСЕ331ПС_РЕ0П1РЕО; } Рассмотрим подробнее работу вызова 1оСотр1е1еПеяие81. Когда некий код некоего драйвера делает этот вызов, программный код 1оСотр1е1еЯеяие91 обращается к ячейкам стека ТКР пакета и анализирует, зарегистрировал ли верхний (над текущим) драйвер процедуру завершения Сотр1е1еКоиИпе — это как раз отмечено в стеке ТКР пакета. В том случае, если таковой процедуры не обнаруживается, указатель стека поднимается и снова выполняется проверка. Если обнаружена зарегистрированная функция, то она выполняется. В том случае, если вызванная таким образом функция возвращает код завершения, отличный от 5ТАТ1Т5_МОКЕ_РКОСЕ55ТМСэ_КЕ(21ЛКЕО, то указатель стека снова поднимается, и действия повторяются. Если в результате вызова получен код завершения 5ТАТи5_МОКЕ_РКОСЕ551МО_КЕ<2111КЕО, то управление возвращается инициатору вызова 1оСотр1е1еПеяие81. Когда код 1оСотр1е1еКеяие81 благополучно достигает в своем рассмотрении вершины стека, то Диспетчер ввода/вывода выполняет действия по освобождению данного ТКР пакета (наряду с некоторыми другими операциями). Отсюда несколько важных следствий. Во-первых, если драйвер сам создал ТКР пакет (подробно рассматривается ниже), то вызов 1оСотр1е1еЯеяие81 означает приказ Диспетчеру ввода/вывода заняться его освобождением — поскольку иных драйверов, процедуры завершения которых можно было бы рассматривать, просто нет. Во-вторых, если текущий драйвер зарегистрировал свою процедуру завершения и, не вызывая нижних драйверов, сразу выполнил 1оСотр1е1еКеяие81, то такая процедура завершения вызвана не будет — код 1оСотр1е1еЯеяие81 ее в рассмотрение просто не примет, переходя сразу к анализу ячеек стека ТКР для вышестоящих драйверов. В-третьих, возможна ситуация, когда после выполнения вызова 1оСотр1е1еЯеяие81 драйвером в середине драйверного стека ТКР пакет все еще существует. Не исключено, что он не завершен, поскольку один из верхних драйверов (который, возможно, существует) отказался это сделать в своей процедуре завершения, которую он, возможно, зарегистрировал. Однако текущий драйвер таких допущений делать не
должен. Впрочем, как и всех остальных предположений относительно верхних драйверных слоев. В любом случае, после вызова 1оСотр1е1еЯеяие81 драйвер не имеет права прикасаться к 1РР пакету, который передан этому вызову как завершаемый. Кроме того, возврат кода 5ТАТ1)5_МОРЕ_РРОСЕ551МО_РЕ(2111РЕО — это практика зарегистрированных процедур завершения, что является "просьбой" Диспетчеру ввода/вывода возвратиться к данной процедуре завершения позже.
ГРгеу|ои51 [Мех!],
Ограничения, накладываемые на МЮМ драйверы спецификацией РпР Для того чтобы соответствовать драйверной модели \Л/ЭМ, драйвер обязан поддерживать обработку специфичных РпР ТИР пакетов, каких конкретно — это определяется конкретным типом объекта устройства — не-шинный ЕОО, шинный ЕОО и РОО. Во всяком случае, ТИР пакеты с приведенными в таблице кодами ТНР_М1\1_Ххх должны поддерживаться драйверами всех типов. Таблица 9.11. Суб-коды 1ПР_МЫ_Ххх тр_м1ч_ххх 1кР_М1\1_5ТАкТ_ОЕУ1СЕ I РР_М 1\1_С2 и Е РУ.5ТО Р_О Е VIС Е I РР_М 1\1_5ТО Р_О Е VIС Е I РР_М 1\1_СА1\1 СЕ 1__5ТО Р_О Е VIС Е 1РР_М1\1_С2иЕРУ_РЕМОУЕ_ОЕУ1СЕ Значение (Ре)Инициализация устройства с заданными ресурсами Осуществима ли остановка устройства для возможного переопределения ресурсов? Остановка устройства с потенциальной возможностью перезапуска или удаления из системы Уведомляет, что предыдущий запрос С2иЕРУ_5ТОР не получит дальнейшего развития Может ли быть выполнено безопасное удаление устройства в текущий момент? I РР_М 1\1_РЕ М О УЕ_О Е VIС Е Выполнить работу, обратную работе АсИОеуюе 1РР_ММ_СА1\1СЕ1__РЕМОУЕ_ОЕУ1СЕ 1РР_М1\1_5иРРР15Е_РЕМСА/А1_ Уведомляет, что предыдущий запрос <2иЕРУ_РЕМСЛ/Е не получит дальнейшего развития Уведомляет, что устройство было удалено без предварительного предупреждения
Передача РпР 1ИР пакетов нижним драйверным слоям Все запросы РпР инициируются РпР Менеджером, и он всегда направляет эти запросы драйверу, находящемуся в стеке устройств на вершине стека. Независимо от того, какие коды 1РР_М1\1_Ххх в составе ТИР запросов могут быть обработаны драйвером, те из них, которые не обрабатываются, должны быть переданы ниже по стеку устройств нижележащим драйверам, которые могут реализовывать свои собственные обработчики. Трансляция РпР запросов вниз по стеку устройств необходима по многим причинам. Некоторые драйверы в стеке могут вносить свою лепту в обработку запроса, но не один драйвер не должен подразумевать, что запрос может быть полностью завершен на данном уровне. Например, уведомление об остановке устройства является критичным для всех слоев драйверных объектов. Чтобы передать РпР запрос вниз, драйвер помечает ТИР пакет как "завершенный" установкой соответствующих значений в полях То51:а1и5.51а1:и5 и ТоБ^аТиз.ТпГогтайоп, а затем производит вызовы 1оСоруСиггеп151аск1.оса11ОпТо№х1 и 1оСа1Юпуег. Нижележащий драйвер известен еще при выполнении Ас1с1Оеу|се (из вызова 1оАНасНОеу|сеТоОеу|се51аск), а указатель на него рекомендуется сохранять в структуре расширении объекта устройства. Пример кода, выполняющего эти действия, приводится ниже. ТоСоруСиггеп'ЫгрЗкаскЬоса'ЫопТоЫех'Ь ( р!гр ) ; РБЕУ1СЕ_ЕХТЕ№ЮИ рТ^1^з^еV^сеЕxкепз^оп = ( РСЕУ1СЕ_ЕХТЕ№1СЖ) рТ^1^з^еV^сеОЬ^ еск->^еV^сеЕxкепз^оп; 1оСа11ВгТ^ег ( рТ^1^з^еV^сеЕxЬепз^оп ->р^пс^е^1у^пд^еV^се, р!гр ); В случае, если у драйвера нет необходимости ожидать окончания обработки переданного вниз запроса драйверами нижних уровней, то может быть использован более эффективный механизм для пропуска участка текущего ТИР стека. Функция 1о5к|рСиггеп11гр51аскЬоса1!оп просто удаляет участок текущего стека ТИР пакета из участия в обработке. Этот механизм предлагается для обращения с РпР запросами, которые не обрабатываются данным драйвером и которые должны быть просто переданы следующему нижележащему драйверу: ЫТЗТАТОЗ 0п1уТгапз1аЪе1грВоип (1М РВЕ\71СЕ_ОВ ЗЕСТ р^еV^сеОЬ^еск, Р1КР р1гр) { ТоЗкТрСиггеп'ЫгрЗ'ЬаскЬоса'Ыоп ( р!гр ); РОЕУ1СЕ_ЕХТЕЫ31ОЫ р^еV^сеЕxкепз^оп = (РСЕ71СЕ_ЕХТЕ№1ОЫ) р^еV^сеОЬ]еск ->^еV^сеЕxкепз^оп; гекигп ТоСаНЭгТ^ег (рТ^1^з^еV^сеЕxкепз^оп->р^пс^е^1у^пд^еV^се, } Бывают случаи, когда драйвер вынужден пропускать вниз РпР запросы раньше, чем он завершает собственную работу над ними. Например, при обработке запроса с кодом ТНР_ММ_5ТАНТ_0Е\/1СЕ драйверу, как правило, необходимо дождаться, пока стартуют низкоуровневые драйверы перед началом работы их собственного
аппаратного обеспечения. Шина и любое низкоуровневое аппаратное обеспечение инициализируется до старта отдельных устройств. Таким образом, высокоуровневые драйвера должны сначала транслировать вниз запрос и затем дождаться завершения низкоуровневой обработки перед продолжением своей работы. Действия, которые драйвер должен выполнить, когда его рабочая процедура возвращает управление Диспетчеру ввода/вывода, рассматриваются ниже.
Оеу1се ЕпитегаНоп — всеобщая перепись устройств Как было сказано ранее, Менеджер конфигурирования РпР ответственен за выполнение переписи устройств, обнаруженных в системе. Стартовало ли устройство при загрузке системы, или оно добавляется/удаляется позже, шинный драйвер отвечает за идентификацию и ведение списка подключенной аппаратуры. Аппаратные ресурсы, необходимые устройству, предоставляются драйверу этого устройства, когда ему отправляется сообщение (1РР пакет) с кодом 1РР_МЗ_РЫР и с суб-кодом 1ИР_М1\1_5ТАРТ_0Е\/1СЕ. Весьма показателен в этом отношении пример из пакета ЭЭК, посвященный драйверам шины и устройств ТОА5ТЕИ.
ГРгеу1ои8~| ГИехМ
Многослойные драйверы Драйверная модель \ЛЮМ ориентирована на многослойные драйверные структуры и базируется на подходе, в котором функциональные драйверы позиционируются над физическими драйверами, возможно, при участии фильтр-драйверов, окружающих функциональный слой. Во многих случаях, требуется только лишь создать фильтр-драйвер чтобы добиться нужного результата от существующего функционального драйвера. В рамках многослойного подхода можно определить три типа драйверов: Шинные драйверы — обеспечивают интерфейс аппаратных шин в базисе "один слот — одна единица" и создают один или более физических объектов устройств (РОО, РИу51са1 Оеуюе ОЬ^ес!:), соответствующих каждому обнаруженному устройству, подключенному к шине. Шинный драйвер конструирует РОО и управляет им, вследствие чего часто его называют физическим драйвером. Функциональные драйверы — обеспечивают чтение, запись и прочую логику функционирования отдельного устройства. Они создают и управляют одним или более функциональными объектами устройств (РОО, Рипсйопа! Оеуюе ОЬ]'ес1:). Фильтр-драйверы — обеспечивают модификацию запроса на ввод/вывод перед предъявлением его драйверам более низких уровней. Фильтры могут быть размещены вокруг функционального драйвера либо над шинным драйвером.
ГРгеу1ои8~| ГИехМ
Когда следует применять многослойную архитектуру? Один из самых первых и самых важных вопросов конструирования драйвера состоит в том, следует ли реализовать драйвер в виде набора слоев или он должен быть монолитным? Доводы "за" Драйвер, реализованный по многослойной методике, имеет два преимущества. Использование слоев позволяет отделить вопросы использования высокоуровневых протоколов от вопросов, связанных с управлением собственно оборудованием. Это позволяет осуществлять поддержку аппаратуры от разных производителей без переписывания больших объемов кода. Многослойная архитектура позволяет повысить гибкость системы за счет использования при одном драйвере, реализующем протокол, сразу нескольких драйверов аппаратуры, подключаемых непосредственно во время работы. Этот прием реализован в сетевых драйверах \Л/1Пс1о\л/5 ЫТ 5. В случаях, когда к одному и тому же контроллеру (как это происходит в случае со 5С51 адаптерами) подключаются различные типы периферийных устройств, многослойная методология позволяет отделить управление периферией от управления контроллером. Для того чтобы решить подобную проблему, необходимо написать лишь один драйвер для контроллера (порт-драйвер, рог1 с/пуег) и после этого реализовать классовые драйверы (с/азз с1п'уег) для каждого типа подключаемых периферийных устройств.
Две основные выгоды, получаемые здесь, состоят в том, что классовые драйвера меньше и проще, и при этом вполне вероятна ситуация, когда порт-драйвер и классовые драйвера поступают от разных поставщиков. В таком случае сборщик компьютера не обременен подбором аппаратных компонентов, имеющих общий драйвер. Создание аппаратных шин 115В и 1ЕЕЕ 1394 базируется на многослойном подходе к драйверам именно по перечисленным выше причинам. Внесение драйверных слоев предоставляет простой способ добавления и удаления дополнительных возможностей устройств без установки нескольких вариантов программного обеспечения для одного и того же устройства. Использование многослойных драйверов позволяется также скрыть от конечного пользователя некоторые аппаратные ограничения используемого устройства или ввести некоторые свойства, не поддерживаемые собственно устройством. Например, если элемент аппаратуры поддерживает работу с данными только определенного размера, то можно поставить над его драйвером другой, который будет дробить поток данных на более мелкие порции, скрывая от пользователя ограниченность возможностей этого устройства. Недостатки многослойной архитектуры Разумеется, существует и оборотная сторона этой медали. Во-первых, обработка запросов ввода/вывода получает дополнительные накладные расходы, связанные с тем, что пакет 1Р.Р пропускается через код Диспетчера ввода/вывода всякий раз, когда он
переходит из одного драйвера в другой. В некоторой мере эти затраты могут быть уменьшены путем введения прямого меж-драйверного интерфейса, который будет действовать в обход Диспетчера ввода/вывода. Потребуются также дополнительные усилия в тестировании для того, чтобы убедиться, что отдельные компоненты получившейся драйверной конфигурации приемлемо стыкуются друг с другом. При отсутствии стандартов это может оказаться весьма болезненным этапом, особенно если драйверы поступают от разных поставщиков. Возникает также не самый простой вопрос совместимости версий разных участников получившейся иерархии. Наконец, инсталляция многослойных драйверов также становится сложнее, поскольку каждый из них требует для себя соответствующей инсталляционной процедуры. При инсталляции необходимо установить отношения зависимости между разными драйверами в иерархии, чтобы обеспечить их запуск в правильной очередности.
ГРгеу|ои81 ГИехП
Работа с нижними слоями драйверного стека Переходя к модели УУОМ и многослойной архитектуре, возникает необходимость уяснения практических механизмов сотрудничества драйверов в рамках этой концепции. Что должен делать драйвер, чтобы задействовать нижние драйвера и соответствовать требованиям модели, оправдывая надежды верхних драйверов? Второе требование удовлетворить легче. Драйвер не знает и не может знать, что за клиент инициировал запрос, какова была его мотивация и полномочия. В этой ситуации драйвер просто получает 1Р.Р пакеты и должен добросовестно их обработать, отправляя их нижним слоям драйверов или выполняя всю обработку самостоятельно. Возвращая управление Диспетчеру ввода/вывода, рабочая процедура должна пометить текущий 1Р.Р пакет как завершенный (вызовом 1оСотр1е1еКеяие81, таблица 9.10) или как требующий дополнительной обработки (вызовом 1оМагк1грРепсНпд, таблица 9.12). Как должна развиваться ситуация в последнем варианте событий, было рассмотрено ранее. Таблица 9.12. Прототип функции 1оМагк1грРепсПпд УОЮ 1оМагк!грРепсНпд <= О15РАТСН_ЬЕУЕЬ Параметры Помечает пакет 1КР как требующий дополнительной обработки Ш Р1РР р1гр Указатель на текущий 1РР пакет Возвращаемое значение УО1С1 © Драйвер обязан возвратить управление и пометить пакет как требующий дополнительной обработки в том случае, если пакет 1РР, отправленный нижним слоям драйверов, вернулся со значением ТПиЕ в поле р1РР->РепсПпдРе1:игпес1 (разумеется, если только драйвер сам не является создателем этого пакета). Ситуация усложняется, если речь заходит о взаимодействии с нижними драйверными слоями. Если драйвер, получая запрос от Диспетчера ввода/вывода, просто отправляет его нижним слоям (устанавливая процедуру Сотр1е1юпП.оийпе перехвата пакета "на обратном пути", или не делая этого), то все сводится к тому, чтобы правильно манипулировать вызовами 1о8е1Сотр1е1юпЯоийпе, 1оСа1Юпуег, 1о(зе1СиггепНгр81аск1.осайоп, 1о8к|рСиггеп11гр81аск1.осайоп и 1оСоруСиггеп11гр81аскЬоса11ОпТоМех1:. Однако в том случае, если драйвер должен дробить поступающие запросы, накапливать, размножать (например, чтобы послать устройствам, работающим параллельно) или формировать собственные, то возникает задача создания новых пакетов 1ПР, поскольку только они являются средством общения между драйверами. Не составляют исключения и драйверы, работающие через прямой интерфейс (адреса вызовов), поскольку начальная инициализация интерфейса поначалу происходит через 1Р.Р запрос. Диспетчер ввода/вывода конструирует пакеты 1Р.Р по запросу драйвера, который тот может осуществить с помощью вызовов: 1оВи11с1А8упсГ|гопои5Е8с1Кеяие81 1оВиИсЮеу|се1оСоп1:го1Кеяие81 1оВи11с15упсГ1опои8Г8с1Пеяие81 Сконструированный пакет 1Р.Р содержит столько стековых ячеек, сколько указано в поле 51аск5|ге целевого (куда передается этот пакет ТИР) объекта устройства. Указатель на целевой объект устройства передается в качестве аргумента этим перечисленным выше функциям. Таким образом, созданные этими функциями
пакеты 1РР содержат достаточное количество стековых ячеек, для обеспечения вызовов всех нижележащих драйверов, но не содержит ячейки для самого промежуточного драйвера. В случае, если промежуточный драйвер использует 1оА11оса1е1гр или ЕхА11оса1еРоо1 (см. далее в тексте) для создания 1РР пакета, то есть создает 1РР с нуля — начиная с выделения памяти, то драйвер должен явным образом указать количество ячеек стека в пакете 1КР при его создании или учесть их количество при выделении памяти. Общепринято использование поля 51аск5|ге в целевом объекте устройства для этой цели. Как правило, у драйвера нет большой необходимости иметь собственную ячейку в стеке 1КР пакета. Но в случае, если драйвер действительно испытывает необходимость в хранении собственных данных, которые потребуются в момент возвращения 1РР пакета, то необходимо создавать 1РР пакет, в котором будет на одну ячейку стека ввода/вывода больше — для самого драйвера. В примере, приведенном ниже показано, как это можно сделать. // Откуда берется указатель рТагдеЕЬечгсе, см. выше в 9 главе рЬеЫгр = 1оА11осаЕе1гр ( рТагдеЕЬечгсе -> ЗЕаскЗЫе + 1, ЕАЬЗЕ ) ; // Новый пакет 1РР создается с указателем стека, изначально // установленным на несуществующую позицию перед первой // существующей ячейкой стека. Переводя указатель стека на // одну позицию вниз, добиваемся того, что он будет указывать // на первую ячейку, которую можно теперь использовать для // сохранения информации, необходимой текущему (верхнему) драйверу. 1оЗеЕЕехЫгрЗЕаскЬосаЫоп ( рЬеШгр ) ; рЬзеЕиТАгеа = ТоЗеЕСиггепЫгрЗЕаскЬосаЫоп ( рЬеЫгр ) ; // Теперь можем использовать пространство ячейки стека вывода, // на которую указывает рЬзеЕиТАгеа, по собственному усмотрению. // Устанавливаем разнообразные необходимые значения в ячейке стека, // соответствующей нижнему драйверу рЕехЫоЗЕаскЬосаЫоп = ТоЗеЕЕехЫгрЗЕаскЬосаЫоп ( рЬеШгр ) ; рЕехЫоЗЕаскЬосаЫоп -> Ма^ огЕипсЫоп = 1РР_МП_ХХХ; // Подключаем процедуру завершения 1оЗеЕСотр1еЫопРоиЫпе ( рЬеЫгр, ОиЫоСотрТеЫопРоиЫпе, ШЬЬ, ТРОЕ, ТРОЕ, ТРОЕ ) ; // Посылаем 1РР пакет целевому драйверу: 1оСа11ЕЫчег (рТагдеЕЕечгсе, рЬеЫгр ) ; Что можно хранить в такой искусственно созданной ячейке? Например, число попыток отослать данный 1РР пакет нижнему драйверу, если он отказывается
обработать его немедленно. При сериализации поступившего от клиента запроса на объемную операцию ввода/вывода, можно хранить число переданных байт данных и общий размер операции, используя один и тот же 1КР пакет многократно.
ГРгеу|ои51 [Мех!],
Создание 1ЯР пакетов вызовами 1оВиИс1(А)§упс11гопои5р5с1Кеяие8( Как уже, наверное, понял читатель, пакеты 1НР можно создавать с нуля (обладая только областью памяти достаточного размера), но можно и прибегнуть к помощи рекомендованных системных вызовов 1оВиН(15упс11ГОПои8Г8с1Кеяие81, 1оВиН(1А8упс11гопои8Г8(1Кеяие81 и 1оВиН(Юеу1сеСоп1го1Пеяие81. Первые два вызова предназначены для конструирования 1КР пакетов с кодами 1РР_МЗ_ИЕАЭ, 1ИР_МЗ_\Л/И1ТЕ, 1ИР_МЗ_Е1_115Н_В11ЕЕЕР5 и 1КР_МЗ_5Н1ПТ>ОУУМ, вполне пригодные для использования во всех драйверах, несмотря на устрашающий суффикс Езй. Последний из этих вызовов, 1оВиНсЮеу1сеСоп1го1Пеяие81, предназначен для конструирования таких ТИР пакетов, как если бы они были инициированы пользовательским АР1 вызовом Оеу1се1оСоп1го1, то есть с кодом 1ИР_МЗ_ОЕ71СЕ_СОЫТИО1 или 1ИР_МЗ_1МТЕИМА1_ОЕУ1СЕ_СОЫТРО1 Таблица 9.13. Описание прототипа функций 1оВиП<1(А)5упсНгопоизРзс1Кедиез1 Р1КР 1оВиИс1§упсНгопои5р5с1Неяие51 Р1КР 1оВиИс1А5упсНгопои5р5с1Неяие51 1Нр1_ == РА551УЕ_ЬЕУЕЬ 1Нр1_ <= О15РАТСН_ЬЕУЕЬ Параметры 1Ы 1Л_О1\1С Ма]ОгРипс1юп Построение 1КР пакета (выделение памяти и настройка полей) • 1кР_МЗ_РЫР или • 1кР_М1_кЕАО или • 1кР_МЗ_\Л/к1ТЕ или • 1кР_МЗ_РШ5Н_ВиРРЕк5 или • 1кР_МЗ_5НиТОО\Л/Ы 1Ы РОЕ71СЕ_ОВЗЕСТ рТагдеЮеУюе Объект устройства, которому отдается 1РР 1Ы ООТ РХ/ОЮ рВиГГег 1Ы и1_О1\1С иЬепдЫ: 1Ы Р1_АР.СЕ_11\1ТЕСЕР. Виг^пдОГГзе! Адрес буфера данных ввода/вывода Размер порции данных в байтах Смещение в устройстве, где начинается/продолжается операция ввода/вывода Только для 1оВиИс15упс11гопои5Е5с1П.едие51: 1Ы РкЕ7Е1\1Т рЕуегИ Объект события, используемый для сигнализации об окончании ввода/вывода (должен быть инициализирован к моменту вызова). Объект переходит в сигнальное состояние, когда нижний драйвер завершил обработку данного 1Р.Р пакета. оит рю_5ТАтив_в1_оск 1озь Возвращаемое значение Для получения завершающего статуса операций ввода/вывода • Не МОИ- — адрес нового пакета 1Р.Р • МОИ- — невозможно создать новый 1РР Число ячеек, создаваемых в стеке ввода/вывода, размещающемся в пакете ТИР, равно значению, указанному в поле рТагде1:0еу|се->51аск512е. В данном случае нет простого способа создать дополнительную ячейку в стеке пакета ТИР собственно для самого вызывающего драйвера. Значения аргументов ВиГГег, 1_епдТ:1э и ЗТагИпдОГГзеТ требуются для операций чтения и записи. Для операций ЛизК и 51ш1с1о\л/п они должны быть установлены равными 0. Нужные значения в области РагатеТегз ячейки стека, соответствующей нижнему драйверу, устанавливаются автоматически, то есть нет необходимости передвигать указатель стека. Для запросов чтения или записи эти функции еще выделяют системное буферное пространство или выполняют построение МО1_ — в зависимости от того, выполняет ли вызываемое устройство (по указателю рТагдеЮеуюе) буферизованный или прямой ввод/вывод. При буферизованных операциях вывода производится также копирование содержимого буфера инициатора вызова в
системный буфер, а в конце операции буферизованного ввода данные автоматически копируются из системного буфера в буферное пространство инициатора вызова. Здесь общие черты этих двух функций заканчиваются. Начинаются различия. Как следует из названия функции 1оВиНс15упс11гопои8Е8с1Педие81, она работает синхронно. Другими словами, поток, который выполняет вызов ХоСаНОгшег, прекращает свою работу до тех пор, пока не завершится операция ввода/вывода в нижних драйверных слоях. Для более удобной реализации такой блокировки, в создаваемый пакет ТИР в виде аргумента передается адрес инициализированного объекта события (еуепТ оЬ]ес1:). Затем, после передачи созданного пакета драйверу нижнего уровня (вызовом 1оСа11Огшег) следует использовать функцию КеМа!1Еог5тд1еОЬ]ес1 — для организации ожидания перехода этого объекта синхронизации в сигнальное состояние. Когда драйвер нижнего уровня завершит обработку данного пакета ТИР, Диспетчер ввода/вывода переведет данный объект события в сигнальное состояние, что и "разбудит" данный драйвер в нужный момент. Аргумент ТозЬ позволяет получить информацию о том, как завершилась обработка. Заметим, что, поскольку текущий драйвер узнает о завершении обработки нового ТИР пакета от функции КеУУа11Еог8тд1еОЬ]ес1, то он не должен устанавливать свою процедуру завершения перед тем, как обратиться к нижнему драйверу вызовом 1оСа1Югшег. Если же процедура завершения все-таки установлена, она всегда должна возвращать 5ТАТ1)5_51)ССЕ55. Пакеты, созданные функцией 1оВиН(15упс11ГОПои8Е8с1Кедие81, должны освобождаться только косвенно — в результате вызова 1оСотр1е1еКедие81 после получения сигнала от объекта события, а Диспетчер ввода/вывода уже сам очистит и освободит память, занятую ТИР пакетом. Это включает освобождение системных буферных областей или МО1_, выделенных для использования в обработке этого ТИР. Использовать 1оЕгее1гр нельзя, так как такой ТИР пакет участвует в очереди, организованной для пакетов, ассоциированных с данным программным потоком. Применение к нему вызова 1оЕгее1гр ранее, чем он будет удален из данной очереди, приведет к краху системы. Кроме того, во избежание неприятностей, следует следить за тем, чтобы объект события существовал к моменту, когда Диспетчер ввода/вывода соберется перевести его в сигнальное состояние. Соответственно, фрагмент кода, который создает синхронный ТИР и адресует его объекту устройства рТагде!:Оеу|сеОЬ]ес1 в нижнем драйвере, мог бы выглядеть следующим образом: Р1КР р!гр; КЕУЕЫТ ЕVеп^:; 1О_ЗТАТЬЗ_ВЬОСК 1озЬ; Ке1п1Ь1а112еЕчепЬ(&ЕчепЬ, ^оЕ^Е^саЕ^опЕVепЕ, ЕАЬЗЕ) ; р1гр = ЬоВиИсТЗупсЬгопоизЕзсТИедиезб (1ИР_Ы0_Ххх, рТагдебВечтсеОбдесЬ, . . . &ЕчепЬ, &1озЬ); збабиз = IоСа11^^^Vе^ (рТа^деб^еV^сеОЬ^есЬ, р1гр) ; збабиз == ЗТАТОЗ_РЕЫО1№ ) { // Ожидаем окончания обработки в нижних слоях КеИатбЕогЗтпдТеОЬ]есб (&Ечепб, ЕхесиЫче, КЕгпе1Мос1е, ЕАЬЗЕ,МО збабиз = тозЬ.Збабиз;
В отличие от пакетов 1РР, производимых по запросу синхронной версии, функция 1оВи!1с1А8упсНгопои9Е9с1Пеяие81 конструирует пакеты, которые не освобождаются автоматически по окончании работы над ним в нижнем драйвере. Вместо этого, драйвер, создающий "асинхронный" пакет 1РР должен обязательно подключить свою процедуру завершения, которая и должна выполнять вызов 1оРгее1гр. Процедура завершения и должна выполнить очистку 1РР с освобождением выделенных ему системных буферных областей или МЭ1_, а затем и освобождения памяти, занятой под структуру самого 1РР пакета. В данном случае, процедура завершения должна возвратить значение 5ТАТи5_МОРЕ_РРОСЕ5511\1Сэ_РЕ(21ЛРЕО. Соответствующий пример кода может выглядеть следующим образом: Р1КР р!гр; 1О_ЗТАТЕЗ_ВЬОСК 1озЬ; р!гр = ТоВиИДАзупсПгспоизЕзсШедиезЕ (1В.Р_ЫТ_Ххх, рТагдеЕ0еч1се0Ь)есб, . . . &1озЬ); 1оЗе'ЬСотр1е'ЫопР.ои'Ыпе ( р1гр, (Р1О_СОМРЬЕТ1ОН_КОЕТ1ЫЕ) МуСотр1еЕ1опВоиЕ1пе, рТЫзОечЕхбепзРоп, ТРЕЕ,ТРЕЕ, ТРЕЕ) ; //Чтобы целевое устройство не "растворилось" за время обработки 1РР: ОЬВеДегепсеОЬдесб (рТагдеЕЕечтсеОЬесб) ; збабиз = 1оСа11Ег1чег(рТагдеЕОечзсеОЬ)есб, р1гр); ОЬЕегеЕегепсеОЬ]есб(рТагдеЕ0еч1се0Ь)есб); // Процедура завершения, зарегистрированная ранее МТ5ТАТЕ5 МуСошрТеЕтопРоибтпе ( РЕЕУ1СЕ_ОВТЕСТ рТЫзОечгсе, Р1РР р!гр, ТОЮ рСопбехЕ ) { // Действия по очистке 1РР 1оЕгее1гр( р!гр ); геЕигп ЗТАТЕЗ_МОРЕ_РРОСЕ331ЫС_РЕ0Е1РЕО; } Драйверы, которые реализуют блокирующийся механизм работы (как это получается при синхронизации по объекту события), могут привести к деградации системы. Такое может случиться, если они будут выполнять вызовы 1оСа1Юпуег с повышенных уровней 1РС)1_. В этом случае они могут остановиться на неопределенно долгое время, ожидая отклика с нижних уровней. Это противоречит общей философии построения УУ|пс1оуу5 МТ 5. Видимо, поэтому разработчики У\Лпс1о\л/5 искусственно затруднили построение синхронных 1РР пакетов на повышенных уровнях 1РС)1_ тем, что вызов 1оВи!1с15упс11гопои8Е8с1Яеяие81 можно сделать только с уровня 1РрЬ, равного РА551УЕ_1_Е7Е1_.
Кроме того, объект события, используемый для организации ожидания, когда же закончится обработка пакета ТИР на нижних уровнях, должен использоваться с максимальной осторожностью, поскольку при использовании такого драйвера в многопоточной манере могут возникнуть сложные ситуации. Допустим, два программных потока одного и того же пользовательского процесса делают запрос на запись с использованием одного и того же дескриптора (иными словами, делают запрос к одному и тому же драйверу). Тогда рабочая процедура МпТеРециезЖапсПег выполняется в контексте первого потока и останавливается в том месте, где она желает дождаться сигнала от объекта события. Затем, та же самая процедура УУп1:еИецие51:Напс11ег, выполняемая в контексте другого потока, использует повторно тот же самый объект события для обработки другого запроса. Когда запускаются оба потока, то ни один из них не может быть уверен, чей же конкретно пакет ТИР обработан, поскольку при окончании обработки любого из ТИР с одинаковым успехом возникает сигнал от объекта события. Решение может состоять в том, чтобы подстраховать объект события при помощи быстрого мьютекса или даже создавать новые объекты события для каждого вновь конструируемого ТИР пакета.
Создание 1ЯР пакетов вызовом 1оВш1с1Оеу|се1оСоп1го1Кеяие8( Последняя из трех упомянутых, предназначенных функций для создания 1КР пакетов, 1оВиН(Юеу1се1оСоп1го1Кеяие51 (таблица 9.14) также призвана облегчить этот процесс. Этот весьма полезный вызов предназначен для построения пакетов 1РР, обслуживающих ввод/вывод устройств с большими вариациями в поведении и с использованием пользовательских ЮСТ1_ кодов запросов. Таблица 9.14. Описание прототипа функции 1оВиП(ЮеУ1се1оСоп(го1Яедиез1 Р1НР IоВи^IсI^еV^сеIоСоп^^оIРе^ие5^ == РА551УЕ_ЬЕУЕЬ Параметры 1Ы 1Л_О1\1С 1оСоп1то1Сос1е Формирует 1РР пакет (с выделением памяти), описывающий обращение с 1ОСТ1_ запросом Код ЮСТ1_, принимаемый (допускаемый) к обработке целевым устройством 1Ы РОЕ71СЕ_ОВЗЕСТ рТагдеЮеУюе Объект устройства, которому предназначен формируемый пакет ШР 1Ы РХ/ОЮ рТприЛВиГГег 1Ы 1Л_О1\1С трииепдЫ Оит РХ/ОЮ рОиГриГВиГГег 1Ы 1Л_О1\1С ои^риНепдЫ 1Ы ВООЬЕАЫ 1п1егпаЮеу|се1оСоп1го1 Адрес буфера ввода/вывода, передаваемого драйверу нижнего уровня Длина буфера р1при1ВиГГег в байтах Адрес буфера ввода/вывода для данных, возвращаемых драйвером нижнего уровня Длина буфера рОи!ри1:ВиГГег в байтах ТКОЕ — буден сформирован 1Р.Р пакет с кодом 1кР_МЗ_1МТЕкМА1__ОЕ\/1СЕ_СОМТкО1_ ЕАЬЗЕ - с кодом 1кР_МЗ_ОЕ71СЕ_СО1\1ТкО1_ 1Ы РКЕ7Е1\1Т рЕуеп! Объект события (еуеп! оЬ]ес1), используемый для сообщения об окончании ввода/вывода оит РЮ_5ТАТи5_В1_ОСК р!озЬ Возвращаемое значение Для получения завершающего статуса операций ввода/вывода • Адрес нового пакета 1Р.Р либо • 1\1иы_ — невозможно создать новый ШР Следует также отметить, что этот вызов может конструировать 1РР как с синхронным способом обработки, так и асинхронным. Для получения "синхронного" 1РР в функцию необходимо просто передать адрес инициализированного объекта события. После того как 1НР пакет будет передан нижнему драйверу вызовом 1оСа1Юпуег, следует использовать КеУ7а11Еог5тд1еОЬ]ес1 для организации ожидания сигнала от этого объекта события. Когда драйвер нижнего уровня завершит обработку 1РР, Диспетчер ввода/вывода переведет объект события в "сигнальное" состояние, и в результате будет разбужен драйвер, который "организовал" весь этот процесс. Блок данных по указателю р!озЬ сообщает об окончательном состоянии пакета 1Р.Р. Так же, как и в случае с 1оВиЛс15упсЬгопои8Е8с1Кеяие81, следует аккуратнее работать в многопоточном режиме. Диспетчер ввода/вывода автоматически выполняет очистку и освобождение 1НР пакетов, созданных по вызову 1оВиН(Юеу1се1оСоп1го1Кеяие51 по завершении их обработки, включая подключенные к этому пакету системные буферные области или МЭ1_. Для запуска такой очистки драйвер должен просто сделать вызов 1оСотр1е1еКеяие81. Обычно, нет необходимости подключать процедуру завершения к пакетам 1К.Р такого типа, если только у драйвера нет необходимости выполнить какие-нибудь специфические действия пост-обработки. Но уж если такая процедура подключена, то
она должна возвращать значение 5ТАТ1)5_511ССЕ55, чтобы позволить Диспетчеру ввода/вывода выполнить очистку этого пакета по окончании процедуры завершения. Метод буферизации, который указан в ЮСТЬ коде, влияет на формирование ТКР пакета. В том случае, если ЮСТЬ код описан как МЕТНОО_В11ЕЕЕКЕО, внутри вызова 1оВиН(Юеу1се1оСоп1го1Кеяие51 выполняется выделение области нестраничной памяти, куда производится копирование содержимого буфера по адресу рТприТВиГГег. Когда обработка ТКР завершается, содержимое буфера в нестраничном пуле автоматически копируется в область памяти по адресу рОиТри^ВиГГег. В случае, если ЮСТЬ код содержит флаги МЕТНСЮ_О1ГГ_О1КЕСУ или МЕТНОО_11\1_ОТКЕСТ, то 1оВи|1сЮеу1се1оСоп1го1Кецие81 всегда выполняет построение МОЕ списка для буфера рОиТриТВиГГег и всегда использует буфер в нестраничной памяти для буфера рТприТВиГГег, независимо от того, указан ли МЕТНОО_11\1_ОТКЕСТ или МЕТНОО_О1ГГ_ОТКЕСТ. В общем-то, формирование ТКР пакета в обоих случаях происходит совершенно аналогично тому, как если бы в \ЛЛп32 обрабатывался вызов Оеу!се1оСоп1го1, поступивший из приложения пользовательского режима.
Создание 1ЯР пакетов "с нуля" В отдельных редких случаях, когда нужно формировать запрос, отличающийся от операций чтения, записи, очистки буферов, операции зКи^оууп или ЮСТ1_ операций, единственным вариантом остается выделение памяти под пакет 1КР и заполнение его нужными данными "вручную". Для формирования пакетов 1НР можно использовать функцию 1оА11оса1е1гр, которая выполняет выделение памяти под пакет 1РР в зонном буфере Диспетчера ввода/ вывода, после чего выполняет некоторые действия по инициализации полей в выделенной области. Попробуем отказаться и от этой услуги Диспетчера ввода/вывода и создать пакет 1Р.Р "совершенно с нуля" на примере 1КР пакета для буферизованного ввода/вывода. В данном случае память под 1РР пакет выделяется в нестраничном пуле при помощи вызова ЕхА11оса1еРоо1, а затем производится инициализация необходимых полей внутри созданной области. Общая инициализация выделенной области по типу "1НР пакет" должна быть выполнена при помощи вызова 1о1т11аН2е1гр. Установка полей в той ячейке стека 1НР пакета, которую будет разбирать драйвер-получатель (владеющий устройством рТагдеЮеуюе), и буферных областей для передачи данных возлагается на текущий драйвер. Предполагается, что текущий драйвер получил 1НР пакет рОпд1па11гр и должен сформировать 1РР пакет для запроса на чтение (хотя именно его проще было бы сформировать описанными ранее вызовами). #с1еЕ1пе ВПЕЕЕК_312Е (1024) ССНАВ пО^ВедиФгейЗкаскЬосз = рТагдеЫеч1се->Зкаск312е ; ПЗНОКТ ФгрЗФхе = ТоЗФхеОЫгр (пОЕКеди1гес15ЫскЬосз) ; Р1О_ЗТАСК_ЬОСАТ1ОП рТадБеч!грЗкаскЬосакФоп; Р1Р.Р рСгеакес11гр = (Р1КР) ЕхА11осакеРоо1 ( ЫопРадес1Роо1, ФгрЗФхе ); 1о1п1Ыа11ге1гр ( рСгеакесПгр, ФгрЗФхе, пОЕКедиФгейЗкаскЬосз) ; // Получаем указатель на ячейку стека 1КР, которая после вызова // 1оСа11РгФчег будет ассоциирована с нижним драйвером: рТад0еч1грЗЫскЬосаЫоп = 1оСекПехЕ1гр5каскЬоса'Ыоп ( рСгеакесПгр ); // Подразумевая операцию чтения, устанавливаем поля ячейки: рТадВеч!грЗЫскЬосаЫоп->Ма ) огЕипсЫоп = 1ВР_МП_В.ЕАВ; рТад0еч1грЗЫскЬосаЫоп->РагашеЫгз . Веа<Ф . ЬепдЫ1 = ВПЕЕЕК_512Е; рТад0еч1грЗЫскЬосаЫоп->РагатеЫгз . Кеас!. ВукеОЫзеЫ фиасФРагк = 0164 ; // В запросе 1ВР_МП_ВЕАВ список МВЬ не может использоваться. // Передаем собственный буфер в качестве системного, // требующегося при данном типе запросов: РУО1В пеиВиЫег = ЕхАФФосабеРооФ ( ЫопРадесФРооФ, ВПЕЕЕР._312Е ) ; рСгеакесПгр -> АззосФаФесПгр. ЗузЫтВиЫег = пеиВиЫег;
// Если вызываемое устройство имеет свойство (флаг) ВО_В1ЕЕСТ_1О: ФР ( рТагдеПВеч±се->Е1адз & ВО_В1ЕЕСТ_1О ) { // Описание 1оА11осабеМс11 см. в таблице 7.19. Поскольку трети // параметр равен ЕАЬ8Е, указатель на созданный МБЬ список бу // сразу занесен в поле рСгеабес11гр-> МсПАсИгезз РМБЬ рИемМсП = 1оА11осабеМс11 ( пемВиРРег, ВПЕЕЕЕ_8IЕЕ, ЕАЬ8Е, ЕАЬ8Е, рСгеабесПгр) ; // для буфера в нестраничной памяти: МтВиИбМсИЕогПопРадебРоо! ( рПемМсИ) ; } // Копируем информацию о потоке инициатора вызова: рСгеабесИгр -> ТаИ . ОчегТау. ТЬгеаб = рОггдгпаИгр -> ТаИ . ОчегТау. ТЬгеаб; // Устанавливаем процедуру завершения обработки сформированного 1ЕР 1о8еПСотр1е‘ЫопЕои‘Ыпе ( рСгеабесИгр, Му1оСотр1еП1опЕои‘Ыпе г ППЬЬ, ТЕПЕ, ТЕПЕ, ТЕПЕ ); // Передаем созданный пакет драйверу нижнего уровня 1оСа11Вг1чег ( рТагдеПВечгсег рСгеабесИгр ) ; Неочевидность приведенных выше манипуляций с ячейкой стека ТЕР пакета говорит о том, что необходимо в совершенстве владеть тонкостями формирования пакетов для тех типов запросов, которые вам захочется создавать самостоятельно. В процедуре завершения следует переместить полученные "снизу" данные соответствующему получателю наверху. Разумеется, в процедуре завершения необходимо выполнить и действия по освобождению ресурсов, присвоенных ТЕР пакету, то есть выполнить освобождение памяти, занятой для системного буфера и собственно ТЕР пакета. ПТ8ТАТИ8 Му1оСотр1е-ЫопЕоиП1пе (та РВЕУ1СЕ_ОВПЕСТ рТЫзБечгсеОЬ]есб, та Р1ЕР р1гр, та РУО1Б рСопбехб ) { // Очистка структуры МБЬ списка: 1оЕгееМс11 ( р!гр->Мс!1Ас1с1гезз ) ; // Освобождение специального буфера: ТоЕгееРоо! ( р1гр->Аззос1абес11гр. 8узбетВи11ег );
// Освобождение собственно 1КР: 1оЕгее1гр ( р!гр ); гебигп ЗТАТПЗ_МОКЕ_РРОСЕ331НС_РЕ0С1РЕО; } ф Следует обратить внимание на то, что освобождение памяти выполняется при помощи вызова 1оРгее1гр, а не ожидаемого ЕхРгееРоо/. В данном случае так можно поступать потому, что Диспетчер ввода/вывода получает из определенного поля 1РР информацию о том, получен ли данный пакет из нестраничного пула или из специального зонного буфера, которым распоряжается только Диспетчер ввода/вывода. Возможны ситуации, когда драйвер ведет собственную политику относительно выделения памяти под 1НР пакеты, например, из ассоциативного списка, им же созданного. Такие 1КР все равно должны быть инициализированы вызовом 1о1пШаН2е1гр, однако, применять к ним вызов 1оЕгее1гр нельзя, поскольку тот, скорее всего, нарушит учет памяти в драйвере. При всей сложности самостоятельного создания ТИР пакетов с нуля, в этом есть одно важное преимущество — драйвер контролирует количество создаваемых ячеек стека ТИР пакета. В том числе — дополнительных, которые могут оказаться в некоторых случаях незаменимыми для хранения специфичной (для данного ТИР пакета и для данного драйвера) информации в течении времени жизни этого ТИР пакета. Ниже приводятся описания прототипов использованных функций. Таблица 9.15. Описание прототипа функции 1оА11оса1е1гр Р1НР 1оА11оса1е1гр <= О15РАТСН_ЬЕУЕЬ Параметры Формирует 1РР пакет с выделением памяти (не требует последующего вызова ТоТтНаНгетР) 1Ы ССНАК 51аск5|ге Количество ячеек стека во вновь создаваемом 1КР пакете 1Ы ВООЬЕАЫ СЬагдеС^ио^а ЕАЬВЕ Возвращаемое значение Адрес нового пакета 1КР либо 1\11Л_1_ — невозможно создать новый 1КР Таблица 9.16. Описание прототипа функции 1о1пШаИхе1гр У/ОЮ 1о1тНаН2е1гр <= О15РАТСН_ЬЕУЕЬ Параметры Формирует 1РР пакет в ранее выделенной области памяти (не должна использоваться для пакетов, созданных вызовом 1оА11оса1е1гр) 1Ы Р1ИР р!гр Указатель на область, используемую под 1КР 1Ы 05НОРТ Раске^ге Заранее вычисленный общий размер 1КР пакета (можно использовать вызов 1о§12еОПгр) 1Ы ССНАР В1аскВ12е Количество ячеек стека во вновь создаваемом 1КР пакете Возвращаемое значение УО1С1 Таблица 9.17. Описание прототипа функции 1оЕгее1гр У/ОЮ 1оЕгее1гр <= О15РАТСН_ЬЕУЕЬ Параметры Очищает и освобождает 1НР пакеты, созданные вызовами 1оАНоса1е1гр или 1оВиЛс1А5упсНгопои5Г5с1Реяие51 1Ы Р1РР р!гр Указатель на освобождаемый 1КР пакет Возвращаемое значение УО1С1
Как было сказано ранее, пакеты, созданные 1оВш1<15упс11гопои8Г8с1Кеяие81: или 1оВиН(Юеу1се1оСоп1го1Кеяие81, освобождаются самим Диспетчером ввода/вывода, когда драйвер завершает обработку такого пакета вызовом 1оСотр1е1еКеяие81. Освобождения пакетов, сделанных нестандартными способами (например, с помощью ЕхА11оса1:еРоо1) выполняет сам драйвер. Таблица 9.18. Описание прототипа функции 1о81хеОПгр 05НОКТ 1о5|2еОПгр 1Пр1_ — любой Параметры Определяет размер 1КР пакета, как если бы он имел §1аск§12е ячеек стека 1Ы ССНАР. 51аск5|ге Предполагаемое число ячеек стека 1Р.Р пакета Возвращаемое значение Размер в байтах
ГРгеу|ои51 [Мех!],
Работа с 1ИР пакетами-репликантами Если промежуточный драйвер занимается "выпуском" собственных ТКР пакетов, направляя при этом нижним драйверным слоям (одному или нескольким разным драйверам) по нескольку экземпляров сразу, то он рано или поздно попадет в следующую непростую ситуацию. До момента окончания обработки всех "вторичных" пакетов драйвер не может завершить обработку исходного (изначально поступившего) ТКР пакета. Возможны два способа работы с размноженными вторичными пакетами. В первом, "синхронном", случае рабочая процедура должна дождаться, пока все созданные драйвером ТКР пакеты не будут обработаны в нижних уровнях. В общем случае, рабочая процедура выполняет следующее: 1. Выполняет вызовы 1оВиН<15упс11гопои8Е8с1Пеяие91 или вызовы IоВи^Iс^^еV^сеIоСоп^^оIпе^ие8^ для того, чтобы создать необходимое количество ТКР пакетов"синхронного" типа. 2. Выполняет вызовы 1оСа1Югшег для передачи всех созданных драйвером пакетов ТКР другим драйверам. 3. Выполняет вызовы КеМа!1ЕогМиШр1еОЬ]ес18 и ожидает завершения обработки всех переданных ТКР пакетов. 4. Выполняет действия по переносу информации из полученных пакетов и их последующую очистку и освобождение. 5. Наконец, выполняет вызов 1оСотр1е1еЯеяие81 относительно исходного ТКР пакета для того, чтобы возвратить его инициатору вызова. Поскольку исходный запрос удерживается внутри рабочей процедуры (поскольку она не возвращает управление), то нет и необходимости помечать исходный ТКР пакет как ожидающий обработки. Второй, "асинхронный", случай несколько сложнее, поскольку непонятно, где именно драйвер может остановиться и дожидаться завершения обработки всех пакетов. В этом случае драйверу рекомендуется подключить процедуру завершения к каждому созданному им ТКР пакету, и процедура завершения (скорее всего — единственная для всех пакетов) должна сама определить, наступило ли время считать, что обработка исходного ТКР запроса действительно завершена. План действий примерно таков: 1. Пометить пакет ТКР, поступивший в рабочую процедуру от Диспетчера ввода/ вывода как ожидающий обработки при помощи ХоМагкРепсНпд. 2. Создать дополнительные пакеты ТКР с использованием одного из описанных выше методов. 3. Подключить процедуру завершения (возможно — одну и ту же) к каждому из вновь созданных ТКР пакетов вызовом 1о5е1Сотр1еНопЯоиНпе. При выполнении этого вызова следует передать указатель на исходный ТКР пакет в аргументе рСопТ:ехТ:. 4. Запомнить число созданных пакетов ТКР в неиспользуемом поле исходного ТКР пакета. Поле РагатеТ:ег5.Кеу текущей ячейки стека ТКР пакета вполне годится. 5. Передать пакеты всем нужным драйверам вызовом 1оСа1Югшег. 6. Возвратить значение 5ТАТ1)5_РЕМОТМС, поскольку обработка исходного запроса (пакета ТКР) не завершена. По окончании обработки каждого ТКР пакета драйвером нижнего уровня во втором, "асинхронном", варианте вызывается процедура завершения рассматриваемого ("нашего") драйвера, которая выполняет следующие операции:
1. Выполняет необходимый перенос информации, очистку и удаление созданного драйвером 1РР пакета, вернувшегося от нижнего драйвера. 2. Уменьшает на единицу сохраненное ранее число незавершенных пакетов 1РР. Это действие рекомендуется выполнять, приняв хотя бы минимальные меры по безопасному доступу к этому значению. Вполне подходит для этой цели вызов 1п1ег1оскес1Оесгетеп1. 3. В случае, если незавершенных пакетов не осталось, выполняет вызов 1оСотр1е1еЯеяие51, что сигнализирует о полном завершении обработки исходного 1РР запроса. 4. Возвращает управление Диспетчеру ввода/вывода с кодом завершения 5ТАТ05_МОРЕ_РРОСЕ551МС;_РЕ(21ЛкЕО — для того, чтобы не допустить вызов процедур завершения вышестоящих драйверов для работы над пришедшим "снизу" 1НР пакетом, созданным данным драйвером. Кстати заметить, к этому моменту рассматриваемый 1НР пакет уже уничтожен.
Удаление 1ПР пакетов Как бывает и в реальной жизни, кто-то, инициировавший 1ЕР запрос, может передумать и инициализировать снятие запроса "с повестки". Пользовательское приложение может запросить уничтожение пакета после длительного ожидания. Приложение может вовсе прекратить работу, бросив все на попечение операционной системы. Наконец, приложение может попытаться завершить свою асинхронную операцию \Л/1п32 АР1 вызовом СапсеПо. Таблица 9.19. Прототип функции 1оСапсе11гр ВООЬЕАИ ТоСапсеПгр 1Кр1_ <= О15РАТСН_ЬЕУЕЬ Параметры Помечает пакет 1НР как требующий удаления и вызывает процедуры удаления, если таковые определены 1Ы Р1К.Р р1гр Указатель на удаляемый 1РР пакет Возвращаемое значение ТРОЕ — если пакет удален ЕАЬЗЕ — в случае неудачи В режиме ядра для удаления запроса выполняется вызов 1оСапсе11гр (таблица 9.19). Операционная система также вызывает 1оСапсе11гр для всех 1КР пакетов, относящихся к потоку, выполнение которого прекращается. Предположим, некий код режима ядра направил пакет (в данном случае — синхронный) другому драйверу. Как он может выполнить удаление отправленного пакета, например, в результате превышения времени ожидания? Пример ниже иллюстрирует этот случай. // формирует синхронный пакет: РТЕР р!гр= ТоВиИйЗупсЬгопоизЕзйЕедиезЬ ( . . ., &ечепЬ, &ФозЬ) ; // Подключаем процедуру завершения: 1о5еЕСотр1еЫопЕоиЫпе ( рТгр, МуСотрТеЫопЕоиЫпе, (УОТС* ) &ечепЬ, ТЕПЕ, ТЕПЕ, ТЕПЕ ) ; ЫТЗТАТИЗ зЬаЬиз = ТоСаИСгФчег ( . . .); зЕаЬиз == ЗТАТПЗ_РЕПС1МС ) { // Некоторое время ожидаем естественного завершения ЬАЕСЕ_ТЫТЕСЕЕ иа1ЬСе1ау; ма±ЬСе1ау.ОиаДРагЬ = - 10000; // относительное время И ( КеИаФЬЕогЗФпдТеОЬ]есЬ ( &ечепЬ, Кегпе1Мос1е, ЕАЬЗЕ, &иа100е1ау) == 5ТАТПЗ_Т1МЕОПТ ) { 1оСапсе11гр(рТгр); КеИаТЬЕогЗФпдТеОЬ]есЬ( &ечепЬ, Кегпе1Мос1е, ЕАЬЗЕ, ЫПЬ } } // Синхронные ТЕР пакеты - их удаляет Диспетчер ввода/вывода: ТоСошрТеЬеЕедиезЬ(рТгр, ТО_ЕЮ_ТМСЕЕМЕМТ); // Процедура завершения ЫТ5ТАТП5 МуСошрТеЫопЕоиЫпе ( РСЕУТСЕ_ОВПЕСТ рТЬ1з0еч1се, РТЕР рТгр,
РУОЮ рСопЬехЬ ) { 1Г (р1гр->РепсНпдКеЕигпес1) КеЗеЬЕVеп11 ( (РКЕУЕЫТ) рСопЬехЬ, 1О_НО_1ЫСКЕМЕЫТ, ЕАЬЗЕ геЬигп ЗТАТПЗ_МОКЕ_РРОСЕ331№_КЕ0Е1РЕО; } Процедура 1оСапсе11гр устанавливает флаг (сапсе! Ы1) в 1КР пакете и выполняет вызов процедуры Сапсе1Иоийпе, если таковая имеется в 1К.Р пакете. Таблица 9.20. Прототип предоставляемой драйвером функции Сапсе/КоиНпе У/ОЮ СапсеШои1!пе Параметры == О15РАТСН_ЬЕУЕЬ Выполняет действия, сопутствующие удалению пакета 1РР Указатель на объект устройства, которое (точнее — драйвер) и 1Ы РОЕУ1СЕ_ОВЗЕСТ рОеуОЬ] зарегистрировало ранее эту функцию в 1Р.Р пакете вызовом 1о§е1Сапсе1Рои11пе (см. ниже) 1Ы Р1К.Р р!гр Возвращаемое значение Указатель на удаляемый 1РР пакет УО1С1 Таблица 9.21. Прототип функции 1о5е1Сапсе1КоиПпе РОН1УЕН_САНСЕЬ 1о5е1Сапсе1Кои11пе 1Н<21. <= О15РАТСН_ЬЕУЕЬ Параметры Устанавливает (переустанавливает) определяемую драйвером функцию СапсеШоиНпе для данного 1РР пакета 1Ы Р1К.Р р!гр Указатель на 1Р.Р пакет, которому будет соответствовать устанавливаемая функция Сапсе1кои11пе Указатель на функцию, которая соответствует прототипу, описанному в 1Ы РОР1\/ЕР._САМСЕ1_ Сапсе1ко1Шпе таблице 9.20, или 1\11Л_1_ (если следует отменить функцию Сапсе1Р.ои11пе для данного пакета 1РР) Указатель на ранее установленную для данного 1РР пакета функцию Возвращаемое значение Сапсе1Р.ои11пе. Соответственно, если таковой не было, то возвращается 1\11Л_1_. Значение Г\1С11_1_ возвращается также, если пакет находится в обработке и не может быть удален В отличие от процедур завершения, которые могут быть указаны для каждого драйвера (в соответствующих ячейках стека 1Р.Р), место для процедуры Сапсе1Иои111пе в 1РР пакете единственное, что указывает на ее необычный статус — она вызывается один раз для удаляемого пакета. Соответственно, эта процедура и должна принадлежать тому драйверу, который наиболее компетентно может распорядиться судьбой 1РР пакета. Правда, этот драйвер может регистрировать разные свои функции для удаления разнотипных ТИР пакетов (чтения, записи и т.п.). Драйвер должен участвовать в схеме удаления ТИР пакетов, если он реализует собственную очередь пакетов или участвует в использовании системной очереди (то есть зарегистрировал процедуру 51агНо). При этом подразумевается, что процедуре удаления подвергаются прежде всего пакеты в состоянии ожидания, то есть не выполненные сразу, а помещенные по этой причине в очередь. Пакеты ТИР, которые переданы на обработку нижним драйверам и "застряли" там — это худшее, что можно придумать в момент удаления. Код вызова 1оСапсе11гр устроен примерно следующим образом — по крайней мере, так уверяет Уолтер Оней:
ВООЬЕАЫ 1оСапсе11гр(Р1КР р!гр) { 1оАсди1геСапсе13р1пЬоск(&р!гр->Сапсе11гд1) ; р1гр->Сапсе1=ТР1)Е; РОК1УЕР_САЫСЕЬ Сапсе1РоиЕ1пе = 1оЗеЕСапсе1КоиЕ1пе (р!гр, №ЗЬЬ) 1Е( Сапсе1КоиЕ1пе != КГОЬЬ) { Р1О_ЗТАСК_ЬОСАТ1ОЫ сиггепЕСеИ = Ройе'ЬСиггеп'ЫгрЗ'ЬаскЬоса'Ыоп (р!гр) ; (*Сапсе1КоиЕ1пе)(си^^епкСе11->^еV^сеОЬ^еск, р!гр); геЕигп ТРЕЕ; } е1зе { 1оКе1еазеСапсе15р1пЬоск(р1гр->Сапсе!1гд1) ; геЕигп ЕАЬЗЕ; } } Для ограничения доступа к удаляемому пакету, код 1оСапсе11гр, прежде всего, запрашивает объект спин-блокировки вызовом 1оАсяи!геСапсе15р1пЬоск (в переменной р1гр->Сапсе11гц1 сохраняется значение текущего уровня 1РС)1_ для использования при последующем вызове 1оПе1еа9еСапсе15рт1-оск). В случае, если за 1Р.Р пакетом закреплена процедура Сапсе1Роийпе, то она вызывается (теперь на нее возложена задача освобождения спин-блокировки). Если же такой процедуры нет, то вызов 1оСапсе11гр завершает работу, освобождая спин-блокировку. Процедура Сапсе1Кои11пе, получив управление, может оказаться в одной из описанных ниже ситуаций. Во-первых, рассматриваемый 1НР пакет в настоящий момент обрабатывается. Если такой пакет все еще находится в одной из рабочих процедур драйвера, то он имеет право на несколько секунд жизни, после чего драйвер должен принять решение о его дальнейшей судьбе, скорее всего, завершить с ошибкой, например: 1гр->1оЗЕаЕиз.ЗЕаЕиз = 5ТАТЕЗ_1О_Т1МЕОЕТ; 1гр->1оЗЕаЕиз.РпЕогтаЕгоп = 0; 1оСотр1еЕеКедиезЕ(1гр, 1О_ЫО_1НСКЕМЕЫТ); В том случае, если пакет "застрял" в нижних слоях драйверов, то это вина нижних драйверов, которые не смогли организовать обработку данной нештатной ситуации. Лучшим приемом будет — дождаться естественного развития событий, когда пакет вернется, вероятнее всего, с каким-нибудь кодом ошибки. Выяснить, обрабатывается ли рассматриваемый пакет именно сейчас, можно при помощи следующего кода, поскольку, если задействован механизм ЗузЬет <2иеи1пд и какой-либо пропущенный через него 1НР пакет в настоящий момент обрабатывается, то именно адрес этого 1НР пакета "лежит" в поле рОеу|сеОЬ]ес1->Сиггеп11гр (иначе там будет 1\11Л_1_):
УОШ МуСапсеЦкои'Ыпе ( 1Ы РОЕ71СЕ_ОВЗЕСТ рОечтсеОЬдеск, га Р1КР рТгр) { ( р!гр == рОечтсеОЬд еск->Сиггеп'Ыгр ) Конкретная реализация действий по удалению текущего пакета (если она возможна) остается задачей разработчика драйвера. Существенно проще становится ситуация, когда пакет, предназначенный для уничтожения, только что поступил в процедуру 51аг11о либо еще находится в очереди отложенных (репсНпд) пакетов. 701В Збаг'Ыо ( 1Ы Р0Е71СЕ_0ВЗЕСТ рОечхсеОЬдесб, 1Ы Р1ВР р1Кр) { К1К0Ь Сапсе11гд1; 1оАсди1геСапсе13р1пЬоск(&Сапсе11гд1) ; (р!гр->Сапсе1) { 1оКе1еазеСапсе15р1пЬоск (СапсеИгд!) ; гекигп; } // Удаляем процедуру обработки удаления, делая пакет // "поб сапсеТаЫе" - неуничтожаемым 1оЗе'ЬСапсе1Вои'Ыпе (р!гр, ЕГОЬЬ) ; 1оВе1еазеСапсе13р1пЬоск(Сапсе11гд1); } В случае, если удаляемый 1РР пакет пока находится в системной очереди (которая называется еще "управляемая 51агНо"), а перед его размещением там (то есть вместе с вызовом 1оМагк1грРепсНпд) была зарегистрирована процедура МуСапсе1Рои11пе для этого 1кР пакета, то действия по удалению такого пакета (не текущего — не находящегося в обработке) могут выглядеть следующим образом: 7010 МуСапсеТКоибтпе( га Р0Е71СЕ_0ВЗЕСТ рОечтсеОЬ]есб, га Р1КР р!гр) { ( р!гр == рОечгсеОЬд есЬ->Сиггеп'Ыгр ) { // Текущий 1КР 1оКе1еазеСапсе15р1пЬоск(р1гр->Сапсе11гд!) ; // Вряд ли можно сделать что-то еще... } е!зе { // Удаляем из системной очереди: КеР.еточеЕпкгуОечтсеОиепе ( &р0еч1се0Ь]есЬ->0еч1се0иеие &р!гр->ТаИ . Очег 1ау. ОечгсеОиеиеЕп'Ьгу) ; // Только теперь можно освободить спин-блокировку: 1оКе1еазеСапсе15р1пЬоск(р!гр->Сапсе11гд1);
р1гр->1оЗкакиз . Зкакиз=ЗТАТПЗ_САЫСЕЬЬЕ0; р1гр->1о5какиз.ТпУогшакаоп = 0; 1оСошр1екеКедиезк ( р!гр, 1О_МО_1МСКЕМЕЫТ ); } гекигп; } Приведенный ниже пример выполняет удаление пакета из очереди, поддерживаемой собственно драйвером (так называемой "Оеу1се-Мападес1 Риеие"). УОЮ МуОЗЬегСапсеТКоиктпе ( РВЕУ1СЕ_ОВЗЕСТ р^еV^сеОЬ^есб, Р1КР р!гр ) { К1К<2Ь о1с11К(2Ь; РМУОЕУ1СЕ_ЕХТЕМ51СМ р^еVЕxк = (РМХ0Е71СЕ_ЕХТЕП310М) р^еV^сеОЬ^ ес^->^еV^сеЕxкепз^оп; 1оЗе'ЬСапсе1Рои'Ыпе (р!гр, ТШЬЬ) ; // Освобождаем спин-блокировку, установленную еще 1оСапсе11гр 1оКе1еазеСапсе15р1пЬоск(р1гр->Сапсе!1гр); // Удаляем 1КР из очереди под защитой спин-блокировки КеАсдитгеЗртпЬоск ( &рВечЕх'Ь->ОиепеЬоск, &о1с!1РОЬ) ; Кетс^еЕпкгуЫзк (&р!гр->ТаИ . ОVе^1ау. ЫзкЕпкгу) ; КеКеТеазеЗртпЬоск (&р^еVЕx^->^иеие^оск, о1с11К(2Ь) ; // р1гр->1оЗкакиз.Зкакиз = ЗТАТЗЗ_САЫСЕЬЬЕ0; р1гр->1о5какиз.ТпГогшактоп = 0; 1оСошр1екеКедиезк( р!гр, 1О_МО_1МСКЕМЕЫТ ); гекигп; } Предполагается, что объект спин-блокировки, используемый для синхронизации доступа к очереди пакетов, рОеуЕх1-><2иеие1_оск был заранее создан и сохранен в структуре расширения данного устройства. ф Вопросы создания и поддержки очередей 1Р.Р пакетов, ведомых собственно драйвером, в данной книге не рассматриваются. Хотя этот прием достаточно широко распространен и присутствует в примерах пакета ВЭК. В заключение, следует отметить, что после вызова 1оСотр1е1еЯеяие91 в конце процедуры Сапсе1РоиИпе (обработки удаления ТИР пакета в данном драйвере) запускаются вызовы процедур завершения вышестоящих драйверов — если такие драйвера имеются и если соответствующие процедуры были зарегистрированы для данного ТИР пакета на случай его удаления (см. описание вызова 1о5е1Сотр1е1!опЯои11пе в таблице 9.8).

Заключение В данной главе были рассмотрены мотивы, обусловившие внедрение РпР спецификации как стандарта конструирования аппаратуры. Были рассмотрены также пути приспособления драйверной модели □О5-У\Лпс1о\л/5 к новым условиям, в результате чего конечной версией драйверной модели на сегодня стала \ЛЮМ модель. Были обсуждены специфические аспекты разработки драйверных процедур в канве новой модели и рассмотрены примеры использования стандартных системных вызовов режима ядра в процедурах \ЛЮМ драйверов, реализующих многослойную методологию современной драйверной модели У\Лпс1о\л/5.
Глава 10
Программные потоки и синхронизация Настоящая глава касается вопросов создания программных потоков, функционирующих в режиме ядра и, разумеется, проблемы, всегда актуальной в многозадачных системах — синхронизации выполнения и доступа к совместно используемым данным.
ГРгеуюиа! [Ыех1]
Системные программные потоки Процесс в операционной системе \ЛЛпс1о\л/5 является единицей владения. Процессу принадлежат, в частности, программные потоки, которые уже и являются единицами исполнения. Для каждого потока установлен независимый программный счетчик и контекст исполнения, который включает состояние регистров центрального процессора, значение уровня 1К.С21_, от которого зависит, когда этот поток получит возможность распорядиться системным процессором. Потоки пользовательского режима хорошо известны программистам пользовательских приложений. Несколько отличаются от них потоки режима ядра. Системный поток есть такой поток, который выполняется исключительно в режиме ядра. Он не имеет контекста пользовательского режима и не может получить доступ в пользовательское адресное пространство. Соответственно, программный код рабочих процедур (в частности, код процедуры обработки ЮСТ1_ запросов, который может пользоваться виртуальными адресами пользовательского приложения) не может считаться системным потоком, хотя и относится к коду драйвера режима ядра. Системные программные потоки созданы специальными вызовами и не имеют возможности интерпретировать виртуальные пользовательские адреса (ниже 0x80000000) ни в одном из пользовательских контекстов. Системный поток не имеет корней в пользовательском режиме. Данная особенность накладывает основное ограничение, свойственное системным программным потокам, — они могут пользоваться только адресами системного адресного пространства. Все остальные адреса (виртуальные адреса пользовательских приложений), переданные им каким-нибудь способом, будут не просто бесполезны — их использование может привести к краху операционной системы. Вторая особенность системных программных потоков состоит в особом статусе программного кода режима ядра. Если пользовательское приложение снимается, например, из панели Диспетчера задач, то происходит приостановка работы всех
потоков снимаемого процесса и соответствующая очистка ресурсов, им принадлежащих. В режиме ядра такую работу за программный код системного потока никто выполнять не будет. Системный программный поток должен самостоятельно завершить свою работу и самостоятельно освободить занимаемые ресурсы. Системные потоки выполняются обычно на уровне приоритета 1К<21_ АРС_1_Е7Е1_ или РА551УЕ_1_ЕУЕ1_ (если системный программный поток искусственно не повысил свой приоритет определенными вызовами). Соответственно, эти потоки соревнуется за использование центрального процессора наряду с программными потоками пользовательского режима. Основная причина использования программных потоков в режиме ядра та же, что и в пользовательских приложениях — обслуживание медленных операций либо событий, наступление которых плохо прогнозируется. Сюда относятся и длительные операции инициализации некоторых специфических устройств. Диспетчер ввод/вывода отводит процедуре ОпуегЕп1ту около полуминуты, после чего загрузка драйвера прекращается. Вполне приемлемым решением мог бы быть запуск программного потока, который продолжал бы инициализацию устройства, а процедура ОпуегЕп1ту быстро возвратила бы код успешного завершения. Таблица 10.1. Прототип вызова РзСгеаЪеЗузЪетТНгеас! 1ЧТ5ТАТ115 Р8Сгеа1е5у8(етТНгеас1 Параметры ОЬТ РНАЫО1Е рТКгеас1Напс11е 1Ы и1_ОГ1С Ое51гес1Ассе55 1Ы РОВЗЕСТ_АТТК1ВиТЕ5 АПпЬ 1Ы НАЫОЬЕ РгосеззНапсПе ОСТ РС1_1ЕМТ_Ю СНепНс! 1Ы РК5ТАНТ_РОиТШЕ 51аг1Ас1с1г 1Ы РУОЮ СогИехС 1КРЬ == РА331УЕ_ЬЕУЕЬ Создает системный программный поток Указатель на переменную для сохранения дескриптора нового программного потока ТНРЕАО_А1_1__АССЕ55 (или 01_) для создаваемого драйвером потока 1\1и1_1_ для создаваемого драйвером потока 1\1и1_1_ для создаваемого драйвером потока 1\1и1_1_ для создаваемого драйвером потока Стартовая функция потока — точка входа в поток Аргумент, передаваемый в стартовую функцию
Возвращаемое значение • 5ТАТ115_511ССЕ55 — поток создан • 5ТАТ115_Ххх — код ошибки Не последнюю роль в потребности использовать программные потоки в драйвере играет и тот факт, что (стандартно) их код выполняется на уровне РА551\/Е_1_Е\/Е1_. Как поступить, если разработчик драйвера желает протоколировать события в драйвере, записывая их в файл на диске, включая события, происходящие при повышенных приоритетах 1КС21_? Ведь функция 2ууСгеа1еЕНе и 7^\Л/г11еР|1е работают только на уровне РА551\/Е_1_Е\/Е1_, следовательно, о протоколировании из 15Р и ОРС процедур следует забыть? Подобная задача достаточно легко решается, если высокоприоритетный код будет помещать свои записи в промежуточный буфер, который будет сброшен на диск позже системным программным потоком, выполняющимся на подходящем для этого уровне РА5517Е_1_ЕУЕ1_. Системные программные потоки создаются вызовом Р8Сгеа(е$у5(етТНгеас1 (таблица 10.1), а завершиться они должны самостоятельно — выполнением вызова Р5Теггтпа1е5уб1етТ11геас1. Таблица 10.2. Прототип вызова Р8Тепгнпа1е8у81етТНгеас1 МТ5ТАТ05 Р8Теггтпа1е5у81етТНгеас1 1КРЬ == РА351УЕ_ЬЕУЕЬ Параметры Вызывается системным программным потоком при окончании работы 1Ы МТ5ТАТ05 ЕхкЗИаСиз Код завершения потока Возвращаемое значение 5ТАТ115_511ССЕ55 — поток прекращен Когда драйвер выгружается, он должен быть уверен, что все системные потоки, созданные драйвером, завершены. Зачастую, это означает, что должен быть использован какой- либо из механизмов сигнализации, при помощи которого можно было уведомить поток о необходимости завершить свою работу и дождаться этого момента.
Создаваемые драйвером системные программные потоки имеют при создании уровень 1С2И1_ равный РА551\/Е_1_Е\/Е1_ в диапазоне приоритетов 1\1огта1 (см. таблицу 6.2), хотя могут иметь любой приоритет в диапазоне 1\1огта1 и Неа1Т1те. В общем случае системный поток, запущенный из драйвера, должен выполняться при приоритете, находящемся вблизи нижней границы диапазона геа1-11гпе. Изменить текущий приоритет потока можно в нем самом, используя вызов Ке5е1РпогИуТ11геас1, как демонстрируется в следующем фрагменте: УО1Е ТЬгеайЗ'Ьаг'ЬКои'Ыпе (РУО1Е рСопРехР) { КеЗе’ЬРгтогт'ЬуТЬгеас! ( КебеРСиггепРТНгеас! () ЬОМ_РЕАЬТ1МЕ_РР1ОР1ТУ); } Заметим, что численное значение ЬОМ_кЕА1_Т1МЕ_РР1ОН1ТУ в заголовочных файлах ЭОК установлено равным 16 (это нижняя граница диапазона Неа1Т|те). Следует помнить, что потоки кеа1Т|те не имеют квантования по времени. Это означает, что процессор перестанет заниматься данным потоком только тогда, когда поток добровольно перейдет в состояние ожидания или его не "перебьет" поток более высокого приоритета. Таким образом, здесь драйвер не может рассчитывать на обычный для пользовательского режима циклический подход в планировании заданий.
Системные рабочие потоки Для нерегулярных коротких операций на уровне 1К.С21_, равном РА551\/Е_1_Е\/Е1_, использование полноценных потоков, которые создаются и тут же завершаются, вряд ли будет эффективным. Альтернативой этому может быть создание системных рабочих потоков, зузЪет ул/огкег 1Нгеас1з. Для того чтобы проделать какую-нибудь несложную и не очень продолжительную работу, драйвер должен выделить память под структуру типа МОкК_С211Е11Е_ГГЕМ, затем инициализировать ее вызовом ЕхХтНаНхеУУогкКет, связав с ней собственную функцию (саПЬаск-функцию), и поместить ее в очередь объектов МОкК_С211Е11Е_ГГЕМ вызовом Ех<2иеиеУУогк11ет. Приоритет, на котором будет работать код вызываемой саПЬаск-функции, зависит от второго параметра вызова ЕхСЗиеиеУУогкНет, (^иеиеТуре, то есть от того, в какую очередь помещен данный объект У\/ОК.К_С2иЕиЕ_1ТЕМ, например, Ое1ауес1УУогк(2иеие. В конце своей работы вызванная саНЬаск-функция должна освободить память, занятую под объектом типа \Л/ОК.К_С2иЕ11Е_1ТЕМ (указатель на него поступает в саПЬаск-функцию при вызове). Перечисленные функции ЕхХпШаНхеУУогкКет и ЕхриеиеУУогкИет считаются устаревшими (предлагаемые теперь функции будут рассмотрены ниже), однако они были удобны тем, что позволяли использовать в качестве объекта- посредника структуры данных большего размера, например, при следующем техническом приеме. Описываем структуру: Ьуре<5е^ зЬгисЬ _МУ_ЭДОКК_1ТЕМ { МОКК_<2ОЕОЕ_1ТЕМ 1Ъеш; сИаг АскИЫопа1Ва1:а [ 64 ] ; } МУ_МОКК_1ТЕМ, *РМУ_ЖЖК_1ТЕМ; Данная структура создается и удаляется драйвером, что позволяет ей иметь нестандартную длину — главное, что начальный блок используется обычным для системы способом (как для \Л/ОНК_011Е11Е_1ТЕМ).
Таблица 10.3. Прототип вызова 1оА11оса1е1МогкНет Р1О_УУОКК1ТЕМ 1оА11оса1еУУогк11ет Параметры 1Ы Р0ЕУ1СЕ_0ВЗЕСТ рОеуОЬ]ес1 1ВО1_< = О15РАТСН_ЬЕУЕЬ Создает объект рабочего потока Объект устройства инициатора вызова Возвращаемое значение Указатель на созданный объект или 1\11Л_1_ в случае неудачи Новые (поддерживаются в \Л/1Пс1о\л/5 Ме/2000/ХР/2003) предлагаемые вызовы 1оА11оса1еУ7огк11ет, 1о(2иеиеУУогк1(ет и ХоЕгееУУогкНет перераспределили обязанности. Теперь вызов 1оА11оса1еУУогк11ет (таблица 10.3) создает структуры типа Ю_\Л/ОНК1ТЕМ (разумеется, только размером 512еоГ(Ю_\Л/ОК.К1ТЕМ)), которые "записываются" за соответствующим объектом устройства. Объект Ю_\Л/ОКК1ТЕМ инициализируется вызовом 1о(2иеиеУУогк1(ет, который связывает с ним саНЬаск- процедуру драйвера и передаваемый при ее вызове контекстный аргумент. Вызов Хо<2иеие№огкХ<:ет также помещает объект Ю_\/\/ОНК1ТЕМ в очередь объектов, тип которой определяется значением третьего параметра, то есть С^иеиеТуре. В конце работы саНЬаск-процедура драйвера должна выполнить освобождение созданного объекта Ю_\Л/ОНК1ТЕМ вызовом ХоРгееУУогкНет. Таблица 10.4. Прототип вызова 1о(}иеие]Л/огкПет УОЮ 1о(2иеие№огкПет 1КОЬ< = О15РАТСН_ЬЕУЕЬ Инициализирует объект рабочего потока и Параметры помещает его в очередь (обычно используется сразу после вызова 1оА11оса1еУУогкПет) 1Ы РЮ_\Л/ОКК1ТЕМ рУУогкНет 1Ы РЮ_\Л/ОНК1ТЕМ_РЮ1ПТМЕ рУУогкКоиИпе 1Ы \Л/ОНК_011Е1)Е_ТУРЕ (^иеиеТуре Объект рабочего потока, созданный вызовом 1оАНоса1е\Л/огкНет СаНЬаск-процедура, предоставляемая драйвером (ее прототип описан в таблице 10.5) Тип очереди. Драйвер должен предоставить одно из значений:
1Ы РУОЮ рСоп1ех1 • СпПсаНЛ/огкриеие • Ое1ауес11/Уогк(2иеие Контекстный аргумент. Его получит саИЬаск-функция при вызове Возвращаемое значение уо!с1 В том случае, если параметр (^иеиеТуре при вызове будет равен Сп'Нса1Шогк(}иеие, то объект Ю_\Л/ОК.К1ТЕМ будет помещен в очередь для объектов с приоритетом в диапазоне К.еа1Т|те (таблица 6.2), при значении Ое1ауес1УУогк(2иеие — в очередь объектов с приоритетом 1\1огта1. Следует помнить, что число объектов Ю_У\/ОР.К1ТЕМ, которые операционная система позволяет получить каждому объекту устройства (соответственно, разместить в своих очередях) не бесконечно, поэтому следует проверять результат вызова 1оА11оса1еУ\/огк11ет на равенство 1\1111_1_. Кроме того, не рекомендуется надолго задерживаться в саНЬаск-функции, поскольку это может затормозить извлечение из соответствующей очереди других Ю_\Л/СЖК1ТЕМ объектов, принадлежащих другим драйверам. В частности, не рекомендуется из таких потоков обращаться к другим драйверам вызовом ХоСаНОгшег. Для выполнения продолжительных операций рекомендуется использовать полноценные системные программные потоки, создаваемые вызовом РбСгеа1е5у81етТНгеас1. О Термин 'системные рабочие потоки' (зуз1ет шогкег 111геас1з) нельзя считать удачным, потому что он в точности копирует термин АР1 пользовательского режима, обозначающий программные потоки пользовательского режима, которые уже никакими временными ограничениями не стеснены. Кроме того, лексическое отличие от "нормальных" системных программных потоков, с описания которых началась данная глава, просто неуловимо. Таблица 10.5. Прототип саНЬаск-функции рабочего потока УОЮ шогкСаНЬаск 1кОЬ == РА551УЕ_ЬЕУЕЬ Параметры Функция, предоставляемая драйвером, которая будет вызвана при извлечении из очереди объекта 1О_\Л/ОКК1ТЕМ (вызывается в контексте, как для системного программного потока, см. выше) 1Ы Р0ЕУ1СЕ_0ВЗЕСТ рОеуОЬ]ес1 Объект устройства, которому принадлежит извлеченный из очереди объект Ю_\Л/ОР.К1ТЕМ
1Ы РУОЮ рСопСех! Контекстный аргумент — для получения дополнительной информации, "запланированной" при вызове 1о(2иеие№огкПет (например, указатель на 1КР пакет и т.п.) Возвращаемое значение УОИ Вызываемая саНЬаск-функция должна освобождать Ю_\Л/ОкК1ТЕМ объект вызовом ТоРгееУУогкПет, таблица 10.6. Таблица 10.6. Прототип вызова 1оРгееУУогкПет Х/ОЮ 1оЕгее\Л/огкПет 1В01_< = О18РАТСН_1_ЕУЕ1_ Параметры Удаляет объект рабочего потока РЮ_\Л/ОКК1ТЕМ рУУогкНет Объект рабочего потока, созданный вызовом ранее 1оА11оса1еУУогк11ет Возвращаемое значение УО1С1 © Драйвер не должен делать какие-либо предположения о внутренней организации объектов Ю_ \/\/ОРК1ТЕМ и изменять данные внутри. Для работы с этими объектами следует использовать только описанные выше вызовы.
ГРгеу1ои8~| ГИехМ
Синхронизация в режиме ядра Синхронизационные функции и примитивы позволяют организовать временные задержки в исполнении программных потоков, которые бывают нужны либо одному отдельно взятому потоку, либо для согласования действий нескольких потоков. Разумеется, следует помнить, что при использовании приемов синхронизации, ориентированных на несколько программных потоков, действие этих приемов распространяется только на тех участников, которые признают эти правила. Никакой код операционной системы не останавливает программный поток по указке кого-то третьего. Иными словами, регулировщик движения (объект синхронизации) действует только на тех участников движения (программные потоки), которые признают его право это движение регулировать. Состояние объекта синхронизации является своего рода жестом регулировщика, который плохие участники могут игнорировать, конечно же, внося угрозу неправильной работы всей системы.
ГРгеу|ои51 [Мех!],
Интервалы ожидания для отдельного потока Достаточно часто встречаются ситуации, когда отдельно взятый поток вынужден откладывать продолжение своей работы на более поздний срок. Например, поток, занимающийся периодическим опросом устройства (если с этим устройством невозможно работать через механизм прерываний), должен задерживать свою работу каким-нибудь способом, более приемлемым, нежели цикл Гог с настраиваемым числом проходов. Поток, который желает приостановить свою работу на время до 50 мкс, может использовать вызов КеЗСаНЕхесиМопРгосеззог. Таблица 10.7. Прототип вызова Ке5(а11ЕхесиНопРгосеззог У/ОЮ Ке51а11Ехеси11ОпРгосе55Ог == любой _ Останавливает работу на указанный интервал, независимо от Параметры г г производительности процессора 1Ы 1Л_О1\1С 1п1еп/а1Соип1 Время задержки в 1 мкс интервалах Возвращаемое значение уо!с1 В случае, если устройство должно опрашиваться быстро, но все-таки с интервалом более чем 50 мкс, драйвер должен использовать несколько программных потоков, о чем речь пойдёт далее. Более сложным является вызов КеОе1ауЕхеси11опТ11геас1 (таблица 10.8). Он удаляет программный поток из очереди "геаду Го гип", следовательно, не мешает выполнению других потоков, готовых к работе. Минимальный временной интервал определяемой им задержки составляет 100 нс. Рекомендуемые для драйверов значения \А/а1ГМос1е=Кегле/МосГе и А1егГаЫе=Л11.55 ограничивают применимость вызова КеОе1ауЕхеси11опТ11геас1 кодом системных потоков, созданных самим драйвером, и кодом процедур инициализации и завершения работы драйвера (то есть работающего заведомо вне пользовательского контекста). Таблица 10.8. Прототип вызова КеОе/ауЕхесиНопТНгеаб ГЧТ5ТАТ115 КеОе1ауЕхеси1|ОпТЬгеас1 ХИрЬ == РА551УЕ_ЬЕУЕЬ _ Останавливает работу на указанный интервал, независимо от Параметры г г производительности процессора 1Ы КРкОСЕ55СЖ_МСЮЕ \А/аИМос1е Для драйверов: Кегпе1Мос1е 1Ы ВООЬЕАЫ А1ег1аЫе Для драйверов: ГАЬЗЕ 1Ы РЬАкСЕ_11\1ТЕСЕК. Т|те1п1еп/а1 Время задержки в 100нс интервалах Возвращаемое значение 5ТАТи5_5иССЕ55 — ожидание завершено Значение Т|те1пГеп/а1 может описывать как относительные, так и абсолютные временные интервалы. Для задания абсолютных интервалов следует использовать вызов Ке<2иегу5у91етТ!те (см. таблицу 7.45), при помощи которого можно получить текущее системное время — как время начала ожидания. Функции, которые можно использовать для операции над типом данных 1_АкСЕ_11\1ТЕСЕК, перечислены в таблице 7.44. Переходным методом (между методами, описанными выше, и методами с использованием синхронизационных примитивов, рассматриваемых далее) является использование саПЬаск-функции 1о"ПтегкоиГ1пе, которую драйвер регистрирует при помощи вызова 1о1пШа112еТ1тег (имя данной функции, 1оТ|тегкоиГ1пе, здесь
1ЧТ5ТАТ05 1о1т11аН2еТ|тег Параметры 1Ы РОЕУ1СЕ_ОВЗЕСТ рОеуОЬ]ес1 присвоено для определенности, разработчик имеет право называть ее любым приглянувшимся ему именем). "Переходным" этот метод можно считать потому, что объект таймера уже используется в полной мере, но этот объект пока еще скрыт от разработчика драйвера за интерфейсом функций, предоставляемых Диспетчером ввода/вывода. Таблица 10.9. Прототип вызова 1о1пШаНхеТ1тег 1КРЬ == РА551УЕ_ЬЕУЕЬ Выполняет регистрацию саНЬаск-функции 1оТ1тегНои11пе, предоставляемой драйвером Объект устройства инициатора вызова, за которым будет "закреплен" создаваемый данным вызовом объект таймера 1Ы РЮ_Т1МЕК_КОиТ1ЫЕ р1оТ|тегР.оийпе Указатель на регистрируемую саНЬаск-функцию 1оТ|тегР.оийпе 1Ы РХ/ОЮ Соп^ех! Аргумент, передаваемый впоследствии в саНЬаск-функцию р 1оТ|тегкоийпе Возвращаемое значение 5ТАТи5_5иССЕ55 при успешном завершении В результате вызова 1о1пШаН2еТ|тег (таблица 10.9) операционная система создает таймерный объект режима ядра и связывает его с объектом устройства и саПЬаск- функцией 1оТ1тегКоийпе, предоставляемой драйвером. Регистрацию функции 1оТ|тегРоиИпе лучше всего выполнять сразу после создания объекта устройства в процедуре Ас1с1ОеУ1се или ОпуегЕп1гу (для не-УУОМ драйверов). Поскольку функция 1оТ|тегРоиИпе при вызове будет получать указатель на объект устройства (из которого можно легко определить местоположение структуры расширения объекта устройства), то необходимые контекстные параметры можно разместить и в расширении устройства, в частности счетчик вызовов. Подробнее вопросы подсчета односекундных интервалов будут обсуждены ниже. Таблица 10.10. Прототип функции обратного вызова ТоТ/тегКоиПпе УОЮ 1оТ!тегПоиЦпе ТК<21_ = = О15РАТСН_ЬЕУЕЬ Параметры СаНЬаск-функция, вызываемая через 1 сек. интервал 1Ы Р0Е\/1СЕ_0ВЗЕСТ рОеу|сеОЬ]ес1 Указатель на объект устройства, с которым соотнесена данная функция 1Ы Р\/0Ю рСоп^ех! Контекстный аргумент Возвращаемое значение уо!с1 Собственно создание функции 1оТ|тегКоийпе и ее регистрация при помощи вызова 1о1тНаН2еТ!тег еще не приводят к работе таймера и периодическим вызовам 1оТ|тегКоиИпе. Если внимательно присмотреться к структуре ЭЕ\/1СЕ_ОВ1ЕСТ, то несложно заметить, что поле "Р1О_Т1МЕк Т1тег" в этой структуре единственное. Это недвусмысленно подразумевает, что более одной функций 1оТ1тегРоиЬпе для данного устройства использовать просто невозможно, хотя ничто не запрещает использовать одну саНЬаск-функцию 1оТ1тегРоиЬпе с несколькими объектами устройств. Для запуска таймера, ассоциированного с саПЬаск-функцией 1оТ1тегкоиЬпе, используется вызов 1о51агПЧтег. Останавливается таймер вызовом 1о51орТ1тег. Таблица 10.11. Прототип функции обратного вызова 1о51аг{Т1тег УОЮ 1о51агГПтег 1В<21-< = О15РАТСН_1_ЕУЕ1_ Параметры Запуск таймера, в результате чего саНЬаск-функция 1оТ|тегВоиИпе, соотнесенная с данным объектом устройства
1Ы РОЕУ1СЕ_ОВЗЕСТ рОеУ1сеОЬ]ес1 будет вызываться каждую секунду Указатель на объект устройства, с которым соотнесен таймер, который следует запустить Возвращаемое значение УО1С1 Таблица 10.12. Прототип функции обратного вызова 1о51орТ1тег У/ОЮ 1о51орТ|Гпег 1^Ь< = О15РАТСН_ЬЕУЕЬ Параметры Остановка таймера 1Ы РОЕУ1СЕ_ОВЗЕСТ рОеУ1сеОЬ]ес1 Указатель на объект устройства, с которым соотнесен таймер, который следует остановить Возвращаемое значение УО1С1 Выполнение вызова 1о51ор"Птег из функции 1оТ1тегКоийпе не допускается. Для организации ожидания, более длительного, чем 1 секунда, можно организовать счетчик (например, внутри структуры расширения устройства), значение которого можно уменьшать (увеличивать) на единицу в самой функции 1оТ1тегКои11пе. Работа с использованием саИЬаск-функции 1оТ|тегРоиИпе может протекать следующим образом. В процедуре Ас1с1Оеу1се (ОпуегЕп1гу) выполняется вызов 1о1пШа1|2еТ|тег и связываются таймерная функция 1оТ|тегРоиИпе и конкретный объект устройства. В момент, когда вызывается драйверная рабочая процедура, предназначенная для обслуживания 1РР_МЗ_СРЕАТЕ (то есть вызова \ЛЛп АР1 Сгеа1еЕПе), выполняется вызов 1о51агГПтег. Все время, пока дескриптор доступа к драйверу (устройству) остается открытым, производятся вызовы функции 1о"ПтегКои1:1пе. Каждый раз, когда производится вызов саИЬаск-процедуры 1оТ|тегРоиИпе, значение счетчика таймерных "тиков" (если он задан) уменьшается (увеличивается) на единицу. Когда пользовательская программа делает вызов ОозеНапсИе, рабочая процедура драйвера, обслуживающая 1КР_МЗ_С1_О5Е, должна вызвать 1о51орТ!тег, который прекратит вызовы функции 1оТ1тегКоийпе по сигналам таймера. Разумеется, практически использовать собственно код саИЬаск-функции 1оТ1тегРои11пе можно весьма ограниченно, поскольку она стоит в стороне от "главных дорог" драйверных потоков. Как правило, при работе с этой функцией привлекаются еще ЭРС процедуры и/или другие синхронизационные примитивы (например, объекты события). Рассмотрим несложный частный случай. Предположим, существует устройство, которое генерирует прерывание всякий раз, когда оно готово принять очередную порцию данных. Дополняем структуру расширения объекта устройства счетчиком времени, оставшегося до наступления таймаута (превышения времени ожидания) с момента последнего прерывания, поступившего от обслуживаемого устройства: Ьурейе^ з'Ьгис'Ь { ЬОМС Кетатптпд; // сколько еще осталось секунд
} МУЭЕУ1СЕ_ЕХТЕ№ЮМ, *РМУЕЕУ1СЕ_ЕХТЕ№10М; В процедуре Ас1с1Оеу1се инициализируем таймер, связанный с данным объектом устройства. Значение счетчика не устанавливаем до момента реального запуска таймера. МТ5ТАТЕ5 Ас1с^^еV^се ( РОК1УЕК_ОВ^СТ рОг^егОЬдесЕ, РЕЕУ1СЕ_ОВ^СТ р^еV^сеОЬ^ есЕ ) { РОЕУ1СЕ_ЕХТЕЫ31ОЫ рОе'У'ЕхЕ = (РОЕУ1СЕ_ЕХТЕЫ31ОЫ) р^еV^сеОЬ^есЕ ->^еV^сеЕxЕепз^оп; 1о1п1Е1а11хеТ1тег(р^еV^сеОЬ^есЕ, Му1оТ1тегРоиЕ1пе, р^еVЕxЕ); } Рабочая процедура драйвера Сгеа^еКециезШапсПег вызывается, когда в пользовательском приложении была попытка доступа к устройству через \ЛЛп АР1 вызов СгеаЕеЕНе. В этот момент вполне можно запустить таймер. Он продолжает отсчеты до тех пор, пока дескриптор доступа к устройству из пользовательского приложения остается открытым. Поскольку таймер работает, а его отсчеты нам еще не нужны, то необходимо инициализировать таймер таким значением, которое показывало бы, что его можно игнорировать (это будет -1). МТ5ТАТВ5 СгеаЕеКедиезЕНапс11ег ( ТО РЭЕУ1СЕ_ОВ^СТ р^еVОЬ^, Р1КР р!гр ) { РОЕУ1СЕ_ЕХТЕЫ31ОЫ рОе'У'ЕхЕ = (РОЕУ1СЕ_ЕХТЕЫ31ОЫ) р^еVОЬ^->^еV^сеЕxЕепз^оп; // ближе к концу инициализируем и запускаем таймер р^еVЕxЕ->В.ета^п^пд = -1; ТоЗЕагЕТттег(р^еVОЬ^); } Всякий раз, когда запускается обработка нового 1РР пакета, а драйвер ожидает разрешающие сигналы прерывания от физического устройства, необходимо производить подсчет "тиков" таймера для того, чтобы можно было определенно сказать, не превышено ли время ожидания очередного сигнала. В счетчике необходимо выставить число секунд, которое драйвер может считать, что допустимая длительность ожидания не превышена. Доступ к счетчику из разных ветвей кода драйвера должен быть синхронизирован во избежание порчи его значения, поскольку и процедура обработки прерываний и саИЬаск-функция, вызываемая по каждому отсчету таймера, работающие как части обработки прерываний, будут обращаться к этому счетчику. Использование системного вызова 1п1ег1оске<1Ехс11апде обеспечивает безопасное обновление и считывание 32-разрядного счетчика срабатываний таймера, который
был ранее размещен в полностью определяемой разработчиком структуре расширения объекта устройства. #с!еЕ1пе МУ_1ЫТЕК1ШРТ_Т1МЕОСТ (10) УОШ З'Ьаг'Ыо ( 1Ы РОЕУ1СЕ_ОВ^СТ рРечОЬ], 1Ы Р1Р.Р р!гр ) { РБЕУ1СЕ_ЕХТЕ№ЮИ рОечЕхб = (РБЕУ1СЕ_ЕХТЕ№1ОМ) рБечОЬ) -ЖечтсеЕхбепзтоп; Тп'ЬегТоскейЕхсИапде (&рВечЕх'Ь-Жета1п1пд, МУ_1НТЕВВПРТ_Т1МЕОПТ // Старт устройства: МуТгапзт1ЖакаРоик1пе (рБечОЬ) , р1гр) ; } Перед физическим стартом устройства необходимо инициализировать счетчик срабатываний таймера. Значение МУ_1МТЕкк1)РТ_Т1МЕО11Т следует выбирать из тех соображений, что устройство может использоваться впервые в данном сеансе либо имело длительный простой перед данным вызовом. При выборе этого значения необходимо учесть все виды внутренних задержек в устройстве (прогрев, самодиагностику и т.п.). Процедура 15К по прибытии ожидаемого прерывания устанавливает соответствующее значение счетчика секунд ожидания. В случае, если нет работы (все операции ввода/ вывода завершены), логично установить значение счетчика -1. ВООЬЕАИ ОпТп'Ьеггир'Ь ( 1Ы РК1ЫТЕВВЕРТ рТибеггирбОк^ есЬ, 1Ы РУО1В рСопбехб ) { РБЕУ1СЕ_ЕХТЕ№ЮИ рОеа1сеЕхб = (РБЕУ1СЕ_ЕХТЕ№1ОМ) рСопбехб; // В случае, если остались еще данные для передачи, то // обновить счетчик 1Е( 1НачеТгапзт1РВубез( рБеаРсеЕхУ ) ) 1пбег1оскес1Ехс11апде ( &рВеч1сеЕхк-Жета1п1пд, МУ_1ЫТЕВВПРТ_Т1МЕОЕТ ) ; е1зе // иначе - очистить счетчик 1пбег1оскес1Ехс11апде ( &рВечЕхк-Жеша1п1пд, -1 ) ; } Наконец, саНЬаск-функция Му1оТ1тегРоиЬпе, которая вызывается всякий раз по срабатыванию таймера (каждую секунду), как только он запущен. В том случае, если данная процедура установила, что время ожидания активности устройства истекло, то она посредством процедуры ОрсЕогТзг завершает работу над текущим 1КР пакетом, объявляя его невыполненным.
УОШ Му1оТ1шегКоиЫпе ( 1И РЭЕ71СЕ_ОВДЕСТ р^еV^сеОЬ^ , ТО РУО1Р рСопкехк ) { РОЕУ1СЕ_ЕХТЕ№1ОЫ р^еVЕxР = (Р0Е71СЕ_ЕХТЕЫ310И) рСогЛехР; // Проверить время ожидания 1Г ( (р^еVЕxР-Жета^п^пд,-1) < 0 ) гекигп; // значение счетчика не важно (поскольку -1) Ы ( 1п'Ьег1оскесЮесгешеп'Ь (&р^еVЕxР->Рета^п^пд) == 0 ) { // Время ожидания истекло 1пРег1оскес1ЕхсЬапде ( &р^еVЕxР->Кета^п^пд, (-1) ); Р1Р.Р рСиггеп'Ыгр = р^еV^сеОЬ^->Сиггеп'Ыгр; рСиггепЫгр->1оЗ'Ьа'Ьиз . Зкакиз = ЗТАТИЗ_1О_Т1МЕОПТ; рСиггеп'Ыгр->1оЗ'какиз . РпГогшактоп = 0; 1оКедиезкВрс( р^еV^сеОЬ^, рСиггеп'Ыгр, РГОЬЬ ) // Некоторые делают совсем "просто": // МуВрсЕогТзг (ЬШЬЬ, р^еV^сеОЬ^, рСиггеп'Ыгр, р^еVЕxЬ } гебигп; } Существует маленький временной зазор между тем, как функция Му1оТ1тегКои11пе убедилась, что счетчик активен, и моментом, когда произошло его уменьшение на единицу. Если предположить, что в этот момент "вклинилась" процедура Оп1п1:еггир11 и установила значение счетчика в -1, то функция Му1оТ1тегКои11пе, получив управление, сделает значение счетчика равным -2. Код, приведенный выше, учитывает эту возможность, сравнивая кета1п1пд с нулем. Зарегистрированная соответствующим образом процедура ОрсЕогТзг может выглядеть следующим образом: 7010 МуОрсГоЫзг ( 1И РКОРС рОрсОЬд , 1Ы РОЕУТСЕ_ОВПЕСТ р^еV^сеОЬ^ , ТЫ Р1Р.Р р!гр, 1Ы РУОЮ рСопбехб ) { // Инициируем поступление 1ВР из внутренней очереди в // процедуру ЗбагЫО (): ТойоЗРагбИехкРаскеб (&р^еVЕxкепз^оп->с^^Кеас1И^^ке, р^еVОЬ^еск) ; // Даем возможность отработать процедурам завершения всех // вышестоящих драйверов, если они есть: 1оСошр1екеКедиезк(р1гр, 1О_ИО_1ИСКЕМЕИТ);
ГРгеу|ои51 [Мех!],
Разделение времени и данных с 15К процедурой Вместо вызовов 1п1ег1оскес1Ххх, примененных выше для синхронизации доступа к счетчику вызовов Му1оТ1тегКои11пе, можно применить следующий метод, который пригоден всегда, когда нужно гарантировать, что в некую работу над некими данными не вмешается неожиданно процедура обработки прерываний, работающая, как правило, с максимальным для конкретного драйвера приоритетом. Для выполнения такой работы (например, модификации совместно используемых данных из низкоприоритетной процедуры) создается обособленная функция ТзгКоиИпеСопсиггеп! по прототипу, описанному в таблице 10.13. Таблица 10.13. Прототип функции ТзгЯоиНпеСопсиггеп! ВООЬЕАИ 15гКои11пеСопсиггеп1 1Пр1_ == см. ниже Параметры Манипуляции на уровне 1Вр1_ прерывания 1Ы Р\/0Ю рСоп!ех1 Контекстный указатель о ТИПЕ — в случае успешного завершения (с точки зрения разработчика Возвращаемое значение ~ . 7 _.7 _ г драйвера) или ЕАЬЗЕ При необходимости выполнить некоторую работу, которая не может быть прервана функцией обработки прерывания, следует выполнить вызов КеЗупсНгогнхеЕхесиНоп, см. таблицу 10.14. Вызов КеЗупсНготзеЕхесиНоп повышает уровень 1ЕЩ1_до значения 5упсКгоп12е1гц1, указанного при создании объекта прерывания р1п1еггирЮЬ] системным вызовом 1оСоппес11п1еггир1, см. таблицу 8.10, в результате чего с данным объектом прерывания оказалась связана 15к процедура драйвера. Кроме того, данный вызов получает доступ к объекту спин-блокировки, связанному с данным объектом прерывания. В результате доступ к данным по контекстному указателю рСоп^ех! становится безопасным в том смысле, что другие низкоприоритетные процедуры драйвера просто не могут работать в это время, так же, как не может стартовать и процедура обработки прерывания (если, разумеется, значения 1гц1 и 5упсКгоп12е1гц1 равны, таблица 8.10). В том случае, если 1гц1 превышает 5упс11гоп12е1гц1, то доступ по указателю рСоп1ех1 из функции ТзгкоийпеСопсиггеп! остается безопасным по причине владения упомянутым объектом спин-блокировки. Таблица 10.14. Прототип вызова КеЗупсЬготгеЕхесиПоп ВООЬЕАИ КеЗупсНготнеЕхесийоп 1Вр1_ <= 1Вр1_ прерывания Параметры СаНЬаск-функция, вызываемая через 1 сек. интервал 1Ы РКИЧТЕкЕШРТ рТпГеггирЮЬ] Указатель на объект прерывания, с 15Р процедурой которого и должна конкурировать функция ТзгРоийпеСопсиггеп! 1Ы РК57ЫСНР01\117Е_Р0иТ1ЫЕ ТзгРои^пеСопсиггеп! Функция ТзгРоийпеСопсиггеп! (таблица 10.14), которая получит управления в результате данного системного вызова КеВупсНготгеЕхесийоп 1Ы Р\/0Ю рСоп1ех! Контекстный аргумент, который получит функция ТзгКои^пеСопсиггеп! при вызове Возвращаемое значение ТИПЕ — если вызов 15гКои11пеСопсиггеп1 успешен Инициатор вызова КеЗупсЬготгеЕхесиНоп должен работать на уровне 1ЕЩ1_ не выше 5упс11гоп12е1гц1 (см. таблицу 8.10) того объекта прерывания, с чьей 15К процедурой должна будет "конкурировать" функция ТзгКоиНпеСопсиггеп!.
Теперь функцию Му1оТ!тегКоийпе, приведенную ранее, можно переписать следующим образом (предполагая, что указатель на объект прерывания был своевременно сохранен в структуре расширения объекта устройства): ВООЬЕАП МуТзгЕоиЕйпеСопсиггепЕЕоиЕйпе ( РБЕУ1СЕ_ОВТЕСТ р^еV^сеОЬ^ ) ; УОЮ МуТоТйтегЕоиЕйпе( ТО РБЕУ1СЕ_ОВТЕСТ р^еV^сеОЬ^, РУСЮ рСопЕехЕ ) { РВЕУ1СЕ_ЕХТЕП8ЮП р^еVЕxЕ = (РВЕУ1СЕ_ЕХТЕП81ОП) рСопЕехЕ; // предоставляем возможность поработать процедуре // МуТзгЕоиЕйпеСопсиггепЕВоиЕйпе КеВупсЬгопйгеЕхесиЕйоп (р^еVЕxЕ->IпЕе^^ирЕОЬ^ есЕ, (РК8ХПСНЕ0Ш2Е_ЕОТТ1ПЕ) МуТзгЕоиЕйпеСопсиггепЕЕоиЕйпе р^еVЕxЕ) ; } ВООЬЕАП МуТзгЕоиЕйпеСопсиггепЕЕоиЕйпе ( РБЕУ1СЕ_ОВТЕСТ р^еV^сеОЬ^ ) ; { РВЕУ1СЕ_ЕХТЕП8ЮП р^еVЕxЕ = (РВЕУ1СЕ_ЕХТЕП81ОП) р^еV^сеОЬ^ ->^еV^сеЕxЕепз^оп; йЕ ( р^еVЕxЕ->Вета^п^пд < 0 | | (--р^еVЕxЕ->Вета^п^пд)1 ) геЕигп ТЕБЕ; Р1ВР рСиггеп'Ыгр = р^еV^сеОЬ^->СиггепЫгр; рСиггепЫгр->1о8Еабиз . 8Шиз = 8ТАТТ8_1О_Т1МЕОТТ; рСиггепЕ1гр->1о8Еабиз . ТпРогтаЫоп = 0; // Планируем вызов БРС процедуры: 1оРедиезЫрс ( р^еV^сеОЬ^, рСиггеп'Ыгр, ПТЬЬ ) геЕигп ТЕТЕ; }
Потоки как объекты синхронизации В качестве синхронизационного примитива может выступать и объект программного потока. Выполняющийся программный поток имеет несигнальное состояние. Состояние становится сигнальным в момент прекращения работы потока, что может сделать только сам поток "естественным" образом, то есть вызовом Р$Тегт1па1е5у$1етТНгеас1. Другой программный поток, каким-либо способом получивший дескриптор нужного для синхронизации потока, может остановить свою работу вызовом КеУУаИЕог5тд1еОЬ]ес1, как показано ниже. РКТННЕАБ рТЬгеабОЬесб; // Предположим, поток уже работает, его дескриптор ЬТЬгеаб. // Получаем указатель на его объект: ПТ8ТАТП8 збабиз = ОЬНеРегепсеОЬ^есбВуНапсИе( ЬТЬгеаб, ТННЕАЕ_АЬЬ_АССЕ 8 8, ППЬЬ, Кегпе1Мобе, (РУО1Б *)& рТЬгеабОЬ^есб, ППЬЬ); гР( !ПТ_8ПССЕ88(збабиз) ) { // Действия по обработке ошибки. Может быть поток уже заверше } // Ожидаем окончания потока ЬТЬгеаб збабиз = КеЭДа±ЕЕог8±пд1еОЬ^есб( (РУО1Б) рТЬгеабОЬ^есб, 8изрепбес1, Кегпе1Мобе, ЕАЬ8Е, (РЬАНСЕ_1ПТЕСЕН) ЬШЬЬ); // Поток завершился. // Даем системе возможность удалить объект потока ОЬБегеРегепсеОЬ^есб(рТЬгеабОЬ^есб);
Исполнительские ресурсы Еще одним объектом, служащим для целей синхронизации, который весьма похож на мьютекс режима ядра, является так называемый исполнительский ресурс (ехесиИуе гезоигсе). Такой объект может находиться в исключительном владении одного потока, либо используется совместно несколькими потоками только для операций чтения. Объекты исполнительских ресурсов обеспечивают лучшую производительность, чем стандартные мьютексы ядра. Исполнительский ресурс является объектом типа ЕкЕ5О11кСЕ (с закрытыми для разработчика полями — в том смысле, что он не должен их использовать непосредственно из своего кода) и применяется для синхронизации доступа к одному или нескольким элементам данных. Любой код, прикасающийся к этим данным, должен сначала сделать запрос на владение соответствующим объектом ЕРЕ500РСЕ. Если открыть определение структуры ЕРЕ5ОЦ1РСЕ в файле, например, уус!т.11, то несложно понять, что исключительный доступ к данным, охраняемым объектом типа ЕРЕ5О11РСЕ, реализуется через механизм спин-блокировок. Для работы с исполнительскими ресурсами используются вызовы, описанные в таблице 10.47. Как и быстрые мьютексы, эти объекты имеют собственные вызовы для запроса на владение, а не вызовы КеУУаКРогХхх. Разумеется, перед получением доступа следует выделить память под структуру ЕРЕ500РСЕ в нестраничной памяти и инициализировать ее при помощи вызова Ех1т11аН2еПе5оигсе1Ле. Таблица 10.47. Функции для работы с исполнительскими ресурсами Действие Используемый вызов Создание Ех1тНаН2е1ге5оигсе1Ле Запрос на владение ЕхАсяииеВе5оигсеЕхс1и5^е1Ле ЕхАсдииеВезоигсеЗНагесИЛе ЕхТгуТоАсяииеВе5оигсеЕхс1и512е1_|1е ЕхСо1™ег1Ехс1и512еТо5Нагес11Ле ЕхАсяи1ге5Нагес151ап/еЕхс1и5^е ЕхАсяшге§Нагес1УУаИГогЕхс1и5ше Запрос состояния Ех15Не5оигсеАсяи1гес1Ехс1и5ше1Ле Ех15Ве5оигсеАсди!гес15Нагес11Ле Освобождение ЕхКе1еа5еКе5оигсеЕогТНгеас1Ь|1е Удаление ЕхОе1е1еВе5оигсе1Ле Запросы на владение можно выполнять из кода, работающего на уровне 1Р.01. ниже О15РАТСН_1_Е\/Е1_, все остальные вызовы можно делать и из кода работающего собственно на этом уровне. Ре-инициализация исполнительского ресурса может быть выполнена вызовом ЕхПе1т11а1|2еПе5оигсе1Ле, который заменяет сразу три вызова (по удалению ресурса, выделению памяти под новую структуру и инициализации) и экономит память.
Группа функций (Ех)1п1ег1оскес1Ххх В том случае, если разработчика драйвера устраивает то, что размер охраняемых данных составит размер 51неоГ(1_01\1С) или 51неоГ(Р\/010), то тогда в его распоряжении оказывается набор вызовов 1п1ег1оскес1Ххх, например, 1п1ег1оске<1Ехс11апде. Эти вызовы реализуют доступ к переменной типа 1_О1МС и некоторые операции над ней в эксклюзивном (атомарном) режиме, например, операции увеличения и уменьшения на единицу, сравнения и т.п., хотя многие из них не документированы в СОК. Операции безопасного доступа и сравнения имеются и для указателей. Функции 1п1ег1оскес1Ххх могут вызываться из программного кода, работающего на любом уровне 1к<21_, а охраняемая переменная может размещаться в страничной памяти, разумеется, только если это позволяет сделать 1Р.р1_ самого программного кода, делающего вызов 1п1ег1оске<1Ххх. Похожие действия выполняет набор функций Ех1п1ег1оскес1Ххх, который позволяет также безопасно работать и со списками. Вызовы 1п1ег1оскес1Ххх являются более быстрыми, если сравнивать их с функционально соответствующими вызовами Ех1п1ег1оскес1Ххх.
Изменение приоритетов как средство синхронизации Как было сказано выше при описании функции КеАсяшге5рт1.оск, программный поток, имеющий более высокий приоритет (уровень 1к<21_) может обращаться к данным, разделяемым с другими потоками, работающими на этом же процессоре, если достоверно известно, что в данный конкретный момент времени их приоритет ниже. Временное повышение уровня 1ЕЩ1_ (вызовом КеЯа15е1гя1) данного конкретного потока может применяться как средство синхронизации или обеспечения непрерывности выполнения кода по отношению к схожим по характеристикам потокам данного драйвера.
ОРС процедуры как средство синхронизации Процедуры ОРС, точнее, объекты, с ними ассоциированные, могут размещаться в очереди ОРС объектов только в единичном экземпляре. Если ОРС объект находится в очереди, то следующему запросу на размещении там ОРС объекта (соответственно, и отложенного вызова ОРС процедуры) будет отказано. Таким образом, в многопроцессорных архитектурах ОРС процедура может безопасно обращаться к данным, если доступ к ним производится только из этой ОРС процедуры драйвера (ассоциированной с данным ОРС объектом), не опасаясь вмешательства кода драйвера, возможно, работающего на другом процессоре. Правда, такого типа синхронизация достаточно вычурна, поскольку произвольно запланировать вызов ОРС процедуры можно при помощи вызова КеХпвег^иеиеОрс, а сделать его можно только из кода, работающего на уровне не ниже О15РАТСН_1_Е\/Е1_. Процедура ОРС будет вызвана, когда приоритет снизится до уровня ниже О15РАТСН_1_ЕУЕ1_. Следует также помнить, что к моменту вызова Ке1п5ег1<2иеиеОрс должен существовать инициализированный ОРС объект (см. описание вызова КеХпШаНгеОрс, таблица 10.23), соотнесенный с интересующей ОРС процедурой.
ГРгеу|ои51 [Мех!],
Таймеры и их использование В рассмотренном выше методе организации временных задержек с использованием предоставляемой драйвером саИЬаск-функции 1оТ1тегРои11пе объект таймера, хотя и участвовал, но в скрытой форме. К тому же, во всем обширном наборе примеров, прилагаемых к ЭЭК, этот прием используется всего 2 раза. (Правда, возможно оттого, что столь длительные интервалы ожидания редко требуются в современной компьютерной технике.) Простейшим из синхронизационных примитивов является объект события, еуеп1, который имеет два состояния: сигнальное и несигнальное. Переход в сигнальное состояние стимулирует работу функции Ке\Л/аНХхх, а выполняется он под влиянием вызова Ке5е^ЕVеп^. Подробнее объекты события будут рассмотрены ниже, пока что отметим, что пребывание в сигнальном либо несигнальном состоянии — есть самое основное свойство всех объектов синхронизации. Не составляют исключения и таймеры. Можно сказать, что таймер — это объект события, который самостоятельно переходит в сигнальное состояние по истечении некоторого времени, заданного при запуске таймера. При этом таймер может выполнять дополнительные "услуги", например, планировать запуск ОРС процедуры (специально созданной и зарегистрированной драйвером процедуры отложенного вызова), что будет рассмотрено позже. Системные вызовы для работы с таймерными объектами перечислены в таблице 10.15. Таблица 10.15. Системные вызовы для работы с таймерными объектами Системные вызовы Описание Ке1т11аН2еТ|тег Ке1п!1!аН2еТ!тегЕх Ке5еГПтег Ке5еГПтегЕх КеСапсе1Т|тег Кекеас151а1еТ|тег Инициализация таймерного объекта Установка таймерного объекта в несигнальное состояние (подготовка к срабатыванию) Прекращает работу таймера Возвращает ТНОЕ, если таймер в сигнальном состоянии КеТтйаНгеОрс Инициализирует ОРС объект, подготавливая его для работы с таймерными вызовами Можно сказать, что вызов КеХпШаНгеОрс находится в чужой компании, однако, этот вызов совершенно необходим, если таймер будет работать с ОРС функциями. Прежде чем перейти к подробному рассмотрению системных вызовов, обслуживающих операции над таймерными объектами, разберем сначала вызовы КеУУаИХхх, то есть КеУУа11Еог81пд1еОЬ]ес1 и КеШаНЕогМиШр1еОЬ]ес19. Если есть сигнализирующие объекты (события, мьютексы, семафоры, таймеры и т.п.), то у программных потоков должны быть и специальные средства, которые позволили бы им остановиться и ожидать изменений в состоянии этих объектов. Именно такими средствами, применяемыми программными потоками для остановки и ожидания, являются вызовы КеУУаКЕог5тд1еОЬ]ес1 и КеУУакЕогМи11|р1еОЬ]ес18. Первый из них заставляет дожидаться перехода одного объекта в сигнальное состояние, второй — сразу нескольких. Таблица 10.16. Прототип вызова Ке]Л/аНРог51пд1еОЬ]'ес1
1ЧТ5ТАТ05 КеШаИЕог§тд1еОЬ]ес1 ИНН <= О15РАТСН_1_ЕУЕ1_ Параметры Приостанавливает данный программный поток до момента перехода указанного объекта в сигнальное состояние либо до момента превышения значения "ПтеоиЕ 1Ы РУОЮ рОЬ]есГ 1Ы К\Л/А1Т_Р.ЕА5ОЫ кеазоп 1Ы КРР.ОСЕ55СЖ_МСЮЕ \Л/а11Мос1е 1Ы ВООЬЕАЫ ЬА1ег1аЫе 1Ы РЬАР.СЕ_11\1ТЕСЕР. ЛтеоШ Указатель на инициализированный ранее объект синхронизационного примитива (объект события, объект таймера, объект потока и т.п.) Для драйверов: ЕхесиИуе Для драйверов: Кегпе1Мос1е Для драйверов: ЕАЬЗЕ • Предельное время ожидания, положительное значение — абсолютное время, отрицательное — относительный временной интервал (в 100нс отсчетах) • МПЬЬ при безусловном (неограниченном) ожидании Возвращаемое значение Для драйверов: • 5ТАТи5_5иССЕ55 — ожидание успешно завершено • ЗТАТиЗ_Т1МЕОПТ — превышено предельное время ожидания Вызов КеУ7а11Еог51пд1еОЬ]ес1 на уровне 1Р.С>1_ равном 015РАТСН_1_Е\/Е1_ следует выполнять только при нулевом значении параметра Т1теои1. На практике этот вызов выполняется из программного потока, работающего на уровне 1К<21_, равном РА551УЕ_1_Е7Е1_. "Самоостанов" программного потока вызовом КеУУа11Еог8тд1еОЬ]ес1: в ожидании, пока соответствующий синхронизационный примитив не перейдет в нужное состояние является широко распространенным приемом при синхронизации потоков и даже, для задержки выполнения одного потока при помощи объекта таймера, как это будет показано ниже. С функцией Ке\Л/аПЕогМи11|р1еОЬ]ес15 (таблица 10.17) следует обращаться с осторожностью, поскольку существуют системные ограничения на количество объектов, которые могут быть заданы в качестве влияющих на ожидание. Каждый поток имеет встроенный массив МаИ-блоков, который он использует для действующих совместно "операций ожидания". Поток может использовать этот массив для ожидания переходов состояния более чем ТНКЕАО_У\/А1Т_ОВ1ЕСТ5 объектов. В случае, если число ТНКЕАО_УУА1Т_ОВ1ЕСТ5 недостаточно, драйвер должен предложить собственный массив МаИ-блоков при выполнении вызова КеУУа11ЕогМи11|р1еОЬ]ес1з. В любом случае, число объектов, от которых зависит завершение ожидания, не может превышать МАХ1М1)М_У7А1Т_ОВЗЕСТ5. Таблица 10.17. Прототип вызова Ке]Л/аИРогМиШр1еОЬдес1в МТ5ТАТ113 КеШаИЕогМи11!р1еОЬ]ес15 Параметры 1Ы 1Л_О1\1С СоипГ 1Ы РУОЮ рОЬ]есГз[] 1Ы \А/А1Т_ТУРЕ \Л/аИ:Туре 1Ы К\Л/А1Т_Р.ЕА5ОЫ кеазоп 1Ы КРР.ОСЕ55СЖ_МСЮЕ \Л/аП:Мос1е 1Ы ВООЬЕАЫ ЬА1ег1аЫе 1Ы РЬАР.СЕ_11\1ТЕСЕР. Лтеои! 11и21_ <= О15РАТСН_ЬЕУЕЬ Приостанавливает данный программный поток до момента перехода всех или хотя бы одного из указанных объектов в сигнальное состояние либо до момента превышения значения Т!теои1 Число объектов, по которым определяется момент окончания ожидание Массив указателей на инициализированные объекты • УУаНАП — ожидание все объектов • УУаНАпу — ожидание хотя бы одного Для драйверов: ЕхесиИуе Для драйверов: Кегпе1Мос1е Для драйверов: ЕАЬЗЕ • Предельное время ожидания, положительное значение — абсолютное время, отрицательное — относительный временной интервал (в 100нс
1Ы РК\Л/А1Т_В1_0СК \Л/айВ1оск5[] ОРТЮЫАЬ Возвращаемое значение отсчетах) • 1\11Л_1_ при безусловном (неограниченном) ожидании Массив \Л/ай-блоков для этой операции (можно указать 1\11Л_1_) Для драйверов: • 5ТАТи5_5иССЕ55 — ожидание успешно завершено • 5ТАТи5_Т1МЕОиТ — превышено предельное время ожидания Вызов КеШаНЕогМиШр1еОЬ]ес19 (также как и описанный выше вызов КеМа!1Еог5тд1еОЬ]ес1) на уровне 1Н<31_ равном О15РАТСН_1_Е\/Е1_ следует выполнять только при нулевом значении параметра Т1теои1. Вернемся к вопросу использования таймера для организации задержек внутри одного программного потока или для разнесения во времени двух точек в разных программных потоках. Прежде всего, следует создать объект таймера. Для этого необходимо выделить область памяти и инициализировать ее вызовами Ке1пШаН2еТ|тег или Ке1пШаН2еТ|тегЕх. Выделенная память должна быть резидентна. Иными словами, ее следует выделять в нестраничном пуле, например, вызовом ЕхА11оса1еРоо1. Х/ОЮ Ке1тНаН2еТ!тег Параметры Таблица 10.18. Прототип вызова Ке1пШаПгеТ1тег 1Н<21. <= О15РАТСН_ЬЕУЕЬ Инициализирует таймер типа 1ЧоПНсаПоп'Птег 1Ы РКТ1МЕР. рТ|тегОЬ] Указатель на место для объекта таймера, заранее подготовленное инициатором вызова Возвращаемое значение УО1С1 Х/ОЮ Ке1тНаН2еТ!тегЕх Параметры Таблица 10.19. Прототип вызова Ке1пШаПгеТ1тегЕх 1Кр1_ <= О15РАТСН_ЬЕУЕЬ Инициализирует таймер с указанием типа 1Ы РКТ1МЕР. рТ|тегОЬ] Указатель на место для объекта таймера, заранее подготовленное инициатором вызова 1Ы Т1МЕк_ТУРЕ Туре • 1УоПЯсаПоп'Птег • ЗупсНготха Поп Т1тег Возвращаемое значение УО1С1 Таймер типа 1УоННсаНопТ1тег запускает выполнение всех потоков, ожидавших его перехода в сигнальное состояние, и остается в сигнальном состоянии до тех пор, пока кто-то не переведет его явным образом (вызовом Ке5е(Т1тегЕх или Ке5е(Т1тег) в несигнальное. Иначе ведут себя таймеры типа ЗупсНгогнхаНогТПтег. По истечении времени ожидания, таймер такого типа переходит в сигнальное состояние и остается в нем, пока не запустится один из ожидающих его программных потоков, после чего таймер автоматически переходит в несигнальное состояние. То есть по истечении интервала ожидания таймер разрешает выполнение одному потоку из числа ожидающих его сигнала. После того как объект таймера создан (это можно сделать в самом начале работы, например, поместив указатель на объект таймера в структуру расширения объекта устройства), следует его запустить, разумеется, в нужном месте программного кода драйвера — в соответствии с логикой работы. Делается это вызовами Ке5е(Т1тег либо КеЗеГПтегЕх.
Когда время Ехр1га!:юпТ|те, заданное в КеЗеГПтег, истекает, объект таймера извлекается из системной очереди таймерных объектов и переходит в сигнальное состояние. Если задана процедура отложенного вызова СизйэгтГПтегОрс, то в этот момент соответствующий ей объект рТ1тегОрсОЬ]ес1: помещается в системную очередь ЭРС объектов (разумеется, если там его еще нет) с тем, чтобы выполнить Си51ютТ|тег0рс в ближайшее время. Ранее окончания ожидания процедура Си51ютТ|тег0рс вызвана быть не может. Для многократного запуска автоматического таймера следует использовать вызов КеЗеГПтегЕх. Таблица 10.20. Прототип вызова Ке5е1Т1тег ВООЬЕАН КеЗеГПтег Параметры 1Ы РКТ1МЕР. рТ|тегОЬ]ес1 1Ы 1_АкСЕ_11\1ТЕСЕР. Ехр1гаГюпТ1Гпе ИНН <= О15РАТСН_ЬЕУЕЬ Инициализирует таймер с указанием типа Указатель на объект таймера, инициализированный вызовами Ке1т1!аН2еТ!тег или Ке1т1!аН2еТ!тегЕх Время ожидания, положительное значение — абсолютное время, отрицательное — относительный временной интервал (в 100нс отсчетах) 1Ы РКОРС рТ|тегОрсОЬ]ес1 ОРТЮЫАЬ Указатель на объект ОРС процедур (прототип см. в таблице) либо Г\1С11_1_ Возвращаемое значение ТРОЕ — если объект таймера находился в системной очереди таймерных объектов в момент вызова Когда время Ехр1га!:юпТ|те, заданное в КеЗеГПтегЕх, истекает, объект таймера извлекается из системной очереди таймерных объектов и переходит в сигнальное состояние. Если существует процедура отложенного вызова Сиз1:от'ПтегОрс, то в этот момент соответствующий ей объект рТ|тегОрсОЬ)ес1: помещается в системную очередь ЭРС объектов (разумеется, если там его еще нет) с тем, чтобы выполнить Си51ютТ|тег0рс в ближайшее время. Ранее времени окончания ожидания Ехр1га11ОпТ|те процедура Си51ют'Птег0рс вызвана быть не может, зато по окончании Ехр1га1юпТ1гпе она вызывается через каждый интервал Репос!, если таковой задан. ВООЬЕАН КеЗеГПтегЕх Параметры 1Ы РКТ1МЕР. рТ|тегОЬ]ес1 Таблица 10.21. Прототип вызова КеЗеГПтегЕх 1Н<21. <= О15РАТСН_ЬЕУЕЬ Инициализирует таймер с указанием типа Указатель на объект таймера, инициализированный вызовами Ке1т11аН2еТ|тег или Ке1т11аН2еТ|тегЕх 1Ы 1_АР.СЕ_11\1ТЕСЕР. Ехр1гаГюпТ|те Время ожидания, положительное значение — абсолютное время, отрицательное — относительный временной интервал (в 100нс отсчетах) 1Ы 1_О1\1С Репос! ОРТЮЫАЬ 1Ы РКОРС рТ|тегОрсОЬ]ес1 ОРТЮЫАЬ Возвращаемое значение Значение периода (в миллисекундах) вызова функции Си51отТ|тегОрс, если она указана. Указатель на объект ОРС процедур (прототип см. в таблице) либо 1\11Л_1_ ТИПЕ — если объект таймера находился в системной очереди таймерных объектов в момент вызова Процедура СиБйэпГПтегОрс может прекратить существование объекта таймера, если КеЗеГПтегЕх указал нулевое значение параметра Репос!. В случае, если вызовы Ке5еГПтег или КеЗеПЧтегЕх сделаны раньше, чем соответствующий объект таймера или СизйэпгГПтегОрс процедуры извлечены из их очередей, то последние отменяются и ожидание стартует заново.
Функция Си51отТ1тегОрс (разумеется, ее имя определяется разработчиком драйвера, данное же используется лишь для определенности при изложении материала) является процедурой отложенного вызова, которая связывается с таймерами. Она запускается (однократно или многократно) не ранее окончания интервала ожидания, выставленного в таймере. Операционная система автоматически организует очередь из объектов, соответствующих ОРС процедурам, ожидающим выполнения. Диспетчер ОРС процедур извлекает данный объект из очереди, и лишь тогда Си51огтГПтегОрс начинает свою работу. Практически всегда имеется некоторая задержка между моментом срабатывания таймера, когда интервал ожидания истек, и стартом Си51отТ|ГпегОрс. Как и все другие ОРС процедуры, Си51опТПтегОрс работает на уровне 1ЕЩ1_ равном 015РАТСН_1_Е\/Е1_. В таблице 10.22 описан прототип ее вызова. Следует обратить внимание на то, что эта процедура получает два зарезервированных параметра, значение которых на настоящий момент еще не определено. Таблица 10.22. Прототип Сиз1отТ1тегОрс Х/ОЮ Сиз1отТ|тегОрс Параметры 1Ы РКОРС рОрс 1Вр1_ == любой Описание □РС объект, инициализировавший вызов 1Ы РХ/ОЮ рСоп^ех! Контекст, указанный в вызове КеТтНаНгеОрс при инициализации данной функции как ОРС процедуры 1Ы РУОЮ 5у81етАгд1 1Ы РУОЮ 5у81етАгд2 Возвращаемое значение Зарезервирован Зарезервирован УО1С1 Работа с процедурой СизйэгтГПтегОрс достаточно проста. Следует выполнить следующие действия: 1. Получить область в нестраничной памяти (возможно, запомнить полученный указатель в структуре расширения объекта устройства) для объекта КОРС, например, при помощи вызова ЕхА11оса1еРоо1. 2. Выполнить, например, во время работы Ас1с10еу|се, вызов КеХшйаНгеОрс (см. описание прототипа в таблице 10.23) для того, чтобы связать с функцией СиБйэгтГПтегОрс передаваемые ей при вызове контекстные указатели (рОрс и рСоп1ех1:). Адрес расширения структуры объекта устройства также является хорошим кандидатом для передачи в вызываемую функцию Си51отТ1тегОрс. Для того чтобы отменить срабатывание активного таймера используется вызов КеСапсеГПтег (прототип описан в таблице 10.24). Этот же вызов может остановить работу "многократного" таймера и, соответственно, вызовы процедуры Си51отТ1тегОрс. Следует обратить внимание, что освобождать память, занятую под объектом таймера или объектом ЭРС следует только после вызова КеСапсе1Т1тег — нарушение этого правила легко приводит к краху системы. Для определения, истекло ли время ожидания таймера, можно использовать вызов КеЯеа<151а1еТ|тег (см. таблицу 10.25). Таблица 10.23. Прототип вызова КеТпШаИхеОрс УОЮ Ке1тНаН2еОрс Параметры 1Ы РКОРС рОрс 1Ы РКО1ЕЕРКЕО_Р.ОиТ1ЫЕ □еГеггес! Ргосес! и ге ИНН == РА551УЕ_ЬЕУЕЬ Описание □РС объект, для которого инициатор данного вызова предоставил область памяти. Указатель на процедуру, которая будет вызываться в момент извлечения объекта ОРС из очереди, в данном случае— Си51отТ|тегОрс
1Ы Р\/ОЮ рСоп^ех! Возвращаемое значение ВООЬЕАИ КеСапсеШтег Параметры 1Ы РКЛМЕкЛтег Контекстный указатель, передаваемый вызываемой ОРС процедуре, в данном случае — Си81отТ|тегОрс УО1С1 Таблица 10.24. Прототип вызова КеСапсеГПтег 1Н<21. <= О15РАТСН_ЬЕУЕЬ Описание Указатель на объект таймера, который следует "отменить". Возвращаемое значение ВООЬЕАИ КеКеас151а1еТ|тег • ТИПЕ — если таймер был "взведен" (несигнальное состояние) перед вызовом • ЕАЬЗЕ — в противном случае Таблица 10.25. Прототип вызова КеКеаб5(а(еТ1тег 1Кр1_ <= О15РАТСН_ЬЕУЕЬ Параметры Описание 1Ы РКЛМЕкЛтег Возвращаемое значение Указатель на объект опрашиваемого таймера. • ТИПЕ — если таймер "истек" и перешел в сигнальное состояние • ЕАЬЗЕ — если таймер еще "взведен" и находится в несигнальном состоянии Программный код, осуществляющий инициализацию ЭРС и таймерных объектов, должен выполняться на уровне 1ЕЩ1_ равном РА551\/Е_1_Е\/Е1_. При выполнении установки, отмены и чтении состояния таймера код должен выполняться на уровне 1Р<31_ меньшем или равном 015РАТСН_1_Е\/Е1_. В общем случае, следует избегать применения вызова КеХпзегСриеиеОрс к тем ОРС объектам, которые используются в связке с процедурами типа рассмотренной выше Си51отТ1тегОрс, работающей с таймером, поскольку это может привести к возникновению гонки состояний внутри драйвера. Рассмотрим пример, реализующий добровольную задержку программного потока драйвера при использовании таймера. В данном примере задержка вставлена в драйверный код обработчика 1ОСТ1. запросов пользовательского приложения, который работает в контексте пользовательского потока (что характерно для обработчика ЮСТЬ запросов), соответственно, уровень 1Н<31_ данного кода не превышает РА551\/Е_1_Е\/Е1_. По этой причине использование задержек в 1-10 секунд не вызывает никаких возражений со стороны операционной системы. // Хотя и нехорошо делать глобальные переменные в драйвере: РКТ1МЕР. рТ1тег=ЫОЪЪ; // указатель на таймер РКВРС рРрсОЬдесб=ЫОЬЬ; // указатель на объект РРС #беЕтпе 1РЬЕ ЮТЕКУАЬ (10000) УО1Р МуВе^еггебР.оиб1пе ( 1Ы РКРРС рбЫзВрсОбдесб, РУОЮ ЭеЕеггебСопбехб, РУОЮ ЗузбетАгдитепб1, 1Ы РУОТР ЗузбетАгдитеп'Ь2 ) { РКТ1МЕК рбгТттег = (РКТ1МЕК)ЭеЕеггебСопбехб; РЬдРгтп’Ь ( "-Ехатр1е- 1п МуРеРеггебКоибтпе . " ) ;
6Р ( КеВеаб8РаРеТ1тег(рРгТгтег) ) { ВЬдРггпР("-Ехатр1е- БРС: КеВеаб8РаРеТ1тег геРигпз ТРЕ } е1зе { ВЬдРггпР ("-Ехатр1е- БРС: КеВеаб8РаРеТ1тег геРигпз ЕАР } } // Обработчик ЮСТЬ вызовов: ЫТ8ТАТ68 Веч1сеСопРго1ВоиР1пе( 1Е РБЕУ1СЕ_ОВ6ЕСТ Ыо, 1Е Р1НР 1гр ) { ЫТ8ТАТ68 зРаРиз; змгРсЬ. ( СопРгоЮобе) { сазе 1ОСТЬ_ТЕ8Т_Т1МЕН: { ЫТ8ТАТ68 зРаРиз; // Выводим сообщения только в отладочной версии БЬдРггпР("-Ехатр1е- 1ОСТЬ_РВ1ЕТ_ВЕВ6С_МЕ88."); 1пР зЬогРСус1ез; 1Р( рТ1тег==ЕбЪЪ ) // если объект таймера не существу { // выделяем память под объект таймера рТбтег= (РКТ1МЕВ) ЕхАИосаРеРоо! (ЫопРадебРоо!г з КеТпбРРаИгеТбтег (рТгтег) ; // инициализируе // выделяем память под БРС объект и инициализ рВрсОЬ^есР= (РКБРС) ЕхАИосаРеРоо! (ЫопРадебРоо! КеТпРРбаИгеВрс (рБрсОЬ^есР, МуБеРеггебВоиРгпе } ЬАВСЕ_ЮТЕСЕВ биеТгте; биеТгте.ОиабРагР = -10000 * 1ВЬЕ_ЮТЕВУАЬ; // 10000*1 // "взводим" таймер: Ке8ебТ1тегЕх( рТбтег, биеТбте, // время ожидания, относительный инт (1ВЬЕ_ЮТЕВУАЬ/2), // период 5 с, то есть 500 рБрсОЬ^есб ); // во время ожидания сигнального состояния таймера // выполним 100 циклов по 50 мкс: Рог ( зйогРСус1ез=0; зйогРСус1ез < 100; зйогРСус1ез++ { ВЬдРгбпб("-Ехатр1е-Ке8Ра11ЕхесиР1опРгосеззог. зйогРСус1ез); Ке8Ра11Ехеси‘ЫопРгосеззог ( 50 ) ; 1Р( КеВеабВРабеТбтег(рТбтег) )
ВЬдРгРпб ("-ЕхатрРе- КеКеабЗЕабеТттег } еРзе { БЬдРгРпб ("-ЕхатрРе- КеКеабЗЕабеТттег } } // Останавливаем поток збабиз = КеИаРЕЕогЗРпдРеОЬ]есб ( рТтшег, ЕхесиЕ^е, // ТО КИА1Т_КЕАЗОИ ИаРбКе КегпеРМобе, // 1Ы КРВОСЕЗЗОВ_МООЕ Иат ЕАЬЗЕ, // 1Ы ВООЬЕАЫ АРегбаЬРе, ЕГОЬЬ); // РИ РЬАКСЕ_1МТЕСЕК ТРтеоиб С 1^( !ЫТ_ЗЕССЕЗЗ (з'Ьа'Еиз) ) { БЬдРгРпб ( "-Ехатр1е- Еггог 1п КеИаРбЕогЗРпдРеС БЬдРгРпб ( "-Ехатр1е- ЗТАТЕЗ ед %х.",збабиз); } е1зе { БЬдРгРпб ( "-Ехатр1е- КеИаРбЕогЗРпдРеОЬ] есЬ ОК. } // Считываем состояние таймера после окончания ожидан 1Е (КеКеабЗбабеТРтег (рТттег) ) { ВЬдРгтп'Ь ( "-Ехатр1е- КеКеасРЗКаКеИтег ге'Ьпгпз } е!зе { ВЬдРгтп'Ь ( "-Ехатр1е- КеКеасРЗКаКеИтег ге'Ьпгпз } Ьгеак; } сазе 1ОСТЬ_САЫСЕЬ_Т1МЕР: // Удаляем объект таймера и объект Е { ВООЬЕАИ гези1б = КеСапсеРТтшег(рТтшег); (гези1б) { ВЬдРгтпб("-Ехатр1е- КеСапсеРТттег гебигпз ТРО } е1зе { ВЬдРгтпб("-ЕхатрРе- КеСапсеРТРтег гебигпз ЕАЪ } ЕхЕгееРооР(рТттег);
ЕхЕгееРоо!(рБрсОЬ^есЕ) ; рТ1тег=ИЬЬЬ ; Ьгеак; } Ниже приводится фрагмент кода пользовательского приложения, которое тестирует рассмотренный выше код драйвера. ШОВБ ВуЬезВеЬигпеа; ипзгдпеа 1опд 1осЕ1Соае=10СТЬ_РР1ИТ_ЬЕВЬС_МЕЗЗ; 1Р ( !Ьеч1се1оСопбго1( ЬНапа1е, ЬосЫСоае, ИИЬЬ, 0, // 1приЕ ИИЬЬ, 0, // ОиЬриЬ &ВуЬезВеЬигпеа, ИИЬЬ ) ) { рг1пЬЬ( "Еггог 1п 1ОСТЬ_РВ1ИТ_ВЕВ13С_МЕЗЗ ! " ); } // Запуская данное приложение в отладчике в пошаговом режиме, // здесь сделаем паузу, давая отработать несколько раз БРС // процедуре, шаги с 216 по 241 в распечатке ниже. // Интервал вызовов БРС процедуры составляет 5 секунд. 1осЕ1Соае=10СТЬ_СНАИСЕ_1Р(2Ь; И ( !Веч1се1оСопбго1( ЬНапа1е, госЫСоае, ИИЬЬ, 0, // 1приЕ ИИЬЬ, 0, // ОиЬриЬ &ВуЬезВеЬигпеа, ИИЬЬ ) ) { рг1пЬЬ( "Еггог 1п 1ОСТЬ_СНАИСЕ_1В(2Ь! " ); } Функция МуОеГеггеаКоийпе начинает запускаться только после перехода таймера в сигнальное состояние. Период ее запусков определен значением третьего параметра в вызове КеЗеГПтегЕх. Ниже приводится распечатка 1од-файла сообщений, выводимых макросами ОЬдРпп1 в окно программы ОеЬидХЛем. Вторая колонка — отсчеты в секундах. 00000010 0.00241344 -Ехатр1е- 1ОСТЬ_ТЕЗТ_Т1МЕВ. 00000011 0.00243578 -Ехатр1е- КеЗЬаНЕхесиЫопРгосеззог . зЬогЬСус1ез 00000012 0.00250171 -Ехатр1е- КеВеааЗбабеИтег гебигпз ЕАЬЗЕ. 00000013 0.00251568 -Ехатр1е- КеЗЬаНЕхесиЫопРгосеззог.зЬогЬСус1ез 00000014 0.00257910 -Ехатр1е- КеВеааЗЕаЬеТгтег гебигпз ЕАЬЗЕ. 00000015 0.00259810 -Ехатр1е- КеЗЬаНЕхесиЫопРгосеззог . зЬогЬСус!ез 00000016 0.00266179 -Ехатр1е- КеВеааЗЕабеТгтег гебигпз ЕАЬЗЕ.
00000208 0.01016721 -Ехатр1е- КеРеас18ЕаЕеТ1тег геЕигпз ЕАЬ8Е. 00000209 0.01018118 -Ехатр1е- Ке80а11ЕхесиЕ1опРгосеззог.зНогЕСус1ез 00000210 0.01024572 -Ехатр1е- КеРеас18ЕаЕеТ1тег геЕигпз ЕАЬ8Е. 00000211 00000212 00000213 00000214 00000215 9.99725244 9.99728234 9.99730357 9.99732033 9.99734128 -Ехатр1е- -Ехатр1е- -Ехатр1е- -Ехатр1е- -Ехатр1е- 1п МуБеРеггеОРоиЕТпе . БРС: КеРеас18ЕаЕеТ1тег геЕигпз ТРОЕ. КеЭДаЮЕог81пд1еОкд есЕ ОК. КеРеас18ЕаЕеТ1тег геЕигпз ТРОЕ (аЕЕег) ^еV^сеIоСопЕ^о1: 0 ЬуЕез мгЮЕеп. 00000216 15.00447208 -Ехатр1е- 1п МуБеРеггеОРоиЕТпе. 00000217 15.00450169 -Ехатр1е- БРС: КеРеас18ЕаЕеТ1тег геЕигпз ТРОЕ. 00000218 20.01165400 -Ехатр1е- 1п МуБеРеггеОРоиЕТпе. 00000219 20.01168780 -Ехатр1е- БРС: КеРеас18ЕаЕеТ1тег геЕигпз ТРОЕ. 00000240 00000241 80.09805464 80.09808928 -Ехатр1е- -Ехатр1е- 1п МуБеРеггеОРоиЕТпе . БРС: КеРеас18ЕаЕеТ1тег геЕигпз ТРОЕ. 00000242 00000243 80.18758250 80.53109152 -Ехатр1е- -Ехатр1е- 1ОСТЬ_САОСЕЬ_Т1МЕР. КеСапсе1Т1тег геЕигпз ТРОЕ. Заметим, что выполнение освобождения памяти вызовами ЕхЕгееРоо! без выполнения вызова КеСапсе1Т!тег здесь неминуемо привело бы к краху системы. В ОРС процедурах, подключенных к таймеру при помощи вызова КеЗеГПтегЕх, допустимо выполнять переустановку таймера (вызов КеЗеГПтегЕх), что приводит к повторному ожиданию. При этом до окончания ожидания ОРС процедура более не вызывается. УОЮ МуБеЕеггеОРоиЕТпе ( 10 РКБРС рЕЫзБрсОЬ^есЕ, 10 РУОЮ БеРеггеОСопЕехЕ, 10 РУОЮ 8узЕетАгдитепЕ1, 10 РУОЮ 8узЕетАгдитепЕ2 { РКТ1МЕР рЕгТТтег = (РКТ1МЕР)ОеРеггеОСопЕехЕ; ЬАРСЕ_1ОТЕСЕР ОиеТТте; 0иеТ1те .ОиаОРагЕ = -10000 * ЮЬЕ_1ОТЕРУАЬ; // Юзесз Ке8еЕТ1тегЕх( рЕгТТтег, ОиеТТте, (ЮЬЕ_1ОТЕРУАЬ/2) , // 5000 тз рЕЫзБрсОЬ^ есЕ } Как было сказано ранее, указатели на объекты таймера и ОРС рекомендуется хранить в структуре расширения объекта устройства. Следовательно, если объект устройства удаляется (например, при выключении питания РпР устройства) и драйвер удаляется,
то необходимо корректно очистить занимаемую такими объектами память. Проблемы отладки ситуаций, когда драйвер завершает работу с еще активным таймером, настолько сложны, что Эпуег УепЛег (программное средство "тренировки" драйверов) делает специальную проверку относительно освобождения памяти, которая содержит работающие таймеры.
ГРгеуюиа! [Ыех1]
Объекты события Событие есть синхронизационный примитив (объект), который должен быть явно установлен в сигнальное либо несигнальное состояние. Событие похоже на бинарный флаг, позволяющий одному потоку подавать сигналы другим потокам о том, что нечто, оговоренное заранее, произошло, и этот сигнал подается путем установки объекта события в сигнальное состояние. Если присмотреться к определению объекта события в заголовочных файлах ООК, то становится очевидным, что объект события состоит только лишь из структуры О15РАТСНЕР_НЕАОЕк, в то время как другие синхронизационные примитивы — таймеры, семафоры и мьютексы — содержат, помимо О15РАТСНЕк_НЕАОЕР, некоторые дополнительные поля. То есть, события — самые простые из них. Объекты события делятся на две категории: объекты для уведомления (Моййсайоп Еуеп^з) и объекты для синхронизации (5упс1пгоп12айоп Еуеп^з). Тип выбирается в момент инициализации объекта. Эти два типа объекта событий проявляют различие в своем поведении в момент, когда объект переводится в сигнальное состояние. Как только объект уведомляющего (Моййсайоп) события переходит в сигнальное состояние, все потоки, реагирующие на него, выходят из состояния ожидания. Однако объект такого типа необходимо перевести в несигнальное состояние явно (вызовом КеС1еагЕуеп(), иначе он так и останется в активном состоянии. Поведение данного типа объектов аналогично поведению объектов события пользовательского режима, которые управляются ручной установкой. Поведение синхронизационных (5упс1"1гоп12айоп) объектов события несколько отличается. Когда синхронизационный объект события переходит в сигнальное состояние, то он остается в этом состояние лишь столько времени, сколько это
необходимо для выполнения одного вызова КеУУакЕогХхх. Затем объект переводит себя в несигнальное состояние автоматически. Другими словами, ворота остаются открытыми только до тех пор, пока кто-то первый не прошел через них, после чего они закрываются автоматически. Этот тип эквивалентен событиям с авто-сбросом (аи^о-гезе!:) в пользовательском режиме. Таблица 10.26. Функции для работы с объектами событий Что необходимо сделать... Создать событие Создать именованное событие Изменить состояние события Какой вызов нужно использовать... Ке1тйаН2еЕуеп1 1оСгеа1е8упсНгоп12аМопЕуеп1 1оСгеа1еМо1И|са11ОпЕуеп1 Ке8е1Еуеп1 КеС1еагЕуеп1 КеКезеСЕуепС Запросить состояние Кепеас^5^а^еЕVеп^ Для использования объекта события сначала необходимо получить память для его хранения размером 51геоГ(КЕ\/ЕМТ), и только после этого можно выполнять вызовы функций, перечисленные выше. Рассмотрим некоторые из них. Несигнальное состояние объекта событий можно установить при помощи вызовов КеЯе8е1Еуеп1 и КеС1еагЕуеп1. Разница между ними заключается в том, что функция КеПезе1Еуеп( еще и возвращает состояние объекта, в котором тот пребывал перед данным вызовом. Функция КеС1еагЕуеп1 работает несколько быстрее, поэтому в тех случаях, когда нет необходимости знать предыдущее состояние объекта, следует использовать этот вызов. Таблица 10.27. Прототип вызова КеХпШаНгеЕуеп! УОЮ Ке1п|11аНгеЕуеп1 1К<21_ == РА551УЕ_1_ЕУЕ1_ Параметры 1Ы РКЕХ/ЕЫТ рЕуеп! 1Ы ЕУЕМТ-ТТРЕ Туре Инициализация объекта события и установка его начального состояния Указатель на область памяти для объекта события Одно из двух значений
• 1УоППсаИопЕуеп1 • ЗупсНгот'гаНопЕуеп! Начальное состояние объекта ВООЬЕАМ Ыпка151а1е • ТРОЕ — сигнальное состояние • РАЬЗЕ — несигнальное состояние Возвращаемое значение УО1С1 Таблица 10.28. Прототип вызовов КеС1еагЕуеп1 и КеПе8е1Е\теп1 УОЮ КеС1еагЕуеп1 1.0146 КеКезеГЕуепГ 1К0Ь <= 015РАТСН_ЬЕУЕЬ Параметры Установка объекта события в несигнальное состояния 1Ы РКЕ\/Е1\1Т рЕуеШ Указатель на инициализированный объект события Возвращаемое значение КеКезе^Еуеп! возвращает предыдущее состояние объекта события Таблица 10.29. Прототип вызова Ке8е1Еуеп1 1.0146 Ке5е1Еуеп1 Параметры 1Ы РКЕУЕМТ рЕуеп! 1Ы КРКЮК1ТУ 1псгетепС 1Ы ВООЬЕАМ ЬУУаИ 1К0Ь <= 015РАТСН_ЬЕУЕЬ Переводит объект события в сигнальное состояние Указатель на инициализированный объект события Обычно используется значение Ю_Ы0_1ЫСИЕМЕЫТ Обычно используется значение ЕА15Е Возвращаемое значение Возвращает ненулевое значение, если предыдущее состояние объекта события было сигнальным В качестве примера, когда применение объекта события будет весьма кстати, можно привести следующую ситуацию. Предположим, драйвер имеет специально созданный программный поток, который выполняет некоторую работу по получению сигнала прерывания. Завершив ее, поток замирает до прихода следующего прерывания. Реализовать эту схему легко, если рассматриваемый программный поток будет ожидать наступления сигнального состояния объекта события, а переводить его в такое состояние будет драйверная процедура ОрсЕогТзг.
В основе объектов события пользовательского режима лежат объекты события режима ядра — именно те, которые обсуждались выше. Основное различие заключается в том, что типовой доступ в пользовательском режиме — по дескриптору, а в режиме ядра — по указателю. Иными словами, один и тот же объект события при некоторой сноровке можно использовать для синхронизации действий между разными драйверами и между драйверами и приложениями пользовательского режима. Совместное использование двумя несвязанными драйверами одного объекта события, созданного вызовом Ке1пк1а11хеЕуеп1, есть весьма непростая задача. Более простого способа передать его, иначе как по специальному предварительному соглашению (например, с использованием специального внутреннего кода ЮСТ1_), не существует. Имеется и такая проблема: как гарантировать, что драйвер, создавший объект события, и в момент получения указателя другим драйвером и во все время его использования все еще останется загруженным? Функции 1оСгеа(е5упсНгоп1ха(!опЕуеп( и 1оСгеа(еМо(1Нса(1опЕуеп( позволяют создавать (или открывать, если таковые существуют) именованные объекты события. До тех пор, пока два драйвера используют одно и то же имя этого объекта, они без труда смогут получать указатель на один и тот же объект события. Действие этих функций вполне эквивалентно поведению АР1 вызова Сгеа1еЕуеп1. Итак, пусть первый драйвер делает вызов с целью создать объект события с определенным именем и действительно создает его. Последующие вызовы (с целью создания объекта с тем же именем) нового объекта не создадут, а всего лишь возвратят дескриптор, относящийся к уже существующему объекту события. При использовании именованного объекта события совместно драйвером и приложением пользовательского режима следует создавать такой объект сначала в пользовательском приложении. Причина кроется в том, что пользовательские объекты события должны размещаться в директории объектов
\ВазеМатес1ОЬ]ес1:8, которая создается после инициализации подсистемы \ЛЛп32 и к моменту запуска драйвера, возможно, еще не существует. После этого, в результате ЮСТ1_ запроса (выступающего в роли команды) к драйверу, последний должен получить доступ к объекту события по заранее определенному имени либо должен получить некоторую дополнительную информацию из ЮСТ1_ запроса — имя или дескриптор созданного объекта события. Таблица 10.30. Прототип вызовов 1оСгеа1е5упсНгот'гаИоп(/ЧоНЯсаНоп )Еуеп1 РКЕУЕМТ 1оСгеа1е5упсНгоп12аЦопЕуеп1 РКЕУЕМТ 1оСгеа1еМоИЛсаЦопЕуеп1 1КРЬ == РА551УЕ_ЬЕУЕЬ Параметры Создает новый или получает доступ к существующему объекту события по имени 1Ы Р1)1\11С00Е_5ТРШ\1(3 ЕуепМагпе Имя объекта, заканчивающаяся нулем строка широких (иГПСООЕ) символов ОСТ РНАЫО1Е ЕуегиНапсПе Указатель, по которому будет возвращен дескриптор объекта. Возвращаемое значение Указатель на созданный или существующий объект события с данным именем либо 1\1и1_1_ в случае ошибки. Для работы драйверу требуется указатель на объект события. Его можно получить из дескриптора существующего объекта следующим способом. Выполнить вызов ОЬЯеГегепсеОЬ]ес<:ВуНапс11е. Эта функция возвращает указатель на собственно объект и увеличивает на единицу число ссылок на данный объект. Поскольку собственно дескриптор становится ненужным, необходимо вы полнить вызов 2шС1о5е со значением дескриптора. Эта функция выполнит уменьшение на единицу счетчика ссылок на данный объект. Когда объект события станет ненужным, необходимо выполнить вызов ОЬОегеГегепсеОЬ]ес1 для того, чтобы уменьшить на единицу счетчик ссылок на объект, что, возможно, уничтожит его.
Эти вызовы могут быть выполнены только с уровня 1Р.(31- равного РА551\/Е_ЬЕ\/ЕЬ, что накладывает ограничения на то, где драйвер сможет их использовать. В том случае, если драйвер получает от приложения дескриптор через ЮСТЬ запрос, то этот дескриптор имеет силу, поскольку код драйвера (обработчика ЮСТЬ запросов) работает в контексте пользовательского потока, обратившегося к драйверу. Пример использования объекта события для синхронизации работы приложения и драйвера можно найти в следующей главе.
Семафоры Семафоры — это объекты синхронизации, имеющие внутренний счетчик обращений и пребывающие в сигнальном состоянии тогда, когда значение этого счетчика больше нуля. Состояние становится несигнальным сразу же, как только счетчик принимает нулевое значение. Другими словами, семафор — это не что иное, как счетный мьютекс (см. ниже). Для увеличения на единицу значения внутреннего счетчика семафора следует выполнить вызов КеЯе1еа8е5етарЬоге. Предположим, два потока 11 и 12 ожидают (используя вызов КеУУа!1Рог5тд1еОЬ]ес1) сигнального состояния семафора 5, счетчик которого в настоящий момент равен 0. Когда третий поток 10 выполнит вызов КеЯе1еа8е5етар11Оге, значение счетчика возрастет до 1. Следовательно, одному из ожидающих потоков будет разрешено продолжить работу. Вызов КеУУакЕог$тд1еОЬ]ес( вернется из состояния ожидания, уменьшив счетчик семафора на единицу. Соответственно, второй поток останется заблокированным. Какому конкретно потоку повезет, почти что неизвестно — в том смысле, что не следует строить на этом расчет. Если это имеет большое значение, то следует усложнить схему синхронизации. Удобно использовать семафоры для "охраны" созданных драйвером очередей или списков объектов. При добавлении объекта в очередь (например, собственную очередь 1НР пакетов) производится увеличение на единицу счетчика семафора. Как только некий рабочий поток удаляет объект из очереди или списка, он уменьшает значение семафора. Когда счетчик семафора станет равным нулю, а очередь опустеет, поток (или потоки) перейдет в состояние ожидания. Таблица 10.31. Функции для работы с объектами семафоров Что необходимо сделать Создать семафор Увеличить счетчик семафора Используемый вызов КеХтйаНхеЗетарНоге КеКе1еазе5етарНоге
Запросить состояние КеКеас151а1е5етарНоге Уменьшить счетчик Ке№аНЕог5тд1еОЬ]ес1 семафора КеМаНЕогМиШр1еОЬ]ес1 Для инициализации семафора используется вызов, КеХтНаНхеЗетарНоге, которому необходимо передать не только два параметра будущего семафора (см. таблицу 10.32), но и область памяти под будущий объект, выделенную, например, вызовом ЕхА11оса(еРоо1. Таблица 10.32. Прототип вызова КеТпШаИгеЗетарНоге Х/ОЮ КеХпЩаНгеЗетарНоге Параметры 1КОЬ == РА551УЕ_ЬЕУЕЬ Инициализирует объект семафора и устанавливает текущее значение его счетчика и предельное значение, которого этот счетчик может достигать 1Ы РК5ЕМАРНОР.Е рЗетарЬоге Указатель на область, подготовленную для объекта семафора 1Ы 1_0Г1С СоипСУа1ие Текущее (начальное) значение счетчика 1Ы 1_О1\1С СоипШтН Предел для значений счетчика (должно быть положительным) Возвращаемое значение УО1С1 Вызов КеКеас151а1е5етар11Оге получает указатель на объект семафора и возвращает значение типа 1_О1\1С, равное текущему значению счетчика семафора. Соответственно, нулевое возвращенное значение указывает на то, что семафор пребывает в несигнальном состоянии. Вызов КеПеас1$(а(е$етарНоге может выполняться на любом уровне 1Р.<С>1_, что однозначно указывает на то, какие правила применяет система к объекту семафора: он непременно должен размещаться в области нестраничной памяти. Параметры вызова КеЯе1еабе5етар11оге описаны в таблице 10.33. Таблица 10.33. Прототип вызова КеПе/еазеЗетарНоге УОЮ КеКе1еазе8етарНоге 1«ОЬ == РА551УЕ_1_ЕУЕ1_ Параметры Инициализирует объект семафора и устанавливает текущее значение его
счетчика и предельное значение, которого этот счетчик может достигать 1Ы РК5ЕМАРН0КЕ рЗетарИоге Указатель на область, подготовленную для объекта семафора 1Ы 1.0146 Соип1А/а1ие Текущее (начальное) значение счетчика 1Ы 1.0146 СоипШтК Предел для значений счетчика (должно быть положительным) Возвращаемое значение УОИ
Мьютексы Мьютекс является синхронизационным примитивом (объектом), которым может владеть только один поток в данный конкретный момент времени. Термин ти1ех является сокращением от 'ти!иа1 ехс1и81оп', совместное исключение. Объект этого типа имеет несигнальное состояние, когда поток им владеет, и сигнальное — когда объект свободен. Мьютексы обеспечивают несложный механизм координации исключительного доступа к совместно используемым ресурсам, обычно — областям памяти. Предположим, потоки 11 и 12 ожидают освобождения мьютекса, которым владеет поток 10. В момент, когда поток 10 освободит мьютекс, один из ожидающих потоков "пробудится" и станет его владельцем. Для использования мьютекса необходимо получить блок памяти размером 51геоГ(КМ11ТЕХ) в области нестраничной памяти (например, вызовом ЕхА11оса1еРоо1), после чего следует выполнить его инициализацию вызовом Ке1т1!аП2еМи1ех (таблица 10.34). Следует помнить, что когда мьютекс инициализируется, то он сразу же устанавливается в сигнальное состояние. В случае, если некий поток выполняет вызов КеУУаИЕогХхх относительно того мьютекса, которым он уже владеет, никакого ожидания не случится. Вместо этого, происходит увеличение на единицу внутреннего счетчика объекта мьютекса, что всего лишь отражает факт повторного запроса на владение данным мьютексом со стороны потока. Когда поток пожелает освободить мьютекс, то ему придется сделать столько вызовов КеКе1еабеМи1ех (таблица 10.35), сколько ранее было сделано запросов на владение им. Только после этого объект мьютекса перейдет в сигнальное состояние. Точно такое же поведение демонстрируют мьютексы и в программировании приложений пользовательского режима. Мьютексы похожи на семафоры с максимальным значение счетчика 1. Правда, с одним существенным отличием: сам программный поток, получивший
владение мьютексом, может сделать это еще много раз (столько же раз он должен и освободить мьютекс). Таблица 10.34. Прототип вызова Ке1пШаИ2еМи1ех УОЮ КеХпЩаНгеМиСех 1КОЬ == РА551УЕ_ЬЕУЕЬ Параметры Инициализирует объект мьютекса и устанавливает его начальное состояние — сигнальное. 1Ы РКМ11ТЕХ рМиСех Указатель на область, подготовленную для объекта мьютекса 1Ы 1_0Г1С 1_еуе1 Уровень, присвоенный мьютексу разработчиком Возвращаемое значение УО1С1 Интересен параметр 1_еуе1, который мало где описан, включая ЭОК, но может улучшить защищенность кода от ситуаций взаимоблокировок, если драйвер использует несколько мьютексов сразу из нескольких программных потоков. При инициализации объекта мьютекса устанавливается номер уровня (параметр 1_еуе1). Позднее, когда поток пытается получить очередной мьютекс, ядро не разрешает владение этим мьютексом в случае, если во владении данного потока уже находится любой другой мьютекс с более низким значением уровня. При умелом использовании этого механизма, ядро автоматически предотвращает взаимоблокировки, возникающие в результате использования в драйвере нескольких объектов мьютексов. Программный поток не должен пытаться освобождать мьютексы, которые он не получал, поскольку это вынудит систему прекратить работу (ЬидсЬеск). Попытка освободить мьютекс, который имеет сигнальное состояние (то есть уже никому не принадлежит) приведет к аналогичным последствиям. Драйвер должен освобождать все мьютексы, которые находятся в его владении, перед тем, как передаст управление в пользовательский режим (то есть завершит рабочую процедуру и вернет управление Диспетчеру ввода/вывода). Ядро воспримет это как ошибку. Процедура Ас1с1Оеу|се, ОпуегЕп1ту или какая-либо рабочая процедура драйвера, получая для себя
мьютекс, не должны планировать его освобождение в другой рабочей процедуре или в другом программном потоке данного драйвера. Таблица 10.35. Прототип вызова КеЯе1еа5еМи1ех 1-0146 КеКе1еазеМи1ех 1КРЬ == РА551УЕ_ЬЕУЕЬ Уменьшает на единицу "счетчик занятости" Параметры объекта мьютекса, обозначая намерение инициатора вызова тут же вызвать (или не вызывать) КеМаКХхх. 1Ы РКМЫТЕХ рМиСех Указатель на объект мьютекса • ТР.11Е — следом за данным вызовом текущий программный поток собирается сделать вызов 1Ы ВООЬЕАМ с1оСа11ОГКе\Л/аИ:Ххх КеМаКЕогХхх (используется редко) • ЕАЬЗЕ — применяемое на практике значение (см. документацию ООК) Возвращаемое значение 0, если объект мьютекса перешел в сигнальное состояние Запрос на владение мьютексом выполняется вызовом КеУУакЕог$тд1еОЬ]ес( либо вызовом КеУУа!1ЕогМи111р1еОЬ]ес1 из кода, работающего на уровне 1ИС21_ равном РА551\/Е_1_Е\/Е1_. Специально для мьютексов придумано также макроопределение КеУУаИЕогМи(ехОЬ]ес1, которое есть текстуальная подстановка все того же системного вызова КеУУа11Гог5тд1еОЬ]ес1. Единственная функция, которую можно вызывать из кода уровня 1РХ21_ выше, чем РА551\/Е_1_Е\/Е1_, — это вызов КеЯеас151а1еМи1ех (таблица 10.36). Таблица 10.36. Прототип вызова КеЯеас181а1еМи1ех ЬОНС КеКеас151а1еМи1ех ткрь <= О15РАТСН_1_ЕУЕ1_ Параметры Возвращает состояние объекта мьютекса 1Ы РКМ11ТЕХ рМ1Лех Указатель на объект мьютекса 1, если объект мьютекса находится в сигнальном Возвращаемое значение ' к состоянии
Описанные выше простые мьютексы применяются теперь не так широко, как быстрые мьютексы, которые рассмотрены ниже. Быстрый мьютекс (Газ1: гтЛех) — это синхронизационный объект, который работает практически так же, как и описанный выше обычный мьютекс режима ядра, однако не допускает повторных (рекурсивных) запросов на владение из одного и того же программного потока. Такой мьютекс выполняет меньше работы и функционирует быстрее. Таблица 10.37. Функции для работы с объектами быстрых мьютексов Что необходимо сделать Создать быстрый мьютекс Сделать запрос на владение Освободить объект Используемый вызов ЕхХпШаНгеЕазСМиСех ЕхАсди!геЕа81Ми1ех ЕхАсди1геЕа81Ми1ех11п8аГе ЕхТгуТо Ася и ге Еа 81М и1ех ЕхВе1еа5еРа51Ми1ех Ехке1еа8еЕа81Ми1ех11п8аТе Объект быстрого мьютекса описывается типом ЕА5Т_М11ТЕХ (см. например, заголовочный файл ООК \л/с1т.1'|) и используется для синхронизации доступа к одному или нескольким элементам данных. Любой код, задумавший воспользоваться этими данными, должен сначала сделать запрос на владение соответствующим объектом ЕА5Т_М11ТЕХ. Следует обратить внимание на то, что эти объекты имеют собственные вызовы для выполнения запроса на владение. Функция КеУУаИРогХхх в данном случае не может быть использована. Перед использованием функций ЕхАсдшгеЕа51Ми1ех и ЕхЯе1еабеРа8<:Ми1ех следует выполнить инициализацию объекта быстрого мьютекса вызовом Ех1пШаН2еЕа8(Ми(ех, см. таблицу 10.38. И хотя память под структуру объекта выделяет инициатор этого вызова, как и в ранее описанных случаях для других объектов синхронизации, непосредственно
обращаться к полям этого объекта не следует — необходимо пользоваться только вызовами, предлагаемыми в ОЭК. Таблица 10.38. Прототип вызова Ех1пШаНхеРа81Ми1ех УОЮ Ех1п111аНнеРа51Ми1ех Параметры 1КОЬ <= О13РАТСН_ЬЕУЕЬ Инициализирует объект быстрого мьютекса Указатель на место в нестраничной памяти, 1Ы РЕА5Т_М11ТЕХ рЕазШиГех подготовленное инициатором данного вызова для объекта быстрого мьютекса Возвращаемое значение УОИ В случае если запрос на владение вызовом ЕхАсдшгеЕа5(Ми1ех удовлетворен быть не может (у объекта быстрого мьютекса уже есть владельцы), поток блокируется до наступления сигнального состояния. Блокируется также и процедура АРС, адресованная данному программному потоку. При успешном завершении вызова поток инициатора вызова выполняется на уровне 1Р<21_ равном АРС_1_Е\/Е1_, а прежнее значение сохраняется в объекте быстрого мьютекса (оно будет восстановлено при освобождении объекта быстрого мьютекса вызовом ЕхЯе1еабеРа81Ми1ех). Таблица 10.39. Прототип вызова ЕхАсдшгеРазЪМиЪех УОЮ ЕхАсяи1геЕаз1Ми1ех 1КРЬ < О15РАТСН_1_ЕУЕ1_ Параметры Запрашивает владение объектом быстрого мьютекса 1Ы РЕА5Т_М11ТЕХ рЕаз1Ми1:ех Возвращаемое значение Указатель на объект быстрого мьютекса УО1С1 Таблица 10.40. Прототип вызова ЕхАсдшгеРа81Ми1ехип5а?е Х/ОЮ Ех Ася и 1 ге Еаз(М и1ех11 пзаТе 1КРЬ == АРС_ЬЕУЕЬ Параметры Запрашивает владение объектом быстрого мьютекса 1Ы РЕА5Т_М11ТЕХ рЕазИМиСех Возвращаемое значение Указатель на объект быстрого мьютекса УОИ В случае если запрос на владение вызовом ЕхАсдшгеЕа5(Ми1ех11п5аГе удовлетворен быть не может,
поток блокируется до наступления сигнального состояния, однако, процедура АРС, адресованная данному программному потоку, не блокируется. Инициатор вызова ЕхАсдшгеЕа5(Ми1ех11п5аГе должен обеспечить условия, чтобы во время вызова не мог быть выполнен АРС вызов для данного потока. Для этого есть два способа. Первый состоит в том, чтобы увеличить уровень 1Р.01_ равный АРС_1_Е\/Е1_. Второй способ состоит в том, чтобы непосредственно перед вызовом ЕхАсдшгеЕа5(Ми1ех11п5аГе выполнить КеЕп1егСгШса1Яедюп, что временно блокирует простые АРС вызовы (в отличие от специальных АРС вызовов режима ядра). В последнем случае не следует забывать делать отменяющий вызов Ке1_еауеСгШса1Педюп. Освобождение быстрого мьютекса, полученного при помощи вызова ЕхАсдшгеЕа8(Ми(ех11п5аГе, следует выполнять только при помощи специально для того предназначенного вызова ЕхЯе1еабеРа81Ми1ехип5аГе. Можно пытаться получить владение объектом быстрого мьютекса без блокировки вызывающего потока (в случае занятости нужного объекта быстрого мьютекса) при помощи вызова ЕхТгуТоАсдшгеЕа51Ми1ех. В случае неудачи этот вызов возвратит значение РАЬ5Е. При удовлетворении запроса возвращается, соответственно, значение ТЯ11Е, см. таблицу 10.41. Владение объектом быстрого мьютекса отменяется его текущим владельцем по вызову ЕхЯе1еа5еЕаз(Ми(ех. Редакция пакета ООК для ХР настаивает на том, чтобы этот вызов выполнялся из кода, работающего на уровне 1КС^1_ равном О15РАТСН_1_Е\/Е1_, вплоть до того, что инициатор вызова должен установить явно этот уровень перед вызовом ЕхЯе1еа8еРаб1Ми<:ех. Обычно не следует об этом беспокоиться, если уровень 1КС^1_ не менялся со времени последнего вызова ЕхАсдшгеЕа8(Ми(ех, поскольку он автоматически устанавливает именно это значение. Таблица 10.41. Прототип вызова ЕхТгуТоАсцшгеГа51Ми1ех ВООЬЕАИ ЕхТгуТоАсяшгеЕавЕМиЕех 1КРЬ < О15РАТСН_1_ЕУЕ1_
Параметры Запрашивает владение объектом быстрого мьютекса 1Ы РЕА5Т_МЫТЕХ рЕаз1Ми1:ех Указатель на объект быстрого мьютекса Возвращаемое значение ТРОЕ — при успешном завершении, иначе — ЕАЬЗЕ Таблица 10.42. Прототип вызова ЕхПе1еа5еРа51Ми1ех УОЮ ЕхКе1еа5еРа51Ми1ех 1К<2Ь == АРС_ЬЕУЕЬ Отменяет владение объектом быстрого Параметры мьютекса, полученного при помощи вызова ЕхАся и । ге Еаз1М и1ехЫ пзаТе 1Ы РЕА5Т_МЫТЕХ рЕаз1Ми1:ех Возвращаемое значение Указатель на объект быстрого мьютекса УО1С1 Таблица 10.43. Прототип вызова ЕхКе1еа8еРа51Ми1ехип5а/е УОЮ ЕхКе1еа5еЕа51Ми1ехЫп5аТе 1КОЬ <= АРС_ЬЕУЕЬ Отменяет владение объектом быстрого Параметры мьютекса, полученного при помощи вызова ЕхАся и । ге Еаз1М и1ехЫ пзаТе 1Ы РЕА5Т_МЫТЕХ рЕаз1Ми1:ех Возвращаемое значение Указатель на объект быстрого мьютекса УО1С1
Спин-блокировки Чуть позже будет рассмотрено использование изменения уровня 1РХ21_ для синхронизации доступа к данным. Однако в многопроцессорных системах изменение 1Р.С21_ одного процессора никак не сказывается на значении 1Р.01- программного кода, исполняемого на другом процессоре. То есть 1Н01_ предоставляет способ защиты совместно используемых данных только при работе с одним процессором. Для безопасного доступа к данным в мультипроцессорной среде, \Л/1Пс1о\л/ ЫТ использует синхронизационные объекты, называемые спин-блокировками (зрИ 1оскз). Спин-блокировка является, по сути, объектом типа мьютекс, однако, с более широкими полномочиями. Когда фрагмент кода, работающего на уровне режима ядра, собирается обратиться к одной из "охраняемых" структур данных, он должен сначала выполнить запрос на владение спин-блокировкой. Так как только один из процессоров в каждый момент времени имеет право собственности на объект спин-блокировки, то таким образом и обеспечивается разделение доступа к охраняемым данным между потоками, работающими на разных процессорах. Если рассматривать функционально полную группу вызовов Ке1пк1а11хе5рт1-оск — КеАсдшге8р1п1_оск — КеЯе1еабе5рт1_оск, то можно сказать, что объект спин- блокировки должен запрашиваться из программного кода, работающего на уровнях 1НС21_ ниже О15РАТСН_1_Е\/Е1_, а освобождается на уровне равном О15РАТСН_1_Е\/Е1_. Таблица 10.44. Прототип вызова КеТпШаИгеЗртЬоск УОЮ Ке1пШаНге8р1п1_оск трь == любой Параметры Инициализирует объект спин-блокировки Указатель на место в нестраничной памяти, 1Ы РК5Р11\1_1_08К р5р1п1_оск подготовленное инициатором данного вызова для объекта спин-блокировки Возвращаемое значение уоИ
Ограничение на выделение памяти под объект спин-блокировки только из пула нестраничной памяти проистекает из того, что программный код, получивший владение объекта спин- блокировки, начинает работать на уровне О15РАТСН_1_Е\/Е1_. После получения владения объектом спин-блокировки в результате вызова КеАсяшге§рт1_оск (таблица 10.45), программный код данного потока получает уровень IК (21- равный О15РАТСН_1_Е\/Е1_, что автоматически означает торможение всех программных потоков, выполняемых на данном процессоре с 1Р(21_ ниже О15РАТСН_1_Е\/Е1_. Таким образом, на этом процессоре реализуется синхронизация доступа к данным методом повышения 1Р(21_. (Разумеется, это не спасет, если за данными обратятся из процедуры обработки прерывания, работающей на более высоких уровнях 01РС21-.) Таблица 10.45. Прототип вызова КеАсдшге5рт1-оск УОЮ КеАсяи!ге5р1пЬоск Параметры 1Ы РК5Р11\1_1_0СК р5р1п1_оск О11Т РК1Ир1_ р01с11гр1 1КРЬ <= О15РАТСН_ЬЕУЕЬ Инициализирует объект спин-блокировки Указатель на место в нестраничной памяти, подготовленное инициатором данного вызова для объекта спин-блокировки Место для сохранения старого значения уровня 1Ир1_ для использования позже в вызове КеКе1еа8е5р1пЬоск Возвращаемое значение УО1С1 В главе 3, в драйверной процедуре обработки ЮСТЬ запросов был применен вызов КеАсяи!ге5рт1_оск, в результате чего значение 1Р(21_ становилось равным 2 (О15РАТСН_1_Е\/Е1_): 00000015 0.00203462 -Ехашр1е- 1К.<2Ьз аге о1с!=2 ... хотя изначально обработчик ЮСТЬ запросов драйвера вызывается драйвером на уровне РА551\/Е_1_Е\/Е1_ (0). Эта неявная работа вызова КеАсяи1ге5р!пЬоск приводит к тому, что при обработке запроса ЮСТ1__МАКЕ_5У5ТЕМ_СРА5Н в драйвере Ехагпр1е.5у5 не происходит перехвата исключительной ситуации конструкцией 1гу-ехсерйоп, нормально работающей при уровне РА551\/Е_1_Е\/Е1_.
Таблица 10.46. Прототип вызова КеЯе1еа5е$рт1-оск УОЮ Кеке1еа8е8р1пЬоск Параметры 1Ы РК5Р11\1_1_0СК р5р1п1_оск 1Ы РК1КО1 рЫе\л/1гр1 Возвращаемое значение 1КРЬ == О15РАТСН_1_ЕУЕ1_ Освобождает объект спин-блокировки Указатель на освобождаемый объект спин- блокировки Устанавливаемый данным вызовом уровень 1Р.<21_ (предполагается, что это — сохраненное ранее вызовом КеАсяшге5рт1_оск значение) УОИ Не рекомендуется удерживать объект спин-блокировки более 25 микросекунд. Категорически не рекомендуется обращаться к страничной памяти из кода, получившего спин-блокировку: рано или поздно это приведет к краху системы. Попытка получить объект спин-блокировки на процессоре, который уже владеет этим объектом, приводит к надежному "замерзанию" процессора. В драйвере Ехатр1е.5уз такая ситуация легко моделируется следующим образом. Если при выходе из обработчика ЮСТ1_ запросов не освободить объект спин-блокировки Му5р1п1_оск, то при следующем входе в этот код система "подвисает": процессор ждет, когда он сам освободит объект спин-блокировки. Чревато опасностями и использование конструкций, в которые заложена зависимость одновременно от нескольких спин- блокировок. По крайней мере, следует избегать получения новых спин-блокировок, когда не освобождены ранее полученные: другой поток, владея запрашиваемыми объектами, в это же время может ожидать доступа к спин-блокировкам, которые отданы первому. Такие ситуации называются еще взаимоблокировками, с1еас11оск5. Рассмотренный тип спин-блокировок носит название спин- блокировок выполнения (ехесиИуе зрш 1оскз), и их основная область применения — охрана различных структур данных при совместном использовании несколькими программными потоками. Уровень 1Р.С21_, на котором они применимы, ограничивается значением О15РАТСН_1_Е\/Е1_.
Помимо рассмотренных "явно выраженных" объектов спин- блокировок выполнения (которые создаются драйвером), существуют и спин-блокировки, косвенно "ощущаемые" драйвером. Например, с объектом прерывания ассоциирован объект спин-блокировки, который практически используется при участии вызова КебупсНготгеЕхесийоп (см. таблицу 10.14 и пояснительный текст к ней). Спин-блокировки этого типа носят название спин-блокировок прерываний (т1еггир( зрт 1оскз), их область применения — охрана различных структур данных на уровнях приоритетов 01К.С21_. Общая схема использования спин-блокировок выполнения такова. Следует определить, какие элементы данных должны оберегаться и как много спин-блокировок следует использовать. Дополнительные спин-блокировки позволяют более точно настроить доступ к данным. Однако когда для получения доступа необходимо получить более одного объекта спин- блокировок, возрастает опасность возникновения взаимоблокировок. Резервирование памяти под структуру (структуры) типа К5Р11\1_1_ОСК в памяти нестраничного типа. Имеет смысл запомнить указатель на полученную область памяти в структуре расширения объекта устройства. Инициализация спин-блокировки вызовом Ке1пШа1|2е5р1п1-оск. Этот вызов может быть сделан из кода любого уровня 1Р<21_, хотя лучше всего это сделать там, где создается структура расширения объекта устройства (Ас1(Юеу1се или ОпуегЕп1ту, для драйверов "в-стиле-ЫТ"). Перед обращением к охраняемым данным следует выполнить получение прав на владение объектом спин- блокировки при помощи вызова КеАсяшге5рт1_оск. Эта функция повышает значение ТРС^Ь до уровня □15РАТСН_1_Е\/Е1_, получает спин-блокировку и возвращает значение 1Р.С)1_ на момент перед вызовом (не восстанавливает, а возвращает значение в одном из параметров, см. таблицу 10.45), которое следует сохранить либо в локальной переменной (если это возможно по логике
работы) либо в переменной в нестраничной памяти. Эта функция должна вызываться из кода на уровне ниже уровня 015РАТСН_1_Е7Е1_ 1Р<21_. Когда доступ к ресурсам завершен, следует освободить объект спин-блокировки вызовом КеЯе1еабе5р!пЬоск (см. таблицу 10.46), восстанавливающим ранее сохраненное значение 1Р.С)1_. Это делается из кода уровня □15РАТСН_1_ЕУЕ1_. Дополнение к п. 5 и п. 6. Если программный код уже выполняется на уровне О15РАТСН_1_Е\/Е1_, то для получения спин-блокировки следует применять вызов КеАсдшге5рт1_оскАШрс1-еуе1, а для освобождения, соответственно, вызов КеЯе1еабе5р1пЬоскРготОрсЬеуе1, который освобождает объект спин-блокировки без изменения 1НС21_. Эти вызовы получают единственный параметр, указатель на объект спин-блокировки, поскольку значение 1Р<21_ теперь предполагается вполне определенным, то есть равным 015РАТСН_1_Е7Е1_. Стремление уменьшить вероятность неточного или недобросовестного программирования (в частности, исключить возможность передачи в КеВе1еа5е5рт1_оск значения уровня 1Н<21_, отличного от значения, полученного ранее из вызова КеАсдшге5рт1_оск) привело к тому, что в \Л/1Пс1о\л/5 ХР появился новый тип спин-блокировок. Этот усовершенствованный тип объектов спин-блокировки получил название квид-спин-блокировок (вольный перевод термина диеиес/ зрт /оскз). Практически, помимо некоторого ускорения в работе, новый тип отличается для разработчика драйвера только тем, что уровень 1Р.<С>1_, предшествующий запросу спин-блокировки, сохраняется без его участия — он ассоциирован теперь с дескриптором, возвращаемым при запросе на владение спин-блокировкой. Можно сказать, что логически квид-спин-блокировка состоит из простой спин- блокировки и дескриптора, полученного при доступе к спин- блокировке при помощи соответствующего вызова, см. ниже. Тем не менее, нельзя смешивать работу над спин-блокировками при помощи разнотипных вызовов (то есть КеХхх(2иеиес15р1п1-оскХхх, см. ниже, и КеХхх5рт1_оскХхх).
Механизм использования квид-спин-блокировок предполагает следующую последовательность действий. Создание (получение области нестраничной памяти и инициализацию вызовом Ке1пк1а11хе5рт1-оск) объекта спин-блокировки обычным способом на уровне 1РХ21_ равном РА5517Е_1_Е7Е1_. При необходимости синхронизировать доступ к охраняемым данным следует получить право на владение объектом спин-блокировки вызовом КеАсяшге1п51аск<2иеиес15рт1-оск либо вызовом КеАсяшге1п51аск<2иеиес15рт1-оскА1Орс1-еуе1 (в зависимости от уровня 1РС21_ кода, из которого производится вызов — если код уже выполняется на уровне □15РАТСН_1_Е\/Е1_, то следует применять второй вызов). Примененный вызов возвращает (по адресу, переданному во втором параметре) дескриптор полученной спин- блокировки, которая к этому моменту уже может считаться квид-спин-блокировкой. При этом текущий программный код безусловно приобретает уровень О15РАТСН_1_Е\/Е1_. Выполнив необходимую работу над совместно используемыми данными, драйвер должен (максимально быстро) освободить спин-блокировку либо вызовом КеЯе1еабе1п5<:аск(2иеиес15рт1-оск, либо вызовом КеЯе1еа5е1п$1аск(2иеиес15р1п1-оскЕготОрс1-еуе1, в зависимости от того, как был получен доступ к объекту спин-блокировки ранее. Единственным параметром, который передается этим вызовам, является дескриптор квид-спин-блокировки, полученный ранее. Дескриптор полученной квид-спин-блокировки должен сохраняться в переменной типа К1_ОСК_(2иЕ11Е_НАЫО1_Е, локальной (если позволяет логика работы текущей процедуры драйвера) или размещенной в области нестраничной памяти, полученной, например, при помощи вызова ЕхА11оса(еРоо1.

Взаимоблокировки Взаимоблокировки (деасПоскз) могут возникать при недостаточно корректном использовании спин- блокировок, объектов событий, мьютексов и семафоров, то есть практически любых синхронизационных примитивов. Даже объекты потоков могут дать взаимоблокировку, если потоки ожидают окончания работы друг друга. Взаимоблокировки легко возникают там, где происходит состязание нескольких потоков за владение несколькими ресурсами, причем каждому из них нужно несколько единиц ресурсов одновременно — и это при том, что каждый поток может получить отказ при попытке доступа к каждому из ресурсов. Разумеется, каждый случай такой "неразберихи" уникален, тем более, что здесь может оказывать влияние еще один квази-ресурс — приоритет. Тем не менее, можно выделить три направления решения этой проблемы. Во-первых, можно указывать максимальное время ожидания (параметр Лтеои!:) при вызовах КеУУаНЕогХхх. Этот способ не так хорош, поскольку не устраняет логическую ошибку в алгоритме работы драйвера, однако, дает локальное решение проблемы. Второй способ состоит в пере-разбиении или укрупнении ресурсов, что сокращает "количество поводов" для разногласия. В-третьих, можно заставить все потоки производить получение ресурсов в одном и том же порядке. Скажем, два потока имеют в своем распоряжении по одному ресурсу и начинают спор о владении вторым (он,
разумеется, находится у конкурента). Проблемы не возникло бы, если оба потока решили, кому владеть первым ресурсом, победителю достался бы и второй, после чего он освободил бы оба ресурса, предоставив их конкуренту.

Заключение В данной главе были рассмотрены основы построения исполнительного рисунка программного кода режима ядра — способы запуска программных потоков, основные синхронизационные примитивы и системные вызовы для работы с ними. Использование программных потоков позволяет отойти от жесткой схемы "Диспетчер ввода/ вывода — рабочая процедура — Диспетчер ввода/ вывода" и реализовывать более гибкие алгоритмы, в том числе — с помощью объектов синхронизации. Следующая глава будет посвящена рассмотрению двух примеров драйверов, работающих с аппаратными прерываниями 1_РТ порта.
Глава 11
Обработка аппаратных прерываний Обработка прерываний в драйверах уже неоднократно рассматривалась ранее, см. гл. 6, 8 и 10. Однако это происходило в теоретическом аспекте, так что пришло время применить полученные сведения на практике. Данная глава посвящена рассмотрению двух вариантов несложного драйвера ЬРТРОкТ.зуз, работающего с реальными аппаратными прерываниями и позволяющего подробно и всесторонне рассмотреть их "живьем".
ГРгеу|ои81 ГИехП
Постановка эксперимента Как известно, аппаратные прерывания — это сигналы, поступающие по специальным линиям в процессор, ради которых процессор приостанавливает работу, сохраняет состояние отложенной работы (контекст) и приступает к обработке возникшей ситуации. Разумеется, при условии, что поступивший сигнал должным образом зарегистрирован, как требующий обработки специально на то предназначенным фрагментом кода, имеющим заранее оговоренный приоритет.
Тестовое приспособление СНескН ЬоорЬаск Оеу|се Читатель вправе задать вопрос: для повторения эксперимента, с каким бы то ни было драйвером и аппаратными прерываниями на его персональном компьютере, обязательно понадобится устройство, которое эти прерывания генерирует. Где его взять? Идея простых тестовых устройств, позволяющих тестировать параллельный порт (до сих пор все еще существующий в персональных компьютерах на пока еще существующей внутренней шине 15А) и получать в нем прерывания, возникла практически в момент появления параллельного порта. Одно из таких устройств называется "заглушка СЬескИ" (СйескН ЬоорЬаск Оеуюе), которую по настоящее время производит фирма ЗткИ М1сго БоШл/аге (см. Интернет сайт ). Данная конструкция неоднократно использовалась авторами книг по драйверам, например, Артом Бейкером и Джерри Лозано, и идеально подходит для практического ознакомления с процедурами обслуживания прерываний. Внутреннее устройство этого приспособления давно является всеобщим достоянием, и его несложно найти в Интернете. Самостоятельное "приготовление" доступно каждому и состоит в том, что следует взять стандартный 25-выводной та1е-разъем и замкнуть в нем 5 пар контактов. Схему для выполнения этой манипуляции можно взять, например, в книге Михаила Гука "Аппаратные средства 1ММ РС, Энциклопедия", 2-е издание, СПб, Питер, 2002, в разделе "Неисправности и тестирование параллельных портов". Однако чтобы не отвлекать читателя поисками столь простой схемы, приведем ее еще раз, см. рис. 11.1. Если сопоставить схему с информацией, приведенной ранее в таблице 5.1, Регистры интерфейса стандартного параллельного порта (БРР), то становится вполне понятной идея этого приспособления. Стандартный параллельный порт имеет регистр данных Ок (для ввода/вывода), регистр управления СР. (для вывода) и регистр состояния 5Р (для ввода). Согласно схеме 11.1, выполняя вывод в регистр данных и управления, можно получать выведенные данные в регистре состояния. Поскольку один из разрядов (один из пяти используемых для вывода) поступает на вывод АСК# (бит 6 регистра состояния, 5Р.6), предназначенный для получения сигнала о прерывании, то получается, что передать можно 4 бита информации и сигнал о прерывании. При выводе данных в регистры ОР и СР, они поступят снова в параллельный порт в регистр 5Р. Включив определенное воображение, можно считать, что к параллельному порту компьютера подключено "сложное" внешнее устройство, которое способно генерировать сигналы прерываний. Рис. 11.1 Разводка тестовой заглушки СЬескП, вид со стороны пайки
В соответствии со схемой рис. 11.1 и свойствами параллельного порта (режим 5РР), получаем следующие переносы данных. Бит СР.О (51гоЬе#, контакт 1, единичное значение бита соответствует низкому уровню на линии) поступает в регистр состояния как бит 5Р.4 (5е1ес1, контакт 13, высокий уровень напряжения на линии соответствует единичному значение бита, низкий — нулю в регистре), то есть СР..0=1 -> 5Р.4=0. Бит при передаче инвертируется. Бит СР.1 (Аи1о 1_Р#, контакт 14, единичное значение бита соответствует низкому уровню на линии) поступает в регистр состояния как бит 5Р.5 (Рарег Епс1, контакт 12, высокий уровень напряжения на линии соответствует единичному значение бита, низкий — нулю в регистре), то есть СР.1 = 1 -> 5Р.5=0. Бит при передаче инвертируется. Бит СР.2 (1п11#, контакт 16, нулевое значение бита соответствует низкому уровню на линии) поступает в регистр состояния как бит 5Р.6 (Аск#, контакт 10, высокий уровень напряжения на линии соответствует единичному значению бита, низкий — нулю в регистре), то есть СР.2=0 -> 5Р.6=0. Бит передается нормально. Бит СР.З (5е1ес11п#, контакт 17, единичное значение бита соответствует низкому уровню на линии) поступает в регистр состояния как бит 5Р.7 (Визу, контакт 11, низкий уровень в линии соответствует единичному значению бита), то есть СР.3=1 -> 5Р.7=1. Бит при передаче инвертируется дважды, то есть передается нормально. Бит ОР.0 (нулевой бит регистра данных, вывод 2) поступает в регистр состояния как бит 5Р.З (Еггог#, контакт 15, высокий уровень на линии соответствует единичному значению бита), то есть ОР.0=1 -> 5Р.З =1. Бит передается нормально. Таким образом, передача данных в заглушку с моментальным возвратом данных возможна по пол-байта, что и будет реализовано в примерах, приводимых далее. © В упомянутом тесте, описанном Артом Бейкером и Джерри Лозано, распознавание поступившего прерывания в 15В процедуре производится по значению бита ЗВ.2 (В1ВС), внутренний бит 2 регистра состояния ЗВ). В некоторых реализация параллельного порта этот бит действительно хранит флаг прерывания. Он имеет нулевое значение, если прерывание имело место (напряжение на выводе Аск#, ЗВ. 6, контакт 10, выполнило отрицательный перепад — переход из высокого в низкое значения при условии СВ.4 = 1). Единичное значение бита 5В.2 устанавливается по аппаратному сбросу или при чтении регистра состояния. Однако на всех компьютерах, доступных автору, параллельный порт в конфигурации 5ВВ вел себя, точно следуя первоисточникам (например, ]ап Ахе/зоп, ВагаНе! Вогё Сотр/еЕе), где указывается, что бит ЗВ. 2 не используется. Соответственно, тест Арта Бейкера и Джерри Лозано не работает без отключения строки кода, выполняющего эту проверку. Строго говоря, такому обстоятельству может быть и несколько иное объяснение. В УШпдоууз ХВ, где разрабатывались приведенные ниже примеры, тяжело отказаться от услуг всех системных компонентов, потенциально касающихся параллельного порта. Поэтому при тестах представляемых драйверов сам стандартный системный драйвер параллельного порта было решено не отключать, а именно он (его процедура обслуживания прерывания) и считывает регистр состояния ЗВ перед тем как получит управление 1зг процедура собственно испытываемых драйверов. Даже если сам порт и позволил бы использовать бит ЗВ, то все равно 1зг процедура собственно испытываемых драйверов могла бы считать только единичное значение бита ЗВ. 2 (что говорило бы об отсутствии прерывания).
Настройка операционной системы При работе с приводимыми тестовыми драйверами и заглушкой СИескИ использовалась операционная система \ЛЛпс1о\л/5 ХР. Несмотря на ее способности к автоконфигурированию, для проведения тестов потребовались некоторые изменения в ее настройках и настройках ВЮ5. Прежде всего, чтобы избежать разночтений и странных ошибок, следует выставить в ВЮ5 компьютера настройки 5РР параллельного порта по адресу 378 с использованием прерывания 7. Эти фиксированные настройки как раз и будет использовать драйвер. Во-вторых, после загрузки операционной системы следует обратиться к настройкам системного драйвера параллельного порта, который будет выполнять начальное инициирование параллельного порта без участия испытываемых драйверов. Для этого следует выполнить Пуск — Настройка — Панель управления — Система — Свойства системы — Диспетчер устройств — Оборудование — Порты (СОМ и 1_РТ) — Порт принтера (1_РТ). Запустив системный апплет "Свойства: Порт принтера (ЬРТ1)" следует проверить, что порту выделены ресурсы портов ввода-вывода (0378) и прерывания 7. Затем в закладке "Параметры порта" указать, что стандартный системный драйвер должен использовать прерывание, см. рисунок 11.2. Рис. 11.2 Настройки системного драйвера для использования прерываний Использование системного драйвера обусловлено тем, что в противном случае пришлось бы самостоятельно заниматься регистрацией ресурсов 1_РТ порта как устройства шины 15А. Теоретически это не является большим затруднением, однако на практике регистрация этих ресурсов всегда завершается неудачей, поскольку ресурсы оказываются выделенными другим системным компонентам. В данном случае испытываемые драйверы объявляют совместное использование прерывания, что не вызывает затруднений при их запуске и работе "рядом" с системным драйвером.
Используемые инструментальные программы Запуск драйверов выполнялся при помощи программы Мопког (из пакета Митеда □пуег 5Ьис1ю) после полной загрузки операционной системы. Этот простой способ запуск драйверов позволяет проследить все диагностические сообщения всех рабочих процедур испытываемых драйверов, начиная от ОпуегЕпЬгу и заканчивая процедурой Опуег11п1оас1. Сборка драйвера выполнялась в отладочной среде У\Лп 2К СЬескес! ВиПс! Епу1гоптеп1, что уже стало традицией для примеров данной книги. Драйвер собирался как 1_едасу Эпуег при помощи определений п(:с1с1к.И и простейшего файла Зоигсез: ТАКСЕТМАМЕ=ЬРТРогк ТАРСЕТТУРЕ=ОРТУЕР ТАКСЕТРАТН=. 1МСШЮЕЗ= $ (ВАЗЕМК) \1ПС; . 50иКСЕЗ=с1г2^ег. срр Компиляция драйверов как 1_едасу Опуег обеспечила отсутствие проблем при старте и остановке драйверов программой Моп^ог. Программный код драйверов размещен в двух файлах Опуег.срр (собственно исполняемые процедуры) и Опуег.Ь (заголовочный файл). Для двух вариантов драйверов, работающих с прерываниями, которые описываются в данной главе, исходные тексты приводятся ниже полностью по причине необходимости большого количества дополнительных комментариев. Поскольку драйверы компилировались в отладочной среде СОК, это позволило выводить отладочную диагностику при помощи условно компилируемых фрагментов вида: 0ВС==1 ВЬдРг1пС ( "ЬРТРОКТ: ТпРеггирТ %с1 соггуегСес! Со к1гд! = %с1, " "кАССРптСу = %с1, кУеског = %Х(кех)\п", р^еVЕxЬ->I^^, к!гд1, кАСС1П1Су, кУесбог) ; #епс11Г Сбор диагностики выполнялся программой ОеЬид\/1еуу, которая всегда запускалась до старта драйвера из программы Мопког. Это позволило собирать отладочные сообщения всех драйверных процедур, включая диагностику из ОпуегЕпЬгу. При работе с объектом события во втором варианте драйвера привлекалась программа \Л/1пОЬ), которая подтвердила создание именованного объекта события "Ва5еЫатедОЬ)ес1:5\1_РТР(ЖТ_Е\/Е1\1Т" в соответствующей ситуации. Кроме того, при отсутствии документированного описания должного уровня детализации на функцию 1оСгеа1е5упсНгот2а1юпЕуеп1 во всех версиях СОК (включая примеры) было предпринято дизассемблирование некоторых системных драйверов при помощи дизассемблера ЮА. Это позволило установить причину отказа данной функции от нормального завершения при вызовах с целью создания именованного объекта события в драйвере и правильно выполнить ее вызов.
Для интерпретации ошибок по их коду использовалась программа ЕггЬоок описанная ранее.
ГРгеу1ои8~| ГИехМ
Простейший драйвер для работы с прерываниями Драйвер, работающий с прерываниями, прежде всего, должен зарегистрировать и подключить к источнику объект прерывания, имея собственную процедуру обработки поступающих прерываний. В нашем случае, при работе с заглушкой СИескИ, драйвер сам выдает в порт комбинацию данных, которая вследствие наличия обратной связи Ск.2 -> 5к.б в заглушке СИескИ (она к этому моменту должна быть включена в системный 1_РТ порт) приводит к генерации прерывания и последующему вызову 1зг процедуры драйвера. После загрузки и старта драйвер пребывает в устойчивом состоянии и ожидает запросов от клиентов. В данном случае клиентом выступает консольное приложение, которое У\Лп32 вызовом Сгеа1еН1е открывает дескриптор для доступа к драйверу, после чего обращается к нему с запросом на запись данных. В тестирующем приложении это осуществляется при помощи \Л/1п32 вызова УУгНеЕПе. Соответствующий 1Р.Р пакет поступает в обработчик О15рак:йУ\/гке драйвера, который после проверки корректности запроса и перенесения данных во входной внутренний буфер драйвера, записывает в параллельный порт половину первого байта поступивших данных (это делает функция □оЫехНТапзГег), совмещая это с генерацией прерывания (функция Еогсе1п1:еггир1:). Такая запись приводит к переносу первого байта данных и к первому вызову функции обработки прерывания 1зг. Для безопасного доступа к данным запуск □оЫехНТапзГег оформлен в драйвере при поддержке системного вызова
Ке5упсЬгоп1геЕхеси11Оп, см. описание прототипа в таблице 10.14. Для соблюдения целостности передачи данных (а передача данных считается здесь завершенной, когда весь входной буфер данных "перекочует" в другой, выходной, внутренний буфер драйвера, предварительно пройдя через параллельный порт) драйвер отвергает все запросы от клиента, как на чтение (из внутреннего выходного буфера), так и на запись данных. Если по окончании обработки запроса на запись от клиента поступил новый запрос на запись, то оба внутренних буфера драйвера (входной и выходной) заполняются новыми данными заново, без какого-либо сохранения старых данных, хотя бы частично — данный драйвер имеет простейшую организацию и не "церемонится" с не полностью перенесенными данными. При обработке прерывания 1зг функция планирует вызов □рсРогТзг процедуры, которая получает управление, читает данные из регистра состояния, помещает их в буфер для хранения и запускает новый перенос (с генерацией прерывания). Таким образом, начиная с момента первой генерации прерывания до окончания всех данных, поступивших от клиента в одном запросе на запись, перенос половины первого байта продолжается переносом половины второго байта и т.п. По окончании передачи половины последнего байта и прохождении последнего прерывания, процедура ОрсЕогТзг, получив управление, выполняет последнее чтение из порта, но уже не вызывает □оЫехНТапзГег. Перенос данных закончен фактически. Формально же обработка запроса от вызова УУгНеЕНе могла бы закончиться раньше — после запуска □оЫехНТапзГег (разумеется, с подачи
КеЗупсНготгеЕхесиНоп) рабочая функция драйвера □|5ра1сИМп1е (обработчик запросов от УУгНеГПе) сразу же пытается завершить обработку 1РР пакета, поскольку ее основная задача — заполнить внутренний буфер и стартовать первый перенос. Однако в том случае, когда заглушка на месте, в работу поочередно вступают высокоприоритетные фрагменты кода (1зг, ОрсЕогТзг, функции, вызываемые с подачи КеЗупсНготгеЕхесиНоп), что более вероятно — перенос данных завершится еще до выхода из □|5ра1сИУ\/п1е. Лишь при отсутствии заглушки СИескИ прерывания не влияют на работу драйвера. Запуская тестовое приложение в отсутствие заглушки, можем наблюдать, что процедура □15ра1сИМп1е завершается, а при входе в □15ра1сИкеас1 драйвер видит, что предыдущий перенос не завершен (нет заглушки — нет способа генерировать прерывания и передавать данные), о чем драйвер и сообщает клиенту кодом ошибки 170, который программой ЕггЬоок расшифровывается как "Требуемый ресурс занят". Такое же сообщение можно увидеть и при повторных запусках тестового приложения (если заглушка СИескИ отсутствует).
ГРгеу|ои51 [Мех!],
Заголовочный файл Опуег.Н В заголовочном файле, относящемся к первому варианту драйвера, описывается структура расширения объекта устройства, которую разработчик драйвера определяет самостоятельно. В этой структуре сохранены имена устройства, символьная ссылка, внутренние рабочие буферы для данных, подлежащих выдаче в параллельный порт, и для данных, уже прошедших через параллельный порт. Данные второго буфера можно передать по запросу \Л/1п32 вызова ЯеайЕНе, при условии завершения переноса, "заказанного" предшествующим \Л/1п32 вызовом \Л/п1еЕПе. Размер рабочих буферов задается макроопределением МАХ_В11ЕЕЕИ_512Е. Первым полем структуры расширения объекта устройства является указатель на сам объект устройства. Это является общепринятой традицией "правописания" драйверов, поскольку достаточно часто указатель на расширение передается в качестве контекстных указателей разным процедурам, которые, в конечном счете, нуждаются и в получении ссылки на сам объект устройства. Здесь же, в структуре расширения, сохраняется указатель на создаваемый объект прерывания р!пЮЬ] и резервируется место под ОРС объект ОрсЕог1зг_ОЬ]ес1. Макроопределения УУп1:еСоп1:го1Ред151:ег, \Л/п1:еОа1аИед151ег и Иеас151:а1и5Иед151ег скрывают использование НА1_ определений, предназначенных для чтения и записи в порт ввода вывода, в данном случае в параллельный порт 378. //=================================================================== // Вгз^ег.Ь - заголовочный файл для драйвера обслуживания // заглушки СйескТб ( Вариант 1 ) // Ву ЗУР, 20 «Типе 2004 //===================== #ргадша опсе ехкегп "С" { #1пс1ис1е <ЫТООК.й> } МеФФпе МАХ_ВЮЕЕЕВ_312Е (32) бурейе^ збгисб _ОЕУ1СЕ_ЕХТЕМ31ОП { РОЕУ1СЕ_ОВЕЕСТ рОечФсе; ВМ1СООЕ_5ТК1МС из^^^еV^се^ате; // внутреннее имя устройства ВМ1СООЕ_5ТК1МС изкгЗушЫпкМаше; // внешнее имя (символьная с ЮСНАВ (ЗечФсеОи'ЬВиФФег [МАХ_ВЮГГЕВ_312Е] , // для вывода в устро с^еV^сеIпВиЕЕе^ [МАХ_ВЮЕЕЕК_512Е] ; // для получения из у ВЬОЫС хФегСоипк, // текущий передаваемый байт хФегВезб; // остаток непереданных байт (индикатор зав
//============================================= РЮСНАВ рогЕВазе; // адрес порта ввода/вывода ЮЬОЕС 1гд; // 1гд в терминах шины 18А для параллельног //============================================= РКЮТЕВВЮРТ р1пЕОЬ^; // гпбеггирб оЬ^есб КБРС ВрсЕог1зг_0Ь^есб; // БРС оЬ^есЕ } БЕУ1СЕ_ЕХТЕЕ810Е, * РБЕУ1СЕ_ЕХТЕЕ8 ЮЫ; // Маски для выделения бит в регистре управления: МеНпе СВ_ЕЮТ_В8Т 0x04 // СВ.2 - 0 Везеб рггпбег МеНпе СВ_ЮТ_ЕЕВ 0x10 // СВ. 4 - 1 1пбеггирб епаЫе МеНпе СВ_ВЕЕАЮЬТ ОхСО // неиспользуемые биты // Макроопределения для записи и чтения в параллельный порт МеПпе БАТА_ВЕС 0 МеПпе 8ТАТЮ8_ВЕС 1 МеПпе СОЕТВОЬ_ВЕС 2 МеНпе ЭДг1беСопбго1Вед1збег ( рБечгсеЕхбепзгоп, Ьубе ) \ ( ЭДВ1ТЕ_РОВТ_ЛСНАВ( рБечгсеЕхбепз1оп->рогЕВазе + СОЕТВОЬ_ВЕСЛ Ьу #бе11пе ЭДггбеВабаВедгзбег( рБечгсеЕхбепзгоп, Ьубе ) \ ( ЭДВ1ТЕ_РОВТ_ЛСНАВ( рБечгсеЕхЕепз1оп->рогЕВазе + ВАТА_ВЕСЛ Ьубе #бе11пе ВеабВЕаЕизВедгзбег( рБечгсеЕхбепзгоп ) \ ( ВЕАВ_РОВТ_ОСНАВ( рВеч1сеЕхбепз1оп->рогЕВазе + 8ТАТО8_ВЕС ) )
Исполняемый код драйвера В файле Эпуег.срр размещен исходный текст всех функций драйвера. В процедуре ОпуегЕп1гу выполняется регистрация процедур ОпуегОп1оас1 (отвечает за завершающие операции при выгрузке драйвера), 015ра1с11Сгеа1е (при получении клиентом дескриптора для доступа к драйверу), О15ра1:сКС1о5е (при закрытии дескриптора, полученного для доступа к драйверу), 015ра1:с11\Л/п1:е (обработка 1РР пакета, поступившего вследствие вызова УУгНеЕПе в приложении-клиенте), 015ра1:сКкеас1 (обработка 1РР пакета, поступившего вследствие вызова КеайЕНе в приложении-клиенте). Действия по созданию объекта устройства, символьной ссылки и подключению драйвера к прерыванию в данном 1_едасу драйвере тоже выполняются в ОпуегЕп1гу, только лишь оформлены они в виде автономной функции Сгеа1еОеУ1се. (В \ЛЮМ драйвере реального РпР устройства эти операции следовало бы выполнять в процедуре Ас1с1ОеУ1се и обработчике 1КР_МЗ_РЫР + 1КР_ММ_5ТАКТ_ОЕ\/1СЕ, поскольку загрузка драйвера является только частью старта РпР устройства). //=================================================================== // Файл йг^ег.с // Драйвер обслуживания заглушки СйескТб (параллельный порт 378к) // Ву ЗУР, 20 Дипе 2004 //=================================================================== #1пс1ис1е "бгтчег.й" // Предварительные объявления функций збабтс ИТЗТАТДЗ СгеабеОечФсе ( га РЭК1УЕК_ОВДЕСТ га пьомс га дьоыо збабФс ЫТЗТАТДЗ ОФзрабсйСгеабе ( 1Ы РОЕУ1СЕ_ОВДЕСТ збабтс ИТЗТАТДЗ ВтзрабсйСТозе ( га РЭЕУ1СЕ_ОВДЕСТ збабтс УО1Б Ог^егУп1оас1 ( га РЭК1УЕК_ОВДЕСТ збабФс ЫТЗТАТДЗ ОФзрабсйИгФбе ( 1Ы РОЕУ1СЕ_ОВДЕСТ збабтс ЫТЗТАТДЗ ОтзрабсйВеас! ( 1Ы РОЕУ1СЕ_ОВДЕСТ рОг±чегОЬ)есб, рогбВазе, 1гд ) ; р^еVОЬ^, га Р1ВР р рОечОЬ/, га Р1КР р рОгтчегОЬ]есб ) ; р^еVОЬ^, га Р1ВР р р^еVОЬ^, га Р1ВР р ВООЬЕАД 1зг га РКгаТЕККДРТ р1пбеггирбОЬ]есб, га РУО1Б рЗе^ФсеСопб ВООЬЕАЫ ОоЫехбТгапзФег ( 1Ы РУО1Р рСопбехб ) ; УО1Э ЭрсЕог1зг( га РКЭРС рБрс, га РУО1В ВеФеггесЮоп'Ьех'Ь, га РУОЮ рАгд1, га РУОЮ рАгд2 ) ; //=================================================================== // Функция: Сгз^егЕп'Ьгу // Назначение: Инициализирует драйвер, подключает объект устройства // получения прерываний. // Аргументы: рБгФчегОЬ/есб - поступает от Диспетчера ввода/вывода // рКедтзбгуРабЬ - указатель на Юникод-строку,
// обозначающую раздел Системного Реестра, созданный // для данного драйвера. // Возвращаемое значение: // НТ8ТАТН8 - в случае нормального завершения 8ТАТН8_8НС // или код ошибки 8ТАТЮ8_Ххх // ехбегп "С" ЕТ8ТАТО8 БггчегЕпбгу ( ТО РВВ1УЕВ_ОВЙЕСТ рБггчегОЬ]есб, га Р0ШС0ВЕ_8ТВгаб рРедгзбгуРага ) { ЕТ8ТАТО8 збабиз; #йЕ ВВС==1 БЬдРгйпб("ЬРТРОВТ: 1п БгйчегЕпбгу, ВедйзЕгуРабй 1з:\п %мз рВед±зЕгуРабй->ВиРРег); #епсНР // Регистрируем рабочие процедуры драйвера: рБгйчегОЬ^ есЕ->ВгйчегНп1оаб = ВгйчегНп1оаб; рБгйчегОЬ^ есб->Ма^огЕипсбйоп[1ВР_МЙ_СВЕАТЕ] = ВйзрабсйСгеабе; рБгйчегОЬ^ есб->Ма^огЕипсбйоп[1ВР_МЙ_СЬО8Е] = ВйзрабсИСЕозе; рБгйчегОЬ^ есб->Ма^ огЕипсбйоп [ гаР_МЙ_ЭДВ1ТЕ] = ВгзрабсШггбе ; рБгйчегОЬ^ есб->Ма^ огЕипсбйоп[1ВР_МЙ_ВЕАВ] = ВйзрабсИВеаб; // Работа по созданию объекта устройства, подключению // ресурсов, прерывания, созданию символьной ссылки: збабиз = СгеабеБечйсе(рБгйчегОЬ^есб, 0x378, 0x7); геЕигп збабиз; } //=================================================================== // Функция: СгеабеБечйсе // Назначение: Создание устройства с точки зрения системы // Аргументы: рБгйчегОЬ^есб - поступает от Диспетчера ввода/вывода // рогЕВазе - адрес базового регистра параллельного порта // 1гд - прерывание (в терминах шины 18А) для обслуживания // Возвращаемое значение: // ЕТ8ТАТЮ8 - в случае нормального завершения 8ТАТН8_8НССЕ // или код ошибки 8ТАТЮ8_Ххх // ИТ8ТАТН8 СгеабеБечйсе ( га РВВ1УЕВ_ОВЙЕСТ рБгйчегОЬ]есб, га НЬОНС рогЕВазе, га НЬОНС 1гд ) { ИТ8ТАТН8 збабиз; РБЕУ1СЕ_ОВЙЕСТ рБечОЬ]; РВЕУ1СЕ_ЕХТЕН81ОИ рБечЕхб; // Создаем внутреннее имя устройства □ШСОБЕ 8ТВгаС бечИате;
В±11п±Ебп±собеЗЕгйпд ( &бечИател Ь"\\Беч±се\\ЬРТРОВТ" ) ; // Создаем объект устройства збабиз= ТоСгеабеБечбсе ( рБг^егОкд есбл збгеоР (БЕУ1СЕ_ЕХТЕИЗ ЮИ) , &бечИател Е1ЬЕ_БЕУ1СЕ_бИКИОПИ , О, ТРОЕ, &рБечОЬ^ 1Р (!ИТ_ЗБССЕЗЗ(збабиз)) геЕигп збабиз; // Будем использовать метод буферизации ВБЕЕЕНЕЕ_1О рБечОЬ^->Е1адз |= ЕО_ВБЕЕЕНЕЕ_1О; // Заполняем данными структуру Бечбсе Ехбепзбоп рБечЕхб = (РБЕУ1СЕ_ЕХТЕИЗЮИ)рБечОЬ^->Беч±сеЕхЕепз±оп; рВечЕхЕ->рБеч±се = рБечОЬ^; // сохраняем - это приго рВечЕхЕ->избгВеч±сеПате = бечПате; рБечЕхб->1гд = 1гд; рБечЕхЕ->рогЕВазе = (РИСНАВ)рогЕВазе; рБечЕхЕ->р1пЕОЬ^ = ПИЬЬ; рВечЕхЕ->хРегВезб = 0; // сейчас нет неотправленных дан рВечЕхб->р1пЕОЬ^ = ПИЬЬ; //================================================ // Инициализируем объект БРС для последующего использования // при обработки прерываний: КеТпбббаИгеБрс ( & (рВечЕхб->ВрсЕог1зг_0Ь^есб) Л БрсЕог1згЛ рБечЕхб // <- рБеРеггебСопЕехЕ в функции Бр //================================================ //На всякий случай блокируем поступление прерываний: ЭДгЮеСопЕгоЮедбзбег ( рБечЕхб, СВ_БЕЕАБЬТ ); //================================================ // Создаем и подключаем объект прерываний: К1В(2Ь к1гд1; КАЕЕ1П1ТХ кАРРйпйбу; БЬОПС кУесбог = На1СеЕ1пЕеггирЕУесЕог(1заЛ 0Л рБечЕхб->1гдЛ рБечЕхб-> &к1гд!Л &кАРР±п±Еу); // Замечание. Для 1за шины второй параметр (номер шины) обычн // равен 0Л а третий и четвертый параметры равны. #йЕ БВС==1 БЬдРгйпб( "ЬРТРОВТ: 1пбеггирб %б сопчегбеб ко к1гд! = "кАРРйпйбу = %бЛ кУесбог = %Х(йех)\п"Л рБечЕхб->1гдЛ к!гд!Л кАРРйпйбу, кУесбог);
#епббЬ зкакиз = 1оСоппесЫпкеггирк ( &рВечЕхк->р1пЬОЬ^ , // Здесь будет создан Тпкегги бзг, // Наша функция 13В рБечЕхб, // Этот указатель 13В функция будет // получать при вызове (контекстный у ПбЬЬ, // Не будем использовать зргп-блокиро // безопасного доступа к совместно ис // данным кУесбог, // транслированное значение прерывани к1гд1, // В1В(2Ь к1гд!, // В1В(2Ь ЬаксЬеб, // Прерывание по перепаду ТЕПЕ, // Совместно используемое (ЗНагеб) пр кАУУУпгку, // Поцессоров в мультипроцессорной си ЕАЬЗЕ ); // Не сохранять значения регистров со 6Р (!ПТ_ЗбССЕЗЗ(зкакиз) ) { //В случае неудачи удаляем объект устройства 1оВе1еЬеВечбсе( рБечОЬ^ ); гебигп збабиз; } #6Ь ЬВС==1 ВЬдРгбпЬ ( "ЬРТРОВТ : 1пЬеггирЬ зиссеззбиНу соппесбеб. \ #епббЬ //================================================ // Создаем символьную ссылку: ПШСОВЕ_ЗТВ1ПС зутЫпкПате; // Сформировать символьное имя: //#беЫпе ЗУМ_ЫПК_ПАМЕ Ь"\\??\\ЬРТРОВТО" // АА проходит только в ЫТ // Для того, чтобы работало в ЭДбпбомз 98 & ХР : #беЫпе ЗУМ_ЫПК_ПАМЕ Ь"\\ВозВечбсез\\ЬРТРОВТО" ВЬИпбЬПпбсобеЗЬгбпд ( &зутЫпкПате, ЗУМ_ЫПК_ПАМЕ ) ; // Создать символьную ссылку: збабиз = 1оСгеакеЗутЬоИсЫпк ( &зутЫпкПате, &бечПате ); 6Р (!ПТ_ЗбССЕЗЗ(зкакиз) ) { // При неудаче - отключаемся от прерывания и // удаляем объект устройства: боВбзсоппесЫпбеггирЬ ( рВечЕхб->р1пЬОЬ^ ) ; боВебебеВечбсе( рБечОЬ^ ); гебигп збабиз; }
рРечЕхЕ->изЕгЗутЫпкМате = зутЫпкЫате; #1Е ЭВС==1 ВЬдРггпЕ ("ЬРТРОКТ: ЗушЬоНс Ыпк 1з сгеаЕес!: %из. \п" рВечЕхЕ->изЕгЗушЫпкЫате . ВиЕЕег) ; #епс11Е геЕигп ЗТАТПЗ_ЗПССЕЗЗ; } Работа с ОРС процедурами может проходить по двум существенно различающимся сценариям. В первом из них, который будет реализован в следующем варианте драйвера, ОРС процедура соотносится с объектом устройства вызовом 1о1пШаН2еОрсЯеяие5(, и код драйвера может запланировать ее вызов путем применения ХоПедиезЮрс со ссылкой на объект устройства. Таким образом, за объектом устройства можно закрепить одну ОРС функцию. А сам ОРС объект "обитает" в объекте устройства и его не рекомендуется "касаться" непосредственно. Другой сценарий, реализуемый ниже, предлагает связывание ОРС объекта (неинициализированный ОРС объект — это просто область памяти под структурой типа КОРС) с одной из функций драйвера вызовом КеХпШаНхеОрс, см. таблицу 10.23. Такой инициализированный ОРС объект может быть вставлен в очередь ОРС объектов с помощью вызова КеХпзегН^иеиеОрс — так можно запланировать к вызову связанную с ним ОРС функцию драйвера в любом месте кода драйвера, правда, работающем при уровне 1К<Д)1_ не ниже О15РАТСН_1_Е\/Е1_. При использовании данного сценария, драйвер (в том числе, его процедура обработки прерывания) может планировать для последующего вызова разные ОРС функции. Заметим, что, временно повысив 1К1_<3 при помощи вызова КеЯа18еХгя1, драйвер может планировать вызовы ОРС функций при помощи КеХпзег^иеиеОрс даже внутри кода, работающего при 1Р<21_, равном РА5517Е_1_ЕУЕ1_. При работе по этому второму сценарию следует, однако помнить, что в условиях принудительной выгрузки драйвера в системной очереди не должно оставаться ОРС объектов от выгружаемого драйвера. Эта мера безопасности реализована ниже в процедуре ОпуегОп1оас1, когда для удаления ОРС объекта из системной очереди используется вызов КеКетоуериеиеОрс. //=================================================================== // Функция: 0гд^ег11п1оас1 // Назначение: Останавливает и удаляет объекты устройств, отключает // прерывания, подготавливает драйвер к выгрузке. // Аргументы: рЕггчегОЬ]есЕ - поступает от Диспетчера ввода/вывода // Возвращаемое значение: нет // УОЮ 0г1чегНп1оас1 ( РВК1УЕК_ОВПЕСТ рБгЕчегОЬ] есЕ ) { #1Е ОВС==1 БЬдРггпЕ("ЬРТРОКТ: 1П 0г1чег0п1оас1 пои\п"); #епсНЕ РВЕУ1СЕ_ОВНЕСТ рЫехЕОЕ^ = рВгЕчегОЬ]есЕ->Веч1сеОЬ]есЕ; // Проход по всем устройствам, контролирумым драйвером Еог( ; рИехЕОЬ]!=ЫПЬЬ; )
{ РБЕУ1СЕ_ЕХТЕН810Н рБечЕхР = (РБЕУ1СЕ_ЕХТЕМ810М) р^еx-^0Ь^->^еV^сеЕx-^еп8^оп; // Удаляем объект прерываний: гР (рБечЕхР->р1пРОЬ^ ) { // На всякий случай блокируем поступление пре // и очищаем БРО очередь от нашего БРО объект ЭДгРРеСопРгоРНедгзРег( рБечЕхР, СВ_БЕЕАБЬТ); КеНеточеОиеиеБрс( &(рБечЕхР->БрсЕог1зг_0Ь^есР 1оБ±зсоппесР1пРеггирР( рБечЕхР->р1пРОЬ^ ); } // Удаляем символьную ссылку: 1оБе1еРе8утЬо1РсЫпк ( &рБечЕхР->изРг8утЫпкНате) ; #1Р БВС==1 БЬдРггпР("ЬРТРОНТ: 8утЫпк %мз бе!еРеб\п", рБечЕхР->изРг8утЫпкНате .ВиРРег) ; #епсИР // Сохраняем ссылку на следующее устройство и удаляем // текущий объект устройства: рНехРОЬ^ = рНехРОЬ^->НехРБечгсе; РоБеРеРеБечгсе( рБечЕхР->рБечгсе ); } // Замечание. Поскольку мы использовали ресурс (параллельный // объявленные не нами, то освобождение этого ресурса можно о } //=================================================================== // Функция: БгзраРсНСгеаРе // Назначение: Обрабатывает запрос по поводу Жп32 вызова СгеаРеЕИе // Аргументы: рБечОЬ^ - поступает от Диспетчера ввода/вывода // р1гр - поступает от Диспетчера ввода/вывода // Возвращаемое значение: 8ТАТБ8_8БССЕ88 // НТ8ТАТБ8 БгзраРсНСгеаРе ( ТО РБЕУ1СЕ_ОВБЕСТ рБечОЬ], ТО Р1НР р1гр ) { #1Р БВС==1 БЬдРггпР("ЬРТРОНТ: 1п БгзраРсНСгеаРе пом\п”); #епсИР р1гр->1о8РаРиз.8РаРиз = 8ТАТБ8_8БССЕ88; р1гр->1о8РаРиз.РпРогтаРгоп = 0; //ни одного байта не передан 1оСотр1еРеНедиезР( р1гр, 1О_РЮ_1НСНЕМЕНТ ); геРигп 8ТАТБ8_8БССЕ88; } //=================================================================== // Функция: БРзраРсНСТозе // Назначение: Обрабатывает запрос по поводу Жп32 вызова СРозеНапб // Аргументы: рБечОЬ^ - поступает от Диспетчера ввода/вывода // р!гр - поступает от Диспетчера ввода/вывода
// Возвращаемое значение: 8ТАТН8_8ЬССЕ88 // ЫТ8ТАТН8 ВТзраксНСТозе ( ТО РБЕУ1СЕ_ОВЙЕСТ рБечОЬ], 1Н Р1ВР р1гр ) { #1Т ЬВС==1 ВЬдРггпк("ЬРТРОВТ: Ьп ВТзраксНСТозе по^\п"); #епс11Т р1гр->1о8какиз.8какиз = 8ТАТН8_8ЬССЕ88; р1гр->1о8какиз.Тпкогтакгоп = 0; //ни одного байта не передан 1оСотр1екеВедиезк( р1гр, 1О_ЬЮ_1НСВЕМЕНТ ); гекигп 8ТАТН8_8ЬССЕ88; } //=================================================================== // Функция: ВТзраксй^ггке // Назначение: Обрабатывает запрос по поводу Жп32 вызова ЭДггкеЕИе // Аргументы: рБечОЬ^ - поступает от Диспетчера ввода/вывода // р1гр - поступает от Диспетчера ввода/вывода // Возвращаемое значение: // ЫТ8ТАТН8 - в случае нормального завершения 8ТАТН8_8ЬС // или код ошибки 8ТАТЬ8_Ххх // ИТ8ТАТН8 ВгзраксМггке ( РВЕУ1СЕ_ОВЙЕСТ рБечОЬ] , Р1ВР р1гр ) { #1Т ЬВС==1 ВЬдРггпк("ЬРТРОВТ: 1п ВТзраксШггке пом\п"); #епс11Т Р1О_8ТАСК_ЬОСАТ1ОН р1гр8каск = 1оСекСиггепк1гр8каскЬосак±оп ( РВЕУ1СЕ_ЕХТЕН8ЮН рБечЕхк (РВЕУ1СЕ_ЕХТЕН8ЮН) рБечОЬ ]-ЮечгсеЕх ЬЬОНС хкегВгге = р1гр8каск->Рагатекегз.ЭДггке.Ье 1Т( хкегВгге == 0 ) // Нет данных для передачи : { #1Т ЬВС==1 ВЬдРггпЬ ( "ЬРТРОВТ : ВгзраЬсМгТЬе : по ЬуЬез ко #епсНТ р1гр->1о8Ьакиз.8Ьакиз = 8ТАТН8_8ЬССЕ88; р1гр->1о8Ьакиз. 1пЬогтаЫоп = 0; // Нет переноса 1оСотр1екеВедиезк( р1грЛ Ю_ЬЮ_1НСВЕМЕНТ ); гекигп 8ТАТН8_8ЬССЕ88; } 1Т( рВечЕхк->хЬегВезЬ> 0 ) { //Не начинаем обрабатывать новый запрос, если остали // непереданные данные (в буфере бечТсеОиЬВиЬЬег драй #±Ь ЬВС==1 ВЬдРггпк ( "ЬРТРОВТ : ВгзраЬсМгТЬе : пок а!1 бак #епсНТ р!гр->1о8какиз.8какиз = 8ТАТН8_ВЕУ1СЕ_ВЬ8Х;
р1гр->1о8ЕаЕиз.ЕпЕогтаЕгоп = 0; // Нет переноса 1оСотр1еЕеВериезЕ( р1гр, 1О_НО_1НСКЕМЕНТ ); геЕигп 8ТАТБ8_ВЕУ1СЕ_ВБ8У; } йЕ( хЕег8йге > МАХ_ВБЕЕЕВ_812Е ) { // Слишком большой запрос. Завершаем обработку 1ВР па #ФЕ ВВС==1 БЬдРгЕпЕ( "РЬРТРОВТ: ВгзраЕсШгЕЕе: хЕег8йге #епсНЕ р1гр->1о8ЕаЕиз.8ЕаЕиз = 8ТАТБ8_1Н8БЕЕ1С1ЕНТ_ВЕ8ОБВСЕ8 р1гр->1о8ЕаЕиз.ЕпЕогтаЕгоп = 0; // Нет переноса 1оСотр1еЕеВедиезЕ( р1гр, 1О_ЕЮ_1НСВЕМЕНТ ); геЕигп 8ТАТБ8_1Н8БЕЕ1С1ЕНТ_ВЕ8ОЖСЕ8 ; } // Буфер с данными, поступивший от клиента, переносим в // рабочий буфер: РБСНАВ изегВиЕЕег = (РБСНАВ)р1гр->АззосйаЕеб1гр.8узЕетВиЕЕег; ВЕЮоруМетогу ( рВечЕхЕ->бечйсеОиЕВиЕЕег, изегВиЕЕег, хЕег8йге рБечЕхЕ->хЕегВезЕ = хЕег8йге; рБечЕхЕ->хЕегСоипЕ = 0; // Запускаем перенос данных в первый раз КеЗупсйгопЕгеЕхесиЕйоп( рБечЕхЕ->р1пЕОЬ^, БоБехЕТгапзЕег, рБечЕхЕ ); // Формально -- передача завершена: р1гр->1о8ЕаЕиз.ЕпЕогтаЕйоп = хЕег8йге; 1оСотр1еЕеВедиезЕ( р1гр, 1О_ЕЮ_1ЕСВЕМЕЕТ ); геЕигп 8ТАТБ8_8БССЕ88; } Обработчик запросов от \Л/| п32 вызова А/Уп1еН1е переносит данные во внутренний буфер с1еу|сеОи1:ВиГГег и инициирует процесс переноса вызовом ОоЫехЕГгапзГег при посредничестве КеЗупсготгеЕхесиНоп. Последний повышает текущий уровень Ш(}1_ работы до уровня, ассоциированного с объектом прерывания, указанного в качестве первого параметра рОеуЕх1>>р1пЮЬ]. В результате (это будет видно позже в распечатке 1од-файла из программы ОеЬидХЛем) код функции ОоЫехПгапзГег выполняется на уровне 1К(Д)1_ равном 1К(Д)1_ кода функции КеасЮа1:а5аГе1у и кода функции 1зг, которые равны 8 в данном тесте. //=================================================================== // Функция: БЕзраЕсйВеас! // Назначение: Обрабатывает запрос по поводу Жп32 вызова КеабЕИе // Аргументы: рБечОЬ^ - поступает от Диспетчера ввода/вывода // р1гр - поступает от Диспетчера ввода/вывода // Возвращаемое значение: // БТ8ТАТБ8 - в случае нормального завершения 8ТАТБ8_8БС
// или код ошибки 8ТАТП8_Ххх // ЫТ8ТАТШ ОйзраЕсНВеаб ( ТО РБЕУ1СЕ_ОВЙЕСТ рБечОЬ^, 1К Р1ВР р1гр ) { РБЕУ1СЕ_ЕХТЕЕ8ЮИ рБечЕхЕ = (РБЕУ1СЕ_ЕХТЕЕ810К) рБечОЬ^->Веч±сеЕхЕепзйоп; Р1О_8ТАСК_ЬОСАТ1ОЕ р1гр8Еаск = 1оСеЕСиггепЕ1гр8ЕаскЬосаЕйоп( ПЬОКС хЕегВйге, хЕеггебВйге; #1Р ВВС==1 БЬдРгйпЕ("ЬРТРОВТ: 1п ВйзраЕсНВеаб пом\п"); #епсНР ФЕ ( рВечЕхЕ->хЕегВезЕ> 0 ) { //Не начинаем обрабатывать новый запрос, если остали // непереданные данные #ФЕ ВВС==1 БЬдРгйпЕ("ЬРТРОВТ: БЕзраЕсНВеаб: ЕхФзЕз попрг #епбФЕ р1гр->1о8ЕаЕиз.ВЕаЕиз = 8ТАТН8_ВЕУ1СЕ_ВН8Х; 1оСотр1еЕеВедиезЕ( р1гр, 1О_КО_1КСВЕМЕКТ ); геЕигп 8ТАТН8_ВЕУ1СЕ_ВН8Х; } // Определяем размер запроса: хЕегВйге = р1гр8Еаск->РагатеЕегз.Реаб.ЬепдЕН; ФЕ( хЕегВйге > МАХ_ВНЕЕЕВ_812Е ) хЕегВйге = МАХ_ВНЕЕЕВ_812Е; // Передаем не более данных, чем размер числа переданных байт хЕеггебВФге = рБечЕхЕ->хЕегСоипЕ; хЕегВФге = (хЕегВФге < хЕеггебВФге ? хЕегВФге : хЕеггебВФге ) ФЕ(хРег8йге> 0) { // Копируем содержимое внутреннего входного буфера // в буфер клиента РУОЮ изегВиРРег = р1гр->Аззосйакеб1гр.ВузкетВиРРег; ВЕЮоруМетогу (изегВиРРег, рВечЕхк->беч±се1пВиРРег, хЕ } // Завершаем обработку 1ВР пакета: р1гр->1о8Еабиз.ВЕабиз = 8ТАТП8_8ПССЕ88; р1гр->1о8Еабиз.1пЕогтабйоп = хЕегВйге; // число переданных б 1оСотр1ебеВедиезЕ( р1гр, Ю_КО_1КСВЕМЕКТ ); #йЕ ВВС==1 ВЬдРгйпЕ("ЬРТРОВТ: ВйзрабсНВеаб: %б Ьубеб ЕгапзЕеггеб #епбйЕ геЕигп 8ТАТП8_8ПССЕ88; } //===================================================================
// Процедура обслуживания прерывания: // ВООЬЕАП 1зг (та РКтаТЕККПРТ р1пбеггирЬ0Ь]есб, та РУО1Б рЗе^гсеСопбех { РБЕУ1СЕ_ЕХТЕПЗ 10И рБечЕхб = (РБЕУ1СЕ_ЕХТЕП31ОП) рЗе^гсеСопбе К1Н(2Ь сиггепЫгд! = КеСеЬСиггепЫгд! () ; #1Р ЬВС==1 БЬдРгГпб("ЬРТРОВТ: 1п 1зг ргосебиге, 13В_1гд1=%с1\п", #епс11Г //=========================================================== // Строго говоря, следовало бы проверить, имело ли место // прерывание и наше ли это прерывание: // ПСНАВ зЬабиз = ВеабЗЬаЬизВедГзЬег( рБечЕхб ); // 1Р( зЬабиз & 0x04 ) гебигп ЕАЬЗЕ; // прерывания не было // Однако в силу упомянутых накладок с использованием бита ЗВ // не проверяем это вовсе, делая допущение, что если 1зг полу // управление, то прерывание ^наше. //=========================================================== // Общей практикой является блокирование поступления прерывай // в этом месте: // ЭДггЬеСопЬгоЦРедгзбег( рБечЕхб, СВ_БЕЕАПЬТ); // Однако, мы не будем этого делать, чтобы не испортить данны // находящиеся сейчас в Збабиз Ведгзбег. Полагаем, что кроме // нашего драйвера такие прерывания никто не генерирует, а др // защищен тем, что ЭДггбе запросы отвергаются до полного пере // данных. //=========================================================== // Планируем вызов БРО процедуры для обработки прерывания поз // Ке1пзегЬ0иеиеБрс( &(рВечЕхЬ->БрсЕог1зг_0Ь^есб), (УО1Б *)ППЬЬ, // <- Агд1 1п БрсЕог1зг (УО1Б *)ППЬЬ); // <- Агд2 1п БрсЕог1зг гебигп ТЕПЕ; // нормальное завершение обработки прерывания } Процедура обработки прерывания 1зг планирует вызов ОрсРогТзг ОРС функции через размещение ОРС объекта в системной очереди. Этим ее функции и ограничиваются в столь простом драйвере. //=================================================================== // Код, который посылает в порт данные, вызывающие (при наличии // СЬеск1Ь заглушки) сигнал прерывания: // УО1Б Еогсе1пбеггирЬ( РБЕУ1СЕ_ЕХТЕП31ОП рБечЕхб, ПСНАК Ыбз ) { // Генерируем сигнал прерывания
^гйбеСопЕгоЕВедйзбег( рБечЕхб, Ыбз | СК_ЮТ_ЕЕВ | СВ_БЕЕАБЬТ Ке8Еа11ЕхесиЕ1опРгосеззог ( 50) ; // Удерживаем состояние 50 мкс // Удерживая информационные биты, снимаем импульс АСК# ^гйбеСопЕгоЕВедйзбег( рБечЕхб, Ыбз | СК_ЮТ_ЕЕВ | СВ_ЕЮТ_В8Т Ке8Еа11ЕхесиЕ1опРгосеззог(50); // Удерживаем состояние 50 мкс // Удерживая информационные биты, снимаем импульс АСК# ^гйбеСопЕгоЕВедйзбег( рБечЕхб, Ыбз | СВ_ЮТ_ЕКВ | СВ_БЕЕАБЬТ } //=================================================================== // Функция БоКехЕТгапзРег безопасно (от вмешательства кода прерывания // функции) записывает данные в параллельный порт: // ВООЬЕАИ БоКехЕТгапзРег ( 1И РУСЮ рСопЕехЕ ) { РВЕУ1СЕ_ЕХТЕИ81ОИ рБечЕхб = (РБЕУ1СЕ_ЕХТЕК81ОК)рСопЕехЕ; БСНАВ. пехЕВубе = ОхОЕ & ( рВечЕхб->беч1сеОиЕВиР1ег[рВечЕхЕ->х1егСоипб] #ФР ВВС==1 БЬдРгйпб("ЬРТРОВТ: БоКехЕТгапзВег: \п"); БЬдРгйпб("ЬРТРОВТ: 8епс11пд 0х%02Х Ео рогб %Х\п", пехЕВубе, рВечЕхб->рогЕВазе); #епс11Е // Отправка полубайта данных. //= 1 ======================================================= // Заглушка Сйеск1Е работает не самым простым образом. // Бит 0 отсылаемого полубайта нужно отправить как бит 0 // в Баба Ведйзбег ЭДгйЕеВабаВедйзЕег ( рБечЕхб, пехЕВубе & 0x01); // // Это бит будет считан как бит 3 из 8Еабиз Ведйзбег //= 2 ============================= // Биты 1-3 отсылаемого полубайта нужно отправить как // биты 0, 1 и 3 в Сопбго! Ведйзбег БСНАВ Ыбз = (пехЕВубе & 0x8) + ((пехЕВубе & 0хб)>> 1); // Таким образом бит 2 всегда равен 0 Ыбз А = 0x3; // Инвертируем биты (0 & 1) перед // записью в Сопбго! Ведйзбег // Эти биты будут считаны в 8Еабиз Ведйзбег как биты 4,5 и 7 #И БВС==1 БЬдРгйпб("ЬРТРОВТ: депегабйпд пехб йпбеггирЕ...\п"); #епбИ // Собственно отправляем данные вместе с генерацией // сигнала прерывания: Еогсе1пбеггирб ( рБечЕхб, Ыбз ); геЕигп ТЕБЕ;
//=================================================================== // Функция ВеабВаОаЗаОеТу выполняет чтение данных из устройства // без опасения быть прерванной кодом 13В функции: // ВООЬЕАИ ВеабБабаЗаОеТу ( ТО РУОЮ рСопЬехЬ ) { РВЕУ1СЕ_ЕХТЕЕЗЮИ рБечЕхб = (РВЕУ1СЕ_ЕХТЕН31ОН)рСопЬехЬ; 6СНАВ зЬаЬиз = ВеабЗЬаЬизВедОзЬег( рБечЕхб ); // Преобазуем полученные через ЗЬаЬиз гедОзбег биты в понятну 6СНАВ геабВубе = ((зЬаЬиз & 0х8)<< 1) | ((зЬаЬиз & 0х30)<< 1) | (зЬаЬи геабВубе >>= 4; рВечЕхб->беч1се1пВи00ег[рВечЕхЬ->хОегСоипЬ++] = геабВубе; #Ю ЬВС==1 К1В(2Ь сиггепЫгд! = КеСеЬСиггепЫгд! () ; ВЬдРгбпЬ( "ЬРТРОВТ: ВеабБабаЗаОеТуг сиггепЫгд1=%б Ве " ВеабВуЬе = %02Х\п"л сиггепЫгд!, зЬаЬи ВЬдРгбпб( "ЬРТРОВТ: \п"); #епббО рВечЕхЬ->хОегВезЬ--; // Число непереданных байт уменьшилось геЕигп (рВечЕхЬ->хОегВезЬ< 1 ? ЕАЬЗЕ : ТВОЕ ); // это значение возвра // через вызов КеЗупсЬгопйгеЕхесиЫоп } //=================================================================== // Функция: БрсЕог1зг // Назначение: Данная функция начинает работу по "заказу" 13В функции // и выполняет ввод/вывод путем безопасного вызова функп // ВеабБабаЗаОеТу и ВоНехЬТгапзОегл то есть реализует // низкоуровневый ввод/вывод // Аргументы: Указатель на текущий БРС объект (не используется) // рБеОеггебСопЬехО - контекстный указатель - так передае // указатель на структуру Бечбсе ЕхЬепзбоп // Возвращаемое значение: нет // УОЮ БрсЕог1зг( РКБРС рБрс, // не используется РУОЮ рВеОеггебСопЬехЬ г РУОЮ рАгд1г // не используется РУОЮ рАгд2 // не используется { РВЕУ1СЕ_ЕХТЕЕЗЮЕ рБечЕхЬ = (РВЕУ1СЕ_ЕХТЕН31ОН)рБеОеггебСопЬе #Ю ЬВС==1
К1К(2Ь сиггепЕ1гд1 = КеСеЪСиггепЪ1гд1 () ; БЬдРгтпЕ("ЬРТРОВТ: Же аге пом ±п БрсЕог1зг, сиггепЕ1г сиггепЕ1гд1, рВечЕхЕ->хРегСоипЕ ) ; #епсИЕ // Безопасно читаем данные из устройства: ВООЬЕАБ с1аЕаЕоЕТгапзРеггегес1 = КеЗупсйгоптгеЕхеси'Ыоп( рВечЕхЕ->р1пЕОЬ^ г НеасЮаЕаЗаЕеЕуг рБечЕхЕ ); тЕ ( с1аЕаБоЕТгапзРеггегес1 ) // остались непереданные данные { // Если остались данные, то записываем следующую поры // данных в порт и запускаем прерывание: КеЗупсйгоптгеЕхеси'Ыоп( рВечЕхЕ->р1пЕОЬ^, ЕоБехЕТгапзЕег, рБечЕхЕ ) ; } е!зе { #1Е ЕВС==1 БЬдРгтпЕ ("ЬРТРОВТ: 1/\1е аге пом 1п БрсЕог1зг, а #епсНЕ } } //
Приложение для тестирования драйвера Простое консольное приложение выполняет несложные операции в последовательной манере. Здесь нет сложных одновременных обращений из многих потоков. Тестирование сводится к передаче в драйвер 17 байт и последующему ожиданию от него ответа, размером 34 байта. Разумеется, драйвер может возвратить только полученные ранее данные, ни байтом больше. Сборку приложения можно осуществлять при помощи такого файла Зоигсез: ТАВСЕТБАМЕ^ЕезЕ ТАНСЕТТУРЕ=РНОСНАМ БМТУРЕ=сопзо1е БМЕБТНУ=та1п БМВА8Е=0х4О ОООО ТАНСЕТРАТН=. 1БСЪББЕ8= $(ВА8ЕБ1Н)\1пс 8ОБНСЕ8=ЕезЕ.срр А это, собственно, исходный код тестового приложения: //=================================================================== // Файл тестовой программы ЕезЕ.срр //=================================================================== #1пс1ис1е <м±пс1омз . Ь> #1пс1ис1е <зЕсИо. Ь> // Предварительное объявление тпб Неаб^ггЕе (НАББЬЕ с1еVНапс11е) ; МеПпе ВБЕЕ812Е (17) зВабгс ипзгдпеб сЬаг оиЕВиББег[ВБЕЕ812Е], гпВиУУег[ВБЕЕ812Е*2 ] ; 1пЕ __сбес! та±п() { рггпЕБ("\п\п\п\п\пРага11е1 РогЕ СЬеск1Е ЬоорЬаск ^еV^се Тезк НАББЬЕ аеVНапа1е; аеVНапа1е = СгеакеЕИе ( "\\\\.\\ЬРТРОНТО" , СЕБЕН1С_НЕАБ | СЕБЕН1С_^Н1ТЕ, О, // зЬаге тоае попе ББЬЬ, // по зесиггку ОРЕБ_ЕХ18Т1БС, Е1ЬЕ_АТТН1ВБТЕ_ПОНМАЬ, ИББЬ ); //по Еетр1аке
1Г ( ск^Напс11е == 1Н7АЫ0_НАЫ0ЬЕ_7АЬПЕ ) { ргтпГГ("Еггог: сап по!: ореп с^еV^се РЬРТРОКТО. И1п32 е ЗеГЬазкЕггог () ); геГигп -1; } ргтпГГ ( "СопдгаГиГаПоп . ЬРТРОКТО с^еV^се 13 ореп.\п\п"); //========================================== ОИОВО 1=3,3=0; Гог ( ; з<з1хеоГ (оиГВиГГег) ; ) оиГВиГГег []++] = (ипзГдпес! сЪ //========================================== //Гог(1=0; К 100000; 1++) //{ йпЕ гези!Е = КеасШгйЕе (ЬечНапЫе) ; // йЕ(гези!Е) Ьгеак; //} //========================================== // Завершение работы йЕ ( ! С1озеНапЫе (ЬечНапЫе) ) { ргйпЕЕ("\п Еггог Ьигйпд С1озеНапс11е: еггпо %с!.\п"л Се геЕигп 5; } ргйпЕЕ ("\п\п\п Бечйсе ЬРТРОВТО зиссеззЕиНу с1озес1. Иогта! ех геЕигп 0; } //=================================================================== // Выделим запись и чтение данных в отдельную функцию: // тпк НеаЬЭДгтке(НАЕБЬЕ ЬечНапсИе) { //========================================== // Передача данных драйверу ргтпЕЬ ( "ЭДгтЫпд ко ЬРТРОНТО Ьечтсе . . . \п" ) ; ШОНБ ЬуЕезЭДгтЕЕеп, оиЕСоипЕ = зтхеоЬ(оиЕВиЬЬег); ФР ( ! ЭДгткеЕИе (ЬечНапЫе, оиЕВиРРег, оиЕСоипЕ, &ЬуЕезЭДг±ЕЕеп { ргтпЕР ("Еггог Ьигтпд ЭДгткеЕЫе: еггпо %с!.\п"Л СеЕЬазЕ геЕигп 1; } йР ( оиЕСоипЕ != ЬуЕезЭДгйЕЕеп ) // если не все передалос { ргйпЕР ("Еггог: мЫ1е мгоке %с1 Ьукез, ЭДгйкеЕЫе герогк оиЕСоипЕ, ЬуЕезЭДгйЕЕеп); геЕигп 2; }
ргйпЕЕ("8иссеззЕи11у мгйЕЕеп %Ь ЬуЕез.\п ВиЕЕег сопЕепЕ шз: оиЕСоипЕ); Вог (БЭДОВБ 1=0; 1<ЬуЕезЭДгйЕЕеп; й++ ) ргйпЕЕ("%02Х ",оиЕВиЕЕе //========================================== //81еер(10); // Ожидание 10 миллисекунд //========================================== // Получение данных из драйвера ргйпЕЕ ( "\п\пКеас!1пд Егот Ьечйсе ЬРТРОВТО...\п"); БЭДОВБ ЬуЕезВеаЬ, ЕпСоипЕ = зйгеоЕ(йпВиЕЕег); ЕЕ ( ! ВеаЬЕНе (ЬечНапЫе, йпВиЕЕег, ЕпСоипЕ, &ЬуЕезВеаЬ, ИБЫ { ргйпЕЕ("Еггог Ьигйпд ВеаЬЕНе: еггпо %Ь.\п", СеЕЬазЕЕ геЕигп 3; } ЕЕ ( ЬуЕезВеаЬ != ЬуЕезЭДгЕЕЕеп ) { // размер записанных и прочитанных данных не совпадает ргЕпЕЕ("Еггог: Ез Ео геаЬ %Ь ЬуЬез, ЬиЕ ВеаЬЕНе геро ЬуЕезЭДгЕЕЕеп, ЕпСоипЕ); геЕигп 4; } ргЕпЕЕ("8иссезЕи11у геаЬ %Ь ЬуЕез.\п ВиЕЕег сопЕепЕ йз: \п", ЬуЕезВеаЬ); йог ( Е=0; й<ЬуЕезВеаЬ; Е++ ) ргЕпЕЕ( "%02Х ", (БСНАВ)ЕпВиЕЕе геЕигп 0; // Нормальное завершение } При запуске тестовой программы в консольном окне наблюдаем вывод на экран следующих сообщений: Ь:\#ех_1рЕ\ЕезЕ\>ЕезЕ Рага11е1 РогЕ СНеск1Е ЬоорЬаск БечЕсе ТезЕ Ргодгат. СопдгаЕийаЕйоп. ЬРТРОВТО ЬечЕсе йз ореп. ЭДгйЕйпд Ео ЬРТРОВТО ЬечЕсе... Зиссеззйиййу мгйЕЕеп 17 ЬуЕез. ВиЕЕег сопЕепЕ маз: 03 04 05 06 07 08 09 0А 0В ОС ОБ 0Е 0Е 10 11 12 13 ВеаЬЕпд Егот Ьечйсе ЬРТРОВТО... 8иссеззЕи11у геаЬ 17 ЬуЕез. ВиЕЕег сопЕепЕ йз: 03 04 05 06 07 08 09 0А 0В ОС ОБ 0Е 0Е 00 01 02 03 Бечйсе ЬРТРОВТО зиссеззЕиНу с!озеЬ. Иогта! ехйЕ. Поскольку через 4 разряда параллельного порта (что обусловлено конструкцией заглушки СЬескК) передается только младшая половина байта, то совпадение вторых
цифр в строках, наблюдаемых на экране, является хорошим результатом, демонстрирующим правильный перенос данных. Ниже приводится информация из отладочного вывода, перехваченного программой ЭеЬид\/|е\л/ (1од-файл этой программы). Средняя часть этого файла, сообщения с 28 по 90, опущена, поскольку в них содержится однообразная и малоинтересная информация. 00000000 00000001 00000002 00000003 00000004 00000005 00000006 00000007 00000008 00000009 00000010 00000011 00000012 00000013 00000014 00000015 00000016 00000017 00000018 00000019 00000020 00000021 00000022 00000023 00000024 00000025 00000026 00000027 00000091 0.00000000 0.00000223 0.00003911 0.00004833 0.00007878 5.38805379 5.38822867 5.38823342 5.38823985 5.38824487 5.38835690 5.38836388 5.38837115 5.38837422 5.38837785 5.38838260 5.38838763 5.38849854 5.38850524 5.38851167 5.38851446 5.38851781 5.38852228 5.38852731 5.38863822 5.38864465 5.38865135 5.38865442 ЬРТРОКТ: 1п Сгл^егЕпЬгу, КедтзРгуРа’Ыт 1з: \КЕО13ТКУ\МАСН1НЕ\ЗУЗТЕМ\СопЬго13еЬ001\Зе^1сез\ ЬРТРОКТ: ТпЬеггирЬ 7 сотгегЬес! Ьо к!гд1 = 8, кАТЫпЬЬу = 1, кУесЬог = 191 (кех) ЬРТРОКТ: Рпкеггирк зиссеззТиНу соппескес!. ЬРТРОКТ: ЗушЬоНс Ыпк Ьз сгеакес!: \^оз^еV^сез\^ ЬРТРОКТ: 1п ЫзракскСгеаке пои ЬРТРОКТ: 1п БРзракскИгНсе пои ЬРТРОКТ: ВоПехЬТгапзГег: ЬРТРОКТ: ЗепсНпдОхОЗ ко рогк 378 ЬРТРОКТ: депегаЫпд пехк Ьпкеггир ЬРТРОКТ: 1п 1зг ргосейиге, 15К_1гд1=8 ЬРТРОКТ: Ие аге пои Ьп ЭрсЕогТзг, сиггепЫгд1=2 ЬРТРОКТ: КеасЮа'ЬаЗаЬеЬу, сиггеп'Ыгд1=8 КеайЗкаки Кеас1ВуЬе=03 ЬРТРОКТ: ЬРТРОКТ: ВоПехЬТгапзГег: ЬРТРОКТ: ЗепсНпд 0x04 Ьо рогЬ 378 ЬРТРОКТ: депегаЫпд пехЬ Ьпкеггир ЬРТРОКТ: 1п 1зг ргосесЗиге, 15К_1гд1=8 ЬРТРОКТ: Ие аге пои Ьп ЭрсЕог1зг, сиггепЫгд1=2 ЬРТРОКТ: КеасЮа'ЬаЗаЬеЬу, сиггеп'Ыгд1=8 КеайЗкаки КеайВуЬе = 04 ЬРТРОКТ: ЬРТРОКТ: ВоПехЬТгапзГег: ЬРТРОКТ: ЗепсНпд 0x05 Ьо рогЬ 378 ЬРТРОКТ: депегаЫпд пехЬ Ьпкеггир ЬРТРОКТ: 1п 1зг ргосесЗиге, 15К_1гд1=8 ЬРТРОКТ: Ие аге пои Ьп 0рсЕог1зг, сиггепЫгд1=2 ЬРТРОКТ: КеасЮа'ЬаЗаЬеЬу, сиггеп'Ыгд1=8 КеайЗкаки КеайВуЬе = 05 ЬРТРОКТ: 5.38991464 ЬРТРОКТ: ОоЫехЬТгапзЬег: 00000092 00000093 00000094 00000095 00000096 5.38991939 5.38992442 5.39003533 5.39004175 5.39004874 ЬРТРОКТ: ЬРТРОКТ: ЬРТРОКТ: ЬРТРОКТ: ЬРТРОКТ: ЗепсНпд ОхОЕ Ьо рогЬ 378 депегаЫпд пехЬ ЬпОеггир 1п 1зг ргосесЗиге, 13К_1гд1=8 Ие аге пои Ьп ВрсЕогТзг, сиггеп'Ыгд1=2 хТегСоипЬ = 12 КеасЮабаЗаГе1у, сиггепЫгд1=8 КеасНОаРи
00000097 00000098 00000099 00000100 00000101 00000102 00000103 00000104 00000105 00000106 00000107 00000108 00000109 00000110 00000111 00000112 00000113 00000114 00000115 00000116 00000117 00000118 00000119 00000120 00000121 00000122 00000123 00000124 00000125 00000126 00000127 00000128 00000129 00000130 00000131 КеайВуке = 0Е 5.39005181 ЬРТРОКТ: 5.39005516 ЬРТРОКТ: ВоПехЫгапзЕег: 5.39005963 ЬРТРОКТ: Зепййпд 0x00 ко рогк 378 5.39006494 ЬРТРОКТ: депегаЕйпд пехЕ йпЕеггир 5.39017557 ЬРТРОКТ: 1п 1зг ргосейиге, 13К_1гд1=8 5.39018199 ЬРТРОКТ: Ие аге пои йп ОрсЕогТзг, сиггепЕ1гд1=2 хЕегСоипЕ = 13 5.3901887 0 ЬРТРОКТ: КеайВаЕа5аЕе1у, сиггепЕ1гд1=8 КеайЗЕаЕи КеайВуЕе = 0 0 5.39019177 ЬРТРОКТ: 5.39019512 ЬРТРОКТ: ОоЫехЕТгапзЕег: 5.39019959 ЬРТРОКТ: Зепййпд 0x01 Ео рогЕ 378 5.39020434 ЬРТРОКТ: депегаЫпд пехЕ ЙпЕеггир 5.39031469 ЬРТРОКТ: 1п 1зг ргосейиге, 15К_1гд1=8 5.39032112 ЬРТРОКТ: Ие аге пои йп ВрсЕогТзг, сиггепЫгд1=2 хЕегСоипЕ = 14 5.39032754 ЬРТРОКТ: КеайОаЕа5аЕе1у, сиггепЫгд1=8 КеайЗЕаЕи КеайВуЕе = 01 5.39033061 ЬРТРОКТ: 5.39033425 ЬРТРОКТ: ОоЫехЕТгапзЕег: 5.39033872 ЬРТРОКТ: Зепййпд 0x02 Ео рогЕ 378 5.39034374 ЬРТРОКТ: депегаЫпд пехЕ ЙпЕеггир 5.39045493 ЬРТРОКТ: 1п 1зг ргосейиге, 13К_1гд1=8 5.39046136 ЬРТРОКТ: Ие аге пои йп ВрсЕогТзг, сиггепЫгд1=2 хЕегСоипЕ = 15 5.39046778 ЬРТРОКТ: КеайОаЕа5аЕе1у, сиггепЫгд1=8 КеайЗЕаЕи КеайВуЕе = 02 5.39047058 ЬРТРОКТ: 5.39047393 ЬРТРОКТ: ЭоИехЕТгапзЕег: 5.39047840 ЬРТРОКТ: Зепййпд 0x03 Во рогЕ 378 5.39048315 ЬРТРОКТ: депегаЫпд пехЕ йпЕеггир 5.39059350 ЬРТРОКТ: 1п 1зг ргосейиге, 13К_1гд1=8 5.39059992 ЬРТРОКТ: Ие аге пои йп ОрсЕогТзг, сиггепЫгд1=2 хЕегСоипЕ = 16 5.39060663 ЬРТРОКТ: КеайВаЕаЗаЕейу, сиггепЫгд1=8 КеайЗЕаЕи КеайВуЕе = 03 5.39060942 ЬРТРОКТ: 5.39061333 ЬРТРОКТ: Ие аге пои йп ОрсЕогТзг, айй йаЕа Егапз 5.39104691 ЬРТРОКТ: йп ВйзраЕсЬКеай пои 5.39105166 ЬРТРОКТ: ВйзраЕсЬКеай: 17 Ъуйей ЕгапзЕеггей. 5.39139639 ЬРТРОКТ: йп ВйзраЕсЬС1озе пои 10.93612205 ЬРТРОКТ: йп ^^ЙVе^^п1оай пои 10.93615557 ЬРТРОКТ: ЗутЫпк \^оз^еV^сез\ЬРТРОКТ0 йе1ейей
Дополнительный тест на скорость переноса В приведенном выше консольном приложении, предназначенном для элементарного тестирования первого варианта драйвера, обслуживающего заглушку СКескК, имеется закомментированный фрагмент, который (если его включить в программу) позволяет многократно повторять элементарный тест по записи и считыванию данных из параллельного порта. Гог(1=0; ±<100000; 1++) ±пГ гези1Г = Кеас1Иг±ке (с1еVНапс^1е) ; ±Г(ге5и1Г) Ьгеак; Воспользовавшись системным апплетом "Производительность", и включив просмотр графика "Число прерываний в секунду", увидим, что при запуске тестирующего приложения резко возрастает нагрузка на систему, рис. 11.3. Рис. 11.3 График числа прерываний в секунду при интенсивном использовании драйвера, обслуживающего заглушку СЬескИ Если обратится к Диспетчеру задач \Л/1Пс1о\л/5 и посмотреть на график разделения времени процессора, расходуемого на обслуживание кода режима ядра и пользовательского режима, то увидим, что доля кода ядра в процентном отношении непривычно велика, см. рис. 11.4. Можно сразу же предположить, что все пользовательские процессы будут притормаживаться. И в самом деле, пока работает тестовое приложение в указанном интенсивном режиме "жизнь" в системе замирает — очень долго открываются и перемещаются окошки (время отклика на процессоре с тактовой частотой ЗГГц до 10 секунд), слегка "зависает" курсор мышки и т.п. Рис. 11.4 Окно Диспетчера задач в момент запуска тестирующего приложения в интенсивном режиме (работа в режиме ядра занимает I более 50% времени процессора) Проанализировав приведенную информацию, несложно сделать заключение, что описанный драйвер, вполне пригодный для изучения прерываний — поскольку показывает прерывания от момента их зарождения до момента вызова программного кода, отвечающего за их обработку, оказывается не столь хорошим для реальной жизни. "Расход" прерываний в отношении 1 прерывание на перенос 1/2 байта данных является непозволительной роскошью, которая приводит к существенной деградации системы. Разумеется, лучшим вариантом использования прерываний является их генерация устройством при готовности к переносу как можно большей порции данных. Это является общей закономерностью использования прерываний.
ГРгеу1ои8~| ГИехМ
Вариант 2. Модификация драйвера для работы с прерываниями И хотя второй вариант драйвера не улучшает соотношения "перенос/прерывание", в нем рассматриваются несколько приемов, которые часто встречаются в драйверах \Л/| пс!о\л/з, исходные тексты которых доступны для анализа. Прежде всего, это — использование системных очередей 1Р.Р пакетов для обеспечения сериализации при доступе к обслуживаемому устройству. Во-вторых, в приведенном ниже примере демонстрируется способ синхронизации работы драйвера и вызывающего его приложения по совместно используемому событию. В-третьих, используется более традиционная схема использования □РС процедур для завершения работы по поводу прерывания, которая связывает только одну ОРС процедуру с объектом устройства и использует вызовы 1о1пШаН2еОрсПедие81 и ТоВедиезЮрс. Наконец, практически показано, как можно использовать для хранения временной информации незадействованные поля 1Р.Р пакетов, здесь — Рагате1:ег5.Оеу|се1оСоп1то1.Туре31при1:ВиГГег в составе текущей (на момент обработки) ячейки стека 1Р.Р пакета. Помимо упомянутых отличий, в драйвере используется механизм ЮСТ1_ запросов от приложения для операций чтения и записи в устройство, тогда как обработчиков запросов от ХЛ/1П32 вызовов УУгНеГПе и Неас1П1е в данной реализации драйвера нет вовсе. Условия компиляции и сборки драйвера и тестирующего приложения не изменились.
ГРгеу|ои51 [Мех!],
Заголовочный файл Опуег.Н В заголовочном файле, относящемся ко второму варианту драйвера, также описывается структура расширения объекта устройства, которую разработчик драйвера всегда определяет самостоятельно. Дополнительно (по сравнению с первой версией драйвера) введены определения пользовательских (задаваемых разработчиком драйвера) ЮСТЬ кодов, предназначенных для применения при вызове \Л/т32 функции Оеу!се1оСоп1го1 в тестирующем приложении. Кроме того, в структуру расширения устройства внесены поля рЕуеп!: и ИЕуеп!:, предназначенные для хранения указателя на объект и дескриптора соответственно, описывающие событие, совместно используемые драйвером и тестирующим приложением для обеспечения синхронизации по моменту окончания вывода всех данных в порт. //=================================================================== // Бгйчег.й. - заголовочный файл для драйвера обслуживания заглушки СИ // ( Вариант 2 ) // Ву 8УР, 21 Йипе 2004 //=================================================================== #ргадта опсе ехбегп "С" { #1пс1ибе <ЕТВВК.й> } #беРйпе #беРйпе #беРйпе #беРйпе #беРйпе МАХ_ВЙ Е ЕЕ В_812 Е (32) 1ОСТЬ_8ЕЕВ_ТО_РОВТ Е1ЪЕ_ВЕ\71СЕ_ЙЫКЕСЖЫ, 1ОСТЬ_8ЕЕВ_ТО_Й8ЕВ Е1ЪЕ_ВЕ\71СЕ_ЙЫКЕСЖЫ, 1ОСТЬ_ТАКЕ_ЕУЕЕТ Е1ЪЕ_ВЕ\71СЕ_ЙЫКЕСЖЫ, 1ОСТЬ_СЬО8Е_ЕУЕЕТ Е1ЪЕ_ВЕ\71СЕ_ЙЫКЕСЖЫ, 0x801, 0x802, 0x803, 0x804, СТЬ_СОБЕ( \ МЕТНОВ_ВЙЕЕЕВЕВ, СТЬ_СОБЕ( \ МЕТНОВ_ВЙЕЕЕВЕВ, СТЬ_СОБЕ( \ МЕТНОВ_ВЙЕЕЕВЕВ, СТЬ_СОБЕ( \ МЕТНОВ_ВЙЕЕЕВЕВ, Е1ЬЕ_АЕУ_АССЕ88) Е1ЬЕ_АЕУ_АССЕ88) Е1ЬЕ_АЕУ_АССЕ88) Е1ЬЕ_АЕУ_АССЕ88) ЕуребеЕ зЕгисб _БЕУ1СЕ_ЕХТЕЕ81ОЕ { РБЕУ1СЕ_ОВЙЕСТ рБечйсе; ЙШСОВЕ_8ТВ1Е[(3 изЕгВечгсеЫате; // внутреннее имя устройства ЙШСОВЕ_8ТВ1Е[(3 избгЗутЫпкЕате; // внешнее имя (символьная с ЙСНАВ. ЬукеТоВеОикТоРогк, // следующий передаваемый ба бечгсеТпВиЕЕег[МАХ_ВЙЕЕЕР_512Е]; // данные, полученные ЙЬОЕЮ хЕегСоипк; // получено (байт) из порта и не передано к //============================================= // Только для применения в операциях передачи данных клиенту РЙСНАВ рйзегВиЕЕег; ЙЬОЕЮ хЕегЗйге;
//============================================= РЮСНАВ рогЕВазе;// адрес порта ввода/вывода ЮЬОЕС 1гд; // 1гд в терминах шины 18А для параллельного //============================================= РКЮТЕВВЮРТ р1пЕОЬ^; // гпбеггирб оЬ^есб //============================================= РКЕУЕЫТ рЕчепб; // ечепб оЬ^есЕ - объект события НАЕБЬЕ ВЕчепЕ; // представления объекта события через дескри } БЕУ1СЕ_ЕХТЕЕ810Е, * РБЕУ1СЕ_ЕХТЕЕ8 ЮЕ; // Маски для выделения бит в регистре управления: МеНпе СВ_ЕЮТ_В8Т 0x04 // СВ.2 - 0 ВезеЕ рггпбег МеНпе СВ_ЮТ_ЕЕВ 0x10 // СВ. 4 - 1 1пбеггирб епаЫе МеНпе СВ_БЕЕАБЬТ ОхСО // неиспользуемые биты // Макроопределения для записи и чтения в параллельный порт МеПпе БАТА_ВЕС 0 МеПпе 8ТАТБ8_ВЕС 1 МеПпе СОЕТВОЬ_ВЕС 2 МеНпе ЭДг1ЕеСопЕго1Вед1збег ( рБечгсеЕхбепзгоп, Ьубе ) \ ( ЭДВ1ТЕ_РОВТ_ЮСНАВ( рВеч1сеЕхбепз1оп->рогЕВазе + СОЕТВОЬ_ВЕСЛ Ьу #бе11пе ЭДггЕеВаЕаВедгзбег( рБечгсеЕхбепзгоп, Ьубе ) \ ( ЭДВ1ТЕ_РОВТ_ЛСНАВ( рБечгсеЕхбепз1оп->рогЕВазе + БАТА_ВЕСЛ Ьубе МеНпе ВеабВЕабизВедгзбег ( рБечгсеЕхбепзгоп ) \ ( ВЕАБ_РОВТ_ОСНАВ( рБеч1сеЕхЕепз1оп->рогЕВазе + 8ТАТО8_ВЕС ) )
ГРгеу|ои51 [Мех!],
Исполняемый код драйвера Как и в первом варианте драйвера, исходный текст всех функций драйвера представлен файлом Эпуег.срр. Часто повторяющийся код по завершению обработки 1НР пакетов собран в функции Сотр1е1е1гр. В процедуре ОпуегЕп1гу дополнительно (по сравнению с первым вариантом) выполняется регистрация функции 51агНо, которая будет использоваться при старте процесса обработки ТИР пакетов, откладываемых в системную очередь обработчиком ЮСТ1_ запросов с ЮСТ1_ кодом ЮСТ1__5ЕМО_ТО_РОИТ. Функция СгеаТеОеу1се по-прежнему выполняет создание объекта устройства, регистрацию символьной ссылки и подключение драйвера к прерыванию. Однако теперь иначе выполняется регистрация ОРС процедуры: при помощи вызова 1о1пШаН2еОрсЯеяие5( (таблица 8.14) с передачей ссылки на объект устройства создается ОРС объект в "недрах" системы. И тот код, который пожелает запланировать вызов функции ОрсЕогТзг (регистрируемой таким образом), должен выполнить вызов ХоПедиезЮрс. Еще раз следует отметить, что в УУОМ драйвере реального РпР устройства эти операции следовало бы выполнять в процедуре Ас1сЮеу|се и обработчике 1Р_М1_РЫР + 1РР_М1\1_5ТАРТ_0Е\/1СЕ, так как загрузка драйвера является только частью старта настоящего РпР устройства. Процедура ОпуегОп1оас1 (она отвечает за завершающие операции при выгрузке драйвера) более не занимается ЭРС объектами (считается, что драйвер теперь не имеет прямого доступа ни к одному такому объекту). Однако в обязанности этой функции теперь входит выполнение вызова ОЬОегеГегепсеОЬ]ес1 для уменьшения числа ссылок на используемый объект события, чтобы разрешить операционной системе его удаление, если эта операция будет сочтена целесообразной. Обработчики О15ра1с1'|Сгеа1:е (при получении клиентом дескриптора для доступа к драйверу) и О15ра1сЬС1о5е (при закрытии дескриптора, полученного в О15ра1сЬСгеа1:е) не претерпели изменений. Обработчики О15ра1сЬ\Л/гИе и О15ра1сЬРеас1 теперь в драйвере отсутствуют, а обработка запросов от клиента на чтение и запись данных в параллельный порт переложена на плечи функции Оеу1сеСоп1го1Коийпе (обработчик 1КР пакетов, поступающих вследствие клиентских вызовов от У\Лп32 функции ^еV^сеIоСоп^^оI). Функционально работу модифицированного драйвера можно описать следующим образом. Драйвер получает ЮСТ1_ запрос 10СТ1__5ЕМ0_Т0_РСЖТ от клиента (тестирующего приложения), откладывает запрос в системную очередь (см. описание механизма 5у51ет (Зиешпд в 6 главе) и завершает обработку 1РР пакета, помечая его как завершенный в состоянии 5ТАТ05_РЕЫ011\1С, указывая на всякий случай процедуру Сапсе1кои11пе драйвера. Эта процедура получит управление, если клиент решит отменить запрос. Заметим, что такая ситуация возникает, когда тестирующее приложение запускается в отсутствие заглушки СКескИ в параллельном порту. Пакет не может быть завершен, и приложение простаивает в ожидании окончания синхронного вызова ^еV^сеIоСоп^^оI — снятие приложения приводит к вызову Сапсе1кои11пе.
При извлечении 1кР пакета из очереди Диспетчер ввода/вывода вызывает зарегистрированную в ОпуегЕп1ту функцию 51аг11о, которая проверяет условия своего вызова, записывает в р1гр51аск->Рагате1ег5.Оеу|се1оСоп1го1.Туре31при1:ВиП:ег ноль и планирует работу ЭРС функции ЭрсЕогТзг вызовом ХоПедиезЮрс. Заметим, что это проходит вполне корректно (несмотря на то, что ХоПедиезЮрс необходимо вызывать на уровнях 1Р<21_ не ниже 015РАТСН_1_Е\/Е1_), поскольку сама функция 51аг11о работает на 1ИС21- равном 015РАТСН_1_Е7Е1_. Работа ЭрсЕогТзг при первом вызове сводится к выводу первого байта (точнее половины байта) в порт и запуску первого прерывания, после чего срабатывает цепная реакция 15г-ОрсЕог15г-перенос/прерывание-15г до полного переноса всей порции данных, поступивших по ЮСТЬ запросу ЮСТ1__5ЕЫ0_Т0_Р0РТ от клиента (тестирующего приложения). При выполнении переноса и при условии, что предварительно клиент запросил использование объекта события, процедура ЭрсЕогТзг выполняет установку объекта события в сигнальное состояние и таким образом сигнализирует клиенту, что перенос завершен. Тут следует обратить внимание, что, при условии регистрации объекта события, когда клиент решил отменить свой запрос на запись в порт и управление получает функция Сапсе1коийпе, то ее код должен что-то сделать с объектом события. В нашем случае он переводится в сигнальное состояние, поскольку прекращение жизни 1РР пакета — это тоже (хотя и своеобразное) завершение работы над ним. //=================================================================== // Файл йгд^ег.срр (Вариант 2) // Драйвер обслуживания заглушки СЬескТР (параллельный порт 378Ь) // Ву ЗУР, 21 Йипе 2004 //=================================================================== #1пс1ис1е "(Згз^ег.к" // Предварительные объявления функций МТ5ТАТЙ5 С^еабе^еV^се ( РЭК1УЕК_ОВЙЕСТ рЭг^егОЬ]есб, ЙЬОМС рогбВазе, 1Ы ЙЬОЫб 1гд ); ЫТЗТАТЙЗ ОФзрабсйСгеабе ( 1Ы РОЕУ1СЕ_ОВЙЕСТ р^еVОЬ^, 1Ы Р1ВР р!гр ); МТ5ТАТЙ5 ВФзрабсйСйозе ( РЭЕУ1СЕ_ОВЙЕСТ р^еVОЬ^, Р1КР р1гр ); УОЮ Бг^егйпйоас! ( РЭК1УЕК_ОВЙЕСТ рЭг^егОЬ]есб ); ЫТЗТАТЙЗ ^еV^сеСопР^о1ВоиР^пе ( 1Ы РОЕУ1СЕ_ОВЙЕСТ р^еVОЬ^, 1Ы Р1Р.Р р! УО1Б 8ЕагЕ1о ( 1Ы РОЕУ1СЕ_ОВЙЕСТ р^еVОЬ^, 1Ы Р1Р.Р р1гр ); ВООЬЕАЕ 1зг ( РК1ЕТЕККЙРТ рТпбеггирбОЬ]есб, ТЕ РУОТБ р УОЮ БрсЕогТзг ( РКБРС рЭрс, 1Ы РУОЮ ВеФеггесЮоп'Ьех'Ь, 1Ы РУОЮ рАгд1, 1Ы РУОЮ рАгд2 ) ; //=================================================================== МТ5ТАТЙ5 Сошр1ебе1гр( Р1КР р!гр, МТЗТАТЙЗ збабиз, ЙЬОМС зйхе) { р1гр->1оЗ'Ьакиз . Збабиз = збабиз; р1гр->1о5бабиз.1пЕогшаЕ1оп = згге; 1оСотр1ебеКедиезб( р!гр, 1О_МО_1МСКЕМЕЫТ ); гебигп збабиз;
// Функция: БЫчегЕпбгу // Назначение: Инициализирует драйвер, подключает объект устройства // получения прерываний. // Аргументы: рБгйчегОЬ^есЕ - поступает от Диспетчера ввода/вывода // рВедйзЕгуРаЕН - указатель на Юникод-строку, // обозначающую раздел Системного Реестра, созданный // для данного драйвера. // Возвращаемое значение: // НТ8ТАТБ8 - в случае нормального завершения 8ТАТБ8_8БС // или код ошибки 8ТАТБ8_Ххх // ехбегп "С" ИТ8ТАТИ8 БггчегЕпбгу ( та РБВ1УЕВ_ОВБЕСТ рБЫчегОЬ]есЕ, та РБШСОБЕ_8ТВтаС рВедйзЕгуРаЕН ) { ИТ8ТАТИ8 збабиз; #Ы БВС==1 БЬдРггпб("ЬРТРОКТ: 1п БгйчегЕпбгу, ВедйзЕгуРаЕН 1з:\п рВед±зЕгуРаЕй->ВиББег) ; #епсЫ // Регистрируем рабочие процедуры драйвера: рБгйчегОЬ^ есб->БгйчегБп1оас1 = БгйчегБпТоас!; рБгйчегОЬ^ есЕ->Ма^ огЕипсЫоп [ 1ВР_МБ_СВЕАТЕ ] = БйзраЫНСгеаЕе; рБгйчегОЬ^ есЕ->Ма^ огЕипсЫоп [ 1ВР_МБ_СЬО8Е ] = БйзраЫНСТозе; рБгйчегОЬ^ есЫ>Ма^ огЕипсЫоп [1ВР_МБ_БЕУ1СЕ_СОНТВОЬ] = БечйсеСопбгоТВои рБгйчегОЬ^ есЕ->Бг±чег8ЕагЕ1о = 8 Ба г Ы о; // Работа по созданию объекта устройства, подключению // ресурсов, прерывания, созданию символьной ссылки: збабиз = СгеабеБечйсе(рБгйчегОЬ^есЕ, 0x378, 0x7); геЕигп збабиз; } //=================================================================== // Функция: СгеабеБечйсе // Назначение: Создание устройства с точки зрения системы // Аргументы: рБгЫегОкд есЕ - поступает от Диспетчера ввода/вывода // рогЕВазе - адрес базового регистра параллельного порт // 1гд - прерывание (в терминах шины 18А) для обслуживай // Возвращаемое значение: // НТ8ТАТБ8 - в случае нормального завершения 8ТАТБ8_8БС // или код ошибки 8ТАТБ8_Ххх // НТ8ТАТБ8 СгеабеБечгсе ( та РБВ1УЕВ_ОВБЕСТ рБЫчегОЬ] есб, та БЬОНС рогЕВазе, та БЬОНС 1гд ) { БТ8ТАТБ8 збабиз; РБЕУ1СЕ_ОВЙЕСТ рБечОЬ]; РБЕУ1СЕ_ЕХТЕН81ОН рБечЕхб;
// Создаем внутреннее имя устройства 1Ж1С0БЕ_8ТКтаС бечИате; ВЫ1п±Ь0п±сос1е8Ьг±пд ( &бечНате, Ь"\\Веч±се\\ЬРТРОВТ" ) ; // Создаем объект устройства збабиз= ТоСгеабеБечтсе ( рБгй^егОкд есб, зтгеоБ ( БЕУ1СЕ_ЕХТЕИ8 ЮЫ) , &бечНате г Е1ЬЕ_ВЕУ1СЕ_ББКБО1лШ, О, ЕАБ8Е, // ТРОЕ - Рог ехс1из^е ассезз &рБечОЬ^ 1Р (ЮТ 8БССЕ88(зЕабиз)) гебигп зЕабиз; // Указывать, что будем использовать метод буферизации // ВБЕЕЕВЕБ_1О при отсутствии обработчиков Веаб/ЭДгтЕе // уже не актуально, поэтому можем закоментировать: // рБечОЬ]->Е1адз |= БО_ВБЕЕЕВЕБ_Ю; // Заполняем данными структуру Бечтсе ЕхЕепзтоп рБечЕхб = (РБЕУ1СЕ_ЕХТЕН8ЮИ)рБечОЬ^->Беч±сеЕхЕепз±оп; рБечЕхЕ->рБеч±се = рБечОЬ^; // сохраняем - это приго рБечЕхЕ->изЕгБеч±сеНате = бечИате; рБечЕхб->1гд рБечЕхЕ->рогЕВазе рБечЕхЕ->р1пЕОЬ^ рБечЕхЕ->хБегСоипЕ рБечЕхЕ->р1пЕОЬ^ рБечЕхЕ->рЕчепЕ рБечЕхЕ->НЕчепЕ //================ 1гд; (РБСНАВ)рогЕВазе; ББЬЬ; 0; // сейчас нет полученных данных ИБЬЬ; ББЬЬ; ББЬЬ; // Теперь не инициализируем объект БРС, // а закрепляем за объектом устройства (а значит и за обслужи // им прерыванием) единственную БрсЕог1зг функцию: 1о1п±Ыа1±геБрсВедиезЕ ( рБечОЬ^, БрсЕог1зг ); //================================================ //На всякий случай блокируем поступление прерываний: ЭДгТЕеСопЕгоТВедТзЕег ( рБечЕхб, СВ_БЕЕАБЬТ ); //================================================ // Создаем и подключаем объект прерываний: К1В(2Ь к1гд1; КАЕЕ1ШТХ кАРРТпТРу; □ЬОНС кУесбог = На1СеР1пРеггирРУесбог(1за, 0, рБечЕхб->1гд, рБечЕхб-> &к1гд1, &кАРР±п±бу); // Замечание. Для 1за шины второй параметр (номер шины) обычн // равен 0, а третий и четвертый параметры равны. #±Р БВС==1 ВЬдРгтпб( "ЬРТРОКТ: Тпбеггирб %б сопчегбеб Ьо кТгд! =
"кАЕЫпбЕу = %б, кУесЕог = %Х(Нех)\п", рВечЕхЕ->1гд, к1гд1, кАЕЫпбЕу, кУесЕог) ; #епббЕ зЕаЕиз = 1оСоппесЕ1пЕеггирЕ ( &рВечЕхЕ->р!пЕОЬ^ , // Здесь будет создан ТпЕеггирЕ ОЬ 1зг, // Наша функция 13В рБечЕхЕ, // Этот указатель 13В функция будет // получать при вызове (контекстный указат ПбЬЬ, // Не будем использовать зрйп-блокировку д // безопасного доступа к совместно использ // данным кУесЕог, // транслированное значение прерывания к1гд!, // В1В(2Ь к1гд1, // В1В(2Ь ЬаЕсЬеб, // Прерывание по перепаду ТВОЕ, // Совместно используемое (ЗНагеб) прерыва кАЕЕйпйЕу, // Поцессоров в мультипроцессорной системе ЕАЬЗЕ ); // Не сохранять значения регистров сопроце 6Е (!ПТ_ЗбССЕЗЗ(зЕаЕиз) ) { //В случае неудачи удаляем объект устройства 1оВе1еЕеВечбсе( рБечОЬ^ ); геЕигп зЕаЕиз; } #6Е ЬВС==1 ВЬдРгйпЕ ( "ЬРТРОВТ : 1пЕеггирЕ зиссеззЕиНу соппесЕеб. \ #епбйЕ //================================================ // Создаем символьную ссылку: 6ШСОВЕ_ЗТВ1ПС зутЫпкПате; // Сформировать символьное имя: //#беЫпе ЗХМ_ЫПК_ПАМЕ Ь"\\??\\ЬРТРОВТО" // АА проходит только в ЫТ // Для того, чтобы работало в Жпбомз 98 & ХР : Йейпе ЗХМ_ЫПК_ПАМЕ Ь"\\ВозВечйсез\\ЬРТРОВТО" ВЕИпбЕПпбсобеЗЕгбпд ( &зутЫпкПате, ЗХМ_ЫПК_ПАМЕ ) ; // Создать символьную ссылку: зЕаЕиз = 1оСгеаЕеЗутЬо1йсЫпк ( &зутЫпкПате, &бечПате ); йТ (!ПТ_ЗбССЕЗЗ(зЕаЕиз) ) { // При неудаче - отключаемся от прерывания и // удаляем объект устройства: 1оВбзсоппесЕ1пЕеггирЕ ( рВечЕхЕ->р1пЕОЬ^ ) ; 1оВе1еЕеВечйсе( рБечОЬ^ ); геЕигп зЕаЕиз; }
рЬечЕхЕ->и8ЕгЗутЫпкНате = зутИпкИате; #йЕ ВВС==1 ЬЬдРгйпЕ("ЬРТРОКТ: ЗутЬоНс Ыпк ±з сгеаЕеск %мз. \п" рЬечЕхЕ->и8ЕгЗутЫпкНате .ВиЕЕег) ; #епс!йЕ геЕигп ЗТАТПЗ_ЗЬССЕЗЗ; } //=================================================================== // Функция: Бг^егОпТоас! // Назначение: Останавливает и удаляет объекты устройств, отключает // прерывания, подготавливает драйвер к выгрузке. // Аргументы: рЬгйчегОЬ^есЕ - поступает от Диспетчера ввода/вывода // Возвращаемое значение: нет // УО1Ь ЬгйчегЬпйоаа ( РЬК1УЕК_ОВЙЕСТ рЬгйчегОЬ]есЕ ) { #йЕ ВВС==1 ЬЬдРгйпЕ("ЬРТРОКТ: йп ЬгйчегЬпйоаб пом\п"); #епайЕ РЬЕУ1СЕ_ОВЙЕСТ рНехЕОЬ^ = рЬгйчегОЬ^ есЕ->ЬечйсеОЬ^ есЕ; // Проход по всем устройствам, контролируемым драйвером Еог ( ; рИехЕОЬ^!=НЬЬЬ; ) { РЬЕУ1СЕ_ЕХТЕНЗЮН рЬечЕхЕ = ( РЬЕУ1СЕ_ЕХТЕП31ОП) рИехЕС // Удаляем объект прерываний: йЕ (рЬечЕхЕ->рйпЕОЬ^) { //На всякий случай блокируем поступление пре ЭДгйЕеСопЕгойКедйзЕег( рЬечЕхЕ, СК_ЬЕЕАЬЬТ); йоЫзсоппесЕйпЕеггирЕ ( рЬечЕхЕ->рйпЕОЬ^ ) ; } // Удаляем символьную ссылку: йоЬейеЕеЗутЬоййсЬйпк ( &рЬечЕхЕ->и8ЕгЗутЫпкНате) ; #йЕ ЬВС==1 ЬЬдРгйпЕ ( "ЬРТРОКТ : ЗутЬйпк %мз с!е1еЕес1\п" , рЬечЕхЕ->и8ЕгЗутЫпкНате.В #епбйЕ // Сообщаем о прекращении использования объекта событ йЕ(рЬечЕхЕ->рЕчепЕ!=НЬЬЬ) { ОЬЬегеЕегепсеОЬ^есЕ(рЬечЕхЕ->рЕчепЕ); #йЕ ЬВС==1 ЬЬдРгйпЕ("ЬРТРОКТ: ЕчепЕ оЬ^есЕ бегеЕегепсеб. #епбйЕ } // Сохраняем ссылку на следующее устройство и удаляем // текущий объект устройства: рНехЕОЬ^ = рНехЕОЬ^->НехЕЬечйсе;
1оВе1ебеВеч±се ( рВечЕхб-УрВечгсе ) ; } // Замечание. Поскольку мы использовали ресурс (параллельный // объявленные не нами, то освобождение этого ресурса можно о } //=================================================================== // Функция: ВТзрабсНСгеабе // Назначение: Обрабатывает запрос по поводу Шп32 вызова СгеабеЕИе // Аргументы: рБечОЬ^ - поступает от Диспетчера ввода/вывода // р1гр - поступает от Диспетчера ввода/вывода // Возвращаемое значение: 8ТАТП8_8ПССЕ88 // НТ8ТАТП8 ВТзрабсНСгеабе ( ТО РБЕУ1СЕ_ОВЙЕСТ рБечОЬ], ТО Р1КР р1гр ) { #1Р ВВС==1 ВЬдРггпб("ЬРТРОКТ: 1п ВТзраЬсНСгеабе пом\п”); #епс11Т Сотр1ебе1гр( р1гр, 8ТАТП8_8ПССЕ88, 0 ); // ничего не передано гебигп 8ТАТП8_8ПССЕ88; } //=================================================================== // Функция: ВТзрабсНСТозе // Назначение: Обрабатывает запрос по поводу Жп32 вызова СТозеНапсПе // Аргументы: рБечОЬ^ - поступает от Диспетчера ввода/вывода // р1гр - поступает от Диспетчера ввода/вывода // Возвращаемое значение: 8ТАТП8_8ПССЕ88 // НТ8ТАТП8 ВТзрабсНСТозе ( ТО РВЕУ1СЕ_ОВЙЕСТ рБечОЬ], ТО Р1КР р1гр ) { РВЕУ1СЕ_ЕХТЕН8ЮН рВечЕхб = (РВЕУ1СЕ_ЕХТЕН810И) рБечОЬ]->Веч±сеЕхЬепз±оп; #1Т ЬВС==1 ВЬдРггпЬ("ЬРТРОКТ: 1п ВТзраЬсНСТозе пом\пн); #епс11Т // Сообщаем о прекращении использования объекта события 1Т(рВечЕхЬ->рЕчепЬ!=НЬЬЬ) { // Удаляем объект события, поскольку клиент заканчивает // работу с драйвером и не сможет больше отдать команду // о прекращении работы с событием: #±Ь ЬВ(3==1 ВЬдРггпЬ("ЬРТРОКТ: ВгзраЬсНСТозе, аЬЬетрЬ Но сТозе На рВечЕхЬ-ЖЕчепЬ ) ; #епсИ_Т НТ8ТАТП8 збз = Е^СТозе (рВечЕхб-ЖЕчепб) ; рВечЕхб->рЕчепб = НПЬЬ; рВечЕхб-ЖЕчепб = НПЬЬ; #±Ь ВВС==1
ВЬдРгйпЕ("ЬРТРОКТ: ВйзраксНСйозег ечепк ЬапсПе сйозеб з к з ) ; // зЬз == 0 <==> зЬз == 8ТАТИ8_8ИССЕ88 #епс1йТ } СотрйеЕейгр( рйгр, 8ТАТЬ8_8ЬССЕ88г 0 ); // ничего не передано геЕигп 8ТАТИ8_8ПССЕ88; } //=================================================================== // Функция: СапсейКоиЕйпе // Назначение: Вызывается в случае отказа клиента от запроса на обраб // йКР пакета, который был отложен (для работы со 8ЕагЫо // Аргументы: рВечОЬ^ - поступает от Диспетчера ввода/вывода // рйгр - поступает от Диспетчера ввода/вывода // Возвращаемое значение: нет // УОЮ СапсейКоиЫпе ( йИ РВЕУйСЕ_ОВЙЕСТ рВечОЬ], йИ РйКР рйгр ) { РВЕУйСЕ_ЕХТЕИ8йОИ рВечЕхЕ = (РВЕУйСЕ_ЕХТЕН8йОИ) рВечОЬ]->ВечйсеЕхЕепзйоп; #йЕ ЬВС==й КйК(2Ь сиггепЕйгдй = КеСеЕСиггепЕйгдй(); ВЬдРгйпЕ("ЬРТРОКТ: йп СапсейКоиЕйпе пом, йгдй=%с1, йКР сиггепЕйгдй ) ; #епс!йЕ йТ (рйгр == рВечОЬ^->СиггепЕйгр) { 1оКе1еазеСапсе18рйпЬоск(рйгр->Сапсеййгдй); СотрйеЕейгр( рйгр, 8ТАТИ8_САНСЕЬЬЕВ, 0 ); У/ааааааааа что ^Ь1 ни утверждалось в ББК, с этим // работает лучше! (Иначе - "глючит" МопйЕог) } ейзе // Пакет - не текущий, удаляем его из системной очереди, { // если он там есть: КеКеточеЕпЕгуВечйсеОиеие( &рВечОЬ^->ВечйсеОиеие, &рйгр->Тайй.Очегйау.ВечйсеОиеиеЕпЕгу); 1оКе1еазеСапсе18рйпЬоск(рйгр->Сапсе1йгд1); Сотрйекейгр( рйгр, 8ТАТП8_САИСЕЬЬЕВ, 0 ); } //Во всяком случае, обработка данного пакета завершена йТ(рВечЕхЕ->рЕчепб!=ИПЬЬ) { Ке8еЕЕчепЕ(рВечЕхЕ->рЕчепЕ, йО_ИО_йИСКЕМЕИТ , ЕАЬ8Е ); } // Не повредит... йо8ЕагЕНехЕРаскеЕ( рВечОЬ^, ТКИЕ ); }
//=================================================================== // Функция: ЗЬагЫо // Назначение: Вызывается для обработки отложенных 1ВР пакетов. // Аргументы: рБечОЬ^ - поступает от Диспетчера ввода/вывода // р1гр - поступает от Диспетчера ввода/вывода // Возвращаемое значение: нет // УОЮ ЗЬагЫо ( РБЕУЮЕ_ОВТЕСТ рБечОЬ], Р1ВР р1гр) { #1Р ЬВС==1 К1В(2Ь сиггепЫгд! = КеСеЬСиггепЫгд! () ; ВЬдРггпЬ("ЬРТРОВТ: ЭДе аге пом 1п ЗЬагЫо , сиггепЫгд #епс11Т РБЕУЮЕ_ЕХТЕНЗЮН рБечЕхЬ = ( РБЕУЮЕ_ЕХТЕНЗЮН) рБечОЬ] ->Беч РЮ_ЗТАСК_ЬОСАТЮН р1грЗЬаск = 1оСеЬСиггепЬ1грЗЬаскЬосаЬ±оп( РЬСНАВ изегВиЬЬег; ЬЬОНС хЬегЗгге; змйзсЬ. ( р1грЗЬаск->Ма^ огЕипсЫоп ) { сазе 1ВР_МТ_БЕУЮЕ_СОНТВОЬ: { ЬЬОНС сопкгоТСобе = р1грЗЬаск->Рагатекегз.Беч±се1оСопЬго1.1оСопЬг 1Т( сопЕгоТСобе != ЮСТЬ_ЗЕНБ_ТО_РОВТ ) { // Сюда мы не должны попадать: #±Ь ЬВС==1 ВЬдРггпЬ("ЬРТРОВТ: ЗЬагЫо: Ьаб ЮСТЬ."); #епсНТ Сотр1еЬе1гр( р1гр, ЗТАТЬЗ_ЬЮТ_ЗЬРРОВТЕВ, 0 ); ЮЗЬагЬНехЬРаскеЬ( рБечОЬ^ г ЕАЬЗЕ ) ; Ьгеак; } // Очищаем место, где будем хранить номер следующего // клиентском буфере, который следует передать: р1грЗЬаск->РагатеЬегз . ВечгсеЮСопЬго! . Туре31приЬВиЬЬе //=================================================== // Можно было сделать как и в варианте 1 для запуска // КеЗупсйгопЮеЕхесиЬгоп ( рБечЕхЬ->р1пЬОЬ^ , // БоНехЬТгапзЬег, // рБечЕхЬ ); // // Но пойдем усложненным путем: внесем изменения в Бр // и БоНехЬТгапзЬег, затем запланируем вызов БрсЕог1з // Замечание. Функцию ЮВедиезЬБрс для планирования С // нужно вызывать с уровня 1В<2Ь не ниже Б13РАТСН_ЬЕУЕ // а ЗЬагЫо работает как раз на этом уровне 1В(2Ь: #Ю ЬВС==1 ВЬдРггпЬ ( "ЬРТРОВТ : ЗЬагЫо: ТоВедиезЬБрс мН!
#епс11^ 1оКедиезкВрс( р^еVОЬ^, ЕГОЬЬ, КГОЬЬ ); //=================================================== Ьгеак; } Ьекаи1Ь: { // Сюда мы не должны попадать: #1^ ОВС==1 ОЬдРгтпЬ("ЬРТРОКТ: ЗЬагЫо: ЬаЬ ТОСТЬ (1п ' зи #епсИГ Сотр1еке1гр( р!гр, ЗТАТЬЗ_ЕЮТ_ЗЬРРОКТЕР, 0 ); ТоЗ'Еаг'ЕЫехЬРаскеЬ ( р^еVОЬ^ , ЕАЬЗЕ ) ; Ьгеак; } } } Как было сказано, работа с процедурой 51аг11о и системной очередью незавершенных 1НР пакетов без особых усилий со стороны разработчика драйвера дает сериализацию (последовательную обработку) пакетов, отражающих поступившие запросы от клиентов драйвера. Если драйвер зарегистрировал процедуру 51аг11о и затем в обработчиках запросов от клиента ставит 1КР пакеты в очередь на обработку соответствующим образом (вызовом 1о51аг1Раске1 и т.д., что выполняет обработчик Оеу|сеСоп1то1Кои11пе в отношении пакетов с ГОСТЬ кодом, равным ГОСТЬ_5ЕМ0_Т0_Р0КТ), то Диспетчер ввода/вывода в каждый момент времени будет "выдавать" драйверу (точнее, объекту устройства) не более чем по одному пакету. Эти порции так и будут попадать в процедуру 51агЫо в связке "объект устройства"-"1ИР пакет". Не нужно организовывать особых "синхронизационных" мероприятий по правильному и безопасному совместному использованию устройства. Процедура 51аг11о должна только запустить процесс (последовательность событий, а не программный!), в результате которого когда-то завершится обработка 1КР пакета (успешно или нет, не имеет принципиального значения). Это может быть запуск устройства (прерывание, извещающее о готовности устройства, может вызвать к работе 15К процедуру и целую цепочку событий) или тривиальное программирование последующего запуска отложенной процедуры, как в данном случае. До момента объявления 1НР пакета "завершенным" Диспетчер ввода/вывода не вызовет 51агЫо снова, несмотря на то, что сама процедура 51агЫо могла уже давно завершить свою работу. Однако если устройство занято, то появляется желание и далее эксплуатировать механизм системной очереди незавершенных запросов (ЗузЬет <2иеи1пд), и поставить запрос снова в эту же очередь, в надежде на то, что очень скоро устройство будет все-таки готово воспринять запрос. В тех случаях, когда в этом действительно имеется необходимость и драйвер собственными силами ведет очередь незавершенных пакетов, ничто не мешает это сделать. Однако в системной очереди это не проходит — повторный вызов 1о51аг1Раске1 с только что извлеченным из системной очереди 1КР пакетом надежно "заклинивает" системную очередь. В этом имеется своя логика — если устройство не смогло обработать запрос сейчас, то зачем его снова вставлять в очередь 1КР пакетов с такими же параметрами? Они все равно не смогут быть обработаны. Лучшее решение состоит в том, чтобы вернуть управление с соответствующим кодом завершения клиенту, который сам решит, как лучше поступить.
Однако, может возразить внимательный читатель, у драйвера одна процедура 51аг11о на все типы запросов. И в том случае, если ко всем будет применяться "прогон" через системную очередь, то вполне возможно, что одни запросы действительно не смогут быть обработаны в данный момент (это может установить, например, сама функция 51аг11о, "пообщавшись" с устройством), в то время как другие запросы могут быть обработаны вполне успешно. Вот если бы можно было выделить из очереди пакеты второго типа, оставляя напоследок пакеты первого типа! Возможно также, что к моменту завершения обработки пакетов более "удачного" типа, устройство станет доступно и для обработки первых, ранее бесперспективных. В данном случае процедуре 51агИо (или другим процедурам, получившим управление по ее воле) придется пожертвовать текущим 1НР пакетом, который следует завершить с кодом ошибки. Но далее процедура 51аг11о при помощи вызова 1о51агШех1Раске1ВуКеу может определить приоритет на извлечение из очереди 1КР пакетов по определенному ключу и воплотить таким образом "экономичное" представление разработчика драйвера о работе с 1РР пакетами и их сериализацией. ф В коде данного драйвера часто встречаются фрагменты, когда указатель на текущий 1РР пакет извлекается из поля СиггепНгр в структуре объекта устройства. Возникает вопрос: а всегда ли так можно поступать? Ведь это достаточно простой и удобный способ получения ссылки на 1РР пакет в любом месте драйвера, которым так хочется пользоваться! Ответ: к сожалению, далеко не всегда и не везде. Диспетчер ввода/вывода устанавливает в поле СиггепНгр в структуре объекта устройства значение указателя на 1РР пакет только для текущего пакета, который участвует в работе через системную очередь (ЗузСет (Зиещ'пд). Этот 1РР пакет сейчас один из всех, которые возможно были занесены в эту очередь данным драйвером или, иными словами, данным объектом устройства, поступил на обработку в функцию ЗСагНо и до окончания его обработки (с каким либо кодом завершения в одной из функций драйвера) другого адреса в поле СиггепИгр не появится. Соответственно, если разработчик так написал драйвер, что адрес такого 1РР пакета где-то "оседает" (задерживается, например, в параллельно работающем системном потоке данного драйвера), то это может быть источником краха системы, поскольку обращение к области хранения полностью завершенных 1РР пакетов совершенно некорректно. //=================================================================== // Процедура обслуживания прерывания: // ВООЬЕАИ 1зг (1И РК1ПТЕККПРТ рРпбеггирЕОЬ]есб, РУОЮ рЗе^тсеСопбех { РОЕУ1СЕ_ЕХТЕ№1ОЫ рОечЕхб = (РОЕУ1СЕ_ЕХТЕЫ31ОЫ) рЗегчтсеСопбе РЕЕ\71СЕ_ОВ ПЕСТ р^еVОЬ^ = р^еVЕxЬ->р^еV^се; ОВС==1 К1К<ЭЪ сиггепЫгд! = КебебСиггеп'Ыгд! () ; ВЬдРгТпб ( "ЬРТРОВТ : 1п Тзг ргосейиге, 13В_1гд1=%с1\п", #епсНЕ //=========================================================== // Строго говоря, следовало бы проверить, имело ли место // прерывание и наше ли это прерывание: // ПСНАК збабиз = КеайЗбабизКедТз'Ьег ( р^еVЕxЬ ) ; // 1Е( збабиз & 0x04 ) гебигп ЕАЬЗЕ; // прерывания не было // Однако в силу упомянутых накладок с использованием бита ЗР // не проверяем это вовсе, делая допущение, что если 1зг полу // управление, то прерывание - наше. //=========================================================== // Общей практикой является блокирование поступления прерывай // в этом месте: // Иг1ЬеСопЬго1Кед1збег ( р^еVЕxЬ, СК_ОЕЕАПЬТ);
// Однако, мы не будем этого делать, чтобы не испортить данны // находящиеся сейчас в Збабиз Ведгзбег. Полагаем, что кроме // нашего драйвера такие прерывания никто не генерирует, а др // защищен тем, что ЭДггбе запросы отвергаются до полного пере // данных. //=========================================================== // Планируем вызов ЬРС процедуры для обработки прерывания поз // ТоВедиезбВрс( рЬечОЬ^, // <- указание // однозначно ; на объект устройства з определяет, какая проце // ОРС (здесь - ВрсЕоЫзг) будет вызв ЫИЬЬ, // <- Агд1 1п ВрсЕог1зг ЫИЬЬ ); // <- Агд2 1п ЬрсЕогТзг гебигп ТИПЕ; // нормальное завершение обработки прерывания } //=================================================================== // Код, который посылает в порт данные, вызывающие (при наличии // СЬеск1б заглушки) сигнал прерывания: // УОЮ Еогсе1пбеггирб( РВЕУ1СЕ_ЕХТЕП31ОП рБечЕхб, ИСНАВ Ыбз ) { // Генерируем сигнал прерывания ^гГбеСопбгоТВедгзбег( рБечЕхб, Ыбз | СВ_1ЫТ_ЕПВ | СВ_ВЕЕАИЬТ КеЗбаПЕхесиббопРгосеззог (50) ; // Удерживаем состояние 50 мкс // Импульс АСК# ЭДЫбеСопбгоТВедгзбег( рБечЕхб, Ыбз | СВ_1ЫТ_ЕИВ | СВ_ЬЮТ_ВЗТ | СВ_ВЕЕАИЬТ ) ; КеЗбаПЕхесибгопРгосеззог (50) ; // Удерживаем состояние 50 мкс // Удерживая информационные биты, снимаем импульс АСК# ^гГбеСопбгоТВедгзбег( рБечЕхб, Ыбз | СВ_1ИТ_ЕПВ | СВ_ВЕЕАИЬТ } //=================================================================== // Функция БоПехбТгапзбег безопасно (от вмешательства кода прерывания // функции) записывает данные в параллельный порт: // ВООЬЕАИ БоПехбТгапзбег ( 1И РУСЮ рСопбехб ) { РБЕУ1СЕ_ЕХТЕПЗТОЙ рБечЕхб ИСНАВ пехбВубе = (РБЕУ1СЕ_ЕХТЕП31ОП)рСопбехб; = ОхОЕ & (рБечЕхб->ЬубеТоВеОибТоРо #66 ЬВС==1 БЬдРЫпб ("ЬРТРОВТ: ВЬдРЫпб ("ЬРТРОВТ: БоПехбТгапзбег:\п"); ЗепсИпд 0х%02Х бо рогб %Х\п", пехбВубе, рБечЕхб->рогбВазе);
#епсИТ // Отправка полубайта данных. //= 1 ======================================================= // Заглушка СНеск1Ь работает не самым простым образом. // Бит 0 отсылаемого полубайта нужно отправить как бит О // в Баба РедгзЬег ЭДгЮеВаЬаРедгзбег ( рБечЕхЬ, пехЬВуЬе & 0x01); // // Это бит будет считан как бит 3 из ЗЬаЬиз РедгзЬег //= 2 ============================= // Биты 1-3 отсылаемого полубайта нужно отправить как // биты 0Л 1 и 3 в СопЬго! РедгзЬег БОНАР ЫЬз = (пехЬВуЬе & 0x8) + ((пехЬВуЬе & 0хб)>> 1); // Таким образом бит 2 всегда равен 0 ЫЬз А = 0x3; // Инвертируем биты (0 & 1) перед // записью в СопЬго! РедгзЬег // Эти биты будут считаны в ЗЬаЬиз РедгзЬег как биты 4Л5 и 7 #Ю БВС==1 ВЬдРггпЬ ( "ЬРТРОКТ : депегаЫпд пехЬ гпбеггирб. #епс11Т // Собственно отправляем данные вместе с генерацией // сигнала прерывания: Еогсе1пЬеггирЬ( рБечЕхЬ, ЫЬз ); геЬигп ТРБЕ; } //=================================================================== // Функция Реас1ВаЬаЗаЬе1у выполняет чтение данных из устройства // без опасения быть прерванной кодом 13В функции: // ВООЬЕАИ РеаББабаЗаРеТу ( 1И РУО1Б рСопЬехЬ ) { РБЕУ1СЕ_ЕХТЕИЗЮИ рБечЕхЬ = ( РБЕУЮЕ_ЕХТЕПЗЮП) рСопЬехЬ; БОНАР зЬаЬиз = РеабЗЬаЬизРедТзЬег( рБечЕхЬ ); // Преобазуем полученные через ЗЬаЬиз гедгзЬег биты // в понятную форму БОНАР геаБВуЬе = ((зЬаЬиз & 0х8)<< 1) | ((зЬаЬиз & 0х30)<< 1) | (зЬаЬи геабВуЬе >>= 4; #Ю БВС==1 К1Р(2Ь сиггепЫгд! = КеСеЮиггепЫгд! () ; БЬдРггпЬ("ЬРТРОРТ: РеабБаЬаЗаБе1уЛ сиггепЬ1гд1=%с! Реа " РеабВуЬе = %02Х\п"Л сиггепЫгд!, зЬ БЬдРггпЬ( "ЬРТРОРТ: \п"); #епс11Т Ю(рВечЕхЬ->хЬегСоипЬ < МАХ_ВБЕЕЕР_312Е)
{ // Помещаем принимаемые во внутренний буфер драйвера, // только если его размер не исчерпан: рБечЕхЕ->бечЕсе1пВиЕЕег [рБечЕхЕ->хЕегСоипЕ++] = геабВуЕе; } геЕигп ТВбЕ; } //=================================================================== // Функция ТгапзЕегТоПзегЗаЕеТу выполняет перенос данных из внутренне // буфера драйвера без опасения быть прерванной кодом 18В функции: // ВООЬЕАП ТгапзЕегТоПзегЗаЕеТу ( РУОЮ рСопЕехЕ ) { РВЕУ1СЕ_ЕХТЕП8ЮП рБечЕхЕ = (РВЕУ1СЕ_ЕХТЕП81ОП)рСопЕехЕ ; бЬОПС хЕегВед = рБечЕхЕ->хЕег8Еге; РбСНАВ ЕпВиЕЕег = рБечЕхЕ->бечЕсе1пВиЕЕег; #ЕЕ ЬВС==1 К1В(2Ь сиггепЕЕгдЕ = КеСеЕСиггепЕЕгдЕ(); БЬдРгЕпЕ ( "ЬРТРОВТ : ТгапзЕегТоПзегЗаЕеТу, сиггепЕ1гдТ= ВЬдРгЕпЕ(" гедиезЕеб %б ЬуЕез, мНЕЕе геабу %б хЕегВед, рБечЕхЕ->хЕегСоипЕ); #епбЕЕ 6Е( рБечЕхЕ->хЕегСоипЕ< 1 || хЕегВед< 1 ) { // Нет никаких полученных данных или нулевой запрос рБечЕхЕ->хЕег8Еге = 0; геЕигп ЕАЬЗЕ; } ЕЕ ( хЕегВед > МАХ_В6ЕЕЕВ_812Е ) хЕегВед = МАХ_В6ЕЕЕВ_812Е; // Передаем не более данных, чем все, полученные из ЬРТ порта // оказавшиеся во внутреннем буфере драйвера: ЕЕ( хЕегВед > рБечЕхЕ->хЕегСоипЕ ) хЕегВед = рБечЕхЕ->хЕегСои // Собственно перенос запрошенных данных в буфер клиента: ВЕЕСоруМетогу( рВечЕхЕ->рбзегВиЕЕег, ЕпВиЕЕег, хЕегВед ); ЕЕ( хЕегВед < рБечЕхЕ->хЕегСоипЕ) { // Перемещаем оставшиеся данные к началу буфера: бЬОПС Е=0,д=хЕегВед; Еог(; <рБечЕхЕ->хЕегСоипЕ; ) ЕпВиЕЕег[Е++]=ЕпВиЕЕег[^++] } рБечЕхЕ->хЕегСоипЕ -= хЕегВед; рБечЕхЕ->хЕег8Еге = хЕегВед; #ЕЕ ЬВС==1 БЬдРгЕпЕ(" ТгапвЕеггеб %б, ЕНе гевЕ %б ЬуЕез. хЕегВед, рБечЕхЕ->хЕегСоипЕ); #епбЕЕ
ге'Ьигп ТЮТЕ; } //=================================================================== // Функция: БрсЕогЕзг // Назначение: Данная функция начинает работу по "заказу" 18К функции // и выполняет ввод/вывод путем безопасного вызова функп // КеасЮаЕаЗаЕеТу и ВоБехЕТгапзЕегг то есть реализует // низкоуровневый ввод/вывод // Аргументы: Указатель на текущий БРС объект (не используется) // рБеЕеггебСопЕехЕ - контекстный указатель - так // передается указатель на структуру объекта устройства // Возвращаемое значение: нет // УОЮ БрсЕогТзг( 1Б РКБРС рБрс, // не используется 1Б РУОЮ рБеЕеггебСопЕехЕ, // Внимание! Теперь здесь // находится указатель рБечОЬ^ 1Б РУОЮ рАгд1, // не используется 1Б РУОЮ рАгд2 // не используется { РБЕУ1СЕ_ОВЙЕСТ рБечОЬ] = (РВЕУ1СЕ_ОВЙЕСТ)рБеЕеггебСопЕе РВЕУ1СЕ_ЕХТЕБ81ОБ рБечЕхЕ (РВЕУ1СЕ_ЕХТЕБ81ОБ)рБечОЬ]->Веч±сеЕхЕ Р1ВР р1гр = рБечОЬ^->СиггепЫгр; Р1О_8ТАСК_ЬОСАТ1ОБ р1гр8Еаск = 1оСеЕСиггепЫгр8ЕаскЬосаЫоп (р БЬОБС кока! = р1гр8Еаск->РагатеЕегз.БечЕсеЕоСопЕго!.1приЕВиЕЕегЬепд сиггепЕВуЕеБитТоЭДгЕЕеТоРогЕ = (БЬОБС)р1гр8Еаск->РагатеЕегз.БечЕсе1оСопЕгоЕ.Туре31пр #ЕЕ БВС==1 К1В(2Ь сиггепЕ1гдЕ = КеСеЕСиггепЕ1гдТ () ; БЬдРгЕпЕ("ЬРТРОКТ: ЭДе аге пом 1п БрсЕог1зг, сиггепЕТг сиггепЫгдЕ, рВечЕхЕ->хЕегСоипЕ ) ; #епс!ЕЕ ЕЕ( сиггепЕВуЕеБитТоЭДгЕЕеТоРогЕ >0 ) // <- т.е. уже были пер { // Безопасно читаем следующий байт из устройства: КеЗупсЬгопЕгеЕхесиЕЕоп ( рБечЕхЕ->р1пЕОЬ^ г КеабВаЕаЗаЕеЕуг рБечЕхЕ ); } ЕЕ ( сиггепЕВуЕеБитТоЭДгЕЕеТоРогЕ < боба! ) { // Если остались данные, то записываем следующий байт // данных в порт и запускаем прерывание: РБСНАК изегВиЕЕег = (РБСНАК)р1гр->АззосЕаЕеБ1гр.ЗузЕетВиЕЕ рБечЕхЕ->ЬуЕеТоВеОиЕТоРогЕ =
изегВиЬЕег [ сиггепЬВукеЫитТоЭДггЬеТоРо #1Р ЬВ(3==1 ВЬдРггпЬ("ЬРТРОКТ: сиггепЬВуЬеЫо = %б, ЬуЬеТоВеОиЬТоР сиггепЬВуЬеМитТоЭДгЬЬеТоРогЬ , рЬечЕ #епс11Т // Заранее корректируем номер следующего байта для записи // и сохраняем его там же: р1грЗЬаск->РагатеЬегз . Веч±се1оСопЬго1. Туре31приЬВиЬЬег = (РУОЮ)( сиггепЬВуЬеЫитТоЭДгТЬеТоРо КеЗупсНгопггеЕхесиЫоп ( рВечЕхЬ->р1пЬ0Ь^ г ВоПехЬТгапз Регг рЬечЕхб ) ; } е!зе { #1Т ЬВ(3==1 ВЬдРггпЬ ("ЬРТРОКТ: ЭДе аге пом 1п ВрсЕог1зг, а!1 баба #епс11Т 1Т(рВечЕхЬ->рЕчепЬ!=ЬЬЬЬ) { // Устанавливаем объект события в сигнальное состояни // что является сигналом клиету о полностью завершенн // переносе данных в параллельный порт КеЗеЬЕчепЬ (рВечЕхЬ->рЕчепЬ, 1О_ЬЮ_1ПСКЕМЕПТ , ЕАЬЗЕ ) //ААААААААААААААА ЗаМеЧаНИе Д, } // Завершаем обработку текущего 1КР пакета. // Поскольку это была обработка пакета от Шп32 вызова // Веч±се1оСопЬго1, который передавал данные в драйвер, // а назад ничего не ожидал, // то указываем, что число байт, возвращаемых клиенту // (третий аргумент вызова Сотр1еЬе1гр), равно 0: Р1КР р1гр = рЬечОЬ^->СиггепЫгр; Сотр1еЬе1гр( р1гр, ЗТАТПЗ_ЗЬССЕЗЗ, 0 ); // Сообщаем Диспетчеру ввода/вывода, что готовы обработать // следующий пакет: 1оЗЬагЬПехЬРаскеЬ( рЬечОЬ^ , ЕАЬЗЕ ) ; } гекигп; } //=================================================================== // Функция: ВечгсеСопЬгоТКоиЫпе // Назначение: Обрабатывает запросы от Жп32 вызова ЬечгсеТоСопЬго!. // Аргументы: рЬечОЬ^ - поступает от Диспетчера ввода/вывода, // р1гр - поступает от Диспетчера ввода/вывода. // Возвращаемое значение: // ЫТЗТАТЬЗ - в случае нормального завершения ЗТАТПЗ_ЗЬС
// или код ошибки 8ТАТЬ8_Ххх // ЫТ8ТАТН8 БечТсеСопЬгоТВоиЬТпе( ТО РБЕУЮЕ_ОВЙЕСТ рБечОЬ^, 1Н Р1ВР р1г { ЫТ8ТАТН8 зЬабиз = 8ТАТЬ8_8ЬССЕ88; РЮ_8ТАСК_Ь0САТЮН р1гр8Ьаск=1оСеЮиггепЬ1гр8ЬаскЬосаЬ±оп(р1г РБЕУЮЕ_ЕХТЕН8 ЮИ рБечЕхк = (РБЕУЮЕ_ЕХТЕН8ЮН) рБечОЬ^->Беч±сеЕхЬепз±о //------------------------------- #1Р ЬВС==1 К1В(2Ь сиггепЫгд! = КеСеЮиггепЫгд! () ; ВЬдРггпЬ("ЬРТРОВТ: Же аге пом ±п БечТсеСопЬгоТВоиЬТпе сиггепЫгд! ); #епсНТ // Диспетчеризация по ЮСТЬ кодам: зюТсЬ. ( р1гр8Ьаск->РагатеЬегз . БечТсеЮСопЫо! . ЮСопЫоТСобе ) { сазе ЮСТЬ_8ЕНБ_ТО_РОВТ : { // Размер данных, поступивших от клиента драйвера // (см. таблицу 8.9): ЬЬОНС хЬегВгге = р1гр8Ьаск->РагатеЬегз.Веч!се1оСопЬго1.ТприЬВиТТегЬепд 1Т ( хЬег8Юе < 1 ) { // Нет данных для отправки в порт: #Ю БВС==1 БЬдРггпЬ("ЬРТРОВТ: БечгсеСопбгоТВоиЫпе: ЮСТ " по ЬуЬез Ьо ЬгапзБег.\п"); #епсНТ // Завершение без переноса данных клиенту: геЕигп Сотр1еЬе1гр( р1гр, 8ТАТБ8_8БССЕ88, 0 ); } // 1Т ( хЬег8Юе > МАХ_ВБЕЕЕВ_812Е ) ? Но размер входящих д // уже не актуален, поскольку будет использоваться буфер, // поступивший от клиента (точнее, от Диспетчера ввода/выв // // Теперь мы ничего не копируем, а просто инициируем проце // программируемого вывода, откладывая пакет в очередь // необработанных 1ВР пакетов (не забываем сообщить адрес // процедуры СапсеТВоиЫпе, которая должна получить управл // если клиент решит отозвать свой запрос): #Ю БВС==1 БЬдРггпЬ("ЬРТРОВТ: БечТсеСопЬгоТВоиЬТпе: ЮСТЬ_8ЕНБ_Т " хЬег з±ге 1з %с1 1гр 1з репЫпд.\п" #епсНТ 1оМагк1грРепсИпд( р1гр ); 1о8ЬагЬРаскеЬ( рБечОЬ^, р1гр, 0, СапсеТВоиЬгпе); геЕигп 8ТАТО8 РЕНБ1Ю;
} сазе ТОСТЬ_8ЕПВ_ТО_68ЕК: { // Размер данных, ожидаемых пользователем рБечЕхЕ->хЕег8±ге = р1гр8Еаск->РагатеЕегз . БечбсеТоСопЕго!. ОиЕриЕВиЕЕегЬеп 1Р ( рВечЕхЕ->хЕег8йге> 0 ) { // Согласно таблице 8.9, адрес клиентского буфера при // методе МЕТНОБ_ВПЕЕЕКЕВ в ТОСТЬ запросе находится т // же, что и при обработке обычного запроса Реаб (см. // первый вариант драйвера, функцию БйзраЕсНКеаб): рБечЕхЕ->рбзегВиЕЕег = (РбСНАК)р1гр->АззосбаЕеб1гр.8узЕетВиЕ // Пытаемся безопасно перенести данные: КеЗупсНгопйгеЕхесиЕйоп( рБечЕхЕ->р1пЕОЬ^, ТгапзЕегТобзег8аЕе1у, рБечЕхЕ ); } // Завершаем обработку 1КР пакета: #ФР ЬВС==1 БЬдРгйпЕ ( "ЬРТРОКТ : ЬечгсеСопЕгоТКоиЕТпе : ТОСТЬ_8ЕПБ_Т " %б ЬуЕез ЕгапзЕеггеб Ео изег.\п”, #епб1Е геЕигп Сотр1еЕе1гр( р1гр, 8ТАТ68_86ССЕ88, рБечЕхЕ->хЕег8±г } сазе ТОСТЬ_ТАКЕ_ЕУЕЫТ: { // Размер данных, поступивших от клиента драйвера бЬОПС хЕегЕготВгбчегЗйге = р1гр8Еаск->РагатеЕегз.БечбсеТоСопЕго!.ОиЕриЕВиЕЕегЬе йТ( хЕегЕготБгйчегЗйге < зйгеоЕ(НАИБЬЕ)) { #йЕ БВС==1 БЬдРгйпЕ ( "ЬРТРОКТ : БечйсеСопЕгоТКоиЕйпе : ТОСТ "ечепЕ йпЕо сап поЕ Ье ЕгапзЕеггеб б #епбйЕ зЕаЕиз = 8ТАТБ8_1ПУАЫБ_РАКАМЕТЕК; // Завершение без переноса данных клиенту: геЕигп Сотр1еЕе1гр( р1гр, зЕаЕиз, 0 ); // 0 - Нет пер } йЕ(рБечЕхЕ->рЕчепЕ==ПБЬЬ) // Объект события еще не был соз { // для данного клиента. // Создаем объект события типа // ЗупсЬгопТОаЕбопЕчепЕ, с автоматическим переходом в // несигнальное состояние: #беЕбпе ЕУЕПТ_ПАМЕ Ь"\\ВазеПатебОЬ]есЕз\\ЬРТРОКТ_ЕУЕ БШСОБЕ 8ТК1ИС ечепЕИате;
РЬИпТОБпбсобеЗЬгбпд ( &еVеп^^ател ЕУЕПТ_ПАМЕ ); НАИБЬЕ ЬЕVепЬ; // Объект события - без имени: РКЕУЕЫТ рЕVепЬ = IоС^еаЬеЗупсП^оп^гаЬ^опЕVепЬ( &еVепЬ^ател &ПЕ 6Т (рЕVеп^==^^^^) { // Объект события не был создан #6Т БВС==1 БЬдРгбпЬ ( "ЬРТРОРТ : БеV^сеСопЬ^о1РоиЬ^ " еггог - еVеп^ мазп #епбТО // Завершение без переноса данных клиенту: геЬигп СотрТеЬебгр( р1грЛ ЗТАТБЗ_1ЖЗБССЕЗЗЕБЬ } #6Б БВС==1 БЬдРгбпЬ("ЬРТРОРТ: ^еV^сеСопЬ^о1РоиЬ^пе: ТОСТ " еVепЬ патеб %мз зиссеззбиНу еVепЬ^ате.ВиЬЬег); #епбТО р^еVЕxЬ->рЕVепЬ = рЕVепЬ; р^еVЕxЬ->ЬЕVепЬ = ЬЕVепЬ; // Предустанавливаем объект события в несигнальное со КеС1еа^ЕVепЬ(р^еVЕxЬ->рЕVепЬ); } // Сообщаем об объекте события клиенту - передаем дескрипт РЬТСоруМетогу( р1гр->АззосбаЬеб1гр.ЗузЬетВиЬЬегг &р^еVЕxЬ->ПЕVепЬ Л збгеоб(НАИБЬЕ) ); #ТО БВС==1 БЬдРгбпЬ( "ЬРТРОРТ: БеV^сеСопЬ^о1РоиЬ^пе: ТОСТЬ_ТАКЕ_ " еVепЬ ЬапбТе = %04Х(Ьех) 1з зепЬ рБеVЕxЬ->ПЕVепЬ); #епбТО геЬигп Сотр1еЬе1гр( р1грЛ ЗТАТБЗ_ЗБССЕЗЗг зггеоБ(НАИБЬЕ) ) } сазе ТОСТЬ_СЬОЗЕ_ЕУЕПТ: { 1Т(рБеVЕxЬ->рЕVепЬ!=ББЬЬ) // объект события был создан { ЫТЗТАТБЗ зЬз = ЕТОТозе(рБеVЕxЬ->ЬЕVепЬ); #ТО БВС==1 ТО(зЬз==ЗТАТБЗ_ЗБССЕЗЗ) БЬдРггпЬ("ЬРТРОРТ: БеV^сеСопЬ^о1РоиЬ^пе: ТОСТ " еVепЬ ЬапбТе сТозеб мТОП 3 БЬдРгбпЬ("ЬРТРОРТ: БеV^сеСопЬ^о1РоиЬ^пе: ТОСТ " еVепЬ (ЬапбТе %04ХЬех) с!о рБеVЕxЬ->ПЕVепЬЛ зЬз ) ;
#епс11Е // Во всяком случае, эти событием пользоваться не буд рЕечЕхЕ->рЕчепЕ = ЕГОЬЬ; рВечЕхЕ->ЬЕчепЕ = 1ТОЬЬ; } геЕигп Сошр1еЕе1гр( рЬгр, ЗТАТЕЗ_ЗЕССЕЗЗ, 0 ); } с!еЕаи1Е: { #1Е ЭВС==1 ЕЬдРгипЕ("ЬРТРОКТ: Эеч1сеСопЕго1КоиЕ1пе : Ьаб #епсНТ // Завершение без переноса данных клиенту: зЕаЕиз = 5ТАТЕЗ_1МУАЫЕ_ЕЕУ1СЕ_КЕ<2ЕЕЗТ; Сотр1еЕе1гр( р!гр, зЕаЕиз, 0 ); } } // <- конец оператора "зидЕск" геЕигп зЕаЕиз; } //=================================================================== Перед тем как перейти к анализу тестирующего приложения и результатов тестирования, детальнее рассмотрим код обработчика ЮТСЬ запросов, то есть функции Оеу|сеСоп1то1Кои11пе данного драйвера. Драйвер обрабатывает четыре ЮСТЬ кода, которые может задать клиент при своем обращении к драйверу (из приложения пользовательского режима это делается через \Л/|п32 вызов ^еV^сеIоСоп^^оI): ЮСТ1__5ЕЫО_ТО_Р(ЖТ обеспечивает отправку данных драйверу. При обработке этого запроса драйвер "откладывает" 1К.Р пакет в системную очередь, передавая работу функции 51аг11о. ЮСТ1__5ЕЫО_ТО_1)5ЕР обеспечивает передачу данных, накопившихся на текущий момент в рабочем буфере с1еу1се1пВиГГег (см. описание структуры расширения объекта устройства для второго варианта драйвера в файле Опуег.Ь), то есть полученных из параллельного порта через механизм прерываний. ЮСТ1__ТАКЕ_Е\/ЕМТ передает клиенту дескриптор объекта события, если оно было создано или создает его, сохраняя данные в полях рЕуепЬ и ЬЕуеп1 структуры расширения объекта устройства (см. описание структуры расширения объекта устройства для второго варианта драйвера в файле Опуег.Ь). ЮСТ1__С1_О5Е_Е\/ЕМТ закрывает дескриптор текущего используемого объекта события. Если речь идет о взаимодействии приложения пользовательского режима с драйвером, то, как правило, событие создается именно в приложении пользовательского режима. Операционная система по \Л/1п32 вызову СгеаЬеЕуепЬ создает событие как объект режима ядра, возвращая приложению открытый дескриптор. Этот дескриптор передается в драйвер (через ЮСТЬ запрос), а драйвер при помощи вызова ОЬКеГегепсеОЬ]ес1ВуНап(11е получает ссылку на созданный объект события по указателю на существующий объект (ссылка на объект является в режиме ядра более привычным способом обращения с событием, мьютексом и т.д.). Описание подобного способа совместного использования события в режиме ядра и пользовательском
режиме несложно отыскать в литературе или в интернете, например, на Интернет сайте сос!едиги.сог1 . Особенностью же данного драйвера является то, что событие изначально создается в драйвере (в режиме ядра), и лишь после этого соответствующий ему дескриптор передается клиенту. Для этого используется практически не применяемый разработчиками вызов IоС^еа^е5упсI1^оп^2а^^опЕVеп^ (не применяемый по причине плохого описания в документации пакета ЭОК всех имеющихся на сегодня версий). Между тем этот удобный вызов, вся "особая" специфика использования которого состоит в правильном задании имени создаваемого объекта (это же относится и к вызову режима ядра 1оСгеа1е1Чо1|Пса1юпЕуеп1), возвращает не только указатель на создаваемый объект, но и его дескриптор одновременно. Созданный объект и его дескриптор продемонстрировали нормальную работу при обращении к событию, созданному драйвером сразу из нескольких приложений (копий тестовой программы) пользовательского режима. Дескрипторы события, полученные ими, имели одинаковое численное значение. ф Данный драйвер (и первый, и второй варианты) в том виде как они приведены, не позволяют одновременно получить доступ к нему из нескольких приложений, поскольку объект устройства создается для эксклюзивного доступа. По этой причине \А/т32 вызов Сгеа1еРПе, который должен получить дескриптор доступа к драйверу, завершается с ошибкой 5 (отказано в доступе). Для того чтобы исправить ситуацию необходимо предпоследний параметр вызова 1оСгеа1еОеу/се в драйверной функции Сгеа^еПеу'юе задать равным РАЬЗЕ (вместо ТРШЕ, как указано изначально). Еще один вопрос-опасение, который может возникнуть у внимательного читателя: как обстоят дела с доступом ко входным буферам с данными и для данных в смысле корректности уровня 1к<21_, на которых к ним обращается драйвер? Например, копирование в пользовательский буфер полученных данных по ГОСТЬ запросу ГОСТ1__5ЕМ0_Т0_115Ек производится функцией Тгап5ГегТо1)5ег5аГе1у, защищенной КеЗупсЬготгеЕхесийоп, то есть работающей на уровне 1Р.<31_, равном 1ЕЩ1_ процедуры обслуживания прерывания 1зг. Почему не происходит сбоя, поскольку достоверно известно, что пользовательские области находятся в странично организованной памяти? Ответ состоит в том, что в обоих драйверах используется буферизованный метод передачи данных при обращениях клиента к драйверу: в первом варианте драйвера было указано, что объект устройства имеет метод буферизации при запросах на запись/чтение рОеуОЬ]->Е1ад5 |= ОО_В1)ЕЕЕКЕО_1О; во втором варианте аналогичное указание Диспетчер ввод/вывода получает в каждом ГОСТЬ коде, когда при его определении явно указывается МЕТНОО_В1)ЕЕЕкЕО. В обоих случаях указание на буферизацию приводит к тому, что между передачами клиент/драйвер Диспетчер ввода/вывода вставляет копирование в/из буферных областей, которые специально выделяются в нестранично организованной памяти, допускающей обращение к ней из кода, работающего на повышенных уровнях 1ЕЩ1_.
ГРгеу|ои51 [Мех!],
Модификация приложения для тестирования драйвера Тестирующее консольное приложение не так сильно изменилось, за исключением переписывания кода на использование вызовов Оеу|се1оСоп1го1 и организации ожидания вызовом УУаИЕог5тд1еОЬ]ес1. //================================================================== // Файл тестовой программы БезБ.срр //================================================================== #1пс1ис1е <м!паомз . Ь> #1пс1ис1е <8-1 сП о . Ь> #1пс1ис1е ”^1п1ос1:1. Ь" МеПпе 1ОСТЬ_8ЕПБ_ТО_РОВТ Е1 ЪЕ_БЕ VI СЕ_ББКПСЖП, 0x801, МеПпе 1ОСТЬ_8ЕПБ_ТО_Б8ЕВ Е1 ЬЕ_БЕ VI СЕ_ЖКЕЮЖ[, 0x802, МеПпе 1ОСТЬ_ТАКЕ_ЕУЕПТ Е1 ЬЕ_БЕ VI СЕ_ББКПСЖП, 0x803, МеПпе 1ОСТЬ_СЬО8Е_ЕУЕПТ Е1 ЬЕ_БЕ VI СЕ_ЖКЕЮЖ[, 0x804, 1пЕ Кеас1ЭДг1Ее (НАИБЬЕ аечНапа1е) ; СТЬ_СОБЕ( \ МЕТНОБ_ВБЕЕЕВЕБ, СТЬ_СОБЕ( \ МЕТНОБ_ВБЕЕЕВЕБ, СТЬ_СОБЕ( \ МЕТНОБ_ВБЕЕЕВЕБ, СТЬ_СОБЕ( \ МЕТНОБ_ВБЕЕЕВЕБ, Е1ЬЕ_АБУ_АССЕ88) Е1ЬЕ_АБУ_АССЕ88) Е1ЬЕ_АБУ_АССЕ88) Е1ЬЕ_АБУ_АССЕ88) МеПпе ВБЕЕ812Е (17) збаЕгс ипзгдпеа сЬаг оиЕВиББег[ВБЕЕ812Е] , гпВиУУег[ВБЕЕ812Е*2]; збаЕгс НАББЬЕ аечНапа1е, ЬЕчепЕ; 1пЕ __сбес! та1п () { рггпЕБ("\п\п\п\п\пРага11е1 РогЕ СНеск1Е ЬоорЬаск ВеV^се Тезк с1еVНапс11е = СгеакеЕИе ( "\\\\.\\ЬРТРОВТ0" , СЕЕЕВ1С_ВЕАБ | СЕБЕР1С_^Р1ТЕ, 0, // зкаге тос!е попе ЕБЬЬ, // по зесиггку ОРЕЕ_ЕХ18Т1Ж, Е1ЬЕ_АТТВ1ВБТЕ_ЕОВМАЬ, ЕБЬЬ ); //по Еетр1аке И ( аеVНапа1е == 1ЕУАЫБ_НАЕБЬЕ_УАЬБЕ ) { рггпЕБ("Еггог: сап пок ореп аеV^се РЬРТРОВТО. ЭД1п32 е СеЕЬазЕЕггог() ); гекигп -1; } рггпЕБ("Сопдгаки1аЕ1оп. ЬРТРОВТО аеV^се 1з ореп.\п\п"); //========================================== // Получение доступа к объекту события,
// создаваемому в драйвере БЭДОВБ ЬуЕезВеаб; йЕ ( !БечйсейоСопЕгой(бечНапбйе, ЮСТЬ_ТАКЕ_ЕУЕПТ, ИББЬ, 0л // отправляем &ЬЕчепЕЛ з±гео^ (НАИБЬЕ) г // получаем из &ЬуЕезВеабЛ ПБЬЬ ) ) { ргйпЕЕ("Еггог бигйпд 1ОСТЬ_ТАКЕ_ЕУЕПТ: еггпо %б.\п"Л (ЗеЕЬазЕЕггог() ); СйозеНапбйе (бечНапбйе) ; геЕигп -1; } ргйпЕЕ("ХпЕчепЕ Ьапбйе = %04Х (Нех) \п" г ЬЕчепЕ); //========================================== // Заполнение буфера данными: ВЕЮРБ й=3,]=0; йог ( ; ^<зйгеоЕ(оиЕВиЕЕег); ) оиЕВиЕЕег[^++] = (ипзйдпеб сЬа //========================================== ВеабЭДгйЕе (бечНапбйе) ; //========================================== // Завершение работы йЕ ( !БечйсейоСопЕгой(бечНапбйе, ЮСТЬ_СЬОЗЕ_ЕУЕПТ, ИББЬ, 0Л // отправляем в драйвер ИББЬ, 0Л // получаем из драйвера &ЬуЕезВеабЛ ПБЬЬ ) ) { ргйпЕЕ("ХпЕггог бигйпд 1ОСТЬ_СЬОЗЕ_ЕУЕПТ: еггпо %б.\п } ейзе ргйпЕЕ("ХпЕчепЕ Еапбйе йз погтаййу сйозеб.\п"); йЕ ( ! СйозеНапбйе(бечНапбйе) ) { ргйпЕЕ("\п Еггог бигйпд СйозеНапбйе: еггпо %б.\п"Л Се геЕигп -1; } ргйпЕЕ("\п\п\п Бечйсе ЬРТРОВТО зиссеззЕиййу сйозеб. Еогтай ех геЕигп 0; } //=================================================================== // передача и получение данных из СйескйЕ заглушки: йпЕ ВеабЭДгйЕе(НАЕБЬЕ бечНапбйе) { //========================================== // Передача данных драйверу
ргЬпЕЕ ( "ЭДгЬЕЬпд Ео ЬРТРОВТО ЬечЬсе...\п"); ШОВБ ЬуЕезВеЕигпес!, оиЕСоипЕ = зйгеоЕ (оиЕВиЕЕег) ; 1Е ( !Вечйсе1оСопЕго1(ЬечНапсИе, 1ОСТЬ_8ЕИВ_ТО_РОВТ, оиЕВиЕЕег, оиЕСоипЕ, // отправляем в др ИИЬЬ, О, // получаем из дра &ЬуЕезВеЕигпес1, ИИЬЬ ) ) { ргйпЕЕ("Еггог Ьигйпд 1ОСТЬ_8ЕИВ_ТО_РОВТ: еггпо %с1.\п" геЕигп 11; } ргйпЕЕ( "8иссеззЕи11у ЕгапзЕеггес! %с1 ЬуЕез.\п" "ВиЕЕег сопЕепЕ шз: \п", оиЕСоипЕ) ; Еог (ШОВБ 1=0; 1<оиЕСоипЕ; 1++ ) рг!пЕЕ("%02Х ",оиЕВиЕЕег[1] //========================================== // Использование созданного события Ш0ВБ гези1Е = ЭДа1ЕЕог81пд1еОЬ^есЕ(ЬЕчепЕ,10); змЕЕсЬ(гези1Е) { сазе ЭДА1Т_Т1МЕОИТ: ргЬпЕЕ("\пЭДа1Е ЕттеоиЕ. \п");Ьгеак; сазе ^А1Т_АВАИВОИЕВ: ргЬпЕЕ("\п№пЕ ^А1Т_АВАИВОИЕВ.\п"); Ьгеа ЬеЕаи1Е: ргЬпЕЕ("\пЭДа1Е ЬеЕаи1Е сазе.\п"); } //========================================== // Получение данных из драйвера ргЬпЕЕ ("\п\пВеас11пд Егот Ьечтсе ЬРТРОКТО ... \п" ) ; БЭДОКВ ЬуЕезВеас!, тпСоипЕ = зтгеоЕ (тпВиЕЕег) ; И ( ! Веч1се1оСопЕго1 (с!ечНапс11еЛ 1ОСТЬ_8ЕИВ_ТО_П8ЕВЛ ИИЬЬЛ 0Л // отправляем в драй тпВиЕЕег, тпСоипЕ, // получаем из драйв &ЬуЕезВеас1Л ИИЬЬ ) ) { ргтпЕЕ ( "Еггог Ьигтпд ОСТЬ_8ЕИВ_ТО_С8ЕВ: еггпо %с!.\п"Л геЕигп 12; } йЕ ( ЬуЕезВеас! != оиЕСоипЕ ) { // размер записанных и прочитанных данных не совпадает ргйпЕЕ ("Еггог: 1з Ео геас! %с1 ЬуЕезЛ\п" "ЬиЕ 1ОСТЬ_8ЕИВ_ТО_П8ЕВ герогЕеЬ %Ь ЬуЕез.\п"Л оиЕСоипЕ, йпСоипЕ); геЕигп 13; } ргйпЕЕ ("8иссезЕи11у геас! %с1 ЬуЕез.\п ВиЕЕег сопЕепЕ 1з: \п", Еог ( й=0; 1<ЬуЕезВеас1; 1++ ) ргЬпЕЕ ( "%02Х ", (ИСНАВ) ЬпВиЕЕе
геЕигп 0; // Нормальное завершение } Как уже обсуждалось в этой главе, запуск процесса переноса данных в параллельный порт и через него в буфер для данных, передаваемых клиенту, производится генерацией одного лишь первого прерывания. Далее, при наличии заглушки СКескК в порту, процесс "регенерируется" автоматически до окончания данных во входном буфере. При такой "цепной реакции" весь перенос может пройти на высоких уровнях 1Р.С21-, и тестирующее приложение даже не успеет получить управление и прибегнуть к услугам "ожидания по событию". В том случае, если запустить драйвер и тестовое приложение в отсутствие заглушки, то приложение остановится, ожидая окончания обработки запроса по записи данных в порт. В этот момент можно убедиться в создании объекта именованного события при помощи программы \ЛЛпОЬ], рис. 11.5. Рис. 11.5 Именованный объект события "1_РТРОкТ_Е\/Е1\1Т", созданный драйвером, в рабочем окне программы \Л/1пОЬ] Вывод на экран (при наличии в параллельном порту заглушки СЬескК) практически не изменился и при запуске тестирующего приложения, текст которого приведен выше, в консольном окне можно наблюдать следующие сообщения: Рага11е1 РогЕ СЬеск1Е ЬоорЬаск ^еV^се ТезЕ Ргодгат. СопдгаЕи1аЕйоп. ЬРТРОНТО с1еV^се ±8 ореп. ЕVепЕ ЬапЫе = 07СС(Ьех) ЭДгйЫпд ко ЬРТРОНТО с1еV^се... ЗиссеззЕиНу ЕгапзЕеггеЬ 17 ЬуЕез. ВиЕЕег сопЕепЕ шз: 03 04 05 06 07 08 09 0А 0В ОС ОБ 0Е 0Е 10 11 12 13 ЭДайЕ ЬеЕаи1Е сазе. НеаЫпд Егот с1еV^се ЬРТРОНТО... ЗиссеззЕиНу геаЬ 17 ЬуЕез. ВиЕЕег сопЕепЕ йз: 03 04 05 06 07 08 09 0А 0В ОС ОБ 0Е 0Е 00 01 02 03 ЕVепЕ ЬапЫе йз погтаНу с1озеЬ. ВеVйсе ЬРТРОНТО зиссеззЕиНу с1озеЬ. Ыогта! ехйЕ. Ниже приводится информация из отладочного вывода, перехваченного программой ОеЬидХЛем (1од-файл этой программы). Средняя часть этого файла (сообщения с 35 по 137) опущена, как и в первом случае, поскольку в этих строках содержится однообразная и малоинтересная информация. 00000000 0.00000000 ЬРТРОНТ: йп Б^^Vе^ЕпЕ^у, НедйзЕгуРаЕЬ йз: 00000001 0.00000223 \НЕС13ТНУ\МАСН1ЕЕ\ЗУЗТЕМ\СопЕго13еЕ001\ 00000002 0.00003101 ЬРТРОНТ: ТпЕеггирЕ 7 сопVе^Еес1 Ео к!гд1 = 8,
00000003 00000004 00000005 00000006 00000007 00000008 0.00004023 О.00006761 3.62195949 3.62206817 3.62210393 3.62210616 ЬРТРОКТ: ЬРТРОКТ: ЬРТРОКТ: ЬРТРОКТ: ЬРТРОКТ: 00000009 00000010 00000011 00000012 00000013 00000014 00000015 00000016 00000017 00000018 00000019 00000020 00000021 00000022 00000023 3.62211231 ЬРТРОКТ: 3.62211426 3.62228496 ЬРТРОКТ: 3.62229082 ЬРТРОКТ: 3.62229250 3.62229864 ЬРТРОКТ: 3.62230256 ЬРТРОКТ: 3.62230982 ЬРТРОКТ: 3.62231485 ЬРТРОКТ: 3.62231848 ЬРТРОКТ: 3.62232323 ЬРТРОКТ: 3.62232826 ЬРТРОКТ: 3.62243972 ЬРТРОКТ: 3.62244671 ЬРТРОКТ: 3.62245341 ЬРТРОКТ: 00000024 00000025 00000026 00000027 00000028 00000029 00000030 00000031 3.62245621 ЬРТРОКТ: 3.62246096 ЬРТРОКТ: 3.62246459 ЬРТРОКТ: 3.62246906 ЬРТРОКТ: 3.62247409 ЬРТРОКТ: 3.62258471 ЬРТРОКТ: 3.62259170 ЬРТРОКТ: 3.62259812 ЬРТРОКТ: 00000032 3.62260120 ЬРТРОКТ: 00000033 3.62260595 ЬРТРОКТ: 00000034 3.62260958 ЬРТРОКТ: кАЬЫпЬку = 1, кУесЬог = 191 (Ьех) ТпЬеггирк зиссеззЬиНу соппесЬес!. ЗутЬоНс Ыпк 1з сгеаЪес!: \^оз^еV^сез\^ 1п ВизракскСгеаке пои Ие аге пои 1п ^еV^сеСопЬ^о1КоиЬ^пе, сиг ^еV^сеСопЬ^о1КоиЫпе: 1ОСТЬ_ТАКЕ_Е7ЕЫТ, еVепЬ патес! \ВазеИатесЮЬ]есЬз\ЬРТР0КТ_Е зиссеззТиНу сгеакесК ^еV^сеСопк^о1Коик^пе: 1ОСТЬ_ТАКЕ_ЕУЕИТ, еVепЬ ЪапсНе = 07СС(1пех) 1з зепЬ Ьо изе Ие аге пои Ьп ^еV^сеСопЬ^о1КоиЫпе, сиг ^еV^сеСопк^о1Коик^пе: 1ОСТЬ_ЗЕИВ_ТО_РОК хЬег з1хе 1з 17 1гр 13 репсНпд. Ие аге пои Ьп ЗЬагЫо , сиггепЫгд1=2 ЗЬагЫо: ЬоКедиезЬБрс иИ1 Ье са11ес1 по Ие аге пои 1п ВрсЕогТзг, сиггепЫгд1=2 сиггепЬВуЬеЫо = 0, ЬуЕеТоВеОи'ЕТоРогР=03 ОоИехЬТгапзЬег: ЗепсНпд 0x03 Во рогЬ 378 депегаЫпд пехЬ гпкеггир 1п 1зг ргосесЗиге, 13К_1гд1=8 Ие аге пои Ьп ОрсЕогТзг, сиггепЫгд1=2 КеасЮаЪаЗаГеЬу, сиггепЫгд1=8 КеасНЬаЕи КеайВуЕе = 03 сиггепЬВуЬеЫо = 1, ЬуЬеТоВеОиЬТоРогЬ=04 ОоИехЬТгапзЬег: ЗепсНпд 0x04 Ьо рогк 378 депегаЫпд пехЬ гпкеггир 1п 1зг ргосес1иге, 15К_1гд1=8 Ие аге пои Ьп ОрсЕогТзг, сиггепЫгд1=2 КеасЮаЬаЗаЬеТу, сиггепЫгд1=8 КеайЗРаки Кеа<1ВуЬе= 04 сиггепЬВуЬеЫо =2, ЬуЬеТоВеОиЬТоРогЬ=05( ВоЫехЬТгапзЬег: 00000138 00000139 00000140 00000141 00000142 3.62448468 3.62448915 3.62449417 3.62460480 3.62461123 ЬРТРОКТ: ЬРТРОКТ: ЬРТРОКТ: ЬРТРОКТ: ЬРТРОКТ: 00000143 3.62461765 ЬРТРОКТ: 00000144 00000145 3.62462073 3.62462548 ЬРТРОКТ: ЬРТРОКТ: ОоИехЬТгапзЬег: ЗепсНпд 0x02 Ьо рогк 378 депегаЫпд пехЬ гпкеггир 1п 1зг ргосес1иге, 15К_1гд1=8 Ие аге пои Ьп ОрсЕогТзг, сиггепЫгд1=2 хЬегСоипк = 15 КеасЮаЬаЗаЬеТу, сиггепЫгд1=8 КеайЗРаки Кеас1ВуЬе= 02 сиггепЬВуЬеЫо = 16, ЬуЕеТоВеОи'ЕТоРог'Е=1
00000146 00000147 00000148 00000149 00000150 00000151 00000152 00000153 00000154 00000155 00000156 00000157 00000158 00000159 00000160 00000161 00000162 00000163 00000164 00000165 00000166 00000167 3.62462883 ЬРТРОКТ: 3.62463330 ЬРТРОКТ: 3.62463833 ЬРТРОКТ: 3.62474868 ЬРТРОКТ: 3.62475566 ЬРТРОКТ: 3.62476209 ЬРТРОКТ: 3.62476488 ЬРТРОКТ: 3.62476879 ЬРТРОКТ: 3.62529372 ЬРТРОКТ: 3.62529875 ЬРТРОКТ: 3.62530322 3.62530769 3.62531271 ЬРТРОКТ: 3.62531439 3.62566360 ЬРТРОКТ: 3.62567449 ЬРТРОКТ: 3.62567645 3.62568231 ЬРТРОКТ: 3.62568427 3.62577506 ЬРТРОКТ: 6.71423547 ЬРТРОКТ: 6.71426704 ЬРТРОКТ: ВоЫехЫгапзЬег: ЗепйЬпд 0x03 Ьо рогЬ 378 депегаЫпд пехЬ ЬпЬеггир Ьп 1зг ргосейиге, 13К_1гд1=8 Ие аге пом Ьп ВрсЕогЬзг, сиггепЫгд1=2 хТегСоипЫ 16 КеайЭаЫЗаТеЬу, сиггепЫгд1=8 КеайЗЬаЬи КеайВуЕе= 03 Ие аге пом Ьп ЭрсЕогТзг, аЫ Йака Ьгапз Ие аге пом Ьп ^еV^сеСопЫо1КоиЫпе, сиг ТгапзТегТоИзегЗаЬе1у, сиггепЫгд1=8 гедиезЫй 34 ЬуЬез, мЬЫе геайу 17 ЬуЬе ТгапзТеггей 17, ЬЬе гезЬ 0 ЬуЬез. ^еV^сеСопЫо1КоиЫпе: 1ОСТЬ_ЗЕИЭ_ТО_йЗЕ 17 ЬуЕез ЬгапзТеггей Ьо изег. Ие аге пом Ьп ^еV^сеСопЫо1КоиЫпе, сиг ^еV^сеСопЫо1КоиЫпе: 10СТЬ_СЬ03Е_Е7ЕИТ еVепЬ Ьапй1е сЬозей мЬЫ 5ТАТЙЗ_5ЕССЕЗЗ ^еV^сеСопЫо1КоиЫпе : Ь0СТЬ_СЬ03Е_Е7ЕИТ еVепЬ (Ьапй1е 07ССЬех) с1озЬпд зЬаЬиз = Ьп ЕЬзраЫЬСЬозе пом Ьп ^^^Vе^^п1оай пом ЗутЬЬпк \^оз^еV^сез\^РТРОКТ0 йеЬеЬей

Заключение Данная глава была посвящена рассмотрению двух реализаций драйвера ЬРТРог^.зуз, работающего с параллельным портом (устройством отмирающей, но еще вполне "живой" шины 15А), который при дополнении его тестовой заглушкой СИескП: становится отличным полигоном для детального рассмотрения зарождения и обработки прерываний в драйверах режима ядра. Драйвер обладает определенными недостатками. Обслуживая не-РпР устройство, он опять-таки построен по методике 1_едасу (как драйвер "в стиле 1\1Т"), а не как современный \Л/ОМ драйвер. Кроме того, для простоты реализации, он использует ресурсы, обозначенные другим, системным драйвером, не уделив должного внимания собственному "захвату" аппаратных ресурсов с последующей регистрацией их в операционной системе. Тем не менее, заглушка СИескИ позволит любому читателю данной книги заполучить несложное устройство, которое позволяет произвольно, по собственному желанию, получать и обрабатывать прерывания на любом компьютере, в офисе или дома. Первая, упрощенная версия драйвера ЬРТРог^.зуз, знакомит с общей методикой обработки прерываний в \Л/|Пс1о\л/5 1\1Т 5.x. Вторая, усложненная версия вводит в использование механизмов системных очередей 1Р.Р пакетов как метода обеспечения последовательного доступа к устройству, а также знакомит с приемами совместного использования объектов события приложениями пользовательского режима и драйверами.
На базе приведенных примеров читатель может самостоятельно достроить достаточно интересные тесты, например, по одновременному доступу к драйверу из разных приложений (с использованием событий уведомляющего, а не синхронизационного типа), а также по реализации собственных очередей отложенных 1КР пакетов. Следующая глава будет посвящена рассмотрению вопросов установки драйверов с использованием !пГ- файлов.
Глава 12
Инсталляция драйверов при помощи 11ЧЕ файлов Если разложить работу по инсталляции драйвера на элементарные составляющие, то можно сказать, что она состоит в том, чтобы записать необходимые файлы в соответствующие директории, возможно, системные, внести новую информацию в Системный Реестр и, возможно, запустить драйвер. Пример, приведенный в главе 3, предлагал два способа установки — при помощи 5СМ Менеджера и при помощи 1пГ-файла. Первый способ запускает драйвер непосредственно из приложения, которое также может выполнить как остановку, так и выгрузку драйвера. (Следует, однако, признать, что не все драйверы поддаются работе через сервисы 5СМ Менеджера). Этот удобный (особенно при экспериментах по изучению системных вызовов режима ядра) способ практически полностью описан в главе 3, поэтому рассмотрим подробнее инсталляцию с использованием 1пГ-файлов. Инсталляция при помощи 1пГ-файла позволяет выполнить все действия по копированию файлов, относящихся к драйверу, и внесению изменений в Системный Реестр практически без участия пользователя. Текстовый формат 1пГ-файла сходен со стилем старых 1пГ-файлов \Л/|Пс1о\л/5 3.x, но теперь этот формат много сложнее. Еще одним, третьим, способом инсталляции можно считать создание специального приложения пользовательского режима, которое выполняет ту же работу (копирование файлов и внесение новой информации в Системный Реестр). Однако такая методика не может быть признана приемлемой при работе с самостоятельно
идентифицирующимися (РпР) устройствами: в этом случае запустившийся в ответ на обнаружение нового устройства Мастер Установки работает с т&файлом, который одной из своих записей должен подтверждать свою "ответственность"за обнаруженное устройство. Данная глава описывает правила построения 1Л/Г файлов и работы с ними.
ГРгеу|ои81 ГИехП
Структура 11ЧЕ файла Инсталляционный 1пГ-файл является текстовым файлом, поставляемым вместе с драйверным программным обеспечением и аппаратным обеспечением, соответственно. В операционных системах \Л/1пс1о\л/з 9х/Ме размер 1пГ-файл не может превосходить 64 килобайта. Для ЫТ систем ограничений нет. Если не указано иначе для конкретного типа ОС, то максимальная длина любого поля в !пГ-файле составляет 512 символов.
Секции тГ-файла и основные общие правила ввода записей Инсталляционный 1пГ-файл поделен на секции, каждая из которых начинается с идентификатора {имени секции'), заключенного в квадратные скобки. Часть секций является обязательной, присутствие других секций зависит от назначения драйвера. Записи внутри каждой секции описывают действия по инсталляции, либо ссылаются на другие секции. Записи, которые регламентированы для секций определенного типа (обязательные или нет), в литературе и в документации ООК часто называются директивами. Весь текст, введенный в 1пГ-файле, не различается в смысле регистра символов — все имена секций и записи могут быть введены и в верхнем, и в нижнем регистре. Поэтому слова Vе^5^оп, \/ЕЙ51ОМ и \/ег51оп являются идентичными для процесса установки. Текст не должен содержать символов табуляции и других невидимых управляющих символов. Символ "точка с запятой" означает начало комментариев в следующей (за точкой с запятой) позиции, которые продолжаются до конца строки. Комментарии не принимаются в рассмотрении при анализе ИГ-файла. Данное правило не действует только в том случае, если такой текстовый фрагмент (содержащий точку с запятой) заключен в кавычки. Строка, содержащая только символы возврата каретки и перевода строки, считается пустой и игнорируется. Если существует необходимость продолжить запись на следующей строке, то в последней позиции текущей строки следует ввести обратный слэш \. Порядок следования секций в файле не играет роли, важно лишь, чтобы секции носили корректные имена и были правильно соотнесены в перекрестных ссылках. (Правда, сами разработчики придерживаются некоторых правил, например, секция [51ппдз] вводится обычно последней.) Секция продолжается до объявления начала следующей секции, либо до обнаружения конца файла. Имя секции должно быть уникальным для данного ИГ-файла. Хотя в некоторых источниках указывается, что содержимое секций, имеющие одинаковые имена, системное программное обеспечение, занимающееся интерпретацией ИГ-файла, объединяет, тем не менее, практика показывает, что это не так. Например, из двух секций [51ппд5] принимается во внимание только содержимое первой (по тексту 1пГ-файла). Имя секции не должно содержать более 28 символов для У\Лпс1оуу5 9х и более 255 символов для \ЛЛпс1о\л/5 1Х1Т. Ссылки на секции могут содержать в своем составе пробелы, но только если имя в целом заключено в кавычки (то же относится и к символу точки с запятой). Допустимы точка и символ подчеркивания. Например, как указывает документация ООК, строка в кавычках 51с1 МГд " является приемлемой ссылкой на имя секции, если указываемая секция имеет имя, в точности совпадающее с содержимым внутри кавычек, а именно [;; 51с1 МГд ]. В теле секции информация представляется в форме записей. Общий формат элемента секции для 1пГ-файлов, применяемых в УУИс1оуу5:
еШгу = уа1ие[,уа1ие[,уа1ие...]] где еп1гу является ключевым словом (начало директивы) либо маркером (ссылкой на значение — такая ссылка обособляется символами %...%). Параметры уа1ие являются значениями, соотносимыми с полем епСгу (именно, соотносимыми, а не присваиваемыми). В редких типах секций в роли поля епЬгу могут выступать имена файлов (например, в секции [8оигсеО18кРНе8]) или имена других секциях (например, в секции [0е51та1юп01Г8]). В 1пГ-файлах для нескольких систем в секциях для \Л/1пс1о\л/з 9х все запятые должны присутствовать в указанном в документации количестве, а в секциях для 1\1Т замыкающие перечисления запятые (если значения опущены) можно опускать (то есть в тех секциях, имена которых которые оформлены суффиксами .п1, .п1х86, и т.п.). Например, запись в секции 8оигсеО18к8РНе8 в общей нотации описывается следующим образом: ГПепате = сН5кк1[, [зиЬсПг] [, з/'ге] ] Пропуская значение зиЬЛг, указываем значение 5/ге и оставляем две запятых в середине: ГПепате = (И5к1с1„512е Если пропускаем два значения (зиЬсНг и з/'/е) в 1пГ-файле для МТ, то запись выглядит так: ГПепате = сНвкк! Для У\/1Пс1оуу59х это требуется ввести иначе: ГПепате = сНзкк!,,
Секция описания версии [Уегзюп] Корректно составленный 1пГ-файл начинается с секции [Мегзюп], которая является заголовком и меткой для всего драйверного !пГ-файла. Допустимые и необходимые записи внутри секции [Уегзюп] перечисляются в таблице 12.1. Таблица 12.1. Элементы секции [Уегя/оп] Записи Значения §|дпа1иге Обязательная запись. Одно из указанных ниже значений "$\Л/1Пс1о\л/5 МТ" — для ОС ряда \ЛЛпс1оуу5 МТ "$\Л/1пс1оуу5 95" — для ОС \Л/1пс1оуу5 9х/Ме "$СЫсадо$" — для всех версий ОС, поддерживающих \Л/ОМ драйвера С1Э55 Имя класса для целого семейства драйверов. Некоторые имена, например, Ие1, 01зр1ау или Опкпоууп зарезервированы (предопределены). В секции [Уегзюп] должна быть либо директива С1азз, соответствующая типу устройства, обслуживаемого устанавливаемым драйвером, либо С1аззСи1с1, либо обе сразу. С1аз5бшс1 Р|тллс1ег Уникальный СОЮ идентификатор для класса устройства, которое обслуживает данный набор драйверного программного обеспечения (см. таблицу 12.2). Поставщик 1МЕ файла, наименование организации и т.п. 1_ауои1Е|1е Используется только в 1МЕ файлах, поставляемых с операционной системой. Файлы, поставляемые ОЕМ (Ог1дта1 Еди1ртеп1 МапиГас1игег), то есть "при аппаратуре", должны вместо этого элемента использовать 5оигсеО1зкМатез и 5оигсеО15кЕПез Са1а1одН1е Указывает на са!-файл (с расширением .САТ), содержащий набор драйверных файлов. Этот набор формируется лабораторией МюгозоГ! Н\Л/ С^иаН1у 1_аЬ и содержит зашифрованную цифровую подпись проверенного драйверного программного обеспечения. Данный файл не должен подвергаться каким-либо формам архивации. ОгшегУег Обязательная запись. Независимо от локализации версии ОС имеет формат тт/с1с1/УУУУ[,х.у.у.2]; Здесь версия драйвера может быть введена через запятую после указания даты. В таблице 12.2 приводятся некоторые из инсталляционных классов, которые можно указывать в директивах С1аз5 и С1а55Си1с1. Наиболее полный и верный на текущий момент набор классов можно найти в разделе Системного Реестра НК1_М\Сиггеп1Соп1го15е1\Соп1го1\С1а88\{...}, где операционная система хранит все поддерживаемые на текущий момент классы устройств. Указанный раздел разбит на подразделы в соответствии с С11Ю идентификаторами классов, причем в каждом подразделе имеется параметр С1аз5, в котором хранится наименование соответствующего класса в текстовой форме. Таблица 12.2. Инсталляционные классы: названия и глобально-уникальные идентификаторы Наименование 1394 ВаИегу Описание Хост-контроллер шины 1394 Аккумуляторные устройства питания 611Ю идентификаторы {6В001ЕС1-810Е-1100-ВЕС7-08002ВЕ2092Е} {72631Е54-78А4-1Ю0-ВСЕ7-00АА00В7В32А} СОКОМ Устройства СО КОМ {4036Е965-Е325-11СЕ-ВЕС1-08002ВЕ10318} 0|зр1ау НЮС1азз 1пГгагес1 Дисплейные адаптеры НЮ устройства Устройства ИК-связи (1гОА) {4036Е968-Е325-11СЕ-ВЕС1-08002ВЕ10318} {745А17А0-74ОЗ-1Ю0-В6ЕЕ-00А0С90Е57ОА} {6В001ЕС5-810Е-1100-ВЕС7-08002ВЕ2092Е} КеуЬоагс! Клавиатура {4036Е96В-Е325-11СЕ-ВЕС1-08002ВЕ10318} МесПа Устройства мультимедиа {4036Е96С-Е325-11СЕ-ВЕС1-08002ВЕ10318} Мос1ет МопКог Модем Монитор {4036Е960-Е325-11СЕ-ВЕС1-08002ВЕ10318} {4036Е96Е-Е325-11СЕ-ВЕС1-08002ВЕ10318}
Моизе Ми1йРог15епа1 Манипулятор "мышь" Многопортовые последовательные адаптеры {4036Е96Е-Е325-11СЕ-ВЕС1-08002ВЕ10318} {5О9О6СВ8-ВА12-1Ю1-ВЕ50-ООООЕ8О5Е53О} ИеЬл/огк Сетевой адаптер {4036Е972-Е325-11СЕ-ВЕС1-08002ЬЕ10318} Ые^СНеп!: Сетевой клиент {4036Е973-Е325-11СЕ-ВЕС1-08002ВЕ10318} Ме1:5егу|се Сетевой сервис {4036Е974-Е325-11СЕ-ВЕС1-08002ВЕ10318} РСМС1А Адаптеры РСМС1А {4036Е977-Е325-11СЕ-ВЕС1-08002ВЕ10318} РОГ15 Порты (СОМ&ЬРТ) {4036Е978-Е325-11СЕ-ВЕС1-08002ВЕ10318} Ргтуег Принтер {4036Е979-Е325-11СЕ-ВЕС1-08002ВЕ10318} 5уз1:ет Системные устройства {4036Е970-Е325-11СЕ-ВЕС1-08002ВЕ10318} ТареОпуе Устройства работы с магнитной лентой {60807884-7021-11СЕ-801С-08002ВЕ10318} ипкпоууп Другие устройства {4036Е97Е-Е325-11СЕ-ВЕС1-08002ВЕ10318} 05В 05В устройства {36ЕС9Е60-С465-11СЕ-8056-444553540000} Указанный раздел Системного Реестра можно пополнять своими классами при инсталляции драйвера. Эта процедура достаточно проста, причем Мастер Установки У\/1пс1оуу5 любезно заполняет необходимые поля соответствующей информацией. Пример внесения собственного класса устройств будет рассмотрен чуть позже.
Секция описания поставщика [МапиГасСигег] Второй по важности секцией любого 1пГ-файла является секция поставщиков оборудования. В ней указываются ссылки на секции описания моделей [Моде/з] устанавливаемого оборудования. Первая из возможных форм записей в данной секции: °/о1океп°/о = тос/е1_зесИоп_пате В секции может быть много поставщиков и, соответственно, ссылок на секции описания моделей. Вот один из примеров СОК: [Мапи^асСигег] %АТАР1_СНСК% = а1зар1_с1адг %СН1ЫОН% = сЫпоп_сс1гот %ОЕЫ(Ж% = с!епоп_сс1гот %Е0ЛТ30% = Гид 1Сзи_сс1гот %Н1ТАСН1% = ЫСасЫ_сс1гот %НР% = Йр_сс1гот %М1ТЗОМ1% = т11:зит1_сс1гот %МЕС% = пес_сс!гот %ОТ1% = о'Ы_сс!гот %Р1ОЫЕЕК% = р1опеег_сс1гот %ИЕАКМЕЗ% = меагпез_сс!гот %0епМапи^ас1зигег% = сдгот с^еV^се Данная секция описания поставщиков содержит 12 записей о поставщиках. Слева указаны маркеры (1океп — идентификаторы, обособленные двумя знаками процента, см. ниже), которые позже, в том же 1пГ-файле в секции [81ппд5], соотнесены со строками-названиями производителей в полной текстовой форме. Справа от знака равенства указаны имена секций (например, секции [а1ар|_сЬдг]), описывающих установку программного обеспечения для моделей аппаратуры данного производителя (ссылки на секции моделей), которые были в этом файле рассмотрены позже. Другая форма возможна для операционных систем, начиная с \Л/1Пс1оуу5 ХР, где можно указывать разные секции моделей в связи с версией операционной системы. В результате строки в секции [МапиГас1игег] приобретают вид: °/о{океп°/о= тос1е1_зесИоп_пате[,Тагде1О5] [,Тагде1О5] Например, для \Л/1Пс1оуу5 5еп/ег 2003: [МапиГас1игег] %М5ЕТ%=М1сго5оГ1_Мос1е1_5ес11Оп, 1ЧТ.5.2 Соответственно, секции моделей будет начинаться так: [М1сгозо:б'Ь_Мос1е1_5ес'Ыоп] ; допустимо для ОС систем до и включая <тело секции моделей> ; Итпдомз ХР
[М1сгозо:Е'Ь_Мос1е1_5ес'Ыоп.МТ. 5.2] ; для Ихпдомз Зех^ег 2003 <тело секции моделей> Значение маркера следует раскрыть в секции [81ппд5]. Для секции описания производителя [МапиГас1игег] возможна и другая форма (содержащая только идентификатор производителя, являющийся одновременно и ссылкой на секцию моделей), например: [МапиГасСигег ] "Мхсгозо^О" Тогда и секция описания модели будет выглядеть иначе, например: [М1сгозоГС] <тело секции моделей> Таким образом, секция описания поставщиков является связкой между идентификаторами поставщиков, чье оборудование обслуживается устанавливаемым !пГ-файлом программным обеспечением, и наборами моделей аппаратуры от каждого из них. В подавляющем большинстве случаев 1пГ-файл содержит одного поставщика и, соответственно, одну секцию моделей. На рисунке 12.1 показана взаимосвязь между секциями !пГ-файла. Каждая запись в секции описания поставщиков [МапиГасСигег] указывает на секцию описания моделей для данного поставщика. В этой секции моделей вводятся уже записи, указывающие на секции, описывающие собственно установку программного обеспечения, то есть копирование файлов (откуда/куда), изменения в Системном Реестре и т.п. Рис. 12.1 Взаимодействие между секциями 114Е файла Могут быть также и секции, стоящие несколько в стороне от основной работы по установке, в частности, секция [С1а551п81а1132] и ей подчиненные секции, которые служат для внесения в Системный Реестр нового класса аппаратуры.
Секция описания моделей аппаратуры [Мос1е18] Для каждого поставщика, указанного в секции [МапиГасЛигег], должна быть представлена соответствующая секция описания моделей его аппаратуры [Мойе/з]. Имя данного типа секций не может быть жестко регламентировано, потому что разработчик сам задает его в секции [МапиГасЕигег]. В каждой такой секции [Мос/е/з] записи представляются по следующей форме: с1еУ1се_с1е8спрИоп = т8{а11_8есНоп_пате,Н}л/_1с1[,сотраНЫе_1'с1...] где деу/'се_с1е5спрПоп представляет собой уникальный набор видимых символов либо маркер, обязательный для определения в секции [51ппдз]. Данная строка будет предъявляться пользователю во время инсталляционного диалога, так что имеет смысл позаботиться о поддержке нескольких языков. Значение 1'пз1:а11_5есИоп_пате представляет собой ссылку на секцию, описывающую собственно действия по инсталляции для данной модели (в документации ООК такого типа секции обозначены как [СЮТлз&э//]). Значение Ъи<_/с/является РпР идентификатором, возвращаемым аппаратным устройством во время опроса РпР-совместимой шины. Например, 115В\Х/Ю_04В4&РЮ_1002 определяет плату тестового набора фирмы Сургезз (так называемый Е2115В КН). Любое количество значений сотраИЫе_1с1 может быть приведено для обозначения того, что та же самая инсталляционная запись должна быть использована для указанного в этом списке устройства. Применение одной и той же группы символов может сбить с толку начинающего разработчика 1пГ-файлов. Рассмотрим показательный пример из ООК для \Л/1пс1охл/з ХР. [Уегзтоп] ЗтдпаСиге = "$И1пс1омз МТ$" ; тпГ-файл для установки только под МТ С1азз=3уз-Ьеш С1аззС1ЛЛ={ 4с13 6е97с1-е325-11се-ЬГс1-08002Ье10318 } Р^ОV^с^е^=%МЗЕТ% СгНегУег= 5/1/2001 [МапиГасСигег ] %МЗЕТ%=МЗЕТ ; со знаками процента - маркер [МЗЕТ] %_МСАБе з с % =_МСА_1пз О,_МСА0 ООО [_МСА_1пз'Ь . пСх8 6 ] СоруЕНез = _МСА. ЕНез . х8 6_12 [ЗСгтпдз] МЗЕТ= "МтсгозоЕС" ; раскрываем маркер МСАЛезс= "МтсгозоГС МСА ВгИег"
В секции [МапиГасСигег] видим, что маркер %М5ЕТ% "приравнивается" ссылке М5ЕТ. Это означает, что в данной секции описан поставщик М1сго5оГ1: (именно так раскрывается маркер %М5РТ% в секции [81ппд8]), а модели аппаратуры представлены в секции описания моделей [М5ЕТ]. Переходя к рассмотрению секции моделей, видим маркер %_МСАОе5с% (раскрываемый как "М1СГ050Л МСА Опуег" — для диалога с пользователем при установке), который ассоциирован со ссылкой на секцию установки драйвера [_МСА_1п51]. Правда, эта секция снабжена суффиксом '.1\1Тх86' (регистр не имеет значения), сообщающим о том, что данная секция применима только для установки в операционной системе ЫТ на платформе 1п1е1 х86 (в документации СОК такое присоединение суффиксов называется декорированием имен секций). Начиная с версии операционной системы У\Лпс1о\л/5 ХР, имя секции моделей может декорироваться идентификатором версии, например: [МапиЕасЬигег ] %МЗЕТ%=МЗЕТ_шос1е1 [МЗЕТ_тос1е1. МТ . 5.1 ] [МЗЕТ_тос1е1.МТ.5.2] ; И1пс1оиз ХР ; И1пс1омз Зег^ег 2003
Замечания по декорированию имен Начинающие разработчики ИГ-файлов в большинстве своем испытывают значительные затруднения при работе с ними. Между тем, при определенном взгляде на данное "странное явление", каковым является ИГ-файл, трудности можно существенно уменьшить. Прежде всего, маркер, который является идентификатором, взятым в два знака процента, есть не что иное, как формальная переменная ('икс' в школьной задаче). Его внешнее сходство с чем-либо еще в записях ИГ-файла обманчиво. Во всех тех местах, где встречается маркер, следует подставлять его значение, соотнесенное с ним в секции [81ппд5]. Причем, это значение, может быть не обязательно текстовым, например: [Ехашр1е.ЗегVIсе ] В1зр1ауМате = %Ехатр1е . 5е:гу1сеЫате% Зег^1сеТуре = %ЗЕКУ1СЕ_КЕКМЕЬ_ОКТУЕК% ЗСагСТуре = %ЗЕК71СЕ_0ЕМАМ0_ЗТАКТ% [ЗСгтпдз] Ехашр1е . Зег^1сеМате="Ехашр1е К1ТЕЕК йгл^ег (7.001)" ЗЕК71СЕ_КЕКЫЕЪ_СК17ЕК=1 ЗЕК71СЕ_ОЕМАШ_ЗТАКТ=3 Здесь с маркером %Ехатр1е.5еп/1сеЫате% соотнесено строковое значение "Ехатр1е Г1ТООК с1пуег (7.001)", а с маркером %5ЕН71СЕ_КЕНЫЕ1_0к17ЕН% соотнесено значение 1. Текстовые значения (здесь Ехатр1е.5еплсеЫате) называются локализуемыми значениями (их можно задавать разными для разных языковых версий операционной системы, локализаций). Нетекстовые значения считаются нелокализуемыми. В том случае, если значение маркера не раскрывается, то будет полагаться, что поле, в котором введен маркер, просто имеет текстовое значение, совпадающее с маркером, например, "%Ехатр1е.5еплсеЫате%" в примере выше. Во-вторых, двусмысленна роль точки в формировании имен секций и маркеров. Если точка встречается в написании маркера, то следует считать ее обычным символом, который разработчик употребил ради большей выразительности. В случае если точка встретилась в имени секции (или в ссылке на имя секции), то возможно два варианта: Присоединяемый после точки суффикс — один из предопределенных суффиксов ЫТ, 1\1Тх86, х8б, а1рИа, 1аб4, 1\ГПа64, 1МТ.5.1, 1МТ.5.2 либо Зеплсез, МТ.Зеплсез, 1\1Тх86.5еплсез и некоторые другие. В данном случае, это модификация свойств секции. В частности, секция [Мос1е11_18Г.МТ.5.1] будет приниматься к рассмотрению только в операционной системе \ЛЛпс1оуу5 ХР. Присоединяемый суффикс не является модификатором свойств секции. В данном случае точку следует рассматривать как рядовой видимый символ, как и в упомянутом выше случае, когда точка встречается в идентификаторе маркера.
Секция [СоруЕНез] Секции [СоруЕНез'] имеют уникальные для 11\1Р файла названия, ссылки на них исходят из директив СоруЕНез секций [ОО1пз1аН]. Соответственно, конкретные имена этих секций определяет сам разработчик !пГ-файла. Каждая запись внутри секции [СоруЕНез] имеет вид с/езНпаНоп-Я1епате[, зоигсе-Н1епате][, 1етр-/Непате][, Над] где дезИпаИоп-Н1епате является целевым (то есть новым, конечным) именем файла после копирования. Предполагается, что и исходный файл имеет такое же имя. В том случае, если исходный файл все-таки называется иначе, необходимо указать зоигсе-Н1епате. Требование указывать 1етр-Н1епате все еще требуется для ХЛ/1Пс1о\/У5 98/Ме, и это поле вводит промежуточное имя для нового файла до момента первой перезагрузки системы. В \А/1пс1о\л/з 2000/ХР/2003 это значение игнорируется. Таблица 12.5. Определение значения Над в записях секции [СоруЕНез] Значение Символьное имя Описание 0x0400 СОРУР1-(э_НЕР1-АСЕОМ1-У Копировать исходный файл только в том случае, если в целевой директории есть файл с таким именем 0x0800 СОРУЕ1-(э_МООЕСОМР Копировать без разархивации (если файл обработан архиватором) Если файл с целевым именем в целевой директории 0x0008 СОРУЕ1_С_ЕОНСЕ_Е11_Е_1М_115Е сейчас открыт, то следует копировать исходный файл в файл с временным именем, форсировать перезагрузку, после чего переименовать временный файл 0x0010 СОРУЕ1_С_&Ю_ОУЕКУУК1ТЕ Не переписывать существующие одноименные файлы в целевой директории 0x1000 СОРУЕЬб_НЕРЬАСЕ_ВООТ_Е1ЬЕ Файл является частью системной загрузки, форсировать перезагрузку системы 0x2000 СОРУЕ1_С_МОРК1ШЕ Осуществить копирование, даже если инсталлятор не считает эту операцию целесообразной Не переписывать одноименные существующие файлы, 0x0020 СОРУЕ1-(э_Г1О_УЕК51ОГ1_О1А1-О(э которые датированы как более новые, нежели предназначенные к записи (игнорируется, если инсталлируемый пакет имеет цифровую подпись) Всегда переписывать целевые файлы (флаг 0x0004 СОРУЕ1-(э_МОУЕН51ОМСНЕСК игнорируется, если инсталлируемый пакет имеет цифровую подпись) Переписывать только те существующие файлы, которые 0x0040 СОРУЕЬС_ОУЕКУУК1ТЕ_О1_ОЕК_О&11-У являются более старыми, чем имеющиеся в пакете (данный флаг игнорируется, если инсталлируемый пакет имеет цифровую подпись) Предупреждать пользователя о возникшей 0x0001 СОРУЕ1-(э_УУАНМ_1Е_5К1Р необходимости пропустить переписывание файл (игнорируется, если инсталлируемый пакет имеет цифровую подпись) Запретить пользователю выбор возможности пропуска 0x0002 СОРУЕ1-(э_МО5К1Р каких-либо файлов при копировании (всегда применяется, если инсталлируемый пакет имеет цифровую подпись) Значение Над определяет управление новым целевым файлом, что подробнее отражено в таблице 12.5. Для описания сложного управления необходимо
выполнять ИЛИ над операндами — для получения одновременного воздействия указываемых вариантов. Некоторые варианты взаимно исключают друг друга (например, СОРУР1_С_УУАк1\1_1Р_5К1Р и СОРУР1_С_1\1О5К1Р), поэтому следует в сомнительных ситуациях обратиться к документации. Так как секции [СоруРНез] не имеют синтаксических средств указывать диск или полный путь к исходному файлу, то следует использовать другие секции, такие как [5оигсеО18к8№те8] и [5оигсеО18к8П1е8]. Место (конкретные файловые каталоги), куда файлы будут помещены в результате установки, определяется другой секцией, называемой [Ое81та1опО1Г8]. Следует отметить, что здесь секция [СоруРНез] описывается, как присутствующая в 1пГ-файле по той причине, что на нее ссылалась директива СоруРНез из секции [ОО/лгГа//]. На самом деле, директива СоруРНез может присутствовать и в секции [С1азз1п81а1132], которая посвящена инсталляции нового класса устройств в системе (будет рассмотрена ниже). Вводимая таким образом секция [СоруРНез] должна быть построена по таким же правилам, как указано здесь.
Секции [8еплсе1п51а11] Секции типа [5ел//се/пз6э//] предназначены для заполнения или модификации подраздела Системного Реестра, описывающего загрузку драйвера в сервисном подразделе для данного драйвера, а именно — в подразделе НК1_М\5у51ет\Сиггеп1Соп1го15е1\5еплсе8\<5еп/1се-пате>. Здесь оеп/юе- пате> — это значение поля зеплсе-пате, указанное в директиве Ас1с18еплсе в секции [ОО1пз{а11.Ххх.Беги 1сез]. Конкретное имя секции типа [Зегу/сеТпз&з//] выбирается разработчиком 1пГ-файла. Декорирование имен секций данного типа (с целью отразить ее предназначение для конкретной версии системы) уже не имеет смысла и не воспринимается, поскольку эта принадлежность должна была быть введена раньше — на уровне секций [ОО1пз1а11.Ххх.Зегу/^сез]. Описание директив для секций типа [5егу/се1пз1:а1Г\ приводится в таблице 12.10, причем директивы 5еплсеТуре, 51аг1Туре, ЕггогСоп1го1 и ЗеплсеВтагу являются обязательными. Эти директивы однозначно определяют информацию (значения одноименных параметров), которая появится в Системном Реестре в сервисном подразделе для данного драйвера — пример, касающийся драйвера Ехатр1е.5уз, рассмотрен в Приложении В. Таблица 12.10. Записи секции [5егУ1'се1пз1а11] Запись □|зр1ауПате Значение поля Развернутое наименование драйвера, выводится на экран Мастером Установки Оборудования □езспрйоп Краткое описание назначения драйвера или сервиса, выводится Мастером Установки Оборудования 5егу|сеТуре Для драйвера режима ядра 0x01 (см. также Приложение В) Определяет момент загрузки драйвера 0 — 5Ек\/1СЕ_ВООТ_5ТАкТ — во время загрузки системы (МОМ драйверы, опирающиеся на системные драйверы не должны использовать такой тип запуска) 1 — 5Ек\/1СЕ_5У5ТЕМ_5ТАкТ — во время инициализации системы (МОМ драйверы, опирающиеся на системные драйверы должны использовать такой тип запуска с 51аг1Туре осторожностью) 2 — 5Ек\/1СЕ_АиТ0_5ТАкТ — автостарт после запуска системы средствами 5СМ Менеджера (МОМ драйверы и драйверы РпР устройств не должны указывать этот код запуска) 3 — 5Ек\/1СЕ_ОЕМАПО_5ТАкТ — старт по требованию: либо по запросу РпР Менеджера при обнаружении РпР устройства, либо по явному запросу приложения при помощи вызовов 5СМ Менеджера 4 — 5ЕР.\/1СЕ_О15АВ1_ЕО — не может стартовать Распоряжение относительно возникающих ошибок: 0 — игнорировать все ошибки при загрузке драйвера 1 — показывать сообщения об ошибках пользователю ЕггогСоп1го1 2 — выполнить рестарт с набором параметров, обеспечившим последнюю удачную загрузку (1_а51Кпоууп(Зоос1), игнорировать дальнейшие ошибки 3 — выполнить рестарт с набором параметров, обеспечившим последнюю удачную загрузку (1_а51Кпоууп(Зоос1), контроль ошибок если таковые возникнут со стороны пользователя 5еплсеВ1пагу Путь к файлу драйвера (может включать коды сНпс!, таблица 12.6) АсШкед Вводит (через запятую) ссылки на секции типа [Ас/с/Яед], в которых описываются действия над Реестром, которые следует выполнить дополнительно к описанным в данной секции Идентифицирует группу, в которой должен загружаться драйвер (возможные группы можно 1_оасЮгс1ег(Згоир увидеть в разделе Системного Реестра НКЬМ\§у51ет\Сиггеп1Соп1го18е1\Соп1го1\(згоирОгс1ег1-151) Указывает сервисы (драйверы) или группы загрузки, которые должны быть загружены к 0ерепс1епс1е5 моменту загрузки драйвера. Имена групп выделяются при вводе предшествующим им знаком
Директивы 1_оасЮгс1ег6гоир и 0ерепс1епс1е8 широко используются при установке драйверов 5С51 устройств и фильтр-драйверов. Директива Ое1Яед, которая также может быть в составе [5егу/се1л5(а//], вводит ссылки на секции, описывающие удаление из Системного Реестра информации для уже установленных программных продуктов. Используется эта директива редко. Остальные директивы, возможные для ввода в секциях типа [5егу/се1л5(а//], а именно, 81агШате и ВЛЯед практически не используются.
Секция [С1а551п81а1132] Разработчик драйвера может создать собственный класс устройств (с собственным С11Ю, созданным при помощи программы СЫс1Сеп) и использовать его при установке своего драйвера. Данная операция не является сложной и выполняется при помощи секции [С1а$$1п$1а1132]Л например: [Уегзтоп] 51дпаЕиге="$СЫсадо$ " С1азз=Ехатр1еБгчС1азз С1аззСи1с1= { БС16ВЕ99-С0 6В-4801-А144-4 3А98ВВ99052 } [С1азз1пзЕа1132] АсИгед=Ехатр1еС1аззВед [Ехатр1еС1аззВед] ; секция изменений в Реестре НККЛг г Ог %С1аззМате% ; имя класса вводится через маркер %С1аззМате% [ЗЕгтпдз] ; Дополняем секцию значением маркера С1аззЕате=”Ехатр1е1з Бгтчег С1азз" Рис. 12.2 Новый класс в окне Диспетчера устройств Внесем приведенные выше дополнения в 1пГ-файл, предназначенный для установки драйвера Ехатр1е.5уз, см. главу 3. В результате установки Мастером установки появится новый класс с указанным СОЮ в разделе НКЬМ\8у81ет\Сиггеп1Соп1го18е1\Соп1го1\С1а88 Системного Реестра, см. рисунок 12.3. В его подразделе будет указан параметр С1аз5, содержащий значение имени "Ехатр1еОп/С1а55", и один вложенный подраздел \0000, описывающий установленный драйвер Ехатр1е.5у5, см. рисунок 12.4. Рис. 12.3. Новый класс в окне Редактора Системного Реестра Рис. 12.4. Вновь установленный драйвер класса Ехатр1еС1а88Р.ед Открывая описания классов в Системном Реестре при помощи Редактора Реестра, можно увидеть, что другие классы имеют существенно больше параметров, чем создано для нового класса Ехатр1еОп/С1а55 при помощи указанных выше записей.
Все недостающие параметры можно ввести в секции описания изменений в Реестре, в данном случае — [Ехатр1еС1а55Кед]. Имя секции [С1азз1п81а1132] может быть декорировано при помощи суффиксов .пЬ, .п1:х8б и .пйа64 для того, чтобы ограничить применимость данной секции. Помимо директивы АсИКед, обязательной для секции [С1азз1п81а1132], в данной секции могут присутствовать некоторые другие директивы (следует обратиться к документации СОК), из которых самой важной является директива СоруРНез. Синтаксис директивы СоруРНез совершенно аналогичен тому, который используется в секции [ОО1пз{а1Г\, и вводит информацию о копировании файлов, если таковые необходимы для завершения установки нового класса. В том случае, если при установке нового класса устройств еще требуется установить и некоторые драйверы, предусмотрена возможность их установки с использованием секции [С1аз81п51а1132.8еплсез] ([С1аз81п81а1132.Ххх.8еплсез]), использование которой аналогично [ОО1пз1а11. Зеплсез]. Созданный класс легко и безболезненно удаляется из Системного Реестра (в интерактивном Редакторе), если только в системе не осталось устройств данного класса.
Секции [ОеТаи111п51а1132.Ххх] и [ОеГаи111п51а1132.Ххх.§еплсе8] В \ЛЛпс1о\л/5 98 была возможность установки драйвера по нажатию правой кнопки мышки в программе Проводник на 1пГ-файле с последующим выборе в открывшемся меню пункта "Установить". В У\Лпс1о\л/5 2000/ХР/2003 для такой установки необходимо наличие в 1пГ-файле секций [ОеГаи111пз1а1132.Ххх] и [ОеГаи111пз1а1132.Ххх.Беплсез], где "Ххх" обозначает суффиксы декорирования имен пЬ, п1:х8б, пйа64. Использование таких секций и усеченная установка из программы Проводник (то есть без вовлечения Мастера Установки) зачастую дают неприемлемые результаты, поэтому рекомендуется при установке драйверов использовать обычный способ установки через Мастера Установки новых устройств.
ГРгеу|ои51 [Мех!],
Секции [ОО1п51а11] Для каждой модели, указанной в секции описания моделей аппаратуры данного поставщика, следует сделать ссылку на секцию описания собственно установки программного обеспечения драйвера — секции [001п51а11]. Конкретное название этой секции устанавливает разработчиком драйвера и, в общем случае, должно быть уникальным для каждой модели каждого производителя из тела каждой секции описания моделей. Однако бывают случаи, когда одному драйверу удается обслуживать сразу несколько моделей РпР устройств, предоставляющих при подключении разные идентификаторы. В таких случаях возможна ситуация, когда одна секция типа [001п51а11] соответствует сразу нескольким ссылкам из секции описания моделей. Основные директивы секции [ОО1п51а11] перечисляются в таблице 12.3. По поводу использования остальных следует обратиться к документации пакета ЭОК. Таблица 12.3. Элементы секции [ОО1пз(а11] Записи Значения ОпуегУег т т/с1 с1/у у у у [, х. у. V. 2 ] Здесь версия драйвера может быть введена через запятую после указания даты СоруЕПез Любое имя секции, указывающей имена файлов для инсталляции, либо конкретное имя файла, предваряемое префиксом @ СоруТпГ Директива, определяющая копирование 1пГ-файлов на целевой диск. Введена только в \Л/1пс1о\л/8 ХР. АсИкед Обязательна для ввода. Перечисляет имена секций, где содержится информация, предназначенная для занесения в Системный Реестр во время инсталляции. 1пс1ис1е Указатель на другие 1ЫЕ файлы, необходимые для данной инсталляции Ыеес15 Подмножество/а записи 1пс1ис1е (выше), перечисляющее имена всех необходимых секций (считая все включаемые 1ЫЕ файлы). 0е1ЕНез Указывает имена других секций, которые перечисляют файлы, подлежащих удалению в целевой директории (обычно, в процессе обновления, ирдгас1е). кепЕНез Указывает имена других секций, которые перечисляют файлы, подлежащих переименованию перед инсталляцией (обычно, чтобы сохранить состояние предыдущей инсталляции). Об организации секций, описывающих переименование см. подробнее в документации ОЮК. Юе1кед Указывает имена других секций, которые содержат информацию, что именно следует удалить из Системного Реестра при инсталляции В то время, как АсШКед требуется только с точки зрения синтаксиса, директива СоруЕПез является весьма значимой директивой секции [ОШлзЬэ//]. Директива СоруРНез имеет форму СоруРНез = Н1е_Нз1_зесНоп[,Н1е_Нз1_зесНоп...] либо СоруРПез=@Н1епате Первый из двух вышеприведенных вариантов является более емким, поскольку позволяет косвенно указать другую секцию, где содержится список файлов, подлежащих инсталляции. Однако для простых инсталляций, непосредственное указание имени файла успешно справляется с этой задачей. Назначение Ас1с1Яед и СоруРНез более проясняется в нижеследующих частях данной главы.
Когда имя секции [ОО1п5Ёа11] упоминается в ссылке из секции описания модели, то суффиксы, задающие версию системы, применять не следует. В момент ссылки в директивах секций описания моделей имя секции [ООТпзЁаН] задается универсально, одинаково для всех типов операционных систем (без стандартных суффиксов, типа . 1\1Т или .1МТх86). Зато, в начале собственно тела секции, имя [ООТпзЁаН] секции может быть декорировано одним из суффиксов типа .п1, .пЬ<86 или п11а64, что означает принятие к исполнению данной секции только в соответствующей операционной системе. Пример ниже демонстрирует, что конкретизация происходит в момент описания собственно секции [ОШлзЬэ//]. [Мапи^асЬигег] %М5ЕТ%=М5ЕТ [МЗЕТ] %_МСАВезс%=_МСА_1пз'Е, _МСА0000 [_МСА_1пзЕ.пЕх8 б] СоруЕНез = _МСА. ЕНез . х8 6_12 Здесь в секции моделей [МЗЕТ] введена ссылка на [ОШлзЬэ//] секцию с конкретным именем _МСА_1п51, и эта секция была введена только для использования в \ЛЛпс1оу75 1\1Т. Поэтому имя было декорировано суффиксом .1МТх86, что в результате выглядит как [_МСА_1п51:.п1:х86].
Секция [ОО1п51а11.8еплсе5] Чтобы скопированные в положенное место файлы действительно заработали как настоящий драйвер, необходимо надлежащим образом уведомить 5СМ Менеджер. Соответственно, для этого необходимо иметь записи в системном Реестре в разделе НКЬМ\5у81ет\Сиггеп1:Соп1го15е1\5егу1се8 по поводу каждого драйвера. В \Л/1Пс1оу75 2000/ХР/2003 работа по регистрации драйвера вынесена в отдельный тип секций [ОО1п5Ёа11.Зеплсез]. Можно считать, что данная секция является придатком секции [ОО1пз[а1Г\ и всего лишь конкретизирует, какие секции 1пГ-файла (зеплсе-тз^аП-зесИоп) определяют, что именно будет внесено/удалено в Системный Реестр в рамках установки драйвера. В \ЛЛпс1оал/5 98 следовало пользоваться директивой АсИЯед в секции [ОО1пз1:а11]. В \ЛЛпс1оал/5 2000 ИГ-файл стал сложнее, и работа с Системным Реестром была выделена в еще один "промежуточный" тип секций [/Э/Э1л8&э//.5еплсе8]. В том случае, если имя [ООТлзЬэ//] секции декорировано одним из суффиксов .п1, .п1х86 или пйа64 (что означает принятие к исполнению данной секции только в соответствующей операционной системе), то суффикс .Зенлсез следует присоединять именно к получившемуся декорированному имени [ОО1п5Ёа11.Ххх]. Среди записей секции [ООТпзкаН.Ххх.Зенлсез] имеется обязательная директива АсШЗеплсе (помимо еще трех необязательных), которая имеет вид: Ас1с15еп/1се=5еп/1се-пате,[Г1адз],5еп/1се-1пз1а11-зесНоп[ ,еуепИод-1пз1а11- зесНоп[,еуеп1-1од[,еуеп1-пате]]] где зегу/се-пате представляет название сервиса, что обычно совпадает с названием Драйвера (если отбросить расширение .зуз). Подраздел с таким именем будет создан в результате инсталляции в разделе Системного Реестра НКЬМ\5уз1:ет\Сиггеп1:Соп1го15е1\5егу1се8. Директив АсШЗенлсе в секциях [ОО1пз1:а11.Ххх.5ег\псе5] может быть несколько. Возможные значения поля Ладз приводятся в таблице 12.4. Можно вводить значения, которые представляют "побитовое ИЛИ" указанных в таблице значений. Значение поля 5егУ1'се-1'пз1:а11-зесИоп и необязательное для ввода значение поля еуепИод-1п5Ёа11-зесИоп (ссылка на секцию, где описывается установка сервисов протоколирования) объявляют имена секций 11\1Р файла типа [5еп/1се1пз{а1Г\, например: [Мапи^асЬигег] ; секция описания поставщиков %Т^1^зМФд%=^еV^се^^зЬ [^еV^се^^зЬ] ; секция описания моделей %Мос1е11%= Мос1е11_1пз'Ь, _МХХ0001 ; <- идентификатор [Мос1е11_1пзб. п1;х8 б] ; [СЛТпзбаИ] секция инсталляции для ЛТ [Мос1е11_1пз'Ь . п'Ьхй 6 . Зез^Фсез ] ; установка драйвера как сервиса МххОО
; Поле Ладз имеет значение 0x0002 (см. таблицу 12.4) ; ссылка на секцию [Зе^1се1пзба11] Ас1с13е^1се= Мхх0001, 0x0002, МООЕЫ_АООЗЕКУ1СЕ_ЗЕСТ1ОМ [МОВЕЫ_АВВЗЕНУ1СЕ_ЗЕСТ1ОЫ] ; секция [ зегчФсе-Фпз'ЬаН-зес'Ыоп] БтзрТауПате = %Мос1е11.5ег^1сеЭезс% Зе^ФсеТуре = 1 ; = ЗЕК71СЕ_КЕКПЕЬ_0К17ЕК, см. пбсМк.Ь, и<±п.Ь ЗРагбТуре = 3 ; = ЗЕКУ1СЕ_ОЕМАЫО_ЗТАКТ, см. пЫйк.Ь, и<±п.Ь ЕггогСопбго1 = 1 ; = ЗЕК71СЕ_ЕКК0К_П0КМАЬ, см. пЫйк.Ь, искп.Ь 5ег^ФсеВФпагу= %12%\ЗресФаЮ^. зуз [ЗбгФпдз] ; Расшифровка значений ТНФзМФд= "ТЫз МапиФасЬигег" Мос1е11= "Мос1е11 (тас1е Ьу ТЬФз МапиФасбигег) " Мос1е11.5ег^ФсеЭезс= "Мос1е11 ЗресФа! Огз^ег V. 1.000" Таблица 12.4. Значения Надз директивы Ас1с18еплсе Значение Символьное имя 5Р55/С1Н5Т_Ххх Описание 0x0002 5Р5\/С1М5Т_А55ОС5ЕР\/1СЕ Драйвер является функциональным драйвером или драйвером в "стиле-МТ" 0x0008 ...МОС1_ОВВЕР_ _О15Р1_АУЫАМЕ Не переписывать существовавшее в Системном Реестре значение 018р1ауНате, если такой сервис уже был установлен ранее 0x0100 ...МОС1_ОВВЕР_ .□Е5СН1РТЮЫ Не переписывать описание 0x0010 ...МОС1_ОВВЕР_ .ЗТАкТТУРЕ Не переписывать 51аг1Туре 0x0020 ...МОС1_ОВВЕР_ _Екк(ЖСО1\1ТкО1_ Не переписывать ЕггогСоп1го1 0x0040 ...МОС1_ОВВЕР_ ЛОАООкОЕкСкООР Не переписывать 1_оасЮгс1егСгоир 0x0080 ...МОС1_ОВВЕР_ .□ЕРЕЫОЕМС1Е5 Не переписывать 0ерепс1епс1е5 Более подробно организация секций типа [5еп/1се1п5(:аИ] (включающих директивы О19р1ауМате, 5еплсеТуре, 51аг1Туре и т.п.) будет рассмотрена далее, после описания секций типа [Ас1с1Я.ед]. В секциях типа [ОО1п5(:а11 .Зеплсез] могут быть введены директивы Ое15еплсе (удаления ранее установленных сервисов), 1пс1ис1е (включения текста внешних 1пГ- файлов) и директива №е<15. Для получения информации по данным необязательным директивам следует обратиться к соответствующей документации, например, документации ООК.
ГРгеу|ои81 ГИехП
Другие секции, определяющие копирование файлов Как было сказано выше, дополнительную информацию о том, где взять исходные файлы и куда их скопировать предоставляют секции [8оигсеО18к8Ыате8], [8оигсеО18к8РЛе8] и [Ое81та1опО1Г8] (имена данных секций не подлежат изменению разработчиком).
Секция [§оигсеО15кЫате5] В случае, если файлы, относящиеся к установке драйвера и управляемые данным 11МЕ файлом, размещены более чем на одном диске (СО или дискете), то 11МЕ файл должен содержать секцию [5оигсеО18кзМатез]. Эта секция содержит по одной записи на каждый диск из набора, предлагаемого для установки. Запись имеет вид: сН5кк1=сП5к_с1е5спрНоп[,[{адН1е],[неиспользуемое_поле,ра1Н][,Яад5]] где сНзк/с/ это уникальное в пределах набора дисков неотрицательное целое число. Как правило, нумерация дисков начинается с 1, хотя возможна и шестнадцатеричная нумерация, например 0x0, 0x1 и т.п. Поле сИзк_с1е5спрИоп является понятной человеку строкой (выделенной кавычками), которая может быть использована для того, чтобы проинформировать пользователя о том, какой диск требуется установить в привод. Здесь можно применять маркер (вместо строки в кавычках), который, соответственно, раскрывается строкой в секции [51ппд8]. Значение СадЛ1е играет двойную роль. Для уверенности в том, что пользователь предоставил правильный диск во время процесса установки, значение 1адП1е (имя файла и расширение, путь не указывается) используется для проверки: файл СадН1е должен присутствовать на вставленном носителе в корневой инсталляционной директории или в путях раС/1. В случае, если файл на носителе отсутствует, будет выведена строка подсказки, предлагающая пользователю вставить правильный диск (дискету). В случае, если значение СадЯе содержит расширение .САВ, в дальнейшем будет полагаться, что этот файл представляет собой набор сжатых (архивированных) файлов в качестве файлов, предназначенных для инсталляции с этого диска. Поле является значением пути (относительно инсталляционной директории, то есть где находится интерпретируемый 1пГ-файл) к исходным драйверным файлам на предлагаемом диске. Так же, как и 1адЯ1е, значение раМ1 является необязательным. В случае, если оно не указано, то полагается, что инсталляционная директория и содержит исходные файлы, относящиеся к установке драйвера. Поле Падз обычно не используется, как и еще одно поле (1ад_Н1е), введенное только В \Л/|ПС|0\/У5 ХР. Имя секции [5оигсеО18к8№те8] может декорироваться суффиксами версий операционной системы х86 и 1а64, так что в 1пГ-файле может быть несколько секций данного типа, отличающихся суффиксами, например: [ ЗоигсеВгзкзМашез.х8 6] [ ЗоигсеВ1зкзМашез.1а64]
Секция [§оигсеО15кН1е5] Инсталляционный 1пГ-файл должен содержать секцию [8оигсеО18кП1е8], в которой перечисляются имена файлов, составляющих предмет инсталляции. Каждый файл представлен одной записью в этой секции в форме: Ыепате = сНзкк! [, [зиЬсНг] [,5/ге] ] Соответственно, значение сНзк/с! указывает диск, введенный в секции [8оигсеО18к8№те8], где находится файл Я/епате. Необязательное для ввода значение зиЬсНг указывает путь к этому файлу относительно директории, указанной полем ра111 в соответствующей (по сНвк/сТ) записи секции [8оигсеО18к8Ыате8]. Если значение ра1Ь не было там указано, то подразумевается инсталляционная директория (там, откуда взят 1пГ-файл). Необязательное значение 5/ге описывает размер файла в байтах в несжатой форме. В процессе инсталляции эти данные о размерах могут быть использованы для прогнозирования, достаточно ли дискового пространства в системе, до начала копирования файлов. © Практически, значение сНвкн! следует рассматривать всего лишь как идентификатор для построения связки "запись в секции [5оигсеО'1зкРПез]" — "запись в секции [ЗоигсеО1зкзЫатез]", что необходимо системному программному обеспечению, выполняющему установку, для уяснения, откуда следует брать исходные файлы. Имя секции [8оигсеО18кН1е8] также может декорироваться суффиксами версий операционной системы х86 и 1а64.
Секция [ОезНпаНопОив] Данная обязательная секция 11\1Р файла описывает файловый каталог (директорию), в который будет производиться копирование исходных файлов. Без этой секции программа, выполняющая установку драйвера, не будет знать, куда копировать файлы исходных носителей. Записи в секции [0е51та1юп01Г5] имеют вид: ГНе_И5{_5есИоп=сНпс1[,5иЬсНг] либо ОеГаи1Юе5Ю1г=сИг1с1[,5иЬсИг] где Я1е_Пз1:_зесНоп как раз указывает на секцию типа [СоруРПез], на которую была объявлена ссылка также и в директиве СоруЕНез из секции типа [ОО1пз1:а11] (или [ОО/лзГа/АХхх]). Это означает, что все файлы, перечисленные в этой "перекрестно ссылаемой" секции типа [СоруРПез], будут скопированы в файловый каталог (директорию), которому присвоен идентификатор сНпд. Значение сИпс/ указывает численное значение, за которым стоит достаточно конкретный файловый каталог (директория) назначения. Таблица 12.6 описывает возможные числовые коды сИпс/ и ассоциированные с ними файловые каталоги. Запись 0еГаи110е8101г=... указывает место, куда будут скопированы файлы, перечисленные в остальных секциях типа [СоруРПез], для которых не было указано в явной форме (записями вида П1е_||51_5ес1!оп=с1|пс1[,5иЬс1|г]) то место, куда переносить эти файлы. В том случае, если указано значение зиЬсНг, то оно используется для того, чтобы конкретнее указать позицию целевого файлового каталога относительно каталога, заданного кодом сПпс1. Таблица 12.6. Значения кодов сИпс! в контексте секции ОезИпаИопО/гз Значения Описание 12 %уу|п<3|г%\5у51ет32\с1пуег5 для \ЛЛпс1оуу5 2000/ХР/2003 %)А/1пс1|го/о\5у51ет\1о5иЬ5у5 для \Л/1пс1о\л/5 98 10 %\лл псИг%\ 11 %уу|ПсПг%\5у51ет32 для \Л/1Пс1о\л/5 2000/ХР/2003 %\ллпс1|г%\5у51ет для \ЛЛпс1о\л/5 98 30 Корневая директория загрузочного диска 54 Загрузочная (Ьоо!) директория \Л/тс1о\л/5 2000/ХР/2003 01 Директория данного 11Х1Р файла 17 Директория 1ИЕ файлов 20 Директория шрифтов 51 5роо1-директория 52 Директория зроо1-драйверов 55 Директория программ поддержки печати (рпп! ргосеззогз) 23 Со1ог (1СМ) -1 Полное имя файлового каталога (абсолютный путь) 21 Директория вьюверов (программ просмотра файлов различных форматов — графики, текстов, файлов БД и т.п.)
53 24 25 18 16406 16407 16408 16409 16415 16419 16422 16427 16429 Директория пользовательского лицевого счета (Озег РгоГПе) Директория размещения приложений Совместно используемая директория Директория файлов-справок (Ие1р сПгес!огу) АП изегз\51аг1 Мели АП изегз\51аг1 Мепи\Ргодгатз АП изегз\51аг1 Мепи\Ргодгатз\51аг1ир АП изегз\Оезк1ор АП ОзегзХЕауогКез АП изегз\АррПса1юп Оа1а Ргодгат ЕНез Ргодгат ЕПез\Соттоп АП изег5\Тетр1а1ез 16430 АП изегз\Ооситеп15 В секции [0е81та1юп01Г5] может быть только одна запись 0еГаи110е5101г=... и много ссылок на секции вида Н1е_П5{_5есНоп=сНпс1[,5иЬсНг].
Примеры описания процедуры копирования файлов Рассмотрим простейший пример взаимодействия информации, вводимой в секциях, управляющих копированием файлов [8оигсеО!$к$Ыате$], [8оигсеО!$к$Р!1е$], [Ое§1та1опО|г§] и [СоруЕПез]. [МапиЕаскигег] %ТЫзМ^д%= МодеХЫзк ; ссылка на секцию моделей [МобеХЫзЕ ] "13А Наштег"=1пзба!ХНаттег, 13А\Наттег [ ХпзбаХХНаттег] ; секция инсталляции конкретной модели СоруЕХХез=СоруНаттегЕХХез ; секция СоруЕХХез СоруЕХХез=СоруНаттегНеХр ; еще одна секция СоруЕХХез Ас1с1Кед=НаттегКедЗесЕХоп ; ссылка на секцию АббКед [ВезЕХпаЕХопЕХгз] ; Куда следует выполнять копирование: ЕеЕаи1ЕЕезЕВ1г=12 ; по умолчанию -> %мХпс!Хг%\8у8Еет32\с1гХчег8 СоруНаттегНеХр=Х8 ; стандартная директория для НеХр файлов [СоруНашшегЕХХез ] Наттег.вув ; <- будет скопирован в директорию с1ХгХс1=Х2 [Сору Наштег НеХр] Наштег.НХр ; <- будет скопирован в директорию с1ХгХс1=Х8 [ЗоигсеБХзкзКатез] ; Подразумевается, что устанавливаемые файлы ; находятся в том же файловом каталоге, что и данный ХпЕ-файл. Х="Наштег БгХчег ЕНез” [ЗоигсеБХзкзЕХХез ] Наштег.зуз=Х; Ссылается на единственную запись в [ЗоигсеБХзкзКатез] Наттег.Ыр=1; Ссылается на единственную запись в [ЗоигсеБХзкзКатез] [ЗЕгХпдз] ТНХзМЕд="ВХд Наштег МапиЕаскигег" Файл Иаттег.зуз будет скопирован в директорию \Л/|пс!о\л<з\Не1р (\Л/|пс!о\л<з ХР) или \Л/т1\1Т\Не1р (\Л/1Пс1о\л<з 2000) . Немного модифицируем пример: используем в имени секции точку и изменяем направление [ ХпзбаХХНаштег] СоруЕ11е8=СоруЬаипсННе1р. ЗесЕХоп [ ВезЕХпаЕХопВХгз ] СоруНаттегНеХр.ЗесЕХоп = -1, С:\Наттег
[СоруНаттегНе1р. ЗесЫоп] Наттег. Ыр От введения суффикса .ЗесЫоп взаимодействие секций не изменяется. Поскольку в секции [0е81та1юп01Г8] теперь указано '-1' (абсолютный путь), то файл Иаттег.Ыр будет скопирован в каталог СДНаттег. В случае, если такой каталог не существует, он будет создан.
ГРгеуюц51 ГИехН
Секции типа [Абб Ре д] содержат описание действий по внесению новых подразделов и/или параметров и их значений в Системный Реестр, а также действия по модификации значений уже существующих параметров. Ссылки на секции данного типа могут присутствовать в секциях [ООТпзСаП], [С1а551п51а1132] и секциях [ЗеплсеТлзСаН] (обозначенных ссылками из директив АсШ5еплсе в секциях [рО1пзСаН.Зеплсез]). Кроме того, ссылка на [АббРед] может быть введена и из секций, описывающих установку интерфейса (для организации доступа к объекту устройства по идентификатору интерфейса), что в данной книге не рассматривается. В перечисленных типах секций ссылки на [АббРед] вводятся директивами АсШКед. Конкретное имя секций типа [АббРед] зависит от разработчика ИГ-файла. Каждая запись внутри секции [АббРед] имеет вид гед-гооЁ, [зиЬкеу], [уа1ие-пате], [Над] , [уа1ие] Здесь в поле гед-гоо1 следует ввести аббревиатуру одного из корневых разделов Системного Реестра, возможные значения которых перечислены в таблице 12.7. Эти значения указывают на корневые разделы, в чьих подразделах будут сделаны изменения. Поле зи/экеу представляет наименование подраздела внутри указанного корневого раздела. Значении НКН не имеет конкретного, раз и навсегда определенного, значения. Его конечное значение в записях секции [АббРед] зависит оттого, из какой секции была сделана ссылка на [АббРед]. Таблица 12.7. Аббревиатуры корневых разделов Системного Реестра Значения Описание НКСЯ НКЕУ_С1_А55Е5_НООТ НКСО НКЕУ_С1ЖкЕ1\1Т_и5Ек НКЬМ НКЕУ_1_ОСА1__МАСН1ИЕ НК11 НКЕУ_115ЕР.5 Контекстный раздел Системного Реестра (то есть какой конкретно раздел Реестра будет НКР модифицирован, зависит от того, в какой секции была сделана ссылка на секцию типа [Ас/с/Яед]) Значение уа1ие-пате обозначает имя параметра в модифицируемом подразделе зиЬкеу, который (параметр подраздела, то есть) будет добавлен или модифицирован. Значение Над описывает тип данных, который должен быть сохранен в поле значения параметра данного модифицируемого подраздела Системного Реестра. Возможные значения (из числа применимых в \ЛЛпс1о\л/з 2000, ХР и 2003), которые может принимать поле Над, перечисляются в таблице 12.8. Таблица 12.8. Основные значение поля Над в записях секции [АббРед] Значение Символьное имя Описание 0x00000 РЬ(э_АООЯЕ(э_ТУРЕ_§Н Строка символов, завершающаяся нулем {значение Лад
по умолчанию, если это поле в записи опущено) 0x00001 ЕЬ(э_ АООРЕС _В1МУА1_ОЕТУРЕ Бинарные данные 0x00002 ЕЬ(э_ АООРЕС _МОСЬОВВЕР Не замещать существующее значение 0x00004 ЕЬ(э_ АООРЕО _ОЕ13/А1_иЕ Стереть подраздел или параметр 0x00010 ЕЬ(э_ АООРЕО _КЕУО1Ч1-У Создать подраздел, игнорировать параметр и его значение 0x00020 ЕЬ(э_ АООРЕО _ОУЕРУУР1ТЕО1Ч1-У Если параметр существует, заменить, иначе ничего не предпринимать 0x10000 ЕЬ(э_ АООРЕО _ТТРЕ_М1Л-Т1_§г Данные К.ЕС_М111_Т1_52 (массив строк) 0x00008 ЕЬ(э_ АООРЕО _АРРЕ№ Присоединить к существующему массиву строк К.ЕС_М 11 ЕЛ-52. Применим только совместно с ЕЕС_А00кЕС_Т7РЕ_МиЕП_52 0x20000 ЕЬ(э_ АООРЕО _ТУ РЕ_ЕXРА^ о_§г Данные типа Р.ЕС_ЕХРАПО_52 0x10001 ЕЬ(э_ АООРЕО _ТУРЕ_ОАЛЮРО Данные типа РЕС_О\А/ОРО 0x20001 ЕЬ(э_ АООРЕО _ТУРЕ_1Ч(ЖЕ Данные Р.ЕС_1\ЮПЕ Значения Над в ИГ-файлах должны указываться собственно в числовом значении (символьные имена, указанные в таблице 12.8 не применяются). Однако при желании, для улучшения читаемости тГ-файла, можно применять маркеры,например: [ 064106111311311360111011] АсМКед = Эе41сеАс1с1КедЗес111оп [ Эе41сеАс1с1КедЗес111оп] НКК,, ТМзОгйег, %КЕС_ОИОКО%, 1 НКК,, ТпзкаЫесЮгУчегз, %КЕС_МНЬТ1_32%, Оеч1се0001 [ЗСггпдз] ; расшифровка значений маркеров КЕС_32 = 0x00000000 КЕС_МОЬТ1_32 = 0x00010000 КЕС_ЕХРАКЭ_32 = 0x00020000 КЕС_В1КАКУ = 0x00000001 КЕС_ОИОКБ = 0x00010001 Следует помнить, что в том случае, если в полях записей вводится значение, содержащее пробелы или другие специальные символы, то эту группу символов следует заключить в кавычки. Если же значение простое (пусть даже строка), то его заключать в кавычки не обязательно, например: [МуБггчег. Тпз'ЬаИ ] СоруЕИез=. . . Ас1с1Кед=МуОг1 чет. АскЛКед [МуБггчег. АсИКед] НКК, , ВечЬоас1ег, , *п!1кегп НКК,,МТМРОгтчег,, МуБгтчег.зуз ; (Над = ЕЪС_АРРКЕС_ТУРЕ_32)
что равносильно НКК, , "МТМРОгз^ег", , "МуОгз^ег. зуз"
Значения НКЯ Аббревиатурой НКН в 1пГ-файлах обозначаются подходящие по контексту подразделы Системного Реестра, применимые для данной операции. Число типов приемлемых подразделов, на которые мог бы указывать контекстный параметр НКН, невелико, среди которых разработчику драйверов могут понадобиться следующие: Подраздел экземпляра аппаратуры, Нагдууаге 1п51апсе Кеу. Такие подразделы описывают экземпляр устройства в процессе перечисления и видны в разделе НК1_М\5у8(ет\Сиггеп(Соп1го18е1\Епит, например, НК1_М\5у51ет\Сиггеп1:Соп1то18е1:\Епит\118В\\/1с1_0458&Р1с1_000е\5&1е1Г5333& 0&1, мышь "Сепшз". Подраздел класса, С1аз5 Кеу, описывает зарегистрированный класс драйвера. Его можно найти в разделе Системного Реестра НК1_М\5у8(ет\Сиггеп1Со11(го18е1\Соп1го1\С1а58 (обязательно с добавлением С11Ю класса), например, для описанной выше мышки это будет ...\С1а55\{745А17А0-74ОЗ-1Ю0-ВбЕЕ-00А0С90Е57СА}, то есть НЮС1а55. Драйверный подраздел, Опуег Кеу, описывает установленный драйвер в подразделе для всех устройств данного класса (подразделе класса), например, .. .\С1а55\{745А17А0-74ОЗ-1Ю0-В6ЕЕ-00А0С90Е57ОА}\0000. Сервисный подраздел, 8егу1се Кеу (или 8оГЬ\л/аге Кеу), описывает, где находится загружаемый файл драйвера, когда его следует загружать, как обрабатывать ошибки и т.п. Такие подразделы видны в разделе НК1_М\5у8(ет\Сиггеп1Соп(го18е1\5еплсе8, для примера с 115В мышкой это будет НК1_М\8у51ет\Сиггеп1:Соп1то18е1:\8еплсе5\Н1с1115Ь. Теперь можно перечислить, куда указывает НКЯ в записях конкретной секции типа [Ас1с1Пед], в зависимости оттого, какая секция сослалась на данную секцию типа [АсЮПед], см. таблицу 12.9. Таблица 12.9. Значение параметра НКР в секциях [Ас1с1Пед] Откуда исходит ссылка на [Ас/с/Яед] Куда указывает НКЯ Секция [ООТпзСаИ], директива АсИЯед Драйверный подраздел Секция [ОО1пз1:а11.Ххх.Н\м]г директива АсИЯед Подраздел экземпляра аппаратуры Директива АсИЯед в секции [Зегу/сеТпзСаП], на которую указывает ~ директива АсИЗеплсе секции [ООТпзЁаН.Ххх.Зегулсез] р м Секция [С1а551п51а1132] или [013551051311], директива АсИЯед Подраздел класса Секция [ОО1п5Са11.Ххх.Со\пзЪа\\ег5]г директива АсИЯед Драйверный подраздел
ГРгеу|ои51 [Мех!],
Проверка синтаксиса ПЧР файла Отладка 1пГ-файлов не является простой задачей. В состав ЭЭК входит утилита СНК11ЧЕ, служащая для проверки правильности синтаксиса и организации 1пГ-файлов, и ее можно найти в подкаталоге Тоо1з каталога ЭЭК. Она опирается на интерпретатор Рег1, который доступен для загрузки с интернет-сайта эег1.сот. Вывод результатов выполняется в форме НТМ1_ файла. Хотя нельзя не признать определенные достоинства этой утилиты, тем не менее, как указывают многие источники, она выдает ошибки при проверке вполне нормальных 1пГ-файлов. Установив АсИуеРег! интерпретатор, загруженный с сайта , и запустив его командой из директории пакета ЭЭК \1оо1з\сНк1пГ сНктГ.ЬаГ раГ/?\Ехатр1елпГ /Ь где раГЪ — это путь к проверяемому ИГ-файлу, ключ /Ь обеспечивает автозапуск 1Е (ТпГегпеГ Ехр1огег) для просмотра результатов, получаем результаты проверки в окне 1Е, см. рисунок 12.5. Текстовый вариант до исправления некоторых погрешностей выглядел следующим образом: Зиттагу о! "к:\Ех\Ехатр1е.тпГ" Тока! Еггогз: 2 Тока! Иагптпдз: 6 Еггогз: Ыпе 5: (Е22.1.1081) ОхгесЫ^е: Сака1одЕ11е гедиЫес! (апс! тизк пок Ыапк) 1п зесЫоп [Уегзгоп] Тог ИН<2Ь сНд1Ы1 згдпакиге. Ыпе 11: (Е22.1.1310) С1азз Ехатр1еО^С1азз (С1аззСУ1Б {БС16ВЕ99-С06В-4801-А144-43А98ВВ99052}) тз ипгесодптхес!. Иагпгпдз: Ыпе 0: (И22.1.2212) Ыпе 34: (И22.1.2 023) [ЗкЫпдз] зесЫоп. Ыпе 45: Ыпе 45: Ыпе 49: Ыпе 53: (И22.1.2208) (И22.1.2083) (И22.1.2083) (И22.1.2083) Мо СоруЫдЫ тпЫгтаЫоп Еоипс!. Езе а зЕЫпд Еокеп, апс! рик 1оса11хаЫе кехк ЫТ-зреЫЫс зесЫоп (з) Тоипс!. ТдпоЫпд депега ЗесЫоп [ЕХАМРЬЕ. 1ЕЗТАЬЬ] пок гекегепсес! ЗесЫоп [ЕХАМРЬЕ.АБОКЕС] пок геТегепсес! ЗесЫоп [ЕХАМРЬЕ. РТЬЕЗ . БР.1 УЕВ] пок геТегепсес) Первая ошибка (Е22.1.1081) обусловлена тем, что отсутствует .саГ файл, где содержится подписанный М|Сго5оГГ инсталляционный пакет. Вторая ошибка (Е22.1.1310) обусловлена тем, что утилита СНК11ЧЕ не отреагировала на секции и директивы установки нового класса устройств Ехатр1е0п/С1аз5 (к тому же, к моменту проверки новый класс уже находился в Системном Реестре). Можно заменить класс на Ыпкпо\лт, но тогда утилита СНК11ЧЕ будет "недовольна" тем, что это — устаревший класс. Если попробовать ввести класс 115В, то утилита СНК11ЧЕ будет вполне "удовлетворена", хотя не отследит неверный формат идентификатора модели (неверный для 115В устройства, см. ниже), который задан здесь как
"*5урВоок\Ехатр1е". Это лишний раз подтверждает тот факт, что СНК1МР проверяет только базисные правила синтаксиса и структуры ИГ-файлов. Предупреждения тоже имеют несложные объяснения. Первое (\Л/22.1.2212) указывает на то, что в ИГ-файле нет записи сорупдЫ. Исправляется этот недостаток следующим образом: ; Ехашр1е.1пЕ - тпзбаИ гпЕогшакгоп Ше ; Сгеакес! 22 ЕеЬ 2002 Ьу 3\7Р ; Соруг1дЬР (с) ЗУР 2003. АН г1дИТз гезегческ Последняя строка, которая добавлена для устранения данного предупреждения, хотя формально и является комментарием, но считается информативной относительно авторских прав (содержит ключевые слова СорупдКГ (с)). Второе предупреждение (\Л/22.1.2023) указывает: [ЗоигсеЕгзкзИашез] 1="Ехатр1е ЬиН<1 сНгесЬогу", , , ; (И22.1.2023) Озе а зкг!пд кокеп, апс! рик 1осаНхаЫе 06x0 ; 1п Оке [Зкгтпдз] зескгоп. То есть рекомендуется заменить строку "Ехатр1е ЬиНд сНгесГогу" на маркер, значение которого следует раскрыть в секции [ЗГгИдз], например, следующим образом: [ЗоигсеЕгзкзМашез] 1=%ЕхатЕ1г%,,, [Зкггпдз] ; Раскрываем значение нового маркера ЕхатВ1г="Ехатр1е ЬиНс! сНгескогу" Третье предупреждение (М/22.1.2208) информирует, что в файле найдены секции для операционной системы МТ, поэтому стандартные секции (для \ЛЛпс1оу75 98) будут проигнорированы. Оставшиеся предупреждения даны потому, что указанные секции (введенные для использования в У\Лпс1о\л/5 98) проигнорированы, соответственно, на них нет ни одной ссылки. Рис. 12.5. Результаты работы СНК11ЧР (после исправлений) В пакете СОК в подкаталоге Тоо1з имеется также утилита СЕМ1МЕ.ЕХЕ, которая облегчает создание ИГ-файлов, но более всего подходит для изучения процесса их создания и может оказать некоторую помощь начинающим разработчикам.
ГРгеу1ои8~| ГИехМ
Использование 11ЧР файлов Когда 11\1Е файл создан, он должен быть должным образом активирован (обработан), для того, чтобы драйвер заработал. Рассмотренные выше правила, предъявляемые к структуре и синтаксису 11МР файла, продиктованы тем, что он поступит на обработку (для интерпретации) некоему формально работающему программному средству, которое не может допускать излишних вольностей в описании установки. Рассмотрим два варианта запуска установки — при помощи явно вызываемого Мастера Установки (метод для не-РпР устройств и драйверов "в-стиле-МТ") и автоматическую установку, запускаемую системой автоматически при обнаружении новых РпР устройств.
[ Ргеуюиз] Г№х1]
Мастер Установки/удаления новой аппаратуры В главе 3 рассматривался вариант инсталляции Ехатр1е.5уз — драйвера "в-стиле-МТ" (1едасу с1пуег) при помощи Мастера Установки нового оборудования и 1пГ-файла. Процесс установки драйвера для РпР устройства, которое предъявляет системе идентификационные коды, отличается тем, что Мастер Установки нового оборудования самостоятельно находит драйвер (если для данного типа оборудования драйвер уже устанавливался ранее хотя бы раз) либо предлагает пользователю выбрать более подходящий драйвер, который заявляет соответствующие коды в соответствующей установочной секции 1пГ-файла. Если установка успешно завершена Мастером Установки, процедуры драйвера ОпуегЕпСгу и Ас1сЮеу1се должны, кроме того, подтвердить, что аппаратное обеспечение, которым их "пригласили" управлять, удовлетворяет требованиям выбранного драйвера, подтверждая правильность выбора именно этого варианта установки. Другими словами, не исключена ситуация, когда интерактивный выбор может довести установку до конца, но инициализация устройства все же завершится неудачей (потому что собственно программный код драйвера не "согласился" работать с предложенной аппаратурой в предложенных условиях).
Установка РпР устройств В момент, когда подключается РпР устройство, в результате взаимодействия нескольких подсистем инициируется загрузка нового драйвера. При подключении нового устройства аппаратное обеспечение, представляющее шину, используя механизмы автоматического обнаружения и уведомления, оповещает шинный драйвер о наличии устройства. В зависимости от аппаратного обеспечения шины, это может привести к тому, что шинный драйвер, которому поступило уведомление об изменениях, инициирует новую процедуру перечисления (епитегайоп) всех подключенных к шине устройств. В любом случае, в конце этой операции шинный драйвер будет точно знать, что новое устройство подключено, и какой специфический идентификатор оно имеет. Дальнейшую работу можно разделить на этапы: 1. РпР Менеджер режима ядра (см. документацию ООК по указателю на ключевую фразу "0еу|се 1п51а11айоп Сотропеп^з") уведомляет РпР Менеджер пользовательского режима о том, что в системе обнаружено новое устройство со специфическими кодами РпР идентификации (кодами производителя, модели, версии и т.п.). РпР Менеджер пользовательского режима конструирует список возможных драйверов для нового устройства, в частности, проверяется системный файловый каталог 1пГ-файлов на наличие подходящего 1пГ- файла (по полученной от нового устройства информации). Инсталляционные 1пГ-файлы для дополнительно доставляемых драйверов чаще всего попадают туда под новым именем оетХххяпТ, где Ххх — это целое число, начиная с 0. 2. Если подходящий 1пГ-файл не обнаружен, система откладывает все последующие действия до момента, пока в систему войдет пользователь с достаточным уровнем привилегий. Этому пользователю и предлагается диалог с
Мастером Установки Оборудования (Ас1с1 Нагс1\л/аге \Л/1гагс1). Пользователь должен указать место (чаще всего, СО), где размещены файлы нового драйвера и его 1пГ-файл. 3. Как только выявлен приемлемый 1пГ-файл, начинается его обработка при помощи библиотеки вызовов СопГ|дигайоп Мападег АР1 (СГдМдг АР1, см. документацию ООК по указателю на ключевую фразу "Оеуюе 1п51а11айоп СотропепСз"). Выполняется копирование файлов драйвера и модификация информации Системного Реестра. Эта работу делает, главным образом, РпР Менеджер режима ядра. 4. На основе директив (пГ-файла РпР Менеджер режима ядра загружает все фильтр-драйверы нижнего уровня, затем функциональный драйвер и, наконец, верхние фильтр- драйверы, предназначенные для обслуживания нового устройства. Драйверу, который находится на вершине стека, затем направляются соответствующие РпР запросы (1РР пакеты с кодом 1РР_МЗ_РЫР), включая 1КР_ММ_5ТАКТ_ОЕУ1СЕ.
Идентификаторы РпР устройств Процесс автоматической установки РпР устройств сильно зависит от способности устанавливающего программного обеспечения локализовать нужный !пГ-файл и секции в нем, относящиеся к драйверу. Особое значение имеет в данном случае то, как указываются в ИГ-файле наименования классов и идентификаторы устройств. Каждое устройство, спроектированное по спецификации РпР, должно иметь идентификатор, который однозначно определяет модель данного устройства. Этот идентификатор должен быть предоставлен шинному аппаратному обеспечению (а следовательно, и шинному драйверу) по поступлении запроса. Разумеется, шинный драйвер подает запрос сразу же, как только новое устройство подключено. Секция [Мос1е15] в 1пГ-файле содержит значение играющее роль идентификатора модели. В примере инсталляционного 1пГ-файла для драйвера Ехатр1е.5уз (глава 3) в роли такого идентификатора выступила строка "*5урВоок\Ехатр1е", что было приемлемо для не-РпР устройства. Значение, вводимое в поле 1пул/_1с1 для РпР устройств, должно придерживаться определенного формата, изменяющегося в зависимости от типа шины, к которой устройство подключается, но обычно идентификатор поступает в устанавливающий программный код в виде: тип_шины\идентификатор_модели например: РС1\УЕЫ_1011&ОЕУ_002&ЗЕВЗУЗ_00000000&КЕУ_02 Устанавливающие системные сервисы весьма просто могут проследить, согласуется ли запись в ИГ-файле с возвращаемым вновь подключенным устройством идентификатором. В той же записи 1пГ-файла допускается
описание списка совместимого аппаратного обеспечения (разумеется, если совместимость существует) в форме задания дополнительных идентификаторов устройств. В случае, если точное совпадение с полученным Ю устройства не обнаружено в данном 1пГ-файле, делается попытка найти совпадения по совместимым идентификаторам.
Полный идентификатор для РпР РС1 устройств имеет форму РС1 \Vеп_VVVV&^еV_С^С1С^С^&8иЬ8уЗ_3 3 33 3 33 3&КеV_^^ Здесь уууу является идентификатором поставщика (производителя), зарегистрированным в группе РС1 5рес1а1 1п1егез1: Сгоир, с1с!с1с1 — идентификатор, присвоенный производителем данной РС1 карте, 55555555 — идентификатор конструкции (5иЬ5у5Сет Ю), гг — номер версии разработки. Все упомянутые поля вводятся как шестнадцатеричные числа. Поле 55555555 обычно вводится как нулевое. Кроме того, допустимо в 1пГ-файлах представлять усеченные варианты идентификационной информации, например: РС1 \УеП_'7’Л7'Л7"7’&Ре'7’_С1с1с1с1&8иЬ8уЗ_3 3 33 3 33 3 РСI\Vеп_VVVV&^еV_с^с^с^с^&КеV_^^ РС1 \Vеп_VVVV&^еV_с1с1с1с1 РС1 \\7еп_7Л7"\7Л7’&Ре'7_с1с1с1с1&Ке‘\7_гх‘&СС_ссзз РС1 \Vеп_VVVV&^еV_С^С1С^С^&СС_СС8 8рр РСI\Vеп_VVVV&^еV_С^С1С^С^&СС_СС8 8 РС1 \Уеп_'7”\7"\7”7’&СС_сс8 зрр РС1 \УеП_‘7”\7'Л7''7’&СС_СС8 8 РС1 \УеП_'7’Л7'Л7"7’ РС1\СС_ссззрр РС1\СС_ссзз Здесь сс является кодом базового класса из конфигурационного пространства РС1 устройства, 55 — код подкласса, рр — идентификатор программного интерфейса.
РпР идентификаторы 8С81 устройств Полный идентификатор для РпР 5С51 устройств имеет форму 8С81 \ЬЬЬЬVVVVVVVVрррррррррррррррр^^^^ Здесь Ий является типо-кодом устройства, ууууууууявляется 8-символьным идентификатором поставщика (производителя), рррррррррррррррр— 16 символьный идентификатор устройства, гггг — номер версии разработки. Кроме того, допустимо в 1пГ-файлах представлять усеченные варианты идентификационной информации, например: 8С81 Х'Ь'Ь'Ь'Ь‘\7"\7”7”\7"\7”7''7’'\7'рррррррррррррррр 8 С 8 I \ Ъ Ъ1111V V V V V V V V 8С8 I \’\7"\7”7''7’'\7"\7”7’'\7'ррррррррррррррррг '\7’'7’'\7”7’'7’'\7"\7”7’рррррррррррррррр Г дддд Здесь дддд является одним из групповых типов (депепс 1уре) классов, приведенных в таблице 12.11. Таблица 12.11. Типы 8С81 и 1ОЕ устройств 8С81 код Устройство Тип Групповой ТИП 01НЕСТ_АССЕ55_0Е71СЕ (0) Дисковое О|5к СепО15к 5Е011ЕЫТ1А1__АССЕ55_0Е71СЕ (1) Последовательное Зедиепйа! РН11\1ТЕН_0Е71СЕ (2) Принтер РппЬег СепРппЬег РНОСЕ55ОН_ОЕУ1СЕ (3) Сканнеры, Ргосеззог принтеры и т.п. \Л/Р1ТЕ_ОМСЕ_РЕАО_М1Л_Т1Р1_Е_ОЕ\/1СЕ (4) УУогт \А/огт СепМогт НЕАО_О1\11_У_О1Р.ЕСТ_АССЕ55_ОЕ\/1СЕ (5) СО КОМ Сс1Иот Сепсс1Кот 5САЫЫЕН_0Е71СЕ (6) Сканирующее Зсаппег СепЗсаппег ОРТ1СА1__ОЕУ1СЕ (7) Оптические диски Орйса1 СепОрйса!
МЕО111М_СНА1\1СЕк (8) Устройство со СЬапдег 5с51СИапдег сменными либо носителями СепСЬапдег (Для ЮЕ) С0ММ1Л\11САТ101\1_0ЕУ1СЕ (9) Сетевое Ые1: 5сз|Ые1: Для 5С51 диска, имеющего полный установочный РпР идентификатор 5С51\О13к5ЕА(ЗАТЕ5Т391021_\Л/0004, шинный драйвер сконструирует также и следующий список идентификаторов: ЗС31\В1зкЗЕА(9АТЕ_ЗТ39102ЪТС 0004 ЗС81\01зкЗЕАСАТЕ_ ЗС31\О1зкЗЕА8АТЕ_ЗТ3 9Ю2Ш 0 О1зкЗЕА8АТЕ_8ТЗ9102ЬМ 0004 (ЗепЕтзк
Идентификаторы ЮЕ устройств схожи с идентификаторами для 5С51 устройств. Для ЮЕ допустимо в (пГ-файлах представлять следующие варианты идентификационной информации, например: 1СЕ\1:‘Ь‘Ь1^_У’ГГГГГГГГ 1ВЕ^_угггггггг 1СЕ\1:‘Ь‘Ы^_у V ллгггггггг дддд Здесь Ий является типо-кодом устройства (см. таблицу 12.11), является 40-символьным идентификатором поставщика (производителя), гггггггг — 8-сим-вольный номер версии разработки. В случае, если идентификатор поставщика короче 40 символов, то он дополняется символами подчеркивания. Пример для третьего варианта представления РпР идентификационной информации в ИГ-файле: I ЕЕ \Сс1КотАЬР8_ОС5 4 4_____________________________
Полный идентификатор для РпР 115В устройств имеет форму ^5В\V^С^_VVVV&Р^С^_С^С^С^С^&КеV_^^ Здесь уууу является идентификатором поставщика (производителя), зарегистрированным в Комитете 115В производителей, с!с1с1с1 — идентификатор, присвоенный производителем данной модели устройства, гг — номер версии разработки. Все упомянутые поля вводятся как шестнадцатеричные числа. Кроме того, допустимо в 1пГ-файлах представлять усеченные варианты идентификационной информации, например: О 3 В \ VI С1_У V V V— & Р1 с1_с!с1 с! с1 ОЗВ\С1азз_сс&ЗиЬС1азз_зз&Рго-Ь_рр ОЗВ\С1а88_сс&ЗиЬС1аз8_88 03В\С1азз_сс Здесь сс является кодом базового класса из полученного дескриптора устройства или дескриптора интерфейса данного 115В устройства, 55 — код подкласса, рр — идентификатор протокола.
РпР идентификаторы устройств 1ЕЕЕ-1394 (НгеМЛге) Полный идентификатор для РпР 115В устройств имеет форму 1394 \Уепс1огМате&Мос1е1Мате 13 94 \Ип1'Ь8рес1с1&Пп1'Ь8мУегз1оп Здесь \/епс1огЫате является наименованием поставщика (производителя), Мос1е1Ыате — идентификатор, присвоенный производителем данной модели устройства, 11пк5рес1с1 и IIП11:5\л/\/ег51Оп — идентификаторы программных спецификаций, получаемые из конфигурационных ПЗУ подключаемых устройств, например: 1394\М1СКО8ОЕТ&1394_О1АСЫО8Т1С_ОЕУ1СЕ 1394Х031887&040892

Заключение Установка драйвера при помощи тГ-файлов является приемлемым и общепринятым решением для проведения установки драйвера в системе, особенно если драйвер следует передать для использования другим людям, которые не могут и вовсе не обязаны быть в курсе тонкостей установки разработанного драйвера. В данной главе были приведены сведения, как создавать корректные !пГ-файлы, как раз и предоставляющие преимущества стандартизованного механизма установки. В следующей главе будут рассмотрены общие вопросы отладки драйверного кода.
Глава 13
Тестирование и отладка Никакой сложный фрагмент программного кода не может считаться безошибочным. Фольклор программистов утверждает, что если в программе нет ошибок, то ошибка в компиляторе, поэтому он не выявил всех ошибок в программе. Ошибки нельзя уничтожить, их можно только лишь ограничить. Все это становится наиболее очевидным, когда программное обеспечение начинает взаимодействовать с программами и аппаратурой других поставщиков, зачастую представляющими черный ящик. Тестирование можно разделить на два этапа. Первый — достаточно сумбурный этап первоначального эмпирического накопления данных о поведении сложившейся конфигурации аппаратуры и программного обеспечение. Разумеется, у опытных разработчиков этот этап короче и проходит рациональнее. Второй этап обязан обобщить полученные на первом этапе сведения, простроить приемлемую модель возникновения проблем и провести их поэтапную ликвидацию. Если второй этап не справляется с такой постановкой вопроса и превращается в первый, то вся работа превращается в беспорядочные метания. Разумеется, поэтапное тестирование значительно эффективнее, нежели тестирование по окончании разработки системы целиком. Хотя такой подход требует большего набора фрагментарных тестов, действенность этого подхода неоспорима. Хотя бы по той простой причине, что дата завершения тестирования при таком
подходе более предсказуема, нежели для продукта, который ни разу не тестировался по частям. В литературе, посвященной тестированию, различают "прирастающее" тестирование (с постепенным усложнением) и более формальное регрессивное тестирование (когда происходит откат на предыдущий шаг в случае неудачи). По мере расширения проекта, эти сохраненные предшествующие тесты позволят удостовериться, что изменения не внесли ошибок в прежде разработанные фрагменты. Зачастую аппаратура разрабатывается параллельно программному обеспечению. При раннем частичном тестировании дефекты в аппаратуре выявляются раньше и, соответственно, остается больше времени на исправление ошибок ее проектирования. Вообразите, насколько убийственной для графика работ окажется неожиданное осознание того, что следует закупить другие микросхемы, сделать еще одну итерацию печатной платы или, что гораздо хуже, полузаказной интегральной схемы. Немногие фирмы могут позволить себе иметь отдельные команды разработчиков и испытателей. Таким образом, автор драйвера должен зачастую параллельно разрабатывать и драйвер, и поэтапные тесты для него. Преимуществом такого "унитарного" подхода является то, что автор в курсе предельных параметров драйвера и устройства. Недостаток состоит в том, что оптимально устроенное мышление разработчика не планирует тестов на некоторые редко встречающиеся ошибочные ситуации, что впоследствии может оказаться серьезной проблемой конструкции и программного обеспечения.
Разумеется, должна быть установлена хорошая дисциплина для обеспечения достаточного времени и для разработки, и для тестирования. Сокращение стадии тестирования для ускорения общего графика — плохая услуга любому проекту.

Что следует проверять? В общем случае, тесты для драйвера (предполагая, что аппаратное обеспечение тестируется дополнительно) можно разделить на следующие категории. Тесты на нормальную реакцию должны подтверждать полноту и точность функций драйвера. Так ли откликается драйвер на команды, как это от него ожидается? Тесты на ошибочные воздействия должны удостовериться, правильно ли реагирует драйвер на воздействия, которые, вообще говоря, не должны к нему применяться. "Ошибочное" воздействие может также состоять в плохом наборе данных, поступившем в пользовательском запросе. Тесты граничных условий испытывают анонсированные пределы функционирования драйвера и устройства. Не исключено, что в силу работы драйвера в системе конечные предельные параметры окажутся хуже прогнозируемых. Тесты на предельную нагрузку проверяют драйвер и устройство при высоких уровнях активности. Тесты на функционирование в условиях ограниченности ресурсов, то есть работа при ограниченной доступности центрального процессора, ограниченность объемов доступной оперативной памяти. Фирма М1сго5оЛ предлагает тесты на аппаратурную совместимость (НСТ, Нагс1\л/аге Сот рай Ы Шу Тез1:5), которые являются официальными тестами для аппаратуры по поводу возможности ее работы под
\Л/1Пс1о\л/5 2000/ХР/2003. Набор включает следующие тесты: Общие системные тесты, которые экзаменуют центральный процессор, последовательные и параллельные порты материнской платы, клавиатуру и средства поддержки слоя аппаратный абстракций НА1_. Тесты по проверке специфических типов аппаратного обеспечения, а именно — видеоадаптеров, мультимедийных устройств, сетевых интерфейсов, накопителей на магнитной ленте, 5С51 устройств и т.п. Общие тесты по тестированию работы системы под действием высоких нагрузок на системные ресурсы и устройства ввода/вывода. Тестирование проходит под управлением тест-менеджера с графическим интерфейсом, который автоматизирует прохождение тестов и сбор результатов. Даже если класс аппаратуры, для которой разрабатывается драйвер, не прошел серию испытаний НСТ, то все равно этот набор тестов может послужить средством для выяснения того, как будет работать новый драйвер в условиях повышенной нагрузки на систему.
Цифровое подписание драйвера Огромное количество драйверов сторонних поставщиков (третьей стороны, 1:И|гс1-раг1:у сотрап1ез) поставляется в составе СО дистрибутива М1СгозоГ1: У\Лпс1о775. Для того чтобы участвовать в этой программе, поставщик драйвера должен выполнить некоторые требования, сформулированные М1сгозо^. В интересах М1СгозоГ1: обеспечить достижение двух взаимоисключающих целей: обеспечить работу под управлением \Л/|пс1о\л/5 максимально разнообразной и многочисленной аппаратуры при обычном способе распространения дистрибутива; обеспечить стабильность работы новых драйверов без нарушения каких-либо свойств или целостности системы. Поскольку драйверы работают в режиме ядра, у них имеется возможность привести систему к краху. Так как нестабильность системы, скорее всего, будет отнесена пользователем на нестабильность самого ядра, очевидно, что в интересах МюгозоЛ определить список сертифицированных поставщиков и сертифицированных драйверов для своей операционной системы. Разумеется, совместимость \Л/|пс1о\л/5 2000/ХР/2003 с большим количеством аппаратуры, нежели другие операционные системы, является сильным фактором стимулирования продаж. Следовательно, М1сгозоЙ: непрерывно контактирует с производителями аппаратного обеспечения на предмет получения своевременных и протестированных конечных версий драйверов. Вплоть до того, что конфигурация \Л/1Пс1о\л/5
5еп/ег 2003 Оа1:асеп1:ег ЕсПйоп поставляется только ОЕМ поставщиками совместно с компьютером. Для достижения вышеописанных целей, в МюгозоП: была создана специальная группа, 1:1пе \Л/1Пс1о\л/5 Нагбууаге <ЗиаН1:у 1_аЬз (У\/Н(21_), которая проводит сертификацию аппаратуры и драйверов устройств. Выгоды от участия в этой программе сертификации для производителей/ поставщиков аппаратного обеспечения состоит в использовании логотипа У\Лпс1о\л/5 как знака соответствующей ступени сертифицированности поставляемой аппаратуры и программного обеспечения к нему; включении в официальный перечень поддерживаемого и сертифицированного оборудования для различных операционных систем разработки МюгозоЛ: (см. интернет-страницу т1Сго5оГ1.сот /Ьс1); автоматического распространение драйверного обеспечения вместе с будущими поставками новых под-версий У\Лпс1о775; получении цифровой подписи. Подробнее ознакомиться с условиями участия в этой программе (процедурой и ценами на работы) можно на интернет-странице гшсгоаоД.сот/ЬууЕе Как часть результата участия в программе У\/Н(21_, драйвер получает цифровую подпись (сНд!1:а1 31дпа1:иге), защищенную криптографическими методами, которая позволяет \Л/1пйо\л/5 2000/ХР/2003 осуществлять инсталляцию драйвера без выдачи обескураживающего предупреждения о "надвигающейся" опасности. Цифровая подпись является, по мнению МюгозоЛ:, средством отсеивания ненадежного кода и устройств и
гарантией того, что устанавливаемый код является оригинальным драйверным кодом от поставщика. Применение собственно цифровой подписи касается двух моментов: Файла-каталога (с расширением САТ), который включается в распространяемый пакет драйверного программного обеспечения. Он и содержит цифровую подпись, предоставленную М1сго5оГ^. Запись в 11\1Р файле в составе секции [Уегвюп], где указывается ссылка на САТ файл. Сама цифровая подпись ни в коей степени не производит модификацию кода драйверного программного обеспечения.

Драйвер отказывается работать? Если тестирование сигнализирует о наличии ошибок, то более сложной проблемой является локализация их источника. Разумеется, драйверы могут отказываться работать многими интересными способами. Вряд ли возможно или следовало бы приводить обширный список причин сбоев, но перечислить некоторые общие типы драйверных патологий все-таки имеет смысл.
Аппаратные проблемы Априори, аппаратура есть источник проблем. Сильнее всех в этом убежден разработчик программного обеспечения. Вероятность того, что это действительно так, тем выше, чем новее и неиспытаннее аппаратура. Симптомами аппаратных проблем являются: ошибки при передаче данных; коды состояния устройства сигнализируют об ошибке; устройство не реагирует должным образом на команды; сигналы прерывания не поступают, либо они ложные. Причина упомянутых отклонений может быть и просто в недостаточной документированности поведения устройства. Разработчик аппаратуры после некоторых размышлений изменил конструкцию, но не исправил документацию и не сообщил об изменениях разработчику драйвера, возможно, посчитав их незначительными. Могут существовать малоизвестные или неисследованные ограничения на порядок следования команд — как в смысле последовательности, так и в смысле их временных диаграмм. Аппаратные прошивки (программы, загружаемые в обслуживаемые драйвером устройства) также могут содержать ошибки. Могут возникать сбои в шинных протоколах, причем из- за непериодических сбоев других устройств, подключенных к данной шине.
Программные проблемы Поскольку драйвер работает в режиме ядра, для него весьма несложной задачей является "обрушение" всей операционной системы. Наиболее сложными для трассирования сценариями являются операции ОМА, в которых некорректно установлены регистры отображения (тарр1пд гед151:ег5). Данные записываются устройством в случайные области памяти, и происходит сбой, виноватыми в котором кажутся совершенно другие подсистемы. Утечка ресурсов Операционная система никогда не контролирует, как драйвер использует ресурсы и как он их возвращает по окончании работы. Когда драйвер завершает работу и выгружается, то именно на нем лежит вся ответственность за освобождение всех когда-либо занятых им ресурсов. Утечка памяти может происходить и в то время, когда драйвер еще продолжает работать. Например, если он периодически производит временное выделение памяти под свои нужды и "забывает" их освободить. Драйверы, которые занимаются генерацией дополнительных пакетов 1КР, могут "забывать" выполнить очистку этих областей памяти. Утечки ресурсов приводят к падению производительности системы и, в конечном счете, к ее фатальному сбою. Поиск утечки ресурсов и в пользовательском режиме является непростой задачей, а в режиме ядра он граничит с магией. Возможно, здесь может оказаться полезной методика выделения блоков памяти, помеченных дескрипторами, при помощи вызова ЕхАНоса1еРоо1УУН11Тад. При анализе системной памяти
после фатального сбоя, по сохраненному на диске состоянию оперативной памяти, эти идентификаторы позволяют определить, где блоки были выделены, каков их размер и, что самое важное, какая подсистема выполнила их выделение. Торможение программных потоков Одна из ошибок, приводящих к остановке программных потоков, состоит в том, что драйвер не завершает обработку 1КР пакетов. В результате, поток пользовательского приложения, сделавший запрос, так и остается в состоянии ожидания. Это может быть обусловлено несколькими причинами. Самая простая ошибка состоит в том, что драйвер по какой-то причине не выполняет вызов 1оСотр1е1еКедие51. В результате, присланный 1Р.Р пакет никогда не возвращается Диспетчеру ввода/ вывода. Иногда не столь очевидна необходимость сделать вызов 1о§1аг1№х1Раске1 (при использовании очередей 1Р.Р пакетов). Однако даже если не существует запросов, ожидающих обработки, драйвер должен выполнить этот вызов, поскольку только таким образом объект устройства будет "помечен" как простаивающий. Без этого вызова новые 1РР пакеты будут помещаться в очередь ожидания обработки, так и не поступая в процедуру З^агНо драйвера. Другая логическая ошибка, состоящая в попытке рекурсивно получить объект синхронизации или другие ресурсы исполнительной подсистемы, может остановить программный поток драйвера в одной из его рабочих процедур. Возможно, что другой фрагмент кода должен был освободить объект синхронизации, но так и не смог "добраться" до нужного места или "ушел по другой
дороге". Последующие запросы только усугубляют ситуацию. Аналогично, ОМА драйверы могут остановиться в точке, где они сделали запрос на владение объектом адаптера. Драйверы, которые управляют сложными (составными) контроллерами, могут создать сходные проблемы в случае, если они не освобождают объект контроллера. К сожалению, не существует безусловно хорошего способа решения таких проблем. Иногда полезно определить некую структуру данных, в которой все владельцы объектов синхронизации будут "отмечаться", когда они что-то используют, и удалять эти отметки, когда использование соответствующего объекта синхронизации прекращается. Разумеется, такой прием потребует дополнительных усилий, но он весьма эффективен в выявлении источника проблем. Порой ошибки драйвера могут вызвать блокировку всей системы. Например, фатальную взаимоблокировку могут вызвать некорректное перекрестное использование нескольких объектов спин-блокировок или попытки повторно получить спин-блокировку на однопроцессорной платформе. Бесконечные циклы из кода процедур обслуживания прерывания или процедур □рсБогТзг могут привести к аналогичному результату. Лучшим решением в данной ситуации было бы осуществление интерактивной отладки драйвера. Проблема приоритетов времени выполнения Как мог заметить читатель, описание всех системных вызовов в данной книге (кроме того — и в документации □□К) приводятся с обязательным указанием, на каком уровне 1Р.(21_ (приоритетов уровня ядра) может работать
программный код в момент обращения к ним. Поскольку приоритет вызывающего кода по умолчанию переходит на вызываемые процедуры (которые, возможно, не должны никогда работать на этом уровне), то операционная система тщательно отслеживает, чтобы уровень приоритета, который "передался" ее компонентам, соответствовал бы установленным правилам. Наиболее часто жертвой столь ревностного соблюдения правил становится высокоприоритетный код, обращающийся к страничной памяти, которая оказалась сброшенной на диск. Система не позволяет вступать в работу своим функциям, чтобы обслужить данное затруднение, поскольку отступление от данного правила считается вредным для системы. Разработчик драйвера должен всегда следить за приоритетом текущего программного кода и документированным уровнем 1РС21_ вызываемых системных функций. В противном случае возможен (а иногда — просто неминуем) крах операционной системы, а ответ придется искать уже в сгасЬ с!итр файле. Показательным случаем неправильного использования приоритетов является вызов другого драйвера при помощи 1оСа1Юпуег, который формально может быть выполнен на уровнях 1ЕЩ1. АРС_1_Е\/Е1_ и □15РАТСН_1_Е\/Е1_. Повысив текущий приоритет потока перед вызовом 1оСа1Юпуег до значения, например, □15РАТСН_1_Е\/Е1_, драйвер, скорее всего, организует блокировку: если вызываемый драйвер должен работать на уровне РА551\/Е_1_Е\/Е1_, то он не может сделать нужные ему вызовы, пока работает вызвавший его поток, который ждет возвращения управления из 1оСа1Юпуег. Система приходит в неработоспособное состояние ("замерзает").
Отслеживание ошибок Исследования показывают, что ошибки не распределяются равномерно по всему программному коду, а имеют тенденцию концентрироваться в нескольких специфических процедурах, обычно, почти пропорционально их функциональной сложности. Тщательное протоколирование позволяет идентифицировать эти процедуры для того, чтобы держать их впоследствии "на заметке". Важнейшим моментом отладки является воспроизведение ошибочных ситуаций. Протоколирование может помочь и здесь. Хорошо выполненное протоколирование позволяет понять проблемы, проистекающие из конфигурации проекта, упорядочивая упомянутую выше первую фазу тестирования — этап накопления наблюдений. Кроме того, выявляются бреши в собственно стратегии тестирования.
ГРгеу1ои8~| ГИехМ
Отладчики Отладчик УУтОЬд Одним из отладчиков, который может использовать разработчик драйверов, является отладчик У\ЛпОЬд, поставляющийся в составе пакета СОК. Отладчик У\ЛпОЬд представляет собой гибрид отладчика уровня ядра и пользовательского режима. При помощи У\ЛпОЬд можно проводить анализ файлов "сгазЬ с!итр ГПез" (отображение физической памяти в момент сбоя), отлаживать драйверный код путем трассирования (исполнения), анализировать "с!итр ГПе" приложений (пользовательского режима). Помимо того, что У\ЛпОЬд выполняет свои задачи и в режиме ядра, и в пользовательском режиме, он также предоставляет рабочий интерфейс и в графической форме, и в форме командной строки, характерной для старых отладчиков. Одной из самых значительных возможностей, которой обладает отладчик У\ЛпОЬд, является его способность поддерживать отладку компонентов уровня ядра в исходных текстах отлаживаемых модулей. При этом отладчику должны быть доступны и исходные тексты, и файлы с отладочными символами (далее они будут называться "файлы идентификаторов"). Тем не менее, отладка в исходных текстах представляет достаточно существенную организационную проблему. Во-первых, для отладки драйвера требуется два компьютера, на одном из которых работает собственно отладчик, а на втором — отладочный агент, относительно
небольшой объем кода, который получает управляющие команды с первого компьютера. Во-вторых, для проведения отладки потребуется отладочная версия операционной системы, для которой имеются соответствующие файлы отладочных символов. Сумма этих затруднений может перевесить чашу весов в пользу других отладчиков, в особенности, отладчика 5оШсе фирмы Сот ри Маге Согрогайоп (разумеется, если не принимать в рассмотрение цену полной версии пакета Опуег 51:ис1ю). Директории идентификаторов Файлы идентификаторов (зутЬо! Л 1ез) создаются по дополнительно установленным флагам компиляции при компиляции и сборке проекта. В них находятся имена локальных и глобальных переменных, адреса функций и информация об определениях типов данных. Приводится также информация о номерах строк, поэтому машинные инструкции могут быть легко соотнесены с исходным текстом. Программные средства МкгозоЛ: могут предоставлять эти файлы в нескольких форматах: более старом (но более типичном) СОРЕ (Соттоп ОЬ]'ес1: Н1е Еогта1:) и формате РОВ (Ргодгат Оа^аЬазе), что определяется настроечными флагами компиляции и сборки. Сама операционная система \Л/|пс1о\л^5 тоже поставляется с файлами идентификаторов, которые могут быть и на дополнительном диске дистрибутива, и в составе ООК (разумеется, речь не идет об исходных текстах). Для операционной системы \Л/1пс1о\/У5 2000 такие файлы, возможно, до сих пор можно загрузить с Интернет сервера М1сго5оГЬ. Для остальных версий они
распространяются МюгозоЙ: в рамках подписки М5ОЫ либо в составе набора СО дисков Опуег Вийе. Поскольку файлы идентификаторов меняются при каждой новой сборке (Ьш1с1), то обновление системы (зеплсе раск) требует также и обновления файлов идентификаторов, относящихся к системным модулям. Доступ к файлам идентификаторов драйвера и системных модулей исключительно важен, поскольку они обеспечивают информацию о стеке вызовов и привязку к исходному тексту (драйвера). Когда установлены необходимые файлы идентификаторов, отладчику МтОЬд необходимо сообщить об их местонахождении. Это выполняется с использованием в пункте ВутЬо! ЕПе Ра<=Ь пункта меню ЕНе. Идентификаторы операционной системы обычно устанавливаются в директории %Вуз1:етО1г%\ВутЬо15. Файлы идентификаторов драйвера (расширения .ОБО или .РЭВ) обычно хранятся вместе с бинарным файлом драйвера (.ВУВ, .МОМ). Директории исходных текстов Помимо файлов идентификаторов отладчику понадобятся файлы исходных текстов драйвера (.с, .срр, .И). Путь к ним необходимо указать в пункте Воигсе ЕНе Ра<=Ь пункта меню ЕНе. Запуск и окончание отладочной сессии Основное предназначение отладчика МтОЬд — интерактивная отладка. Для этого необходимо наличие двух компьютеров: целевого (на нем будет запущен тестовый код) и хост-компьютер (на котором производится разработка и работает МтОЬд).
Существенно здесь то, что для управления и мониторинга активности на целевом компьютере используется интерфейс последовательных портов. На рисунке 13.1 показано соединение хост и целевого компьютеров и размещение файлов на них. Для успешной отладки необходимо, чтобы все нужные файлы оказались на своих местах. Последовательность действий такова. Рис. 13.1 Интерактивная отладка с использованием У\ЛпОЬд 1. Инсталлировать бинарный файл драйвера на целевой машине, причем там не нужно устанавливать файлы идентификаторов или исходного текста. 2. На хост-компьютере необходимо иметь файлы идентификаторов драйвера и файлы идентификаторов операционной системы, установленной на целевом компьютере. Неплохо было бы, чтобы на двух компьютерах была бы установлена одна и та же версия системы (и одинаковые обновления, Зеплсе раск), однако это не является жестким условием. 3. На хост-компьютере запускается У\ЛпОЬд. Происходит установка путей к исходному коду и к файлам идентификаторов. Эти файлы должны соответствовать бинарному файлу отлаживаемого драйвера, который находится на целевом компьютере.
4. Необходимо выбрать отладку в режиме ядра в меню Х/1е\л/ закладки Орйопз Кегпе! ОеЬиддег в отладчике У\ЛпОЬд. 5. Необходимо установить приемлемые значения номера СОМ порта и скорости передачи из упомянутого диалогового окна. 6. Следует выбрать кнопку СО или ввести команду д в окне командной строки отладчика для того, чтобы перевести У\ЛпОЬд в режим ожидания соединения с целевым компьютером. 7. Необходимо выполнить перезагрузку целевого компьютера, разрешив использование клиента отладки. Когда завершится перезагрузка системы на целевом компьютере, соединение будет установлено. В командном окне У\ЛпОЬд будет выведено соответствующее сообщение. Хост-компьютер теперь имеет полный контроль над целевым компьютером. Можно устанавливать точки прерывания, даже в том коде, который еще не был загружен в память ядра. Чтобы разорвать отладочную сессию, необходимо выполнить следующее. 1. Сделать паузу, нажав С1т1-С в командном окне У\ЛпОЬд. 2. Следует выбрать Ебк — Вгеакро1п1:5 в отладчике МпОЬд, затем выполнить С1еаг АП. Важно выполнить очистку точек останова до прекращения отладочной сессии. 3. Следует выбрать кип — Со для того, чтобы разрешить дальнейшую работу на целевом компьютере. 4. Выйти из У\ЛпОЬд (АК-Г4).
Целевой компьютер может отреагировать некоторой задержкой на разрыв соединения, возможно из-за того, что процедуры Кс1Рпп1 и ОЬдРпп! более не имеют получателя своих сообщений и им нужно некоторое время для отработки этой ситуации.
Отладчик 5оП1се Отладку драйвера режима ядра на одном компьютере обеспечивает мощный отладчик 5оШсе, разработанный фирмой Сот ри Маге Согрогайоп и входящий в состав пакета Опуег51:ис1ю. Внешний вид интерфейса отладчика сохранился еще со времен М5 005, однако, при некоторой тренировке с использованием мнемонических команд, вряд ли это доставит много неудобств, тем более что ЗоШсе управляется мышкой, включая мышку с колесиком. Параметры запуска устанавливаются конфигуратором ОЗСопЛд, который определяет способ запуска ЗоШсе (при загрузке, по СОМ, по 1Р адресу, запуск по требованию на данном компьютере), настройки размера окна, настройки мышки. При настройке загрузки по требованию следует явно запустить из меню команд Пуск -... - 51:аг1: ЗоШсе. Данная команда загружает отладчик, а собственно его активация производится комбинацией С1г1-0. В том случае, если нужно выполнить отладку драйвера, после загрузки ЗоШсе следует загрузить файл драйвера (для отладки в исходных текстах — обязательно отладочную версию) программой 1оас1ег32 командой Пуск — ... — 5утЬо1 1_оас1ег. Лишь после этого следует активировать отладчик нажатием С1г1-О. При помощи команды И в окне активированного отладчика можно ознакомиться с полным набором и форматом команд. Это же можно узнать и из прилагаемой документации.
При помощи определенных команд несложно отыскать драйвер в памяти, если известно имя драйвера или объекта устройства, после чего можно устанавливать точки прерывания в коде драйвера. Затем следует деактивировать отладчик повторным нажатием С1г1-О. В момент, когда приложение пользовательского режима вызовет драйвер и, соответственно, достигнет указанной точки прерывания, отладчик активизируется самостоятельно. В окне отладчика будет виден нужный фрагмент драйвера. В то время, когда активно окно отладчика ЗоГНсе, активность операционной системы замирает: не обновляются показания часов, не работает обмен через сПрЬоагс! и т.п. Следует отметить, что при работе с ЗоШсе не следует использовать сложные мышки (например, четырехкнопочные, с двумя колесиками) и мышки 05В. Объясняется это тем, что, являясь настоящим отладчиком режима ядра, ЗоШсе пытается работать с устройствами ввода напрямую, не всегда понимая новые экзотические устройства. Более детально ознакомиться с отладчиком ЗоШсе можно, загрузив 1:па1-версию с Интернет сайта Сот ри Маге Согрогайоп.
ГРгеу|ои51 [Мех!],
Чтение сгавН-экранов Несмотря на ужасающее название "зуз1ет сгазК", системный сбой является вполне заурядным событием. Операционная система всего лишь сообщает, что она перешла в такое внутреннее состояние, которое несовместимо с возможностью продолжения работы. Следовательно, полагает операционная система, лучше прекратить работу сейчас, нежели функционировать с этим ошибочным состоянием. И это решение во многих случаях предпочтительнее. Кто инициирует объявление системного сбоя? Во-первых, некоторые компоненты уровня ядра, которые "по долгу службы" обнаружили некорректное состояния, могут решить, что пришла пора "сказать стоп". Например, если Диспетчер ввода/вывода обнаруживает, что драйвер передал в вызов 1оСотр1е1еПеяие91 завершенный ранее пакет 1НР, то Диспетчер ввода/вывода непременно вызовет этот самый сигнал фатального системного сбоя. Второй сценарий сложнее. Программный код, работающий в режиме ядра, является непосредственным источником ошибки и генерирует исключение (ехсерИоп) во время какой-либо операции. Если процедура уровня ядра, которая выполнила эту запрещенную операцию, непосредственно не перехватит это исключение, то результатом будет необрабатываемое исключение режима ядра. Когда это происходит, в работу включается стандартный обработчик исключений в ядре операционной системы, который инициирует системный останов. Независимо оттого, какие подсистемы ядра выступают инициатором системного останова, реально выполняется вызов одной из двух функций: 701Э КеВидСИеск( ВидСЬСойе ); 7010 КеВидСкескЕх( ВидСкСойе, Агд1, Агд2, АгдЗ, Агд4 ); Любая из этих функций вызывает останов системы и (необязательно) сохраняет файл со снимком памяти (сгазК с1итр Л1е) на жестком диске. В зависимости от выбора системного администратора, операционная система после этого прекращает работу, перезапускается или запускает отладку ядра (кегпеГз с!еЬид с11еп1). Аргумент ВидС11Сос1е в вызове КеВидСЬеск указывает причину сбоя в виде значения типа ОУУОРЭ. Этот аргумент известен также под именем "ЬидсКеск сос1е". Дополнительные 4 аргумента в функции КеВидСНескЕх дают возможность видеть в диагностическом сообщении дополнительную информацию (что, возможно, позволит удачнее локализовать источник сбоя). Коды "ЬидсЬеск содез" могут быть использованы из числа определенных МюгозоН: (см. файл В1)СС00Е5.Ь), либо можно определить в драйвере дополнительно. Драйвер может выполнить вызов любой из этих двух функций. Вообще говоря, общепринятой практикой для отладочных версий драйвера является организация системных 5ТОР-сообщений в критических точках кода.
Голубой экран смерти (В8ОО) Системное сообщение об останове системы — это появляющееся на голубом фоне сообщение, которое носит закрепившееся за ним название "В1ие Зсгееп ОГ ОеаГГ)" (В500). В разных версиях 1\1Т вид В500 сообщений сильно менялся. В русифицированных версиях зачастую на экране видна абракадабра вместо текста (из- за неверной кодировки), что, однако, не должно сильно обременять разработчика. Главной информацией являются две строки, содержащие код ошибки и некоторые параметры сбоя (шестнадцатеричные числа), например: 0К17ЕК_1КСЬ_Н0Т_ЬЕЗЗ_0К_Е<2ЕАЬ *** ЗТОР: 00000001 (00000000 00000002 00000000 Е8В57624) *** ЕхатрТе. зуз - асШгезз Е8В57624 Ьазе аб Е8В57000 Эабезбатр Зебс1а09 Сообщение код "ЬидсЬеск соде", указанный в текстовой форме (ОЯ1УЕЯ_1Ж21__ЫОТ_1_Е55_ОЯ_Е<2иА1.), его шестнадцатеричное значение (здесь ООООООО!, после слова 5ТОР) и четыре аргумента, переданные в вызове КеВидСНескЕх. В зависимости от кода интерпретируется значение остальных 4-х аргументов (см. Приложение А). Коду 0x01 соответствует ОЯ1УЕЯ_1Ж21__ЫОТ_1_Е55_ОЯ_Е<2иА1.. Приложение А указывает, что код 0x01 означает ошибку, связанную с обращением к отсутствующей в физической памяти странице (раде Гайк) на уровне 1РС)1_ равном О15РАТСН_1_Е\/Е1_ или выше. Кроме того, при этом коде ошибки можно сказать, что четыре аргумента КеВидСНескЕх означают следующее: Агд1: Ссылочный адрес (обращение к которому вызвало сбой) 00000000 Агд2: Текущий 1К<2Ь уровень 2 АгдЗ: Тип доступа 0 (что означает "чтение") Агд4: Адрес инструкции, которая вызвала сбой 0хЕ8В57624
Анализ информации СгазН Оитр файлов Когда происходит фатальный сбой системы, операционная система может (при определенных настройках системы) сохранить состояние системы в момент сбоя в файле на жестком диске. Чтобы это выполнялось, в апплете Пуск - Настройка - Панель Управления - Система - Дополнительно - Загрузка и восстановление - Параметры (рисунок 13.3) необходимо ввести приемлемые данные. Фиксирование состояние системы в момент сбоя позволит затем вернуться к анализу причин сбоя. Рис. 13.3 Окно системного апплета для установки параметров сгазЬ йитр файла При помощи отладчика ХЛЛпЭЬд можно проанализировать состояние системы в момент сбоя по содержимому сгазК с!итр файла. Там можно обнаружить всю информацию, которая была бы доступна отладчику, если бы он оказался включенным в момент сбоя. Это тип экспертизы позволяет добывать убедительные доказательства причин, приведших к фатальному сбою. Во время анализа необходимо установить: Какой драйвер выполнялся в момент сбоя? Какую операцию пытался выполнить драйвер в момент сбоя? Каково содержимое структуры расширения объекта устройства в момент сбоя? Какой объект устройства работал? После запуска У\ЛпОЬд необходимо открыть сгазЬ дитр файл (полученный с целевого компьютера) для анализа, выбрав пункт меню ЕНе и затем Орел СгазЬ Оитр. После выбора и загрузки необходимого файла, на экране появится информация следующего (например)типа: И1пс1оиз ХР Кегпе! Уегз!оп 2 600 ЮР Егее х8 6 сотра'ЫЫе Кегпе! Ьазе = 0x804(34000 РзЬоас1ес1Мос1и1еЫзЕ = 0х8054Ье30 ВиГсЬеск 000000Р1 : 00000000 00000002 00000000 Е8В57624 Как видим, начальная информация показывает те же данные, что и сообщение экрана В500. Не должно вводить в заблуждение сообщение о не перехваченном исключении с кодом 0x80000003. Это точка прерывания, используемая собственно КеВидСЬеск для останова системы, поэтому не следует придавать ей значения. Следует отметить, что в отсутствие отладочных символов операционной системы картина получается безрадостной. Распечатка сообщений \Л/1пОЬд для этого случая приводится ниже. ЬоасНпд Еитр ЕНе [Е :\5узкетСгазМитр\МЕМ0КХ . ОМР] Кегпе! Ситр ЕНе: ЕиН асИгезз зрасе 13 аVа^1аЫе МтсгозоН (К) И1пс1оиз Кегпе! ОеЬиддег Уегзтоп 3.0.0020.0 Соругтдк’Ь (с) МтсгозоЕЕ СогрогаНоп. АН НдНз гезе^ес!.
ЬоаЬеЬ ЬЬдЬе!р ех^егшоп БЬЬ ЬоаЬеЬ ехк ехкепзйоп БЬЬ ЬоаЬеЬ ехкз ехкепзйоп БЬЬ ЬоаЬеЬ кехк ехкепзйоп БЬЬ ЬоаЬеЬ кЬехкз ехкепзйоп БЬЬ ЗутЬо! зеагсЬ ракЬ йз: *** 1тла!йс1 *** : Уегйку _к[Т_ЗУМВОЬ_РАТН зеккй ЕхесикаЫе зеагсЬ ракЬ ±з: * 8утЬо1з сап пок Ье 1оаЬеЬ Ьесаизе зутЬо! ракЬ ±з пок йпйкйа!йгес1. * * * * ТЬе 8утЬо1 РакЬ сап Ье зек Ьу: * * изйпд кЬе _к[Т_ЗУМВОЬ_РАТН епV^^оптепк уагкаЫе. * * изйпд кЬе -у <зутЬо!_ракЬ> агдитепк мЬеп зкагкйпд кЬе ЬеЬиддег. * * изйпд .зутракЬ апЬ .зутракЬ+ * * ** ЕВВОВ: 8утЬо1 кй!е сои!Ь пок Ье коипЬ. Бекаийкес! ко ехрогк зутЬо! пкозкгпк.ехе - кНпЬомз ХР Кегпе! Уегзйоп 2600 (Зе^йсе Раск 1) ЬР Егее х86 сотракйЫ ВиНк Ьу: 2600.хрзр!.020828-1920 Кегпе! Ьазе = 0х804Ь4000 РзЬоаЬеЬМоЬиЬеЫзк = 0х8054Ье30 БеЬид зеззйоп Ыте: Ег! !ип 13 02:20:17 2003 8узкет ЬрЫте: 0 Ьауз 3:15:59 * 8утЬо1з сап пок Ье 1оаЬеЬ Ьесаизе зутЬо! ракЬ йз пок йпйкйаНгеЬ. * * 'к * ТЬе ЗутЬо! РакЬ сап Ье зек Ьу: * * изйпд кЬе _ЕТ_8УМВОЬ_РАТН епV^^оптепк уагйаЫе. * * изйпд кЬе -у <зутЬо!_ракЬ> агдитепк мкеп зкагкйпд кЬе ЬеЬиддег. * * изйпд .зутракЬ апЬ .зутракЬ+ * ЭДайкЕо^ЕVепк кай!ес1 ЭДАНШЕЮ: Зкаск ипмйпЬ йпкогтакйоп пок ауай!аЫе. ЕоИомйпд кгатез та у ЭДАНШЕЮ: Зкаск ипмйпЬ йпкогтакйоп пок ауай!аЫе. ЕоИомйпд кгатез та у * * ВидсЬеск Апа!узйз * * **** Кеппе! зутЬо!з аге ЭДВСЖС. Р1еазе кйх зутЬо!з ко Ьо ЬидсЬеск апа * * ВидсЬеск Апа!узйз * ВидсЬеск соЬе 000000Б1 Агдитепкз 00000000 00000002 00000000 !8Ь57624
СЫ1с1ЕВР ВеЕАсИг Агдз Ео СЫ1с1 ЭДАКШЫС: 8Еаск ипмтпс! 1п1огтаЕ1оп пок аVа^1аЫе. Ео11о^±пд Ргатез та у Е2270Ь54 804с1се53 0000000а 00000000 00000002 пкозкгп!!КеВидСкескЕх+Ох Е2270Ь70 00000000 00000000 00000000 00000000 пкозкгп!!Ке138бЕо±Не1рег еах=ЕЕсИЕ13с еЬх=0000000а есх=00000000 ес!х=40000000 ез!=Е8Ь57624 ес11= е!р=805266с1Ь езр=Е2270ЬЗс еЬр=Е2270Ь54 1ор1=0 пу ир е! пд пг сз=0008 зз = 0010 с!з=0023 ез=0023 1з=0030 дз=0000 еЕ1= пкозкгп!!КеВидСкескЕх+19: 805266с1Ь 5с1 рор еЬр Самой важной информацией, которую можно извлечь из приведенного выше набора строк — это код ошибочной ситуации (ЬидсЬеск сос!е), равный 0x0001. Остальные сведения, связанные с раскруткой стека вызовов в данной ситуации недоступны. Хотя именно данная информация особенно ценна в сгазЬ с!итр файлах.
ГРгеу|ои51 [Мех!],
Общие приемы отладки Зачастую проблемы исправления ошибок можно легко устранить при наличии достаточного объема диагностических сообщений. Ниже приводятся приемы, основанные как раз на этом.
Установка фиксированных точек прерывания При использовании интерактивных отладчиков не найдется много причин устанавливать фиксированные точки прерывания внутри кода драйвера (Иагс1 Ьгеакро1П15). Тем не менее, это можно сделать, если воспользоваться двумя функциями: УО1Э ВЬдВгеакРогпк() ; УО1Э Кс1ВгеакРо1пк () ; Вызов К<1ВгеакРо1п1 представляет из себя макроопределение, которое определяет условную компиляцию с целью выполнить вызов ОЬдВгеакРотЕ. Это макроопределение не выполняет данного вызова, если выполнена релизная (без отладочных инструкций) сборка драйвера (Ггее ЬиПс1). Будьте внимательны: \Л/1Пс1охл/5 дает фатальный сбой с сообщением КМООЕ_ЕХСЕРТЮ1\1_МОТ_НАМО1_ЕО в том случае, если драйвер применил фиксированную точку прерывания (через упомянутые вызовы), но в этот момент клиент отладки (см. рисунок 13.1) был недоступен. Если драйвер добрался до такой точки прерывания, и не оказалось отладчика, подключенного к последовательному порту, то драйвер "виснет". В некоторых случаях, ситуация может быть выправлена запуском отладчика на хост-компьютере.
Промежуточный вывод на экран Делать сообщения из недр отлаживаемого кода при помощи функции рпп1Г — это метод с давними и славными традициями. Его иногда называют "генерацией промежуточного вывода". Фактически, нет таких ошибок, которые нельзя было бы найти, если применить такой метод в достаточном (!) объеме. И хотя данный метод не так ныне распространен, как отладка с использованием точен прерывания в интерактивных отладчиках, тем не менее, он может быть очень полезен при поисках сбоев, связанных с временными затруднениями драйвера (например, ошибках или сбоях в последовательности событий, связанных с устройством). Для генерации промежуточных сообщений используются две функции ОЬдРпп1 и К<1Рпп1. Обе функции посылают форматированные строки, созданные на целевом компьютере, отладчику \Л/1пОЬд, работающему на хост-компьютере. Как было сказано ранее, собирать отладочные сообщения можно и при помощи ОеЬид\/1еуу. Вызов Кс1Рпп1 на самом деле является макроопределением и превращается в пустышку (невыполняемый участок) в релизной сборке драйвера (Ггее Ьи!1с1).
Сохранение отладочного кода в исходном тексте драйвера Хорошим приемом в деле доводки драйвера является сохранение отладочных фрагментов кода на их месте, даже если для потребителя была собрана релизная версия (Ггее версия без отладочных инструкций). Все это несложно сделать при помощи директив условной компиляции, и при возврате к выявлению более глубоких ошибок и повторному тестированию этот прием окажет отличную поддержку. Утилита В1ЛЮ использует символ времени компиляции ОВС, который может быть использован при составлении условно компилируемых фрагментов. В отладочной версии (сЬескес! ЬиНс!) этому символу присвоено значение 1, в версии Ггее значение ОВС равно 0. При внесении отладочного кода в драйвер следует ограничивать его рамками директив условной компиляции типа #И О6В и #епсНГ.
Перехват некорректных условий Разработчик драйвера, как и всякий разработчик, создает свой продукт, отталкиваясь от некоторых изначальных допущений. Важно лишь, чтобы они не были беспочвенными или чрезмерно оптимистическими. В том случае, если возникают некоторые сомнения в условиях работы некоторых важных участков кода драйвера, лучше подстраховаться. Например, предполагая, что некий фрагмент кода, будет вызван только на определенном уровне 1К<21_, автор закладывает потенциальную возможность будущих сбоев (если данные ожидания не будут оправданы). Для того чтобы перехватить эти непредусмотренные отклонения, следует применять несложный прием: такие допущения должны проверяться во время выполнения, хотя бы в отладочной сборке драйвера. Макроопределения А55ЕЯТ и А55ЕЙТМ56 помогут решить эту задачу: А55ЕКТ( Ехргеззтоп ); АЗЗЕКТМЗ(5( Меззаде, Ехргеззгоп ); В случае если выражение Ехргеззюп, рассмотренное как логическое, будет равно ЕА15Е, макроопределение А55ЕКТ произведет запись сообщения в командное окно \ЛЛпОЬд, подключенного на хост-компьютере для проведения отладки. Это сообщение содержит исходный код выражения, значение которого оказалось равным ЕА15Е, имя файла (в котором был исходный текст данного фрагмента) и номер строки, где было вызвано макроопределение А55ЕКТ. Затем предлагается сделать выбор, сделать ли прерывание в месте данного макроопределения А55ЕЯТ, игнорируя причину остановки, либо прервать процесс или поток, в котором "сработал" А55ЕКТ (что, впрочем, совершенно аналогично применению А55ЕКТ в \/15иа1 5Гис1ю). Макроопределение А55ЕКТМ5(з демонстрирует точно такое же поведение, с той лишь разницей, что в сообщение включается содержимое Меззаде (являющееся строкой). В отличие от деЬид рппГ функций, описанных ранее, А55ЕЯТМ5(з не допускает форматного вывода данных в стиле рппМ. Следует также обратить внимание на то, что оба макроопределения используют условную компиляцию, вследствие чего в версии "Ггее" оно совершенно исчезает. Следовательно, совершенно недопустимо помещать какой-либо исполняемый код внутрь этих макроопределений — в версии "Ггее" они исчезнут вовсе. Кроме того, в основе упомянутых выше макроопределений лежит использование функции КПАззегГ, которая во "Ггее" версиях \Л/1Пс1охл/5 2000/ХР/2003 превращается в пустышку. Следовательно, чтобы наблюдать последствия ошибок в макроопределениях А55ЕКТ и А55ЕЙТМ56 следует тестировать драйвер под отладочной (сЬескес!) версией \Л/1пс1оу75.
Использование диагностических саНЬаск-функций Диагностические обратные вызовы являются процедурами драйвера, который тот предлагает для вызова в моменты начала фатальных сбоев системы. Эти процедуры предлагают удобный способ захвата отладочной информации во время сбоя. Кроме того, они могут быть использованы для того, чтобы перевести аппаратуру в определенное состояние перед тем, как система окончательно "рухнет". Для использования диагностических обратных вызовов нужно выполнить следующее. 1. В процедуре ОпуегЕп1ту необходимо выполнить вызов функции Ке1т11аН2еСа11ЬаскКесогс1 для выполнения настройки структуры КВОССНЕСК_СА1_1_ВАСК_КЕССЖО. Место для хранения этой закрытой структуры должно быть выделено в нестраничном пуле (причем должно содержаться в не прикосновенности до момента ее де-регистрации, выполняемой при помощи функции Ке0егед18(егВидС11ескСа11Ьаск в процедуре Оп1оас1 данного драйвера). 2. В ОпуегЕпЁгу необходимо выполнить вызов КеКед181егВидСНескСа11Ьаск для подключения драйвера к механизму уведомления об ошибочных ситуациях. Аргументами этого вызова будет указатель на структуру типа КВОССНЕСК_СА1_1_ВАСК_КЕССЖО, адрес предоставляемой драйвером саИЬаск- функции, адрес и размер предоставляемого драйвером буфера и строка, которая будет использована для идентификации буфера. Место для буфера должно быть выделено в нестраничном пуле (причем также должно содержаться в неприкосновенности до момента вызова функции Ке0егед15(егВидСНескСа11Ьаск). 3. В том случае, если была обнаружена ошибка, система выполняет вызов зарегистрированной в п. 2 саИЬаск-функции, которой будет передан адрес буфера и его размер. Работа вызванной функции состоит в том, что она должна заполнить предоставленный буфер информацией, которую сочтет необходимой, и которая не смогла бы иным образом найти свое отражение в сгазК с!итр файле, например, внутреннее содержимое регистров обслуживаемого устройства. 4. При анализе сгазК с!итр файла при помощи ХЛЛпОЬд эту информацию можно вывести на экран, если воспользоваться командой !Ьидс1итр. Существуют некоторые ограничения на действия, разрешенные регистрируемой саИЬаск-функции. Во время работы она не может получать какие-либо системные ресурсы (например, память). Она также не должна использовать объекты синхронизации. Однако разрешено делать вызовы процедур ядра, которые не нарушают указанных ограничений, и вызовы процедур НА1_ уровня, например, для того, чтобы получить доступ к регистрам устройства.
Обнаружение утечек памяти Утечки памяти являются самой жестокой патологией программных кодов. Драйверы, которые получили для себя пространство в одном из пулов памяти, а затем забыли его освободить, могут привести к серьезной деградации системы и, через некоторое время, к ее фатальному сбою. Использование тегового механизма выделения памяти может оказать существенную помощь в обнаружении утечек. Как это работает? 1. Необходимо заменить вызовы ЕхА11оса1еРоо1 на вызовы ЕхАНоса1еРоо1У\ЛЫ1Тад. Дополнительный аргумент, представляющий собой четырехбайтную величину (4 символа), используется для того, чтобы пометить вновь выделенный блок этим значением (тегом). 2. Необходимо запустить драйвер под отладочной версией (сКескес! Ьи11с1) \Л/1Пс1о\л/5. Поддержка трассировки страниц пула является дорогостоящим "удовольствием", поэтому доступно ОНО ТОЛЬКО В отладочных версиях \Л/|ПС|ОУ75. 3. Когда выполняется анализ сгазК с1итр файла или в ситуации, когда достигну та точка прерывания при отладке "живого" драйвера, следует воспользоваться командами !роо1изес1 или !роо1Г1пс1 для того, чтобы ознакомиться с состоянием пулов памяти. Эти команды сортируют области пулов по значению тегов и высвечивают различные статистические данные об использовании памяти. Следует помнить, что в отсутствии файлов отладочных символов указанные команды \Л/1пОЬд не работают. Легкий способ повсеместной замены вызовов ЕхА11оса1еРоо1 на вызовы ЕхА11оса1еРоо1Ех состоит в том, чтобы изначально использовать фрагменты условной компиляции, например: #1^ ОВС==1 #с1еГ1пе АЬЬОСАТЕ_РООЬ (1:уре, 512е) \ ЕхАИоса'ЬеРооТИт'ЬЬТад ( ('Ьуре) , (зтхе) , ’ 1234 ' ) #е1зе #с1е^1пе АЪЪОСАТЕ_РООЬ ('Ьуре, З12е) ЕхА11осаРеРоо1 ( (Руре) , (зххе #епсИГ Аргумент тега в вызове ЕхАНосаЕеРооЮТННТад состоит из четырех букв (в верхнем регистре), которые на экране отладчика предстанут в обратном порядке, то есть '4321'. В данном примере для всех операций выделения памяти при помощи ЕхАНосаЕеРооЮТННТад использован один и тот же тег. В некоторых ситуациях может оказаться целесообразным использование разных значений тега для выделения памяти под структуры данных разного типа, или для выделения памяти в разных фрагментах драйвера. Эти приемы помогут эффективнее идентифицировать утечку памяти. Поставляемая в составе СЭК утилита Роо1Тад позволяет наблюдать теговое выделение памяти и без привлечения отладчика \ЛЛпОЬд. Эта программа непрерывно выводит на экран обновляемые данные о страничных тегах.
Установка параметров загрузки в файле Ьоо1.!т Полезным для изучения загрузки системы и отслеживания набора загружаемых драйверов может оказаться использование параметров, вводимых в файле ЬооГ.1п1. Его можно редактировать при помощи редактора поГерас! (если убрать атрибут "только для чтения", например, в программе УУ1пСоттап<Зег-То1:а1Соттапс1ег), но более правильно это делать в окне системного апплета, показанного на рисунке 13.3. В документации СОК несложно отыскать описание задаваемых параметров загрузки — по ключу "Ьоо1лп|" - "РагатеГегз Гог ЬооГлп|". Помимо параметра /зоз, подробно рассмотренного в приложении Б, разработчику драйверов может быть также полезен параметр /тахтет=Ххх, который ограничивает размер используемой физической памяти, что позволит протестировать систему и драйвер в условиях недостатка физической памяти.
ГРгеу1ои8~| ГИехМ
Частные приемы восстановления системы При поэтапном тестировании драйвера (сначала — процедур загрузки и выгрузки, затем скелета рабочих процедур, затем алгоритма функционирования и т.п.) неполадки в коде вряд ли вызовут проблемы общего функционирования системы. Однако полностью исключать вероятность разрушения системы нельзя. Поэтому рассмотрим некоторые методы приведения ее к работоспособному состоянию после сбоев. Прежде всего, после фатального сбоя системы следует разрешить ей выполнить проверку целостности файловой системы. Несмотря на длительность этой процедуры, следует довести ее до завершения (при соответствующем запросе системы), поскольку накопления дефектов недопустимо — это опаснее, нежели каждый из сбоев в отдельности. В некоторых случаях установка нового драйвера может приводить к невозможности загрузить систему обычным способом. В этом случае возможны несколько вариантов развития событий. Во-первых, по нажатию клавиши Е8 можно произвести выбор в начале загрузки компьютера разных вариантов, из которых наиболее часто применяемыми для восстановления работоспособности являются два: загрузка последней удачной конфигурации; загрузка в безопасном режиме. В последнем случае по завершении загрузки можно удалить драйвер, ставший источником неприятностей, из
системы средствами Диспетчера устройств, либо просто стереть бинарный файл нового "плохого" драйвера в том месте, где его должна обнаруживать операционная система — с целью не допустить его загрузку в следующий раз. В тех случаях, когда загрузка системы более или менее успешно доходит до конца, администратор может выполнить восстановление состояния системы в одной из ранее сохраненных резервных точек (об этом говорилось в 3 главе). При более серьезных проблемах можно воспользоваться загрузкой с внешнего носителя. Способ создания загрузочных дискет для У\Лпс1о\л/5 ХР описан на Интернет- сайте МюгозоЛ (статья Базы Знаний номер (230559) по адресу 9. Загрузочная дискета позволит получить доступ к логическим дискам с установленной системой У\Лпс1о\л/5. Логично пользоваться этим способом на компьютерах, не поддерживающих загрузки с СО кОМ. (Имеется в виду то, что дискета обеспечивает доступ к загрузочному СО диску, который также необходим в этом процессе.) В том случае, если в распоряжении имеется компакт- диск для установки У\Лпс1о\л/5 и компьютер может быть загружен с СО НОМ (практически все современные компьютеры на платформе 1п1:е1 позволяют это сделать), то следует выполнить именно такой способ загрузки и войти в Консоль Восстановления системы (Несоуегу Сопзо1е). В этом режиме будут доступны системные консольные команды, список которых можно получить по команде Ье1р. Наиболее актуальной является команда принудительной проверки диска (например, логического диска О)
СНК05К О: /р В данном случае ключ /р обеспечивает принудительную проверку. В том случае, если оказалась испорчена загрузочная запись (МВк) или следует избавиться от загрузочной записи нестандартных загрузчиков 1_11_О, М11_О и т.п., следует воспользоваться программой НхтЬг. В этом же режиме можно выполнить поверку диска. Перезаписать загрузочный (Ьоо!:) сектор на логическом диске можно при помощи команды НхЬооГ При ненарушенной файловой системе затруднения в загрузке могут быть обусловлены плохим состоянием информации в Системном Реестре. Некоторые источники предлагают (помимо специальных программ) такой метод восстановления как простое копирование ранее сохраненных файлов Системного Реестра на их место в директории %5уз1:етгоо1:%\сопГ|д. В нормальном рабочем состоянии операционная система не разрешает доступа к ним (для просмотра, копирования или перезаписи), однако, при загрузке с установочного СО диска, это становится допустимой операцией. Файлы Реестра, которые всегда имеют солидные размеры, для этого случая можно сохранять на других логических дисках компьютера. Заметим также, что все затруднения, связанные со спецификой доступа к разделам файловой системы в формате 1ХПТ5, можно обойти, если установить операционную систему на логический диск с форматом ЕАТ32. Утратив некоторые преимущества МТГ5 (такие, как запись файлов более 4Гбайт и более высокая надежность), можно будет получать доступ к таким разделам с дискеты М5-ОО5 или при загрузке У\Лпс1о\л/5
98 с другого логического диска. Совершенно аналогичный и официальный совет дает МюгозоЛ:, предлагая установить на компьютере еще один экземпляр У\Лпс1оуу8 1ЧТ на другом логическом диске, что, безусловно, предоставит возможность доступа ко всем 1МТР5 разделам, в том числе — к тому, на котором испытывается драйвер и, возможно, произошел сбой. Наконец, еще одним средством доступа к файлам операционной системы \Л/1пс1о\/У5 1\1Т 5, размещенным на диске с файловой системой 1ХПТ5, является программа 1ХПТ5ОО5, разработанная фирмой 5уз1п1:егпа15. При загрузке М5 005 либо при загрузке с загрузочной дискеты, сделанной под \Л/|пс1о\л/5 98, эта программа (если она записана в не-1\ПТ5 разделах) может быть запущена и позволяет получить доступ к 1ЧТГ5 разделам жесткого диска, включая запуск программы СНК05К. (Интересно, что в свой загрузочный набор программа 1ХПТ5ОО5 при установке включает еще файлы 1\ко5кгп1.ехе, Ы^Гз.зуз и Ык111.с111 из установленной на компьютере версии \Л/1пс1о\л,5 1\1Т.) Соответственно, это позволяет модифицировать данные в 1МТГ8 разделах — при наличии полной версии МТЕ5005 РгоГеззюпа! — или копировать их в другие не-1\ПТ5 разделы (демо-версия), если они присутствуют в системе. Возможен вариант, при котором подготавливается комплект из 2-х дискет, с которых и выполняется загрузка. Приступая к тестированию драйвера, разработчик должен в достаточной мере подготовиться к потенциально возможным неприятностям, которые могут возникнуть из-за нарушения целостности операционной системы, сводя свое вмешательство к минимуму. Тем более, что неполадки, действительно требующие переустановки всей операционной системы, возникают не так уж часто.
ГРгеу1ои8~| ГИехМ
Заключение Данная глава была посвящена рассмотрению общих вопросов тестирования драйверов. Ошибки программирования драйверов обладают большой разрушительной и деморализующей силой. Простой пользователь в этой ситуации напоминает мирного жителя, который не может точно сосчитать неожиданно появившихся диверсантов. И не следует его в том винить. Драйвер является кодом, которому операционная система априори доверяет больше, нежели коду приложений пользовательского режима. Эта банальность есть основной аргумент в пользу тщательной проверки всех неясных мест в коде драйвера, включая дополнительную проверку, зачастую, весьма скупо описанных в документации ООК возможностей и особенностей системных вызовов. Возможно, приведенные в данной главе немногочисленные методы обнаружения, изоляции и предотвращения ошибок драйверного кода помогут разработчику в его нелегком труде. Интерактивная отладка всегда более привлекательна, однако, оснащение для этого требуется существенное, включая возможные затраты по подписке на дополнительную информацию от МюгозоТС. Операционная система \Л/1пс1о\л/5 1\1Т 5 (версии 2000, ХР и 5еп/ег 2003) отличается богатым набором инструментальных средств, которыми разработчик быстро и эффективно может локализовать ошибку, особенно, если она является в большей степени
программной (в отличие от аппаратных ошибок обслуживаемого устройства). Остается лишь научиться это применять на практике.
ГРгеуюиа! [Мех!] Приложение А
Коды ошибочных ситуаций При выводе системных сообщений о прекращении работы (известные как Ьид-сИескз), выводятся также коды, по которым можно определить, что побудило систему запаниковать. В зависимости от ошибки, система сообщает до 4-х дополнительных параметров, которые дают дополнительную информацию о возникшей проблеме. Хотя полный перечень кодов можно найти в заголовочном файле Ьид- сос1е5.К, входящий в пакет ООК, расшифровки значений там не приводится. По этой причине ниже приводятся наиболее часто встречающиеся коды, основные причины данных ситуаций и расшифровка дополнительных параметров. Данному вопросу посвящена статья М1СгозоГ1 Кпо\л/1ес1де Вазе <2103059. 0x08 1К<21-_МОТ_О18РАТСН_1-ЕУЕ1- Был выполнен функцией (предназначенной для вызова с уровня равного О18РАТСН—1_ЕУЕ1_) из кода, работающего на более высоких уровнях, например 0111(21. Параметры Описание 1-4 Зарезервировано ОхОА т(21._МОТ_1.Е55_ОК_Е(2иА1. Драйвер пытается получить доступ к странично организованной памяти при работе на уровне привилегий 1ВД1- равном О18РАТСН_1_ЕУЕ1_ или выше, причем эта страница в момент обращения отсутствует в оперативной памяти (ситуация не может быть перехвачена обработчиком РАСЕ ЕА11ЬТ) Параметры Описание 1 Адрес в страничной памяти, обращение по которому вызвало сбой 2 Значение уровня в момент обращения 3 Тип доступа при этом обращении 0 — чтение, 1 — запись 4 Адрес инструкции, которая выполняла некорректное обращение 0x10 5Р1М_ЬОСК_МОТ_ОУ\ЖЕО Программный поток пытается освободить объект спин-блокировки, которым не владеет Параметры 1-4 Описание Зарезервировано 0x19 МЕМОКУ_МАПА6ЕМЕМТ Структура "кучи" (Пеар — область, из которой программному коду динамически выделяется память) нарушена Параметры Описание 1-4 Зарезервировано 0х1Е КМООЕ_Е5СЕРТ1ОМ_1ЧОТ_НАПО1.ЕО
Программный код режима ядра вызвал исключение, для которого нет обработчика (в частности, его не предоставил сам программный код) Параметры Описание 1 Код исключения (как правило, это значения, которые можно найти в файле уллпеггог.И), в частности: • 0хС0000005 — нарушение прав доступа (ассезз ую1айоп): ошибочный указатель или запись в область памяти "только для чтения" • ОхСОООООЮ — ошибочная инструкция. Возможно, при выполнении вызова по указателю произошло неправильное декодирование инструкции, поскольку адрес был ошибочен. Возможно, что стек был испорчен, в результате чего не сохранился адрес возврата из текущей функции • 0хС0000094 — целочисленное деление на ноль • ОхСОООООРО — переполнение стека, например, в результате многочисленных рекурсивных вызовов 2 Адрес инструкции, которая вызвала исключение 3 Первый параметр исключения 4 Второй параметр исключения 0x24 1ЧТЕ5_ЕП.Е_5У5ТЕМ Выявлена проблема в пНз.зуз Параметры Описание 1 Исходный файл и номер строки 2 Адрес записи исключения (необязательно) 3 Адрес записи контекста (необязательно) 4 Адрес инструкции, где было вызвано исключение (необязательно) 0х2А ПЧСОП515ТЕМТ_тР Проблема в структуре 1ЯР пакета: параметры конфликтуют. Возможно, в результате того, что указатель на 1ВР пакет ошибочно использовался в качестве указателя на элемент данных другого типа Параметры Описание 1 Адрес 1Р.Р пакета 2-4 Зарезервировано 0х2Е 0АТА_В0 5_Е РИО И Данная ошибка обычно бывает обусловлена сбоем в контроле четности в системной памяти — аппаратная проблема. (Данная ошибка может быть также вызвана обращением драйвера по несуществующему виртуальному адресу Охвххххххх в системном адресном пространстве.) Параметры 1 2 3 4 Описание Виртуальный адрес, который вызвал сбой Физический адрес, который вызвал сбой Содержимое Р5Р. (Ргосезз 51а1из Р.ед1з1ег, регистра состояния процессора) Еаи111пд 1пз1тис1юп гед!з1ег (Е1Р.) 0x3 5 П О_М О КЕ_1 КР_5ТАС К_1_ОС АТ10 П 5 Был создан 1НР пакет, в котором оказалось недостаточно ячеек стека (з1аск
1осаНоп5) для того, чтобы передать его в 1оСа11Огшег — для обработки нижними слоями драйверов Параметры Описание 1 Адрес 1Р.Р пакета 2-4 Зарезервировано 0x36 ОЕУ1СЕ_РЕРЕРЕМСЕ_СО1ЛЧТ_МОТ_2ЕРО Был выполнен системный вызов 1оОе1е1еОеу|се, в процессе которого обнаружилось, что число ссылок на объект устройства все еще не равно нулю, то есть операция удаления объекта некорректна Параметры Описание 1 Адрес объекта устройства (0еу!се 0Ь]ес1) 2-4 Зарезервировано 0x3 Р П О_М О КЕ_5 У5ТЕ М_РТЕ 5 Системная таблица страниц заполнена. Наиболее вероятная причина состоит в том, что драйвер не выполняет освобождение страниц памяти. Возможно, слишком мала таблица страниц (следует обратиться к методам ее увеличения) Параметры Описание 1-4 Зарезервировано 0x44 М 01_Т1 РЬЕ-ШР-СОМ Р1_ЕТЕ_КЕ(2и Е5Т5 Был выполнен системный вызов 1оСотр1е(еРеяие5(, в процессе которого обнаружилось, что относительно данного 1РР пакета это действие уже выполнялось ранее Параметры Описание Адрес 1Р.Р пакета 2-4 Зарезервировано 0x50 РАС Е_Р А11 ЬТ_11ЧЦЧ О П РАС Е О_А РЕ А Ошибочное обращение к области памяти в системном адресном пространстве Параметры 1 2 Описание Адрес, обращение по которому вызвало сбой Код доступа при возникновении ошибки 0 — чтение, 1 — запись 3 Адрес инструкции, из которой был осуществлен ошибочный доступ (если он известен) 4 Зарезервировано 0x51 РЕС15ТРУ_Е РИО И Структура Системного Реестра нарушена, возможно, в процессе предыдущего системного сбоя. К сожалению, переустановка \Л/1пс1о\л/5 самый вероятный способ преодоления этой проблемы. (Разумеется, если не выполнялось резервное копирование системных файлов.) Параметры Описание 1-4 Зарезервировано
0x58 РТ_ О15К_ ПЧТЕР1ЧА1-—ЕРРОР Система была загружена из восстановленного основного раздела (рптагу рагННоп). (Поддерево реестра говорит — все хорошо, но на самом деле это не так.) Параметры Описание 1-4 Зарезервировано 0x76 РНОСЕ55_НА5_ЬОСКЕО_РА6Е5 Работа завершена, но остались зафиксированные в памяти (1оскес1) страницы. Возможно, драйвер пытался освободить заблокированные страницы после операции ввода/вывода, в частности, в процедуре 11п1оас1 или обработчике запроса 5Ьи1с!о\л/п, но это ему не удалось Параметры 1 2 Описание 0 Адрес процесса 3 4 Число заблокированных страниц 0 или указатель на стек драйвера 0x7 7 КЕППЕ 1__5ТАС К_1 14 РАС Е_Е РРО И Запрошенная страница системного адресного пространства, размещенная в страничном файле, не может быть считана в оперативную память Параметры 1 Описание Код состояния или 0 2 Код состояния ввода/вывода или значение, найденное в стеке, где должна размещаться сигнатура 3 4 0 или номер страничного файла Адрес сигнатуры в стеке или смещение в страничном файле 0x79 М15МАТСНЕО_НАЬ Версии (геу|51оп 1еуе1) программного кода ядра и программного кода НА1_ не согласуются. Это может происходить из-за неправильного использования ключей /кегле! и /На! в файле ВООТ.П41. (См. М!сгозоН Кпош1ес1де Вазе, <2103059.) Параметры Описание 1: Рассогласование редакций РР.СВ 1 2: Рассогласование версий сборки 3: Рассогласование М!сго СИаппе! 1: Редакция (ге1еазе 1еуе1) п1озкгп1.ехе 2 2: Тип сборки (Ьи!1с1 1уре) п1озкгп1.ехе 3: Тип компьютера, обнаруженный во время загрузки 1: Редакция Иа1.с111 3 2: Тип сборки Иа1.с111 3: Тип компьютера, поддерживаемый НА1_ 4 Зарезервировано 0x7 А КЕППЕ 1__О АТА_114 РАС Е_Е РРО Р Запрошенная страница системного адресного пространства, размещенная в страничном файле, не может быть считана в оперативную память
Параметры 1 2 Описание Тип блокировки (значения 1,2,3 или адрес записи в таблице страниц) Код статуса операции ввода/вывода (во время которой произошла ошибка) 3 Если тип блокировки 1 или 2 — текущий процесс Если тип блокировки 3 — виртуальный адрес 4 Виртуальный адрес, соответствующая которому страница не может быть считана в оперативную память 0x7В 1ПАССЕ551ВЬЕ_ВООТ_ОЕУ1СЕ Операционная система \Л/1пс1о\л/ 1ЧТ потеряла доступ к системному разделу (зуз^ет рагШ1Оп) во время запуска. Это может происходить из-за ошибок в драйвере, обслуживающем устройство, с которого должна осуществляться загрузка, из-за наличия программного обеспечения, несовместимого с 1ЧТ, вирусов, неверного конфигурирования СО-РОМ, ошибок в разбиении или незавершенного разбиения жесткого диска (рагННоп 1аЫе). (См. также МйсгозоН Кпо\л/1ес1де Вазе <2103069, <2105026, <2115339, <2120744, <2124307, <2126423, <2131337, <2131712, <2136074, <2137860, <2153296, <2156168, <2161960.) Параметры Описание 1 Адрес объекта устройства, которое не может быть смонтировано 2-4 О 0х7Р иПЕХРЕСТЕО_КЕРПЕЬ_МООЕ_ТРАР Только для платформы ПЧТЕЬ. Процессор сгенерировал системное прерывание (1гар), которое не может быть обработано ядром Параметры Описание 1 Код прерывания 2-4 Зарезервировано 0x81 5Р11Ч_1»ОСК_1М1Т_ЕАП-11РЕ Ошибка при инициализации спин-блокировки. Условия возникновения данной ошибки четко не определены, однако ее природа всегда — программная Параметры Описание 1-4 Зарезервировано 0x9 Р О УЕ К_РО УУ Е Р_5ТАТЕ_Р А1Ш РЕ Драйвер находится в ошибочном состоянии с точки зрения участия в схеме энергопотребления Параметры Описание 1: Освобождаемый драйверный объект имеет незавершенный запрос по схеме энергопотребления, который ожидает обработки 2: Драйверный объект завершил обработку системного 1Р.Р запроса по энергопотреблению, но не выполнил вызов Ро§1аг1№х1Рошег1гр 1 3: Драйвер не оформил должным образом 1Р.Р запрос как ожидающий обработки или как обработанный 100: Объекты устройств в узле (с!еупос1е) несостоятельны в своем использовании 00_Р0\Л/ЕК_РА6АВ1_Е 101: Родительский объект устройства обнаружил, что дочерний объект устройства не установил бит 00_Р0\Л/ЕК_РА6АВ1_Е
2 1: Указатель на объект устройства 2: Указатель на целевой объект устройства 3: Указатель на целевой объект устройства 100: Указатель на объект устройства в нестраничной памяти 101: Дочерний объект устройства (РОО) 3 1: Зарезервировано 2: Указатель на объект устройства 3: Указатель на объект устройства 100: Указатель на целевой объект устройства 101: Дочерний объект устройства (РОО) 4 1: Зарезервировано 2: Зарезервировано 3: 1Р.Р пакет 100: Указатель на уведомляемый объект устройства 101: Родительский объект устройства ОхВЕ АТТЕ М РТЕ О_АЛ/ К1ТЕ-ТО-КЕ А ОО П ЬУ_М Е М О РУ Драйвер пытается выполнить запись в область памяти, предназначенную только для чтения Параметры 1 2 Описание Виртуальный адрес, по которому производится попытка записи Содержимое записи в таблице страниц 3-4 Зарезервировано 0хС1 5РЕС1А1»_РОО1__ОЕТЕСТЕО_МЕМОРУ_СОРР11РТ1ОП Драйвер произвел запись в ошибочную секцию специального пула памяти Параметры Описание Попытка освободить область памяти, которая не была ранее получена (выделена а11оса1ес1) 1 Адрес, к которому предпринята попытка "освободить" его 2 Зарезервировано 3 О 4 0x20 Попытка освободить область по ошибочному адресу 1 Адрес, к которому предпринята попытка "освободить" 2 Запрошенное число байт 3 Рассчитанное число байт 4 0x21, 0x22 Попытка освободить область по адресу, в то время как близлежащие байты на странице испорчены 1 Адрес, к которому предпринята попытка "освободить" 2 Адрес, где размещается испорченная область 3 Зарезервировано 4 0x23 Попытка освободить область по адресу, в то время как байты после данной выделенной области перезаписаны 1 Адрес, к которому предпринята попытка "освободить"
2 Адрес, где размещается испорченная область 3 Зарезервировано 4 0x24 Попытка получить область в пуле памяти при ненадлежащем уровне 1Нрь 1 Текущий уровень 1РС21_ 2 Тип пула памяти 3 Число байт 4 0x30 Попытка освободить область в пуле памяти при ненадлежащем уровне 1Нрь 1 Текущий уровень 1РС21_ 2 Тип пула памяти 3 Адрес, к которому предпринята попытка "освободить" 4 0x31 0хС2 ВАО_РООЬ_САЬЬЕН Текущий программный поток сделал ошибочный запрос к области памяти Параметры Описание Заголовок пула памяти испорчен 1 0x01, 0x02 или 0x04 2 Указатель на заголовок пула 3 Первая часть содержимого заголовка пула 4 0 Попытка освободить область в пуле памяти, которая уже освобождена 1 0x06 2 Зарезервировано 3 Указатель на заголовок пула 4 Содержимое заголовка пула Попытка освободить область в пуле памяти, которая уже освобождена 1 0x07 2 Зарезервировано 3 Указатель на заголовок пула 4 0 Попытка получить область в пуле памяти при ненадлежащем уровне 1Крь 1 0x08 2 Текущий уровень 1Р<21_ 3 Тип пула памяти 4 Размер запрошенной области Попытка освободить область в пуле памяти при ненадлежащем уровне 1Крь 1 0x09 2 Текущий уровень 1РС21_ 3 Тип пула памяти 4 Адрес пула Попытка освободить область в пуле памяти уровня ядра по указателю, который имеет вид адреса пользовательского режима
1 0x40 2 3 4 Начальный адрес области Начало системного адресного пространства 0 Попытка освободить область в пуле нестраничной памяти, которая не была ранее получена (выделена, а11оса1ес1) 1 2 0x41 Начальный адрес области 3 4 Страничный блок физической памяти Максимальный страничный блок физической памяти Попытка освободить область в нуле страничной памяти, которая не была выделена 1 2 3 4 0x50 Начальный адрес области Смещение (в страницах) от начала страничного пула Размер области страничного пула в байтах Попытка освободить область в пуле памяти по ошибочному адресу либо с разрушенным заголовком 1 2 0x99 Адрес, к которому предпринята попытка "освободить" 3 4 0 0 0хС5 ОР1УЕР_СОРРиРТЕО_ЕХРООЬ Драйвер, возможно, разрушил пул в системном адресном пространстве Параметры 1 Описание Указатель на область памяти, обращение к которой вызвало сбой 2 Уровень 1КС21_ в момент обращения 3 4 Код доступа при возникновении ошибки 0 — чтение, 1 — запись Адрес инструкции, обратившейся к области памяти ОхСб ОР1УЕР_САибНТ_МОО1РУ1П6_РРЕЕО_РООЬ Драйвер обращается к области пула памяти, которая освобождена Параметры 1 2 Описание Указатель на область памяти, обращение к которой вызвало сбой Код доступа при возникновении ошибки 0 — чтение, 1 — запись 3 0: в режиме ядра 1: в пользовательском режиме 4 4 0хС7 Т1МЕК_ОК_ОРС_1МУА1ЛО Области памяти под объектами уровня ядра — таймером или ОРС — были освобождены, хотя они еще находятся в очереди, ожидая активации Параметры Описание
1 0: объект таймера 1: объект ОРС 2: ОРС процедура 2 Адрес объекта 3 Начало области, обращение к которой вызвало ошибку 4 Конец области, обращение к которой вызвало ошибку ОхСА РПР_РАТАЬ_ЕККОК РпР Менеджер обнаружил критическую ошибку, вероятно, в результате ошибки в функционировании РпР драйвера Параметры Описание Обнаружены двойники-РОО (объекты физического устройства) — отдельные фрагменты драйвера создали несколько РОО объектов с одинаковыми идентификатором устройства 1 0x01 2 Адрес вновь созданного РОО объекта 3 Адрес ранее созданного РОО объекта, который теперь "повторен" 4 Зарезервировано Ошибка в объекте физического устройства. Программный поток, которых запросил объект РОО, не выполнил инициализацию объектов РОО или РОО 1 0x02, 0x03 2 Адрес подразумеваемого РОО объекта 3 Зарезервировано 4 Зарезервировано Ошибка в идентификаторе (10). Модуль ядра, который выполнял перечисление (епитегаНоп) возвратил идентификатор, который содержит ошибочные символы или неверно завершен. Идентификаторы должны содержать только символы А5С11 0x20..0x2В и 0x20..0х7Р 1 0x04 2 Адрес РОО объекта с установленным 00_0Е1_ЕТЕ_РЕМ011\16 3 Адрес буфера, содержащего Ю 1: Оеу|сеЮ 4 2: ип1цие10 3: Аппаратные идентификаторы 4: Совместимые идентификаторы Объект РОО был освобожден (то есть Менеджер объектов уменьшил число ссылок на него до нуля), но сам объект еще участвует в дереве объектов устройств (с1еупос1е (гее). Как правило, это означает, что драйвер не добавил ссылку (соответствующим системным вызовом) в тот момент, когда возвратил РОО объект в ответ на поступивший соответствующий 1КР запрос 1 0x05 2 Адрес РОО объекта 3 Зарезервировано 4 Зарезервировано ОхСВ ОК1УЕК_ЬЕРТ_ЬОСКЕО_РА6Е5_1П_РКОСЕ55 Драйверу не удалось выполнить освобождение зафиксированных в оперативной памяти (1оскес1) страниц после операции ввода/вывода
Параметры 1 2 Описание Адрес инициатора (в драйвере) фиксации данных страниц Инициатор вызова инициатора фиксации 3 Указатель на М01_ список, содержащий зафиксированные в оперативной памяти страницы 4 Указатель на имя "виновного" драйвера (в формате Отсосе) ОхСС РА6Е_ЕА1Л.Т_1М_ЕКЕЕО_5РЕС1А1._РОО1. Система обратилась к ранее освобожденной области памяти Параметры 1 Описание Адрес, обращение к которому, вызвало сбой 2 Код доступа при возникновении ошибки 0 — чтение, 1 — запись 3 Адрес инструкции, из которой был осуществлен ошибочный доступ (если он известен) 4 Зарезервировано ОхСО РА(зЕ_ЕА1Л-Т_ВЕУОПО_ЕПО_ОЕ_А1ХОСАТ1ОП Система обратилась к памяти за пределами выделенной драйвером области (в некотором пуле) Параметры 1 2 Описание Адрес, обращение по которому вызвало сбой Код доступа при возникновении ошибки 0 — чтение, 1 — запись 3 Адрес инструкции, из которой был осуществлен ошибочный доступ (если он известен) 4 Зарезервировано ОхСЕ ОК1УЕК_иПЬОАОЕО_УУ1ТНОиТ_САПСЕЬЬ1П6_РЕПО11Ч6_ОРЕКАТ1ОП Драйвер не отменил незавершенные (репсПпд) операции перед своей выгрузкой Параметры 1 2 Описание Адрес, обращение по которому вызвало сбой Код доступа при возникновении ошибки 0 — чтение, 1 — запись 3 Адрес инструкции, из которой был осуществлен ошибочный доступ (если он известен) 4 Зарезервировано 0x00 ОР1УЕР_СОРРиРТЕО_ММРООЬ Драйвер испортил системный пул памяти Параметры 1 2 3 4 Описание Адрес, обращение по которому вызвало сбой Уровень 1КС21_ в момент обращения Код доступа при возникновении ошибки 0 — чтение, 1 — запись Адрес инструкции, из которой был осуществлен ошибочный доступ 0x01 О1ПУЕН_т(21__Г1ОТ_1_Е55_ОН_Е(2иА1_
Драйвер пытался получить доступ к страничной памяти при работе на высоком уровне Параметры Описание 1 Адрес, обращение по которому вызвало сбой 2 Уровень 1КС21_ в момент обращения 3 Код доступа при возникновении ошибки 0 — чтение, 1 — запись 4 Адрес инструкции, из которой был осуществлен ошибочный доступ 0x03 И V Е К_РО КТ101Ч_М 0 5Т_В ЕЦЧ О П Р Аб Е И Драйвер поступил некорректно, пометив сегмент своего кода или своих данных как размещаемый в страничной памяти Параметры Описание 1 Адрес, обращение по которому вызвало сбой 2 Уровень 1КС21_ в момент обращения 3 Код доступа при возникновении ошибки 0 — чтение, 1 — запись 4 Адрес инструкции, из которой был осуществлен ошибочный доступ 0x04 5У5ТЕМ_5СА1Ч_АТ_КА15ЕО_т(21-_САи(зНТ_1МРКОРЕК_ОК1УЕК_иГ11_ОАО Драйвер не отменил незавершенные (репсПпд) операции перед своей выгрузкой Параметры Описание 1 Адрес, обращение по которому вызвало сбой 2 Уровень 1КС21_ в момент обращения 3 Код доступа при возникновении ошибки 0 — чтение, 1 — запись 4 Адрес инструкции, из которой был осуществлен ошибочный доступ 0x05 ОР1УЕР_РА6Е_РАиЬТ_1П_РРЕЕО_5РЕС1АЬ_РООЬ Драйвер обратился к ранее освобожденной области памяти Параметры Описание 1 Адрес, обращение по которому вызвало сбой 2 Код доступа при возникновении ошибки 0 — чтение, 1 — запись 3 Адрес инструкции, из которой был осуществлен ошибочный доступ (если он известен) 4 Зарезервировано 0x06 ОК1УЕК-РА(зЕ_РА1Л_Т_ВЕУОПО_ЕПО_ОР_А1_1_ОСАТ1ОП Драйвер обратился к памяти за пределами выделенного пула памяти Параметры Описание 1 Адрес, обращение по которому вызвало сбой 2 Код доступа при возникновении ошибки 0 — чтение, 1 — запись 3 Адрес инструкции, из которой был осуществлен ошибочный доступ (если он известен) 4 Зарезервировано 0x07 0К1УЕК_иПМАРР11Ч(з_1МУА1Л0_У1ЕУУ
Драйвер пытается получить физический адрес для виртуального, который не существует Параметры Описание 1 Виртуальный адрес 2 0: Система не является терминальным сервером 1: Система является терминальным сервером 3 0 4 0 0x08 0Р1УЕР_и5Е0_ЕХСЕ551УЕ_РТЕ5 Не осталось свободных записей в системной таблице страниц виртуальной памяти Параметры Описание 1 Указатель на имя "виновного" драйвера (С1гнсос1е) либо 0 2 Число записей в системной таблице страниц, используемых "виновным" драйвером (если первый параметр не равен 0) 3 Общее количество свободных записей в системной таблице страниц 4 Общее количество записей в системной таблице страниц ОхОВ 0 Р1УЕ Р_СО РИО РТЕ О_5 У5 РТЕ5 Была сделана попытка обращения к памяти при ненадлежащем уровне 1<2К1_, вероятно, из-за разрушения записей в системных страничных таблицах виртуальной памяти Параметры Описание 1 Адрес, обращение по которому вызвало сбой 2 Уровень в момент обращения 3 Код доступа при возникновении ошибки 0 — чтение, 1 — запись 4 Адрес инструкции, из которой был осуществлен ошибочный доступ ОхОС ОК1УЕК_1МУА1ЛО_5ТАСК_АССЕ55 Драйвер пытался получить доступ к пространству стека, которое находится ниже указателя границы стека (зСаск ротСег) текущего рабочего потока Параметры Описание 1-4 Зарезервировано ОхОЕ РООЬ_СОРРиРТЕО_1П_Р1ЬЕ_АРЕА Драйвер разрушил пул памяти, используемый для хранения страниц, предназначенных для работы с диском Параметры Описание 1-4 Зарезервировано 0хЕ1 АЛЮ ИКЕ Р_ТН РЕ А О_РЕТ11 РП Е О_АТ_В А 0_1 Р(21_ Рабочий поток, созданный драйвером, завершился и вернул управление на IК (21- уровне равном 015РАТСН_ЬЕУЕЬ или выше Параметры Описание 1 Адрес рабочей процедуры
2 3 4 Уровень 1КС21_ (должен быть 0) Параметры рабочей единицы Адрес рабочей единицы 0хЕ2 МАГ1иА1_1_У_1Г11Т1АТЕ0_СНА5Н Пользователь сознательно вызвал сбой из отладчика либо с клавиатуры Параметры Описание 1-4 Зарезервировано ОхЕЗ НЕЗОО КСЕ-П ОТ_ОУ\Ж Е О Программный поток пытается освободить ресурс, который ему не принадлежит Параметры 1 Описание Адрес объекта ресурса 2 3 4 Адрес объекта потока Адрес таблицы владельца (если существует) Зарезервировано 0хЕ4 УУОККЕК_1МУА1ЛО Запись рабочего процесса подмножества ЕхесиНуе была обнаружена в области памяти, которая не должна была содержать такую запись Параметры 1 Описание Индикатор положения кода 2 Адрес записи рабочего процесса 3 4 Начало блока памяти Конец блока памяти
ГРгеу|ои51 [Мех!], Приложение Б
Загрузка операционной системы При загрузке 32-разрядных версий операционной системы \Л/1Пс1охл/з ЫТ 5 (2000, ХР или Зегуег 2003) используются следующие файлы: СДЫТЮР — стадии подготовки к загрузке и загрузка СДВООТ.1Ы1 — стадия загрузки ядра СДВООТ5ЕСТ.ЭО5 — стадия загрузки ядра (опционально) СДЫТОЕТЕСТ.СОМ — стадия загрузки ядра %5у5^етгоо^%\5уз1ет32\1\1ТО5КР1\11_.ЕХЕ — стадия загрузки ядра (или ЫТКИЫЬРА.ЕХЕ) %5у5^етгоо^%\5уз1ет32\НА1_.О1_1_ — стадия загрузки ядра Раздел реестра НК1_М\5Х5ТЕМ — стадия инициализации ядра %5у5^етгоо^%\5уз1ет32\*.зуз — стадия инициализации ядра Здесь под %5уз(:етгооЁо/о подразумевается директория, где размещены основные файлы операционной системы, например, О:\\ЛЛпс1о\л/з — для У\Лпс1оуу5 ХР, установленной на логический диск О. Для системы \ЛЛпс1о\л/з 2000, например, установленной на логическом диске С, это будет директория С:\\Л/1ЫЫТ.
Подготовка к загрузке 1. Компьютер тестирует себя (стадия РО5Т, Ро\л/ег Оп 5е1ГТе51), оперативную память, физические устройства. Если В1О5 поддерживает спецификацию РпР, то происходит определение и настройка такого типа устройств. 2. В1О5 обнаруживает загрузочное устройство (жесткий диск, привод СО НОМ), загружает и запускает на выполнение основную загрузочную запись (МВИ). 3. МБР просматривает таблицу разделов (рагШюп 1аЫе), чтобы найти активный, загружает загрузочный вектор активного раздела в память и запускает его на выполнение. 4. Загружает и инициализирует файл ЫТЮК, который представляет собой загрузчик ОС.
Начальная стадия загрузки Загрузчик ЫТЮК переключает процессор из реального режима в 32-разрядный защищенный, что необходимо для получения возможности выполнить любую дополнительную функцию. Файловая мини-система, встроенная в ЫТЮК позволяет загружать \Л/1пс1о\л/5 1\1Т 5 с носителей ЕАТ, ЕАТ32 и 1МТЕ5.
Стадия загрузки Загрузчик ЫТЮК считывает файл В00Т.11М1, использует его для отображения экрана загрузчика, предоставляет пользователю возможность выбора ОС, выбирает профиль оборудования и загружает ядро, но не инициализирует его. Файл ВООТ.11М1 содержит 2 раздела: [Воо11_оас1ег] и [ОрегаИпд 5у51ет]
Распознавание оборудования 1МТОЕТЕСТ.СОМ запускается после надписи "Выберите операционную систему для загрузки", составляет список установленного на данный момент оборудования, и возвращает этот список в МТЮК для последующего включения его в раздел системного реестра НК1_М\НАКОУУАкЕ. МТОЕТЕСТ.СОМ выполняет определение характеристик устройств: Тип шины. Последовательные порты. Математический сопроцессор. Гибкие диски. Клавиатура и указывающие устройства. Параллельные порты. Адаптеры 5С51. Видео адаптеры. Следует отметить, что устройства, которые подключаются к шинам и должны быть обнаружены шинными драйверами (например, внешние устройства на шинах 115В), здесь не обнаруживаются — по той простой причине, что шинные драйверы, которые должны выполнить эту работу, на данный момент еще не загружены.
Выбор конфигурации После того как МТЮК начинает загрузку и получит информацию об оборудовании, будет отображен экран с диалогом "Выбор профиля оборудования/Восстановление системы" (Нагс1\л/аге ргоЛ1е/СопЛдига1:юп гесоуегу). В случае если профиль оборудования является единственным, то диалога представлено не будет.
Загрузка ядра На этапе загрузки ядра ЫТЮР выполняет следующие действия: Загружает код ядра из файла МТО5КРМ1..ЕХЕ (МТКР1М1.РА.ЕХЕ при наличии опции / РАЕ в файле ЬооМп!), но не инициализирует его. Загружает код слоя аппаратных абстракций из файла НА1_.О1_1_. Загружает раздел НКЬМ\5У5ТЕМ из %8у81етгоо1%\5у81:ет32\СопГ|д\5у51ет. Выбирает набор параметров для конфигурации (список драйверов, устройств, устройств и служб, которые необходимо запустить). Загружает драйверы (обычно это низкоуровневые драйвера, как, например, драйвера дисков) со значением параметра 51аг1 равным 0x0. Значение параметра Ыз!: в НКЬМ\5У5ТЕМ\Сиггеп1Соп1ог15е1\Соп1го1\5егу1сеСгоирОгс1ег определяет порядок загрузки их загрузчиком ЫТЮИ. Драйверы, регулирующие свою загрузку таким способом, должны иметь соответствующие значения параметра Огоир в своих подразделах раздела Системного Реестра НК1_М\5у81ет\Сиггеп1:Соп1го15е1\5еплсе8.
Инициализация ядра По завершении загрузки, ядро инициализируется и ему передается управление от загрузчика 1\1Т1_ОН. 1. Создается раздел НК1_М\Нагс№аге по результатам распознавания аппаратуры, куда заносится информация о системной плате, устройствах и прерываниях. 2. Создается набор параметров С1опе путем копирования управляющих параметров; информация о которых содержится в параметре Сиггеп! в разделе НКЬМ\5у81ет\5е1ес1. Набор С1опе никогда не модифицируется. 3. Загружаются драйверы, указанные в разделе системного реестра НК1_М\5у81ет\Сиггеп1Соп1го15е1\5еплсе8, в параметрах которых присутствует значение 51аг1: равное 0x01 , порядок загрузки которых так же, как и было указано выше, определяется в параметре Сгоир. Драйверы инициализируются сразу же после их загрузки. Значения параметра ЕггогСоп1го1 в описании драйвера (то есть в его параметре, указанном в Системном Реестре) определяет реакцию системы в том случае, если при загрузке и инициализации данного драйвера произошла ошибка. Подробнее возможные значения параметра ЕггогСоп1го1 и соответствующие способы реакции операционной системы представлены в Приложении В. 4. Запускаются сервисы (например, Служба Журнала Событий) и драйверы.
Вывод на экран информации о процессе загрузки Указав параметр /зоз в соответствующей строке файла Ьоо1.1П1, например, ши!Н ( 0 ) сПз к ( 0 ) гсНзк ( 0 ) рагк±к±оп (2 ) \й\Нпс1омз = " Комментарий для поль зова можно обеспечить вывод на экран информацию о загружаемых программных модулях операционной системы. Автору удалось наблюдать следующие записи: ти!Н ( 0 ) сПзк ( 0) гсНзк (0) рагкННоп (1) \1АНпс1о1л78\8у8кет32 \пкозкгп1. ехе ти!Н (0) сПз к (0) гсНзк (0) рагкННоп (1) \1лНпс1о™з\8узкет32\]та1. с111 ти!Н ( 0 ) сПз к ( 0 ) гсНзк ( 0 ) рагЪНИоп (1) \1лНпс1омз \8узкет32 \КОСОМ. БЬЬ тиЮт ( 0 ) сПз к ( 0 ) гсНзк ( 0 ) рагкННоп (1) \й\Нпс1омз \8узкет32 \ВООТУЮ . БЬЬ тиЮт (0) сНзк (0) гсНзк ( 0) рагкННоп (1) \1лНпс1о™з\8уз0ет32\соп:(Нд\зуз0ет тиЮт (0) сНзк (0) гсНзк ( 0) рагкННоп (1) \1лНпс1о^з\8уз'Ьет32\с_12 51. п1з тиЮт ( 0 ) сНзк ( 0) гсНзк ( 0) рагкННоп (1) \й\Нпс1омз\8узкет32 \с_8 66 . п1з тиЮт ( 0 ) сНзк ( 0) гсНзк ( 0) рагкННоп (1) \й\Нпс1омз\8узкет32 \1_1пО1. п1з тиЮт ( 0 ) сНзк ( 0) гсНзк ( 0) рагкННоп (1) \й\Нпс1омз\ЕОКТ8^да8 66.Роп тиЮт ( 0 ) сНзк ( 0) гсНзк ( 0) рагкННоп (1) \1лНпс1омз\АррРаЕс]т\с1^та±п. зс!Ь тиЮт ( 0 ) сНзк ( 0 ) гсНзк ( 0 ) рагкННоп (1) \й\Нпс1омз\8узкет32 \БК1УЕК8\АСР1.8У8 тиЮт ( 0 ) сНзк ( 0 ) гсНзк ( 0 ) рагкННоп (1) \Жпс1омз\8узкет32 \ОН1УЕН8\ЭДМ1Ь1В . 8 тиЮт (0) сНзк (0) гсНзк ( 0) рагкННоп (1) \йНпс1о™з\8уз0ет32\ВВ1УЕВ8\рс± . зуз тиЮт (0) сНзк (0) гсНзк ( 0) рагкННоп (1) \йНпс1о™з\8уз0ет32\ВВ1УЕВ8\±зарпр. з тиЮт ( 0 ) сНзк ( 0 ) гсНзк ( 0 ) рагкННоп (1) \й\Нпс1омз\8узкет32 \ВВ1УЕВ8^±а±с1е . з тиЮт ( 0 ) сНзк ( 0 ) гсНзк ( 0 ) рагкННоп (1) \й\Нпс1омз\8узкет32 \ВВ1УЕВ8\РСИВЕХ . тиЮт (0) сНзк (0) гсНзк (0) рагкННоп (1) \йНпс1о™з\8уз0ет32\ВВ1УЕВ8\Моип0Мдг тиЮт (0) сНзк (0) гсНзк ( 0) рагкННоп (1) \йНпс1о™з\8уз0ет32\ВВ1УЕВ8\Е0сНзк. з тиЮт ( 0 ) сНзк ( 0 ) гсНзк ( 0 ) рагкННоп (1) \Жпс1омз\8узкет32 \ВВ1УЕВ8\с1т1оас1. з тиЮт ( 0 ) сНзк ( 0 ) гсНзк ( 0 ) рагкННоп (1) \Жпс1омз\8узкет32 \ВВ1УЕВ8\с1т±о . зуз тиЮт (0) сНзк (0) гсНзк ( 0) рагкННоп (1) \1лНпс1о™з\8уз0ет32\ВВ1УЕВ8\Раг0Мдг. тиЮт (0) сНзк (0) гсНзк ( 0) рагкННоп (1) \йНпс1о™з\8уз0ет32\ВВ1УЕВ8\Уо18пар. тиЮт ( 0 ) сНзк ( 0 ) гсНзк ( 0 ) рагкННоп (1) \й\Нпс1омз\8узкет32 \ВВ1УЕВ8\акар± . зу тиЮт ( 0 ) сНзк ( 0 ) гсНзк ( 0 ) рагкННоп (1) \й\Нпс1омз\8узкет32 \ВВ1УЕВ8\сНзк. зуз тиЮт (0) сНзк (0) гсНзк (0) рагкННоп (1) \1лНпс1о™з\8уз0ет32\ВВ1УЕВ8\СЬА88РКР тиЮт (0) сНзк (0) гсНзк ( 0) рагкННоп (1) \йНпс1о™з\8уз0ет32\ВВ1УЕВ8\зг. зуз тиЮт ( 0 ) сНзк ( 0 ) гсНзк ( 0 ) рагкННоп (1) \й\Нпс1омз\8узкет32 ХВВТУЕВЗХЕазЕРак . тиЮт ( 0 ) сНзк ( 0 ) гсНзк ( 0 ) рагкННоп (1) \й\Нпс1омз\8узкет32 \ВВ1УЕВ8\К8есВВ . з тиЮт (0) сНзк (0) гсНзк ( 0) рагкННоп (1) \йНпс1о™з\8уз0ет32\ВВ1УЕВ8\КВ18 . зуз тиЮт (0) сНзк (0) гсНзк ( 0) рагкННоп (1) \1лНпс1о™з\8уз0ет32\ВВ1УЕВ8^±аадр. з тиЮт ( 0 ) сНзк ( 0 ) гсНзк ( 0 ) рагОЮтоп (1) \й\Нпс1омз\8узкет32 \ВВ1УЕВ8\птР±1Еег тиЮт ( 0 ) сНзк ( 0 ) гсНзк ( 0 ) рагОЮтоп (1) \й\Нпс1омз\8узкет32 \ВВ1УЕВ8\8±ггпНс1. з тиЮт (0) сНзк (0) гсНзк ( 0) рагкННоп (1) \йНпс!о™з\8уз0ет32\ВВ1УЕВ8\Мир. зуз В случаях, когда сбой системы происходит на одном из этапов загрузки, такая диагностика может быть весьма полезна. В том случае, если в строке описания параметров загрузки после опции /зоз ввести /РАЕ (поддержка расширенной физической адресации, РАЕ, см. главу 4), то первая строка примет вид: тиШ(0)сИ5к(0)гсИ5к(0)рагИИоп( 1) \ \Мтск}шз\5узСет32\п1:кгп1ра. ехе
ф Убедиться программным способом в том, поддерживает ли операционная система в настоящий момент работу с РАЕ (то есть с каким ключом она загружена), несложно. Достаточно в текст драйвера включить следующий фрагмент: 1Е(*Мтб4ЬИРйу51'са1Ас1с1ге55==ТРС1Е) { #!ГОВЗ ЕЬдРп'пСС'ЗузСет зиррогСз 10 орегаЦ'опз оуег 43В"); #епс/1р } е/зе { #!ГОВЗ ЕЬдРп'пСС'ЗузСет с/оезп'!: зиррогС 10 /оега&опз оуег 43В"); #епс/1р } Переменная Мт64ВНРНу81са1Ас1с1ге88 объявляется в файлах уус1т.Ь и п1с!с1к.11. Другое подтверждение работы операционной системы с поддержкой РАЕ (РЬу51са1 АсМгезз Ех^епзюп) можно обнаружить в Системном Реестре: в таком случае параметр РЬу51са1Ас1с1ге55Ех1еп510п в разделе НК1_М\5у81ет\Сиггеп1Соп1го15е1\Соп1го1\5е88Юп Мападег\Метогу Мападетеп1 примет значение 1 (в отличие от значения 0 в отсутствие поддержки РАЕ).
ГРгеу1ои8~| Приложение В
Некоторые стандартные параметры описания драйвера в Системном Реестре В Системном Реестре в разделе Н К 1_М \§у81ет \Сиггеп1Соп1го1§е1\§еплсе8 сосредоточены описания загружаемых драйверов, представленные набором параметров и/или вложенных подразделов. После загрузки и запуска драйвера Ехатр1е.5уз, рассмотренного в главе 3, при помощи программы топГСог, ранее описанной во 2 главе, можно наблюдать следующие изменения в Системном Реестре (рисунок В- 1). Программа топког при загрузке драйвера создает в Системном Реестре соответствующий подраздел в НКЬМ\§у81ет\Сиггеп1Соп(го1§е1\§егу|се8 с новым именем Ехатр1е, в который помещает параметры □|5р1ауЫате, ЕггогСоп1то1, ТтадеРа^Ь, 5^аг1 и Туре. Все перечисленные параметры достаточно типичны для описания драйверов в Системном Реестре, поэтому рассмотрим их подробнее.
Параметр 01вр1ау№те Значение этого параметра описывает текст, используемый в служебных программах операционной системы, в частности, в программах Панели Управления. В случае, если данный параметр не указан, используется имя драйвера.
Параметр ЕггогСоп1то1 (тип Р.Е6_0\Л/СЖ0) предписывает операционной системе способ поведения в той ситуации, когда при загрузке и инициализации драйвера (отработке процедуры ОпуегЕп1ту) произошла ошибка. Таблица В-1. Значения параметра ЕггогСоп1го1 Значение 0x0 Символьное имя (см. \л/с1т.И, пШсИсЬ, \л/1пп1.Ь) Поведение операционной системы В процессе загрузки ошибки игнорируются, загрузка продолжается без уведомления об ошибках в данном драйвере 5Ек71СЕ_Екк0к_1С1\10кЕ 0x1 5Ек71СЕ_ЕккСЖ_1\1(ЖМА1_ В процессе загрузки ошибки игнорируются, но выводятся 0x2 5Ек71СЕ_ЕккСЖ_5Е7ЕкЕ сообщения о них, при этом загрузка продолжается Порядок загрузки нарушается и начинается заново с использованием набора параметров 1_аз1:Кпо\л/п(Зоос1, а если он уже используется, то ошибка игнорируется Порядок загрузки нарушается и начинается заново с 0x3 использованием набора 5ЕК\/1СЕ_ЕК.кСЖ_СК.ГПСА1_ параметров Ьаз1:Кпо\л/пСоос1, а если он уже используется, то загрузка прерывается и выводится сообщение об ошибке
Параметр 1тадеРа111 Значение этого параметра описывает полный путь к файлу, содержащему исполняемый код драйвера. В данном примере, загрузка драйвера была выполнена программой топГСог из файла К:\Ех\Ехатр1е.5уз, что и было отражено в значении параметра 1тадеРа1:И в данном подразделе Реестра. В случае, если данный параметр не указан, то используется значение %5уз1:ет%\0п7ег5\с1п7егпате.5у5.
Значение этого параметра описывает стадию загрузки операционной системы, когда следует загружать данный драйвер. Таблица В-2. Значения параметра 21аг1 Значение 0x0 Символьное имя (см. \л/с1т.И, П1с1с1к.11, \ллпп1:.1п) 5ЕК71СЕ_ВООТ_5ТАКТ Время загрузки драйвера Драйвер запускается загрузчиком мтиж 0x1 5ЕК71СЕ_5У5ТЕМ_5ТАКТ Драйвер запускается на стадии загрузки компонентов ядра ОС Драйвер будет запущен средствами 0x2 5 Е К71СЕ_А11ТО_5ТАКТ 5СМ после загрузки компонентов ядра ОС Драйвер запущен "вручную", то 0x3 5ЕК71СЕ_0ЕМАЫ0_5ТАКТ есть по запросу пользовательского приложения при помощи средств 5СМ (после загрузки ОС) 0x4 5ЕК71СЕ_015АВ1_Е0 Драйвер не может быть запущен
Параметр Туре Значение этого параметра описывает тип драйвера, некоторые из значений приведены в таблице В-3. Таблица В-3. Значения параметра Туре Значение Символьное имя _ / _||» 4-»1—11 и д. Тип драйвера (см. \л/с1т.И, П1с1с1к.11, \л/тп1.И) 0x1 5Ек\/1СЕ_КЕкМЕ1__Ок1\/Ек Драйвер режима ядра 0x2 5Ек\/1СЕ_Е11_Е_5У5ТЕМ_0П1\/ЕП. Драйвер файловой системы 0x4 5Ек\/1СЕ_АОАРТЕР. Драйвер адаптера Помимо описанных выше часто используемых параметров, используемых для описания драйверов в Системном Реестре, могут встречаться параметры ОерепсЮпбгоир, ОерепсЮпЗеплсе, Тад, бгоир, определяющие порядок загрузки, и параметр Рагате^егз, официально предназначенный для хранения частных настроечных параметров.
Параметры подраздела \Епит Подраздел НК1_М\§у51ет\Сиггеп1Соп1го1§е1\§еплсе5\с1пуегпа те\Епит (здесь сНуегпате — имя драйвера) в Системном Реестре присутствует постоянно для драйверов, установленных при помощи Мастера Установки Оборудования. Для драйверов, загружаемых при помощи сервиса 5СМ, он появляется только после их удачного старта. В этом подразделе присутствуют параметры Соип!: (число обслуживаемых устройств), ЫехНпз^апсе, параметры 0, 1 и т.п. Рис. В-3 Подраздел \Епит после удачного старта драйвера Ехатр1е.5уз Параметры 0, 1 и т.п. появляются только для удачно стартовавших драйверов, а их значения указывают на подраздел в НКЬМ\§у51ет\Сиггеп1Соп1го18е1\Епит (где отражаются все когда-либо удачно стартовавшие драйвера). Если пойти по указанной в параметре 0 (или 1 и т.п.) ссылке, то можно увидеть в соответствующем подразделе НКЬМ\§у51ет\Сиггеп1Соп(го1§е1\Епит\...\Соп1го1 параметр АсйуеЗеплсе, дающий обратную ссылку НК1_М\§у51ет\Сиггеп1Соп1го1§е1\§еплсе5\с1пуегпа те. Если одна из этих ссылок в Реестре не существует (прямая или обратная), то это признак того, что драйвер не запустился.
ГРгеу1ои8~| ГИехМ
Программа ОеЬидРг1п1 Весьма интересная программа ОеЬидРпп!: (полное название — ЭеЬид Рпп1: Мопйог), была разработана фирмой РНО Сотри1:ег СопзиНап!: Ис1., с программными продуктами которой подробнее можно ознакомиться на интернет сайте р!пс1сс.сот. Совместно с программой пользовательского режима □еЬидРпп!: работает драйвер режима ядра ОеЬидРг^.зуз (разработанный в РНО СС Иск), который должен быть установлен заранее. Доступ к ОеЬидРг^.зуз клиентский драйвер должен получить средствами специального программного кода (использующего, в частности, редкий для драйверного кода вызов 2шСгеа1еН1е). Разработчик присоединяет к отладочной версии своего драйвера (в данной ситуации — клиентского по отношению к □еЬидРг^.зуз) этот специальный программный код, предоставляемый в форме исходного текста на языке С, обеспечивая таким образом связь с ОеЬидРг^.зуз и вывод сообщений в его внутренние буферные области. Работа собственно программы ОеЬид Рпп!: Мопког состоит в том, чтобы выводить в своем рабочем окне сообщения отладочного характера, переданные драйверу □еЬидРг^.зуз клиентским драйвером. В том случае, если при старте операционной системы драйвер-получатель сообщений (ОеЬидРг^.зуз) загружается раньше отлаживаемого драйвера, все сообщения из отлаживаемого драйвера, даже переданные в процедурах инициализации, будут сохранены драйвером-получателем. Впоследствии, все сообщения можно просмотреть при помощи ЭеЬид Рпп1: МопГСог, имеющей удобный графический интерфейс и
позволяющей сохранять сообщения в текстовом файле для последующего анализа. Для разработчика драйвера использование функций передачи выглядят почти что как использование функций рппГГ или зрпп^Г. Однако следует изначально использовать приемы условной компиляции, чтобы использование этого отладочного трюка не навредило в окончательной версии драйвера (где такие операции являются непозволительной роскошью и при слишком обильной диагностике — могут приводить к сбою системы), а переход от отладочной (сЬескес!) версии к релизной (Ггее) и наоборот отнимал бы минимум усилий у разработчика. При правильном применении условной компиляции разработчику достаточно выбрать среду компиляции СОК (сГ)ескес1/Ггее), что уже определяет полностью версию собранного проекта (см. п. 2.1.2). Драйвер ОеЬидРгГ.зуз, необходимый для работы программы ОеЬид РппГ МопНог, устанавливается при помощи мастера установки оборудования и 1пГ файла и запускается в процессе загрузки операционной системы.
Программа Оеу\Леш от Уолтера Оней Программа Оеу\/1е\л/ (полное название Оеуюе ОЬ]'ес± Х/1е\л/ег), созданная Уолтером Оней, автором замечательной книги "Ргодгатгтппд ТИе М1Сго5оЛ: \Л/1Пс1о\л/5 Опуег Мойе!", вышедшей уже в двух изданиях, является полным аналогом программы ХЛЛпОЬ^, правда, работающим только под У\Лпс1о775 1\1Т.
Программа Роо1Тадот О8Н 1пс Программа Роо1Тад МопГСог (Роо1Моп — консольная версия), поставляемая в составе пакета ООК, динамически (с интервалом в несколько секунд) отображает на экране состояние страничного и нестраничного пулов памяти режима ядра. В рабочем окне программы (см. рисунок 2.18) отражаются события выделения и освобождения областей страничной и нестраничной памяти с использованием дескрипторов (при помощи вызова функции режима ядра ЕхА11оса1еРоо1УУШ1Тад), что зачастую может облегчить поиск ошибок, связанных с некорректным использованием памяти. Выделение памяти с применением дескрипторов является необычным приемом для программистов приложений пользовательского режима, но при программировании модулей режима ядра это может оказаться лучшим решением, нежели получение области памяти просто по ее адресу, поскольку появляется возможность "персонального" мониторинга выделенных блоков памяти. Рис.2.18 Программа РооГГад
Программа просмотра файлов Простая программа просмотра файлов, вызываемая в старом добром 1\1ог1оп Соттапс1ег (впрочем как и в современном То1а1Соттапс1ег) по нажатию клавиши ЕЗ, также может быть грозным орудием в руках опытного программиста. На рисунке 2.19 видно (даже без использования программы Перепев, упомянутой ранее), какие системные вызовы использует данный драйвер. В некоторых случаях эта незначительная информация может дать существенные сведения о принципах работы анализируемых примеров. файла С1уе1о.5у5
Программа РЕ Ехр1огег Удобным в использовании средством изучения бинарных загружаемых модулей ехе, с!11, ухс1, зуз является программа РЕ Ехр1огег фирмы Неауеп1оо1з 5оГЬл/аге (рис. 2.21). 30-дневную испытательную версию можно загрузить с интернет-сайта фирмы по адресу Неауеп*оо15.сот. Рис. 2.21 Окно просмотра заголовка файла Оерепс15.ехе в окне программы РЕ Ехр1огег Возможности этой программы обширны и не ограничиваются только лишь просмотром заголовков исполняемых модулей с общей информацией, просмотром секций и списков имен импортируемых и экспортируемых функций (это позволяют делать многие программы, в частности упомянутая программа Оерепс15). Наличие встроенного дизассемблера позволяет определить в исследуемых модулях места вызова интересующих системных функций, в том числе — вызовов режима ядра. При анализе импортируемых функций (импортируемых из системных динамических библиотек) программа РЕ Ехр1огег показывает справочную информацию о протоколе вызова многих из них. По точности и подробности дизассемблирования программа РЕ Ехр1огег приближается к дизассемблеру ЮА Рго. Своеобразной визитной карточкой РЕ Ехр1огег является способность к просмотру и редактированию ресурсов,
скомпилированных в данном бинарном модуле. Заметим, что в разделе ресурсов зачастую находится самая достоверная информация об авторе и дате создания драйвера (возможно даже — его предназначении), которая другим образом может быть получена, как правило, лишь системными средствами после установки драйвера, что далеко не всегда возможно и целесообразно.
Дизассемблер ША Интерактивный дизассемблер ЮА фирмы Оа1акезсие позволяет получать крупинки знания о взаимодействии драйверов с операционной системой путем изучения хорошо зарекомендовавших себя драйверов, исходный текст которых зачастую недоступен. Для исследования драйверов МТ, которые собираются в бинарный модуль (файл) с расширением зуз, наиболее подходят версии ЮА после 4.15. Последнюю испытательную версию можно загрузить (после регистрационного запроса) с Интернет-сайта фирмы с!а1аге5сие.сот. На момент подготовки книги к печати это была версия 4.6. Помимо того, что полная версия дизассемблера ЮА позволяет работать с исполняемым кодом не только для процессоров 1п1е1, весьма полезной и интересной особенностью этой программы является ее умение визуализировать структуру вызовов в графических диаграммах, как это показано на рисунке 2.22. Рис. 2.22 Диаграмма вызовов в одном из окон дизассемблера ЮА Безусловно, рассмотреть возможности дизассемблера ЮА Рго весьма сложно даже в рамках отдельной главы. Поэтому, учитывая мощь и полезность этой программы для самообразования разработчика драйверов, следует порекомендовать книгу Криса Касперских "Образ мышления — дизассемблер ЮА", изд. Солон-Р, 2001, 15ВЫ 5-93455-093-4, 480 страниц (правда, читателю
следует быть готовым к тому, что в упомянутой книге рассмотрена далеко не последняя версия программы).
ГРгеу|ои51 [Мех!],
Процедура ОпуегЕп^гу и предварительные объявления Все приведенные ниже отрывки кода следует последовательно поместить в один файл (обычно, файл, содержащий описание ОпуегЕпйу, разработчики называют ТпП.с). Редактирование, разумеется, удобнее всего выполнять в каком-нибудь редакторе интегрированной среды. Рекомендуется использовать редактор из среды \/1зиа1 51ис1ю, поскольку в нем производится динамический контроль синтаксиса и типов данных языка С. В главе 2 приводится содержимое файлов настройки проекта для драйвера Ехатр1е, соблюдение которых позволит воспользоваться динамическими подсказками среды во время редактирования и позволит также выполнять контрольную компиляцию кода. Последнее весьма удобно, поскольку в интегрированной среде легко перейти к месту возникновения ошибки по диагностическому сообщению. Окончательную компиляцию драйвера (как чистовую, так и отладочную) категорически рекомендуется выполнять утилитой ВиИй из среды ЭЭК, поскольку иные способы компиляции могут быть источником необъяснимых странностей в поведении драйвера. ///////////////////////////////////////////////////////////////////// // хпх'Ь.срр: Инициализация драйвера // Замечание. Рабочая версия данного драйвера должна быть // скомпилирована как не-ИБМ версия. В противном случае - драйвер // не сможет корректно загружаться и выгружаться с использованием // программы шопФРог (пакет Ыитеда Вгтчег ЗбисНо) и сервисов ЗСМ // Менеджера. ///////////////////////////////////////////////////////////////////// // ВгтчегЕпбгу Главная точка входа в драйвер // ПпРоайР.ои'Ыпе Процедура выгрузки драйвера // ОечРсеСопДгоХРоиДРпе Обработчик ОечРсеРоСопСго! 1КР пакетов ///////////////////////////////////////////////////////////////////// #1пс1ис1е "ВгФчег.Н" // Предварительные объявления функций: МТ5ТАТП5 БечЕсеСопСгоРРоиСРпе( РИ РЭЕ7РСЕ_ОВПЕСТ Гйо, РИ РРКР Ргр ); 701В ПпРоайР.ои'Ыпе (1Ы РОВРУЕВ._ОВПЕСТ ЕгтчегОЬ]еск); ЫТЗТАТИЗ КеайИг1ке_РВРЬапй1ег( 1Ы РОЕУРСЕ_ОВПЕСТ ^йо, 1Ы РРВР Ргр ); МТ5ТАТП5 Сгеабе_Е11е_РКРргосезз1пд(РИ РВЕ7РСЕ_0ВЗЕСТ Йо, РИ РРКР Ргр МТ5ТАТП5 С1озе_Напй1еРКРргосезз1пд(РИ РВЕ7РСЕ_0ВЗЕСТ Йо, РИ РРКР Ргр // Хотя и нехорошо делать глобальные переменные в драйвере... К5Р1И_Ь0СК МуЗртпЬоск; #ргадша сойе_зед("РЕНТ") // начало секции РИРТ ////////////7//////////////////////////////////////////////////////// // (Файл РпФ'Ь.срр) // ОгРчегЕпДгу - инициализация драйвера и необходимых объектов // Аргументы: указатель на объект драйвера // раздел реестра (йгФчег зегчйсе кеу) в ИЫРСОВЕ // Возвращает: ЗТАТИЗ_Ххх
ехЕегп "С" ПТ8ТАТП8 ВгТОегЕпЕгу ( та РБК1УЕК_0ВЙЕСТ БгйчегОЬ] есЕ, та РОШСОБЕ_8ТКтаС РедгзЕгуРаЕЕ ) { ПТ8ТАТП8 зЕаЕиз = 8ТАТП8_8ПССЕ88; РБЕУ1СЕ_ОВ ЙЕСТ Ебо ; □ШСОВЕ_8ТВтаС бечПате ; #1Е ВВС ВЬдРггпЕ(”=Ехатр1е= 1п ВгТОегЕпЕгу. " ) ; ВЬдРггпЕ(”=Ехатр1е= ВедйзЕгуРаЕЬ = %мз г ВедйзЕгуРаЕЬ->ВиЕЕе #епЫЕ // Экспорт точек входа в драйвер (АббВечйсе объявлять не буде // ВгйчегОЬ^есЕ->Вг±чегЕхЕепз±оп->АббВечйсе= ОигАббВечйсеВоиЕ ВгйчегОЬ^ есЕ->ВгйчегПп1оаб = Пп1оабВоиЕйпе; ВгйчегОЬ^ есЕ->Ма^огЕипсЕйоп[1ВР_МЙ_СВЕАТЕ]= СгеаЕе_ЕЫе_1ВРрг ВгйчегОЬ^ есЕ->Ма^огЕипсЕйоп[1ВР_МЙ_СЬО8Е] = С1озе_Напб1е1ВРрг ВгйчегОЬ^ есЕ->Ма^ огЕипсЫоп [ 1ВР_МЙ_ВЕАВ] = Веаб1л1г±Ее_1ВРЬапб ВгйчегОЬ^ есЕ->Ма^ огЕипсЫоп [ 1ВР_МС_ТО1ТЕ] = ВеабТО±Ее_1ВРЬапб ВгйчегОЬ^ есЕ->Ма^ огЕипсЫоп [ 1ВР_МС_ВЕУТОЕ_СОПТВОЬ] = ВечйсеСоп //======================================================== // Действия по созданию символьной ссылки // (их нужно было бы делать в ОигАббВечйсеВоиЕйпег но у нас // очень простой драйвер и эта процедура отсутствует): ВЕИпйЕВпйсобеВЕгйпд ( &бечПате, Ь"\\Вечйсе\\ЕХАМРЬЕ" ) ; // Создаем наш ЕипсЫопа! Вечйсе ОЬ^есЕ (ЕВО) и получаем // указатель на созданный ЕВО в нашей переменной Ебо. // (В ТОМ драйвере эту работу также следовало бы выполнять // в процедуре ОигАббВечйсеВоиЕйпе. ) При создании ЕВО // будет выделено место и под структуру расширения устройства // ЕХАМРЬЕ_ВЕУ1СЕ_ЕХТЕП8ТОП (для этого мы передаем в вызов // ее размер, вычисляемый оператором зЫеоЕ) : зШиз = 1оСгеаЕеВечйсе (ВгйчегОЬ^есЕ, зйгеоЕ(ЕХАМРЬЕ_ВЕУ1СЕ_ЕХТЕП8ТОЙ), &бечПате, // может быть и ТОЬЬ Е1ЬЕ_ВЕУ1СЕ_ППКПОта, О л ЕАЬ8Е, // без эксклюзивного доступа &Ебо); Ы ( !ИТ_8ПССЕ88 (зШиз) ) геЕигп зЕаЕиз; // Получаем указатель на область, предназначенную под // структуру расширение устройства РЕХАМРЬЕ_ВЕУ1СЕ_ЕХТЕП810И бх = (РЕХАМРЬЕ_ВЕУ1СЕ_ЕХТЕП8ТОП)Ебо бх->Ебо = Ебо; // Сохраняем обратный указатель
// Применяя прием условной компиляции, вводим функцию ЬЬдРгйп // сообщения которой мы сможем увидеть в окне БеЬидУйем, если // выполним сборку нашего драйвера как сЬескеб (отладочную) // версию: #йЕ ЬВС ЕЬдРгйпЕ(”=Ехатр1е= ЕБО %Х, БечЕхЕ=%Х.”,Ебо,бх); #епбйЕ //======================================= // Действия по созданию символьной ссылки // (их нужно было бы делать в ОигАббВечйсеВоиЕйпе, но у нас // очень простой драйвер): 6ШСОРЕ_8ТН1Е[(3 зутЬйпкИате; // Сформировать символьное имя: // #беЕйпе 8ХМ_Ь1ИК_ИАМЕ Ь"\\??\\Ехатр1е" // Такого типа символьные ссылки АА проходят только в ИТ. // (То есть, если перенести бинарный файл драйвера в // ЭДйпбомз 98, то пользовательские приложения заведомо // не смогут открыть файл по такой символьной ссылке.) // Для того, чтобы ссылка работала в и ЭДйпбомз 98 и в ИТ, // необходимо поступать следующим образом: #беЕйпе 8ХМ_Ь1Е[К_ИАМЕ Ь" \ \ВозБечйсез \ \Ехатр1е " ВЕИпйЕИпйсобеВЕгйпд ( &зутЬйпкИате, 8ХМ_Ь1Е[К_ИАМЕ ) ; бх->изЕг8утЬйпкИате = зутЬйпкИате; // Создаем символьную ссылку зЕаЕиз = 1оСгеаЕе8утЬо1йсЬйпк( &зутЬйпкИате, &бечИате ); йЕ (!ИТ_8бССЕ88(зЕаЕиз) ) { // при неудаче V удалить Бечйсе ОЬ^есЕ и вернуть управление 1оРе1еЕеБечйсе( Ебо ); геЕигп зЕаЕиз; } // Теперь можно вызывать СгеаЕеЕййе("\\\\.\\Ехатр1е",...); // в пользовательских приложениях // Объект спин-блокировки, который будем использовать для // разнесения во времени выполнения кода обработчика // ЮСТЬ запросов. Инициализируем его: Ке1пйЕйа1йге8рйпЬоск(&Му8рйпЬоск) ; // Снова используем условную компиляцию, чтобы выделить код, // компилируемый в отладочной версии и не компилируемый в // версии Егее (релизной): #йЕ ВВС БЬдРгйпЕ(”=Ехатр1е= ЬгйчегЕпЕгу зиссеззЕиййу сотрйеЕеб."); #епбйЕ геЕигп зЕаЕиз; } #ргадта собе_зед() // епб ШТ зесЕйоп
Функция Сотр1е1е1гр Вспомогательная функция Сотр1е1:е1гр реализует действия по завершению обработки 1НР пакета с кодом завершения зЬаТиз. Данная функция предназначена для внутренних нужд драйвера и нигде не регистрируется. Параметр 1пГо, если он не равен нулю, чаще всего содержит число байт, переданных клиенту (полученных от клиента) драйвера. // // (Файл хпхб.срр) // Сошр1ебе!гр: Устанавливает ТоЗбабиз и завершает обработку ТКР // Первый аргумент - указатель на объект нашего ЕРО. // ЫТЗТАТИЗ Сошр1ебе!гр( Р1КР 1гр, ЫТЗТАТПЗ збабиз, ПЬОЫС 1пбо) { 1гр->1о5бабиз.Збабиз = збабиз; 1гр->1оЗбабиз.1пбогтаб1оп = ъпбо; IоСотр1ебеКе^иезб(1гр,Ю_Ы0_1НСКЕМЕЫТ); гебигп збабиз; }
Рабочая процедура обработки запросов геас1/шп1е Процедура Неас1УУп1:е_1кР11апс11ег предназначена для обработки запросов Диспетчера ввода/вывода, которые он формирует в виде 1КР пакетов с кодами 1ИР_МЗ_ИЕАО/1РР_МЗ_УУР1ТЕ по результатам обращения к драйверу из пользовательских приложений с вызовами геас!/'л/п1е или из кода режима ядра с вызовами 2\л/йеас1Р|1е или 2\л/УУп1еРПе. В данном примере наша функция обработки запросов чтения/записи ничего полезного не делает, и ее регистрация выполнена только для демонстрации, как это могло бы быть в более "развитом" драйвере. ф Описание прототипов рабочих процедура драйвера (параметров их вызова) можно найти в документации ООК (2000, ХР, 2003), если в режиме указателя задать ключевые слова О15ра1сй..., например, О/зраЁсбРеас!. Если ваша программа просмотра файлов справки не поддерживает переходов между разными .сйт файлами (представляющими полную документацию по ООК), то можно сразу обратиться к файлу ктагс11.с11т (который, собственно, и содержит информацию по рабочим процедурам). Там же можно узнать, на каком уровне происходит вызов конкретной функции. // // (Файл гпгб.срр) // Кеас1Иг1Ре_1КРк1апс11ег: Берет на себя обработку запросов // чтения/записи и завершает обработку 1КР вызовом Сотр1еРе1гр // с числом переданных/полученных байт (ВубезТхб) равным нулю. // Аргументы: // Указатель на объект нашего ЕБО // Указатель на структуру 1НР, поступившего от Диспетчера ввода/вывод БТЗТАТБЗ Кеас1Иг1Ре_1КР?1апс11ег ( 1Б РЭЕУ1СЕ_ОВДЕСТ Гс1о, 1Б Р1КР 1гр ) { БЪОЫС ВуРезТхД = 0; ЫТЗТАТДЗ збабиз = ЗТАТДЗ_ЗДССЕЗЗ; //Завершение с кодом збабиз // Задаем печать отладочных сообщений V если сборка отладочна ВВС □ЬдРттлб ( "-Ехатр1е- дп Веас1Иг1'Ье_1ВР1дапс11ег . " ) ; #епс11Д гебигп Сотр1ебе1гр (1гр, збабиз, ВуСезТхс!) ; }
Рабочая процедура обработки запросов открытия драйвера Процедура Сгеа1:е_Н1е_1кРргосе551пд предназначена для обработки запросов Диспетчера ввода/вывода, которые он формирует в виде 1КР пакетов с кодами 1ИР_МЗ_СИЕАТЕ по результатам обращения к драйверу из пользовательских приложений с вызовами СгеаСеРЛе или из кода режима ядра с вызовами 2\л/Сгеа1еРЛе. В нашем примере эта функция не выполняет никаких особых действий (хотя можно было бы завести счетчик открытых дескрипторов и т.п.), однако без регистрации данной процедуры система просто не позволила бы клиенту "открыть" драйвер для работы с ним (хотя сам драйвер мог бы успешно загружаться и стартовать). // // (Файл хпх'Ь.срр) // СгеаДе_Е11е_1КРргосезз1пд: Берет на себя обработку запросов с // кодом 1КР_МЙ_СКЕАТЕ. // Аргументы: // Указатель на объект нашего ЕВО // Указатель на структуру 1КР, поступившего от Диспетчера ВВ // МТ5ТАТВ5 Сгеабе_ЕИе_1КРргосезз1пд (1И РВЕУ1СЕ_0ВЗЕСТ Ыо,1М Р1КР 1гр) { Р1О_ЗТАСК_ЬОСАТ1ОЫ РгрЗДаск = РоСекСиггепкРгрЗкаскЬосакДоп(1г // Задаем печать отладочных сообщений - если сборка отладочна ВВС ВЬдРгтпк ("-Ехатр1е- Сгеаке ЕНе 13 %мз", & (1грЗкаск->Е11еОЬд еск->ЕИеЫате . ВиДДег) ) ; #епсН Д гекигп СотрДеДеДгр(1гр,5ТАТВЗ_ЗВССЕЗЗ,0); // Успешное завершение }
Рабочая процедура обработки запросов закрытия драйвера Процедура С1о5е_П1е_ШРргосе551пд предназначена для обработки запросов Диспетчера ввода/вывода, которые он формирует в виде ТКР пакетов с кодом 1КР_МЗ_С1_О5Е по результатам обращения к драйверу из пользовательских приложений с вызовами ОозеНапсИе или из кода режима ядра с вызовами 2\л/С1озе. В нашем примере эта функция не выполняет никаких особых действий, однако, выполнив регистрацию процедуры открытия файла, мы теперь просто обязаны зарегистрировать процедуру завершения работы клиента с открытым дескриптором. Заметим, что если клиент пользовательского режима забывает закрыть полученный при открытии доступа к драйверу дескриптор, то за него эти запросы выполняет операционная система (впрочем, как и в отношении всех открытых приложениями файлов, когда приложения завершаются без явного закрытия открытых файлов). // (Файл гпгб.срр) // С1озе_ЕИе_1КРргосезз1пд: Берет на себя обработку запросов с // кодом 1КР_МД_СДОЗЕ. // Аргументы: // Указатель на объект нашего ЕБО // Указатель на структуру 1КР, поступившего от Диспетчера ввода/вывод ЫТЗТАТБЗ С1озе_Напс11е1РРргосезз1пд (1Ы РБЕУ1СЕ_ОВЕЕСТ Дйо,1Ы Р1ВР 1гр) { эвс // Задаем печать отладочных сообщений - если сборка отладочна БЬдРгДп'Ь ( "-Ехатр1е- 1п С1озе Ьапс11ег. " ) ; #епсИГ гебигп Сотр1ебе1гр(1гр,5ТАТУЗ_ЗБССЕЗЗ,0);// Успешное завершение }
Рабочая процедура обработки ЮСТЬ запросов Процедура Оеу|сеСоп1го1кои111пе предназначена для обработки запросов Диспетчера ввода/вывода, которые он формирует в виде 1НР пакетов с кодом 1КР_МЗ_ОЕ\/1СЕ_СОМТКО1_ по результатам обращения к драйверу из пользовательских приложений с вызовами ^еV^сеIоСоп^^оI. В нашем примере это самая важная функция. Она реализует обработку пяти ГОСТЬ запросов: ЮСТ1__РР11\1Т_ОЕВ11Сэ_МЕ55 — выводим отладочное сообщение в окно ОеЬид\/1е\л/. ЮСТ1__СНАМ6Е_1Р<21_ — проводим эксперимент, насколько высоко можно искусственно поднять уровень 1Р<21_ в коде драйвера. ЮСТ1__МАКЕ_5У5ТЕМ_СРА5Н — проводим эксперимент по "обрушению" операционной системы и пытаемся его предотвратить. ЮСТ1__ТО1)СН_РОРТ_378Н — проводим эксперимент по обращению к аппаратным ресурсам системы. 1ОСТЬ_5ЕЫО_ВУТЕ_ТО_Ы5ЕК — отправляем байт данных в пользовательское приложение. Эти ГОСТЬ коды являются пользовательскими — они определены с помощью макроса СТ1__СОЭЕ в файле Опуег.К, который является частью данного проекта, и речь о котором пойдет ниже. ф Определения используемых ниже непривычных для программистов ]Л/1п32 типов данных (например, С1СНАП или РУСНАК) можно найти в ООК в файле ]Мпс1еГ.11 . // (Файл тптб.срр) // ВечТсеСопЬго1РоиЬ1пе: обработчик 1КР_МП_ВЕУГОЕ_СОМТКОЬ запросов // Аргументы: // Указатель на объект нашего ЕВО // Указатель на структуру 1КР, поступившего от Диспетчера ВВ // Возвращает: ЗТАТП5_ХХХ // МеГОпе ЗМАЬЬ_ЧЕВ31ОЫ // В том случае, если не закомментировать верхнюю строчку V будет // выполнена компиляция версии, в которой будет обрабатываться только // один тип ГОСТЬ запросов — 1ОСТЬ_МАКЕ_5УЗТЕМ_СКАЗН ЫТЗТАТЫЗ Веч1сеСопбго1Р.О1ГО1пе ( 1Ы РВЕЧ1СЕ_ОВТЕСТ ЕДо, 1Ы Р1ВР 1гр ) { ЫТЗТАТПЗ збабиз = 5ТАТП5_5ПССЕ55; ПЬОЫС ВуРезТхД =0; // Число переданных/полученных байт (пока РГО_ЗТАСК_ЬОСАТГОЫ 1грЗРаск=1оСеРСиггепЫгр31:аскЬосаЫоп (1гр) // Получаем указатель на расширение устройства РЕХАМРЬЕ_ВЕЧ1СЕ_ЕХТЕЫ31ОЫ с!х = (РЕХАМРЬЕ_ВЕУ1СЕ_ЕХТЕЫЗГОЫ)ГОо->0еч1 //-------------------------------- // Выделяем из 1КР собственно значение ГОСТЬ кода, по поводу
// которого случился вызов: ВЬОПС СопбгоТСоЬе = 1гр8Ьаск->РагатеЬегз . ВечТсеТоСопкго! . ТоСопкго1Собе; ВЬОПС теЬНос! = СопкгоТСобе & 0x03; // Получаем текущее значение уровня 1К<2Ь V приоритета, // на котором выполняется поток (вообще говоря, целое число): К1К(2Ь ТгдТ, сиггепЫгдТ = КеСеЬСиггепЬ1гд1 () ; #1Т ВВС ВЬдРгТпЬ("-ЕхатрТе- 1п ВечТсеСопЬгоТВоиЬТпе (Тс1о= %Х)\п",Тс1о) ВЬдРгТпЬ("-ЕхатрТе- ВечТсеЮСопЕго!: ЮСТЬ %х.", СопЬгоТСобе Ю(сиггепЬ1гд1==РА881УЕ_ЬЕУЕЬ) ВЬдРгТпЬ("-ЕхатрТе- РА881УЕ_ЬЕУЕЬ (ча1=%с!) ”,сиггепШ #епсНТ // Запрашиваем владение объектом спин-блокировки. В данном // примере не выполняется никаких критичных действий, но, // вообще говоря, этот прием может быть полезен и даже // незаменим, если в приведенном ниже коде должны будут // выполнены манипуляции, которые можно делать только // эксклюзивно. Пока потоку выделен объект спин-блокировки V // никакой другой поток не сможет войти в оператор змТЬсН: КеАсдиТгеЗрТпЬоск(&Му8рТпЬоск,&1гд1); // Диспетчеризация по ЮСТЬ кодам: зюТсЬ. ( СопкгоТСобе) { #ТТпЬеТ 8МАЬЬ_УЕВ81ОП сазе 1ОСТЬ_РВ1ПТ_ВЕВВС_МЕ88 : { // Только вводим сообщение и только в отладочной версии #Ю ВВС ВЬдРггпЬ ( "-ЕхатрТе- 1ОСТЬ_РВ1ПТ_ВЕВВС_МЕ88 . " ) ; #епаю Ьгеак; } сазе ЮСТЬ_СНАПСЕ_1В0Ь: { #Ю ВВС // Эксперименты по искусственному повышению // 1В<2Ь у/ только в отладочной версии! ВЬдРггпЬ("-ЕхатрТе- 1ОСТЬ_СНАПСЕ_1В(2Ь . " ) ; К1В(2Ь с11 = В18РАТСН_ЬЕУЕЬ, // только для распечатки ( оТЫгдТ, пем1гдТ=25; // Новый уровень 1В<2Ь (например, 25) // Устанавливаем пемТгдТ, сохраняя текущий в оТсИгдТ: КеВаТзеТгдТ (пемТгдТ, &о1Ыгд1) ; пемТгдТ=КеСеЬСиггепЬ1гдТ(); // Что реально получили?
ВЬдРггпк ("-Ехатр1е- В18РАТСН_ЬЕУЕЬ ча1ие = %Ь”,Ы); ВЬдРггпк("-Ехатр1е- 1В0Ьз аге о!с1=%с1 пем=%сГ'л о1Ыгд1, пем1гд1) ; КеЬомег 1гд1 (о1Ыгд1) ; // Возвращаем старое значение #епсНР Ьгеак; } #епЫР // 8МАЬЬ_УЕВ81ОП сазе 1ОСТЬ_МАКЕ_8У8ТЕМ_СВА8Н: { 1пЬ еггВеЬесЬес1=0; сЬаг х = (сЬаг)ОхЕЕ; #1У ВВС // Вообще говоря, под ПТ мы этого уже не ВЬдРггпЬ("-ЕхатрТе- 1ОСТЬ_МАКЕ_8У8ТЕМ_СВА8Н."); #епсИР // Вызываем системный сбой обращением по нулевому адр __Ьгу { х = *(сЬаг*)ОхОЬ; // ошибочная ситуация II А А А А А А А А А А А А 3деСЬ СЛУЧИТСЯ СбОЙ ПТ, НО НС } __ехсерЬ(ЕХСЕРТ1ОП_ЕХЕСВТЕ_НАПВЬЕВ) { // Перехват исключения не работает! // Эта занимательная ситуация объяснена в 10. // при рассмотрении объектов спин-блокировок. еггВеЬесЬес1=1 ; }; #И ВВС ВЬдРггпЬ("-ЕхатрТе- УаТие о В х 1з %Х.",х); И(еггВеЬесЬес!) ВЬдРгТпЬ("-ЕхатрТе- ЕхсерЬ ЬеЬесЬес! 1п Ехатр! #епс11В Ьгеак; } #ТТпс1еТ 8МАЬЬ_УЕВ81ОП сазе 1ОСТЬ_ТОВСН_РОВТ_378Н: { ипзТдпеб зЬогк ЕСВедгзкег = 0x378+0x402; #И ВВС ВЬдРггпк("-Ехатр1е- 1ОСТЬ_ТОВСН_РОВТ_378Н."); #епЫВ // Пробуем программно перевести параллельный порт 378 // сконфигурированный средствами В1О8 как ЕСР+ЕРР, в // режим ЕРР.
_азт } { ШОУ хог оиЬ ШОУ оиЬ ах,ЕСВедФзЬег г Установить Биты 7:5 = Установить ЕРР ТОО ЕРР тоВе тоВе ООО 100 аТ, аТ ах, аТ аТ,095П ах, аТ г г г г // Подобные действия в приложении пользовательского // режима под МТ обязательно привело бы к аварийной // выгрузке приложения с сообщением об ошибке! // Практически эти пять строк демонстрируют, что можн // работать с ЬРТ портом под Шпбомз МТ ! Ьгеак; } сазе { 1ОСТЬ_8ЕМВ_ВУТЕ_ТО_В8ЕВ: // Размер данных, поступивших от пользователя: ПЬОМС ТприЬЬепдЬЬ = //только лишь для примера 1гр8Ьаск->РагатеЬегз.ВечФсеТоСопкгоТ.ТприЬВиФ // Размер буфера для данных, ожидаемых пользователем ПЬОМС ОикриЬЬепдЬЬ = 1гр8Ьаск->Рагатекегз.ВечФсеТоСопкгоТ.ОикриЬВиТТегЬепд #ФТ ВВС ВЬдРгФпк ( "-ЕхатрТе- ВиТТег оиЬТепдЬЬ %а" , ОикриЬЬепдЬЬ #епсНР ФТ( ОикриЬЬепдЬЬ<1 ) {// Если не предоставлен буфер V завершить 1ВР с ошиб зкакиз = 8ТАТП8_1МУАЫВ_РАВАМЕТЕВ; Ьгеак; } ЬСНАВ *ЬиТТ; // ипзФдпеа сПаг, привыкаем к новой нота ФР(теЬЬоа==МЕТНОВ_ВПЕЕЕВЕВ) { ЬиТТ = (РЬСНАВ) 1гр->АззосФаЬеа1гр . 8узЬетВиТТе #ФТ ВВС ВЬдРгФпЬ("-ЕхатрТе- МеЬЬоа : ВВЕЕЕВЕВ."); #епЫТ } еТзе ФР (теЬЬоа==МЕТНОВ_МЕ1ТНЕВ) { ЬиТТ=(ипзФдпеа сЬаг*)1гр->ПзегВиТТег; #ФФ ВВС ВЬдРгФпЬ("-ЕхатрТе- МеЫтоа : МЕ1ТНЕВ. #епаФТ }
еТзе { #1Т ВВС ВЬдРгТпк("-ЕхатрТе- МеЬЬос! : ипзиррог #епсИТ зкакиз = 8ТАТВ8_1ПУАЬ1В_ВЕУ1СЕ_ВЕ0ВЕ8 Ьгеак; } #1Т ВВС ВЬдРгТпк("-ЕхатрТе- ВиТТег аЬЬгеаа ±8 %08Х",ЬиТТ); #епЫТ *ЬиТТ=33; // Любимое число Штирлица ВукезТхс! = 1; // Передали 1 байт Ьгеак; } #епЫТ // 8МАЬЬ_УЕВ81ОП // Ошибочный запрос (код ЮСТЬ, который не обрабатывается): ЬеТаиЮ: збабиз = 8ТАТВ8_1ПУАЬ1В_ВЕУ1СЕ_ВЕ0ВЕ8Т; } // Освобождение спин-блокировки КеВеТеазеВрТпЬоск(&Му8рТпЬоск,тгд!); #Ю ВВС ВЬдРгТпк("-ЕхатрТе- ВечТсеТоСопкгоТ: %с1 Ьукез мгТЬЬеп.", (Тпк #епЫТ гекигп СотрТекеТгр (1гр, зкакиз, ВукезТхс!) ; // Завершение 1ВР } ф Корректность фрагмента кода, посвященного обработке ЮСТЬ запроса ЮСТЬ_ТОиСН_РОКТ_378Н, может вызвать споры, поскольку действует "напролом", не обращая внимания на то, что в системе могут быть устройства и драйвера, работающие с этим портом, существование которых следует учитывать. Однако цель данного примера — показать, что само по себе обращение к аппаратным ресурсам в режиме ядра является делом тривиальным, не имеющим ограничений со стороны операционной системы.
Рабочая процедура выгрузки драйвера Процедура С1п1оас1Рлэи1:1пе выполняет завершающую работу перед тем как драйвер растворится в небытии. ф При следовании ШМ модели, драйвер должен был бы зарегистрировать обработчик РпР запросов (то есть 1РР_МЗ_РИР) и перед вызовом ип/оас/Рои&пе получал бы 1РР пакеты с кодом 1РР_МЗ_РИР и суб- кодом 1РР_М1\1_5ТОР_ОЕ\/1СЕ (например, когда пользователь решил отключить устройство, воспользовавшись окном Диспетчера Устройств в Настройках системы). В этом обработчике и следует выполнять действия, предшествующие удалению УУИМ драйвера. // // (Файл тптк.срр) // ПпХоасШоибтпе: Выгружает драйвер, освобождая оставшиеся объекты // Вызывается системой, когда необходимо выгрузить драйвер. // Как и процедура АббВечтсе, регистрируется иначе чем // все остальные рабочие процедуры и не получает никаких 1ВР. // Агдитепбз: указатель на объект драйвера // #ргадта собе_зед("РАСЕ") // Допускает размещение в странично организованной памяти // УО1В ОпЕоабВоиРтпе(ТО РОВ17ЕВ_ОВЙЕСТ рБггчегОЬ]есб) { РОЕ71СЕ_ОВЙЕСТ рПехТОечОЬ]; гпб г; // Задаем печать отладочных сообщений V если сборка отладочна #Ю ВВС ОЬдРгтпб("-ЕхатрРе- 1п ЮпРоаб Воибгпе."); #епб10 //========================================================== // Нижеприведенные операции в полномасштабном ТОМ драйвере // следовало бы поместить в обработчике 1ВР_МЙ_РКР запросов // с субкодом 1ВР_МК_ВЕМО7Е_ОЕ71СЕ, но в силу простоты // драйвера, сделаем это здесь. // Проходим по всем объектам устройств, контролируемым // драйвером рКехРОечОЬ^ = рВггчегОЬ^ есб-ТОечгсеОЬ^ есб; Рог(1=0; рКехТОечОЬ^!=КВЬЬ; 1++) { РЕХАМРЬЕ_0Е71СЕ_ЕХТЕК8ЮК бх = (РЕХАМРЬЕ_0Е7ЮЕ_ЕХТЕК8ЮК) рКехРОечОЬ // Удаляем символьную ссылку и уничтожаем ЕБО: ОШСООЕ_8ТВ1КС *рЫпкКате = & (бх->и8бг8утЫпкКате) ; // !!! сохраняем указатель: рКехкОечОЬ^ = рКехкОечОЬ^ -ЖехкОечбсе;
#1Е БВС БЬдРг±пЕ("-Ехатр1е- Бе1еЕес1 с1еV^се (%с1) : ротпкег Ео 1л с!х->Ес1о) ; БЬдРг±пЕ("-Ехатр1е- Бе1еЕес1 зутИпк = %^з.", рЫпкЕат #епсИЕ 1оЕе1еке8утЬо1±сЫпк (рЫпкЕате) ; Iо^е1еЕе^еV^се ( с!х->Ес1о) ; } } #ргадта сос!е_зед() // епс! РАСЕ зесШп
Заголовочный файл Опуег.Н Ниже приводится полный текст файла Опуег.И, содержащий объявления, необходимые для компиляции драйвера Ехатр1е.5у5. #ТЕпбеТ _ВК1УЕК_Н_04802_ВА8НВВ_1М1ТО1_8239_1МТКВН832_901_ #беТТпе _ВК1УЕК_Н_04802_ВА8НВВ_ТМ1ТО1_8239_ТМЙКВН832_90Т_ // Выше приведены две строки (в конце файла имеется еще #епсИЕ), // которые в больших проектах запрещают повторные проходы по тексту, // который находится внутри й-файла (что весьма удобно для повышения // скорости компиляции). // (Файл БгТчег.й) #ТТбеТ __срТизрТиз ехбегп "С" { #епсИЕ #ТпсТибе "пбсИк. к" //#гпсТибе "мбт. к" II аааааааааааааа е с ли выбрать эту строку и закомментировать // предыдущую, то компиляция в среде ББК (при помощи утилиты Ви±14) // также пройдет успешно, однако драйвер Ехатр1е не станет от этого // настоящим ТОМ драйвером. #ТТбеТ ___срТизрТиз } #епсИЕ // Определяем структуру расширения устройства. Включим в нее // указатель на ЕБО (для удобства последующей работы МпТоабКоибТпе) и // имя символьной ссылки в формате ММОСОВЕ_8ТР1М(3. буребеТ збгисб _ЕХАМРЬЕ_ВЕУ1СЕ_ЕХТЕМ81ОМ { РБЕУ1СЕ_ОВ6ЕСТ Тбо; 1Ж1СОБЕ_8ТВ1ТО избгЗутЫпкМате; // Ь"ХХБозБечТсез\\Ехатр1е" } ЕХАМРЬЕ_БЕУ1СЕ_ЕХТЕМ8ТОМ, * РЕХАМРЬЕ_БЕУ1СЕ_ЕХТЕМ8 ТОМ; // Определяем собственные коды ЮСТЬ, с которыми можно будет // обращаться к драйверу при помощи вызова БечТсеТоСопбгоТ. // Определение макроса СТЬ_СОБЕ можно найти в файле ББК ТОпФосбТ.И. // Там же можно найти и численные значения, скрывающиеся под именами // МЕТНОВ_ВМЕЕЕКЕБ и МЕТНОБ_МЕ1ТНЕК. // Внимание! Текст приведенный ниже должен войти в файл ТосбТ.й, // который будет необходим для компиляции тестового приложения.
// (Разумеется, за исключением последней строки с "#епс115".) МеПпе 1ОСТЬ_РК1МТ_ОЕВО6_МЕЗЗ СТЬ_СОЭЕ ( \ Е1ЬЕ_ОЕУ1СЕ_ОЫКЫОИЫ, 0x801, МЕТНОО_ВЮГЕЕКЕО, Е1ЬЕ_АЫУ_АССЕЗЗ) МеПпе 1ОСТЬ_СНАМ6Е_1К<2Ъ СТЬ_СОБЕ(\ Е1ЬЕ_ОЕ71СЕ_1ЖКЕЮИЕ, 0x802, МЕТНОВ_ВОЕЕЕКЕО, Е1ЬЕ_АИУ_АССЕЗЗ) #с1е11пе 1ОСТЬ_МАКЕ_ЗУЗТЕМ_СРАЗН СТЬ_СООЕ ( \ Е1ЬЕ_ОЕ71СЕ_1ЖКЕЮИЕ, 0x803, МЕТНОВ_ВОЕЕЕКЕО, Е1ЬЕ_АИУ_АССЕЗЗ) #с1е11пе 1ОСТЬ_ТООСН_РОРТ_378Н СТЬ_СООЕ ( \ Е1ЬЕ_ОЕУ1СЕ_ОЫКЫОИЫ, 0x804, МЕТНОО_ВЮГЕЕКЕО, Е1ЬЕ_АЫУ_АССЕЗЗ) МеПпе 1ОСТЬ_ЗЕЕВ_ВУТЕ_ТО_ОЗЕК СТЬ_СОБЕ ( \ Е1ЬЕ_ОЕУ1СЕ_ОЫКЫОИЫ, 0x805, МЕТНОО_ВЮГЕЕКЕО, Е1ЬЕ_АЫУ_АССЕЗЗ) // Вариант : //МеИпе 1ОСТЬ_5ЕЕО_ВУТЕ_ТО_ОЗЕК СТЬ_СОБЕ ( \ // Е1ЬЕ_ОЕУ1СЕ_ОНКЫОИЫ, 0x805, МЕТНОО_ЫЕ1ТНЕК, Е1ЬЕ_АЫУ_АССЕЗЗ) #епсН1 Третий параметр СТ1__ССЮЕ называется Еипсйоп и при составлении собственных (пользовательских) ЮСТЬ кодов его значение не должно быть менее 0x800. Пересечение пользовательских ЮСТЬ кодов со значениями ЮСТЬ кодов других драйверов не имеет никакого значения, поскольку они действуют только в пределах конкретного драйвера.
ГРгеу1ои8~| ГИехМ
Цели разработки Впервые концепции У\Лпс1о\л/5 1\1Т ('Ые\л/ ТесИпо1оду', безусловно, вызывающее название из числа тех еще!) начали приобретать черты реальности в начале 1989 года. Однако хотя дата рождения столь отдаленна, пять фундаментальных 1\1Т задач остались неизменными. Совместимость. Операционная система должна поддерживать максимально возможное множество программного и аппаратного обеспечения. Переносимость. Операционная система должна функционировать на максимально возможном количестве имеющихся сейчас и ожидаемых в перспективе аппаратных платформ. Расширяемость. Поскольку требования рынков постоянно растут, операционная система должна уметь с легкостью расширять набор своих возможностей и перечень поддерживаемой аппаратуры при минимальном вмешательстве в свой внутренний код. Надежность и устойчивость. Операционная система должна быть стойкой к неумышленному или преднамеренному неправильному использованию компонентов. Пользовательские приложения не должны иметь возможности приведения системы к фатальным сбоям. (Несмотря на огромное количество злых шуток в адрес зависаний \Л/|пс1о\л/5, следует, наконец, признать, что ХР РгоГ работает хорошо.) Производительность. Операционная система должна обеспечивать хорошую производительность на всех поддерживаемых аппаратных платформах. Разумеется, провозглашение и достижение цели не всегда есть одно и то же, и серьезные компромиссы
общих и текущих задач оказываются неизбежными. ЫТ столь же подвержена компромиссам, как и все остальные операционные системы.
Уровни аппаратных привилегий в \Л/|ПС1о\Л/5 ИТ 5 Для достижения устойчивости в работе системы, разработчики 1\1Т выбрали для построения ядра так называемую 'архитектуру клиент-сервер'. В данном случае, пользовательское приложение и является клиентом служб операционной системы. Пользовательское приложение функционирует в специальном режиме (относительно аппаратного обеспечения), называемом 'изег тос/е' — пользовательский режим. В пределах этого режима, код приложения ограничен выполнением "безвредных" инструкций. Например, через реализацию "таинственного" маппинга (тарр!пд, отображение) виртуальной памяти (страничное представление виртуальной памяти) пользовательский код лишается возможности доступа к виртуальной памяти, предоставленной другим приложениям (за исключением случаев обоюдного согласия, что реализуется специально предназначенными на тот случай методами). Инструкции аппаратного ввода/вывода также не могут быть выполнены кодом пользовательского режима. Целый класс инструкций центрального процессора (называемых привилегированными) запрещен в \Л/1пс1о\/У5 1\1Т для выполнения кодом пользовательского режима, как, например, команды процессора 11\1, О11Т (результат таких попыток запечатлен на рисунке 1.1). Если вдруг приложению потребуется выполнить что-нибудь из числа таких запрещенных для нее действий, оно должно запросить соответствующую службу операционной системы.
Код самой операционной системы выполняется в так называемом 'кегпе! тос1е' — режиме ядра (режиме уровня ядра). Код режима ядра вправе выполнить любую процессорную инструкцию, не исключая инструкций ввода/вывода. Память, принадлежащая любому приложению, может быть доступна коду режима ядра, конечно, если страничная память приложения в данный момент не сброшена на жесткий диск. Современные процессоры реализуют несколько форм привилегированного режима в отличие от непривилегированного. Код режима ядра выполняется в привилегированном контексте, в то время как пользовательский код выполняется в непривилегированной среде. Так как разные процессоры (и платформы на их основе) реализуют привилегированные режимы по-разному, то, для обеспечения переносимости, разработчики операционной системы ввели особые абстрактные элементы программирования, которые позволяют разграничивать пользовательский режим и режим ядра. Код операционной системы использует их для переключения привилегированного/ непривилегированного контекста, следовательно, при перенесении операционной системы только лишь код этих дополнительных элементов необходимо "портировать" (переписывать конкретно под специфику новой аппаратной платформы). На платформе 1п1:е1 пользовательский режим реализуется из набора инструкций Р.1пд 3 (Кольца 3), в то время как режим ядра реализован с использованием РЛпд 0 (Кольца 0). Драйверы уровня ядра (режима ядра) работают в привилегированном контексте. Соответственно, плохо написанный драйверный код может оказаться вредоносным для операционной системы. Разработчик
должен с особым вниманием относиться к создаваемому коду, чтобы не обрушить все здание операционной системы. Фирма М1сго5оЛ: пытается решить проблему надежности драйверов, поставляемых в составе дистрибутива \Л/1Пс1о\л/5, через механизм тестирования и подписания драйверов.
Переносимость В качестве способа решения задачи переносимости конструкторы 1\1Т выбрали многослойную архитектуру, как показано на рисунке 4.1. Рис. 4.1 Слои операционной системы \Л/1пс1оУ75 1\1Т 5 Пользовательский режим Режим ядра Диспетчер ввода/вывода Исполнительные компоненты Драйверы устройств Ядро Слой аппаратных абстракций (Нагбшаге АЬк1гас11оп 1_ауег) Аппаратная платформа Слой аппаратных абстракций (Нагс1\л/аге АЬ51гас1юп 1_ауег, НА1_) изолирует процессорные и платформенные особенности от кода операционной системы. Его услугами М1СГО5ОГ1 предлагает пользоваться и разработчику драйверного кода. Вполне возможно так написать драйвер, что для перенесения его на другую платформу потребуется разве что перекомпилировать его. Как можно это сделать, если изначально драйвер есть такая программная единица, которая жестко привязана и к своему устройству, и к конкретному процессору, и к конкретной платформе?! Просто драйвер должен обратиться к использованию средств уровня НА1_ для взаимодействия с аппаратными регистрами и аппаратной поддержкой шин. В отдельных случаях разработчик драйвера может опереться на код, предоставляемый Диспетчером ввода/вывода, для работы с совместно используемыми аппаратными ресурсами. Например, при ОМА операциях (прямого
доступа к памяти) используется такая абстракция программирования, как объект адаптера
Расширяемость На рисунке 4.1 обозначена и еще одна важная особенность представленной архитектуры — ядро отделено от слоя, который носит название "исполнительные компоненты" (Ехесийуе). В данном случае, ядро несет ответственность за планировку активности программных потоков (1Ьгеас15). Поток является всего лишь "независимой тропинкой" в выполнении программного кода. Чтобы сохранить независимость от деятельности других потоков, для каждого из них необходимо сохранять уникальный потоковый контекст (Сйгеас! сопСехС). Потоковый контекст состоит из состояния регистров процессора (включая также изолированный стек и счетчик инструкций, Ргодгат Соип1ег), сохраненного Ю (идентификатора потока, так называемого ТЬгеас! Ю или ТЮ), значения приоритета, распределения памяти, связанной с потоком (ТЬгеас! 1_оса1 51огаде), и другой информации, имеющей отношение к данному потоку. Обязанностью планировщика потоков является определение, какой поток должен выполняться в данный момент. В среде с единственным процессором, в каждый момент времени только один поток получает в свое распоряжение процессор. В многопроцессорной конфигурации разные потоки могут выполняться на разных процессорах, реализуя настоящую параллельность выполнения кода. Планировщик в большинстве случаев выделяет потоку процессор на фиксированный временной интервал, известный под названием 1Игеас1 Ите диап1ит (потоковый временной квант). Предоставление процессора происходит, главным образом, на основе величины приоритета потока.
Так как основной задачей ядра является управление потоками, работа по управлению памятью, вопросами доступа (зесип1:у) и действиями по вводу/выводу возлагается на другие компоненты операционной системы. Эти компоненты известны под собирательным названием 'Ехеси^уе', Исполнительные Компоненты. Они сконструированы как модульное программное обеспечение (хотя, Диспетчер ввода/вывода сам является существенным исключением из этого правила). Идея поддержания ядра как "маленького и чистого", при сохранении модульности исполнительных компонентов, обеспечивает основу заявления МюгозоП: о сохранении курса ЫТ на расширяемость. По крайней мере, следует признать, что эта операционная система выдержала более десяти лет переработок и регулировок, значительно улучшив свои показатели.
Производительность Поскольку многие участники сложных программных проектов хорошо знакомы с такой особенностью многослойного программного обеспечения как его "невзрачная" производительность, то особое внимание со стороны разработчиков 1\1Т было уделено быстрому межслойному взаимодействию. Во-первых, все слои, обсуждаемые далее, выполняются в одном аппаратном режиме — режиме ядра. Следовательно, межслойные вызовы не используют ничего сложнее, чем инструкция процессора САН. Средства же, предоставляемые уровнем НА1_, в основном, представляет собой макроопределения, являющиеся 1пПпе-включаемым кодом. Во-вторых, разработчики предприняли много усилий, направленных на то, чтобы заставить работать параллельно максимально возможное число программных потоков исполнительных компонентов. Вспомогательные процедуры редко блокируются или находятся в состоянии ожидания, что минимизирует время простоя процессора. Вопросы повышение производительности в значительной степени касаются и разработчиков драйверов. В момент, когда пользовательское приложение или системный поток посылает запрос к обслуживаемому драйвером устройству (точнее сказать, объекту устройства), жизненно важно, чтобы драйверный код не блокировал выполнения запроса. Если же запрос не может быть обработан немедленно (например, устройство занято или медленно работает), запрос должен быть поставлен в очередь для последующей обработки. Диспетчер ввода/
вывода предоставляет необходимые для этого процедуры.