Jak se pověsit (na interrupt)
-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-
In the beginning there was data. The data was without form and null,
and darkness was upon the face of the console; and the Spirit of
IBM was moving over the face of the market. And DEC said, "Let there
be registers"; and there were registers. And DEC saw that they
carried; and DEC separated the data from the instructions. DEC
called the data Stack, and the instructions they called Code. And
there was evening and there was morning, one interrupt.
-- Rico Tudor, "The Story of Creation or, The Myth of Urk"
O nastavování interruptu se stará DPMI server. Pokud chcete
pověsit nějakou funkci na interrupt, je nutné udělat několik věcí.
Nejdůležitější je zabránit swapování. Intelové totiž virtualní paměť
v tomto případě dost zkomplikovali. Pokud je vyvolán hardwarový
interrupt (ten co zpravujete) a z něho se vyvolá další (výpadek
stránkování), procesor spanikaří a vyvolá se automaticky interrupt
"dvojitá chyba". Z tohoto interruptu už není návratu a většinou
způsobí, že se počítač zničeho nic vyrezetuje.
Nevidím žádný logický důvod pro toto chování. Následek ale je,
že není možné, aby se ovladač preřušení odswapoval na disk. Proto
je nutné DPMI serveru říct, že tento kus paměti se nesmí swapovat
(zamknout ho). Nejjdedušší řešení je zablokovat celé swapování,
pomocí flagu _CRT0_FLAG_LOCK_MEMORY. Takové zamykání naveliko ale
rozhodně není dobrý nápad a hlavně multitáskovému OS může pořádně
zavařit.
Je možné také zamykat jednodlivé části pomocí funkcí
_go32_dpmi_lock_data a _go32_dpmi_lock_code. Allegro
definuje následující makro pro zamykání proměných:
#define LOCK_VARIABLE(x) _go32_dpmi_lock_data((void *)&x, sizeof(x))
Toto makro je nutné zavolat na všechny proměné, které handler
používá. Navíc je nutné zamknout vlastní funkci handleru (a všechny
funkce, co volá). Nejde ale zjistit velikost funkce přes sizeof.
Allegro to řeší tak, že za koncem funkce se napíše další prázdná
funkce pomocí makra:
#define END_OF_FUNCTION(x) void x##_end() { }
A makro pro zamykání funkcí potom zamkne oblast mezi funkcí
jmeno a funkcí xjmeno_end:
#define LOCK_FUNCTION(x) _go32_dpmi_lock_code(x, (long)x##_end - (long)x)
Vlastní handler přerušení vypadá naprosto stejně jako normální
funkce:
void int8(void)
{
unsigned offset = ScreenPrimary+(2*79)+1;
_farsetsel(_dos_ds);
_farnspokeb(offset,1+_farnspeekb(offset));
}
END_OF_FUNCTION(int8)
Metod, jak nainstalovat handler na prerušení je několik.
Normálně by se člověk ještě musel starat o ukládání registrů atd. To
jde obejít pomocí funkce _go32_dpmi_allocate_iret_wrapper. Ta vyrobí
krátkou assemblerovou funkci, která se o to postará. Použití vypadá
asi takto:
_go32_dpmi_seginfo info;
info.pm_offset = int8;
_go32_dpmi_allocate_iret_wrapper(&info);
_go32_dpmi_set_protected_mode_interrupt_handler(8, &info);
...
_go32_dpmi_free_iret_wrapper(&info); /*uvolní paměť použítou wrapperem*/
Ještě by ale bylo nutné se starat o volání původního handleru.
Pokud chcete aby i o to se postarala knihovna, stačí použít
funkci_go32_dpmi_chain_protected_mode_interrupt_vector ta se sama
postará o wrapery a volání:
_go32_dpmi_seginfo pmint;
pmint.pm_selector = _my_cs();
pmint.pm_offset = (unsigned)&int8;
_go32_dpmi_chain_protected_mode_interrupt_vector(8, &pmint);
už všechno zařídí a obrazovka začne pěkně blikat. Ale jenom až
do chvíle, dokud se DPMI nepokusí o swapování. Není totiž pamět
zamknutá. To se udělá takto:
LOCK_FUNCTION(int8);
LOCK_VARIABLE(ScreenPrimary);
Taky je nutné se ujistit, že optimalizace jsou zapnuté a že se
funkce pro přístup do VRAM inlinují.
Z handelrů nelze skákat pomocí longjmp a není dobrý nápad volat
knihovní funkce jako printf.
Samozřejmě, že assemlerové wrappery trošku zdržují. Pokud vám
jde o čas, je možné si všechno napsat ručně. Tak to dělá například
allegro a tak není nic jednoduššího, než se podívat do zdrojáků.
Protože se o interrupty ale stará DPMI, nelze se spolehnout na to,
že budou rychlé. Pokud použijete CSDPMI, je možné takto napsat
i ovladač od pcspeakeru (40000 interruptů do sekundy). Například ale
pod Qemmin DPMI to už nestihne.
výheň