Instrukcja do laboratorium Systemów Operacyjnych (semestr drugi) Ćwiczenie drugie (jedne zajęcia) Temat: Procesy i sygnały w Linuksie. Opracowanie: mgr in ż. Arkadiusz Chrobot
Wprowadzenie 1. Budowa procesu w Uniksie. W systemach uniksowych (w tym w Linuksie) przestrzeń procesu użytkownika można podzielić na dwa konteksty: kontekst użytkownika Pierwszy z nich może być podzielony na sześć obszarów: tekstu, stałych, i kontekst jądra. zmiennych zainicjowanych, zmiennych niezainicjowanych, sterty i stosu. Drugi zawiera wyłącznie dane. Obszar tekstu zawiera rozkazy maszynowe, które są wykonywane przez sprzęt. procesów Ten obszar jest tylko do odczytu, a więc może go współdzielić kilka równocześnie. Obszar stałych jest również tylko do odczytu. We współczesnych systemach uniksowych jest łączony w jeden obszar z obszarem tekstu. Obszar zmiennych zainicjowanych zawiera zmienne, którym zostały przypisane wartości początkowe, ale proces może je dowolnie modyfikowa ć. Obszar zmiennych niezainicjowanych (bss) zawiera zmienne, które mają wartość początkową zero, a więc nie trzeba ich wartości inicjujących przechowywać w pliku programu. Sterta (ang. heap) i stos (ang. stack) tworzą w zasadzie jeden obszar sterta służy do dynamicznego przydzielania dodatkowego obszaru w pamięci, natomiast na stosie przechowywane są ramki stosu, czyli informacje związane z wywołaniem podprogramów. Sterta rozszerza się w stronę wyższych adresów, natomiast stos w stronę niższych adresów. Proces użytkownika nie ma bezpośredniego dostępu procesu. Ten obszar może w tym kontekście do kontekstu jądra, odpowiednie wywołania systemowe. który zawiera informacje o stanie tego być modyfikowany tylko przez jądro. mogą być modyfikowane z poziomu procesu użytkownika Pewne wartości poprzez 2. Tworzenie nowych procesów Działający proces może stworzyć proces potomny używając funkcji fork() udostępnianej z poziomu biblioteki standardowej języka funkcja ta jest opakowaniem na wywołanie C. W systemie Linux clone(), które nie jest wywołaniem standardowym, tzn. nie jest dostępne w innych systemach kompatybilnych z Uniksem i nie należy przenośne. go bezpośrednio stosować w programach, które mają być W Linuksie zastosowany jest wariant tworzenia procesów określany angielsku copy-on-write. Oznacza to, ze po stworzeniu nowego procesu współdzieli on zarówno obszar tekstu, jak i obszar danych (tj. stert ę, stos, zmienne zainicjowane i niezainicjowane) z rodzicem. Dopiero, kiedy któryś z nich będzie po próbował dokonać 2
modyfikacji danych nastąpi rozdzielenie obszaru danych (proces potomny otrzyma kopię obszaru rodziciela). Aby wykonać nowy program należy w procesie potomnym użyć jednej z funkcji exec(). Sterowanie z procesu rodzicielskiego do procesu potomnego nigdy bezpośrednio wykonania procesu potomnego wykonując rodzicielski nie wykona tej funkcji, to zakończony nie wraca, ale proces rodzicielski może poznać status jedną z funkcji wait(). Jeśli procesem zombie. W przypadku, gdy proces-rodziciel zakończy proces proces proces potomny zostaje się wcześniej proces potomny, to ten ostatni jest adoptowany przez proces init, którego PID (identyfikator procesu) wynosi 1 lub inne procesy należące do jego grupy procesu rodzicielskiego. niż 3. Sygnały Sygnały można uznać za prostą formę komunikacji między procesami, ale przede wszystkim służą one do powiadomienia procesu, że zaszło jakieś zdarzenie, stąd też nazywa się je przerwaniami programowymi. Sygnały wykonania procesu (nie można przewidzieć kiedy się pojawi ą). z procesu do procesu lub z jądra kill(), która umożliwia procesem jest związana wysłanie są asynchroniczne względem Mogą być wysłane do procesu. Programista ma do dyspozycji funkcję sygnału do procesu o podanym PID. Z każdym struktura, w której umieszczone są adresy procedur obsługi sygnałów. Jeśli programista nie napisze własnej funkcji obsługującej dany sygna ł, to wykonywana jest procedura domyślna, która powoduje natychmiastowe zakończenie procesu lub inne, zależne ignorowa ć, lub zablokować je na określony od konfiguracji zachowanie. Część sygnałów czas. Niektórych sygnałów samemu obsłuży ć, ani zignorowa ć, ani zablokowa ć (np. SIGKILL). można nie można 4. Opis ważniejszych funkcji fork() - stwórz proces potomny. Funkcja ta zwraca dwie wartości: macierzystego - PID potomka, dla procesu potomnego 0. Jeśli dla procesu jej wywołanie się nie powiedzie, to zwraca wartość -1. Oto fragment kodu, pozwalający oprogramowa ć zachowanie potomka i rodzica: int porcpid = fork(); if(procid == -1) exit(exit_failure); 3
if(procpid == 0) { /*tu kod potomka*/ } else { } /* tu kod rodzica*/ Szczegóły: man fork clone() - funkcja specyficzna dla Linuksa, służy Szczegóły: man clone do tworzenia nowego procesu. getpid() i getppid() - funkcje zwracają odpowiednio: PID procesu bieżącego i PID jego rodzica. Szczegóły: man getpid sleep() - służy man 3 sleep do uśpienia procesu na określoną liczbę sekund. Szczegóły: wait - nie jest to jedna funkcja, ale rodzina funkcji (wait(),waitpid(),wait3(), wait4()). Powodują one, że proces macierzysty czeka na zakończenie procesu potomnego. Status zakończenia procesu możemy poznać korzystając z odpowiednich makr. Szczegóły: man 2 wait. exit() - funkcja kończąca wykonanie procesu. Istnieje kilka innych podobnych funkcji. Szczegóły: man 3 exit. exec rodzina funkcji (execl(),execlp(),execle(),execv(),execv()), które zastępują obraz w pamięci aktualnie wykonywanego procesu obrazem nowego procesu odczytanym z pliku. Szczegóły: man 3 exec. kill() funkcja powodująca wysłanie sygnału o określonym numerze do procesu o określonym PID. Szczegóły: man 2 kill. signal() funkcja pozwala określić zachowanie procesu, po otrzymaniu odpowiedniego sygnału. Z tą funkcją powiązane są funkcje sigblock() i sigsetmask(). Współcześnie zalecane jest stosowanie sigaction() i sigprocmask () zamiast signal(). Szczegóły: sigaction, man sigprocmask. man signal, man sigblock, man sigsetmask, man pause() funkcja powoduje, że proces czeka na otrzymanie sygnału. Szczegóły: man pause. alarm() - pozwala ustawić czas, po którym proces otrzyma sygnał SIGALRM. Szczegóły: man alarm. 4
Zadania: 1. Napisz program, który utworzy dwa procesy: macierzysty i potomny. Proces rodzicielski powinien wypisać swoje PID i PID potomka, natomiast proces potomny powinien wypisa ć swoje PID i PID rodzica. 2. Zademonstruj w jaki sposób mog ą powsta ć w systemie procesy zombie. 3. Napisz program, który stworzy dwa procesy. Proces macierzysty powinien poczekać na wykonanie procesu potomnego i zbada ć status jego wyjścia. 4. Napisz program, który w zależności od wartości argumentu podanego w linii poleceń wygeneruje odpowiednią liczbę procesów potomnych, które będą się wykonywały współbieżnie. Każdy z procesów potomnych powinien wypisać 4 razy na ekranie swój PID, PID swojego rodzica oraz numer określający, którym jest potomkiem rodzica (1, 2, 3...), a następnie usnąć na tyle sekund, ile wskazuje ten numer (pierwszy 1 sekunda, 2 dwie sekundy, trzeci - 3 sekundy). Proces macierzysty powinien poczeka ć na zakończenie wykonania wszystkich swoich potomków. 5. Napisz dwa programy. Program pierwszy stworzy dwa procesy, a następnie program procesu potomnego zastąpi programem drugim. 6. Napisz program, który wyśle do siebie sygna ł SIGALRM i obsłuży go. 7. Napisz program, który stworzy dwa procesy. Proces rodzicielski wyśle sygnał SIGINT (można do potomka go wysłać ręcznie naciskając na klawiaturze równocześnie Ctrl + C). Proces potomny powinien ten sygnał obsłużyć za pomocą napisanej przez Ciebie funkcji. 8. Napisz cztery osobne programy. Każdy Ciebie sygna ł. Pierwszy z procesów będzie procesu, drugi proces pod odebraniu sygnału komunikat, a następnie z nich powinien obsługiwać wybrany przez co sekundę wysyłał sygnał do drugiego powinien wypisać na ekranie przesłać sygnał do procesu trzeciego. Proces trzeci powinien zachowywać się podobnie jak drugi, a proces czwarty powinien jedynie wypisywać komunikat na ekranie. Odliczanie czasu w pierwszym procesie należy za pomoc ą SIGALARM. 9. Napisz program, który udowodni, że obszar danych jest współdzielony zrealizować między procesem potomnym i macierzystym do chwili wykonania modyfikacji danych przez jednego z nich. 10. Ze względów wykonywane były bezpieczeństwa zaleca si ę, aby w ramach funkcji obsługującej sygnał tylko proste czynności, o otrzymaniu sygnału, a skomplikowane czynności jak np. ustawienie flagi informującej żeby były wykonywane 5
w osobnym kodzie. Przedstaw schemat takiego rozwiązania macierzysty i potomny. stosując proces 11. Poka ż w jaki sposób sygnały mog ą by ć przez proces blokowane lub ignorowane. 12. Aby procesy potomne nie stawały się procesami zombie wystarczy, żeby proces macierzysty ignorował sygnał SIGCHLD. Napisz program, który sprawdzi, czy rzeczywiście zakończeniu potomka. tak się dzieje i co w takim przypadku zwracawait() lubwaitpid() po 6