programowanie
współbieżne
hahahaha rozwalilo mnie to
Podstawowe pojęcia współbieżności
Bezpieczeństwo
Żywotność
Blokada
Zagłodzenie
Uczciwość
Skutki stosowania współbieżności
Stany procesów
Deskryptor procesu
Fazy wykonania procesu
Atrybuty procesu
Dziedziczenie atrybutów
Funkcje
Makra
Akcje przy zakończeniu procesu
Pliki i funkcje dostępu do pliku
Łącza nienazwane
Wykorzystanie
Funkcja mkfifo
Funkcja select
Komunikacja przez wspólną pamięć w standardzie POSIX
Funkcje
Kolejki komunikatów POSIX i zastosowania
Funkcje
Wzajemne wykluczanie
Operacje atomowe
Sekcja krytyczna
Warunki poprawnego rozwiązania sekcji krytycznej
Niesystemowe i systemowe metody ochrony sekcji krytycznej
Sprzętowa ochrona sekcji krytycznej
Ochrona sekcji krytycznej
Semafory nienazwane i nazwane POSIX
Funkcje
Definicja
Zastosowanie
Oczekiwanie wewnątrz monitora
Zmienne warunkowe
Funkcje
Implementacja semafora poprzez monitor
Wątki pojęcie i zasoby
Tworzenie
Synchronizacja
Funkcje
Mutexy
Zmienne warunkowe
Blokady czytelników i pisarzy
Wirujące blokady
Interfejs gniazd
Funkcje
Serwer sekwencyjny
Serwer współbieżny
Sygnały i ich obsługa
Instalacja handlera sygnału
Blokowanie sygnałów
Sygnały a wątki
Zdalne wykonywanie procedur RPC
DO ZROBIENIA
Podstawowe pojęcia
Wiązanie dynamiczne
Język opisu interfejsu IDL
Tworzenie aplikacji w standardzie Sun RPC
System Linda
Linda – własności
Wspiera tworzenie aplikacji równoległych typu zarządca – wykonawca
Kod niezależny od liczby procesów wykonawczych
Wspiera równoległość i komunikację międzyprocesową
Nakładanie się komunikacji i obliczeń
Nadaje się do systemów heterogenicznych (składających się z różnych maszyn)
Przestrzeń krotek
Operacje
Wyrażanie operacji synchronizacyjnych i komunikacyjnych
Problem producenta i konsumenta, czytelników i pisarzy
Podstawowe pojęcia współbieżności
●
Procesy sekwencyjne - najpierw wykonuje się jeden, potem drugi.
●
Procesy współbieżne - jeden z procesów rozpoczyna się przed zakończeniem
drugiego.
●
Procesy równoległe - j/w, ale wykonywane są na oddzielnych procesorach.
Bezpieczeństwo
Aplikacja jest bezpieczna, jeśli w przypadku działania współbieżengo nie wykrzaczy się.
Tzn:
●
Wszystkie sekcje krytyczne są prawidłowo zabezpieczone - jeśli nie, to wyniki mogą
być nieprawidłowe (jeden wątek nadpisujący dane innego (czyli wyścigi) itp),
●
.
Można też powiedzieć, że aplikacja jest bezpieczna, jeśli nie zaprzestanie obsługi zleceń i
będzie je zawsze realizowała w prawidłowy sposób.
Żywotność
Gwarancja, że każda żądana akcja musi się kiedyś wykonać. Przykładowo, każdy klient
podłączany do serwera zostanie (prędzej czy później) obsłużony.
Blokada
Zwana również
. Generalnie chodzi o to, że każdy zablokowany proces
czeka na inny zablokowany proces, następuje błędne koło i żaden z nich się nie odblokuje.
Zagłodzenie
Zagłodzenie występuje gdy procesowi cały czas odmawia się dostępu do zasobów których
ten potrzebuje by wykonać zlecone mu zadanie.
- na przykład, jeśli mamy dużo procesów o wysokim priorytecie i
planista systemowy nie jest na to przygotowany, to procesy o niskim priorytecie mogą
nigdy nie dopchać się do zasobów, których potrzebują.
Uczciwość
Procesy żądające obsługi są traktowane zgodnie ze swoimi priorytetami lub jednakowo.
Rodzaje uczciwości:
●
Uczciwość słaba – jeżeli proces nieprzerwanie zgłasza żądanie to kiedyś będzie ono
obsłużone.
●
Uczciwość mocna – jeśli proces zgłasza żądanie nieskończenie wiele razy to w
końcu zostanie ono obsłużone.
●
Uczciwość liniowa – jeśli proces zgłasza żądanie będzie ono obsłużone zanim
dowolny inny proces będzie obsłużony więcej niż raz.
●
Uczciwość typu FIFO – żądania procesów są obsługiwane zgodnie z kolejnością ich
zgłaszania. (FIFO – ang. First-In First-Out)
Skutki stosowania współbieżności
Korzyści wynikające z zastosowania współbieżności:
1. Polepszenie wykorzystania zasobów. Gdy jakiś proces czeka na niedostępny w
danej chwili zasób, procesor może wykonywać inny proces.
2. Podział zadania na procesy umożliwia wykonywanie ich na oddzielnych maszynach.
Prowadzi to do zrównoleglenia przetwarzania.
3. Podział dużego zadania na wiele mniejszych komunikujących się procesów
prowadzi do dekompozycji problemu. Przez co ułatwia ich implementację,
uruchamianie i testowanie przez wielu niezależnych programistów.
Trudności powstające przy implementacji aplikacji współbieżnych:
●
problem sekcji krytycznej
●
problem synchronizacji procesów
●
problem zakleszczenia
Procesy tworzące aplikację nie działają w izolacji. Muszą jakoś ze sobą współpracować co
prowadzi do:
- Konieczności wzajemnej wymiany informacji - komunikacja międzyprocesowa.
- Zapewnienia określonej kolejności wykonania pewnych akcji - problem synchronizacji.
Przedmiot programowania współbieżnego
Metodologia tworzenia aplikacji składających się z wielu komunikujących się i dzielących
zasoby procesów współbieżnych.
Wstęp do procesów
Stany procesów
●
Wykonywany - proces, który aktualnie się wykonuje. Tutaj nie ma wiele do
dyskusji.
●
Gotowy - proces, który aktualnie się nie wykonuje. Mógłby, ale brakuje dla niego
zasobów (np. mamy tylko jeden procesor, więc nie może wykonywać się więcej niż
jeden proces jednocześnie).
●
Zablokowany - Proces, który na coś czeka, np. na zakończenie wątku
(pthread_join()), podprocesu (wait()), dostępu do sprzętu itp.
●
Zombie - Proces, który się zakończył, ale jego proces macierzysty nie wykonał
funkcji wait()
Jak widać na powyższym rysunku, proces wykonywany może zmienić stan na gotowy i
odwrotnie (głównie ze względu na planistę systemowego, przydział czasu procesora itp).
Ale już zablokować może się tylko gdy jest wykonywany (no bo w sumie ciężko, żeby
wywołał wait() w trakcie gotowości), a z zablokowanego może stać się tylko gotowy.
Proces może zakończyć się w każdej chwili, a po uruchomieniu przechodzi od razu do
stanu gotowości.
Deskryptor procesu
(ang. process descriptor) rekord w którym system operacyjny utrzymuje wszystkie
informacje niezbędne do zarządzania procesem.
Zawartość deskryptora procesu
●
PID
●
●
Wskaźnik na poprzedni i następny deskryptor
●
Wskaźnik do poprzedniego i następnego procesu w kolejce (zablokowanych,
gotowych itp)
●
Informacje o szeregowaniu (priorytet itp)
●
Informacje o obsłudze sygnałów (sygnały dostarczane, zablokowane)
●
Informacje o hierarchii procesów (proces macierzysty, procesy potomne)
●
Kontekst procesu (rejestry)
●
Informacje o zużytym czasie procesora
●
Nazwa pliku, z którego proces został stworzony
●
UID, GID, EUID, EGID
●
Rozmiar segmentu kodu, danych i stosu
●
Położenie segmentu kodu, danych i stosu
●
Informacje o stronach zajmowanych przez proces
●
Katalog bieżący i macierzysty
●
Informacja o terminalu sterującym
●
- wzorzec uprawnień dla nowych plików
●
Wskaźnik na tablicę deskryptorów plików otwartych
Fazy wykonania procesu
1. Tworzenie :
Alokacja deskryptora procesu, przydział PID
Ustalenie zmiennych otoczenia – zwykle dziedziczone z procesu macierzystego
2. Ładowanie
Załadowanie segmentu kodu i danych oraz inicjacji stosu. Ładowanie wykonywane
jest przez oddzielny watek ładujący aby nie blokować administratora procesu Proc.
3. Faza wykonania
Po zaladowaniu nowy proces jest umieszczany w kolejce procesów gotowych,
uruchamiany i zaczyna wspólzawodniczyc o zasoby.
4. Faza zakonczenia
Zakończenie procesu może być zainicjowane przez sam proces – gdy wykona on
funkcje exit, lub poprzez wysłany z zewnątrz sygnał. Zakonczenie sklada sie z
dwu etapów.
a. Zwolnienie zasobów - proces zwalnia wszystkie zajmowane zasoby jak
pamiec, nazwy, itd. oraz likwiduje interakcje z innymi procesami. Po
wykonaniu tej fazy zajmuje tylko deskryptor.
b. Zawiadomienie procesu macierzystego o zakończeniu. Dopóki proces
macierzysty nie wykona funkcji wait lub waitpid konczony proces pozostaje
w stanie „zombie”. Aby uniknac pozostawania procesów w stanie „zombie”
mozna ustawic reakcje na sygnal SIGCHLD w funkcji signal na SIG_IGN.
Atrybuty procesu
●
PID - identyfikator procesu
●
PPID - PID procesu macierzystego
●
UID - identyfikator użytkownika
●
GID - identyfikator grupy użytkownika
●
SID - identyfikator sesji
●
PGRP - identyfikator grupy procesów
●
priorytet procesu
●
CWD - katalog bieżący
●
katalog główny
●
otoczenie procesu
Dziedziczenie atrybutów
Proces potomny dziedziczy większość atrybutów procesu macierzystego, ale ma swoje PID,
PPID oraz własne kopie deskryptorów otwartych plików.
Procesy Posix
Tworzenie procesów
fork()
tworzy kopię bieżącego procesu. Od procesu macierzystego różni się PIDem,
Parent PIDem, a dla wszystkich otwartych plików w procesie macierzystym proces
potomny otrzymuje kopie ich deskryptorów (zamiast korzystać z deskryptorów
utworzonych przez proces macierzysty).
Funkcja fork() tworzy deskryptor nowego procesu oraz kopię segmentu danych i stosu.
Funkcje
●
fork()
- Utworzenie kopii procesu bieżącego
●
exec() - Zastąpienie procesu bieżącego innym procesem – rodzina funkcji.
●
wait(), waitpid() - Czekanie na zakończenie procesu
●
exit() - Zakończenie procesu
●
spawn() - Utworzenie procesu potomnego – rodzina funkcji.
Makra
●
WEXITSTATUS - Zwraca kod powrotu y przekazany przez funkcję exit(y) z procesu
potomnego
●
WTERMSIG - jeśli proces był zakończony przez sygnał, to zwraca numer sygnału
●
WIFEXITED - jeśli proces potomny był zakończony normalnie, to zwraca > 0
●
WIFSIGNALED - zwraca >0, jeśli proces potomny został zakończony przez nie
obsłużony sygnał
Akcje przy zakończeniu procesu
●
Należy zakończyć komunikację z innymi procesami
●
Należy zwolnić zajmowane zasoby
●
Należy zaczekać na zakończenie procesów potomnych (bo inaczej są one
adoptowane przez init)
1. Zamykane są otwarte pliki i strumienie
2. Najmłotszy bajt z kodu powrotu x jest przekazywany do zmiennej odczytywanej
przez funkcję wait() w procesie macierzystym. Kod powrotu jest zapamiętywany
w deskryptorze.
3. Jeśli proces macierzysty wywołał wait() albo waitpid(), to zostaje on
odblokowany a deskryptor jest usuwany
4. Jeśli jednak nie wywołał tych funkcji, to proces potomny przechodzi do stanu
zombie, a kod powrotu czeka sobie w deskryptorze.
5. Proces macierzysty otrzymuje sygnał SIGCHLD.
Pliki i funkcje dostępu do pliku
●
open()
- funkcja otwierająca plik (lub np. urządzenie będące plikiem). Potrafi
utworzyć plik, jeśli takowy nie istnieje, ale to zależy od ustawień oflag. Funkcja
zwraca deskryptor pliku (uchwyt), którego używamy do identyfikacji pliku.
●
creat()
- funkcja tworząca nowy plik. W argumencie podajemy nazwę pliku i
atrybuty (prawa dostępu). Funkcja, podobnie jak open(), zwraca deskryptor pliku.
●
read()
- Za parametr bierze deskryptor pliku, bufor docelowy i ilość bajtów do
odczytu, a co robi z tą wiedzą to już można się domyśleć. Czyta w bieżącej pozycji
w pliku, jeśli nam to nie pasuje to pozostaje
●
write()
- Parametry to deskryptor pliku, bufor zawierający dane do zapisu i ilość
bajtów do zapisania. Analogicznie jak read(), ale na odwrót.
●
close()
zamyka plik.
●
lseek()
- Pozwala zmieniać obecną pozycję w pliku. Argumenty to deskryptor
pliku, offset stanowiący o ile mamy się przesunąć i flagę która decyduje, czy
przesuwamy się względem bieżącej pozycji, początku lub końca pliku.
●
{,f,l}stat()
- Zwraca informacje o pliku (inode, uprawnienia itp).
●
fcntl()
- Zmienia atrybuty pliku.
●
unlink()
, remove() - usuwa plik
●
dup()
, dup2() - duplikuje deskryptor pliku. Można robić takie rzeczy jak np.
czitować program żeby myślał, że pisze na stdout a w rzeczywistości pisałby do
pliku.
Łącza nienazwane
Wykorzystanie
Prosta komunikacja pomiędzy procesem macierzystym i potomnym, nie ma możliwości
wymiany deskryptora pliku, w którym znajduje się łącze nienazwane inaczej niż gdy oba
procesy są w relacji macierzysty/potomny. Ten kanał komunikacji jest jednostronny dla
danego procesu, tzn np. macierzysty tylko czyta, a potomny tylko pisze.
1. Tworzymy łącze za pomocą funkcji pipe(), której parametrem jest wskaźnik na
dwuelementową tablicę int - w niej znajdą się deskryptory.
2. fork()ujemy sobie program
3. W procesie potomnym zamykamy nieużywany deskryptor za pomocą close(), np
jeśli tylko będziemy pisać, to zamykamy deskryptor do pisania (close(fd[1])),
po czym czytamy/zapisujemy jak z normalnym plikiem
4. W procesie macierzystym analogicznie
5. Na końcu robimy close() na pozostałych deskryptorach, z których korzystaliśmy i
to tyle
Funkcje
●
pipe()
- tworzy łącze nienazwane.
●
open()
, read(), write(), close() - jak w przypadku
. Uwaga: nie
używamy open() w przypadku łącz nienazwanych, bo nie mamy pliku, który
chcemy otworzyć - zamiast tego korzystamy z pipe().
●
- blokuje plik, pozwalając na synchronizację dostępu do niego pomiędzy
kilkoma procesami.
●
- zwraca deskryptor pliku dla argumentu FILE*.
Łącza nazwane
Wykorzystanie
Łącza nazwane mogą być używane przez niepowiązane ze sobą procesy. Są to po
prostu specjalne pliki, które są normalnie dostępne w systemie plików. Pliki te giną po
wyłączeniu komputera.
1. Tworzymy plik FIFO za pomocą mkfifo(),
2. Otwieramy plik poprzez open(),
3. read() i write() jak kto potrzebuje,
4. tradycyjne close().
Funkcja mkfifo
- tworzy łącze nazwane, jako argument przyjmuje nazwę pliku FIFO oraz
prawa dostępu do pliku.
int mkfifo(char * path, mode_t mode)
●
path -
Nazwa pliku FIFO (ze ścieżką)
●
mode
- Prawa dostępu do pliku .
Funkcja zwraca: 0 – sukces, -1 – błąd.
Funkcja select
Funkcja ta blokuje bieżący proces do momentu, kiedy dany deskryptor stanie się gotowy
albo wystąpi błąd. Można mu ustawić timeout, czyli czas, po którym funkcja daje sobie
spokój, dłużej nie czeka tylko zwraca błąd.
Tutaj nie ma wiele do mówienia, lepiej
.
Pamięć dzielona
Komunikacja przez wspólną pamięć w standardzie
POSIX
1. Alokujemy pamięć dzieloną poprzez funkcję shm_open(), w atrybutach której
znajduje się nazwa segmentu pamięci dzielonej i jej atrybuty, i która zwraca
deskryptor pliku,
ustalamy obszar zajmowanej pamięci,
3. Używając mmap() inicjujemy zmienną w pamięci dzielonej, zwykle ładując tam
wcześniej zdefiniowaną strukturę; jako wynik owej funkcji otrzymujemy wskaźnik
do zmiennej
4. Korzystamy sobie ze struktury normalnie
5. Zwalniamy nazwę pamięci dzielonej za pomocą shm_unlink(nazwa).
Przykład:
https://bitbucket.org/fleg/glupotki/src/831bc7d2d314/wspolbiezne/lab5/semafory/
Funkcje
●
shm_open()
●
shm_unlink()
●
ftruncate()
●
mmap()
Kolejki komunikatów
Kolejki komunikatów POSIX i zastosowania
●
Są widziane jako plik specjalny
●
Komunikaty w kolejce zachowują swoją strukturę; są separowane. Nie jest już
tak, jak w FIFO, gdzie po prostu czytamy tyle a tyle bajtów. Komunikaty w kolejce
mogą być różnej długości.
●
Komunikaty w kolejce mogą mieć różne priorytety.
●
Kolejki komunikatów są wygodne, gdy:
Proces wysyłający komunikaty nie może zostać wstrzymany,
Proces wysyłający nie wymaga informacji zwrotnej od adresata,
Konieczne jest przekazywanie danych od producenta do konsumenta.
Korzystanie z kolejki w praktyce: (
1. Za pomocą struktury mq_attr ustawiamy opcje kolejki
2. Tworzymy/otwieramy kolejkę komunikatów za pomocą funkcji mq_open(), która
za parametry przyjmuje nazwę kolejki, tryb otwarcia (zapis/odczyt), uprawnienia i
wskaźnik na strukturę z punktu 1, a zwraca mqd_t,
3. operujemy sobie na kolejce za pomocą mq_send() lub mq_receive(),
4. zamykamy kolejkę za pomocą mq_close(),
5. kasujemy kolejkę za pomocą mq_unlink(). Kasowanie to nie powinno
spowodować problemów, jeśli jakiś inny proces aktualnie korzysta z kolejek
(w końcu nie interesuje go ścieżka do pliku itp, a licznik otwarcia nadal będzie
większy od 0), ale to tylko moje przemyślenie.
Funkcje
●
mq_open
●
mq_receive
●
mq_send
●
mq_attr
●
mq_notify
Synchronizacja
Wzajemne wykluczanie
Wzajemne wykluczanie - wymaganie aby ciąg operacji na pewnym zasobie (zwykle
pamięci) był wykonany w trybie wyłącznym przez tylko jeden z potencjalnie wielu
procesów.
Operacje atomowe
Operacje, które nie mogą zostać przerwane, np. przez przełączenie procesu.
Sekcja krytyczna
Sekcja, która może być wykonywana równolegle/współbieżnie przez tylko jeden proces.
Warunki poprawnego rozwiązania sekcji krytycznej
Rozwiązanie problemu wzajemnego wykluczania musi spełniać następujące warunki:
1. W sekcji krytycznej może być tylko jeden proces to znaczy instrukcje z sekcji
krytycznej nie mogą być przeplatane.
2. Nie można czynić żadnych założeń co do względnych szybkości wykonywania
procesów.
3. Proces może się zatrzymać w sekcji lokalnej nie może natomiast w sekcji
krytycznej. Zatrzymanie procesu w sekcji lokalnej nie może blokować innym
procesom wejścia do sekcji krytycznej.
4. Każdy z procesów musi w końcu wejść do sekcji krytycznej.
?
Niesystemowe i systemowe metody ochrony sekcji
krytycznej
DO UZUPEŁNIENIA
Systemowe:
1. Blokowanie przerwań
2. Metoda zmiennej blokującej (nieprawidłowa)
Niesystemowe:
1. Wirujące blokady (ang. Spin Locks) wykorzystujące sprzętowe wsparcie w postaci
instrukcji sprawdź i przypisz oraz zamień. Stosuje się je do synchronizacji wątków
ze względu na mały narzut operacji systemowych.
2. Blokowanie przerwań – do ochrony wewnętrznych sekcji krytycznych systemu
operacyjnego.
Sprzętowa ochrona sekcji krytycznej
DO UZUPEŁNIENIA
●
TAS - sprawdź i przypisz
●
CAS - porównaj i zamień
●
XCHG - zamień
Semafory i ich zastosowanie
Semafor - jest obiektem abstrakcyjnym służącym do kontrolowania dostępu do
ograniczonego zasobu. Semafory są szczególnie przydatne w środowisku gdzie wiele
procesów lub wątków komunikuje się przez wspólną pamięć.
Ochrona sekcji krytycznej
1. Tworzymy semafor za pomocą funkcji sem_init(), której pierwszym parametrem
jest wskaźnik na semafor (zmienna typu semaphore), a drugim początkowa
wartość semafora.
2. Wywołujemy sem_wait(). Funkcja ta dekrementuje wartość semafora jeśli
jest on dodatni, a jeśli jest równy 0 to blokuje proces bieżący, który zostanie
odblokowany wtedy, gdy inny proces wywoła na tym samym semaforze
sem_post()
.
3. Wykonujemy kod sekcji krytycznej
4. Wywołujemy sem_post(), który albo odblokowuje inny proces oczekujący na tym
semaforze, albo inkrementuje wartość semafora.
Generalnie kwestia tej zmiennej, zera itp jest dość prosta, ale trzeba sobie to wyobrazić.
Semafory nienazwane i nazwane POSIX
Wyróżnione są tu dwa typy semaforów:
1. Semafory nienazwane - dostęp do semafora nienazwanego następuje po adresie
semafora. Stąd nazwa semafor nienazwany.
2. Semafory nazwane - identyfikowane są w procesach poprzez ich nazwę. Na
semaforze nazwanym operuje się tak samo jak na semaforze nienazwanym z
wyjątkiem funkcji otwarcia i zamknięcia semafora.
Semafory nienazwane nadają się do synchronizacji wątków w obrębie jednego procesu.
Dostęp do semafora nienazwanego następuje poprzez jego adres. Może on być także
użyty do synchronizacji procesów o ile jest umieszczony w pamięci dzielonej. Dostęp do
semaforów nazwanych następuje poprzez nazwę. Ten typ semaforów bardziej nadaje się
synchronizacji procesów niż wątków. Semafory nienazwane działają szybciej niż nazwane.
Funkcje
●
sem_open
●
sem_init
●
sem_wait
●
sem_post
Monitory
Definicja
●
Monitor jest strukturalnym narzędziem synchronizacji.
●
Zmienne i procedury, które na nich operują są zebrane w jednym module. Dostęp
do zmiennych monitora jest możliwy tylko i wyłącznie za pomocą procedur
monitora.
●
Tylko jeden proces może w danej chwili wywoływać procedury monitora. Każdy
inny proces chcący wywołać procedurę monitora zostanie zablokowany, aż pierwszy
proces nie skończy.
●
Można wstrzymywać i wznawiać procedury monitora za pomocą zmiennych
warunkowych, na których można wykonywać operacje wait() i signal().
Zastosowanie
DO ZROBIENIA
Oczekiwanie wewnątrz monitora
Podejrzewam, że chodzi o wait().
Mianowicie, w trakcie wykonywania procedury monitora można wywołać wait(), po czym
obecny proces zostanie wstrzymany i wrzucony na koniec kolejki procesów oczekujących
na mieszanie w monitorze lub jakiejś kolejki uprzywilejowanej (zależy od implementacji),
a dostęp do monitora zostaje przekazany innemu procesowi.
Ma to sens w przypadku, kiedy wykonujemy jakąś operację na monitorze, ale okazuje się,
że musimy czekać na jeszcze coś innego. Wtedy, zamiast blokować inne procesy czekające
na dostęp do monitora i bezczynnie czekać, możemy wpuścić kogoś samemu czekając.
Zmienne warunkowe
DO ZROBIENIA
Funkcje
●
wait
●
signal
●
noempty
●
notify
●
broadcast
Implementacja semafora poprzez monitor
DO ZROBIENIA
Wątki
Wątek
– elementarna jednostka szeregowania korzystająca z zasobów procesu.
Wątki wykonywane w ramach jednego procesu dzielą jego przestrzeń adresową i
inne zasoby procesu.
W ramach jednego procesu może się wykonywać wiele wątków
Własności wątków
● Koszt utworzenia i przełączania wątku jest mniejszy niż procesu.
● Dane statyczne procesu są dla wątków działających w ramach jednego
procesu wzajemnie widoczne.
● Wykonanie każdego wątku przebiega sekwencyjnie, każdy wątek ma swój
licznik rozkazów.
● Wątki mogą być wykonywane na oddzielnych procesorach co umożliwia
przyspieszenie obliczeń.
● Ponieważ wątki dzielą wspólne dane konieczna jest synchronizacja dostępu
do tych wspólnych danych.
Wątki pojęcie i zasoby
Wątek dzieli ze swym procesem macierzystym następujące zasoby:
● Dane statyczne (segment danych)
● Deskryptory otwartych plików, blokady plików
● Maskę tworzenia plików (umask)
● Środowisko
● Katalog macierzysty i główny
● Limity zasobów (setrlimit)
● Timery
● Sesję, użytkownika, grupę, terminal sterujący
Zasoby własne wątku:
● Identyfikator wątka (thread ID)
● Maska sygnałów
● Zmienna errno
● Priorytet i strategię szeregowania
Atrybuty i zasoby własne wątku:
1. Identyfikator wątku TID (ang.Thread Identifier) - każdy watek ma unikalny w
ramach procesu identyfikator. Jest to liczba całkowita. Pierwszy wątek ma TID
1, następny 2 itd.
2. Zestaw rejestrów (ang. Register set) - każdy wątek posiada własny obszar
pamięci w którym pamiętany jest zestaw rejestrów procesora (tak zwany
kontekst procesora). Gdy watek jest wywłaszczany lub blokowany w obszarze
tym pamiętane są rejestry procesora. Gdy watek będzie wznowiony obszar
ten jest kopiowany do rejestrów procesora.
3. Stos (ang. Stack) - każdy wątek ma swój własny stos umieszczony w
przestrzeni adresowej zawierającego go procesu. Na stosie tym pamiętane są
zmienne lokalne wątku.
4. Maska sygnałów (ang. Signal mask) - każdy wątek ma swą własną maskę
sygnałów. Maska sygnałów specyfikuje które sygnały mają być obsługiwane
a które blokowane. Początkowa maska jest dziedziczona z procesu
macierzystego.
5. Obszar TLS wątku (ang. Thread Local Storage) – każdy wątek ma
pewien obszar pamięci przeznaczony na utrzymywanie różnych danych
administracyjnych takich jak TID, PID, początek stosu, kod ewentualnego
błędu errno i inne dane. Obszar TLS jest odpowiednikiem deskryptora
procesu.
6. Procedura zakończenia (ang. Cancellation Handler) - gdy wątek się kończy
wykonywana jest procedura zakończenia w ramach której zwalniane są
zasoby wątku.
Tworzenie
Tworzenie wątku
Nowy wątek tworzy się przy pomocy funkcji pthread_create. Funkcja ta tworzy
wątek, którego kod znajduje się w funkcji podanej jako argument func. Wątek jest
uruchamiany z parametrem arg, a informacja o nim jest umieszczana w strukturze
thread
.
Synchronizacja
W bibliotece pthreads do zapewnienia wyłączności dostępu do danych stosuje się
mechanizm muteksu (ang. mutex). Nazwa ta pochodzi od słów Mutual exclusion
czyli
wzajemne wykluczanie.
Funkcje
●
pthread_create
●
pthread_join -
Funkcja pthread_join zawiesza działanie wołającego wątku
aż do momentu, gdy watek podany jako argument nie zakończy działania.
-odczytuje wartość zakończenia wątku
●
pthread_exit
Mutexy
●
mutex_init
●
mutex_lock -
Zajęcie muteksu - zapewnia wyłączność w korzystaniu z zasobu.
●
mutex_unlock -
Zwolnienie muteksu - zwalnia użyty i zablokowany wcześniej
zasób
.
Działanie funkcji mutex_unlock zależy od tego czy inne wątki czekają
zblokowane na muteksie:
1. Brak wątków zablokowanych na muteksie – stan muteksu zostaje
zmieniony na wolny.
2. Są wątki zablokowane na muteksie – jeden z czekających wątków zostaje
odblokowany
Zmienne warunkowe
Zmienna warunkowa jest narzędziem do blokowania wątku wewnątrz sekcji
krytycznej aż do momentu gdy pewien warunek zostanie spełniony. Warunek ten
może być dowolny i niezależny od zmiennej warunkowej. Zmienna warunkowa
musi być użyta w połączeniu z muteksem o ile konstrukcja ma zapewnić własności
monitora
.
●
cond_init
●
cond_wait -
Zawieszenie wątku w kolejce.
●
cond_signal -
Wznowienie wątku zawieszonego w kolejce danej zmiennej
warunkowej.
●
cond_broadcast -
Wznowienie wszystkich wątków zawieszonych w kolejce
danej zmiennej warunkowej.
Blokady czytelników i pisarzy
Zasada działania blokad czytelników i pisarzy:
● Odczyt może być wykonywany współbieżnie do innych odczytów
● Zapis musi być wykonywany w trybie wyłącznym względem innych zapisów
lub odczytów.
Stan blokady:
● Wolna
● Zajęta do odczytu być może przez wiele wątków czytających
● Zajęta do zapisu
Wirujące blokady
Wirujące blokady są środkiem zabezpieczania sekcji krytycznej. Wykorzystują
jednak czekanie aktywne zamiast przełączenia kontekstu wątku tak jak się to dzieje
w muteksach.
Blokada może być w dwóch stanach:·
● Wolna
● Zajęta
●
pthread_spin_init - podczas inicjacji wirującej blokady definiujemy czy
mogą
operować wątki należące do różnych procesów czy tylko wątki należące do
tego samego procesu
●
pthread_spin_lock - Zajęcie blokady -
Działanie funkcji zależy od stanu
blokady. Gdy blokada jest wolna następuje jej zajęcie. Gdy blokada jest
zajęta wątek wykonujący funkcję pthread_spin_lock(...)ulega
zablokowaniu do czasu gdy inny wątek nie zwolni blokady wykonując funkcję
pthread_spin_unlock
●
pthread_spin_unlock - zwolnienie blokady -
Działanie funkcji zależy od stanu
blokady. Gdy są wątki czekające na zajęcie blokady to jeden z nich zajmie
blokadę. Gdy żaden wątek nie czeka na zajęcie blokady będzie ona
zwolniona.
Inwersja priorytetów
Dzika sytuacja,
gdy dwa lub więcej wątki o różnych priorytetach używają wspólnego
zasobu chronionego przez pewien mechanizm zapewnienia wzajemnego
wykluczania (np. muteks)
Inwersja priorytetów – zjawisko polegające na wykonywaniu się wątku o niższym
priorytecie mimo, iż wątek o wyższym priorytecie pozostaje gotowy. Inwersja
priorytetów może się pojawić gdy wątki o różnych priorytetach używają wspólnego
muteksu lub podobnego mechanizmu synchronizacyjnego.
Ochrona przed inwersją
W systemach czasu rzeczywistego stosowane są dwie strategie postępowania z
problemem inwersji priorytetów. Jest to:
1. Dziedziczenie priorytetu (ang.Priority Inheritance)
2. Zastosowanie stosowanie protokołu wykorzystującego tzw. pułap priorytetów
(ang.
Priorty Ceiling)
Metody dziedziczenia priorytetów
Dziedziczeniem priorytetu polega na tym, że gdy wątek W3 o wyższym priorytecie
próbuje zająć muteks zajęty już przez wątek W1 o priorytecie niższym, to system
podwyższa chwilowo priorytet wątku W1 zajmującego muteks do wysokości
priorytetu wątku W3. Dzięki podwyższonemu priorytetowi wątek W1 szybciej wykona
swe zadanie i zwolni muteks. Po zwolnieniu muteksu wątkowi W1 zostaje mu
przywrócony pierwotny priorytet.
I piękna definicja
Dziedziczeniem priorytetu – tymczasowe zwiększenie priorytetu wątku
posiadającego zasób do najwyższego priorytetu z priorytetów wątków ubiegających
się o zajęcie tego zasobu. Po zwolnieniu zasobu wątkowi przywracany jest
początkowy priorytet.
Dodatkowa wiedza
Protokół dziedziczenia priorytetów działa prawidłowo w przypadku użycia jednego
typu zasobu. Gdy używana jest większa liczba zasobów może dojść do różnych
niekorzystnych zjawisk jak:
● blokowanie przechodnie
● zakleszczenie.
Metody pułapu priorytetów
Każdemu chronionemu zasobowi (w tym przypadku jest to muteks) przypisuje
się pewien określony statyczny priorytet. Priorytet ten powinien być wyższy od
najwyższego priorytetu z tych wątków które o dany zasób będą konkurowały. Gdy
jakiś wątek będzie próbował zająć zasób to zostanie mu tymczasowo przydzielony
priorytet związany z tym
zasobem. Po zwolnieniu zasobu priorytet wątku wróci do wielkości wyjściowej.
Protokół z pułapem priorytetu (ang. priority ceiling protocol)
W protokole z pułapem priorytetu następuje tymczasowe zwiększenie priorytetu w
wątku usiłującego zająć zasób do pewnego ustalonego priorytetu (wyższego
od priorytetu jakiegokolwiek wątku konkurującego o zasób). Wszystkim wątkom
konkurujące o zasób zostaje tymczasowo nadany ten jednakowy priorytet. Dzięki
temu że watek zajmujący zasób zyskuje chwilowo priorytet wyższy niż jakiekolwiek
inny wątek konkurujący o zasób – ma on szansę zakończyć operację na zasobie bez
wywłaszczenia.
Zalety protokołu:
● Protokół zapobiega powstawaniu zakleszczeń.
● Zapewnia dobry czas oczekiwania na zasób (dla najgorszego przypadku)
poprzez wątek o najwyższym priorytecie. Czas ten równy jest długości
najdłuższej sekcji krytycznej wątków o niższym priorytecie.
Wady protokołu:
● Należy z góry wyznaczyć zbiór wszystkich wątków które będą konkurowa ły o
zasób i jako pułap priorytetu przyjąć najwyższy priorytet z zadań z tego zbioru
+ 1 . Może to być czasochłonne lub nawet niemożliwe.
● Posiada zły średni czas odpowiedzi z związku z narzutami na implementację.
Gniazdka
Interfejs gniazd
Jednolity interfejs API (Application Program Interface) do mechanizmów
komunikacji sieciowej.
Główna idea gniazdek polega na użyciu do komunikacji (lokalnej i zdalnej)
tego samego mechanizmu, co dostępu do plików. Jest to mechanizm oparty o
deskryptory plików i funkcje read, write .
Termin gniazdko ma dwa znaczenia:
1. Biblioteka + funkcje interfejsowe (API).
2. Końcowy punkt komunikacji
Biblioteka gniazdek maskuje mechanizmy transportu sieci.
Własności gniazd:
● Gniazdo jest identyfikowane przez liczbę całkowitą nazywaną deskryptorem
gniazda
● Gniazdo można nazwać i wykorzystywać do komunikacji z innymi gniazdami
w tej samej domenie komunikacyjnej
Komunikacja bezpołączeniowa - Komunikacja bez
kontroli połączenia
Klient:
Tworzy gniazdko
- socket
Nadaje gniazdku adres
- bind (konieczne przy odbiorze)
Nadaje lub odbiera dane
- sendto, recfrom, write, read, recv, send
Serwer:
Tworzy gniazdko
- socket
Nadaje gniazdku adres
- bind (konieczne przy odbiorze)
Nadaje lub odbiera dane
- sendto, recfrom, write, read, recv, send
Funkcja recfrom
- Odbiór danych z gniazdka - umożliwia odczyt bajtów z
gniazdka znajdującego sięw stanie niepołączonym jak i połączonym.
Funkcja sendto
- Zapis do gniazdka - Funkcja sendto umożliwia wysłanie
bajtów do gniazdka znajdującego się w stanie nie połączonym jak i połączonym.
Komunikacja połaczeniowa - Transmisja z kontrolą
połączenia
Klient:
1. Tworzy gniazdko
socket
2. Nadaje gniazdku adres
bind
(konieczne przy odbiorze)
3.Łączy się z serwerem
connect
4. Nadaje lub odbiera dane
write, read, recv, send
Serwer:
1. Tworzy gniazdko
socket
2. Nadaje gniazdku adres
bind
(konieczne przy odbiorze)
3. Wchodzi w tryb akceptacji po łączeń listen
4. Oczekuje na po łączenia
accept
Gdy połączenie zostanie nawiązane:
1. Tworzy dla tego po łączenia nowe gniazdko
2. Nadaje lub odbiera dane - write, read, recv, send
3. Zamyka gniazdko
Funkcje
●
connect - Połączenie ze zdalnym gniazdkiem. Funkcja powoduje próbę nawiązania
połączenie ze zdalnym gniazdkiem wyspecyfikowanym jako adres.
●
listen - Wprowadzenie serwera w stan gotowości do nawiązania połączenia
●
accept - Nawiązanie połączenia przez serwer -
Działanie funkcji accept:
Wywołanie accept może być blokujące. Gdy przychodzi nowe połączenie
następuje odblokowanie procesu bieżącego i wykonanie następujących czynności:
1. Pobranie pierwszego połączenie z kolejki oczekujących połączeń.
2. Utworzenie nowego gniazdka o identycznych własnościach jak gniazdko
utworzone poleceniem socket.
3. Alokacja nowego deskryptora pliku dla gniazdka.
4. Nadanie wartości parametrom name i namelen.
●
read - Odczyt z gniazdka – Funkcja jest używana do odbioru danych z gniazdka
w trybie połączeniowym. Funkcja powoduje odczyt z gniazdka i umieszczenie
odczytanych n bajtów w buforze.
●
write - j.w tylko w 2 strone
●
recv
-
Funkcja jest używana do odbioru danych z gniazdka w trybie
połączeniowym lub bezpołączeniowym. - podobnie jak read, ale występuje
tu dodatkowy 3 parametr z flagami: MSG_WAITALL - Funkcja czeka na
tyle bajtów ile wymieniono w wywołaniu, MSG_OOB - Odbiór danych poza
pasmem – znaczenie zależy od protokołu, MSG_PEEK Dane odczytane na
próbę, nie znikają z bufora
●
send - analogicznie
Serwer sekwencyjny
Serwer sekwencyjny to serwer składający się z jednego tylko procesu. W danej chwili
może on obsługiwać tylko jednego klienta.
Klient:
1. Lokalizacja serwera
2. Utworzenie komunikatu specyfikującego żądanie
3. Wysłanie komunikatu do procesu serwera
4. Odbiór odpowiedzi.
5. Wykorzystanie wyniku.
Serwer:
1. Rejestracja nazwy własnej w serwerze nazw.
2. Odbiór zlecenia.
3. Identyfikacja zlecenia
4. Realizacja zlecenia.
5. Wysłanie odpowiedzi do klienta
Serwer współbieżny
Współbieżne działanie serwera pozwala, w sytuacji gdy do serwera łączy się wielu
klientów, na ich współbieżne obsłużenie.
Schemat działania serwera współbieżnego
1.
Tworzy gniazdko
- socket
2.
Nadaje gniazdku adres
- bind (konieczne przy odbiorze)
3.
Wchodzi w tryb akceptacji połączeń - listen
4.
Oczekuje na połączenia
- accept
5.
Gdy przychodzi nowe połączenie funkcja accept zwraca identyfikator
nowego gniazdka. To gniazdko będzie używane w połączeniu z klientem. Dla
połączenia tworzy się nowy proces i przechodzi się do 4.
Proces obsługujący połączenie:
Korzysta z nowego gniazdka którego numer jest przekazany jako parametr
1. Nadaje lub odbiera dane
- write, read, recv, send
2. Zamyka gniazdko
Sygnały
Sygnały i ich obsługa
Sygnał – mechanizm asynchronicznego powiadamiania procesów o zdarzeniach –
zwykle awaryjnych.
Sygnały mogą być generowane przez:
1. System operacyjny, zwykle po wykonaniu nieprawidłowej operacji.
2. Z konsoli operatorskiej poprzez polecenia kill i slay.
3. Z programu aplikacyjnego poprzez funkcje (np. kill, raise, abort, alarm, i inne)
oraz timery.
Proces może zareagować na sygnały w sposób następujący:
1. Obsłużyć sygnał czyli wykonać funkcję dostarczoną poprzez programistę.
2. Zignorować sygnał – nie każdy sygnał daje się zignorować.
3. Zablokować sygnał to znaczy odłożyć jego obsługę na później.
4. Zakończyć się po otrzymaniu sygnału.
Reakcja procesu na sygnał w zależności od stanu w jakim znajduje się proces.
1. Gdy proces jest wykonywany lub gotowy to następuje przerwanie sekwencji
wykonania i skok do procedury obsługi sygnału.
2. Gdy proces jest zablokowany to następuje jego odblokowanie i wykonanie
procedury obsługi tego sygnału.
Instalacja handlera sygnału
Funkcje:
●
signal() (UNIX)
void(*signal(int sig, void(*func)(int)))(int))
gdzie sig to numer lub symbol sygnału, a func jest wskaźnikiem na funkcję
która ma być wykonana. Nie można obsłużyć sygnałów SIGSTOP i SIGKILL.
Jako funkcję można podać SIG_IGN (ignore) lub SIG_DFL- domyślna reakcja
na sygnał (zakończenie lub zignorowanie)
●
sigaction() (POSIX)
int sigaction(int signo, struct sigaction *act, struct sigaction *oldact),
sigaction to struktura z informacjami o obsłudze sygnału:
struct sigaction {
void (*sa_handler)(int) ;
void(*sa_sigaction)(int signo,siginfo_t *info, void *inne)
sigset_t sa_mask; // Sygnały blok. podczas obsługi
int sa_flags; // Flagi modyfikacji działania
}
Dokładny opis parametrów: 13_Sygnaly.pdf strona 15
Blokowanie sygnałów
Blokada sygnałów
Podczas obsługi sygnału dostarczanie innych sygnałów jest zablokowane.
2. Sygnały i funkcje systemowe
W większości przypadków w czasie wykonania funkcji systemowych sygnały są
zablokowane. Wyjątek stanowią:
- Funkcje read, write, open w odniesieniu do terminali.
- Funkcje wait, pause, sigsuspend
Funkcje te będą przerywane przez sygnał. Możliwe jest ustawienie flagi
SA_RESTART aby przerwane funkcje kontynuować.
Sygnały a wątki
Sygnały mogą być kierowane do procesów i do wątków.
Zachowanie się sygnałów w środowisku procesów wielowątkowych zdefiniowane
jest regułami:
1. Sygnały obsługiwane są na poziomie procesu. Znaczy to że gdy wątek zignoruje
lub obsłuży sygnał, fakt ten wpływa na inne wątki tego procesu.
2. Maskowanie sygnałów zachodzi na poziomie wątków.
3. Jeżeli sygnał skierowany jest do określonego wątku to będzie on do tego wątku
dostarczony.
4. Jeżeli sygnał skierowany jest do procesu to będzie dostarczony do pierwszego
wątku który nie blokuje danego sygnału.
Zasada obsługi sygnałów w środowiskach wielowątkowych:
Standardową strategią obsługi sygnałów w środowisku procesów wielowątkowych
jest zamaskowanie sygnałów we wszystkich watkach z wyjątkiem jednego. Ten
właśnie wątek będzie obsługiwał sygnały.
Timery
Timery - specjalne obiekty systemu operacyjnego, odpowiedzialne za generowanie
zdarzeń które w ustalonym czasie uruchomić mają określone akcje systemu.
Timery mogą wywoływać sygnał lub tworzyć nowy wątek, w zalezności o tego, co
wybierzemy w strukturze sigevent przekazywanej jako argument do funkcji tworzącej
nowy timer.
Posługiwanie się timerem
1. Tworzymy strukturę typu sigevent, w której wybieramy, co timer ma zrobić po
odliczeniu czasu (nowy wątek, sygnał itp),
2. Tworzymy timer za pomocą timer_create()
3. Wybieramy sposób określenia czasu - absolutny lub relatywny (parametr funkcji
timer_settime())
4. Wybrać tryb pracy - jednorazowy lub cykliczny
W strukturze itimerspec mamy dwa pola - value, czyli czas, który musi upłynąć
przed pierwszą aktywacją timera, i interval, czyli czas pomiędzy kolejnymi
wywołaniami timera. Można ustawić interval na 0, co będzie skutkować
jednorazową aktywacją timera.
5. Nastawić timer, czyli wywołaś timer_settime() z odpowiednimi parametrami.
RPC
Zdalne wykonywanie procedur RPC
DO ZROBIENIA
Podstawowe pojęcia
Przetaczanie parametrów (ang. parameters marschalling) – pakowanie parametrów
procedury do komunikatu z jednoczesną konwersją danych.
Przetaczanie parametrów obejmuje:
1. Konwersję formatu komunikatu
2. Serializację danych
Namiastka klienta (ang. client stub) - reprezentuje serwer po stronie klienta
Namiastka serwera (ang. server stub) - reprezentuje klienta po stronie serwera
Synchroniczne RPC - klient czeka na odpowiedź serwera
Asynchroniczne RPC - klient przekazuje parametry do serwera i kontynuuje działanie.
Odroczone asynchroniczne RPC - klient przekazuje parametry do serwera i kontynuuje
działanie. Gdy serwer opracuje odpowiedź wywołuje procedurę po stronie klienta.
Wiązanie dynamiczne
Przy zdalnym wywoływaniu procedur powstaje pytanie jak klient ma zlokalizować procedury
serwera.
Wiązanie (ang. binding) – odwzorowanie nazwy (procedury RPC) w konkretny obiekt
określony identyfikatorem komunikacyjnym. Postać identyfikatora zależy od systemu (np.
adres gniazdka - IP, port).
Łącznik (ang. binder) – specjalna usługa RPC utrzymująca tablicę odwzorowań nazw usług
(procedur RPC) na porty serwerów tych usług. Łącznik utrzymywany jest przez serwery
które udostępniają identyfikatory portów swoim klientom. Od usług łącznika zależą wszystkie
inne usługi. Dlatego łączniki tworzy się tak aby tolerowały awarie. Np. tablice odwzorowań
zapisuje się w pliku z którego mogą być one wczytane w przypadku awarii.
Język opisu interfejsu IDL
DO ZROBIENIA
Tworzenie aplikacji w standardzie Sun RPC
Realizacja Sun RPC oparta jest na gniazdkach. Mechanizm gniazdek
jest zamaskowany przed użytkownikiem. Ma on do dyspozycji funkcje
wyższego poziomu.
Można korzystać z protokołu:
· TCP
· UDP (rozmiar danych ograniczony do 8 KB).
Dostępne trzy poziomy:
Poziom pierwszy - gotowych funkcji (np. nusers - liczba zal. użytk.)
Poziom pośredni – łatwy w użyciu lecz ograniczona funkcjonalność
Poziom trzeci – stosowane są funkcje niskiego poziomu, pełna funkcjonalność
Najczęściej stosowany jest poziom pośredni. Jest on odpowiedni dla
większości typowych aplikacji.
Aplikacja poziomu pośredniego nie umożliwia:
· Kontroli przeterminowań
· Użycie wielu procesów / wątków po stronie serwera
· Elastycznej obsługi błędów
· Użycia zaawansowanej identyfikacji strony wywołującej
Tworzenie aplikacji RPC odbywa się w następujących krokach:
1. Utworzenie interfejsu serwera – specyfikacja (w języku opisu interfejsu RPCGEN )
co serwer ma wykonać i jak przekazuje się parametry.
2. Przy użyciu programu rpcgen
generuje się namiastkę klienta (ang. client stub),
namiastkę serwera (ang. server stub), plik nagłówkowy, plik konwersji.
3. Implementacja usług serwerowych.
4. Implementacja aplikacji klienta – wykorzystanie stopki klienta.
Program serwera działa według schematu:
1. Otrzymanie identyfikatora transportu (ang. transport handle)
2. Zarejestrowanie usługi u demona portmap
3. Oczekiwanie na zgłoszenia klienta i wykonywanie jego zleceń
Program klienta działa według schematu:
1. Otrzymanie identyfikatora klienta (ang. client handle)
2. Wywoływanie odległych procedur
3. Likwidacja identyfikatora klienta gdy nie jest potrzebny
Linda
System Linda
System Linda jest oryginalnym i zarazem mocną koncepcją tworzenia systemów
współbieżnych i równoległych. Linda nie jest samodzielnym językiem programowania ale
dodatkiem do innych języków. A więc istnieje C-Linda, FORTRAN-Linda i inne. Koncepcyjnie
Linda jest prosta a fakt że stanowi ona dodatek do znanego już języka (nie trzeba się uczyć
od początku) podnosi jej atrakcyjność. Linda bazuje na koncepcji pamięci dzielonej (być
może rozproszonej) nazywanej przestrzenią krotek TS (ang. Tuple Space) i niewielkiej
liczbie operacji zapewniającej dostęp do tej przestrzeni, synchronizację i wzajemną
komunikację procesów współbieżnych. Koncepcja Lindy wspiera model programowania
równoległego znany jako zarządca – wykonawca (ang. Worker – Manager). Koncepcja
systemu pochodzi od Davida Gelerntera z Yale University. Linda jest językiem opisującym
koordynację (ang. cordination language) (synchronizację i komunikację) procesów.
Linda – własności
● Wspiera tworzenie aplikacji równoległych typu zarządca – wykonawca
● Kod niezależny od liczby procesów wykonawczych
● Wspiera równoległość i komunikację międzyprocesową
● Nakładanie się komunikacji i obliczeń
● Nadaje się do systemów heterogenicznych (składających się z różnych maszyn)
Przestrzeń krotek
Przestrzeń krotek TS jest abstrakcją zawierającą posiadające typy ciągi danych. Przestrzeń
krotek jest globalna w całym systemie chociaż może być implementowana na maszynach z
rozproszoną pamięcią dzieloną.
Przestrzeń krotek – dzielona przestrzeń danych.
Krotka – lista do 16 pól zawierających dane języka bazowego. Pola te są rozdzielone
przecinkami. W języku C-Linda pola mogą być znakami, łańcuchami, typu całkowitego,
rzeczywistego lub typami złożonymi jak tablice lub struktury ale nie zawierające krotek.
Przykłady krotek:
(“dane1”, 13, 2)
(x1, x2)
(“zadanie1”,i,j)
Przestrzeń krotek oparta jest na pamięci asocjacyjnej.
· Dostęp do krotek opiera się na ich zawartości
· Krotki nie posiadają adresu przez który można by do nich sięgnąć.
Krotki mogą zawierać dwie kategorie danych:
· Parametry rzeczywiste (ang. actual parameters) – zawierają ustalone dane pewnego typu.
· Parametry formalne (ang. formal parameters) – są pojemnikiem na dane pewnego typu.
Krotki dziurawe
Krotki zawierające parametr formalny nazywane są krotkami dziurawymi.
Znak ? oznacza że jest to parametr formalny. Ma on typ ale nie posiada wartości.
Przykład:
("rzeczywisty", ",formalny",? x) – x jest zmienną typu int
Krotki pasywne i aktywne.
· Krotki mogą być pasywne lub aktywne.
· Krotka pasywna zawiera ustalone dane.
· Krotka aktywna zawiera przynajmniej jeden parametr którego wartość nie jest jeszcze
ustalona. Taki nieustalony parametr jest funkcją. Gdy krotka aktywna umieszczana jest w
przestrzeni krotek system tworzy współbieżny proces który oblicza jej wartość.
· Po obliczeniu wartości wynik jest wstawiany do krotki i krotka staje się pasywna.
Przykład:
("test", i, f(i));
Operacje
●
out
Umieszczenie krotki pasywnej w TS
●
eval
Umieszczenie krotki aktywnej w TS
●
in
Pobranie krotki z TS – wersja blokująca
●
inp
Pobranie krotki z TS – wersja nieblokująca
●
rd
Odczyt krotki z TS – wersja blokująca
●
rdp
Odczyt krotki z TS – wersja nieblokująca
Wyrażanie operacji synchronizacyjnych i
komunikacyjnych
DO ZROBIENIA
Problem producenta i konsumenta,
czytelników i pisarzy
Rozwiązanie za pomocą semaforów
DO ZROBIENIA
Rozwiązanie za pomocą monitorów
DO ZROBIENIA
Rozwiązanie za pomocą zmiennych warunkowych
DO ZROBIENIA
Rozwiązanie za pomocą mechanizmów języka Linda
DO ZROBIENIA