tryby rzeczywistości 6/9 #Małpowanie pamięci;
//wróć do spisu treści; tekst jest fragmentem większej całości;
#Małpowanie pamięci;
To jest druga rzecz zapewniająca producentom maści na ból dupy dochody, nad pierwszą przeszliśmy do porządku dziennego, tutaj się nie da. To wyjdzie na końcu. Bo to jest pierwsza zwirtualizowana rzecz.
BIOS startuje z 64kilo. To jest coś co Tygrysek swoim małym, C64 rozumkiem ogarnąć potrafi. Może ciasno, ale sobie poradzę. Jak tego było 128, a później 256 na amisiach – to jest do ogarnięcia moim rozumkiem. To nie jest jakaś epicka liczba danych. Ponieważ umiem robić rozgałęzienia na takich wąskich przepustach logicznych (jak dwadzieścia kabelków) to gdy klepię mapę 3d do gry porafię zmieścić się w kilku mb pliku tekstowego gdzie spisane jest które szuflady otworzyć i gdzie wsadzić, jak ustawić. Pod tym kryjąc się kolejne struktury danych sterowane tym procesem i zawalę 1gb ramu. I drugie tyle albo wincyj w gpu.
Jestem to w stanie zrobić tworząc sobie modele abstrakcji, grupowania danych etc. Mapowanie pamięci na potrzeby użyszkodnika (to ja) działa tak samo. I korzysta z tej samej logiki sprawdzeń (co gdzie jest legalne, co wolno, komu wolno). Normalnie powinien zająć się tym OS, obecnie tym OS jest UEFI.
Fizycznie adresy 16bitowe zapisujemy jako segment * 16 + offset. I to mieści się w 16bitach. W 32bitach jest tak samo, tylko wykładnik inny jednak rozszerzenie PAE dodaje jeszcze 4 bity i pomajstrowano tym tak, że da się zaadresować 64gigi pamięci. Ale jak używacie 64bitowego (czyli normalnego dziś klocka) to offset ma na przykład 4kilo (wagon danych) i mieści się w 48 bitach (czyli teoretycznie 256 terahopsztyliony, ale jakby iść bo bandzie to jakiś eksabajt się da adresowć; tyle że na razie w klockach jest 48 kabli na szynie czipsetu i wystarcza, a jak co majstruje się rozmiarem fizycznego odwzorowania “bajta” bo skoro może zwracać 8bitów sygnału to dlaczego nie 73 albo 4k) gdzie wirtualnie zapisujecie indeks PML4 (cpu sobie z tego bierze indeks z konstytutki CR3 pod wpisem PML4), PDPT (adres PML4), PD (adres PDPT), PT(adres PD), z tego składa fizyczny adres i dodaje offset. Czyli kontynent, land, miasto, ulica, numer baraku zawierającego na przykład 4kilo. Oczywiście żeby tę poczt pocztę prowadzić do CPU doklejono Departament Zarządzania Pamięcią (MMU), który robi za archiwistę odpowiadającego Bogu o co On w zasadzie pytał. Zaznaczmy, że w GPU ta konwencja jest istotnie przycięta i pamięci służą do przechowywania jednej i tylko jednej długości (zawsze tej samej) offsetu dla danego (zaprogramowanego na przykład shaderem) ustawienia. Czyli istnieje oddzielne archiwum na b1, i8,f8,i16,f16,f32 oraz na takie długie fikołki zwane teksturami, których pamięć jest dzielona abstraktem dla homosapacza na teksture page, czyli jak duży i w jakim formacie ciąg bitów przedstawia “teksturę”, a nawet może przedstawiać jej uproszczenia (mipmap) w określonym cyklu następników zawartego w baraku. Fizycznie struktura adresowania pamięci się nie różni, ale archiwista srogi i rygorystyczny inaczej ma podzielony magazyn i nie wolno mu przestawiać w trakcie pracy. Oraz inne formaty istotne (na przykład zamrożony buffor vertexów) zgodne z wprowadzoną definicją jak to czytać. Jest to o tyle istotne, że w GPU nie ma wewnątrz tych zapisów flagowania, tylko na wejściu meldujesz w jakim formacie wkładasz i archiwista tę kartkę podaje tylko dalej do przetwarzania, ale jej nie odda. Jak zgubiłeś formatowanie i po przetworzeniu dostajesz bufor zwrotny to jest to Twoje zmartwienie jak to teraz odczytać. Czyli nie gubić kartek.
Tutaj też dopisane są konkretny abstrakty (co ostatnio czytałem, czyli dane, które właśnie są obrabiane) w postaci TLB, to taka ściąga dla danych, które dla procesów w cr8 mają wysoką wagę i fajnie jest trzymać te adresy pod ręką, żeby nie męczyć archiwisty MMU (czyli przychodzić do baru krzycząc “to co zawsze!”).
Teraz ten ból dupy. Czyli to, że wiążemy sobie łapki i zlecamy zarządzanie pamięci tym automatom, które od strony algorytmicznej działają bardzo dobrze. Ale zostaną przekazane pod zarząd OS i jest to zarząd komisaryczny, a programy odpalone przez OS są często napisane w warunkach “wystarczy że działa”, “na wczoraj”, “budżet się skończył, puszczamy jak jest” oraz typowe choroby, które już widzicie na nastu poprzednich stronach – legacy, tak wyszło i tak zostało, a teraz czekamy, aż tak się zakopiemy, że wprowadzenie nowego standardu na tyle uprości robotę (i ktoś napisze na to sensowny kod), że nie przesiądziemy się z ulgą. Jak widzicie pamięć na dużych systemach (64bity to dla mnie tyle, że nie wiem co z tym robić nawet adresując po 48) jest zarządzana w paczkach. Jeśli w programie odpalicie wątek, który przechowuje tylko boola w fixed buffer, to on dalej wciągnie komórkę pamięci czyli najmniej 4kilo (przy takiej standardowej fragmentacji). Można tak sfragmentować pamięć w kompletny mess. Czyli problem labelek – będzie ich więcej niż treści. Zaletą tego (na PC) jest to, że archiwista nie wie jak kontroler zrealizował “znajdź pamięć” i kiedy ta w kostce się skończy, a OS ma obsługę twardego dysku jako tymczasowego bufora na RAM, to się tam wrąbie i będzie udawał, że to taki bardzo wolny RAM. Co się w zasadzie nie różni od kostki startowej flash z 1mb na ram i rom. Przynajmniej od strony logicznej. Oczywiście na embedzie się to nie uda (nie ma ficzera) więc robiąc porty na konsole takie numery są niedopuszczalne (i w drugą stronę – portowanie z native konsoli na PC zwala na łeb obsługę wszelkich, dziwacznych konfiguracji sprzętu, innej ilości cache, poziomów cache, rdzeni i ramu, ale za to pozwala na użycie abstrakcyjnych obejść omawianą wirtualizacją).
Oczywiście możecie sobie wybić dziurę w pokładzie i zwalić tablice pamięci ustawiając wedle własnego uznania, ale jest to bardzo chroniony porządek. Jeśli macie taki plan to znaczy, że macie w planie odpalenie jednego i tylko jednego programu bazodanowego (mielenie jakiś coinów), albo robicie własną wariację jakiegoś nieskomplikowanego kernela. Większość zarządzania pamięcią to jest legacy z kuchni włoskiej – copypasta. Narosło wokół tego tyle rytuałów (legacy, jak czegoś nie rozje…), że się tego w zasadzie nie tyla. Twój program wiesza własną tablicę wewnątrz przydzielonej pamięci i sobie tam rządź. Zamkniesz – OS posprząta.
To teraz ten pierwszy ból dupy. Jak zauważacie istnieje bardzo dużo gratów (choćby w ALU), które po zadaniu instrukcji i zwaleniu na rejestr ALU argumentów w jednym cyklu możemy dostać na rejestr co najwyżej int. Chodzi o to, że aby przepuścić float w wybranym standardzie trzeba wewnątrz ALU przepiąć prąd kilka razy, krok po kroku. Gdyby na ALU zrzucić jeden i tylko jeden typ obliczeń (czyli nie zmienia się komenda ani typ argumentu) wielocyklowcyh (powiedzmy mul dwa float64 i masz tu n takich par ładowanych po kolei) to można je wsadzać w każdym cyklu i w tej kolejności wypadną z maszynki z drugiej strony. Jednak na klocku widzicie, że jak jest policz to przez te cykle nic się nie dzieje, aż ALU nie zwróci (albo jiff mikrokodu [nie ten OSowy] nie obudzi klocka, że za długo czeka i coś musiało umrzeć; na przykład wyjebała się zegarynka na liczą inkrementy na złej pozycji zawracając d, że dużo czasu minęło).
Co oznacza, że można klocka skomplikować aby pracował wydajniej, bo jak zauważyliście reżim pracy ALU w int, a reżim we float (a jeszcze w gpu macie jednostki do zabaw wektorowych, na macierzach) tworzy tam na tyle skomplikowaną strukturę, że w zasadzie można by ją obsługiwać oddzielnie i tak się od dawna klocki robi, a nasz cpu/fpu core0 jest tam tylko rozdzielaczem (bo tym tak technicznie jest, to skomplikowany włącznik od wielu świateł). Ponieważ każda z tych jednostek (zarządzania pamięcią, zarządzania urządzeniami, zarządzania każdym z ALU, zwrotki z procesów) wymaga oddzielnego księgowego, którego głupia jest robota bo przekłada tylko z kabla na kabel meldując, że to jego czas aby wjechać ze swoim kartonem to i tymi księgowymi ktoś musi zarządzać. Ponieważ na początku (zanim na klocki wejdzie układ) taki proces jest wirtualizowany (czy dobry, czy się przyda, jak się przyjął to może taki klocek dokleić – gpu są tego przykładem ekstremalnym, kiedy układ zmutował jak raczysko i chce własnego zasilacza, własnego chłodzenia, a rozpasł się tak, że mało płyty nie złamie własnym ciężarem) to czasem zostają tego artefakty w przydziale pamięci (stos fifo) i struktury danych (kopiec – sortowanie). Które niby tam zostają (bo już w kodzie są), ale w układzie dodawana jest struktura robiąca dokładnie to samo, żeby nie brać trzech cykli na głupie pytania “co teraz” tylko w cyklu na rejestr od razu wchodzi kolejna instrukcja. Bo jeśli przyjrzycie się trybowi rzeczywistemu, to cpu musi odwołać się do komórki pamięci (arg), załadować do rejestru, wczytać z drugiego rejestru (arg), i wczytać na rejestr instrukcji “co z tym urwał zrobić”. Nie wszystkie polecenia mają argumenty, ale rozumiecie, że za każdym razem oznacza to przynajmniej cykl zegarowy na linijkę, a z czytanie przynajmniej dwie bo sygnał jeszcze musi wrócić z danymi. Więc żeby to wszystko szło szybciej i klocek się nie lenił jak w trybie rzeczywistym to trzeba mu nadbudować umownych abstraktów. Jakoś tym g trzeba sterować (kolejką wykonań) i dlatego UEFI odpala od razu sterowanie taką liczbą jednostek (znaczy CPU włączając wszystkie tryby rozszerzenia rejestru na FPU wydaje rozkazy co mają zrobić, ale później to te jednostki karmią w zgodnie z cyklami rejestr instrukcji i argumentów będąc historycznie oddzielnymi układami pod kontrolą CPU, obecnie są tak ciasno i blisko, że nie ma sensu rozróżniać czy to królowa czy robotnica, po prostu jest to taka duża i dziwna mrówka, która ma dużo nóżek wsadzanych do gniazda klocka).
Dlatego bardzo niezdrowo byłoby wepchnąć w taki klocek polecenia majstrowania przy CR kiedy nie wie co się robi, bo możemy bardzo szybko znaleźć się w miejscu, w którym abstrakcje nie działają, a procedura ich kolejnego uruchomienia jest w procedurze EFI działającej tylko i wyłącznie jeśli klocek jest w tanie przebadanym (zerowym, prąd mu włączyli; względnie w jakimś ustawieniu uśpienia dla wybranych OS – nie polecam, ma się co wykrzaczyć). W każdym innym uruchamianie tej serii poleceń może być wykluczone, ponieważ trzeba mieć uprawnienia ring0 do każdego z nich i naprawdę dużą dziurę wielkości kadłuba trzeba wybić w kadłubie, żeby to odpalić jako ring0 po przejściu w ring3 z pamięci ring3.
Te wszystkie poziomy abstrakcji są po to aby nam ułatwić życie i utrudnić potykanie się o własne nogi. CPU tak naprawdę nie ma żadnej wiedzy, czy działa w trybie emulacji czy na prawdziwych urządzeniach. Po podłączeniu abstraktu z własnym sterowaniem dla CPU to są tylko kable, którymi przychodzi różnica potencjału albo jakaś, albo żadna. W samym CPU nie ma żadnej informacji ani skąd to przyszło, ani dokąd poszło. Więc z punktu widzenia cpu/fpu to jak zmałpowana jest pamięć (czy po 4 kilo czy po 8bit) jest nierozróżnialne, wola paczkę – dostaje paczkę, tę paczkę 4k dzieli mu urządzenie (podzespół), który i tak jest 3 poziomy rozkazów od CPU i jedyne co ma zrobić to podać do kontrolera adres, a odzyskany sygnał posiekać na strawne, bit length odcinki i wsadzić do uciekającego mu przed nosem położenia tych danych z l3cache. Tam zostanie ta współdzielona pamięć podzielona między rdzenie (zgodnie z lockami, semaforami i ewentualnym szczypaniem po atomach) i zrzucona na ich prywatne l2 w kolejności zadanej z kopca w paczkach, gdzie ewentualnie dojdą przerwania systemowe (petenci tacy jak zegarynka, klawiatura, termometr) ze swoimi zadaniami i z tego załadowana kolejka na l1 (równie szybko odjeżdżająca), żeby klocek miał co mielić. I w drugą stronę też to jeździ, czyli coś tym zarządza. Czyli układa stosy co robić, czego nie robić, kopce co by zrobić wypadało i czy to jest teraz bardzo ważne, a nawet musi przewidzieć jakie polecenie wrzucić do wykonania, bo kodoklepca napisał widły (branching) i pod warunkiem można zrobić to lub śmo, więc trzeba wrzucić obie komendy (a jak ifelse jest pińcet to się zrobi ciasno – z gatunku jak nie piać kodu) w takiej kolejności, w jakiej predyktor przewiduje, że schodzą warunki jako spełnione.
Jak chcecie normalnym kodem wywalić predyktor i całą kolejkę, to zróbcie generator mapy, w którym każdy kolejny branch zależy od warunków losowych i jest tam więcej rozwiązań niż osiem (11,13,17 – liczby pierwsze najlepsze na klocka), a do tego liczb losowych jest w każdym warunku kilka. Ponieważ klocek będzie musiał te liczby zwrócić (wywróżyć z termometru, zegarynki i seed) to mu to chwilę zajmie, predyktor co prawda załaduje jakąś kolejność w stos (ale mu się nie zmieści^^) i jakieś argumenty, ale one zawsze okażą się nie takie (przynajmniej prawie zawsze) i wtedy trzeba ze stosu pousuwać to co należy do naszego kodu (czyli puste przebiegi “nie wykonuj”), a z poziomów cache sflushować cały, misternie utkany przez predyktor plan. Przy dobrym wietrze jakieś 10-20 cykli idzie w gwizdek. I tak co instrukcję^^ Jak dobrze uknujecie generator, to klocek przygotuje sobie rachunki na osiem cykli, a później 20 będzie się podnosił z tego bajzlu, bo to wszystko było źle predykowane^^. I mówimy tu o zupełnie normalnym programie, który jest po prostu z d napisany bo pobiera rnd w locie, zamiast przygotować sobie tablicę wyników wcześniej.
//nerdowy wtręt, dygresja; możecie wylistować flushe branch predictora przy brach misiach na przykład używając perf pod linuxem; odczytuje hardware perfomance counters (PMC): sudo perf stat -e branches,branch-misses ./moj_program [argumenty] i wywali ile jest gałęzi w programie, i ilu mu się miśło wywołując flush; –sort branch_misses które funkcje najczęściej krzaczyły (winni!) etc; Jeśli popełnialiście w gcc, clang to tam są hinty dla predictora [__builtin_expect], a nawet można wbić gotowy benchmark takich ustawień z symulatora danego klocka (zestawem SPEC CPU) ale nie wiem czy ktoś ich jeszcze używa, a nawet jeśli to obecne klocki i tak mają na hinta mocno wywalone, ponieważ te elementy hintów powstały kiedy predyktory były jeszcze takie sobie; trzeba bardzo specyfikować na maszynę (w serwerowych zastosowaniach ma to sens) i wtedy to ma jakiekolwiek szanse być lepsze, od tego co predyktor będzie wiedział po kilku cyklach sam. Obecnie musicie na bardzo niskiej warstwie wsadzić liczbę z syscalla (termometr rdzenia, stan zasilania z mpu), która jest zwrotna i nieznana dla core (pseudorandom jest przewidywalny) i zrobić gruby branching if-elfów, żeby się krasnolud poniżej blisko stu procent trafił. Obecnie predyktor wyfluszy na samych błędach systemowych (targetowanie predykowanej pamięci i wywołanie czegoś co zostało popchnięte równolegle o “cykl” wcześniej), co i tak mitygujecie try/catchem bo by się cały, misterny plan wywalił na #GPfaulcie; najczęściej – masowe targetowanie z listy cpu to gpu po referencjach niezamrożonych w gpu, względnie – mielenie bazy danych bez chwili przerwy i wpieprzenie się z garba collectorem między tryby.
I raczej na tym skończę, bo zaraz wbijemy na reacta i useMemo, useCallback, HOC i reszta menażerii z tego tylko powodu, że klocki mają wewnętrznie symulatory klocków (zależne od tego jak skompilowaliście) więc Wasz smartfon z droidem może udawać model pamięci desktopa tylko dlatego, że arm i intel mają odwrotny problem i na intelu łatwiej targetować predyktorowi pamięć, a na armie instrukcje. Więc można dać hinta, że dla danego zakresu nic się nie zmieniło, więc te dane co były ostatnio to są dalej dobre. Koniec dygresji//
Przyjmijmy jednak, że chcemy ominąć te abstrakty. Tak najbrutalniej, najzupełniej i najużyteczniej. Polecenie In/Out nie musi być generowany przez normalny cpu/fpu. Może być inkrementalny poleceniem Out kolejnych adresów (dlatego inkrement) dla danego kontrolera oraz In na drugiej linii dla wyjścia. Można w ten sposób na chama sklonować całą pamięć (na przykład HDD) z boot sectorem i wszystkim co tam jest. Gdzieś w gratach chyba nawet mam takie urządzenie (musiałem dać słowo harcerza jankeskiej trzyliterówce, że nie użyję go w żadnym sprośnym celu kiedy jeszcze prowadziłem serwi), ale pasuje ono tylko do dysków tak zabytkowych, że leżą w muzeum obok gramofonu. Takie urządzenia (nowsze oczywiście) są powodem, dla którego hdd warto szyfrować kluczem sprzętowym wpinanym w kontroler hdd. @gruby będzie zorientowany co i jak, bo mi to się nawet domu już nie chce zamykać.
Innym wynalazkiem (gdzieś mi jeszcze karton tych zabytków pewnie się wala o ile nie wyrzuciłem) były sprzętowe “zamrażarki” do hdd. Polegały na tym, że po zainstalowaniu OS i uporządkowaniu dysk ustawiało się piny, ile pierwszych adresów nie przyjmuje write (po prostu do kontrolera sygnał nie docierał jeśli polecenie dotyczyło tego adresu) i nie można było tam wprowadzić zmian. Ale już w 2010 nowszy OS od M$ nie potrafił na takim hdd wstać. Takie zabezpieczenie łatwiej jest zwirtualizować, ale jak każde wirtualne można je na chama wyważyć z całymi drzwiami (dziury w pokładzie).
