Motto:
X=[xk-1-β ϕ (xk-1)](β k-1- β -m)+Σxiβ+[xk-1-(β - 1) ϕ(xk-1)] β -m.
prof. Janusz Biernat „Arytmetyka komputerów” 1996
/* w założeniu miało to być tłumaczenie książki prof. Janusz Biernata „Architektura komputerów” na język przyjazny studentowi. Niestety, okazało się to niemal niemożliwe. Tłumaczenie następowało z prędkością ok. 25 stron/dzień, a egzamin był coraz bliżej. Wiele fragmentów jest więc żywcem przepisanych z książki - jest to po prostu streszczenie książki, z nielicznymi komentarzami. Mam nadzieję, że na coś się przyda. Od razu przepraszam za literówki itp. - pisałem szybko i na sprawdzenie tekstu, jak na razie, nie miałem czasu. Niech, w poniedziałek Bóg ma nas w swojej opiece i niech moc będzie z nami. */
1. KOMPUTER I PROGRAM.
1.1. Komputer z programem zintegrowanym. --> [Author:DD]
[ ogólnie o modelu von Neumanna]
Główne części komputera z programem zintegrowanym to: pamięć (memory), centralna jednostka przetwarzająca (central processing unit CPU) i linia je łącząca (connecting tube), zwana współcześnie magistralą.
W modelu von Neumanna zadaniem procesora jest przetwarzanie danych, których jedynym źródłem jest pamięć. Wykonanie rozkazu rozpoczyna się pobraniem z pamięci słowa interpretowanego jako kod instrukcji i obejmuje: pobranie danych z pamięci, przetworzenie i zapis wyniku do pamięci. Chwilowe umieszczenie danych w rejestrach procesora służy zmniejszeniu obciążenia magistrali i nie jest sprzeczne ze schematem przetwarzania. Wynik rozkazu zależy od skutku przetwarzania rozkazów poprzednich.
Zasadniczą rolę pełni pamięć, zorganizowana zgodnie z następującymi zasadami:
informacja jest przechowywana w komórkach (cell) o jednakowym rozmiarze, każda komórka zawiera jednostkę informacji zwaną słowem (word)
znaczenie słów nie jest przypisane ich treści, sposób przechowywania danych i instrukcji jest identyczny (kod informacji nie zawiera żadnej etykiety)
struktura (kod) słowa nie pozwala odróżnić instrukcji od danych - interpretacja słowa zależy jedynie od stanu maszyny w momencie przyjmowania słowa z pamięci
komórki tworzą zbiór uporządkowany, a każdemu słowu można przypisać unikatowy wskaźnik lokacji - adres
zawartość komórki pamięci może zmienić jedynie procesor dokonując przesłania słowa do pamięci.
Procesor może czytać tylko z pamięci głównej (operacyjne), bo tylko ją adresuje. Aby coś odczytać z innych pamięci (np. wtórnej) należy odpowiednie dane przesłać do pamięci operacyjnej i dopiero z niej odczytać. Pamięć główna charakteryzuje się swobodnym dostępem - znaczy to, że czas przesłania słowa między procesorem a pamięcią nie zależy od lokacji (adresu) tej komórki.
Program jest sekwencją rozkazów, co oznacza, że w chwili wykonywania rozkazu wiemy gdzie znajduje się następny rozkaz (lub możemy to określić). Układ wytwarzający adres następnego rozkazu to licznik rozkazów (program counter). Na zmianę domyślnej kolejności rozkazów pozwalają rozkazy rozgałęzień (branch) i skoków (jump).
Klasyczny model von Neumanna z czasem został nieco zmieniony. Najważniejsze innowacje to:
rozdzielenie pamięci programu i danych (zabezpiecza rozkazy przed zniszczeniem, równoczesne pobranie kodu i argumentu)
dodanie stosu programowego (przydatne szczególnie przy rekurencjach)
wprowadzeni trybów adresowania
pamięć wirtualna - metoda adresowania symbolicznego
nowe typy danych - zmiennoprzecinkowe, łańcuchy itp.
1.1.1. Komputer jako automat.
[Rozdział mocno zjechany]
Działanie komputera z programem zintegrowanym można porównać do działania automatu skończonego. Stan komputera określa stan pamięci i stan procesora ( a dokładniej jego rejestrów) - ogólnie przyjmuje się, że stan komputera jest konfiguracją pamięci. Zmiana stanu komputera jest skutkiem wykonania rozkazu przez CPU. Rozkaz jest funkcją, która przeprowadza stan wejściowy w stan wyjściowy. Pamięć jest zatem zarówno dziedziną, jak i zbiorem wartości tej funkcji. Należy jednak pamiętać, że komputer w odróżnieniu od innych automatów może zmieniać swoją sekwencję sterowania (chodzi o zmianę kolejności wykonywanych programów - czyli skoki). Automat, który jest w stanie interpretować kody rozkazów, nazywa się maszyną właściwą (mikromaszyna) (chodzi po prosu o procesor, który potrafi jedynie zinterpretować rozkaz, ale nic z nim nie zrobi bez oprogramowania), a ten który przetwarza sekwencje instrukcje na bezpośrednio wykonywane rozkazy zakodowane w pamięci to maszyna wirtualna (komputer z oprogramowaniem).
Cykl procesora - czas potrzebny na zmianę jego stanu.
Cykl pamięci (rozkazowy) - czas potrzebny na zmianę stanu pamięci - może obejmować kilka cykli procesora..
Etapy wykonywania rozkazu są wykonywane w kolejnych cyklach procesora:
fetch - pobranie kodu rozkazu z pamięci (do rejestru)
decode - dekodowanie rozkazu (by określić sygnały sterujące)
address generation - wytworzenie adresu argumentu
memory read - odczytanie argumentów z pamięci
execute - wytworzenie wyniku
write - zapisanie wyniku do pamięci.
1.1.2. Poziomy opisu programu.
Poziomy maszynowe (chodzi o abstrakcyjne poziomy umożliwiające wykonanie programu):
L0 - przetwornik sygnałów (hardware)
L1 - struktura logiczna (firmware)
[poniższe są dopiero dostępne dla użytkownika]
L2 - system operacyjny (udostępnia listę rozkazów i standardowe usługi systemu np. odczyt plików itp.)
L3 - język asemblerowy
L4 - język algorytmiczny (np. C++, Pascal)
L5 - język makropoleceń.
Konwersje między poszczególnymi poziomami:
kompilacja - tłumaczenie całego algorytmu na sekwencje rozkazów procesora. Kod wynikowy musi być załadowany do pamięci. Otrzymujemy optymalny kod. Wymaga dużej ilości pamięci. [.txt(kompilator)->.obj(konsolidator)->.exe(do pamięci i procesora).
interpretacja - osobne tłumaczenie danego polecenia dopiero w momencie jego użycia. Wymaga niewielkiej ilości pamięci, ale jest czasochłonne. Niektóre interpretery generują najpierw kod pośredni (p-kod), który jest następnie natychmiast wykonywany (są to pseudokompilatory)
Z procesami tymi łączy się problem przenośności programów, czyli możliwości wykonania danego kodu na każdej maszynie - rozwiązaniem jest stosowanie na poziomie systemu operacyjnego maszyny wirtualnej - przetwarza ona kod źródłowy na kod wymyślonego procesora, tak zdefiniowanego, aby łatwo można było przejść na kod maszyny docelowej (tak właśnie jest w JAVIE).
1.2. Architektura komputera.
Architektura - sztuka projektowania i konstruowania obiektu.
Kompozycja - architektura, budowa, z czego zbudowane zegarek - tarcza, wskazówki).
Organizacja/konstrukcja - moduł wykonawczy, to co nadaje obiektowi kształt operacyjny (rodzaj napędu i układ przekładni).
Realizacja - wyraz technologiczny organizacji, jakimi metodami wykonane (technologia wykonania balansu i przekładni).
Architektura komputera - zbiór rozkazów i pamięć.
Architektura procesora - opis cech funkcjonalnych komputera na poziomie przepływu danych w centralnej jednostce przetwarzającej CPU.
Cechy przejrzystej architektury:
spójność - możliwość odtworzenia pełnej architektury na podstawie niepełnej specyfikacji (wszystkie mają te same cechy - np. reagowanie na błędny argument, dokładność wykonania, struktura kodu). BŁĄD: w 80x86 wszystkie operacje logiczne modyfikują rejestr warunków F oprócz operacji NOT.
ortogonalność - niezależna specyfikacja funkcji należących do odrębnych kategorii (tryby adresacji, funkcje arytmetyczne itp.) BŁĄD: w Motorolach możliwość kopiowania typu pamięć-pamięć, ale nie można już dodawać pamięć-pamięć [chyba chodzi o to aby wszystkie rozkazy danej grupy uzywały tych samych typów i formatów danych]
trafność - specyfikacja i implementacja tylko tych funkcji, które są logicznie spójne z architekturą: ekonomiczność (wyeliminowanie funkcji logicznie niespójnych), przeźroczystość (oddzielenie architektury od organizacji - budowy od modułu wykonawczego)
ogólność - zapewnienie możliwości działania danej funkcji zgodnie z jej przeznaczeniem (bazując na jej dziedzinie). Brak ograniczenia od strony organizacji : kompletność (funkcjonalna pełność w obrębie klas funkcji - np. wszystkie potrzebne operacje arytmetyczne - niestety zajmuje to przestrzeń kodową) BŁĄD: realizacja OR i AND, a nie ma NAND i NOR; otwartość (możliwość ciągłego rozwoju).
1.3. Szybkość przetwarzania.
Czas wykonania programu jest wprost proporcjonalny do czasu cyklu procesora Tc (potrzebnego na wykonanie zmiany stanu procesora), zależy od liczby wykonań (cykli) danej instrukcji oraz średniego czasu potrzebnego na wykonanie jednej takiej instrukcji.
Liczba wykonań instrukcji i średni czas jej wykonania są ze sobą ściśle powiązane. W maszynach o bogatym repertuarze instrukcji liczba instrukcji potrzebnych do wykonania danego programu jest stosunkowo niewielka, za to średni czas ich wykonania jest dłuższy, bo są to operacje bardziej skomplikowane.
Średnia liczba cykli potrzebnych na wykonanie instrukcji zależy od złożoności listy rozkazów i organizacji procesora. Im więcej rozkazów mamy do dyspozycji, tym dłużej jest wykonywany proces dekodowania rozkazów i generacji sygnałów sterujących procesorem.
Jednym z głównych powodów zwolnienia całego procesu wykonania rozkazu jest ciągłe odwoływanie się do pamięci (przy pobraniu rozkazu, a także zapisania jego wyniku), gdyż czas dostępu do pamięci jest znacznie dłuższy od cyklu procesora. Działania na pamięci możemy przyspieszyć na kilka sposobów:
użycie lepszej technologii wykonania pamięci
użycie blokowej struktury modułów pamięci, umożliwiającej współbieżne wykonanie cykli pamięci (skraca czas selekcji) - przeplot pamięci
zwiększenie liczby rejestrów (Nie można jednak dodawać ich zbyt dużo - nie opłaca się - utrudnione jest adresowanie rejestrów i wydłuża się czas dekodowania rozkazu. Poza tym w rejestrach nie umieścimy przecież programu do wykonania)
użycie pamięci podręcznej (cache memory) zawierającej jedynie kopie danych potrzebnych do wykonania bieżącej części programu. Czas dostępu do tej pamięci jest porównywalny z dostępem do rejestrów procesora.
Drugim czynnikiem znacząco wpływającą na czas wykonania programu jest organizacja przetwarzania. W maszynach ściśle sekwencyjnych każdy etap wykonania programu wykonywany jest jeden po drugim FDEW (fetch,decode...).
Jednym ze sposób poprawienia wydajności jest oddzielenie od siebie jednostki wykonującej rozkazy od jednostki je pobierającej (jednostka magistrali) i zastosowanie bufora kolejki rozkazów.
Odseparowanie układów wykonujących różne etapy kolejnych rozkazów umożliwia ich współbieżne wykonanie w trybie potokowym. Np. w tym samym momencie można pobierać następny kod rozkazu, dekodować aktualny, wykonywać poprzedni..
Niestety, wydajność potoku spada skutkiem występowania nieuniknionych konfliktów przetwarzania:
konflikt sterowania - zaburzenie wykonywanej sekwencji poprzez rozkaz rozgałęzienia - problem rozwiązuje tzw. prognoza rozgałęzień realizowana na podstawie rozpoznania rozkazu rozgałęzienia.
konflikt danych - jednoczesne użycie tej samej danej
konflikt zasobu - jednoczesne żądanie przez układy wykonawcze dostępu do pamięci (np. równocześnie chce pobrać następny rozkaz i zapisać wynik). By wyeliminować konflikty dostępu do pamięci wewnętrzną pamięć można rozdzielić na pamięć programu (stąd pobiera rozkazy) i danych (tutaj wrzuca wyniki).
Przetwarzanie superpotokowe - dodatkowe podzielenie etapów wykonywania rozkazu na podetapy (np. etap wykonania na 2 części), umożliwia skrócenie cyklu, a to z kolei pozwala na wzrost szybkości przetwarzania. Głębokość potoku nie może być jednak zbyt duża, bo podział etapu wymaga zwiększenia liczby buforów separujących i spowalnia cały proces.
Przetwarzanie superskalarne (skalowane) - wzrost wydajności uzyskany jest poprzez użycie większej ilości jednostek wykonawczych (tzn. równocześnie można wykonywać np. dwie fazy execution), pod warunkiem, że między wykonywanymi rozkazami nie występuje konflikt danych. Innym sposobem jest równoczesne użycie specjalizowanych jednostek np. jednostki stało- i zmiennoprzecinkowej). [rys.2.10]
1.4 Ewolucja architektury komputerów.
CISC (complex instruction set computer) - duża liczba rozkazów, w tym te bardzo skomplikowane, złożone tryby adresowania, mały plik rejestrowy (mało rejestrów). Zauważono, że najbardziej złożone rozkazy (i tryby adresowania) używane są najrzadziej. Poza tym duża ilość rozkazów zwalnia proces ich dekodowania i wykonywania.
RISC (reduced instruction set computer) - mała liczba rozkazów, krótszy czas dekodowania, więcej rejestrów (rzadsza komunikacja za pamięcią). Obecnie istotą architektury RISC jest rozsądne projektowania listy rozkazów (jednolita struktura kodów rozkazów, łatwość dekodowania itd.).
2. ARCHITEKTURA LISTY ROZKAZÓW.
Architektura listy rozkazów jest najniższą warstwą abstrakcyjnego opisu komputera.
2.1 Architektura procesora.
Zasadniczymi elementami funkcjonalnymi architektury komputera są [rys.3.1]:
procesor - zawierający jednostkę sterującą CU, jednostkę wykonawczą EU oraz jednostkę adresową MU
pamięć - przechowująca dane
magistrala - łącząca procesor z pamięcią
Jednostka sterująca - zawiera:
rejestr rozkazów IR - służy do przechowywania kodu rozkazu pobranego z pamięci operacyjnej
rejestr stanu SR - pamięta bieżący stan procesora.
W układzie dekodera, stosownie do treść pobranego kodu i bieżącego stanu procesora wytwarzane są sygnały sterujące działanie procesora.
Jednostka wykonawcza - tworzy ją jednostka arytmetyczno logiczna ALU, bufor pamięci MBR i plik rejestrów roboczych R. Jednostka wykonawcza musi posiadać co najmniej jeden rejestr roboczy (do tymczasowego przechowywania argumentów). Informacja o poprawności wyniku umieszczana jest w rejestrze warunków CR. ALU jest zwykle rozdzielona na jednostkę stałoprzecinkową (operacje logiczne i na liczbach całkowitych) i jednostkę zmiennoprzecinkową . Korzystają one zwykle z osobnych plików rejestrowych.
Jednostka adresowa - jej zasadniczą częścią jest układ wytwarzania adresu MAG (memory addres generator). Zawiera on plik rejestrów adresowych MAR , a wśród nich SP - wskaźnik stosu.
Architektury ALU i procesora [rys.3.2]:
akumulatorowa AC - argument i wynik operacji jest umieszczany w tym samym rejestrze (czyli normalnie w AX - akumulatorze). Drugi argument jest odczytywany z pamięci i umieszczany jest w rejestrze buforowym MBR.
Szczególny rodzaj to architektura stosowa - argumenty czytane są ze stosu (ściągane), a wynik wpisywany jest w miejsce wskazywane przez SP (po ściągnięciu argumentów).Taką architekturę mają koprocesory numeryczne INTEL 80x87 i FPU INTEL 80486+.
Rejestr - pamięć R/M. - przynajmniej jeden argument podawany jest z rejestru. Drugi argument podawany jest również z rejestru lub pamięci, tam też umieszczony zostaje wynik działania (miejsce drugiego argumentu pełni rolę akumulatora).
Uniwersalna R+M - argumenty i wynik może znajdować się zarówno w rejestrze jak i w pamięci. Konieczne jest tu użycie kilku rejestrów MBR buforujących pamięć.
Rejestrowa L/S - wszystkie argumenty umieszczone w rejestrach ogólnego przeznaczenia. Wyjątkiem jest operacja kopiowania zawartości rejestru do pamięci, lub pamięci do rejestru (wtedy jednym z argumentów musi być pamięć).
Inaczej wygląda schemat przetwarzania w procesorze potokowym [rys.3.4]
Mamy tutaj z separowanymi stanowiskami przetwarzania: stanowisko pobierania kodu, dekodowania i dostępu do rejestrów, wykonania, przekazywania wyniku. Poza tym mamy już rozdzielenie pamięci podręcznej na pamięć kodu i danych.
W pierwszym etapie, na podstawie obliczonej wartości licznika rozkazów, z bufora kodu IM pobierany jest rozkaz. Następnie bieżąca wartość PC oraz kod rozkazu zostają zatrzaśnięte w buforze separującym. W drugim etapie, kod wpisany do rejestru rozkazów IR zostaje zdekodowany, zostaje odtworzony pełny kod i wpisany do kolejnego bufora separującego. W etapie wykonania zostaje uruchomiona jednostka arytmetyczno-logiczna lub sumator adresów- wynik działania zostaje zapisany w kolejnym buforze separującym. W ostatnim etapie wynik zostaje zapisany do pamięci danych DM lub do odpowiednich rejestrów. W dowolnej chwili w każdym stanowisku może być przetwarzany inny rozkaz, każdy w innym etapie zaawansowania.
2.2 Lista rozkazów.
Specyfikacja każdego rozkazu zawiera:
składnia - formalna postać zapisu działania ( mov ax,bx)
funkcja - opis realizowanej funkcji w notacji symbolicznej
wynik argument1 [OPERACJA argument2] np. al. (al.)+(dx)
cechy - rozmiar i format argumentów. Np. Motorola korzysta z zapisu
MNEMONIK[.rozmiar] [źródło] [cel] np. inc.b (a6,1) - zwiększa liczbę w bajcie pamięci o adresie (a6+1). Opis funkcji to [(a6+1)][(a6+1)]+1. Instrukcje procesora klasyfikuje się wg liczby argumentów:
zeroadresowe - do sterowania działaniem komputera (bezargumentowe), lub po prostu argumentami są domniemane rejestru procesora (np. stosb itp.)
jednoadresowe - używany jest tylko jeden argument lub pozostałe są domniemane (inc AX, MUL BX)
półtoraadresowe - jeden z argumentów musi być umieszczony w rejestrze procesora, wynik zaś jest zapisywany w lokacji zajmowanej przez argument
Wieloadresowe - wiele argumentów (np. operacje arytmetyczne)
- opis funkcjonalny - słowny opis instrukcji np. dodanie do półsłowa o adresie (bx+si) w segmencie es liczby z rejestru dx. (add es:[bx+si],dx).)
kody warunków - co zmienia w rejestrze stanów.
format kodu - wykaz i opis pól słowa kodu maszynowego. Poszczególne pola identyfikują rodzaj operacji i wszystkie jej argumenty. Argumenty umieszczone w rejestrach procesora są identyfikowane przez wewnętrzny kod rejestru. Identyfikacja argumentów umieszczonych w pamięci następuje przez wskazanie trybu adresowania (sposobu wyliczenia adresu argumentu). Argumenty domniemane (np. akumulator) są identyfikowane kontekstowo. Argumenty będące danymi podawanymi bezpośrednio stanowią odrębną cześć kodu rozkazu. Gdy możliwe jest użycie operandów różnego rozmiaru to kod operacji musi zawierać również identyfikator rozmiaru argumentów.
W architekturze rejestrowej L/S (RISC) pełny kodu rozkazu ma rozmiar słowa maszynowego (32-bity). Przez to operandy mają bardzo ograniczony zakres. W znaczący sposób ogranicza to rozmiar listy rozkazów i tryby adresowania. Zaletą jest wspólny rozmiar kodu dla wszystkich rozkazów co znacznie przyspiesza proces dekodowania rozkazu. [rys. 3.5]
W architekturze akumulatorowej, rejestr-pamięć i uniwersalnej mamy już różne rozmiary kodów (bo argumenty z pamięci i rejestrów, skomplikowane tryby adresowania) (CISC). Różnorodność rozkazów procesorów CISC wpłynęła na niejednolitość w strukturze kodów rozkazów (chodzi nie tylko o różną długość rozkazów, ale także strukturę części służącej do identyfikacji rodzaju operacji).
W architekturze Intel 80x86 jednostką informacji jest bajt (pozostałość po 8085). Pełny kod instrukcjo realizowanej przez jednostkę stałoprzecinkową 80x86 może zawierać od 1 do 15 bajtów (* dopiero od 80386):
przedrostek blokady magistrali lock
przedrostek* zmiany domniemanego rozmiaru adresu
przedrostek* zmiany domniemanego rozmiaru operandu
przedrostek zmiany domniemanego segmentu
przedrostek powtórzenia rep dla operacji na łańcuchach
kod rozkazu
rozszerzenie kodu
bajt trybu adresowania AMB (addressing mode byte)
bajt* rozszerzenia adresu SIB (scale-index-base)
1,2, lub 4* bajty przemieszczenia
1,2, lub 4* bajty argumentu bezpośredniego
W architekturze Motorola 680x0 pełny kod rozkazu zawiera od 1 do 8 półsłów 16 bitowych w tym:
właściwy kod rozkazu
rozszerzenie kodu opisujące tryb adresowania
1 lub 2 półsłowa przemieszczenia bazy
1 lub 2 półsłowa przemieszczenia zewnętrznego
1 lub 2 półsłowa argumentu bezpośredniego.
2.3 Adresowanie danych.
2.3.1 Indeksowania danych.
W architekturze klasycznej pamięć jest uporządkowanym zbiorem komórek, każda komórka zawiera jednostkę informacji zwaną słowem. Informacje, których rozmiar przekracza pojemność słowa, musza być umieszczone w kilku komórkach. Występuje wtedy problem, którą część zapisać jako pierwszą (czy bardziej czy mniej znaczącą). Stosowane są dwie konwencje.
- Big Endian - (BE) - ważniejszy niższy, końcowy wyższy - słowo (bit) zawierające bardziej znaczącą część informacji ma niższy adres. Czyli po prostu zapisujemy do pamięci po kolei idąc od lewej strony. Np. gdy mamy liczbę 4562 to najpierw zapiszemy 45 (niższy adres), a dopiero potem 62 (wyższy adres).(POWERPC)
- Little Endian - (LE) - ważniejszy wyższy, końcowy wyższy - słowo (bit) zawierające mniej znaczącą cześć informacji ma niższy adres. Czyli tak jak normalnie w procesorach Intela - zaczynamy zapisywać do pamięci od części najmniej znaczącej (od prawej) i to ona ma najniższy adres, a część najbardziej znacząca ma adres najwyższy. Np. 4562, 62 -zapisana jako pierwsza, niższy adres.
W architekturze Motorola 680x0 numerowanie bajtów jest zgodne z BE, a numerowanie bitów w konwencji LE.
2.3.2 Rozdzielczość adresowania.
Rozdzielczość adresowania wpływa na konstrukcję kompilatora. Jest to po prostu rozmiar jednostki informacji (słowa). Możliwe jest również (poprzez specjalne rozkazy) działanie na pojedynczych bitach - uzyskujemy wówczas rozdzielczość absolutną.
Ponieważ zasadniczą jednostką jest jednak słowo muszą mu „podlegać” wszystkie mechanizmy wewnętrzne - np. dane systemowe są tworzone w formacie zgodnym z rozmiarem słowa maszynowego, szerokość magistrali danych procesora powinna umożliwiać przesłanie co najmniej jednego słowa maszynowego w jednym cyklu pamięci. W momencie gdy rozmiar magistrali danych umożliwia jednoczesne przesłanie kilku jednostek danych (mniejszych od rozmiaru słowa - np. możemy adresować po bajcie, a słowo jest 2-bajtowe ), pojawia się problem wpasowania. Aby go uniknąć należy przestrzegać prostej zasady - jeśli słowo obejmuje dwie komórki, to adres słowa powinien być parzysty, jeśli cztery to powinien być podzielny przez 4. Tylko takie wpasowanie umożliwia dostęp do danych w jednym cyklu pamięci i nie spowalnia wykonywanego programu.
2.3.3 Przestrzenie adresowe.
Ze względu na sposób adresowania wyróżnia się:
rejestry robocze - identyfikowane przez unikatowe nazwy (kody) rejestrów
pamięć główną - miejsce w pamięci identyfikuje adres (wyznaczany zgodnie z trybem adresowania)
przestrzeń sterowania - identyfikowana przez unikatowe nazwy (kody) rejestrów
stos - identyfikowany przez adres szczytu stosu (SP)
przestrzeń urządzeń peryferyjnych (wejścia - wyjścia) - identyfikuje adres.
Szczególnym rodzajem danej nie pochodzącej od powyższych źródeł jest identyfikator źródła przerwania.
Aby zapewnić jednoznaczność adresowania przestrzenie adresowe powinny być separowane (fizycznie i logicznie na poziomie architektury rozkazów). Separacja ta powinna wykluczyć możliwość jednoczesnego wskazania różnych danych lub dostępu do danej w różnych obszarach adresowych (po prostu, jeden adres ma wskazywać tylko jedną daną w obszarze).
2.3.4 Odwzorowanie przestrzeni adresowych.
[temat ciężki do opisania - trzeba popatrzeć na rysunki w książce]
Atrybutami separowanej przestrzeni adresowej są: dokładność (rozmiar najmniejszej adresowalnej jednostki danych) oraz zakres adresowania (liczba potencjalnie adresowalnych jednostek). W przestrzeni adresowej niektóre obszary są zastrzeżone przez producenta sprzętu lub przez system operacyjny. Blokada dostępu może być tymczasowa i unieważniana przez przełączenie bloków pamięci dostępnych alternatywnie w tym samym obszarze adresowym . Bloki takie tworzą pamięć przysłoniętą (shadow memory).
W procesorach Intela 80x86 przestrzeń wejścia - wyjścia i przestrzeń pamięci są rozdzielone logicznie i możliwe są różne ich odwzorowania (mapping):
osobne adresowanie pamięci i przestrzeni we-wy (odrębne rozkazy dla obu przestrzeni).
jednolite adresowanie, rozdzielone są jedynie fizycznie, ale w obu można stosować te same rozkazy.
W 486 i Pentium wydzielono dodatkowo przestrzeń stosu zmiennoprzecinkowego (rejestry)( 8 lokacji 80-bitowych). W Pentium MMX i II rejestry te wykorzystywane są również do zastosowań multimedialnych.
[najlepiej sytuacje prezentuje rys.3.10]
W Motorolach 680x0 wydzielono z całej pamięci przestrzeń we-wy i przestrzeń rejestrów koprocesorów.
Podobne rozwiązania są stosowane w architekturze RISC, gdzie oprócz przestrzeni pamięci i przestrzeni sterowania, zdefiniowano osobne przestrzenie rejestrów stało-i zmiennoprzecinkowych.
2.3.5 Tryby adresowania.
Użycie danej wymaga wytworzenia jej adresu rzeczywistego w pamięci głównej, zwanego też adresem fizycznym. Na poziomie programu każdej danej jest przypisany adres logiczny. Współbieżne wykonanie programów (tzn. jednoczesna praca na dwóch aplikacjach korzystających z tych samych zasobów) wymaga nadania każdej danej na poziomie procesu unikatowego adresu wirtualnego zawierającego oprócz adresu logicznego jeszcze identyfikator procesu. Trybem adresowania nazywamy sposób przetworzenia adresu wirtualnego lub logicznego w celu wyznaczenia adresu liniowego. Jeśli dostępna pamięć jest mniejsza od tej, którą jesteśmy w stanie zaadresować, to adres liniowy podlega dodatkowej translacji.
Tryby adresowania ze względu na liczbę składowych wskaźnika adresu:
Adresowania zeroelementowe - jest to adresowanie bezpośrednie, w którym adres jest niejawny. Można je podzielić na:
zwarte - jeden lub wszystkie operandy są domniemane
błyskawiczne - krótki kod danej umieszczony w wyróżnionym polu bitowym słowa kodu operacji jest częścią tego kodu.
natychmiastowe - za pomocą licznika rozkazów PC, słowo kodu danej stanowi rozszerzenie kodu rozkazu.
Adresowanie jednolelementowe - bezpośrednie lub pośrednie, jeden jawnie podany wskaźnik. Wyróżnia się adresowanie:
bezwzględne - skrócony adres danej jest umieszczony w polu słowa kodu operacyjnego rozkazu lub pełny adres jest polem rozszerzenia kodu
bezwzględne pośrednie - adres adresu danej jest słowem rozszerzenia kodu rozkazu
rejestrowe bezpośrednie - argument jest umieszczony we wskazanym rejestrze , identyfikowanym w słowie kodu rozkazu
rejestrowe pośrednie - adres danej jest umieszczony w rejestrze, identyfikowanym w słowie kodu rozkazu.
rejestrowe pośrednie z modyfikacją - adres umieszczany w rejestrze jest automatycznie zwiększany (+1) po uzyciu lub zmniejszany (-1) przed użyciem.
Adresowania wieloelementowe - jest zawsze adresowaniem pośrednim. Wskaźnik adresu jest wyznaczany jako funkcja dwóch lub większej liczby składowych adresu, którymi są:
baza - adres odniesienia (miejsce, od którego zaczynamy obliczać adres) umieszczony w rejestrze procesora.
przemieszczenie bazy (offset) - stała adresowa, w adresowaniu dwupoziomowym dodawana do bazy, stanowiąca osobne słowo rozszerzenia kodu rozkazu
indeks - umieszczony w rejestrze procesora wraz z mnożnikiem skali wskazanym w słowie lub słowach rozszerzenia kodu
relokacja lub przemieszczenie zewnętrzne - stała dodawana do wyznaczonego adresu, stanowiąca osobne słowo rozszerzenia kodu rozkazu.
W adresowaniu jednopoziomowym suma składowych adresu wyznacza lokację danej w pamięci. W adresowaniu dwupoziomowym niektóre składowe adresu są użyte do wskazania lokacji w pamięci zawierającej adres odniesienia (bazę pośrednią), który jest dodawany do pozostałych składowych. Po prostu niektóre składowe nie są dodawane, ale jedynie wskazują inną daną i to ona właśnie ma być użyta w sumowaniu.
Szczególnym typem adresowania wieloargumentowego jest adresowanie względne, w którym adresem bazowym jest zawartość licznika rozkazów.
Najprostszym rodzajem adresowania wieloargumentowego jest adresowanie dwuelementowe, które można podzielić na:
bazowe z przemieszczeniem
indeksowe z przemieszczeniem
bazowo-indeksowe
względne z przemieszczeniem
względne indeksowe
W architekturze Intel 80x86 zaadresowanie każdej danej w pamięci wymaga dwóch wskaźników: segmentu i adresu względnego wewnątrz segmentu o wartości nie przekraczającej rozmiaru segmentu. Wskaźnik segmentu, umieszczony w rejestrze, wyznacza adres liniowy początku segmentu (bazę odniesienia). Najbardziej złożonym trybem adresowania w Intel 80x86 jest dwuwskaźnikowe skalowane adresowanie bazowo-indeksowe z przemieszczeniem:
Adres liniowy = ([wskaźnik segmentu])+(baza)+(indeks)*skala+przemieszczenie
W Motorolach używany jest m.in. mechanizm adresowania dwupoziomowego, używający bazy pośredniej jako odniesienia dla innych składowych. Stosuje się również:
adresowanie preindeksowe - indeks jest użyty do wyliczenia adresu bazy pośredniej, a adres logiczny jest obliczany ze wzoru:
LA = [(baza)+przemieszczenie+(indeks)*skala]+relokacja
[na podstawie bazy, przemieszczenia, indeksu i skali wyznaczany jest drugi adres bazy (baza pośrednia) i potem jest ona traktowana jak zwyczajna baza - dodajemy do niej relokacje (odpowiednik przeniesienia).
adresowania postindeksowe - indeks służy do modyfikacji bazy pośredniej, a adres logiczny jest obliczany ze wzoru:
LA = [(baza)+przemieszczenie]+(indeks)*skala+relokacja
[baza pośrednia jest wyliczana na podstawie bazy i przemieszczenia - i ta suma jest traktowana jako nowa baza do której później dodawana jest relokacja(przesunięcie) oraz skalowany indeks.
Adresowanie opisowe (deskryptorowe) - jest to metoda adresowania wieloelementowego (adresowanie pośrednie). Rolę bazy pośredniej pełni tutaj deskryptor (realizowany przez wskaźnik) i to do niego dodawana jest baza, przemieszczenie i skalowany indeks. Oprócz adresu bazowego (pośrednia baza) deskryptor zawiera również informacje umożliwiające realizowanie selektywnego dostępu do bloku.. Adresowanie opisowe ułatwia elastyczne adresowanie zmiennych strukturalnych przy jednoczesnym zachowaniu prywatności (np. w pamięci wirtualnej. Podstawowo różnica między adresowaniem postindeksowym, a deskryptorowym jest to, że w dwupoziomowym adresowaniu pośrednim (post...) adres bazowy zawarty jest rejestrze, a w deskryptorowym jest to pełny adres wirtualny danej.
Adresowanie wieloelementowe jest charakterystyczne dla architektury CISC. W maszynach RISC-owych najbardziej złożonym trybem jest adresowania skalowane bazowo-indeksowe.
Adresowanie danych strukturalnych - w praktyce sprowadza się do adresowanie stosu i kolejki. W przypadku stosu do adresowania wykorzystywany jest wskaźnik wierzchołka stosu. Takie adresowanie wymaga modyfikacji wskaźnika po działaniach na stosie. Mamy tu dwie sytuacje - jeśli stos jest rozbudowywany w kierunku adresów malejących to mamy tryb predekrementacji wskaźnika (dla przesłań na stos) i postinkrementacji (dla odczytu ze stosu) [ogólnie rzecz biorą stos rośnie w dół]. Mechanizm odwroty jest używany w obrębie stosu tworzonego w kierunku adresów rosnących.[stos rośnie w górę]
Drugim typem strukturalnym jest kolejka (bufor LIFO). Jej adresowanie wymaga dwóch wskaźników - początku i końca kolejki (lub rozmiaru kolejki). Mechanizm ten jest stosowany jedynie przy tworzeniu tzw. bufora rozkazów. Inne mechanizmy wspomagające adresowanie danych strukturalnych: sprawdzanie uprawnień dostępu do danych, testowanie zakresu (gdy mieści się w nim adres), adresowanie pól w rekordach.
2.4. Projektowanie listy rozkazów.
Projektowanie listy rozkazów rozpoczyna się zwykle od określenia repertuaru rozkazów i trybów adresowania, których użycie wydaje się intuicyjnie konieczne. Czujemy również, że sens ma tworzenie rozkazów bardziej skomplikowanych od samego warunkowego kopiowania (które notabene wystarczy) oraz tworzenia nieco bardziej skomplikowanych trybów adresowania, chociażby po to aby uzyskać dostęp do danych strukturalnych.
Analiza częstości używania danych rozkazów pokazuje, że istnieje tylko pewien repertuar rozkazów intensywnie używanych. Na poziomie języków wyższego rzędu są instrukcje warunkowe (if..then..else), wywołania funkcji, pętle i przypisania. Na poziomie architektury rzeczywistej najczęściej używa się instrukcji rozgałęzień i pętli, instrukcji arytmetycznych i logicznych oraz porównań. Rzadziej używamy rotacji, przesunięć i operacji na polach bitowych. Instrukcje kopiowania typu move są szczególnie często używane w architekturze CISC, ze względu na mała ilość rejestrów. Nie możemy jednak określać listy rozkazów jedynie po częstości ich używania - niektóre rozkazy, pomimo tego, że są rzadko używane, są całkowicie niezbędne (chociażby rozkazy ochrony danych czy trybu wirtualnego). Podobnie sprawa wygląda z trybami adresowania - praktyka pokazuje, że najczęściej używamy adresowania pośredniego bazowo-indeksowego oraz rejestrowego z modyfikacją (w tym adresowanie stosu). Na potrzeby ochrony danych niezbędna jest również implementacja adresowania deskryptorowego.
Koncepcja architektury list rozkazów.
Tworzenie architektury listy rozkazów wymaga rozstrzygnięcia kilku zasadniczych problemów:
repertuar działań podstawowych lub uznanych za niezbędne
sposób wskazywania argumentów
tryby adresowania pamięci
sposób przekazywania stałych do programu
sposób realizacji rozgałęzień i zmiany przepływu sterowania
realizacja procedur i metoda ich zagnieżdżania.
Ponadto rozkazy powinny mieć jednolitą strukturę kodu (spójność), używać jednakowych typów i formatów danych (ortogonalność) i obsługiwać jednakowe tryby adresowania. Należy dokonać wnikliwej analizy spójności (z innymi założeniami projektowymi, takimi jak sposób obsługi wyjątków i przerwań), ortogonalności i przejrzystości.
Struktura kodu rozkazu
Przejrzystość wstępnie zaprojektowanej architektury rozkazów ułatwia projekt struktury kodowania. W architekturze ortogonalnej można bowiem zdefiniować strukturę kodu rozkazu jako złożenie pól bitowych określających:
rodzaj działania
kody argumentów
rozdzielczość adresowania argumentów
wartości stałych
alternatywne sposoby wykonania.
Niestety nie jest możliwe ustalenie jednego formatu dla wszystkich instrukcji - nie pozwalają na to chociażby rozkazy rozgałęzień, wymagające innych argumentów niż np. rozkazy arytmetyczne. Prowadzi to do ustalenia kilku standardowych formatów rozkazów (np. dla arytmetycznych), jednak z zachowaniem maksymalnej spójności ich struktury. Wspólne dla wszystkich rozkazów powinny być przynajmniej pola określające rodzaj działania, a dla rozkazów używających podobnych argumentów także pola wskazujące argument docelowy i jeden argument źródłowy.
3. DANE I DZIAŁANIA.
[rozdział nie będzie opisany w całości, większość została już przerobiona na „Arytmetyce komputerów” i miejmy nadzieję, że rozdziała ten nie będzie obowiązywał na egzaminie.]
W architekturze CISC standardowe słowo ma 16 bitów, zatem złożenie dwóch bajtów nazywamy słowem. W maszynach klasy RISC, gdzie typowe słowo maszynowe ma 32 bity, termin słowo oznacza złożenie 4 uporządkowanych bajtów.
Wszystkie dane przetwarzane przez komputer można zaklasyfikować do jednej z trzech grup:
3.1. Kody rozkazów.
Kody rozkazów zawierają informację określającą rodzaj operacji i identyfikatory argumentów tych operacji. Dla danej architektury jest ustalona struktura i rozmiar kodu, będący wielokrotnością elementarnej jednostki informacji. Zwykle na kilku bardziej znaczących bitach słowa jest kodowany rodzaj lub klasa operacji (np. arytmetyczne ), kolejne pola bitowe identyfikują argumenty, pozostała część słowa zawiera informacje dodatkowe. Np. w Intel 8080 grupę operacji wskazywały 2 najbardziej znaczące bity jednobajtowego słowa maszynowego, znaczenie kolejnych bitów było różne zależnie od rodzaju operacji. W współczesnych procesorach, których słowo maszynowe jest zwykle 32-bitowe, pole rodzaju działań, identyfikujące rodzaj informacji, jest zwykle 6-bitowe, kolejne pola 5- lub 6-bitowe są używane do wskazania operandów w pliku rejestrowym , znaczenie pozostałych bitów słowa zależy od rodzaju rozkazu wskazanego w polu identyfikującym typ operacji.[rys.4.1]
W praktyce cecha ortogonalności rozkazów może być zachowana tylko w grupach rozkazów (bo przecież operacje arytmetyczne i np. skoków korzystają z innych argumentów), czego skutkiem są różne formaty kodów. W momencie gdy długość słowa była niewystarczająca do zakodowania wszystkich informacji (w starych procesorach) wprowadzano kody złożone, w których pierwsze słowo udostępniało dodatkową przestrzeń kodową, a właściwy kod udostępniało dopiero drugie słowo. Niestety takie działanie zwiększa złożoność dekoderów i wydłuża etap dekodowania.
3.2. Dane systemowe.
Podczas wykonywania programu niezbędne jest pamiętanie informacji kontekstowej, umożliwiającej zapewnienie spójności programu, szczególnie podczas współbieżnej realizacji programów. Dane te są wytwarzane przez procesor i mają ustaloną strukturę. Typowe to oczywiście stos i kolejka (opisane już wcześniej). W przypadku stosu może wystąpić jego przepełnienie (stack overflow) (ponieważ rozmiar stosu jest ograniczony), lub wyczerpanie (stack underflow), podczas próby pobrania z pustego stosu. Jeśli chodzi o kolejkę to po dodaniu do niej nowego elementu zwiększany jest jej wskaźnik końca, przy pobraniu zwiększany jest wskaźnik początku. Przy pustej kolejce wskaźniki początku i końca pokazują na to same miejsce w pamięci. Jeśli rozmiar kolejki jest ograniczony, to, niezależnie od wartości wskaźnika końca, może nastąpić jej wyczerpanie, gdy wskaźnik czoła przekroczy wartość maksymalną. Zapobiega temu zapętlenie kolejki, któremu odpowiada obliczanie bieżących wskaźników czoła i końca modulo rozmiar kolejki. Może wówczas nastąpić przepełnienie kolejki, jeśli wskaźnik czoła po kolejnym zapełnieniu osiągnie wartość równą wartości wskaźnika końca.
[??????????????cos się chyba komuś w tej książce pomyliło????????????????]
Dane systemowe mogą być też zorganizowane w formie bloków o strukturze narzuconej rodzajem informacji, zwanych segmentami lub tablicami systemowymi (np. kontekst procesora obejmujący zapis „zamrożonego” stanu procesora.
3.3. Dane użytkowe.
Najbardziej różnorodny jest repertuar typów danych użytkowych definiowanych przez projektanta algorytmu. Można wyróżnić trzy zasadnicze typy takich danych:
skalarne - używane do ilościowego opisu wielkości jednowymiarowych
jakościowe lub wyliczenia, indeksujące cechy obiektu:
- logiczne
- znakowe
- opisowe
dyskretne, o ustalonej dokładności
- porządkowe, często utożsamiane z naturalnymi
- całkowite
- wymierne - stałoprzecinkowe lub ułamkowe
pseudorzeczywiste
- zmiennoprzecinkowe
- logarytmiczne
strukturalne - uporządkowane zestawy danych skalarnych
zestawy - nieuporządkowane zbiory danych
wektory i tablice - uporządkowane zbiory danych, w tym łańcuchy (uporządkowane ciągi znaków) i liczby zespolone (uporządkowane pary liczb)
rekordy - nieregularne struktury danych dowolnych typów
wskaźnikowe - definiują dane, występujące w operandach w trybach adresowania, lokalizujące obiekt. Dzielimy je na:
skalarne - używane do lokalizacji danych w liniowej przestrzeni adresowej
strukturalne
wektorowe - umożliwiają elastyczne adresowanie bezpośrednie. Zawierają identyfikator spójnego bloku pamięci i identyfikator lokalizacji wewnątrz wskazywanego bloku (czyli przesunięcia w bloku).
deskryptorowe, umożliwiające elastyczne adresowanie pośrednie (wskaźnik do tablicy wskaźników). Potrzebne do realizacji mechanizmów ochrony i separacji zadań.
Należy pamiętać przy tym, że w rzeczywistej maszynie o architekturze klasycznej, żadnej jednostce informacji nie jest przypisany typ, etykietowanie danych jest bowiem sprzeczne z koncepcja komputera z programem zintegrowanym. Typ danej zawartej w słowie maszynowym jest zawsze odczytywany na podstawie przypisania rozkazu i jego argumentów. Przypisanie takie nie musi być jednoznaczne - należy więc sprawdzić poprawność wyniku na poziomie generowania kodu. Standardowe typy argumentów implikowanych kontekstowo (których typ możemy ustalić analizując powiązanie rozkazu z argumentami) to: typ całkowity, porządkowy (naturalny), logiczny i zmiennoprzecinkowy ( w niektórych architekturach również znakowy, i typ pixel ).
4. PRZEPŁYW STEROWANIA.
W projektowaniu architektury listy rozkazów zakłada się zwykle, że każdy rozkaz jest niezależną jednostką. Czasami jednak przydaje się rozpatrzyć zależności między poszczególnymi rozkazami, gdyż może okazać się to przydatne w niektórych konstrukcjach programowych. Typowymi przykładami instrukcji funkcjonalnie zależnych są instrukcje repeat i execute. Instrukcja repeat zawiera specyfikacje poleceń, które należy powtórzyć oraz liczbę powtórzeń lub warunek zakończenia, execute natomiast, wskazuje polecenie które jest faktycznie wykonywane.
4.1. Rozgałęzienia.
W komputerze z programem zintegrowanym domyślnym porządkiem wykonania instrukcji jest kolejność ustalona w programie. Uporządkowana struktura programu implikuje zależność lokacyjną rozkazów, może być ona : łańcuchowa (każda instrukcja dodatkowo zawiera adres następnej instrukcji - dodatkowe miejsce), lub sekwencyjna (wykonywane po kolei, jak są w pamięci - niestety, nie umożliwia sterowania przebiegiem programu). Nietrudno zgadnąć, że kompromisem jest przyjęcie jako zasady zależności sekwencyjnej oraz dopuszczenie jako wyjątku zależności łańcuchowej, która umożliwi sterowanie. Naruszenie liniowego porządku instrukcji przez realizację zależności łańcuchowej jest skutkiem decyzji. Decyzje zależą od warunków, a ich podjęcie przebiega w trzech etapach: wytworzenie warunku, wybór warunku i użycie warunku. Wytworzenie warunku jest skutkiem wykonania działania, zwykle arytmetycznego lub logicznego. Najczęściej podstawą podjęcia decyzji (źródłem warunku) jest porównanie. Porównanie polega zwykle na sprawdzeniu przynależności do zakresu, poprawności wyniku lub zgodności ze wzorcem. Kod warunku zazwyczaj jest umieszczany w przestrzeni warunków: w rejestrze warunków (flagowym) lub wyjątków. Dzieje się tak przeważnie po wykonaniu operacji arytmetycznych lub logicznych. Zazwyczaj decyzja jest podejmowana na podstawie ustawień rejestru flag dokonanych przez operację poprzedzającą warunek. W PowerPC stan rejestru warunków może być ustalony wcześniej.
Użycie warunku może nastąpić na trzy sposoby:
przechowanie stanu logicznego warunku - spełniony, niespełniony
realizacja rozgałęzienia w sposób jawny (instrukcja)
realizacja rozgałęzienia w sposób implikowany (pułapka, obsługa wyjątku).
Wykonanie decyzji przez użycie wybranego warunku realizują rozkazy warunkowe, implementowane na poziomie architektury maszyny rzeczywistej jako rozgałęzienia, zwane też skokami warunkowymi. Rozgałęzienie może być wykonane jako skok ze śladem (na stosie odkładany jest adres instrukcji następnej po rozgałęzieniu). Pułapka (trap) jest rozgałęzieniem realizowany w trybie obsługi wyjątku. Wytworzenie : cmp AX,BX
Rozgałęzienie: jng adres [loop adres]
Zapamiętanie : setng zmienna
Kopiowanie : cmovcc AX,BX (warunkowe kopiowanie, gdy nie spełnione omija tylko jedną
instrukcję)
Pułapka: into
„Budowa” rozgałęzienia wymaga wskazania adresu docelowego. Użycie adresów względnych ogranicza zakres skoku, lecz ułatwia relokację kodu. Dla rozgałęzień typu pułapka i przerwanie lepsze jest użycie adresów bezwzględnych, bo lokalizacja procedury obsługi wyjątku może być znana przed rozgałęzieniem i niezależna od miejsca i czasu wystąpienia wyjątku.
W architekturach RISC poszczególne elementy instrukcji warunkowej są często zintegrowane jako pojedyncza instrukcja „porównaj i skocz” ( w MIPS R2000, bcc arg1,arg2,adres).
W procesorach klasy CISC warunki są wyrażane jako funkcje stanu rejestru kodów warunkowych (chodzi o FLAGS), modyfikowanego zwykle przez instrukcję wykonaną bezpośrednio przed rozkazem rozgałęzienia. [ tutaj należy obejrzeć Tablicę 5.3. Warunki w procesorach klasy CISC).
Szczególnym rodzajem warunku jest wynik testowania licznika powtórzeń, umieszczonego w jednym z rejestrów procesora (CX) - następuje zliczanie w kierunku zera, zatrzymanie gdy 0 ( w Motorolach gdy -1). W procesorach Intela wykorzystuje to np. instrukcja LOOP i REP.
Rozgałęzienia w językach programowania.
[ bardzo prostu temat więc omówię w skrócie, w większości na przykładach programów]
if A>B then polecenie
mov eax,A
cmp eax,B
jle koniec ; alternatywą zapisującą wynik operacji logicznej jest setle B
call polecenie
koniec:
case i,n ((i<n)=>polecenie[i],(i>n)=>poleceni[0])
;(nie implementowana na poziomie listy rozkazów) operacja przebiega dwuetapowo - najpierw w rejestrze procesora zostaje umieszczony indeks ;wariantu , który jest następnie użyty jako argument rozkazu skoku pośredniego, wskazujący adres ;docelowy skoku w tablicy adresów
cmp bx,N
jb mniejszy
xor bx,bx
mniejszy:
shl bx,2
call ds:[bx]
;w architekturze RISC nie mamy niestety rozkazów skoku pośredniego - koniecznej jest wówczas przekazywanie adresu pośredniego przez stos lub rejestr połączenia.
for licznik:=wartość_pocz step przyrost(-1) until limit do polecenie
mov cx, limit
sub cx,wartość_pocz-1
powt:
polecenie()
loop powt
;w architekturze Motoroli licznik rozkazów może być umieszczony w rejestrze danych D#, ;kontynuowanie wykonania pętli może być uzależnione od spełnienia dodatkowego warunku, tak ;jak zwykłych rozkazach rozgałęzień. Podobnie jest w PowerPC, natomiast w MIPS nie ma ;w ogóle pętli.
while warunek(A>B) do polecenie
start:
mov eax,b
cmp eax,a
jle skip
polecenie()
jmp start
skip:
repeat funkcja until warunek(A>B)
start:
polecenie()
mov eax,A
cmp eax,B
jg start
4.2. Funkcje i procedury.
[niestety, tutaj zaczynają się schody]
W kategoriach abstrakcyjnych poziomów architektury można wyróżnić cztery poziomy hierarchii w programie: moduły pełniące rolę funkcji, funkcje złożone z instrukcji wykonujących elementarne działania i mikrooperacje tworzące instrukcje, wykonywane w cyklach maszynowych.
Podział programu na moduły, wykonujące odrębne funkcje, ułatwia implementację algorytmu, bo moduły mogą być traktowane jak rozbudowane pojedyncze instrukcje. Moduły, łatwiejsze do testowania i tłumaczenia na kod maszynowy, mogą mieć również strukturę modularną. W efekcie tego cały program może być zorganizowany jako hierarchiczna struktura funkcji realizowanych przez moduły. Stosowanie funkcji daje wiele korzyści:
redukcja rozmiaru kodu przez usunięcie powtarzalnych fragmentów programu
ukrycie szczegółów realizowanego algorytmu i struktury danych
łatwą implementacje wyższego poziomu abstrakcji - maszyny wirtualnej (?)
Strukturalizacja kodu może jednak utrudniać optymalizację kodu, ponieważ musimy się zajmować zmiennymi lokalnymi, wywoływaniem i powrotem z funkcji, jak również sama budowa hierarchiczna zwiększa czas wykonania programu. Dodatkowo wywołanie funkcji wywołuje duże zaburzenia w przetwarzaniu potokowym.
Wynikiem działania funkcji jest wartość (zwykle liczba) albo wskaźnik obiektu, przekazywany przez rejestr lub stos. Procedury to funkcje, które nie zwracają wartości. W językach strukturalnych wyższego rzędu opis funkcji (procedury) ma postać bloku, który zaczyna deklaracja stałych i zmiennych lokalnych. Następnie zapisywany jest adres powrotu do funkcji wywołującej oraz stan procesora.[rys.5.1] Zgodnie z koncepcja hierarchicznej struktury modułów, wywołanie funkcji może również nastąpić z poziomu funkcji wywołanej (zagnieżdżenie) - chodzi po prostu o to, że wywołana funkcją wywołuje następną funkcję lub samą siebie (rekurencja). Funkcje mogą być całkowicie lub częściowo odizolowane od reszty programu, bo mogą operować jedynie na swoich zmiennych lokalnych. Dane te nie są dostępne dla funkcji wywołującej, mogą być udostępniane funkcjom wywoływanym z poziomu, na którym je zadeklarowano (tzn. ze zmiennych lokalnych mogą korzystać jedynie funkcje wewnętrzne). Ten mechanizm umożliwia przesłonięcie (shadowing) i nakładanie (overlapping) zmiennych, czyli użycie jednakowych nazw zmiennych lokalnych i globalnych. Część programu, w której nazwa jest widoczna, nazywa się zasięgiem.
Przekazywanie parametrów do funkcji odbywa się zazwyczaj przez referencję (gdy przekazujemy wskaźnik obiektu) lub wartość (przekazujemy konkretny obiekt np. liczbę).Identyfikatory użyte w definicji funkcji do wskazania obiektów, przekazywanych przez funkcję wywołująca nazywa się parametrami formalnymi (np. funkcja( int c)), a obiekty przekazywane to parametry bieżące (aktualne) (funkcja (3)). Wewnątrz programu jest również konieczne dokonanie powiązań ,czyli ustalenie jednoznacznej relacji między nazwami symbolicznymi (nazwami zmiennych), a wartościami (stałe z wartościami, zmienne z adresami, parametry formalne z aktualnymi ( pierwszemu na liście parametrów formalnych jest przypisywany pierwszy z listy parametrów aktualnych - funkcja(int a, int c), funkcja (2,3) a=2,c-=3 itd.). Powiązania statyczne są dokonywane podczas kompilacji (na podstawie analizy tekstu programu) lub ładowania programu. Podczas kompilacji dokonywane są wczesne powiązania statyczne - dotyczy to wszystkich stałych.
Powiązania dynamiczne, realizowane podczas wykonywania programu, dotyczą tylko zmiennych. Są one stosowane w technikach programowania obiektowego, gdzie wskaźnik obiektu może być określony dopiero podczas wykonania programu (dlatego łatwo może je wykonywać interpreter, ale nie kompilator).
W dowolnym etapie wykonania programu można określić listę nazw zmiennych i stałych, będących w zasięgu danej funkcji. Lista tych nazw nazywa się środowiskiem wykonania lub kontekstem funkcji. Kontekst funkcji może się zmieniać podczas jej różnych wywoływań.
Wywołanie każdej funkcji wymaga najpierw odwzorowania używanych zmiennych w pamięci. Jest dosyć trudne na poziomie języka maszynowego. Do przechowywania zmiennych stosuje się więc stos (stosowany jest też mechanizm okien rejestrowych). Ponieważ zasięg zmiennych i stałych deklarowanych w obrębie jednej funkcji jest jednakowy, więc mogą być one odwzorowane w spójnym bloku pamięci. Zwykle jest on tworzony w chwili uaktywnienia funkcji i jest nazywany blokiem aktywacji. Każdy blok musi być umieszczony w jakimś miejscu pamięci. Alokacja statyczna bloku aktywacji, wykonana podczas kompilacji, pozwala na użycie adresów bezwzględnych, jednakże wyklucza rekurencję oraz jednoczesne udostępnienie funkcji różnym procesom, każdej funkcji możemy bowiem przydzielić tylko jeden blok aktywacji. Alokacja dynamiczna oznacza przydział pamięci na czas wykonania funkcji i jego unieważnienie po jej zakończeniu. Może być dokonana albo od razu dla całego bloku (w momencie wywołania funkcji), albo indywidualnie dla poszczególnych zmienny (w chwili użycia jej identyfikatora - jest to jednak czasochłonne i rzadko stosowane - używana jest w językach strukturalnych i realizowana w postaci drugiego stosu zwanego stertą programową ). Przydzielony blok musi być zwolniony przed zwolnieniem bloków przydzielonych wcześniej (czyli tak jak stos). W obszarze stosu, w którym umieszczono blok aktywacji, można wyróżnić w kolejności odpowiadającej rozbudowie stosu, obszary:
parametrów bezpośrednich - to co jest przekazywane z funkcji wywołującej funkcja(3,1,2), są one umieszczane na stosie przez funkcje wywołującą przed samym wywołaniem. Są to wartości bądź adresy
parametrów implikowanych tworzonych automatycznie - niezbędnych do prawidłowej komunikacji między funkcja wywołującą i wywoływaną (uwaga - odkładane na stosie odwrotnie niż tutaj zapisałem]:
wskaźnik powiązania statycznego SL (static link) - określający adres bloku aktywacji funkcji nadrzędnej, zgodnie z porządkiem statycznego zagnieżdżenia funkcji
wskaźnik powiązania dynamicznego DL, określający adres bloku aktywacji funkcji wywołującej
adres powrotu - czyli odtwarzany stan licznika rozkazów PC
kontekst funkcji wywołującej - zapisanie ustawionych flag PSW i zawartości rejestrów, które mogą być zmodyfikowane przez funkcję wywoływaną
danych lokalnych lub wskaźników danych strukturalnych.
Wskaźniki DL i SL są różne tylko przy rekurencyjnym wykonywaniu programu - SL wskazuje blok funkcji nadrzędnej, DL lokalizuje blok aktywacji poprzedniego wywołania funkcji.
Blok aktywacji utworzony ostatnio w obszarze stosu nazywa się ramką stosu, a jego położenie określa wskaźnik ramki FP. Podczas tworzenia nowego bloku aktywacji wskaźnik ramki FP jest pamiętany jako wskaźnik powiązania dynamicznego DL. Wskaźnik ramki jest również używany jako adres bazowy w adresowaniu zmiennych lokalnych.
W procesorach RISC jest stosowany także inny mechanizm powiązania funkcji zwany oknami rejestrowymi. Na każdym spośród P poziomów zagnieżdżenia dostępnych jest n rejestrów, w tym s rejestrów globalnych, dostępnych na każdym poziomie zagnieżdżenia, 2 lokalne zestawy po k rejestrów transferowych dostępnych na sąsiednich poziomach oraz jeden zestaw rejestrów dostępnych lokalnie. Indeksy rejestrów globalnych i lokalnych są na każdym poziomie jednakowe, a rejestry transferowe są przeindeksowywane po każdym wykonaniu funkcji, lub po jej zakończeniu. [trochę tego nie rozumiem, więc nie będę się starał tłumaczyć]. Zaletą stosowania okien rejestrowych jest szybkość przekazywania parametrów, wadą jest ograniczona pojemność pliku rejestrowego.
Zasadą powinno być umożliwienie wielokrotnego użycia funkcji bez potrzeby odnawiania przy każdym wykonaniu. W najprostszym przypadku będzie to możliwe, jeśli funkcja nie modyfikuje własnego kodu poza obszarem parametrów przekazywanych. Warunek ten nie jest jednak wystarczający, jeśli funkcja ma być ponawialna, czyli, gdy wymaga się, aby możliwe było wywołanie funkcji niezależne od innych jej wywołań. Szczególnym rodzajem takiej funkcji jest funkcja rekurencyjna. Szczególnie ważny w przypadku funkcji ponawialnych jest przydział pamięci - zadanie to musi realizować funkcja wywołująca (!) lub system operacyjny. Na poziomie architektury procesora są stosowane dwa mechanizmy wspomagania wywołania funkcji i procedury:
wywołanie podprogramu, dla którego nie jest tworzone środowisko wykonania (funkcje nieponawialne)
wywołanie funkcji, połączone z rezerwacją pamięci dla nowego środowiska wykonania (funkcje ponawialne).
Mechanizm wywołania funkcji możemy symulować poprzez wywołanie podprogramu połączone z czynnościami niezbędnymi do utworzenia bloku aktywacji.
Przekazywanie parametrów funkcji jest realizowane za pośrednictwem stosu, przez przekazywanie wskaźnika listy parametrów, lub też przez rejestry.
Ważne rozkazy to : push, pop, call, ret, reti, pusha, popa (do zapamiętania kontekstu), enter (utworzenie ramki stosu), leave (usunięcie ramki stosu).
5. SYSTEM OPERACYJNY.
[welcome in hell]
System operacyjny możemy uznać, za maszynę wirtualną umożliwiającą łatwiejszą implementację algorytmów. Zapewnia ponadto kontrolę przydziału oraz ochrony zasobów systemu, takich jak czas procesora, przestrzeń pamięci itd. Ponadto system operacyjny zajmuje się synchronizacją procesów, obsługiwaniem wyjątków, zarządzaniem pamięcią oraz obsługą urządzeń we/wy. Wdrożenie systemu operacyjnego wprowadza niewielki (chyba nie Winxxx) narzut czasowy (wydłużenie czasu wykonywania programów) i przestrzenny (wykorzystywanie niewielkiego obszaru pamięci) na wykonanie programu.
5.1. Klasyfikacja systemów operacyjnych i ich funkcje.
Systemu operacyjne ogólnego przeznaczenia, zależnie od sposobu wykonania ich głównych funkcji, można podzielić na cztery zasadnicze typy:
Systemy wsadowe (szybkie, ale nakładają duże ograniczenia na użytkownika, stosowana jedynie w specjalizowanych systemach). Programy wykonywane są kolejno i w całości, a wszystkie działania nadzoruje procesor. Jeśli zatem program wymaga intensywnej komunikacji z we/wy, to efektywność wykorzystania procesora jest niewielka. Można to poprawić poprzez buforowanie we/wy (równocześnie odczytuje/zapisuje dane i przetwarza poprzednie), a także przez spooling (ulepszone buforowanie), polegający na tym, że wszystkie przesłania danych wykonywane są między procesorem, a pamięcią dyskową (z we/wy do pamięci i dopiero procesor je przetwarza). Spooling powoduje, że na dysku jest zawsze pewna liczba zadań oczekujących na wykonanie, jest zatem możliwa optymalizacja harmonogramu ich wykonania.
Systemy wieloprogramowe - (szybkie, ale nakładają duże ograniczenia na użytkownika, stosowana jedynie w specjalizowanych systemach). Równolegle wykonuje program i realizuje obsługę we/wy (dzięki specjalizowanym procesorom komunikacyjnym).
System z podziałem czasu - naprzemiennie wykonuje fragmenty różnych programów (dając złudzenie ich równoczesnego wykonywania). Metoda skuteczna, ale wymaga dostatecznie silnego komputera. Gdy mamy kilka procesorów to liczba współbieżnie wykonywanych zadań nie musi być równa liczbie procesorów, część jednego programu może być również przetwarzana równocześnie przez kilka procesorów.
Systemy rozproszone - poszczególne funkcje są wykonywane równolegle przez różne procesory. W celu zapewnienia dużej niezawodności przetwarzania kod zasadniczej części systemu (jądro - kernel), wykonują niezależnie wszystkie procesory. Pozostałe funkcje systemu mogą być wykonywane przez różne procesory, analogicznie jak zadania użytkowników (ale mają większy priorytet).
Funkcje systemu operacyjnego.
Funkcje użytkowe:
sterownie i kontrola wykonania programu - zapewnienie pełnej kontroli użytkownika nad kompilacją i uruchomieniem programu
obsługa we/wy - udostępnienie komunikacji z urządzeniami we/wy (bez konieczności znajomości szczegółów obsługi tych urządzeń)
obsługa zbioru plików - umożliwia przetwarzanie plików
Funkcje systemowe (zadanie niezbędne do poprawnego działania):
zarządzanie pamięcią
ochrona zasobów - wzajemne separacja programów w celu zapewnienia ich prywatności
przydział zasobów systemu poszczególnym programom
obsługa wyjątków - reagowanie na zdarzenia zagrażające integralności systemu lub poprawnemu wykonaniu programów i podejmowanie rozstrzygnięć powstałych problemów
harmonogramowanie - ustalanie porządku wykonania programów
raportowanie - tworzenie statystyk wykorzystania zasobów.
5.2. Model procesowy systemu operacyjnego.
W momencie, gdy mamy do dyspozycji tylko jeden procesor (lub liczba wykonywanych procesów jest większa niż liczba procesorów) konieczne jest okresowe przełączanie wykonywanych programów. Przy dużej szybkości przełączania uzyskuje się złudzenie, że programy wykonywane są równolegle. Do opisu mechanizmów związanych z wykonywaniem funkcji zarządzania równoległością i innych usług służy model procesowy systemu operacyjnego. W modelu tym przyjmuje się, że wszystkie funkcje systemu i wszystkie programy użytkowników mogą być wykonywane równolegle. Program w chwili uruchomienia staje się procesem. Każdy proces może być uruchomiony na osobnym procesorze, lub dzielić zasoby jednego, jeśli pozwala na to system operacyjny. Bardzo ważne jest przyjęcie reguł harmonogramowania (szeregowania) procesów, które określają, w jakiej kolejności należy je uruchamiać, zamykać, wstrzymywać. W systemie operacyjny procesowym muszą być też określone reguły tworzenia nowych procesów i usuwania zakończonych. Działanie systemu rozpoczyna się od uruchomienia procesu inicjującego (macierzystego), który z kolei może utworzyć nowy, niezależny proces poprzez tzw. wywołanie systemowe (system call). Wygląda to mniej więcej tak [rys.6.2]:
Utworzenie nowego procesu potomnego (syscall)
Umieszczenie procesu na liście harmonogramowania (start)
Uruchomienie przez układ harmonogramujący
może teraz nastąpić wstrzymanie wykonane przez układ harmonogramujący, czyli powrót do 3
może też nastąpić oczekiwanie na zdarzenie np. od innego procesu (synchronizacja), wystąpienie zdarzenia i powrót do 3
może zostać wywołane kolejne syscall - wtedy z tego miejsca idziemy do 1 i mamy kolejny proces potomny (całkowicie niezależny) wykonywany równolegle.
Stop - zatrzymanie procesu.
Utworzenie nowego procesu wymaga ustanowienia procesu, czyli przygotowania struktur danych opisujących jego relacje ze środowiskiem programowym, zwłaszcza zaś sposób przydziału pamięci procesowi i mechanizmy jego współpracy z innymi procesami, oraz na koniec umieszczenie procesu na liście procesów aktywnych.
Poziom aktywności procesu podczas jego wykonania (czy aktywny, wstrzymany chwilo, zatrzymany, usunięty itp.) nazywa się stanem procesu. Informacja niezbędna do pełnego opisu bieżącego stanu procesu nosi nazwę kontekstu procesu. Zestaw ten zawiera:
blok sterujący procesu - alokowany w tablicy procesów przez cały czas istnienia procesu. Zawiera kontekst minimalny procesu obejmujący:
identyfikator procesu
minimalny kontekst procesora - rejestr stanu i PC
wskaźnik kontekstu pamięci
Blok sterujący zawiera także:
wskaźnik obszaru pamięci, w którym opisano pełny kontekst procesora
informacje dotyczące wykorzystania czasu procesora i harmonogramowania
kontekst procesora - rejestr stanu, PC, rejestry ogólne, rejestry sterujące i połączenia do procesów współpracujących
kontekst pamięci - niezbędny tylko dla procesów aktywnych, obejmuje obszary programu, danych użytkownika oraz systemowych.
Kryterium porządkującym procesy systemu operacyjnego jest relacja używalności (usage). Zgodnie z nią, proces używający innego procesu, lub taki, którego przebieg zależy od wyników innego procesu, jest umieszczony na wyższym poziomie ważności (uprzywilejowania). Zazwyczaj mamy 4 poziomy uprzywilejowania, na których wykonywane są procesy:
obsługi wyjątków - zawsze najwyższy poziom, bo zagrażają stabilności
obsługi we/wy - odmierzanie czasu, obsługa przerwań, obsługa dysków
procesy nadzoru - inicjacja i harmonogramowanie procesów, zarządzanie pamięcią, obsługa plików
procesy użytkowników
Funkcje zarządzania procesami obejmują zestaw zadań wykonujących:
tworzenie i aktualizacje struktur danego procesu
inicjowanie, synchronizację i usuwanie procesu
ochronę procesu i jego zasobów
harmonogramowanie procesu
przełączanie procesów
Ale to już zupełnie inna bajka [30 stron u Biernata] - i niestety, nie mam już na to siły. Miejmy nadzieję, że nie będzie na egzaminie.
6. SYSTEM MAGISTRAL
[może opiszę później]
7. SYSTEM PAMIĘCI.
7.1. Organizacja i obsługa pamięci.
Czas upływający od chwili ustabilizowania się informacji wskazującej lokalizacje odczytywanej danej do chwili jej ustabilizowania nazywamy czasem dostępu (tłumaczenie -czas od kiedy mamy już ustabilizowany adres komórki (do której mamy się dostać) do czasu aż odczytamy tę komórkę i ustabilizowaną wartość pochodzącą z tej komórki będziemy mieli w pełni odczytaną).
Ze względu na sposób dostępu do danych wyróżnia się trzy rodzaje pamięci:
Pamięć o dostępie swobodnym (random access memory) RAM
RAM - odczyt -zapis
ROM - tylko odczyt
PROM - zawartość pamięci programowalnej jest ustalana przez niszczenie odpowiednich połączeń w matrycy.
EPROM - reprogramowalna, jej stan jest wymuszany za pomocą impulsów elektrycznych po uprzednim skasowaniu poprzedniego stanu promieniami ultrafioletowymi (EPROM) lub silnym impulsem elektrycznym (EEPROM - jej szczególnym rodzajem jest pamięć typu FLASH - jedyna pamięć ROM, która może być programowana w systemie, a nie za pomocą osobnego, zewnętrznego urządzenia)
Zależnie od technologii wykonania zapisywalno-odczytywalne pamięci RAM dzielimy na:
DRAM - dynamiczna pamięć RAM. Nośnikiem informacji jest ładunek elektryczny zgromadzony w pojemności kondensatora MOS. Każda komórka pamięci musi być odświeżana (doładowanie kondensatora) - można odświeżać cały rząd komórek naraz. Komórki pamięci zapisane są w matrycy (dokładniej w tablicy matryc). Odczyt następuje w dwóch etapach - najpierw jest wybierany rząd (sygnałem RAS), następnie odpowiednia pozycja w rzędzie (CAS). By odczyta kilka następujących po sobie komórek wystarczy jeden sygnał wyboru rzędu i potem kilka wyboru komórki (RAS-CAS-CAS). Wolniejsza od SRAM, ale o większych pojemnościach i tańsza.
SRAM - statyczna pamięć RAM. Komórkę pamięci stanowi przerzutnik bistabilny. Również struktura matrycowa. Nie jest potrzebne odświeżanie Dostęp do komórki wymaga jej pełnej identyfikacji - nawet jak pobieramy z jednego rzędu to trzeba je ponownie adresować. ( W zasadzie jest możliwy dostęp sekwencyjny - powoduje to jednak duży pobór mocy. Lepszy rozwiązaniem przyspieszającym, jest równoległa transmisja danych szeroką magistralą z wielu jednocześnie uaktywnionych modułów.
Pamięć o dostępie sekwencyjnym SAM i pamięć o dostępie bezpośrednim DAM.
W pamięciach o dostępie sekwencyjnym (czyli po kolei) czas dostępu do danej zależy od jej położenia w łańcuchu danych. Jest on najczęściej stosowany w buforach pamięci o organizacji stosowej lub kolejkowej, a także w pamięciach masowych, magnetycznych (HDD i FDD) i optycznych (CD-ROM, DVD, CD-R,CD-RW) (co jest całkiem logiczne - w końcu korzystając z HDD nie jest dla nas tak ważny czas znalezienia początku pliku, ale czas odczytania całego pliku). Dostęp ściśle sekwencyjny stosowany jest w streamerach (pamięciach taśmowych do robienia backupu systemu - wtedy również nie interesuje nas dostęp skokowy, ale jedynie ciągłe odtwarzanie informacji). Dostęp do dyskowych pamięci masowych realizowany jako transfer bloków nazywany jest dostępem bezpośrednim (pół-swobodnym). Oznacza to mniej więcej tyle, że po blokach możemy skakać swobodnie, ale dostęp do bloku jest już sekwencyjny. Jednym z ważniejszym problemów związanych z użyciem pamięci masowych jest zapewnienie pewności danych - robi się to za pomocą kodów korekcyjnych np. CRC.
Pamięć adresowana zawartością (skojarzeniowa CAM).
[Nie do końca ją rozumiem - w zasadzie nie wiem po co jest - może do buforów antycypowanych?]
Pamięć ta jest układem, który w odpowiedzi na pobudzenie wzorcem sygnalizuje trafienie, jeśli słowo jest zgodne ze wzorcem, lub chybienie w przeciwnym razie. Oczywiście można trafić wiele słów - dlatego ustalamy kryteria - pierwsze/ostatnie zgodne, lub przez maskowanie, i to właśnie słowo wrzucane jest do bufora wyjściowego (decyduje o tym MMR -układ rozstrzygania zgodności). Słowa w pamięci CAM mogą być zmieniane w trybie sekwencyjnym.
Hierarchia pamięci.
Najlepszym rozwiązaniem zwiększającym szybkość pracy procesora byłoby umieszczenie wszystkich danych i kodu w rejestrach procesora - niestety rozmiar pliku rejestrowego jest mocno ograniczony. Hierarchię pamięci ustala się zgodnie z szybkością dostępu: rejestry procesora, pamięć główna RAM, pamięć wtórna HDD, archiwum CD.
Organizacja pamięci głównej.
Pamięć główna jest zwykle realizowana jako dynamiczna i ma strukturę modułową. Zazwyczaj pamięć SIMM (Single In-line Memory Module) lub DIMM (2xSIMM). Adresowanie odbywa się przez wybranie modułu i wszystkich i wszystkich układów zawierających bity tego samego bajta. Moduły mogą być organizowane w banki, obejmujące bajty rozróżnione najniższymi bitami adresu (cokolwiek to znaczy). Zalecanym typem dostępu do pamięci SIMM i DIMM jest RAS-CAS-CAS - dzięki temu odczyt kolejnych danych mamy w jednym cyklu. [dalej coś jeszcze jest, ale nie bardzo to chwytam).
7.2. Pamięć podręczna [podobno jeden z koników Dżej Bi ]
Zasada lokalności.
Zasada lokalności przestrzennej mówi, że bardzo prawdopodobne jest użycie informacji z lokacji pamięci sąsiedniej wobec bieżącej (wynika to z sekwencyjnego przetwarzania rozkazów, oraz organizowania pamięci w struktury).
Lokalność czasowa programu jest skutkiem jego typowej struktury, zwierającej zwykle pętle - powtarzalne sekwencje rozkazów przetwarzających indeksowane dane (tablice i wektory).
Wyciągnąć więc można wniosek, że należałoby stworzyć kopie aktualnie używanych bloków pamięci w sąsiedztwie procesora (w sensie czasu dostępu) i korzystać z kopii, zamiast oryginału znajdującego się w pamięci głównej. Tak właśnie narodziła się pamięć podręczna (cache memory).
Organizacja bufora pamięci.
Każdą daną umieszczoną w pamięci głównej identyfikuje jednoznacznie jej adres, który, zgodnie z klasyczną koncepcją pamięci, jako domniemany numer porządkowy, nie jest nigdzie przechowywany. Ponieważ w buforze przechowywane są tylko wybrane słowa, konieczne jest przypisanie im etykiet identyfikacyjnych, którymi w naturalny sposób są właśnie adresy fizyczne tych słów w pamięci głównej. Jeżeli informacje umieszczone w buforze mogą mieć różne rozmiary, to konieczne jest także rozróżnienie danych pod względem ich rozmiaru. Jednym ze skutecznych rozwiązań jest ustalenie rozmiaru jednostkowego bloku danych w buforze i opatrzenie poszczególnych bajtów bloku etykietami ważności (chyba chodzi o to który bajt jest bardziej znaczący). Jeśli liczba bajtów w bloku jest naturalną potęgą dwójki, to do identyfikacji bloku wystarczy adres skrócony o k bitów.[?].
Najprostszym sposobem organizacji bufora pamięci jest powiązanie każdego bloku z jedną lokacją pamięci asocjacyjnej CAM (wreszcie wiem po co jest), zawierającej etykiety adresowe bloków. W razie dopuszczenia zmiennego rozmiaru danych w bloku konieczne jest również przypisanie etykiet ważności poszczególnym bajtom [rys.8.13]. Chodzi o to, że w pamięci CAM umieszczamy również adresy fizyczne danych - dlatego potrzebna jest nam pamięć skojarzeniowa CAM - ponieważ naszymi wartościami są adresy, więc musimy poszukiwać po wartościach. Mam nadzieje, że da się to zrozumieć.
Sposób obsługi bufora zależy od jego przeznaczenia - należy jednak pamiętać, że przy stosie i kolejce należy zwrócić uwagę na możliwość przepełnienia. W buforze typu sterta (pamięć podręczna!) konieczne jest określenie strategii wymiany bloków danych w razie zapełnienia bufora.
Pamięć wielopoziomowa.
Pamięć podręczna przechwytuje transfery danych do lub z pamięci. Ponieważ kopiowanie pamięci jest operacją czasochłonną, należy zadbać o to, aby bufor cache był odpowiednio duży, by w jak największym stopniu wyeliminować niepotrzebne wymiany danych. Pamięć podręczna powinna zatem umożliwiać przechowanie co najmniej kilku jednostkowych bloków (stron) pamięci przydzielanej procesowi. Jeśli pojemność bufora pamięci podręcznej byłaby wystarczająca do pomieszczenia zbioru roboczego procesu, to częstość komunikacji procesu z pamięcią główną byłaby minimalna.
W systemie dwupoziomowym pamięć poziomu pierwszego (L1) zawiera kopie niektórych danych umieszczonych w pamięci poziomu drugiego (L2), która z kolei zawiera kopie fragmentów pamięci głównej. Wynika stąd, że każda dana znajdująca się w L1 jest również w L2 (ale nie na odwrót).
Zasada lokalności nie gwarantuje obecności kopii potrzebnych danych w pamięci podręcznej. Skuteczność użycia pamięci podręcznej określa współczynnik trafień h, wyrażający szansę (prawdopodobieństwo) znalezienia kopii poszukiwanych danych w pamięci podręcznej.
Drugim ważnym parametrem jest średnia strata czasu w razie chybienia. Z obliczeń wynika, że pomięć podręczna jest sposobem wykorzystania szybkich pamięci statycznych i wolnych, ale pojemnych pamięci dynamicznych. Jeśli jej pojemność jest wystarczająca to większość bieżących transferów ominie pamięć główną.
Organizacja i charakterystyki pamięci podręcznej.
Zgodnie z zasadą lokalności staramy się zapisać do pamięci podręcznej bajt wraz z najbliższym sąsiedztwem. Wprowadza to pomysł podziału przestrzeni adresowej procesora na rozłączne obszary, w razie potrzeby w całości kopiowane do danej lokacji w pamięci podręcznej, zwanej linią wymiany. Odpowiednio, fragment pamięci głównej kopiowany w całości do pamięci podręcznej nazywa się linią. Jeśli linia zawiera 2k bajtów, to tylko część adresu fizycznego jest użyta jako identyfikator adresu linii, a pozostałe k bitów adresu służy do identyfikacji bajtów. Rozmiar linii powinien być dostosowany możliwości szybkich transferów danych do i z pamięci głównej, jednocześnie powinien tez zapewniać przemieszczenie kilku (2d) danych o największym rozmiarze obsługiwanym przez procesor.
Architektura komputerów prawie po polsku
21 Gulczas 2001