Rozdział ten zawiera opis algorytmów i struktury
programu MONITOR. Oprócz tego została przedstawiona krótka
charakterystyka niektórych mechanizmów komunikacji procesów w
systemie UNIX.
Sygnał [9] jest zdarzeniem asynchronicznym zewnętrznym
w stosunku do procesu, który go otrzymuje. W systemie SunOS istnieje
31 typów sygnałów, które są zdefiniowane w pliku signal.h. Przykładowym sygnałem jest SIGKILL (bezwarunkowe zabicie
procesu), czy też SIGINT (przerwanie z klawiatury - na terminalu
ANSI jest to naciśnięcie klawisza DEL), lub SIGUSR1
i SIGUSR2 (sygnały definiowane przez użytkownika).
W procesie, który otrzyma, lub raczej wykryje sygnał, sterowanie jest
przekazywane do procedury obsługi. Jest to albo procedura standardowa
(oznaczona symbolicznie przez SIG_DFL) albo procedura użytkownika.
Proces może także zażądać zignorowania sygnału (symbol
SIG_IGN). Do wykonania tych operacji służy funkcja:
signal() - zdefiniuj obsługę przerwania
Składnia: void ( *signal(int signum , void(func()) )(),
gdzie signum - numer sygnału, func - funkcja obsługi
sygnału. Funkcja zwraca adres poprzedniej procedury obsługi sygnału.
Instrukcja signal() jest ważna tylko do najbliższego przerwania.
Wysłanie sygnału do procesu można natomiast zrealizować
instrukcją:
kill() - wyślij sygnał do procesu
Składnia: int kill( int pid , signum ),
gdzie pid - identyfikator procesu, signum - numer sygnału.
Przerwania mogą być wykorzystywane do odtwarzania wcześniejszego
stanu procesu (cofania historii). Służą do tego następujące
instrukcje:
setjmp() - zapamiętaj stan procesu w buforze
Składnia: int setjmp( jmp_buf env ),
gdzie env - bufor przechowujący stan procesu. Funkcja zwraca, jako
wartość, drugi parametr odpowiedniej funkcji longjmp().
longjmp() - odtwórz stan procesu zapamiętany w
buforze
Składnia: void longjmp( jmp_buf env , int val ),
gdzie env - bufor przechowujący stan procesu, val -
wartość, którą ma zwrócić setjmp().
Do grupy funkcji, związanych z obsługą sygnałów należą
także alarm() - ustawienie budzika, powodujące wysłanie
sygnału SIGALRM po upływie określonego czasu oraz pause()
- zawieszenie procesu aż do otrzymania sygnału.
Potok (pipeline) jest strukturą łączącą dwa procesy
poprzez łącze (pipe), będące kolejką typu FIFO, do
której można zapisywać dane i z której można dane
pobierać. Łącze to można przedstawić jako plik o dwóch
deskryptorach, z których jeden opisuje ``stronę'' służącą
do pisania, a drugi do czytania. Podobnie jak w przypadku zwykłych plików
istnieją tu dwa mechanizmy dostępu.
Mechanizm pierwszy oparty jest o poniższą funkcję:
int pipe( int pdes[2] ) pdes - tablica deskryptorów
łącza (0 - czytanie, 1 - pisanie).
Funkcja tworzy łącze i zwraca w dwuelementowej tablicy typu int
deskryptory do czytania i do pisania. Z deskryptorów można
następnie korzystać tak, jak w przypadku zwykłych plików, czyli
poprzez funkcje read(), write() i close().
Mechanizm drugi opiera się na następujących funkcjach:
FILE *popen( char *cmd , char *mode ) - utwórz łącze
, utwórz proces potomny i przyporządkuj jego wy(we) do łącza.
int pclose( FILE *p ) - usuń łącze, proces potomny.
Mechanizm ten jest przydatny wówczas, gdy dane mają sobie
przesyłać różne programy (jeden z nich jest wówczas
żródłem danych lub programem do obróbki danych).
Mianem tym określa się mechanizm podobny do opisanego powyżej,
lecz służący do porozumiewania się procesów
niespokrewnionych. Łącze musi zostać jawnie utworzone jako plik
specjalny poprzez funkcję mknod(). Dalsze mechanizmy porozumiewania
się są analogiczne jak w przypadku plików. Usunięcie
łącza następuje poprzez funkcję unlink().
Mechanizmy IPC (Inter-Proces Communication) obejmują semafory (
semaphores), kolejki komunikatów (messages) oraz wspólną
pamięć (shared memory). Semafory są zrealizowane jako
wielowartościowe.
Wspólna pamięć jest bardzo wygodnym mechanizmem do przekazywania
danych w środowisku niesieciowym. Każdy proces, dołączony do
obszaru wspólnej pamięci widzi ją w swojej przestrzeni adresowej
jako tablicę typu char[] i korzysta z danych, jak ze zwykłych
zmiennych.
Program MONITOR wykorzystuje mechanizm kolejek komunikatów,
w celu przesłania danych do programu przeprowadzającego symulację.
Program wyświetlający wyniki symulacji wysyła informację
sterującą do programu symulacji. Ten ostatni na tej podstawie, zmienia
wartość parametru (w przykładzie - temperatury)
układu symulowanego (rysunek 3.1).
Aby rozpocząć wymianę danych należy najpierw utworzyć
kolejkę komunikatów. Dokonujemy tego funkcją init_queue()
, która tworzy nową kolejkę, ale tylko wtedy, gdy taka kolejka
nie istniała. Jeżeli kolejka istniała już wcześniej, to
nowa nie jest tworzona. Dane są wtedy wymieniane przez ``starą''
kolejkę.
Jeżeli nie chcemy korzystać ze ``starej'' kolejki, przed uruchmieniem
programu należy wydać polecenie rmq, usuwające kolejkę.
Program, który jest dostarczycielem danych, musi wywołać funkcję
init_queue() z argumentem Q_SERVER. (tu program MONITOR).
Program odbierający dane (program symulacyjny) wywołuje wymienioną
funkcję z argumentem Q_CLIENT.
Po uworzeniu kolejki można już wymieniać dane pomiędzy oboma
procesami. Wymiana danych odbywa się w trybie asynchronicznym. Jeżeli
program wyświetlający wyniki chce przesłać dane do programu
symulacyjnego, wykonuje funkcję snd_data(), która umieszcza
dane w kolejce i powiadamia proces odbierający dane o dostarczeniu danych,
wysyłajç do niego sygnał SIGUSR1.
Jeżeli program dokonujący symulacji odbierze sygnał (SIGUSR1),
że przybyły nowe dane, wtedy wywołuje funkcję rcv_data(),
która odczytauje dane z kolejki i wpisuje je do zmiennej, której adres
znajduje się w zmiennej New_data.
Przed zakończeniem pracy programu symulacyjnego,
wywoływana jest funkcja leave_queue(), która informuje program
prezentujący wyniki symulacji, że nie będziemy już
odbierać danych z kolejki. W takim przypadku nie będzie więc
możliwe nadawanie danych.
Więcej informacji na temat powyższych funkcji można znależć
w podrozdziale 3.3.4.
Do wymiany danych i przygotowania komunikacji służą
następujące cztery funkcje [14] :
int msgsnd( int msgid , struct msgbuf *msgp , int msgsz , int msgflg )
Funkcja ta wysyła wiadomość do kolejki o numerze
msgid, gdzie msgp jest wskażnikiem na strukturę zawierającą
wiadomość, struktura ta ma następujące pola:
long mtype - typ wiadomości.
char mtext[1] - tekst wiadomości.
mtype jest liczbą dodatnią, która może być używana
przez proces odbierający wiadomości, w celu ich selekcji.
mtext jest dowolnym tekstem o długości msgsz bajtów.
msgflg specyfikuje akcje, wykonywane w sytuacjach wyjątkowych (
np. przekroczenie limitu ilości wiadomości).
Jeżeli msgflg && IPC_NOWAIT ma wartość PRAWDA,
wiadomość, nie będzie wysłana, a proces wywołujący
tę funkcję, wykonuje następną instukcję po msgsnd.
Jeżeli msgflg && IPC_NOWAIT ma wartość FAŁSZ,
proces wywołujący zawisa, aż wiadomość będzie
odebrana, lub np. kolejka zostanie usunięta z systemu. W takim
przypadku, funkcja zwraca wartość -1 i ustawia zmienną errno
na wartość EIDRM.
Funkcja ta zwraca wartość 0, jeżeli wszystko przebiegło
pomyślnie i -1 (ustawia dodatkowo zmienną errno na kod błędu
) w przeciwnym przypadku.
int msgrcv( int msgid , struct msgbuf *msgp , int msgsz , long msgtyp , int msgflg )
funkcja ta czyta wiadomość z kolejki o numerze msgid i zapisuje
ją do struktury wskazywanej przez msgp (opis struktury j. w. ).
Jeżeli długość wiadomości jest większa niż msgsz
bajtów i msgflg && MSG_NOERROR ma wartość PRAWDA,
wtedy wiadomość jest obcinana do wartości msgsz.
Część obcinana jest usuwana, ale żadna informacja o obcięciu
nie jest zwracana do procesu wykonującego tą funkcję.
Zmienna msgtyp specyfikuje typ wiadomości do odebrania, według
następującego schematu:
Jeśli msgtyp równa się zero, odbierana jest pierwsza
wiadomość z kolejki.
Jeśli msgtyp jest większy od zera, odczytywana jest
pierwsza wiadomość typu msgtyp.
Jeśli msgtyp jest mniejszy od zera, odczytywana jest
pierwsza wiadomość ``najniższego'' typu, który jest
mniejszy lub równy niż wartość bezwzględna z msgtyp.
msgflg określa akcje wykonywane, gdy w kolejce nie ma wiadomości
o żądanym przez nas typie.
Jeżeli msgflg && IPC_NOWAIT
ma wartość PRAWDA, wtedy funkcja zwraca wartość -1 i
podstawia pod zmienną errno wartość ENOMSG.
Jeżeli msgflg && IPC_NOWAIT
ma wartość FAŁSZ, wtedy proces wywołujący funkcję
zawiesza się, aż:
wiadomość żądanego typu, zostanie zapisana do kolejki,
kolejka zostanie usunięta z systemu; wtedy funkcja zwróci
wartość -1 i ustawi zmienną errno na wartość
EIDRM,
proces otrzyma sygnał i go przechwyci; w takim
przypadku wiadomość nie jest odbierana, a proces wykonuje akcję
zgodnie ze znaczeniem otrzymanego sygnału.
Funkcja ta zwraca liczbę bajtów wpisanych do mtext, gdy wszystko
przebiegło pomyślnie i wartość -1 w przeciwnym przypadku i
dodatkowo wpisuje do zmiennej errno kod błędu.
Do utworzenia kolejki są służą poniższe funkcje:
int msgget( key_t key , int msgflg )
Funkcja ta tworzy nową kolejkę i
zwraca identyfikator kolejki związanej z kluczem key.
Kolejka i związane z nią struktury danych są tworzona jeżeli:
wartość klucza key wynosi IPC_PRIVATE,
dany klucz nie jest związany z żadną kolejką i
msgflg && IPC_CREAT ma wartość PRAWDA.
W przeciwnym razie funkcja ta nie tworzy żadnej kolejki, a tylko zwraca
identyfikator kolejki związanej z kluczem key.
Identyfikator kolejki jest unikalną dodatnią liczbą całkowitą.
Jest on zwracany jeżeli wszystko przebiegło pomyślnie, w przeciwnym
przypadku funkcja zwraca wartość -1 i wpisuje do zmiennej errno
kod błędu.
key_t ftok( char *path , char id )
Funkcja ta tworzy klucz, używany przez funkcję msgget.
Zmienna path musi zawierać nazwę ścieżki do pliku,
który jest w pełni dostępny dla procesu.
Zmienna id - znak, który charakteryzującym projekt (unikalnie).
Funkcja ta zwraca wartość key_t, jeśli wszytko przebiegło
pomyślnie lub -1, jeśli ścieżka nie istnieje lub nie jest
dostępna dla procesu.
3.3.4 Opis funkcji komunikacyjnych programu MONITOR
W tym rozdziale można znależć informacje o funkcjach zawartych w module
com.c, które są odpowiedzialne za komunikację pomiędzy programem wyświetlającym wyniki symulacji molekularnej, a programem dokonującym symulacji.
Moduł com.c zawiera definicje następujących funkcji:
void init_queue( int mode )
Funkcja ta sprawdza czy kolejka istnieje. Jeżeli tak, pobiera jej numer i
wpisuje go do zmiennej MsgID, w przeciwnym przypadku tworzy nową
kolejkę i zapisuje jej adres do wymienionej zmiennej. Zmienna ta jest
następnie wykorzystywana przez pozostałe funkcje komunikacyjne.
Argument mode może przyjmować wartości: Q_SERVER i
Q_CLIENT. Argument ten określa, który z procesów jest
odpowiedzialny za dostarczanie danych, a który za odbieranie.
Aby proces dostarczający dane, mógł powiadomić proces
odbierający je o nadejściu danych, musi wysłać mu odpowiedni sygnaał (tu SIGUSR1). Aby wysłać jakiemuś procesowi jakiś sygnał, należy
znać jego numer (PID). Jeżeli funkcja init_client()
zostanie wywołana z argumentem Q_CLIENT, wtedy
proces ten wyśle swojemu partnerowi (wywołany z argumentem
Q_SERVER) swój PID.
Aby stwierdzić czy kolejka istnieje należy wywołać
funkcję ftok() i msgget() w następujący
sposób:
int leave_queue( void )
Funkcja ta jest wykonywana przez program symulacyjny, bezpośrednio przed
zakończeniem działania. Jej zadaniem jest poinformowanie procesu
wysyłającego dane, że kończymy działanie i nie będziemy
odbierać nowych danych.
Funkcja ta wysyła procesowi dostarczającemu dane swój PID
, wraz z informacją, że kończy odbieranie danych.
void snd_data( char *data )
Funkcja ta jest wywoływana przez program MONITOR w celu wysłania
danych do kolejki komunikatów. Po wpisaniu danych do kolejki, zostaje
wysłany sygnał do procesu odbierającego dane o przybyciu nowych
informacji.
void rcv_data( void )
Funkcja ta jest wywoływana przez program symulacyjny (po odebraniu
sygnału SIGUSR1), w celu pobrania danych z kolejki komunikatów.
Po odebraniu informacji, proces zapisuje do kolejki komunikatów swój
PID.
Program został napisany w języku C i skompilowany przy pomocy
standardowego kompilalatora cc systemu SunOS. Obecna wersja
została uruchomiona i przetestowana na stacji roboczej Sun SPARC SLC
firmy Sun Microsystems. Procedury graficzne zostały zaczerpnięte
z biblioteki Xview, pozostałe procedury korzystają ze standardowych
bibliotek ANSI C.
Cały program główny znajduje się w pliku monitor.c.
Funkcje komunikacyjne znajdują się w pliku com.c. Plik init.h zawiera deklaracje zmiennych i symboli dla procedur komunikacyjnych.
W programie przyjęto następującą konwencję dotyczącą
nazw zmiennych i symboli:
Zmienne zewnętrzne zaczynają się od dużej litery np.
Bufor.
Symbole (definiowane przez #define) są pisane wielkimi
literami np. MARGIN.
pozostałe zmienne i nazwy funkcji są pisane małymi literami.
Na początku programu znajdują się (w kolejności ich
występowania): definicje symboli, deklaracje zapowiadające funkcji,
definicje zmiennych zewnętrznych oraz deklaracje właściwe funkcji.
Funkcja main(), stanowi główny trzon programu. Z niej są
wywoływane pozostałe funkcje. Na początku tej funkcji zostaje wykonana procedura xv_init() w celu zainicjowania programu w środowisku
WINDOWS. Następnie za pomocą procedury xv_create() są
tworzone:
Ramka główna programu.
Ramki i panele pomocnicze.
Obszar graficzny ramki głównej.
Menu sterujące programem.
Nowy kształt kursora.
Ikona programu.
Z kolei przydzielana jest pamięć dla bufora danych i tworzona
kolejka komunikatów. W celu uruchomienia całego programu
wykonujemy procedurę xv_main_loop().
Jak wspomniałem wcześniej z poziomu funkcji main() są
wywoływane pozostałe funkcje. Rozdział ten zawiera ich opis.
void draw_chart( Canvas canvas , Pixwin *canvas_pw )
Zadaniem tej procedury jest rysowanie wykresu funkcji, na podstawie danych
zawartych w buforze. Oprócz tego jest ona odpowiedzialna
za wypisanie wartości na poszczególnych osiach i podpisanie osi.
Procedura ta jest zarejestrowana w obszarze graficznym (CANVAS), jako
standardowa procedura do obsługi zdarzeń, w wyniku których
wymagane jest przerysowanie wykresu, np. zmiana wymiarów okienka.
void read_datas( void )
Funkcja ta czyta dane (wierszami) z pliku i umieszcza je w buforze
(sortując je rosnąco, jeżeli jest takie polecenie.
double get_n_token( char *tab , int pos , int *is_ok )
Z linijki tekstu tab, funkcja wybiera token o numerze pos. Poszczególne
tokeny są rozdzielone białymi znakami. Każdy token jest napisem,
zawierającym liczbę typu float.
Ponieważ dane w pliku wejściowym są zapisywane wierszami, można
powiedzieć, że zadaniem tej funkcji (w połączeniu z funkcją
read_datas()) jest wczytanie z pliku określonej kolumny danych.
void sort_bufor( double *tablica , int lewa , int prawa )
Sortuje bufor danych, wskazywany przez zmienną tablica, począwszy
od pozycji lewa, aż do pozycji prawa.
Funkcja ta jest implementacją algorytmu sortowania szybkiego (Quick
Sort) [15].
void my_event_proc( Xv_Window window , Event *event )
Procedura obsługi zdarzeń dla obszaru graficznego ramki głównej.
Procedura ta jest drugą obok funkcji draw_chart, standardową
procedurą do obsługi obszaru CANVAS. Jej zadaniem jest interpretacja
zdarzeń zachodzących w obszarze graficznym np. ruchy myszki,
wciśnięcie dowolnego klawisza myszki, wciśnięcie dowolnego
klawisza na klawiaturze i inne.
W moim przypadku są obsługiwane następujące zdarzenia:
Ruchy myszki - powodują w stanie Off line, wyświetlania
współrzędnych punktów znajdujących się pod kursorem
(podrozdział 2.1.4, strona ).
Wciśnięcie lewego klawisza myszki - oznaczenie granic obszaru
rozszerzania wykresu (podrozdział 2.1.4, strona ).
Naciśnięcie prawego klawisza myszki - otwarcie menu
głównego.
Przeciągnięcie ikony pliku z obszaru programu File Manager do
obszaru graficznego ramki (Drag and Drop) - załadowanie danego
pliku (podrozdział 2.1.4, strona ).
int pushed_x( Panel_item item , Event *event )
int pushed_y( Panel_item item , Event *event )
Procedury obsługi klawisza nazw osi (opcja Set - X axis value, Set - Y axis value).
Klawisze nazw osi są tworzone dynamicznie podczas ładowania pliku z
danymi, na podstawie nagłówków kolumn (podrozdział 2.1.1).
Naciśnięcie danego klawisza powoduje zmianę wartości na
danej osi.
int set_y_max( Panel_item item , Event *event )
int set_y_min( Panel_item item , Event *event )
Procedury te obsługują panele tekstowe znajdujące się w
okienku Set Y axis limit.
void set_pressed( Panel_item item , Event *event )
Procedura obsługi przycisku Set w okienku Set Y axis limit.
Po wciśnięciu omawianego klawisza następuje aktualizacja (rysowanie)
wykresu w przedziale określonym przez wartości w panelach: Y max
oraz Y min.
void end_proc( void )
Procedura końcowa, powodująca zlikwidowanie wszystkich
ramek, zamknięcie pliku wejściowego i powrót do
systemu. Jest wywoływana, gdy wybierzemy opcję Quit.
int sort( Panel_item , int value , Event *event )
Procedura obsługi klawisza sortowania danych (opcja Sort X axis).
void show_x_axis( Menu menu , Menu_item menu_item )
void show_y_axis( Menu menu , Menu_item menu_item )
void show_load_frame( Menu menu , Menu_item menu_item )
void show_send_data_frame( Menu menu , Menu_item menu_item )
void show_others_frame( Menu menu , Menu_item menu_item )
void show_limit_frame( Menu menu , Menu_item menu_item )
Procedury te służą do ``otwierania'' ramek pomocniczych.
Są one wywoływane, gdy z menu wybierzemy daną pozycję
np. ramka Load jest ``otwierana'', gdy z menu wybierzemy opcję
Load.
void change_status( Menu menu , Menu_item menu_item )
Procedura wywoływana, gdy z menu wybrano opcję On LineÓff Line.
Powoduje zmianę statusu programu na przeciwny.
void show_status( void )
Zadaniem tej procedury jest wypisanie
w obszarze prawej stopki aktualnego
statusu programu i wykresu.
void change_fix( Menu menu , Menu_item menu_item )
Procedura wywoływana, gdy z menu wybrano opcję Fixed/Unfixed.
Powoduje zmianę statusu wykresu na przeciwny.
int load_file( Panel_item item , Event *event )
Pobiera z panela tekstowego File ramki Load nazwę pliku i
wczytuje podany plik.
void read_names_of_column(char *col )
Tworzy panel z przyciskami nazw osi na podstawie stringu wskazywanego przez
zmienną col. Poszczególne nazwy w stringu są rozdzielone
białymi znakami.
void print_names_of_columns( int margin )
Procedura wypisująca na danej osi jej nazwę. Jest wywoływana,
gdy przyciśnięto klawisz z nazwą osi (opcja Set - X axis value,
Set - Y axis value).
void bell( void )
Wysłanie krótkiego sygnału dżwiękowego. Jest
najczęściej wywoływana, gdy jakaś operacja została
wykonana błędnie.
static Notify_value meter_itimer_expired( Notify_client meter ,
int wchich )
Procedura ta powoduje uaktualnianie wykresu co pewien ustalony czas.
int period( Panel_item , int value , Event *event )
Powoduje zmianę okresu uaktualniania wykresu. Jest wywoływana gdy
w panelu numerycznym Period, ramki Options, zmieniono wartość
okresu uaktualniania.
int send_data( Panel_item item , Event *event )
Procedura obsługi przycisku Send i panela tekstowego ramki Send data.
W wyniku wywołania tej procedury dane z panela tekstowego (np. wartość
temperatury), są wysyłane do koleki komunikatów.
long filesize( FILE *f )
Procedura podaje rozmiar pliku wejściowego. Korzysta się z niej, aby
stwierdzić czy program symulacyjny dostarczył nowych danych.
int go_to_begin_of_file( Panel_item item , Event *event )
int go_to_end_of_file( Panel_item item , Event *event )
int go_next( Panel_item item , Event *event )
int go_previous( Panel_item item , Event *event )
Procedury obsługi przycisków ramki z parametrami programu.
Powodują one odpowiednio: przejście na początek pliku,
koniec pliku, przejście do następnego ekranu, przejście do
poprzedniego ekranu.
static Notify_value check_is_open( Notify_client , int which ,
char *name)
Procedura ta sprawdza cyklicznie (co 1 sekundę) czy ramka główna
ukazała się już na ekranie. Jeżeli tak, to wywołuje
procedurę open_file(), która ładuje plik wejściowy i
rysuje na podstawie wczytanych danych wykres funkcji.
Procedura ta jest wykonywana tylko wtedy, gdy program MONITOR wywołano
z parametrem.
W przypadku niepoprawnej obsługi programu wyświetlającego
wyniki symulacji, w obszarze stopek i na standardowym wyjściu
błędów są wyświetlane wszelkie komunikaty informujące
o błędach zaistniałych podczas działania programu;
Są one dodatkowo poprzedzane sygnałem dżwiękowym.
``File non available'' (Plik nie dostępny).
Należy jeszcze raz załadować podany plik.
``Not enough memory to create a bufor'' (Brak pamięci na
utworzenie bufora danych).
Ponieważ bufor nie został utworzony, dlatego
nie można załadować do niego żadnych danych z pliku, ani
ich wyświetlać.
W przypadku wystąpinia tego błędu (choć jest on mało prawdopodobny)
należy zmienić długość bufora (symbol BUFOR_LENGTH) w
programie żródłowym i skompilować cały program jeszcze raz.
``File empty'' (plik z danymi jest pusty).
Należy poczekać, aż plik będzie zawierał jakieś dane i
załadować go jeszcze raz.
``Queue not init'' (Brak kolejki komunikatów).
W przypadku wystąpienia tego błędu, nie jest możliwa wymiana
danych między procesami.