tryby rzeczywistości 7/9 #Uprośćmy – jak to wyrosło;
//wróć do spisu treści; tekst jest fragmentem większej całości;
#Uprośćmy – jak to wyrosło;
Samo serce klocka (to co byśmy 50 lat temu nazwali procesorem) startując w najprostszy sposób (a jakoś musi i żaden inny nie działa bez wykonania instrukcji zapisanych w kamieniu – ROM startowy). Fetch (spuścić psy!) instrukcji (rejestr instruction pointer) przez szynę danych (kabelki) trafia do tego co nam się wydaje tak samo dzisiaj jak i 50 lat temu. Części klocka przełączającego wejścia do labiryntu dla kolejnych liczb, jakie pójdą z akumulatorów. One się w zasadzie od istrukcji nie różnią niczym bo też są sygnałem on/off w sekwencji i też trafią na tranzystory sprytnie popakowane w bramki logiczne (dokładnie tak jak instrukcja). Rozróżnienie jest dla nas, że ta pierwsza liczba (instrukcja) kieruje do podzespołu, gdzie te kolejne liczby mają kontekst dla nas (znaczy dodają się, albo są adresem pamięci, albo inny brzęczyk czy vga). Z punktu widzenia tranzystorów to niby jeden pies (ale im więcej bitów tym bardziej skomplikowany proces tego labiryntu, wykonywany automagicznie, który odpowiada na pytanie “a ile urwał prądu wpuścić do układu, żeby przez labirynt było na końcu cokolwiek do zmierzenia?” bo na każdej warstwie “drukowanej” trzeba innego prądu i na przykład mnożenie wymaga innego napięcia i natężenia niż dodawanie, bo będzie się zwijało po większej liczbie kabli).
Czyli IR (rejestr instrukcji), AX, EAX, RAX oraz kolejne literki “akumulatorów” to jest dokładnie taki sam rodzaj układu i on w zasadzie (przy obecnym d(t) cyklu) występuje niejako w “locie”, przy czym dla nas są to tranzystory, które na zadany sygnał mają wybrane ustawienie. I jedyne rozróżnienie w nazewnictwie, że to jest rejestr instrukcji a tamto danych/argumentów/akumulator jest dla nas do komunikacji homosapskiej. Różnicy technologicznej w abstrakcie nie ma, to tylko kwestia w jakiej kolejności te stany układu (on/off) będą się przewalać przez labirynt, bo gdyby to rozpisać jako labirynt to są to tylko kierunki otwórz/zamknij lub inaczej prawo/lewo na binary tree.
Klocek co prawda ma 64bity (long), ale RIP używa tylko 16 bitów. Tak – startujecie klocek w trybie rzeczywistym i rejestr instrukcji jednostki wykonawczej w tym trybie pozostaje (znaczy innego nie potrzeba; przy czym mamy 128 bitów więc opcode w kolejnych 16 i prefixy też jest częścią instrukcji, ale nie komplikujmy w jakim aktualnie stanie jest klocek 16/32/64 bo to wynika z poprzednio odpalonych na 16bit instrukcjach zgodnie z ROM). Poleceń i tak jest (z wariantami dla klocków) ledwo kilka tysięcy, ale samo polecenie (jako całość z akumulatorami) może zająć do 15 bajtów (120 bitów) bo zawiera opcode (instrukcja do dwóch bajtów czyli te nasze 16 bitów), prefixy (fpu32/64 czyli te EAX i RAX), operandy (argumenty, czyli same wartości adresowe pointer lub natychmiastowe immediate) [modR/M-tryb rejestr/pointer, SIB-skalowanie indeksu; displacment-offset rejestru; immediate-pamięć; – takie tam szczególiki co czym jest dla naszego rozumienia]. Przy czym prefixy są opcjonalne (znaczy jak klocek jest w trybie 64bit to wołanie eax jeszcze na zabytku, z jakiego to piszę by poszło, bo znaczy, że odpalamy antyczną aplikację; ale na nowym aniołki wcześniej zadbają by to wyprostować i takie garbate się nie robiło, albo w ogóle zakazać i użyszkodnika pogonić kodem błędu; wynika to z tego, że odpalanie takich rzeczy to proszenie się o wybijanie dziur w kadłubie).
Z grzeczności cały rejestr ma więc 128bitów, ale te pierwsze 16 jest na instrukcję (powiedzmy, że te 128 ustawień jest bardziej elastyczne i takiego sztywnego podziału 16 i reszta nie ma, ale na ludzki rozum takie uproszczenie wystarczy bo strukturalnie to tak działa). Dokładnie tak jak piszecie polecenie w dowolnego poziomu języku czasownik(wulgarne argumenty). Ten argument (pointer lub rejestr – z punktu widzenia klocka zestaw kabli; dla nas różnica do wyobrażenia) na obecnej szynie ma 48 bit. Jak się nie obrócicie to 112 bitów ze 128 mieści wszystko, bo po prostu w obecnych kompach nie ma więcej kabelków na szynie (i do niczego nie jest to na razie potrzebne; jak przejdziemy na exabajty to może będzie, ale na razie terrabajta l0cache nie planują^^ – z cyklu “na co komu więcej niż 640kB?” by Bill Szczypawka). Oczywiście po starcie kompa fetch jest powolny (bezpośrednio z ROM i później RAM), ale służy on uruchomieniu pozostałych podzespołów, które włączą swoje ścieżki fetchowania całych, przygotowanych, zdekodowanych paczek instrukcji hurtem. Dokładnie tak jak na początku dokonując sprawdzenia czy z nimi wszystko dobrze, czy klocek robi co mu kazano (znaczy klocek sam się sprawdza, czy noga, którą właśnie włączył daje mu kopa tak jak w instrukcji) i czy to dobrze wychodzi. Czyli to jest bardzo umowne (tak się umówiono, taki jest standard, taka lista poleceń jak w manualu to uruchamia, flipflop zostaje ustawiony i coś tam jakoś ma karmić procesor instrukcjami, tylko trzeba to najpierw włączyć w procesie real->ROM->OS/UEFI).
Zaznaczę, gdyby nie dotarło – samo serce klocka wykonuje instrukcję na FIZYCZNYM adresie (pamięci, urządzenia, po prostu kabla). Nic o żadnej wirutalizacji, stronicowaniu i takich tam bredniach do samego serca nie dotrze, to musi być zdekodowane (przez archiwistów wcześniej). Kiedy coś już trafi na klocek to nie zmiłuj, dlatego przy uruchomieniu grzecznie wczytujemy te wszystkie zabezpieczenia przed naszą głupością, ponieważ wykonych będzie wiele programów, i fajnie jakbyśmy nigdzie nie napsocili. Ale jak już coś wbijecie na klocek (na przykład przez dziurę w pokładzie) to fajnie jest wiedzieć uprzednio, co akurat w danym adresie jest lub ma być. To może być RAM, HDD, flipflop, brzęczyk.
Warto zauważyć, że już na tym etapie mając 32bitowe dane rejestrów (RAX, RBX) 64 bitowy adres pamięci (no bo 48 nijak w 32 nie wejdzie) nie jest wsadzany w klocek tylko wykonywany przez odwołanie. Czyli nasz 48 bitowy adres kabli (64bit standard, tylko na razie tylu kabli jeszcze nie trzeba podpinać) jest wsadzany (mox) do RBX (na przykład) i klocek nie obraca adresu fizycznego, tylko go podłącza z rejestru RBX (który to ma w 32 bitach „instrukcji”) i to dalej ustawia labirynt. Działa to tak, że powiedzmy w linii wczytujemy do pamięci RAX adres czegoś tam w RAM (fizyczny), a następnie jakiś inny adres fizyczny pamięci do RBX i trzecim poleceniem dopiero kopiujemy z „tego co tam jest w RBX” do „tego tam adresu w RAX”. Oczywiście z offsetami i takimi tam duperelami. To tylko konwecja otwierania ścieżek w labiryncie, instrukcja otwiera drogę do RAX, który otwiera drogę do fizycznego adresu, instrukcja otwiera drogę do RBX, który ma drogę do drugiego fizycznego adresu i po otwartej ścieżcie sygnał z fizycznego adresu pamięci jest przepisywany do drugiego. Teoretycznie mógłby przejść przez serce naszego klocka (przy wstawaniu tak się dzieje), ale później do tej zabawy jest uruchamiany oddzielny układ copypasty (w kontrolerze pamięci) i po zadaniu mu ścieżki serce klocka jest zwolnione z roboty (bo już zaprogramowało przy wstawaniu instrukcje jak urządzenia mają ze sobą gadać i przez którą część inkorporowaną do klocka gdziekolwiek fizycznie by się nie znajdowała, bo w każdym razie podpiętej do szyny). Akurat tutaj podałem przykład głupiego Jasia RAM-RAM, ale tak samo RAM-HDD czy inne udziwnienia działają. Jednak o tym co kto komu decyduje serce klocka.
Po starcie to serce klocka (jeszcze w trybie 16bit) jak zauważacie (start BIOSem) jest dość urwał wolne. Ponieważ musi się ono zająć całą zabawą w fde (fetch, decode, execute). I robi to z ROM i RAM, cache nie jest jeszcze uruchomiony (dopiero muszą paść komendy otwierające ścieżki spinające to z rejestrem). Czyli nadużywane jest polecenie JMP (przerzutnik – fizycznie) gdy podstawowe funkcje samego serca klocka to dwie warstwy – kontrolna (CU) i dekoder (D).
Fajnie jest go włączyć i wtedy mamy taką bardzo podręczną pamięć zaraz przy IR. Jest ona podzielona (kablami) na dwie części jak patrz wyżej – instrukcja i (L1l) i wartości (L1D), które prawie od razu wbijają klockowi w cyklu na odpowiedni tranzystor. Prawie, ponieważ przy zabójczych częstotliwościach obecnych klocków dzikie rzeczy dzieją się z prądem i instrukcje przelatując przez dekoder mogą się tak od razu nie przyjąć (zależy od liczby jedynek, które pożrą prąd) więc po drodze jest taki „bufor podtrzymania stanu” czyli zdekodowanej, surowej instrukcji gdzie zostanie podane już odpowiadanie dla danej warstwy napięcie (bo klocki są jak Ogry i mają warstwy), a niektóre bardzo nowoczesne klocki (mosty z piasku) mają tam po prostu wlepiony l0chache na 128bitów w obie strony bo inaczej nie działa.
Oczywiście tak samo uruchamiany jest cache poziomu drugiego i trzeciego (wspólny dla rdzeni). Ta drabinka pamięci istnieje dlatego, że wycieczka poleceniem do RAM i wrzucenie tego na klocka zajmuje jakieś 100 cykli (pamięć działa obecnie na innej częstotliwości, a po drodze trzeba jeszcze zmieniać prąd) gdy do cache nie więcej niż 4 cykle (a jeden z l1). Gdybyśmy rozpatrywali bardzo powolne, zabytkowe klocki ośmiobitowe to w ich cyklu pracy RAM był technicznie l1cache (w rozumieniu na dzisiejsze, tylko co innego robił – znaczy był pamięcią ulotną dostępną w jednym cyklu, a nie buforem na synchronizację danych jak drabinka cache).
W L1l można przechować (przyjmując 32k) jakieś osiem kilo instrukcji po średnio 4 bajty na instrukcję (te wszystkie opcode, immediaty i inny szpej). I to jest w zasadzie jednokierunkowa wycieczka, ponieważ ostatnio załadowany RIP wskazuje na pozycję L1D do załadowania i kolejną do instrukcji. W takim opisie dla człowieka, bo to jest po prostu ustawienie przełączników. Oczywiście może to być pozycja pusta (po uruchomieniu zarządzania pamięcią przez jednostkę pilnującą kto co kiedy ma wołać, czyli już po uruchomieniu dużej liczby podzespołów, a to akurat dopiero na wysokim poziomie). Cała ta drabinka cachy i RAM (której kiedyś nie było) wynika z tego, że ze względu na prędkość klocka, bardzo nikły prąd w najniższej warstwie klocka, różnicę prądu pomiędzy tym co chodzi „na górze” i na dole oraz wynikające z tego różnice w częstotliwości trzeba dane buforować i synchronizować. I dlatego ta sztafeta z podawaniem danych lejkiem do klocka. Kiedyś RAM chodził synchronicznie w jednym cyklu z klockiem i problem nie istniał. Obecnie ta zwłoka w okolicach jednej nanosekundy na sygnale z cache do IR jest tak duża, że trzeba rozwiązywać problem rozwijając drabinkę.
Kupmy się na przestrzeni ostatniego szczebla drabinki. Czyli sam IR (serce CPU) przechowujący (wyjątkowo krótko) we flipflopach 128bitową instrukcję (nie używamy całej, ale tyle tam miejsca obecnie jest w tym co średnio na jeża mamy na biurku). Po wuj tam jakiekolwiek dekodowanie instrukcji na raw? Czyli po co ten dekoder po drodze? Przecież instrukcja jest ładowana z cache… No więc tych numerków instrukcji i opcodów jest w pip i trochę, a kontekstem są flipflopy CR (wpływająna operand czy jesteśmy w 16/32/64 czy stronicujemy i takie tam) i „oczekiwany, zwrotny typ danych” co jest tak trochę obsługą wyjątków, z tym że… w trakcie działania większości programów używamy może ośmiu różnych instrukcji na krzyż (między innymi z powodu biasów w kompilatorach) i jeśli przejrzycie swoje programy to z copypasty wynika, dlaczego – tak za dużo to tam nawymyślane nie jest: ify, elsy, switche, iteracje, deklaracje, porównania (też ify), matma (ALU), zapisz/skreśl/nadpisz (MOV), zwróć no i raz na jakiś czas pytanie o stan klawiatury, myszy, karty sieciowej, kopnięcie czegoś do gpu czy w hdd. Czyli I/O, mov lub ALU. Dekodowanie sprowadza się głównie do odpowiedzi na pytanie ile prądu zadać na układ, na który i kiedy układ zwróci odpowiedź (potok). Cała zabawa (fetch i decode) zajmują od dwóch do sześciu cykli. Ale samo wykonanie (add int 1 cykl; mov to RAM do 100, a mov to HDD można wpisać w kalendarz) może zająć więcej i w tym czasie klocek zajmie się czymś innym. Stare klocki czekałyby na odpowiedź. No i te prostsze polecenia mają fasttrack przez klocek. Dlatego w iteracjach przetrwał zapis i++ zamiast i+=1, ponieważ instrukcja zwiększ wartość rejestru (dajmy na to eax) o 1, w kontekście (eax przechowuje int8) wymaga uruchomienie bardzo skromnej, z punktu widzenia rozkładu ścieżek bardzo krótkiej sekwencji i „reszta chuj”. A jeśli do tego eax odpowiada za iterowanie po kolejnym cache target (czyli odwołań do kolejnych pozycji) to cała operacja ma od razu pętlę zwrotną do cache bez odpalania ALU (oczywiście iterujemy po coś, więc tam po drodze pewnie będą inne zagadnienia), ale przyjmijmy, że iterujecie po danych zapisanych w liście i w każdej dokonujecie inkrementacji zamiast dodawania – taki zestaw instrukcji zostanie wykonany przez core inny niż zero (bo ten ma na głowie obsługę sprzętu jeszcze) w paczkach po 8 pozycji na cykl, a zwracanie do tego z powrotem do RAM i wołanie do cache będzie trwało dłużej od całej operacji). Jeśli jednak zamiast ++ wrzucicie i+=1.0 (bo w deklaracji użyliście, że i jest float32, mieliście jakiś tam powód – w gpu ten powód może mieć istotne znaczenie praktyczne, pewnie później to poruszę) to za każdym razem klocek będzie wołał ALU na kilka cykli i cały kod będzie wykonany jakieś 30 razy wolniej. Oczywiście jeśli kompilator został napisany dla kulawych przez potłuczonych to nie zauważycie różnicy^^
Dlatego na przykład suma dwóch akumulatorów o złośliwie różnych długościach bitowych (add [rax], ebx) zostanie zdekodowana na kilka cykli pracy ALU i jeśli w CR# użyliśmy stronicowania to jeszcze z pytaniem do archiwisty co tam akurat miało niby być wpisane (i miejmy nadzieję, że trafiło do L1D cachu uprzednio, bo jak nie…). Ponieważ w L1cahce kod polecenia jest dość długi to operując na poleceniach podstawowych można ich tam upchnąć multum i jeśli są zgrabnie dopasowane (na przykład operujemy na samych int8, używamy tylko dodawania, zwracamy do rejestrów wejściowych czyli a += -b) to dekoder otworzy naraz kilka bramek do adderów ALU i podepnie je do rejestrów wejściowych na wyjściu cyklu. Z takich innych superszybkich poleceń jest a != a i operacje bitwise. Za to normalnie używany comparing (porównania, nie SCAS) zwyczajowo są w kompilatorach robione głupio (czyli wolno) i bez specjalnych obejść (porównanie do zera) dla rozwiązań, które byłyby obsłużone fastrtrackiem.
Drobnym szczególikiem tego „dekodowania” jest to, że w cahche na linii nie jest przechowywana jedna instrukcja. Ale mogłaby być, wtedy byłby to „skrypt” działający (tylko i wyłącznie) na podstawowym klocku x86 z czasów mocno zaprzeszłych (8086), wtedy by działało, tylko wtedy by nie było cache i odwołania do linii, ale opcode byłby mapowany bezpośrednio i wprost (np. add to otwórz bramkę ALU) – obecnie trzeba rozwiązać po drodze zagadkę ile mamy ALU, czy są zajęte i na ile cykli w jakim trybie (bo można puszczać przez jedno alu kilka rzeczy w sekwencji, zanim dane wyjdą na końcu pod warunkiem, że przeprowadzamy tę samą operację na takiej samej szerokości danych; potok superskalarny). Przyjmijmy, że robimy coś złośliwego (na przykład kompilator nie zamienia dzieleń na mnożenia odwrotności) i ALU trafia nam z cachu siedem poleceń dzielenia. Sam klocek naraz może wykonać 4 do 6 z linii. Dzielenie jest przypadkiem ciężkim, więc ALU zostanie zajęte na 20 cykli. Takich ALU jest maksymalnie 6 na rdzeń (chyba że macie jakiś wyjątkowy custom super ekstra klocek). Więc to siódme (złośliwie dałem) tak czy stak poczeka. Biorąc pod uwagę zwrotki z tych rejestrów jedna liniia kodu zamist śmignąć z odpowiedzią w dwa cykle zamuli nam rdzeń na jakieś 45. W tym czasie to bylibyśmy już w pół drogi do RAM i przyjmując, że mamy taką strukturę danych, gdzie w paczce do semafora jest siedem dzieleń rdzeń będzie poważnie zamulał. I scheduler będzie ciężko dusił kolejne polecenia pchające się na IR nie robiąc hitów do cache po więcej zadań bo ma zajęy ROB (przełączniki w rdzeniu, które trzymają kolejne stany akumulatorów). Są jednak polecenia, które klocek potrafi zupełnie zignorować (szczególnie taki nowszy). Na przykład NOP (RIP++, next cycle) w „cachu zeorwym” w ogóle nie potrąci klocka tylko odwoła się do następnego cache target. Niby klocek powinien tę instrukcję wykonać, ale do tego trzeba mu na chama wepchnąć tryb rzeczywisty. Czyli dla Skylake istnieje instrukcja wykonywana w zero cykli^^; I to nie jedna. Zero idioms (zerowanie akumulatora/rejestru) takie jak xor eax, eax czy sub rax, rax też są zerocyklowe na skylake. Dzięki dekoderowi właśnie. Ale mul rax, 0 na przykład nie jest, choć wynik jest oczywisty i wepchnie się na ALU. Więc nie wszystko zostało przewidziane. I o ile ALU w nowych klockach jest już na tyle ogarnięte by flagować CI i OF przerzucając takie numery widłami szybko, to w 386 dalej by muliło 20do40 cykli, żeby jednak przez to zero pomnożyć. Do tego standard ieee754 nie zawsze nam odpowiada (i klepiemy matmę ręcznie w kodzie), ponieważ… infinite + num nie zwraca informacji o inf i liczbie tylko inf. inf+ (-inf) zwraca NaN choć mogłoby zero (jeśli bawimy się w ciało hyperreal między epsilonem i infem to zazwyczaj w kategoriach 0-1 i robimy to na gpu).
To polecenie ma jednak pewien niuans związany z zasilaniem. Otóż używanie go wcześniej pozwalało… „schłodzić” mocno przetaktowany klocek. I było wrzucane „atomowo” (bez locków i semaforów) cyklicznie między rdzenie przy znacznym obciążeniu ALU (na przykład w miningu).
A jak chcecie kogoś wq… zamulając mu kloca (bo na przykład zalega z fakturą) to w kodzie wystarczy dorzucić funkcję, która na bazie linked list korzysta z losowych adresów pamięci uparcie nakazując dzielenie. Kilo iteracji i jesteśmy ćwierć miliona cykli w plecy w większości na samych cache miśsiach, a dzielnie to przy tym vat. Oczywście z poleceń głupich polecam jeszcze SCAS.
Dlaczego dekoder instrukcji jest taki ważny i się nad nim pieklę? Ponieważ to jest najistotniejszy moduł w schemacie. Tłumaczy on instrukcje na prąd względem jakiegoś schematu i tak naprawdę nie ma znaczenia jaka jest logika rdzenia. Bo z x86 obecne mają wspólne co najwyżej szczytne kartii historii. Przyjmijmy, że za dekoderem stałby komputer kwantowy, demon siódmego piekła, albo konclave czarodzieji – też by działało, po prostu dekoder musiałby się przynajmniej w tę stronę dogadać niezależnie czy sygnał dalej byłby elektryczny, optyczny, magiczny a nawet w kratkę. A programowalibyśmy dalej tak samo.
Zaznaczam, że dekoder instrukcji w trybie rzeczywistym prawie nic nie robi, gdyż nie ma po co uruchamiać schedulera (bo nie działa cache, a nawet jak zacznie to byłoby to głównie cache miss) z tego prostego faktu, że mocno zamulony liczeniem rdzeń wygrzebie się z operacji w 40 cykli, a pobranie czegoś z ram lub rom zajmie najmniej 50 (i to przy dużej dozie optymizmu). Działanie dekodera w abstrakcie dla jajogłowych nazywane jest mikroops, ale to są po prostu przełączniki ścieżek w labiryncie na podstawie dodanego tam labiryntu, który udaje patern recognition. Najbliżej tego co można wyjaśnić człowieko, który tego nie rozgrzebuje, to nazwanie tego sitem szyfrowym – czymś znanym już kilka stuleci temu, nakładało się to na kartkę i „sprwdzało hash” – jak był ok, znaczy to jest właściwie sporządzony dokument.
Skoro już poruszyliśmy scheduler to ma on jeszcze jedną, istotną funkcję fizyczną – zarządza „dostawami prądu” (sterując PMU, ROB – reaorder buffer, czyli „kolejkowaniem”, ale nie tym, które macie programowalne, tylko fizycznym bajzlem flipflopów na wejściu do IR) do zbilansowania otwieranych ścieżek fizycznych po układzie. No i dlatego w trybie innym niż rzeczywisty, jeśli wybijecie dziurę w kadłubie i zaczniecie majstrować przy ustawieniach schedulera in/out na chama, to ten bilans w zakresie 1 do 10 mikroampera na tranzystor się nie doda. Głupie dzielenie SSE/AVX to odpalenie nawet jakiś stu tysięcy tranzystorów na nawet 40 cykli. Rejestry odpowiadające za te ustawienia są w MSR (model specyfic register) ustawiane przez BIOS, a później cedowane do OS (w nasyzm przypadku to UEFI jest już OS) i BIOS dla przykładu ustawia limity mocy PL1 i PL2 (P state). Jest też mikrokod obsługujący przypadki szczególne (bardzo poważne problemy energetyczne – włączanie/wyłącznie rdzenia i krytyczne padaki związane z poważną awarią czegoś po drodze czy thermal throttling). W trybie rzeczywistym PMU w ogóle nie jest uruchomione. Prąd jaki jest zadany na klocek „każdy widzi”. No i dlatego lepiej, że on sobie powoli mieli^^ Czyli z grzeczności nie majstrujemy w CR3 przy PMLn dla 64bit. No chyba, że bardzo nie lubimy naszego klocka. MSR, które Was pewnie zaciekawią to IA32_PMCx (licznik PMU wraz z IA32_PERF_CTL steruje napięciem i częstotliwością), IA32_MPERF (wydajność), IA32_APIC_BASE (nazwa sama wskazuje) oraz „jak ma być” IA32_ENERGY_PREF_BIAS (balans) gdzie możemy wbić przez RDMSR i WRMSR (read/write) i dopuścić się nikczemności (wymagany CR0, czyli dziura w kadłubie). Oczywiście kloca możemy wywalić na wiele sposobów (grzebiać w rejestrach segmentowych kodu, stacku), ale palenie kloca uważam za dużo bardziej klickbaitowe.
PMLn to stan „wysiłku” energtycznego. Im niższy numerek tym większy. Wyższe napięcie, więcej jednostek dla rdzeni odpalonych. Ponieważ wielu czytelników korzysta z linuxa do liczenia to najczęściej robi to w trybie PML0 (cpufreq) na pełnej petardzie. Przy czym po uruchomieniu wbijanie na adres 0x199 IA32_PERF_CTL z WRMSR na P-state P0 nic nie da, ponieważ dostęp do ROM/RAM jest tak powolny, że klocek i tak pomiędzy tymi rzadkimi cyklami dłubie w nosie, a tylko by się grzał (agresywnie czekając).
