Funkcje
Transkrypt
Funkcje
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 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 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. 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 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 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() 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 ● ● ● ● 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, 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] uruchamian 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 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 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 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(), 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 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 Nadaje gniazdku adres Nadaje lub odbiera dane Serwer: Tworzy gniazdko Nadaje gniazdku adres Nadaje lub odbiera dane - socket - bind (konieczne przy odbiorze) - sendto, recfrom, write, read, recv, send - socket - bind (konieczne przy odbiorze) - 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 2. Nadaje gniazdku adres 3.Łączy się z serwerem 4. Nadaje lub odbiera dane socket bind (konieczne przy odbiorze) connect 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 - write, read, recv, send 1. Nadaje lub odbiera dane 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. 3. 4. Przy użyciu programu rpcgen generuje się namiastkę klienta (ang. client stub), namiastkę serwera (ang. server stub), plik nagłówkowy, plik konwersji. Implementacja usług serwerowych. 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