Úkolem tohoto cvičení je:
Procesory i8086 obsahují osm 16bitových registrů, které dělíme na zápisníkovou paměť (registry AX, BX, CX, DX), skupinu ukazatelů (SP - ukazatel zásobníku, BP - ukazatel dna zásobníku a IP - čítač instrukcí) a skupinu indexových registrů (SI, DI). Registry zápisníkové paměti můžeme používat buď jako jeden 16bitový nebo jako dva nezávislé 8bitové registry (např. šestnáctibitový registr AX jako dva osmibitové registry AH a AL).
Procesor má také 9 jednobitových příznaků. Šest z nich jsou stavové indikátory, které nastavuje aritmeticko-logická jednotka podle výsledku operace. Příznaky jsou sdruženy do 16bitového registru F.
Dále procesor obsahuje 4 16bitové segmentové registry (CS, DS, SS, ES). Segmentové registry se užívají pouze pro adresování hlavní paměti.
Obsah segmentových registrů se chápe jako bázová adresa segmentu (segmentová adresa). Při přístupu k hlavní paměti se fyzická adresa vytváří tak, že se do 20bitové sčítačky zavede segmentová adresa posunutá o 4 bity vlevo (tj. vynásobená šestnácti) a k ní se přičte posunutí (offset). Například adresa odkud bude čtena následující instrukce vznikne složením segmentového registru CS a čítače instrukcí IP:
adresa = (CS * 16) + IP
Jednotlivé segmentové registry se používají pro různé druhy přístupu k paměti:
| Typ odkazu | Zdroj posunutí | Implicitní (alternativní) zdroj báze |
|---|---|---|
| Čtení instrukce | IP | CS |
| Proměnná | vypočtená adresa | DS (CS, ES, SS) |
| Op. se zásobníkem | SP | SS |
| Zdrojový řetězec | SI | DS (CS, ES, SS) |
| Cílový řetězec | DI | ES |
| BP jako báze | vypočtená adresa | SS (CS, DS, ES) |
Například instrukce PUSH AX ukládá registr AX na zásobník; k
tomu užívá registr ukazatele zásobníku SP a registr zásobníkového segmentu
SS.
Instrukci PUSH AX bychom mohli rozepsat takto:
SP := SP - 2; { zásobník roste směrem k nižším adresám }
mem[SS:SP ] := AL; { méně významný byte na nižší adresu }
mem[SS:SP+1] := AH; { významnější byte na vyšší adresu }
Instrukce volání podprogramu CALL ukládá na zásobník obsah
registru čítače intrukcí, instrukce návratu z podprogramu RET
ho opět vybírá.
Volání podprogramu existují "dlouhá" (mezisegmentová) a krátká (uvnitř
segmentu). Mezisegmentová volání podprogramu ukládají na zásobník registry
CS a IP, vnitrosegmentová volání ukládají pouze registr čítače instrukcí IP.
Obdobně návrat z mezisegementového volání se provádí "dlouhou" instrukcí
RET, návrat z vnitrosegmentového volání podprogramu "krátkou" instrukcí RET.
Volbu typu instrukcí volání a návratu z podprogramu provádí obvykle
překladač; v jazyce Turbo Pascal si můžeme používání dlouhých volání vynutit
přepínačem { $F+ } (Force Far Calls).
Přerušení můžeme podle jejich původu rozdělit na externí a interní.
Externí přerušení jsou reakcí procesoru na události vzniklé mimo procesor;
interní přerušení vyvolává procesor, například při provádění instrukce
INT.
Procesor rozpoznává 256 různých typů přerušení. Každému přerušení je přiděleno číslo (typ přerušení) v rozsahu 0 až 255. Přijme-li procesor žádost o přerušení, uschová svůj stav (uloží na zásobník registry F, CS a IP), vynuluje příznaky IF a TF (jsou součástí registru F) a vyvolá příslušný obslužný podprogram přerušení. Obslužné podprogramy přerušení se ukončují instrukcí IRET, která obnoví obsah registrů CS, IP a F a pokračuje v přerušeném výpočtu.
Zařízení žádají o přerušení na vývodu procesoru INTR (Interrupt Request)
nebo NMI (Non-Maskable Interrupt). Vstup INTR je maskovatelný, tj. procesor
bude přerušení ignorovat, je-li příznak IF=0 (příznak IF lze ovládat
instrukcemi CLI a STI; CLI přerušení
zakáže a STI povolí). Vstup INTR je v počítačích PC spojen s
prioritním řadičem přerušení 8259, ke kterému jsou připojena zařízení
žádající o přerušení.
Vstup NMI se používá jen pro signalizaci "katastrofických" událostí, v počítačích PC je spojen s testováním parity hlavní paměti. Přerušení vyvolané vstupem NMI nelze zakázat.
Interní přerušení typu n může být vyvoláno instrukcí
INT n (n je v rozsahu 0 až 255).
Instrukce INT se používá kromě jiného i pro volání služeb MS
DOSu.
Pro vyvolání programového přerušení se používá funkce
Intr(číslo_přerušení, obsah_registrů). Proměnná
obsah_registrů je záznam typu Registers (podrobnosti
naleznete v nápovědě Turbo Pascalu).
V volání služeb MS DOSu existuje procedura MsDos(regs), která se chová jako volání Intr($20, regs).
Pomocí služby č. 9 MS DOSu vypište řetězec "Hello world!" s odřádkováním.
Službu pro výpis řetězce vyvoláme takto:
Novou obslužnou proceduru přerušení nastavíme procedurou SetIntVec(číslo_přerušení, adresa_procedury). Potřebnou adresu procedury můžeme zjistit operátorem ``@''; například konstrukce
var Adresa: pointer;
...
Adresa = @MojeProcedura;
přiřadí proměnné Adresa hodnotu adresy procedury MojeProcedura.
Adresu původní obslužné procedury přerušení (která je v případě mnohých externích přerušení součástí DOSu) můžeme zjistit procedurou GetIntVec(číslo_přerušení, ukazatel). Adresu původní obslužné procedury je vhodné obnovit při ukončení vašeho programu.
Poznámka: I když bychom mohli nastavit novou obslužnou proceduru "ručně" zápisem přerušovacích vektorů na příslušné adresy, doporučuje se využívat procedur GetIntVec/SetIntVec, protože provádějí čtení a zápis atomicky.
Vzhledem k tomu, že přerušení může být vyvoláno prakticky kdykoli, je nutné, aby jeho obslužná procedura uchovávala hodnotu všech registrů procesoru. Standardní procedury Turbo Pascalu tento požadavek nesplňují - datové registry procesoru jsou zde hojně využívány a jejich původní obsah se neuchovává (zaručeno je pouze uchování registrů BP, SP, SS a DS).
Obslužné procedury přerušení musejí být v Turbo Pascalu uvozeny direktivou interrupt. Tato direktiva způsobí, že se při překladu procedury vloží na její začátek instrukce pro uchování obsahu všech registrů a inicializaci registru DS:
push ax ; uložení registrů na zásobník push bx push cx push dx push si push di push ds push es push bp mov bp, sp ; uchování hodnoty SP v BP sub sp, velikost_oblasti_lokálních_proměnných ; vyhrazení oblasti lokálních proměnných mov ax, seg data mov ds, ax ; do DS odkaz na datový segment
Při výstupu z procedury je obsah všech registrů obnoven a provede se instrukce návratu z přerušení:
mov sp, bp pop bp ; totéž pro ES, DS, DI, SI, DX, CX ... pop ax iret
Procedura obsluhující externí přerušení nemůže používat služby DOSu ani procedury pro vstup/výstup nebo alokaci paměti Turbo Pascalu, protože tyto nejsou reentrantní.
Obslužná procesura přerušení tedy bude vypadat takto:
procedure Obsluha;
interrupt;
begin
...
end;
Obsluhuje-li vaše procedura přerušení, které je obvykle obsluhováno DOSem, budete často chtít vyvolat i původní obslužný podprogram přerušení. To můžete provést následující konstrukcí:
asm pushf; call ukazatel_na_původní_obsluhu; end;
Uložení registru příznaků na zásobník je nutné, protože původní obslužná procedura se bude navracet instrukcí iret, která registr příznaků na zásobníku očekává.
Postup událostí pak můžeme znázornit následujícím grafem:
přerušení běžící program -------------> vaše obslužná procedura ---> původní obsluha pokračování v běhu <--------- výběr registrů + iret <----- iret
Testovací otázka: Ve kterém okamžiku se ukládají/vybírají které registry a proč?
Při každém stisku a uvolnění klávesy je vygenerováno přerušení č. 9.
Napište obslužnou proceduru tohoto přerušení, která při každém stisku a uvolnění klávesy vygeneruje krátké pípnutí.
Lukáš Petrlík