Occam Wyk ad czwarty ł Ten wyk ad dotyczy przyk adowych struktur
Transkrypt
Occam Wyk ad czwarty ł Ten wyk ad dotyczy przyk adowych struktur
Occam Wykład czwarty Ten wykład dotyczy przykładowych struktur procesów jakie możemy konstruować w occamie. Wszystkie przykłady dotyczące tego zagadnienia pochodzą ze skryptu M.Wysockiego i B.Kwolka „Obliczenia równoległe i transputery w automatyce”. 1. Struktura producent – konsument Ta struktura została już zaprezentowana w poprzednim wykładzie. Jeden proces z grupy procesów pełni rolę producenta, a więc „wytwarza” dane, które są następnie „konsumowane” przez jeden lub większą liczbę procesów konsumenckich. Taką strukturę można przedstawić przy pomocy odpowiedniego schematu: kan producent konsument Komunikacja z użytkownikiem (o ile jest konieczna) może się odbywać za pośrednictwem procesu producenta (wejście) i konsumenta (wyjście) lub tylko za pośrednictwem producenta. W tym ostatnim przypadku musi istnieć kanał zwrotny, którym producent otrzymywałby wyniki pracy konsumentów. Procesy komunikujące się z użytkownikiem muszą być umieszczone na transputerze zerowym. Zakładając, że kod procesu producenta został umieszczony w procedurze o nazwie producent, a kod procesu konsumenta w procedurze o nazwie konsument i że oba procesy komunikują się ze sobą przy pomocy kanału o protokole pozwalającym wysyłać tylko liczby typu INT, to kod programu opisujący taką strukturę można zapisać w occamie następująco: CHAN OF INT kan: PAR producent(kan) konsument(kan) 2. Struktura producent – konsument z procesem buforującym Ta struktura nie jest samodzielną strukturą, ale stanowi uzupełnienie struktury typu producent – konsument. Elementem dodanym do poprzedniej struktury jest proces buforujący. Jego zadaniem jest skrócenie czasu oczekiwania procesów na komunikację. Kod wykonywany przez taki proces może być zapisany następująco: PROC bufor (CHAN OF INT we,wy) WHILE TRUE INT buf: SEQ we?buf wy!buf : Poniżej przedstawiono kod programu, który tworzyłby taką strukturę procesów wraz ze schematem tej struktury: 1 Occam CHAN OF INT od, do: PAR producent od bufort do konsument producent(od) bufor(od,do) konsument(do) To rozwiązanie pozwala producentowi „wytworzyć” i wysłać daną bez konieczności oczekiwania na gotowość do odbioru procesu konsumenta. Można wprowadzić modyfikację tego układu dodając do procesu bufora osobny kanał, którym proces konsumencki mógłby sygnalizować buforowi gotowość do odebrania danej: PROC bufor.daj (CHAN OF INT we, daj, wy) INT buf: To rozwiązanie również nie jest pozbawione wad. Możliwe jest, że proces konsumencki będzie odbierał kilkukrotnie tę samą daną lub nie odbierze którejś z danych, które są wysyłane przez producenta. Lepszym rozwiązanie byłoby zastosowanie tablicy 1 buforów . Wartość indeksu wskazującego kolejne miejsce w tej tablicy (bufor), które może być wypełnione lub kolejne miejsce, które może być opróżnione musiałaby być przekazywana przez kanał. Ten schemat wymagałby uważnego rozważenia kwestii 2 modyfikacji wartości indeksu . Przedstawione tu procesy buforujące działają ciągle za sprawą niekończącej się pętli WHILE. W rzeczywistości należy określić jakiś warunek jej zakończenia. Może to być np.: otrzymanie przez kanał o protokole z wariantami odpowiedniej etykiety. SEQ we?buf INT x: WHILE TRUE ALT we?buf SKIP daj?x wy!buf : Opisaną wyżej strukturę można rozszerzyć na większą niż trzy grupę procesów. 3. Struktura multiplekser – demultiplekser Multiplekser jest procesem iteracyjnym, który odbiera informacje wieloma kanałami i wysyła je do jednego kanału. Działanie demultipleksera jest odwrotne – odbiera informacje jednym kanałem i wysyła je jednym z kilku kanałów wyjściowych. Procesami współpracującymi z multiplekserem są producenci, natomiast procesami współpracującymi z demultiplekserem są konsumenci. Oto schemat takiej struktury: prod 1 prod 2 od[0] do[0] od[1] kan mux kan demux kon 1 do[1] kon 2 . . . . . od[n-1] do[n-1] . prod n 1 2 kon n W tym wypadku byłaby to oczywiście tablica elementów typu INT. Ten temat jest poruszony między innymi w książce A.Silberschatza, J.L.Petersona, P.B.Galvina „Podstawy systemów operacyjnych”, WNT, Warszawa 1993 2 Occam Multiplekser odbiera dane z jednego z kanałów wejściowych i wysyła je do procesu demultipleksera, wraz z informacją, do którego konsumenta należy tę daną wysłać. Demultiplekser odbiera tę informację i przekazuje ją do wskazanego procesu. Taka konstrukcja wymaga zdefiniowania odpowiednich protokołów kanałów. Protokół kanałów od i do będzie zdefiniowany następująco: PROTOCOL kom CASE dana;INT koniec : Protokół kanału kan będzie natomiast zdefiniowany następująco: PROTOCOL kom.ident CASE dana;INT;INT koniec : Załóżmy, że procesy konsumenta i producenta są opisane procedurami, które mają następujące nagłówki: PROC prod(CHAN OF kom wy) PROC kons(CHAN OF kom we) a procesy multipleksera i demultipleksera opisane procedurami o następujących nagłówkach: PROC mux([n] CHAN OF kom we, CHAN OF kom.ident wy) PROC demux(CHAN OF kom.ident we, [n] CHAN OF kom wy) Wówczas program tworzący taką strukturę mógłby mieć następującą postać: CHAN OF kom.ident kan: PAR [n] CHAN OF kom od: PAR i=0 FOR n prod(od[i]) mux(od,kan) [n] CHAN OF kom do: PAR demux(kan,do) PAR i=0 FOR n kons(do[i]) 3 Occam Procesy multipleksera i demultipleksera mogłyby wykonywać następujący kod: PROC mux([n] CHAN OF kom we, CHAN OF kom.ident wy) PROC demux(CHAN OF kom.ident we, [n]CHAN OF kom wy) INT liczba.nzak: BOOL praca: [n] BOOL nzak: SEQ BOOL praca: praca:=TRUE SEQ WHILE praca liczba.nzak:=n INT numer, wart: SEQ i=0 FOR n we?CASE nzak[i]:=TRUE koniec praca:=0<liczba.nzak praca:=FALSE WHILE praca dana;numer;wart ALT i=0 FOR n SEQ INT wart: IF nzak[i]&we[i]?CASE (0<=numer) AND (numer<n) koniec wy[numer]!dana;wart SEQ TRUE liczba.nzak:=liczba.nzak-1 SKIP nzak[i]:=FALSE PAR i=0 FOR n praca:=0<liczba.nzak dana;wart wy[i]!koniec : wy!dana;i;wart wy!koniec : Zmienna „liczba.nzak” oznacza liczbę nie zakończonych jeszcze procesów producenta, natomiast tablica „nzak” informuje o stanie każdego z nich, a jej elementy pełnią funkcję wartowników dla poszczególnych kanałów. 4. Struktura typu farmer – worker Ta struktura składa się z procesu nadrzędnego określonego mianem „farmera”, który rozdziela zadania między procesy podporządkowane mu, nazywane „worker'ami”. Procesy te przekazują wyniki swego działania do procesu nadrzędnego, który może im przekazać inne zadania. Taka struktura może być przedstawiona za pomocą następującego schematu: 4 Occam farmer dla[0] od[0] dla[1] worker 1 od[1] dla[n-1] worker 2 . . . od[n-1] worker n Przekazanie zadań odbywa się zgodnie z protokołem o nazwie Zadania: PROTOCOL Zadania CASE nowe.zad;INT;REAL64 koniec : Wyniki są przekazywane zgodnie z protokołem o nazwie Wyniki: PROTOCOL Wyniki IS INT; REAL64: Program tworzący przedstawioną na schemacie strukturę mógłby mieć następującą postać: [n] CHAN OF Zadania dla: [n] CHAN OF Wyniki od: [liczba.zad] REAL64 zad: [liczba.wyn] REAL64 wyn: ... PAR farmer(zad,dla,od,wyn) PAR j=0 FOR n worker(dla[j],od[j]) Procesy farmera i workera mogłyby być opisane następującymi procedurami: 5 Occam PROC worker(CHAN OF Zadania od.farm, CHAN OF Wyniki do.farm) BOOL tak: SEQ tak:=TRUE WHILE tak od.farm?CASE INT i: REAL64 zad, wyn: nowe.zad;i;zad SEQ wyn:=f(zad) do.farm!i;wyn koniec tak:=FALSE : PROC farmer(VAL []REAL64 zad, [n] CHAN OF Zadania do.work, [n] CHAN OF Wyniki od.work, [] REAL64 wyn) SEQ PAR j=0 FOR n do.work[j]!nowe.zad;j;zad[j] SEQ i=n FOR (SIZE zad) – n ALT j=0 FOR n INT k: od.work[j]?k;wyn[k] do.work[j]!nowe.zad;i;zad[i] SEQ i=0 FOR n ALT j=0 FOR n INT k: od.work[j]?k;wyn[k] do.work[j]!koniec : 6 Occam 5. Struktury procesów w bibliotece „course.lib” Biblioteka „course.lib” dostarczana wraz z kompilatorem „kroc” zawiera przykładowe zaimplementowane jako sieci podprocesów, czyli są przykładami struktur procesów: procesy, które zostały proces numbers zdefiniowany jako procedura PROC numbers (CHAN INT out!), generuje kolejne liczby naturalne, zaczynając od zera i wysyła je na kanał wyjściowy out; warto zwrócić uwagę na wykrzyknik występujący po nazwie kanału; jest to jedna z modyfikacji języka occam, którą wprowadzono na Uniwersytecie Kent; taki zapis oznacza, że parametr jest kanałem nadawczym i pozwala wykryć na etapie kompilacji sytuację kiedy za ten parametr byłby podstawiony nieprawidłowy rodzaj kanału; inną modyfikacją jest brak konieczności umieszczania słowa kluczowego OF, po słowie CHAN, proces integrate jest procesem zdefiniowanym w postaci procedury PROC integrate(CHAN INT in?, out!) dodaje liczby całkowite otrzymane przez kanał in i zwraca sumę przez kanał out; jest to dodawanie akumulujące; pytajnik w liście parametrów pełni funkcje podobną do funkcji wykrzyknika – oznacza kanał do wejściowy, czyli do odbioru, proces pairs zdefiniowany jest jako procedura PROC pairs(CHAN INT in?, out!) sumuje kolejne pary liczb podawanych mu przez kanał in i wysyła wynik na kanał out, proces squares zdefiniowany jest jako procedura PROC squares(CHAN INT out!) i generuje kwadraty kolejnych liczb naturalnych, zaczynając od 1, proces fibonacci zdefiniowany jest jako procedura PROC fibonacci(CHAN INT out!) i generuje kolejne liczby Fibonacciego, poczynając od zera. Oprócz tych przykładowych programów budujących określone struktury biblioteka „course.lib” zawiera również elementarne programy służące do budowania takich struktur: proces id jest zdefiniowany jako procedura PROC id (CHAN INT in?, out!) jest prostym procesem buforującym, takim jak zdefiniowany wcześniej proces bufor, proces succ zdefiniowany jako procedura PROC succ(CHAN INT in?, out!) buforuje dane nadchodzące kanałem in, zwiększa ich wartość o 1 i wysyła kanałem out, proces plus zdefiniowany jako procedura PROC plus(CHAN INT in.1?, in.2?, out!) dodaje wartości otrzymane przez kanały in.1 i in.2 i wysyła sumę przez kanał out, proces delta zdefiniowany jako procedura PROC delta(CHAN INT in?, out.1!, out.2!) wysyła współbieżnie na kanały out.1 i out.2 wartość otrzymaną przez kanał in, proces prefix zdefiniowany jako procedura PROC prefix(VAL INT n, CHAN INT in?, out!) najpierw wysyła wartość przekazaną przez parametr n na kanał out, a potem staje się procesem id, proces tail zdefiniowany jako procedura PROC tail (CHAN INT in?, out!) ignoruje pierwszą wartość przesłaną mu przez kanał in, a następnie staje się procesem id. Należy nadmienić, że wszystkie te procesy są procesami cyklicznymi, tzn. działają w pętli i nigdy się nie kończą. Oprócz wymienionych wyżej procesów biblioteka „course.lib” zawiera, jak wspomniano na poprzednich wykładach szereg procedur związanych z komunikacją ze światem zewnętrznym. Do tej pory opisane zostały dwie: out.string i out.int. Oto zestaw kolejnych procedur z tej kategorii: out.repeat ma prototyp PROC out.repeat (VAL BYTE ch, VAL INT n, CHAN BYTE out!) i wysyła znak ch na kanał out n razy, 7 Occam out.ch ma prototyp PROC out.ch (VAL BYTE ch, VAL INT field, CHAN BYTE out!) i wysyła znak ch na kanał out, z formatowaniem określonym przez field (ilością miejsca na ekranie), out.byte ma prototyp PROC out.byte (VAL BYTE b, VAL INT field, CHAN BYTE out!) i wysyła na kanał out liczbę typu BYTE, out.hex działa jak out.int, ale wysyła na kanał out liczbę typu INT zapisaną w kodzie szesnastkowym; zawsze co najmniej 9 znaków jest wysłanych, włączając w to znak #, out.bool ma prototyp PROC out.bool (VAL BOOL b, VAL INT field, CHAN BYTE out!) i wysyła na kanał out wartość typu BOOL, out.yes.no, ma prototyp PROC out.yes.no (VAL BOOL b, VAL INT field, CHAN BYTE out!) i wysyła na kanał out ciąg znaków yes lub no w zależności od wartości parametru b, Prostą obsługę wejścia zapewniają następujące procedury: in.skip o prototypie PROC in.skip (BYTE ch, CHAN BYTE in?) wczytuje z kanału in znaki dopóki pojawi się znak nie będący odstępem (ang. non-space), który jest zapisywany w parametrze ch, in.digit o prototypie PROC in.digit (BYTE d, CHAN BYTE in?, out!) wczytuje liczbę typu BYTE i zapisuje ją do parametru d; jeśli wartość podana przez użytkownika nie jest liczbą o żądanym typie, to na kanał out jest wysyłany znak BEL (dzwonek), in.string o prototypie PROC in.string([]BYTE s, INT length, VAL INT max, CHAN BYTE in?, out!) wczytuje łańcuch składający się maksymalnie z max znaków i zapisuje go w parametrze s; znak kończący, czyli znak nowej linii lub powrotu karetki nie jest zapamiętywany; tylko tyle znaków, ile określa parametr length jest zapamiętywanych w parametrze s, reszta jest ustawiana na NUL (znak pusty); wczytywanie odbywa się z echem, in.bool o prototypie PROC in.bool(BOOL b, CHAN BYTE in?, out!) oczekuje na znak „y” lub „n” (lub „Y”/„N”) na wejściu; jeśli otrzyma inny znak, to na kanał out wysyła znak BEL, a jeśli otrzyma oczekiwane znaki to na out wysyła napis „yes” lub „no” i ustawia odpowiednio wartość parametru b, in.byte o prototypie PROC in.byte(BYTE b, VAL INT max, CHAN BYTE in?, out!) wczytuje liczbę typu BYTE, która może zawierać maksymalnie max cyfr, liczby większe niż 255 są ignorowane; wyczytywanie odbywa się z echem, in.int o prototypie PROC in.int(INT n, VAL INT max, CHAN BYTE in?, out!) działa podobnie jak in.byte, ale wczytuje liczby typu INT, które rozpoczynają się znakiem „+” lub „-”; sprawdza, czy nie nastąpił nadmiar. Interaktywną komunikację z użytkownikiem zapewniają następujące procedury: ask.string o prototypie PROC ask.string(VAL []BYTE prompt, []BYTE s, INT length, VAL INT max, CHAN BYTE in?, out!) działa jak in.string, ale zanim cokolwiek wczyta, to wysyła na kanał out ciąg znaków przekazanych przez parametr prompt, ask.bool o prototypie PROC ask.bool(VAL []BYTE prompt, BOOL b, CHAN BYTE in?, out!) działa jak in.bool, ale zanim cokolwiek wczyta wysyła na kanał out ciąg znaków przekazanych przez parametr prompt, ask.byte o prototypie PROC ask.byte(VAL []BYTE prompt, BYTE b, CHAN BYTE in?, out!) działa jak in.byte, ale zanim cokolwiek wczyta wysyła na kanał out ciąg znaków przekazanych przez parametr prompt, ask.int o prototypie PROC ask.int(VAL []BYTE prompt, INT n, VAL INT max, CHAN BYTE in?, out!) działa tak jak ask.int, ale nim cokolwiek wczyta wysyła na kanał out ciąg znaków przekazany przez parametr prompt. 8 Occam Sterowanie kursorem za pomocą protokołu terminala VT220 lub zgodnego zapewniają następujące procedury: cursor.x.y o prototypie PROC cursor.x.y (VAL BYTE x, y, CHAN BYTE out!) wysyła na kanał out ciąg sterujący, który umieszcza kursor na ekranie w miejscu o współrzędnych x i y, cursor.up o prototypie PROC cursor.up(VAL BYTE n, CHAN BYTE out!) wysyła na kanał out ciąg sterujący przesuwający kursor o n wierszy do góry, cursor.down o prototypie PROC cursor.down(VAL BYTE n, CHAN BYTE out!) jak wyżej, ale w dół, cursor.left o prototypie PROC cursor.left(VAL BYTE n, CHAN BYTE out!) wysyła ciąg sterujący przesuwający kursor o n kolumn w lewo, cursor.right o prototypie PROC cursor.right(VAL BYTE n, CHAN BYTE out!) jak wyżej, ale w prawo, cursor.visible o prototypie PROC cursor.visible(CHAN BYTE out!) - jeśli kursor był niewidoczny to staje się widoczny, cursor.invisible o prototypie PROC cursor.invisible(CHAN BYTE out!) - ukrywa kursor. Dodatkowo zdefiniowano następujące procedury: erase.eol o prototypie PROC erase.eol (CHAN BYTE out!) wysyła na kanał out ciąg sterujący, który usuwa wszystkie znaki od bieżącej pozycji kursora do końca wiersza, erase.bol o prototypie PROC erase.bol (CHAN BYTE out!) tak, jak wyżej, ale do początku wiersza, erase.line o prototypie PROC erase.line (CHAN BYTE out!) jak wyżej, ale kasowany jest cały wiersz, erase.eos o prototypie PROC erase.eos (CHAN BYTE out!) jak wyżej, ale kasowane są wszystkie znaki do końca ekranu, erase.bos o prototypie PROC erase.bos (CHAN BYTE out!) tak jak wyżej, ale znaki kasowane są do początku ekranu, erase.screen o prototypie PROC erase.screen (CHAN BYTE out!) jak wyżej, ale kasowane są wszystkie znaki na ekranie. Poza tymi procedurami biblioteka „course.lib” zawiera również cztery następujące: black.hole o prototypie PROC black.hole (CHAN BYTE in?) odpowiednik uniksowego /dev/null, flush o prototypie PROC flush (CHAN BYTE out!) opróżnia kanał out, który jest podłączony do ekranu, wysyłając na ekran wszystkie znaki z tego kanału, pos.int o prototypie PROC pos.int (VAL INT col, row, n, field, CHAN BYTE out!) wysyła na kanał out ciąg sterujący, który ustawia kursor w miejscu ekranu o współrzędnych col i row i zapisuje tam liczbę typu INT, z określonym formatowaniem, pos.yes.no o prototypie PROC pos.yes.no(VAL INT col, row, VAL BOOL b, VAL INT field, CHAN BYTE out!) jak wyżej, ale wypisuje ciąg „yes” lub „no” w zależności od wartości parametru b i nie stosuje formatowania. 9