Upozornění: Pokud následujícímu textu neporozumíte, nezoufejte. Prostě zatím nemáte dosatatek informací o "vnitřku" systému PC.

Jak udělat rezidentní program

Chtěl bych vám něco povědět o vektorech přerušení a o tom, jak napsat rezidentní program. K tomu je potřeba znát následující informace:

1)CO JE TO REZIDENTNÍ PROGRAM

Pod tímto názvem rozumíme programy, které po svém ukončení zůstávají v paměti počítače a "číhají" na událost v systému, po jejímž uskutečnění se nějak projeví. Například program NeoGrab čeká na aktivační klávesu, po jejímž stisknutí uloží obsah obrazovky do obrázku PCX. Jiný rezidentní program SmartDrive (který mnozí z vás jistě dobře znáte) je "napojen" na diskové služby a umožňuje pomocí vyrovnávací paměti rychlejší práci s disky. Jakým způsobem se rezidentní program "doví", kdy se má aktivovat? Jak se vůbec zajišťuje koexistence několika programů v DOSu, který je dělán jako jednoúlohový operační systém? Na tyto a další otázky se vám pokusím odpovědět.

2)CO JE TO PŘERUŠENÍ

Vezměme si následující příklad kódu v Pascalu, často používaný v různých realtimově běžících programech:
repeat
prikaz1;
prikaz2;
. . . prikazN;
until keypressed;
Pokud bude tento kus kódu detailně rozebírat někdo, kdo nemá ani ponětí o přerušovacím systému, vytanou mu (možná) na mysli tyto otázky:
Jak se počítač uvnitř cyklu dozví, že byla stisknuta klávesnice - vždy přece vykonává naše příkazy a jeden procesor nemůže dělat dvě věci naráz? Argument, že si vydetekuje stisknutí klávesy prohlédnutím klávesnicového bufferu (=vyrovnávací paměti), s sebou stejně přináší další otázku: tento buffer je přece paměť jako každá jiná - někdo do ní přece tuto klávesu musel vložit!
Nebudu vás už napínat, ve skutečnosti to probíhá asi takhle: Procesor se skutečně soustředil plnou silou svých instrukcí na vaše příkazy a nějaká klávesnice mu v ten moment byla úplně lhostejná, ale v okamžiku, kdy klávesnice registrovala stisk klávesy, předala procesoru signál. Ten na tento signál přerušil váš program a odskočil si do podprogramu ošetřujícího klávesnici. A právě takovémuto signálu, který způsobí, že procesor přeruší vykonávanou činnost a věnuje se ošetření nějaké události, se říká přerušení. Podprogramu, který toto ošetření realizuje, říkáme obsluha přerušení. V našem případě s klávesnicí si obsluha otestuje, zda se nejedná o klávesy CTRL+BREAK, CTRL+ALT+DEL či jinou speciální kombinaci (potom provede příslušnou akci), dále uloží klávesu do bufferu a nakonec samozřejmě vrátí řízení přerušenému procesu.
"Napojení se" na hardwarové události je původním účelem, kvůli kterému přerušení vznikla. Dnes však tvoří přerušení s touto funkcí pouze jeden typ přerušení: hardwarová přerušení. Naproti tomu softwarová přerušení využívají toho, že přerušení je v podstatě část kódu, kterou je možno spustit ze všech programů na počítači. Proto je možno veškeré základní operace, u kterých nemá smysl, aby je každý jednotlivý program programoval sám "na vlastní triko", tyto operace naprogramovat jenom jednou, uložit do ROM a každý program je potom z přerušení pouze zavolá a nemusí je znovu sám vymýšlet (toto je podstata služeb BIOSu).
Například když potřebuju pracovat s grafickou kartou, pouze uložím do registrů procesoru parametry a zavolám přerušení INT 10h, na němž je pověšen prográmek, který mé požadavky vykoná. Další často používaná služba je INT 21h, která obsahuje funkce DOSu (práce se soubory, vstup/výstup řetězce, spouštění příkazového řádku...). Výhodou tohoto použití je, že pokud nám nějaká takováto služba nevyhovuje či ji chceme rozšířit, není problém si na obsluhu přerušení napojit svůj program.

