Assemblykielisen ohjelman toiminnan ymmärtäminen edellyttää ymmärrystä siitä, miten tietokoneet on organisoitu ja miten ne näyttävät toimivan hyvin matalalla tasolla. Yksinkertaisimmillaan tietokoneissa on kolme pääosaa:
- keskusmuisti tai RAM-muisti, johon tallennetaan tietoja ja ohjeita,
- prosessori, joka käsittelee tietoja suorittamalla ohjeita, ja
- input ja output (joskus lyhennettynä I/O), joiden avulla tietokone voi kommunikoida ulkomaailman kanssa ja tallentaa tietoja keskusmuistin ulkopuolelle, jotta se voi saada tiedot takaisin myöhemmin.
Päämuisti
Useimmissa tietokoneissa muisti on jaettu tavuihin. Jokainen tavu sisältää 8 bittiä. Jokaisella muistissa olevalla tavulla on myös osoite, joka on numero, joka kertoo tavun sijainnin muistissa. Muistin ensimmäisen tavun osoite on 0, seuraavan tavun osoite on 1 ja niin edelleen. Muistin jakaminen tavuihin tekee siitä tavuosoitteistettavan, koska jokaiselle tavulle annetaan yksilöllinen osoite. Tavumuistien osoitteita ei voi käyttää viittaamaan tavun yksittäiseen bittiin. Tavu on pienin muistin osa, jota voidaan käsitellä.
Vaikka osoite viittaa tiettyyn tavuun muistissa, prosessorit mahdollistavat useiden tavujen käytön peräkkäin. Tämän ominaisuuden yleisin käyttötapa on käyttää joko 2 tai 4 tavua peräkkäin numeron, yleensä kokonaisluvun, esittämiseen. Yksittäisiä tavuja käytetään joskus myös kokonaislukujen esittämiseen, mutta koska ne ovat vain 8 bitin pituisia, niihin mahtuu vain 28 tai 256 erilaista mahdollista arvoa. Käyttämällä kahta tai neljää tavua peräkkäin saadaan eri arvojen lukumäärä 216 , 65536 tai 232 , 4294967296.
Kun ohjelma käyttää tavua tai useita tavuja peräkkäin kuvaamaan jotakin kirjainta, numeroa tai muuta, näitä tavuja kutsutaan objekteiksi, koska ne ovat kaikki osa samaa asiaa. Vaikka kaikki objektit on tallennettu identtisiin tavuihin muistiin, niitä kohdellaan ikään kuin niillä olisi "tyyppi", joka kertoo, miten tavut on ymmärrettävä: joko kokonaislukuna tai merkkinä tai jonain muuna tyyppinä (kuten muuna kuin kokonaislukuna). Konekoodi voidaan ajatella myös tyyppinä, joka tulkitaan käskyinä. Tyypin käsite on hyvin, hyvin tärkeä, koska se määrittelee, mitä asioita objektille voidaan tehdä ja mitä ei voida tehdä ja miten objektin tavuja tulkitaan. Esimerkiksi negatiivista lukua ei saa tallentaa positiivisen luvun objektiin eikä murtolukua saa tallentaa kokonaislukuun.
Osoite, joka osoittaa (on monen tavun objektin osoite), on kyseisen objektin ensimmäisen tavun osoite - tavu, jolla on pienin osoite. Sivuhuomautuksena on tärkeää huomata, että osoitteen perusteella ei voi päätellä objektin tyyppiä tai edes sen kokoa. Itse asiassa et voi edes kertoa objektin tyyppiä katsomalla sitä. Kokoonpanokielisen ohjelman on pidettävä kirjaa siitä, missä muistiosoitteissa mitäkin objekteja on ja kuinka suuria nämä objektit ovat. Ohjelma, joka tekee näin, on tyyppiturvallinen, koska se tekee objekteille vain sellaisia asioita, jotka on turvallista tehdä niiden tyypin perusteella. Ohjelma, joka ei tee niin, ei luultavasti toimi kunnolla. Huomaa, että useimmat ohjelmat eivät itse asiassa nimenomaisesti tallenna objektin tyyppiä, vaan ne vain käyttävät objekteja johdonmukaisesti - samaa objektia käsitellään aina saman tyyppisenä.
Prosessori
Prosessori suorittaa (executes) ohjeita, jotka on tallennettu konekoodina keskusmuistiin. Sen lisäksi, että useimmissa prosessoreissa on mahdollisuus käyttää muistia tallennusmuistina, niissä on myös muutamia pieniä, nopeita ja kiinteän kokoisia tiloja, joihin tallennetaan kohteita, joiden kanssa parhaillaan työskennellään. Näitä tiloja kutsutaan rekistereiksi. Prosessorit suorittavat yleensä kolmenlaisia ohjeita, vaikka jotkin ohjeet voivat olla näiden tyyppien yhdistelmiä. Alla on esimerkkejä kustakin tyypistä x86-kokoonpanokielellä.
Ohjeet, jotka lukevat tai kirjoittavat muistia
Seuraava x86-kokoonpanokielen käsky lukee (lataa) kahden tavun objektin osoitteessa 4096 olevasta tavusta (0x1000 heksadesimaalisessa muodossa) 16-bittiseen rekisteriin nimeltä 'ax':
Tässä kokoonpanokielessä numeron (tai rekisterin nimen) ympärillä olevat hakasulkeet tarkoittavat, että numeroa on käytettävä käytettävän datan osoitteena. Osoitteen käyttämistä datan osoittamiseen kutsutaan indirektioksi. Seuraavassa esimerkissä ilman hakasulkeita toiseen rekisteriin, bx, ladataan itse asiassa arvo 20.
Koska indirektiota ei käytetty, rekisteriin laitettiin itse arvo.
Jos operandit (asiat, jotka tulevat muistisäännön jälkeen) ovat käänteisessä järjestyksessä, käsky, joka lataa jotain muistista, kirjoittaa sen sijaan sen muistiin:
Tässä tapauksessa muistiin osoitteessa 1000h tulee bx:n arvo. Jos tämä esimerkki suoritetaan heti edellisen esimerkin jälkeen, kaksi tavua osoitteissa 1000h ja 1001h on kahden tavun kokonaisluku, jonka arvo on 20.
Ohjeet, jotka suorittavat matemaattisia tai loogisia operaatioita.
Jotkin ohjeet tekevät asioita kuten vähennyslasku tai loogisia operaatioita kuten ei:
Aiemmin tässä artikkelissa esitetty konekoodiesimerkki olisi tämä assembler-kielellä:
Tässä 42 ja ax lasketaan yhteen ja tulos tallennetaan takaisin ax:iin. x86-kokoonpanossa on myös mahdollista yhdistää muistin käyttö ja matemaattinen operaatio tällä tavoin:
Tämä käsky lisää 1000h:n kohdalle tallennetun kahden tavun kokonaisluvun arvon ax:iin ja tallentaa vastauksen ax:iin.
Tämä käsky laskee rekisterien ax ja bx sisällön tai ja tallentaa tuloksen takaisin ax:iin.
Ohjeet, jotka päättävät, mikä on seuraava ohje.
Yleensä ohjeet suoritetaan siinä järjestyksessä, jossa ne näkyvät muistissa, eli siinä järjestyksessä, jossa ne on kirjoitettu assembly-koodiin. Prosessori vain suorittaa ne yksi toisensa jälkeen. Jotta prosessorit voisivat tehdä monimutkaisia asioita, niiden on kuitenkin suoritettava eri ohjeita sen mukaan, mitä tietoja niille on annettu. Prosessoreiden kykyä suorittaa erilaisia ohjeita riippuen jonkin asian tuloksesta kutsutaan haarautumiseksi. Ohjeita, jotka päättävät, mikä on seuraava ohje, kutsutaan haarautumisohjeiksi.
Tässä esimerkissä oletetaan, että joku haluaa laskea maalin määrän, jonka hän tarvitsee maalatakseen neliön, jonka sivun pituus on tietty. Mittakaavaedun vuoksi maalikauppa ei kuitenkaan myy hänelle pienempää määrää maalia kuin mitä tarvitaan 100 x 100 neliön maalaamiseen.
Maalattavan neliön pituuden perusteella tarvittava maalimäärä lasketaan seuraavalla tavalla:
- vähennetään 100 sivun pituudesta
- jos vastaus on pienempi kuin nolla, asetetaan sivun pituudeksi 100.
- kerrotaan sivun pituus itsellään
Algoritmi voidaan ilmaista seuraavalla koodilla, jossa ax on sivun pituus.
mov bx, ax sub bx, 100 jge continue mov ax, 100 continue: mul ax
Tässä esimerkissä esitellään useita uusia asioita, mutta kaksi ensimmäistä ohjetta ovat tuttuja. Ne kopioivat ax:n arvon bx:ään ja vähentävät sitten bx:stä 100.
Yksi tämän esimerkin uusista asioista on nimeltään label, joka on käsite, joka esiintyy assembly-kielissä yleensä. Merkinnät voivat olla mitä tahansa, mitä ohjelmoija haluaa (paitsi jos se on käskyn nimi, mikä sekoittaisi assemblerin). Tässä esimerkissä merkintä on 'continue'. Assembler tulkitsee sen käskyn osoitteeksi. Tässä tapauksessa se on mult axin osoite.
Toinen uusi käsite on liput. x86-prosessoreissa monet käskyt asettavat prosessoriin "lippuja", joita seuraava käsky voi käyttää päättääkseen, mitä tehdä. Tässä tapauksessa, jos bx oli pienempi kuin 100, sub asettaa lipun, joka kertoo, että tulos oli pienempi kuin nolla.
Seuraava käsky on jge, joka on lyhenne sanoista 'hyppää jos suurempi tai yhtä suuri kuin'. Se on haarautumiskäsky. Jos prosessorin lippujen mukaan tulos on suurempi tai yhtä suuri kuin nolla, prosessori hyppää seuraavaan käskyyn sen sijaan, että siirtyisi seuraavaan käskyyn, joka on mul ax.
Tämä esimerkki toimii hyvin, mutta useimmat ohjelmoijat eivät kirjoittaisi sitä. Vähennyskäsky asetti lipun oikein, mutta se myös muuttaa arvoa, johon se vaikuttaa, mikä edellytti ax:n kopioimista bx:ään. Useimmat assembler-kielet sallivat vertailukomennot, jotka eivät muuta mitään niille välitettyjä argumentteja, mutta asettavat silti liput oikein, eikä x86-assembleri ole poikkeus.
cmp ax, 100 jge continue mov ax, 100 continue: mul ax
Nyt sen sijaan, että vähennettäisiin 100 ax:stä, katsottaisiin, onko luku pienempi kuin nolla, ja määritettäisiin se takaisin ax:iin, ax jätetään ennalleen. Liput asetetaan edelleen samalla tavalla, ja hyppy tehdään edelleen samoissa tilanteissa.
Tulo ja lähtö
Vaikka syöttö ja tulostus ovatkin olennainen osa tietojenkäsittelyä, niitä ei voi tehdä yhdellä ainoalla tavalla assembler-kielellä. Tämä johtuu siitä, että se, miten I/O toimii, riippuu tietokoneen kokoonpanosta ja käyttöjärjestelmästä, eikä pelkästään siitä, millainen prosessori siinä on. Alla olevassa esimerkkiosiossa Hello World -esimerkki käyttää MS-DOS-käyttöjärjestelmän kutsuja ja sen jälkeinen esimerkki BIOS-kutsuja.
I/O on mahdollista tehdä kokoonpanokielellä. Assembler-kielellä voidaan yleensä ilmaista mitä tahansa, mitä tietokone pystyy tekemään. Vaikka assembler-kielessä on käskyjä lisätä ja haarautua, jotka tekevät aina saman asian, assembler-kielessä ei kuitenkaan ole käskyjä, jotka tekevät aina I/O:n.
Tärkeää on huomata, että tapa, jolla I/O toimii, ei kuulu mihinkään kokoonpanokieleen, koska se ei ole osa prosessorin toimintaa.