Wykład IV: Język programowania systemów współbieżnych czasu rzeczywistego ada instrukcja accept



Pobieranie 68.04 Kb.
Data28.04.2016
Rozmiar68.04 Kb.

WYKŁAD IV: Język programowania systemów współbieżnych czasu rzeczywistego ADA

  • Instrukcja accept,

  • Instrukcja select,


Ada jest nowoczesnym językiem programowania zaprojektowanym z myślą o systemach czasu rzeczywistego i programowania współbieżnego.


Służy także do programowania wbudowanych systemów komputerowych, tj. systemów, w których komputer jest połączony bezpośrednio z nadzorowaną lub sterowaną aparaturą.


Ogólne uwagi na temat ADY:

  1. Odpowiednikiem program Pascala jest procedure (główna) w ADZIE,




  1. Przy deklaracji zmiennych nie używa się słowa kluczowego var;




  1. Odpowiednikiem repeat....until jest loop...end loop;




  1. Procesy noszą nazwy zadań (task). Task zawiera specyfikację, która deklaruje wejścia (entry), czyli nazwy spotkań o ile występują. Treść (body) jest definiowana osobno i zawiera dane i procedury lokalne dla zadania,




  1. Parametr wejściowy (in) może być jedynie czytany (realizowane przez wołanie przez wartość), a parametrowi wyjściowemu (out) wolno jedynie nadawać wartość.




  1. Do zadeklarowania kilku identycznych zadań definiujemy typ zadań (type task), a następnie deklarujemy wiele wcieleń tego samego procesu np. w tablicy.


  1. Deklaracja zadania automatycznie inicjuje je, tak, że w programie głównym umieszczamy null. Task oznacza żądanie współbieżnego wykonania.

Modularyzacja


Oprócz tradycyjnych konstrukcji podprogramów (tzn. procedur i funkcji) w Adzie istnieją zadania i pakiety.

Pakiety umożliwiają projektantowi grupowanie powiązanych jednostek programowych w większe jednostki, dostarczając mechanizmu modularyzacji mocniejszego niż podprogramy.


Taki pakiet składa się z dwóch rozłącznych części: widocznej specyfikacji i treści.
Część widoczna jest dostępna użytkownikowi pakietu i może obejmować deklaracje typów, stałych, podprogramów, zadań, a nawet pakietów. Realizacja natomiast jest ukryta przed użytkownikiem.
Własność ta pozwala kompilować części widoczne pakietu niezależnie od ich treści.

Definiowanie zadań

Zadania można definiować zarówno wewnątrz procedury jak i pakietu.


Definicja każdego zadania składa się z dwóch części: części specyfikacyjnej i z treści (tzw. ciało zadania).
W części specyfikacyjnej wyszczególnia się wszystkie wejścia, poprzez które inne zadania mogą komunikować się z tym zadaniem, natomiast w części implementacyjnej (w ciele) przedstawia się sposób funkcjonowania zadania.

Specyfikacja prostego zadania, które posiada dwa wejścia We1 i We2 może wyglądać następująco:


-- Specyfikacja zadania Bufor


task Bufor is

entry Wstaw (I: in Integer);

entry Pobierz(I: out Integer);

end Bufor;

Za słowem task pojawia się nazwa zadania, za pomocą, której można się do niego odwoływać.


Umieszczenie wejść umożliwia komunikację z danym zadaniem z innej jednostki programowej (innego zadania, procedury itp.).
Zadania nie muszą posiadać wejść do komunikacji.
Jeżeli zadanie nie posiada wejść oznacza to, że może wywoływać wejścia innych zadań.
W przykładzie przedstawionym wyżej zostały zdefiniowane dwa wejścia o nazwach: Wstaw oraz Pobierz.
Zadanie pragnące spotkać się z naszym zadaniem Bufor w wejściu Wstaw, musi w swojej treści mieć instrukcję postaci np. Bufor.Wstaw(10).
Po wyspecyfikowaniu zadania należy przystąpić do jego implementacji. Najważniejszą instrukcją w ciele zadania jest instrukcja accept. Umożliwia ona określenie sposobu komunikacji z zadaniem. Jeżeli w części specyfikacyjnej pojawiła się nazwa wejścia, to w części implementacyjnej musi pojawić się co najmniej jedna instrukcja postaci:

