Høynivåspråk og assembly

Like dokumenter
Datamaskinens virkemåte

Det viktigste i en moderne datamaskin er hovedkortet («motherboard»):

Det viktigste i en moderne datamaskin er hovedkortet («motherboard»):

Oppbygningen av en datamaskin Det viktigste i en moderne datamaskin er hovedkortet («motherboard»):

Det viktigste i en moderne datamaskin er hovedkortet («motherboard»):

Institiutt for informatikk og e-læring, NTNU CPUens deler og virkemåte Geir Ove Rosvold 4. januar 2016 Opphavsrett: Forfatter og Stiftelsen TISIP

Institiutt for informatikk og e-læring, NTNU Kontrollenheten Geir Ove Rosvold 4. januar 2016 Opphavsrett: Forfatter og Stiftelsen TISIP

Overordnet maskinarkitektur. Maskinarkitektur zoomet inn. I CPU: Kontrollenheten (CU) IT1101 Informatikk basisfag, dobbeltime 11/9

NOTAT (pensum!) Javas klasse-filer, byte-kode og utførelse. INF 5110, 10/5-2011, Stein Krogdahl

Debugging. Tore Berg Hansen, TISIP

Litt om Javas class-filer og byte-kode

Hvordan en prosessor arbeider, del 1

NOTAT (pensum!) Javas klasse-filer, byte-kode og utførelse

Operativsystemer og grensesnitt

Kapittel 9: Følge Instruksjoner Prinsipper for Datamaskinens Virkemåte

