int21h

DOS a chráněný režim - 2. díl

Ve druhém díle si povíme o chráněném módu na hardwarové úrovni. Zaměřime se na rozdíly v chování procesoru mezi reálem a protektem. Napřed ale doplním dvě věci z minula.
Fungování extenderu HDPMI32 v prostředí s emulací Soundblasteru můžeme vylepšit pomocí proměnné prostředí HDPMI. Je dobré dát si to souboru AUTOEXEC.BAT toto: set HDPMI=512
Manuál říká, že tento parametr zajišťuje, že se tabulky LDT a IDT nebudou vytvářet na vršku paměti, ale že budou níže (patrně v dosahu běžného nastavení pracovního selektoru). Je možné, že zvuk pod CWSDPMI nefunguje proto, že tento DPMI server vytváří LDT a IDT na vrcholu paměti, na což má sice svaté právo, ale nerozumí tomu ovladač (resp. emulátor) soundlableru u PCI karet od Creative Labs.

A za druhé bych se chtěl zmínit o volání protektových procedur z reálu. V praxi je to callback handleru myši. Jde o to, že ovladač myši poskytuje službu INT33h/AX=0Ch - nainstaluj ovladač události. Po každé ze sledovaných událostech myši se zavolá procedura, která ji nějak ošetří. Tato funkce mimo jiné řeší zapeklitý problém jak ve VESA režimech zobrazovat kurzor myši, aniž by byl třeba polling.
Nápověda FP poskytuje příklad použití:
uses crt,go32;
{$ASMMODE ATT}
const mouseint  =  $33;
var mouse_regs     :  trealregs;  external  name '___v2prt0_rmcb_regs';
    mouse_seginfo  :  tseginfo;

var mouse_numbuttons  :  longint;
    mouse_action  :  word;
    mouse_x,  mouse_y, mouse_b  :  Word;
          userproc_installed  :  Longbool;
          userproc_length  :  Longint;
          userproc_proc  :  pointer;

procedure  callback_handler;  assembler;
asm
pushw  %ds
pushl  %eax
   movw  %es,  %ax
   movw  %ax,  %ds
 cmpl  $1,  USERPROC_INSTALLED
 jne  .LNoCallback
pushal
   movw  DOSmemSELECTOR,  %ax
   movw  %ax,  %fs
 call  *USERPROC_PROC
popal
.LNoCallback:

popl  %eax
popw  %ds
pushl  %eax
   movl  (%esi),  %eax
   movl  %eax,  %es:  42(%edi)
   addw  $4,  %es:46(%edi)
popl  %eax
iret
end;
procedure  mouse_dummy;  begin  end;

procedure  textuserproc;
begin
mouse_b  :=  mouse_regs.bx;
mouse_x  :=  (mouse_regs.cx  shr  3)  +  1;
mouse_y  :=  (mouse_regs.dx  shr  3)  +  1;
end;

procedure  install_mouse(userproc  :  pointer;  userproclen  :  longint);
var  r  :  trealregs;
begin
r.eax  :=  $0;  realintr(mouseint,  r);
if  (userproc  <>  nil) then
    begin userproc_proc:=userproc; userproc_installed:=true; userproc_length:=userproclen; lock_code(userproc_proc, userproc_length); end
    else begin userproc_proc:=nil; userproc_length:=0; userproc_installed:=false; end;
lock_data(mouse_x, sizeof(mouse_x)); lock_data(mouse_y, sizeof(mouse_y)); lock_data(mouse_b, sizeof(mouse_b)); lock_data(mouse_action, sizeof(mouse_action));
lock_data(userproc_installed, sizeof(userproc_installed)); lock_data(userproc_proc, sizeof(userproc_proc));
lock_data(mouse_regs, sizeof(mouse_regs)); lock_data(mouse_seginfo, sizeof(mouse_seginfo));
lock_code(@callback_handler,longint(@mouse_dummy)-longint(@callback_handler));
get_rm_callback(@callback_handler,  mouse_regs,  mouse_seginfo);
r.eax:=$0c; r.ecx:=$7f; r.edx:=longint(mouse_seginfo.offset); r.es:=mouse_seginfo.segment;
realintr(mouseint,r); r.eax:=$01; realintr(mouseint,r);
end;

procedure  remove_mouse;
var  r  :  trealregs;
begin
r.eax  :=  $02;  realintr(mouseint,  r);
r.eax  :=  $0c;  r.ecx  :=  0;  r.edx  :=  0;  r.es  :=  0;realintr(mouseint,  r);
free_rm_callback(mouse_seginfo);
if  (userproc_installed)  then
    begin unlock_code(userproc_proc,  userproc_length);userproc_proc  :=  nil;userproc_length  :=  0;userproc_installed  :=  false;end;