accept

do end
lub
accept =>

do end
Przykładowo fragment ciała zadania może wyglądać następująco:
-- Implementacja zadania Bufor
task body Bufor is
loop

accept Wstaw (I: in Integer) do

-- Instrukcje stanowiace tresc accept



end Wstaw;
accept Pobierz (I: out Integer) do

-- Instrukcje stanowiace tresc accept



end Pobierz;

end loop;

end Bufor;

Spotkania (rendezvous)

Procesy w Adzie komunikują się za pomocą mechanizmu spotkań.


Oznacza to, że działanie dwóch zadań zbiega się na określony odcinek czasu, po którym kontynuują one wykonywanie swoich czynności równolegle.
Podczas spotkania w Adzie mogą być dodatkowo wykonywane pewne obliczenia, przy czym mogą one być wykonywane tylko w jednym z komunikujących się zadań.
Zadanie to jest nazywane zadaniem obsługującym (server).
Zadanie obsługujące udostępnia zadaniu obsługiwanemu (client) różne miejsca spotkań.
Są one widoczne na zewnątrz w postaci wejść (entry).
Klient musi znać nazwę serwera i wejścia, przez które chce się komunikować.
Natomiast zadanie serwera nie zna nazwy zadania obsługiwanego.

Instrukcja accept jest wykonywana wtedy, gdy dojdzie do niej licznik instrukcji, z tym zastrzeżeniem, że jej definicja wymaga synchronizacji z zadaniem obsługiwanym.


Najczęściej albo zadanie obsługiwane dojdzie do wywołania wejścia, zanim zadanie obsługujące osiągnie instrukcje accept dla tego wejścia, albo odwrotnie.
Którekolwiek z zadań będzie pierwsze, zostanie wstrzymane, aż drugie dojdzie do odpowiadającej instrukcji. W tym momencie zaczyna się spotkanie. Semantyka spotkania jest następująca:


  • Zadanie obsługiwane przekazuje swoje parametry wejściowe (in) do zadania obsługującego i zostaje zawieszone do momentu zakończenia spotkania.

  • Zadanie obsługujące wykonuje instrukcje z treści accept.

  • Parametry wyjściowe (out) są przekazywane do zadania obsługiwanego.

  • Spotkanie jest zakończone i żadne zadanie nie jest już wstrzymywane.


Spotkanie dwóch procesów w ADZIE:

partner, który przybywa wcześniej musi czekać,


Proces Q może się spotykać z procesami P_i, ale tylko z jednym w tym samym czasie. Jeśli proces Q nie jest gotowy na spotkanie P_i, to P_i musi czekać.
Jeśli proces P_j chce spotkać się z procesem Q, to musi zaczekać, aż zakończy się spotkanie Q i P_i, i proces Q będzie ponownie gotów do podjęcia spotkania.
Cechy spotkań:






  • Pojedyncza tranzakcja jest doprowadzana do końca w trakcie spotkania,



  • Wzajemne wykluczanie tranzakcji dokonywanych przez ten sam proces,




  • Dwustronna wymiana informacji jako metoda komunikacji pomiędzy procesami biorącymi udział w spotkaniu



Typy zadaniowe

Zadania, które zostały zdefiniowane, są automatycznie uruchamiane w trakcie działania programu.

Czasami jednak jest wymagane, aby pewne zadania zostały uruchomione dopiero w określonym momencie np. po spełnieniu założonych warunków.

Wówczas należy, zamiast definiować zadanie, zdefiniować typ zadaniowy i zmienną wskaźnikową do zadania, a następnie utworzyć egzemplarz zadania za pomocą instrukcji new.

Innym powodem może być konieczność utworzenia pewnej określonej ilości identycznych zadań np. poprzez zdefiniowanie tablicy zadań.

Przykład definicji typu zadaniowego dla prostego bufora:



task type Bufor is

entry Wstaw (I: in Integer);

entry Pobierz (I: out Integer);

end Bufor;

Jeżeli w programie powinno wystąpić pięć egzemplarzy takich buforów wystarczy zapisać:


