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