Symulatory systemów komputerowych: znany symulator pełnej platformy i nieznany zgodnie z ruchem wskazówek zegara i ślady

W drugiej części artykułu o symulatorach systemów komputerowych w dalszym ciągu będę mówił w prostej formie wprowadzającej o symulatorach komputerowych, a mianowicie o symulacji pełnoplatformowej, z którą najczęściej spotyka się przeciętny użytkownik, a także o zegarze -model zegara i ślady, które są bardziej powszechne w kręgach programistów.

Symulatory systemów komputerowych: znany symulator pełnej platformy i nieznany zgodnie z ruchem wskazówek zegara i ślady

В część pierwsza Mówiłem ogólnie o tym, czym są symulatory, a także o poziomach symulacji. Teraz, w oparciu o tę wiedzę, proponuję zejść nieco głębiej i porozmawiać o symulacji pełnej platformy, o tym, jak zbierać ślady, co z nimi później zrobić, a także o emulacji mikroarchitektury zegar po zegarze.

Pełnoplatformowy symulator, czyli „Sam na polu walki nie jest wojownikiem”

Jeśli chcesz przestudiować działanie jednego konkretnego urządzenia, na przykład karty sieciowej, lub napisać oprogramowanie układowe lub sterownik dla tego urządzenia, wówczas takie urządzenie można symulować osobno. Jednak korzystanie z niego w oderwaniu od reszty infrastruktury nie jest zbyt wygodne. Do uruchomienia odpowiedniego sterownika potrzebny będzie centralny procesor, pamięć, dostęp do magistrali danych itp. Ponadto sterownik wymaga do działania systemu operacyjnego (OS) i stosu sieciowego. Ponadto może być wymagany oddzielny generator pakietów i serwer odpowiedzi.

Pełnoplatformowy symulator tworzy środowisko do uruchamiania kompletnego stosu oprogramowania, który obejmuje wszystko, od systemu BIOS i modułu ładującego po sam system operacyjny i jego różne podsystemy, takie jak ten sam stos sieciowy, sterowniki i aplikacje na poziomie użytkownika. W tym celu implementuje modele oprogramowania większości urządzeń komputerowych: procesora i pamięci, dysku, urządzeń wejścia/wyjścia (klawiatura, mysz, wyświetlacz), a także tej samej karty sieciowej.

Poniżej znajduje się schemat blokowy chipsetu x58 firmy Intel. Pełnoplatformowy symulator komputera na tym chipsecie wymaga implementacji większości wymienionych urządzeń, w tym tych wewnątrz IOH (koncentratora wejścia/wyjścia) i ICH (koncentratora kontrolera wejścia/wyjścia), które nie są szczegółowo przedstawione na schemacie blokowym . Chociaż, jak pokazuje praktyka, nie ma wielu urządzeń, z których nie korzysta oprogramowanie, które będziemy uruchamiać. Modeli takich urządzeń nie trzeba tworzyć.

Symulatory systemów komputerowych: znany symulator pełnej platformy i nieznany zgodnie z ruchem wskazówek zegara i ślady

Najczęściej symulatory pełnoplatformowe są implementowane na poziomie instrukcji procesora (ISA, patrz poniżej). poprzedni artykuł). Pozwala to na stosunkowo szybkie i niedrogie stworzenie samego symulatora. Poziom ISA też jest dobry, bo pozostaje mniej więcej stały, w przeciwieństwie np. do poziomu API/ABI, który zmienia się częściej. Dodatkowo implementacja na poziomie instrukcji pozwala na uruchomienie tzw. niezmodyfikowanego oprogramowania binarnego, czyli uruchomienie już skompilowanego kodu bez żadnych zmian, dokładnie tak, jak jest używany na prawdziwym sprzęcie. Innymi słowy, możesz wykonać kopię („zrzut”) dysku twardego, określić ją jako obraz modelu w pełnoplatformowym symulatorze i voila! – System operacyjny i inne programy są ładowane do symulatora bez żadnych dodatkowych działań.

Wydajność symulatora

Symulatory systemów komputerowych: znany symulator pełnej platformy i nieznany zgodnie z ruchem wskazówek zegara i ślady

Jak wspomniano powyżej, proces symulowania całego systemu, czyli wszystkich jego urządzeń, jest przedsięwzięciem dość powolnym. Jeśli zaimplementujesz to wszystko również na bardzo szczegółowym poziomie, na przykład mikroarchitektonicznym lub logicznym, wykonanie stanie się niezwykle powolne. Jednak poziom instrukcji jest właściwym wyborem i umożliwia działanie systemu operacyjnego i programów z szybkością wystarczającą do wygodnej interakcji użytkownika z nimi.