unlock_data(mouse_x,  sizeof(mouse_x));unlock_data(mouse_y,  sizeof(mouse_y));unlock_data(mouse_b,  sizeof(mouse_b));unlock_data(mouse_action,  sizeof(mouse_action));
unlock_data(userproc_proc,  sizeof(userproc_proc));unlock_data(userproc_installed,  sizeof(userproc_installed));
unlock_data(mouse_regs,  sizeof(mouse_regs));unlock_data(mouse_seginfo,  sizeof(mouse_seginfo));
unlock_code(@callback_handler,longint(@mouse_dummy)-longint(@callback_handler));
fillchar(mouse_seginfo,  sizeof(mouse_seginfo),  0);
end;

begin
install_mouse(@textuserproc,  400);
Writeln('Press  any  key  to  exit...');
while  (not  keypressed)  do  begin
       gotoxy(1,  wherey);
       write('MouseX  :  ',  mouse_x:2,  '  MouseY  :  ',mouse_y:2,'  Buttons  :  ',  mouse_b:2);
       end;
remove_mouse;
end.
Vidíte, že bezprostředně od ovladače přebírá řízení procedura Callback_handler, která volá TextUserProc
Vše je zdá se jasné. Jenže až začnete modifikovat proceduru TextUserProc a doplníte zobrazování kurzoru, tak začnou problémy. Zřejmě totiž použijete něco jako PutImage a GetImage a patrně je napíšete v assembleru.
Jenže dost pravděpodobně se vám začne program hroutit. Až nyní jsem přišel na to proč. Procedury PutImage a GetImage musí být napsány buďto celé v pascalu nebo celé v assembleru. Nic mezi tím!
Moje PutImage totiž vypadlo asi takhle:
Procedure PutImage(kurzor:pointer;sirka,vyska:longint;x,y:longint);
begin
if not VyresClipping(kurzor,sirka,vyska,x,y) then Exit; {kurzor, sirka a vyska jsou volane pres VAR a procedura je nalezite upravi}
asm
...
end;
end;
Zkrátka je potřeba napsat PutImage v assembleru celý včetně clippingu. Tedy:
Procedure PutImage(kurzor:pointer;sirka,vyska:longint;x,y:longint);assembler;
asm
...
end;
Alternativou je rozdělit clipping a vykreslování do dvou různých procedur:

Procedure _VykresliTo(kurzor:pointer;sirka,vyska:longint;x,y:longint);assembler;
asm
...
end;

Procedure PutImage(kurzor:pointer;sirka,vyska:longint;x,y:longint);
begin
VyresClipping(kurzor,sirka,vyska,x,y) then Exit;
_VykresliTo(kurzor,sirka,vyska,x,y);
end;

Tak. Tím jsme vyřešili resty z minula a vrhneme se na zákulisí protektu.
Zeptejme se takto: "Jak se od sebe liší reálný a chráněný mód?"
Začátečník asi odpoví že:
1) v protektu lze adresovat celou paměť počítače, zatímco v reálu jen 1MB
2) v reálu 16bitová adresace paměti, a v protektu 32bitová

Ve skutečnosti jsou oba tyto body sporné. Nejpodstatnější je, že:
V protektu je zapnutý systém ochrany paměti, oproti reálu, kde je vypnutý
Před tím, než budu psát o ochraně paměti se zastavíme u bodů 1 a 2.
Víme, že lineární adresa se v protektu spočítá jako SEGMENT * 16 + OFFSET
V assembleru se k paměti přistupuje pomocí segmentových registrů. Takhle by třeba vypadala procedura DoPameti:
Procedure DoPametiB(segm:word;offs:word;b:byte);assembler;
asm
mov ax,segm
mov ds,ax
mov si,offs
mov al,b
mov ds:[si],al
end;
Jasné. Položme si ale otázku. Co se stane, jestliže místo registru DI použiju celé EDI ?
Připusťme, že TP umí 32bitové instrukce:
Procedure DoPametiB(segm:word;offs:longint;b:byte);assembler;
asm
mov ax,segm
mov ds,ax
mov esi,offs
mov al,b
mov ds:[esi],al
end;
Turbo pascal ale 32bitové instrukce neumí. Je třeba to zapsat takto:
Procedure DoPametiB(segm:word;offs:longint;b:byte);assembler;
asm
mov ax,segm
mov ds,ax
mov al,b
db 66h;mov di,offs.word
db 67h;mov ds:[si],al
end;

