Simulatorer av datasystemer: en kjent fullplattformsimulator og ukjent med klokken og spor

I den andre delen av artikkelen om datasystemsimulatorer vil jeg fortsette å snakke i en enkel introduksjonsform om datasimulatorer, nemlig om fullplattformssimuleringen, som den gjennomsnittlige brukeren oftest møter, samt om klokke-for- -klokkemodell og spor, som er mer vanlig i utviklerkretser.

Simulatorer av datasystemer: en kjent fullplattformsimulator og ukjent med klokken og spor

В første del Jeg snakket om hva simulatorer er generelt, samt om nivåene av simulering. Nå, basert på den kunnskapen, foreslår jeg å dykke litt dypere og snakke om fullplattformsimulering, hvordan man samler spor, hva man skal gjøre med dem senere, så vel som om klokke-for-klokke mikroarkitektonisk emulering.

Full plattformsimulator, eller "Alene i felten er ikke en kriger"

Hvis du vil studere driften av en bestemt enhet, for eksempel et nettverkskort, eller skrive fastvare eller en driver for denne enheten, kan en slik enhet simuleres separat. Men å bruke den isolert fra resten av infrastrukturen er ikke særlig praktisk. For å kjøre den tilsvarende driveren trenger du en sentral prosessor, minne, tilgang til en databuss osv. I tillegg krever driveren et operativsystem (OS) og en nettverksstabel for å fungere. I tillegg kan det være nødvendig med en separat pakkegenerator og svarserver.

En fullplattformsimulator skaper et miljø for å kjøre en komplett programvarestabel, som inkluderer alt fra BIOS og bootloader til selve operativsystemet og dets ulike undersystemer, for eksempel samme nettverksstabel, drivere og applikasjoner på brukernivå. For å gjøre dette implementerer den programvaremodeller for de fleste datamaskinenheter: prosessor og minne, disk, inngangs-/utgangsenheter (tastatur, mus, skjerm), samt det samme nettverkskortet.

Nedenfor er et blokkskjema over x58-brikkesettet fra Intel. En full-plattform datasimulator på dette brikkesettet krever implementering av de fleste av de listede enhetene, inkludert de inne i IOH (Input/Output Hub) og ICH (Input/Output Controller Hub), som ikke er avbildet i detalj på blokkdiagrammet . Selv om det, som praksis viser, ikke er mange enheter som ikke brukes av programvaren vi skal kjøre. Det er ikke nødvendig å lage modeller av slike enheter.

Simulatorer av datasystemer: en kjent fullplattformsimulator og ukjent med klokken og spor

Oftest implementeres fullplattformsimulatorer på prosessorinstruksjonsnivå (ISA, se nedenfor). forrige artikkel). Dette lar deg lage selve simulatoren relativt raskt og rimelig. ISA-nivået er også bra fordi det holder seg mer eller mindre konstant, i motsetning til for eksempel API/ABI-nivået, som endres oftere. I tillegg lar implementering på instruksjonsnivå deg kjøre såkalt umodifisert binær programvare, det vil si kjøre allerede kompilert kode uten endringer, akkurat slik den brukes på ekte maskinvare. Med andre ord, du kan lage en kopi ("dump") av harddisken din, spesifisere den som et bilde for en modell i en fullplattformsimulator, og voila! – OS og andre programmer lastes inn i simulatoren uten ytterligere handlinger.

Simulatorytelse

Simulatorer av datasystemer: en kjent fullplattformsimulator og ukjent med klokken og spor

Som nevnt ovenfor, er prosessen med å simulere hele systemet, det vil si alle enhetene, en ganske treg foretak. Hvis du også implementerer alt dette på et veldig detaljert nivå, for eksempel mikroarkitektonisk eller logisk, vil utførelsen bli ekstremt treg. Men instruksjonsnivået er et passende valg og lar OS og programmer kjøre med hastigheter som er tilstrekkelige til at brukeren kan samhandle med dem komfortabelt.

Her vil det være på sin plass å berøre temaet simulatorytelse. Det måles vanligvis i IPS (instruksjoner per sekund), mer presist i MIPS (millioner IPS), det vil si antall prosessorinstruksjoner som utføres av simulatoren på ett sekund. Samtidig avhenger hastigheten på simuleringen også av ytelsen til systemet som selve simuleringen kjører på. Derfor kan det være mer riktig å snakke om "nedgangen" til simulatoren sammenlignet med det originale systemet.