W tym miejscu wypadałoby poruszyć temat wydajności symulatora. Zwykle mierzona jest w IPS (instrukcje na sekundę), a dokładniej w MIPS (miliony IPS), czyli liczbie instrukcji procesora wykonanych przez symulator w ciągu jednej sekundy. Jednocześnie szybkość symulacji zależy również od wydajności systemu, na którym przeprowadzana jest sama symulacja. Dlatego bardziej słuszne może być mówienie o „spowolnieniu” symulatora w porównaniu z oryginalnym systemem.

Najpopularniejsze na rynku symulatory pełnoplatformowe, takie jak QEMU, VirtualBox czy VmWare Workstation, charakteryzują się dobrą wydajnością. Użytkownik może nawet nie zauważyć, że w symulatorze trwają prace. Dzieje się tak dzięki specjalnym możliwościom wirtualizacji zaimplementowanym w procesorach, algorytmom translacji binarnej i innym ciekawym rzeczom. To wszystko temat na osobny artykuł, ale w skrócie wirtualizacja to funkcja sprzętowa współczesnych procesorów, która pozwala symulatorom nie symulować instrukcji, ale wysyłać je do wykonania bezpośrednio do prawdziwego procesora, jeśli oczywiście architektury symulator i procesor są podobne. Tłumaczenie binarne to tłumaczenie kodu maszynowego gościa na kod hosta i późniejsze wykonanie na prawdziwym procesorze. W rezultacie symulacja jest tylko nieznacznie wolniejsza, 5–10 razy, a często nawet przebiega z tą samą szybkością, co system rzeczywisty. Chociaż ma na to wpływ wiele czynników. Przykładowo, jeśli chcemy zasymulować system z kilkudziesięciu procesorami, to prędkość od razu spadnie o te kilkadziesiąt razy. Z drugiej strony symulatory, takie jak Simics w najnowszych wersjach, obsługują wieloprocesorowy sprzęt hosta i skutecznie łączą symulowane rdzenie z rdzeniami prawdziwego procesora.

Jeśli mówimy o szybkości symulacji mikroarchitektury, to zwykle jest ona o kilka rzędów wielkości, około 1000-10000 razy wolniejsza niż wykonanie na zwykłym komputerze, bez symulacji. A implementacje na poziomie elementów logicznych są wolniejsze o kilka rzędów wielkości. Dlatego na tym poziomie jako emulator wykorzystuje się FPGA, co może znacznie zwiększyć wydajność.

Poniższy wykres przedstawia przybliżoną zależność szybkości symulacji od szczegółowości modelu.

Symulatory systemów komputerowych: znany symulator pełnej platformy i nieznany zgodnie z ruchem wskazówek zegara i ślady

Symulacja uderzenia po uderzeniu

Pomimo niskiej szybkości wykonywania, symulatory mikroarchitektury są dość powszechne. Aby dokładnie symulować czas wykonania każdej instrukcji, konieczna jest symulacja wewnętrznych bloków procesora. Tutaj może pojawić się nieporozumienie – w końcu wydawałoby się, dlaczego nie zaprogramować po prostu czasu wykonania każdej instrukcji. Ale taki symulator będzie bardzo niedokładny, ponieważ czas wykonania tej samej instrukcji może się różnić w zależności od połączenia.

Najprostszym przykładem jest instrukcja dostępu do pamięci. Jeśli żądana lokalizacja pamięci jest dostępna w pamięci podręcznej, czas wykonania będzie minimalny. Jeśli tej informacji nie ma w pamięci podręcznej („brak pamięci podręcznej”), znacznie wydłuży to czas wykonania instrukcji. Dlatego do dokładnej symulacji wymagany jest model pamięci podręcznej. Sprawa nie ogranicza się jednak do modelu pamięci podręcznej. Procesor nie będzie po prostu czekał na pobranie danych z pamięci, gdy nie znajdują się one w pamięci podręcznej. Zamiast tego zacznie wykonywać kolejne instrukcje, wybierając te, które nie zależą od wyniku odczytu z pamięci. Jest to tzw. wykonanie „poza kolejnością” (OOO, wykonanie poza kolejnością), niezbędne do zminimalizowania czasu bezczynności procesora. Modelowanie odpowiednich bloków procesora pomoże uwzględnić to wszystko przy obliczaniu czasu wykonania instrukcji. Wśród tych instrukcji, wykonywanych w czasie oczekiwania na wynik odczytu z pamięci, może wystąpić operacja skoku warunkowego. Jeżeli wynik warunku nie jest w tej chwili znany, wówczas ponownie procesor nie zatrzymuje wykonywania, ale „zgaduje”, wykonuje odpowiednią gałąź i kontynuuje proaktywne wykonywanie instrukcji od punktu przejścia. Taki blok, zwany predyktorem rozgałęzień, należy również zaimplementować w symulatorze mikroarchitektury.

