Simulatorer av datorsystem: en välbekant fullplattformssimulator och okänd medurs och spår

I den andra delen av artikeln om datorsystemsimulatorer kommer jag att i en enkel introduktion fortsätta att prata om datorsimulatorer, nämligen om fullplattformssimuleringen, som den genomsnittliga användaren oftast stöter på, samt om klocka-för- -klockmodell och spår, som är vanligare i utvecklarkretsar.

Simulatorer av datorsystem: en välbekant fullplattformssimulator och okänd medurs och spår

В den första delen Jag pratade om vad simulatorer är i allmänhet, liksom om nivåerna av simulering. Nu, baserat på den kunskapen, föreslår jag att jag ska dyka lite djupare och prata om fullplattformssimulering, hur man samlar in spår, vad man ska göra med dem senare, samt om mikroarkitektonisk emulering klocka för klocka.

Helplattformssimulator, eller "Ensam i fältet är inte en krigare"

Om du vill studera driften av en specifik enhet, till exempel ett nätverkskort, eller skriva firmware eller en drivrutin för denna enhet, kan en sådan enhet simuleras separat. Att använda det isolerat från resten av infrastrukturen är dock inte särskilt bekvämt. För att köra motsvarande drivrutin behöver du en central processor, minne, tillgång till en databuss etc. Dessutom kräver drivrutinen ett operativsystem (OS) och en nätverksstack för att fungera. Dessutom kan en separat paketgenerator och svarsserver krävas.

En fullplattformssimulator skapar en miljö för att köra en komplett mjukvarustack, som inkluderar allt från BIOS och bootloader till själva operativsystemet och dess olika delsystem, såsom samma nätverksstack, drivrutiner och applikationer på användarnivå. För att göra detta implementerar den mjukvarumodeller för de flesta datorenheter: processor och minne, disk, in-/utgångsenheter (tangentbord, mus, skärm) samt samma nätverkskort.

Nedan är ett blockschema över x58-kretsuppsättningen från Intel. En datorsimulator med full plattform på denna styrkrets kräver implementering av de flesta av de listade enheterna, inklusive de inuti IOH (Input/Output Hub) och ICH (Input/Output Controller Hub), som inte är avbildade i detalj i blockschemat . Även om det, som praxis visar, inte finns många enheter som inte används av programvaran som vi ska köra. Modeller av sådana enheter behöver inte skapas.

Simulatorer av datorsystem: en välbekant fullplattformssimulator och okänd medurs och spår

Oftast implementeras fullplattformssimulatorer på processorinstruktionsnivå (ISA, se nedan). föregående artikel). Detta gör att du kan skapa själva simulatorn relativt snabbt och billigt. ISA-nivån är också bra eftersom den förblir mer eller mindre konstant, till skillnad från till exempel API/ABI-nivån som ändras oftare. Dessutom tillåter implementering på instruktionsnivå att du kan köra så kallad omodifierad binär programvara, det vill säga köra redan kompilerad kod utan några ändringar, precis som den används på riktig hårdvara. Med andra ord kan du göra en kopia ("dumpa") av din hårddisk, ange den som en bild för en modell i en fullplattformssimulator, och voila! – OS och andra program laddas i simulatorn utan några ytterligare åtgärder.

Simulatorprestanda

Simulatorer av datorsystem: en välbekant fullplattformssimulator och okänd medurs och spår

Som nämndes precis ovan är processen att simulera hela systemet, det vill säga alla dess enheter, ett ganska långsamt företag. Om du dessutom implementerar allt detta på en mycket detaljerad nivå, till exempel mikroarkitektonisk eller logisk, kommer exekveringen att bli extremt långsam. Men instruktionsnivån är ett lämpligt val och låter operativsystemet och programmen köras med hastigheter som är tillräckliga för att användaren ska kunna interagera med dem bekvämt.