Bohužel při testování zjistíme, že jakmile je offset větší než 64KB, tak se program zhroutí, a to i když nedojde k překročení 1MB hranice. To vypadá jako neotřesitelné potvrzení bodu 2
Všimněte si ale, že funkce DOSu INT21h/AH=49h umožmuje alokovat bloky větší než 64KB - umí vytvořit bloky až do velikosti 1 048 560 bajtů. To, že je není možné celé uadresovat z jednoho segmentu je sice pravda, ale pokud přičítáme segmentovou část adresy, tak ho přece jen uadresujeme.
Zamysleme se dále. Proč přesně nelze adresovat pomocí EDI? Teoreticky by to totiž jít mělo... Dojde totiž k překročení tzv. segmentační jednotky, která je defaultně nastavena právě na 64KB.
Segmentační jednotku ale lze změnit a umožnit tak adresaci pomocí 32bitových registrů. MOžná jste už někdy slyšeli pojmy Flat mode nebo Unreal mode. Tak to je ono. Adresace 4 GB z reálného módu.
Dělá se to změnou systémového registru CR0. Instrukce manipulující s registrem CR0 ale patří mezi privilegované instrukce, což znamená, že se nedají použít v režimu V86. Vo je to režim V86 si povíme za chvíli, ale podstatné je, že je aktivní pod běžícími windows a taky pod ovladačem EMM386.
Prakticky každý používá buďto windows nebo EMM386 (nebo DOSemu), takže praktické využití je mizivé. Nicméně používá to několik starých her (myslím že třeba Ultima IV a V) a některá dema.
Přesto se ale s flat módem můžeme setkat zprostředkovaně. Manipulací se segmentační jednotkou totiž využívá ovladač HIMEM.SYS pro ovládání paměti XMS. Pokud tedy kopírujete něco do XMS bufferu, ovladač přenastaví segmentační jednotku, adresací [ES:EDI] zkopíruje vaše data a vrátí ji do původního stavu. HIMEM.SYS se totiž při bootování zavádí dříve než EMM386, a proto mu EMM386 tyhle čachry dovolí.

Systém ochrany

