ÚÄÄÄ        ÚÄÄÄ     ÚÄ       ÚÄ
  ÚÄ  ÚÄ      ÚÄ   ÚÄ   ÚÄÚÄ   ÚÄÚÄ
 ÚÄ    ÚÄ     ÚÄ        ÚÄ  ÚÄÄ  ÚÄ
ÚÄÄ    ÚÄÄ      ÚÄÄ     ÚÄ   Ú   ÚÄ  ASSEMBLY RULEZ
ÚÄÄÄÄÄÄÄÄÄ         ÚÄ   ÚÄ       ÚÄ
ÚÄÄ    ÚÄÄ   ÚÄ    ÚÄ   ÚÄ       ÚÄ     II.rész
ÚÄÄ    ÚÄÄ     ÚÄÄÄÄ    ÚÄ       ÚÄ

 

Szegmentálás

     Most pedig következik a végzet, a szegmentálás elmagyarázása. Ha néhol kissé érthetetlen lennék, azért elnézést kérek, de ezt nehéz így írásban elmagyarázni. Akinek bármi problémája lenne az írjon nekem, a címem benne van az újságban. Mielõtt bárki feladná olvassa végig ezt a részt, mert lehet, hogy csak a végén közlöm, ami nem világos számodra.

     A 8086 procit 20 darab címvezetékkel tervezték, amivel 2^20=1048576 (1MB) byte memória érhetõ el. Egy memóriarekesz eléréséhez 20 bitre van szükség, mert ezek adják a szükséges lehetõségeket az eléréshez. A probléma az volt, hogy a regiszterek 16 bitesek voltak és nem lett volna elõnyös egyes regisztereket 20 bitesre gyártani, mert a proci 16 bitekben gondolkodott és szörnyen lassította volna a 20 bittel dolgozás. Persze megoldás lett volna, ha az egész proci 20 bites, de ez nem lett volna kompatibilis semmivel, ezért kitalálták a szegmentálást.

     A szegmentálás lényege, hogy a memóriát felosztják 64 KB-os részekre. Egy ilyen rendszerben egy byte eléréséhez két dolog kell: a szegmens kezdõcíme és a szegmens kezdetéhez viszonyított mutató (offset). Most úgy tûnhet hogy semmit se csináltunk, csak bonyolítottuk a helyzetet, hisz a szegmens kezdetének tárolásához szintén 20 bit kell. Itt jött a nagy ötlet és azt mondták, hogy szegmens CSAK és KIZÁRÓLAG 16-tal osztható címen kezdõdhet. Ennek nagy elõnye hogy az 1 MB-os tárban így csak 1048576/16= 65536 szegmens lehet. Igenám, de a 65536 a pontosan 2^16 és ez már elfér 16 biten. Ezek után a szegmenscím valójában nem egy memóriacímet fog jelenteni, hanem egy sorszámot amit 16-tal szorozva kapjuk a pontos memóriacímet.

     Most mindezt átültetjük a gyakorlatba. A 20 bites cím hexadecimálisan 5 számjeggyel írható le pl.: 3EC5Ah. Egy szegmens csak 16-tal osztható címen kezdõdhet, ezért a hexa alakja MINDIG 0-ra végzõdik (ugyanis, minden hexa helyiérték 16-szorost ér. Az elsõ helyre 0-t írva biztos, hogy 16-tal osztható lesz a szám, mert minden jegyét 16 többszörösével szoroztuk. Az elsõ hely szorzótényezõje 1, de ha egy tizenhattal osztható számhoz 16-tal nem oszthatót adunk, akkor az eredmény sem lesz osztható, remélem világos?!). De ha ez így van akkor ez a 0 simán elhagyható, elég ha majd a proci odaképzeli. Nekünk pedig marad 4 hexa számjegy, ami 16 bit.

     A szegmens elejéhez viszonyított mutató (offset) is 16 bites, mert így a 64 KB hosszú szegmens minden elemét elérhetjük. A két elembõl áll össze a pontos cím. Ez a következõképp jelöljük:

szegmenscím:offset példul 43DCh:06BF5h.

     Most már csak néhány példa hiányzik a sikerhez. Tegyük fel, hogy érdekel melyik byteot jelenti a következõ cím:
     37C4h:3D6Ah. Ehhez elõször a szegmenscímet kell 16-tal szorozni, ami hexadecimálisan nem más, mint egy 0-t utána írni, így a szegmens kezdõcíme 37C40h. Ehhez kell hozzáadni az offsetet. Ezt a legegyszerûbb számológéppel megtenni. Akinek nem lenne ilyen gépe az elvégezheti papíron is (persze vannak számológép programok amik tudnak ilyet).
     Papíron ugyanúgy kell csinálni, mint tizes számrendszerben, csak a számjegyekbõl több van:
        37C40h
      
 + 3D6Ah
        3B9AAh

   Megvan a keresett érték, ezzel utána elvégezhetjük amit akarunk.

     Kissé bonyolultabb, mikor egy pontos címet akarunk szegmentált formába írni. Ennek egyszerû z oka. A szegmensek 16 byteonként követik egymást és egy-egy szegmens 65536 byte hosszú, ezért egy címet sokféleképpen meg lehet adni. Egy lehetséges módszert mutatok be, van még sok más is.

     Alakítsuk vissza az elõbb kapott értéket, lássuk mi lesz belõle. Elõször is vágjuk le az abszolút cím - az elõbbi példában ez 3B9AAh lett - utolsó számjegyét. Ez a 0Ah számjegy. írjunk a helyére egy 0-t. Ha most jól megnézzük az eredményt (3B9A0h), akkor láthatjuk, hogy pont olyan a szám mint a nullával bõvített szegmenscím. Akkor a 0-t nyugodtan el is hagyhatjuk és a maradékot elnevezzük a szegmenscímnek. Az 0Ah szám pedig az offszet lesz. így a 3B9AAh szegmens:offszet alakban így is felírható:
   3B9Ah:000Ah. Ez persze még csak nem is hasonlít az elõzõ példa kezdõ értékére (37C4h:3D6Ah). Lehet viszont olyan helyzet, ahol egy abszolút címet egy meghatározott szegmensre vonatkoztatva kell felbontani (nem túl gyakran, de akadhat). legyen ez a szegmens a 37C4h. Ekkor az elõbb leírt módon felbontjuk elõször úgy, hogy az offszet érték 0Fh-nál kisebb legyen (az elõzõ módszerrel ez mindig így lesz). Ezután le kell vonni a kívánt szegmenscímet (37C4h) az elõzõ módszerrel kapott szegmenscímbõl (3B9Ah). Az így kapott szám - jelen esetben 3D6h - végéhez hozzátesszük ez elsõ lépésben kapott számot. ezzel megkapjuk a kívánt szegmensben az offszet címet, ami itt 3D6Ah. Ezt összehasonlítva a kiinduló példa értékeivel örömmel mondhatod, hogy visszakaptuk azt (37C4h:3D6Ah).

     Egyébként mikor egy hexa szám végérõl elveszünk egy számjegyet, az a 16-tal való osztás eredménye lesz, az elvett szám pedig a maradék. Amikor hozzáillesztünk egy számjegyet, akkor elõször 16-tal szorzunk, majd hozzáadjuk a számot. Ezzel gondolom nem mondtam túl nagy újdonságot.

 