3)TO JE SICE VŠECHNO HEZKÝ, ALE JAK UDĚLÁM TEN REZIDENT?

Právě teď k tomu docházím! Rezidentní program na PC se bez přerušovacího systému totiž neobejde - proto jsem o tomto systému vlastně psal. Samotný příkaz v Pascalu pro to, aby program zůstal po svém skončení nadále v paměti je totiž triviální - je to procedura keep, jejímž parametrem je pouze návratový kód, který program předá DOSu (tzv. ERRORLEVEL). Ten sice většinou k ničemu není, ale neboť Pascal neumožňuje proměnný počet parametrů, musíte tam stejně něco napsat (nejlépe kód 0="program skončil bez chyby"). Pouhé volání této procedury je však k ničemu - v paměti sice zůstane viset kód, ale protože ho nemá kdo nebo co spustit, je v počítači platný asi jako XT-čko v bytě pařana. Musíme tedy zařídit, aby byl program při určité konkrétní události v systému spuštěn - a, jak již víme, události systému spracovávají právě přerušení. Je tedy třeba z určité části našeho rezidentního programu udělat obsluhu přerušení (samozřejmě je možno udělat i více obsluh více přerušení).
Na nejnižších adresách paměti se nachází tzv. tabulka vektorů přerušení. Skládá se z 256ti 4bajtových adres, přičemž začíná adresou přerušení INT 0, pak následuje INT 1 atd. až poslední INT 255. Každé z těchto přerušení má svůj úkol (přesněji řečeno většina z nich, některá jsou nepoužitá). Například INT 8 se generuje každých 55milisekund vnitřními hodinami počítače, INT 9 při stisku klávesnice atd. atd., zbytek si jistě vyhledáte sami. Změnu a čtení této tabulky uskutečníme pomocí procedur
SetIntVec(cisloPreruseni:byte;AdresaProcedury:pointer)
a GetIntVec(cisloPreruseni:byte;var AdresaProcedury:pointer).
Přičemž adresu procedury získáme použitím operátoru @ na proceduru nebo funkci. Procedury použité jako obsluhy vektorů přerušení však nejsou jen tak ledajaké podprogramy. Musí splňovat tyto podmínky:
Typicky se obsluha instaluje tak, že se nejprve uschová původní adresa, kterou přerušení spouštělo dříve. Ta se potom v těle naší nové obsluhy zavolá (ta totiž obsahuje původní ošetření dané události před tím, než se k přerušení dostal náš rezident). Například takto:
const CISLO=...;
var stara_obsluha:procedure;

procedure nova_obsluha;forward;

procedure prohod_vektory; {volá se z hlavního programu}
begin
getintvec(CISLO,@stara_obsluha);
setintvec(CISLO,@nova_obsluha);
end;

procedure nova_obsluha;interrupt;
begin
......
asm pushf end;
stara_obsluha;
......
end;
Assemblerovská instrukce PUSHF se musí před voláním uvést. Důvod je malinko složitější, následující odstavec s vysvětlením proto, jestli chcete, můžete přeskočit:

Volání přerušení (instrukcí INT nebo hardwarovou událostí) se liší od normálního volání podprogramu tím, že se před vlastním voláním ukládají vlajky procesoru. Obsluha přerušení očekává, že přerušení bylo zavoláno výše uvedeným způsobem. Ale my jsme ji spustili obyčejným voláním, takže před ním musíme toto uložení vlajek provést sami právě instrukcí PUSHF. Tím v podstatě napodobujeme normální volání přerušení.

