Daniel' A.Norton. Drajvery ustrojstv v sisteme WINDOWS --------------------------------------------------------------- Date: 14 Apr 1997 From: Kazennov Vladimir Perevod Vladimira Kazennova --------------------------------------------------------------- Drajvery ustrojstv, kak pravilo, - naibolee kriticheskaya chast' programmnogo obespecheniya komp'yuterov. Po ironii sud'by eto takzhe i naibolee skrytaya chast' razrabotki programmnogo obespecheniya. Drajvery ustrojstv sistemy Windows firmy Microsoft ne yavlyayutsya isklyucheniem. Esli vy kogda-libo pisali obychnoe prilozhenie v sisteme Windows, to vam izvestno, chto trebuetsya opredelennoe kolichestvo skrytyh sposobov, chtoby prilozhenie rabotalo nadezhno. Kak podmnozhestvo prilozhenij Windows, drajvery ustrojstv sistemy Windows sleduyut etomu zhe pravilu. V dannoj stat'e avtor rassmatrivaet rabotayushchij drajver ustrojstva, kotoryj obespechivaet dostup k portam vvoda-vyvoda i obrabatyvaet preryvaniya, i virtual'nyj drajver ustrojstva (VxD), kotoryj imitiruet tehnicheskie sredstva. Predpolagaetsya, chto chitatel' znaet osnovy programmirovaniya v sisteme Windows, vklyuchaya biblioteki dinamicheskoj svyazi (dynamic link libraries - DLLs). Ustrojstvo Rassmatrivaemoe ustrojstvo - eto ne chast' tehnicheskih sredstv, kotoraya byla razrabotana, chtoby prodemonstrirovat', kak pisat' drajver ustrojstva v sisteme Windows. Skoree, eto virtual'noe ustrojstvo, polnost'yu realizovannoe v programmnom obespechenii. Programma-primer vypolnyaetsya tol'ko s virtual'nym ustrojstvom, kotoroe avtor opredelil rabotaya s sistemoj Windows v rasshirennom rezhime (Enhanced mode) processora 386, i pri uslovii, chto ustanovlen virtual'nyj drajver ustrojstva (VxD). Dalee v stat'e bolee detal'no budet opisan ishodnyj kod dlya etogo ustrojstva. Na dannyj moment sleduet znat', chto ustrojstvo imeet dva porta: port sostoyaniya i port upravleniya, oba na odnom i tom zhe adrese. Na ris. 1 pokazany bity, ispol'zuemye v porte sostoyaniya. Bit 2 ukazyvaet, chto imela mesto oshibka ustrojstva, bit 1 pokazyvaet, chto zapros na preryvanie yavlyaetsya otlozhennym, a bit 0 ukazyvaet, chto ustrojstvo zanyato. Bit 7 govorit o tom, chto ustrojstvo est' v nalichii. V etom sluchae dannyj bit raven nulyu. Esli zhe ustrojstvo ne ustanovleno ili k nemu net dostupa, to bit prinimaet znachenie, ravnoe 1. +--------------------------------------------------------+ | 7 6...3 2 1 0 | +---------+-------------------+---------+-------+--------+ | PRESENT | | ERROR | IRQ | BUSY | +---------+-------------------+---------+-------+--------+ PRESENT - ustrojstvo est' v nalichii; ERROR - proizoshla oshibka ustrojstva; IRQ - preryvanie otlozheno; BUSY - ustrojstvo zanyato. (Ostal'nye bity ignoriruyutsya dlya dal'nejshej sovmestimosti.) Ris. 1. Bity porta sostoyaniya ustrojstva Na ris. 2 pokazany bity, ispol'zuemye v porte upravleniya. Bit 1 ukazyvaet ustrojstvu, chto CPU zakonchilo obrabotku preryvaniya. Bit 0 pokazyvaet, chto ustrojstvo mozhet nachat' obrabotku vvoda-vyvoda. (V dannyj moment ne sleduet zaostryat' vnimanie na tom, chto fakticheski ustrojstvo delaet. Vmesto etogo, neobhodimo udelit' vnimanie tomu, kak napisat' drajver dlya takogo ustrojstva, kotoroe obespechivaet apparatnye preryvaniya.) +--------------------------------------------------------+ | 7...2 1 0 | +---------------------------------------+-------+--------+ | 1 1 1 1 1 1 | EOI | START | +---------------------------------------+-------+--------+ EOI - signal dlya ustrojstva, podtverzhdayushchij priem preryvaniya; START - signal dlya ustrojstva nachinat' peresylku vvoda-vyvoda. (Ostal'nye bity dolzhny byt' ustanovleny v 1 dlya dal'nejshej sovmestimosti.) Ris. 2. Bity porta upravleniya ustrojstva Drajver ustrojstva v sisteme MS-DOS Na listinge 1 pokazana programma dostest.asm, predstavlyayushchaya soboj obychnyj drajver ustrojstva dlya sistemy MS-DOS, kotoryj obshchaetsya s ustrojstvom. Nesmotrya na prostotu i malyj razmer dannaya programma soderzhit osnovnye komponenty drajvera ustrojstva, kotoryj obrabatyvaet preryvaniya. _____________________________________________________________________ page ,132 ; masm tisr ; >err .286p .xlist include ..\..\include\bogus.inc .list Words struc LoWord dw ? HiWord dw ? Words ends EOI equ 020h ; komanda EOI dlya kontrollera PIC INTA00 equ 020h ; upravlenie glavnym kontrollerom PIC INTA01 equ 021h ; registr maski glavnogo kontrollera PIC INT_MASTER_0 equ 08h ;nomer INT glavn. kontrollera PIC INTB00 equ 0A0h ; upravlenie podchinennym kontrollerom PIC INTB01 equ 0A1h ;registr maski podchinennogo kontrollera PIC INT_SLAVE_0 EQU 70h ; nomer INT podchinennogo kontrollera PIC ; ; Ustanovit' peremennye dlya nashego nomera preryvaniya ; ife (FAKE_IRQ GE 8) INT_DEV equ (INT_MASTER_0+(FAKE_IRQ AND 7)) PIC00 equ INTA00 PIC01 equ INTA01 else INT_DEV equ (INT_SLAVE_0+(FAKE_IRQ AND 7)) INT_MASK equ 1 SHL (FAKE_IRQ AND 7) PIC00 equ INTB00 PIC01 equ INTB01 endif page CONST SEGMENT DWORD PUBLIC 'DATA' sdNoBogus db 'I do not see the bogus device.',Odh,Oah,'$' sdPrompt db Odh,Oah,'S)tart, or Q)uit: ','$' sdCRLF db Odh,Oah,'$' sdDot db '.','$' CONST ENDS DATA SEGMENT DWORD PUBLIC 'DATA' dwCount1 dw 0 dwCount2 dw 0 lpPrevISR dd 0 ; adres predydushchej programmy ISR fStopping db 0 ; znachenie TRUE pri zavershenii DATA ENDS STACK SEGMENT DWORD STACK 'STACK' db 512 dup (?) STACK ENDS DGroup GROUP CONST,DATA,STACK page ;IP IntSvcRtn - The Interrupt Service Routine (Programma obsluzhivaniya ; preryvaniya) ; WARNINGS (preduprezhdeniya) ; ; NOTES (primechaniya) ; Dannaya programma ISR uvelichivaet schetchik preryvanij (dwCount1) ; i zanovo maskiruet ustrojstvo. ; ; Esli ustanovlen flag "fStopping", ustrojstvo ne maskiruetsya zanovo. ; FIXED_TEXT SEGMENT PARA PUBLIC 'CODE' segData1 dw DGroup assume CS:FIXED_TEXT,DS:NOTHING IntSvcRtn proc far push ax push dx push ds mov ds,segDatal assume ds:DGroup inc dwCount1 mov al,NOT FAKE_CTL_EOI mov dx,FAKE_PORT out dx,al ; poslat' EOI ustrojstvu mov al,EOI out PIC00,al ; poslat' EOI kontrolleru PIC ife (PIC00 EQ INTA00) out INTA00,al ; poslat' EOI glavn. kontrolleru PIC takzhe endif cmp fStopping,0 ; sushchestvuet? jnz isr9 ; esli da, to ne nuzhen perezapusk mov al,NOT FAKE_CTL_START mov dx,FAKE_PORT out dx,al ; perezapusk vvoda-vyvoda isr9: pop ds assume ds:NOTHING pop dx pop ax iret IntSvcRtn endp FIXED_TEXT ENDS page ;IP_main - tochka vhoda v programmu ; NOTES (primechaniya) ; Drajver ustrojstva vydaet dlya pol'zovatelya priglashenie: ;S)tart(nachat') ili Q)uit(vyjti). Esli pol'zovatel' nazhimaet S, ;programma razreshaet preryvaniya i vooruzhaet ustrojstvo, pechataya tochku ;kazhdyj raz, kak ustrojstvo preryvaetsya. ; _TEXT SEGMENT PARA PUBLIC 'CODE' segData2 dw DGroup segfixed dw FIXED_TEXT assume cs:_TEXT,ds:NOTHING _main label far mov ds,segData2 ;inicializiruetsya segment dannyh po umolchaniyu assume ds:DGroup mov dx,FAKE_PORT in al,dx ; prisutstvuet li fiktivnoe ustrojstvo? or al,al jns m10 ;propustit', esli da mov dx,OFFSET DGroup:sdNoBogus mov ah,9 int 21h ; v protivnom sluchae pechatat' soobshchenie ob oshibke mov ax,4C01h int 21h ; i vyjti iz sistemy m10: mov ax,3500h+INT_DEV cli int 21h ; zaprosit' tekushchuyu programmu ISR mov lpPrevISR.LoWord,bx mov lpPrevISR.HiWord,es ; sohranit' ee mov dx,OFFSET FIXED_TEXT:IntSvcRtn push ds mov ds,segFixed assume ds:NOTHING mov ax,2500h+INT_DEV int 21h ; ustanovit' nashu programmu ISR pop ds assume ds:DGroup sti mov dx,OFFSET DGroup:sdPrompt mov ax,9 int 21h ; S)tart ili Q)uit ml1: mov dl,0PFh mov ah,6 int 21h ; chitat' s konsoli, ne ozhidaya jz ml3 or al,40h cmp al,'q' je ml8 ; propustit', esli nazhato "Q" cmp al,'s' jne ml3 ; propustit', esli ne nazhato "S" cli in al,PIC01 ; razmaskirovat' preryvanie and al,NOT INT_MASK out PIC01,al sti mov al,NOT FAKE_CTL_START mov dx,FAKE_PORT out dx,al ; nachat' vvod-vyvod s ustrojstva ml3: mov ax,dwCount1 cmp ax,dwCount2 je ml4 ; propustit', esli schetchik preryvanij ne izmenilsya mov dwCount2,ax mov dx,OFFSET DGroup:sdDot mov ah,9 int 21,h ;v protivnom sluchae vydat' tochku ml4: jmp ml1 ; cikl ml8: mov fStopping,1 ; ukazanie dlya programmy ISR zavershit' rabotu mov dx,FAKE_PORT ml9: in al,dx rcr al,1 jnc ml9 ; cikl, esli zanyato cli in al,PIC01 or al,INT_MASK out PIC01,al ; maskirovat' uroven' preryvaniya sti push ds lds dx,lpPrevISR assume ds:NOTHING mov ax,2500h+INT_DEV int 21h ; vosstanovit' predydushchuyu programmu ISR pop ds assume ds:DGroup mov ax,4C00h int 21h ; vyhod _TEXT ENDS end_main ; Konec fajla _____________________________________________________________________ Listing 1. Programma dostest.asm Drajver ustrojstva nachinaet rabotu s proverki starshego bita porta sostoyaniya, chtoby ubedit'sya v nalichii ustrojstva. Zatem on ustanavlivaet svyaz' s vektorom preryvaniya MS-DOS dlya preryvaniya 11. Drajver sohranyaet predydushchee znachenie, hranimoe v etom vektore, tak chtoby mozhno bylo zamenit' znachenie, esli programma sushchestvuet. Dalee drajver ustrojstva vydaet priglashenie dlya pol'zovatelya : Start(nachat') ili Quit(vyjti). Esli pol'zovatel' nazhimaet S, programma nachinaet peresylku vvoda-vyvoda. Esli pol'zovatel' nazhimaet Q, to programma otklyuchaet ustrojstvo, vosstanavlivaet vektor preryvaniya i zavershaetsya. CHtoby nachat' operaciyu vvoda-vyvoda, drajver MS-DOS snachada razmaskiruet programmiruemyj kontroller preryvanij (programmable interrupt controller - PIC) dlya urovnya preryvaniya ustrojstva (v primere preryvanie 11). Zatem drajver nachinaet operaciyu vvoda-vyvoda dlya ustrojstva putem zapisi 1 v bit 0 porta upravleniya. Tak kak preryvaniya vklyucheny, to pri vozniknovenii preryvanij na ustrojstve poluchit upravlenie programma obsluzhivaniya preryvanij (interrupt service routine - ISR). Esli proishodit preryvanie na ustrojstve, to programma ISR podtverzhdaet priem preryvaniya, posylaya znachenie EOI ustrojstvu (t.e. zapisyvaya 1 v bit 1 porta upravleniya ustrojstva) i kontrolleru PIC. Esli programma, vypolnyayushchaya vvod-vyvod, sushchestvuet, to programma ISR vypolnyaetsya. V protivnom sluchae programma ISR osushchestvlyaet inicializaciyu peresylki vvoda-vyvoda vnov', zapisyvaya 1 v bit 0 porta upravleniya ustrojstva. Itak, programma ISR vozobnovlyaet vvod-vyvod vsyakij raz, kogda proishodit preryvanie, takim obrazom ustrojstvo nepreryvno vypolnyaet operaciyu vvoda-vyvoda. Krome obespecheniya nepreryvnogo vvoda-vyvoda programma ISR uvelichivaet schetchik (dwCount1) vsyakij raz, kogda obrabatyvaet preryvanie. V processe vypolneniya vvoda-vyvoda programma sledit za schetchikom preryvanij, otobrazhaet tochku (".") dlya kazhdoj zakonchennoj peresylki vvoda-vyvoda i prodolzhaet skanirovat' klaviaturu, chtoby opredelit', hochet li pol'zovatel' ostanovit' peresylku. CHtoby zavershit' programmu, pol'zovatel' nazhimaet klavishu Q. Programma ustanavlivaet flag, kotoryj informiruet programmu ISR o tom, chto sleduet ostanovit' obrabotku. Posle togo, kak operaciya vvoda-vyvoda ostanovlena, programma maskiruet uroven' preryvaniya v kontrollere PIC i vosstanavlivaet vektor preryvaniya. Drajver ustrojstva v sisteme Windows CHrezvychajno trivial'nyj drajver ustrojstva MS-DOS, opisannyj v predydushchem razdele, po sushchestvu dovol'no slozhno realizovat' v sisteme Windows. Pri napisanii drajvera ustrojstva v sisteme Windows, obrabatyvayushchego preryvaniya, neobhodimo ispol'zovat' arhitekturu, otlichnuyu ot toj, kotoraya byla ispol'zovana dlya drajvera MS-DOS. V chastnosti, neobhodimo otdelit' komponentu obrabotki preryvaniya ot komponenty prilozheniya. Vmesto edinstvennoj programmy, upravlyayushchej kak programmoj ISR, tak i interfejsom pol'zovatelya, kak sdelano v sisteme MS-DOS, v sisteme Windows neobhodimo vydelit' eti funkcii v otdel'nye programmnye moduli, nazyvaemye bibliotekoj dinamicheskoj svyazi (DLL) i interfejsom prikladnyh programm (Application Program Interface - API). Biblioteka DLL dlya drajvera Pri napisanii prilozhenij v sisteme Windows obychno v programmnom module imeyut delo tol'ko s dvumya tipami segmentov: peremeshchaemym (moveable) i vygruzhaemym (discardable). Segmenty dannyh programmy yavlyayutsya peremeshchaemymi, t.e. ih linejnye adresa v pamyati mogut izmenyat'sya, kogda programme upravleniya pamyat'yu sistemy Windows trebuetsya organizovat' pamyat'. Selektor (selector) i smeshchenie, ispol'zuemye dlya dostupa k opredelennoj yachejke pamyati, ostayutsya fiksirovannymi, no pod shemoj selektor-smeshchenie sistema Windows mozhet peremeshchat' fakticheskie dannye v linejnoj pamyati. Segmenty programm-kodov takzhe peremeshchaemye, no imeyut dopolnitel'nyj atribut - vygruzhaemye. Ih soderzhimoe mozhet byt' vygruzheno polnost'yu, a pri neobhodimosti zagruzheno s diska, tak kak nel'zya pisat' i (ili) modificirovat' informaciyu v segmente programmy-koda. Esli pri obrashchenii k segmentu iz programmy Windows, on okazalsya vygruzhennym, programma upravleniya pamyat'yu sistemy Windows avtomaticheski obratitsya k disku i prochitaet ranee vygruzhennyj segment. Itak, kakim obrazom eto obstoyatel'stvo vliyaet na kod dlya programmy ISR? Tak kak preryvanie mozhet proizojti v lyuboe vremya, a kod ISR mozhet okazat'sya vygruzhennym, to vozniknet problema zagruzit' kod v pamyat', esli fiksiruetsya preryvanie. Vmesto etogo, mozhno opisat' segment kak FIXED (fiksirovannyj), a ne kak MOVEABLE (peremeshchaemyj) ili DISCARDABLE (vygruzhaemyj). Segment s atributom FIXED budet ostavat'sya v edinstvennom meste linejnoj pamyati i ne budet vygruzhat'sya, dazhe esli on soderzhit kod. V etom sluchae, esli proizojdet preryvanie, kod budet dostupen i gotov k vypolneniyu. Odnako sleduet otmetit' odin maloizvestnyj fakt, a imenno: v sisteme Windows tol'ko te segmenty budut schitat'sya FIXED, kotorye byli opisany v biblioteke DLL. Segment FIXED v obychnom programmnom module budet rassmatrivat'sya kak MOVEABLE. Takim obrazom v sisteme Windows nel'zya budet pomestit' programmu ISR v obychnyj programmnyj modul'. Vmesto etogo ee neobhodimo pomestit' v biblioteku DLL. Listing 2 predstavlyaet ishodnyj kod bogusa.asm na assemblere dlya biblioteki DLL, kotoryj soderzhit programmu ISR i mozhet vypolnyat'sya v okruzhenii Windows. Programma IntSvcRtn ochen' pohozha na svoj dublikat, rabotayushchij v sisteme MS-DOS. Odnako krome uvelicheniya peremennoj- schetchika dannaya programma ISR takzhe zapisyvaet v ochered' soobshchenie Windows. CHtoby izbezhat' perepolneniya ocheredi, zapis' soobshcheniya proizvoditsya tol'ko v sluchae, kogda peremennaya-schetchik wCount izmenyaet znachenie ot 0 k 1. Funkciya obnuleniya schetchika wCount posle togo, kak zakonchena obrabotka soobshcheniya, peredana vysokourovnevoj programme sistemy Windows. S pervogo vzglyada vse eti rassuzhdeniya kazhutsya prostymi, odnako obrabotka preryvanij v sisteme Windows sovsem ne tak prosta, kak v sisteme MS-DOS. _____________________________________________________________________ page ,132 ; masm tisr ; >err .286p .xlist include bogus.inc include pic.h .list WM_COMMAND =0111h EXTRN POSTMESSAGE:FAR Words struc LoWord dw ? HiWord dw ? Words ends ; ; Ustanovit' peremennye dlya nashego nomera preryvaniya ; ife (FAKE_IRQ GE 8) INT_DEV equ (INT_MASTER_0+(FAKE_IRQ AND 7)) PIC00 equ INTA00 PIC01 equ INTA01 else INT_DEV equ (INT_SLAVE_0+(FAKE_IRQ AND 7)) INT_MASK equ 1 SHL (FAKE_IRQ AND 7)) PIC00 equ INTB00 PIC01 equ INTB01 endif page FIXED_DATA SEGMENT DWORD PUBLIC 'DATA' PUBLIC _hWndEvent,_wParamEvent,_wCount _hWndEvent label word hWndEvent dw 0 ; Okno dlya postirovaniya sobytij _wParamEvent label word wParamEvent dw 0 ; Znachenie wParam dlya postirovaniya _wCount label word wCount dw 0 ; Schetchik neobrabotannyh preryvanij FIXED_DATA ENDS page ; IP IntSvcRtn - programma obsluzhivaniya preryvanij ; ; WARNINGS (Preduprezhdeniya) ; ; NOTES (Primechaniya) ; Dannaya programma ISR uvelichivaet schetchik preryvanij i zanovo ; maskiruet ustrojstvo. ; Esli predydushchee znachenie schetchika bylo ravno 0, to zapisyvaetsya ; soobshchenie ; Esli ustanovlen flag "fStopping", ustrojstvo ne maskiruetsya zanovo. ; FIXED_TEXT SEGMENT PARA PUBLIC 'CODE' selData1 dw FIXED_DATA assume CS:FIXED_TEXT,DS:NOTHING PUBLIC _IntSvcRtn _IntSvcRtn label far IntSvcRtn proc far push ax push dx push ds mov ds,selDatal assume ds:FIXED_DATA inc wCount mov al,NOT FAKE_CTL_EOI mov dx,FAKE_PORT out dx,al ; posylaem ustrojstvu EOI mov al,EOI out PIC00,al ; posylaem EOI kontrolleru PIC ife (PIC00 EQ INTA00) out INTA00,al ; posylaem EOI takzhe glavnomu kontrolleru PIC endif cmp hWndEvent,0 ; zavershat'? jz isr9 ; esli da, to ne delaem perezapuska i ; postirovaniya cmp wCount,1 ; trebuetsya postirovanie? jne isr8 ; propuskaem, esli net push bx ; sohranyaem ostavshiesya registry push cx push es push hWndEvent push WM_COMMAND push wParamEvent push 0 ; lParam ravno 0 push 0 call POSTMESSAGE ; registriruem sobytie pop es pop cx pop bx isr8: mov al,NOT FAKE_CTL_START mov dx,FAKE_PORT out dx,al ; vozobnovlyaem vvod-vyvod isr9: pop ds assume ds:NOTHING pop dx pop ax iret IntSvcRtn endp ; trebuetsya programme AllocIntReflector PUBLIC _BogusCallback _BogusCallback label far BogusCallback proc far pushf call IntSvcRtn ret BogusCallback endp FIXED_TEXT ENDS end ; konec fajla _____________________________________________________________________ Listing 2. Programma bogusa.asm Drajvernyj interfejs API Krome otdel'nogo programmnogo modulya dlya programmy ISR (v forme biblioteki DLL sistemy Windows), dlya raboty drajvera neobhodim takzhe programmnyj modul' pol'zovatel'skogo interfejsa, nazyvaemyj interfejs API. Na listinge 3 privedena programma bogus.h, predstavlyayushchaya soboj primer interfejsa API. |ta programma soderzhit 4 tochki vhoda v biblioteku DLL. _____________________________________________________________________ #ifndef EXPORT #define EXPORT #endif extern int EXPORT FAR PASCAL BogusCheck(void) ; extern void EXPORT FAR PASCAL BogusStart(HWND hWnd,WPARAM wParam) ; extern int EXPORT FAR PASCAL BogusGetEvent(void) ; extern void EXPORT FAR PASCAL BogusStop(void) ; /* Konec fajla */ ______________________________________________________________________ Listing 3. Programma bogus.h. V tochke vhoda BogusCheck prosto proveryaetsya nalichie ustrojstva. Programma vozvrashchaet znachenie TRUE, esli ustrojstvo obnaruzheno (bit 7 porta sostoyaniya), i znachenie FALSE v protivnom sluchae. Tochki vhoda BogusStart i BogusStop nachinayut i zavershayut rabotu ustrojstva. Krome togo, tochka vhoda BogusStart razreshaet preryvaniya i obespechivaet svyaz' s apparatnym preryvaniem, a tochka vhoda BogusStop vyklyuchaet preryvaniya ustrojstva i vosstanavlivaet apparatnoe preryvanie. Tochka vhoda BogusGetEvent vozvrashchaet kolichestvo preryvanij, obrabotannyh so vremeni pervogo starta ustrojstva, libo so vremeni poslednego vyzova tochki vhoda BogusGetEvent. (Tochka vhoda BogusGetEvent obnulyaet schetchik preryvanij pri kazhdom ee vyzove.) Preryvaniya pri standartnom rezhime raboty sistemy Windows Pri napisanii drajvera, kotoryj budet vypolnyat'sya v standartnom rezhime raboty sistemy Windows, neobhodimo uchityvat' vozmozhnost' poyavleniya preryvaniya, kogda processor rabotaet v real'nom rezhime. Dazhe esli rabotayut tol'ko prilozheniya sistemy Windows, a ne prilozheniya sistemy MS-DOS, processor chasto pereklyuchaetsya iz real'nogo v zashchishchennyj rezhim. Tak kak sistema Windows 3.1 ne yavlyaetsya operacionnoj sistemoj, a skoree predstavlyaet soboj okruzhenie pol'zovatel'skogo interfejsa, ona vozlagaet vypolnenie opredelennogo kolichestva osnovnyh funkcij, vklyuchaya funkciyu vvoda-vyvoda fajlov, na operacionnuyu sistemu (a imenno MS-DOS). Poetomu kogda prilozhenie sistemy Windows vypolnyaet funkciyu MS-DOS vvoda-vyvoda fajla i processor pri etom rabotaet v real'nom rezhime, ustrojstvo mozhet preryvat' CPU. Po umolchaniyu, esli biblioteka DLL obespechila svyaz' s preryvaniem, to sistema Windows pereklyuchit CPU v zashchishchennyj rezhim dlya obrabotki preryvaniya i, kak tol'ko programma ISR zavershit rabotu, pereklyuchit CPU obratno v real'nyj rezhim dlya prodolzheniya vypolneniya funkcij sistemy MS-DOS. Hotya eto v men'shej mere otnositsya k CPU 80386, pereklyuchenie processora iz zashchishchennogo rezhima v real'nyj rezhim, naprimer na processore 80286, sozdaet ogromnye nakladnye rashody, trebuyushchie kontroliruemogo sbrosa CPU, kotoryj vypolnyaetsya v techenii millisekund. Esli neobhodimo uskorit' srednee vremya otveta, nuzhno predotvratit' pereklyuchenie processora v zashchishchennyj rezhim, esli on poluchaet preryvanie, rabotaya v real'nom rezhime. Obespechenie svyazi s vektorom preryvaniya v zashchishchennom rezhime iz biblioteki DLL sistemy Windows - trivial'no, chto i pokazano v programme SetPMVector, predstavlennoj v listinge 4 (programma bogus.c). Ustanovlenie svyazi s vektorom proizvoditsya takim zhe sposobom, kak i v sisteme MS-DOS, - s pomoshch'yu funkcii setvector sistemy MS-DOS. Odnako v otlichie ot vyzova v sisteme MS-DOS, v sisteme Windows pri obrashchenii k funkcii peredayutsya selektor i smeshchenie, a ne segment i smeshchenie. YAdro sistemy Windows sledit za vsem. Funkcii sleduet peredat' normal'nyj selektor i smeshchenie (natural'nyj ukazatel' far dlya sistemy Windows), a ne segment i smeshchenie (natural'nyj ukazatel' far dlya sistemy MS-DOS). Odnako, kak uzhe upominalos', ustanovleniya svyazi s vektorom preryvaniya v zashchishchennom rezhime nedostatochno. Neobhodimo takzhe obespechit' svyaz' s vektorom preryvaniya v real'nom rezhime, a eto ne trivial'naya zadacha. ______________________________________________________________________ /*EM BOGUS.C - Drajver fiktivnogo ustrojstva biblioteki DLL * * SUMMARY (Rezyume) * Bazovye funkcii LibMain, WEP * * COMMENTS (Kommentarii) * * WARNINGS (Preduprezhdeniya) * */ #include #include "bogusa.h" #include "pic.h" #include "dpmi.h" #define EXPORT _export _loadds #include "bogus.h" #define FAKE_PORT 0x141 /* Uroven' fiktivnosti (bogosity) - 9.4 */ #define FAKE_IRQ 11 /* Uroven' fiktivnosti (bogosity) - 9.8 */ #define FAKE_CTL_START 0x01 /* komanda "nachat'" fiktivnogo porta (ustanavlivaetsya v nul') */ #define FAKE_CTL_EOI 0x02 /* EOI fiktivnogo porta */ #define FAKE_STAT_BUSY 0x01 /* indikaciya zanyatosti fiktivnogo porta (zero=>busy) */ #define FAKE_STAT_IRQ 0x02 /* IRQ fiktivnogo porta (zero=>IRQ) */ #define FAKE_STAT_ERROR 0x04 /* oshibka vvoda-vyvoda (zero=>error) (sbrasyvaetsya pri chtenii) */ /* Ustanovit' peremennye dlya nashego nomera preryvaniya */ #if (FAKE_IRQ<8) #define INT_DEV (INT_MASTER_0+(FAKE_IRQ & 7)) #define PIC00 INTA00 #define PIC01 INTA01 #else #define INT_DEV (INT_SLAVE_0+(FAKE_IRQ & 7)) #define PIC00 INTB00 #define PIC01 INTB01 #endif #define INT_MASK (1 << (FAKE_IRQ & 7)) BOOL FAR PASCAL LibMain(HANDLE hInstance /* obrabotchik bibliotechnogo ekzemplyara*/ ,WORD wDataSeg /* segment dannyh po umolchaniyu */ ,WORD cbHeap /* razmer dinamicheskoj oblasti po umolchaniyu */ ,LPSTR lpszCmdLine) ; /* komandnaya stroka */ int FAR PASCAL WEP(int fSystemExit) ; #pragma alloc_text(INIT_TEXT,LibMain) /* derzhat' vmeste s LIBENTRY.ASM */ #pragma alloc_text(FIXED_TEXT,WEP) HANDLE hLibInstance ; FARPROC lpfnPrevISR ; /* Sohranennaya predydushchaya programma ISR*/ DWORD lpfnPrevRMISR ; /* Sohranennaya predydushchaya programma ISR real'nogo rezhima*/ HANDLE hReflector ; DWORD DPMI_AllocateRMCallback(FARPROC lpfnCallback, _RMCS FAR *lpRMCS) { DWORD dwRet ; _asm { push ds lds si,lpfnCallback les di,lpRMCS mov ax,DPMI_ALLOCRMC int IVEC_DPMI pop ds jc lbl1 mov word ptr dwRet,dx ; vozvrat adresa obratnogo vyzova mov word ptr dwRet+2,cx jmp short lbl2 lbl1: mov word ptr dwRet,ax ; kod oshibki v registre ax mov word ptr dwRet+2,0 ; vozvratit' seg=0,esli proizoshla oshibka lbl2: } return dwRet ; } DWORD DPMI_FreeRMCallback(FARPROC lpfnCallback) { DWORD wRet ; _asm { mov dx,word ptr lpfnCallback mov cx,word ptr lpfnCallback+2 mov ax,DPMI_FREERMC int IVEC_DPMI jc lbl1 xor ax,ax lbl1: mov wRet,ax } return wRet ; } DWORD DPMI_GetRMVector(int iVector) { DWORD dwRet ; _asm { mov ax,DPMI_GETRMVEC mov bl,byte ptr iVector int 31h mov word ptr dwRet,dx mov word ptr dwRet+2,cx } return dwRet ; } void DPMI_SetRMVector(int iVector, DWORD lpfnRMISR) { _asm { mov ax,DPMI_SETRMVEC mov bl,byte ptr iVector mov dx,word ptr lpfnRMISR mov cx,word ptr lpfnRMISR+2 int 31h } } FARPROC GetPMVector(int iVector) { FARPROC dwRet ; _asm { mov bl,byte ptr iVector mov ah,35h int 21h mov word ptr dwRet,bx mov word ptr dwRet+2,es ; Sohranit' } return dwRet ; } void SetPMVector(int iVector, FARPROC lpfnISR) { _asm { push ds lds dx,lpfnISR mov al,byte ptr iVector mov ah,25h int 21h ; Ustanovit' nashu programmu ISR pop ds } } HANDLE AllocIntReflector(int iVector, FARPROC lpfnCallback) { DWORD dwDosMem ; LPSTR lpLowRMISR ; DWORD lpfnRMCallback ; _RMCS FAR *lpSaveRegs ; /* Raspredelit' pamyat' DOS dlya programmy obsluzhivaniya preryvaniya ISR, *rabotayushchej v real'nom rezhime */ dwDosMem = GlobalDosAlloc(16 + sizeof (int) + sizeof (_RMCS) ; if (dwDosMem == 0) return 0; lpLowRMISR = (LPSTR) MAKELONG(0,LOWORD(dwDosMem)) ; lpSaveRegs = (_RMCS FAR *) (&lpLowRMISR[16]) ; /* Raspredelit' obratnyj vyzov (callback), rabotayushchij v real'nom * rezhime */ lpfnRMCallback = DPMI_AllocateRMCallback((FARPROC)lpfnCallback, lpSaveRegs) ; if (HIWORD((DWORD)lpfnRMCallback == 0) { GlobalDosFree(LOWORD(dwDosMem)) ; return 0; } /* Sgenerirovat' kod v nizhnih adresah pamyati (tol'ko 6 bajtov)*/ lpLowRMISR[0] = 0x9A ; /* Vyzov ukazatelya na FAR */ *((DWORD FAR *)&(lpLowRMISR[1])) = lpfnRMCallback ; lpLowRMISR[5] = 0xCF ; /*IRET */ *((int FAR *)&(lpLowRMISR[6])) = iVector ; /* Ustanovit' svyaz' s vektorom preryvanij real'nogo rezhima */ DPMI_SetRMVector(iVector,MAKELONG(0,HIWORD(dwDosMem))) ; return (HANDLE) LOWORD(dwDosMem) ; /* vozvrat obrabotchika-otrazhatelya */ } void FreeIntReflector(HANDLE hReflector) { LPSTR lpLowRMISR ; DWORD lpfnRMCallback ; /* Poluchit' adres nizhnego ISR v zashchishchennom rezhime */ lpLowRMISR = (LPSTR)MAKELONG(0,(WORD)hReflector) ; /* Sleduet ubedit'sya, chto eto otrazhatel' */ if ((lpLowRMISR[0] != 0x9A) || (lpLowRMISR[5] != 0xCF)) return ; /* vyhod, esli ne otrazhatel' */ /* Vybrat' adres obratnogo vyzova i osvobodit' obratnyj vyzov */ lpfnRMCallback = *((DWORD FAR *)&((lpLowRMISR[1])) ; DPMI_FreeRMCallback(lpfnRMCallback) ; /* Osvobodit' programmu obsluzhivaniya preryvanij real'nogo rezhima*/ GlobalDosFree((WORD)hReflector) ; } /*XP< LibMain - osnovnaya bibliotechnaya tochka vhoda */ * * ENTRY (vhod) * * EXIT (vyhod) * * RETURNS (vozvrat) * Esli inicializaciya zavershaetsya uspeshno prinimaet znachenie, ravnoe * TRUE, v protivnom sluchae - FALSE * * WARNINGS (preduprezhdeniya) * * CALLS (vyzovy) * * NOTES (primechanie) * Nastoyashchaya bibliotechnaya tochka vhoda nahoditsya v assemblernom module * LIBENTRY.ASM, a v dannuyu tochku prosto peredaetsya upravlenie * */ BOOL FAR PASCAL LibMain(HANDLE hInstance /* obrabotchik bibliotechnogo ekzemplyara*/ ,WORD wDataSeg /* segment dannyh po umolchaniyu */ ,WORD cbHeap /* razmer dinamicheskoj oblasti po umolchaniyu */ ,LPSTR lpszCmdLine) ; /* komandnaya stroka */ /*>*/ { lpszCmdLine = lpszCmdLine ; /* Izbegat' preduprezhdeniya -W4 */ wDataSeg = wDataSeg ; cbHeap = cbHeap ; hInstance = hInstance ; /* |to mozhet ponadobit'sya pozzhe dlya dostupa k resursam iz nashego *ispolnitel'nogo modulya */ return TRUE ; } /*XP< WEP - procedura vyhoda v sisteme Windows */ * * ENTRY (vhod) * fSystemExit ukazyvaet na zavershenie sessii v sisteme Windows. V * protivnom sluchae proishodit tol'ko razgruzka dannoj biblioteki DLL. * RETURNS (vozvrat) * Vsegda vozvrashchaetsya znachenie 1 * * WARNINGS (preduprezhdeniya) * Iz-za oshibok v sisteme Windows 3.0 i bolee rannih versiyah (a * vozmozhno i v bolee pozdnih versiyah) dannaya funkciya dolzhna byt' *pomeshchena v fiksirovannyj segment. |ti zhe oshibki privodyat k tomu, chto * znachenie DS somnitel'no, a poetomu nel'zya ego ispol'zovat' (takzhe * kak i lyubye staticheskie dannye). * * V lyubom sluchae, nesomnenno ne nado nichego delat' v etoj tochke. * * CALLS (vyzovy) * Net * NOTES (primechaniya) * |to standartnaya procedura vyhoda DLL. * */ int FAR PASCAL WEP(int fSystemExit) /*>*/ { fSystemExit = fSystemExit /* Izbegat' preduprezhdeniya -W4 */ return 1 ; /* vsegda ukazyvaet na uspeshnoe zavershenie */ } int EXPORT FAR PASCAL BogusCheck(void) { BYTE bPortVal ; _asm { mov dx,FAKE_PORT in al,dx ; Prisutsvuet fiktivnoe ustrojstvo ? mov bPortVal,al } return ((bPortVal & 0x80) == 0) ; /* Vozvrashchaet znachenie TRUE, esli ustrojstvo prisutstvuet */ } void EXPORT FAR PASCAL BogusStart(HWND hWnd, WPARAM wParam) { wParamEvent = wParam ; hWndEvent = hWnd ; if (!lpfnPrevISR) { /* Sohranit' predydushchuyu programmu ISR i zagruzit' novuyu */ _asm cli lpfnPrevISR = GetPMVector(INT_DEV) ; SetPMVector(INT_DEV,(FARPROC)IntSvcRtn) ; _asm sti /* Sohranit' predydushchuyu programmu ISR real'nogo rezhima i *otrazit' na novuyu */ lpfnPrevRMISR = DPMI_GetRMVector(INT_DEV) ; hReflector = AllocIntReflector(INT_DEV,(FARPROC)BogusCallback) ; /* Razreshit' preryvanie i nachat' operaciyu vvoda-vyvoda na *ustrojstve */ _asm { cli in al,PIC01 ; razreshit' preryvanie and al,NOT INT_MASK out PIC01,al sti mov al,NOT FAKE_CTL_START mov dx,FAKE_PORT out dx,al ;nachat' operaciyu vvoda-vyvoda na ustrojstve } } } int EXPORT FAR PASCAL BogusGetEvent(void) { WORD wCountRet ; _asm { mov ax,SEG wCount mov es,ax xor ax,ax xchg ax,es:wCount ;poluchit' schetchik, ustanovit' v nul' mov wCountRet,ax } return wCountRet ; } void EXPORT FAR PASCAL BogusStop(void) { hWndEvent - 0x0000 ; /*komanda dlya ISR "zavershit' rabotu"*/ if (!lpfnPrevISR) return ; /* vozvratit'sya, esli programma ne startovala */ _asm { mov dx,FAKE_PORT l1: in al,dx rcr al,1 jnc l1 ; cikl, esli zanyato cli in al,PIC01 or al,INT_MASK out PIC01,al ; maskirovat' uroven' preryvaniya sti } DPMI_SetRMVector(INT_DEV, lpfnPrevRMISR) ; /* Vosstanovit' vektor real'nogo rezhima */ FreeIntReflector(hReflector) ; /* Osvobodit' otrazhatel' */ SetPMVector(INT_DEV, lpfnPrevISR) ; /* Vosstanovit' vektor zashchishchennogo rezhima */ lpfnPrevISR = NULL ; } /* konec fajla*/ _____________________________________________________________________ Listing 4. Programma bogus.c Interfejs sistemy MS-DOS dlya zashchishchennogo rezhima CHtoby ustanovit' svyaz' s vektorom real'nogo rezhima iz koda sistemy Windows zashchishchennogo rezhima, neobhodimo rabotat' s interfejsom sistemy MS-DOS dlya zashchishchennogo rezhima (MS-DOS Protected Mode Interface - DPMI). (Tekushchaya versiya DPMI predstavlyaet soboj uroven' 1.0, no sistema Windows naibolee polno realizuet tol'ko uroven' 0.9. Nekotorye funkcii urovnya 1.0 realizovany v sisteme Windows 3.1.) Funkciya DPMI_SetRMVector vyzyvaet interfejs DPMI, chtoby ustanovit' vektor real'nogo rezhima. Mozhno videt', chto interfejs DPMI vzaimodejstvuet cherez registry (registr AX soderzhit funkcional'nyj kod) i INT31h. Avtor vklyuchil vysokourovnevyj interfejs v dannuyu i drugie funkcii DPMI (dostupen tol'ko na diske kodov ili v interaktivnom rezhime), chtoby mozhno bylo imet' dostup k interfejsu DPMI iz yazyka Si i vydelil kod, napisannyj na yazyke assembler, na sluchaj, esli vozniknet neobhodimost' ispol'zovat' chto-to otlichnoe ot kompilyatora Si firmy Microsoft. Funkciya DPMI_AllocateRMCallback vyzyvaet interfejs DPMI, chtoby raspredelit' obratnyj vyzov (callback), predstavlyayushchij soboj adres, vyzyvaemyj iz real'nogo rezhima, kotoryj peredaet upravlenie kodu zashchishchennogo rezhima. Naprimer, programma TSR sistemy MS-DOS mozhet vyzvat' kod v biblioteke DLL sistemy Windows cherez obratnyj vyzov. Funkciya DPMI_AllocateRMCallback prinimaet dva parametra: adres koda zashchishchennogo rezhima, kotoryj budet vyzyvat'sya obratno, i registrovuyu strukturu, kotoraya obnovlyaetsya pri vypolnenii real'nogo obratnogo vyzova, takim obrazom kod zashchishchennogo rezhima mozhet issledovat' soderzhimoe registrov real'nogo rezhima vo vremya obratnogo vyzova. Funkciya DPMI_FreeRMCallback osvobozhdaet vse struktury, kotorye byli raspredeleny v rezul'tate obrashcheniya k funkcii DPMI_AllocateRMCallback. Funkciya DPMI_FreeRMCallback dolzhna vyzyvat'sya tol'ko togda, kogda bol'she net neobhodimosti v obratnom vyzove. Programma ISR v real'nom rezhime Nesmotrya na to, chto avtor rekomendoval obespechivat' razdel'nuyu programmu ISR v real'nom rezhime, v dannom primere eta rekomendaciya ne byla vypolnena. Vmesto etogo, avtor predostavil programmy, neobhodimye pri realizacii programmy ISR na yazyke Si. Fakticheski, dannyj primer ustanavlivaet svyaz' s preryvaniyami real'nogo rezhima tol'ko dlya togo, chtoby pereklyuchit' CPU v zashchishchennyj rezhim dlya obrabotki preryvaniya. Takovo po umolchaniyu povedenie sistemy Windows, kogda s preryvaniyami real'nogo rezhima ne ustanavlivaetsya svyaz' voobshche, takim obrazom avtor rassmatrivaet neskol'ko ciklov, kotorye ne imeyut nikakogo drugogo naznacheniya, krome kak pokazat', kakim obrazom vse rabotaet. Rassmotrim kod dlya tochki vhoda BogusStart. Po sushchestvu on rabotaet tak zhe, kak rabotal by v sisteme MS-DOS. Kod sohranyaet staroe znachenie preryvaniya, obespechivaet svyaz' s tekushchim znacheniem i podaet ustrojstvu znak nachat' rabotu. Odnako vmesto obespecheniya svyazi tol'ko s vektorom zashchishchennogo rezhima, on ustanavlivaet svyaz' kak s vektorom real'nogo rezhima, tak i s vektorom zashchishchennogo rezhima. Ustanavlivaya svyaz' s vektorom real'nogo rezhima, kod vyzyvaet AllocIntReflector, chtoby obespechit' ssylku vektora preryvanij real'nogo rezhima na obratnyj vyzov, kotoryj prosto obrashchaetsya k programme ISR zashchishchennogo rezhima. Tochka vhoda BogusStart podaet znak ustrojstvu nachinat' rabotu odinakovym obrazom pri oboih rezhimah raboty: zashchishchennom i real'nom. Ona razmaskiruet bit IRQ dlya kontrollera PIC i podaet znak ustrojstvu nachinat' rabotu, zapisyvaya 1 v bit START porta upravleniya ustrojstvom. Kak tol'ko prilozhenie obrashchaetsya k dannoj programme, nachinaetsya obrabotka preryvanij i registraciya soobshchenij v sootvetstvii s programmoj ISR. Programma BogusStop trivial'na i prosto otklyuchaet ustrojstvo i razryvaet svyazi, ustanovlennye programmoj BogusStart. Itak, ostalos' privesti primer prikladnoj programmy, chtoby pokazat' rabotu operacij vvoda-vyvoda. Prilozhenie WINTEST Prilozhenie wintest.c, pokazyvayushchee rabotu vvoda-vyvoda (sm. listing 5), sostoit glavnym obrazom iz nemodul'nogo dialogovogo bloka, v kotorom nepreryvno vysvechivaetsya kolichestvo preryvanij, obrabotannyh s nachala raboty programmy. Programma MainDlgProc vyzyvaet programmu BogusStart vo vremya vypolneniya WM_INITDIALOG, peredavaya v kachestve parametra obrabotchik okna dialogovogo bloka. Programma ISR registriruet soobshcheniya k dannomu obrabotchiku v teh sluchayah, kogda schetchik preryvanij izmenyaetsya ot nulya k edinice. Programma MainDlgProc sohranyaet tekushchee summarnoe znachenie schetchika v peremennoj wCountTotal. Vsyakij raz, kogda dialog poluchaet soobshchenie WM_COMMAND s parametrom wParam, ravnym IDM_BOGUSEVENT, programma obnovlyaet summarnyj schetchik, otobrazhaemyj v dialogovom bloke. Sleduet otmetit', chto hotya programma ISR registriruet soobshchenie tol'ko togda, kogda schetchik izmenyaetsya ot nulya k edinice, vozmozhna (i ves'ma veroyatno) obrabotka kolichestva preryvanij do togo, kak soobshchenie WM_COMMAND fakticheski budet peredano dialogovoj procedure. Metodika, pokazannaya v dannoj programme, pri kotoroj programma ISR registriruet soobshchenie tol'ko pri pervom perehode, a programma BogusCheck chistit schetchik, obespechivaet tochnyj podschet kolichestva preryvanij, dazhe esli na urovne prilozheniya nel'zya uchest' kazhdoe preryvanie v moment ego vozniknoveniya. Pri vypolnenii dannoj programmy mozhno nablyudat', chto schetchik preryvaniya v dialogovom bloke nepreryvno uvelichivaetsya, ukazyvaya kolichestvo vypolnennyh operacij vvod-vyvoda. _____________________________________________________________________ #include #include "bogus.h" #include "wintest.h" HANDLE hPgmInstance ; #define IDM_BOGUSEVENT 0x3000 void CenterWindow(HWND hWnd) { int xSize, ySixe, xPos, yPos ; RECT rc ; xSize = GetSystemMetrics(SM_CXSCREEN) ; ySize = GetSystemMetrics(SM_CYSCREEN) ; GetWindowRect(hWnd, &rc) ; xPos = (xSize - (rc.right - rc.left)) / 2 ; yPos = (ySize - (rc.bottom - rc.top)) / 2 ; SetWindowRect(hWnd, NULL, xPos, yPos, 0, 0, SWP_DRAWFRAME | SWP_NOSIZE | SWP_NOZORDER) ; } LRESULT _loadds FAR PASCAL MainDlgProc(HWND hwndDlg, UINT msg, WPARAM wParam, LPARAM lParam) { static WORD wCountTotal = 0; WORD wCount ; lParam = lParam ; switch (msg) { case WM_INITDIALOG: RemoveMenu(GetSystemMenu(hwndDlg,0), SC_CLOSE,MF_BYCOMMAND) ; BogusStart(hwndDlg, IDM_BOGUSEVENT) ; break ; case WM_SHOWWINDOW: if (wParam) CenterWindow(hwndDlg) ; break ; case WM_COMMAND: switch (wParam) { case IDM_BOGUSEVENT: wCount = BogusGetEvent() ; while 9wCount) { wCountTotal += wCount ; wCount = BogusGetEvent() ; } SetDlgItemInt(hwndDlg, IDM_COUNT, wCountTotal, FALSE); break ; case IDCANCEL: EndDialog(hwndDlg, 0) ; break ; } break ; default: return FALSE ; } return TRUE ; } int PASCAL WinMain(HANDLE hInstance, HANDLE hPrevInstance, LPSTR lpCmdLine, intnCmdShow) { hPgmInstance = hInstance ; hPrevInstance = hPrevInstance ; lpCmdLine = lpCmdLine ; nCmdShow = nCmdShow ; if (!hPrevInstance) { if (BogusCheck()) { if (MessageBox(0, "Press OK to begin bogus I/O", "WinTest", MB_OKCANCEL|MB_APPLMODAL) == IDOK) { DialogBox(hPgmInstance, "M