VEREM

     Ez egy igazán fontos része az assembly programozásnak. Azért írom ezt, mert magas szintû nyelvben ritkán találkozunk a verem használatával. Legfeljebb beállítjuk a méretét oszt kalap. Az assembly ebben is más. Amikor különbözõ rutinokat írunk, akkor gyakran kellhet, hogy a regiszterek értékét ne változtassuk meg, mert a hívó programrésznek esetleg még szüksége van rájuk. Még ennél is fontosabb a verem, mikor rezidens programot írunk, hiszen ekkor egy megszakításon ülünk rajta, és esetleg a legváratlanabb pillanatban aktiválódhatunk (az éppen futó program számára váratlanul) és ekkor a futó proggy nem is tudná elmenteni a regisztereket, míg ez egyszerû rutinoknál lehetséges. Ezért nekünk kell gondoskodni a tárolásról. Az nem jó megoldás, hogy minden elmentendõ regiszterhez egy memóriarészt jelölünk ki, mert ez egyrészt pocsékolja a memóriát, másrészt - és ez a fontosabb, hiszen maga a verem is memóriát használ - a sok címke csak áttekinthetetlenné teszi a programot, mert ha nem definiálunk minden egyes mentéshez - és ilyen mentésre nem csak, rutinok elején lehet szükség, hanem akkor is, ha ideiglenesen másra kell az adott regiszter és más regiszterbe nem tudjuk átmásolni, mert az is kell - külön címkét akkor még hamarabb elkavarodunk, míg a veremnél könnyen meg lehet találni a betevés-kivétel párokat. Huhh, de szép barokkos mondat volt, szép hosszú. Már én se értem.

    Na akkor mi is az a verem? A verem az verem. Vagyis amit beleteszünk, azt addig megõrzi amíg ki nem vesszük. Amit utoljára beletettünk, azt vehetjük ki belõle elõször, mint egy igazi veremnél. Ezt nevezik LIFO (Last In/First Out) típusú veremnek. A fõ eltérés az igazi vermektõl az, hogy ez lefele bõvül. A legelõször beletett adat a tetején "lebeg", a többi pedig ez alá kerül szépen sorjában. A veremben lévõ helyek sorszámozása a verem aljától kezdõdik. A veremmutató (SP - Stack Pointer) mindig az aktuális elsõ szabad helyre mutat a verem tetejétõl számítva. A következõ kis ábra mindent megmutat:

       xxxx  <- ez a verem teteje, ennél feljebb tilos menni
       yyyy  <- egy verembe tétel után ide fog mutatni az SP
       ....
       ....
       xxxx  <- ez a verem alja, ennél lejjebb nem szabad menni, mert akkor más részeket írunk át, esetleg a programot, az adatokat, vagy éppen a DOS-t

   A verem kezelésére két utasítás van a PUSH (beletenni) és a POP (kivenni). Ezentúl minden új utasításnál leírom a használatát is.

   PUSH utasítás (PUSH word onto stack):

     Használat:  PUSH regiszter16/memória16

     Az adat lehet bármilyen 16 bites regiszter, vagy bármilyen címzéssel meghatározott memóriatartalom. A procit nem érdekli, hogy te esetleg csak egy byteot akartál lenyomni a verembe, mindig egy egész szót fog letenni. Erre figyelni kell, nehogy a kivételkor a szó második tagja is visszaállítódjon, mert esetleg az már mást tartalmaz. Miután letette a verembe, 2-vel csökkenti SP-t, hogy jelezze a verem növekedését. A PUSH utasítás speciális változata a PUSHF, ami a FLAG regiszter tartalmát helyezi a lyukba. Ez nagyon hasznos, mikor pl egy összehasonlítás eredményét meg kell õrizni.

   POP utasítás (POP a word from the stack):

     Használat:  POP regiszter16/memória16

     Az utoljára betett szót berakja a 16 bites regiszterbe, vagy a memória egy szó részére. Ezután az SP-t 2-vel növeli, hogy ennek a szónak a helye felszabaduljon. A CS regiszterbe nem lehet POPolni!! A POPnak is van FLAG verziója, a POPF. Ez az SP által címzett szót a verembõl a FLAG-be teszi át.

     És most egy bónusz a késõbbi programokhoz (ha kezdõ vagy átugorhatod):
A 8086 máshogy hajtja végre a PUSH SP utasítást, mint a fejlettebb procik. A 8086 elõször csökkenti SP-t 2-vel, utána leteszi a értékét a verembe, míg a 80286+ procik elõször leteszik SP-t a gödörbe, majd csak ezután csökkentik az értékét. Ezzel a kis különbséggel pillanatok alatt el lehet dönteni, milyen proci van a gépben. Aki PUSH SP-t akarja használni, az a következõ módon teheti, hogy minden procin ugyanazt az eredményt kapja:
      PUSH BP          ; BP lemegy a gödörbe
      MOV BP,SP      ; SP átmegy BP-be
      XCHG BP,[BP]   ; BP-t kicseréljük az SS:BP által címzett bytetal, így SP lenn van a vermen és BP értéke is megmaradt

     No jöjjenek a megszakítások.

Lucifer of ZeroBit