OSA II. TIETOKONEJÄRJESTELMÄT¶
Kurssin tässä osassa tutustumme suorittimen (mikroprosessori, keskusyksikkö, CPU) että tietokonejärjestelmän arkkitehtuuriin ja toimintaan. Kurssiosuuden suorittamisesta on kerrottu tarkemmin materiaalin etusivulla.
Mutta, ennenkuin sukelletaan syvemmälle suorittimiin ja tietokonejärjestelmiin, palataan hetkeksi ajassa taaksepäin..
Ohjelmoitavan tietokoneen historiaa¶
Nykyaikaisen ohjelmoitavan tietokoneen historian merkkipaaluja.
- 100-200 eaa.: antiikin kreikkalaisten kehittämä Antikytheran kone, jolla voitiin mekaanisesti laskea taivaankappaleiden liikkeitä.
- 1670-80-luvut: Yleisnero Gottfried Wilhelm Leibniz kehitti neljään perusoperaatioon (yhteen, vähennys-, kerto- ja jakolasku) pystyvän mekaanisen laskukoneen mallin ja myöhemmin laajensi mallia niin että se pystyi ratkaisemaan algebrallisia yhtälöitä. Hän myös rakensi Leibnizin pyörän joka oli ensimmäinen laajalti käytetty mekaaninen laskin. Leibniz on myös binäärilukujärjestelmän isä!
- 1804: Joseph-Marie Jacquard esitteli kutomakoneen, jolla voitiin kankaisiin kutoa reikäkorteilla syötettyjä kuvioita.
- 1834: Charles Babbage esitteli suunnitelman analyyttisen koneen rakentamiseksi, joka pystyisi suorittamaan erilaisia laskutoimituksia konekielellään. Tosin tuolloin käytössä ollut teknologia, höyryvoima ja hammasrattaat, ei oikein soveltunut monimutkaisen laskimen rakentamiseen.
- 1843: Ada Lovelace esitteli Babbagen koneelle suunnitellun algoritmin (=tietokoneohjelman), josta syystä häntä pidetään ensimmäisenä tietokoneohjelmoijana.
- 1847: George Boole esitteli totuusarvoihin perustuvan algebransa, joka loi perustan myöhemmin tietokoneissa yleistyneelle digitaalilogiikalle.
- 1936: Matemaatikko Alan Turing esittelee teoreettisen mallin yleiskäyttöisestä laskimesta.
- 1938: Claude Shannon esitti diplomityössään miten Boolen algebraa voitaisiin käyttää digitaalisen logiikan toteutukseen. Shannon tunnetaan myös informaatioteorian isänä, johon suurelta osin nykyaikainen tietoliikennetekniikka perustuu.
- 1941: Konrad Zuse esitteli ensimmäisen ohjelmoitavan automaattisen laskukoneen.
- 1942: Esiteltiin Atanasoff-Berry-tietokone, jota pidetään ensimmäisenä sähköisesti toimivana tietokoneena, mutta se ei ollut (uudelleen)ohjelmoitava eli yleiskäyttöinen.
- 1944: Howard Aiken rakensi IBM:n kanssa sähkömekaanisen laskimen (Harvard Mark I) joka toteutti Babbagen suunnitteleman laskukoneen toiminnallisuuden.
- 1945: Pennsylvanian yliopistossa esiteltiin yleiskäyttöinen ENIAC-tietokone, joka suoritti aritmeettisia operaatioita kymmenjärjestelmän luvuilla.
- 1945: Konrad Zuse julkaisi myös ensimmäisen korkean tason ohjelmointikielen Plankalkul:n.
- 1949: Esiteltiin yleiskäyttöinen EDVAC-tietokone, jonka toiminta perustui binäärijärjestelmään ja jossa oli myös mahdollisuus tallentaa ohjelmia muistiin (engl. stored-program computer). Matemaatikko John von Neumann oli merkittävässä roolissa koneen suunnittelemisessa. Myös Konrad Zuse kehitteli itsenäisesti vastaavaa tietokonetta.
- 1952: Grace Hopper kehitti ensimmäisen kääntäjäohjelman, jolla pystyttiin kääntämään englanninkieltä mukailevaa ohjelmointikieltä tietokoneelle.
- 1959: Hopperin ajatusten pohjalta kehitetty yleiskäyttöinen COBOL-ohjelmointikieli julkaistaan.
- 1964: Douglas Engelbart esittelee ensimmäisen tietokoneen, jossa on (nykyisenkaltainen) hiiri ja graafinen käyttöliittymä.
- 1971: Intel julkaisee maailman ensimmäisen kaupallisen mikroprosessorin Intel 4004:sen.
- 1975: Julkaistaan Altair 8080-mikrotietokone, jonka harrastajat pystyvät itse kokoamaan rakennussarjasta.
- 1976: Steve Jobs and Steve Wozniak esittelevät Apple I-kotimikron.
- 1978: Intel esittelee ensimmäisen x86-prosessoriperheen suorittimen.
- 1985: Microsoft julkaisee Windows-käyttöjärjestelmän ensimmäisen version.
- 1991: Linus Torvalds (tuolloin Helsingin yliopiston opiskelija) esittelee unixeihin pohjautuvan avoimen lähdekoodin linux-käyttöjärjestelmän.
Suomen ensimmäinen oma tietokone oli ESKO (Elektroninen SarjaKomputaattori) jota rakennettiin vuosina 1954-60. Oulun yliopiston ensimmäinen tietokone oli Elliott 803 1960-luvulta.
Professori Matti Otala ja Elliott 803 Oulun yliopistossa vuonna 1970.
Elliott 803:sta ohjelmointiin mm. ALGOL-ohjelmointikielellä.
FLOATING POINT ALGOL TEST' BEGIN REAL A,B,C,D' READ D' FOR A:= 0.0 STEP D UNTIL 6.3 DO BEGIN PRINT PUNCH(3),££L??' B := SIN(A)' C := COS(A)' PRINT PUNCH(3),SAMELINE,ALIGNED(1,6),A,B,C' END' END'
Mooren laki¶
Mooren laki on (mm. Intelin perustajan) Gordon Mooren tekemä havainto siitä kuinka tekniikan kehityksen myötä transistorien määrä mikropiirillä kaksinkertaistuu noin kahden vuoden välein. Tietokonetekniikassa Mooren lakia on käytetty / käytetään yhtenä tietokonejärjestelmien kehityksen mittarina.
Mooren laki on jatkuvan keskustelun alla, mutta tähän saakka se on pitänyt varsin hyvin paikkansa. Transistorien määrä on tuplaantunut jopa nopeamminkin, noin 18 kuukauden ajassa. Nykyisin arvellaan, että Mooren laki ei toteudu enää 2020-luvulla. Mutta nähtäväksi jää, lakia on yritetty kaataa aiemminkin..
Sinänsä transistorien määrän kasvu ei kerro suoraan tietokoneen laskentatehon kasvusta (kuten tulemme luentomateriaalissa näkemään). Kuitenkin, samassa fyysisessä mittakaavassa piirille saadaan prosessoreihin lisättyä sisäistä toimintalogiikkaa ja muistia, jonka seurauksena saadaan toiminta tehokkaammaksi, voidaan toteuttaa laajempia konekielen käskykantoja, jne.
Kuvissa alla vasemmalla Intelin suorittimien käskykannan muutosnopeus ja oikealla DRAM-muistipiirien kasvanut kapasiteetti. Intelin käskykannassa kantava periaate on ollut alaspäin yhteensopivuus eri prosessorisukupolvien kesken.
C-kielestä konekieleen¶
Kurssilla olemme jo tutustuneet yhteen korkean tason ohjelmointikieleen eli C-kieleen. Ok ok.. aiemmin puhuttiin C:stä matalan tason laiteläheisenä kielenä ja sitä se nykyään onkin verrattuna moniin uudempiin kieliin, kuten Ohjelmoinnin alkeet-kurssin Pythoniin. Tietokoneen keskusyksikölle C-kieli kuitenkin on aivan liiian korkealentoista/ilmaisuvoimaista ymmärrettävää.
Suorittimelle ohjelma pitää esittää sen digitaalilogiikan ymmärtämässä muodossa, eli konekielellä. Kuten tulemme näkemään, konekielinen ohjelma koostuu joukosta hyvin yksinkertaisia käskyjä, jotka tekevät operaatioita (pääasiassa) suorittiminen sisäisissä rekistereissä olevalle datalle.
Vaikka ohjelmoija nykyisin harvemmin pääsee (/joutuu) ohjelmoimaan konekielellä, auttaa konekielen ja suorittimen toiminnan ymmärtäminen tuottamaan tehokkainta mahdollista koodia, kun saadaan suorittimesta tehot irti. Tämä tietämys on yleistä tietotekniikan insinöörin ammattitaitoa, joka heijastuu myös ohjelmointiin korkean tason kielillä. Sulautettujen ohjelmoinnissa työelämässä tulee kyllä eteen tilanteita, jossa ohjelmassa aikakriittinen osa koodia täytyy toteuttaa assembly-kielellä.
Aikoinaan konekielinen (tai assembly-kielinen) ohjelmointi oli ainoa vaihtoehto ylipäänsä koodata tai tuottaa tehokasta koodia silloisiin "sulautettuihin".. vaikkapa nyt avaruusraketteihin.
(Hamilton ei toki itse kirjoittanut kaikkea tätä koodia. Lisäksi hän johti Apollo-projektien koodaustiimiä..)
C-kielen käännösprosessi¶
Koska keskusyksikkö-arkkitehtuureja on erilaisia, konekieli on laitteisto/suoritinriippuvaista siinä missä C-kielelle on standardoitu toteutus, joka toimii yleisesti järjestelmästä toiseen. C-kieli tarvitsee siis kääntää kunkin suorittimen konekielelle erikseen kääntäjän (engl. compiler) avulla. Kurssilla emme mene kääntäjien toteutukseen syvemmälle, mutta yleinen käännösprosessi on syytä tuntea. Ja juu, kääntäjäkin on siis tietokoneohjelma..
Ensimmäisessä vaiheessa siis C-kielinen ohjelma esikäännetään ja käännetään assembly-kieliseksi. Seuraavaksi assembler-ohjelma kääntää assembly-kielisen toteutuksen itse konekielelle objekti-tiedostomuotoon (pääte .o, .so tai .obj, tms). Sitten linkkeri yhdistää objektitiedostot ajettavaksi konekieliseksi ohjelmaksi. Usein ohjelmamme käyttää valmiina järjestelmäkirjastoja (C-kielessä stdio, RTOS:n kirjastot, jne), joiden objektimuotoiset valmiit toteutukset linkkeri liittää ajettavaan ohjelmaamme. Jos ohjelmassa käytetään muita kirjastoja niin ne liitetään tässä vaiheessa tuotokseen, esimerkissä C-standardikirjaston
printf
-funktion toteutus. Lopputuloksena saadaan konekielinen ohjelma, jonka käyttöjärjestelmässä sen komponentti loader lataa RAM-muistiin, kun ohjelma käynnistetään, ja käynnistää ohjelman suorituksen. Assembly- ja konekieli¶
Suorittimen toteutukset perustuvat digitaalisiin mikropiireihin (joita käsiteltiin aiemmilla kursseilla), jotka käsittelevät vain bittejä kombinaatio- sekvenssilogiikassaan, Boolen algebran avulla. Näin ollen suorittimen ymmärtämät käskytkin pitää esitellä bitteinä, ts. binäärilukuina, joiden jokaisella bitillä käskyssä ohjelmoijan ja suorittimen yhdessä tuntema merkitys. Konekieli on siis syvimmältä olemukseltaan pelkkiä bittejä.
Yleisesti käskyä kuvaavassa binääriluvussa osa biteistä identifioi käskyn ja osa kertoo millaisia ja mistä käskyn operandit saadaan. Tästä voikin jo aavistella, että konekielen käskyt ovat todella yksinkertaisia verrattuna korkean tason kielten käskyihin ja ohjelmalauseisiin ja tekevät varsin pieniä juttuja kerrallaan. Nyt, meille korkean tason kielellä ohjelmointiin tottuneille, yksinkertaisenkin toiminnallisuuden toteuttaminen vaatii riveittäin konekieltä. Konekielen binäärilukuesitys on ohjelmoijan tietenkin vaikea hallita (vrt. Hamiltonin tiimi..), mutta sen logiikan ja toiminnan ymmärtäminen auttaa ohjelmoijaa ajattelemaan kuin tietokone ohjelmaa suunnitellessaan ja tehdessään. Tuloksena on kaikenkaikkiaan tehokkaampaa koodia, kun tiedämme miten suoritin toimii ja miten koodimme suoritetaan (parhaassa tapauksessa optimoidusti) keskusyksikössä.
Konekielisen ohjelmoinnin helpottamiseksi niille tarjotaan symbolinen esitystapa (ts. ihmisen luettava) konekielien syntaksista nimeltään assembly-kieli. Assemblyn käskyt on laadittu siten, että ne ovat tekstimuotoisina ihmisen luettavaa koodia, siis käskyillä on nimet jne, mutta ne kuvautuvat suoraan yksi yhteen (vaikka päässälaskien) varsinaiselle konekielelle. Assembly-kieliin menemme tarkemmin seuraavassa luentomateriaalissa, mutta esitetään alla esimerkkejä.
Esimerkki. C-ohjelma
mahtijuttu.c
:int main() {
int i=0,a=0;
for (i=0; i<10; i++) {
a += i;
}
}
..käännetään koodi käskyllä
gcc -S -Og mahtijuttu.c
, jolloin kääntäjä tuottaa x86:n assembly-kielisen käännöksen mahtijuttu.s
ohjelmasta. Katsotaanpa mitä siellä löytyy:main: .LFB0: .cfi_startproc pushq %rbp .cfi_def_cfa_offset 16 .cfi_offset 6, -16 movq %rsp, %rbp .cfi_def_cfa_register 6 movl $0, -4(%rbp) # muuttujan i esittely: i=0 movl $0, -8(%rbp) # muuttujan a esittely: a=0 movl $0, -4(%rbp) # sijoitus (loopin alku) i=0 jmp .L2 # hyppy ehtolauseeseen .L2 .L3: movl -4(%rbp), %eax # haetaan i muistipaikasta rekisteriin addl %eax, -8(%rbp) # a = a + i addl $1, -4(%rbp) # i++ .L2: cmpl $9, -4(%rbp) # vertailu, onko i < 10 jle .L3 # jos totta, hyppy suoritusosaan .L3 popq %rbp .cfi_def_cfa 7, 8 ret # paluu main:iin .cfi_endproc
Okei, varsin kryptisen näköistä tavaraa. Jokainen assembly-kielen käskyt esitetään omalla rivillään ja käskyissä on kaksi osaa, itse käskykoodi ja sen operandit. Esimerkiksi lause
movl $0, -4(%rbp)
, jossa käsky on movl
ja sen perässä ovat operandit (joita C-kielessä vastaisi muuttujat) eli assembly-kielessä rekisterit ja/tai muistiosoitteet. Esimerkkikäskyssä operandit ovat lukuarvo 0, merkitään $0
, rekisteri rbp
ja muistiosoitus -4
. No näihin palaamme vielä yksityiskohtaisemmin..(
.
-alkuiset komennot ovat ohjeita assembly-kääntäjälle, miten ohjelma käännetään konekielelle, ikäänkuin siis C-kielen esikääntäjäkäskyt. Ohjelmassa näkyy myös useita nimettyjä koodilohkoja, esimerkiksi main:
tai .L2:
. Mitä ilmeisimmin main-lohko voisi vastata ohjelmamme main-funktiota.)Seuraavaksi käännetään C-koodi suoraan konekielelle käskyllä
gcc -c mahtijuttu.c
. Tuloksena saadaan konekielinen objektitiedosto mahtijuttu.o
, jossa konekielen käskyt ovat suorittimen ymmärtämässä binääriluku-esityksessä. Tämän konekielisen tiedoston voi sitten tulkita takaisin assembly-kielelle ohjelmallisesti objdump -d mahtijuttu.o
, josta alla kuva. 0000000000000000 main: 0: 55 push %rbp 1: 48 89 e5 mov %rsp,%rbp 4: c7 45 fc 00 00 00 00 movl $0x0,-0x4(%rbp) b: c7 45 f8 00 00 00 00 movl $0x0,-0x8(%rbp) 12: c7 45 fc 00 00 00 00 movl $0x0,-0x4(%rbp) 19: eb 0a jmp 251b: 8b 45 fc mov -0x4(%rbp),%eax 1e: 01 45 f8 add %eax,-0x8(%rbp) 21: 83 45 fc 01 addl $0x1,-0x4(%rbp) 25: 83 7d fc 09 cmpl $0x9,-0x4(%rbp) 29: 7e f0 jle 1b 2b: 5d pop %rbp 2c: c3 retq
Nyt meillä on vasemmassa reunassa muistiosoitteet ja niihin tallennettua konekieltä heksalukuina . Huomataan, että yhtä assemblykielistä käskyä voi vastata 1-n tavua. Kuvasta katsoen, vaikka assemblykieli oli jo kohtalaisen kryptistä niin on se huomattavasti luettavampaa, kuin konekieli, eikös..
Esimerkissä rivi
0: 55
tarkoittaa sitä että muistiosoitteessa 0x0 on käsky 0x55. Oikealla näkyy vastaavat assembly-kielen käskyt ja todetaan että heksaluku 0x55
tarkoittaa käskyä push %rbp
. Vastaavasti voimme arvailla koodia lukemalla, että movl-käskyä näyttäisi vastaavan heksaluvut 0xc7
ja 0x45
. Nyt kun konekielen käskyjen rakenne on tarkkaan määritelty, ne voidaan ne tulkita takaisin assembly-kielelle disassembler-ohjemalla, esimerkiksi ylläoleva objdump-ohjelma. Allaolevassa tiedostossa on esimerkin vuoksi sama silmukka käännetty 8-bittiselle RISC-arkkitehtuurin (maltetaan vielä hetki) sulautetulle mikrokontrollerille ATtiny2313. Huomataan, että AVR-arkkitehtuurissa konekielen käskykanta on yksinkertaisempi, jolloin saman c-koodin konekieliseen esitykseen tarvitaan enemmän käskyjä. Lisäksi huomataan erilainen tavujärjestys verrattuna x86-arkkitehtuuriin.
Hox! Konekielen tai assembly-kielen kääntäminen takaisin C-kielelle ei enää onnistukaan, koska C-kielen käskyillä on huomattavasti parempi ilmaisuvoima. Oliko C-kielisessä ohjelmassa käytössä esimerkiksi for- vai while-silmukkarakenne? Oliko konekielen ehtolauseke C-kielessä
i==9
? Vai i < 10
?Lopuksi¶
Tässä lyhyessä esityksessä kerrottiin yleistietoa tietokonetekniikan historiasta ja esitettiin, miten C-kielisestä ohjelmasta päästään konekieleen. Itseasiassa prosessorikaan ei aina suorita konekielen käskyjä kerrallaan, vaan sisäisesti konekielen käsky puretaan mikro-ohjelmaksi, jonka suoritin sellaisenaan suorittaa. Tämä koskee monimutkaisia konekielen käskyjä, mutta palataan asiaan..
Esimerkeissä annetuilla
gcc
-kääntäjän käskyillä ja vivuilla voitte itsekin kokeilla vaikkapa millaiselta kurssin Johdanto-osan C-kieliset ohjelmat näyttävät assembly- ja konekielisenä. gcc
:llä voi myös kokeilla, miten käännös konekielelle muuttuu, kun käytetään erilaisia sen tarjoamia koodioptimointeja O1-O3
tai Os
.Anna palautetta
Kommentteja materiaalista?