Przypominam o 1. zadaniu zaliczeniowym.
W tym tygodniu pojawi się (pojawiło się) 2. zdanie zaliczeniowe. Dotyczy asemblera i współbieżności: tematy zeszło- i tego-tygodniowe.
Warto przypomnieć sobie z Programowania Współbieżnego, czym jest wątek. Na dzisiejszych zajęciach trzeba pamiętać, że wątki jednego procesu współdzielą pamięć (czyli wszystkie mają dostęp do zmiennych globalnych), ale każdy ma oddzielny stos i wartości rejestrów: każdy wątek widzi tylko swój stos, a w rejestrach tylko wartości, które sam wpisał. (Jak to jest realizowane, będziemy omawiać podczas zajęć z MINIX-em).
Dyrektywa align <wartość>
powoduje, że adres tego miejsca (a dokładniej, adres początku instrukcji/miejsca umieszczonego za tę dyrektywą) będzie wyrównany do <wartość> bajtów
. Odpowiednie wyrównanie odpowiednich fragmentów (zmienne, adresy docelowe skoków) może sprawić, że wykonanie będzie szybsze. Czy faktycznie będzie i o ile, to może zależeć od konkretnego procesora – warto poeksperymentować.
Funkcja inc_thread()
jest wołana z C, więc musi przestrzegać konwencji języka C (zob. poprzednie zajęcia). Warto zacząć od przeczytania i zrozumienia pliku inc_thread_test.c
, w którym ta funkcja jest wołana.
Warto zauważyć, że o ile pisząc kod np. w C naturalnie zaimplementowalibyśmy pętlę zwiększającą zmienną globalną jako pętlę while-do
, czyli w kolejności spr. warunku – ciało pętli – skok do spr. warunku
, to w podanym kodzie asemblerowym warunek pętli jest umieszczony po ciele pętli, więc jest to układ do-while
: ciało pętli – spr. warunku – ew. skok do ciała
. Jest to tylko układ jak do-while
, bo wykonanie jest nadal jak while-do
: na początku przeskakujemy ciało pętli (jmp count_test
), aby najpierw sprawdzić warunek pętli i dopiero jeśli jest on spełniony, to skaczemy do góry, do ciała pętli. Taki układ kodu pętli, choć wydaje się być mniej naturalny, jest ,,standardem’’ w asemblerze i większość kompilatorów właśnie tak kompiluje pętle. Dlaczego? Polecam np. to pytanie na SO .
W podanym kodzie wartość jest zwiększana za pomocą operacji inc dword [rsi]
. To dword
nie jest argumentem operacji inc
. Jest ono podpowiedzią dla asemblera, że ma on wygenerować takie inc
, które odczytuje i zwiększa 32-bitową liczbę zapisaną pod adresem trzymanym w rsi
. Bez dword
asembler nie ma tu jak domyśleć się, czy chodzi nam o liczbę 8-, 16-, 32-, a może 64-bitową.
Należy zauważyć, że inc dword [rsi]
wykonuje 2 odwołania do pamięci: odczytuje wartość z pamięci, zwiększą ją i zapisuje zwiększoną wartość do pamięci. Zatem jest tu klasyczny problem ze współbieżnością: zanim jeden wątek zapisze zwiększoną wartość, drugi wątek może zdążyć doczytać starą wartość, którą też zwiększy i zapisze.
Użycie \
w kodzie, to jest złamanie linii. Dzięki temu możemy (np. dla czytelności) zapisać coś w 2 liniach, ale dla programu będzie to dalej ta sama linia. Np.
repne \
scasb
NASM interpretuje, jakby było zapisane w jednej linii:
repne scasb
Warto przypomnieć sobie z Programowania Współbieżnego, które (dwie) operacje muszą zostać wykonane atomowo, aby spinlock rzeczywiście działał. W dalszej części tego scenariusza będą rozważane różne sposoby (instrukcje), które mogą dać nam właśnie takie działanie.
To ZF
(Zero Flag
) to jest właśnie jedna z flag, o których mówiliśmy na poprzednich zajęciach, jak omawialiśmy, jak realizowane są w asemblerze skoki warunkowe (“if’y”). Warto zajrzeć do tabelek Instrukcje warunkowe załączonych do zeszłotygodniowego tematu laboratorium.