background image

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

Wstęp do procesów

Stany procesów
Deskryptor procesu

Zawartość deskryptora procesu

Fazy wykonania procesu
Atrybuty procesu
Dziedziczenie atrybutów

Procesy Posix

Tworzenie procesów

background image

Funkcje
Makra
Akcje przy zakończeniu procesu

Pliki i funkcje dostępu do pliku
Łącza nienazwane

Wykorzystanie
Funkcje

Łącza nazwane

Wykorzystanie
Funkcja mkfifo
Funkcja select

Pamięć dzielona

Komunikacja przez wspólną pamięć w standardzie POSIX
Funkcje

Kolejki komunikatów

Kolejki komunikatów POSIX i zastosowania
Funkcje

Synchronizacja

Wzajemne wykluczanie
Operacje atomowe
Sekcja krytyczna
Warunki poprawnego rozwiązania sekcji krytycznej
Niesystemowe i systemowe metody ochrony sekcji krytycznej
Sprzętowa ochrona sekcji krytycznej

Semafory i ich zastosowanie

Ochrona sekcji krytycznej
Semafory nienazwane i nazwane POSIX
Funkcje

Monitory

Definicja
Zastosowanie
Oczekiwanie wewnątrz monitora
Zmienne warunkowe
Funkcje
Implementacja semafora poprzez monitor

Wątki

Wątki pojęcie i zasoby
Tworzenie
Synchronizacja
Funkcje
Mutexy
Zmienne warunkowe
Blokady czytelników i pisarzy
Wirujące blokady

Inwersja priorytetów

Ochrona przed inwersją
Metody dziedziczenia priorytetów

background image

Metody pułapu priorytetów

Gniazdka

Interfejs gniazd
Funkcje
Serwer sekwencyjny
Serwer współbieżny

Sygnały

Sygnały i ich obsługa
Instalacja handlera sygnału
Blokowanie sygnałów
Sygnały a wątki

Timery

Posługiwanie się timerem

RPC

Zdalne wykonywanie procedur RPC
DO ZROBIENIA
Podstawowe pojęcia
Wiązanie dynamiczne
Język opisu interfejsu IDL
Tworzenie aplikacji w standardzie Sun RPC

Linda

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

Rozwiązanie za pomocą semaforów
Rozwiązanie za pomocą monitorów
Rozwiązanie za pomocą zmiennych warunkowych
Rozwiązanie za pomocą mechanizmów języka Linda

 

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.

background image

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),

Aplikacja na pewno się nie 

zablokuje

.

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ż 

zakleszczeniem

. 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.

Zagłodzenie procesu

 - 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 

background image

danej chwili zasób, procesor może wykonywać inny proces.

background image

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

Wyróżniamy 3 

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()

background image

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

Stan procesu

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

background image

Katalog bieżący i macierzysty

Informacja o terminalu sterującym

UMASK

 - 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.

[

źródło

]

background image

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.

background image

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 

lseek()

.

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

background image

Funkcje

pipe()

 - tworzy łącze nienazwane.

open()

, read(), write(), close() - jak w przypadku 

plików

. Uwaga: nie 

używamy open() w przypadku łącz nienazwanych, bo nie mamy pliku, który 

chcemy otworzyć - zamiast tego korzystamy z pipe().

flock()

 - blokuje plik, pozwalając na synchronizację dostępu do niego pomiędzy 

kilkoma procesami.

fileno()

 - 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

Strona w manie

 - 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 

przeczytać stronę manuala

.

Pamięć dzielona

background image

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,

2. Poprzez 

ltrunc()

 lub 

ftruncate()

 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/

main.c

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: (

kod źródłowy

)

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(),

background image

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.

A nie chodzi 

o to

?

Niesystemowe i systemowe metody ochrony sekcji 

background image

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:

background image

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), 

background image

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)

background image

● 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

background image

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.

background image

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.

background image

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 

background image

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 

background image

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 - writereadrecvsend

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 - 

background image

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 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

background image

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:

background image

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:

background image

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

  

background image

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

background image

  
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

background image

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

background image

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