Systém ochrany je navržen pro multitasková prostředí. V multitaskových prostředí je správce procesů(obvykle jádro OS, nicméně ne vždy) a potom jednotlivé programy, které běží pod správcem. Podstatný prvek funkce chráněného režimu je systém práv. Různé procesy mají rozdílná práva. Procesor rozlišuje čtyři úrovně práv procesů. Nejvyšší je tzv. Ring 0, nejnižší Ring 3. Většinou se ale využívají jenom úrovně 0 (správce multitaskingu) a 3 (všechno ostatní)
Při prvotním přepnutí do protektu je procesor v režimu Ring 0. Do tohoto terénu se zavede OS (potažmo správce multitaskingu), který potom spouští jednotlivé programy v Ringu 3. Windows prý některé ovladače pouštějí v Ringu 1, ale to je výjimka. Podstatné je, že některé instrukce procesoru jsou dostupné jenom z Ringu 0. Týká se to instrukcí řízení chráněného módu a do jisté míry i instrukcí OUT, IN a INT.
Při spouštění úlohy SPRÁVCE načte z disku program, přečte jeho hlavičku a alokuje pro něj kus paměti.
Co to znamená "alokuje kus paměti"?
Především pro něj vytvoří deskriptor. Deskriptor je to datová struktura podobná pascalovskému typu record. Má dosti složitou strukturu, nicméně v podstatě obsahuje tyto údaje: báze - adresa začátku paměťového bloku, limit - velikost paměťového bloku, přístupová práva a příznaky
Báze a limit jsou srozumitelné. Zajímavá jsou práva. Pokud je tam např. hodnota 3 (ve skutečnosti je to komplexnější údaj - ne jenom jedno čísílko), tak program běžící v adresovém prostoru popsaném tímto deskriptorem nemůže volat privilegované instrukce. Taky nemůže sahat do cizích deskriptorů se srtejnými nebo vyššími právy(proces v deskriptoru s právem Ringu 0(tedy SPRÁVCE) může sahat do deskriptorů s úrovní 1-3).
Potom správce spouštěnému programu vytvoří datovou strukturu TSS(task segment state) Sem při přepínání úloh procesor zapíše aktuální stav všech registrů, seznam všech alokovaných lokálních deskriptorů (tzn. všech alokovaných kusů paměti) a seznam povolených a zakázaných portů.
S těmi porty je to zajímavé. Pokud je daný port pro program povolený, tak do něj instrukce IN a OUT přistupují stejně jako v reálném módu. Pokud je ale zakázaný, tak se vyvolá přerušení INT0Dh na kterém sedí SPRÁVCE. On rozhodne, co se s tím udělá. Na tomto proncipu funguje tzv. virtualizace hardware. Rovněž tak směšování zvuků z různých programů zajišťují windows tímto mechanismem.
Přerušení 0Dh není voláno jenom při pokusu o přístup k nepovolenému portu. Ve skutečnosti je voláno vždy, když nastane nějaká nesrovnalost s právy procesů. Třeba při pokusu o čtení z kusu paměti, který není můj nebo při zavolání privilegované instrukce z Ringu 3, atd.
Ošetřování privilegovaných instrukcí přes záchranou síť 0Dh je dobrá věc, ale zase platí, že různí správci povolí různé věci. Obzvlášť tragické je to v DOSu, u kterého nikdy nevíte, jaký správce je aktivní.
Teď je čas, abychom si vyjasnili, co to ten SPRÁVCE je. V případě windows nebo Linuxu je jím jádro systému. V případě DOSu buďto DPMI server nebo EMM386.
O DPMI byla řeč minule, takže se zmíním o EMM386. Ten rovněž řídí běh programu ve chráněném módu, ale nemá DPMI rozhraní. EMM386 místo toho nabízí rozhraní VCPI, což je vlastně předchůdce DPMI. Na rozdíl od něho ale nemá žádnou podporu multitaskingu. Nicméně to hlavní, co EMM386 poskytuje je režim V86.
Pokud jste totiž v DOSu a máte nainstalovaný EMM386, tak se nacházíte ve chráněném módu!
Jste v protektu! EMM386 ale řídí hardwarovou emulaci reálného módu. V módu V86 probíhá konverze realmódové adresace segment:offset na realmódový deskriptor:realseg*16+realoffs
Abych řekl pravdu, tak nevím, jestli tuto konverzi provádí ovladač EMM386 nebo sám procesor, ale všechno pracuje překvapivě hladce. Jenom ve výjimečných případech zpozorujete, že nejste v reálu. Stane se vám to při volání privilegovaných instrukcí. Sice se všechno tváří jako real(při kterém smíte používat privilegované instrukce), ještě neznamená, že jste doopravdy v Ringu 0. Ve skutečnosti běží Ring 3.
Proto můžete zjistit, že ačkoliv nějaké programy bez EMM386 normálně běhají, tak s ním už ne.
Velmi neradostné je, že poslední verze microsoftího EMM386 přišla s MS-DOSem 7.0 - tedy s Windows 95. Jenže dnešní procesory mají některé privilegované instrukce, které procesory z roku 1995 neměly (a nedělejme si iluze, že poslední MS-EMM386 byl doopravdy naprogramován v tomto roce), takže je nemůžou pomocí INT0Dh zpracovat a emulovat, ačkoliv by to nebyl problém.
Krásně je to vidět na FreeDOSu. Ten je ve vývoji dodnes a jeho EMM386 nové privilegované instrukce zná. Můžete pod ním třeba volat RDMSR a WRMSR - velmi užitečné instrukce, které mimo jiné nastavují cache. (při dobře nastavené cache se přístupy do LFB čtyřnásobně zrychlí(na mém stroji)) Starý MS-EMM386 to ale nevezme a kousne se počítač.
Zvláštní moment nastává, je-li zaveden EMM386 a pokusíme-li se zavést DPMI server. Ten si při zavádění nejprve ohmatá prostředí. Pokud jsme v reálu(tedy bez EMM386), tak se normálně zavede a čeká. Pokud jsme v protektu(s EMM386) tak ho nějak donutí, aby mu předal správu chráněného módu. Asi to udělá přes rozhraní VCPI (staré verze FD-EMM386 totiž VCPI neuměly a DPMI server se pod nimi sespustil)

Povídání o EMM386 byla ale jenom odbočka. Vraťme se k deskriptorům a k protektové adresaci. Realmódová adresace je jasná. Jakým způsobem procesor zpracuje segmentový registr je jasné. Ale v protektu?
V protektu se hodnotám, ukládaným do segmentových registrů (DS,ES,FS,GS) neříká segmenty, ale selektory. Procesor je zpracovává úplně jinak. Neprovádí žádné násobení šestnácti, ale z registru LDTR přečte adresu tabulky LDT.
LDT znamená Local descriptor table a jsou tu uvedeny všechny deskriptory právě aktivního programu.
Pokud například máme DS=3 a čteme mov eax,ds:[esi] tak se procesor podívá do třetího deskriptoru uvedeného v tabulce LDT, zjistí si z něho, na jakou adresu ukazuje (položka base), zkontroluje práva a když to souhlasí, tak k BASE přičte offset (v tomto případě z ESI) a tím pádem má konečně adresu.

