Assembly
A Wikipédiából, a szabad enciklopédiából.
Az assembly (angol: a.m. összerakás, összegyűjtés) a gépi kódhoz (a számítógép „anyanyelvéhez”) legközelebb álló, és így helykihasználás és futási idő szempontjából a leghatékonyabb programozási nyelv.
Az assembly nyelv nem keverendő össze a gépi kóddal: egy assembly nyelvű program végrehajtható utasításai általában egy gépi kódú utasításnak felelnek meg, tehát az assembly egy programozási nyelv, a gépi kód az a tárgykód, amit csaknem minden programozási nyelv előállít végeredményként.
Tartalomjegyzék |
[szerkesztés] Keletkezés
Az első számítógépek programozása úgy történt, hogy a számításokat végző elemek huzalozását változtatták meg.
A számítógépeket a kezdetekben a processzoruk utasításaihoz rendelt számok bevitelével (gépi kóddal) lehetett programozni, melyek ábrázolása eleinte bináris majd később oktális (nyolcas) vagy hexadecimális (tizenhatos) számrendszerben (vagy röviden csak „hexában”) történt.
Állítólag az atombomba-számításokat végző dr. Glennie 1954-ben saját szakállára elkészítette a MARK I első assembler fordítóját.
Az assembly kódhoz tartozó fordítóprogramot assemblernek nevezik. Ez készít a szöveges forrásprogramból egy olyan állományt, amely csaknem teljes egészében megfelel annak a memória képnek, amelyet a processzor végrehajtható programként értelmezni fog.
Az assembler "párja" a disassembler ami a lefordított bináris kódot értelmezi és assembly forráskódú listává alakítja. Alacsonyszintű programozás további kellékei:
- memóriatartalom vizsgáló, "dump" program
- debugger – hibakereső program
- hexa editor – állományok hexadecimális (16-os számrendszerű), ha szükséges utasításszintű módosítását teszi lehetővé
- processzor szimulátor – adott processzorra írt programot "szoftveresen" futtat egy másik számítógépen, esetleg a célgép további hardver elemeit is emulálja
[szerkesztés] Formai jegyek
Az assembly nyelvet utasítások (tényleges kódot hoznak létre) és direktívák, vagy pszeudó utasítások (a fordítás vagy kódgenerálás vezérlése) alkotják.
A különböző processzorcsaládok utasításkészlete bár sokszor jelentősen eltér, de a gyártók által megadott assembly nyelvű szintaxis általában hasonló irányelvekre épül. Az utasítások általában néhány betűs rövidítések, azoknak a gépi utasításoknak a mnemonikjai, amelyek a processzor utasításkészletét alkotják.
A direktívákkal vezérelhető a változók és a program elhelyezése, igazítása, a program belépési pontjának meghatározása. A direktívák hatására léterjövő információk egy részét a fordító szintaktikai ellenőrzéshez használja, más részük a szerkesztő és/vagy a betöltő program számára ad információt. Ez a két program teszi lehetővé, hogy az assembler által készített kódból futtatható program jöjjön létre.
Az assembly forráskód soronként logikailag egy műveletet tartalmaz, egészében nézve a forrás felépítése a következő:
- Deklarációs rész: változók, konstansok, makrók definiálása.
- Végrehajtható, ill kód-rész: utasítások egymásutánja. Egy utasítássorhoz klasszikus esetben egy gépi kódú utasítás (és annak esetleges paraméterei) tartozik, az újabb, úgynevezett makró assemblerekkel definiálhatunk nevesített kódrészleteket is, mintegy „magasabb szintre emelve” ezáltal a nyelvet.
-
- Címke: „megcímkézhetünk” egy utasítást, melyet ugró utasítások célpontjaként, esetleg változók illetve konstansok azonosítására használhatunk.
- Az elvégzendő művelet (operátor) megnevezése (mnemonikja)
- Egy szóköz után az esetlegesen szükséges operandus(ok) (paraméterek), több operandust hagyományosan vesszővel választunk el. Az operandus(ok) előtt vagy után speciális jelölések mutatják a címzési módot.
- Megjegyzések, ezeket szintaxistól függő elválasztó karakter után írva rögzíthetjük.
- Utasítástípusok
Egy processzornak vagy műveletvégzőnek általában 50-80 mnemonikkal megkülönböztetett végrehajtható utasítása van, de ez nagyon eszközfüggő.
[szerkesztés] Jellemző utasítások típusok
[szerkesztés] Memóriakezelő utasítások
Ezek az utasítások az operatív memóriával közvetlenül kapcsolatos olvasó és/vagy író utasításokat valósítják meg. Minden esetben legalább egy operandusuk van (egy címes gépek esetén), ami az adott memóriacímre hivatkozás (bár ez a cím lehet implicit oprandus is, vagyis már adott regiszterben előkészített; nem pedig a műveleti kód adott mezőjében megadott). Az olvasási utasítások a kijelölt memóriacím tartalmat egy kijelölt regiszterbe töltik, míg az írási utasítások a kijelölt regiszeter tartalmát tárolják el a kijelölt memóriacímen; továbbá léteznek memória-memória utasítások is, melyekkel egy memóriarekeszt vagy tartományt mozgatnak át (egyes helyeken string-kezelő utasításokként említik őket). Külön utasítások létezhetnek a byte, szó (2 byte) duplaszó (2 szó) stb. tartományok elérésére, de lehetséges, hogy ezt előtéttagok – más néven prefixum – segítségével kell elérni. Ezek – formailag – az utasítás előtt helyezkednek el, és egy utasítás előtt egyszerre több is lehet belőlük (a maximális szám processzorfüggő). Tehát a memóriareferens utasítások kimenetelét illetve lezajlását különböző előtéttagok befolyásolhatják:
- ismétlő prefix – ismétlés adott feltétel fennállásáig
- operandusméret prefix – az elérni kívánt memóriarekesz nagyságát befolyásolja
- címmódosító prefix – az elérni kívánt memóriarekesz címének értelmezését befolyásolja
- szinkronizációs prefix – a CPU és az FPU közötti szinkronizáció céljára (a 386-os PC-nél már hardveresen működik eme mechanizmus)
- buszlezárás prefix – az adatmódosítás biztonságát (konzisztencia) növeli, például többprocesszoros rendszerekben
[szerkesztés] Regiszterkezelő utasítások
A regiszterek között végezhető műveleteket (regiszterek közötti csere, regiszter jobb-bal oldalának cseréje, speciális regiszterekhez való hozzáférés stb.) valósítják meg.
[szerkesztés] Aritmetikai és logikai utasítások
Ezek az utasítások aritmetikai műveletek (összeadás, kivonás, szorzás, osztás, esetleg ugyanezek lebegőpontos változatai, kiegészítve az un. normalizáló utasítással), illetve elemi logikai műveletek (és, vagy, nem, kizáró-vagy) végrehajtására szolgálnak.
Általában ide sorolják az un. léptetési utasításokat is, mivel ezek – a bináris számrendszer speciális esete miatt – épp 2 egész számú hatványaival való szorzás, osztás műveletet valósítanak meg.
[szerkesztés] Ugró utasítások
Az ugró utasításokkal a program végrehajtásának folyamata vezérelhető. Általában feltétel nélküli és feltételes ugrási utasításokról beszélünk. A feltételek egy regiszter, vagy az úgynevezett program- vagy processzorjelző (flag) adott bitjének/bitjeinek állapotához köthetőek (nulla, nem nulla, pozítív, negatív, kisebb, nagyobb, van túlcsordulás, van átvitel stb). Az egyes utasítások leírásánál pontosan meg van adva, hogy a processzorjelzőket hogyan módosítják a végrehajtás során. (A módosító hatásúak legtöbbször aritmetikai-logikai műveletek vagy kifejezetten erre a célra szánt utasítások.)
Az utasítások operandusa az a hely (vagyis egy cím), ahol a programot folytatni kell, a feltételtől függően. Ha a feltétel nem teljesül, akkor a program a következő utasítást hajtja végre, tehát a végrehajtás folyamata nem módosul. Maga a címoperandus lehet literális – vagyis cimke vagy cimke kifejezéssel megadott cím – vagy regiszterben kijelölt, esetleg valamilyen indirekció. Továbbá a feltételes ugrásoknál előfordul implicit operandusú megoldás, vagyis amikor az ugrási címet nem adjuk meg konkrétan. Ilyen elágazó utasítás esetén a végrehajtás mindig a (program)sorban következő vagy a következő utáni utasításra helyeződik át – az elágazástól függően.
[szerkesztés] Speciális utasítások
Speciális utasítások a megállító (halt, sleep), az üres (nop) utasítás és egyes processzor állapot kezelő utasítások.
[szerkesztés] Megállító utasítás
Megállítja a program futását, és csak "külső beavatkozás" (pontosabban interrupt vagy reset) hatására lép tovább. Egyes processzorok ekkor automatikusan energiatakarékos üzemmódba lépnek.
[szerkesztés] Üres utasítás
Az üres utasítás "nem tesz semmit". Ezt általában időzítésre használják (valamilyen hardver időzítés miatt, elágazott programszálak futási idejének kiegyenlítésére esetleg hardver diagnosztikai célra → címsín vizsgálatához).
[szerkesztés] Processzor állapot kezelő utasítások
A megszakítások kezelésére szolgáló, valamint a ki- és beviteli műveletek, illetve egyéb, a számítógép és/vagy a processzor működését vezérlő utasítások tartoznak ebbe a csoporba.
[szerkesztés] Direktívák
A direktívák az assemblernek szóló utasítások, amelyekből nem keletkezik gépi kód. Néhány fontosabb csoportjuk
- listakészítés engedélyezése/tiltása, pl
PRINT ON ; /360
- Program tulajdonságainak megadása, pl
ORG cím ; Z80: program (végső) címe SEGMENT AT cím ; x86: memóriaterület címe END cimke ; Program vége, egyben belépési pont megadása
- Program szakaszokra bontása, szakaszok tulajdonságainak megadása, pl
snév SEGMENT CODE ; x86: kódszegmens snév DESCT ; /360: 'dummy' adatszegmens snév CSECT READ ; /360: kódszegmens, írásvédett snév AMODE ANY ; /360: csak 31-bites módban futtatható
- Címzéshez használandó regiszterek definiálása, pl
ASSUME CS:code,DS:data,ES:video ; 8086: definíció ASSUME ES:nothing ; 8086: visszavonás USING code,R12 ; /360: definíció DROP R12 ; /360? visszavonás
- Szimbólumok exporja/importja, pl
ENTRY rutin ; /360 EXTRN valtozo ; /360
- Változók definíciója
var EQU érték ; általános var = érték ; /8086
- Makrók definíciója és meghívása, illetve a makrókban használható speciális utasítások (pl elágazás)
[szerkesztés] Assembly nyelvjárások
A különféle architektúráknak, platformoknak, processzoroknak eltérő, egymással általában semmilyen kompatibilitást nem biztosító assembly nyelvei vannak, bár ezen nyelvek alapszerkezete nagyon hasonló.
[szerkesztés] Intel x86 (8086 – 80486 – Pentium)
- Intel szintaxis
- Microsoft Macro Assembler (MASM, Microsoft) – Ingyenesen hozzáférhető a MASM32 projekt részeként [1]
- Turbo Assembler (TASM, Borland) [2]
- Netwide Assembler (NASM, GNU) [3]
- High Level Assembler (HLA, Public Domain) – Randall Hyde-nak, a The Art of Assembly Language című könyv szerzőjének saját assemblere [4]
- Flat Assembler (FASM, GNU GPL) [5]
Példa (C eredeti: *p = n):
MOV EDX,[EBP-16] MOV EAX,[EBP-20] MOV [EDX],EAX
- AT&T szintaxis
- GNU Assembler (GAS, GNU)
Példa (ugyanaz, mint az előbbi):
mov -16(%ebp),%edx mov -20(%ebp),%eax mov %eax,(%edx)
[szerkesztés] MOS-6510
A 6510 (pontosabban, a 65xx, 75xx, 85xx processzorcsalád) a Commodore 64 és társai (mint amilyen a C16, VIC-20 vagy a Commodore 128) processzora. Három általános célú nyolcbites regiszterrel rendelkezik: az A akkumulátorral, valamint az X és Y indexregiszterekkel. Címtartománya 64 kilobyte, de külön címzési módok támogatják az első 256-byte (00xxH címek, az ugynevezett zero page) elérését, a második 256-byte (01xxH címek) pedig rögzítetten a stack helye (ezért az S stack-pointer regiszter is csak nyolc bites).
[szerkesztés] Motorola 68xxx
Ezen processzorok főként a Commodore Amiga gépekben váltak ismertté.
[szerkesztés] Z80
Az Intel 8080 adaptációjaként a Zilog cég által gyártott, és korának legelterjedtebb processzorának nyelve. Számos összetettebb utasítással is rendelkezik. A Z80 assembly nyelve az Intel 8080-asénak egy kibővített és egyben egyszerűsített változata, például az összes adatmozgató utasítás mnemonikja egységesen LD, szemben a 8080 különféle változataival, például:
8080 Z80 MOV A,B LD A,B MOV A,M LD A,(HL) MOV M,A LD (HL),A LDAX B LD A,(BC) STAX B LD (BC),A MVI A,n LD A,n LXI H,nn LD HL,nn LHLD cím LD HL,(cím)
[szerkesztés] IBM System/360
Az IBM mainframe-ei processzorainak nyelve. Tizenhat általános célú 32 bites regisztere van (R0-R15), a használható címtartomány eredetileg 24 bites volt (16 MB címtartomány), a /370-es sorozattól ez 31 bitesre bővült (2 GB címtartomány). A gépi utasítások 2, 4 vagy 6 byte hosszúak, a használható adattípusok: byte, félszó (16 bit), szó (32 bit), pakolt szám (1-16 byte), általános memóriaterület (1-256 byte). Kétcímű gép, azaz egy utasításban két memóriaterület is szerepelhet.
Példaprogram (egy eljárás tipikus kezdete/vége/hívása):
RUTIN STM R14,R12,12(R13) ; regiszterek mentése a hívó ; regisztermentési területére BASR R12,R0 ; bázisregisztrer beállítása USING *,R12 ; assembly direktíva: ; használd a bázisregiszert LA R14,SAVEA ; saját mentési területünk ST R13,4(R14) ; elmentjük a hívóét LR R13,R14 ; használjuk a sajátunkat ... KILÉP L R15,visszatérési_érték L R13,4(R13) ; vissza a hívó mentési területére L R14,12(R13) ; visszatérési cím LM R0,R12,20(R13) ; hívó regisztereinek visszatöltése ; kivéve az R15-öt BR R14 ; visszatérés SAVEA DS 18F ; regisztermentési terület: 72 byte ... HÍVÁS L R15,=A(RUTIN) ; rutin címe R15-be LA R1,paraméterek_címe BASR R14,R15 ; ugrás R15-re, ; visszatérési cím R14-be UTÁNA LTR R15,R15 ; visszaadott érték vizsgálata BZ SIKER ; tipikusan 0=siker
[szerkesztés] A nyelv előnyei és hátrányai
Az összehasonlítást a magas(abb) szintű programozási nyelvekkel szemben kell érteni.
[szerkesztés] Előnyei:
- Kisebb méretű kód, mert a programozó maga dönti el mely utasítások szükségesek az adott probléma megoldásához.
- Az esetek többségében gyorsabb futás jellemzi.
- Könnyebben lehet optimalizálni mind memória használatra, mind futási sebességre.
- Vannak olyan eszközök (pl. mikrokontrollerek), amelyek nagyon kis méretű operatív tárral és memóriával rendelkeznek, és feldolgozási sebességük is igen lassú. A magas szintű nyelvek pedig az assemblyvel szemben többnyire lényegesen nagyobb programkódot állítanak elő, és sem a memória, sem pedig a futási sebességre nem igazán lehet velük optimalizálni.
- Bitműveletek használata egyszerűbb.
- Az általunk megírt eljárások pontos működését ismerjük, könnyebben lehet további speciális célokra úgy módosítani, hogy az ne vonjon maga után aránytalanul nagyobb kódot, memória használatot, vagy hosszabb futási időt, sőt az esetek többségében még javíthatunk is rajta!
- A nyelv határai a rendelkezésre álló hardver(ek) tényeges határáig terjednek, másszóval: a programozó a rendelkezésre álló hardver(ek) összes elérhető funkcióját 100%-ig ki bírja használni.
[szerkesztés] Hátrányai:
- Magát a programozást lényegesen nehezebb elsajátítani.
- A komoly számítást igénylő feladatok elvégzése sok plusz munkát és hozzáértést igényel.
- Vannak olyan eszközök, amelyek olyan alap matematikai műveleteket sem tartalmaznak mint a szorzás vagy osztás. Ezeket többnyire lekezeli a magas szintű programnyelv, ellenben az assemblyvel, ahol ezeket nekünk kell megírnunk mint eljárás.
- Maga a forrás nehezebben átlátható (a sok ide-oda ugrálás miatt).
- Platform-függő, azaz szorosan kötődik az adott processzorhoz és/vagy futtató szoftverhez. Emiatt sokszor csak a program teljes újraírásával lehet átvinni más futtató környezetre. (A környezet jelen esetben mind a processzor típusára, mind a futtató szoftveres környezetre értendő!)
- Az optimalizálási előny hatályát veszti olyan rendszerekben, ahol kimagaslóan sok regiszter áll rendelkezésre a futtató processzorban, vagy sok processzor áll rendelkezésre a feladatok megosztására.
[szerkesztés] Használata napjainkban
Manapság legelterjedtebben mikrokontroller alapú rendszerekben alkamazzák, mivel ott sokszor maga a futtató eszköz és/vagy annak környezete nem teszi lehetővé magas szintű nyelvek alkalmazását vagy akár fordítóprogramok létrehozását (túl kevés támogatott utasítás, kis méretű memória illetve stack, stb.). Megjegyzendő azonban, hogy a mikrokontroller architektúrák robbanásszerű fejlődése eredményeképp erősen terjed a C, C++ nyelv használta is e téren.
"Nagyszámítógépes" környezetben (PC-k, munkaállomások stb.) a hardverillesztő szoftvert és az operációs rendszert programozók írnak assembly nyelven, más esetekben használata ritka, csak a nagyon méret – vagy időkritikus feladatoknál alkalmazzák.
[szerkesztés] Egy példa
Ez a példa (rutin) a Z80-as (Zilog Z80) mikroprocesszor assembly-jében íródott. Bájtok blokkját másolja át a memória (RAM) egyik helyéről (címéről) a másikra. Értelme a Sinclair ZX Spectrum számítógépen az, hogy a másolás a videomemória célterületére történik (a gyakorlatban arra használták, hogy a pl. egy sok számítást igénylő kép előállítása ezen a számítógépen sokáig tartott, s ha annak folyamatát a felhasználó elől el akarták rejteni, akkor először a memória egy használatlan területén állították elő a képet, majd ez – nagyon rövidke idő alatt – be lett másolva a videomemória területére. A megjegyzések pontosvesszővel vannak elválasztva a programkódtól.
ld hl, 16384 ; a hl regiszterpárba a videomemória első bajtjának címe kerül ld bc, 6912 ; a bc regiszterpárba kerül az átmozgatott blokk hossza (a videomemória hossza) bájtban ld de, 40000 ; a de regiszterpárba a forráscím loop ld a, (de) ; az a regiszterbe a forrás értéke kerül ld (hl), a ; a hl regiszterpár értékének a címére a kiolvasott érték kerül inc hl ; célcím növelése eggyel inc de ; forráscím növelése eggyel dec bc ; a hátralévő hossz csökkentése ld a, b ; ez és a következő sor a vizsgálja, hogy a bc regiszterpár or c ; értéke 0 jr nz, loop ; ha nem, ugrás vissza (a loop fejlécű sorra) ret ; visszatérés
Érdekes megfigyelni a tizenhat bites bc regiszterpár nullás értékének vizsgálátát egy nyolcbites regiszterrel, ami két lépésben történik. Először az ld a, b utasítással a b regiszter értéke (mely a bc regiszterpár egyik regisztere) az a regiszterbe töltődik, majd az or c utasítással az a és a c regiszter értékei közt or logikai utasítás hajtódik végre, melynek értéke az a regiszterbe kerül. Ha az eredmény éppen 0, az azt jelenti, hogy a bc regiszterpár értéke is éppen 0 és a jr nz, loop feltételes ugrás nem hajtódik végre (de egyébként igen).
A 8 bites processzorhoz képest fejlett utasításkészletnek köszönhetően az előző példát egyszerűbben is megírhatjuk (és gyorsabb lesz a végrehajtási sebesség is: előző esetben 52 órajel/bájt, következő esetben 21 órajel/bájt):
ld bc,6912 ;bc-ben a hossz bájtokban ld de,40000 ;de-ben a forrásterület címe ld hl,16384 ;hl-ben a célterület címe ldir ;blokkmozgató utasítás automata ismétléssel (21 órajel átvitt bájtonkánt) ret
A játékokhoz korábban a végsőkig igénybevették a processzorok számítási teljesítményét. Pl a következő trükkel újabb 25%-nyi időt nyerhetünk (16,5 órajel/bájt):
ld b,0 ;számláló (0=256) ld de,40000 ld hl,16384 loop ldi ;blokkmozgató utasítás, nem ismétel (16 órajel) ldi . . . ldi ;összesen 27 db ldi utasítás egymás után (27*256=6912) djnz loop ;b-t csökkenti és visszaugrik, ha b<>0 (13 órajel) ret