tryby rzeczywistości 1/9 #wstęp
Ponieważ dyskusja pod ostatnim grafomaństwem się rozwinęła, a @Koncereyra natchnął mnie księżniczkowaniem (które omawialiśmy na zjeździe w kontekście “dlaczego moje teksty są o kant, chociaż u mnie działa”) to poczułem się sprowokowany do wyjaśnienia krok po kroku dlaczego operowanie (tymi) abstraktami jako bytami rzeczywistymi się przyjęło i później ciężko przestawić tryb myślenia (mi w górę, innym w dół). Bo cały ten porządek stoi na ramionach krasnoludków.
Dla żartu podrzucę “jak Ci jakaś gałąź z lasu pasuje to ją bierz”:
A dla kontekstu “skąd się wziąłem”:
Wiele zawiłych rzeczy odnośnie “dlaczego potrzebujemy abstrakcji” jest w tym filmie wyjaśnione w sposób strawny dla ludu, bez wnikania w szczegóły.
Podzielę to na części, i wrzucę hash co można pominąć;
#Po co abstrakcja pierwszej warstwy; dwie strony;
#Różnica między BIOS a UEFI; BSP; sprawdzanie czy wszystkie nóżki są w gnieździe;
#Konstytutka do wiązania rąk Bogu; Control Register;
#Co tam w rdzeniu są za kable? Kontynuacja uprośćmy;
Na sam początek strolluję tekst, którego zapewne i tak nie przeczytacie. Wszystko tutaj pomijając pewne rozgryzione struktury i antyczną dokumentację, która już wyszła spod kołderki patentowej (bo i tak układy lub ich odpowiedniki są dziergane na całym świecie) oraz tajemnicy firmy jest opisem… abstrakcji. Te układy o których opowiem, poza bazą x86 na real mode i pewnej części FPU32 nie istnieją obecnie jako fizycznie wyodrębnione układy w klocku. Ze względu na ilość identycznych jednostek robiący to samo (na przykład jednostka wykonawcza zwana ALU to jest tak naprawdę to co nazywamy programowalnym procesorem, a reszta to przybudówki tego urządzenia to tam są tablice prawdy w postaci układu bramek złożonych z tranzystorów, które możecie w dużej skali złożyć na stole) są one na ostatnich warstwach poprzeplatane, tak żeby dla każdego układu starczyło i klocek nie był na tyle duży aby obliczanie rezystencji i potrzebnego do poskromienia jej prądu nie rozbijało się o ścieżkę, która w d(t) zegara zadającego napięcie ma długość będącą dobrym przybliżeniem nieskończoności[co dla 40nm pod 4gigahece redukowane przez interkonektor do ca 3gigahec to astronomiczne… 500mikrometrów w M0, ale ścieżki robi się w mul0.1 tej długości maks, ze względu na inne problemy, i najpóźniej na setce mikrometrów trzeba wbijać repeater, czyli gdzieś tam musi być tranzystor; jeśli trzeba jechać dalej, a często trzeba to wbija się na wyższą warstwę, gdzie są szersze ścieżki i więcej prądu; dlatego istnieją oddzielne rdzenie i mogą się dzielić pamięcią dopiero w wysokich warstwach L3chache]. Opisując układy początkowo będziemy w świecie rzeczywistej części klocka, później historycznie dającego wyodrębnić się układu, który niekoniecznie jest wyodrębnialny w nowych klockach; ale im bardziej będziemy w fpu64 tym bardziej będą to abstrakty, doraźnie składane tablicą ROM (uops, mikrokod), których skutkiem logicznym jest zachowanie tożsame z pewną wariacją (najczęściej uproszczeniami) zadań kooprocesora 8087. Którego nie można nasrać po całym układzie bo i tak nie miałby co robić, więc doraźnie się składa uopsami potrzebny. Na potrzeby tekstu będę przyjmował, że taki układ dla operacji jest potrzebny, ale gdzieś po 20 stronach będę mocno zaznaczał, że to jest doraźnie złożony puzzel. I postaram się nie wjechać w tym tekście na uopsy. Z początku będzie fizycznie, później będzie coraz bardziej abstrakcyjnie. I postaram się tego nie łamać – oryginalnie każdy z tych układów istniał, tylko poskładanie ich w takiej ilości jaka jest dziś “emulowana” w klocku (nie na raz, ale przyjmijmy, że każdy układ ma zależnie od potrzeb dostęp) wymagałoby grubego kabla z prądem stałym doprowadzonego do klocka rozmiaru pokoju i byłby… wolniejszy.
Z czego płynie kolejny wniosek (dla zorientowanych), że będę opisywał czas w “cyklach”, który to opis miał sens w klockach jeszcze 30 lat temu, i chodziło o cykl zegarowy jedynego cpu na pokładzie, taktowanego synchronicznie z pamięcią zadaniem napięcia. Obecnie każdy układ w każdym rdzeniu pobiera prąd negocjacyjnie (między innymi po to, żeby nie spalić kloca, w tekście będzie gdzie to można popsuć jeśli odpowiednio rozdziewiczyć pierścienie dostępu, ale nie polecam, i to jest też mocno zależne od konkretnego klocka; w niektórych możecie sobie ustawić cokolwiek, a on i tak jest nadpisywany sygnałem własnej końcówki pmu, którą trudniej wykoleić, ponieważ nie ma do niej jawnej dokumentacji) i dopasowuje swój “cykl” w locie. Ponieważ zajmiemy się przez kolejnych jakieś… trzydzieści stron tym co się dzieje zanim odpalimy OS (w rozumieniu potocznym, jakiegoś linuxa lub windowsa, bo UEFI to też już OS, a BIOS trochę jeszcze nie – zagadnienie z gatunku “czy wirus jest żywy?”) to w kontekście wewnątrz układu w rdzeniu jest cykl prądowy (co nam zazwyczaj nic nie daje w przypadku większości operacji bardziej skomplikowanych od cepa), w rdzeniu jest to zwrot do rejestru (więc nawet 50 cykli prądowych, ale rejestrów jest kilka i w tym czasie mogą one robić rożne rzeczy, a nawet asynchronicznie się zakolejkować by zrobić jedną, co najczęściej oznacza, że wbiliście dzielenie i pierwiastkowanie na dużych wykładnikach przy zmiennym przecinku), dla rdzenia jest to zwrot do cachu, dla zespołu rdzeni pod semaforem prędkość zwrotek do l3cache, a przy atomizacji każdy sobie, i dla całego klocka jest to zdolność komunikacji z urządzeniem kontrolującym RAM (lub innymi urządzeniami zewnętrznymi tego typu). Sprawa stała się skomplikowana i postaram się tu przejść do porządku dziennego nad pewnymi uproszczeniami przyjmując, że w rozumieniu cykl chodzi zazwyczaj o cykl zegara zadającego prąd do jednocyklowej jednostki logicznej (tablicy prawdy jednocyklowej, ponieważ obecne klocki mają operacje zerocyklowe, odpowiedź jest znana zanim padnie pytanie). Po prostu nie chcę tego komplikować nad potrzebę tłumacząc, że tak zwana jednocyklowa operacja add int zajmuje tak naprawdę 14 cykli przełączeń prądu przez oscylator taktujący klocka. Przyjmujemy, że na nasze potrzeby to jest jeden cykl.
Czego tu nie będzie (postaram się uniknąć, żeby zmniejszyć liczbę dygresji) – mikrokodu nie będzie tym razem. Gdzieś tam pewnie się natkniecie na jego artefakty, takie jak dlaczego pierwsza (zerowa) komórka pamięci to ostatnia? Dlaczego istnieje offset pamięci skoro jest znamy data type (i dlaczego tego na gpu nie trzeba) oraz podobne historie. Celem tego uniku (oczywiście aby mieć o czym ględzić później) jest skupienie się na architekturze x86 i jej mutacjach (czyli klockach, które raczej macie poza bardzo mobilnymi zastosowaniami i siuperkompiutrem w instytucie) oraz rozwinięcie liczby abstrakcji, które przez obecnych kodoklepców uznawane są za z Nieba dane raz na zawsze, a nie na skonstruowane abstrakty, którymi zawiadują inne abstrakty w tylu warstwach, że zanim dojdziemy do kabli to jest to nierozróżnialne od magii. Więc ten “szczególik” w jaki sposób generowany i odwracany (oraz po wuj jest odwracany) jest sygnał przepływający przez klocek pominę. To odrębna dyskusja. Z której wynika wiele głupot, które też powszechnie uważane są za zdeterminowane maszyną – nie są. Po prostu tak wyszło bo legacy. Sam proces zostanie wspominany pod pozorem predyktorów w czymś co dziś zaczyna być cacheL0 klocka, oraz wewnętrznym zarządzaniu klocka pomiędzy dostępne ALU, FPU i podobne udziwnienia. Ale nie będę tłumaczył jak to fizycznie się odbywa i dlaczego. Mikrokod pomijamy, upraszczamy, że jakaś tam linijka ASM jest ciągiem 0/1 dla dekodera instrukcji i ta część klocka “jakoś” sobie radzi ze zrobieniem z tego sygnału In na rejestr instrukcji rozkładając go na kabelki. Mikrokod po drodze odwalają na razie “krasnoludki”, kwestią big i little endian’mess się nie zajmiemy. Tą razą. Choć tu i tam zahaczę o pipelining (na kilku poziomach, nie tylko na tym) czy opcode.