Kromě registru LDTR(ukazatel na LDT) má procesor ještě registry GDTR(ukazatel na GDT) a IDTR(ukazatel na IDT)
Tabulka GDT Global descriptor table vypadá skoro stejně jako LDT, akorát je jenom pro potřeby pana správce. Nic bližšího o ní nevím.
IDT znamená Interrupt descriptor table a DOSovému programátorovi je možná nejsrozumitelnější. Souvisí s voláním přerušení. V reálném módu začíná na adrese 0000:0000 tabulka vektorů přerušení dlouhá 1024 bajtů (256*4). Když třeba zavoláme přerušení 10h (tedy 16d), tak se procesor podívá na adresu 0000:16*4, přečte z ní vektor přerušení a skočí na něj.
V protektu ale tabulka vektorů(neboli IDT) nemusí(ani nemůže) být nutně na 0000:0000, ale je tam, kam ukazuje registr IDTR.
Registry LDTR, GDTR a IDTR pochopitelně nastavuje správce (pomocí instrukcí SLDT, SGDT a SIDT).

Dovolte mi vrátit se ještě k EMM386. Proč vlastně máte nainstalovaný tento ovladač?
Dobrý důvod je ovladač soundblasteru u karet SB 128PCI nebo SBLive! či podobných. Tady se využívá virtualizace hardware. Další a snad častější důvod je využití paměti UMB, což uvolní část konvenční paměti, za kterou se mezi námi dosaři platí zlatem.
Co to pamět UMB ale je? Vězte, že tento pojem v reálu neexistuje. Pro použití UMB potřebujete být v módu V86 - tedy v protektu.
Bill Gates na začátku 80. let pravil: "640KB paměti by mělo stačit každému."
Ale, ještě než to dořekl, se začaly na základní desky dávat rozšiřující karty s dalšími paměťovými čipy. Tato přídavná paměť se namapovala někam do adresového prostoru stejně jako se mapuje videopaměť. To byla tzv. hardwarová paměť EMS. Po zavedení řady 286 se ale paměťové čipy dávaly přímo do základní desky hned vedle základních 640KB (tak se to ostatně dělá dodnes).
Nastal problém, jak k této paměti přistupovat. Pro zachování kompatibility s rozšiřujícími paměťovými kartami se standard EMS mírně upravil a od té doby máme softwarovou paměť EMS
Paralelně s tím vznikl ještě standard XMS, který je jednodušší a podle mě lépe navržený. Práce s XMS se velice podobá práci se soubory na disku. Jak už jsme si řekli, ovladač XMS (obvykle HIMEM.SYS) používá techniku flat mode.
EMS paměť funguje spíše jako videopaměť. Do určitého adresového prostoru - neboli adresového okna (nejčastěji B000:0000 - B000:FFFF) se namapuje kus paměti nad 1MB. Realmódový program tedy přistupuje k "vysoké paměti" stejně jako by šlo o konvenčku. Samotný přístup je sice jednoduchý, ale prvotní nastavení okna je dost složité.
Uživatelské aplikace si potom vytvářejí další adresní okna, do kterých se mapuje vysoká paměť. Ta se v tomto případě už nenazývá UMB (tento pojem je vyhražen jenom pro okno na B000), ale EMS.
Je zřejmé, že pro mapování vysoké paměti do malého realmódového okna, je nutné být v protektu, resp. v jeho variantě V86. Celá tahle pochybná šaškárna okolo UMB/EMS je hodně komplikovaná, jak z hlediska chudáka EMM386, tak i programátora. Pokud programujete pro real a potřebujete vysokou paměť, tak raději použijte standard XMS.

Myslím, že tento text pro přehled o funkci chráněného módu stačí. Jestli byste měli nějaké nápady, čím ho doplnit anebo jste našli nějaké nepravdy (nebudeme si zastírat, že se občas pohybuju na sakra tenkém ledě), tak napište na vzkazník na hlavní stránce nebo mi napište email.
2006-11-30 | Laaca
Reklamy:
„Kdyby se mužům dostávalo manželek, jakých si zasluhují, měli by zatraceně těžký život.“ Oscar Wilde