Zadaniem będzie uruchomienie programu w języku ANSI C, który pracując w pętli nieskończonej będzie odczytywał jednoliterowe polecenia użytkownika wpisywane z klawiatury, i wykonywał opisane poniżej operacje.
Realizacja każdego polecenia musi nastąpić w podprocesie. Proces główny
zajmuje się tylko odczytem polecenia użytkownika (funkcja fgets
),
uruchomieniem podprocesu do obsługi tego polecenia (funkcja fork
),
oraz powrotem do odczytu kolejnych poleceń.
Rozróżniamy dwa rodzaje poleceń: terminalowe, które korzystają z terminala do komunikacji z użytkownikiem, a więc proces główny musi czekać na zakończenie procesu potomka przed powrotem do realizacji kolejnych poleceń, oraz polecenia okienkowe, które otwierają własne okienko i nie korzystają z terminala, zatem proces główny nie czeka na ich zakończenie, tylko natychmiast wraca do realizacji kolejnych poleceń użytkownika.
Program powinien po wczytaniu poniższych jednoznakowych poleceń realizować
następujące funkcje:
(a) 'd' - uruchom program date
do wyświetlenia aktualnej daty i
czasu na terminalu,
(b) 's' - uruchomi interpreter poleceń /bin/sh
, w którym użytkownik
będzie mógł wykonywać dowolne polecenia, po czym będzie musiał wpisać
'exit' żeby zakończyć proces interpretera,
(c) 'x' - uruchomi zegar okienkowy xclock
z sekundnikiem
(opcja -update 1
)
(d) 'e' - wykorzystując program okienkowy zenity
z opcją
--file-selection
pozwoli użytkownikowi wybrać plik, a następnie
uruchomi edytor okienkowy xedit
z wybranym plikiem
(e) 'q' - zakończenie pracy programu.
Wykorzystując poniższy szkielet programu stwórz na jego podstawie kompletny
program, który będzie wczytywał polecenia użytkownika, i początkowo tylko
potwierdzał przyjęcie ich do wykonania. Skompiluj i uruchom program.
Kompilator ANSI C uruchamia się na systemach uniksowych poleceniem
cc
z nazwą pliku programu źródłowego jako argumentem. W przypadku
poprawnej kompilacji wynikowy program binarny tworzony jest w pliku
a.out
.
main ()
{
while (1) {
printf("Podaj polecenie do wykonania [d,s,x,e,q]:\n");
fgets(buf, sizeof(buf), stdin);
sscanf(buf, "%s", polecenie);
if (fork()==0) {
printf("Tu potomek pid=%d\n", getpid());
/* ... wykonanie polecenia potomka ... */
switch (polecenie[0]) {
case 'd': /* ... */;
}
exit(0); /* obowiazkowe zakonczenie potomka */
}
printf("Tu rodzic po utworzeniu potomka.\n");
/* ... czekanie na potomka terminalowego ... */
/* ... lub sprzatanie zombie okienkowego ... */
switch (polecenie[0]) {
case 'd': /* ... */;
}
} /* nieskonczona petla rodzica */
}
Należy zacząć od uruchomienia programu, który będzie tylko odczytywał polecenia użytkownika, a następnie uruchamiał procesy potomków, które będą tylko potwierdzały przyjęcie polecenia do wykonania.
Następnie należy uruchamiać po kolei funkcje programu, od najprostszych.
Początkowo najłatwiej będzie uruchamiać wymagane programy w procesie
potomka za pomocą funkcji system
. Tworzy ona interpreter poleceń,
który wykona dane polecenie. Jednak w końcowej wersji programu funkcję
system
należy zastąpić wywołaniem jednej z funkcji exec
która
jedynie wywoła właściwy program w już utworzonym procesie potomka, bez
interpretera poleceń.
W przypadku opcji 'e' wymagane jest kolejne wywołanie dwóch programów:
najpierw programu zenity
do spowodowania wyboru pliku przez użytkownika,
a następnie programu xedit
do uruchomienia edycji tego pliku. W
przypadku realizacji tej operacji przez funkcję system
można to zrobić
jednym wywołaniem, z zagnieżdżonym poleceniem shella wywołującym zenity
w
celu uzyskania nazwy pliku. Jednak w przypadku docelowej realizacji przez
exec
nie jest to możliwe. Najprostszym i polecanym rozwiązaniem jest
uruchomienie programu zenity
za pomocą funkcji popen
, a po jej
zakończeniu wywołanie właściwego programu xedit
za pomocą funkcji
exec
. Alternatywnym dla popen
sposobem byłoby utworzenie przez proces
potomka jeszcze jednego procesu wykonującego następnie zenity
za pomocą
exec
, z wcześniejszym przekierowaniem jego strumienia stdout
aby
przeczytać wybraną przez ten program nazwę pliku.
Podprocesy tworzone funkcją fork
powinny być poprawnie obsłużone
przez program główny, co polega na odczytaniu statusu po ich zakończeniu.
W przypadku poleceń 'd' i 's' jest to proste, ponieważ program główny
powinien czekać na zakończenie uruchomionego podprocesu. Czekanie na
zakończenie i odczytanie statusu podprocesu realizowane jest funkcją
waitpid
.
Jednak poprocesy tworzone funkcjami 'x' i 'e' są okienkowe i proces główny
nie powinien czekać na ich zakończenie, zatem w chwili zakończenia będą
stawały się procesami zombie. Ponieważ program główny nie czeka na te
podprocesy, zatem nie może w prosty sposób wywołać wait
(ani waitpid
) i
zakończyć zombie. Początkowo program może ignorować to zjawisko, jednak w
końcowej wersji należy uzupełnić program o niezbędne wywołania funkcji
waitpid
odczytujące status zakończonych potomków. Przykładowe sposoby
rozwiązania tego problemu przedstawione są w PDF-ie do wykładu o procesach,
od strony 31.
Pewne elementy wykonania tego zadania mają niewielki wpływ na działanie programu, ale uważny programista powinien zwracać uwagę na takie subtelności jak:
gdy poleceniem użytkownika jest 'q' program nie powinien w ogóle tworzyć potomka, tylko od razu wyjść z pętli dialogowej i zakończyć pracę
gdy użytkownik nie wprowadzi żadnego polecenia (tylko ENTER), albo wpisze znak inny niż dopuszczalny, program powinien ponowić zapytanie
w przypadku napotkania końca danych (EOF) na wejściu, program powinien wykryć to i wyjść, zamiast próbować odczytów w nieskończoność
dobrą praktyką jest, aby doprowadzić program do takiego stanu by kompilował się bez ostrzeżeń kompilatora; optymalnie na obu systemach (Solaris i Linux); pozwala to na szybkie zauważenie nowo pojawiających się ostrzeżeń, które mogą wskazywać na niepoprawne użycie jakichś funkcji
również bardzo zalecane jest w tym zadaniu porządne sformatowanie programu z wcięciami odzwierciedlającymi jego strukturę; pozwala to na łatwe ,,optyczne'' odróżnienie kodu rodzica i potomka i uniknięcie trudnych do zlokalizowania błędów
Typową trudnością w zadaniu jest rozróżnienie kodu rodzica i potomka.
Często skutkiem nieprawidłowego rozdzielenia ich funkcji jest: (a)
niewspółbieżna praca rodzica i potomka okienkowego, (b) współbieżna praca
rodzica i potomka terminalowego (co skutkuje konkurencją w dostępie do
terminala, i zastopowaniem procesu rodzica przez system), a także czasami
(c) kompletna porażka polegająca na wejściu procesu potomka do kodu rodzica
(brak return
lub exit
w kodzie potomka), co powoduje konkurencję o
dostęp do terminala i/lub nieograniczone rozmnażanie się potomków !!
Bardzo częsta jest nieprawidłowa obsługa zombie.
W przypadku zdalnej pracy z Windowsa, dla uruchamiania uniksowych programów okienkowych konieczne będzie zainstalowanie i uruchomienie na lokalnym systemie programu zwanego serwerem X Window.
Krótka instrukcja: http://diablo.kcir.pwr.edu.pl/xwindow/
W razie napotkania problemów proszę zwracać się o pomoc do prowadzącego na zajęciach, konsultacjach, lub emailem, lub do techników/administratorów (szczegółowe informacje na stronach WWW diablo i panaminta).
Minimalny wynik: uzupełniony i uruchomiony program szkieletowy, odczyt poleceń użytkownika, tworzenie podprocesów, zakończenie programu 'q' (2 punkty).
Pożądany wynik: uruchomione co najmniej dwie z funkcji 'd', 's', 'x'
przynajmniej w wersji przez funkcję system
(+2 punkty).
Minimalny wynik: wszystkie cztery funkcje programu uruchomione z
wykorzystaniem jednej z funkcji exec*
(2 punkty, lub 1 punt w przypadku
niepełnej realizacji, braku współbieżności, lub innych błędów).
Pożądany wynik: poprawna obsługa podprocesów przez program główny, poprawne odczytywanie statusu podprocesów (+1 punkt - na zajęciach).
Raport z wykonania zadania, wysłany jednorazowo po jego zakończeniu całego
zadania powinien zawierać potwierdzenie i opis sposobu wykonania wszystkich
elementów zadania (system
/popen
/exec
) i/lub uwagi na temat
ewentualnych problemów.
Dodatkowo, proszę podać w raporcie czy i w jaki sposób program rozwiązuje problem powstających procesów zombie.
W przypadku uruchomienia i przetestowania programu na obydwóch systemach (diablo/Solaris, panamint/Linux), proszę również zaznaczyć to w raporcie.
Uwaga: w ocenie zadania jeden punkt będzie przyznawany za brak ostrzeżeń
kompilacji programu z opcją -Wall
(gcc na panamincie) oraz -Xc
(cc na
diablo).