Treść zadania indywidualnego:
Opracować zestaw programów typu producent-konsument realizujących przy wykorzystaniu mechanizmu kolejek komunikatów, następujący schemat komunikacji międzyprocesowej:
Proces 1: czyta dane (pojedyncze wiersze) ze standardowego strumienia wejściowego i przekazuje je w niezmienionej formie do procesu 2.
Proces 2: pobiera dane przesyłane przez proces 1. Szyfruje odebrane dane przy pomocy dowolnego prostego algorytmu i przekazuje do procesu 3.
Proces 3: pobiera dane wyprodukowane przez proces 2 i umieszcza je w standardowym strumieniu wyjściowym. Każda odebrana jednostka danych powinna zostać wyprodukowana w osobnym wierszu.
Należy zaproponować i zaimplementować mechanizm informowania procesów o swoim stanie. Należy wykorzystać do tego dostępny mechanizm sygnałów i łączy komunikacyjnych (pipes). Wszystkie trzy procesy powinny być powoływane automatycznie z jednego procesu inicjującego.
Zaimplementować mechanizm asynchronicznego przekazywania informacji pomiędzy operatorem a procesami oraz pomiędzy procesami. Wykorzystać do tego dostępny mechanizm sygnałów i łączy komunikacyjnych (pipes).
Operator może wysyłać do dowolnego procesu sygnał zakończenia działania (S1), sygnał wstrzymania działania (S2) i sygnał wznowienia działania (S3). Sygnał S2 powoduje wstrzymanie synchronicznej wymiany danych pomiędzy procesami. Sygnał S3 powoduje wznowienie tej wymiany. Sygnał S1 powoduje zakończenie działania oraz zwolnienie wszelkich wykorzystywanych przez procesy zasobów.
Każdy z sygnałów przekazywany jest przez operatora tylko do jednego, dowolnego procesu. O tym, do którego procesu wysłać sygnał, decyduje operator, a nie programista. Każdy z sygnałów operator może wysłać do innego procesu. Mimo, że operator kieruje sygnał do jednego procesu, to pożądane przez operatora działanie musi zostać zrealizowane przez wszystkie trzy procesy. W związku z tym, proces odbierający sygnał od operatora musi powiadomić o przyjętym żądaniu pozostałe dwa procesy. Powinien wobec tego wysłać do nich sygnał (S4) oraz przekazać informacje o tym, jakiego działania wymaga operator, przekazując im stosowny komunikat (lub komunikaty) poprzez mechanizm sygnałów i łączy komunikacyjnych (pipes). Procesy odbierające sygnał S4, powinny odczytać skierowany do nich komunikat (lub komunikaty) w procedurze odbierania sygnału S4. Wszystkie trzy procesy powinny zareagować zgodnie z żądaniem operatora.
Sygnały oznaczone w opisie zadania symbolami S1- S4 należy wybrac samodzielnie spośród dostępnych w systemie (np. SIGUSR1, SIGUSR2, SIGINT, SIGCONT).
Zobrazowanie zadania:
Opis użytych mechanizmów:
kolejki komunikatów
Kolejki komunikatów razem z semaforami i pamięcią współdzieloną należą do zaawansowanych urządzeń IPC, które sprawiają, że Unix jest systemem nadzwyczaj bogatym w obszarze komunikacji procesowej.
Cechą charakterystyczną wyżej wymienionych urządzeń są klucze, które są używane do identyfikacji obiektów IPC w systemie. Klucz pozwala na wspólne użycie zasobów IPC przez kilka procesów. Rzeczywisty typ danych klucza jest określony przez zależny od implementacji typ key_t, zdefiniowany w systemowym pliku nagłówkowym <sys/types.h>.
Komunikaty są przekazywane między procesami za pomocą kolejek komunikatów, tworzonych lub udostępnianych przez funkcje pierwotną msgget. Gdy kolejka jest utworzona, proces, mając stosowne uprawnienia kolejki, może umieścić w niej komunikat za pomocą funkcji msgsnd. Inny proces może wtedy odczytać ten komunikat za pomocą funkcji msgrcv, która ponadto usuwa go z kolejki.
Funkcja msgget
Deklaracja: int msgget(key_t key, int permflags);
key :
- identyfikator kolejki
permflags :
- 9 mniej znaczących bitów służy do nadania uprawnień kolejce,
- flaga IPC_CREAT odpowiada za utworzenie kolejki komunikatow dla wartości key, o ile ona jeszcze nie istnieje,
- flaga IPC_EXCL razem z IPC_CREAT przeznaczone SA tylko do utworzenia kolejki komunikatów.
Funkcja msgsnd
Deklaracja: msgsnd(int mqid, const void* message, size_t size, int flags);
mqid:
- wartość uzyskana z wywołania msgget,
message:
- struktura komunikatu,
size:
- maksymalna długość, która może być trzymana wewnątrz struktury komunikatu,
Flags:
- zawiera informację kontrolna (IPC_NOWAIT i MSG_NOERROR).
Funkcja msgrcv
Deklaracja: msgrcv(int mqid, void *message, size_t, long msg_type, int flags);
msg_type:
- określa dokładnie, jaki komunikat został właśnie odebrany,
- jeśli jest równy 0, to odczytywany będzie pierwszy komunikat z kolejki, a w przeciwnym wypadku wg priorytetu.
Sygnały
Procesy komunikują się z jądrem systemu, a także między sobą ,aby koordynować swoją działalność. Linux wspiera kilka mechanizmów komunikacji. Jednym z nich są sygnały, zwane inaczej przerwaniami programowymi.
Sygnały mogą być generowane bezpośrednio przez użytkownika (funkcja kill()), może wysyłać je jądro oraz procesy między sobą (funkcja systemowa kill()) . Dodatkowo pewne znaki z terminala powodują wygenerowanie sygnałów.
Sygnały są mechanizmem asynchronicznym - proces nie wie z góry, kiedy sygnał może nadejść i głównym ich zadaniem jest informowanie procesu o zaistnieniu w systemie wyjątkowej sytuacji .
Sygnały działają jak rodzaj programowego zaworu, który przerywa proces, niezależnie od tego, co on aktualnie robi. Z powodu swojej natury, sygnały SA używane raczej do obsługi nietypowych sytuacji, a nie do prostego przesłania danych pomiędzy procesami.
Sygnał może zrobić z sygnałem następujące trzy rzeczy:
wybrać sposób reakcji po otrzymaniu konkretnego sygnału (obsługa sygnałów),
blokować sygnały ( pozostawić je na później) w określonych fragmentach krytycznego kodu,
wysyłać sygnały do innego procesu.
łącza komunikacyjne (pipes)
Potok to jeden z prostszych mechanizmów komunikacji międzyprocesowej umożliwiający wymianę danych pomiędzy dwoma procesami. Odbywa się to najczęściej poprzez połączenie standardowego wyjścia jednego procesu ze standardowym wejściem drugiego.
Proces może wysyłać dane „w dół” potoku za pomocą funkcji systemowej write, a inny proces może odebrać dane na drugim końcu potoku, używając funkcji systemowej read.
Wewnątrz programu potok jest tworzony za pomocą funkcji systemowej o nazwie pipe().Jeśli wywołanie było pomyślne, zwraca ona dwa deskryptory plików: jeden do zapisu do potoku, a drugi do odczytu z niego.
Deklaracja: int pipe(int fildes[2]);
fields[0] - deskryptor pliku tylko do odczytu,
fields[1] - deskryptor pliku tylko do zapisu.
Kod programu:
- plik „kolejki.h”:
//biblioteki
#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <unistd.h>
#include <sys/ipc.h>
#include <sys/msg.h>
#include <string.h>
#include <fcntl.h>
#include "synch.c"
#include <signal.h>
#include <sys/stat.h>
#include <errno.h>
// identyfikatory kluczy kolejek komunikatow
#define KLUCZ_K1 (key_t)1
#define KLUCZ_K2 (key_t)2
// prawa dostepu dla kolejki
#define PRAWA 0660
// maksymalna dlugosc stringu
#define MAX_DL_STR 512
// deklaracja struktury komunikatu
// przekazywanego przez kolejke komunikatow
struct q_entry
{
long mtype;
char mtext[MAX_DL_STR];
};
- plik „synch.c”:
// definicja listy dostepnych sygnalow
#define S1 SIGUSR1
#define S2 SIGUSR2
#define S3 SIGCHLD
#define S4 SIGALRM
//definicja nazw plikow tworzonych do zapisu PID procesu i identyfikatora kolejki
#define PID_FILE_P1 "p1.pid"
#define PID_FILE_P2 "p2.pid"
#define PID_FILE_P3 "p3.pid"
#define ID_K1_FILE "id_k1"
#define ID_K2_FILE "id_k2"
//funkcja zapisu PID danego procesu do danego pliku
int savePID(int PID, char file[])
{
FILE *fs=fopen(file, "w+");
fprintf(fs, "%d\n", PID);
fclose(fs);
return 0;
}
//funkcja pobierajaca PID danego procesu z danego pliku
int loadPID(char file[])
{
int tmp=0;
FILE *fs=fopen(file, "r");
fscanf(fs, "%d", &tmp);
fclose(fs);
return tmp;
}
//funkcja zapisu identyfikatora danej kolejki komunikatow do danego pliku
int saveID_K(int id, char file[])
{
FILE *fs=fopen(file, "w+");
fprintf(fs, "%d\n", id);
fclose(fs);
return 0;
}
//funkcja pobierajaca identyfikatora danej kolejki komunikatow z pliku
int loadID_K(char file[])
{
int tmp=0;
FILE *fs=fopen(file, "r");
fscanf(fs, "%d", &tmp);
fclose(fs);
return tmp;
}
- plik „dane”:
komunikat
wprowadzam komunikat
cos
wiadomosc 1
wiadomosc 2
wiadomosc 3
wiadomosc 4
wiadomosc 5
wiadomosc 6
wiadomosc 7
wiadomosc 8
.
.
.
koniec
- plik „P0.c”:
#include "kolejki.h"
main()
{
int p1,p2,p3;
int pipe1[2], pipe2[2], pipe3[2];
char wynik_1[12], wynik_2[12], wynik_3[12];
//otwieranie potokow
if(pipe(pipe1) == -1)
{
printf("[P0] Blad przy otwieraniu potoku pipe1\n");
exit(1);
}
if(pipe(pipe2) == -1)
{
printf("[P0] Blad przy otwieraniu potoku pipe2\n");
exit(1);
}
if(pipe(pipe3) == -1)
{
printf("[P0] Blad przy otwieraniu potoku pipe3\n");
exit(1);
}
printf("[P0] PID: %d\n",getpid());
//tworzenie procesow potomnych
if((p1=fork())==0)
{
sprintf(wynik_1, "%d", pipe1[0]); // konwersja lizczby
sprintf(wynik_2, "%d", pipe2[1]); // (deskryptora zapisu lub odczytu potoku)
sprintf(wynik_3, "%d", pipe3[1]); // na lancuch znakowy
// uruchomienie pliku p1 i przekazanie deskryptorow jako parametrow
execl("p1", "p1", wynik_1, wynik_2, wynik_3, NULL);
exit(0);
}
else if(p1==-1)
{
printf("[P0] Blad wywolania fork w procesie o PID: %d\n",getpid());
exit(2);
}
if((p2=fork())==0)
{
sprintf(wynik_1, "%d", pipe1[1]); // konwersja lizczby
sprintf(wynik_2, "%d", pipe2[0]); // (deskryptora zapisu lub odczytu potoku)
sprintf(wynik_3, "%d", pipe3[1]); // na lancuch znakowy
// uruchomienie pliku p2 i przekazanie deskryptorow jako parametrow
execl("p2", "p2", wynik_1, wynik_2, wynik_3, NULL);
exit(0);
}
else if(p2 == -1)
{
printf("[P0] Blad wywolania fork w procesie o PID: %d\n",getpid());
exit(2);
}
if((p3 =fork()) == 0)
{
sprintf(wynik_1, "%d", pipe1[1]); // konwersja lizczby
sprintf(wynik_2, "%d", pipe2[1]); // (deskryptora zapisu lub odczytu potoku)
sprintf(wynik_3, "%d", pipe3[0]); // na lancuch znakowy
// uruchomienie pliku p3 i przekazanie deskryptorow jako parametrow
execl("p3", "p3", wynik_1, wynik_2, wynik_3, NULL);
exit(0);
}
else if(p3 == -1)
{
printf("[P0] Blad wywolania fork w procesie o PID: %d\n",getpid());
exit(2);
}
exit(0);
}
- plik „P1.c”:
//biblioteki
#include "kolejki.h"
#define PLIK "dane"
int PID, flag=0;
int pipeDoMnie, pipe2, pipe3;
int PID_2, PID_3;
int id_k1, id_k2;
// deklaracje funkcji wysyania i odbierania sygnalu, oraz usuwania kolejki
void sygnal(int s);
void sygnalS4(int s);
void usun_kolejke(int id_k);
int main( int argc, char*argv[])
{
if(argc!=4)
{
printf("[P1] Za malo argumentow\n");
exit(1);
}
// konwersja lancucha na liczbe
pipeDoMnie = atoi(argv[1]);
pipe2 = atoi(argv[2]);
pipe3 = atoi(argv[3]);
int id_k1,
dl_ciag,
y;
char ciag[MAX_DL_STR+100],
wejscie[MAX_DL_STR+200];
struct q_entry komunikat;
FILE *fd;
PID=getpid();
// zapis PID do pliku
savePID(PID, PID_FILE_P1);
//wywolanie funkcji signal
signal(S1, sygnal);
signal(S2, sygnal);
signal(S3, sygnal);
signal(S4, sygnalS4);
printf("[P1] PID: %d\n",getpid());
// tworzenie(otwieranie) kolejki komunikatow
if((id_k1=msgget(KLUCZ_K1, IPC_CREAT | PRAWA)) == -1)
{
printf("[P1] Nie udalo sie utworzyc(otworzyc) kolejki\n");
exit(1);
}
saveID_K(id_k1, ID_K1_FILE);
printf("[P1] Dane z pliku\n");
// otwieranie pliku do odczytu
if((fd=fopen(PLIK ,"r")) == NULL)
{
perror("[P1] fopen()");
exit(1);
}
do
{
// wyzerowanie bufora wejsciowego
memset(wejscie, 0, MAX_DL_STR);
// odczytanie linii danych z otwartego pliku i umieszczenie w buforze wejscie
if(fgets(wejscie, MAX_DL_STR, fd)==NULL)
{
printf("Blad fgets\n");
printf("Usuniecie kolejek komunikatow\n");
//popranie identyfikatora drugiej kolejki komunikatow
id_k2=loadID_K(ID_K2_FILE);
//usuwanie kolejek komunikatow
usun_kolejke(id_k1);
usun_kolejke(id_k2);
printf("Wysanie sygnalu SIGKILL do P2 i P3\n");
//pobranie PID innych procesow
PID_2=loadPID(PID_FILE_P2);
PID_3=loadPID(PID_FILE_P3);
//wyslanie SIGKILL do innych procesow
kill(PID_2, SIGKILL);
kill(PID_3, SIGKILL);
exit(1);
}
dl_ciag=strlen(wejscie);
wejscie[dl_ciag-1] = '\0';
printf("[P1] z pliku: '%s' [%d B]\n", wejscie, strlen(wejscie));
komunikat.mtype=1;
memset(komunikat.mtext, 0, MAX_DL_STR);
strcpy(komunikat.mtext, wejscie);
// dodanie komunikatu do kolejki komunikatow
if((y=msgsnd(id_k1, &komunikat, strlen(komunikat.mtext),0)) == -1)
{
printf("[P1] Nie udalo sie wpisac komunikatu do kolejki\n");
exit(1);
}
else
{
printf("[P1] wyslalem: '%s' [%d B]\n", komunikat.mtext, strlen(komunikat.mtext));
}
sleep(3);
//wstrzymanie procesu jesli dostal sygnal S2
while(flag == 1){}
}while(1);
exit(0);
}
void usun_kolejke(int id_k)
{
//usuwanie kolejki komunikatow
if(msgctl(id_k,IPC_RMID,NULL) == -1)
{
printf("Nie moge usunac kolejki komunikatow 1 \n");
exit(1);
}
}
void sygnal(int s)
{
printf("[P1] przechwycilem sygnal %d\n",s);
char kom;
int PID_2, PID_3;
//pobranie PID innych procesow
PID_2=loadPID(PID_FILE_P2);
PID_3=loadPID(PID_FILE_P3);
printf("[P1] Wysylam sygnaly do %d, %d\n", PID_2, PID_3);
// wiadomosc jaka przesyla proces p1 do innych procesow w zaleznosci od otrzymanego sygnalu
switch(s)
{
case S1:
kom = '1';
break;
case S2:
kom = '2';
break;
case S3:
kom = '3';
break;
}
// wysyanie sygnau S4 do pozostaych procesow
if(kill(PID_2, S4) == -1)
printf("[P1] kill error do P2\n");
if(kill(PID_3, S4) == -1)
printf("[P1] kill error do P3\n");
// zapis do potoku wiadomosci o wyslanym sygnale
if(write(pipe2, &kom, 1) < 0)
perror("[P1] write error: ");
if(write(pipe3, &kom, 1) < 0)
perror("[P1] write error: ");
// odpowiedz procesu p1 na sygnal wyslany przez uzytkownika
switch(s)
{
case S1:
printf("[P1] dostalem sygnal S1, wiec koncze dzialanie\n");
//popranie identyfikatorow kolejek komunikatow
id_k1=loadID_K(ID_K1_FILE);
id_k2=loadID_K(ID_K2_FILE);
//usuwanie kolejek komunikatow
usun_kolejke(id_k1);
usun_kolejke(id_k2);
//wyslanie sygnalu SIGKILL do siebie
kill(PID, SIGKILL);
break;
case S2:
printf("[P1] dostalem sygnal S2, wiec wstrzymuje dzialanie\n");
flag=1;
break;
case S3:
printf("[P1] dostalem sygnal S1, wiec wznawiam dzialanie\n");
flag=0;
break;
}
}
void sygnalS4(int s)
{
char c;
// odczytanie wiadomosci o sygnale z potoku
read(pipeDoMnie, &c, 1);
// odpowiedz procesu p1 na sygnal S4
switch(c)
{
case '1':
printf("[P1] sygnal S4 - koniec dzialania\n");
kill(PID, SIGKILL);
break;
case '2':
printf("[P1] sygnal S4 - zatrzymanie\n");
flag=1;
break;
case '3':
printf("[P1] sygnal S4 - wznowienie\n");
flag=0;
break;
}
}
- plik „P2.c”:
//biblioteki
#include "kolejki.h"
int PID, flag=0;
int pipe1, pipeDoMnie, pipe3;
int id_k1, id_k2;
// deklaracje funkcji wysyania i odbierania sygnalu, oraz usuwania kolejki komunikatow
void sygnal(int s);
void sygnalS4(int s);
void usun_kolejke(int id_k);
int main(int argc, char*argv[])
{
if(argc!=4)
{
printf("Za malo argumentow\n");
exit(1);
}
//konwersja lancucha na liczbe
pipe1 = atoi(argv[1]);
pipeDoMnie = atoi(argv[2]);
pipe3 = atoi(argv[3]);
int id_k1, id_k2, dl_kom, i;
struct q_entry komunikat;
PID=getpid();
// zapis PID do pliku
savePID(PID, PID_FILE_P2);
printf("[P2] PID: %d\n", PID);
//wywolanie funkcji signal
signal(S1, sygnal);
signal(S2, sygnal);
signal(S3, sygnal);
signal(S4, sygnalS4);
// otworanie kolejek komunikatow
if((id_k1=msgget(KLUCZ_K1, IPC_CREAT | PRAWA)) == -1)
{
printf("[P2] Nie udalo sie otworzyc kolejki\n");
exit(1);
}
if((id_k2=msgget(KLUCZ_K2, IPC_CREAT | PRAWA)) == -1)
{
printf("[P2] Nie udalo sie utworzyc(otworzyc) kolejki 2\n");
exit(1);
}
saveID_K(id_k2, ID_K2_FILE);
do
{
// wyzerowanie bufora wejsciowego
memset(komunikat.mtext, 0, MAX_DL_STR);
//odczytanie komunikatu z kolejki komunikatow
if((dl_kom=msgrcv(id_k1, &komunikat, MAX_DL_STR, 0, MSG_NOERROR)) == -1)
{
printf("[P2] Nie udalo sie odczytac komunikatu z kolejki\n");
}
else
{
printf("[P2] odczyalem: '%s' [%d B]\n",komunikat.mtext, dl_kom);
//kodowanie pobranego komunikatu
for(i=0; i<dl_kom; i++)
{
komunikat.mtext[i] = komunikat.mtext[i]+1;
}
// dodanie komunikatu do kolejki komunikatow
if(msgsnd(id_k2, &komunikat, strlen(komunikat.mtext),0) == -1)
{
printf("[P2] Nie udalo sie wpisac komunikatu do kolejki\n");
}
else
{
printf("[P2] wyslalem: '%s' [%d B]\n", komunikat.mtext, strlen(komunikat.mtext));
}
}
//wstrzymanie procesu jesli dostal sygnal S2
while(flag == 1){}
}while(1);
exit(0);
}
void usun_kolejke(int id_k)
{
//usuwanie kolejki komunikatow
if(msgctl(id_k,IPC_RMID,NULL) == -1)
{
printf("Nie moge usunac kolejki komunikatow 1 \n");
exit(1);
}
}
void sygnal(int s)
{
printf("[P2] przechwycilem sygnal %d\n",s);
char kom;
int PID_1, PID_3;
//pobranie PID innych procesow
PID_1=loadPID(PID_FILE_P1);
PID_3=loadPID(PID_FILE_P3);
printf("[P2] Wysylam sygnaly do %d, %d\n", PID_1, PID_3);
// wiadomosc jaka przesyla proces p2 do innych procesow w zaleznosci od otrzymanego sygnalu
switch(s)
{
case S1:
kom = '1';
break;
case S2:
kom = '2';
break;
case S3:
kom = '3';
break;
}
//wysyanie sygnau S4 do pozostaych procesow
if(kill(PID_1, S4) == -1)
printf("[P2] kill error do P2\n");
else
printf("[P2] kill -> %d succed \n", PID_1);
if(kill(PID_3, S4) == -1)
printf("[P2] kill error do P3\n");
else
printf("[P2] kill -> %d succed \n", PID_3);
// zapis do potoku wiadomosci o wyslanym sygnale
if(write(pipe1, &kom, 1) < 0)
perror("[P2] write error: ");
if(write(pipe3, &kom, 1) < 0)
perror("[P2] write error: ");
// odpowiedz procesu p2 na sygnal wyslany przez uzytkownika
switch(s)
{
case S1:
printf("[P2] dostalem sygnal S1, wiec koncze dzialanie\n");
//popranie identyfikatorow kolejek komunikatow
id_k1=loadID_K(ID_K1_FILE);
id_k2=loadID_K(ID_K2_FILE);
//usuwanie kolejek komunikatow
usun_kolejke(id_k1);
usun_kolejke(id_k2);
//wyslanie sygnalu SIGKILL do siebie
kill(PID, SIGKILL);
break;
case S2:
printf("[P2] dostalem sygnal S2, wiec wstrzymuje dzialanie\n");
flag=1;
break;
case S3:
printf("[P2] dostalem sygnal S1, wiec wznawiam dzialanie\n");
flag=0;
break;
}
}
void sygnalS4(int s)
{
char c;
// odczytanie wiadomosci o sygnale z potoku
read(pipeDoMnie, &c, 1);
// odpowiedz procesu p2 na sygnal S4
switch(c)
{
case '1':
printf("[P2] sygnal S4 - koniec dzialania\n");
kill(PID, SIGKILL);
break;
case '2':
printf("[P2] sygnal S4 - zatrzymanie\n");
flag=1;
break;
case '3':
printf("[P2] sygnal S4 - wznowienie\n");
flag=0;
break;
}
}
- plik „P3.c”:
//biblioteki
#include "kolejki.h"
int PID, flag=0;
int pipe1, pipe2, pipeDoMnie;
int id_k1, id_k2;
// deklaracje funkcji wysyania i odbierania sygnalu, oraz usuwania kolejki komunikatow
void sygnal(int s);
void sygnalS4(int s);
void usun_kolejke(int id_k);
int main(int argc, char*argv[])
{
if(argc!=4)
{
printf("[P3] Za malo argumentow\n");
exit(1);
}
// konwersja lancucha na liczbe
pipe1 = atoi(argv[1]);
pipe2 = atoi(argv[2]);
pipeDoMnie = atoi(argv[3]);
int id_k2, dl_kom, i;
struct q_entry komunikat;
PID=getpid();
// zapis PID do pliku
savePID(PID, PID_FILE_P3);
printf("[P3] PID: %d\n", PID);
//wywolanie funkcji signal
signal(S1, sygnal);
signal(S2, sygnal);
signal(S3, sygnal);
signal(S4, sygnalS4);
// otworanie kolejki komunikatow
if((id_k2=msgget(KLUCZ_K2, IPC_CREAT | PRAWA)) == -1)
{
printf("[P3] Nie udalo sie otworzyc kolejki\n");
exit(1);
}
do
{
// wyzerowanie bufora wejsciowego
memset(komunikat.mtext, 0, MAX_DL_STR);
//odczytanie komunikatu z kolejki komunikatow
if((dl_kom=msgrcv(id_k2, &komunikat, MAX_DL_STR, 0, MSG_NOERROR)) == -1)
{
printf("[P3] Nie udalo sie odczytac komunikatu z kolejki\n");
}
else
{
//dekodowanie pobranego komunikatu
for(i=0; i<dl_kom; i++)
{
komunikat.mtext[i] = komunikat.mtext[i]-1;
}
printf("[P3] odczyalem: '%s' [%d B]\n",komunikat.mtext, dl_kom);
}
//wstrzymanie procesu jesli dostal sygnal S2
while(flag == 1){}
}while(1);
exit(0);
}
void usun_kolejke(int id_k)
{
//usuwanie kolejki komunikatow
if(msgctl(id_k,IPC_RMID,NULL) == -1)
{
printf("Nie moge usunac kolejki komunikatow 1 \n");
exit(1);
}
}
void sygnal(int s)
{
printf("[P3] przechwycilem sygnal %d\n",s);
char kom;
int PID_1, PID_2;
//pobranie PID innych procesow
PID_1=loadPID(PID_FILE_P1);
PID_2=loadPID(PID_FILE_P2);
printf("[P3] Wysylam sygnaly do %d, %d\n", PID_1, PID_2);
// wiadomosc jaka przesyla proces p3 do innych procesow w zaleznosci od otrzymanego sygnalu
switch(s)
{
case S1:
kom = '1';
break;
case S2:
kom = '2';
break;
case S3:
kom = '3';
break;
}
//wysyanie sygnau S4 do pozostaych procesow
if(kill(PID_1, S4) == -1)
printf("[P3] kill error do P1\n");
else
printf("[P3] kill -> %d succed \n", PID_1);
if(kill(PID_2, S4) == -1)
printf("[P3] kill error do P2\n");
else
printf("[P3] kill -> %d succed \n", PID_2);
// zapis do potoku wiadomosci o wyslanym sygnale
if(write(pipe1, &kom, 1) < 0)
perror("[P3] write error: ");
if(write(pipe2, &kom, 1) < 0)
perror("[P3] write error: ");
// odpowiedz procesu p3 na sygnal wyslany przez uzytkownika
switch(s)
{
case S1:
printf("[P3] dostalem sygnal S1, wiec koncze dzialanie\n");
//popranie identyfikatorow kolejek komunikatow
id_k1=loadID_K(ID_K1_FILE);
id_k2=loadID_K(ID_K2_FILE);
//usuwanie kolejek komunikatow
usun_kolejke(id_k1);
usun_kolejke(id_k2);
//wyslanie sygnalu SIGKILL do siebie
kill(PID, SIGKILL);
break;
case S2:
printf("[P3] dostalem sygnal S2, wiec wstrzymuje dzialanie\n");
flag=1;
break;
case S3:
printf("[P3] dostalem sygnal S1, wiec wznawiam dzialanie\n");
flag=0;
break;
}
}
void sygnalS4(int s)
{
char c;
// odczytanie wiadomosci o sygnale z potoku
read(pipeDoMnie, &c, 1);
// odpowiedz procesu p1 na sygnal S4
switch(c)
{
case '1':
printf("[P3] sygnal S4 - koniec dzialania\n");
kill(PID, SIGKILL);
break;
case '2':
printf("[P3] sygnal S4 - zatrzymanie\n");
flag=1;
break;
case '3':
printf("[P3] sygnal S4 - wznowienie\n");
flag=0;
break;
}
}
- plik „kompilacja”:
gcc -o p0 P0.c
echo ----------------------------------------
gcc -o p1 P1.c
echo ----------------------------------------
gcc -o p2 P2.c
echo ----------------------------------------
gcc -o p3 P3.c
Wynik działania programu:
Część I zadania:
Uruchomienie programu „kompilacja”, kompilującego pliki „P0.c”, „P1.c”, „P2.c”, „P3.c” poprzez instrukcje:
./kompilacja
Uruchomienie programu „P0.c” poprzez:
./p0
Program:
powołuje inne procesy (programy: „P1.c”, „P2.c”, „P3.c”),
wypisuje na ekran identyfikatory procesów
wykorzystując mechanizm kolejek komunikatów procesy przekazują sobie kolejno komunikaty, które program „P1.c” przeczytał ze standardowego strumienia wejściowego ( w tym wypadku z pliku „dane”),
program „P2.c” koduje komunikaty, a program „P3.c” dekoduje je i wypisuje je na standardowy strumień wyjściowy (w typ wypadku na konsole operatorską),
kończy się w momencie końca pliku „dane” ( następuje usunięcie kolejek komunikatów oraz wysłanie sygnału kill do wszystkich procesów ).
Część II zadania:
W celu sprawdzenia wyniku działania tej części zadania należy dodatkowo otworzyć druga konsolę operatorską .
Na drugiej konsoli operatorskiej przeglądam drzewo procesów poprzez:
pstree -p
Następnie wysyłam sygnały:
SIGUSR2 do procesu p2 (wstrzymanie działania procesów),
SIGCHLD do procesu p3 (wznowienie działania procesów),
SIGUSR1 do procesu p1 (zakończenie działania procesów).
Nie ma znaczenia jaki sygnał wyśle do jakiego procesu, ponieważ każdy proces po otrzymaniu danego sygnału zachowuje się tak samo i przesyła wiadomość o sygnale do innych procesów powodując ze zachowują się identycznie jak proces, od którego otrzymały wiadomość o sygnale.
Wynik działania programu obserwuje na pierwszej konsoli operatorskiej.
Konsola I
Konsola II
Po zakończeniu działania programu sprawdzam, czy wszystkie kolejki komunikatów zostały usunięte, a wszystkie procesy zostały zabite poprzez:
- ipcs
- pstree -p
proces macierzysty P0
proces potomny P1
proces potomny P3
S4
proces potomny P2
S4
S
Zbiór sygnałów:
{S1, S2, S3}
stdin
stdout
UZYTKOWNIK
pobieranie danych
wyświetlanie danych