De vanligste fullplattformsimulatorene på markedet, som QEMU, VirtualBox eller VmWare Workstation, har god ytelse. Det er kanskje ikke engang merkbart for brukeren at det pågår arbeid i simulatoren. Dette skjer takket være de spesielle virtualiseringsmulighetene implementert i prosessorer, binære oversettelsesalgoritmer og andre interessante ting. Alt dette er et tema for en egen artikkel, men kort fortalt er virtualisering en maskinvarefunksjon i moderne prosessorer som lar simulatorer ikke simulere instruksjoner, men sende dem for utførelse direkte til en ekte prosessor, hvis selvfølgelig arkitekturene til simulatoren og prosessoren er like. Binær oversettelse er oversettelse av gjestemaskinkode til vertskode og påfølgende kjøring på en ekte prosessor. Som et resultat er simuleringen bare litt langsommere, 5-10 ganger, og kjører ofte til og med i samme hastighet som det virkelige systemet. Selv om dette påvirkes av mange faktorer. For eksempel, hvis vi ønsker å simulere et system med flere dusin prosessorer, vil hastigheten umiddelbart falle med disse flere dusin ganger. På den annen side støtter simulatorer som Simics i de nyeste versjonene multiprosessor vertsmaskinvare og paralleliserer effektivt de simulerte kjernene til kjernene til en ekte prosessor.

Hvis vi snakker om hastigheten på mikroarkitektonisk simulering, er det vanligvis flere størrelsesordener, omtrent 1000-10000 ganger langsommere enn utførelse på en vanlig datamaskin, uten simulering. Og implementeringer på nivå med logiske elementer er tregere med flere størrelsesordener. Derfor brukes en FPGA som emulator på dette nivået, noe som kan øke ytelsen betydelig.

Grafen nedenfor viser en omtrentlig avhengighet av simuleringshastighet på modelldetaljer.

Simulatorer av datasystemer: en kjent fullplattformsimulator og ukjent med klokken og spor

Slag-for-slag-simulering

Til tross for deres lave utførelseshastighet, er mikroarkitektoniske simulatorer ganske vanlige. Simulering av de interne blokkene til prosessoren er nødvendig for å nøyaktig simulere utførelsestiden for hver instruksjon. Det kan oppstå misforståelser her - når alt kommer til alt, ser det ut til, hvorfor ikke bare programmere utførelsestiden for hver instruksjon. Men en slik simulator vil være veldig unøyaktig, siden utførelsestiden for den samme instruksjonen kan variere fra samtale til samtale.

Det enkleste eksemplet er en minnetilgangsinstruksjon. Hvis den forespurte minneplasseringen er tilgjengelig i hurtigbufferen, vil utførelsestiden være minimal. Hvis denne informasjonen ikke er i cachen ("cache miss"), vil dette øke utførelsestiden for instruksjonen betraktelig. Dermed kreves en cache-modell for nøyaktig simulering. Saken er imidlertid ikke begrenset til cache-modellen. Prosessoren vil ikke bare vente på at data skal hentes fra minnet når den ikke er i hurtigbufferen. I stedet vil den begynne å utføre de neste instruksjonene, og velge de som ikke avhenger av resultatet av lesing fra minnet. Dette er den såkalte "out of order"-kjøringen (OOO, out of order execution), som er nødvendig for å minimere prosessorens inaktive tid. Modellering av de tilsvarende prosessorblokkene vil bidra til å ta hensyn til alt dette når du beregner utførelsestiden for instruksjoner. Blant disse instruksjonene, utført mens resultatet av lesing fra minnet avventes, kan en betinget hoppoperasjon forekomme. Hvis resultatet av tilstanden er ukjent for øyeblikket, stopper ikke prosessoren kjøringen, men gjør en "gjetning", utfører den aktuelle grenen og fortsetter proaktivt å utføre instruksjoner fra overgangspunktet. En slik blokk, kalt en grenprediktor, må også implementeres i den mikroarkitektoniske simulatoren.

