1.8 Rozkazy procesora

Każdy procesor charakteryzuje się swoją listą rozkazów jakie może wykonywać. Rozkazy mogą przyjmować do kilku parametrów którymi mogą być rejestry, stałe lub komórki pamięci. Każdy rozkaz jest przedstawiany w języku asemblera jako tak zwany mnemonik. Jest to zwykle skrót od angielskiego wyrazu opisującego działanie danej instrukcji. W programowaniu rozwinęły się zasadniczo dwie składnie zapisu instrukcji i parametrów. Są to AT&T lub składania Intela. Według mojej opinii najlepszą składnią jest składnia Intela ponieważ posiada minimalną ilość dodatkowych znaków i nawiasów co sprawia, że jest dużo bardziej przejrzysta i łatwiej się ją czyta.

Instrukcje zapisuje się w formie:
mnemonik    argument1,    argument2

Mnemonikiem jest po prostu nazwa instrukcji. Argumentami mogą być rejestry, stałe liczbowe lub znakowe, adresy pamięci lub zawartości komórek pamięci. Podstawową ideą składni Intela jest to iż argument1 jest zawsze argumentem docelowym instrukcji w którym jest umieszczany wynik jej działania.

Są oczywiście instrukcje bardziej zawiłe i skomplikowane np. przyjmujące 3 argumenty itp. Jednak większość instrukcji wpisuje się w powyższy schemat. Dla przykładu opiszę działanie kilku najczęściej używanych instrukcji asemblera.

MOV

Move to instrukcja służąca to kopiowania zawartości drugiego argumentu do pierwszego. Można by tą instrukcję równie dobrze nazwać Copy, ale twórcy języka nazwali tą operację dość mylnie jako przenoszenie Move.

Przykład:
mov ax, 1

Powyższa instrukcja wykona wpisanie do rejestru ax wartości 1. W tym przypadku argumentami są rejestr i wartość bezpośrednia. Sytuacja odwrotna oczywiście nie mogła by mieć miejsca ponieważ nie można przypisać liczbie wartości rejestru. To jest zupełnie nie logiczne więc kompilator na pewno odrzucił by nam takie przypisanie na poziomie kompilowania programu. Możemy natomiast kopiować między sobą wartości rejestrów i zawartość pamięci.

Przykład:
mov ax, 1 mov cx, 3 mov bx, 2 mov cx, ax

Po wykonaniu powyższych instrukcji otrzymamy następujące wartości rejestrów ax = 1, bx = 2, cx = 1.

ADD

Add czyli dodawanie. Jest prostą operacją wykonującą działanie na liczbach całkowitych. Jeżeli mowa o dodawaniu liczb całkowitych to należy pamiętać iż operacja ta działa równie dobrze na liczbach dodatnich i ujemnych. Wynikiem działania tej instrukcji jest argument1 = argument1 + argument2

Przykład:
mov ax, 1 mov bx, 2 add bx, ax add ax, bx

Po prostym prześledzeniu kolejnych instrukcji można zauważyć, że w wyniku otrzymamy wartości bx = 3, ax = 4. Tak samo jak poprzednio nie można użyć stałej jako argumetu1 ponieważ argument1 służy jednocześnie do zapisu wyniku operacji.

Przykład:
mov ax, 0x4F add ax, 1

W wyniku powyższych instrukcji otrzymamy ax = 0x50.

SUB

Odejmowanie, wynik tej operacji to: argument1 = argument1 - argument2.

Przykład:
mov ax, 5 mov cx, 1 sub ax, cx sub cx, 3

Wynikiem będzie ax = 4, cx = ?. No właśnie w przypadku odejmowania nawet posługując się wyłącznie liczbami dodatnimi możemy otrzymać wynik ujemny. Tak też się dzieje w tym przypadku gdy wykonujemy operację 1 - 3 = -2. Wynikiem będzie liczba -2 zapisana w kodzie U2 w rejestrze cx który jest rejestrem 16 bitowym. Wystarczy sobie przypomnieć jak działa kodowanie liczb U2 aby zauważyć że wynik to cx = 0xFFFE.