Poniższy rysunek przedstawia główne bloki procesora, nie trzeba ich znać, pokazano je jedynie w celu pokazania złożoności implementacji mikroarchitektury.

Symulatory systemów komputerowych: znany symulator pełnej platformy i nieznany zgodnie z ruchem wskazówek zegara i ślady

Praca wszystkich tych bloków w rzeczywistym procesorze jest synchronizowana specjalnymi sygnałami zegarowymi i to samo dzieje się w modelu. Taki symulator mikroarchitektury nazywany jest dokładnym cyklem. Jego głównym celem jest dokładne przewidzenie wydajności opracowywanego procesora i/lub obliczenie czasu wykonania konkretnego programu, np. benchmarku. Jeżeli wartości będą mniejsze niż wymagane, konieczna będzie modyfikacja algorytmów i bloków procesora lub optymalizacja programu.

Jak pokazano powyżej, symulacja zegar po zegarze jest bardzo powolna, dlatego stosuje się ją tylko przy badaniu określonych momentów działania programu, gdzie konieczne jest poznanie rzeczywistej szybkości wykonywania programu i ocena przyszłej wydajności urządzenia, którego prototyp jest symulowany.

W tym przypadku używany jest symulator funkcjonalny, który symuluje pozostały czas działania programu. Jak to połączenie zastosowań dzieje się w rzeczywistości? Najpierw uruchamiany jest symulator funkcjonalny, na który ładowany jest system operacyjny i wszystko, co niezbędne do uruchomienia badanego programu. Przecież nie interesuje nas sam system operacyjny, ani początkowe etapy uruchamiania programu, jego konfiguracji itp. Nie możemy jednak pominąć tych części i od razu przejść do wykonywania programu od środka. Dlatego wszystkie te wstępne kroki przeprowadzane są na symulatorze funkcjonalnym. Po wykonaniu programu do interesującego nas momentu możliwe są dwie opcje. Można zastąpić model modelem zegarowym i kontynuować wykonywanie. Tryb symulacji, w którym wykorzystuje się kod wykonywalny (czyli zwykłe skompilowane pliki programów), nazywany jest symulacją sterowaną wykonaniem. Jest to najczęstsza opcja symulacji. Możliwe jest również inne podejście – symulacja oparta na śladzie.

Symulacja oparta na śladach

Składa się z dwóch etapów. Za pomocą symulatora funkcjonalnego lub w rzeczywistym systemie zbierany jest dziennik działań programu i zapisywany w pliku. Ten dziennik nazywa się śladem. W zależności od tego, co jest sprawdzane, ślad może zawierać instrukcje wykonywalne, adresy pamięci, numery portów i informacje o przerwaniach.

Następnym krokiem jest „odtworzenie” śladu, kiedy to symulator zegar po zegarze odczytuje ślad i wykonuje wszystkie zapisane w nim instrukcje. Na koniec otrzymujemy czas wykonania tego fragmentu programu, a także różne cechy tego procesu, na przykład procent trafień w pamięci podręcznej.

Ważną cechą pracy ze śladami jest determinizm, czyli przeprowadzając symulację w sposób opisany powyżej, w kółko odtwarzamy tę samą sekwencję działań. Umożliwia to zmianę parametrów modelu (rozmiary pamięci podręcznej, bufora i kolejki) oraz użycie różnych algorytmów wewnętrznych lub ich dostrojenie, aby zbadać, jak dany parametr wpływa na wydajność systemu i która opcja daje najlepsze wyniki. Wszystko to można zrobić za pomocą prototypowego modelu urządzenia przed stworzeniem rzeczywistego prototypu sprzętu.

Złożoność tego podejścia polega na konieczności pierwszego uruchomienia aplikacji i zebrania śladów, a także na ogromnym rozmiarze pliku śledzenia. Do zalet należy to, że wystarczy zasymulować tylko część interesującego nas urządzenia lub platformy, natomiast symulacja poprzez wykonanie zwykle wymaga kompletnego modelu.

Dlatego w tym artykule przyjrzeliśmy się funkcjom symulacji na pełnej platformie, rozmawialiśmy o szybkości wdrożeń na różnych poziomach, symulacji zegar po cyklu i śladach. W kolejnym artykule opiszę główne scenariusze wykorzystania symulatorów, zarówno do celów osobistych, jak i z punktu widzenia rozwojowego w dużych firmach.

Źródło: www.habr.com

Dodaj komentarz