Bildet nedenfor viser hovedblokkene til prosessoren, det er ikke nødvendig å vite det, det vises bare for å vise kompleksiteten til den mikroarkitektoniske implementeringen.

Simulatorer av datasystemer: en kjent fullplattformsimulator og ukjent med klokken og spor

Driften av alle disse blokkene i en ekte prosessor er synkronisert av spesielle klokkesignaler, og det samme skjer i modellen. En slik mikroarkitektonisk simulator kalles syklusnøyaktig. Hovedformålet er å nøyaktig forutsi ytelsen til prosessoren som utvikles og/eller beregne utførelsestiden til et spesifikt program, for eksempel en benchmark. Hvis verdiene er lavere enn nødvendig, vil det være nødvendig å endre algoritmene og prosessorblokkene eller optimalisere programmet.

Som vist ovenfor er klokke-for-klokke-simulering veldig sakte, så den brukes bare når man studerer visse øyeblikk av et programs operasjon, der det er nødvendig å finne ut den virkelige hastigheten på programutførelse og evaluere den fremtidige ytelsen til enheten hvis prototypen blir simulert.

I dette tilfellet brukes en funksjonell simulator for å simulere gjenværende kjøretid for programmet. Hvordan skjer denne kombinasjonen av bruk i virkeligheten? Først lanseres den funksjonelle simulatoren, hvor OS og alt som er nødvendig for å kjøre programmet som studeres lastes inn. Tross alt er vi ikke interessert i selve operativsystemet, og heller ikke i de innledende stadiene av å starte programmet, dets konfigurasjon, etc. Vi kan imidlertid heller ikke hoppe over disse delene og umiddelbart gå videre til å kjøre programmet fra midten. Derfor kjøres alle disse foreløpige trinnene på en funksjonell simulator. Etter at programmet er utført til øyeblikket av interesse for oss, er to alternativer mulig. Du kan erstatte modellen med en klokke-for-syklus-modell og fortsette utførelse. Simuleringsmodusen som bruker kjørbar kode (det vil si vanlige kompilerte programfiler) kalles kjøringsdrevet simulering. Dette er det vanligste simuleringsalternativet. En annen tilnærming er også mulig - sporedrevet simulering.

Sporbasert simulering

Den består av to trinn. Ved å bruke en funksjonell simulator eller på et ekte system, samles en logg over programhandlinger og skrives til en fil. Denne loggen kalles et spor. Avhengig av hva som undersøkes, kan sporingen inkludere kjørbare instruksjoner, minneadresser, portnumre og avbruddsinformasjon.

Det neste trinnet er å "spille" sporet, når klokke-for-klokke-simulatoren leser sporet og utfører alle instruksjonene som er skrevet i den. På slutten får vi utførelsestiden for denne delen av programmet, samt ulike egenskaper ved denne prosessen, for eksempel prosentandelen av treff i hurtigbufferen.

Et viktig trekk ved å jobbe med spor er determinisme, det vil si at ved å kjøre simuleringen på den måten som er beskrevet ovenfor, gjenskaper vi den samme sekvensen av handlinger om og om igjen. Dette gjør det mulig, ved å endre modellparametere (cache, buffer og køstørrelser) og bruke ulike interne algoritmer eller justere dem, å studere hvordan en bestemt parameter påvirker systemytelsen og hvilket alternativ som gir de beste resultatene. Alt dette kan gjøres med en prototype enhetsmodell før du lager en faktisk maskinvareprototype.

Kompleksiteten til denne tilnærmingen ligger i behovet for først å kjøre applikasjonen og samle sporet, samt den enorme størrelsen på sporingsfilen. Fordelene inkluderer det faktum at det er nok å simulere bare den delen av enheten eller plattformen som er av interesse, mens simulering ved utførelse vanligvis krever en komplett modell.

Så i denne artikkelen så vi på funksjonene til fullplattformsimulering, snakket om hastigheten på implementeringer på forskjellige nivåer, klokke-for-syklus-simulering og spor. I neste artikkel vil jeg beskrive hovedscenarioene for bruk av simulatorer, både til personlige formål og fra et utviklingssynspunkt i store selskaper.

Kilde: www.habr.com

Legg til en kommentar