Generování a obsaluha přerušení

Úkolem tohoto cvičení je:

Registry procesoru i8086

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.

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).

Mechanismus přerušení procesoru i8086

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.

Generování programového přerušení v Turbo Pascalu

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).

Úloha 1: Výpis řetězce pomocí služby DOSu

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:

  1. do registru AH vložíme hodnotu 9 (číslo služby systému)
  2. do registrů DS:DX vložíme adresu počátku řetězce (DS bude obsahovat segment, DX offset). Řetězec musí být ukončen znakem "$".
  3. vyvoláme přerušení 21h

Obsluha přerušení v jazyce Turbo Pascal

Nastavení obslužné procedury

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.

Vlastní obslužná procedura

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;

Vyvolání původní obslužné procedury

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č?

Úloha 2: Obsluha přerušení od klávesnice

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í.

Příkladové řešení úloh


Lukáš Petrlík
luki@kiv.zcu.cz