W asemblerze operujemy na rejestrach, które mają możliwość przechowywania jedynie skończonych liczb. Konsekwencją tego między innymi jest to, że liczba -2 jest zapisana jednakowo jak liczba 65534 = 0xFFFE. Procesor praktycznie nie widzi różnicy między liczbami całkowitymi dodatnimi i ujemnymi. To na nas praktycznie spoczywa odpowiedzialność za przewidzenie czy w wyniku otrzymamy liczbę dodatnią czy może to być liczba ujemna. W przypadku innych instrukcji takich jak mnożenie i dzielenie stosuje się różne instrukcje dla liczb ze znakiem i bez znaku jednak w przypadku add i sub jest to bez znaczenia.

Jako przykład mogę wspomnieć sytuację w której będziemy chcieli się odwoływać do tablicy jednowymiarowej o pewnym rozmiarze. Jeżeli obliczając indeks komórki do której chcemy się odwołać otrzymamy liczbę ujemną np. -2 to procesor potraktuje to jako zaadresowanie elementu tablicy o numerze 65534. Co w efekcie da na pewno dostęp do obszaru pamięci, którym nie będzie tablica, a jakieś inne "przypadkowo" wybrane dane. Tak więc należy wiedzieć kiedy i czy w naszym kodzie może pojawić się liczba ujemna. W przeważającej części kodu podczas programowania nie stosuje się wartości ujemnych więc nie powinno to być kłopotliwe.

Wykonywanie programu

Pora teraz wspomnieć o tym jak procesor wykonuje instrukcje. Istnieje oczywiście kilka zagadnień składających się na całość wykonywania instrukcji, ale zacznę od strony programistycznej. Zgodnie z powszechnym standardem instrukcje zapisuje się w kodzie z góry do dołu i są one tak kompilowane żeby procesor wykonał je w takiej kolejności. Procesor za pomocą pary rejestrów CS:IP określa adres kolejnej instrukcji jaką będzie wykonywał. Po pobraniu z pamięci i wykonaniu instrukcji procesor zwiększa automatycznie wartość rejestru IP o rozmiar wykonanej instrukcji. W ten sposób para rejestrów CS:IP adresuje następną instrukcję, którą wykona procesor. Kierunek kolejnych instrukcji jest wyznaczony przez wzrastające adresy pamięci i nie istnieje żadna możliwość wykonywania programu w odwrotnej kolejności. Dodatkowo procesor automatycznie zwiększa tylko zawartość rejestru IP, a rejestr CS zawsze pozostaje stały. Wartość tego rejestru może jednak być zmieniana za pomocą innych instrukcji procesora.

Ważnym zagadnieniem pomagającym zrozumieć zagadnienie optymalizacji jest proces pobierania i wykonywania rozkazów przez CPU. Jako że rozkazy nie mają stałej wielkości w pamięci, procesor nie pobiera ich w całości tylko analizuje kolejne bajty aby określić co to jest za instrukcja i jakie parametry pobiera. Fazy pobierania i wykonywania rozkazów są zagadnieniem ważnym, ale zbyt obszernym i wydaje mi się zbędnym jak na podstawy programowania. Jeżeli ktoś jest chętny aby poznać szczegółowo te zagadnienia to odsyłam niestety w gąszcz sieci.

Poza opisanymi przeze mnie instrukcjami jest jeszcze co najmniej setka innych. Wszystkie zgodne z procesorami Intela można znaleźć w dokumentacjach I64_V2A i I64_V2B w dziale z plikami. Niestety są one w języku angielskim, ale dla kogoś kto chcę programować i poznawać swój komputer jest to przeszkoda niezbędna do przeskoczenia.