Javas klasse-filer, byte-kode og utførelse (og litt om C# sin CIL-kode)

Kapittel 1: Datamaskiner og programmeringsspråk

Innhold. 2 Kompilatorer. 3 Datamaskiner og tallsystemer. 4 Oppsummering. 1 Skjerm (monitor) 2 Hovedkort (motherboard) 3 Prosessor (CPU)

Kapittel 1. Datamaskiner og programmeringsspråk. 1.1 Programmering

Introduksjon til programmering og programmeringsspråk

Dagens temaer. Fra kapittel 4 i Computer Organisation and Architecture. Kort om hurtigminne (RAM) Organisering av CPU: von Neuman-modellen

Introduksjon til programmering og programmeringsspråk. Henrik Lieng Høgskolen i Oslo og Akershus

Minnehåndtering i operativsystemer

Forelesning 5. Diverse komponenter/større system

Forhistorien Menneskene har alltid prøvd å lage maskiner for å løse sine problemer. Dagens tema

Datamaskinenes historie Når, hvor og hvorfor ble de første datamaskiner laget? Hvordan har utviklingen gått? Hva inneholder en datamaskin?

Dagens tema: Enda mer MIPS maskinkode

Minnehåndtering i operativsystemer

Pensum Hovedtanker Selvmodifiserende Overflyt Veien videre Eksamen. Oppsummering

En oppsummering (og litt som står igjen)

Maskinvaredelen av INF 103: oversikt og innhold (1)

Den siste dagen. Pensumoversikt Hovedtanker i kurset Selvmodifiserende kode Overflyt Veien videre... Eksamen

156C. Algoritmer og maskinspråk. IT1101 Informatikk basisfag. Maskinspråk: det maskinen forstår. Assembler / assemblerspråk

Datamaskinenes historie Når, hvor og hvorfor ble de første datamaskiner laget? Hvordan har utviklingen gått? Hva inneholder en datamaskin?

Forhistorien Menneskene har alltid prøvd å lage maskiner for å løse sine problemer. Dagens tema INF1070 INF1070 INF1070

public static <returtype> navn_til_prosedyre(<parameter liste>) { // implementasjon av prosedyren

Kapittel 1: Datamaskiner og programmeringsspråk

Kjøresystemer. Hva er et kjøresystem? Den abstrakte maskinen SIMPLESEM (2.6) Klassifisering av språk: Parametre (2.7.7) Statiske språk (

Dark load-store-maskin

Introduksjon til DARK assembly

Kodegenerering del 3: Tilleggsnotat fra AHU Samt litt om class-filer og byte-kode INF5110 V2007. Stein Krogdahl, Ifi UiO

Dagens tema INF1070. Makroer. Sanntidsprogrammering. Avbrudd. Bruker- og supermodus. Blanding av C og assemblerkode. Selvmodifiserende kode

Dagens tema. Makroer Ofte gjentar man kodelinjer når man skriver assemblerkode. Da kan det lønne seg å definere en makro:

Kapittel 1 En oversikt over C-språket

Generiske mekanismer i statisk typede programmeringsspråk

INF1400 Kap4rest Kombinatorisk Logikk

TDT4110 Informasjonsteknologi, grunnkurs Uke 35 Introduksjon til programmering i Python

Læringsmål og pensum. v=nkiu9yen5nc

Kapittel 1. Datamaskiner og programmeringsspråk. 1.1 Hva er en datamaskin? En datamaskins bestanddeler

Kort om meg. INF1000 Uke 2. Oversikt. Repetisjon - Introduksjon

Del 1 En oversikt over C-programmering

Generelt om operativsystemer

Singletasking OS. Device minne Skjerm minne. Brukerprogram. Brukerdata/heap. Stack. Basis for flerprosess-systemer.

Forelesning ISA-nivået Kap 5.1

Forelesning Instruksjonstyper Kap 5.5

Velkommen til INF2100

Forelesning Datatyper Kap 5.2 Instruksjonsformat Kap 5.3 Flyttall App B

TDT4110 Informasjonsteknologi grunnkurs: Kapittel 1 Introduksjon til Programmering og Python. Professor Alf Inge Wang

Oppsummering Assemblerkode Hopp Multiplikasjon Kode og data Array Oppsummering

Kapittel 1: Datamaskiner og programmeringsspråk. Java som første programmeringsspråk

Bakgrunnen for INF2100. Velkommen til INF2100. Prosjektet. Hva gjør en kompilator?

Forelesning inf Java 1

Programmeringsspråket C

TDT ITGK - Hardware. Kapittel 9: Følge Instruksjoner - Prinsipper for Datamaskinens Virkemåte. Terje Rydland - IDI/NTNU

public static <returtype> navn_til_prosedyre(<parameter liste>) { // implementasjon av prosedyren

IN 147 Program og maskinvare

Programmering. Carsten Wulff

Oversikt. Introduksjon Kildekode Kompilering Hello world Hello world med argumenter. 1 C programmering. 2 Funksjoner. 3 Datatyper. 4 Pekere og arrays

2 Om statiske variable/konstanter og statiske metoder.

Tildeling av minne til prosesser

Håndtering av minne i et OS

HØGSKOLEN I SØR-TRØNDELAG

Oversikt. INF1000 Uke 2. Repetisjon - Program. Repetisjon - Introduksjon

Pensumoversikt - kodegenerering. Kap. 8 del 1 kodegenerering INF5110 v2006. Hvordan er instruksjonene i en virkelig CPU? Arne Maus, Ifi UiO

Dagens tema. Rask-maskinen. Rasko-kode Raskas-kode. Litt datamaskinhistorie Registre og lagre Instruksjoner

En overikt. Dagens tema. Datamaskinenes historie. Rask-maskinen Litt datamaskinhistorie Registre og lagre Instruksjoner. Rasko-kode.

1. Å lage programmer i C++

Datamaskinen LC-2. Dagens tema. Tall i datamaskiner Hvorfor kan LC-2 lagre tall i intervallet ? Hvorfor er det akkurat celler i lageret?

Dagens tema. Datamaskinen LC-2 En kort repetisjon. Binære tall Litt om tallsystemer generelt. Binære tall. Heksadesimale og oktale tall

Del 4 Noen spesielle C-elementer

Programmeringsspråket C

Programmeringsspråket C

1. Å lage programmer i C++

INF3430. Funksjoner og prosedyrer Standardbiblioteker Komplekse sekvensielle systemer

Datamaskinens oppbygning

Velkommen til INF5110 Kompilatorteknikk

TDT4105 Informasjonsteknologi, grunnkurs (ITGK)

Oversikt Kodegenerering Variable Setninger Uttrykk While-setningen Oppsummering

Dagens tema. Mer MIPS maskinkode. Maske-operasjoner Skift-operasjoner Lesing og skriving Pseudo-instruksjoner Mer om funksjonskall Registeroversikt

P1 P2 P3 P1 P2 P3 P1 P2. OS gjør Contex Switch fra P1 til P2

Oversikt. INF1000 Uke 1 time 2. Repetisjon - Introduksjon. Repetisjon - Program

ITPE/DATS 2400: Datamaskinarkitektur og Nettverk

Installere JBuilder Foundation i Windows XP

Resymé: I denne leksjonen blir de viktigste tallsystemer presentert. Det gjelder det binære, heksadesimale og desimale tallsystem.

Oppsummering av Uke 3. MAT1030 Diskret matematikk. Binære tall. Oppsummering av Uke 3

Velkommen til INF2100 Jeg er Dag Langmyhr

TDT4105 Informasjonsteknologi, grunnkurs (ITGK)

Forelesning inf Java 1

MAT-INF 1100: Obligatorisk oppgave 1

Transkript:

Geir Ove Rosvold 4. januar 2016 Opphavsrett: Forfatter og Stiftelsen TISIP Resymé: Denne leksjonen beskriver hvordan høynivåkode brytes ned til en sekvens av enkle instruksjoner. Den beskriver også hvordan vi kan programmere direkte på instrukjsonsnivå; såkalt assemblyprogrammering. I tillegg diskuteres bytekode i forbindelse med java. Innhold 1.1. PROGRAMMERING. HØYNIVÅ KONTRA ASSEMBLY... 2 1.1.1. Symbolske program kontra assemblyprogram... 2 1.1.2. Oppsummering og viktige begreper... 3 1.2. ASSEMBLYPROGRAMMERING... 4 1.2.1. Utstyrsnær programmering... 4 1.2.2. Tidskritiske deler av programmet... 5 1.2.3. Programutvikling i assembly... 6 1.3. OVERSETTING AV KILDEKODE TIL MASKINKODE... 6 1.3.1. Tolking... 6 1.3.2. Oversetting (kompilering)... 7 1.3.3. Mellomspråk (bytekode)... 7 1.4. JAVA... 8 1.4.1. Java bytekode... 8 1.4.2. Java på Internett... 8 1.4.3. Java-prosessorer... 9 VEDLEGG A. INLINE ASSEMBLY PÅ INTELS X86-PROSESSORER... 10 Syntaks i et assemblyprogram... 10 Oversetting av assemblykode. Assembleren... 10 Inline assembly... 10 VEDLEGG B. REGISTRE PÅ INTEL X86... 12 Generelle registre... 12 Registre for adressering i minnet... 12 Flagg-registeret... 12 Programtelleren... 13 32-bits prosessorer... 13 64-bits prosessorer... 14

1.1. Programmering. Høynivå kontra assembly Når vi programmerer, foretrekker vi å uttrykke problemløsningen i et høynivåspråk: De byggesteiner vi bruker er på omlag samme abstraksjonsnivå som vi bruker i en verbal formulering av problemet - matematiske funksjoner, dataposter, prosedyrer. Høynivåspråk er tilpasset problemløsningene, ikke maskinene, og er mer eller mindre uavhengig av hva slags maskin programmet skal kjøres på. Fortran, Pascal, C++ og java er eksempler på slike språk. Datamaskinene arbeider på et langt lavere abstraksjons-nivå. Derfor må en kompilator oversette våre programmer i høynivåspråk til enklere og mer primitiv maskinkode. Ulike maskintyper har ulik maskinkode, og resultatet av kompilatorens oversetting er bundet til en bestemt maskintype. Maskinkoden er bygd opp av instruksjoner i binært format som tolkes direkte av maskinvaren. I en del tilfeller kan vi ikke programmere i høynivåspråk, men vi må programmere direkte i maskinkode som regel for å få tilgang til mekanismer som ikke finnes i høynivåspråket. Da skriver vi likevel ikke binære instruksjoner direkte som bitmønstre. Isteden har instruksjonene symbolske (tekstlige) navn, og vi angir for eksempel tallverdier på desimal eller heksadesimal form. Slike maskinkode-språk kalles assemblyspråk. I motsetning til høynivåspråk er assembler-program forskjellige for hver CPU-type. Hver CPU-type har sitt eget assemblyspråk som gjenspeiler egenskaper med akkurat den CPUtypen. Også assemblerprogram må oversettes (assembleres), men oversettingen er en-til-en: Hver programlinje blir en instruksjon, hvert ord i assemblerspråket svarer til et bitmønster. Med litt trening er det lett å se hvilket bitmønster som blir resultatet av en assembler-instruksjon. De fleste maskiner kan utføre noen hundre ulike maskin-instruksjoner. Vi kaller denne samlingen av instruksjoner for instruksjonssettet, og deler det gjerne opp i ulike grupper som aritmetiske instruksjoner, logiske instruksjoner, kontrollflyt-instruksjoner osv. 1.1.1. Symbolske program kontra assemblyprogram I leksjonen om Grunnleggende virkemåte så vi et eksempel på et symbolsk program. Dette programmet så slik ut: Eksempel på symbolsk program LOAD 940 ADD 941 STORE 941 Det første ordet på hver linje er en operasjonskode (ofte bare kalt op-koden). Operasjonskoden er egentlig et binært bitmønster som forteller CPU hva den skal gjøre, men for at programmet skal være mest mulig lettlest for mennesker har hver operasjonskode fått et symbolsk navn som beskriver instruksjonen. Dette navnet korresponderer til ett bestemt bitmønster, og kan derfor enkelt oversettes. Tallet bak det symbolske navnet er en adresse som forteller hvor de data som skal brukes i instruksjonen ligger. (Tiden kan være inne til å lese leksjonen om grunnleggende virkemåte en gang til). Forfatter og Stiftelsen TISIP side 2 av 14

Assemblyprogrammering er en videreutvikling av symbolske program. Blant annet er adressene byttet ut med variabler. En variabel er altså egentlig bare at vi har satt navn på en minnelokasjon, slik at vi ikke trenger å huske - eller vite - nøyaktig hvor i minnet den ligger. Hvis det symbolske programmet legger sammen tallene A og B, kunne vi isteden skrevet det i assembly på følgende måte: Eksempel på assemblyprogram for den hypotetiske maskinen: LOAD A ; Hent variabelen A og legg den i AC-registeret ADD B ; Hent variabelen B og legg den til tallet i AC. Resultatet legges i AC STORE B ; Lagre resultatet i B Alt som står etter semikolon på en linje er kommentarer for å gjøre programmet mer lettlest. Legg merke til følgende: Vi ser ikke hvor i minnet A og B ligger. Dette overlater vi til maskinen - eller rettere sagt operativsystemet - å bestemme Hver linje i et assemblyprogram tilsvarer én instruksjon for CPU Et assemblyprogram kan bare kjøres på én type CPU. Forskjellige CPU-typer har forskjellige assemblyspråk. I motsetning til høynivåspråk som for eksempel C++ er assemblyspråk altså ikke maskinuavhengig. Et C-program kan kompileres (mer eller mindre) uendret på en hvilken som helst maskin og virke like bra på alle. Et assemblyprogram utvikles for èn og bare èn CPU-type. Programmet i rammen ovenfor er derfor bare et eksempel på hvordan et assemblyprogram kan være. Assemblyspråkene er laget for å være mest mulig lett å lese for mennesker, men dette kommer i annen rekke. Det er CPUens oppbygging som bestemmer hvilke instruksjoner som kan utføres på den, og assemblyspråket er preget av dette. Det er store variasjoner mellom assemblyspråk på forskjellige maskiner. 1.1.2. Oppsummering og viktige begreper Viktige begreper i denne leksjonen har så langt vært: høynivåspråk, assemblyspråk, symbolsk språk og maskinkode. Høynivåspråk er maskinuavhengig og mennesketilpasset språk. Et eksempel på høynivåspråk er java-linjen: b = b + a; Denne linjen legger sammen verdien på to variabler, a og b, og legger resultatet tilbake i variabelen b. Variabelen b får altså endret sin verdi. CPU vil ikke kunne utføre denne linjen som en enkelt operasjon. Isteden brytes linjen ned i operasjoner som er så enkle at CPU kan utføre dem. Disse operasjonene kalles instruksjoner. En kompilator vil oversette høynivåkoden til maskinspråk. Maskinspråk er instruksjoner på binær form. Dersom vi antar at variabelen a ligger i minnelokasjon 94016 og variabelen b Forfatter og Stiftelsen TISIP side 3 av 14

ligger i minnelokasjon 94116 vil linjen i høynivåspråk bli oversatt til følgende instruksjoner på vår hypotetiske maskin fra leksjonen om grunnleggende virkemåte: 0001 1001 0100 0000 0101 1001 0100 0001 0010 1001 0100 0001 Hver linje er en instruksjon. Når en instruksjonen presenteres for CPU, vil den utføre instruksjonen. Det er tungvindt for mennesker å lese slik maskinkode. For å lette lesingen kan de binære kodene erstattes av symbolsk kode. Symbolsk kode benytter seg av at instruksjonene er bygget opp av felter. De viktigste feltene er opkode og operand. Symbolsk kode erstatter opkoden med et symbolsk navn som er lettere å huske for mennesker. I symbolsk kode skrives adresser i heksadesimal form. På vår hypotetiske maskin vil de tre instruksjonene ovenfor vil se slik ut i symbolsk kode: LOAD 940 ADD 941 STORE 941 Av og til ønsker vi å programmere instruksjon for instruksjon. Da trenger vi et litt mer avansert språk enn symbolsk kode. Vi vil fortsatt programmere hver enkelt instruksjon, men vil blant annet ta i bruk variabler. Et slikt utvidet symbolsk språk kalles assemblyspråk. En assembler oversetter assemblykode til maskinkode. Assembleren oversetter hver linje i assemblyprogrammet til en instruksjon i maskinkode. I assemblykode vil den symbolske koden vår se slik ut: LOAD a ADD b STORE b 1.2. Assemblyprogrammering Vi skal ikke lære assemblyprogrammering i dette kurset. De som er interesserte kan ta en titt på vedleggene til denne leksjonen for å få en første innføring. Derimot skal vi se på betydningen av assemblyprogrammering. Det brukes nemlig fortsatt, selv om mange kanskje synes det er gammeldags, tungvindt og lite spennende. Tidligere var assemblyprogrammering mye mer brukt enn det er nå. Høynivåspråkene har blitt så gode at de dekker våre behov i de aller fleste tilfeller. Det er først og fremst i to tilfeller vi kan bli nødt til å programmere i assembly: Utstyrsnær programmering. Hvis man utvikler program som bruker spesiell hardware - for eksempel en driver til et eller annet utstyr er ofte assemblyprogrammering nødvendig. Tidskritiske deler av et program. Selv om kompilatorene har blitt stadig bedre er det fortsatt slik at den kompilerte koden til et høynivåspråk kan optimaliseres på hastighet. Det er ikke uvanlig at det i brosjyrer reklameres med at deler av et program er utviklet i assembly for å få opp hastigheten. 1.2.1. Utstyrsnær programmering Abstraksjonsnivået i de ulike høynivåspråk varierer. Det finnes høynivåspråk der avstanden til maskinspråk er meget stor, og høynivåspråket i svært liten grad gjenspeiler datamaskinens Forfatter og Stiftelsen TISIP side 4 av 14

maskinvarestrukturer (og dermed ikke setter krav til at programmereren skal ha detaljkunnskap om det heller). Andre høynivåspråk ligger atskillig nærmere maskinvarens abstraksjonsnivå. Disse egner seg derfor langt bedre til utstyrsnær programmering. Et slikt språk er C. Dette språket egner seg godt til systemprogrammering (det vil si maskinnær programmering), men likevel finnes det maskinvarefinesser som man ikke kan bruke i noe høynivåspråk. Det enkleste eksemplet på dette er selvsagt at man i assembly har direkte tilgang til alle instruksjoner i instruksjonssettet, og kan bruke disse helt fritt. Det er ikke mulig i et høynivåspråk. På en x86 er et annet eksempel på en slik maskinvarefinesse det såkalte flaggregisteret. (Se vedlegg B til denne leksjonen for en nærmere beskrivelse). Et flagg er en enkelt bit som sier noe om status. Flaggregisteret er et register som består av seksten slike flagg. Dette er et helt spesielt register fordi bitene i registeret har hver sin betydning, ja til og med hver sitt navn. Noen av flaggregisterets biter viser status for prosessoren, og andre sier noe om resultatet av forrige instruksjon. Hvis sist utførte instruksjon ga et aritmetisk resultat sier flaggregisteret noe om dette resultatet. For eksempel: var resultatet positivt eller negativt, var det likt null, ga det mente i resultatet, ga det et like eller odde tall som resultat, eller førte resultatet til overflow? I assembly er det svært enkelt (og svært hurtig) å teste på flaggregisteret, og la testen bestemme hva som skal skje videre i programmet. Dette er ikke mulig i et høynivåspråk. Å programmere i assembly vil være en fordel av og til et krav for utstyrsnær programmering. Lavnivå operativsystemdrivere og avbruddsrutiner er eksempler på programmeringsoppgaver som ofte løses best i assembly (og noen må jo programmere slike også). Vi bør også nevne en anvendelse i en litt annen gate, nemlig programmering av innebygde systemer. Det vil si systemer der en liten datamaskin er innebygd i selve gjenstanden som styres. Eksemplene på slikt utstyr er tallrike. Smartkort, mobiltelefoner, fjernkontroller, video- og stillbildekamera, bil- og husalarmer, vaskemaskiner, kjøkkenmaskiner Dette er bare et lite knippe av eksempler. Det er ingen stor overdrivelse å si at nesten alle elektroniske produkt som koster mer enn 500,- har minst en mikroprosessor innebygd. I slikt utstyr er ofte størrelse og hastighet helt kritisk. Datasystemet er gjerne forminsket i størst mulig grad. CPU, minne og I/O-elektronikk er ofte bygget sammen i en krets og har liten kapasitet. Programmering av slike setter store krav til optimalisering både bruk av maskinressursene (for eksempel minne) og tidsforbruk. Et smartkort inneholder en prosessor og minne. Men prosessoren har begrenset hastighet, og minnekapasiteten måles i KB og ikke MB eller GB. Likevel utfører kortet temmelig avansert kryptografiske beregninger. For at dette skal være realiserbart må programkoden være både liten og effektiv. I slike systemer er assemblyprogrammering fortsatt vanlig, selv om mange forsøker å utvikle gode høynivå alternativer. Blant annet ble java opprinnelig utviklet for programmering av slike innebygde systemer. 1.2.2. Tidskritiske deler av programmet På det som er sagt ovenfor kan det se ut som om man velger å programmere dataløsninger i sin helhet enten i assembly eller i høynivåspråk. Dette er imidlertid ikke tilfellet. Forfatter og Stiftelsen TISIP side 5 av 14

I så godt som alle applikasjoner og dataløsninger viser det seg at en liten del av koden står for det meste av tidsforbruket. Det kan være løkker eller andre deler av programmet som kjøres gjentatte ganger, og derfor brukes det meste av tiden. En tommelfingerregel sier at 10% av koden står for 90% av tidsforbruket (og at 1% av koden står for 50 % av tidsforbruket). Av denne grunnen er det selvsagt viktig å identifisere disse tidskritiske delene av programmet, og bruke mest ressurser på å optimalisere akkurat disse stykkene av programmet. Dette kalles gjerne tuning i systemutvikling. Det er slettes ikke uvanlig at man bruker assembly for å optimalisere slike tidskritiske deler av koden. 1.2.3. Programutvikling i assembly Det er ingen grunn til å legge skjul på at assemblyprogrammering er mer arbeidskrevende og vanskeligere enn utvikling i høynivåspråk. Det er tidkrevende å utvikle, debugge og vedlikeholde et assemblyprogram langt mer tidkrevende enn tilsvarende oppgaver for et system som er programmert i høynivå. Valget mellom assembly og høynivåspråk blir dermed alltid en avveining mellom pris og ytelse. Når vi først er inne på tommelfingerregler kan vi nevne at man ofte regner med at en god assemblyprogrammerer lager kode som er omtrent tre ganger så hurtig som en kompilator greier (selv om det nøyaktige tallet variere svært mye med hvilken oppgave det dreier seg om). På den andre siden er kostnadene med assemblykoding gjerne 3 til 5 ganger større enn ved høynivåkoding. Som vi ser: tuning vil alltid være en avveining mellom mange hensyn. 1.3. Oversetting av kildekode til maskinkode De aller fleste som programmerer skriver koden sin i et høynivåspråk. Da må kildekoden oversettes til instruksjoner som CPU forstår. Det er i hovedsak tre ulike metoder for dette: 1. Tolking 2. Oversetting (kompilering) 3. Bruk av mellomspråk (bytekode) 1.3.1. Tolking Ved tolking oversettes høynivåkoden til maskinkode samtidig som programmet kjøres. Når programmet starter vil tolkeprogrammet lese kildekoden linje for linje. Hver linje oversettes, hvoretter den utføres. Deretter gjentas dette for neste linje, helt til tolken når slutten på kildekoden. Ulempen med denne metoden er at programmene blir trege fordi CPU bruker mye av tiden til tolkingen. Fordelen med tolking er først og fremst at det er lettvint og hurtig å gjøre endringer i programmet, og så teste programmet med disse endringene. Det er ingen tidkrevende oversetting av programmet før det kan kjøres. Språk som tolkes De fleste versjoner av høynivåspråket Basic er tolket. (I Visual Basic, som er mye brukt i Windows-miljø, kan man velge mellom tolket og kompilert kode. Tradisjonelt er imidlertid Basic et språk som tolkes før utføring). Forfatter og Stiftelsen TISIP side 6 av 14

Andre eksempler på høynivåspråk som tolkes er macrospråkene og scriptspråkene. Et macrospråk er et språk som tillater at man automatiserer oppgavene til et brukerprogram. De fleste kontorapplikasjoner har muligheten til macroprogrammering. Det samme gjelder de mest populære nettleserne (som kan kjøre Javascript og andre scriptspråk). Når man kjører en macro eller et script, er det vertsprogrammet som inneholder den tolken som oversetter koden. 1.3.2. Oversetting (kompilering) For å unngå tidsforbruket med å oversette programmet hver gang det kjører, vil det være hensiktsmessig å utføre oversettingen til maskinkode en gang for alle. Det er akkurat det som skjer ved kompilering. En kompilator er et program som leser en fil med høynivå kildekode, og oversetter den i sin helhet. Resultatet lagres i en ny fil med kjørbar maskinkode. Fordelen med dette er at fila med den kjørbare kildekoden kan lastes inn i minnet, og kjøres uten noe forsinkende tolkning. I parentes kan det nevnes at beskrivelsen av kompilering som ble gitt ovenfor er noe forenklet. Kompilering skjer egentlig i to trinn: først oversettes programmet og deretter linkes standardbibliotekene for operativsystemrutinene inn, før den kjørbare koden blir ferdig og lagret som et kjørbart program. I dette faget henger vi oss ikke opp i detaljer på akkurat dette. Vi er mer opptatt av prinsippene på dette området. Språk som kompileres De aller fleste høynivåspråk kompileres. C, C++, Pascal og Fortran er gode eksempler. Det er først og fremst to ulemper med det: Kompileringen tar tid. Det kan være ganske frustrerende å måtte vente flere minutt på en ny kompilering når man har gjort en bitteliten endring i kildekoden. Maskinavhengig resultat. En kompilator lager kjørbare kode for en bestemt prosessortype, og man kan ikke flytte denne kjørbare koden over på en annen maskin dersom denne maskinen har en annen type prosessor. 1.3.3. Mellomspråk (bytekode) Som vi så ovenfor, så er det en ulempe med kompilering at resultatfila bare kan kjøres av en bestemt type prosessorer. I enkelte sammenhenger er det viktig å sikre kompatibilitet på tvers av maskinplattformer. Da kan man ikke bruke kompilering. På den andre siden ønsker man heller ikke forsinkelsen som ligger i en tolking av høynivåkoden. En alternativ metode er å bruke det beste fra begge leire: Kildekoden kompileres til et språk som i ligner på maskinkode, men som ikke er bundet til en bestemt type prosessor. Dette språket kalles bytekode, og er ikke lettere å forstå for oss mennesker enn annen maskinkode. For å kjøre programmet på en konkret datamaskin, må datamaskinen kjøre et tolkeprogram som leser bytekoden linje for linje, og oversetter hver linje til maskinkode for akkurat denne datamaskintypen. Forfatter og Stiftelsen TISIP side 7 av 14

Hvorfor kan man ikke bare ha tolket høynivåkoden isteden? Jo, hovedsakelig fordi tolking av høynivåspråket er tidkrevende, mens tolking av bytekoden er atskillig enklere og mindre tidkrevende (fordi bytekode ligner mye på maskinkode). På denne måten oppnår man plattformuavhengighet, men prisen vi må betale er en forsinkelse ved kjøring av koden, fordi bytekoden må oversettes. Denne forsinkelsen er likevel mye mindre enn dersom høynivåkoden skulle tolkes. Språk med bytekode Konseptet med plattformuavhengig bytekode forbindes først og fremst med programmeringsspråket java. Det finnes javatolkere for de aller fleste plattformer, så java bytekode er bortimot universell. 1.4. Java Java er et generelt programmeringsspråk. Det ligner en del på C++, men har rettet opp en del av ulempene med C++. Språket ble utviklet av firmaet Sun Microsystems på midten av 90- tallet. Siden java er et generelt språk kan koden utmerket godt kompileres til maskinkode. Det finnes slike java-kompilatorer for de fleste plattformer, og man kan derfor godt skrive store applikasjoner i java, og kompilere kildekoden for en bestemt plattform. Resultatet er like hurtig kode som i andre programmeringsspråk, men også like plattformavhengig. Det er imidlertid konseptet med bytekode som i løpet av få år har gjort java til et av de aller mest brukte programmeringsspråkene. 1.4.1. Java bytekode Istedenfor å kompilere kildekoden til maskinkode for en bestemt maskin, blir java-kildekode kompilert til maskinkode for en tenkt (eller virtuell) ikke-eksisterende maskin. Denne virtuelle maskinen kalles JVM (Java Virtual Machine). Sun har spesifisert nøye hvordan denne tenkte maskinen er bygget opp. Den har for eksempel 226 instruksjoner i instruksjonssettet, og minnet er bygget opp med lokasjoner à 32 bits. De aller fleste instruksjoner er svært enkle, men det finnes noen få unntak. For å kunne kjøre JVM-kode på en reell maskin må JVM-koden oversettes til maskinkode for den aktuelle prosessoren. Dette skjer ved at en tolk oversetter JVM-koden linje for linje under kjøring. Sun har skrevet både kompilatoren og tolken i programmeringsspråket C. Dermed kan man kompilere både kompilatorprogrammet og tolkeprogrammet for så godt som enhver maskin som er laget. Det finnes nemlig svært få datamaskintyper i verden der det ikke er utviklet en C-kompilator. Resultatet er at JVM-kode kan distribueres, og kan kjøres på de aller fleste maskiner. 1.4.2. Java på Internett Det at man kan distribuere den plattformuavhengige JVM-bytekoden, og ved hjelp av en tolk kjøre bytekoden på mange ulike maskiner, har gjort java populær i forbindelse med Internettapplikasjoner. Det finnes JVM-tolker for alle de populære datamaskintypene. Det har gjort java til et stort språk på kort tid. Forfatter og Stiftelsen TISIP side 8 av 14

En enkel, og noe redusert, tolk sitter i de mest populære nettleserne. Tanken med disse er at man kan hente ned små java-snutter (såkalte java-apletter), og kjøre dem i nettleseren sitt miljø. Tolken er redusert av sikkerhetsgrunner. Man vil at tolken ikke skal oversette kode som kan skade maskinen. Du kan for eksempel ikke formatere harddisken fra en java-aplett. Man har altså begrenset hva apletten kan gjøre, ved kun å implementere ufarlig funksjonalitet i tolken. I Internettsammenheng sier man at apletter lever i en sandkasse. Dette var ment som en viktig beskyttelsesmekanisme mot virus og andre skadelige program. De senere år har vi sett at det har vært store problemer med å få sikkerhetsmekanismen til å fungere godt. Det har vært store sikkerhetshuller i java-sandkassen. Dette har gitt java-tolken for nettlesere et svært frynsete rykte. Det dårlige ryktet har ikke blitt bedre av at java-apletter også har dårlig ytelse i form av utføringshastighet, og også reduserer ytelsen til resten av nettleseren. 1.4.3. Java-prosessorer Siden Java Virtual Machine ligner svært mye på maskinkode kan man kanskje lage en reell prosessor som implementerer disse instruksjonene direkte? Jo visst. Det lages slike javaprosessorer. Hvis du søker på wikipedia etter «Java processor» finner du en oversikt over de fleste av dem. Med en slik prosessor kan man kjøre JVM-koden direkte, og slipper å bruke tolkeprogram. Java-prosessorer har etter hvert begynt å bli brukt i innebygde systemer det anvendelsesområdet som faktisk var den opprinnelige grunnen til at språket java ble utviklet. Innebygde datasystemer er systemer der prosessorer brukes til å styre og kontrollere utstyr som vi vanligvis ikke forbinder med datamaskiner; for eksempel fjernkontroller, mobiltelefoner, vaskemaskiner, TV, radio, spillmaskiner, leketøy og tallrike andre anvendelser. Dette er svært utbredt. Det lages ikke generelle datamaskiner (for eksempel PCer) med java-prosessorer. Forfatter og Stiftelsen TISIP side 9 av 14

Vedlegg A. Inline assembly på Intels x86-prosessorer Vi skal ikke lære assemblyprogrammering i dette kurset. Dette vedlegget er først og fremst med for at dere skal få se hvordan man på en svært enkelt måte kan assemblyprogrammere små deler av et stort program. De som har lyst å lære assembly kan finne flere hyllemeter med bøker på en bokhandel, eller lete på Internett. Der finnes det store mengder stoff om dette temaet. Se for eksempel: http://webster.cs.ucr.edu/. Syntaks i et assemblyprogram Oppbyggingen av et asseblyprogram er ganske annerledes enn hva vi er vant med i høynivåspråk. En av grunnene er selvsagt at hver linje skal oversettes til nøyaktig en instruksjon. Hver linje i et assemblyprogram kan bestå av følgende deler: label: symbolsk-opkode operand1, operand2 ;kommentar label: Brukes til å markere en linje slik at programmet kan hoppe dit senere. Bare et fåtall linjer i et assemblyprogram bruker å ha en label. symbolsk-opkode Angir hvilken instruksjon som skal utføres. Operander ;kommentar Dette er operandene til instruksjonen. Noen instruksjoner har ikke operander, andre har èn operand og atter andre har to. Alt etter semikolon på en linje oppfattes som kommentarer Den symbolske opkoden kalles vanligvis mnemonic. Hver instruksjon i instruksjonssettet har sin egen mnemonic. Som vi ser av rammen ovenfor kan instruksjonene ha inntil to operander. Det er viktig å merke seg at kun en operand kan ligge i minnet. Den andre operanden er som regel et register. Oversetting av assemblykode. Assembleren For at assemblykoden skal kunne kjøres på en maskin må koden oversettes til bitmønstre som prosessoren forstår. Vi sier at koden må oversettes til maskinspråk. Et program som leser et assemblyprogram og oversetter det til maskinspråk kalles en assembler. Assemblerens jobb minner mye om jobben til en kompilator. Kompilatoren oversetter imidlertid program som er skrevet i et høynivåspråk. Assemblerens jobb er mye enklere siden hver linje i et assemblyprogram bare oversettes til en instruksjon. Som kjent oversettes kompilatoren hver linje i et høynivåprogram til mange instruksjoner. Inline assembly Tidligere var det ikke uvanlig at hele, eller ihvertfall store deler, av et program var programmert i assembly. Da ble assemblykoden samlet i egne filer som ble assemblert med en assembler. Senere ble disse lenket inn i resten av programmet. Nå er det mer vanlig at bare små deler av programmet skrives i assembly. For eksempel bare noen få funksjoner, eller til og med bare deler av en funksjon. For å gjøre dette enklest mulig Forfatter og Stiftelsen TISIP side 10 av 14

har de fleste kompilatorer nå en innebygget assembler slik at assemblykode og høynivåkode kan blandes i samme kildekodefil. I Microsofts C++-kompilatorer skjer dette ved å sette assemblykoden i en såkalt asm-blokk. Alt som står inne i en slik blokk oppfattes som assemblykode. Blokken starter med nøkkelordet asm, og selve assemblykoden står innenfor klammeparenteser: Eksempel på inline assembly i C++: // Program som bruker assembly til å øke verdien til et heltall med 3. #include <iostream.h> void main (void) { int MinVariabel; MinVariabel = 2; asm { mov ax, MinVariabel ; Kopier MinVariabel til ax-registeret add ax, 3 ; Legg til 3 mov MinVariabel, ax ; Kopier den nye verdien tilbake til MinVariabel } cout << MinVariabel; } I dette programmet deklareres variabelen MinVariabel som gis verdien to. I assemblykoden økes verdien med tre. Tilsist skrives den nye verdien ut. Legg merke til at variabler som deklareres i C-programmet uten videre kan brukes i assemblykoden. Forfatter og Stiftelsen TISIP side 11 av 14

Vedlegg B. Registre på Intel x86 X86-prosessoren inneholder et stort antall registre. De fleste av disse har sine spesielle anvendelser. Noen brukes til korttidslagring av data og er såkalte generelle registre, noen til å adressere minnelokasjoner, og ett register består av enkeltbits hvor hver bit sier noe om resultatet av forrige instruksjon. I dette kapittelet antar vi at prosessoren brukes i real mode. Registrene er følgelig 16 bits. Generelle registre Disse registrene heter AX, BX, CX, og DX. De er altså generelle registre som brukes på lignende måte som AC-registeret i den hypotetiske maskinen vi har beskrevet tidligere i dette kurset. Til tross for at de er generelle registre har de sine faste bruksområder. Det er viktig å legge merke til at de er 16-bits registre. Helt spesielt er det at hver av de generelle registrene også kan sees på som to 8-bits register. Vi bruker AX-registeret som eksempel på dette: AX-registeret kan sees på som ett 16-bitsregister med navnet AX eller to 8-bitsregister med navnene AH og AL (A-High og A-Low). I siste tilfelle er den mest signifikante byten AH og den minst signifikante AL. På samme måte kan de andre generelle registrene sees på som 16-bits registre eller 8-bitsregistrene BL og BH, CL og CH samt DL og DH. Registre for adressering i minnet Vi skal ikke legg så mye vekt på disse registrene enda. Bare vær klar over at i real-mode bruker man to registre for å adressere en minnelokasjon. Dette kalles segmentert adressering. Registrene som brukes til dette er segmentregistrene og de såkalte peker- og indeksregistre. Disse registrene har navnene CS, DS, ES, SS, SI, DI, SP og BP. En adresse i minnet adresseres ved å bruke et segmentregister - CS, DS, SS eller ES - og enten et pekerregister (SP eller BP) eller et indeksregister (SI eller DI). I denne leksjonen bryr vi oss ikke om peker- og indeksregistrene. Når det gjelder segmentregistrene holder det foreløbig å vite at koden til programmet og data til programmet legges på forskjellige steder i minnet. Det er operativsystemet som bestemmer nøyaktig hvor de skal legges. CS-registeret (eller Code Segment) peker på starten av koden. DS (Data Segment) peker på starten av programmets dataområde. Operativsystemet setter verdiene til disse registrene når programmet startes. Flagg-registeret Det normale er at innholdet av et register sees på under ett. Det finnes et unntak og det er flaggregisteret. Et flagg er en enkelt bit som sier noe om status. Flaggregisteret er et register som består av seksten slike flagg. Dette er et helt spesielt register fordi bitene i registeret har hver sin betydning, ja til og med hver sitt navn. Noen av flaggregisterets biter viser status for prosessoren, men de fleste sier noe om resultatet av forrige instruksjon. For eksempel: Hvis sist utførte instruksjon ga et aritmetisk resultat sier flaggregisteret noe om dette resultatet. Bitene i flaggregisteret forteller blant annet hvorvidt resultatet var positivt eller negativt, om det var likt null, om det ga mente i resultatet og om det ga et like eller odde tall som resultat. Flaggregisteret har en svært viktig funksjon i forbindelse med betingede hopp fordi slike hopp består i å utføre to instruksjoner. Først en aritmetisk instruksjon, og deretter en instruksjon som leser flaggregisteret og avgjør om hoppet skal foretas. For eksempel kan man utføre en subtraksjon (første instruksjon) og deretter hoppe til en ny plass i programmet hvis resultat Forfatter og Stiftelsen TISIP side 12 av 14

var lik null (andre instruksjon). Hvis hoppbetingelsen ikke er sann foretar man ikke noe hopp, men fortsetter med neste sekvensielle instruksjon. Tabell 1 viser de viktigste bitene i flaggregisteret. Legg merke til at det ikke bare er slik at hver enkelt bit i dette registeret har fått et eget navn, men hver verdi biten kan innta (0 eller 1) har også fått et eget navn. Biten ZF (Zero Flag) forteller om resultatet av forrige aritmetiske instruksjon var lik null. Dersom resultatet var lik null er verdien til ZF lik ZR (Zero) hvis resultatet var ulikt null har ZF verdien NZ (Non-Zero). Navn Fullt navn Verdier Funksjon ZF Zero Flag NZ / ZR Angir om resultatet ble null (ZR) eller ikkenull (NZ) SF Sign Flag PL / NG Angir om resultatet ble positivt (PL) eller negativt (NG) OF Overflow flag NV / OV Angir om resultatet førte til overflow (OV) eller ikke (NV) PF Parity Flag PO / PE Angir om resultatet har odde (PO) eller like antall 1-ere (PE) CF Carry Flag NC / CY Angir om resultatet endte uten mente (NC) eller med mente (CY) IF Interrupt Flag DI / EI Er avbruddsmekanismer frakoblet (disabled DI) eller tilkoblet (enabled EI) Tabell 1 Flagg. Tabellen viser de viktigste flaggene i flaggregisteret, deres mulige verdier og deres funksjon. Programtelleren Det siste registeret heter IP. IP, eller InstruksjonsPekeren, er det registeret som på mange andre prosessorer kalles programteller eller PC. IP holder altså rede på hvor neste instruksjon skal hentes. 32-bits prosessorer Den første 32-bits prosessoren fra Intel var 386-prosessoren. En 32-bits prosessor har registre som er 32-bits. På 386, 486 og på Pentrium er altså de fleste registre 32-bits. Dette er gjort på den måten at de generelle registrene er blitt utvidet til 32 bits. Navnet på registrene er de samme som tidligere, men for å skille dem fra 16-bits-registrene har de fått en E foran navnet: EAX er navnet på akkumulatoren, AX utgjør de nederste 16 bitene av dette registeret (og AL utgjør de 8 nederste). De generelle registrene på en 386 og nyere er altså EAX, EBX, ECX og EDX. Forfatter og Stiftelsen TISIP side 13 av 14

64-bits prosessorer De nyeste Pentium-modellene er 64-bits. Der har registrene blitt utvidet enda en gang på samme måte som ved overgangen fra 16 til 32-bits registre. Nå har regisrtrene fått en R først i navnet for å vise at det er 64-bits registre (RAX, RBX, RCX, RDX, osv). Nå er EAX de 32 minst signifikante bitene i RAX, og tilsvarende for de andre registrene. Forfatter og Stiftelsen TISIP side 14 av 14