Potom se také musí ošetřit zásobník: to je oblast paměti, kde si podprogramy předávají parametry a kam se ukládají návratové hodnoty z volání procedur a funkcí. Každý program má ve svém těle oblast vyhrazenou pro zásobník a náš rezident by mu ji neměl porušit. Proto si rezident při spuštění zapamatuje hodnoty registrů SS a SP (=Stack Segment a Stack Pointer), které ukazují na zásobník, a tyto hodnoty potom při volání obsluhy prohazuje. A to takhle:
var jehoSS,jehoSP,mujSS,mujSP:word;
procedure nova_obsluha;interrupt;
begin
asm
cli
mov jehoSS,SS
mov jehoSP,SP
mov SP,mujSP
mov SS,jehoSP
sti
end;
...... {vlastní tělo obsluhy}
asm
cli
mov SS,jehoSS
mov SP,jehoSP
sti
end;
end;
To je celkem pochopitelné - prostě na začátku nahradíme jeho hodnoty svými a na konci ty jeho zase vrátíme (...toto je obecná filozofie zacházení se systémovými zdroji, obvzláště se musí striktně dodržovat v rezidentním programu, pokud si nechceme pobourat systém). Hodnoty mujSS, mujSP zjistíme na začátku, tedy v hlavním programu:
begin
{hlavní program}
asm
cli
mov mujss,SS
mov mujsp,SP
sti
end;
..........
end.
Instrukce CLI zabezpečuje, že během provádění dalších akcích neproběhnou přerušení, které by akce mohly porušit, dokud přerušení zase nepovolím instrukcí STI.
To je v podstatě všechno, co k tvorbě jednoduchého rezidentu potřebujete. Stačí vymyslet vlastní obsluhu. Tento můj demonstrační program je napojen na "devítce", reaguje tedy na klávesnici. Jeho smyslem je pouze to, že když zaregistruje po sobě klávesy D, A a N (čili mé jméno), udělá drobnou famfárku ve formě pípnutí. Využívá při tm toho, že na portu $60 je SCAN-kód poslední stisknuté klávesy a první dva bity portu $61 zapínají/vypínají interní speaker počítače (rezidentní program má být co nejmenší a já jsem nechtěl přilinkovávat celou jednotku CRT.) Všiměte si také NUTNÉ úvodní direktivy $M, která redukuje použitou paměť programu na minimum.

{$D-,Q-,R-,S-}
uses dos;
{$M $400,0,0}
{$F+}
var
old09:procedure; {stará obsluha přerušení}
mujss,mujsp:word; {..registry ukazující na můj zásobník}
jehoss,jehosp:word; {..tyto ukazují na zásobník přerušeného programu}

procedure new09;interrupt;
const count:byte=0;
var scan,s:byte;i:word;
begin
asm pushf end;
old09; {<--tímto volám starou obsluhu přerušení}
asm
cli
mov jehoss,SS
mov jehosp,SP
mov SS,mujss
mov SP,mujsp
sti
end;
{JÁDRO OBSLUHY PŘERUŠENÍ}
s:=scan;
scan:=port[$60];
if scan in [10..50] then begin
if
scan<>s then
case
scan of
32:if count=0 then inc(count);
30:if count=1 then inc(count);
49:if count=2 then inc(count);
else count:=0;
end;{case}
if count=3 then begin
for i:=1 to 3000 do port[$61]:=port[$61] or (i mod 4); {různé zapínej/vypínej reprák}
port[$61]:=port[$61] and (not 3);{nosound}
count:=0;
end;{count3}
end;{scan v 10 az 50}
{TADY KONČÍ JÁDRO OBSLUHY}
asm
cli
mov SS,jehoss
mov SP,jehosp
sti
end;
end;{proc New09}
{$F-}

begin
asm
{schovám hodnoty zásobníku}
cli
mov mujss,SS
mov mujsp,SP
sti
end;
getintvec(9,@old09); {prohodím vektory přerušení}
setintvec(9,@new09);
keep(0); {skonči a zůstaň rezidentní}
end.
Ještě byste mohli namítnout, proč jsem ten rezident nenapsal tak, aby se dovedl odinstalovat. Podle mého názoru je to však zbytečné, neboť Volkov Commander má jednu skvělou vlastnost, že pokud byly po jeho natažení instalovány nějaké rezidentní programy, sám je při svém ukončení nebo i explicitně na váš příkaz umí odinstalovat. Rezidentní programy se také samy uvolňují, pokud byly spuštěny z Wokenního DOS-PROMPTu. Toť vše. Rezidentní programy mají spoustu použití, tak zkuste nějaké vymyslet!
This page was created
using notepad.exe and
my brain





DANNY