Bufory : array(1 .. 5) of Bufor;
Zapis taki oznacza utworzenie pięciu egzemplarzy zadania typu Bufor w postaci tablicy.
Oczywiście istnieje możliwość utworzenia pięciu egzemplarzy w inny sposób:

Bufor1 : Bufor;

Bufor2 : Bufor;

Bufor3 : Bufor;

Bufor4 : Bufor;

Bufor5 : Bufor;

Jednak odwoływanie się do tak zdefiniowanych buforów nie jest już tak uniwersalne, jak dla pierwszego przypadku.
-- Wstawienie liczby 25

Bufory(3).Wstaw(25);

-- Przypadek(1)

Bufor3.Wstaw(25);

-- Przypadek (2)

procedure gospodauADY is

liczba procesów: constant := ?;



task type proces;

task Q is

entry gospoda(mięso: in żywność;

kanapki: out żywność);



end Q;
task body proces is

mors, wiktualy: żywność;



begin

loop

mors := upolujmorsa,

gospoda(mors, wiktualy);

jedz(wiktualy);



end loop;

end process;
task body Q is

pieczywo: zywność;



begin

loop

pieczywo := odwiedź piekarnię;



accept gospoda(mięso: in żywność; kanapki: out

żywność) do

gotuj(mięso);

kanapki := mięso + pieczywo;



end gospoda;

end loop;

end Q;
P: array(1..liczbaprocesów) of process;

begin


null;

end gospodauADY;

Instrukcja accept należy do programu Q.
Jeśli proces Q i jakiś proces P(i) wykonają parę instrukcji:

accept gospoda i gospoda, to znaczy, że spotkanie się odbyło.
W celu uruchomienia programu trzeba zdefiniować:

liczbaprocesów, typ żywność, podprogramy upoluj, odwiedźpiekarnię, ....


Treść instrukcji accept stanowi sekcję krytyczną.

Symulacja semafora za pomocą spotkań:
procedure wzajemnewykluczanie is
task semaphore is

entry wait;



entry signal;

end semaphore;
task body semaphore is

begin

loop

accept wait;

accept signal;

end loop;

end semaphore;
task P1;

task body P1 is

begin

loop

lokalne1;

wait;

kryt1;


signal;

end loop;

end P1;

task P2;

task body P2 is

begin

loop

lokalne2;

wait;

kryt2;


signal;

end loop;

end P2;
begin

null;


end wzajemnewykluczanie;
Procesy P1 i P2 nie odwołują się do wspólnej zmiennej ani do wspólnej kolejki.
Kolejki związane z instrukcją accept sa realizowane jako kolejki proste.
Kiedy proces ustawi się w kolejce to zostanie w końcu obsłużony, musi tylko poczekać na obsłużenie wszystkich procesów z kolejki przed nim.
Jeśli potrzeba wielu semaforów, to przez zdefiniowanie task type semaphore można tworzyć wiele realizacji tego typu przez proste parametryczne definicje zmiennych – tablica zadań typu semaphore.

Instrukcja select

Z punktu widzenia zadania obsługiwanego, wywołanie wejścia jest podobne do wywołania procedury, oczywiście poza zależnościami czasowymi.


Wykonanie procedury rozpoczyna się natychmiast, a wykonanie obsługi wejścia - dopiero po przygotowaniu się zadania obsługującego do przyjęcia wywołania.
Jeżeli zadanie wywoła wejście zwykłą instrukcją, to musi czekać (być może nieskończenie długo), aż zadanie wywoływane będzie przygotowane do przyjęcia tego wywołania.
W celu ograniczenia okresu oczekiwania używa się instrukcji select.
Przy zastosowaniu instrukcji select, zadanie posiadające kilka wejść może przyjąć wywołanie spotkania na dowolnym z nich.
Instrukcję select stosuje się zarówno w zadaniu obsługującym jak i w zadaniu obsługiwanym.

Zadania obsługiwane - instrukcje komunikacji

W ciele zadania może znajdować się pojedyncza instrukcja powodująca bezwarunkowe wywołanie wejścia zadania, np.:

Bufor.Wstaw(10);

Po dojściu programu do tego miejsca, zadanie "zamiera'' i oczekuje, aż dojdzie do spotkania. Jest to niepożądane dla systemów uwarunkowanych czasowo.


