Programowanie w asemblerze Wprowadzenie 17 stycznia 2017
Motto: R7 is used by the processor as its program counter (PC). It is recommended that R7 not be used as a stack pointer. Źródło: PDP-11 04/34/45/55 processor handbook, Digital Equipment Corporation, 1976.
Po co sięgać do języka wewnętrznego? Dostęp do rejestrów sprzętowych procesora i kart I/O. Dostęp do instrukcji, których nie zna kompilator. Precyzyjne sterowanie wykonaniem kodu w miejscach grożacych zakleszczeniem na poziomie sprzętu lub wyścigami. Atomowe operacje test-and-set. Naruszenie konwencji kompilatora pozwalajace na lepsza optymalizację (przekazywanie parametrów, przydział pamięci, wywołania finalne tail-recursion). Dostęp do nietypowych trybów pracy procesora, wykonywanie kodu sprzętowego z pamięci ROM itp. Ograniczenia sprzętowe na zasoby, np. programy wbudowane (embedded).
Czym się za to płaci? Żmudny i męczacy (zwłaszcza poczatkowo) proces kodowania Rewelacyjnie łatwo popełnić bład Błędy usuwa się bardzo ciężko Trudna konserwacja Znikoma przenaszalność (ale zob. kompatybilność ) Dla typowych programów kod wygenerowany przez dobry kompilator będzie zazwyczaj lepszy
Łagodzenie bólu Pisać w asemblerze tylko te fragmenty, gdzie jest to niezbędne. Kod w asemblerze obudowywać dobrze zdefiniowanych interfejsami (procedury/funkcje). Starać się w miarę możności generować kod w asemblerze automatycznie: makra, reguły przepisywania, wzorce itp.
Ogladanie generowanego kodu Użyć flagi -S w kompilatorze GCC, warto też dać -fverbose-asm. Szukać miejsc, które w oczywisty sposób można ulepszyć. Najlepiej wcześniej użyć profilera, aby nie ulepszać na próżno.
Architektura komputera próba definicji Abstrakcyjna struktura komputera, której znajomość jest niezbędna programiście piszacemu w języku maszynowym (lub zbliżonym). Uwaga: struktura taka może mieć różne implementacje sprzętowe, np. sterowanie układowe, sekwencjami taktujacymi lub mikroprogramowane.
Poziomy interfejsu maszyn wirtualnych ISA: język wewnętrzny (Instruction Set Architecture) ABI (z systemem operacyjnym) API (z bibliotekami)
Podstawowe cechy architektury systemu komputerowego interesujace programistę: długość słowa, przestrzeń adresowa pamięci, sposoby adresacji, repertuar instrukcji, czas wykonania (zależy od postaci argumentów), organizacja stosu, system przerwań (liczba poziomów).
Organizacja prostego komputera Klasyczny model von Neumanna. Składowe: procesor, pamięć, urzadzenia zewnętrzne. Magistrale (szyny), kanał DMA. Przechowywanie programów w pamięci, nieodróżnialność instrukcji i danych. Instrukcja = kod operacji + argumenty. Położenie argumentów: w rejestrach, w kodzie programu, w innym miejscu w pamięci. Sposób kodowania, pola. Komórki pamięci. Adresacja. Bit, bajt, słowo. Pojemność pamięci. Cykl pracy pamięci.
Uproszczony schemat procesora Rejestry uniwersalne i specjalne Jednostka arytmetyczno-logiczna (ALU) Dekoder instrukcji Licznik rozkazów Przesłania międzyrejestrowe
Rejestry uniwersalne Na Pentium (32 bity): EAX (AX, AH AL) EBX (BX, BH BL) ECX (CX, CH CL) EDX (DX, DH DL) ESI (SI) EDI (DI) EBP (BP)
Typowe rejestry specjalne licznik rozkazów (EIC, niedostępny), rejestr instrukcji (IR, niedostępny), słowo stanu procesora (FLAGS), wskaźnik stosu (ESP), rejestr adresowy i buforowy pamięci (niedostępne), rejestry segmentowe (CS, DS, ES, FS, GS, SS). Ponadto rejestr EBP najczęściej używany jako wskaźnik ramki na stosie.
Cykl rozkazowy Typowy cykl rozkazowy = fazy wykonania instrukcji: 1 fetch pobranie z pamięci instrukcji wskazanej licznikiem rozkazów, 2 decode rozpoznanie i ustawienie trybu argumentów 3 read pobranie argumentów z pamięci 4 execute wykonanie 5 write-back zapisanie wyniku 6 interrupt sprawdzenie, czy nie było przerwania.
Magistrala (szyna) Maksymalna częstotliwość pracy ograniczona występowaniem tzw. efektu bus skew nierównomiernej prędkości przepływu sygnału na poszczególnych liniach. Multipleksowanie adresów i danych oznacza dzielenie linii szyny. Polega to na przesyłaniu po tych samych liniach w różnych taktach cyklu szyny adresów i danych. Dodatkowe linia sterujace, np. stany oczekiwania (wait states) do wyrównywania szybkości pracy.
Arytmetyka binarna i reprezentacja danych Liczby całkowite binarne bez znaku (naturalne). Operacje arytmetyczne dla liczb całkowitych bez znaku (nieujemnych) Przeniesienie (carry) i pożyczka (borrow). Arytmetyka wielokrotnej precyzji.
Reprezentacja liczb całkowitych ze znakiem Warianty: znak-moduł uzupełnieniowy do 1 (do zmniejszonej podstawy liczenia) uzupełnieniowy do 2 (do podstawy liczenia), najwyższy bit traktowany tak, jakby jego waga była ujemna. z przesunięciem
Operacje arytmetyczne Przepełnienie (overflow). Reprezentacja w kodzie BCD, korekta przy dodawaniu.
Reprezentacja liczb rzeczywistych Liczby zmiennopozycyjne, postać znak 2 k f, gdzie znak jest 1 lub -1 k jest całkowite f jest ułamkiem Normalizacja dodatkowy warunek 1 > f >= 1/2, pozwala jednoznacznie wyznaczyć k i f. Dla zera f = 0. Daje maksymalna precyzję, ułatwia porównywanie Operacje arytmetyczne, denormalizacja.
Optymalizacja procesora Przetwarzanie potokowe W przetwarzaniu potokowym (pipeline), dokonuje się równoległego przetwarzania różnych faz dla kolejnych instrukcji. Największe przyśpieszenie osiaga się dla sekwencyjnych ciagów instrukcji, komplikacje powstaja podczas zmian sterowania oraz przerwań. Wymagane jest wtedy opróżnienie struktury potokowej. Rozwiazanie z procesorów RISC: stosowanie delayed branch (znane też jako delay slot) skok następuje dopiero po wykonaniu następnej instrukcji. Oczywiście kompilator musi generować odpowiedni kod przestawiajac odpowiednio instrukcje (możliwe w 90% przypadków). W architekturze 80x86 wprowadzono przesłania warunkowe.
Architektura superskalarna Współczesne procesory (np. Pentium, PowerPC) posiadaja kilka równoległych potoków wraz z niezależnymi modułami wykonawczymi. Pozwala to równocześnie wykonywać kilka instrukcji. Mówimy wtedy o architekturze superskalarnej. Ciekawe sa jej konsekwencje dla optymalizacji. Okazuje się, że często warto (np. w Pentium) zastępować instrukcje złożone ciagami instrukcji prostych, bo moga one być wykonywane równocześnie.
Wydajność Przykład: Mamy program, którego wykonanie trwa 200 sekund, z czego 160 sekund to operacje mnożenia. Ile razy szybciej powinien działać układ mnożacy, aby wykonanie było 5 razy krótsze? Oznaczmy ten wzrost w: 200 sek. 5 = 160 sek. w + (200 160) sek. czyli 40 sek. = 160 sek. w + 40 sek.
Literatura B.S. Chalk Organizacja i architektura komputerów A. Skorupski Podstawy budowy i działania komputerów A.S. Tanenbaum Structured Computer Organization Zaaawansowane: M.L. Schmitt Procesory Pentium