Här skulle det vara lämpligt att beröra ämnet simulatorprestanda. Det mäts vanligtvis i IPS (instruktioner per sekund), närmare bestämt i MIPS (miljoner IPS), det vill säga antalet processorinstruktioner som exekveras av simulatorn på en sekund. Samtidigt beror hastigheten på simuleringen också på prestandan hos systemet som själva simuleringen körs på. Därför kan det vara mer korrekt att tala om simulatorns "avmattning" jämfört med det ursprungliga systemet.

De vanligaste fullplattformssimulatorerna på marknaden, som QEMU, VirtualBox eller VmWare Workstation, har bra prestanda. Det kanske inte ens märks för användaren att det pågår arbete i simulatorn. Detta sker tack vare de speciella virtualiseringsmöjligheter som implementerats i processorer, binära översättningsalgoritmer och andra intressanta saker. Allt detta är ett ämne för en separat artikel, men kortfattat är virtualisering en hårdvarufunktion hos moderna processorer som gör det möjligt för simulatorer att inte simulera instruktioner, utan att skicka dem för exekvering direkt till en riktig processor, om, naturligtvis, arkitekturerna för simulatorn och processorn liknar varandra. Binär översättning är översättning av gästmaskinkod till värdkod och efterföljande exekvering på en riktig processor. Som ett resultat är simuleringen bara något långsammare, 5-10 gånger, och går ofta till och med i samma hastighet som det verkliga systemet. Även om detta påverkas av många faktorer. Om vi ​​till exempel vill simulera ett system med flera dussin processorer, kommer hastigheten omedelbart att sjunka med dessa flera dussin gånger. Å andra sidan stödjer simulatorer som Simics i de senaste versionerna multiprocessor-värdhårdvara och parallelliserar effektivt de simulerade kärnorna till kärnorna i en riktig processor.

Om vi ​​pratar om hastigheten för mikroarkitektonisk simulering, så är det vanligtvis flera storleksordningar, cirka 1000-10000 gånger långsammare än exekvering på en vanlig dator, utan simulering. Och implementeringar på nivån för logiska element är långsammare med flera storleksordningar. Därför används en FPGA som emulator på denna nivå, vilket kan öka prestandan avsevärt.

Grafen nedan visar ett ungefärligt beroende av simuleringshastighet på modelldetaljer.

Simulatorer av datorsystem: en välbekant fullplattformssimulator och okänd medurs och spår

Simulering takt för slag

Trots sin låga exekveringshastighet är mikroarkitektoniska simulatorer ganska vanliga. Simulering av processorns interna block är nödvändig för att exakt simulera exekveringstiden för varje instruktion. Missförstånd kan uppstå här - trots allt verkar det som, varför inte helt enkelt programmera exekveringstiden för varje instruktion. Men en sådan simulator kommer att vara mycket felaktig, eftersom exekveringstiden för samma instruktion kan skilja sig från samtal till samtal.

Det enklaste exemplet är en minnesåtkomstinstruktion. Om den begärda minnesplatsen är tillgänglig i cachen, kommer exekveringstiden att vara minimal. Om denna information inte finns i cachen ("cachemiss"), kommer detta att avsevärt öka instruktionens exekveringstid. Således krävs en cachemodell för korrekt simulering. Emellertid är saken inte begränsad till cachemodellen. Processorn väntar inte bara på att data ska hämtas från minnet när den inte finns i cachen. Istället kommer den att börja utföra nästa instruktioner och välja de som inte beror på resultatet av läsning från minnet. Detta är den så kallade "out of order" exekveringen (OOO, out of order exekvering), nödvändig för att minimera processorns vilotid. Att modellera motsvarande processorblock hjälper till att ta hänsyn till allt detta när man beräknar exekveringstiden för instruktioner. Bland dessa instruktioner, som exekveras medan resultatet av läsning från minnet inväntas, kan en villkorad hoppoperation inträffa. Om resultatet av tillståndet är okänt för tillfället, stoppar inte processorn exekveringen, utan gör en "gissning", utför lämplig gren och fortsätter att proaktivt exekvera instruktioner från övergångspunkten. Ett sådant block, kallat en grenprediktor, måste också implementeras i den mikroarkitektoniska simulatorn.