Dlatego też, zamiast pojedynczej instrukcji wywołania wejścia zadania, w zadaniach uwarunkowanych czasowo stosuje się instrukcję select.

Spotkania uwarunkowane czasowo
select

Bufor.Wstaw (10);



or

delay 10.0;

...

end select;
Jeżeli w podanym czasie nie nastąpi spotkanie, wykonywane są instrukcje znajdujące się za instrukcją delay.

Spotkania natychmiastowe
Jeżeli nie nastąpi natychmiastowe spotkanie, to następuje zaniechanie oczekiwania na spotkanie i zostają wykonywane inne instrukcje, znajdujące się za else:

select

Bufor.Wstaw (10);



else

-- Instrukcje wykonywane w przypadku

-- braku mozliwosci

-- natychmiastowego spotkania





end select;

Zadania obsługujące - instrukcje komunikacji


W ciele zadania obsługującego znajdują się instrukcje accept umożliwiające spotkania z zadaniem obsługiwanym. Jeżeli serwer posiada tylko jedno wejście, to spotkanie na nim nastąpi, jeżeli zadanie klienta je wywoła:

...


accept ...

...


Po dojściu programu do instrukcji accept działanie zadania zostaje wstrzymane i następuje oczekiwanie na spotkanie.

Najczęściej jednak zadanie obsługujące posiada kilka wejść, dlatego też stosuje się instrukcję select aby umożliwić spotkanie na dowolnym z wejść. Spotkanie takie jest nazywane spotkaniem selektywnym.



Spotkania selektywne

select

accept ...

or

accept ...

else



end select;
Jeżeli nie nastąpi spotkanie na żadnym z wejść, to następuje wykonanie występujących po instrukcji else.

Spotkania z dozorami

Spotkania na dowolnym z wejść mogą być możliwe dopiero po zajściu odpowiedniego warunku. Warunek ten jest nazywany dozorem i jest wykorzystywany w:



select

when => accept ...

or

when => accept ...

end select;
Odpowiednia gałąź instrukcji select jest wybierana w zależności od spełnienia warunków oraz .
Jeżeli w żadnej z gałęzi nie może dojść do spotkania pomimo spełnienia dozorów, to instrukcja select zawiesza się w oczekiwaniu na takie spotkanie.
Jeśli żaden z dozorów nie jest spełniony, to wykonanie tej instrukcji kończy się błędem.
Aby zapobiec takiemu konfliktowi, na końcu umieszcza się dodatkowo instrukcję else.
Jeżeli teraz wszystkie dozory są fałszywe lub nie może dojść do spotkania, wykonywany jest ciąg instrukcji umieszczonych za instrukcją else.
Niekiedy spotkanie jest uwarunkowane czasowo, tzn. spotkanie musi się odbyć w określonym czasie:
select

accept ...

or

delay 10.0;



end select;
Jeżeli nie nastąpi spotkanie w czasie wyspecyfikowanym instrukcją delay, to następuje przerwanie oczekiwania na spotkanie i zostają wykonywane instrukcje .

task buforograniczony is

entry włóż(v: in integer);

entry pobierz(v: out integer);

end task;

task body buforograniczony is

rozmiar: constant := ?

b: array (0.. rozmiar) of integer;

inind, outind: integer;

n: integer;


begin

n := 0; inind := 0; outind := 0;

loop

select


when n < rozmiar =>

accept włóż(v: in integer) do

b(inind) := v;

end włóż;

n := n+1;

inind := (inind +1)mod rozmiar;

or

when n > 0 =>



accept pobierz(v: out integer) do

v := b(outind);

end pobierz;

n := n-1;

outind := (outind +1)mod rozmiar;

end select;

end loop;

end buforograniczony;


Składnia instrukcji select



select

when warunek1 => accept wejscie1 do instrukcje1 end;

inne instrukcje1


or

when warunek2 => accept wejscie2 do instrukcje2 end;

inne instrukcje2

or

..........



else instrukcje

end select;



Klauzula else jest opcjonalna.
Jeśli dozór jest zawsze prawdziwy można opuścić konstrukcję when true =>.


©absta.pl 2016
wyślij wiadomość

    Strona główna