Bilden nedan visar processorns huvudblock, det är inte nödvändigt att känna till det, det visas bara för att visa komplexiteten i den mikroarkitektoniska implementeringen.

Simulatorer av datorsystem: en välbekant fullplattformssimulator och okänd medurs och spår

Driften av alla dessa block i en riktig processor synkroniseras av speciella klocksignaler, och samma sak händer i modellen. En sådan mikroarkitektonisk simulator kallas cycle accurate. Dess huvudsakliga syfte är att exakt förutsäga prestandan hos processorn som utvecklas och/eller beräkna exekveringstiden för ett specifikt program, till exempel ett riktmärke. Om värdena är lägre än vad som krävs, kommer det att vara nödvändigt att modifiera algoritmerna och processorblocken eller optimera programmet.

Som visas ovan är klocka-för-klocka-simulering mycket långsam, så den används endast när man studerar vissa ögonblick av ett programs drift, där det är nödvändigt att ta reda på den verkliga hastigheten för programexekveringen och utvärdera den framtida prestandan för enheten vars prototypen simuleras.

I detta fall används en funktionssimulator för att simulera programmets återstående körtid. Hur sker denna kombination av användning i verkligheten? Först startas den funktionella simulatorn, på vilken OS och allt som behövs för att köra programmet som studeras laddas. När allt kommer omkring är vi inte intresserade av själva operativsystemet, inte heller i de inledande stadierna av att starta programmet, dess konfiguration etc. Men vi kan inte heller hoppa över dessa delar och omedelbart gå vidare till att köra programmet från mitten. Därför körs alla dessa preliminära steg på en funktionell simulator. Efter att programmet har körts till det ögonblick som är intressant för oss är två alternativ möjliga. Du kan ersätta modellen med en klocka-för-cykel-modell och fortsätta körningen. Simuleringsläget som använder körbar kod (det vill säga vanliga kompilerade programfiler) kallas exekveringsdriven simulering. Detta är det vanligaste simuleringsalternativet. Ett annat tillvägagångssätt är också möjligt - spårdriven simulering.

Spårbaserad simulering

Den består av två steg. Med hjälp av en funktionell simulator eller på ett riktigt system samlas en logg över programåtgärder in och skrivs till en fil. Denna logg kallas spår. Beroende på vad som undersöks kan spårningen innehålla körbara instruktioner, minnesadresser, portnummer och avbrottsinformation.

Nästa steg är att "spela upp" spåret, när klocka-för-klocka-simulatorn läser spåret och utför alla instruktioner som är skrivna i den. I slutet får vi exekveringstiden för denna del av programmet, såväl som olika egenskaper hos denna process, till exempel procentandelen träffar i cachen.

En viktig egenskap för att arbeta med spår är determinism, det vill säga genom att köra simuleringen på det sätt som beskrivits ovan, om och om igen reproducerar vi samma sekvens av åtgärder. Detta gör det möjligt att, genom att ändra modellparametrar (cache-, buffert- och köstorlekar) och använda olika interna algoritmer eller ställa in dem, studera hur en viss parameter påverkar systemets prestanda och vilket alternativ som ger bäst resultat. Allt detta kan göras med en prototypenhetsmodell innan en verklig hårdvaruprototyp skapas.

Komplexiteten i detta tillvägagångssätt ligger i behovet av att först köra applikationen och samla in spåret, såväl som den enorma storleken på spårningsfilen. Fördelarna inkluderar det faktum att det räcker att simulera endast den del av enheten eller plattformen som är av intresse, medan simulering genom utförande vanligtvis kräver en komplett modell.

Så i den här artikeln tittade vi på funktionerna i fullplattformssimulering, pratade om hastigheten på implementeringar på olika nivåer, klocka-för-cykel-simulering och spår. I nästa artikel kommer jag att beskriva huvudscenarierna för att använda simulatorer, både för personliga ändamål och ur utvecklingssynpunkt i stora företag.

Källa: will.com

Lägg en kommentar